From 3dc1b8e83f2459a58d65f3bb918975cd6f1bb794 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 16 Feb 2022 06:04:22 -0500 Subject: Add podman volume mount support Fixes: https://github.com/containers/podman/issues/12768 Signed-off-by: Daniel J Walsh --- cmd/podman/volumes/exists.go | 2 +- cmd/podman/volumes/mount.go | 51 +++++++++++++++++++++++++ cmd/podman/volumes/unmount.go | 48 +++++++++++++++++++++++ docs/source/markdown/podman-volume-mount.1.md | 28 ++++++++++++++ docs/source/markdown/podman-volume-unmount.1.md | 27 +++++++++++++ docs/source/markdown/podman-volume.1.md | 2 + libpod/volume.go | 13 +++++++ libpod/volume_internal_linux.go | 1 + pkg/domain/entities/engine_container.go | 2 + pkg/domain/entities/volumes.go | 14 +++++++ pkg/domain/infra/abi/volumes.go | 32 ++++++++++++++++ pkg/domain/infra/tunnel/volumes.go | 8 ++++ test/system/160-volumes.bats | 25 ++++++++++++ 13 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 cmd/podman/volumes/mount.go create mode 100644 cmd/podman/volumes/unmount.go create mode 100644 docs/source/markdown/podman-volume-mount.1.md create mode 100644 docs/source/markdown/podman-volume-unmount.1.md diff --git a/cmd/podman/volumes/exists.go b/cmd/podman/volumes/exists.go index 3cac27220..b1bdf9da5 100644 --- a/cmd/podman/volumes/exists.go +++ b/cmd/podman/volumes/exists.go @@ -10,7 +10,7 @@ var ( volumeExistsDescription = `If the given volume exists, podman volume exists exits with 0, otherwise the exit code will be 1.` volumeExistsCommand = &cobra.Command{ Use: "exists VOLUME", - Short: "volume exists", + Short: "Volume exists", Long: volumeExistsDescription, RunE: volumeExists, Example: `podman volume exists myvol`, diff --git a/cmd/podman/volumes/mount.go b/cmd/podman/volumes/mount.go new file mode 100644 index 000000000..1f78187b8 --- /dev/null +++ b/cmd/podman/volumes/mount.go @@ -0,0 +1,51 @@ +package volumes + +import ( + "fmt" + + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" + "github.com/spf13/cobra" +) + +var ( + volumeMountDescription = `Mount a volume and return the mountpoint` + volumeMountCommand = &cobra.Command{ + Annotations: map[string]string{ + registry.UnshareNSRequired: "", + registry.ParentNSRequired: "", + registry.EngineMode: registry.ABIMode, + }, + Use: "mount NAME", + Short: "Mount volume", + Long: volumeMountDescription, + RunE: volumeMount, + Example: `podman volume mount myvol`, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteVolumes, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: volumeMountCommand, + Parent: volumeCmd, + }) +} + +func volumeMount(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + reports, err := registry.ContainerEngine().VolumeMount(registry.GetContext(), args) + if err != nil { + return err + } + for _, r := range reports { + if r.Err == nil { + fmt.Println(r.Path) + continue + } + errs = append(errs, r.Err) + } + return errs.PrintErrors() +} diff --git a/cmd/podman/volumes/unmount.go b/cmd/podman/volumes/unmount.go new file mode 100644 index 000000000..dd0cebc06 --- /dev/null +++ b/cmd/podman/volumes/unmount.go @@ -0,0 +1,48 @@ +package volumes + +import ( + "fmt" + + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" + "github.com/spf13/cobra" +) + +var ( + volumeUnmountDescription = `Unmount a volume` + volumeUnmountCommand = &cobra.Command{ + Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, + Use: "unmount NAME", + Short: "Unmount volume", + Long: volumeUnmountDescription, + RunE: volumeUnmount, + Example: `podman volume unmount myvol`, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteVolumes, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: volumeUnmountCommand, + Parent: volumeCmd, + }) +} + +func volumeUnmount(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + reports, err := registry.ContainerEngine().VolumeUnmount(registry.GetContext(), args) + if err != nil { + return err + } + for _, r := range reports { + var errs utils.OutputErrors + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/docs/source/markdown/podman-volume-mount.1.md b/docs/source/markdown/podman-volume-mount.1.md new file mode 100644 index 000000000..a5f35a34d --- /dev/null +++ b/docs/source/markdown/podman-volume-mount.1.md @@ -0,0 +1,28 @@ +% podman-volume-mount(1) + +## NAME +podman\-volume\-mount - Mount a volume filesystem + +## SYNOPSIS +**podman volume mount** [*volume* ...] + +## DESCRIPTION +Mounts the specified volumes' file system in a location which can be +accessed from the host, and returns its location. + +Rootless mode only supports mounting file volumes, unless you enter the user namespace +via the `podman unshare` command. All other volume types will fail to mount. + +## RETURN VALUE +The location of the mounted file system. On error an empty string and errno is +returned. + +## EXAMPLE + +``` +podman volume mount foo +/home/dwalsh/.local/share/containers/storage/volumes/foo/_data +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-volume(1)](podman-volume.1.md)**, **[podman-volume-unmount(1)](podman-volume-unmount.1.md)**, **[podman-unshare(1)](podman-unshare.1.md)**, **mount(8)** diff --git a/docs/source/markdown/podman-volume-unmount.1.md b/docs/source/markdown/podman-volume-unmount.1.md new file mode 100644 index 000000000..e2fcd425f --- /dev/null +++ b/docs/source/markdown/podman-volume-unmount.1.md @@ -0,0 +1,27 @@ +% podman-volume-unmount(1) + +## NAME +podman\-volume\-unmount - Unmount a volume + +## SYNOPSIS +**podman volume unmount** *volume* [...] + +**podman volume umount** *volume* [...] + +## DESCRIPTION +Unmounts the specified volume, if there are no other containers +using it. + +Volume storage increments a mount counter each time a volume is mounted. +When a volume is unmounted, the mount counter is decremented, and the +volume's filesystem is physically unmounted only when the mount +counter reaches zero indicating no other processes are using the mount. + +## EXAMPLE + +podman volume unmount volumeID + +podman volume unmount volumeID1 volumeID2 volumeID3 + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-volume(1)](podman-volume.1.md)**, **[podman-volume-mount(1)](podman-volume-mount.1.md)** diff --git a/docs/source/markdown/podman-volume.1.md b/docs/source/markdown/podman-volume.1.md index d8bc11aad..d05f007c8 100644 --- a/docs/source/markdown/podman-volume.1.md +++ b/docs/source/markdown/podman-volume.1.md @@ -19,8 +19,10 @@ podman volume is a set of subcommands that manage volumes. | import | [podman-volume-import(1)](podman-volume-import.1.md) | Import tarball contents into a podman volume. | | inspect | [podman-volume-inspect(1)](podman-volume-inspect.1.md) | Get detailed information on one or more volumes. | | ls | [podman-volume-ls(1)](podman-volume-ls.1.md) | List all the available volumes. | +| mount | [podman-volume-mount(1)](podman-volume-mount.1.md) | Mount a volume filesystem. | | prune | [podman-volume-prune(1)](podman-volume-prune.1.md) | Remove all unused volumes. | | rm | [podman-volume-rm(1)](podman-volume-rm.1.md) | Remove one or more volumes. | +| unmount | [podman-volume-unmount(1)](podman-volume-unmount.1.md) | Unmount a volume. | ## SEE ALSO **[podman(1)](podman.1.md)** diff --git a/libpod/volume.go b/libpod/volume.go index d60d978ed..f79ceaa87 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -255,3 +255,16 @@ func (v *Volume) IsDangling() (bool, error) { func (v *Volume) UsesVolumeDriver() bool { return !(v.config.Driver == define.VolumeDriverLocal || v.config.Driver == "") } + +func (v *Volume) Mount() (string, error) { + v.lock.Lock() + defer v.lock.Unlock() + err := v.mount() + return v.config.MountPoint, err +} + +func (v *Volume) Unmount() error { + v.lock.Lock() + defer v.lock.Unlock() + return v.unmount(false) +} diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go index 60d3667a9..7d7dea9d0 100644 --- a/libpod/volume_internal_linux.go +++ b/libpod/volume_internal_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package libpod diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 21272d33f..7cf7ca17f 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -99,6 +99,8 @@ type ContainerEngine interface { VolumeMounted(ctx context.Context, namesOrID string) (*BoolReport, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) + VolumeMount(ctx context.Context, namesOrIds []string) ([]*VolumeMountReport, error) VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*reports.PruneReport, error) VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) + VolumeUnmount(ctx context.Context, namesOrIds []string) ([]*VolumeUnmountReport, error) } diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go index 9bdce8392..f2e60a0db 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -187,3 +187,17 @@ type VolumeCreateBody struct { // Required: true Name string `json:"Name"` } + +// VolumeMountReport describes the response from volume mount +type VolumeMountReport struct { + Err error + Id string //nolint + Name string + Path string +} + +// VolumeUnmountReport describes the response from umounting a volume +type VolumeUnmountReport struct { + Err error + Id string //nolint +} diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index ee7269807..19fc6d2d3 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -178,3 +178,35 @@ func (ic *ContainerEngine) VolumeMounted(ctx context.Context, nameOrID string) ( } return &entities.BoolReport{Value: false}, nil } + +func (ic *ContainerEngine) VolumeMount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeMountReport, error) { + reports := []*entities.VolumeMountReport{} + for _, name := range nameOrIDs { + report := entities.VolumeMountReport{Id: name} + vol, err := ic.Libpod.LookupVolume(name) + if err != nil { + report.Err = err + } else { + report.Path, report.Err = vol.Mount() + } + reports = append(reports, &report) + } + + return reports, nil +} + +func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeUnmountReport, error) { + reports := []*entities.VolumeUnmountReport{} + for _, name := range nameOrIDs { + report := entities.VolumeUnmountReport{Id: name} + vol, err := ic.Libpod.LookupVolume(name) + if err != nil { + report.Err = err + } else { + report.Err = vol.Unmount() + } + reports = append(reports, &report) + } + + return reports, nil +} diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go index f4abeab0f..33e090148 100644 --- a/pkg/domain/infra/tunnel/volumes.go +++ b/pkg/domain/infra/tunnel/volumes.go @@ -100,3 +100,11 @@ func (ic *ContainerEngine) VolumeExists(ctx context.Context, nameOrID string) (* func (ic *ContainerEngine) VolumeMounted(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { return nil, errors.New("not implemented") } + +func (ic *ContainerEngine) VolumeMount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeMountReport, error) { + return nil, errors.New("mounting volumes is not supported for remote clients") +} + +func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeUnmountReport, error) { + return nil, errors.New("unmounting volumes is not supported for remote clients") +} diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index a3c972b3e..d0088b994 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -387,4 +387,29 @@ NeedsChown | true run_podman volume rm $myvolume } +@test "podman volume mount" { + skip_if_remote "podman --remote volume mount not supported" + myvolume=myvol$(random_string) + myfile=myfile$(random_string) + mytext=$(random_string) + + # Create a named volume + run_podman volume create $myvolume + is "$output" "$myvolume" "output from volume create" + + if ! is_rootless ; then + # image mount is hard to test as a rootless user + # and does not work remotely + run_podman volume mount ${myvolume} + mnt=${output} + echo $mytext >$mnt/$myfile + run_podman run -v ${myvolume}:/vol:z $IMAGE cat /vol/$myfile + is "$output" "$mytext" "$myfile should exist within the containers volume and contain $mytext" + run_podman volume unmount ${myvolume} + else + run_podman 125 volume mount ${myvolume} + is "$output" "Error: cannot run command \"podman volume mount\" in rootless mode, must execute.*podman unshare.*first" "Should fail and complain about unshare" + fi +} + # vim: filetype=sh -- cgit v1.2.3-54-g00ecf