From 6ca705bf1af6fd516b14ab043dc922e55eeaf832 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Thu, 29 Oct 2020 13:28:01 -0400 Subject: Add support for mounting external containers Continue progress on use of external containers. This PR adds the ability to mount, umount and list the storage containers whether they are in libpod or not. Signed-off-by: Daniel J Walsh --- docs/source/markdown/podman-mount.1.md | 6 ++- docs/source/markdown/podman-unmount.1.md | 4 ++ libpod/runtime_ctr.go | 50 ++++++++++++++++++++++ pkg/domain/infra/abi/containers.go | 73 ++++++++++++++++++++++++++++++-- test/system/060-mount.bats | 31 +++++++++++++- 5 files changed, 157 insertions(+), 7 deletions(-) diff --git a/docs/source/markdown/podman-mount.1.md b/docs/source/markdown/podman-mount.1.md index 33c5aece8..a6ed3f10d 100644 --- a/docs/source/markdown/podman-mount.1.md +++ b/docs/source/markdown/podman-mount.1.md @@ -13,7 +13,9 @@ Mounts the specified containers' root file system in a location which can be accessed from the host, and returns its location. If you execute the command without any arguments, Podman will list all of the -currently mounted containers. +currently mounted containers, including external containers. External containers are +containers in container/storage by tools other then Podman. For example Buildah and +CRI-O. Rootless mode only supports mounting VFS driver, unless you enter the user namespace via the `podman unshare` command. All other storage drivers will fail to mount. @@ -26,7 +28,7 @@ returned. **--all**, **-a** -Mount all containers. +Mount all podman containers. (External containers will not be mounted) **--format**=*format* diff --git a/docs/source/markdown/podman-unmount.1.md b/docs/source/markdown/podman-unmount.1.md index 47c55cc0b..08d1286ab 100644 --- a/docs/source/markdown/podman-unmount.1.md +++ b/docs/source/markdown/podman-unmount.1.md @@ -22,6 +22,10 @@ container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount. An unmount can be forced with the --force flag. +Note: Podman can be used to unmount Podman containers as well as external containers. +External containers are containers created in container/storage by other tools like +Buildah and CRI-O. + ## OPTIONS **--all**, **-a** diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index de73a9ff3..c84268889 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -918,6 +918,56 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int return prunedContainers, pruneErrors, nil } +// MountStorageContainer mounts the storage container's root filesystem +func (r *Runtime) MountStorageContainer(id string) (string, error) { + if _, err := r.GetContainer(id); err == nil { + return "", errors.Wrapf(define.ErrCtrExists, "ctr %s is a libpod container", id) + } + container, err := r.store.Container(id) + if err != nil { + return "", err + } + mountPoint, err := r.store.Mount(container.ID, "") + if err != nil { + return "", errors.Wrapf(err, "error mounting storage for container %s", id) + } + return mountPoint, nil +} + +// UnmountStorageContainer unmounts the storage container's root filesystem +func (r *Runtime) UnmountStorageContainer(id string, force bool) (bool, error) { + if _, err := r.GetContainer(id); err == nil { + return false, errors.Wrapf(define.ErrCtrExists, "ctr %s is a libpod container", id) + } + container, err := r.store.Container(id) + if err != nil { + return false, err + } + return r.store.Unmount(container.ID, force) +} + +// MountedStorageContainer returns whether a storage container is mounted +// along with the mount path +func (r *Runtime) IsStorageContainerMounted(id string) (bool, string, error) { + var path string + if _, err := r.GetContainer(id); err == nil { + return false, "", errors.Wrapf(define.ErrCtrExists, "ctr %s is a libpod container", id) + } + + mountCnt, err := r.storageService.MountedContainerImage(id) + if err != nil { + return false, "", err + } + mounted := mountCnt > 0 + if mounted { + path, err = r.storageService.GetMountpoint(id) + if err != nil { + return false, "", err + } + } + return mounted, path, nil +} + // StorageContainers returns a list of containers from containers/storage that // are not currently known to Podman. func (r *Runtime) StorageContainers() ([]storage.Container, error) { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 98b886845..855f9ece8 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1058,11 +1058,23 @@ func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIDs []strin os.Exit(ret) } } - ctrs, err := getContainersByContext(options.All, options.Latest, nameOrIDs, ic.Libpod) + reports := []*entities.ContainerMountReport{} + // Attempt to mount named containers directly from storage, + // this will fail and code will fall through to removing the container from libpod.` + names := []string{} + for _, ctr := range nameOrIDs { + report := entities.ContainerMountReport{Id: ctr} + if report.Path, report.Err = ic.Libpod.MountStorageContainer(ctr); report.Err != nil { + names = append(names, ctr) + } else { + reports = append(reports, &report) + } + } + + ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) if err != nil { return nil, err } - reports := make([]*entities.ContainerMountReport, 0, len(ctrs)) for _, ctr := range ctrs { report := entities.ContainerMountReport{Id: ctr.ID()} report.Path, report.Err = ctr.Mount() @@ -1072,6 +1084,30 @@ func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIDs []strin return reports, nil } + storageCtrs, err := ic.Libpod.StorageContainers() + if err != nil { + return nil, err + } + + for _, sctr := range storageCtrs { + mounted, path, err := ic.Libpod.IsStorageContainerMounted(sctr.ID) + if err != nil { + return nil, err + } + + var name string + if len(sctr.Names) > 0 { + name = sctr.Names[0] + } + if mounted { + reports = append(reports, &entities.ContainerMountReport{ + Id: sctr.ID, + Name: name, + Path: path, + }) + } + } + // No containers were passed, so we send back what is mounted ctrs, err = getContainersByContext(true, false, []string{}, ic.Libpod) if err != nil { @@ -1091,15 +1127,44 @@ func (ic *ContainerEngine) ContainerMount(ctx context.Context, nameOrIDs []strin }) } } + return reports, nil } func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIDs []string, options entities.ContainerUnmountOptions) ([]*entities.ContainerUnmountReport, error) { - ctrs, err := getContainersByContext(options.All, options.Latest, nameOrIDs, ic.Libpod) + reports := []*entities.ContainerUnmountReport{} + names := []string{} + if options.All { + storageCtrs, err := ic.Libpod.StorageContainers() + if err != nil { + return nil, err + } + for _, sctr := range storageCtrs { + mounted, _, _ := ic.Libpod.IsStorageContainerMounted(sctr.ID) + if mounted { + report := entities.ContainerUnmountReport{Id: sctr.ID} + if _, report.Err = ic.Libpod.UnmountStorageContainer(sctr.ID, options.Force); report.Err != nil { + if errors.Cause(report.Err) != define.ErrCtrExists { + reports = append(reports, &report) + } + } else { + reports = append(reports, &report) + } + } + } + } + for _, ctr := range nameOrIDs { + report := entities.ContainerUnmountReport{Id: ctr} + if _, report.Err = ic.Libpod.UnmountStorageContainer(ctr, options.Force); report.Err != nil { + names = append(names, ctr) + } else { + reports = append(reports, &report) + } + } + ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) if err != nil { return nil, err } - reports := []*entities.ContainerUnmountReport{} for _, ctr := range ctrs { state, err := ctr.State() if err != nil { diff --git a/test/system/060-mount.bats b/test/system/060-mount.bats index 73d210084..f04f34bf6 100644 --- a/test/system/060-mount.bats +++ b/test/system/060-mount.bats @@ -2,7 +2,6 @@ load helpers - @test "podman mount - basic test" { # Only works with root (FIXME: does it work with rootless + vfs?) skip_if_rootless "mount does not work rootless" @@ -151,4 +150,34 @@ load helpers run_podman rm -f $cid } +@test "podman mount external container - basic test" { + # Only works with root (FIXME: does it work with rootless + vfs?) + skip_if_rootless "mount does not work rootless" + skip_if_remote "mounting remote is meaningless" + + # Create a container that podman does not know about + external_cid=$(buildah from $IMAGE) + + run_podman mount $external_cid + mount_path=$output + + # Test image will always have this file, and will always have the tag + test -d $mount_path + is $(< "$mount_path/home/podman/testimage-id") "$PODMAN_TEST_IMAGE_TAG" \ + "Contents of well-known file in image" + + # Make sure that 'podman mount' (no args) returns the expected path + run_podman mount --notruncate + + reported_mountpoint=$(echo "$output" | awk '{print $2}') + is $reported_mountpoint $mount_path "mountpoint reported by 'podman mount'" + + # umount, and make sure files are gone + run_podman umount $external_cid + if [ -d "$mount_path" ]; then + die "'podman umount' did not umount" + fi + buildah rm $external_cid +} + # vim: filetype=sh -- cgit v1.2.3-54-g00ecf