mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Update to containerd 1.7.18, which now migrated to the errdefs module. The
existing errdefs package is now an alias for the module, and should no longer
be used directly.
This patch:
- updates the containerd dependency: https://github.com/containerd/containerd/compare/v1.7.17...v1.7.18
- replaces uses of the old package in favor of the new module
- adds a linter check to prevent accidental re-introduction of the old package
- adds a linter check to prevent using the "log" package, which was also
migrated to a separate module.
There are still some uses of the old package in (indirect) dependencies,
which should go away over time.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 86f7762d48)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
263 lines
8.0 KiB
Go
263 lines
8.0 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
containerdimages "github.com/containerd/containerd/images"
|
|
cerrdefs "github.com/containerd/errdefs"
|
|
"github.com/containerd/log"
|
|
"github.com/distribution/reference"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/image"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/internal/compatcontext"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var imagesAcceptedFilters = map[string]bool{
|
|
"dangling": true,
|
|
"label": true,
|
|
"label!": true,
|
|
"until": true,
|
|
}
|
|
|
|
// errPruneRunning is returned when a prune request is received while
|
|
// one is in progress
|
|
var errPruneRunning = errdefs.Conflict(errors.New("a prune operation is already running"))
|
|
|
|
// ImagesPrune removes unused images
|
|
func (i *ImageService) ImagesPrune(ctx context.Context, fltrs filters.Args) (*types.ImagesPruneReport, error) {
|
|
if !i.pruneRunning.CompareAndSwap(false, true) {
|
|
return nil, errPruneRunning
|
|
}
|
|
defer i.pruneRunning.Store(false)
|
|
|
|
err := fltrs.Validate(imagesAcceptedFilters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
danglingOnly, err := fltrs.GetBoolOrDefault("dangling", true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// dangling=false will filter out dangling images like in image list.
|
|
// Remove it, because in this context dangling=false means that we're
|
|
// pruning NOT ONLY dangling (`docker image prune -a`) instead of NOT DANGLING.
|
|
// This will be handled by the danglingOnly parameter of pruneUnused.
|
|
for _, v := range fltrs.Get("dangling") {
|
|
fltrs.Del("dangling", v)
|
|
}
|
|
|
|
filterFunc, err := i.setupFilters(ctx, fltrs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return i.pruneUnused(ctx, filterFunc, danglingOnly)
|
|
}
|
|
|
|
func (i *ImageService) pruneUnused(ctx context.Context, filterFunc imageFilterFunc, danglingOnly bool) (*types.ImagesPruneReport, error) {
|
|
report := types.ImagesPruneReport{}
|
|
|
|
allImages, err := i.images.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// How many images make reference to a particular target digest.
|
|
digestRefCount := map[digest.Digest]int{}
|
|
// Images considered for pruning.
|
|
imagesToPrune := map[string]containerdimages.Image{}
|
|
for _, img := range allImages {
|
|
digestRefCount[img.Target.Digest] += 1
|
|
|
|
if !danglingOnly || isDanglingImage(img) {
|
|
canBePruned := filterFunc(img)
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"image": img.Name,
|
|
"canBePruned": canBePruned,
|
|
}).Debug("considering image for pruning")
|
|
|
|
if canBePruned {
|
|
imagesToPrune[img.Name] = img
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Image specified by digests that are used by containers.
|
|
usedDigests := map[digest.Digest]struct{}{}
|
|
|
|
// Exclude images used by existing containers
|
|
for _, ctr := range i.containers.List() {
|
|
// If the original image was deleted, make sure we don't delete the dangling image
|
|
delete(imagesToPrune, danglingImageName(ctr.ImageID.Digest()))
|
|
|
|
// Config.Image is the image reference passed by user.
|
|
// Config.ImageID is the resolved content digest based on the user's Config.Image.
|
|
// For example: container created by:
|
|
// `docker run alpine` will have Config.Image="alpine"
|
|
// `docker run 82d1e9d` will have Config.Image="82d1e9d"
|
|
// but both will have ImageID="sha256:82d1e9d7ed48a7523bdebc18cf6290bdb97b82302a8a9c27d4fe885949ea94d1"
|
|
imageDgst := ctr.ImageID.Digest()
|
|
|
|
// If user didn't specify an explicit image, mark the digest as used.
|
|
normalizedImageID := "sha256:" + strings.TrimPrefix(ctr.Config.Image, "sha256:")
|
|
if strings.HasPrefix(imageDgst.String(), normalizedImageID) {
|
|
usedDigests[imageDgst] = struct{}{}
|
|
continue
|
|
}
|
|
|
|
ref, err := reference.ParseNormalizedNamed(ctr.Config.Image)
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"ctr": ctr.ID,
|
|
"image": ref,
|
|
"nameParseErr": err,
|
|
}).Debug("filtering container's image")
|
|
|
|
if err == nil {
|
|
// If user provided a specific image name, exclude that image.
|
|
name := reference.TagNameOnly(ref)
|
|
delete(imagesToPrune, name.String())
|
|
}
|
|
}
|
|
|
|
// Create dangling images for images that will be deleted but are still in use.
|
|
for _, img := range imagesToPrune {
|
|
dgst := img.Target.Digest
|
|
|
|
digestRefCount[dgst] -= 1
|
|
if digestRefCount[dgst] == 0 {
|
|
if _, isUsed := usedDigests[dgst]; isUsed {
|
|
if err := i.ensureDanglingImage(ctx, img); err != nil {
|
|
return &report, errors.Wrapf(err, "failed to create ensure dangling image for %s", img.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
possiblyDeletedConfigs := map[digest.Digest]struct{}{}
|
|
var errs error
|
|
|
|
// Workaround for https://github.com/moby/buildkit/issues/3797
|
|
defer func() {
|
|
if err := i.unleaseSnapshotsFromDeletedConfigs(compatcontext.WithoutCancel(ctx), possiblyDeletedConfigs); err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
}
|
|
}()
|
|
|
|
for _, img := range imagesToPrune {
|
|
log.G(ctx).WithField("image", img).Debug("pruning image")
|
|
|
|
blobs := []ocispec.Descriptor{}
|
|
|
|
err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, desc ocispec.Descriptor) error {
|
|
blobs = append(blobs, desc)
|
|
if containerdimages.IsConfigType(desc.MediaType) {
|
|
possiblyDeletedConfigs[desc.Digest] = struct{}{}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
return &report, errs
|
|
}
|
|
continue
|
|
}
|
|
err = i.images.Delete(ctx, img.Name, containerdimages.SynchronousDelete())
|
|
if err != nil && !cerrdefs.IsNotFound(err) {
|
|
errs = multierror.Append(errs, err)
|
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
return &report, errs
|
|
}
|
|
continue
|
|
}
|
|
|
|
report.ImagesDeleted = append(report.ImagesDeleted,
|
|
image.DeleteResponse{
|
|
Untagged: imageFamiliarName(img),
|
|
},
|
|
)
|
|
|
|
// Check which blobs have been deleted and sum their sizes
|
|
for _, blob := range blobs {
|
|
_, err := i.content.ReaderAt(ctx, blob)
|
|
|
|
if cerrdefs.IsNotFound(err) {
|
|
report.ImagesDeleted = append(report.ImagesDeleted,
|
|
image.DeleteResponse{
|
|
Deleted: blob.Digest.String(),
|
|
},
|
|
)
|
|
report.SpaceReclaimed += uint64(blob.Size)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &report, errs
|
|
}
|
|
|
|
// unleaseSnapshotsFromDeletedConfigs removes gc.ref.snapshot content label from configs that are not
|
|
// referenced by any of the existing images.
|
|
// This is a temporary solution to the rootfs snapshot not being deleted when there's a buildkit history
|
|
// item referencing an image config.
|
|
func (i *ImageService) unleaseSnapshotsFromDeletedConfigs(ctx context.Context, possiblyDeletedConfigs map[digest.Digest]struct{}) error {
|
|
all, err := i.images.List(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to list images during snapshot lease removal")
|
|
}
|
|
|
|
var errs error
|
|
for _, img := range all {
|
|
err := i.walkPresentChildren(ctx, img.Target, func(_ context.Context, desc ocispec.Descriptor) error {
|
|
if containerdimages.IsConfigType(desc.MediaType) {
|
|
delete(possiblyDeletedConfigs, desc.Digest)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
return errs
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
// At this point, all configs that are used by any image has been removed from the slice
|
|
for cfgDigest := range possiblyDeletedConfigs {
|
|
info, err := i.content.Info(ctx, cfgDigest)
|
|
if err != nil {
|
|
if cerrdefs.IsNotFound(err) {
|
|
log.G(ctx).WithField("config", cfgDigest).Debug("config already gone")
|
|
} else {
|
|
errs = multierror.Append(errs, err)
|
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
return errs
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
label := "containerd.io/gc.ref.snapshot." + i.StorageDriver()
|
|
|
|
delete(info.Labels, label)
|
|
_, err = i.content.Update(ctx, info, "labels."+label)
|
|
if err != nil {
|
|
errs = multierror.Append(errs, errors.Wrapf(err, "failed to remove gc.ref.snapshot label from %s", cfgDigest))
|
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
return errs
|
|
}
|
|
}
|
|
}
|
|
|
|
return errs
|
|
}
|