summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.tool/lint2
-rw-r--r--cmd/podman/play_kube.go5
-rw-r--r--cmd/podman/shared/create.go30
-rw-r--r--cmd/podman/shared/create_cli.go184
-rw-r--r--pkg/spec/containerconfig.go17
-rw-r--r--pkg/spec/createconfig.go268
-rw-r--r--pkg/spec/spec.go116
-rw-r--r--pkg/spec/spec_test.go18
-rw-r--r--pkg/spec/storage.go765
9 files changed, 841 insertions, 564 deletions
diff --git a/.tool/lint b/.tool/lint
index f7bf81c1d..01f44311d 100755
--- a/.tool/lint
+++ b/.tool/lint
@@ -25,7 +25,6 @@ ${LINTER} \
--deadline=600s --disable-all\
--enable=deadcode\
--enable=errcheck\
- --enable=goconst\
--enable=gofmt\
--enable=golint\
--enable=ineffassign\
@@ -41,7 +40,6 @@ ${LINTER} \
--exclude='duplicate of.*_test.go.*\(dupl\)$'\
--exclude='cmd\/client\/.*\.go.*\(dupl\)$'\
--exclude='libpod\/.*_easyjson.go:.*'\
- --exclude='.* other occurrence\(s\) of "(container|host|tmpfs|unknown)" found in: .*\(goconst\)$'\
--exclude='vendor\/.*'\
--exclude='podman\/.*'\
--exclude='server\/seccomp\/.*\.go.*$'\
diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go
index 2e51457c4..e778bafb9 100644
--- a/cmd/podman/play_kube.go
+++ b/cmd/podman/play_kube.go
@@ -205,7 +205,8 @@ func playKubeYAMLCmd(c *cliconfig.KubePlayValues, ctx context.Context, runtime *
return pod, errors.Errorf("Directories are the only supported HostPath type")
}
}
- if err := shared.ValidateVolumeHostDir(hostPath.Path); err != nil {
+
+ if err := createconfig.ValidateVolumeHostDir(hostPath.Path); err != nil {
return pod, errors.Wrapf(err, "Error in parsing HostPath in YAML")
}
volumes[volume.Name] = hostPath.Path
@@ -351,7 +352,7 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
if !exists {
return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
}
- if err := shared.ValidateVolumeCtrDir(volume.MountPath); err != nil {
+ if err := createconfig.ValidateVolumeCtrDir(volume.MountPath); err != nil {
return nil, errors.Wrapf(err, "error in parsing MountPath")
}
containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", host_path, volume.MountPath))
diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go
index ebc42457b..1e185d21f 100644
--- a/cmd/podman/shared/create.go
+++ b/cmd/podman/shared/create.go
@@ -25,7 +25,6 @@ import (
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/google/shlex"
- spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
@@ -341,18 +340,6 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
}
blkioWeight = uint16(u)
}
- var mountList []spec.Mount
- if mountList, err = parseMounts(c.StringArray("mount")); err != nil {
- return nil, err
- }
-
- if err = parseVolumes(c.StringArray("volume")); err != nil {
- return nil, err
- }
-
- if err = parseVolumesFrom(c.StringSlice("volumes-from")); err != nil {
- return nil, err
- }
tty := c.Bool("tty")
@@ -636,6 +623,8 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
HTTPProxy: c.Bool("http-proxy"),
NoHosts: c.Bool("no-hosts"),
IDMappings: idmappings,
+ Init: c.Bool("init"),
+ InitPath: c.String("init-path"),
Image: imageName,
ImageID: imageID,
Interactive: c.Bool("interactive"),
@@ -696,26 +685,13 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
Tty: tty,
User: user,
UsernsMode: usernsMode,
- Mounts: mountList,
+ MountsFlag: c.StringArray("mount"),
Volumes: c.StringArray("volume"),
WorkDir: workDir,
Rootfs: rootfs,
VolumesFrom: c.StringSlice("volumes-from"),
Syslog: c.Bool("syslog"),
}
- if c.Bool("init") {
- initPath := c.String("init-path")
- if initPath == "" {
- rtc, err := runtime.GetConfig()
- if err != nil {
- return nil, err
- }
- initPath = rtc.InitPath
- }
- if err := config.AddContainerInitBinary(initPath); err != nil {
- return nil, err
- }
- }
if config.Privileged {
config.LabelOpts = label.DisableSecOpt()
diff --git a/cmd/podman/shared/create_cli.go b/cmd/podman/shared/create_cli.go
index 4f9cb1699..f731e8db5 100644
--- a/cmd/podman/shared/create_cli.go
+++ b/cmd/podman/shared/create_cli.go
@@ -2,15 +2,11 @@ package shared
import (
"fmt"
- "os"
- "path/filepath"
"strings"
"github.com/containers/libpod/cmd/podman/shared/parse"
cc "github.com/containers/libpod/pkg/spec"
"github.com/containers/libpod/pkg/sysinfo"
- "github.com/docker/go-units"
- spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -78,186 +74,6 @@ func addWarning(warnings []string, msg string) []string {
return append(warnings, msg)
}
-// Format supported.
-// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
-// podman run --mount type=tmpfs,target=/dev/shm ..
-func parseMounts(mounts []string) ([]spec.Mount, error) {
- // 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 suppored options.
- var mountList []spec.Mount
- errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>[,options]")
- for _, mount := range mounts {
- var tokenCount int
- var mountInfo spec.Mount
-
- arr := strings.SplitN(mount, ",", 2)
- if len(arr) < 2 {
- return nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
- }
- kv := strings.Split(arr[0], "=")
- if kv[0] != "type" {
- return nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
- }
- switch kv[1] {
- case "bind":
- mountInfo.Type = string(cc.TypeBind)
- case "tmpfs":
- mountInfo.Type = string(cc.TypeTmpfs)
- mountInfo.Source = string(cc.TypeTmpfs)
- mountInfo.Options = append(mountInfo.Options, []string{"rprivate", "noexec", "nosuid", "nodev", "size=65536k"}...)
-
- default:
- return nil, errors.Errorf("invalid filesystem type %q", kv[1])
- }
-
- tokens := strings.Split(arr[1], ",")
- for i, val := range tokens {
- if i == (tokenCount - 1) {
- //Parse tokens before options.
- break
- }
- kv := strings.Split(val, "=")
- switch kv[0] {
- case "ro", "nosuid", "nodev", "noexec":
- mountInfo.Options = append(mountInfo.Options, kv[0])
- case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
- if mountInfo.Type != "bind" {
- return nil, errors.Errorf("%s can only be used with bind mounts", kv[0])
- }
- mountInfo.Options = append(mountInfo.Options, kv[0])
- case "tmpfs-mode":
- if mountInfo.Type != "tmpfs" {
- return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0])
- }
- mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("mode=%s", kv[1]))
- case "tmpfs-size":
- if mountInfo.Type != "tmpfs" {
- return nil, errors.Errorf("%s can only be used with tmpfs mounts", kv[0])
- }
- shmSize, err := units.FromHumanSize(kv[1])
- if err != nil {
- return nil, errors.Wrapf(err, "unable to translate tmpfs-size")
- }
-
- mountInfo.Options = append(mountInfo.Options, fmt.Sprintf("size=%d", shmSize))
-
- case "bind-propagation":
- if mountInfo.Type != "bind" {
- return nil, errors.Errorf("%s can only be used with bind mounts", kv[0])
- }
- mountInfo.Options = append(mountInfo.Options, kv[1])
- case "src", "source":
- if mountInfo.Type == "tmpfs" {
- return nil, errors.Errorf("cannot use src= on a tmpfs file system")
- }
- if err := ValidateVolumeHostDir(kv[1]); err != nil {
- return nil, err
- }
- mountInfo.Source = kv[1]
- case "target", "dst", "destination":
- if err := ValidateVolumeCtrDir(kv[1]); err != nil {
- return nil, err
- }
- mountInfo.Destination = kv[1]
- default:
- return nil, errors.Errorf("incorrect mount option : %s", kv[0])
- }
- }
- mountList = append(mountList, mountInfo)
- }
- return mountList, nil
-}
-
-func parseVolumes(volumes []string) error {
- for _, volume := range volumes {
- arr := strings.SplitN(volume, ":", 3)
- if len(arr) < 2 {
- return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume)
- }
- if err := ValidateVolumeHostDir(arr[0]); err != nil {
- return err
- }
- if err := ValidateVolumeCtrDir(arr[1]); err != nil {
- return err
- }
- if len(arr) > 2 {
- if err := validateVolumeOpts(arr[2]); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-func parseVolumesFrom(volumesFrom []string) error {
- for _, vol := range volumesFrom {
- arr := strings.SplitN(vol, ":", 2)
- if len(arr) == 2 {
- if strings.Contains(arr[1], "Z") || strings.Contains(arr[1], "private") || strings.Contains(arr[1], "slave") || strings.Contains(arr[1], "shared") {
- return errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", arr[1])
- }
- if err := validateVolumeOpts(arr[1]); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// ValidateVolumeHostDir ...
-func ValidateVolumeHostDir(hostDir string) error {
- if len(hostDir) == 0 {
- return errors.Errorf("host directory cannot be empty")
- }
- if filepath.IsAbs(hostDir) {
- if _, err := os.Stat(hostDir); err != nil {
- return errors.Wrapf(err, "error checking path %q", hostDir)
- }
- }
- // If hostDir is not an absolute path, that means the user wants to create a
- // named volume. This will be done later on in the code.
- return nil
-}
-
-// ValidateVolumeCtrDir ...
-func ValidateVolumeCtrDir(ctrDir string) error {
- if len(ctrDir) == 0 {
- return errors.Errorf("container directory cannot be empty")
- }
- if !filepath.IsAbs(ctrDir) {
- return errors.Errorf("invalid container path, must be an absolute path %q", ctrDir)
- }
- return nil
-}
-
-func validateVolumeOpts(option string) error {
- var foundRootPropagation, foundRWRO, foundLabelChange int
- options := strings.Split(option, ",")
- 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", option)
- }
- case "z", "Z":
- foundLabelChange++
- if foundLabelChange > 1 {
- return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option)
- }
- 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", option)
- }
- default:
- return errors.Errorf("invalid option type %q", option)
- }
- }
- return nil
-}
-
func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
warnings := []string{}
sysInfo := sysinfo.New(true)
diff --git a/pkg/spec/containerconfig.go b/pkg/spec/containerconfig.go
index 87c1b5155..f9bb075b8 100644
--- a/pkg/spec/containerconfig.go
+++ b/pkg/spec/containerconfig.go
@@ -4,6 +4,7 @@ import (
"github.com/containers/libpod/libpod"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
// MakeContainerConfig generates all configuration necessary to start a
@@ -15,15 +16,27 @@ func (config *CreateConfig) MakeContainerConfig(runtime *libpod.Runtime, pod *li
return nil, nil, errors.Wrapf(libpod.ErrInvalidArg, "pod was given but no pod is specified")
}
- runtimeSpec, namedVolumes, err := config.createConfigToOCISpec(runtime)
+ // Parse volumes flag into OCI spec mounts and libpod Named Volumes.
+ // If there is an identical mount in the OCI spec, we will replace it
+ // with a mount generated here.
+ mounts, namedVolumes, err := config.parseVolumes(runtime)
if err != nil {
return nil, nil, err
}
- options, err := config.getContainerCreateOptions(runtime, pod, namedVolumes)
+ logrus.Debugf("got mounts as %v", mounts)
+
+ runtimeSpec, err := config.createConfigToOCISpec(runtime, mounts)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ options, err := config.getContainerCreateOptions(runtime, pod, mounts, namedVolumes)
if err != nil {
return nil, nil, err
}
+ logrus.Debugf("created OCI spec and options for new container")
+
return runtimeSpec, options, nil
}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 80c7b586c..4d2d8304c 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -1,7 +1,6 @@
package createconfig
import (
- "fmt"
"net"
"os"
"strconv"
@@ -12,7 +11,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
- "github.com/containers/storage/pkg/stringid"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -26,12 +24,6 @@ import (
const (
bps = iota
iops
- // TypeBind is the type for mounting host dir
- TypeBind = "bind"
- // TypeVolume is the type for remote storage volumes
- // TypeVolume = "volume" // re-enable upon use
- // TypeTmpfs is the type for mounting tmpfs
- TypeTmpfs = "tmpfs"
)
// CreateResourceConfig represents resource elements in CreateConfig
@@ -87,6 +79,8 @@ type CreateConfig struct {
HostAdd []string //add-host
Hostname string //hostname
HTTPProxy bool
+ Init bool // init
+ InitPath string //init-path
Image string
ImageID string
BuiltinImgVolumes map[string]struct{} // volumes defined in the image config
@@ -125,14 +119,16 @@ type CreateConfig struct {
UsernsMode namespaces.UsernsMode //userns
User string //user
UtsMode namespaces.UTSMode //uts
- Mounts []spec.Mount //mounts
- Volumes []string //volume
+ Mounts []spec.Mount
+ MountsFlag []string // mounts
+ NamedVolumes []*libpod.ContainerNamedVolume
+ Volumes []string //volume
VolumesFrom []string
- WorkDir string //workdir
- LabelOpts []string //SecurityOpts
- NoNewPrivs bool //SecurityOpts
- ApparmorProfile string //SecurityOpts
- SeccompProfilePath string //SecurityOpts
+ WorkDir string //workdir
+ LabelOpts []string //SecurityOpts
+ NoNewPrivs bool //SecurityOpts
+ ApparmorProfile string //SecurityOpts
+ SeccompProfilePath string //SecurityOpts
SecurityOpts []string
Rootfs string
Syslog bool // Whether to enable syslog on exit commands
@@ -146,222 +142,6 @@ func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) {
return c.createBlockIO()
}
-// AddContainerInitBinary adds the init binary specified by path iff the
-// container will run in a private PID namespace that is not shared with the
-// host or another pre-existing container, where an init-like process is
-// already running.
-//
-// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
-// to execute the bind-mounted binary as PID 1.
-func (c *CreateConfig) AddContainerInitBinary(path string) error {
- if path == "" {
- return fmt.Errorf("please specify a path to the container-init binary")
- }
- if !c.PidMode.IsPrivate() {
- return fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
- }
- if c.Systemd {
- return fmt.Errorf("cannot use container-init binary with systemd")
- }
- if _, err := os.Stat(path); os.IsNotExist(err) {
- return errors.Wrap(err, "container-init binary not found on the host")
- }
- c.Command = append([]string{"/dev/init", "--"}, c.Command...)
- c.Mounts = append(c.Mounts, spec.Mount{
- Destination: "/dev/init",
- Type: TypeBind,
- Source: path,
- Options: []string{TypeBind, "ro"},
- })
- return nil
-}
-
-func processOptions(options []string) []string {
- var (
- foundrw, foundro bool
- rootProp string
- )
- options = append(options, "rbind")
- for _, opt := range options {
- switch opt {
- case "rw":
- foundrw = true
- case "ro":
- foundro = true
- case "private", "rprivate", "slave", "rslave", "shared", "rshared":
- rootProp = opt
- }
- }
- if !foundrw && !foundro {
- options = append(options, "rw")
- }
- if rootProp == "" {
- options = append(options, "rprivate")
- }
- return options
-}
-
-func (c *CreateConfig) initFSMounts() []spec.Mount {
- var mounts []spec.Mount
- for _, m := range c.Mounts {
- m.Options = processOptions(m.Options)
- if m.Type == "tmpfs" {
- m.Options = append(m.Options, "tmpcopyup")
- } else {
- mounts = append(mounts, m)
- }
- }
- return mounts
-}
-
-// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
-func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) {
- m := []spec.Mount{}
- for _, i := range c.Volumes {
- var options []string
- spliti := strings.Split(i, ":")
- if len(spliti) > 2 {
- options = strings.Split(spliti[2], ",")
- }
-
- m = append(m, spec.Mount{
- Destination: spliti[1],
- Type: string(TypeBind),
- Source: spliti[0],
- Options: processOptions(options),
- })
-
- logrus.Debugf("User mount %s:%s options %v", spliti[0], spliti[1], options)
- }
-
- if c.ImageVolumeType == "ignore" {
- return m, nil
- }
-
- for vol := range c.BuiltinImgVolumes {
- if libpod.MountExists(specMounts, vol) || libpod.MountExists(m, vol) {
- continue
- }
-
- mount := spec.Mount{
- Destination: vol,
- Type: c.ImageVolumeType,
- Options: []string{"rprivate", "rw", "nodev"},
- }
- if c.ImageVolumeType == "tmpfs" {
- mount.Source = "tmpfs"
- mount.Options = append(mount.Options, "tmpcopyup")
- } else {
- // TODO: Move support for this and tmpfs into libpod
- // Should tmpfs also be handled as named volumes? Wouldn't be hard
- // This will cause a new local Volume to be created on your system
- mount.Source = stringid.GenerateNonCryptoID()
- mount.Options = append(mount.Options, TypeBind)
- }
- m = append(m, mount)
- }
-
- return m, nil
-}
-
-// GetVolumesFrom reads the create-config artifact of the container to get volumes from
-// and adds it to c.Volumes of the current container.
-func (c *CreateConfig) GetVolumesFrom(runtime *libpod.Runtime) error {
- if os.Geteuid() != 0 {
- return nil
- }
-
- for _, vol := range c.VolumesFrom {
- options := ""
- splitVol := strings.SplitN(vol, ":", 2)
- if len(splitVol) == 2 {
- options = splitVol[1]
- }
- ctr, err := runtime.LookupContainer(splitVol[0])
- if err != nil {
- return errors.Wrapf(err, "error looking up container %q", splitVol[0])
- }
-
- logrus.Debugf("Adding volumes from container %s", ctr.ID())
-
- // Look up the container's user volumes. This gets us the
- // destinations of all mounts the user added to the container.
- userVolumesArr := ctr.UserVolumes()
-
- // We're going to need to access them a lot, so convert to a map
- // to reduce looping.
- // We'll also use the map to indicate if we missed any volumes along the way.
- userVolumes := make(map[string]bool)
- for _, dest := range userVolumesArr {
- userVolumes[dest] = false
- }
-
- // Now we get the container's spec and loop through its volumes
- // and append them in if we can find them.
- spec := ctr.Spec()
- if spec == nil {
- return errors.Errorf("error retrieving container %s spec", ctr.ID())
- }
- for _, mnt := range spec.Mounts {
- if mnt.Type != TypeBind {
- continue
- }
- if _, exists := userVolumes[mnt.Destination]; exists {
- userVolumes[mnt.Destination] = true
- localOptions := options
- if localOptions == "" {
- localOptions = strings.Join(mnt.Options, ",")
- }
- c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", mnt.Source, mnt.Destination, localOptions))
- }
- }
-
- // We're done with the spec mounts. Add named volumes.
- // Add these unconditionally - none of them are automatically
- // part of the container, as some spec mounts are.
- namedVolumes := ctr.NamedVolumes()
- for _, namedVol := range namedVolumes {
- if _, exists := userVolumes[namedVol.Dest]; exists {
- userVolumes[namedVol.Dest] = true
- }
- localOptions := options
- if localOptions == "" {
- localOptions = strings.Join(namedVol.Options, ",")
- }
- c.Volumes = append(c.Volumes, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, localOptions))
- }
-
- // Check if we missed any volumes
- for volDest, found := range userVolumes {
- if !found {
- logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
- }
- }
- }
- return nil
-}
-
-//GetTmpfsMounts takes user provided input for Tmpfs mounts and creates Mount structs
-func (c *CreateConfig) GetTmpfsMounts() []spec.Mount {
- var m []spec.Mount
- for _, i := range c.Tmpfs {
- // Default options if nothing passed
- options := []string{"rprivate", "rw", "noexec", "nosuid", "nodev", "size=65536k"}
- spliti := strings.Split(i, ":")
- destPath := spliti[0]
- if len(spliti) > 1 {
- options = strings.Split(spliti[1], ",")
- }
- m = append(m, spec.Mount{
- Destination: destPath,
- Type: string(TypeTmpfs),
- Options: options,
- Source: string(TypeTmpfs),
- })
- }
- return m
-}
-
func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, error) {
config, err := runtime.GetConfig()
if err != nil {
@@ -395,7 +175,7 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err
}
// GetContainerCreateOptions takes a CreateConfig and returns a slice of CtrCreateOptions
-func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod, namedVolumes []*libpod.ContainerNamedVolume) ([]libpod.CtrCreateOption, error) {
+func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod, mounts []spec.Mount, namedVolumes []*libpod.ContainerNamedVolume) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var portBindings []ocicni.PortMapping
var err error
@@ -422,22 +202,18 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
}
}
- if len(c.Volumes) != 0 {
- // Volumes consist of multiple, comma-delineated fields
- // The image spec only includes one part of that, so drop the
- // others, if they are included
- volumes := make([]string, 0, len(c.Volumes))
- for _, vol := range c.Volumes {
- // We always want the volume destination
- splitVol := strings.SplitN(vol, ":", 3)
- if len(splitVol) > 1 {
- volumes = append(volumes, splitVol[1])
- } else {
- volumes = append(volumes, splitVol[0])
- }
+ if len(mounts) != 0 || len(namedVolumes) != 0 {
+ destinations := []string{}
+
+ // Take all mount and named volume destinations.
+ for _, mount := range mounts {
+ destinations = append(destinations, mount.Destination)
+ }
+ for _, volume := range namedVolumes {
+ destinations = append(destinations, volume.Dest)
}
- options = append(options, libpod.WithUserVolumes(volumes))
+ options = append(options, libpod.WithUserVolumes(destinations))
}
if len(namedVolumes) != 0 {
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 5ffa6dc4c..591a28703 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -2,7 +2,6 @@ package createconfig
import (
"os"
- "path"
"path/filepath"
"strings"
@@ -21,61 +20,6 @@ import (
const cpuPeriod = 100000
-func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
- if len(mounts) > 0 {
- // If we have overlappings mounts, remove them from the spec in favor of
- // the user-added volume mounts
- destinations := make(map[string]bool)
- for _, mount := range mounts {
- destinations[path.Clean(mount.Destination)] = true
- }
- // Copy all mounts from spec to defaultMounts, except for
- // - mounts overridden by a user supplied mount;
- // - all mounts under /dev if a user supplied /dev is present;
- mountDev := destinations["/dev"]
- for _, mount := range configMount {
- if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
- if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
- // filter out everything under /dev if /dev is user-mounted
- continue
- }
-
- logrus.Debugf("Adding mount %s", mount.Destination)
- mounts = append(mounts, mount)
- }
- }
- return mounts
- }
- return configMount
-}
-
-// Split named volumes from normal volumes
-func splitNamedVolumes(mounts []spec.Mount) ([]spec.Mount, []*libpod.ContainerNamedVolume) {
- newMounts := make([]spec.Mount, 0)
- namedVolumes := make([]*libpod.ContainerNamedVolume, 0)
- for _, mount := range mounts {
- // If it's not a named volume, append unconditionally
- if mount.Type != TypeBind {
- newMounts = append(newMounts, mount)
- continue
- }
- // Volumes that are not named volumes must be an absolute or
- // relative path.
- // Volume names may not begin with a non-alphanumeric character
- // so the HasPrefix() check is safe here.
- if strings.HasPrefix(mount.Source, "/") || strings.HasPrefix(mount.Source, ".") {
- newMounts = append(newMounts, mount)
- } else {
- namedVolume := new(libpod.ContainerNamedVolume)
- namedVolume.Name = mount.Source
- namedVolume.Dest = mount.Destination
- namedVolume.Options = mount.Options
- namedVolumes = append(namedVolumes, namedVolume)
- }
- }
- return newMounts, namedVolumes
-}
-
func getAvailableGids() (int64, error) {
idMap, err := user.ParseIDMapFile("/proc/self/gid_map")
if err != nil {
@@ -89,11 +33,11 @@ func getAvailableGids() (int64, error) {
}
// CreateConfigToOCISpec parses information needed to create a container into an OCI runtime spec
-func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spec.Spec, []*libpod.ContainerNamedVolume, error) { //nolint
+func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userMounts []spec.Mount) (*spec.Spec, error) {
cgroupPerm := "ro"
g, err := generate.New("linux")
if err != nil {
- return nil, nil, err
+ return nil, err
}
// Remove the default /dev/shm mount to ensure we overwrite it
g.RemoveMount("/dev/shm")
@@ -139,7 +83,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
if isRootless {
nGids, err := getAvailableGids()
if err != nil {
- return nil, nil, err
+ return nil, err
}
if nGids < 5 {
// If we have no GID mappings, the gid=5 default option would fail, so drop it.
@@ -214,7 +158,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
if hostname == "" && (config.NetMode.IsHost() || config.UtsMode.IsHost()) {
hostname, err = os.Hostname()
if err != nil {
- return nil, nil, errors.Wrap(err, "unable to retrieve hostname")
+ return nil, errors.Wrap(err, "unable to retrieve hostname")
}
}
g.RemoveHostname()
@@ -304,13 +248,13 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
// already adding them all.
if !rootless.IsRootless() {
if err := config.AddPrivilegedDevices(&g); err != nil {
- return nil, nil, err
+ return nil, err
}
}
} else {
for _, devicePath := range config.Devices {
if err := devicesFromPath(&g, devicePath); err != nil {
- return nil, nil, err
+ return nil, err
}
}
}
@@ -340,7 +284,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
spliti := strings.SplitN(i, ":", 2)
if len(spliti) > 1 {
if _, _, err := mount.ParseTmpfsOptions(spliti[1]); err != nil {
- return nil, nil, err
+ return nil, err
}
options = strings.Split(spliti[1], ",")
}
@@ -384,32 +328,33 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
g.AddMount(tmpfsMnt)
}
}
+
for name, val := range config.Env {
g.AddProcessEnv(name, val)
}
if err := addRlimits(config, &g); err != nil {
- return nil, nil, err
+ return nil, err
}
if err := addPidNS(config, &g); err != nil {
- return nil, nil, err
+ return nil, err
}
if err := addUserNS(config, &g); err != nil {
- return nil, nil, err
+ return nil, err
}
if err := addNetNS(config, &g); err != nil {
- return nil, nil, err
+ return nil, err
}
if err := addUTSNS(config, &g); err != nil {
- return nil, nil, err
+ return nil, err
}
if err := addIpcNS(config, &g); err != nil {
- return nil, nil, err
+ return nil, err
}
configSpec := g.Config
@@ -417,7 +362,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
// NOTE: Must happen before SECCOMP
if !config.Privileged {
if err := setupCapabilities(config, configSpec); err != nil {
- return nil, nil, err
+ return nil, err
}
} else {
g.SetupPrivileged(true)
@@ -428,7 +373,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
if config.SeccompProfilePath != "unconfined" {
seccompConfig, err := getSeccompConfig(config, configSpec)
if err != nil {
- return nil, nil, err
+ return nil, err
}
configSpec.Linux.Seccomp = seccompConfig
}
@@ -439,27 +384,14 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
}
// BIND MOUNTS
- if err := config.GetVolumesFrom(runtime); err != nil {
- return nil, nil, errors.Wrap(err, "error getting volume mounts from --volumes-from flag")
- }
-
- volumeMounts, err := config.GetVolumeMounts(configSpec.Mounts)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "error getting volume mounts")
- }
-
- configSpec.Mounts = supercedeUserMounts(volumeMounts, configSpec.Mounts)
- //--mount
- configSpec.Mounts = supercedeUserMounts(config.initFSMounts(), configSpec.Mounts)
-
- // Split normal mounts and named volumes
- newMounts, namedVolumes := splitNamedVolumes(configSpec.Mounts)
- configSpec.Mounts = newMounts
+ configSpec.Mounts = supercedeUserMounts(userMounts, configSpec.Mounts)
+ // Process mounts to ensure correct options
+ configSpec.Mounts = initFSMounts(configSpec.Mounts)
// BLOCK IO
blkio, err := config.CreateBlockIO()
if err != nil {
- return nil, nil, errors.Wrapf(err, "error creating block io")
+ return nil, errors.Wrapf(err, "error creating block io")
}
if blkio != nil {
configSpec.Linux.Resources.BlockIO = blkio
@@ -468,7 +400,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
if rootless.IsRootless() {
if addedResources {
- return nil, nil, errors.New("invalid configuration, cannot set resources with rootless containers")
+ return nil, errors.New("invalid configuration, cannot set resources with rootless containers")
}
configSpec.Linux.Resources = &spec.LinuxResources{}
}
@@ -476,7 +408,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
// Make sure that the bind mounts keep options like nosuid, noexec, nodev.
mounts, err := pmount.GetMounts()
if err != nil {
- return nil, nil, err
+ return nil, err
}
for i := range configSpec.Mounts {
m := &configSpec.Mounts[i]
@@ -492,7 +424,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
}
mount, err := findMount(m.Source, mounts)
if err != nil {
- return nil, nil, err
+ return nil, err
}
if mount == nil {
continue
@@ -510,7 +442,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime) (*spe
}
}
- return configSpec, namedVolumes, nil
+ return configSpec, nil
}
func findMount(target string, mounts []*pmount.Info) (*pmount.Info, error) {
diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go
index c037bf69e..b7c9c9c0d 100644
--- a/pkg/spec/spec_test.go
+++ b/pkg/spec/spec_test.go
@@ -8,22 +8,22 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestCreateConfig_GetVolumeMounts(t *testing.T) {
+func TestGetVolumeMountsOneVolume(t *testing.T) {
data := spec.Mount{
Destination: "/foobar",
Type: "bind",
- Source: "foobar",
+ Source: "/foobar",
Options: []string{"ro", "rbind", "rprivate"},
}
config := CreateConfig{
- Volumes: []string{"foobar:/foobar:ro"},
+ Volumes: []string{"/foobar:/foobar:ro"},
}
- specMount, err := config.GetVolumeMounts([]spec.Mount{})
+ specMount, _, err := config.getVolumeMounts()
assert.NoError(t, err)
- assert.True(t, reflect.DeepEqual(data, specMount[0]))
+ assert.True(t, reflect.DeepEqual(data, specMount[data.Destination]))
}
-func TestCreateConfig_GetTmpfsMounts(t *testing.T) {
+func TestGetTmpfsMounts(t *testing.T) {
data := spec.Mount{
Destination: "/homer",
Type: "tmpfs",
@@ -33,7 +33,7 @@ func TestCreateConfig_GetTmpfsMounts(t *testing.T) {
config := CreateConfig{
Tmpfs: []string{"/homer:rw,size=787448k,mode=1777"},
}
- tmpfsMount := config.GetTmpfsMounts()
- assert.True(t, reflect.DeepEqual(data, tmpfsMount[0]))
-
+ tmpfsMount, err := config.getTmpfsMounts()
+ assert.NoError(t, err)
+ assert.True(t, reflect.DeepEqual(data, tmpfsMount[data.Destination]))
}
diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go
new file mode 100644
index 000000000..7806a8c72
--- /dev/null
+++ b/pkg/spec/storage.go
@@ -0,0 +1,765 @@
+package createconfig
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/storage/pkg/stringid"
+ 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")
+ badOptionError = errors.Errorf("invalid mount option")
+ 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-from, --volumes, --tmpfs, --init, and --init-path flags.
+// TODO: Named volume options - should we default to rprivate? It bakes into a
+// bind mount under the hood...
+// TODO: Tmpfs options - we should probably check user-given ones, provide sane
+// defaults even if the user provides a few...
+func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, []*libpod.ContainerNamedVolume, error) {
+ // Add image volumes.
+ baseMounts, baseVolumes, err := config.getImageVolumes()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Add --volumes-from.
+ // Overrides image volumes unconditionally.
+ vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime)
+ if err != nil {
+ return nil, nil, err
+ }
+ for dest, mount := range vFromMounts {
+ baseMounts[dest] = mount
+ }
+ for dest, volume := range vFromVolumes {
+ baseVolumes[dest] = volume
+ }
+
+ // Next mounts from the --mounts flag.
+ // Do not override yet.
+ unifiedMounts, unifiedVolumes, err := config.getMounts()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --volumes flag.
+ // Do not override yet.
+ volumeMounts, volumeVolumes, err := config.getVolumeMounts()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --tmpfs flag.
+ // Do not override yet.
+ tmpfsMounts, err := config.getTmpfsMounts()
+ 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 container init binary
+ if config.Init {
+ initPath := config.InitPath
+ if initPath == "" {
+ rtc, err := runtime.GetConfig()
+ if err != nil {
+ return nil, nil, err
+ }
+ initPath = rtc.InitPath
+ }
+ initMount, err := config.addContainerInitBinary(initPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := unifiedMounts[initMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
+ }
+ unifiedMounts[initMount.Destination] = initMount
+ }
+
+ // Supercede volumes-from/image volumes with unified volumes from above.
+ // This is an unconditional replacement.
+ for dest, mount := range unifiedMounts {
+ baseMounts[dest] = mount
+ }
+ for dest, volume := range unifiedVolumes {
+ baseVolumes[dest] = volume
+ }
+
+ // TODO: Allow an explicit list of named volumes and spec mounts in the
+ // createconfig. Podman doesn't need them, but other libpod consumers
+ // might want them.
+
+ // Final step: maps to arrays
+ finalMounts := make([]spec.Mount, 0, len(baseMounts))
+ for _, mount := range baseMounts {
+ finalMounts = append(finalMounts, mount)
+ }
+ finalVolumes := make([]*libpod.ContainerNamedVolume, 0, len(baseVolumes))
+ for _, volume := range baseVolumes {
+ finalVolumes = append(finalVolumes, volume)
+ }
+
+ return finalMounts, finalVolumes, nil
+}
+
+// Parse volumes from - a set of containers whose volumes we will mount in.
+// Grab the containers, retrieve any user-created spec mounts and all named
+// volumes, and return a list of them.
+// Conflicts are resolved simply - the last container specified wins.
+// Container names may be suffixed by mount options after a colon.
+func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ // TODO: This can probably be disabled now
+ if os.Geteuid() != 0 {
+ return nil, nil, nil
+ }
+
+ // Both of these are maps of mount destination to mount type.
+ // We ensure that each destination is only mounted to once in this way.
+ // We tiebreak only within each category; we'll let libpod tiebreak
+ // between mounts and named volumes.
+ // TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
+ // one mount, and we already have /tmp/a and /tmp/b, should we remove
+ // the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ for _, vol := range config.VolumesFrom {
+ options := []string{}
+ // TODO: now that container names may contain colons this is no
+ // longer safe.
+ // I don't know if there's much we can do here...
+ splitVol := strings.SplitN(vol, ":", 2)
+ if len(splitVol) == 2 {
+ if strings.Contains(splitVol[1], "Z") ||
+ strings.Contains(splitVol[1], "private") ||
+ strings.Contains(splitVol[1], "slave") ||
+ strings.Contains(splitVol[1], "shared") {
+ return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1])
+ }
+ if err := ValidateVolumeOpts(splitVol[1]); err != nil {
+ return nil, nil, err
+ }
+ options = strings.Split(splitVol[1], ",")
+ }
+ ctr, err := runtime.LookupContainer(splitVol[0])
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
+ }
+
+ logrus.Debugf("Adding volumes from container %s", ctr.ID())
+
+ // Look up the container's user volumes. This gets us the
+ // destinations of all mounts the user added to the container.
+ userVolumesArr := ctr.UserVolumes()
+
+ // We're going to need to access them a lot, so convert to a map
+ // to reduce looping.
+ // We'll also use the map to indicate if we missed any volumes along the way.
+ userVolumes := make(map[string]bool)
+ for _, dest := range userVolumesArr {
+ userVolumes[dest] = false
+ }
+
+ // Now we get the container's spec and loop through its volumes
+ // and append them in if we can find them.
+ spec := ctr.Spec()
+ if spec == nil {
+ return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
+ }
+ for _, mnt := range spec.Mounts {
+ if mnt.Type != TypeBind {
+ continue
+ }
+ if _, exists := userVolumes[mnt.Destination]; exists {
+ userVolumes[mnt.Destination] = true
+
+ if len(options) != 0 {
+ mnt.Options = options
+ }
+
+ if _, ok := finalMounts[mnt.Destination]; ok {
+ logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
+ }
+ finalMounts[mnt.Destination] = mnt
+ }
+ }
+
+ // We're done with the spec mounts. Add named volumes.
+ // Add these unconditionally - none of them are automatically
+ // part of the container, as some spec mounts are.
+ namedVolumes := ctr.NamedVolumes()
+ for _, namedVol := range namedVolumes {
+ if _, exists := userVolumes[namedVol.Dest]; exists {
+ userVolumes[namedVol.Dest] = true
+ }
+
+ if len(options) != 0 {
+ namedVol.Options = options
+ }
+
+ if _, ok := finalMounts[namedVol.Dest]; ok {
+ logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
+ }
+ finalNamedVolumes[namedVol.Dest] = namedVol
+ }
+
+ // Check if we missed any volumes
+ for volDest, found := range userVolumes {
+ if !found {
+ logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
+ }
+ }
+ }
+
+ return finalMounts, finalNamedVolumes, nil
+}
+
+// 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 (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
+
+ // TODO(vrothberg): the manual parsing can be replaced with a regular expression
+ // to allow a more robust parsing of the mount format and to give
+ // precise errors regarding supported format versus suppored options.
+ for _, mount := range config.MountsFlag {
+ 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 fylesystem 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,
+ }
+
+ setSource := false
+ setDest := false
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "nosuid", "nodev", "noexec":
+ // TODO: detect duplication of these options
+ newMount.Options = append(newMount.Options, kv[0])
+ case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
+ // TODO: detect duplicate mount propagation options
+ newMount.Options = append(newMount.Options, kv[0])
+ case "bind-propagation":
+ // TODO: this should conflict with the mount propagation
+ // options above.
+ 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 := 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 := ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = kv[1]
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(badOptionError, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ if !setSource {
+ newMount.Source = newMount.Destination
+ }
+
+ // Process options
+ newMount.Options = processOptions(newMount.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,
+ }
+
+ setDest := false
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "nosuid", "nodev", "noexec":
+ // TODO: detect duplication of these options
+ newMount.Options = append(newMount.Options, kv[0])
+ case "tmpfs-mode":
+ // TODO: detect duplicate modes
+ 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":
+ // TODO: detect duplicate sizes
+ 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 := ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = kv[1]
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(badOptionError, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ return newMount, nil
+}
+
+// Parse a single volume mount entry from the --mount flag.
+// Note that the volume-label option for named volumes is currently NOT supported.
+// TODO: add support for --volume-label
+func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) {
+ newVolume := new(libpod.ContainerNamedVolume)
+
+ setSource := false
+ setDest := false
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "nosuid", "nodev", "noexec":
+ // TODO: detect duplication of these options
+ 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 := ValidateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ newVolume.Dest = kv[1]
+ setDest = true
+ default:
+ return nil, errors.Wrapf(badOptionError, kv[0])
+ }
+ }
+
+ if !setSource {
+ return nil, errors.Errorf("must set source volume")
+ }
+ if !setDest {
+ return nil, noDestError
+ }
+
+ return newVolume, nil
+}
+
+// ValidateVolumeHostDir validates a volume mount's source directory
+func ValidateVolumeHostDir(hostDir string) error {
+ if len(hostDir) == 0 {
+ return errors.Errorf("host directory cannot be empty")
+ }
+ if filepath.IsAbs(hostDir) {
+ if _, err := os.Stat(hostDir); err != nil {
+ return errors.Wrapf(err, "error checking path %q", hostDir)
+ }
+ }
+ // If hostDir is not an absolute path, that means the user wants to create a
+ // named volume. This will be done later on in the code.
+ return nil
+}
+
+// ValidateVolumeCtrDir validates a volume mount's destination directory.
+func ValidateVolumeCtrDir(ctrDir string) error {
+ if len(ctrDir) == 0 {
+ return errors.Errorf("container directory cannot be empty")
+ }
+ if !filepath.IsAbs(ctrDir) {
+ return errors.Errorf("invalid container path %q, must be an absolute path", ctrDir)
+ }
+ return nil
+}
+
+// ValidateVolumeOpts validates a volume's options
+func ValidateVolumeOpts(option string) error {
+ var foundRootPropagation, foundRWRO, foundLabelChange int
+ options := strings.Split(option, ",")
+ 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", option)
+ }
+ case "z", "Z":
+ foundLabelChange++
+ if foundLabelChange > 1 {
+ return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option)
+ }
+ 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", option)
+ }
+ default:
+ return errors.Errorf("invalid option type %q", option)
+ }
+ }
+ return nil
+}
+
+// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
+func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ volumeFormatErr := errors.Errorf("incorrect volume format, should be host-dir:ctr-dir[:option]")
+
+ for _, vol := range config.Volumes {
+ var (
+ options []string
+ src string
+ dest string
+ )
+
+ splitVol := strings.Split(vol, ":")
+ if len(splitVol) > 3 {
+ return nil, nil, errors.Wrapf(volumeFormatErr, vol)
+ }
+
+ src = splitVol[0]
+ if len(splitVol) == 1 {
+ dest = src
+ } else if len(splitVol) > 1 {
+ dest = splitVol[1]
+ }
+ if len(splitVol) > 2 {
+ if err := ValidateVolumeOpts(splitVol[2]); err != nil {
+ return nil, nil, err
+ }
+ options = strings.Split(splitVol[2], ",")
+ }
+
+ if err := ValidateVolumeHostDir(src); err != nil {
+ return nil, nil, err
+ }
+ if err := ValidateVolumeCtrDir(dest); err != nil {
+ return nil, nil, err
+ }
+
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ // This is not a named volume
+ newMount := spec.Mount{
+ Destination: dest,
+ Type: string(TypeBind),
+ Source: src,
+ Options: processOptions(options),
+ }
+ if _, ok := mounts[newMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
+ }
+ mounts[newMount.Destination] = newMount
+ } else {
+ // This is a named volume
+ newNamedVol := new(libpod.ContainerNamedVolume)
+ newNamedVol.Name = src
+ newNamedVol.Dest = dest
+ newNamedVol.Options = options
+
+ if _, ok := volumes[newNamedVol.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
+ }
+ volumes[newNamedVol.Dest] = newNamedVol
+ }
+
+ logrus.Debugf("User mount %s:%s options %v", src, dest, options)
+ }
+
+ return mounts, volumes, nil
+}
+
+// Get mounts for container's image volumes
+func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*libpod.ContainerNamedVolume)
+
+ if config.ImageVolumeType == "ignore" {
+ return mounts, volumes, nil
+ }
+
+ for vol := range config.BuiltinImgVolumes {
+ if config.ImageVolumeType == "tmpfs" {
+ // Tmpfs image volumes are handled as mounts
+ mount := spec.Mount{
+ Destination: vol,
+ Source: TypeTmpfs,
+ Type: TypeTmpfs,
+ Options: []string{"rprivate", "rw", "nodev"},
+ }
+ mounts[vol] = mount
+ } else {
+ namedVolume := new(libpod.ContainerNamedVolume)
+ namedVolume.Name = stringid.GenerateNonCryptoID()
+ namedVolume.Options = []string{"rprivate", "rw", "nodev"}
+ namedVolume.Dest = vol
+ volumes[vol] = namedVolume
+ }
+ }
+
+ return mounts, volumes, nil
+}
+
+// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
+func (config *CreateConfig) getTmpfsMounts() (map[string]spec.Mount, error) {
+ m := make(map[string]spec.Mount)
+ for _, i := range config.Tmpfs {
+ // Default options if nothing passed
+ options := []string{"rprivate", "rw", "noexec", "nosuid", "nodev", "size=65536k"}
+ spliti := strings.Split(i, ":")
+ destPath := spliti[0]
+ if len(spliti) > 1 {
+ options = strings.Split(spliti[1], ",")
+ }
+
+ if _, ok := m[destPath]; ok {
+ return nil, errors.Wrapf(errDuplicateDest, destPath)
+ }
+
+ // TODO: Do we want to set some default options if the user
+ // passed options?
+ // TODO: should we validate the options passed in?
+ mount := spec.Mount{
+ Destination: destPath,
+ Type: string(TypeTmpfs),
+ Options: options,
+ Source: string(TypeTmpfs),
+ }
+ m[destPath] = mount
+ }
+ return m, nil
+}
+
+// AddContainerInitBinary adds the init binary specified by path iff the
+// container will run in a private PID namespace that is not shared with the
+// host or another pre-existing container, where an init-like process is
+// already running.
+//
+// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
+// to execute the bind-mounted binary as PID 1.
+func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, error) {
+ mount := spec.Mount{
+ Destination: "/dev/init",
+ Type: TypeBind,
+ Source: path,
+ Options: []string{TypeBind, "ro"},
+ }
+
+ if path == "" {
+ return mount, fmt.Errorf("please specify a path to the container-init binary")
+ }
+ if !config.PidMode.IsPrivate() {
+ return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
+ }
+ if config.Systemd {
+ return mount, fmt.Errorf("cannot use container-init binary with systemd")
+ }
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ return mount, errors.Wrap(err, "container-init binary not found on the host")
+ }
+ config.Command = append([]string{"/dev/init", "--"}, config.Command...)
+ return mount, nil
+}
+
+// Handle options for volume mounts
+func processOptions(options []string) []string {
+ var (
+ foundrw, foundro bool
+ rootProp string
+ )
+ options = append(options, "rbind")
+ for _, opt := range options {
+ switch opt {
+ case "rw":
+ foundrw = true
+ case "ro":
+ foundro = true
+ case "private", "rprivate", "slave", "rslave", "shared", "rshared":
+ rootProp = opt
+ }
+ }
+ if !foundrw && !foundro {
+ options = append(options, "rw")
+ }
+ if rootProp == "" {
+ options = append(options, "rprivate")
+ }
+ return options
+}
+
+func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
+ if len(mounts) > 0 {
+ // If we have overlappings mounts, remove them from the spec in favor of
+ // the user-added volume mounts
+ destinations := make(map[string]bool)
+ for _, mount := range mounts {
+ destinations[path.Clean(mount.Destination)] = true
+ }
+ // Copy all mounts from spec to defaultMounts, except for
+ // - mounts overridden by a user supplied mount;
+ // - all mounts under /dev if a user supplied /dev is present;
+ mountDev := destinations["/dev"]
+ for _, mount := range configMount {
+ if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
+ if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
+ // filter out everything under /dev if /dev is user-mounted
+ continue
+ }
+
+ logrus.Debugf("Adding mount %s", mount.Destination)
+ mounts = append(mounts, mount)
+ }
+ }
+ return mounts
+ }
+ return configMount
+}
+
+// Ensure mount options on all mounts are correct
+func initFSMounts(inputMounts []spec.Mount) []spec.Mount {
+ var mounts []spec.Mount
+ for _, m := range inputMounts {
+ m.Options = processOptions(m.Options)
+ if m.Type == "tmpfs" {
+ m.Options = append(m.Options, "tmpcopyup")
+ } else {
+ mounts = append(mounts, m)
+ }
+ }
+ return mounts
+}