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:
parent
cf1321f670
commit
0fefcf8a4f
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue