aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2020-04-03 23:53:45 +0200
committerGitHub <noreply@github.com>2020-04-03 23:53:45 +0200
commitf7dffedeb610df662e69915fcff1bb37986baf55 (patch)
tree33ced51adee58d38b39416191e2e8a207ce4ec47
parent35f586783388cdff6b4f15e7aff4df1ee72d9b67 (diff)
parent6514a5c80ef91ef6e16e283339cd0b5f78a42322 (diff)
downloadpodman-f7dffedeb610df662e69915fcff1bb37986baf55.tar.gz
podman-f7dffedeb610df662e69915fcff1bb37986baf55.tar.bz2
podman-f7dffedeb610df662e69915fcff1bb37986baf55.zip
Merge pull request #5710 from baude/v2create
v2podman container create
-rw-r--r--cmd/podmanV2/Makefile2
-rw-r--r--cmd/podmanV2/common/create.go534
-rw-r--r--cmd/podmanV2/common/create_opts.go103
-rw-r--r--cmd/podmanV2/common/createparse.go51
-rw-r--r--cmd/podmanV2/common/default.go121
-rw-r--r--cmd/podmanV2/common/ports.go126
-rw-r--r--cmd/podmanV2/common/specgen.go647
-rw-r--r--cmd/podmanV2/containers/create.go102
-rw-r--r--cmd/podmanV2/parse/common.go50
-rw-r--r--cmd/podmanV2/parse/net.go (renamed from cmd/podmanV2/parse/parse.go)45
-rw-r--r--cmd/podmanV2/parse/net_test.go (renamed from cmd/podmanV2/parse/parse_test.go)0
-rw-r--r--pkg/api/handlers/libpod/containers_create.go7
-rw-r--r--pkg/domain/entities/containers.go4
-rw-r--r--pkg/domain/entities/engine_container.go2
-rw-r--r--pkg/domain/infra/abi/containers.go13
-rw-r--r--pkg/domain/infra/tunnel/containers.go9
-rw-r--r--pkg/specgen/config_linux.go93
-rw-r--r--pkg/specgen/config_linux_cgo.go1
-rw-r--r--pkg/specgen/container_validate.go4
-rw-r--r--pkg/specgen/generate/container.go168
-rw-r--r--pkg/specgen/generate/container_create.go (renamed from pkg/specgen/container_create.go)19
-rw-r--r--pkg/specgen/namespaces.go7
-rw-r--r--pkg/specgen/oci.go2
-rw-r--r--pkg/specgen/security.go165
-rw-r--r--pkg/specgen/specgen.go13
-rw-r--r--pkg/specgen/storage.go885
26 files changed, 3110 insertions, 63 deletions
diff --git a/cmd/podmanV2/Makefile b/cmd/podmanV2/Makefile
index f2f7bd73c..b847a9385 100644
--- a/cmd/podmanV2/Makefile
+++ b/cmd/podmanV2/Makefile
@@ -1,2 +1,2 @@
all:
- GO111MODULE=off go build -tags 'ABISupport systemd'
+ CGO_ENABLED=1 GO111MODULE=off go build -tags 'ABISupport systemd seccomp'
diff --git a/cmd/podmanV2/common/create.go b/cmd/podmanV2/common/create.go
new file mode 100644
index 000000000..724ed2f42
--- /dev/null
+++ b/cmd/podmanV2/common/create.go
@@ -0,0 +1,534 @@
+package common
+
+import (
+ "fmt"
+ "os"
+
+ buildahcli "github.com/containers/buildah/pkg/cli"
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/pflag"
+)
+
+const (
+ sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))"
+)
+
+var (
+ defaultContainerConfig = getDefaultContainerConfig()
+)
+
+func getDefaultContainerConfig() *config.Config {
+ defaultContainerConfig, err := config.Default()
+ if err != nil {
+ logrus.Error(err)
+ os.Exit(1)
+ }
+ return defaultContainerConfig
+}
+
+func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
+ //createFlags := c.Flags()
+ createFlags := pflag.FlagSet{}
+ createFlags.StringSliceVar(
+ &cf.Annotation,
+ "annotation", []string{},
+ "Add annotations to container (key:value)",
+ )
+ createFlags.StringSliceVarP(
+ &cf.Attach,
+ "attach", "a", []string{},
+ "Attach to STDIN, STDOUT or STDERR",
+ )
+ createFlags.StringVar(
+ &cf.Authfile,
+ "authfile", buildahcli.GetDefaultAuthFile(),
+ "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override",
+ )
+ createFlags.StringVar(
+ &cf.BlkIOWeight,
+ "blkio-weight", "",
+ "Block IO weight (relative weight) accepts a weight value between 10 and 1000.",
+ )
+ createFlags.StringSliceVar(
+ &cf.BlkIOWeightDevice,
+ "blkio-weight-device", []string{},
+ "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)",
+ )
+ createFlags.StringSliceVar(
+ &cf.CapAdd,
+ "cap-add", []string{},
+ "Add capabilities to the container",
+ )
+ createFlags.StringSliceVar(
+ &cf.CapDrop,
+ "cap-drop", []string{},
+ "Drop capabilities from the container",
+ )
+ createFlags.StringVar(
+ &cf.CGroupsNS,
+ "cgroupns", getDefaultCgroupNS(),
+ "cgroup namespace to use",
+ )
+ createFlags.StringVar(
+ &cf.CGroups,
+ "cgroups", "enabled",
+ `control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`,
+ )
+ createFlags.StringVar(
+ &cf.CGroupParent,
+ "cgroup-parent", "",
+ "Optional parent cgroup for the container",
+ )
+ createFlags.StringVar(
+ &cf.CIDFile,
+ "cidfile", "",
+ "Write the container ID to the file",
+ )
+ createFlags.StringVar(
+ &cf.ConmonPIDFile,
+ "conmon-pidfile", "",
+ "Path to the file that will receive the PID of conmon",
+ )
+ createFlags.Uint64Var(
+ &cf.CPUPeriod,
+ "cpu-period", 0,
+ "Limit the CPU CFS (Completely Fair Scheduler) period",
+ )
+ createFlags.Int64Var(
+ &cf.CPUQuota,
+ "cpu-quota", 0,
+ "Limit the CPU CFS (Completely Fair Scheduler) quota",
+ )
+ createFlags.Uint64Var(
+ &cf.CPURTPeriod,
+ "cpu-rt-period", 0,
+ "Limit the CPU real-time period in microseconds",
+ )
+ createFlags.Int64Var(
+ &cf.CPURTRuntime,
+ "cpu-rt-runtime", 0,
+ "Limit the CPU real-time runtime in microseconds",
+ )
+ createFlags.Uint64Var(
+ &cf.CPUShares,
+ "cpu-shares", 0,
+ "CPU shares (relative weight)",
+ )
+ createFlags.Float64Var(
+ &cf.CPUS,
+ "cpus", 0,
+ "Number of CPUs. The default is 0.000 which means no limit",
+ )
+ createFlags.StringVar(
+ &cf.CPUSetCPUs,
+ "cpuset-cpus", "",
+ "CPUs in which to allow execution (0-3, 0,1)",
+ )
+ createFlags.StringVar(
+ &cf.CPUSetMems,
+ "cpuset-mems", "",
+ "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.",
+ )
+ createFlags.BoolVarP(
+ &cf.Detach,
+ "detach", "d", false,
+ "Run container in background and print container ID",
+ )
+ createFlags.StringVar(
+ &cf.DetachKeys,
+ "detach-keys", getDefaultDetachKeys(),
+ "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`",
+ )
+ createFlags.StringSliceVar(
+ &cf.Device,
+ "device", getDefaultDevices(),
+ fmt.Sprintf("Add a host device to the container"),
+ )
+ createFlags.StringSliceVar(
+ &cf.DeviceCGroupRule,
+ "device-cgroup-rule", []string{},
+ "Add a rule to the cgroup allowed devices list",
+ )
+ createFlags.StringSliceVar(
+ &cf.DeviceReadBPs,
+ "device-read-bps", []string{},
+ "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
+ )
+ createFlags.StringSliceVar(
+ &cf.DeviceReadIOPs,
+ "device-read-iops", []string{},
+ "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)",
+ )
+ createFlags.StringSliceVar(
+ &cf.DeviceWriteBPs,
+ "device-write-bps", []string{},
+ "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)",
+ )
+ createFlags.StringSliceVar(
+ &cf.DeviceWriteIOPs,
+ "device-write-iops", []string{},
+ "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)",
+ )
+ createFlags.StringVar(
+ &cf.Entrypoint,
+ "entrypoint", "",
+ "Overwrite the default ENTRYPOINT of the image",
+ )
+ createFlags.StringArrayVarP(
+ &cf.env,
+ "env", "e", getDefaultEnv(),
+ "Set environment variables in container",
+ )
+ createFlags.BoolVar(
+ &cf.EnvHost,
+ "env-host", false, "Use all current host environment variables in container",
+ )
+ createFlags.StringSliceVar(
+ &cf.EnvFile,
+ "env-file", []string{},
+ "Read in a file of environment variables",
+ )
+ createFlags.StringSliceVar(
+ &cf.Expose,
+ "expose", []string{},
+ "Expose a port or a range of ports",
+ )
+ createFlags.StringSliceVar(
+ &cf.GIDMap,
+ "gidmap", []string{},
+ "GID map to use for the user namespace",
+ )
+ createFlags.StringSliceVar(
+ &cf.GroupAdd,
+ "group-add", []string{},
+ "Add additional groups to join",
+ )
+ createFlags.Bool(
+ "help", false, "",
+ )
+ createFlags.StringVar(
+ &cf.HealthCmd,
+ "health-cmd", "",
+ "set a healthcheck command for the container ('none' disables the existing healthcheck)",
+ )
+ createFlags.StringVar(
+ &cf.HealthInterval,
+ "health-interval", cliconfig.DefaultHealthCheckInterval,
+ "set an interval for the healthchecks (a value of disable results in no automatic timer setup)",
+ )
+ createFlags.UintVar(
+ &cf.HealthRetries,
+ "health-retries", cliconfig.DefaultHealthCheckRetries,
+ "the number of retries allowed before a healthcheck is considered to be unhealthy",
+ )
+ createFlags.StringVar(
+ &cf.HealthStartPeriod,
+ "health-start-period", cliconfig.DefaultHealthCheckStartPeriod,
+ "the initialization time needed for a container to bootstrap",
+ )
+ createFlags.StringVar(
+ &cf.HealthTimeout,
+ "health-timeout", cliconfig.DefaultHealthCheckTimeout,
+ "the maximum time allowed to complete the healthcheck before an interval is considered failed",
+ )
+ createFlags.StringVarP(
+ &cf.Hostname,
+ "hostname", "h", "",
+ "Set container hostname",
+ )
+ createFlags.BoolVar(
+ &cf.HTTPProxy,
+ "http-proxy", true,
+ "Set proxy environment variables in the container based on the host proxy vars",
+ )
+ createFlags.StringVar(
+ &cf.ImageVolume,
+ "image-volume", cliconfig.DefaultImageVolume,
+ `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`,
+ )
+ createFlags.BoolVar(
+ &cf.Init,
+ "init", false,
+ "Run an init binary inside the container that forwards signals and reaps processes",
+ )
+ createFlags.StringVar(
+ &cf.InitPath,
+ "init-path", getDefaultInitPath(),
+ // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string)
+ fmt.Sprintf("Path to the container-init binary"),
+ )
+ createFlags.BoolVarP(
+ &cf.Interactive,
+ "interactive", "i", false,
+ "Keep STDIN open even if not attached",
+ )
+ createFlags.StringVar(
+ &cf.IPC,
+ "ipc", getDefaultIPCNS(),
+ "IPC namespace to use",
+ )
+ createFlags.StringVar(
+ &cf.KernelMemory,
+ "kernel-memory", "",
+ "Kernel memory limit "+sizeWithUnitFormat,
+ )
+ createFlags.StringArrayVarP(
+ &cf.Label,
+ "label", "l", []string{},
+ "Set metadata on container",
+ )
+ createFlags.StringSliceVar(
+ &cf.LabelFile,
+ "label-file", []string{},
+ "Read in a line delimited file of labels",
+ )
+ createFlags.StringVar(
+ &cf.LogDriver,
+ "log-driver", "",
+ "Logging driver for the container",
+ )
+ createFlags.StringSliceVar(
+ &cf.LogOptions,
+ "log-opt", []string{},
+ "Logging driver options",
+ )
+ createFlags.StringVarP(
+ &cf.Memory,
+ "memory", "m", "",
+ "Memory limit "+sizeWithUnitFormat,
+ )
+ createFlags.StringVar(
+ &cf.MemoryReservation,
+ "memory-reservation", "",
+ "Memory soft limit "+sizeWithUnitFormat,
+ )
+ createFlags.StringVar(
+ &cf.MemorySwap,
+ "memory-swap", "",
+ "Swap limit equal to memory plus swap: '-1' to enable unlimited swap",
+ )
+ createFlags.Int64Var(
+ &cf.MemorySwappiness,
+ "memory-swappiness", -1,
+ "Tune container memory swappiness (0 to 100, or -1 for system default)",
+ )
+ createFlags.StringVar(
+ &cf.Name,
+ "name", "",
+ "Assign a name to the container",
+ )
+ createFlags.BoolVar(
+ &cf.NoHealthCheck,
+ "no-healthcheck", false,
+ "Disable healthchecks on container",
+ )
+ createFlags.BoolVar(
+ &cf.OOMKillDisable,
+ "oom-kill-disable", false,
+ "Disable OOM Killer",
+ )
+ createFlags.IntVar(
+ &cf.OOMScoreAdj,
+ "oom-score-adj", 0,
+ "Tune the host's OOM preferences (-1000 to 1000)",
+ )
+ createFlags.StringVar(
+ &cf.OverrideArch,
+ "override-arch", "",
+ "use `ARCH` instead of the architecture of the machine for choosing images",
+ )
+ //markFlagHidden(createFlags, "override-arch")
+ createFlags.StringVar(
+ &cf.OverrideOS,
+ "override-os", "",
+ "use `OS` instead of the running OS for choosing images",
+ )
+ //markFlagHidden(createFlags, "override-os")
+ createFlags.StringVar(
+ &cf.PID,
+ "pid", getDefaultPidNS(),
+ "PID namespace to use",
+ )
+ createFlags.Int64Var(
+ &cf.PIDsLimit,
+ "pids-limit", getDefaultPidsLimit(),
+ getDefaultPidsDescription(),
+ )
+ createFlags.StringVar(
+ &cf.Pod,
+ "pod", "",
+ "Run container in an existing pod",
+ )
+ createFlags.BoolVar(
+ &cf.Privileged,
+ "privileged", false,
+ "Give extended privileges to container",
+ )
+ createFlags.BoolVarP(
+ &cf.PublishAll,
+ "publish-all", "P", false,
+ "Publish all exposed ports to random ports on the host interface",
+ )
+ createFlags.StringVar(
+ &cf.Pull,
+ "pull", "missing",
+ `Pull image before creating ("always"|"missing"|"never")`,
+ )
+ createFlags.BoolVarP(
+ &cf.Quiet,
+ "quiet", "q", false,
+ "Suppress output information when pulling images",
+ )
+ createFlags.BoolVar(
+ &cf.ReadOnly,
+ "read-only", false,
+ "Make containers root filesystem read-only",
+ )
+ createFlags.BoolVar(
+ &cf.ReadOnlyTmpFS,
+ "read-only-tmpfs", true,
+ "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp",
+ )
+ createFlags.StringVar(
+ &cf.Restart,
+ "restart", "",
+ `Restart policy to apply when a container exits ("always"|"no"|"on-failure")`,
+ )
+ createFlags.BoolVar(
+ &cf.Rm,
+ "rm", false,
+ "Remove container (and pod if created) after exit",
+ )
+ createFlags.BoolVar(
+ &cf.RootFS,
+ "rootfs", false,
+ "The first argument is not an image but the rootfs to the exploded container",
+ )
+ createFlags.StringArrayVar(
+ &cf.SecurityOpt,
+ "security-opt", getDefaultSecurityOptions(),
+ fmt.Sprintf("Security Options"),
+ )
+ createFlags.StringVar(
+ &cf.ShmSize,
+ "shm-size", getDefaultShmSize(),
+ "Size of /dev/shm "+sizeWithUnitFormat,
+ )
+ createFlags.StringVar(
+ &cf.StopSignal,
+ "stop-signal", "",
+ "Signal to stop a container. Default is SIGTERM",
+ )
+ createFlags.UintVar(
+ &cf.StopTimeout,
+ "stop-timeout", defaultContainerConfig.Engine.StopTimeout,
+ "Timeout (in seconds) to stop a container. Default is 10",
+ )
+ createFlags.StringSliceVar(
+ &cf.StoreageOpt,
+ "storage-opt", []string{},
+ "Storage driver options per container",
+ )
+ createFlags.StringVar(
+ &cf.SubUIDName,
+ "subgidname", "",
+ "Name of range listed in /etc/subgid for use in user namespace",
+ )
+ createFlags.StringVar(
+ &cf.SubGIDName,
+ "subuidname", "",
+ "Name of range listed in /etc/subuid for use in user namespace",
+ )
+
+ createFlags.StringSliceVar(
+ &cf.Sysctl,
+ "sysctl", getDefaultSysctls(),
+ "Sysctl options",
+ )
+ createFlags.StringVar(
+ &cf.SystemdD,
+ "systemd", "true",
+ `Run container in systemd mode ("true"|"false"|"always")`,
+ )
+ createFlags.StringArrayVar(
+ &cf.TmpFS,
+ "tmpfs", []string{},
+ "Mount a temporary filesystem (`tmpfs`) into a container",
+ )
+ createFlags.BoolVarP(
+ &cf.TTY,
+ "tty", "t", false,
+ "Allocate a pseudo-TTY for container",
+ )
+ createFlags.StringSliceVar(
+ &cf.UIDMap,
+ "uidmap", []string{},
+ "UID map to use for the user namespace",
+ )
+ createFlags.StringSliceVar(
+ &cf.Ulimit,
+ "ulimit", getDefaultUlimits(),
+ "Ulimit options",
+ )
+ createFlags.StringVarP(
+ &cf.User,
+ "user", "u", "",
+ "Username or UID (format: <name|uid>[:<group|gid>])",
+ )
+ createFlags.StringVar(
+ &cf.UserNS,
+ "userns", getDefaultUserNS(),
+ "User namespace to use",
+ )
+ createFlags.StringVar(
+ &cf.UTS,
+ "uts", getDefaultUTSNS(),
+ "UTS namespace to use",
+ )
+ createFlags.StringArrayVar(
+ &cf.Mount,
+ "mount", []string{},
+ "Attach a filesystem mount to the container",
+ )
+ createFlags.StringArrayVarP(
+ &cf.Volume,
+ "volume", "v", getDefaultVolumes(),
+ "Bind mount a volume into the container",
+ )
+ createFlags.StringSliceVar(
+ &cf.VolumesFrom,
+ "volumes-from", []string{},
+ "Mount volumes from the specified container(s)",
+ )
+ createFlags.StringVarP(
+ &cf.Workdir,
+ "workdir", "w", "",
+ "Working directory inside the container",
+ )
+ createFlags.StringVar(
+ &cf.SeccompPolicy,
+ "seccomp-policy", "default",
+ "Policy for selecting a seccomp profile (experimental)",
+ )
+ return &createFlags
+}
+
+func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
+ switch name {
+ case "healthcheck-command":
+ name = "health-cmd"
+ case "healthcheck-interval":
+ name = "health-interval"
+ case "healthcheck-retries":
+ name = "health-retries"
+ case "healthcheck-start-period":
+ name = "health-start-period"
+ case "healthcheck-timeout":
+ name = "health-timeout"
+ case "net":
+ name = "network"
+ }
+ return pflag.NormalizedName(name)
+}
diff --git a/cmd/podmanV2/common/create_opts.go b/cmd/podmanV2/common/create_opts.go
new file mode 100644
index 000000000..9d12e4b26
--- /dev/null
+++ b/cmd/podmanV2/common/create_opts.go
@@ -0,0 +1,103 @@
+package common
+
+import "github.com/containers/libpod/pkg/domain/entities"
+
+type ContainerCLIOpts struct {
+ Annotation []string
+ Attach []string
+ Authfile string
+ BlkIOWeight string
+ BlkIOWeightDevice []string
+ CapAdd []string
+ CapDrop []string
+ CGroupsNS string
+ CGroups string
+ CGroupParent string
+ CIDFile string
+ ConmonPIDFile string
+ CPUPeriod uint64
+ CPUQuota int64
+ CPURTPeriod uint64
+ CPURTRuntime int64
+ CPUShares uint64
+ CPUS float64
+ CPUSetCPUs string
+ CPUSetMems string
+ Detach bool
+ DetachKeys string
+ Device []string
+ DeviceCGroupRule []string
+ DeviceReadBPs []string
+ DeviceReadIOPs []string
+ DeviceWriteBPs []string
+ DeviceWriteIOPs []string
+ Entrypoint string
+ env []string
+ EnvHost bool
+ EnvFile []string
+ Expose []string
+ GIDMap []string
+ GroupAdd []string
+ HealthCmd string
+ HealthInterval string
+ HealthRetries uint
+ HealthStartPeriod string
+ HealthTimeout string
+ Hostname string
+ HTTPProxy bool
+ ImageVolume string
+ Init bool
+ InitPath string
+ Interactive bool
+ IPC string
+ KernelMemory string
+ Label []string
+ LabelFile []string
+ LogDriver string
+ LogOptions []string
+ Memory string
+ MemoryReservation string
+ MemorySwap string
+ MemorySwappiness int64
+ Name string
+ NoHealthCheck bool
+ OOMKillDisable bool
+ OOMScoreAdj int
+ OverrideArch string
+ OverrideOS string
+ PID string
+ PIDsLimit int64
+ Pod string
+ Privileged bool
+ PublishAll bool
+ Pull string
+ Quiet bool
+ ReadOnly bool
+ ReadOnlyTmpFS bool
+ Restart string
+ Rm bool
+ RootFS bool
+ SecurityOpt []string
+ ShmSize string
+ StopSignal string
+ StopTimeout uint
+ StoreageOpt []string
+ SubUIDName string
+ SubGIDName string
+ Sysctl []string
+ SystemdD string
+ TmpFS []string
+ TTY bool
+ UIDMap []string
+ Ulimit []string
+ User string
+ UserNS string
+ UTS string
+ Mount []string
+ Volume []string
+ VolumesFrom []string
+ Workdir string
+ SeccompPolicy string
+
+ Net *entities.NetOptions
+}
diff --git a/cmd/podmanV2/common/createparse.go b/cmd/podmanV2/common/createparse.go
new file mode 100644
index 000000000..89524a04b
--- /dev/null
+++ b/cmd/podmanV2/common/createparse.go
@@ -0,0 +1,51 @@
+package common
+
+import (
+ "github.com/containers/libpod/cmd/podmanV2/parse"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/pkg/errors"
+)
+
+// validate determines if the flags and values given by the user are valid. things checked
+// by validate must not need any state information on the flag (i.e. changed)
+func (c *ContainerCLIOpts) validate() error {
+ var ()
+ if c.Rm && c.Restart != "" && c.Restart != "no" {
+ return errors.Errorf("the --rm option conflicts with --restart")
+ }
+
+ if _, err := util.ValidatePullType(c.Pull); err != nil {
+ return err
+ }
+ // Verify the additional hosts are in correct format
+ for _, host := range c.Net.AddHosts {
+ if _, err := parse.ValidateExtraHost(host); err != nil {
+ return err
+ }
+ }
+
+ if dnsSearches := c.Net.DNSSearch; len(dnsSearches) > 0 {
+ // Validate domains are good
+ for _, dom := range dnsSearches {
+ if dom == "." {
+ if len(dnsSearches) > 1 {
+ return errors.Errorf("cannot pass additional search domains when also specifying '.'")
+ }
+ continue
+ }
+ if _, err := parse.ValidateDomain(dom); err != nil {
+ return err
+ }
+ }
+ }
+ var imageVolType = map[string]string{
+ "bind": "",
+ "tmpfs": "",
+ "ignore": "",
+ }
+ if _, ok := imageVolType[c.ImageVolume]; !ok {
+ return errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.ImageVolume)
+ }
+ return nil
+
+}
diff --git a/cmd/podmanV2/common/default.go b/cmd/podmanV2/common/default.go
new file mode 100644
index 000000000..fea161edf
--- /dev/null
+++ b/cmd/podmanV2/common/default.go
@@ -0,0 +1,121 @@
+package common
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/libpod/pkg/apparmor"
+ "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/sysinfo"
+ "github.com/opencontainers/selinux/go-selinux"
+)
+
+// TODO these options are directly embedded into many of the CLI cobra values, as such
+// this approach will not work in a remote client. so we will need to likely do something like a
+// supported and unsupported approach here and backload these options into the specgen
+// once we are "on" the host system.
+func getDefaultSecurityOptions() []string {
+ securityOpts := []string{}
+ if defaultContainerConfig.Containers.SeccompProfile != "" && defaultContainerConfig.Containers.SeccompProfile != parse.SeccompDefaultPath {
+ securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", defaultContainerConfig.Containers.SeccompProfile))
+ }
+ if apparmor.IsEnabled() && defaultContainerConfig.Containers.ApparmorProfile != "" {
+ securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", defaultContainerConfig.Containers.ApparmorProfile))
+ }
+ if selinux.GetEnabled() && !defaultContainerConfig.Containers.EnableLabeling {
+ securityOpts = append(securityOpts, fmt.Sprintf("label=%s", selinux.DisableSecOpt()[0]))
+ }
+ return securityOpts
+}
+
+// getDefaultSysctls
+func getDefaultSysctls() []string {
+ return defaultContainerConfig.Containers.DefaultSysctls
+}
+
+func getDefaultVolumes() []string {
+ return defaultContainerConfig.Containers.Volumes
+}
+
+func getDefaultDevices() []string {
+ return defaultContainerConfig.Containers.Devices
+}
+
+func getDefaultDNSServers() []string { //nolint
+ return defaultContainerConfig.Containers.DNSServers
+}
+
+func getDefaultDNSSearches() []string { //nolint
+ return defaultContainerConfig.Containers.DNSSearches
+}
+
+func getDefaultDNSOptions() []string { //nolint
+ return defaultContainerConfig.Containers.DNSOptions
+}
+
+func getDefaultEnv() []string {
+ return defaultContainerConfig.Containers.Env
+}
+
+func getDefaultInitPath() string {
+ return defaultContainerConfig.Containers.InitPath
+}
+
+func getDefaultIPCNS() string {
+ return defaultContainerConfig.Containers.IPCNS
+}
+
+func getDefaultPidNS() string {
+ return defaultContainerConfig.Containers.PidNS
+}
+
+func getDefaultNetNS() string { //nolint
+ if defaultContainerConfig.Containers.NetNS == "private" && rootless.IsRootless() {
+ return "slirp4netns"
+ }
+ return defaultContainerConfig.Containers.NetNS
+}
+
+func getDefaultCgroupNS() string {
+ return defaultContainerConfig.Containers.CgroupNS
+}
+
+func getDefaultUTSNS() string {
+ return defaultContainerConfig.Containers.UTSNS
+}
+
+func getDefaultShmSize() string {
+ return defaultContainerConfig.Containers.ShmSize
+}
+
+func getDefaultUlimits() []string {
+ return defaultContainerConfig.Containers.DefaultUlimits
+}
+
+func getDefaultUserNS() string {
+ userns := os.Getenv("PODMAN_USERNS")
+ if userns != "" {
+ return userns
+ }
+ return defaultContainerConfig.Containers.UserNS
+}
+
+func getDefaultPidsLimit() int64 {
+ if rootless.IsRootless() {
+ cgroup2, _ := cgroups.IsCgroup2UnifiedMode()
+ if cgroup2 {
+ return defaultContainerConfig.Containers.PidsLimit
+ }
+ }
+ return sysinfo.GetDefaultPidsLimit()
+}
+
+func getDefaultPidsDescription() string {
+ return "Tune container pids limit (set 0 for unlimited)"
+}
+
+func getDefaultDetachKeys() string {
+ return defaultContainerConfig.Engine.DetachKeys
+}
diff --git a/cmd/podmanV2/common/ports.go b/cmd/podmanV2/common/ports.go
new file mode 100644
index 000000000..7e2b1e79d
--- /dev/null
+++ b/cmd/podmanV2/common/ports.go
@@ -0,0 +1,126 @@
+package common
+
+import (
+ "fmt"
+ "net"
+ "strconv"
+
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/docker/go-connections/nat"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// ExposedPorts parses user and image ports and returns binding information
+func ExposedPorts(expose []string, publish []ocicni.PortMapping, publishAll bool, imageExposedPorts map[string]struct{}) ([]ocicni.PortMapping, error) {
+ containerPorts := make(map[string]string)
+
+ // TODO this needs to be added into a something that
+ // has access to an imageengine
+ // add expose ports from the image itself
+ //for expose := range imageExposedPorts {
+ // _, port := nat.SplitProtoPort(expose)
+ // containerPorts[port] = ""
+ //}
+
+ // add the expose ports from the user (--expose)
+ // can be single or a range
+ for _, expose := range expose {
+ //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+ _, port := nat.SplitProtoPort(expose)
+ //parse the start and end port and create a sequence of ports to expose
+ //if expose a port, the start and end port are the same
+ start, end, err := nat.ParsePortRange(port)
+ if err != nil {
+ return nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", expose, err)
+ }
+ for i := start; i <= end; i++ {
+ containerPorts[strconv.Itoa(int(i))] = ""
+ }
+ }
+
+ // TODO/FIXME this is hell reencarnated
+ // parse user inputted port bindings
+ pbPorts, portBindings, err := nat.ParsePortSpecs([]string{})
+ if err != nil {
+ return nil, err
+ }
+
+ // delete exposed container ports if being used by -p
+ for i := range pbPorts {
+ delete(containerPorts, i.Port())
+ }
+
+ // iterate container ports and make port bindings from them
+ if publishAll {
+ for e := range containerPorts {
+ //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+ //proto, port := nat.SplitProtoPort(e)
+ p, err := nat.NewPort("tcp", e)
+ if err != nil {
+ return nil, err
+ }
+ rp, err := getRandomPort()
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debug(fmt.Sprintf("Using random host port %d with container port %d", rp, p.Int()))
+ portBindings[p] = CreatePortBinding(rp, "")
+ }
+ }
+
+ // We need to see if any host ports are not populated and if so, we need to assign a
+ // random port to them.
+ for k, pb := range portBindings {
+ if pb[0].HostPort == "" {
+ hostPort, err := getRandomPort()
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debug(fmt.Sprintf("Using random host port %d with container port %s", hostPort, k.Port()))
+ pb[0].HostPort = strconv.Itoa(hostPort)
+ }
+ }
+ var pms []ocicni.PortMapping
+ for k, v := range portBindings {
+ for _, pb := range v {
+ hp, err := strconv.Atoi(pb.HostPort)
+ if err != nil {
+ return nil, err
+ }
+ pms = append(pms, ocicni.PortMapping{
+ HostPort: int32(hp),
+ ContainerPort: int32(k.Int()),
+ //Protocol: "",
+ HostIP: pb.HostIP,
+ })
+ }
+ }
+ return pms, nil
+}
+
+func getRandomPort() (int, error) {
+ l, err := net.Listen("tcp", ":0")
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to get free port")
+ }
+ defer l.Close()
+ _, randomPort, err := net.SplitHostPort(l.Addr().String())
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to determine free port")
+ }
+ rp, err := strconv.Atoi(randomPort)
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to convert random port to int")
+ }
+ return rp, nil
+}
+
+//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs
+func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding {
+ pb := nat.PortBinding{
+ HostPort: strconv.Itoa(hostPort),
+ }
+ pb.HostIP = hostIP
+ return []nat.PortBinding{pb}
+}
diff --git a/cmd/podmanV2/common/specgen.go b/cmd/podmanV2/common/specgen.go
new file mode 100644
index 000000000..5245e206e
--- /dev/null
+++ b/cmd/podmanV2/common/specgen.go
@@ -0,0 +1,647 @@
+package common
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/cmd/podmanV2/parse"
+ "github.com/containers/libpod/libpod"
+ ann "github.com/containers/libpod/pkg/annotations"
+ envLib "github.com/containers/libpod/pkg/env"
+ ns "github.com/containers/libpod/pkg/namespaces"
+ "github.com/containers/libpod/pkg/specgen"
+ systemdGen "github.com/containers/libpod/pkg/systemd/generate"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/docker/go-units"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error {
+ var (
+ err error
+ //namespaces map[string]string
+ )
+
+ // validate flags as needed
+ if err := c.validate(); err != nil {
+ return nil
+ }
+
+ inputCommand := args[1:]
+ if len(c.HealthCmd) > 0 {
+ s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
+ if err != nil {
+ return err
+ }
+ }
+
+ s.IDMappings, err = util.ParseIDMapping(ns.UsernsMode(c.UserNS), c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
+ if err != nil {
+ return err
+ }
+ if m := c.Memory; len(m) > 0 {
+ ml, err := units.RAMInBytes(m)
+ if err != nil {
+ return errors.Wrapf(err, "invalid value for memory")
+ }
+ s.ResourceLimits.Memory.Limit = &ml
+ }
+ if m := c.MemoryReservation; len(m) > 0 {
+ mr, err := units.RAMInBytes(m)
+ if err != nil {
+ return errors.Wrapf(err, "invalid value for memory")
+ }
+ s.ResourceLimits.Memory.Reservation = &mr
+ }
+ if m := c.MemorySwap; len(m) > 0 {
+ var ms int64
+ if m == "-1" {
+ ms = int64(-1)
+ s.ResourceLimits.Memory.Swap = &ms
+ } else {
+ ms, err = units.RAMInBytes(m)
+ if err != nil {
+ return errors.Wrapf(err, "invalid value for memory")
+ }
+ }
+ s.ResourceLimits.Memory.Swap = &ms
+ }
+ if m := c.KernelMemory; len(m) > 0 {
+ mk, err := units.RAMInBytes(m)
+ if err != nil {
+ return errors.Wrapf(err, "invalid value for kernel-memory")
+ }
+ s.ResourceLimits.Memory.Kernel = &mk
+ }
+ if b := c.BlkIOWeight; len(b) > 0 {
+ u, err := strconv.ParseUint(b, 10, 16)
+ if err != nil {
+ return errors.Wrapf(err, "invalid value for blkio-weight")
+ }
+ nu := uint16(u)
+ s.ResourceLimits.BlockIO.Weight = &nu
+ }
+
+ s.Terminal = c.TTY
+ ep, err := ExposedPorts(c.Expose, c.Net.PublishPorts, c.PublishAll, nil)
+ if err != nil {
+ return err
+ }
+ s.PortMappings = ep
+ s.Pod = c.Pod
+
+ //s.CgroupNS = specgen.Namespace{
+ // NSMode: ,
+ // Value: "",
+ //}
+
+ //s.UserNS = specgen.Namespace{}
+
+ // Kernel Namespaces
+ // TODO Fix handling of namespace from pod
+ // Instead of integrating here, should be done in libpod
+ // However, that also involves setting up security opts
+ // when the pod's namespace is integrated
+ //namespaces = map[string]string{
+ // "cgroup": c.CGroupsNS,
+ // "pid": c.PID,
+ // //"net": c.Net.Network.Value, // TODO need help here
+ // "ipc": c.IPC,
+ // "user": c.User,
+ // "uts": c.UTS,
+ //}
+ //
+ //if len(c.PID) > 0 {
+ // split := strings.SplitN(c.PID, ":", 2)
+ // // need a way to do thsi
+ // specgen.Namespace{
+ // NSMode: split[0],
+ // }
+ // //Value: split1 if len allows
+ //}
+ // TODO this is going to have be done after things like pod creation are done because
+ // pod creation changes these values.
+ //pidMode := ns.PidMode(namespaces["pid"])
+ //usernsMode := ns.UsernsMode(namespaces["user"])
+ //utsMode := ns.UTSMode(namespaces["uts"])
+ //cgroupMode := ns.CgroupMode(namespaces["cgroup"])
+ //ipcMode := ns.IpcMode(namespaces["ipc"])
+ //// Make sure if network is set to container namespace, port binding is not also being asked for
+ //netMode := ns.NetworkMode(namespaces["net"])
+ //if netMode.IsContainer() {
+ // if len(portBindings) > 0 {
+ // return nil, errors.Errorf("cannot set port bindings on an existing container network namespace")
+ // }
+ //}
+
+ // TODO Remove when done with namespaces for realz
+ // Setting a default for IPC to get this working
+ s.IpcNS = specgen.Namespace{
+ NSMode: specgen.Private,
+ Value: "",
+ }
+
+ // TODO this is going to have to be done the libpod/server end of things
+ // USER
+ //user := c.String("user")
+ //if user == "" {
+ // switch {
+ // case usernsMode.IsKeepID():
+ // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
+ // case data == nil:
+ // user = "0"
+ // default:
+ // user = data.Config.User
+ // }
+ //}
+
+ // STOP SIGNAL
+ signalString := "TERM"
+ if sig := c.StopSignal; len(sig) > 0 {
+ signalString = sig
+ }
+ stopSignal, err := util.ParseSignal(signalString)
+ if err != nil {
+ return err
+ }
+ s.StopSignal = &stopSignal
+
+ // ENVIRONMENT VARIABLES
+ //
+ // Precedence order (higher index wins):
+ // 1) env-host, 2) image data, 3) env-file, 4) env
+ env := map[string]string{
+ "container": "podman",
+ }
+
+ // First transform the os env into a map. We need it for the labels later in
+ // any case.
+ osEnv, err := envLib.ParseSlice(os.Environ())
+ if err != nil {
+ return errors.Wrap(err, "error parsing host environment variables")
+ }
+
+ if c.EnvHost {
+ env = envLib.Join(env, osEnv)
+ }
+ // env-file overrides any previous variables
+ for _, f := range c.EnvFile {
+ fileEnv, err := envLib.ParseFile(f)
+ if err != nil {
+ return err
+ }
+ // File env is overridden by env.
+ env = envLib.Join(env, fileEnv)
+ }
+
+ // env overrides any previous variables
+ if cmdLineEnv := c.env; len(cmdLineEnv) > 0 {
+ parsedEnv, err := envLib.ParseSlice(cmdLineEnv)
+ if err != nil {
+ return err
+ }
+ env = envLib.Join(env, parsedEnv)
+ }
+ s.Env = env
+
+ // LABEL VARIABLES
+ labels, err := parse.GetAllLabels(c.LabelFile, c.Label)
+ if err != nil {
+ return errors.Wrapf(err, "unable to process labels")
+ }
+
+ if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists {
+ labels[systemdGen.EnvVariable] = systemdUnit
+ }
+
+ s.Labels = labels
+
+ // ANNOTATIONS
+ annotations := make(map[string]string)
+
+ // First, add our default annotations
+ annotations[ann.TTY] = "false"
+ if c.TTY {
+ annotations[ann.TTY] = "true"
+ }
+
+ // Last, add user annotations
+ for _, annotation := range c.Annotation {
+ splitAnnotation := strings.SplitN(annotation, "=", 2)
+ if len(splitAnnotation) < 2 {
+ return errors.Errorf("Annotations must be formatted KEY=VALUE")
+ }
+ annotations[splitAnnotation[0]] = splitAnnotation[1]
+ }
+ s.Annotations = annotations
+
+ workDir := "/"
+ if wd := c.Workdir; len(wd) > 0 {
+ workDir = wd
+ }
+ s.WorkDir = workDir
+ entrypoint := []string{}
+ userCommand := []string{}
+ if ep := c.Entrypoint; len(ep) > 0 {
+ // Check if entrypoint specified is json
+ if err := json.Unmarshal([]byte(c.Entrypoint), &entrypoint); err != nil {
+ entrypoint = append(entrypoint, ep)
+ }
+ }
+
+ var command []string
+
+ // Build the command
+ // If we have an entry point, it goes first
+ if len(entrypoint) > 0 {
+ command = entrypoint
+ }
+ if len(inputCommand) > 0 {
+ // User command overrides data CMD
+ command = append(command, inputCommand...)
+ userCommand = append(userCommand, inputCommand...)
+ }
+
+ if len(inputCommand) > 0 {
+ s.Command = userCommand
+ } else {
+ s.Command = command
+ }
+
+ // SHM Size
+ shmSize, err := units.FromHumanSize(c.ShmSize)
+ if err != nil {
+ return errors.Wrapf(err, "unable to translate --shm-size")
+ }
+ s.ShmSize = &shmSize
+ s.HostAdd = c.Net.AddHosts
+ s.DNSServer = c.Net.DNSServers
+ s.DNSSearch = c.Net.DNSSearch
+ s.DNSOption = c.Net.DNSOptions
+
+ // deferred, must be added on libpod side
+ //var ImageVolumes map[string]struct{}
+ //if data != nil && c.String("image-volume") != "ignore" {
+ // ImageVolumes = data.Config.Volumes
+ //}
+
+ s.ImageVolumeMode = c.ImageVolume
+ systemd := c.SystemdD == "always"
+ if !systemd && command != nil {
+ x, err := strconv.ParseBool(c.SystemdD)
+ if err != nil {
+ return errors.Wrapf(err, "cannot parse bool %s", c.SystemdD)
+ }
+ if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) {
+ systemd = true
+ }
+ }
+ if systemd {
+ if s.StopSignal == nil {
+ stopSignal, err = util.ParseSignal("RTMIN+3")
+ if err != nil {
+ return errors.Wrapf(err, "error parsing systemd signal")
+ }
+ s.StopSignal = &stopSignal
+ }
+ }
+ swappiness := uint64(c.MemorySwappiness)
+ if s.ResourceLimits == nil {
+ s.ResourceLimits = &specs.LinuxResources{}
+ }
+ if s.ResourceLimits.Memory == nil {
+ s.ResourceLimits.Memory = &specs.LinuxMemory{}
+ }
+ s.ResourceLimits.Memory.Swappiness = &swappiness
+
+ if s.LogConfiguration == nil {
+ s.LogConfiguration = &specgen.LogConfig{}
+ }
+ s.LogConfiguration.Driver = libpod.KubernetesLogging
+ if ld := c.LogDriver; len(ld) > 0 {
+ s.LogConfiguration.Driver = ld
+ }
+ if s.ResourceLimits.Pids == nil {
+ s.ResourceLimits.Pids = &specs.LinuxPids{}
+ }
+ s.ResourceLimits.Pids.Limit = c.PIDsLimit
+ if c.CGroups == "disabled" && c.PIDsLimit > 0 {
+ s.ResourceLimits.Pids.Limit = -1
+ }
+ // TODO WTF
+ //cgroup := &cc.CgroupConfig{
+ // Cgroups: c.String("cgroups"),
+ // Cgroupns: c.String("cgroupns"),
+ // CgroupParent: c.String("cgroup-parent"),
+ // CgroupMode: cgroupMode,
+ //}
+ //
+ //userns := &cc.UserConfig{
+ // GroupAdd: c.StringSlice("group-add"),
+ // IDMappings: idmappings,
+ // UsernsMode: usernsMode,
+ // User: user,
+ //}
+ //
+ //uts := &cc.UtsConfig{
+ // UtsMode: utsMode,
+ // NoHosts: c.Bool("no-hosts"),
+ // HostAdd: c.StringSlice("add-host"),
+ // Hostname: c.String("hostname"),
+ //}
+
+ sysctl := map[string]string{}
+ if ctl := c.Sysctl; len(ctl) > 0 {
+ sysctl, err = util.ValidateSysctls(ctl)
+ if err != nil {
+ return err
+ }
+ }
+ s.Sysctl = sysctl
+
+ s.CapAdd = c.CapAdd
+ s.CapDrop = c.CapDrop
+ s.Privileged = c.Privileged
+ s.ReadOnlyFilesystem = c.ReadOnly
+
+ // TODO
+ // ouitside of specgen and oci though
+ // defaults to true, check spec/storage
+ //s.readon = c.ReadOnlyTmpFS
+ // TODO convert to map?
+ // check if key=value and convert
+ sysmap := make(map[string]string)
+ for _, ctl := range c.Sysctl {
+ splitCtl := strings.SplitN(ctl, "=", 2)
+ if len(splitCtl) < 2 {
+ return errors.Errorf("invalid sysctl value %q", ctl)
+ }
+ sysmap[splitCtl[0]] = splitCtl[1]
+ }
+ s.Sysctl = sysmap
+
+ for _, opt := range c.SecurityOpt {
+ if opt == "no-new-privileges" {
+ s.ContainerSecurityConfig.NoNewPrivileges = true
+ } else {
+ con := strings.SplitN(opt, "=", 2)
+ if len(con) != 2 {
+ return fmt.Errorf("invalid --security-opt 1: %q", opt)
+ }
+
+ switch con[0] {
+ case "label":
+ // TODO selinux opts and label opts are the same thing
+ s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
+ case "apparmor":
+ s.ContainerSecurityConfig.ApparmorProfile = con[1]
+ case "seccomp":
+ s.SeccompProfilePath = con[1]
+ default:
+ return fmt.Errorf("invalid --security-opt 2: %q", opt)
+ }
+ }
+ }
+
+ // TODO any idea why this was done
+ // storage.go from spec/
+ // grab it
+ //volumes := rtc.Containers.Volumes
+ // TODO conflict on populate?
+ //if v := c.Volume; len(v)> 0 {
+ // s.Volumes = append(volumes, c.StringSlice("volume")...)
+ //}
+ //s.volu
+
+ //s.Mounts = c.Mount
+ s.VolumesFrom = c.VolumesFrom
+
+ // TODO any idea why this was done
+ //devices := rtc.Containers.Devices
+ // TODO conflict on populate?
+ //
+ //if c.Changed("device") {
+ // devices = append(devices, c.StringSlice("device")...)
+ //}
+
+ // TODO things i cannot find in spec
+ // we dont think these are in the spec
+ // init - initbinary
+ // initpath
+ s.Stdin = c.Interactive
+ // quiet
+ //DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
+
+ if bps := c.DeviceReadBPs; len(bps) > 0 {
+ if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return err
+ }
+ }
+
+ if bps := c.DeviceWriteBPs; len(bps) > 0 {
+ if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return err
+ }
+ }
+
+ if iops := c.DeviceReadIOPs; len(iops) > 0 {
+ if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return err
+ }
+ }
+
+ if iops := c.DeviceWriteIOPs; len(iops) > 0 {
+ if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return err
+ }
+ }
+
+ s.ResourceLimits.Memory.DisableOOMKiller = &c.OOMKillDisable
+
+ // Rlimits/Ulimits
+ for _, u := range c.Ulimit {
+ if u == "host" {
+ s.Rlimits = nil
+ break
+ }
+ ul, err := units.ParseUlimit(u)
+ if err != nil {
+ return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
+ }
+ rl := specs.POSIXRlimit{
+ Type: ul.Name,
+ Hard: uint64(ul.Hard),
+ Soft: uint64(ul.Soft),
+ }
+ s.Rlimits = append(s.Rlimits, rl)
+ }
+
+ //Tmpfs: c.StringArray("tmpfs"),
+
+ // TODO how to handle this?
+ //Syslog: c.Bool("syslog"),
+
+ logOpts := make(map[string]string)
+ for _, o := range c.LogOptions {
+ split := strings.SplitN(o, "=", 2)
+ if len(split) < 2 {
+ return errors.Errorf("invalid log option %q", o)
+ }
+ logOpts[split[0]] = split[1]
+ }
+ s.LogConfiguration.Options = logOpts
+ s.Name = c.Name
+
+ if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil {
+ return err
+ }
+
+ if s.ResourceLimits.CPU == nil {
+ s.ResourceLimits.CPU = &specs.LinuxCPU{}
+ }
+ s.ResourceLimits.CPU.Shares = &c.CPUShares
+ s.ResourceLimits.CPU.Period = &c.CPUPeriod
+
+ // TODO research these
+ //s.ResourceLimits.CPU.Cpus = c.CPUS
+ //s.ResourceLimits.CPU.Cpus = c.CPUSetCPUs
+
+ //s.ResourceLimits.CPU. = c.CPUSetCPUs
+ s.ResourceLimits.CPU.Mems = c.CPUSetMems
+ s.ResourceLimits.CPU.Quota = &c.CPUQuota
+ s.ResourceLimits.CPU.RealtimePeriod = &c.CPURTPeriod
+ s.ResourceLimits.CPU.RealtimeRuntime = &c.CPURTRuntime
+ s.OOMScoreAdj = &c.OOMScoreAdj
+ s.RestartPolicy = c.Restart
+ s.Remove = c.Rm
+ s.StopTimeout = &c.StopTimeout
+
+ // TODO where should we do this?
+ //func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
+ return nil
+}
+
+func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) {
+ // Every healthcheck requires a command
+ if len(inCmd) == 0 {
+ return nil, errors.New("Must define a healthcheck command for all healthchecks")
+ }
+
+ // first try to parse option value as JSON array of strings...
+ cmd := []string{}
+ err := json.Unmarshal([]byte(inCmd), &cmd)
+ if err != nil {
+ // ...otherwise pass it to "/bin/sh -c" inside the container
+ cmd = []string{"CMD-SHELL", inCmd}
+ }
+ hc := manifest.Schema2HealthConfig{
+ Test: cmd,
+ }
+
+ if interval == "disable" {
+ interval = "0"
+ }
+ intervalDuration, err := time.ParseDuration(interval)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", interval)
+ }
+
+ hc.Interval = intervalDuration
+
+ if retries < 1 {
+ return nil, errors.New("healthcheck-retries must be greater than 0.")
+ }
+ hc.Retries = int(retries)
+ timeoutDuration, err := time.ParseDuration(timeout)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", timeout)
+ }
+ if timeoutDuration < time.Duration(1) {
+ return nil, errors.New("healthcheck-timeout must be at least 1 second")
+ }
+ hc.Timeout = timeoutDuration
+
+ startPeriodDuration, err := time.ParseDuration(startPeriod)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", startPeriod)
+ }
+ if startPeriodDuration < time.Duration(0) {
+ return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
+ }
+ hc.StartPeriod = startPeriodDuration
+
+ return &hc, nil
+}
+
+func parseWeightDevices(weightDevs []string, s *specgen.SpecGenerator) error {
+ for _, val := range weightDevs {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return fmt.Errorf("bad format for device path: %s", val)
+ }
+ weight, err := strconv.ParseUint(split[1], 10, 0)
+ if err != nil {
+ return fmt.Errorf("invalid weight for device: %s", val)
+ }
+ if weight > 0 && (weight < 10 || weight > 1000) {
+ return fmt.Errorf("invalid weight for device: %s", val)
+ }
+ w := uint16(weight)
+ s.WeightDevice[split[0]] = specs.LinuxWeightDevice{
+ Weight: &w,
+ LeafWeight: nil,
+ }
+ }
+ return nil
+}
+
+func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
+ td := make(map[string]specs.LinuxThrottleDevice)
+ for _, val := range bpsDevices {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return nil, fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return nil, fmt.Errorf("bad format for device path: %s", val)
+ }
+ rate, err := units.RAMInBytes(split[1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+ }
+ if rate < 0 {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+ }
+ td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)}
+ }
+ return td, nil
+}
+
+func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
+ td := make(map[string]specs.LinuxThrottleDevice)
+ for _, val := range iopsDevices {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return nil, fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return nil, fmt.Errorf("bad format for device path: %s", val)
+ }
+ rate, err := strconv.ParseUint(split[1], 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
+ }
+ td[split[0]] = specs.LinuxThrottleDevice{Rate: rate}
+ }
+ return td, nil
+}
diff --git a/cmd/podmanV2/containers/create.go b/cmd/podmanV2/containers/create.go
new file mode 100644
index 000000000..fd5300966
--- /dev/null
+++ b/cmd/podmanV2/containers/create.go
@@ -0,0 +1,102 @@
+package containers
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podmanV2/common"
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var (
+ createDescription = `Creates a new container from the given image or storage and prepares it for running the specified command.
+
+ The container ID is then printed to stdout. You can then start it at any time with the podman start <container_id> command. The container will be created with the initial state 'created'.`
+ createCommand = &cobra.Command{
+ Use: "create [flags] IMAGE [COMMAND [ARG...]]",
+ Short: "Create but do not start a container",
+ Long: createDescription,
+ RunE: create,
+ PersistentPreRunE: preRunE,
+ Args: cobra.MinimumNArgs(1),
+ Example: `podman create alpine ls
+ podman create --annotation HELLO=WORLD alpine ls
+ podman create -t -i --name myctr alpine ls`,
+ }
+)
+
+var (
+ cliVals common.ContainerCLIOpts
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: createCommand,
+ })
+ //common.GetCreateFlags(createCommand)
+ flags := createCommand.Flags()
+ flags.AddFlagSet(common.GetCreateFlags(&cliVals))
+ flags.AddFlagSet(common.GetNetFlags())
+ flags.SetNormalizeFunc(common.AliasFlags)
+}
+
+func create(cmd *cobra.Command, args []string) error {
+ var (
+ err error
+ rawImageInput string
+ )
+ cliVals.Net, err = common.NetFlagsToNetOptions(cmd)
+ if err != nil {
+ return err
+ }
+ if rfs := cliVals.RootFS; !rfs {
+ rawImageInput = args[0]
+ }
+
+ if err := createInit(cmd); err != nil {
+ return err
+ }
+ //TODO rootfs still
+ s := specgen.NewSpecGenerator(rawImageInput)
+ if err := common.FillOutSpecGen(s, &cliVals, args); err != nil {
+ return err
+ }
+
+ report, err := registry.ContainerEngine().ContainerCreate(registry.GetContext(), s)
+ if err != nil {
+ return err
+ }
+ fmt.Println(report.Id)
+ return nil
+}
+
+func createInit(c *cobra.Command) error {
+ if c.Flag("privileged").Changed && c.Flag("security-opt").Changed {
+ logrus.Warn("setting security options with --privileged has no effect")
+ }
+
+ if (c.Flag("dns").Changed || c.Flag("dns-opt").Changed || c.Flag("dns-search").Changed) && (cliVals.Net.Network.NSMode == specgen.NoNetwork || cliVals.Net.Network.IsContainer()) {
+ return errors.Errorf("conflicting options: dns and the network mode.")
+ }
+
+ if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed {
+ return errors.Errorf("--cpu-period and --cpus cannot be set together")
+ }
+ if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed {
+ return errors.Errorf("--cpu-quota and --cpus cannot be set together")
+ }
+
+ if c.Flag("no-hosts").Changed && c.Flag("add-host").Changed {
+ return errors.Errorf("--no-hosts and --add-host cannot be set together")
+ }
+
+ // Docker-compatibility: the "-h" flag for run/create is reserved for
+ // the hostname (see https://github.com/containers/libpod/issues/1367).
+
+ return nil
+}
diff --git a/cmd/podmanV2/parse/common.go b/cmd/podmanV2/parse/common.go
new file mode 100644
index 000000000..a5e9b4fc2
--- /dev/null
+++ b/cmd/podmanV2/parse/common.go
@@ -0,0 +1,50 @@
+package parse
+
+import (
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+// CheckAllLatestAndCIDFile checks that --all and --latest are used correctly.
+// If cidfile is set, also check for the --cidfile flag.
+func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error {
+ argLen := len(args)
+ if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil {
+ if !cidfile {
+ return errors.New("unable to lookup values for 'latest' or 'all'")
+ } else if c.Flags().Lookup("cidfile") == nil {
+ return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'")
+ }
+ }
+
+ specifiedAll, _ := c.Flags().GetBool("all")
+ specifiedLatest, _ := c.Flags().GetBool("latest")
+ specifiedCIDFile := false
+ if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 {
+ specifiedCIDFile = true
+ }
+
+ if specifiedCIDFile && (specifiedAll || specifiedLatest) {
+ return errors.Errorf("--all, --latest and --cidfile cannot be used together")
+ } else if specifiedAll && specifiedLatest {
+ return errors.Errorf("--all and --latest cannot be used together")
+ }
+
+ if ignoreArgLen {
+ return nil
+ }
+ if (argLen > 0) && (specifiedAll || specifiedLatest) {
+ return errors.Errorf("no arguments are needed with --all or --latest")
+ } else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) {
+ return errors.Errorf("no arguments are needed with --all, --latest or --cidfile")
+ }
+
+ if specifiedCIDFile {
+ return nil
+ }
+
+ if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile {
+ return errors.Errorf("you must provide at least one name or id")
+ }
+ return nil
+}
diff --git a/cmd/podmanV2/parse/parse.go b/cmd/podmanV2/parse/net.go
index 10d2146fa..03cda268c 100644
--- a/cmd/podmanV2/parse/parse.go
+++ b/cmd/podmanV2/parse/net.go
@@ -13,7 +13,6 @@ import (
"strings"
"github.com/pkg/errors"
- "github.com/spf13/cobra"
)
const (
@@ -187,47 +186,3 @@ func ValidURL(urlStr string) error {
}
return nil
}
-
-// checkAllLatestAndCIDFile checks that --all and --latest are used correctly.
-// If cidfile is set, also check for the --cidfile flag.
-func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error {
- argLen := len(args)
- if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil {
- if !cidfile {
- return errors.New("unable to lookup values for 'latest' or 'all'")
- } else if c.Flags().Lookup("cidfile") == nil {
- return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'")
- }
- }
-
- specifiedAll, _ := c.Flags().GetBool("all")
- specifiedLatest, _ := c.Flags().GetBool("latest")
- specifiedCIDFile := false
- if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 {
- specifiedCIDFile = true
- }
-
- if specifiedCIDFile && (specifiedAll || specifiedLatest) {
- return errors.Errorf("--all, --latest and --cidfile cannot be used together")
- } else if specifiedAll && specifiedLatest {
- return errors.Errorf("--all and --latest cannot be used together")
- }
-
- if ignoreArgLen {
- return nil
- }
- if (argLen > 0) && (specifiedAll || specifiedLatest) {
- return errors.Errorf("no arguments are needed with --all or --latest")
- } else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) {
- return errors.Errorf("no arguments are needed with --all, --latest or --cidfile")
- }
-
- if specifiedCIDFile {
- return nil
- }
-
- if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile {
- return errors.Errorf("you must provide at least one name or id")
- }
- return nil
-}
diff --git a/cmd/podmanV2/parse/parse_test.go b/cmd/podmanV2/parse/net_test.go
index a6ddc2be9..a6ddc2be9 100644
--- a/cmd/podmanV2/parse/parse_test.go
+++ b/cmd/podmanV2/parse/net_test.go
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
index ebca41151..38a341a89 100644
--- a/pkg/api/handlers/libpod/containers_create.go
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/pkg/specgen/generate"
"github.com/pkg/errors"
)
@@ -19,7 +20,11 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
- ctr, err := sg.MakeContainer(runtime)
+ if err := generate.CompleteSpec(r.Context(), runtime, &sg); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ ctr, err := generate.MakeContainer(runtime, &sg)
if err != nil {
utils.InternalServerError(w, err)
return
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 45dea98bf..74b23cd71 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -153,3 +153,7 @@ type RestoreReport struct {
Err error
Id string
}
+
+type ContainerCreateReport struct {
+ Id string
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 3aaa7136f..025da50f3 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -4,12 +4,14 @@ import (
"context"
"github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/specgen"
)
type ContainerEngine interface {
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
+ ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error)
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 3c38b2093..d3d51db82 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -16,6 +16,8 @@ import (
"github.com/containers/libpod/pkg/checkpoint"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/signal"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/pkg/specgen/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -442,3 +444,14 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
}
return reports, nil
}
+
+func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) {
+ if err := generate.CompleteSpec(ctx, ic.Libpod, s); err != nil {
+ return nil, err
+ }
+ ctr, err := generate.MakeContainer(ic.Libpod, s)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.ContainerCreateReport{Id: ctr.ID()}, nil
+}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 5832d41be..ae8994cba 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/containers/libpod/pkg/bindings/containers"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
)
@@ -296,3 +297,11 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
}
return reports, nil
}
+
+func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) {
+ response, err := containers.CreateWithSpec(ic.ClientCxt, s)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.ContainerCreateReport{Id: response.ID}, nil
+}
diff --git a/pkg/specgen/config_linux.go b/pkg/specgen/config_linux.go
new file mode 100644
index 000000000..82a371492
--- /dev/null
+++ b/pkg/specgen/config_linux.go
@@ -0,0 +1,93 @@
+package specgen
+
+//func createBlockIO() (*spec.LinuxBlockIO, error) {
+// var ret *spec.LinuxBlockIO
+// bio := &spec.LinuxBlockIO{}
+// if c.Resources.BlkioWeight > 0 {
+// ret = bio
+// bio.Weight = &c.Resources.BlkioWeight
+// }
+// if len(c.Resources.BlkioWeightDevice) > 0 {
+// var lwds []spec.LinuxWeightDevice
+// ret = bio
+// for _, i := range c.Resources.BlkioWeightDevice {
+// wd, err := ValidateweightDevice(i)
+// if err != nil {
+// return ret, errors.Wrapf(err, "invalid values for blkio-weight-device")
+// }
+// wdStat, err := GetStatFromPath(wd.Path)
+// if err != nil {
+// return ret, errors.Wrapf(err, "error getting stat from path %q", wd.Path)
+// }
+// lwd := spec.LinuxWeightDevice{
+// Weight: &wd.Weight,
+// }
+// lwd.Major = int64(unix.Major(wdStat.Rdev))
+// lwd.Minor = int64(unix.Minor(wdStat.Rdev))
+// lwds = append(lwds, lwd)
+// }
+// bio.WeightDevice = lwds
+// }
+// if len(c.Resources.DeviceReadBps) > 0 {
+// ret = bio
+// readBps, err := makeThrottleArray(c.Resources.DeviceReadBps, bps)
+// if err != nil {
+// return ret, err
+// }
+// bio.ThrottleReadBpsDevice = readBps
+// }
+// if len(c.Resources.DeviceWriteBps) > 0 {
+// ret = bio
+// writeBpds, err := makeThrottleArray(c.Resources.DeviceWriteBps, bps)
+// if err != nil {
+// return ret, err
+// }
+// bio.ThrottleWriteBpsDevice = writeBpds
+// }
+// if len(c.Resources.DeviceReadIOps) > 0 {
+// ret = bio
+// readIOps, err := makeThrottleArray(c.Resources.DeviceReadIOps, iops)
+// if err != nil {
+// return ret, err
+// }
+// bio.ThrottleReadIOPSDevice = readIOps
+// }
+// if len(c.Resources.DeviceWriteIOps) > 0 {
+// ret = bio
+// writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps, iops)
+// if err != nil {
+// return ret, err
+// }
+// bio.ThrottleWriteIOPSDevice = writeIOps
+// }
+// return ret, nil
+//}
+
+//func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) {
+// var (
+// ltds []spec.LinuxThrottleDevice
+// t *throttleDevice
+// err error
+// )
+// for _, i := range throttleInput {
+// if rateType == bps {
+// t, err = validateBpsDevice(i)
+// } else {
+// t, err = validateIOpsDevice(i)
+// }
+// if err != nil {
+// return []spec.LinuxThrottleDevice{}, err
+// }
+// ltdStat, err := GetStatFromPath(t.path)
+// if err != nil {
+// return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path)
+// }
+// ltd := spec.LinuxThrottleDevice{
+// Rate: t.rate,
+// }
+// ltd.Major = int64(unix.Major(ltdStat.Rdev))
+// ltd.Minor = int64(unix.Minor(ltdStat.Rdev))
+// ltds = append(ltds, ltd)
+// }
+// return ltds, nil
+//}
diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/config_linux_cgo.go
index 6f547a40d..ef6c6e951 100644
--- a/pkg/specgen/config_linux_cgo.go
+++ b/pkg/specgen/config_linux_cgo.go
@@ -17,7 +17,6 @@ import (
func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
var seccompConfig *spec.LinuxSeccomp
var err error
-
scp, err := seccomp.LookupPolicy(s.SeccompPolicy)
if err != nil {
return nil, err
diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go
index b27659f5f..aad14ddcb 100644
--- a/pkg/specgen/container_validate.go
+++ b/pkg/specgen/container_validate.go
@@ -14,7 +14,7 @@ var (
// SystemDValues describes the only values that SystemD can be
SystemDValues = []string{"true", "false", "always"}
// ImageVolumeModeValues describes the only values that ImageVolumeMode can be
- ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"}
+ ImageVolumeModeValues = []string{"ignore", "tmpfs", "bind"}
)
func exclusiveOptions(opt1, opt2 string) error {
@@ -23,7 +23,7 @@ func exclusiveOptions(opt1, opt2 string) error {
// Validate verifies that the given SpecGenerator is valid and satisfies required
// input for creating a container.
-func (s *SpecGenerator) validate() error {
+func (s *SpecGenerator) Validate() error {
//
// ContainerBasicConfig
diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go
new file mode 100644
index 000000000..78c77fec1
--- /dev/null
+++ b/pkg/specgen/generate/container.go
@@ -0,0 +1,168 @@
+package generate
+
+import (
+ "context"
+
+ "github.com/containers/libpod/libpod"
+ ann "github.com/containers/libpod/pkg/annotations"
+ envLib "github.com/containers/libpod/pkg/env"
+ "github.com/containers/libpod/pkg/signal"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
+ "golang.org/x/sys/unix"
+)
+
+func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error {
+
+ newImage, err := r.ImageRuntime().NewFromLocal(s.Image)
+ if err != nil {
+ return err
+ }
+
+ // Image stop signal
+ if s.StopSignal == nil && newImage.Config != nil {
+ sig, err := signal.ParseSignalNameOrNumber(newImage.Config.StopSignal)
+ if err != nil {
+ return err
+ }
+ s.StopSignal = &sig
+ }
+ // Image envs from the image if they don't exist
+ // already
+ if newImage.Config != nil && len(newImage.Config.Env) > 0 {
+ envs, err := envLib.ParseSlice(newImage.Config.Env)
+ if err != nil {
+ return err
+ }
+ for k, v := range envs {
+ if _, exists := s.Env[k]; !exists {
+ s.Env[v] = k
+ }
+ }
+ }
+
+ // labels from the image that dont exist already
+ if config := newImage.Config; config != nil {
+ for k, v := range config.Labels {
+ if _, exists := s.Labels[k]; !exists {
+ s.Labels[k] = v
+ }
+ }
+ }
+
+ // annotations
+ // in the event this container is in a pod, and the pod has an infra container
+ // we will want to configure it as a type "container" instead defaulting to
+ // the behavior of a "sandbox" container
+ // In Kata containers:
+ // - "sandbox" is the annotation that denotes the container should use its own
+ // VM, which is the default behavior
+ // - "container" denotes the container should join the VM of the SandboxID
+ // (the infra container)
+ s.Annotations = make(map[string]string)
+ if len(s.Pod) > 0 {
+ s.Annotations[ann.SandboxID] = s.Pod
+ s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer
+ }
+ //
+ // Next, add annotations from the image
+ annotations, err := newImage.Annotations(ctx)
+ if err != nil {
+ return err
+ }
+ for k, v := range annotations {
+ annotations[k] = v
+ }
+
+ // entrypoint
+ if config := newImage.Config; config != nil {
+ if len(s.Entrypoint) < 1 && len(config.Entrypoint) > 0 {
+ s.Entrypoint = config.Entrypoint
+ }
+ if len(s.Command) < 1 && len(config.Cmd) > 0 {
+ s.Command = config.Cmd
+ }
+ if len(s.Command) < 1 && len(s.Entrypoint) < 1 {
+ return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image")
+ }
+ // workdir
+ if len(s.WorkDir) < 1 && len(config.WorkingDir) > 1 {
+ s.WorkDir = config.WorkingDir
+ }
+ }
+
+ if len(s.SeccompProfilePath) < 1 {
+ p, err := libpod.DefaultSeccompPath()
+ if err != nil {
+ return err
+ }
+ s.SeccompProfilePath = p
+ }
+
+ if user := s.User; len(user) == 0 {
+ switch {
+ // TODO This should be enabled when namespaces actually work
+ //case usernsMode.IsKeepID():
+ // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
+ case newImage.Config == nil || (newImage.Config != nil && len(newImage.Config.User) == 0):
+ s.User = "0"
+ default:
+ s.User = newImage.Config.User
+ }
+ }
+ if err := finishThrottleDevices(s); err != nil {
+ return err
+ }
+ return nil
+}
+
+// finishThrottleDevices takes the temporary representation of the throttle
+// devices in the specgen and looks up the major and major minors. it then
+// sets the throttle devices proper in the specgen
+func finishThrottleDevices(s *specgen.SpecGenerator) error {
+ if bps := s.ThrottleReadBpsDevice; len(bps) > 0 {
+ for k, v := range bps {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v)
+ }
+ }
+ if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 {
+ for k, v := range bps {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v)
+ }
+ }
+ if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 {
+ for k, v := range iops {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v)
+ }
+ }
+ if iops := s.ThrottleWriteBpsDevice; len(iops) > 0 {
+ for k, v := range iops {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v)
+ }
+ }
+ return nil
+}
diff --git a/pkg/specgen/container_create.go b/pkg/specgen/generate/container_create.go
index b4039bb91..aad59a861 100644
--- a/pkg/specgen/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -1,4 +1,4 @@
-package specgen
+package generate
import (
"context"
@@ -7,14 +7,15 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// MakeContainer creates a container based on the SpecGenerator
-func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) {
- if err := s.validate(); err != nil {
+func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) {
+ if err := s.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid config provided")
}
rtc, err := rt.GetConfig()
@@ -22,7 +23,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
return nil, err
}
- options, err := s.createContainerOptions(rt)
+ options, err := createContainerOptions(rt, s)
if err != nil {
return nil, err
}
@@ -31,7 +32,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
if err != nil {
return nil, err
}
- options = append(options, s.createExitCommandOption(rt.StorageConfig(), rtc, podmanPath))
+ options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath))
newImage, err := rt.ImageRuntime().NewFromLocal(s.Image)
if err != nil {
return nil, err
@@ -39,14 +40,14 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er
options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
- runtimeSpec, err := s.toOCISpec(rt, newImage)
+ runtimeSpec, err := s.ToOCISpec(rt, newImage)
if err != nil {
return nil, err
}
return rt.NewContainer(context.Background(), runtimeSpec, options...)
}
-func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
+func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@@ -114,7 +115,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
- namespaceOptions, err := s.generateNamespaceContainerOpts(rt)
+ namespaceOptions, err := s.GenerateNamespaceContainerOpts(rt)
if err != nil {
return nil, err
}
@@ -149,7 +150,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr
return options, nil
}
-func (s *SpecGenerator) createExitCommandOption(storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption {
+func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption {
// We need a cleanup process for containers in the current model.
// But we can't assume that the caller is Podman - it could be another
// user of the API.
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index fa2dee77d..2a7bb3495 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -16,6 +16,9 @@ import (
type NamespaceMode string
const (
+ // Default indicates the spec generator should determine
+ // a sane default
+ Default NamespaceMode = "default"
// Host means the the namespace is derived from
// the host
Host NamespaceMode = "host"
@@ -83,7 +86,7 @@ func validateNetNS(n *Namespace) error {
return nil
}
-// validate perform simple validation on the namespace to make sure it is not
+// Validate perform simple validation on the namespace to make sure it is not
// invalid from the get-go
func (n *Namespace) validate() error {
if n == nil {
@@ -103,7 +106,7 @@ func (n *Namespace) validate() error {
return nil
}
-func (s *SpecGenerator) generateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
+func (s *SpecGenerator) GenerateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
var portBindings []ocicni.PortMapping
options := make([]libpod.CtrCreateOption, 0)
diff --git a/pkg/specgen/oci.go b/pkg/specgen/oci.go
index db60dc25e..0756782b4 100644
--- a/pkg/specgen/oci.go
+++ b/pkg/specgen/oci.go
@@ -11,7 +11,7 @@ import (
"github.com/opencontainers/runtime-tools/generate"
)
-func (s *SpecGenerator) toOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
+func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
var (
inUserNS bool
)
diff --git a/pkg/specgen/security.go b/pkg/specgen/security.go
new file mode 100644
index 000000000..158e4a7b3
--- /dev/null
+++ b/pkg/specgen/security.go
@@ -0,0 +1,165 @@
+package specgen
+
+// ToCreateOptions convert the SecurityConfig to a slice of container create
+// options.
+/*
+func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) {
+ options := make([]libpod.CtrCreateOption, 0)
+ options = append(options, libpod.WithSecLabels(c.LabelOpts))
+ options = append(options, libpod.WithPrivileged(c.Privileged))
+ return options, nil
+}
+*/
+
+// SetLabelOpts sets the label options of the SecurityConfig according to the
+// input.
+/*
+func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidConfig, ipcConfig *IpcConfig) error {
+ if c.Privileged {
+ c.LabelOpts = label.DisableSecOpt()
+ return nil
+ }
+
+ var labelOpts []string
+ if pidConfig.PidMode.IsHost() {
+ labelOpts = append(labelOpts, label.DisableSecOpt()...)
+ } else if pidConfig.PidMode.IsContainer() {
+ ctr, err := runtime.LookupContainer(pidConfig.PidMode.Container())
+ if err != nil {
+ return errors.Wrapf(err, "container %q not found", pidConfig.PidMode.Container())
+ }
+ secopts, err := label.DupSecOpt(ctr.ProcessLabel())
+ if err != nil {
+ return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel())
+ }
+ labelOpts = append(labelOpts, secopts...)
+ }
+
+ if ipcConfig.IpcMode.IsHost() {
+ labelOpts = append(labelOpts, label.DisableSecOpt()...)
+ } else if ipcConfig.IpcMode.IsContainer() {
+ ctr, err := runtime.LookupContainer(ipcConfig.IpcMode.Container())
+ if err != nil {
+ return errors.Wrapf(err, "container %q not found", ipcConfig.IpcMode.Container())
+ }
+ secopts, err := label.DupSecOpt(ctr.ProcessLabel())
+ if err != nil {
+ return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel())
+ }
+ labelOpts = append(labelOpts, secopts...)
+ }
+
+ c.LabelOpts = append(c.LabelOpts, labelOpts...)
+ return nil
+}
+*/
+
+// SetSecurityOpts the the security options (labels, apparmor, seccomp, etc.).
+func SetSecurityOpts(securityOpts []string) error {
+ return nil
+}
+
+// ConfigureGenerator configures the generator according to the input.
+/*
+func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error {
+ // HANDLE CAPABILITIES
+ // NOTE: Must happen before SECCOMP
+ if c.Privileged {
+ g.SetupPrivileged(true)
+ }
+
+ useNotRoot := func(user string) bool {
+ if user == "" || user == "root" || user == "0" {
+ return false
+ }
+ return true
+ }
+
+ configSpec := g.Config
+ var err error
+ var defaultCaplist []string
+ bounding := configSpec.Process.Capabilities.Bounding
+ if useNotRoot(user.User) {
+ configSpec.Process.Capabilities.Bounding = defaultCaplist
+ }
+ defaultCaplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop)
+ if err != nil {
+ return err
+ }
+
+ privCapRequired := []string{}
+
+ if !c.Privileged && len(c.CapRequired) > 0 {
+ // Pass CapRequired in CapAdd field to normalize capabilities names
+ capRequired, err := capabilities.MergeCapabilities(nil, c.CapRequired, nil)
+ if err != nil {
+ logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(c.CapRequired, ","))
+ } else {
+ // Verify all capRequiered are in the defaultCapList
+ for _, cap := range capRequired {
+ if !util.StringInSlice(cap, defaultCaplist) {
+ privCapRequired = append(privCapRequired, cap)
+ }
+ }
+ }
+ if len(privCapRequired) == 0 {
+ defaultCaplist = capRequired
+ } else {
+ logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapRequired, ","))
+ }
+ }
+ configSpec.Process.Capabilities.Bounding = defaultCaplist
+ configSpec.Process.Capabilities.Permitted = defaultCaplist
+ configSpec.Process.Capabilities.Inheritable = defaultCaplist
+ configSpec.Process.Capabilities.Effective = defaultCaplist
+ configSpec.Process.Capabilities.Ambient = defaultCaplist
+ if useNotRoot(user.User) {
+ defaultCaplist, err = capabilities.MergeCapabilities(bounding, c.CapAdd, c.CapDrop)
+ if err != nil {
+ return err
+ }
+ }
+ configSpec.Process.Capabilities.Bounding = defaultCaplist
+
+ // HANDLE SECCOMP
+ if c.SeccompProfilePath != "unconfined" {
+ seccompConfig, err := getSeccompConfig(c, configSpec)
+ if err != nil {
+ return err
+ }
+ configSpec.Linux.Seccomp = seccompConfig
+ }
+
+ // Clear default Seccomp profile from Generator for privileged containers
+ if c.SeccompProfilePath == "unconfined" || c.Privileged {
+ configSpec.Linux.Seccomp = nil
+ }
+
+ for _, opt := range c.SecurityOpts {
+ // Split on both : and =
+ splitOpt := strings.Split(opt, "=")
+ if len(splitOpt) == 1 {
+ splitOpt = strings.Split(opt, ":")
+ }
+ if len(splitOpt) < 2 {
+ continue
+ }
+ switch splitOpt[0] {
+ case "label":
+ configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1]
+ case "seccomp":
+ configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1]
+ case "apparmor":
+ configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1]
+ }
+ }
+
+ g.SetRootReadonly(c.ReadOnlyRootfs)
+ for sysctlKey, sysctlVal := range c.Sysctl {
+ g.AddLinuxSysctl(sysctlKey, sysctlVal)
+ }
+
+ return nil
+}
+
+*/
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 89c76c273..2e6dd9c1d 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -4,8 +4,9 @@ import (
"net"
"syscall"
- "github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod"
+
+ "github.com/containers/image/v5/manifest"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
@@ -371,6 +372,16 @@ type ContainerResourceConfig struct {
// processes to kill for the container's process.
// Optional.
OOMScoreAdj *int `json:"oom_score_adj,omitempty"`
+ // Weight per cgroup per device, can override BlkioWeight
+ WeightDevice map[string]spec.LinuxWeightDevice `json:"weightDevice,omitempty"`
+ // IO read rate limit per cgroup per device, bytes per second
+ ThrottleReadBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"`
+ // IO write rate limit per cgroup per device, bytes per second
+ ThrottleWriteBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteBpsDevice,omitempty"`
+ // IO read rate limit per cgroup per device, IO per second
+ ThrottleReadIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadIOPSDevice,omitempty"`
+ // IO write rate limit per cgroup per device, IO per second
+ ThrottleWriteIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteIOPSDevice,omitempty"`
}
// ContainerHealthCheckConfig describes a container healthcheck with attributes
diff --git a/pkg/specgen/storage.go b/pkg/specgen/storage.go
new file mode 100644
index 000000000..1b903f608
--- /dev/null
+++ b/pkg/specgen/storage.go
@@ -0,0 +1,885 @@
+package specgen
+
+//nolint
+
+import (
+ "fmt"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/libpod/pkg/util"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // TypeBind is the type for mounting host dir
+ TypeBind = "bind"
+ // TypeVolume is the type for named volumes
+ TypeVolume = "volume"
+ // TypeTmpfs is the type for mounting tmpfs
+ TypeTmpfs = "tmpfs"
+)
+
+var (
+ errDuplicateDest = errors.Errorf("duplicate mount destination") //nolint
+ optionArgError = errors.Errorf("must provide an argument for option") //nolint
+ noDestError = errors.Errorf("must set volume destination") //nolint
+)
+
+// Parse all volume-related options in the create config into a set of mounts
+// and named volumes to add to the container.
+// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags.
+// TODO: Named volume options - should we default to rprivate? It bakes into a
+// bind mount under the hood...
+// TODO: handle options parsing/processing via containers/storage/pkg/mount
+func (s *SpecGenerator) parseVolumes(mounts, volMounts, tmpMounts []string) error { //nolint
+
+ // TODO this needs to come from the image and erquires a runtime
+
+ // Add image volumes.
+ //baseMounts, baseVolumes, err := config.getImageVolumes()
+ //if err != nil {
+ // return nil, nil, err
+ //}
+
+ // Add --volumes-from.
+ // Overrides image volumes unconditionally.
+ //vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime)
+ //if err != nil {
+ // return nil, nil, err
+ //}
+ //for dest, mount := range vFromMounts {
+ // baseMounts[dest] = mount
+ //}
+ //for dest, volume := range vFromVolumes {
+ // baseVolumes[dest] = volume
+ //}
+
+ // Next mounts from the --mounts flag.
+ // Do not override yet.
+ //unifiedMounts, _, err := getMounts(mounts)
+ //if err != nil {
+ // return err
+ //}
+ //
+ //// Next --volumes flag.
+ //// Do not override yet.
+ //volumeMounts, _ , err := getVolumeMounts(volMounts)
+ //if err != nil {
+ // return err
+ //}
+ //
+ //// Next --tmpfs flag.
+ //// Do not override yet.
+ //tmpfsMounts, err := getTmpfsMounts(tmpMounts)
+ //if err != nil {
+ // return err
+ //}
+
+ //// Unify mounts from --mount, --volume, --tmpfs.
+ //// Also add mounts + volumes directly from createconfig.
+ //// Start with --volume.
+ //for dest, mount := range volumeMounts {
+ // if _, ok := unifiedMounts[dest]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ // }
+ // unifiedMounts[dest] = mount
+ //}
+ //for dest, volume := range volumeVolumes {
+ // if _, ok := unifiedVolumes[dest]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ // }
+ // unifiedVolumes[dest] = volume
+ //}
+ //// Now --tmpfs
+ //for dest, tmpfs := range tmpfsMounts {
+ // if _, ok := unifiedMounts[dest]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ // }
+ // unifiedMounts[dest] = tmpfs
+ //}
+ //// Now spec mounts and volumes
+ //for _, mount := range config.Mounts {
+ // dest := mount.Destination
+ // if _, ok := unifiedMounts[dest]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ // }
+ // unifiedMounts[dest] = mount
+ //}
+ //for _, volume := range config.NamedVolumes {
+ // dest := volume.Dest
+ // if _, ok := unifiedVolumes[dest]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ // }
+ // unifiedVolumes[dest] = volume
+ //}
+ //
+ //// If requested, add container init binary
+ //if config.Init {
+ // initPath := config.InitPath
+ // if initPath == "" {
+ // rtc, err := runtime.GetConfig()
+ // if err != nil {
+ // return nil, nil, err
+ // }
+ // initPath = rtc.Engine.InitPath
+ // }
+ // initMount, err := config.addContainerInitBinary(initPath)
+ // if err != nil {
+ // return nil, nil, err
+ // }
+ // if _, ok := unifiedMounts[initMount.Destination]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
+ // }
+ // unifiedMounts[initMount.Destination] = initMount
+ //}
+ //
+ //// Before superseding, we need to find volume mounts which conflict with
+ //// named volumes, and vice versa.
+ //// We'll delete the conflicts here as we supersede.
+ //for dest := range unifiedMounts {
+ // if _, ok := baseVolumes[dest]; ok {
+ // delete(baseVolumes, dest)
+ // }
+ //}
+ //for dest := range unifiedVolumes {
+ // if _, ok := baseMounts[dest]; ok {
+ // delete(baseMounts, dest)
+ // }
+ //}
+ //
+ //// Supersede volumes-from/image volumes with unified volumes from above.
+ //// This is an unconditional replacement.
+ //for dest, mount := range unifiedMounts {
+ // baseMounts[dest] = mount
+ //}
+ //for dest, volume := range unifiedVolumes {
+ // baseVolumes[dest] = volume
+ //}
+ //
+ //// If requested, add tmpfs filesystems for read-only containers.
+ //if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs {
+ // readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"}
+ // options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
+ // for _, dest := range readonlyTmpfs {
+ // if _, ok := baseMounts[dest]; ok {
+ // continue
+ // }
+ // if _, ok := baseVolumes[dest]; ok {
+ // continue
+ // }
+ // localOpts := options
+ // if dest == "/run" {
+ // localOpts = append(localOpts, "noexec", "size=65536k")
+ // } else {
+ // localOpts = append(localOpts, "exec")
+ // }
+ // baseMounts[dest] = spec.Mount{
+ // Destination: dest,
+ // Type: "tmpfs",
+ // Source: "tmpfs",
+ // Options: localOpts,
+ // }
+ // }
+ //}
+ //
+ //// Check for conflicts between named volumes and mounts
+ //for dest := range baseMounts {
+ // if _, ok := baseVolumes[dest]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ // }
+ //}
+ //for dest := range baseVolumes {
+ // if _, ok := baseMounts[dest]; ok {
+ // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ // }
+ //}
+ //
+ //// Final step: maps to arrays
+ //finalMounts := make([]spec.Mount, 0, len(baseMounts))
+ //for _, mount := range baseMounts {
+ // if mount.Type == TypeBind {
+ // absSrc, err := filepath.Abs(mount.Source)
+ // if err != nil {
+ // return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
+ // }
+ // mount.Source = absSrc
+ // }
+ // finalMounts = append(finalMounts, mount)
+ //}
+ //finalVolumes := make([]*define.ContainerNamedVolume, 0, len(baseVolumes))
+ //for _, volume := range baseVolumes {
+ // finalVolumes = append(finalVolumes, volume)
+ //}
+
+ //return finalMounts, finalVolumes, nil
+ return nil
+}
+
+// Parse volumes from - a set of containers whose volumes we will mount in.
+// Grab the containers, retrieve any user-created spec mounts and all named
+// volumes, and return a list of them.
+// Conflicts are resolved simply - the last container specified wins.
+// Container names may be suffixed by mount options after a colon.
+// TODO: We should clean these paths if possible
+// TODO deferred baude
+func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ // Both of these are maps of mount destination to mount type.
+ // We ensure that each destination is only mounted to once in this way.
+ //finalMounts := make(map[string]spec.Mount)
+ //finalNamedVolumes := make(map[string]*define.ContainerNamedVolume)
+ //
+ //for _, vol := range config.VolumesFrom {
+ // var (
+ // options = []string{}
+ // err error
+ // splitVol = strings.SplitN(vol, ":", 2)
+ // )
+ // if len(splitVol) == 2 {
+ // splitOpts := strings.Split(splitVol[1], ",")
+ // for _, checkOpt := range splitOpts {
+ // switch checkOpt {
+ // case "z", "ro", "rw":
+ // // Do nothing, these are valid options
+ // default:
+ // return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1])
+ // }
+ // }
+ //
+ // if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil {
+ // return nil, nil, err
+ // }
+ // }
+ // ctr, err := runtime.LookupContainer(splitVol[0])
+ // if err != nil {
+ // return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
+ // }
+ //
+ // logrus.Debugf("Adding volumes from container %s", ctr.ID())
+ //
+ // // Look up the container's user volumes. This gets us the
+ // // destinations of all mounts the user added to the container.
+ // userVolumesArr := ctr.UserVolumes()
+ //
+ // // We're going to need to access them a lot, so convert to a map
+ // // to reduce looping.
+ // // We'll also use the map to indicate if we missed any volumes along the way.
+ // userVolumes := make(map[string]bool)
+ // for _, dest := range userVolumesArr {
+ // userVolumes[dest] = false
+ // }
+ //
+ // // Now we get the container's spec and loop through its volumes
+ // // and append them in if we can find them.
+ // spec := ctr.Spec()
+ // if spec == nil {
+ // return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
+ // }
+ // for _, mnt := range spec.Mounts {
+ // if mnt.Type != TypeBind {
+ // continue
+ // }
+ // if _, exists := userVolumes[mnt.Destination]; exists {
+ // userVolumes[mnt.Destination] = true
+ //
+ // if len(options) != 0 {
+ // mnt.Options = options
+ // }
+ //
+ // if _, ok := finalMounts[mnt.Destination]; ok {
+ // logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
+ // }
+ // finalMounts[mnt.Destination] = mnt
+ // }
+ // }
+ //
+ // // We're done with the spec mounts. Add named volumes.
+ // // Add these unconditionally - none of them are automatically
+ // // part of the container, as some spec mounts are.
+ // namedVolumes := ctr.NamedVolumes()
+ // for _, namedVol := range namedVolumes {
+ // if _, exists := userVolumes[namedVol.Dest]; exists {
+ // userVolumes[namedVol.Dest] = true
+ // }
+ //
+ // if len(options) != 0 {
+ // namedVol.Options = options
+ // }
+ //
+ // if _, ok := finalMounts[namedVol.Dest]; ok {
+ // logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
+ // }
+ // finalNamedVolumes[namedVol.Dest] = namedVol
+ // }
+ //
+ // // Check if we missed any volumes
+ // for volDest, found := range userVolumes {
+ // if !found {
+ // logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
+ // }
+ // }
+ //}
+ //
+ //return finalMounts, finalNamedVolumes, nil
+ return nil, nil, nil
+}
+
+// getMounts takes user-provided input from the --mount flag and creates OCI
+// spec mounts and Libpod named volumes.
+// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
+// podman run --mount type=tmpfs,target=/dev/shm ...
+// podman run --mount type=volume,source=test-volume, ...
+func getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
+
+ // TODO(vrothberg): the manual parsing can be replaced with a regular expression
+ // to allow a more robust parsing of the mount format and to give
+ // precise errors regarding supported format versus supported options.
+ for _, mount := range mounts {
+ arr := strings.SplitN(mount, ",", 2)
+ if len(arr) < 2 {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+ kv := strings.Split(arr[0], "=")
+ // TODO: type is not explicitly required in Docker.
+ // If not specified, it defaults to "volume".
+ if len(kv) != 2 || kv[0] != "type" {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+
+ tokens := strings.Split(arr[1], ",")
+ switch kv[1] {
+ case TypeBind:
+ mount, err := getBindMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case TypeTmpfs:
+ mount, err := getTmpfsMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case "volume":
+ volume, err := getNamedVolume(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalNamedVolumes[volume.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
+ }
+ finalNamedVolumes[volume.Dest] = volume
+ default:
+ return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1])
+ }
+ }
+
+ return finalMounts, finalNamedVolumes, nil
+}
+
+// Parse a single bind mount entry from the --mount flag.
+func getBindMount(args []string) (spec.Mount, error) { //nolint
+ newMount := spec.Mount{
+ Type: TypeBind,
+ }
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "bind-nonrecursive":
+ newMount.Options = append(newMount.Options, "bind")
+ case "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once")
+ }
+ setRORW = true
+ // Can be formatted as one of:
+ // ro
+ // ro=[true|false]
+ // rw
+ // rw=[true|false]
+ switch len(kv) {
+ case 1:
+ newMount.Options = append(newMount.Options, kv[0])
+ case 2:
+ switch strings.ToLower(kv[1]) {
+ case "true":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "false":
+ // Set the opposite only for rw
+ // ro's opposite is the default
+ if kv[0] == "rw" {
+ newMount.Options = append(newMount.Options, "ro")
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1])
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val)
+ }
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "bind-propagation":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, kv[1])
+ case "src", "source":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeHostDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Source = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ case "relabel":
+ if setRelabel {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once")
+ }
+ setRelabel = true
+ if len(kv) != 2 {
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ switch kv[1] {
+ case "private":
+ newMount.Options = append(newMount.Options, "z")
+ case "shared":
+ newMount.Options = append(newMount.Options, "Z")
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ if !setSource {
+ newMount.Source = newMount.Destination
+ }
+
+ options, err := parse.ValidateVolumeOpts(newMount.Options)
+ if err != nil {
+ return newMount, err
+ }
+ newMount.Options = options
+ return newMount, nil
+}
+
+// Parse a single tmpfs mount entry from the --mount flag
+func getTmpfsMount(args []string) (spec.Mount, error) { //nolint
+ newMount := spec.Mount{
+ Type: TypeTmpfs,
+ Source: TypeTmpfs,
+ }
+
+ var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "tmpcopyup", "notmpcopyup":
+ if setTmpcopyup {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once")
+ }
+ setTmpcopyup = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "tmpfs-mode":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
+ case "tmpfs-size":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
+ case "src", "source":
+ return newMount, errors.Errorf("source is not supported with tmpfs mounts")
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ return newMount, nil
+}
+
+// Parse a single volume mount entry from the --mount flag.
+// Note that the volume-label option for named volumes is currently NOT supported.
+// TODO: add support for --volume-label
+func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { //nolint
+ newVolume := new(libpod.ContainerNamedVolume)
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "rw":
+ if setRORW {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "volume-label":
+ return nil, errors.Errorf("the --volume-label option is not presently implemented")
+ case "src", "source":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ newVolume.Name = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ newVolume.Dest = filepath.Clean(kv[1])
+ setDest = true
+ default:
+ return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setSource {
+ return nil, errors.Errorf("must set source volume")
+ }
+ if !setDest {
+ return nil, noDestError
+ }
+
+ return newVolume, nil
+}
+
+func getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
+
+ for _, vol := range vols {
+ var (
+ options []string
+ src string
+ dest string
+ err error
+ )
+
+ splitVol := strings.Split(vol, ":")
+ if len(splitVol) > 3 {
+ return nil, nil, errors.Wrapf(volumeFormatErr, vol)
+ }
+
+ src = splitVol[0]
+ if len(splitVol) == 1 {
+ // This is an anonymous named volume. Only thing given
+ // is destination.
+ // Name/source will be blank, and populated by libpod.
+ src = ""
+ dest = splitVol[0]
+ } else if len(splitVol) > 1 {
+ dest = splitVol[1]
+ }
+ if len(splitVol) > 2 {
+ if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Do not check source dir for anonymous volumes
+ if len(splitVol) > 1 {
+ if err := parse.ValidateVolumeHostDir(src); err != nil {
+ return nil, nil, err
+ }
+ }
+ if err := parse.ValidateVolumeCtrDir(dest); err != nil {
+ return nil, nil, err
+ }
+
+ cleanDest := filepath.Clean(dest)
+
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ // This is not a named volume
+ newMount := spec.Mount{
+ Destination: cleanDest,
+ Type: string(TypeBind),
+ Source: src,
+ Options: options,
+ }
+ if _, ok := mounts[newMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
+ }
+ mounts[newMount.Destination] = newMount
+ } else {
+ // This is a named volume
+ newNamedVol := new(libpod.ContainerNamedVolume)
+ newNamedVol.Name = src
+ newNamedVol.Dest = cleanDest
+ newNamedVol.Options = options
+
+ if _, ok := volumes[newNamedVol.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
+ }
+ volumes[newNamedVol.Dest] = newNamedVol
+ }
+
+ logrus.Debugf("User mount %s:%s options %v", src, dest, options)
+ }
+
+ return mounts, volumes, nil
+}
+
+// Get mounts for container's image volumes
+// TODO deferred baude
+func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ //mounts := make(map[string]spec.Mount)
+ //volumes := make(map[string]*define.ContainerNamedVolume)
+ //
+ //if config.ImageVolumeType == "ignore" {
+ // return mounts, volumes, nil
+ //}
+ //
+ //for vol := range config.BuiltinImgVolumes {
+ // cleanDest := filepath.Clean(vol)
+ // logrus.Debugf("Adding image volume at %s", cleanDest)
+ // if config.ImageVolumeType == "tmpfs" {
+ // // Tmpfs image volumes are handled as mounts
+ // mount := spec.Mount{
+ // Destination: cleanDest,
+ // Source: TypeTmpfs,
+ // Type: TypeTmpfs,
+ // Options: []string{"rprivate", "rw", "nodev", "exec"},
+ // }
+ // mounts[cleanDest] = mount
+ // } else {
+ // // Anonymous volumes have no name.
+ // namedVolume := new(define.ContainerNamedVolume)
+ // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"}
+ // namedVolume.Dest = cleanDest
+ // volumes[cleanDest] = namedVolume
+ // }
+ //}
+ //
+ //return mounts, volumes, nil
+ return nil, nil, nil
+}
+
+// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
+func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint
+ m := make(map[string]spec.Mount)
+ for _, i := range mounts {
+ // Default options if nothing passed
+ var options []string
+ spliti := strings.Split(i, ":")
+ destPath := spliti[0]
+ if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil {
+ return nil, err
+ }
+ if len(spliti) > 1 {
+ options = strings.Split(spliti[1], ",")
+ }
+
+ if _, ok := m[destPath]; ok {
+ return nil, errors.Wrapf(errDuplicateDest, destPath)
+ }
+
+ mount := spec.Mount{
+ Destination: filepath.Clean(destPath),
+ Type: string(TypeTmpfs),
+ Options: options,
+ Source: string(TypeTmpfs),
+ }
+ m[destPath] = mount
+ }
+ return m, nil
+}
+
+// AddContainerInitBinary adds the init binary specified by path iff the
+// container will run in a private PID namespace that is not shared with the
+// host or another pre-existing container, where an init-like process is
+// already running.
+//
+// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
+// to execute the bind-mounted binary as PID 1.
+// TODO this needs to be worked on to work in new env
+func addContainerInitBinary(path string) (spec.Mount, error) { //nolint
+ mount := spec.Mount{
+ Destination: "/dev/init",
+ Type: TypeBind,
+ Source: path,
+ Options: []string{TypeBind, "ro"},
+ }
+
+ //if path == "" {
+ // return mount, fmt.Errorf("please specify a path to the container-init binary")
+ //}
+ //if !config.Pid.PidMode.IsPrivate() {
+ // return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
+ //}
+ //if config.Systemd {
+ // return mount, fmt.Errorf("cannot use container-init binary with systemd")
+ //}
+ //if _, err := os.Stat(path); os.IsNotExist(err) {
+ // return mount, errors.Wrap(err, "container-init binary not found on the host")
+ //}
+ //config.Command = append([]string{"/dev/init", "--"}, config.Command...)
+ return mount, nil
+}
+
+// Supersede existing mounts in the spec with new, user-specified mounts.
+// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
+// one mount, and we already have /tmp/a and /tmp/b, should we remove
+// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
+func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
+ if len(mounts) > 0 {
+ // If we have overlappings mounts, remove them from the spec in favor of
+ // the user-added volume mounts
+ destinations := make(map[string]bool)
+ for _, mount := range mounts {
+ destinations[path.Clean(mount.Destination)] = true
+ }
+ // Copy all mounts from spec to defaultMounts, except for
+ // - mounts overridden by a user supplied mount;
+ // - all mounts under /dev if a user supplied /dev is present;
+ mountDev := destinations["/dev"]
+ for _, mount := range configMount {
+ if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
+ if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
+ // filter out everything under /dev if /dev is user-mounted
+ continue
+ }
+
+ logrus.Debugf("Adding mount %s", mount.Destination)
+ mounts = append(mounts, mount)
+ }
+ }
+ return mounts
+ }
+ return configMount
+}
+
+func InitFSMounts(mounts []spec.Mount) error {
+ for i, m := range mounts {
+ switch {
+ case m.Type == TypeBind:
+ opts, err := util.ProcessOptions(m.Options, false, m.Source)
+ if err != nil {
+ return err
+ }
+ mounts[i].Options = opts
+ case m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev":
+ opts, err := util.ProcessOptions(m.Options, true, "")
+ if err != nil {
+ return err
+ }
+ mounts[i].Options = opts
+ }
+ }
+ return nil
+}