Skip to content

Commit

Permalink
image prune: support removing external containers
Browse files Browse the repository at this point in the history
Support removing external containers (e.g., build containers) during
image prune.

Fixes: containers#11472
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
  • Loading branch information
vrothberg committed Sep 28, 2021
1 parent 3401668 commit a9a54ee
Show file tree
Hide file tree
Showing 26 changed files with 208 additions and 70 deletions.
1 change: 1 addition & 0 deletions cmd/podman/images/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-image-prune.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
21 changes: 21 additions & 0 deletions libpod/runtime_img.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions pkg/api/handlers/libpod/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/server/register_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 2 additions & 0 deletions pkg/bindings/images/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
15 changes: 15 additions & 0 deletions pkg/bindings/images/types_prune_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions pkg/domain/entities/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
12 changes: 10 additions & 2 deletions pkg/domain/infra/abi/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/infra/tunnel/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 19 additions & 5 deletions test/system/040-ps.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion test/system/330-corrupt-images.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion vendor/github.com/containers/common/libimage/copier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 29 additions & 9 deletions vendor/github.com/containers/common/libimage/filters.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a9a54ee

Please sign in to comment.