From a9a54eefab34b25628906a786d6c4ca302f743b1 Mon Sep 17 00:00:00 2001
From: Valentin Rothberg <rothberg@redhat.com>
Date: Fri, 24 Sep 2021 14:39:36 +0200
Subject: [PATCH] image prune: support removing external containers

Support removing external containers (e.g., build containers) during
image prune.

Fixes: #11472
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
---
 cmd/podman/images/prune.go                    |  1 +
 docs/source/markdown/podman-image-prune.1.md  |  4 ++
 go.mod                                        |  3 +-
 go.sum                                        |  4 +-
 libpod/runtime_img.go                         | 21 ++++++++
 pkg/api/handlers/libpod/images.go             |  8 +--
 pkg/api/server/register_images.go             |  6 +++
 pkg/bindings/images/types.go                  |  2 +
 pkg/bindings/images/types_prune_options.go    | 15 ++++++
 pkg/domain/entities/images.go                 |  5 +-
 pkg/domain/infra/abi/images.go                | 12 ++++-
 pkg/domain/infra/tunnel/images.go             |  2 +-
 test/system/040-ps.bats                       | 24 +++++++--
 test/system/330-corrupt-images.bats           |  2 +-
 .../containers/common/libimage/copier.go      |  2 +-
 .../containers/common/libimage/filters.go     | 38 ++++++++++----
 .../containers/common/libimage/image.go       | 50 ++++++++++++-------
 .../common/libimage/manifests/manifests.go    | 10 ++--
 .../containers/common/libimage/runtime.go     | 35 +++++++++++--
 .../common/pkg/apparmor/apparmor_linux.go     | 12 ++---
 .../containers/common/pkg/retry/retry.go      |  2 +-
 .../common/pkg/subscriptions/subscriptions.go | 10 ++--
 .../common/pkg/supplemented/supplemented.go   |  4 +-
 .../common/pkg/sysinfo/sysinfo_linux.go       |  2 +-
 .../containers/common/version/version.go      |  2 +-
 vendor/modules.txt                            |  2 +-
 26 files changed, 208 insertions(+), 70 deletions(-)

diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go
index 7e6a29d947..6c39e5c69d 100644
--- a/cmd/podman/images/prune.go
+++ b/cmd/podman/images/prune.go
@@ -41,6 +41,7 @@ func init() {
 
 	flags := pruneCmd.Flags()
 	flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all images not in use by containers, not just dangling ones")
+	flags.BoolVarP(&pruneOpts.External, "external", "", false, "Remove images even when they are used by external containers (e.g., by build containers)")
 	flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation")
 
 	filterFlagName := "filter"
diff --git a/docs/source/markdown/podman-image-prune.1.md b/docs/source/markdown/podman-image-prune.1.md
index bd08d18fc1..493332ec02 100644
--- a/docs/source/markdown/podman-image-prune.1.md
+++ b/docs/source/markdown/podman-image-prune.1.md
@@ -17,6 +17,10 @@ The image prune command does not prune cache images that only use layers that ar
 
 Remove dangling images and images that have no associated containers.
 
+#### **--external**
+
+Remove images even when they are used by external containers (e.g., build containers).
+
 #### **--filter**=*filters*
 
 Provide filter values.
diff --git a/go.mod b/go.mod
index a2a246cf59..3b6e383926 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/containernetworking/cni v1.0.1
 	github.com/containernetworking/plugins v1.0.1
 	github.com/containers/buildah v1.23.0
-	github.com/containers/common v0.46.0
+	github.com/containers/common v0.46.1-0.20210928081721-32e20295f1c6
 	github.com/containers/conmon v2.0.20+incompatible
 	github.com/containers/image/v5 v5.16.0
 	github.com/containers/ocicrypt v1.1.2
@@ -61,7 +61,6 @@ require (
 	github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
 	github.com/uber/jaeger-client-go v2.29.1+incompatible
 	github.com/vbauerster/mpb/v6 v6.0.4
-	github.com/vbauerster/mpb/v7 v7.1.4 // indirect
 	github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
 	go.etcd.io/bbolt v1.3.6
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
diff --git a/go.sum b/go.sum
index c8d8f68ff5..e25bb592a1 100644
--- a/go.sum
+++ b/go.sum
@@ -250,8 +250,8 @@ github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNB
 github.com/containers/buildah v1.23.0 h1:qGIeSNOczUHzvnaaOS29HSMiYAjw6JgIXYksAyvqnLs=
 github.com/containers/buildah v1.23.0/go.mod h1:K0iMKgy/MffkkgELBXhSXwTy2HTT6hM0X8qruDR1FwU=
 github.com/containers/common v0.44.0/go.mod h1:7sdP4vmI5Bm6FPFxb3lvAh1Iktb6tiO1MzjUzhxdoGo=
-github.com/containers/common v0.46.0 h1:95zB7kYBQJW+aK5xxZnaobCwoPyYOf85Y0yUx0E5aRg=
-github.com/containers/common v0.46.0/go.mod h1:zxv7KjdYddSGoWuLUVp6eSb++Ow1zmSMB2jwxuNB4cU=
+github.com/containers/common v0.46.1-0.20210928081721-32e20295f1c6 h1:DojkCc4a9f3WB25Fk0GDap1/OkKU9UmDLvPJyqw3TBc=
+github.com/containers/common v0.46.1-0.20210928081721-32e20295f1c6/go.mod h1:L4+sJlqi+R7frlbiWBW0baPra/cH8u5ZYwbxkukw3Lk=
 github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
 github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
 github.com/containers/image/v5 v5.16.0 h1:WQcNSzb7+ngS2cfynx0vUwhk+scpgiKlldVcsF8GPbI=
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index 66cf7a4d59..1915a5c4df 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -48,6 +48,27 @@ func (r *Runtime) RemoveContainersForImageCallback(ctx context.Context) libimage
 	}
 }
 
