summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorBrent Baude <bbaude@redhat.com>2020-03-29 11:25:56 -0500
committerBrent Baude <bbaude@redhat.com>2020-04-03 15:43:03 -0500
commit6514a5c80ef91ef6e16e283339cd0b5f78a42322 (patch)
tree33ced51adee58d38b39416191e2e8a207ce4ec47 /cmd
parent35f586783388cdff6b4f15e7aff4df1ee72d9b67 (diff)
downloadpodman-6514a5c80ef91ef6e16e283339cd0b5f78a42322.tar.gz
podman-6514a5c80ef91ef6e16e283339cd0b5f78a42322.tar.bz2
podman-6514a5c80ef91ef6e16e283339cd0b5f78a42322.zip
v2podman container create
create a container in podmanv2 using specgen approach. this is the core implementation and still has quite a bit of code commented out specifically around volumes, devices, and namespaces. need contributions from smes on these parts. Signed-off-by: Brent Baude <bbaude@redhat.com>
Diffstat (limited to 'cmd')
-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
11 files changed, 1735 insertions, 46 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