mirror of
https://github.com/containers/podman.git
synced 2025-06-26 21:07:02 +08:00
Add podman image mount
There are many use cases where you want to just mount an image without creating a container on it. For example you might want to just examine the content in an image after you pull it for security analysys. Or you might want to just use the executables on the image without running it in a container. The image is mounted readonly since we do not want people changing images. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
139
cmd/podman/images/mount.go
Normal file
139
cmd/podman/images/mount.go
Normal file
@ -0,0 +1,139 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
|
||||
"github.com/containers/podman/v2/cmd/podman/registry"
|
||||
"github.com/containers/podman/v2/cmd/podman/utils"
|
||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
mountDescription = `podman image mount
|
||||
Lists all mounted images mount points if no images is specified
|
||||
|
||||
podman image mount IMAGE-NAME-OR-ID
|
||||
Mounts the specified image and prints the mountpoint
|
||||
`
|
||||
|
||||
mountCommand = &cobra.Command{
|
||||
Use: "mount [flags] [IMAGE...]",
|
||||
Short: "Mount an images's root filesystem",
|
||||
Long: mountDescription,
|
||||
RunE: mount,
|
||||
Example: `podman image mount imgID
|
||||
podman image mount imgID1 imgID2 imgID3
|
||||
podman image mount
|
||||
podman image mount --all`,
|
||||
Annotations: map[string]string{
|
||||
registry.UnshareNSRequired: "",
|
||||
registry.ParentNSRequired: "",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
mountOpts entities.ImageMountOptions
|
||||
)
|
||||
|
||||
func mountFlags(flags *pflag.FlagSet) {
|
||||
flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all images")
|
||||
flags.StringVar(&mountOpts.Format, "format", "", "Print the mounted images in specified format (json)")
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode},
|
||||
Command: mountCommand,
|
||||
Parent: imageCmd,
|
||||
})
|
||||
mountFlags(mountCommand.Flags())
|
||||
}
|
||||
|
||||
func mount(_ *cobra.Command, args []string) error {
|
||||
var (
|
||||
errs utils.OutputErrors
|
||||
)
|
||||
if len(args) > 0 && mountOpts.All {
|
||||
return errors.New("when using the --all switch, you may not pass any image names or IDs")
|
||||
}
|
||||
reports, err := registry.ImageEngine().Mount(registry.GetContext(), args, mountOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 || mountOpts.All {
|
||||
for _, r := range reports {
|
||||
if r.Err == nil {
|
||||
fmt.Println(r.Path)
|
||||
continue
|
||||
}
|
||||
errs = append(errs, r.Err)
|
||||
}
|
||||
return errs.PrintErrors()
|
||||
}
|
||||
|
||||
switch mountOpts.Format {
|
||||
case "json":
|
||||
return printJSON(reports)
|
||||
case "":
|
||||
// do nothing
|
||||
default:
|
||||
return errors.Errorf("unknown --format argument: %s", mountOpts.Format)
|
||||
}
|
||||
|
||||
mrs := make([]mountReporter, 0, len(reports))
|
||||
for _, r := range reports {
|
||||
mrs = append(mrs, mountReporter{r})
|
||||
}
|
||||
row := "{{.ID}} {{.Path}}\n"
|
||||
format := "{{range . }}" + row + "{{end}}"
|
||||
tmpl, err := template.New("mounts").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
|
||||
defer w.Flush()
|
||||
return tmpl.Execute(w, mrs)
|
||||
}
|
||||
|
||||
func printJSON(reports []*entities.ImageMountReport) error {
|
||||
type jreport struct {
|
||||
ID string `json:"id"`
|
||||
Names []string
|
||||
Repositories []string
|
||||
Mountpoint string `json:"mountpoint"`
|
||||
}
|
||||
jreports := make([]jreport, 0, len(reports))
|
||||
|
||||
for _, r := range reports {
|
||||
jreports = append(jreports, jreport{
|
||||
ID: r.Id,
|
||||
Names: []string{r.Name},
|
||||
Repositories: r.Repositories,
|
||||
Mountpoint: r.Path,
|
||||
})
|
||||
}
|
||||
b, err := json.MarshalIndent(jreports, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
type mountReporter struct {
|
||||
*entities.ImageMountReport
|
||||
}
|
||||
|
||||
func (m mountReporter) ID() string {
|
||||
if len(m.Repositories) > 0 {
|
||||
return m.Repositories[0]
|
||||
}
|
||||
return m.Id
|
||||
}
|
71
cmd/podman/images/unmount.go
Normal file
71
cmd/podman/images/unmount.go
Normal file
@ -0,0 +1,71 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/podman/v2/cmd/podman/registry"
|
||||
"github.com/containers/podman/v2/cmd/podman/utils"
|
||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
description = `Image storage increments a mount counter each time an image is mounted.
|
||||
|
||||
When an image is unmounted, the mount counter is decremented. The image'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.
|
||||
`
|
||||
unmountCommand = &cobra.Command{
|
||||
Use: "unmount [flags] IMAGE [IMAGE...]",
|
||||
Aliases: []string{"umount"},
|
||||
Short: "Unmount an image's root filesystem",
|
||||
Long: description,
|
||||
RunE: unmount,
|
||||
Example: `podman unmount imgID
|
||||
podman unmount imgID1 imgID2 imgID3
|
||||
podman unmount --all`,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
unmountOpts entities.ImageUnmountOptions
|
||||
)
|
||||
|
||||
func unmountFlags(flags *pflag.FlagSet) {
|
||||
flags.BoolVarP(&unmountOpts.All, "all", "a", false, "Unmount all of the currently mounted images")
|
||||
flags.BoolVarP(&unmountOpts.Force, "force", "f", false, "Force the complete unmount of the specified mounted images")
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode},
|
||||
Parent: imageCmd,
|
||||
Command: unmountCommand,
|
||||
})
|
||||
unmountFlags(unmountCommand.Flags())
|
||||
}
|
||||
|
||||
func unmount(cmd *cobra.Command, args []string) error {
|
||||
var errs utils.OutputErrors
|
||||
if len(args) < 1 && !unmountOpts.All {
|
||||
return errors.New("image name or ID must be specified")
|
||||
}
|
||||
if len(args) > 0 && unmountOpts.All {
|
||||
return errors.New("when using the --all switch, you may not pass any image names or IDs")
|
||||
}
|
||||
reports, err := registry.ImageEngine().Unmount(registry.GetContext(), args, unmountOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range reports {
|
||||
if r.Err == nil {
|
||||
fmt.Println(r.Id)
|
||||
} else {
|
||||
errs = append(errs, r.Err)
|
||||
}
|
||||
}
|
||||
return errs.PrintErrors()
|
||||
}
|
@ -1524,6 +1524,56 @@ _podman_info() {
|
||||
esac
|
||||
}
|
||||
|
||||
_podman_image_umount() {
|
||||
_podman_image_unmount
|
||||
}
|
||||
|
||||
_podman_image_unmount() {
|
||||
local boolean_options="
|
||||
--all
|
||||
-a
|
||||
--help
|
||||
-h
|
||||
--force
|
||||
-f
|
||||
"
|
||||
local options_with_args="
|
||||
"
|
||||
|
||||
local all_options="$options_with_args $boolean_options"
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
|
||||
;;
|
||||
*)
|
||||
__podman_complete_images --force-tag --id
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_podman_image_mount() {
|
||||
local boolean_options="
|
||||
--all
|
||||
-a
|
||||
--help
|
||||
-h
|
||||
"
|
||||
|
||||
local options_with_args="
|
||||
--format
|
||||
"
|
||||
|
||||
local all_options="$options_with_args $boolean_options"
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
|
||||
;;
|
||||
*)
|
||||
__podman_complete_images --force-tag --id
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_podman_image_build() {
|
||||
_podman_build
|
||||
}
|
||||
@ -1590,6 +1640,7 @@ _podman_image() {
|
||||
inspect
|
||||
load
|
||||
ls
|
||||
mount
|
||||
prune
|
||||
pull
|
||||
push
|
||||
@ -1598,6 +1649,8 @@ _podman_image() {
|
||||
sign
|
||||
tag
|
||||
trust
|
||||
umount
|
||||
unmount
|
||||
untag
|
||||
"
|
||||
local aliases="
|
||||
|
1
docs/source/markdown/links/podman-image-umount.1
Normal file
1
docs/source/markdown/links/podman-image-umount.1
Normal file
@ -0,0 +1 @@
|
||||
.so man1/podman-image-unmount.1
|
76
docs/source/markdown/podman-image-mount.1.md
Normal file
76
docs/source/markdown/podman-image-mount.1.md
Normal file
@ -0,0 +1,76 @@
|
||||
% podman-image-mount(1)
|
||||
|
||||
## NAME
|
||||
podman\-image\-mount - Mount an image's root filesystem
|
||||
|
||||
## SYNOPSIS
|
||||
**podman image mount** [*options*] [*image* ...]
|
||||
|
||||
## DESCRIPTION
|
||||
Mounts the specified images' 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 images.
|
||||
|
||||
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.
|
||||
|
||||
## RETURN VALUE
|
||||
The location of the mounted file system. On error an empty string and errno is
|
||||
returned.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--all**, **-a**
|
||||
|
||||
Mount all images.
|
||||
|
||||
**--format**=*format*
|
||||
|
||||
Print the mounted images in specified format (json).
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
```
|
||||
podman image mount fedora ubi8-init
|
||||
|
||||
/var/lib/containers/storage/overlay/f3ac502d97b5681989dff84dfedc8354239bcecbdc2692f9a639f4e080a02364/merged
|
||||
/var/lib/containers/storage/overlay/0ff7d7ca68bed1ace424f9df154d2dd7b5a125c19d887f17653cbcd5b6e30ba1/merged
|
||||
```
|
||||
|
||||
```
|
||||
podman mount
|
||||
|
||||
registry.fedoraproject.org/fedora:latest /var/lib/containers/storage/overlay/f3ac502d97b5681989dff84dfedc8354239bcecbdc2692f9a639f4e080a02364/merged
|
||||
registry.access.redhat.com/ubi8-init:latest /var/lib/containers/storage/overlay/0ff7d7ca68bed1ace424f9df154d2dd7b5a125c19d887f17653cbcd5b6e30ba1/merged
|
||||
```
|
||||
|
||||
```
|
||||
podman image mount --format json
|
||||
[
|
||||
{
|
||||
"id": "00ff39a8bf19f810a7e641f7eb3ddc47635913a19c4996debd91fafb6b379069",
|
||||
"Names": [
|
||||
"sha256:58de585a231aca14a511347bc85b912a6f000159b49bc2b0582032911e5d3a6c"
|
||||
],
|
||||
"Repositories": [
|
||||
"registry.fedoraproject.org/fedora:latest"
|
||||
],
|
||||
"mountpoint": "/var/lib/containers/storage/overlay/0ccfac04663bbe8813b5f24502ee0b7371ce5bf3c5adeb12e4258d191c2cf7bc/merged"
|
||||
},
|
||||
{
|
||||
"id": "bcc2dc9a261774ad25a15e07bb515f9b77424266abf2a1252ec7bcfed1dd0ac2",
|
||||
"Names": [
|
||||
"sha256:d5f260b2e51b3ee9d05de1c31d261efc9af28e7d2d47cedf054c496d71424d63"
|
||||
],
|
||||
"Repositories": [
|
||||
"registry.access.redhat.com/ubi8-init:latest"
|
||||
],
|
||||
"mountpoint": "/var/lib/containers/storage/overlay/d66b58e3391ea8ce4c81316c72e22b332618f2a28b461a32ed673e8998cdaeb8/merged"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
podman(1), podman-image-umount(1), mount(8), podman-unshare(1)
|
43
docs/source/markdown/podman-image-unmount.1.md
Normal file
43
docs/source/markdown/podman-image-unmount.1.md
Normal file
@ -0,0 +1,43 @@
|
||||
% podman-image-unmount(1)
|
||||
|
||||
## NAME
|
||||
podman\-image\-unmount - Unmount an image's root filesystem
|
||||
|
||||
## SYNOPSIS
|
||||
**podman image unmount** [*options*] *image* [...]
|
||||
|
||||
**podman image umount** [*options*] *image* [...]
|
||||
|
||||
## DESCRIPTION
|
||||
Unmounts the specified images' root file system, if no other processes
|
||||
are using it.
|
||||
|
||||
Image storage increments a mount counter each time a image is mounted.
|
||||
When a image is unmounted, the mount counter is decremented, and the
|
||||
image'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.
|
||||
|
||||
## OPTIONS
|
||||
**--all**, **-a**
|
||||
|
||||
All of the currently mounted images will be unmounted.
|
||||
|
||||
**--force**, **-f**
|
||||
|
||||
Force the unmounting of specified images' root file system, even if other
|
||||
processes have mounted it.
|
||||
|
||||
Note: This could cause other processes that are using the file system to fail,
|
||||
as the mount point could be removed without their knowledge.
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
podman image unmount imageID
|
||||
|
||||
podman image unmount imageID1 imageID2 imageID3
|
||||
|
||||
podman image unmount --all
|
||||
|
||||
## SEE ALSO
|
||||
podman(1), podman-image-mount(1), podman-container-mount(1)
|
@ -17,21 +17,23 @@ The image command allows you to manage images
|
||||
| diff | [podman-image-diff(1)](podman-image-diff.1.md) | Inspect changes on an image's filesystem. |
|
||||
| exists | [podman-image-exists(1)](podman-image-exists.1.md) | Check if an image exists in local storage. |
|
||||
| history | [podman-history(1)](podman-history.1.md) | Show the history of an image. |
|
||||
| import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. |
|
||||
| inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. |
|
||||
| list | [podman-images(1)](podman-images.1.md) | List the container images on the system.(alias ls) |
|
||||
| load | [podman-load(1)](podman-load.1.md) | Load an image from the docker archive. |
|
||||
| prune | [podman-image-prune(1)](podman-image-prune.1.md)| Remove all unused images from the local store. |
|
||||
| pull | [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. |
|
||||
| push | [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. |
|
||||
| rm | [podman-rmi(1)](podman-rmi.1.md) | Removes one or more locally stored images. |
|
||||
| save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. |
|
||||
| search | [podman-search(1)](podman-search.1.md) | Search a registry for an image. |
|
||||
| sign | [podman-image-sign(1)](podman-image-sign.1.md) | Create a signature for an image. |
|
||||
| tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. |
|
||||
| untag | [podman-untag(1)](podman-untag.1.md) | Removes one or more names from a locally-stored image. |
|
||||
| tree | [podman-image-tree(1)](podman-image-tree.1.md) | Prints layer hierarchy of an image in a tree format. |
|
||||
| trust | [podman-image-trust(1)](podman-image-trust.1.md)| Manage container registry image trust policy. |
|
||||
| import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. |
|
||||
| inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. |
|
||||
| list | [podman-images(1)](podman-images.1.md) | List the container images on the system.(alias ls) |
|
||||
| mount | [podman-image-mount(1)](podman-image-mount.1.md) | Mount an image's root filesystem. |
|
||||
| load | [podman-load(1)](podman-load.1.md) | Load an image from the docker archive. |
|
||||
| prune | [podman-image-prune(1)](podman-image-prune.1.md) | Remove all unused images from the local store. |
|
||||
| pull | [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. |
|
||||
| push | [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. |
|
||||
| rm | [podman-rmi(1)](podman-rmi.1.md) | Removes one or more locally stored images. |
|
||||
| save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. |
|
||||
| search | [podman-search(1)](podman-search.1.md) | Search a registry for an image. |
|
||||
| sign | [podman-image-sign(1)](podman-image-sign.1.md) | Create a signature for an image. |
|
||||
| tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. |
|
||||
| untag | [podman-untag(1)](podman-untag.1.md) | Removes one or more names from a locally-stored image. |
|
||||
| unmount | [podman-image-unmount(1)](podman-image-unmount.1.md) | Unmount an image's root filesystem. |
|
||||
| tree | [podman-image-tree(1)](podman-image-tree.1.md) | Prints layer hierarchy of an image in a tree format. |
|
||||
| trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container registry image trust policy. |
|
||||
|
||||
## SEE ALSO
|
||||
podman
|
||||
|
@ -1593,6 +1593,63 @@ func (i *Image) newImageEvent(status events.Status) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mount mounts a image's filesystem on the host
|
||||
// The path where the image has been mounted is returned
|
||||
func (i *Image) Mount(options []string, mountLabel string) (string, error) {
|
||||
defer i.newImageEvent(events.Mount)
|
||||
return i.mount(options, mountLabel)
|
||||
}
|
||||
|
||||
// Unmount unmounts a image's filesystem on the host
|
||||
func (i *Image) Unmount(force bool) error {
|
||||
defer i.newImageEvent(events.Unmount)
|
||||
return i.unmount(force)
|
||||
}
|
||||
|
||||
// Mounted returns whether the image is mounted and the path it is mounted
|
||||
// at (if it is mounted).
|
||||
// If the image is not mounted, no error is returned, and the mountpoint
|
||||
// will be set to "".
|
||||
func (i *Image) Mounted() (bool, string, error) {
|
||||
mountedTimes, err := i.imageruntime.store.Mounted(i.TopLayer())
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
if mountedTimes > 0 {
|
||||
layer, err := i.imageruntime.store.Layer(i.TopLayer())
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
return true, layer.MountPoint, nil
|
||||
}
|
||||
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
// mount mounts the container's root filesystem
|
||||
func (i *Image) mount(options []string, mountLabel string) (string, error) {
|
||||
mountPoint, err := i.imageruntime.store.MountImage(i.ID(), options, mountLabel)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error mounting storage for image %s", i.ID())
|
||||
}
|
||||
mountPoint, err = filepath.EvalSymlinks(mountPoint)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error resolving storage path for image %s", i.ID())
|
||||
}
|
||||
return mountPoint, nil
|
||||
}
|
||||
|
||||
// unmount unmounts the image's root filesystem
|
||||
func (i *Image) unmount(force bool) error {
|
||||
// Also unmount storage
|
||||
if _, err := i.imageruntime.store.UnmountImage(i.ID(), force); err != nil {
|
||||
return errors.Wrapf(err, "error unmounting image %s root filesystem", i.ID())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LayerInfo keeps information of single layer
|
||||
type LayerInfo struct {
|
||||
// Layer ID
|
||||
|
@ -16,6 +16,7 @@ type ImageEngine interface {
|
||||
Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, []error, error)
|
||||
List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error)
|
||||
Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error)
|
||||
Mount(ctx context.Context, images []string, options ImageMountOptions) ([]*ImageMountReport, error)
|
||||
Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
|
||||
Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
|
||||
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
|
||||
@ -27,6 +28,7 @@ type ImageEngine interface {
|
||||
Shutdown(ctx context.Context)
|
||||
Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error
|
||||
Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error)
|
||||
Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error)
|
||||
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
|
||||
ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error)
|
||||
ManifestInspect(ctx context.Context, name string) ([]byte, error)
|
||||
|
@ -91,7 +91,7 @@ type ImageRemoveOptions struct {
|
||||
}
|
||||
|
||||
// ImageRemoveResponse is the response for removing one or more image(s) from storage
|
||||
// and containers what was untagged vs actually removed.
|
||||
// and images what was untagged vs actually removed.
|
||||
type ImageRemoveReport struct {
|
||||
// Deleted images.
|
||||
Deleted []string `json:",omitempty"`
|
||||
@ -318,3 +318,31 @@ type SignOptions struct {
|
||||
|
||||
// SignReport describes the result of signing
|
||||
type SignReport struct{}
|
||||
|
||||
// ImageMountOptions describes the input values for mounting images
|
||||
// in the CLI
|
||||
type ImageMountOptions struct {
|
||||
All bool
|
||||
Format string
|
||||
}
|
||||
|
||||
// ImageUnmountOptions are the options from the cli for unmounting
|
||||
type ImageUnmountOptions struct {
|
||||
All bool
|
||||
Force bool
|
||||
}
|
||||
|
||||
// ImageMountReport describes the response from image mount
|
||||
type ImageMountReport struct {
|
||||
Err error
|
||||
Id string //nolint
|
||||
Name string
|
||||
Repositories []string
|
||||
Path string
|
||||
}
|
||||
|
||||
// ImageUnmountReport describes the response from umounting an image
|
||||
type ImageUnmountReport struct {
|
||||
Err error
|
||||
Id string //nolint
|
||||
}
|
||||
|
@ -11,8 +11,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v2/pkg/rootless"
|
||||
|
||||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/image/v5/docker"
|
||||
dockerarchive "github.com/containers/image/v5/docker/archive"
|
||||
@ -27,6 +25,7 @@ import (
|
||||
libpodImage "github.com/containers/podman/v2/libpod/image"
|
||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||
domainUtils "github.com/containers/podman/v2/pkg/domain/utils"
|
||||
"github.com/containers/podman/v2/pkg/rootless"
|
||||
"github.com/containers/podman/v2/pkg/trust"
|
||||
"github.com/containers/podman/v2/pkg/util"
|
||||
"github.com/containers/storage"
|
||||
@ -85,6 +84,125 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entiti
|
||||
return &history, nil
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entities.ImageMountOptions) ([]*entities.ImageMountReport, error) {
|
||||
var (
|
||||
images []*image.Image
|
||||
err error
|
||||
)
|
||||
if os.Geteuid() != 0 {
|
||||
if driver := ir.Libpod.StorageConfig().GraphDriverName; driver != "vfs" {
|
||||
// Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part
|
||||
// of the mount command.
|
||||
return nil, errors.Errorf("cannot mount using driver %s in rootless mode", driver)
|
||||
}
|
||||
|
||||
became, ret, err := rootless.BecomeRootInUserNS("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if became {
|
||||
os.Exit(ret)
|
||||
}
|
||||
}
|
||||
if opts.All {
|
||||
allImages, err := ir.Libpod.ImageRuntime().GetImages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, img := range allImages {
|
||||
if !img.IsReadOnly() {
|
||||
images = append(images, img)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, i := range nameOrIDs {
|
||||
img, err := ir.Libpod.ImageRuntime().NewFromLocal(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
images = append(images, img)
|
||||
}
|
||||
}
|
||||
reports := make([]*entities.ImageMountReport, 0, len(images))
|
||||
for _, img := range images {
|
||||
report := entities.ImageMountReport{Id: img.ID()}
|
||||
if img.IsReadOnly() {
|
||||
report.Err = errors.Errorf("mounting readonly %s image not supported", img.ID())
|
||||
} else {
|
||||
report.Path, report.Err = img.Mount([]string{}, "")
|
||||
}
|
||||
reports = append(reports, &report)
|
||||
}
|
||||
if len(reports) > 0 {
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
images, err = ir.Libpod.ImageRuntime().GetImages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, i := range images {
|
||||
mounted, path, err := i.Mounted()
|
||||
if err != nil {
|
||||
if errors.Cause(err) == storage.ErrLayerUnknown {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if mounted {
|
||||
tags, err := i.RepoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reports = append(reports, &entities.ImageMountReport{
|
||||
Id: i.ID(),
|
||||
Name: string(i.Digest()),
|
||||
Repositories: tags,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
}
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) {
|
||||
var images []*image.Image
|
||||
|
||||
if options.All {
|
||||
allImages, err := ir.Libpod.ImageRuntime().GetImages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, img := range allImages {
|
||||
if !img.IsReadOnly() {
|
||||
images = append(images, img)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, i := range nameOrIDs {
|
||||
img, err := ir.Libpod.ImageRuntime().NewFromLocal(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
images = append(images, img)
|
||||
}
|
||||
}
|
||||
|
||||
reports := []*entities.ImageUnmountReport{}
|
||||
for _, img := range images {
|
||||
report := entities.ImageUnmountReport{Id: img.ID()}
|
||||
if err := img.Unmount(options.Force); err != nil {
|
||||
if options.All && errors.Cause(err) == storage.ErrLayerNotMounted {
|
||||
logrus.Debugf("Error umounting image %s, storage.ErrLayerNotMounted", img.ID())
|
||||
continue
|
||||
}
|
||||
report.Err = errors.Wrapf(err, "error unmounting image %s", img.ID())
|
||||
}
|
||||
reports = append(reports, &report)
|
||||
}
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer {
|
||||
l := entities.ImageHistoryLayer{}
|
||||
l.ID = layer.ID
|
||||
@ -225,7 +343,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
|
||||
case "v2s2", "docker":
|
||||
manifestType = manifest.DockerV2Schema2MediaType
|
||||
default:
|
||||
return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format)
|
||||
return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format)
|
||||
}
|
||||
|
||||
var registryCreds *types.DockerAuthConfig
|
||||
@ -292,7 +410,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
|
||||
//
|
||||
// // TODO: Determine Size
|
||||
// report := entities.ImagePruneReport{}
|
||||
// copy(report.Report.Id, id)
|
||||
// copy(report.Report.ID, id)
|
||||
// return &report, nil
|
||||
// }
|
||||
|
||||
|
@ -54,6 +54,14 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
|
||||
return is, nil
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) Mount(ctx context.Context, images []string, options entities.ImageMountOptions) ([]*entities.ImageMountReport, error) {
|
||||
return nil, errors.New("mounting images is not supported for remote clients")
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) Unmount(ctx context.Context, images []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) {
|
||||
return nil, errors.New("unmounting images is not supported for remote clients")
|
||||
}
|
||||
|
||||
func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) {
|
||||
results, err := images.History(ir.ClientCxt, nameOrID)
|
||||
if err != nil {
|
||||
|
@ -211,7 +211,8 @@ can_use_shortcut ()
|
||||
break;
|
||||
}
|
||||
|
||||
if (argv[argc+1] != NULL && strcmp (argv[argc], "container") == 0 &&
|
||||
if (argv[argc+1] != NULL && (strcmp (argv[argc], "container") == 0 ||
|
||||
strcmp (argv[argc], "image") == 0) &&
|
||||
strcmp (argv[argc+1], "mount") == 0)
|
||||
{
|
||||
ret = false;
|
||||
|
@ -59,4 +59,25 @@ var _ = Describe("Podman mount", func() {
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman image mount", func() {
|
||||
setup := podmanTest.PodmanNoCache([]string{"pull", ALPINE})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
mount := podmanTest.PodmanNoCache([]string{"image", "mount", ALPINE})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).ToNot(Equal(0))
|
||||
Expect(mount.ErrorToString()).To(ContainSubstring("podman unshare"))
|
||||
})
|
||||
|
||||
It("podman unshare image podman mount", func() {
|
||||
setup := podmanTest.PodmanNoCache([]string{"pull", ALPINE})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
session := podmanTest.Podman([]string{"unshare", PODMAN_BINARY, "image", "mount", ALPINE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
@ -282,4 +282,143 @@ var _ = Describe("Podman mount", func() {
|
||||
umount.WaitWithDefaultTimeout()
|
||||
Expect(umount.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman image mount", func() {
|
||||
setup := podmanTest.PodmanNoCache([]string{"pull", ALPINE})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
images := podmanTest.PodmanNoCache([]string{"images"})
|
||||
images.WaitWithDefaultTimeout()
|
||||
Expect(images.ExitCode()).To(Equal(0))
|
||||
|
||||
mount := podmanTest.PodmanNoCache([]string{"image", "mount", ALPINE})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
|
||||
umount := podmanTest.PodmanNoCache([]string{"image", "umount", ALPINE})
|
||||
umount.WaitWithDefaultTimeout()
|
||||
Expect(umount.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(Equal(""))
|
||||
|
||||
// Mount multiple times
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount", ALPINE})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount", ALPINE})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
|
||||
// Unmount once
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount", ALPINE})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(ContainSubstring(ALPINE))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "umount", "--all"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman mount with json format", func() {
|
||||
setup := podmanTest.PodmanNoCache([]string{"pull", fedoraMinimal})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
mount := podmanTest.PodmanNoCache([]string{"image", "mount", fedoraMinimal})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
|
||||
j := podmanTest.PodmanNoCache([]string{"image", "mount", "--format=json"})
|
||||
j.WaitWithDefaultTimeout()
|
||||
Expect(j.ExitCode()).To(Equal(0))
|
||||
Expect(j.IsJSONOutputValid()).To(BeTrue())
|
||||
|
||||
umount := podmanTest.PodmanNoCache([]string{"image", "umount", fedoraMinimal})
|
||||
umount.WaitWithDefaultTimeout()
|
||||
Expect(umount.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman mount many", func() {
|
||||
setup := podmanTest.PodmanNoCache([]string{"pull", fedoraMinimal})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
setup = podmanTest.PodmanNoCache([]string{"pull", ALPINE})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
setup = podmanTest.PodmanNoCache([]string{"pull", "busybox"})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
mount1 := podmanTest.PodmanNoCache([]string{"image", "mount", fedoraMinimal, ALPINE, "busybox"})
|
||||
mount1.WaitWithDefaultTimeout()
|
||||
Expect(mount1.ExitCode()).To(Equal(0))
|
||||
|
||||
umount := podmanTest.PodmanNoCache([]string{"image", "umount", fedoraMinimal, ALPINE})
|
||||
umount.WaitWithDefaultTimeout()
|
||||
Expect(umount.ExitCode()).To(Equal(0))
|
||||
|
||||
mount := podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(ContainSubstring("busybox"))
|
||||
|
||||
mount1 = podmanTest.PodmanNoCache([]string{"image", "unmount", "busybox"})
|
||||
mount1.WaitWithDefaultTimeout()
|
||||
Expect(mount1.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(Equal(""))
|
||||
|
||||
mount1 = podmanTest.PodmanNoCache([]string{"image", "mount", fedoraMinimal, ALPINE, "busybox"})
|
||||
mount1.WaitWithDefaultTimeout()
|
||||
Expect(mount1.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(ContainSubstring(fedoraMinimal))
|
||||
Expect(mount.OutputToString()).To(ContainSubstring(ALPINE))
|
||||
|
||||
umount = podmanTest.PodmanNoCache([]string{"image", "umount", "--all"})
|
||||
umount.WaitWithDefaultTimeout()
|
||||
Expect(umount.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(Equal(""))
|
||||
|
||||
mount1 = podmanTest.PodmanNoCache([]string{"image", "mount", "--all"})
|
||||
mount1.WaitWithDefaultTimeout()
|
||||
Expect(mount1.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(ContainSubstring(fedoraMinimal))
|
||||
Expect(mount.OutputToString()).To(ContainSubstring(ALPINE))
|
||||
|
||||
umount = podmanTest.PodmanNoCache([]string{"image", "umount", "--all"})
|
||||
umount.WaitWithDefaultTimeout()
|
||||
Expect(umount.ExitCode()).To(Equal(0))
|
||||
|
||||
mount = podmanTest.PodmanNoCache([]string{"image", "mount"})
|
||||
mount.WaitWithDefaultTimeout()
|
||||
Expect(mount.ExitCode()).To(Equal(0))
|
||||
Expect(mount.OutputToString()).To(Equal(""))
|
||||
})
|
||||
})
|
||||
|
@ -33,7 +33,11 @@ var _ = Describe("Podman untag", func() {
|
||||
})
|
||||
|
||||
It("podman untag all", func() {
|
||||
Skip(v2remotefail)
|
||||
SkipIfRemote()
|
||||
setup := podmanTest.PodmanNoCache([]string{"pull", ALPINE})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
tags := []string{ALPINE, "registry.com/foo:bar", "localhost/foo:bar"}
|
||||
|
||||
cmd := []string{"tag"}
|
||||
@ -63,6 +67,10 @@ var _ = Describe("Podman untag", func() {
|
||||
})
|
||||
|
||||
It("podman tag/untag - tag normalization", func() {
|
||||
setup := podmanTest.PodmanNoCache([]string{"pull", ALPINE})
|
||||
setup.WaitWithDefaultTimeout()
|
||||
Expect(setup.ExitCode()).To(Equal(0))
|
||||
|
||||
tests := []struct {
|
||||
tag, normalized string
|
||||
}{
|
||||
|
Reference in New Issue
Block a user