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>
This commit is contained in:
Valentin Rothberg
2021-09-24 14:39:36 +02:00
parent 340166876e
commit a9a54eefab
26 changed files with 208 additions and 70 deletions

View File

@ -41,6 +41,7 @@ func init() {
flags := pruneCmd.Flags() 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.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") flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation")
filterFlagName := "filter" filterFlagName := "filter"

View File

@ -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. 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* #### **--filter**=*filters*
Provide filter values. Provide filter values.

3
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v1.0.1 github.com/containernetworking/cni v1.0.1
github.com/containernetworking/plugins v1.0.1 github.com/containernetworking/plugins v1.0.1
github.com/containers/buildah v1.23.0 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/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.16.0 github.com/containers/image/v5 v5.16.0
github.com/containers/ocicrypt v1.1.2 github.com/containers/ocicrypt v1.1.2
@ -61,7 +61,6 @@ require (
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
github.com/uber/jaeger-client-go v2.29.1+incompatible github.com/uber/jaeger-client-go v2.29.1+incompatible
github.com/vbauerster/mpb/v6 v6.0.4 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 github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97

4
go.sum
View File

@ -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 h1:qGIeSNOczUHzvnaaOS29HSMiYAjw6JgIXYksAyvqnLs=
github.com/containers/buildah v1.23.0/go.mod h1:K0iMKgy/MffkkgELBXhSXwTy2HTT6hM0X8qruDR1FwU= 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.44.0/go.mod h1:7sdP4vmI5Bm6FPFxb3lvAh1Iktb6tiO1MzjUzhxdoGo=
github.com/containers/common v0.46.0 h1:95zB7kYBQJW+aK5xxZnaobCwoPyYOf85Y0yUx0E5aRg= github.com/containers/common v0.46.1-0.20210928081721-32e20295f1c6 h1:DojkCc4a9f3WB25Fk0GDap1/OkKU9UmDLvPJyqw3TBc=
github.com/containers/common v0.46.0/go.mod h1:zxv7KjdYddSGoWuLUVp6eSb++Ow1zmSMB2jwxuNB4cU= 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 h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= 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= github.com/containers/image/v5 v5.16.0 h1:WQcNSzb7+ngS2cfynx0vUwhk+scpgiKlldVcsF8GPbI=

View File

@ -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 // newBuildEvent creates a new event based on completion of a built image
func (r *Runtime) newImageBuildCompleteEvent(idOrName string) { func (r *Runtime) newImageBuildCompleteEvent(idOrName string) {
e := events.NewEvent(events.Build) e := events.NewEvent(events.Build)

View File

@ -150,7 +150,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct { query := struct {
All bool `schema:"all"` All bool `schema:"all"`
External bool `schema:"external"`
}{ }{
// override any golang type defaults // override any golang type defaults
} }
@ -190,8 +191,9 @@ func PruneImages(w http.ResponseWriter, r *http.Request) {
imageEngine := abi.ImageEngine{Libpod: runtime} imageEngine := abi.ImageEngine{Libpod: runtime}
pruneOptions := entities.ImagePruneOptions{ pruneOptions := entities.ImagePruneOptions{
All: query.All, All: query.All,
Filter: libpodFilters, External: query.External,
Filter: libpodFilters,
} }
imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions) imagePruneReports, err := imageEngine.Prune(r.Context(), pruneOptions)
if err != nil { if err != nil {

View File

@ -1050,6 +1050,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// description: | // description: |
// Remove all images not in use by containers, not just dangling ones // Remove all images not in use by containers, not just dangling ones
// - in: query // - 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 // name: filters
// type: string // type: string
// description: | // description: |

View File

@ -74,6 +74,8 @@ type ExportOptions struct {
type PruneOptions struct { type PruneOptions struct {
// Prune all images // Prune all images
All *bool All *bool
// Prune images even when they're used by external containers
External *bool
// Filters to apply when pruning images // Filters to apply when pruning images
Filters map[string][]string Filters map[string][]string
} }

View File

@ -32,6 +32,21 @@ func (o *PruneOptions) GetAll() bool {
return *o.All 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 // WithFilters set field Filters to given value
func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions { func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions {
o.Filters = value o.Filters = value

View File

@ -251,8 +251,9 @@ type ImageListOptions struct {
} }
type ImagePruneOptions struct { type ImagePruneOptions struct {
All bool `json:"all" schema:"all"` All bool `json:"all" schema:"all"`
Filter []string `json:"filter" schema:"filter"` External bool `json:"external" schema:"external"`
Filter []string `json:"filter" schema:"filter"`
} }
type ImageTagOptions struct{} type ImageTagOptions struct{}

View File

@ -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) { func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) {
pruneOptions := &libimage.RemoveImagesOptions{ pruneOptions := &libimage.RemoveImagesOptions{
Filters: append(opts.Filter, "containers=false", "readonly=false"), RemoveContainerFunc: ir.Libpod.RemoveContainersForImageCallback(ctx),
WithSize: true, IsExternalContainerFunc: ir.Libpod.IsExternalContainerCallback(ctx),
ExternalContainers: opts.External,
Filters: append(opts.Filter, "readonly=false"),
WithSize: true,
} }
if !opts.All { if !opts.All {
pruneOptions.Filters = append(pruneOptions.Filters, "dangling=true") 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 var pruneReports []*reports.PruneReport

View File

@ -95,7 +95,7 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
f := strings.Split(filter, "=") f := strings.Split(filter, "=")
filters[f[0]] = f[1:] 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) reports, err := images.Prune(ir.ClientCtx, options)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -92,24 +92,38 @@ load helpers
# Force a buildah timeout; this leaves a buildah container behind # Force a buildah timeout; this leaves a buildah container behind
PODMAN_TIMEOUT=5 run_podman 124 build -t thiswillneverexist - <<EOF PODMAN_TIMEOUT=5 run_podman 124 build -t thiswillneverexist - <<EOF
FROM $IMAGE FROM $IMAGE
RUN touch /intermediate.image.to.be.pruned
RUN sleep 30 RUN sleep 30
EOF EOF
run_podman ps -a 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 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]}" \ is "${lines[1]}" \
"[0-9a-f]\{12\} \+$IMAGE *buildah .* seconds ago .* storage .* ${PODMAN_TEST_IMAGE_NAME}-working-container" \ "[0-9a-f]\{12\} \+$IMAGE *buildah .* seconds ago .* storage .* ${PODMAN_TEST_IMAGE_NAME}-working-container" \
"podman ps --external" "podman ps --external"
cid="${lines[1]:0:12}"
# 'rm -a' should be a NOP # 'rm -a' should be a NOP
run_podman rm -a run_podman rm -a
run_podman ps --external -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 # We can't rm it without -f, but podman should issue a helpful message
run_podman 2 rm "$cid" run_podman 2 rm "$cid"

View File

@ -78,7 +78,7 @@ function _corrupt_image_test() {
# Run the requested command. Confirm it succeeds, with suitable warnings # Run the requested command. Confirm it succeeds, with suitable warnings
run_podman $* 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" "$* with missing $what_to_rm"
run_podman images -a --noheading run_podman images -a --noheading

View File

@ -304,7 +304,7 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
defaultContainerConfig, err := config.Default() defaultContainerConfig, err := config.Default()
if err != nil { 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 { } else {
c.imageCopyOptions.MaxParallelDownloads = defaultContainerConfig.Engine.ImageParallelCopies c.imageCopyOptions.MaxParallelDownloads = defaultContainerConfig.Engine.ImageParallelCopies
} }

View File

@ -47,11 +47,11 @@ func filterImages(images []*Image, filters []filterFunc) ([]*Image, error) {
// compileImageFilters creates `filterFunc`s for the specified filters. The // compileImageFilters creates `filterFunc`s for the specified filters. The
// required format is `key=value` with the following supported keys: // required format is `key=value` with the following supported keys:
// after, since, before, containers, dangling, id, label, readonly, reference, intermediate // after, since, before, containers, dangling, id, label, readonly, reference, intermediate
func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]filterFunc, error) { func (r *Runtime) compileImageFilters(ctx context.Context, options *ListImagesOptions) ([]filterFunc, error) {
logrus.Tracef("Parsing image filters %s", filters) logrus.Tracef("Parsing image filters %s", options.Filters)
filterFuncs := []filterFunc{} filterFuncs := []filterFunc{}
for _, filter := range filters { for _, filter := range options.Filters {
var key, value string var key, value string
split := strings.SplitN(filter, "=", 2) split := strings.SplitN(filter, "=", 2)
if len(split) != 2 { if len(split) != 2 {
@ -77,11 +77,16 @@ func (r *Runtime) compileImageFilters(ctx context.Context, filters []string) ([]
filterFuncs = append(filterFuncs, filterBefore(img.Created())) filterFuncs = append(filterFuncs, filterBefore(img.Created()))
case "containers": case "containers":
containers, err := strconv.ParseBool(value) switch value {
if err != nil { case "false", "true":
return nil, errors.Wrapf(err, "non-boolean value %q for dangling filter", value) 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": case "dangling":
dangling, err := strconv.ParseBool(value) dangling, err := strconv.ParseBool(value)
@ -190,13 +195,28 @@ func filterReadOnly(value bool) filterFunc {
} }
// filterContainers creates a container filter for matching the specified value. // 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) { return func(img *Image) (bool, error) {
ctrs, err := img.Containers() ctrs, err := img.Containers()
if err != nil { if err != nil {
return false, err 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
} }
} }

View File

@ -2,6 +2,7 @@ package libimage
import ( import (
"context" "context"
"fmt"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@ -51,7 +52,7 @@ func (i *Image) reload() error {
logrus.Tracef("Reloading image %s", i.ID()) logrus.Tracef("Reloading image %s", i.ID())
img, err := i.runtime.store.Image(i.ID()) img, err := i.runtime.store.Image(i.ID())
if err != nil { if err != nil {
return errors.Wrap(err, "error reloading image") return errors.Wrap(err, "reloading image")
} }
i.storageImage = img i.storageImage = img
i.cached.imageSource = nil i.cached.imageSource = nil
@ -232,11 +233,15 @@ func (i *Image) Containers() ([]string, error) {
} }
// removeContainers removes all containers using the image. // removeContainers removes all containers using the image.
func (i *Image) removeContainers(fn RemoveContainerFunc) error { func (i *Image) removeContainers(options *RemoveImagesOptions) error {
// Execute the custom removal func if specified. if !options.Force && !options.ExternalContainers {
if fn != nil { // Nothing to do.
return nil
}
if options.Force && options.RemoveContainerFunc != nil {
logrus.Debugf("Removing containers of image %s with custom removal function", i.ID()) 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 return err
} }
} }
@ -246,6 +251,19 @@ func (i *Image) removeContainers(fn RemoveContainerFunc) error {
return err 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()) logrus.Debugf("Removing containers of image %s from the local containers storage", i.ID())
var multiE error var multiE error
for _, cID := range containers { for _, cID := range containers {
@ -392,11 +410,9 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
return processedIDs, nil return processedIDs, nil
} }
// Perform the actual removal. First, remove containers if needed. // Perform the container removal, if needed.
if options.Force { if err := i.removeContainers(options); err != nil {
if err := i.removeContainers(options.RemoveContainerFunc); err != nil { return processedIDs, err
return processedIDs, err
}
} }
// Podman/Docker compat: we only report an image as removed if it has // 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 { if err != nil {
// We must be tolerant toward corrupted images. // We must be tolerant toward corrupted images.
// See containers/podman commit fd9dd7065d44. // 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 hasChildren = false
} }
@ -416,7 +432,7 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
if err != nil { if err != nil {
// We must be tolerant toward corrupted images. // We must be tolerant toward corrupted images.
// See containers/podman commit fd9dd7065d44. // 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 parent = nil
} }
@ -440,7 +456,7 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma
if err != nil { if err != nil {
// See Podman commit fd9dd7065d44: we need to // See Podman commit fd9dd7065d44: we need to
// be tolerant toward corrupted images. // 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 danglingParent = false
} }
if !danglingParent { if !danglingParent {
@ -462,7 +478,7 @@ func (i *Image) Tag(name string) error {
ref, err := NormalizeName(name) ref, err := NormalizeName(name)
if err != nil { 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 { if _, isDigested := ref.(reference.Digested); isDigested {
@ -499,7 +515,7 @@ func (i *Image) Untag(name string) error {
ref, err := NormalizeName(name) ref, err := NormalizeName(name)
if err != nil { 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 // 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() { defer func() {
if err := newImg.Close(); err != nil { 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 imageDigest := newImg.ConfigInfo().Digest
if err = imageDigest.Validate(); err != nil { 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 return "@" + imageDigest.Encoded(), nil
} }

View File

@ -125,19 +125,19 @@ func (l *list) SaveToImage(store storage.Store, imageID string, names []string,
if err != nil { if err != nil {
if created { if created {
if _, err2 := store.DeleteImage(img.ID, true); err2 != nil { 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) err = store.SetImageBigData(imageID, instancesData, instancesBytes, nil)
if err != nil { if err != nil {
if created { if created {
if _, err2 := store.DeleteImage(img.ID, true); err2 != nil { 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 return imageID, nil
} }
@ -200,7 +200,7 @@ func (l *list) Push(ctx context.Context, dest types.ImageReference, options Push
} }
defer func() { defer func() {
if err2 := policyContext.Destroy(); err2 != nil { if err2 := policyContext.Destroy(); err2 != nil {
logrus.Errorf("error destroying signature policy context: %v", err2) logrus.Errorf("Destroying signature policy context: %v", err2)
} }
}() }()

View File

@ -2,6 +2,7 @@ package libimage
import ( import (
"context" "context"
"fmt"
"os" "os"
"strings" "strings"
@ -306,7 +307,7 @@ func (r *Runtime) lookupImageInLocalStorage(name, candidate string, options *Loo
if errors.Cause(err) == os.ErrNotExist { if errors.Cause(err) == os.ErrNotExist {
// We must be tolerant toward corrupted images. // We must be tolerant toward corrupted images.
// See containers/podman commit fd9dd7065d44. // 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 image, nil
} }
return nil, err return nil, err
@ -484,10 +485,16 @@ func (r *Runtime) imageReferenceMatchesContext(ref types.ImageReference, options
return true, nil 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. // ListImagesOptions allow for customizing listing images.
type ListImagesOptions struct { type ListImagesOptions struct {
// Filters to filter the listed images. Supported filters are // Filters to filter the listed images. Supported filters are
// * after,before,since=image // * after,before,since=image
// * containers=true,false,external
// * dangling=true,false // * dangling=true,false
// * intermediate=true,false (useful for pruning images) // * intermediate=true,false (useful for pruning images)
// * id=id // * id=id
@ -495,6 +502,11 @@ type ListImagesOptions struct {
// * readonly=true,false // * readonly=true,false
// * reference=name[:tag] (wildcards allowed) // * reference=name[:tag] (wildcards allowed)
Filters []string 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 // 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 var filters []filterFunc
if len(options.Filters) > 0 { if len(options.Filters) > 0 {
compiledFilters, err := r.compileImageFilters(ctx, options.Filters) compiledFilters, err := r.compileImageFilters(ctx, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -550,8 +562,17 @@ type RemoveImagesOptions struct {
// containers using a specific image. By default, all containers in // containers using a specific image. By default, all containers in
// the local containers storage will be removed (if Force is set). // the local containers storage will be removed (if Force is set).
RemoveContainerFunc RemoveContainerFunc 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 // Filters to filter the removed images. Supported filters are
// * after,before,since=image // * after,before,since=image
// * containers=true,false,external
// * dangling=true,false // * dangling=true,false
// * intermediate=true,false (useful for pruning images) // * intermediate=true,false (useful for pruning images)
// * id=id // * id=id
@ -581,6 +602,10 @@ func (r *Runtime) RemoveImages(ctx context.Context, names []string, options *Rem
options = &RemoveImagesOptions{} 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 // The logic here may require some explanation. Image removal is
// surprisingly complex since it is recursive (intermediate parents are // surprisingly complex since it is recursive (intermediate parents are
// removed) and since multiple items in `names` may resolve to the // 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: 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 { if err != nil {
appendError(err) appendError(err)
return nil, rmErrors return nil, rmErrors

View File

@ -97,22 +97,22 @@ func InstallDefault(name string) error {
} }
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
if pipeErr := pipe.Close(); pipeErr != 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) return errors.Wrapf(err, "start %s command", apparmorParserPath)
} }
if err := p.generateDefault(apparmorParserPath, pipe); err != nil { if err := p.generateDefault(apparmorParserPath, pipe); err != nil {
if pipeErr := pipe.Close(); pipeErr != 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 { 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") return errors.Wrap(err, "generate default profile into pipe")
} }
if pipeErr := pipe.Close(); pipeErr != 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.Wrap(cmd.Wait(), "wait for AppArmor command") return errors.Wrap(cmd.Wait(), "wait for AppArmor command")
@ -252,7 +252,7 @@ func CheckProfileAndLoadDefault(name string) (string, error) {
if name != "" { if name != "" {
return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name)
} else { } else {
logrus.Debug("skipping loading default AppArmor profile (rootless mode)") logrus.Debug("Skipping loading default AppArmor profile (rootless mode)")
return "", nil return "", nil
} }
} }
@ -292,7 +292,7 @@ func CheckProfileAndLoadDefault(name string) (string, error) {
if err != nil { if err != nil {
return "", errors.Wrapf(err, "install profile %s", name) 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 { } else {
logrus.Infof("AppAmor profile %q is already loaded", name) logrus.Infof("AppAmor profile %q is already loaded", name)
} }

View File

@ -30,7 +30,7 @@ func RetryIfNecessary(ctx context.Context, operation func() error, retryOptions
if retryOptions.Delay != 0 { if retryOptions.Delay != 0 {
delay = retryOptions.Delay 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 { select {
case <-time.After(delay): case <-time.After(delay):
break break

View File

@ -114,13 +114,13 @@ func getMounts(filePath string) []string {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
// This is expected on most systems // This is expected on most systems
logrus.Debugf("file %q not found, skipping...", filePath) logrus.Debugf("File %q not found, skipping...", filePath)
return nil return nil
} }
defer file.Close() defer file.Close()
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
if err = scanner.Err(); err != nil { 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 return nil
} }
var mounts []string var mounts []string
@ -128,7 +128,7 @@ func getMounts(filePath string) []string {
if strings.HasPrefix(strings.TrimSpace(scanner.Text()), "/") { if strings.HasPrefix(strings.TrimSpace(scanner.Text()), "/") {
mounts = append(mounts, scanner.Text()) mounts = append(mounts, scanner.Text())
} else { } else {
logrus.Debugf("skipping unrecognized mount in %v: %q", logrus.Debugf("Skipping unrecognized mount in %v: %q",
filePath, scanner.Text()) filePath, scanner.Text())
} }
} }
@ -176,7 +176,7 @@ func MountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPoint str
if _, err := os.Stat(file); err == nil { if _, err := os.Stat(file); err == nil {
mounts, err := addSubscriptionsFromMountsFile(file, mountLabel, containerWorkingDir, uid, gid) mounts, err := addSubscriptionsFromMountsFile(file, mountLabel, containerWorkingDir, uid, gid)
if err != nil { 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 subscriptionMounts = mounts
break break
@ -192,7 +192,7 @@ func MountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPoint str
switch { switch {
case err == nil: case err == nil:
if err := addFIPSModeSubscription(&subscriptionMounts, containerWorkingDir, mountPoint, mountLabel, uid, gid); 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): case os.IsNotExist(err):
logrus.Debug("/etc/system-fips does not exist on host, not mounting FIPS mode subscription") logrus.Debug("/etc/system-fips does not exist on host, not mounting FIPS mode subscription")

View File

@ -83,12 +83,12 @@ func (s *supplementedImageReference) NewImageSource(ctx context.Context, sys *ty
if iss != nil { if iss != nil {
// The composite source has been created. Use its Close method. // The composite source has been created. Use its Close method.
if err2 := iss.Close(); err2 != nil { if err2 := iss.Close(); err2 != nil {
logrus.Errorf("error opening image: %v", err2) logrus.Errorf("Opening image: %v", err2)
} }
} else if top != nil { } else if top != nil {
// The composite source has not been created, but the top was already opened. Close it. // The composite source has not been created, but the top was already opened. Close it.
if err2 := top.Close(); err2 != nil { if err2 := top.Close(); err2 != nil {
logrus.Errorf("error opening image: %v", err2) logrus.Errorf("Closing image: %v", err2)
} }
} }
} }

View File

@ -237,7 +237,7 @@ func checkCgroupPids(cgMounts map[string]string, quiet bool) cgroupPids {
_, ok := cgMounts["pids"] _, ok := cgMounts["pids"]
if !ok { if !ok {
if !quiet { if !quiet {
logrus.Warn("unable to find pids cgroup in mounts") logrus.Warn("Unable to find pids cgroup in mounts")
} }
return cgroupPids{} return cgroupPids{}
} }

View File

@ -1,4 +1,4 @@
package version package version
// Version is the version of the build. // Version is the version of the build.
const Version = "0.46.0" const Version = "0.46.1-dev"

2
vendor/modules.txt vendored
View File

@ -97,7 +97,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/util
github.com/containers/buildah/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
github.com/containers/common/libimage/manifests github.com/containers/common/libimage/manifests
github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/apparmor