From 8ae97b2f57a845dd05f70f244a763c53250b4e81 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 17 Jul 2019 10:49:03 -0400 Subject: Add support for listing read/only and read/write images When removing --all images prune images only attempt to remove read/write images, ignore read/only images Signed-off-by: Daniel J Walsh --- API.md | 2 ++ cmd/podman/imagefilters/filters.go | 10 ++++++ cmd/podman/images.go | 43 ++++++++++++++++++------ cmd/podman/rmi.go | 4 +-- cmd/podman/varlink/io.podman.varlink | 3 +- docs/podman-images.1.md | 64 +++++++++++++++++++++++------------- libpod/image/image.go | 24 ++++++++++---- libpod/image/prune.go | 2 +- pkg/adapter/runtime.go | 13 +++++++- pkg/adapter/runtime_remote.go | 19 +++++++++++ pkg/varlinkapi/images.go | 4 +++ test/e2e/images_test.go | 16 +++++++++ 12 files changed, 161 insertions(+), 43 deletions(-) diff --git a/API.md b/API.md index febd094df..ec8512fd4 100755 --- a/API.md +++ b/API.md @@ -1592,6 +1592,8 @@ labels [map[string]](#map[string]) isParent [bool](https://godoc.org/builtin#bool) topLayer [string](https://godoc.org/builtin#string) + +readOnly [bool](https://godoc.org/builtin#bool) ### type ImageHistory ImageHistory describes the returned structure from ImageHistory. diff --git a/cmd/podman/imagefilters/filters.go b/cmd/podman/imagefilters/filters.go index aa5776599..0b08314ce 100644 --- a/cmd/podman/imagefilters/filters.go +++ b/cmd/podman/imagefilters/filters.go @@ -46,6 +46,16 @@ func DanglingFilter(danglingImages bool) ResultFilter { } } +// ReadOnlyFilter allows you to filter images based on read/only and read/write +func ReadOnlyFilter(readOnly bool) ResultFilter { + return func(i *adapter.ContainerImage) bool { + if readOnly { + return i.IsReadOnly() + } + return !i.IsReadOnly() + } +} + // LabelFilter allows you to filter by images labels key and/or value func LabelFilter(ctx context.Context, labelfilter string) ResultFilter { // We need to handle both label=key and label=key=value diff --git a/cmd/podman/images.go b/cmd/podman/images.go index f842573d9..281d93e71 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -30,14 +30,16 @@ type imagesTemplateParams struct { Created string CreatedTime time.Time Size string + ReadOnly bool } type imagesJSONParams struct { - ID string `json:"id"` - Name []string `json:"names"` - Digest digest.Digest `json:"digest"` - Created time.Time `json:"created"` - Size *uint64 `json:"size"` + ID string `json:"id"` + Name []string `json:"names"` + Digest digest.Digest `json:"digest"` + Created time.Time `json:"created"` + Size *uint64 `json:"size"` + ReadOnly bool `json:"readonly"` } type imagesOptions struct { @@ -49,6 +51,7 @@ type imagesOptions struct { outputformat string sort string all bool + useReadOnly bool } // Type declaration and functions for sorting the images output @@ -175,6 +178,13 @@ func imagesCmd(c *cliconfig.ImagesValues) error { return errors.Wrapf(err, "unable to get images") } + for _, image := range images { + if image.IsReadOnly() { + opts.outputformat += "{{.ReadOnly}}\t" + break + } + } + var filteredImages []*adapter.ContainerImage //filter the images if len(c.Filter) > 0 || len(c.InputArgs) == 1 { @@ -282,6 +292,7 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma CreatedTime: createdTime, Created: units.HumanDuration(time.Since(createdTime)) + " ago", Size: sizeStr, + ReadOnly: img.IsReadOnly(), } imagesOutput = append(imagesOutput, params) if opts.quiet { // Show only one image ID when quiet @@ -305,11 +316,12 @@ func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) size = nil } params := imagesJSONParams{ - ID: img.ID(), - Name: img.Names(), - Digest: img.Digest(), - Created: img.Created(), - Size: size, + ID: img.ID(), + Name: img.Names(), + Digest: img.Digest(), + Created: img.Created(), + Size: size, + ReadOnly: img.IsReadOnly(), } imagesOutput = append(imagesOutput, params) } @@ -351,6 +363,11 @@ func GenImageOutputMap() map[string]string { if value == "ID" { value = "Image" + value } + + if value == "ReadOnly" { + values[key] = "R/O" + continue + } values[key] = strings.ToUpper(splitCamelCase(value)) } return values @@ -378,6 +395,12 @@ func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, filters []s return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, imagefilters.CreatedAfterFilter(after.Created())) + case "readonly": + readonly, err := strconv.ParseBool(splitFilter[1]) + if err != nil { + return nil, errors.Wrapf(err, "invalid filter readonly=%s", splitFilter[1]) + } + filterFuncs = append(filterFuncs, imagefilters.ReadOnlyFilter(readonly)) case "dangling": danglingImages, err := strconv.ParseBool(splitFilter[1]) if err != nil { diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index 9229d497e..57e78c34a 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -87,7 +87,7 @@ func rmiCmd(c *cliconfig.RmiValues) error { if removeAll { var imagesToDelete []*adapter.ContainerImage - imagesToDelete, err = runtime.GetImages() + imagesToDelete, err = runtime.GetRWImages() if err != nil { return errors.Wrapf(err, "unable to query local images") } @@ -107,7 +107,7 @@ func rmiCmd(c *cliconfig.RmiValues) error { removeImage(i) } lastNumberofImages = len(imagesToDelete) - imagesToDelete, err = runtime.GetImages() + imagesToDelete, err = runtime.GetRWImages() if err != nil { return err } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 0bf236b77..72b15c328 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -69,7 +69,8 @@ type Image ( containers: int, labels: [string]string, isParent: bool, - topLayer: string + topLayer: string, + readOnly: bool ) # ImageHistory describes the returned structure from ImageHistory. diff --git a/docs/podman-images.1.md b/docs/podman-images.1.md index 2a732de57..6360bf580 100644 --- a/docs/podman-images.1.md +++ b/docs/podman-images.1.md @@ -23,6 +23,26 @@ Show image digests Filter output based on conditions provided + Filters: + + **after==TIMESTRING** + Filter on images created after the given time.Time. + + **before==TIMESTRING** + Filter on images created before the given time.Time. + + **dangling=true|false** + Show dangling images. Dangling images are a file system layer that was used in a previous build of an image and is no longer referenced by any active images. They are denoted with the tag, consume disk space and serve no active purpose. + + **label** + Filter by images labels key and/or value. + + **readonly=true|false** + Show only read only images or Read/Write images. The default is to show both. Read/Only images can be configured by modifying the "additionalimagestores" in the /etc/containers/storage.conf file. + + **reference=** + Filter by image name, specified as regular expressions. + **--format**=*format* Change the default output format. This can be of a supported type like 'json' @@ -94,31 +114,31 @@ REPOSITORY TAG IMAGE ID CREATED SIZE # podman images --format json [ { - "id": "e3d42bcaf643097dd1bb0385658ae8cbe100a80f773555c44690d22c25d16b27", - "names": [ - "docker.io/kubernetes/pause:latest" - ], - "digest": "sha256:0aecf73ff86844324847883f2e916d3f6984c5fae3c2f23e91d66f549fe7d423", - "created": "2014-07-19T07:02:32.267701596Z", - "size": 250665 + "id": "e3d42bcaf643097dd1bb0385658ae8cbe100a80f773555c44690d22c25d16b27", + "names": [ + "docker.io/kubernetes/pause:latest" + ], + "digest": "sha256:0aecf73ff86844324847883f2e916d3f6984c5fae3c2f23e91d66f549fe7d423", + "created": "2014-07-19T07:02:32.267701596Z", + "size": 250665 }, { - "id": "ebb91b73692bd27890685846412ae338d13552165eacf7fcd5f139bfa9c2d6d9", - "names": [ - "\u003cnone\u003e" - ], - "digest": "sha256:ba7e4091d27e8114a205003ca6a768905c3395d961624a2c78873d9526461032", - "created": "2017-10-26T03:07:22.796184288Z", - "size": 27170520 + "id": "ebb91b73692bd27890685846412ae338d13552165eacf7fcd5f139bfa9c2d6d9", + "names": [ + "\u003cnone\u003e" + ], + "digest": "sha256:ba7e4091d27e8114a205003ca6a768905c3395d961624a2c78873d9526461032", + "created": "2017-10-26T03:07:22.796184288Z", + "size": 27170520 }, { - "id": "4526339ae51c3cdc97956a7a961c193c39dfc6bd9733b0d762a36c6881b5583a", - "names": [ - "docker.io/library/ubuntu:latest" - ], - "digest": "sha256:193f7734ddd68e0fb24ba9af8c2b673aecb0227b026871f8e932dab45add7753", - "created": "2017-10-10T20:59:05.10196344Z", - "size": 126085200 + "id": "4526339ae51c3cdc97956a7a961c193c39dfc6bd9733b0d762a36c6881b5583a", + "names": [ + "docker.io/library/ubuntu:latest" + ], + "digest": "sha256:193f7734ddd68e0fb24ba9af8c2b673aecb0227b026871f8e932dab45add7753", + "created": "2017-10-10T20:59:05.10196344Z", + "size": 126085200 } ] ``` @@ -148,7 +168,7 @@ docker.io/library/alpine latest 3fd9065eaf02 5 months ago 4.41 MB ``` ## SEE ALSO -podman(1) +podman(1), containers-storage.conf(5) ## HISTORY March 2017, Originally compiled by Dan Walsh diff --git a/libpod/image/image.go b/libpod/image/image.go index f9879b85b..a057bc720 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -54,7 +54,6 @@ type Image struct { inspect.ImageResult inspectInfo *types.ImageInspectInfo InputName string - Local bool //runtime *libpod.Runtime image *storage.Image imageruntime *Runtime @@ -119,7 +118,6 @@ func setStore(options storage.StoreOptions) (storage.Store, error) { func (ir *Runtime) newFromStorage(img *storage.Image) *Image { image := Image{ InputName: img.ID, - Local: true, imageruntime: ir, image: img, } @@ -132,7 +130,6 @@ func (ir *Runtime) newFromStorage(img *storage.Image) *Image { func (ir *Runtime) NewFromLocal(name string) (*Image, error) { image := Image{ InputName: name, - Local: true, imageruntime: ir, } localImage, err := image.getLocalImage() @@ -153,13 +150,11 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile // We don't know if the image is local or not ... check local first newImage := Image{ InputName: name, - Local: false, imageruntime: ir, } if !forcePull { localImage, err := newImage.getLocalImage() if err == nil { - newImage.Local = true newImage.image = localImage return &newImage, nil } @@ -199,7 +194,6 @@ func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.Im for _, name := range imageNames { newImage := Image{ InputName: name, - Local: true, imageruntime: ir, } img, err := newImage.getLocalImage() @@ -299,6 +293,11 @@ func (i *Image) ID() string { return i.image.ID } +// IsReadOnly returns whether the image ID comes from a local store +func (i *Image) IsReadOnly() bool { + return i.image.ReadOnly +} + // Digest returns the image's digest func (i *Image) Digest() digest.Digest { return i.image.Digest @@ -439,12 +438,25 @@ func (ir *Runtime) getImage(image string) (*Image, error) { // GetImages retrieves all images present in storage func (ir *Runtime) GetImages() ([]*Image, error) { + return ir.getImages(false) +} + +// GetRWImages retrieves all read/write images present in storage +func (ir *Runtime) GetRWImages() ([]*Image, error) { + return ir.getImages(true) +} + +// getImages retrieves all images present in storage +func (ir *Runtime) getImages(rwOnly bool) ([]*Image, error) { var newImages []*Image images, err := ir.store.Images() if err != nil { return nil, err } for _, i := range images { + if rwOnly && i.ReadOnly { + continue + } // iterating over these, be careful to not iterate on the literal // pointer. image := i diff --git a/libpod/image/prune.go b/libpod/image/prune.go index a4f8a0c9f..6ef5d321f 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -12,7 +12,7 @@ func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) { var ( pruneImages []*Image ) - allImages, err := ir.GetImages() + allImages, err := ir.GetRWImages() if err != nil { return nil, err } diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index e65f07898..dc193c738 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -85,16 +85,27 @@ func getRuntime(runtime *libpod.Runtime) (*LocalRuntime, error) { // GetImages returns a slice of images in containerimages func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) { + return r.getImages(false) +} + +// GetRWImages returns a slice of read/write images in containerimages +func (r *LocalRuntime) GetRWImages() ([]*ContainerImage, error) { + return r.getImages(true) +} + +func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) { var containerImages []*ContainerImage images, err := r.Runtime.ImageRuntime().GetImages() if err != nil { return nil, err } for _, i := range images { + if rwOnly && i.IsReadOnly() { + continue + } containerImages = append(containerImages, &ContainerImage{i}) } return containerImages, nil - } // NewImageFromLocal returns a containerimage representation of a image from local storage diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index db3f23629..9fae39df0 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -129,6 +129,7 @@ type remoteImage struct { isParent bool Runtime *LocalRuntime TopLayer string + ReadOnly bool } // Container ... @@ -169,12 +170,24 @@ type remoteVolume struct { // GetImages returns a slice of containerimages over a varlink connection func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) { + return r.getImages(false) +} + +// GetRWImages returns a slice of read/write containerimages over a varlink connection +func (r *LocalRuntime) GetRWImages() ([]*ContainerImage, error) { + return r.getImages(true) +} + +func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) { var newImages []*ContainerImage images, err := iopodman.ListImages().Call(r.Conn) if err != nil { return nil, err } for _, i := range images { + if rwOnly && i.ReadOnly { + continue + } name := i.Id if len(i.RepoTags) > 1 { name = i.RepoTags[0] @@ -207,6 +220,7 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu isParent: i.IsParent, Runtime: runtime, TopLayer: i.TopLayer, + ReadOnly: i.ReadOnly, } return &ContainerImage{ri}, nil } @@ -302,6 +316,11 @@ func (ci *ContainerImage) Created() time.Time { return ci.remoteImage.Created } +// IsReadOnly returns whether the image is ReadOnly +func (ci *ContainerImage) IsReadOnly() bool { + return ci.remoteImage.ReadOnly +} + // Size returns the size of the image func (ci *ContainerImage) Size(ctx context.Context) (*uint64, error) { usize := uint64(ci.remoteImage.Size) diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 2bebfd406..0ead1c23d 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -69,6 +69,7 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error { Containers: int64(len(containers)), Labels: labels, IsParent: isParent, + ReadOnly: image.IsReadOnly(), } imageList = append(imageList, i) } @@ -98,6 +99,8 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { return err } + fmt.Println("DAN isReadOnly %d", newImage.IsReadOnly()) + il := iopodman.Image{ Id: newImage.ID(), ParentId: newImage.Parent, @@ -109,6 +112,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { Containers: int64(len(containers)), Labels: labels, TopLayer: newImage.TopLayer(), + ReadOnly: newImage.IsReadOnly(), } return call.ReplyGetImage(il) } diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index b6dae33ee..4eadc77e7 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -388,4 +388,20 @@ LABEL "com.example.vendor"="Example Vendor" output = session.OutputToString() Expect(output).To(Equal("[]")) }) + + It("podman images --filter readonly", func() { + SkipIfRemote() + dockerfile := `FROM docker.io/library/alpine:latest +` + podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") + result := podmanTest.Podman([]string{"images", "-f", "readonly=true"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + result1 := podmanTest.Podman([]string{"images", "--filter", "readonly=false"}) + result1.WaitWithDefaultTimeout() + Expect(result1.ExitCode()).To(Equal(0)) + Expect(result.OutputToStringArray()).To(Not(Equal(result1.OutputToStringArray()))) + }) + }) -- cgit v1.2.3-54-g00ecf