From 581dd312af6ada92b96e16dd95d45967bde5fd8a Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Thu, 26 Mar 2020 17:16:59 -0700 Subject: V2 podman image prune * Fixed header for `podman image ls` * Implemented prune `all` flag, preserved filter method for backwards capability * Updated binding tests Signed-off-by: Jhon Honce --- cmd/podmanV2/images/images.go | 2 +- cmd/podmanV2/images/list.go | 2 +- cmd/podmanV2/images/prune.go | 86 +++++++++++++++++++++++++++++++++++++++ pkg/api/handlers/compat/images.go | 3 +- pkg/api/handlers/libpod/images.go | 7 ++-- pkg/bindings/images/images.go | 7 +++- pkg/bindings/test/images_test.go | 40 +++++++++++------- pkg/domain/entities/images.go | 7 ++-- pkg/domain/infra/abi/images.go | 11 +++-- pkg/domain/infra/tunnel/images.go | 12 ++++-- 10 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 cmd/podmanV2/images/prune.go diff --git a/cmd/podmanV2/images/images.go b/cmd/podmanV2/images/images.go index f248aa65f..d00f0996e 100644 --- a/cmd/podmanV2/images/images.go +++ b/cmd/podmanV2/images/images.go @@ -15,7 +15,7 @@ var ( Args: listCmd.Args, Short: listCmd.Short, Long: listCmd.Long, - PreRunE: listCmd.PreRunE, + PreRunE: preRunE, RunE: listCmd.RunE, Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), } diff --git a/cmd/podmanV2/images/list.go b/cmd/podmanV2/images/list.go index 4714af3e4..9a5b47299 100644 --- a/cmd/podmanV2/images/list.go +++ b/cmd/podmanV2/images/list.go @@ -212,7 +212,7 @@ func imageListFormat(flags listFlagType) (string, string) { row += "\t{{.Digest}}" } - hdr += "\tID" + hdr += "\tIMAGE ID" if flags.noTrunc { row += "\tsha256:{{.ID}}" } else { diff --git a/cmd/podmanV2/images/prune.go b/cmd/podmanV2/images/prune.go new file mode 100644 index 000000000..6577c458e --- /dev/null +++ b/cmd/podmanV2/images/prune.go @@ -0,0 +1,86 @@ +package images + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + pruneDescription = `Removes all unnamed images from local storage. + + If an image is not being used by a container, it will be removed from the system.` + pruneCmd = &cobra.Command{ + Use: "prune", + Args: cobra.NoArgs, + Short: "Remove unused images", + Long: pruneDescription, + RunE: prune, + Example: `podman image prune`, + } + + pruneOpts = entities.ImagePruneOptions{} + force bool + filter = []string{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pruneCmd, + Parent: imageCmd, + }) + + flags := pruneCmd.Flags() + flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all unused images, not just dangling ones") + flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation") + flags.StringArrayVar(&filter, "filter", []string{}, "Provide filter values (e.g. 'label==')") + +} + +func prune(cmd *cobra.Command, args []string) error { + if !force { + reader := bufio.NewReader(os.Stdin) + fmt.Printf(` +WARNING! This will remove all dangling images. +Are you sure you want to continue? [y/N] `) + answer, err := reader.ReadString('\n') + if err != nil { + return errors.Wrapf(err, "error reading input") + } + if strings.ToLower(answer)[0] != 'y' { + return nil + } + } + + // TODO Remove once filter refactor is finished and url.Values rules :) + for _, f := range filter { + t := strings.SplitN(f, "=", 2) + pruneOpts.Filters.Add(t[0], t[1]) + } + + results, err := registry.ImageEngine().Prune(registry.GetContext(), pruneOpts) + if err != nil { + return err + } + + for _, i := range results.Report.Id { + fmt.Println(i) + } + + for _, e := range results.Report.Err { + fmt.Fprint(os.Stderr, e.Error()+"\n") + } + + if results.Size > 0 { + fmt.Fprintf(os.Stdout, "Size: %d\n", results.Size) + } + + return nil +} diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index cce718f54..354a13bf5 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -64,6 +64,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { + All bool Filters map[string][]string `schema:"filters"` }{ // This is where you can override the golang default value for one of fields @@ -80,7 +81,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) + pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index f8e666451..ee85c1a41 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -119,12 +119,12 @@ func GetImages(w http.ResponseWriter, r *http.Request) { func PruneImages(w http.ResponseWriter, r *http.Request) { var ( - all bool err error ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { + All bool `schema:"all"` Filters map[string][]string `schema:"filters"` }{ // override any golang type defaults @@ -140,7 +140,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { if _, found := r.URL.Query()["filters"]; found { dangling := query.Filters["all"] if len(dangling) > 0 { - all, err = strconv.ParseBool(query.Filters["all"][0]) + query.All, err = strconv.ParseBool(query.Filters["all"][0]) if err != nil { utils.InternalServerError(w, err) return @@ -152,7 +152,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) } } - cids, err := runtime.ImageRuntime().PruneImages(r.Context(), all, libpodFilters) + + cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index e67965042..5e3af7a60 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -154,7 +154,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c // Prune removes unused images from local storage. The optional filters can be used to further // define which images should be pruned. -func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { +func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]string, error) { var ( deleted []string ) @@ -163,6 +163,9 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { return nil, err } params := url.Values{} + if all != nil { + params.Set("all", strconv.FormatBool(*all)) + } if filters != nil { stringFilter, err := bindings.FiltersToString(filters) if err != nil { @@ -174,7 +177,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { if err != nil { return deleted, err } - return deleted, response.Process(nil) + return deleted, response.Process(&deleted) } // Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required. diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 5e4cfe7be..13b6086c3 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -16,22 +16,22 @@ import ( var _ = Describe("Podman images", func() { var ( - //tempdir string - //err error - //podmanTest *PodmanTestIntegration + // tempdir string + // err error + // podmanTest *PodmanTestIntegration bt *bindingTest s *gexec.Session err error ) BeforeEach(func() { - //tempdir, err = CreateTempDirInTempDir() - //if err != nil { + // tempdir, err = CreateTempDirInTempDir() + // if err != nil { // os.Exit(1) - //} - //podmanTest = PodmanTestCreate(tempdir) - //podmanTest.Setup() - //podmanTest.SeedImages() + // } + // podmanTest = PodmanTestCreate(tempdir) + // podmanTest.Setup() + // podmanTest.SeedImages() bt = newBindingTest() bt.RestoreImagesFromCache() s = bt.startAPIService() @@ -41,12 +41,13 @@ var _ = Describe("Podman images", func() { }) AfterEach(func() { - //podmanTest.Cleanup() - //f := CurrentGinkgoTestDescription() - //processTestResult(f) + // podmanTest.Cleanup() + // f := CurrentGinkgoTestDescription() + // processTestResult(f) s.Kill() bt.cleanup() }) + It("inspect image", func() { // Inspect invalid image be 404 _, err = images.GetImage(bt.conn, "foobar5000", nil) @@ -71,7 +72,7 @@ var _ = Describe("Podman images", func() { Expect(err).To(BeNil()) // TODO it looks like the images API alwaays returns size regardless // of bool or not. What should we do ? - //Expect(data.Size).To(BeZero()) + // Expect(data.Size).To(BeZero()) // Enabling the size parameter should result in size being populated data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue) @@ -142,7 +143,7 @@ var _ = Describe("Podman images", func() { err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName) Expect(err).To(BeNil()) - //Validates if name updates when the image is retagged. + // Validates if name updates when the image is retagged. _, err := images.GetImage(bt.conn, "alpine:demo", nil) Expect(err).To(BeNil()) @@ -165,7 +166,7 @@ var _ = Describe("Podman images", func() { Expect(err).To(BeNil()) Expect(len(imageSummary)).To(Equal(3)) - //Validate the image names. + // Validate the image names. var names []string for _, i := range imageSummary { names = append(names, i.RepoTags...) @@ -289,6 +290,7 @@ var _ = Describe("Podman images", func() { Expect(data.Comment).To(Equal(testMessage)) }) + It("History Image", func() { // a bogus name should return a 404 _, err := images.History(bt.conn, "foobar") @@ -343,4 +345,12 @@ var _ = Describe("Podman images", func() { Expect(len(imgs)).To(BeNumerically(">=", 1)) }) + It("Prune images", func() { + trueBoxed := true + results, err := images.Prune(bt.conn, &trueBoxed, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(len(results)).To(BeNumerically(">", 0)) + Expect(results).To(ContainElement("docker.io/library/alpine:latest")) + }) + }) diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 4a51b3de4..20af0356f 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -119,7 +119,7 @@ type ImageInspectOptions struct { type ImageListOptions struct { All bool `json:"all" schema:"all"` - Filter []string `json:",omitempty"` + Filter []string `json:"Filter,omitempty"` Filters url.Values `json:"filters" schema:"filters"` } @@ -128,8 +128,9 @@ type ImageListOptions struct { // } type ImagePruneOptions struct { - All bool - Filter ImageFilter + All bool `json:"all" schema:"all"` + Filter []string `json:"filter" schema:"filter"` + Filters url.Values `json:"filters" schema:"filters"` } type ImagePruneReport struct { diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index e3b3e310b..44420c1e1 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -88,13 +88,18 @@ func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{}) + results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) if err != nil { return nil, err } - report := entities.ImagePruneReport{} - copy(report.Report.Id, results) + report := entities.ImagePruneReport{ + Report: entities.Report{ + Id: results, + Err: nil, + }, + Size: 0, + } return &report, nil } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 6a241641e..6a3adc9ee 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -2,7 +2,6 @@ package tunnel import ( "context" - "net/url" images "github.com/containers/libpod/pkg/bindings/images" "github.com/containers/libpod/pkg/domain/entities" @@ -72,12 +71,17 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entiti } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := images.Prune(ir.ClientCxt, url.Values{}) + results, err := images.Prune(ir.ClientCxt, &opts.All, opts.Filters) if err != nil { return nil, err } - report := entities.ImagePruneReport{} - copy(report.Report.Id, results) + report := entities.ImagePruneReport{ + Report: entities.Report{ + Id: results, + Err: nil, + }, + Size: 0, + } return &report, nil } -- cgit v1.2.3-54-g00ecf