filter added to image pruge command.

filter option accepts two filters.
- label
- until
label supports "label=value" or "label=key=value" format
until supports all golang compatible time/duration formats.

Signed-off-by: Kunal Kushwaha <kunal.kushwaha@gmail.com>
This commit is contained in:
Kunal Kushwaha
2019-11-14 17:30:46 +09:00
parent c200583f31
commit 5082496cc0
11 changed files with 101 additions and 22 deletions

4
API.md
View File

@ -95,7 +95,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[func ImageSave(options: ImageSaveOptions) MoreResponse](#ImageSave)
[func ImagesPrune(all: bool) []string](#ImagesPrune)
[func ImagesPrune(all: bool, filter: []string) []string](#ImagesPrune)
[func ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) string](#ImportImage)
@ -766,7 +766,7 @@ ImageSave allows you to save an image from the local image storage to a tarball
### <a name="ImagesPrune"></a>func ImagesPrune
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
method ImagesPrune(all: [bool](https://godoc.org/builtin#bool)) [[]string](#[]string)</div>
method ImagesPrune(all: [bool](https://godoc.org/builtin#bool), filter: [[]string](#[]string)) [[]string](#[]string)</div>
ImagesPrune removes all unused images from the local store. Upon successful pruning,
the IDs of the removed images are returned.
### <a name="ImportImage"></a>func ImportImage

View File

@ -175,8 +175,9 @@ type HistoryValues struct {
}
type PruneImagesValues struct {
PodmanCommand
All bool
Force bool
All bool
Force bool
Filter []string
}
type PruneContainersValues struct {

View File

@ -38,6 +38,7 @@ func init() {
flags := pruneImagesCommand.Flags()
flags.BoolVarP(&pruneImagesCommand.All, "all", "a", false, "Remove all unused images, not just dangling ones")
flags.BoolVarP(&pruneImagesCommand.Force, "force", "f", false, "Do not prompt for confirmation")
flags.StringArrayVar(&pruneImagesCommand.Filter, "filter", []string{}, "Provide filter values (e.g. 'label=<key>=<value>')")
}
func pruneImagesCmd(c *cliconfig.PruneImagesValues) error {
@ -62,7 +63,7 @@ Are you sure you want to continue? [y/N] `)
// Call prune; if any cids are returned, print them and then
// return err in case an error also came up
pruneCids, err := runtime.PruneImages(getContext(), c.All)
pruneCids, err := runtime.PruneImages(getContext(), c.All, c.Filter)
if len(pruneCids) > 0 {
for _, cid := range pruneCids {
fmt.Println(cid)

View File

@ -117,7 +117,8 @@ Are you sure you want to continue? [y/N] `, volumeString)
// Call prune; if any cids are returned, print them and then
// return err in case an error also came up
pruneCids, err := runtime.PruneImages(ctx, c.All)
// TODO: support for filters in system prune
pruneCids, err := runtime.PruneImages(ctx, c.All, []string{})
if len(pruneCids) > 0 {
fmt.Println("Deleted Images")
for _, cid := range pruneCids {

View File

@ -1217,7 +1217,7 @@ method UnmountContainer(name: string, force: bool) -> ()
# ImagesPrune removes all unused images from the local store. Upon successful pruning,
# the IDs of the removed images are returned.
method ImagesPrune(all: bool) -> (pruned: []string)
method ImagesPrune(all: bool, filter: []string) -> (pruned: []string)
# This function is not implemented yet.
# method ListContainerPorts(name: string) -> (notimplemented: NotImplemented)

View File

@ -74,6 +74,11 @@ type InfoImage struct {
Layers []LayerInfo
}
// ImageFilter is a function to determine whether a image is included
// in command output. Images to be outputted are tested using the function.
// A true return will include the image, a false return will exclude it.
type ImageFilter func(*Image) bool //nolint
// ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store
var ErrRepoTagNotFound = stderrors.New("unable to match user input to any specific repotag")

View File

@ -2,23 +2,78 @@ package image
import (
"context"
"strings"
"time"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/timetype"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) {
switch filter {
case "label":
var filterArray = strings.SplitN(filterValue, "=", 2)
var filterKey = filterArray[0]
if len(filterArray) > 1 {
filterValue = filterArray[1]
} else {
filterValue = ""
}
return func(i *Image) bool {
labels, err := i.Labels(context.Background())
if err != nil {
return false
}
for labelKey, labelValue := range labels {
if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) {
return true
}
}
return false
}, nil
case "until":
ts, err := timetype.GetTimestamp(filterValue, time.Now())
if err != nil {
return nil, err
}
seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0)
if err != nil {
return nil, err
}
until := time.Unix(seconds, nanoseconds)
return func(i *Image) bool {
if !until.IsZero() && i.Created().After((until)) {
return true
}
return false
}, nil
}
return nil, nil
}
// GetPruneImages returns a slice of images that have no names/unused
func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) {
func (ir *Runtime) GetPruneImages(all bool, filterFuncs []ImageFilter) ([]*Image, error) {
var (
pruneImages []*Image
)
allImages, err := ir.GetRWImages()
if err != nil {
return nil, err
}
for _, i := range allImages {
// filter the images based on this.
for _, filterFunc := range filterFuncs {
if !filterFunc(i) {
continue
}
}
if len(i.Names()) == 0 {
pruneImages = append(pruneImages, i)
continue
@ -38,9 +93,25 @@ func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) {
// PruneImages prunes dangling and optionally all unused images from the local
// image store
func (ir *Runtime) PruneImages(ctx context.Context, all bool) ([]string, error) {
var prunedCids []string
pruneImages, err := ir.GetPruneImages(all)
func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
var (
prunedCids []string
filterFuncs []ImageFilter
)
for _, f := range filter {
filterSplit := strings.SplitN(f, "=", 2)
if len(filterSplit) < 2 {
return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
}
generatedFunc, err := generatePruneFilterFuncs(filterSplit[0], filterSplit[1])
if err != nil {
return nil, errors.Wrapf(err, "invalid filter")
}
filterFuncs = append(filterFuncs, generatedFunc)
}
pruneImages, err := ir.GetPruneImages(all, filterFuncs)
if err != nil {
return nil, errors.Wrap(err, "unable to get images to prune")
}

View File

@ -27,7 +27,7 @@ import (
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
)
// LocalRuntime describes a typical libpod runtime
@ -147,8 +147,8 @@ func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, for
}
// PruneImages is wrapper into PruneImages within the image pkg
func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) {
return r.ImageRuntime().PruneImages(ctx, all)
func (r *LocalRuntime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
return r.ImageRuntime().PruneImages(ctx, all, filter)
}
// Export is a wrapper to container export to a tarfile

View File

@ -415,8 +415,8 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error)
}
// PruneImages is the wrapper call for a remote-client to prune images
func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) {
return iopodman.ImagesPrune().Call(r.Conn, all)
func (r *LocalRuntime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) {
return iopodman.ImagesPrune().Call(r.Conn, all, filter)
}
// Export is a wrapper to container export to a tarfile

View File

@ -21,7 +21,7 @@ import (
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
@ -29,7 +29,7 @@ import (
"github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
"github.com/containers/storage/pkg/archive"
"github.com/opencontainers/image-spec/specs-go/v1"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -740,8 +740,8 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.
}
// ImagesPrune ....
func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error {
prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all)
func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []string) error {
prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all, []string{})
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}

View File

@ -64,7 +64,7 @@ var _ = Describe("Podman prune", func() {
hasNone, _ := none.GrepString("<none>")
Expect(hasNone).To(BeTrue())
prune := podmanTest.Podman([]string{"image", "prune"})
prune := podmanTest.Podman([]string{"image", "prune", "-f"})
prune.WaitWithDefaultTimeout()
Expect(prune.ExitCode()).To(Equal(0))
@ -78,7 +78,7 @@ var _ = Describe("Podman prune", func() {
It("podman image prune unused images", func() {
podmanTest.RestoreAllArtifacts()
prune := podmanTest.PodmanNoCache([]string{"image", "prune", "-a"})
prune := podmanTest.PodmanNoCache([]string{"image", "prune", "-af"})
prune.WaitWithDefaultTimeout()
Expect(prune.ExitCode()).To(Equal(0))