mirror of
https://github.com/containers/podman.git
synced 2025-06-27 05:26:50 +08:00
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 <dwalsh@redhat.com>
This commit is contained in:
2
API.md
2
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)
|
||||
### <a name="ImageHistory"></a>type ImageHistory
|
||||
|
||||
ImageHistory describes the returned structure from ImageHistory.
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 <none> 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 <dwalsh@redhat.com>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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())))
|
||||
})
|
||||
|
||||
})
|
||||
|
Reference in New Issue
Block a user