From 472a721bdd5007fe4b68a982ac5f979bec40a3d1 Mon Sep 17 00:00:00 2001
From: Kunal Kushwaha <kunal.kushwaha@gmail.com>
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 <kunal.kushwaha@gmail.com>
---
 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 <kunal.kushwaha@gmail.com>
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 <kunal.kushwaha@gmail.com>
---
 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
 ### <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
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=<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)
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("<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))
 
-- 
cgit v1.2.3-54-g00ecf