moby/daemon/containerd/soft_delete.go
Derek McGowan 87c87bccb5
Update cleanup logic to use resolve all images
Ensure that when removing an image, an image is checked consistently
against the images with the same target digest. Add unit testing around
delete.

Signed-off-by: Derek McGowan <derek@mcg.dev>
2023-12-18 14:04:48 -08:00

77 lines
2.4 KiB
Go

package containerd
import (
"context"
cerrdefs "github.com/containerd/containerd/errdefs"
containerdimages "github.com/containerd/containerd/images"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/internal/compatcontext"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
const imageNameDanglingPrefix = "moby-dangling@"
// softImageDelete deletes the image, making sure that there are other images
// that reference the content of the deleted image.
// If no other image exists, a dangling one is created.
func (i *ImageService) softImageDelete(ctx context.Context, img containerdimages.Image, imgs []containerdimages.Image) error {
// From this point explicitly ignore the passed context
// and don't allow to interrupt operation in the middle.
// Create dangling image if this is the last image pointing to this target.
if len(imgs) == 1 {
err := i.ensureDanglingImage(compatcontext.WithoutCancel(ctx), img)
// Error out in case we couldn't persist the old image.
if err != nil {
return errdefs.System(errors.Wrapf(err, "failed to create a dangling image for the replaced image %s with digest %s",
img.Name, img.Target.Digest.String()))
}
}
// Free the target name.
// TODO: Add with target option
err := i.images.Delete(compatcontext.WithoutCancel(ctx), img.Name)
if err != nil {
if !cerrdefs.IsNotFound(err) {
return errdefs.System(errors.Wrapf(err, "failed to delete image %s which existed a moment before", img.Name))
}
}
return nil
}
func (i *ImageService) ensureDanglingImage(ctx context.Context, from containerdimages.Image) error {
danglingImage := from
danglingImage.Labels = make(map[string]string)
for k, v := range from.Labels {
switch k {
case containerdimages.AnnotationImageName, ocispec.AnnotationRefName:
// Don't copy name labels.
default:
danglingImage.Labels[k] = v
}
}
danglingImage.Name = danglingImageName(from.Target.Digest)
_, err := i.images.Create(compatcontext.WithoutCancel(ctx), danglingImage)
// If it already exists, then just continue.
if cerrdefs.IsAlreadyExists(err) {
return nil
}
return err
}
func danglingImageName(digest digest.Digest) string {
return imageNameDanglingPrefix + digest.String()
}
func isDanglingImage(image containerdimages.Image) bool {
// TODO: Also check for expired
return image.Name == danglingImageName(image.Target.Digest)
}