diff --git a/docs/source/markdown/options/mount.md b/docs/source/markdown/options/mount.md index ac8e1b058d..cc53331587 100644 --- a/docs/source/markdown/options/mount.md +++ b/docs/source/markdown/options/mount.md @@ -6,7 +6,7 @@ Attach a filesystem mount to the container -Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and **devpts**. [[1]](#Footnote1) +Current supported mount TYPEs are **bind**, **devpts**, **glob**, **image**, **tmpfs** and **volume**. [[1]](#Footnote1) e.g. @@ -16,6 +16,8 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true + type=glob,src=/usr/lib/libfoo*,destination=/usr/lib,ro=true + type=volume,source=vol1,destination=/path/in/container,ro=true type=tmpfs,tmpfs-size=512M,destination=/path/in/container @@ -26,10 +28,12 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and Common Options: - · src, source: mount source spec for bind and volume. Mandatory for bind. + · src, source: mount source spec for bind, glob, and volume. Mandatory for bind and glob. · dst, destination, target: mount destination spec. + Paths matching globs, are mounted on the destination directory with the identical name inside the container. + Options specific to volume: · ro, readonly: true or false (default). @@ -47,7 +51,7 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and · rw, readwrite: true or false (default). - Options specific to bind: + Options specific to bind and glob: · ro, readonly: true or false (default). diff --git a/docs/source/markdown/podman-create.1.md.in b/docs/source/markdown/podman-create.1.md.in index b247ce1e5c..0576675c5a 100644 --- a/docs/source/markdown/podman-create.1.md.in +++ b/docs/source/markdown/podman-create.1.md.in @@ -443,6 +443,12 @@ $ podman create --name container3 --requires container1,container2 -t -i fedora $ podman start --attach container3 ``` +### Exposing shared libraries inside of container as read-only using a glob + +``` +$ podman create --mount type=glob,src=/usr/lib64/libnvidia\*,ro -i -t fedora /bin/bash +``` + ### Configure keep supplemental groups for access to volume ``` diff --git a/docs/source/markdown/podman-run.1.md.in b/docs/source/markdown/podman-run.1.md.in index 95f3632996..a42ddd67f7 100644 --- a/docs/source/markdown/podman-run.1.md.in +++ b/docs/source/markdown/podman-run.1.md.in @@ -468,6 +468,12 @@ $ podman run --read-only -i -t fedora /bin/bash $ podman run --read-only --read-only-tmpfs=false --tmpfs /run -i -t fedora /bin/bash ``` +### Exposing shared libraries inside of container as read-only using a glob + +``` +$ podman run --mount type=glob,src=/usr/lib64/libnvidia\*,ro=true -i -t fedora /bin/bash +``` + ### Exposing log messages from the container to the host's log Bind mount the _/dev/log_ directory to have messages that are logged in the container show up in the host's diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go index 72859cc727..d3751cb29d 100644 --- a/pkg/specgenutil/volumes.go +++ b/pkg/specgenutil/volumes.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "path" + "path/filepath" "strings" "github.com/containers/common/pkg/config" @@ -18,7 +19,7 @@ import ( var ( errOptionArg = errors.New("must provide an argument for option") errNoDest = errors.New("must set volume destination") - errInvalidSyntax = errors.New("incorrect mount format: should be --mount type=,[src=,]target=[,options]") + errInvalidSyntax = errors.New("incorrect mount format: should be --mount type=,[src=,]target=[,options]") ) // Parse all volume-related options in the create config into a set of mounts @@ -196,6 +197,20 @@ func Mounts(mountFlag []string, configMounts []string) (map[string]spec.Mount, m return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest) } finalMounts[mount.Destination] = mount + case "glob": + mounts, err := getGlobMounts(tokens) + if err != nil { + return err + } + for _, mount := range mounts { + if _, ok := finalMounts[mount.Destination]; ok { + if ignoreDup { + continue + } + return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest) + } + finalMounts[mount.Destination] = mount + } case define.TypeTmpfs: mount, err := getTmpfsMount(tokens) if err != nil { @@ -438,12 +453,49 @@ func parseMountOptions(mountType string, args []string) (*spec.Mount, error) { return nil, fmt.Errorf("%s: %w", kv[0], util.ErrBadMntOption) } } - if len(mnt.Destination) == 0 { + if mountType != "glob" && len(mnt.Destination) == 0 { return nil, errNoDest } return &mnt, nil } +// Parse glob mounts entry from the --mount flag. +func getGlobMounts(args []string) ([]spec.Mount, error) { + mounts := []spec.Mount{} + + mnt, err := parseMountOptions("glob", args) + if err != nil { + return nil, err + } + + globs, err := filepath.Glob(mnt.Source) + if err != nil { + return nil, err + } + if len(globs) == 0 { + return nil, fmt.Errorf("no file paths matching glob %q", mnt.Source) + } + + options, err := parse.ValidateVolumeOpts(mnt.Options) + if err != nil { + return nil, err + } + for _, src := range globs { + var newMount spec.Mount + newMount.Type = define.TypeBind + newMount.Options = options + newMount.Source = src + if len(mnt.Destination) == 0 { + newMount.Destination = src + } else { + newMount.Destination = filepath.Join(mnt.Destination, filepath.Base(src)) + } + mounts = append(mounts, newMount) + } + + return mounts, nil +} + // Parse a single bind mount entry from the --mount flag. func getBindMount(args []string) (spec.Mount, error) { newMount := spec.Mount{ diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index dd823f79c6..d593bf8e24 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -664,7 +664,7 @@ func ConvertContainer(container *parser.UnitFile, names map[string]string, isUse paramsMap[kv[0]] = kv[1] } if paramType, ok := paramsMap["type"]; ok { - if paramType == "volume" || paramType == "bind" { + if paramType == "volume" || paramType == "bind" || paramType == "glob" { var err error if paramSource, ok := paramsMap["source"]; ok { paramsMap["source"], err = handleStorageSource(container, service, paramSource, names) diff --git a/test/system/060-mount.bats b/test/system/060-mount.bats index c59b3e1d05..918b5e1189 100644 --- a/test/system/060-mount.bats +++ b/test/system/060-mount.bats @@ -247,4 +247,43 @@ EOF buildah rm $external_cid } +@test "podman volume globs" { + v1a=v1_$(random_string) + v1b=v1_$(random_string) + v2=v2_$(random_string) + vol1a=${PODMAN_TMPDIR}/$v1a + vol1b=${PODMAN_TMPDIR}/$v1b + vol2=${PODMAN_TMPDIR}/$v2 + touch $vol1a $vol1b $vol2 + + # if volumes source and dest match then pass + run_podman run --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,ro $IMAGE ls $vol1a $vol1b + run_podman 1 run --rm --mount source=${PODMAN_TMPDIR}/v1\*,type=glob,ro $IMAGE ls $vol2 + is "$output" ".*No such file or directory" "$vol2 should not be mounted in the container" + + run_podman 125 run --rm --mount source=${PODMAN_TMPDIR}/v3\*,type=glob,ro $IMAGE ls $vol2 + is "$output" "Error: no file paths matching glob \"${PODMAN_TMPDIR}/v3\*\"" "Glob does not match so should throw error" + + run_podman 1 run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=glob,ro,Z $IMAGE touch $vol2 + is "$output" "touch: $vol2: Read-only file system" "Mount should be read-only" + + run_podman run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=glob,ro=false,Z $IMAGE touch $vol2 + + run_podman run --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,destination=/non/existing/directory,ro $IMAGE ls /non/existing/directory + is "$output" ".*$v1a" "podman images --inspect should include $v1a" + is "$output" ".*$v1b" "podman images --inspect should include $v1b" + + run_podman create --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,ro $IMAGE ls $vol1a $vol1b + cid=$output + run_podman container inspect $output + is "$output" ".*$vol1a" "podman images --inspect should include $vol1a" + is "$output" ".*$vol1b" "podman images --inspect should include $vol1b" + + run_podman 125 run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=bind,ro=false $IMAGE touch $vol2 + is "$output" "Error: must set volume destination" "Bind mounts require destination" + + run_podman 125 run --rm --mount source=${PODMAN_TMPDIR}/v2\*,destination=/tmp/foobar, ro=false $IMAGE touch $vol2 + is "$output" "Error: invalid reference format" "Default mounts don not support globs" +} + # vim: filetype=sh