From 4bbe6e7cc0986f07c23f9939ceebb466b2210c70 Mon Sep 17 00:00:00 2001 From: umohnani8 Date: Thu, 22 Feb 2018 12:41:22 -0500 Subject: [PATCH] Implement --volume and --shm-size for bud and from Add the remaining --volume and --shm-size flags to buildah bud and from --volume supports the following options: rw, ro, z, Z, private, slave, shared Signed-off-by: umohnani8 Closes: #491 Approved by: rhatdan --- buildah.go | 4 +++ cmd/buildah/common.go | 82 +++++++++++++++++++++++++++++++++++++++++++ docs/buildah-bud.md | 73 ++++++++++++++++++++++++++++++++++++++ docs/buildah-from.md | 73 ++++++++++++++++++++++++++++++++++++++ run.go | 60 +++++++++++++++++++++++++++++-- tests/from.bats | 27 ++++++++++++++ 6 files changed, 316 insertions(+), 3 deletions(-) diff --git a/buildah.go b/buildah.go index 097b15252..79a7fdb7d 100644 --- a/buildah.go +++ b/buildah.go @@ -161,8 +161,12 @@ type CommonBuildOptions struct { LabelOpts []string SeccompProfilePath string ApparmorProfile string + //ShmSize is the shared memory size + ShmSize string //Ulimit options Ulimit []string + //Volumes to bind mount into the container + Volumes []string } // BuilderOptions are used to initialize a new Builder. diff --git a/cmd/buildah/common.go b/cmd/buildah/common.go index 855ea1728..ec42f6494 100644 --- a/cmd/buildah/common.go +++ b/cmd/buildah/common.go @@ -268,10 +268,19 @@ var fromAndBudFlags = []cli.Flag{ Name: "security-opt", Usage: "security Options (default [])", }, + cli.StringFlag{ + Name: "shm-size", + Usage: "size of `/dev/shm`. The format is ``.", + Value: "65536k", + }, cli.StringSliceFlag{ Name: "ulimit", Usage: "ulimit options (default [])", }, + cli.StringSliceFlag{ + Name: "volume, v", + Usage: "bind mount a volume into the container (default [])", + }, } func parseCommonBuildOptions(c *cli.Context) (*buildah.CommonBuildOptions, error) { @@ -299,6 +308,12 @@ func parseCommonBuildOptions(c *cli.Context) (*buildah.CommonBuildOptions, error } } } + if _, err := units.FromHumanSize(c.String("shm-size")); err != nil { + return nil, errors.Wrapf(err, "invalid --shm-size") + } + if err := parseVolumes(c.StringSlice("volume")); err != nil { + return nil, err + } commonOpts := &buildah.CommonBuildOptions{ AddHost: c.StringSlice("add-host"), @@ -310,7 +325,9 @@ func parseCommonBuildOptions(c *cli.Context) (*buildah.CommonBuildOptions, error CPUShares: c.Uint64("cpu-shares"), Memory: memoryLimit, MemorySwap: memorySwap, + ShmSize: c.String("shm-size"), Ulimit: c.StringSlice("ulimit"), + Volumes: c.StringSlice("volume"), } if err := parseSecurityOpts(c.StringSlice("security-opt"), commonOpts); err != nil { return nil, err @@ -360,6 +377,71 @@ func parseSecurityOpts(securityOpts []string, commonOpts *buildah.CommonBuildOpt return nil } +func parseVolumes(volumes []string) error { + if len(volumes) == 0 { + return nil + } + 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 validateVolumeHostDir(hostDir string) error { + if _, err := os.Stat(hostDir); err != nil { + return errors.Wrapf(err, "error checking path %q", hostDir) + } + return nil +} + +func validateVolumeCtrDir(ctrDir string) error { + if ctrDir[0] != '/' { + return errors.Errorf("invalid container directory 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": + if foundRWRO > 1 { + return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", option) + } + foundRWRO++ + case "z", "Z": + if foundLabelChange > 1 { + return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option) + } + foundLabelChange++ + case "private", "rprivate", "shared", "rshared", "slave", "rslave": + if foundRootPropagation > 1 { + return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", option) + } + foundRootPropagation++ + default: + return errors.Errorf("invalid option type %q", option) + } + } + return nil +} + // validateExtraHost validates that the specified string is a valid extrahost and returns it. // ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). // for add-host flag diff --git a/docs/buildah-bud.md b/docs/buildah-bud.md index 783b2437c..716e292db 100644 --- a/docs/buildah-bud.md +++ b/docs/buildah-bud.md @@ -194,6 +194,12 @@ Security Options "apparmor=unconfined" : Turn off apparmor confinement for the container "apparmor=your-profile" : Set the apparmor confinement profile for the container +**--shm-size**="" + +Size of `/dev/shm`. The format is ``. `number` must be greater than `0`. +Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes). +If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`. + **--signature-policy** *signaturepolicy* Pathname of a signature policy file to use. It is not recommended that this @@ -213,6 +219,71 @@ Require HTTPS and verify certificates when talking to container registries (defa Ulimit options +**-v**|**--volume**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] + + Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman + bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman + container. The `OPTIONS` are a comma delimited list and can be: + + * [rw|ro] + * [z|Z] + * [`[r]shared`|`[r]slave`|`[r]private`] + +The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` +must be an absolute path as well. podman bind-mounts the `HOST-DIR` to the +path you specify. For example, if you supply the `/foo` value, podman creates a bind-mount. + +You can specify multiple **-v** options to mount one or more mounts to a +container. + +You can add `:ro` or `:rw` suffix to a volume to mount it read-only or +read-write mode, respectively. By default, the volumes are mounted read-write. +See examples. + +Labeling systems like SELinux require that proper labels are placed on volume +content mounted into a container. Without a label, the security system might +prevent the processes running inside the container from using the content. By +default, podman does not change the labels set by the OS. + +To change a label in the container context, you can add either of two suffixes +`:z` or `:Z` to the volume mount. These suffixes tell podman to relabel file +objects on the shared volumes. The `z` option tells podman that two containers +share the volume content. As a result, podman labels the content with a shared +content label. Shared volume labels allow all containers to read/write content. +The `Z` option tells podman to label the content with a private unshared label. +Only the current container can use a private volume. + +By default bind mounted volumes are `private`. That means any mounts done +inside container will not be visible on the host and vice versa. This behavior can +be changed by specifying a volume mount propagation property. + +When the mount propagation policy is set to `shared`, any mounts completed inside +the container on that volume will be visible to both the host and container. When +the mount propagation policy is set to `slave`, one way mount propagation is enabled +and any mounts completed on the host for that volume will be visible only inside of the container. +To control the mount propagation property of volume use the `:[r]shared`, +`:[r]slave` or `:[r]private` propagation flag. The propagation property can +be specified only for bind mounted volumes and not for internal volumes or +named volumes. For mount propagation to work on the source mount point (mount point +where source dir is mounted on) has to have the right propagation properties. For +shared volumes, the source mount point has to be shared. And for slave volumes, +the source mount has to be either shared or slave. + +Use `df ` to determine the source mount and then use +`findmnt -o TARGET,PROPAGATION ` to determine propagation +properties of source mount, if `findmnt` utility is not available, the source mount point +can be determined by looking at the mount entry in `/proc/self/mountinfo`. Look +at `optional fields` and see if any propagaion properties are specified. +`shared:X` means the mount is `shared`, `master:X` means the mount is `slave` and if +nothing is there that means the mount is `private`. + +To change propagation properties of a mount point use the `mount` command. For +example, to bind mount the source directory `/foo` do +`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This +will convert /foo into a `shared` mount point. The propagation properties of the source +mount can be changed directly. For instance if `/` is the source mount for +`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount. + ## EXAMPLE buildah bud . @@ -237,5 +308,7 @@ buildah bud --memory 40m --cpu-period 10000 --cpu-quota 50000 --ulimit nofile=10 buildah bud --security-opt label=level:s0:c100,c200 --cgroup-parent /path/to/cgroup/parent -t imageName . +buildah bud --volume /home/test:/myvol:ro,Z -t imageName . + ## SEE ALSO buildah(1), podman-login(1), docker-login(1) diff --git a/docs/buildah-from.md b/docs/buildah-from.md index afba8a67a..b39b302a1 100644 --- a/docs/buildah-from.md +++ b/docs/buildah-from.md @@ -181,6 +181,12 @@ Security Options "apparmor=unconfined" : Turn off apparmor confinement for the container "apparmor=your-profile" : Set the apparmor confinement profile for the container +**--shm-size**="" + +Size of `/dev/shm`. The format is ``. `number` must be greater than `0`. +Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes). +If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`. + **--signature-policy** *signaturepolicy* Pathname of a signature policy file to use. It is not recommended that this @@ -195,6 +201,71 @@ Require HTTPS and verify certificates when talking to container registries (defa Ulimit options +**-v**|**--volume**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] + + Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman + bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman + container. The `OPTIONS` are a comma delimited list and can be: + + * [rw|ro] + * [z|Z] + * [`[r]shared`|`[r]slave`|`[r]private`] + +The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` +must be an absolute path as well. podman bind-mounts the `HOST-DIR` to the +path you specify. For example, if you supply the `/foo` value, podman creates a bind-mount. + +You can specify multiple **-v** options to mount one or more mounts to a +container. + +You can add `:ro` or `:rw` suffix to a volume to mount it read-only or +read-write mode, respectively. By default, the volumes are mounted read-write. +See examples. + +Labeling systems like SELinux require that proper labels are placed on volume +content mounted into a container. Without a label, the security system might +prevent the processes running inside the container from using the content. By +default, podman does not change the labels set by the OS. + +To change a label in the container context, you can add either of two suffixes +`:z` or `:Z` to the volume mount. These suffixes tell podman to relabel file +objects on the shared volumes. The `z` option tells podman that two containers +share the volume content. As a result, podman labels the content with a shared +content label. Shared volume labels allow all containers to read/write content. +The `Z` option tells podman to label the content with a private unshared label. +Only the current container can use a private volume. + +By default bind mounted volumes are `private`. That means any mounts done +inside container will not be visible on the host and vice versa. This behavior can +be changed by specifying a volume mount propagation property. + +When the mount propagation policy is set to `shared`, any mounts completed inside +the container on that volume will be visible to both the host and container. When +the mount propagation policy is set to `slave`, one way mount propagation is enabled +and any mounts completed on the host for that volume will be visible only inside of the container. +To control the mount propagation property of volume use the `:[r]shared`, +`:[r]slave` or `:[r]private` propagation flag. The propagation property can +be specified only for bind mounted volumes and not for internal volumes or +named volumes. For mount propagation to work on the source mount point (mount point +where source dir is mounted on) has to have the right propagation properties. For +shared volumes, the source mount point has to be shared. And for slave volumes, +the source mount has to be either shared or slave. + +Use `df ` to determine the source mount and then use +`findmnt -o TARGET,PROPAGATION ` to determine propagation +properties of source mount, if `findmnt` utility is not available, the source mount point +can be determined by looking at the mount entry in `/proc/self/mountinfo`. Look +at `optional fields` and see if any propagaion properties are specified. +`shared:X` means the mount is `shared`, `master:X` means the mount is `slave` and if +nothing is there that means the mount is `private`. + +To change propagation properties of a mount point use the `mount` command. For +example, to bind mount the source directory `/foo` do +`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This +will convert /foo into a `shared` mount point. The propagation properties of the source +mount can be changed directly. For instance if `/` is the source mount for +`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount. + ## EXAMPLE buildah from imagename --pull @@ -215,5 +286,7 @@ buildah from --memory 40m --cpu-shares 2 --cpuset-cpus 0,2 --security-opt label= buildah from --ulimit nofile=1024:1028 --cgroup-parent /path/to/cgroup/parent myregistry/myrepository/imagename:imagetag +buildah from --volume /home/test:/myvol:ro,Z myregistry/myrepository/imagename:imagetag + ## SEE ALSO buildah(1), podman-login(1), docker-login(1) diff --git a/run.go b/run.go index 627aae18e..8db497d7e 100644 --- a/run.go +++ b/run.go @@ -147,7 +147,7 @@ func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) return nil } -func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles, volumes []string) error { +func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts []specs.Mount, bindFiles, builtinVolumes, volumeMounts []string, shmSize string) error { // The passed-in mounts matter the most to us. mounts := make([]specs.Mount, len(optionMounts)) copy(mounts, optionMounts) @@ -162,6 +162,9 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts } // Add mounts from the generated list, unless they conflict. for _, specMount := range spec.Mounts { + if specMount.Destination == "/dev/shm" { + specMount.Options = []string{"nosuid", "noexec", "nodev", "mode=1777", "size=" + shmSize} + } if haveMount(specMount.Destination) { // Already have something to mount there, so skip this one. continue @@ -204,7 +207,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts } // Add temporary copies of the contents of volume locations at the // volume locations, unless we already have something there. - for _, volume := range volumes { + for _, volume := range builtinVolumes { if haveMount(volume) { // Already mounting something there, no need to bother. continue @@ -233,6 +236,57 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts Options: []string{"bind"}, }) } + // Bind mount volumes given by the user at execution + var options []string + for _, i := range volumeMounts { + spliti := strings.Split(i, ":") + if len(spliti) > 2 { + options = strings.Split(spliti[2], ",") + } + if haveMount(spliti[1]) { + continue + } + options = append(options, "rbind") + var foundrw, foundro, foundz, foundZ bool + var rootProp string + 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 { + if err := label.Relabel(spliti[0], spec.Linux.MountLabel, true); err != nil { + return errors.Wrapf(err, "relabel failed %q", spliti[0]) + } + } + if foundZ { + if err := label.Relabel(spliti[0], spec.Linux.MountLabel, false); err != nil { + return errors.Wrapf(err, "relabel failed %q", spliti[0]) + } + } + if rootProp == "" { + options = append(options, "private") + } + + mounts = append(mounts, specs.Mount{ + Destination: spliti[1], + Type: "bind", + Source: spliti[0], + Options: options, + }) + } // Set the list in the spec. spec.Mounts = mounts return nil @@ -381,7 +435,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { g.AddMount(cgroupMnt) bindFiles := []string{"/etc/hosts", "/etc/resolv.conf"} - err = b.setupMounts(mountPoint, spec, options.Mounts, bindFiles, b.Volumes()) + err = b.setupMounts(mountPoint, spec, options.Mounts, bindFiles, b.Volumes(), b.CommonBuildOpts.Volumes, b.CommonBuildOpts.ShmSize) if err != nil { return errors.Wrapf(err, "error resolving mountpoints for container") } diff --git a/tests/from.bats b/tests/from.bats index 21a8d8523..861190820 100644 --- a/tests/from.bats +++ b/tests/from.bats @@ -182,3 +182,30 @@ load helpers [[ "$output" =~ 41943040 ]] buildah rm $cid } + +@test "from volume test" { + cid=$(buildah from --volume=${TESTDIR}:/myvol --pull --signature-policy ${TESTSDIR}/policy.json alpine) + run buildah run $cid -- cat /proc/mounts + echo $output + [ "$status" -eq 0 ] + [[ "$output" =~ /myvol ]] + buildah rm $cid +} + +@test "from volume ro test" { + cid=$(buildah from --volume=${TESTDIR}:/myvol:ro --pull --signature-policy ${TESTSDIR}/policy.json alpine) + run buildah run $cid -- cat /proc/mounts + echo $output + [ "$status" -eq 0 ] + [[ "$output" =~ /myvol ]] + buildah rm $cid +} + +@test "from shm-size test" { + cid=$(buildah from --shm-size=80m --pull --signature-policy ${TESTSDIR}/policy.json alpine) + run buildah run $cid -- df -h + echo $output + [ "$status" -eq 0 ] + [[ "$output" =~ 80 ]] + buildah rm $cid +}