diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index d3fd17440a..4e4b001add 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -61,6 +61,7 @@ func imageRemoveFlagSet(flags *pflag.FlagSet) { flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images") flags.BoolVarP(&imageOpts.Ignore, "ignore", "i", false, "Ignore errors if a specified image does not exist") flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image") + flags.BoolVar(&imageOpts.NoPrune, "no-prune", false, "Do not remove dangling images") } func rm(cmd *cobra.Command, args []string) error { diff --git a/docs/source/markdown/podman-rmi.1.md b/docs/source/markdown/podman-rmi.1.md index 8d0e5e5009..93658daafa 100644 --- a/docs/source/markdown/podman-rmi.1.md +++ b/docs/source/markdown/podman-rmi.1.md @@ -28,6 +28,9 @@ This option will cause podman to remove all containers that are using the image If a specified image does not exist in the local storage, ignore it and do not throw an error. +#### **--no-prune** + +This options will not remove dangling parents of specified image Remove an image by its short ID ``` diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 67943ecf17..bccaad9328 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -547,6 +547,7 @@ func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { Ignore bool `schema:"ignore"` LookupManifest bool `schema:"lookupManifest"` Images []string `schema:"images"` + NoPrune bool `schema:"noprune"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -554,7 +555,7 @@ func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { return } - opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore, LookupManifest: query.LookupManifest} + opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore, LookupManifest: query.LookupManifest, NoPrune: query.NoPrune} imageEngine := abi.ImageEngine{Libpod: runtime} rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts) strErrs := errorhandling.ErrorsToStrings(rmErrors) diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 0664afc1bc..9783a8e184 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -15,6 +15,8 @@ type RemoveOptions struct { Ignore *bool // Confirms if given name is a manifest list and removes it, otherwise returns error. LookupManifest *bool + // Does not remove dangling parent images + NoPrune *bool } //go:generate go run ../generator/generator.go DiffOptions diff --git a/pkg/bindings/images/types_remove_options.go b/pkg/bindings/images/types_remove_options.go index 559ebcfd53..8972ac93c7 100644 --- a/pkg/bindings/images/types_remove_options.go +++ b/pkg/bindings/images/types_remove_options.go @@ -76,3 +76,18 @@ func (o *RemoveOptions) GetLookupManifest() bool { } return *o.LookupManifest } + +// WithNoPrune set field NoPrune to given value +func (o *RemoveOptions) WithNoPrune(value bool) *RemoveOptions { + o.NoPrune = &value + return o +} + +// GetNoPrune returns value of field NoPrune +func (o *RemoveOptions) GetNoPrune() bool { + if o.NoPrune == nil { + var z bool + return z + } + return *o.NoPrune +} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index dad2dc6cca..21c1372b99 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -94,6 +94,8 @@ type ImageRemoveOptions struct { Ignore bool // Confirms if given name is a manifest list and removes it, otherwise returns error. LookupManifest bool + // NoPrune will not remove dangling images + NoPrune bool } // ImageRemoveReport is the response for removing one or more image(s) from storage diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 94178a8e2e..1f34cbd010 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -565,6 +565,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie libimageOptions.Force = opts.Force libimageOptions.Ignore = opts.Ignore libimageOptions.LookupManifest = opts.LookupManifest + libimageOptions.NoPrune = opts.NoPrune if !opts.All { libimageOptions.Filters = append(libimageOptions.Filters, "intermediate=false") } @@ -581,7 +582,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie rmErrors = libimageErrors - return + return report, rmErrors } // Shutdown Libpod engine diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 4f79325fd4..4fecefaa38 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -28,7 +28,7 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo } func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) { - options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All).WithLookupManifest(opts.LookupManifest) + options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All).WithLookupManifest(opts.LookupManifest).WithNoPrune(opts.NoPrune) return images.Remove(ir.ClientCtx, imagesArg, options) } diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index d1a0cd6f54..f87f65c340 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -307,4 +307,82 @@ RUN touch %s`, CIRROS_IMAGE, imageName) } wg.Wait() }) + + It("podman rmi --no-prune with dangling parents", func() { + podmanTest.AddImageToRWStore(ALPINE) + session := podmanTest.Podman([]string{"create", "--name", "c_test1", ALPINE, "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test1", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"create", "--name", "c_test2", "test1", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test2", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID2 := session.OutputToString() + + session = podmanTest.Podman([]string{"create", "--name", "c_test3", "test2", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test3", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID3 := session.OutputToString() + + session = podmanTest.Podman([]string{"untag", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"untag", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"rmi", "-f", "--no-prune", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring(imageID3)) + Expect(session.OutputToString()).NotTo(ContainSubstring(imageID2)) + }) + + It("podman rmi --no-prune with undangling parents", func() { + podmanTest.AddImageToRWStore(ALPINE) + session := podmanTest.Podman([]string{"create", "--name", "c_test1", ALPINE, "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test1", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"create", "--name", "c_test2", "test1", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test2", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID2 := session.OutputToString() + + session = podmanTest.Podman([]string{"create", "--name", "c_test3", "test2", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test3", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID3 := session.OutputToString() + + session = podmanTest.Podman([]string{"rmi", "-f", "--no-prune", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring(imageID3)) + Expect(session.OutputToString()).NotTo(ContainSubstring(imageID2)) + }) })