Add glob support to podman run/create --mount

HPC Community asked for this support specifically for using GPUs
within containers. Nvidia requires the correct shared library to
to be present in the directory that matches the device mounted
into the container. These libraries have random suffixes based
on versions of the installed libraries on the host.

podman run --mount type=glob:src=/usr/lib64/nvidia\*:ro=true. This helps
quadlets be more portable for this use case.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh 2023-07-26 09:23:37 -04:00
parent cf1321f670
commit 0fefcf8a4f
No known key found for this signature in database
GPG Key ID: A2DF901DABE2C028
6 changed files with 113 additions and 6 deletions

View File

@ -6,7 +6,7 @@
Attach a filesystem mount to the container
Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and **devpts**. <sup>[[1]](#Footnote1)</sup>
Current supported mount TYPEs are **bind**, **devpts**, **glob**, **image**, **tmpfs** and **volume**. <sup>[[1]](#Footnote1)</sup>
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).

View File

@ -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
```

View File

@ -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

View File

@ -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=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
errInvalidSyntax = errors.New("incorrect mount format: should be --mount type=<bind|glob|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,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{

View File

@ -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)

View File

@ -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