Skip to content

Commit

Permalink
Merge pull request #10363 from vrothberg/fix-10350
Browse files Browse the repository at this point in the history
image prune: remove unused images only with `--all`
  • Loading branch information
openshift-merge-robot authored May 17, 2021
2 parents 62a7d4b + 2a43fcf commit 93c3e03
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 30 deletions.
8 changes: 3 additions & 5 deletions cmd/podman/images/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import (
)

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{
pruneDescription = `Removes dangling or unused images from local storage.`
pruneCmd = &cobra.Command{
Use: "prune [options]",
Args: validate.NoArgs,
Short: "Remove unused images",
Expand All @@ -41,7 +39,7 @@ func init() {
})

flags := pruneCmd.Flags()
flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all unused images, not just dangling ones")
flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all images not in use by containers, not just dangling ones")
flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation")

filterFlagName := "filter"
Expand Down
3 changes: 1 addition & 2 deletions docs/source/markdown/podman-image-prune.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ podman-image-prune - Remove all unused images from the local store

## DESCRIPTION
**podman image prune** removes all dangling images from local storage. With the `all` option,
you can delete all unused images. Unused images are dangling images as well as any image that
does not have any containers based on it.
you can delete all unused images (i.e., images not in use by any container).

The image prune command does not prune cache images that only use layers that are necessary for other images.

Expand Down
30 changes: 7 additions & 23 deletions pkg/domain/infra/abi/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,50 +40,34 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo
}

func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) {
// NOTE: the terms "dangling" and "intermediate" are not used
// consistently across our code base. In libimage, "dangling" means
// that an image has no tags. "intermediate" means that an image is
// dangling and that no other image depends on it (i.e., has no
// children).
//
// While pruning usually refers to "dangling" images, it has always
// removed "intermediate" ones.
defaultOptions := &libimage.RemoveImagesOptions{
Filters: append(opts.Filter, "intermediate=true", "containers=false", "readonly=false"),
pruneOptions := &libimage.RemoveImagesOptions{
Filters: append(opts.Filter, "containers=false", "readonly=false"),
WithSize: true,
}

// `image prune --all` means to *also* remove images which are not in
// use by any container. Since image filters are chained, we need to
// do two look ups since the default ones are a subset of all.
unusedOptions := &libimage.RemoveImagesOptions{
Filters: append(opts.Filter, "containers=false", "readonly=false"),
WithSize: true,
if !opts.All {
pruneOptions.Filters = append(pruneOptions.Filters, "dangling=true")
}

var pruneReports []*reports.PruneReport

// Now prune all images until we converge.
numPreviouslyRemovedImages := 1
for {
removedDefault, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, defaultOptions)
if rmErrors != nil {
return nil, errorhandling.JoinErrors(rmErrors)
}
removedUnused, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, unusedOptions)
removedImages, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, pruneOptions)
if rmErrors != nil {
return nil, errorhandling.JoinErrors(rmErrors)
}

for _, rmReport := range append(removedDefault, removedUnused...) {
for _, rmReport := range removedImages {
r := *rmReport
pruneReports = append(pruneReports, &reports.PruneReport{
Id: r.ID,
Size: uint64(r.Size),
})
}

numRemovedImages := len(removedDefault) + len(removedUnused)
numRemovedImages := len(removedImages)
if numRemovedImages+numPreviouslyRemovedImages == 0 {
break
}
Expand Down
47 changes: 47 additions & 0 deletions test/e2e/prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,53 @@ var _ = Describe("Podman prune", func() {
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
})

It("podman image prune - remove only dangling images", func() {
session := podmanTest.Podman([]string{"images", "-a"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
hasNone, _ := session.GrepString("<none>")
Expect(hasNone).To(BeFalse())
numImages := len(session.OutputToStringArray())

// Since there's no dangling image, none should be removed.
session = podmanTest.Podman([]string{"image", "prune", "-f"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(len(session.OutputToStringArray())).To(Equal(0))

// Let's be extra sure that the same number of images is
// reported.
session = podmanTest.Podman([]string{"images", "-a"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(len(session.OutputToStringArray())).To(Equal(numImages))

// Now build a new image with dangling intermediate images.
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")

session = podmanTest.Podman([]string{"images", "-a"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
hasNone, _ = session.GrepString("<none>")
Expect(hasNone).To(BeTrue()) // ! we have dangling ones
numImages = len(session.OutputToStringArray())

// Since there's at least one dangling image, prune should
// remove them.
session = podmanTest.Podman([]string{"image", "prune", "-f"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
numPrunedImages := len(session.OutputToStringArray())
Expect(numPrunedImages >= 1).To(BeTrue())

// Now make sure that exactly the number of pruned images has
// been removed.
session = podmanTest.Podman([]string{"images", "-a"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(len(session.OutputToStringArray())).To(Equal(numImages - numPrunedImages))
})

It("podman image prune skip cache images", func() {
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")

Expand Down

0 comments on commit 93c3e03

Please sign in to comment.