diff options
Diffstat (limited to 'cmd/podman/common')
-rw-r--r-- | cmd/podman/common/create.go | 56 | ||||
-rw-r--r-- | cmd/podman/common/create_opts.go | 4 | ||||
-rw-r--r-- | cmd/podman/common/createparse.go | 21 | ||||
-rw-r--r-- | cmd/podman/common/default.go | 121 | ||||
-rw-r--r-- | cmd/podman/common/netflags.go | 107 | ||||
-rw-r--r-- | cmd/podman/common/ports.go | 112 | ||||
-rw-r--r-- | cmd/podman/common/specgen.go | 464 | ||||
-rw-r--r-- | cmd/podman/common/volumes.go | 569 |
8 files changed, 983 insertions, 471 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 49a40dfa0..a0aed984c 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -49,14 +49,15 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "cap-drop", []string{}, "Drop capabilities from the container", ) + cgroupNS := "" createFlags.StringVar( - &cf.CGroupsNS, - "cgroupns", getDefaultCgroupNS(), + &cgroupNS, + "cgroupns", containerConfig.CgroupNS(), "cgroup namespace to use", ) createFlags.StringVar( - &cf.CGroups, - "cgroups", "enabled", + &cf.CGroupsMode, + "cgroups", containerConfig.Cgroups(), `control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`, ) createFlags.StringVar( @@ -121,12 +122,12 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringVar( &cf.DetachKeys, - "detach-keys", GetDefaultDetachKeys(), + "detach-keys", containerConfig.DetachKeys(), "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(), + &cf.Devices, + "device", containerConfig.Devices(), fmt.Sprintf("Add a host device to the container"), ) createFlags.StringSliceVar( @@ -161,7 +162,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringArrayVarP( &cf.env, - "env", "e", getDefaultEnv(), + "env", "e", containerConfig.Env(), "Set environment variables in container", ) createFlags.BoolVar( @@ -238,7 +239,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringVar( &cf.InitPath, - "init-path", getDefaultInitPath(), + "init-path", containerConfig.InitPath(), // 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"), ) @@ -247,9 +248,10 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "interactive", "i", false, "Keep STDIN open even if not attached", ) + ipcNS := "" createFlags.StringVar( - &cf.IPC, - "ipc", getDefaultIPCNS(), + &ipcNS, + "ipc", containerConfig.IPCNS(), "IPC namespace to use", ) createFlags.StringVar( @@ -329,15 +331,16 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "use `OS` instead of the running OS for choosing images", ) // markFlagHidden(createFlags, "override-os") + pid := "" createFlags.StringVar( - &cf.PID, - "pid", getDefaultPidNS(), + &pid, + "pid", containerConfig.PidNS(), "PID namespace to use", ) createFlags.Int64Var( &cf.PIDsLimit, - "pids-limit", getDefaultPidsLimit(), - getDefaultPidsDescription(), + "pids-limit", containerConfig.PidsLimit(), + "Tune container pids limit (set 0 for unlimited, -1 for server defaults)", ) createFlags.StringVar( &cf.Pod, @@ -391,12 +394,13 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringArrayVar( &cf.SecurityOpt, - "security-opt", getDefaultSecurityOptions(), + "security-opt", containerConfig.SecurityOptions(), "Security Options", ) + shmSize := "" createFlags.StringVar( - &cf.ShmSize, - "shm-size", getDefaultShmSize(), + &shmSize, + "shm-size", containerConfig.ShmSize(), "Size of /dev/shm "+sizeWithUnitFormat, ) createFlags.StringVar( @@ -427,7 +431,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { createFlags.StringSliceVar( &cf.Sysctl, - "sysctl", getDefaultSysctls(), + "sysctl", containerConfig.Sysctls(), "Sysctl options", ) createFlags.StringVar( @@ -452,7 +456,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringSliceVar( &cf.Ulimit, - "ulimit", getDefaultUlimits(), + "ulimit", containerConfig.Ulimits(), "Ulimit options", ) createFlags.StringVarP( @@ -460,14 +464,16 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])", ) + userNS := "" createFlags.StringVar( - &cf.UserNS, - "userns", getDefaultUserNS(), + &userNS, + "userns", containerConfig.Containers.UserNS, "User namespace to use", ) + utsNS := "" createFlags.StringVar( - &cf.UTS, - "uts", getDefaultUTSNS(), + &utsNS, + "uts", containerConfig.Containers.UTSNS, "UTS namespace to use", ) createFlags.StringArrayVar( @@ -477,7 +483,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringArrayVarP( &cf.Volume, - "volume", "v", getDefaultVolumes(), + "volume", "v", containerConfig.Volumes(), "Bind mount a volume into the container", ) createFlags.StringSliceVar( diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 9d12e4b26..2f08bb6a6 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -11,7 +11,7 @@ type ContainerCLIOpts struct { CapAdd []string CapDrop []string CGroupsNS string - CGroups string + CGroupsMode string CGroupParent string CIDFile string ConmonPIDFile string @@ -25,7 +25,7 @@ type ContainerCLIOpts struct { CPUSetMems string Detach bool DetachKeys string - Device []string + Devices []string DeviceCGroupRule []string DeviceReadBPs []string DeviceReadIOPs []string diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go index aca6f752e..fe6e322c2 100644 --- a/cmd/podman/common/createparse.go +++ b/cmd/podman/common/createparse.go @@ -1,7 +1,6 @@ package common import ( - "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) @@ -17,27 +16,7 @@ func (c *ContainerCLIOpts) validate() error { 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": "", diff --git a/cmd/podman/common/default.go b/cmd/podman/common/default.go index 853f87ab6..7233b2091 100644 --- a/cmd/podman/common/default.go +++ b/cmd/podman/common/default.go @@ -1,16 +1,7 @@ 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/specgen" - "github.com/containers/libpod/pkg/sysinfo" - "github.com/opencontainers/selinux/go-selinux" + "github.com/containers/libpod/cmd/podman/registry" ) var ( @@ -24,112 +15,6 @@ var ( DefaultHealthCheckTimeout = "30s" // DefaultImageVolume default value DefaultImageVolume = "bind" + // Pull in configured json library + json = registry.JsonLibrary() ) - -// 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 containerConfig.Containers.SeccompProfile != "" && containerConfig.Containers.SeccompProfile != parse.SeccompDefaultPath { - securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", containerConfig.Containers.SeccompProfile)) - } - if apparmor.IsEnabled() && containerConfig.Containers.ApparmorProfile != "" { - securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", containerConfig.Containers.ApparmorProfile)) - } - if selinux.GetEnabled() && !containerConfig.Containers.EnableLabeling { - securityOpts = append(securityOpts, fmt.Sprintf("label=%s", selinux.DisableSecOpt()[0])) - } - return securityOpts -} - -// getDefaultSysctls -func getDefaultSysctls() []string { - return containerConfig.Containers.DefaultSysctls -} - -func getDefaultVolumes() []string { - return containerConfig.Containers.Volumes -} - -func getDefaultDevices() []string { - return containerConfig.Containers.Devices -} - -func getDefaultDNSServers() []string { //nolint - return containerConfig.Containers.DNSServers -} - -func getDefaultDNSSearches() []string { //nolint - return containerConfig.Containers.DNSSearches -} - -func getDefaultDNSOptions() []string { //nolint - return containerConfig.Containers.DNSOptions -} - -func getDefaultEnv() []string { - return containerConfig.Containers.Env -} - -func getDefaultInitPath() string { - return containerConfig.Containers.InitPath -} - -func getDefaultIPCNS() string { - return containerConfig.Containers.IPCNS -} - -func getDefaultPidNS() string { - return containerConfig.Containers.PidNS -} - -func getDefaultNetNS() string { //nolint - if containerConfig.Containers.NetNS == string(specgen.Private) && rootless.IsRootless() { - return string(specgen.Slirp) - } - return containerConfig.Containers.NetNS -} - -func getDefaultCgroupNS() string { - return containerConfig.Containers.CgroupNS -} - -func getDefaultUTSNS() string { - return containerConfig.Containers.UTSNS -} - -func getDefaultShmSize() string { - return containerConfig.Containers.ShmSize -} - -func getDefaultUlimits() []string { - return containerConfig.Containers.DefaultUlimits -} - -func getDefaultUserNS() string { - userns := os.Getenv("PODMAN_USERNS") - if userns != "" { - return userns - } - return containerConfig.Containers.UserNS -} - -func getDefaultPidsLimit() int64 { - if rootless.IsRootless() { - cgroup2, _ := cgroups.IsCgroup2UnifiedMode() - if cgroup2 { - return containerConfig.Containers.PidsLimit - } - } - return sysinfo.GetDefaultPidsLimit() -} - -func getDefaultPidsDescription() string { - return "Tune container pids limit (set 0 for unlimited)" -} - -func GetDefaultDetachKeys() string { - return containerConfig.Engine.DetachKeys -} diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index 41eed2988..2bb45476b 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -3,7 +3,11 @@ package common import ( "net" + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -15,15 +19,15 @@ func GetNetFlags() *pflag.FlagSet { "Add a custom host-to-IP mapping (host:ip) (default [])", ) netFlags.StringSlice( - "dns", getDefaultDNSServers(), + "dns", containerConfig.DNSServers(), "Set custom DNS servers", ) netFlags.StringSlice( - "dns-opt", getDefaultDNSOptions(), + "dns-opt", containerConfig.DNSOptions(), "Set custom DNS options", ) netFlags.StringSlice( - "dns-search", getDefaultDNSSearches(), + "dns-search", containerConfig.DNSSearches(), "Set custom DNS search domains", ) netFlags.String( @@ -35,7 +39,7 @@ func GetNetFlags() *pflag.FlagSet { "Container MAC address (e.g. 92:d0:c6:0a:29:33)", ) netFlags.String( - "network", getDefaultNetNS(), + "network", containerConfig.NetNS(), "Connect a container to a network", ) netFlags.StringSliceP( @@ -58,20 +62,60 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { if err != nil { return nil, err } - servers, err := cmd.Flags().GetStringSlice("dns") - if err != nil { - return nil, err + // Verify the additional hosts are in correct format + for _, host := range opts.AddHosts { + if _, err := parse.ValidateExtraHost(host); err != nil { + return nil, err + } } - for _, d := range servers { - if d == "none" { - opts.DNSHost = true - break + + if cmd.Flags().Changed("dns") { + servers, err := cmd.Flags().GetStringSlice("dns") + if err != nil { + return nil, err + } + for _, d := range servers { + if d == "none" { + opts.UseImageResolvConf = true + if len(servers) > 1 { + return nil, errors.Errorf("%s is not allowed to be specified with other DNS ip addresses", d) + } + break + } + dns := net.ParseIP(d) + if dns == nil { + return nil, errors.Errorf("%s is not an ip address", d) + } + opts.DNSServers = append(opts.DNSServers, dns) } - opts.DNSServers = append(opts.DNSServers, net.ParseIP(d)) } - opts.DNSSearch, err = cmd.Flags().GetStringSlice("dns-search") - if err != nil { - return nil, err + + if cmd.Flags().Changed("dns-opt") { + options, err := cmd.Flags().GetStringSlice("dns-opt") + if err != nil { + return nil, err + } + opts.DNSOptions = options + } + + if cmd.Flags().Changed("dns-search") { + dnsSearches, err := cmd.Flags().GetStringSlice("dns-search") + if err != nil { + return nil, err + } + // Validate domains are good + for _, dom := range dnsSearches { + if dom == "." { + if len(dnsSearches) > 1 { + return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") + } + continue + } + if _, err := parse.ValidateDomain(dom); err != nil { + return nil, err + } + } + opts.DNSSearch = dnsSearches } m, err := cmd.Flags().GetString("mac-address") @@ -85,6 +129,7 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { } opts.StaticMAC = &mac } + inputPorts, err := cmd.Flags().GetStringSlice("publish") if err != nil { return nil, err @@ -95,6 +140,38 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { return nil, err } } + + ip, err := cmd.Flags().GetString("ip") + if err != nil { + return nil, err + } + if ip != "" { + staticIP := net.ParseIP(ip) + if staticIP == nil { + return nil, errors.Errorf("%s is not an ip address", ip) + } + if staticIP.To4() == nil { + return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", ip) + } + opts.StaticIP = &staticIP + } + opts.NoHosts, err = cmd.Flags().GetBool("no-hosts") + + if cmd.Flags().Changed("network") { + network, err := cmd.Flags().GetString("network") + if err != nil { + return nil, err + } + + ns, cniNets, err := specgen.ParseNetworkNamespace(network) + if err != nil { + return nil, err + } + + opts.Network = ns + opts.CNINetworks = cniNets + } + return &opts, err } diff --git a/cmd/podman/common/ports.go b/cmd/podman/common/ports.go index 7e2b1e79d..a96bafabd 100644 --- a/cmd/podman/common/ports.go +++ b/cmd/podman/common/ports.go @@ -1,28 +1,11 @@ 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] = "" - //} - +func verifyExpose(expose []string) error { // add the expose ports from the user (--expose) // can be single or a range for _, expose := range expose { @@ -30,97 +13,10 @@ func ExposedPorts(expose []string, publish []ocicni.PortMapping, publishAll bool _, 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) + _, _, 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 errors.Wrapf(err, "invalid range format for --expose: %s", expose) } } - 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} + return nil } diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 85b344b3c..5d5816ea4 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -1,7 +1,6 @@ package common import ( - "encoding/json" "fmt" "os" "path/filepath" @@ -23,43 +22,135 @@ import ( "github.com/pkg/errors" ) -func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error { - var ( - err error - //namespaces map[string]string - ) +func getCPULimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxCPU, error) { + cpu := &specs.LinuxCPU{} + hasLimits := false - // validate flags as needed - if err := c.validate(); err != nil { - return nil + if c.CPUShares > 0 { + cpu.Shares = &c.CPUShares + hasLimits = true + } + if c.CPUPeriod > 0 { + cpu.Period = &c.CPUPeriod + hasLimits = true + } + if c.CPUSetCPUs != "" { + cpu.Cpus = c.CPUSetCPUs + hasLimits = true + } + if c.CPUSetMems != "" { + cpu.Mems = c.CPUSetMems + hasLimits = true + } + if c.CPUQuota > 0 { + cpu.Quota = &c.CPUQuota + hasLimits = true + } + if c.CPURTPeriod > 0 { + cpu.RealtimePeriod = &c.CPURTPeriod + hasLimits = true + } + if c.CPURTRuntime > 0 { + cpu.RealtimeRuntime = &c.CPURTRuntime + hasLimits = true } - s.User = c.User - inputCommand := args[1:] - if len(c.HealthCmd) > 0 { - s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod) + if !hasLimits { + return nil, nil + } + return cpu, nil +} + +func getIOLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxBlockIO, error) { + var err error + io := &specs.LinuxBlockIO{} + hasLimits := false + if b := c.BlkIOWeight; len(b) > 0 { + u, err := strconv.ParseUint(b, 10, 16) if err != nil { - return err + return nil, errors.Wrapf(err, "invalid value for blkio-weight") } + nu := uint16(u) + io.Weight = &nu + hasLimits = true } - s.IDMappings, err = util.ParseIDMapping(ns.UsernsMode(c.UserNS), c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName) - if err != nil { - return err + if len(c.BlkIOWeightDevice) > 0 { + if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil { + return nil, err + } + hasLimits = true + } + + if bps := c.DeviceReadBPs; len(bps) > 0 { + if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { + return nil, err + } + hasLimits = true + } + + if bps := c.DeviceWriteBPs; len(bps) > 0 { + if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil { + return nil, err + } + hasLimits = true + } + + if iops := c.DeviceReadIOPs; len(iops) > 0 { + if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { + return nil, err + } + hasLimits = true + } + + if iops := c.DeviceWriteIOPs; len(iops) > 0 { + if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil { + return nil, err + } + hasLimits = true + } + + if !hasLimits { + return nil, nil + } + return io, nil +} + +func getPidsLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxPids, error) { + pids := &specs.LinuxPids{} + hasLimits := false + if c.CGroupsMode == "disabled" && c.PIDsLimit > 0 { + return nil, nil + } + if c.PIDsLimit > 0 { + pids.Limit = c.PIDsLimit + hasLimits = true + } + if !hasLimits { + return nil, nil } + return pids, nil +} + +func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxMemory, error) { + var err error + memory := &specs.LinuxMemory{} + hasLimits := false if m := c.Memory; len(m) > 0 { ml, err := units.RAMInBytes(m) if err != nil { - return errors.Wrapf(err, "invalid value for memory") + return nil, errors.Wrapf(err, "invalid value for memory") } - s.ResourceLimits.Memory.Limit = &ml + memory.Limit = &ml + hasLimits = true } if m := c.MemoryReservation; len(m) > 0 { mr, err := units.RAMInBytes(m) if err != nil { - return errors.Wrapf(err, "invalid value for memory") + return nil, errors.Wrapf(err, "invalid value for memory") } - s.ResourceLimits.Memory.Reservation = &mr + memory.Reservation = &mr + hasLimits = true } if m := c.MemorySwap; len(m) > 0 { var ms int64 @@ -69,99 +160,106 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } else { ms, err = units.RAMInBytes(m) if err != nil { - return errors.Wrapf(err, "invalid value for memory") + return nil, errors.Wrapf(err, "invalid value for memory") } } - s.ResourceLimits.Memory.Swap = &ms + memory.Swap = &ms + hasLimits = true } if m := c.KernelMemory; len(m) > 0 { mk, err := units.RAMInBytes(m) if err != nil { - return errors.Wrapf(err, "invalid value for kernel-memory") + return nil, errors.Wrapf(err, "invalid value for kernel-memory") } - s.ResourceLimits.Memory.Kernel = &mk + memory.Kernel = &mk + hasLimits = true } - if b := c.BlkIOWeight; len(b) > 0 { - u, err := strconv.ParseUint(b, 10, 16) + if c.MemorySwappiness >= 0 { + swappiness := uint64(c.MemorySwappiness) + memory.Swappiness = &swappiness + hasLimits = true + } + if c.OOMKillDisable { + memory.DisableOOMKiller = &c.OOMKillDisable + hasLimits = true + } + if !hasLimits { + return nil, nil + } + return memory, nil +} + +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 + } + + s.User = c.User + inputCommand := args[1:] + if len(c.HealthCmd) > 0 { + if c.NoHealthCheck { + return errors.New("Cannot specify both --no-healthcheck and --health-cmd") + } + s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod) if err != nil { - return errors.Wrapf(err, "invalid value for blkio-weight") + return err + } + } else if c.NoHealthCheck { + s.HealthConfig = &manifest.Schema2HealthConfig{ + Test: []string{"NONE"}, } - nu := uint16(u) - s.ResourceLimits.BlockIO.Weight = &nu } - s.Terminal = c.TTY - ep, err := ExposedPorts(c.Expose, c.Net.PublishPorts, c.PublishAll, nil) + userNS := ns.UsernsMode(c.UserNS) + s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName) if err != nil { return err } - s.PortMappings = ep - s.Pod = c.Pod + // If some mappings are specified, assume a private user namespace + if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) { + s.UserNS.NSMode = specgen.Private + } - //s.CgroupNS = specgen.Namespace{ - // NSMode: , - // Value: "", - //} + s.Terminal = c.TTY - //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") - // } - //} + if err := verifyExpose(c.Expose); err != nil { + return err + } + // We are not handling the Expose flag yet. + // s.PortsExpose = c.Expose + s.PortMappings = c.Net.PublishPorts + s.PublishImagePorts = c.PublishAll + s.Pod = c.Pod - // 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 - // } - //} + for k, v := range map[string]*specgen.Namespace{ + c.IPC: &s.IpcNS, + c.PID: &s.PidNS, + c.UTS: &s.UtsNS, + c.CGroupsNS: &s.CgroupNS, + } { + if k != "" { + *v, err = specgen.ParseNamespace(k) + if err != nil { + return err + } + } + } + // userns must be treated differently + if c.UserNS != "" { + s.UserNS, err = specgen.ParseUserNamespace(c.UserNS) + if err != nil { + return err + } + } + if c.Net != nil { + s.NetNS = c.Net.Network + } // STOP SIGNAL signalString := "TERM" @@ -191,7 +289,23 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string if c.EnvHost { env = envLib.Join(env, osEnv) + } else if c.HTTPProxy { + for _, envSpec := range []string{ + "http_proxy", + "HTTP_PROXY", + "https_proxy", + "HTTPS_PROXY", + "ftp_proxy", + "FTP_PROXY", + "no_proxy", + "NO_PROXY", + } { + if v, ok := osEnv[envSpec]; ok { + env[envSpec] = v + } + } } + // env-file overrides any previous variables for _, f := range c.EnvFile { fileEnv, err := envLib.ParseFile(f) @@ -259,6 +373,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string var command []string + s.Entrypoint = entrypoint + // Build the command // If we have an entry point, it goes first if len(entrypoint) > 0 { @@ -277,23 +393,27 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } // SHM Size - shmSize, err := units.FromHumanSize(c.ShmSize) - if err != nil { - return errors.Wrapf(err, "unable to translate --shm-size") + if c.ShmSize != "" { + shmSize, err := units.FromHumanSize(c.ShmSize) + if err != nil { + return errors.Wrapf(err, "unable to translate --shm-size") + } + s.ShmSize = &shmSize } - s.ShmSize = &shmSize s.HostAdd = c.Net.AddHosts - s.DNSServer = c.Net.DNSServers + s.UseImageResolvConf = c.Net.UseImageResolvConf + s.DNSServers = 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.DNSOptions = c.Net.DNSOptions + s.StaticIP = c.Net.StaticIP + s.StaticMAC = c.Net.StaticMAC + s.UseImageHosts = c.Net.NoHosts s.ImageVolumeMode = c.ImageVolume + if s.ImageVolumeMode == "bind" { + s.ImageVolumeMode = "anonymous" + } + systemd := c.SystemdD == "always" if !systemd && command != nil { x, err := strconv.ParseBool(c.SystemdD) @@ -313,14 +433,28 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string 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, err = getMemoryLimits(s, c, args) + if err != nil { + return err + } + s.ResourceLimits.BlockIO, err = getIOLimits(s, c, args) + if err != nil { + return err + } + s.ResourceLimits.Pids, err = getPidsLimits(s, c, args) + if err != nil { + return err + } + s.ResourceLimits.CPU, err = getCPULimits(s, c, args) + if err != nil { + return err + } + if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil { + s.ResourceLimits = nil } - s.ResourceLimits.Memory.Swappiness = &swappiness if s.LogConfiguration == nil { s.LogConfiguration = &specgen.LogConfig{} @@ -329,19 +463,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string 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 - } + s.CgroupParent = c.CGroupParent + s.CgroupsMode = c.CGroupsMode + s.Groups = c.GroupAdd // TODO WTF //cgroup := &cc.CgroupConfig{ - // Cgroups: c.String("cgroups"), // Cgroupns: c.String("cgroupns"), - // CgroupParent: c.String("cgroup-parent"), - // CgroupMode: cgroupMode, //} // //userns := &cc.UserConfig{ @@ -358,6 +485,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // Hostname: c.String("hostname"), //} + s.Hostname = c.Hostname sysctl := map[string]string{} if ctl := c.Sysctl; len(ctl) > 0 { sysctl, err = util.ValidateSysctls(ctl) @@ -411,19 +539,20 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } } - // 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.SeccompPolicy = c.SeccompPolicy - //s.Mounts = c.Mount + // TODO: should parse out options s.VolumesFrom = c.VolumesFrom + // Only add read-only tmpfs mounts in case that we are read-only and the + // read-only tmpfs flag has been set. + mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, (c.ReadOnlyTmpFS && c.ReadOnly)) + if err != nil { + return err + } + s.Mounts = mounts + s.Volumes = volumes + // TODO any idea why this was done //devices := rtc.Containers.Devices // TODO conflict on populate? @@ -432,6 +561,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // devices = append(devices, c.StringSlice("device")...) //} + for _, dev := range c.Devices { + s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev}) + } + // TODO things i cannot find in spec // we dont think these are in the spec // init - initbinary @@ -440,32 +573,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // 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" { @@ -495,30 +602,18 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string if len(split) < 2 { return errors.Errorf("invalid log option %q", o) } - logOpts[split[0]] = split[1] + switch { + case split[0] == "driver": + s.LogConfiguration.Driver = split[1] + case split[0] == "path": + s.LogConfiguration.Path = split[1] + default: + 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 @@ -537,10 +632,15 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start // 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} + + if inCmd == "none" { + cmd = []string{"NONE"} + } else { + 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, diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go new file mode 100644 index 000000000..6b0b6e9cf --- /dev/null +++ b/cmd/podman/common/volumes.go @@ -0,0 +1,569 @@ +package common + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/libpod/pkg/specgen" + "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") + optionArgError = errors.Errorf("must provide an argument for option") + noDestError = errors.Errorf("must set volume destination") +) + +// Parse all volume-related options in the create config into a set of mounts +// and named volumes to add to the container. +// Handles --volumes, --mount, and --tmpfs flags. +// Does not handle image volumes, init, and --volumes-from flags. +// Can also add tmpfs mounts from read-only tmpfs. +// TODO: handle options parsing/processing via containers/storage/pkg/mount +func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, error) { + // Get mounts from the --mounts flag. + unifiedMounts, unifiedVolumes, err := getMounts(mountFlag) + if err != nil { + return nil, nil, err + } + + // Next --volumes flag. + volumeMounts, volumeVolumes, err := getVolumeMounts(volumeFlag) + if err != nil { + return nil, nil, err + } + + // Next --tmpfs flag. + tmpfsMounts, err := getTmpfsMounts(tmpfsFlag) + if err != nil { + return nil, nil, err + } + + // Unify mounts from --mount, --volume, --tmpfs. + // 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 + } + + // If requested, add tmpfs filesystems for read-only containers. + if addReadOnlyTmpfs { + readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} + options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} + for _, dest := range readonlyTmpfs { + if _, ok := unifiedMounts[dest]; ok { + continue + } + if _, ok := unifiedVolumes[dest]; ok { + continue + } + localOpts := options + if dest == "/run" { + localOpts = append(localOpts, "noexec", "size=65536k") + } else { + localOpts = append(localOpts, "exec") + } + unifiedMounts[dest] = spec.Mount{ + Destination: dest, + Type: TypeTmpfs, + Source: "tmpfs", + Options: localOpts, + } + } + } + + // Check for conflicts between named volumes and mounts + for dest := range unifiedMounts { + if _, ok := unifiedVolumes[dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + } + } + for dest := range unifiedVolumes { + if _, ok := unifiedMounts[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(unifiedMounts)) + for _, mount := range unifiedMounts { + 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([]*specgen.NamedVolume, 0, len(unifiedVolumes)) + for _, volume := range unifiedVolumes { + finalVolumes = append(finalVolumes, volume) + } + + return finalMounts, finalVolumes, 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(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { + finalMounts := make(map[string]spec.Mount) + finalNamedVolumes := make(map[string]*specgen.NamedVolume) + + 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 mountFlag { + 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) { + 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) { + 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) (*specgen.NamedVolume, error) { + newVolume := new(specgen.NamedVolume) + + 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(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { + mounts := make(map[string]spec.Mount) + volumes := make(map[string]*specgen.NamedVolume) + + volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") + + for _, vol := range volumeFlag { + 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(specgen.NamedVolume) + 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 +} + +// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts +func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) { + m := make(map[string]spec.Mount) + for _, i := range tmpfsFlag { + // 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 +} |