Merge pull request #12687 from rhatdan/volume

Support volume bind mounts for rootless containers
This commit is contained in:
OpenShift Merge Robot 2022-01-06 15:16:13 +01:00 committed by GitHub
commit 755b7aa521
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 47 deletions

View file

@ -762,7 +762,7 @@ func (c *Container) export(path string) error {
if !c.state.Mounted {
containerMount, err := c.runtime.store.Mount(c.ID(), c.config.MountLabel)
if err != nil {
return errors.Wrapf(err, "error mounting container %q", c.ID())
return errors.Wrapf(err, "mounting container %q", c.ID())
}
mountPoint = containerMount
defer func() {

View file

@ -35,7 +35,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
volume := newVolume(r)
for _, option := range options {
if err := option(volume); err != nil {
return nil, errors.Wrapf(err, "error running volume create option")
return nil, errors.Wrapf(err, "running volume create option")
}
}
@ -50,7 +50,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
// Check if volume with given name exists.
exists, err := r.state.HasVolume(volume.config.Name)
if err != nil {
return nil, errors.Wrapf(err, "error checking if volume with name %s exists", volume.config.Name)
return nil, errors.Wrapf(err, "checking if volume with name %s exists", volume.config.Name)
}
if exists {
return nil, errors.Wrapf(define.ErrVolumeExists, "volume with name %s already exists", volume.config.Name)
@ -67,9 +67,15 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
if volume.config.Driver == define.VolumeDriverLocal {
logrus.Debugf("Validating options for local driver")
// Validate options
for key := range volume.config.Options {
switch key {
case "device", "o", "type", "UID", "GID", "SIZE", "INODES":
for key, val := range volume.config.Options {
switch strings.ToLower(key) {
case "device":
if strings.ToLower(volume.config.Options["type"]) == "bind" {
if _, err := os.Stat(val); err != nil {
return nil, errors.Wrapf(err, "invalid volume option %s for driver 'local'", key)
}
}
case "o", "type", "uid", "gid", "size", "inodes":
// Do nothing, valid keys
default:
return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key)
@ -92,17 +98,17 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
// Create the mountpoint of this volume
volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name)
if err := os.MkdirAll(volPathRoot, 0700); err != nil {
return nil, errors.Wrapf(err, "error creating volume directory %q", volPathRoot)
return nil, errors.Wrapf(err, "creating volume directory %q", volPathRoot)
}
if err := os.Chown(volPathRoot, volume.config.UID, volume.config.GID); err != nil {
return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID)
return nil, errors.Wrapf(err, "chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID)
}
fullVolPath := filepath.Join(volPathRoot, "_data")
if err := os.MkdirAll(fullVolPath, 0755); err != nil {
return nil, errors.Wrapf(err, "error creating volume directory %q", fullVolPath)
return nil, errors.Wrapf(err, "creating volume directory %q", fullVolPath)
}
if err := os.Chown(fullVolPath, volume.config.UID, volume.config.GID); err != nil {
return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID)
return nil, errors.Wrapf(err, "chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID)
}
if err := LabelVolumePath(fullVolPath); err != nil {
return nil, err
@ -132,7 +138,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
lock, err := r.lockManager.AllocateLock()
if err != nil {
return nil, errors.Wrapf(err, "error allocating lock for new volume")
return nil, errors.Wrapf(err, "allocating lock for new volume")
}
volume.lock = lock
volume.config.LockID = volume.lock.ID()
@ -149,7 +155,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
// Add the volume to state
if err := r.state.AddVolume(volume); err != nil {
return nil, errors.Wrapf(err, "error adding volume to state")
return nil, errors.Wrapf(err, "adding volume to state")
}
defer volume.newVolumeEvent(events.Create)
return volume, nil
@ -181,7 +187,7 @@ func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin
createReq.Name = name
createReq.Options = options
if err := plugin.CreateVolume(createReq); err != nil {
return errors.Wrapf(err, "error creating volume %q in plugin %s", name, plugin.Name)
return errors.Wrapf(err, "creating volume %q in plugin %s", name, plugin.Name)
}
}
@ -225,13 +231,13 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
continue
}
return errors.Wrapf(err, "error removing container %s that depends on volume %s", dep, v.Name())
return errors.Wrapf(err, "removing container %s that depends on volume %s", dep, v.Name())
}
logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name())
if err := r.removeContainer(ctx, ctr, force, false, false, timeout); err != nil {
return errors.Wrapf(err, "error removing container %s that depends on volume %s", ctr.ID(), v.Name())
return errors.Wrapf(err, "removing container %s that depends on volume %s", ctr.ID(), v.Name())
}
}
}
@ -244,7 +250,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
// them.
logrus.Errorf("Unmounting volume %s: %v", v.Name(), err)
} else {
return errors.Wrapf(err, "error unmounting volume %s", v.Name())
return errors.Wrapf(err, "unmounting volume %s", v.Name())
}
}
@ -288,13 +294,13 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
if removalErr != nil {
logrus.Errorf("Removing volume %s from plugin %s: %v", v.Name(), v.Driver(), removalErr)
}
return errors.Wrapf(err, "error removing volume %s", v.Name())
return errors.Wrapf(err, "removing volume %s", v.Name())
}
// Free the volume's lock
if err := v.lock.Free(); err != nil {
if removalErr == nil {
removalErr = errors.Wrapf(err, "error freeing lock for volume %s", v.Name())
removalErr = errors.Wrapf(err, "freeing lock for volume %s", v.Name())
} else {
logrus.Errorf("Freeing lock for volume %q: %v", v.Name(), err)
}
@ -304,7 +310,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo
// from /var/lib/containers/storage/volumes
if err := v.teardownStorage(); err != nil {
if removalErr == nil {
removalErr = errors.Wrapf(err, "error cleaning up volume storage for %q", v.Name())
removalErr = errors.Wrapf(err, "cleaning up volume storage for %q", v.Name())
} else {
logrus.Errorf("Cleaning up volume storage for volume %q: %v", v.Name(), err)
}

View file

@ -81,7 +81,7 @@ func (v *Volume) save() error {
func (v *Volume) refresh() error {
lock, err := v.runtime.lockManager.AllocateAndRetrieveLock(v.config.LockID)
if err != nil {
return errors.Wrapf(err, "error acquiring lock %d for volume %s", v.config.LockID, v.Name())
return errors.Wrapf(err, "acquiring lock %d for volume %s", v.config.LockID, v.Name())
}
v.lock = lock

View file

@ -7,7 +7,6 @@ import (
"strings"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/rootless"
pluginapi "github.com/docker/go-plugins-helpers/volume"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -32,13 +31,6 @@ func (v *Volume) mount() error {
return nil
}
// We cannot mount 'local' volumes as rootless.
if !v.UsesVolumeDriver() && rootless.IsRootless() {
// This check should only be applied to 'local' driver
// so Volume Drivers must be excluded
return errors.Wrapf(define.ErrRootless, "cannot mount volumes without root privileges")
}
// Update the volume from the DB to get an accurate mount counter.
if err := v.update(); err != nil {
return err
@ -90,22 +82,27 @@ func (v *Volume) mount() error {
// TODO: might want to cache this path in the runtime?
mountPath, err := exec.LookPath("mount")
if err != nil {
return errors.Wrapf(err, "error locating 'mount' binary")
return errors.Wrapf(err, "locating 'mount' binary")
}
mountArgs := []string{}
if volOptions != "" {
mountArgs = append(mountArgs, "-o", volOptions)
}
if volType != "" {
switch volType {
case "":
case "bind":
mountArgs = append(mountArgs, "-o", volType)
default:
mountArgs = append(mountArgs, "-t", volType)
}
mountArgs = append(mountArgs, volDevice, v.config.MountPoint)
mountCmd := exec.Command(mountPath, mountArgs...)
logrus.Debugf("Running mount command: %s %s", mountPath, strings.Join(mountArgs, " "))
if output, err := mountCmd.CombinedOutput(); err != nil {
logrus.Debugf("Mount %v failed with %v", mountCmd, err)
return errors.Wrapf(errors.Errorf(string(output)), "error mounting volume %s", v.Name())
return errors.Errorf(string(output))
}
logrus.Debugf("Mounted volume %s", v.Name())
@ -139,20 +136,6 @@ func (v *Volume) unmount(force bool) error {
return nil
}
// We cannot unmount 'local' volumes as rootless.
if !v.UsesVolumeDriver() && rootless.IsRootless() {
// If force is set, just clear the counter and bail without
// error, so we can remove volumes from the state if they are in
// an awkward configuration.
if force {
logrus.Errorf("Volume %s is mounted despite being rootless - state is not sane", v.Name())
v.state.MountCount = 0
return v.save()
}
return errors.Wrapf(define.ErrRootless, "cannot mount or unmount volumes without root privileges")
}
if !force {
v.state.MountCount--
} else {
@ -184,7 +167,7 @@ func (v *Volume) unmount(force bool) error {
// Ignore EINVAL - the mount no longer exists.
return nil
}
return errors.Wrapf(err, "error unmounting volume %s", v.Name())
return errors.Wrapf(err, "unmounting volume %s", v.Name())
}
logrus.Debugf("Unmounted volume %s", v.Name())
}

View file

@ -319,5 +319,30 @@ EOF
is "$output" "" "no more volumes to prune"
}
@test "podman volume type=bind" {
myvoldir=${PODMAN_TMPDIR}/volume_$(random_string)
mkdir $myvoldir
touch $myvoldir/myfile
myvolume=myvol$(random_string)
run_podman 125 volume create -o type=bind -o device=/bogus $myvolume
is "$output" "Error: invalid volume option device for driver 'local': stat /bogus: no such file or directory" "should fail with bogus directory not existing"
run_podman volume create -o type=bind -o device=/$myvoldir $myvolume
is "$output" "$myvolume" "should successfully create myvolume"
run_podman run --rm -v $myvolume:/vol:z $IMAGE \
stat -c "%u:%s" /vol/myfile
is "$output" "0:0" "w/o keep-id: stat(file in container) == root"
}
@test "podman volume type=tmpfs" {
myvolume=myvol$(random_string)
run_podman volume create -o type=tmpfs -o device=tmpfs $myvolume
is "$output" "$myvolume" "should successfully create myvolume"
run_podman run --rm -v $myvolume:/vol $IMAGE stat -f -c "%T" /vol
is "$output" "tmpfs" "volume should be tmpfs"
}
# vim: filetype=sh

View file

@ -63,7 +63,7 @@ function basic_setup() {
for line in "${lines[@]}"; do
set $line
echo "# setup(): removing stray external container $1 ($2)" >&3
run_podman rm $1
run_podman rm -f $1
done
# Clean up all images except those desired