From 472a721bdd5007fe4b68a982ac5f979bec40a3d1 Mon Sep 17 00:00:00 2001 From: Kunal Kushwaha Date: Tue, 12 Nov 2019 13:50:35 +0900 Subject: warning added before image prune command Warning message added before executing image prune Added a force option, to execute without user input. Signed-off-by: Kunal Kushwaha --- cmd/podman/cliconfig/config.go | 3 ++- cmd/podman/images_prune.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 780b68333..ff38064f7 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -175,7 +175,8 @@ type HistoryValues struct { } type PruneImagesValues struct { PodmanCommand - All bool + All bool + Force bool } type PruneContainersValues struct { diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 5745edd6b..9c0a18c41 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -1,7 +1,10 @@ package main import ( + "bufio" "fmt" + "os" + "strings" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" @@ -34,9 +37,23 @@ func init() { pruneImagesCommand.SetUsageTemplate(UsageTemplate()) 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") } func pruneImagesCmd(c *cliconfig.PruneImagesValues) error { + if !c.Force { + reader := bufio.NewReader(os.Stdin) + fmt.Printf(` +WARNING! This will remove all dangling images. +Are you sure you want to continue? [y/N] `) + ans, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(ans)[0] != 'y' { + return nil + } + } runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") -- cgit v1.2.3-54-g00ecf From 5082496cc05a11d72e8658cce857523df41c203f Mon Sep 17 00:00:00 2001 From: Kunal Kushwaha Date: Thu, 14 Nov 2019 17:30:46 +0900 Subject: 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 --- API.md | 4 +- cmd/podman/cliconfig/config.go | 5 ++- cmd/podman/images_prune.go | 3 +- cmd/podman/system_prune.go | 3 +- cmd/podman/varlink/io.podman.varlink | 2 +- libpod/image/image.go | 5 +++ libpod/image/prune.go | 79 ++++++++++++++++++++++++++++++++++-- pkg/adapter/runtime.go | 6 +-- pkg/adapter/runtime_remote.go | 4 +- pkg/varlinkapi/images.go | 8 ++-- test/e2e/prune_test.go | 4 +- 11 files changed, 101 insertions(+), 22 deletions(-) (limited to 'cmd') diff --git a/API.md b/API.md index d96ea6cd0..c288e6b28 100755 --- a/API.md +++ b/API.md @@ -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 ### func ImagesPrune
-method ImagesPrune(all: [bool](https://godoc.org/builtin#bool)) [[]string](#[]string)
+method ImagesPrune(all: [bool](https://godoc.org/builtin#bool), filter: [[]string](#[]string)) [[]string](#[]string) ImagesPrune removes all unused images from the local store. Upon successful pruning, the IDs of the removed images are returned. ### func ImportImage diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index ff38064f7..158306ccc 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -175,8 +175,9 @@ type HistoryValues struct { } type PruneImagesValues struct { PodmanCommand - All bool - Force bool + All bool + Force bool + Filter []string } type PruneContainersValues struct { diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 9c0a18c41..2b498f83d 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -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==')") } 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) diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go index b499d8dd2..c4d76b2dd 100644 --- a/cmd/podman/system_prune.go +++ b/cmd/podman/system_prune.go @@ -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 { diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index f9339fccb..4f810dd53 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -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) diff --git a/libpod/image/image.go b/libpod/image/image.go index c912ac2ca..5871c8a57 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -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") diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 006cbdf22..f5be8ed50 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -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") } diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 81a43853c..069283bde 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -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 diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 12bf550f2..32eb934bf 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -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 diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 8d44e6373..c27088805 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -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()) } diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index df0525a79..eba12dca4 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -64,7 +64,7 @@ var _ = Describe("Podman prune", func() { hasNone, _ := none.GrepString("") 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)) -- cgit v1.2.3-54-g00ecf