+// IsExternalContainerCallback returns a callback that be used in `libimage` to
+// figure out whether a given container is an external one.  A container is
+// considered external if it is not present in libpod's database.
+func (r *Runtime) IsExternalContainerCallback(_ context.Context) libimage.IsExternalContainerFunc {
+	// NOTE: pruning external containers is subject to race conditions
+	// (e.g., when a container gets removed). To address this and similar
+	// races, pruning had to happen inside c/storage.  Containers has to be
+	// labelled with "podman/libpod" along with callbacks similar to
+	// libimage.
+	return func(idOrName string) (bool, error) {
+		_, err := r.LookupContainer(idOrName)
+		if err == nil {
+			return false, nil
+		}
+		if errors.Is(err, define.ErrNoSuchCtr) {
+			return true, nil
+		}
+		return false, nil
+	}
+}
+
 // newBuildEvent creates a new event based on completion of a built image
 func (r *Runtime) newImageBuildCompleteEvent(idOrName string) {
 	e := events.NewEvent(events.Build)
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 0023479eae..1c6cc917c0 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -150,7 +150,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
 	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
 	decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
 	query := struct {
-		All bool `schema:"all"`
+		All      bool `schema:"all"`
+		External bool `schema:"external"`
 	}{
 		// override any golang type defaults
 	}
@@ -190,8 +191,9 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
 	imageEngine := abi.ImageEngine{Libpod: runtime}
 
 	pruneOptions := entities.ImagePruneOptions{
-		All:    query.All,
-		Filter: libpodFilters,
+		All:      query.All,
+		External: query.External,
+		Filter:   libpodFilters,
 	}
 	imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions)
 	if err != nil {
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 5e0de7defe..aa573eaa6e 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -1050,6 +1050,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
 	//    description: |
 	//      Remove all images not in use by containers, not just dangling ones
 	//  - in: query
+	//    name: external
+	//    default: false
+	//    type: boolean
+	//    description: |
+	//      Remove images even when they are used by external containers (e.g, by build containers)
+	//  - in: query
 	//    name: filters
 	//    type: string
 	//    description: |
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
index 6ff9f18ec7..dc6bd91c3d 100644
--- a/pkg/bindings/images/types.go
+++ b/pkg/bindings/images/types.go
@@ -74,6 +74,8 @@ type ExportOptions struct {
 type PruneOptions struct {
 	// Prune all images
 	All *bool
+	// Prune images even when they're used by external containers
+	External *bool
 	// Filters to apply when pruning images
 	Filters map[string][]string
 }
diff --git a/pkg/bindings/images/types_prune_options.go b/pkg/bindings/images/types_prune_options.go
index 77bef32e35..c9772045e8 100644
--- a/pkg/bindings/images/types_prune_options.go
+++ b/pkg/bindings/images/types_prune_options.go
@@ -32,6 +32,21 @@ func (o *PruneOptions) GetAll() bool {
 	return *o.All
 }
 
+// WithExternal set field External to given value
+func (o *PruneOptions) WithExternal(value bool) *PruneOptions {
+	o.External = &value
+	return o
+}
+
+// GetExternal returns value of field External
+func (o *PruneOptions) GetExternal() bool {
+	if o.External == nil {
+		var z bool
+		return z
+	}
+	return *o.External
+}
+
 // WithFilters set field Filters to given value
 func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions {
 	o.Filters = value
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 80d570764f..2822b1ad71 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -251,8 +251,9 @@ type ImageListOptions struct {
 }
 
 type ImagePruneOptions struct {
-	All    bool     `json:"all" schema:"all"`
-	Filter []string `json:"filter" schema:"filter"`
+	All      bool     `json:"all" schema:"all"`
+	External bool     `json:"external" schema:"external"`
+	Filter   []string `json:"filter" schema:"filter"`
 }
 
 type ImageTagOptions struct{}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 98d6684346..c060592056 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -41,13 +41,21 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo
 
 func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) {
 	pruneOptions := &libimage.RemoveImagesOptions{
-		Filters:  append(opts.Filter, "containers=false", "readonly=false"),
-		WithSize: true,
+		RemoveContainerFunc:     ir.Libpod.RemoveContainersForImageCallback(ctx),
+		IsExternalContainerFunc: ir.Libpod.IsExternalContainerCallback(ctx),
+		ExternalContainers:      opts.External,
+		Filters:                 append(opts.Filter, "readonly=false"),
+		WithSize:                true,
 	}
 
 	if !opts.All {
 		pruneOptions.Filters = append(pruneOptions.Filters, "dangling=true")
 	}
+	if opts.External {
+		pruneOptions.Filters = append(pruneOptions.Filters, "containers=external")
+	} else {
+		pruneOptions.Filters = append(pruneOptions.Filters, "containers=false")
+	}
 
 	var pruneReports []*reports.PruneReport
 
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 2827706132..d41a20348e 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -95,7 +95,7 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
 		f := strings.Split(filter, "=")
 		filters[f[0]] = f[1:]
 	}
-	options := new(images.PruneOptions).WithAll(opts.All).WithFilters(filters)
+	options := new(images.PruneOptions).WithAll(opts.All).WithFilters(filters).WithExternal(opts.External)
 	reports, err := images.Prune(ir.ClientCtx, options)
 	if err != nil {
 		return nil, err
diff --git a/test/system/040-ps.bats b/test/system/040-ps.bats
index 182d75547e..b87d177faa 100644
--- a/test/system/040-ps.bats
+++ b/test/system/040-ps.bats
@@ -92,24 +92,38 @@ load helpers
     # Force a buildah timeout; this leaves a buildah container behind
     PODMAN_TIMEOUT=5 run_podman 124 build -t thiswillneverexist - <<EOF
 FROM $IMAGE
+RUN touch /intermediate.image.to.be.pruned
 RUN sleep 30
 EOF
 
     run_podman ps -a
-    is "${#lines[@]}" "1" "podman ps -a does not see buildah container"
+    is "${#lines[@]}" "1" "podman ps -a does not see buildah containers"
 
     run_podman ps --external -a
-    is "${#lines[@]}" "2" "podman ps -a --external sees buildah container"
+    is "${#lines[@]}" "3" "podman ps -a --external sees buildah containers"
     is "${lines[1]}" \
        "[0-9a-f]\{12\} \+$IMAGE *buildah .* seconds ago .* storage .* ${PODMAN_TEST_IMAGE_NAME}-working-container" \
        "podman ps --external"
 
-    cid="${lines[1]:0:12}"
-
     # 'rm -a' should be a NOP
     run_podman rm -a
     run_podman ps --external -a
-    is "${#lines[@]}" "2" "podman ps -a --external sees buildah container"
+    is "${#lines[@]}" "3" "podman ps -a --external sees buildah containers"
+
+    # Cannot prune intermediate image as it's being used by a buildah
+    # container.
+    run_podman image prune -f
+    is "$output" "" "No image is pruned"
+
+    # --external for removing buildah containers.
+    run_podman image prune -f --external
+    is "${#lines[@]}" "1" "Image used by build container is pruned"
+
+    # One buildah container has been removed.
+    run_podman ps --external -a
+    is "${#lines[@]}" "2" "podman ps -a --external sees buildah containers"
+
+    cid="${lines[1]:0:12}"
 
     # We can't rm it without -f, but podman should issue a helpful message
     run_podman 2 rm "$cid"
diff --git a/test/system/330-corrupt-images.bats b/test/system/330-corrupt-images.bats
index eeffff3ec2..86da06cb0a 100644
--- a/test/system/330-corrupt-images.bats
+++ b/test/system/330-corrupt-images.bats
@@ -78,7 +78,7 @@ function _corrupt_image_test() {
 
         # Run the requested command. Confirm it succeeds, with suitable warnings
         run_podman $*
-        is "$output" ".*error determining parent of image.*ignoring the error" \
+        is "$output" ".*Failed to determine parent of image.*ignoring the error" \
            "$* with missing $what_to_rm"
 
         run_podman images -a --noheading
diff --git a/vendor/github.com/containers/common/libimage/copier.go b/vendor/github.com/containers/common/libimage/copier.go
index 42d3690b94..636b97bfde 100644
--- a/vendor/github.com/containers/common/libimage/copier.go
+++ b/vendor/github.com/containers/common/libimage/copier.go
@@ -304,7 +304,7 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
 
 	defaultContainerConfig, err := config.Default()
 	if err != nil {
-		logrus.Warnf("failed to get container config for copy options: %v", err)
+		logrus.Warnf("Failed to get container config for copy options: %v", err)
 	} else {
 		c.imageCopyOptions.MaxParallelDownloads = defaultContainerConfig.Engine.ImageParallelCopies
 	}
diff --git a/vendor/github.com/containers/common/libimage/filters.go b/vendor/github.com/containers/common/libimage/filters.go
index 0cc5cc3119..833f940cca 100644
--- a/vendor/github.com/containers/common/libimage/filters.go
+++ b/vendor/github.com/containers/common/libimage/filters.go
@@ -47,11 +47,11 @@ func filterImages(images []*Image, filters []filterFunc) ([]*Image, error) {
 // compileImageFilters creates `filterFunc`s for the specified filters.  The
 // required format is `key=value` with the following supported keys:
 //           after, since, before, containers, dangling, id, label, readonly, reference, intermediate
-func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]filterFunc, error) {
-	logrus.Tracef("Parsing image filters %s", filters)
+func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOptions) ([]filterFunc, error) {
+	logrus.Tracef("Parsing image filters %s", options.Filters)
 
 	filterFuncs := []filterFunc{}
-	for _, filter := range filters {
+	for _, filter := range options.Filters {
 		var key, value string
 		split := strings.SplitN(filter, "=", 2)
 		if len(split) != 2 {
@@ -77,11 +77,16 @@ func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]
 			filterFuncs = append(filterFuncs, filterBefore(img.Created()))
 
 		case "containers":
-			containers, err := strconv.ParseBool(value)
-			if err != nil {
-				return nil, errors.Wrapf(err, "non-boolean value %q for dangling filter", value)
+			switch value {
+			case "false", "true":
+			case "external":
+				if options.IsExternalContainerFunc == nil {
+					return nil, fmt.Errorf("libimage error: external containers filter without callback")
+				}
+			default:
+				return nil, fmt.Errorf("unsupported value %q for containers filter", value)
 			}
-			filterFuncs = append(filterFuncs, filterContainers(containers))
+			filterFuncs = append(filterFuncs, filterContainers(value, options.IsExternalContainerFunc))
 
 		case "dangling":
 			dangling, err := strconv.ParseBool(value)
@@ -190,13 +195,28 @@ func filterReadOnly(value bool) filterFunc {
 }
 
 // filterContainers creates a container filter for matching the specified value.
-func filterContainers(value bool) filterFunc {
+func filterContainers(value string, fn IsExternalContainerFunc) filterFunc {
 	return func(img *Image) (bool, error) {
 		ctrs, err := img.Containers()
 		if err != nil {
 			return false, err
 		}
-		return (len(ctrs) > 0) == value, nil
+		if value != "external" {
+			boolValue := value == "true"
+			return (len(ctrs) > 0) == boolValue, nil
+		}
+
+		// Check whether all associated containers are external ones.
+		for _, c := range ctrs {
+			isExternal, err := fn(c)
+			if err != nil {
+				return false, fmt.Errorf("checking if %s is an external container in filter: %w", c, err)
+			}
+			if !isExternal {
+				return isExternal, nil
+			}
+		}
+		return true, nil
 	}
 }
 
diff --git a/vendor/github.com/containers/common/libimage/image.go b/vendor/github.com/containers/common/libimage/image.go
index 8456d5280a..00a2d620ef 100644
--- a/vendor/github.com/containers/common/libimage/image.go
+++ b/vendor/github.com/containers/common/libimage/image.go
@@ -2,6 +2,7 @@ package libimage
 
 import (
 	"context"
+	"fmt"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -51,7 +52,7 @@ func (i *Image) reload() error {
 	logrus.Tracef("Reloading image %s", i.ID())
 	img, err := i.runtime.store.Image(i.ID())
 	if err != nil {
-		return errors.Wrap(err, "error reloading image")
+		return errors.Wrap(err, "reloading image")
 	}
 	i.storageImage = img
 	i.cached.imageSource = nil
@@ -232,11 +233,15 @@ func (i *Image) Containers() ([]string, error) {
 }
 
 // removeContainers removes all containers using the image.
-func (i *Image) removeContainers(fn RemoveContainerFunc) error {
-	// Execute the custom removal func if specified.
-	if fn != nil {
+func (i *Image) removeContainers(options *RemoveImagesOptions) error {
+	if !options.Force && !options.ExternalContainers {
+		// Nothing to do.
+		return nil
+	}
+
+	if options.Force && options.RemoveContainerFunc != nil {
 		logrus.Debugf("Removing containers of image %s with custom removal function", i.ID())
-		if err := fn(i.ID()); err != nil {
+		if err := options.RemoveContainerFunc(i.ID()); err != nil {
 			return err
 		}
 	}
@@ -246,6 +251,19 @@ func (i *Image) removeContainers(fn RemoveContainerFunc) error {
 		return err
 	}
 
+	if !options.Force && options.ExternalContainers {
+		// All containers must be external ones.
+		for _, cID := range containers {
+			isExternal, err := options.IsExternalContainerFunc(cID)
+			if err != nil {
+				return fmt.Errorf("checking if %s is an external container: %w", cID, err)
+			}
+			if !isExternal {
+				return fmt.Errorf("cannot remove container %s: not an external container", cID)
+			}
+		}
+	}
+
 	logrus.Debugf("Removing containers of image %s from the local containers storage", i.ID())
 	var multiE error
 	for _, cID := range containers {
@@ -392,11 +410,9 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
 		return processedIDs, nil
 	}
 
-	// Perform the actual removal. First, remove containers if needed.
-	if options.Force {
-		if err := i.removeContainers(options.RemoveContainerFunc); err != nil {
-			return processedIDs, err
-		}
+	// Perform the container removal, if needed.
+	if err := i.removeContainers(options); err != nil {
+		return processedIDs, err
 	}
 
 	// Podman/Docker compat: we only report an image as removed if it has
@@ -406,7 +422,7 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
 	if err != nil {
 		// We must be tolerant toward corrupted images.
 		// See containers/podman commit fd9dd7065d44.
-		logrus.Warnf("error determining if an image is a parent: %v, ignoring the error", err)
+		logrus.Warnf("Failed to determine if an image is a parent: %v, ignoring the error", err)
 		hasChildren = false
 	}
 
@@ -416,7 +432,7 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
 	if err != nil {
 		// We must be tolerant toward corrupted images.
 		// See containers/podman commit fd9dd7065d44.
-		logrus.Warnf("error determining parent of image: %v, ignoring the error", err)
+		logrus.Warnf("Failed to determine parent of image: %v, ignoring the error", err)
 		parent = nil
 	}
 
@@ -440,7 +456,7 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
 	if err != nil {
 		// See Podman commit fd9dd7065d44: we need to
 		// be tolerant toward corrupted images.
-		logrus.Warnf("error determining if an image is a parent: %v, ignoring the error", err)
+		logrus.Warnf("Failed to determine if an image is a parent: %v, ignoring the error", err)
 		danglingParent = false
 	}
 	if !danglingParent {
@@ -462,7 +478,7 @@ func (i *Image) Tag(name string) error {
 
 	ref, err := NormalizeName(name)
 	if err != nil {
-		return errors.Wrapf(err, "error normalizing name %q", name)
+		return errors.Wrapf(err, "normalizing name %q", name)
 	}
 
 	if _, isDigested := ref.(reference.Digested); isDigested {
@@ -499,7 +515,7 @@ func (i *Image) Untag(name string) error {
 
 	ref, err := NormalizeName(name)
 	if err != nil {
-		return errors.Wrapf(err, "error normalizing name %q", name)
+		return errors.Wrapf(err, "normalizing name %q", name)
 	}
 
 	// FIXME: this is breaking Podman CI but must be re-enabled once
@@ -885,12 +901,12 @@ func getImageID(ctx context.Context, src types.ImageReference, sys *types.System
 	}
 	defer func() {
 		if err := newImg.Close(); err != nil {
-			logrus.Errorf("failed to close image: %q", err)
+			logrus.Errorf("Failed to close image: %q", err)
 		}
 	}()
 	imageDigest := newImg.ConfigInfo().Digest
 	if err = imageDigest.Validate(); err != nil {
-		return "", errors.Wrapf(err, "error getting config info")
+		return "", errors.Wrapf(err, "getting config info")
 	}
 	return "@" + imageDigest.Encoded(), nil
 }
diff --git a/vendor/github.com/containers/common/libimage/manifests/manifests.go b/vendor/github.com/containers/common/libimage/manifests/manifests.go
index 81b5343c0a..8d1abfba9a 100644
--- a/vendor/github.com/containers/common/libimage/manifests/manifests.go
+++ b/vendor/github.com/containers/common/libimage/manifests/manifests.go
@@ -125,19 +125,19 @@ func (l *list) SaveToImage(store storage.Store, imageID string, names []string,
 		if err != nil {
 			if created {
 				if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
-					logrus.Errorf("error deleting image %q after failing to save manifest for it", img.ID)
+					logrus.Errorf("Deleting image %q after failing to save manifest for it", img.ID)
 				}
 			}
-			return "", errors.Wrapf(err, "error saving manifest list to image %q", imageID)
+			return "", errors.Wrapf(err, "saving manifest list to image %q", imageID)
 		}
 		err = store.SetImageBigData(imageID, instancesData, instancesBytes, nil)
 		if err != nil {
 			if created {
 				if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
-					logrus.Errorf("error deleting image %q after failing to save instance locations for it", img.ID)
+					logrus.Errorf("Deleting image %q after failing to save instance locations for it", img.ID)
 				}
 			}
-			return "", errors.Wrapf(err, "error saving instance list to image %q", imageID)
+			return "", errors.Wrapf(err, "saving instance list to image %q", imageID)
 		}
 		return imageID, nil
 	}
@@ -200,7 +200,7 @@ func (l *list) Push(ctx context.Context, dest types.ImageReference, options Push
 	}
 	defer func() {
 		if err2 := policyContext.Destroy(); err2 != nil {
-			logrus.Errorf("error destroying signature policy context: %v", err2)
+			logrus.Errorf("Destroying signature policy context: %v", err2)
 		}
 	}()
 
diff --git a/vendor/github.com/containers/common/libimage/runtime.go b/vendor/github.com/containers/common/libimage/runtime.go
index 42461014d6..dabadbec0e 100644
--- a/vendor/github.com/containers/common/libimage/runtime.go
+++ b/vendor/github.com/containers/common/libimage/runtime.go
@@ -2,6 +2,7 @@ package libimage
 
 import (
 	"context"
+	"fmt"
 	"os"
 	"strings"
 
@@ -306,7 +307,7 @@ func (r *Runtime) lookupImageInLocalStorage(name, candidate string, options *Loo
 		if errors.Cause(err) == os.ErrNotExist {
 			// We must be tolerant toward corrupted images.
 			// See containers/podman commit fd9dd7065d44.
-			logrus.Warnf("error determining if an image is a manifest list: %v, ignoring the error", err)
+			logrus.Warnf("Failed to determine if an image is a manifest list: %v, ignoring the error", err)
 			return image, nil
 		}
 		return nil, err
@@ -484,10 +485,16 @@ func (r *Runtime) imageReferenceMatchesContext(ref types.ImageReference, options
 	return true, nil
 }
 
+// IsExternalContainerFunc allows for checking whether the specified container
+// is an external one.  The definition of an external container can be set by
+// callers.
+type IsExternalContainerFunc func(containerID string) (bool, error)
+
 // ListImagesOptions allow for customizing listing images.
 type ListImagesOptions struct {
 	// Filters to filter the listed images.  Supported filters are
 	// * after,before,since=image
+	// * containers=true,false,external
 	// * dangling=true,false
 	// * intermediate=true,false (useful for pruning images)
 	// * id=id
@@ -495,6 +502,11 @@ type ListImagesOptions struct {
 	// * readonly=true,false
 	// * reference=name[:tag] (wildcards allowed)
 	Filters []string
+	// IsExternalContainerFunc allows for checking whether the specified
+	// container is an external one (when containers=external filter is
+	// used).  The definition of an external container can be set by
+	// callers.
+	IsExternalContainerFunc IsExternalContainerFunc
 }
 
 // ListImages lists images in the local container storage.  If names are
@@ -525,7 +537,7 @@ func (r *Runtime) ListImages(ctx context.Context, names []string, options *ListI
 
 	var filters []filterFunc
 	if len(options.Filters) > 0 {
-		compiledFilters, err := r.compileImageFilters(ctx, options.Filters)
+		compiledFilters, err := r.compileImageFilters(ctx, options)
 		if err != nil {
 			return nil, err
 		}
@@ -550,8 +562,17 @@ type RemoveImagesOptions struct {
 	// containers using a specific image.  By default, all containers in
 	// the local containers storage will be removed (if Force is set).
 	RemoveContainerFunc RemoveContainerFunc
+	// IsExternalContainerFunc allows for checking whether the specified
+	// container is an external one (when containers=external filter is
+	// used).  The definition of an external container can be set by
+	// callers.
+	IsExternalContainerFunc IsExternalContainerFunc
+	// Remove external containers even when Force is false.  Requires
+	// IsExternalContainerFunc to be specified.
+	ExternalContainers bool
 	// Filters to filter the removed images.  Supported filters are
 	// * after,before,since=image
+	// * containers=true,false,external
 	// * dangling=true,false
 	// * intermediate=true,false (useful for pruning images)
 	// * id=id
@@ -581,6 +602,10 @@ func (r *Runtime) RemoveImages(ctx context.Context, names []string, options *Rem
 		options = &RemoveImagesOptions{}
 	}
 
+	if options.ExternalContainers && options.IsExternalContainerFunc == nil {
+		return nil, []error{fmt.Errorf("libimage error: cannot remove external containers without callback")}
+	}
+
 	// The logic here may require some explanation.  Image removal is
 	// surprisingly complex since it is recursive (intermediate parents are
 	// removed) and since multiple items in `names` may resolve to the
@@ -635,7 +660,11 @@ func (r *Runtime) RemoveImages(ctx context.Context, names []string, options *Rem
 		}
 
 	default:
-		filteredImages, err := r.ListImages(ctx, nil, &ListImagesOptions{Filters: options.Filters})
+		options := &ListImagesOptions{
+			IsExternalContainerFunc: options.IsExternalContainerFunc,
+			Filters:                 options.Filters,
+		}
+		filteredImages, err := r.ListImages(ctx, nil, options)
 		if err != nil {
 			appendError(err)
 			return nil, rmErrors
diff --git a/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go b/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go
index 4f11e4ed25..735d194932 100644
--- a/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go
+++ b/vendor/github.com/containers/common/pkg/apparmor/apparmor_linux.go
@@ -97,22 +97,22 @@ func InstallDefault(name string) error {
 	}
 	if err := cmd.Start(); err != nil {
 		if pipeErr := pipe.Close(); pipeErr != nil {
-			logrus.Errorf("unable to close AppArmor pipe: %q", pipeErr)
+			logrus.Errorf("Unable to close AppArmor pipe: %q", pipeErr)
 		}
 		return errors.Wrapf(err, "start %s command", apparmorParserPath)
 	}
 	if err := p.generateDefault(apparmorParserPath, pipe); err != nil {
 		if pipeErr := pipe.Close(); pipeErr != nil {
-			logrus.Errorf("unable to close AppArmor pipe: %q", pipeErr)
+			logrus.Errorf("Unable to close AppArmor pipe: %q", pipeErr)
 		}
 		if cmdErr := cmd.Wait(); cmdErr != nil {
-			logrus.Errorf("unable to wait for AppArmor command: %q", cmdErr)
+			logrus.Errorf("Unable to wait for AppArmor command: %q", cmdErr)
 		}
 		return errors.Wrap(err, "generate default profile into pipe")
 	}
 
 	if pipeErr := pipe.Close(); pipeErr != nil {
-		logrus.Errorf("unable to close AppArmor pipe: %q", pipeErr)
+		logrus.Errorf("Unable to close AppArmor pipe: %q", pipeErr)
 	}
 
 	return errors.Wrap(cmd.Wait(), "wait for AppArmor command")
@@ -252,7 +252,7 @@ func CheckProfileAndLoadDefault(name string) (string, error) {
 		if name != "" {
 			return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name)
 		} else {
-			logrus.Debug("skipping loading default AppArmor profile (rootless mode)")
+			logrus.Debug("Skipping loading default AppArmor profile (rootless mode)")
 			return "", nil
 		}
 	}
@@ -292,7 +292,7 @@ func CheckProfileAndLoadDefault(name string) (string, error) {
 		if err != nil {
 			return "", errors.Wrapf(err, "install profile %s", name)
 		}
-		logrus.Infof("successfully loaded AppAmor profile %q", name)
+		logrus.Infof("Successfully loaded AppAmor profile %q", name)
 	} else {
 		logrus.Infof("AppAmor profile %q is already loaded", name)
 	}
diff --git a/vendor/github.com/containers/common/pkg/retry/retry.go b/vendor/github.com/containers/common/pkg/retry/retry.go
index 8eb2da9757..43e3a66888 100644
--- a/vendor/github.com/containers/common/pkg/retry/retry.go
+++ b/vendor/github.com/containers/common/pkg/retry/retry.go
@@ -30,7 +30,7 @@ func RetryIfNecessary(ctx context.Context, operation func() error, retryOptions
 		if retryOptions.Delay != 0 {
 			delay = retryOptions.Delay
 		}
-		logrus.Warnf("failed, retrying in %s ... (%d/%d). Error: %v", delay, attempt+1, retryOptions.MaxRetry, err)
+		logrus.Warnf("Failed, retrying in %s ... (%d/%d). Error: %v", delay, attempt+1, retryOptions.MaxRetry, err)
 		select {
 		case <-time.After(delay):
 			break
diff --git a/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go b/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go
index 4b7253b31c..6c9321e735 100644
--- a/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go
+++ b/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go
@@ -114,13 +114,13 @@ func getMounts(filePath string) []string {
 	file, err := os.Open(filePath)
 	if err != nil {
 		// This is expected on most systems
-		logrus.Debugf("file %q not found, skipping...", filePath)
+		logrus.Debugf("File %q not found, skipping...", filePath)
 		return nil
 	}
 	defer file.Close()
 	scanner := bufio.NewScanner(file)
 	if err = scanner.Err(); err != nil {
-		logrus.Errorf("error reading file %q, %v skipping...", filePath, err)
+		logrus.Errorf("Reading file %q, %v skipping...", filePath, err)
 		return nil
 	}
 	var mounts []string
@@ -128,7 +128,7 @@ func getMounts(filePath string) []string {
 		if strings.HasPrefix(strings.TrimSpace(scanner.Text()), "/") {
 			mounts = append(mounts, scanner.Text())
 		} else {
-			logrus.Debugf("skipping unrecognized mount in %v: %q",
+			logrus.Debugf("Skipping unrecognized mount in %v: %q",
 				filePath, scanner.Text())
 		}
 	}
@@ -176,7 +176,7 @@ func MountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPoint str
 		if _, err := os.Stat(file); err == nil {
 			mounts, err := addSubscriptionsFromMountsFile(file, mountLabel, containerWorkingDir, uid, gid)
 			if err != nil {
-				logrus.Warnf("error mounting subscriptions, skipping entry in %s: %v", file, err)
+				logrus.Warnf("Failed to mount subscriptions, skipping entry in %s: %v", file, err)
 			}
 			subscriptionMounts = mounts
 			break
@@ -192,7 +192,7 @@ func MountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPoint str
 	switch {
 	case err == nil:
 		if err := addFIPSModeSubscription(&subscriptionMounts, containerWorkingDir, mountPoint, mountLabel, uid, gid); err != nil {
-			logrus.Errorf("error adding FIPS mode subscription to container: %v", err)
+			logrus.Errorf("Adding FIPS mode subscription to container: %v", err)
 		}
 	case os.IsNotExist(err):
 		logrus.Debug("/etc/system-fips does not exist on host, not mounting FIPS mode subscription")
diff --git a/vendor/github.com/containers/common/pkg/supplemented/supplemented.go b/vendor/github.com/containers/common/pkg/supplemented/supplemented.go
index a36c3eda4a..196176a1c6 100644
--- a/vendor/github.com/containers/common/pkg/supplemented/supplemented.go
+++ b/vendor/github.com/containers/common/pkg/supplemented/supplemented.go
@@ -83,12 +83,12 @@ func (s *supplementedImageReference) NewImageSource(ctx context.Context, sys *ty
 			if iss != nil {
 				// The composite source has been created.  Use its Close method.
 				if err2 := iss.Close(); err2 != nil {
-					logrus.Errorf("error opening image: %v", err2)
+					logrus.Errorf("Opening image: %v", err2)
 				}
 			} else if top != nil {
 				// The composite source has not been created, but the top was already opened.  Close it.
 				if err2 := top.Close(); err2 != nil {
-					logrus.Errorf("error opening image: %v", err2)
+					logrus.Errorf("Closing image: %v", err2)
 				}
 			}
 		}
diff --git a/vendor/github.com/containers/common/pkg/sysinfo/sysinfo_linux.go b/vendor/github.com/containers/common/pkg/sysinfo/sysinfo_linux.go
index 1935d71f1d..6420ba2747 100644
--- a/vendor/github.com/containers/common/pkg/sysinfo/sysinfo_linux.go
+++ b/vendor/github.com/containers/common/pkg/sysinfo/sysinfo_linux.go
@@ -237,7 +237,7 @@ func checkCgroupPids(cgMounts map[string]string, quiet bool) cgroupPids {
 		_, ok := cgMounts["pids"]
 		if !ok {
 			if !quiet {
-				logrus.Warn("unable to find pids cgroup in mounts")
+				logrus.Warn("Unable to find pids cgroup in mounts")
 			}
 			return cgroupPids{}
 		}
diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go
index 346b0a4233..b6ceabce53 100644
--- a/vendor/github.com/containers/common/version/version.go
+++ b/vendor/github.com/containers/common/version/version.go
@@ -1,4 +1,4 @@
 package version
 
 // Version is the version of the build.
-const Version = "0.46.0"
+const Version = "0.46.1-dev"
diff --git a/vendor/modules.txt b/vendor/modules.txt
index e79bbcc44b..1fb03d302e 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -97,7 +97,7 @@ github.com/containers/buildah/pkg/rusage
 github.com/containers/buildah/pkg/sshagent
 github.com/containers/buildah/pkg/util
 github.com/containers/buildah/util
-# github.com/containers/common v0.46.0
+# github.com/containers/common v0.46.1-0.20210928081721-32e20295f1c6
 github.com/containers/common/libimage
 github.com/containers/common/libimage/manifests
 github.com/containers/common/pkg/apparmor