diff options
Diffstat (limited to 'pkg/spec')
-rw-r--r-- | pkg/spec/config_linux.go | 64 | ||||
-rw-r--r-- | pkg/spec/config_linux_cgo.go | 34 | ||||
-rw-r--r-- | pkg/spec/config_linux_nocgo.go | 11 | ||||
-rw-r--r-- | pkg/spec/containerconfig.go | 5 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 60 | ||||
-rw-r--r-- | pkg/spec/parse.go | 6 | ||||
-rw-r--r-- | pkg/spec/spec.go | 143 | ||||
-rw-r--r-- | pkg/spec/storage.go | 56 |
8 files changed, 307 insertions, 72 deletions
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go index eb2acf984..60d31d78e 100644 --- a/pkg/spec/config_linux.go +++ b/pkg/spec/config_linux.go @@ -4,12 +4,11 @@ package createconfig import ( "fmt" - "io/ioutil" "os" "path/filepath" "strings" - "github.com/docker/docker/profiles/seccomp" + "github.com/containers/libpod/pkg/rootless" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/devices" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -120,37 +119,50 @@ func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error { return err } g.ClearLinuxDevices() - for _, d := range hostDevices { - g.AddDevice(Device(d)) - } - - // Add resources device - need to clear the existing one first. - g.Spec().Linux.Resources.Devices = nil - g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") - return nil -} - -func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { - var seccompConfig *spec.LinuxSeccomp - var err error - if config.SeccompProfilePath != "" { - seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) - if err != nil { - return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath) + if rootless.IsRootless() { + mounts := make(map[string]interface{}) + for _, m := range g.Mounts() { + mounts[m.Destination] = true } - seccompConfig, err = seccomp.LoadProfile(string(seccompProfile), configSpec) - if err != nil { - return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) + newMounts := []spec.Mount{} + for _, d := range hostDevices { + devMnt := spec.Mount{ + Destination: d.Path, + Type: TypeBind, + Source: d.Path, + Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, + } + if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { + continue + } + if _, found := mounts[d.Path]; found { + continue + } + st, err := os.Stat(d.Path) + if err != nil { + if err == unix.EPERM { + continue + } + return errors.Wrapf(err, "stat %s", d.Path) + } + // Skip devices that the user has not access to. + if st.Mode()&0007 == 0 { + continue + } + newMounts = append(newMounts, devMnt) } + g.Config.Mounts = append(newMounts, g.Config.Mounts...) } else { - seccompConfig, err = seccomp.GetDefaultProfile(configSpec) - if err != nil { - return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) + for _, d := range hostDevices { + g.AddDevice(Device(d)) } } - return seccompConfig, nil + // Add resources device - need to clear the existing one first. + g.Config.Linux.Resources.Devices = nil + g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") + return nil } func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) { diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go new file mode 100644 index 000000000..e6e92a7cc --- /dev/null +++ b/pkg/spec/config_linux_cgo.go @@ -0,0 +1,34 @@ +// +build linux,cgo + +package createconfig + +import ( + "io/ioutil" + + "github.com/docker/docker/profiles/seccomp" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" +) + +func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { + var seccompConfig *spec.LinuxSeccomp + var err error + + if config.SeccompProfilePath != "" { + seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) + if err != nil { + return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath) + } + seccompConfig, err = seccomp.LoadProfile(string(seccompProfile), configSpec) + if err != nil { + return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) + } + } else { + seccompConfig, err = seccomp.GetDefaultProfile(configSpec) + if err != nil { + return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) + } + } + + return seccompConfig, nil +} diff --git a/pkg/spec/config_linux_nocgo.go b/pkg/spec/config_linux_nocgo.go new file mode 100644 index 000000000..10329ff3b --- /dev/null +++ b/pkg/spec/config_linux_nocgo.go @@ -0,0 +1,11 @@ +// +build linux,!cgo + +package createconfig + +import ( + spec "github.com/opencontainers/runtime-spec/specs-go" +) + +func getSeccompConfig(config *CreateConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { + return nil, nil +} diff --git a/pkg/spec/containerconfig.go b/pkg/spec/containerconfig.go index b2f8a268f..ae6420117 100644 --- a/pkg/spec/containerconfig.go +++ b/pkg/spec/containerconfig.go @@ -2,6 +2,7 @@ package createconfig import ( "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -11,9 +12,9 @@ import ( // container with libpod from a completed CreateConfig struct. func (config *CreateConfig) MakeContainerConfig(runtime *libpod.Runtime, pod *libpod.Pod) (*spec.Spec, []libpod.CtrCreateOption, error) { if config.Pod != "" && pod == nil { - return nil, nil, errors.Wrapf(libpod.ErrInvalidArg, "pod was specified but no pod passed") + return nil, nil, errors.Wrapf(define.ErrInvalidArg, "pod was specified but no pod passed") } else if config.Pod == "" && pod != nil { - return nil, nil, errors.Wrapf(libpod.ErrInvalidArg, "pod was given but no pod is specified") + return nil, nil, errors.Wrapf(define.ErrInvalidArg, "pod was given but no pod is specified") } // Parse volumes flag into OCI spec mounts and libpod Named Volumes. diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index e4501aaac..214a3c5ed 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -9,6 +9,7 @@ import ( "github.com/containers/image/manifest" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" @@ -62,6 +63,7 @@ type CreateConfig struct { CapDrop []string // cap-drop CidFile string ConmonPidFile string + Cgroupns string CgroupParent string // cgroup-parent Command []string Detach bool // detach @@ -100,6 +102,7 @@ type CreateConfig struct { NetworkAlias []string //network-alias PidMode namespaces.PidMode //pid Pod string //pod + CgroupMode namespaces.CgroupMode //cgroup PortBindings nat.PortMap Privileged bool //privileged Publish []string //publish @@ -162,6 +165,10 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err if config.StorageConfig.GraphDriverName != "" { command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) } + for _, opt := range config.StorageConfig.GraphDriverOptions { + command = append(command, []string{"--storage-opt", opt}...) + } + if c.Syslog { command = append(command, "--syslog", "true") } @@ -259,10 +266,48 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l } options = append(options, libpod.WithNetNSFrom(connectedCtr)) } else if !c.NetMode.IsHost() && !c.NetMode.IsNone() { - postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost() + hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 + postConfigureNetNS := c.NetMode.IsSlirp4netns() || (hasUserns && !c.UsernsMode.IsHost()) options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) } + if c.CgroupMode.IsNS() { + ns := c.CgroupMode.NS() + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined network namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + } else if c.CgroupMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.CgroupMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.CgroupMode.Container()) + } + options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) + } + + if c.UsernsMode.IsNS() { + ns := c.UsernsMode.NS() + if ns == "" { + return nil, errors.Errorf("invalid empty user-defined user namespace") + } + _, err := os.Stat(ns) + if err != nil { + return nil, err + } + options = append(options, libpod.WithIDMappings(*c.IDMappings)) + } else if c.UsernsMode.IsContainer() { + connectedCtr, err := runtime.LookupContainer(c.UsernsMode.Container()) + if err != nil { + return nil, errors.Wrapf(err, "container %q not found", c.UsernsMode.Container()) + } + options = append(options, libpod.WithUserNSFrom(connectedCtr)) + } else { + options = append(options, libpod.WithIDMappings(*c.IDMappings)) + } + if c.PidMode.IsContainer() { connectedCtr, err := runtime.LookupContainer(c.PidMode.Container()) if err != nil { @@ -320,14 +365,16 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithLogPath(logPath)) } - options = append(options, libpod.WithLogDriver(c.LogDriver)) + if c.LogDriver != "" { + options = append(options, libpod.WithLogDriver(c.LogDriver)) + } if c.IPAddress != "" { ip := net.ParseIP(c.IPAddress) if ip == nil { - return nil, errors.Wrapf(libpod.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress) + return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress) } else if ip.To4() == nil { - return nil, errors.Wrapf(libpod.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress) + return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress) } options = append(options, libpod.WithStaticIP(ip)) } @@ -353,7 +400,6 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l } options = append(options, libpod.WithShmSize(c.Resources.ShmSize)) options = append(options, libpod.WithGroups(c.GroupAdd)) - options = append(options, libpod.WithIDMappings(*c.IDMappings)) if c.Rootfs != "" { options = append(options, libpod.WithRootFS(c.Rootfs)) } @@ -365,7 +411,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l if c.RestartPolicy != "" { if c.RestartPolicy == "unless-stopped" { - return nil, errors.Wrapf(libpod.ErrInvalidArg, "the unless-stopped restart policy is not supported") + return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") } split := strings.Split(c.RestartPolicy, ":") @@ -375,7 +421,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l return nil, errors.Wrapf(err, "%s is not a valid number of retries for restart policy", split[1]) } if numTries < 0 { - return nil, errors.Wrapf(libpod.ErrInvalidArg, "restart policy requires a positive number of retries") + return nil, errors.Wrapf(define.ErrInvalidArg, "restart policy requires a positive number of retries") } options = append(options, libpod.WithRestartRetries(uint(numTries))) } diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go index d688b8d1b..c2572a033 100644 --- a/pkg/spec/parse.go +++ b/pkg/spec/parse.go @@ -126,13 +126,9 @@ func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint 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) } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) - } - return &throttleDevice{ path: split[0], - rate: uint64(rate), + rate: rate, }, nil } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 0d953ff6f..15c8c77fa 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/util" pmount "github.com/containers/storage/pkg/mount" "github.com/docker/docker/oci/caps" "github.com/docker/go-units" @@ -46,7 +46,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM canMountSys := true isRootless := rootless.IsRootless() - inUserNS := isRootless || (len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0) && !config.UsernsMode.IsHost() + hasUserns := config.UsernsMode.IsContainer() || config.UsernsMode.IsNS() || len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0 + inUserNS := isRootless || (hasUserns && !config.UsernsMode.IsHost()) if inUserNS && config.NetMode.IsHost() { canMountSys = false @@ -80,23 +81,41 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM g.AddLinuxMaskedPaths("/sys/kernel") } } + gid5Available := true if isRootless { nGids, err := getAvailableGids() if err != nil { return nil, err } - if nGids < 5 { - // If we have no GID mappings, the gid=5 default option would fail, so drop it. - g.RemoveMount("/dev/pts") - devPts := spec.Mount{ - Destination: "/dev/pts", - Type: "devpts", - Source: "devpts", - Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}, + gid5Available = nGids >= 5 + } + // When using a different user namespace, check that the GID 5 is mapped inside + // the container. + if gid5Available && len(config.IDMappings.GIDMap) > 0 { + mappingFound := false + for _, r := range config.IDMappings.GIDMap { + if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size { + mappingFound = true + break } - g.AddMount(devPts) } + if !mappingFound { + gid5Available = false + } + } + if !gid5Available { + // If we have no GID mappings, the gid=5 default option would fail, so drop it. + g.RemoveMount("/dev/pts") + devPts := spec.Mount{ + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}, + } + g.AddMount(devPts) + } + if inUserNS && config.IpcMode.IsHost() { g.RemoveMount("/dev/mqueue") devMqueue := spec.Mount{ @@ -246,10 +265,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM // If privileged, we need to add all the host devices to the // spec. We do not add the user provided ones because we are // already adding them all. - if !rootless.IsRootless() { - if err := config.AddPrivilegedDevices(&g); err != nil { - return nil, err - } + if err := config.AddPrivilegedDevices(&g); err != nil { + return nil, err } } else { for _, devicePath := range config.Devices { @@ -307,6 +324,10 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM if err := addIpcNS(config, &g); err != nil { return nil, err } + + if err := addCgroupNS(config, &g); err != nil { + return nil, err + } configSpec := g.Config // HANDLE CAPABILITIES @@ -350,7 +371,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } if rootless.IsRootless() { - cgroup2, err := util.IsCgroup2UnifiedMode() + cgroup2, err := cgroups.IsCgroup2UnifiedMode() if err != nil { return nil, err } @@ -400,6 +421,62 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } } + // Add annotations + if configSpec.Annotations == nil { + configSpec.Annotations = make(map[string]string) + } + + if config.CidFile != "" { + configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile + } + + if config.Rm { + configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue + } else { + configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse + } + + if len(config.VolumesFrom) > 0 { + configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",") + } + + if config.Privileged { + configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue + } else { + configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse + } + + if config.PublishAll { + configSpec.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + } else { + configSpec.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse + } + + if config.Init { + configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue + } else { + configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse + } + + for _, opt := range config.SecurityOpts { + // Split on both : and = + splitOpt := strings.Split(opt, "=") + if len(splitOpt) == 1 { + splitOpt = strings.Split(opt, ":") + } + if len(splitOpt) < 2 { + continue + } + switch splitOpt[0] { + case "label": + configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1] + case "seccomp": + configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1] + case "apparmor": + configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1] + } + } + return configSpec, nil } @@ -475,15 +552,18 @@ func addPidNS(config *CreateConfig, g *generate.Generator) error { func addUserNS(config *CreateConfig, g *generate.Generator) error { if IsNS(string(config.UsernsMode)) { - g.AddOrReplaceLinuxNamespace(spec.UserNamespace, NS(string(config.UsernsMode))) - + if err := g.AddOrReplaceLinuxNamespace(spec.UserNamespace, NS(string(config.UsernsMode))); err != nil { + return err + } // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) } if (len(config.IDMappings.UIDMap) > 0 || len(config.IDMappings.GIDMap) > 0) && !config.UsernsMode.IsHost() { - g.AddOrReplaceLinuxNamespace(spec.UserNamespace, "") + if err := g.AddOrReplaceLinuxNamespace(spec.UserNamespace, ""); err != nil { + return err + } } return nil } @@ -544,6 +624,23 @@ func addIpcNS(config *CreateConfig, g *generate.Generator) error { return nil } +func addCgroupNS(config *CreateConfig, g *generate.Generator) error { + cgroupMode := config.CgroupMode + if cgroupMode.IsNS() { + return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), NS(string(cgroupMode))) + } + if cgroupMode.IsHost() { + return g.RemoveLinuxNamespace(spec.CgroupNamespace) + } + if cgroupMode.IsPrivate() { + return g.AddOrReplaceLinuxNamespace(spec.CgroupNamespace, "") + } + if cgroupMode.IsContainer() { + logrus.Debug("Using container cgroup mode") + } + return nil +} + func addRlimits(config *CreateConfig, g *generate.Generator) error { var ( kernelMax uint64 = 1048576 @@ -553,6 +650,14 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { ) for _, u := range config.Resources.Ulimit { + if u == "host" { + if len(config.Resources.Ulimit) != 1 { + return errors.New("ulimit can use host only once") + } + g.Config.Process.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) diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index dcc149b55..88f1f6dc1 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -211,6 +211,13 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, } mount.Options = opts } + 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([]*libpod.ContainerNamedVolume, 0, len(baseVolumes)) @@ -251,9 +258,11 @@ func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string] return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1]) } options = strings.Split(splitVol[1], ",") - if err := ValidateVolumeOpts(options); err != nil { + opts, err := ValidateVolumeOpts(options) + if err != nil { return nil, nil, err } + options = opts } ctr, err := runtime.LookupContainer(splitVol[0]) if err != nil { @@ -384,7 +393,7 @@ func (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libp } finalNamedVolumes[volume.Dest] = volume default: - return nil, nil, errors.Errorf("invalid fylesystem type %q", kv[1]) + return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) } } @@ -403,6 +412,8 @@ func getBindMount(args []string) (spec.Mount, error) { for _, val := range args { kv := strings.Split(val, "=") switch kv[0] { + case "bind-nonrecursive": + newMount.Options = append(newMount.Options, "bind") case "ro", "nosuid", "nodev", "noexec": // TODO: detect duplication of these options. // (Is this necessary?) @@ -445,9 +456,11 @@ func getBindMount(args []string) (spec.Mount, error) { newMount.Source = newMount.Destination } - if err := ValidateVolumeOpts(newMount.Options); err != nil { + opts, err := ValidateVolumeOpts(newMount.Options) + if err != nil { return newMount, err } + newMount.Options = opts return newMount, nil } @@ -573,30 +586,45 @@ func ValidateVolumeCtrDir(ctrDir string) error { } // ValidateVolumeOpts validates a volume's options -func ValidateVolumeOpts(options []string) error { - var foundRootPropagation, foundRWRO, foundLabelChange int +func ValidateVolumeOpts(options []string) ([]string, error) { + var foundRootPropagation, foundRWRO, foundLabelChange, bindType int + finalOpts := make([]string, 0, len(options)) for _, opt := range options { switch opt { case "rw", "ro": foundRWRO++ if foundRWRO > 1 { - return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", ")) + return nil, errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", ")) } case "z", "Z": foundLabelChange++ if foundLabelChange > 1 { - return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", ")) + return nil, errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", ")) } case "private", "rprivate", "shared", "rshared", "slave", "rslave": foundRootPropagation++ if foundRootPropagation > 1 { - return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", ")) - } + return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", ")) + } + case "bind", "rbind": + bindType++ + if bindType > 1 { + return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]bind' option", strings.Join(options, ", ")) + } + case "cached", "delegated": + // The discarded ops are OS X specific volume options + // introduced in a recent Docker version. + // They have no meaning on Linux, so here we silently + // drop them. This matches Docker's behavior (the options + // are intended to be always safe to use, even not on OS + // X). + continue default: - return errors.Errorf("invalid option type %q", opt) + return nil, errors.Errorf("invalid mount option %q", opt) } + finalOpts = append(finalOpts, opt) } - return nil + return finalOpts, nil } // GetVolumeMounts takes user provided input for bind mounts and creates Mount structs @@ -626,9 +654,11 @@ func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string } if len(splitVol) > 2 { options = strings.Split(splitVol[2], ",") - if err := ValidateVolumeOpts(options); err != nil { + opts, err := ValidateVolumeOpts(options) + if err != nil { return nil, nil, err } + options = opts } if err := ValidateVolumeHostDir(src); err != nil { @@ -797,7 +827,7 @@ func initFSMounts(inputMounts []spec.Mount) []spec.Mount { if m.Type == TypeBind { m.Options = util.ProcessOptions(m.Options) } - if m.Type == TypeTmpfs { + if m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev" { m.Options = append(m.Options, "tmpcopyup") } mounts = append(mounts, m) |