summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2018-09-21 06:29:18 -0400
committerAtomic Bot <atomic-devel@projectatomic.io>2018-09-21 21:33:41 +0000
commit52c1365f32398b8ba0321c159e739a5416cd9ab2 (patch)
tree8a700c6cc35d1955e973d18b3ed537c380290334
parent9e81f9daa4af9802088530a35a72814172430a36 (diff)
downloadpodman-52c1365f32398b8ba0321c159e739a5416cd9ab2.tar.gz
podman-52c1365f32398b8ba0321c159e739a5416cd9ab2.tar.bz2
podman-52c1365f32398b8ba0321c159e739a5416cd9ab2.zip
Add --mount option for `create` & `run` command
Signed-off-by: Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> Signed-off-by: Daniel J Walsh <dwalsh@redhat.com> Closes: #1524 Approved by: mheon
-rw-r--r--cmd/podman/common.go4
-rw-r--r--cmd/podman/create.go6
-rw-r--r--cmd/podman/create_cli.go90
-rw-r--r--completions/bash/podman1
-rw-r--r--docs/podman-create.1.md30
-rw-r--r--docs/podman-run.1.md38
-rw-r--r--libpod/container_internal.go9
-rw-r--r--libpod/container_internal_linux.go7
-rw-r--r--pkg/spec/createconfig.go76
-rw-r--r--pkg/spec/spec.go64
-rw-r--r--test/e2e/run_test.go39
11 files changed, 303 insertions, 61 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 8d20081f6..9ab0e57e5 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -418,6 +418,10 @@ var createFlags = []cli.Flag{
Usage: "UTS namespace to use",
},
cli.StringSliceFlag{
+ Name: "mount",
+ Usage: "Attach a filesystem mount to the container (default [])",
+ },
+ cli.StringSliceFlag{
Name: "volume, v",
Usage: "Bind mount a volume into the container (default [])",
},
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index ff912560b..fc0c71536 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -24,6 +24,7 @@ import (
"github.com/docker/docker/pkg/signal"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -459,6 +460,10 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
}
blkioWeight = uint16(u)
}
+ var mountList []spec.Mount
+ if mountList, err = parseMounts(c.StringSlice("mount")); err != nil {
+ return nil, err
+ }
if err = parseVolumes(c.StringSlice("volume")); err != nil {
return nil, err
@@ -772,6 +777,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
Tty: tty,
User: user,
UsernsMode: usernsMode,
+ Mounts: mountList,
Volumes: c.StringSlice("volume"),
WorkDir: workDir,
Rootfs: rootfs,
diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go
index 812b62058..218e9b806 100644
--- a/cmd/podman/create_cli.go
+++ b/cmd/podman/create_cli.go
@@ -8,6 +8,8 @@ import (
cc "github.com/containers/libpod/pkg/spec"
"github.com/docker/docker/pkg/sysinfo"
+ "github.com/docker/go-units"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -74,6 +76,94 @@ 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) {
+ 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, errInvalidSyntax
+ }
+ kv := strings.Split(arr[0], "=")
+ if kv[0] != "type" {
+ return nil, errInvalidSyntax
+ }
+ 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("can not 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)
diff --git a/completions/bash/podman b/completions/bash/podman
index de535512f..b97c4b0d5 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -945,6 +945,7 @@ _podman_build() {
--userns-uid-map-user
--userns-gid-map-group
--uts
+ --mount
--volume
-v
"
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index 01e072005..c42671b76 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -372,6 +372,36 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
+**--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]*
+
+Attach a filesystem mount to the container
+
+Current supported mount TYPES are bind, and tmpfs.
+
+ e.g.
+
+ type=bind,source=/path/on/host,destination=/path/in/container
+
+ type=tmpfs,tmpfs-size=512M,destination=/path/in/container
+
+ Common Options:
+
+ · src, source: mount source spec for bind and volume. Mandatory for bind.
+
+ · dst, destination, target: mount destination spec.
+
+ · ro, read-only: true or false (default).
+
+ Options specific to bind:
+
+ · bind-propagation: shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2).
+
+ Options specific to tmpfs:
+
+ · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux.
+
+ · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux.
+
**--name**=""
Assign a name to the container
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index a4c47f5de..fccebb7f7 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -655,6 +655,36 @@ Set the UTS mode for the container
**NOTE**: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
+**--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]*
+
+Attach a filesystem mount to the container
+
+Current supported mount TYPES are bind, and tmpfs.
+
+ e.g.
+
+ type=bind,source=/path/on/host,destination=/path/in/container
+
+ type=tmpfs,tmpfs-size=512M,destination=/path/in/container
+
+ Common Options:
+
+ · src, source: mount source spec for bind and volume. Mandatory for bind.
+
+ · dst, destination, target: mount destination spec.
+
+ · ro, read-only: true or false (default).
+
+ Options specific to bind:
+
+ · bind-propagation: Z, z, shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2).
+
+ Options specific to tmpfs:
+
+ · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux.
+
+ · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux.
+
**-v**|**--volume**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*]
Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman
@@ -931,6 +961,12 @@ colon:
$ podman run -v /var/db:/data1 -i -t fedora bash
```
+Using --mount flags, To mount a host directory as a container folder, specify
+the absolute path to the directory and the absolute path for the container
+directory:
+
+$ podman run --mount type=bind,src=/var/db,target=/data1 busybox sh
+
When using SELinux, be aware that the host has no knowledge of container SELinux
policy. Therefore, in the above example, if SELinux policy is enforced, the
`/var/db` directory is not writable to the container. A "Permission Denied"
@@ -1030,6 +1066,8 @@ $ podman run --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello
subgid(5), subuid(5), libpod.conf(5)
## HISTORY
+September 2018, updated by Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
+
October 2017, converted from Docker documentation to podman by Dan Walsh for podman <dwalsh@redhat.com>
November 2015, updated by Sally O'Malley <somalley@redhat.com>
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index e5e871d6f..c88794212 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -926,6 +926,9 @@ func (c *Container) makeBindMounts() error {
if err != nil {
return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID())
}
+ if err = label.Relabel(newResolv, c.config.MountLabel, false); err != nil {
+ return errors.Wrapf(err, "error relabeling %q for container %q", newResolv, c.ID)
+ }
c.state.BindMounts["/etc/resolv.conf"] = newResolv
// Make /etc/hosts
@@ -937,6 +940,9 @@ func (c *Container) makeBindMounts() error {
if err != nil {
return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
}
+ if err = label.Relabel(newHosts, c.config.MountLabel, false); err != nil {
+ return errors.Wrapf(err, "error relabeling %q for container %q", newHosts, c.ID)
+ }
c.state.BindMounts["/etc/hosts"] = newHosts
// Make /etc/hostname
@@ -946,6 +952,9 @@ func (c *Container) makeBindMounts() error {
if err != nil {
return errors.Wrapf(err, "error creating hostname file for container %s", c.ID())
}
+ if err = label.Relabel(hostnamePath, c.config.MountLabel, false); err != nil {
+ return errors.Wrapf(err, "error relabeling %q for container %q", hostnamePath, c.ID)
+ }
c.state.BindMounts["/etc/hostname"] = hostnamePath
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index b77beaf64..553a612b3 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -283,6 +283,13 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
mounts := sortMounts(g.Mounts())
g.ClearMounts()
for _, m := range mounts {
+ switch m.Type {
+ case "tmpfs", "devpts":
+ o := label.FormatMountLabel("", c.config.MountLabel)
+ if o != "" {
+ m.Options = append(m.Options, o)
+ }
+ }
g.AddMount(m)
}
return g.Config, nil
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index a9c7d2967..887ef8e95 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -122,6 +122,7 @@ type CreateConfig struct {
UsernsMode namespaces.UsernsMode //userns
User string //user
UtsMode namespaces.UTSMode //uts
+ Mounts []spec.Mount //mounts
Volumes []string //volume
VolumesFrom []string
WorkDir string //workdir
@@ -142,54 +143,59 @@ func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) {
return c.createBlockIO()
}
+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) {
var m []spec.Mount
for _, i := range c.Volumes {
- var (
- options []string
- foundrw, foundro, foundz, foundZ bool
- rootProp string
- )
-
- // We need to handle SELinux options better here, specifically :Z
+ var options []string
spliti := strings.Split(i, ":")
if len(spliti) > 2 {
options = strings.Split(spliti[2], ",")
}
- options = append(options, "rbind")
- for _, opt := range options {
- switch opt {
- case "rw":
- foundrw = true
- case "ro":
- foundro = true
- case "z":
- foundz = true
- case "Z":
- foundZ = true
- case "private", "rprivate", "slave", "rslave", "shared", "rshared":
- rootProp = opt
- }
- }
- if !foundrw && !foundro {
- options = append(options, "rw")
- }
- if foundz {
- options = append(options, "z")
- }
- if foundZ {
- options = append(options, "Z")
- }
- if rootProp == "" {
- options = append(options, "rprivate")
- }
m = append(m, spec.Mount{
Destination: spliti[1],
Type: string(TypeBind),
Source: spliti[0],
- Options: options,
+ Options: processOptions(options),
})
logrus.Debugf("User mount %s:%s options %v", spliti[0], spliti[1], options)
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index e115bba7f..ad14ea65d 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -18,6 +18,34 @@ 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
+}
+
// CreateConfigToOCISpec parses information needed to create a container into an OCI runtime spec
func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
cgroupPerm := "ro"
@@ -246,6 +274,12 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
g.AddMount(tmpfsMnt)
}
+ for _, m := range config.Mounts {
+ if m.Type == "tmpfs" {
+ g.AddMount(m)
+ }
+ }
+
for name, val := range config.Env {
g.AddProcessEnv(name, val)
}
@@ -305,36 +339,14 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint
return nil, errors.Wrap(err, "error getting volume mounts from --volumes-from flag")
}
- mounts, err := config.GetVolumeMounts(configSpec.Mounts)
+ volumeMounts, err := config.GetVolumeMounts(configSpec.Mounts)
if err != nil {
return nil, errors.Wrapf(err, "error getting volume mounts")
}
- 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 configSpec.Mounts {
- 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)
- }
- }
- configSpec.Mounts = mounts
- }
+ configSpec.Mounts = supercedeUserMounts(volumeMounts, configSpec.Mounts)
+ //--mount
+ configSpec.Mounts = supercedeUserMounts(config.initFSMounts(), configSpec.Mounts)
if canAddResources {
// BLOCK IO
blkio, err := config.CreateBlockIO()
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 3d487db66..baaca6333 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -234,6 +234,32 @@ var _ = Describe("Podman run", func() {
Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,relatime, shared"))
})
+ It("podman run with mount flag", func() {
+ mountPath := filepath.Join(podmanTest.TempDir, "secrets")
+ os.Mkdir(mountPath, 0755)
+ session := podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("/run/test rw"))
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,ro", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("/run/test ro"))
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,shared", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,relatime shared"))
+
+ mountPath = filepath.Join(podmanTest.TempDir, "scratchpad")
+ os.Mkdir(mountPath, 0755)
+ session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=/run/test", ALPINE, "grep", "/run/test", "/proc/self/mountinfo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,nosuid,nodev,noexec,relatime - tmpfs"))
+ })
+
It("podman run with cidfile", func() {
session := podmanTest.Podman([]string{"run", "--cidfile", tempdir + "cidfile", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
@@ -565,6 +591,19 @@ USER mail`
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman run --mount flag with multiple mounts", func() {
+ vol1 := filepath.Join(podmanTest.TempDir, "vol-test1")
+ err := os.MkdirAll(vol1, 0755)
+ Expect(err).To(BeNil())
+ vol2 := filepath.Join(podmanTest.TempDir, "vol-test2")
+ err = os.MkdirAll(vol2, 0755)
+ Expect(err).To(BeNil())
+
+ session := podmanTest.Podman([]string{"run", "--mount", "type=bind,src=" + vol1 + ",target=/myvol1,z", "--mount", "type=bind,src=" + vol2 + ",target=/myvol2,z", ALPINE, "touch", "/myvol2/foo.txt"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman run findmnt nothing shared", func() {
vol1 := filepath.Join(podmanTest.TempDir, "vol-test1")
err := os.MkdirAll(vol1, 0755)