Browse Source

Add support for removing repo and digest

When repo and digest is provided, remove all references within a
repository for the given digest.

Signed-off-by: Derek McGowan <derek@mcg.dev>
Derek McGowan 1 year ago
parent
commit
0c6e9121b2

+ 6 - 2
daemon/containerd/image.go

@@ -483,7 +483,12 @@ func (i *ImageService) resolveAllReferences(ctx context.Context, refOrID string)
 			if !cerrdefs.IsNotFound(err) {
 			if !cerrdefs.IsNotFound(err) {
 				return nil, nil, convertError(err)
 				return nil, nil, convertError(err)
 			}
 			}
-			return nil, nil, images.ErrImageDoesNotExist{Ref: parsed}
+			// If digest is given, continue looking up for matching targets.
+			// There will be no exact match found but the caller may attempt
+			// to match across images with the matching target.
+			if dgst == "" {
+				return nil, nil, images.ErrImageDoesNotExist{Ref: parsed}
+			}
 		} else {
 		} else {
 			img = &cimg
 			img = &cimg
 			if dgst != "" && img.Target.Digest != dgst {
 			if dgst != "" && img.Target.Digest != dgst {
@@ -492,7 +497,6 @@ func (i *ImageService) resolveAllReferences(ctx context.Context, refOrID string)
 			}
 			}
 			dgst = img.Target.Digest
 			dgst = img.Target.Digest
 		}
 		}
-
 	}
 	}
 
 
 	// Lookup up all associated images and check for consistency with first reference
 	// Lookup up all associated images and check for consistency with first reference

+ 49 - 22
daemon/containerd/image_delete.go

@@ -12,6 +12,7 @@ import (
 	"github.com/docker/docker/api/types/events"
 	"github.com/docker/docker/api/types/events"
 	imagetypes "github.com/docker/docker/api/types/image"
 	imagetypes "github.com/docker/docker/api/types/image"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
+	dimages "github.com/docker/docker/daemon/images"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/internal/compatcontext"
 	"github.com/docker/docker/internal/compatcontext"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
@@ -70,14 +71,40 @@ func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force,
 			return nil, dimages.ErrImageDoesNotExist{Ref: parsed}
 			return nil, dimages.ErrImageDoesNotExist{Ref: parsed}
 		}
 		}
 		imgID = image.ID(all[0].Target.Digest)
 		imgID = image.ID(all[0].Target.Digest)
-		sameRef, err := i.getSameReferences(ctx, nil, all)
+		var named reference.Named
+		if !isImageIDPrefix(imgID.String(), imageRef) {
+			if nn, err := reference.ParseNormalizedNamed(imageRef); err == nil {
+				named = nn
+			}
+		}
+		sameRef, err := i.getSameReferences(ctx, named, all)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 
 
+		if len(sameRef) == 0 && named != nil {
+			return nil, dimages.ErrImageDoesNotExist{Ref: named}
+		}
+
 		if len(sameRef) == len(all) && !force {
 		if len(sameRef) == len(all) && !force {
 			c &= ^conflictActiveReference
 			c &= ^conflictActiveReference
 		}
 		}
+		if named != nil && len(sameRef) > 0 && len(sameRef) != len(all) {
+			var records []imagetypes.DeleteResponse
+			for _, ref := range sameRef {
+				// TODO: Add with target
+				err := i.images.Delete(ctx, ref.Name)
+				if err != nil {
+					return nil, err
+				}
+				if nn, err := reference.ParseNormalizedNamed(ref.Name); err == nil {
+					familiarRef := reference.FamiliarString(nn)
+					i.logImageEvent(ref, familiarRef, events.ActionUnTag)
+					records = append(records, imagetypes.DeleteResponse{Untagged: familiarRef})
+				}
+			}
+			return records, nil
+		}
 	} else {
 	} else {
 		imgID = image.ID(img.Target.Digest)
 		imgID = image.ID(img.Target.Digest)
 		explicitDanglingRef := strings.HasPrefix(imageRef, imageNameDanglingPrefix) && isDanglingImage(*img)
 		explicitDanglingRef := strings.HasPrefix(imageRef, imageNameDanglingPrefix) && isDanglingImage(*img)
@@ -89,7 +116,7 @@ func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force,
 			return nil, err
 			return nil, err
 		}
 		}
 
 
-		sameRef, err := i.getSameReferences(ctx, img, all)
+		sameRef, err := i.getSameReferences(ctx, parsedRef, all)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -255,21 +282,19 @@ func sortParentsByAffinity(parents []imageWithRootfs) {
 //
 //
 // Note: All imgs should have the same target, only the image name will be considered
 // Note: All imgs should have the same target, only the image name will be considered
 // for determining whether images are the same.
 // for determining whether images are the same.
-func (i *ImageService) getSameReferences(ctx context.Context, img *images.Image, imgs []images.Image) ([]images.Image, error) {
+func (i *ImageService) getSameReferences(ctx context.Context, named reference.Named, imgs []images.Image) ([]images.Image, error) {
 	var (
 	var (
-		named      reference.Named
 		tag        string
 		tag        string
 		sameRef    []images.Image
 		sameRef    []images.Image
 		digestRefs = []images.Image{}
 		digestRefs = []images.Image{}
+		allTags    bool
 	)
 	)
-	if img != nil {
-		repoRef, err := reference.ParseNamed(img.Name)
-		if err != nil {
-			return nil, err
-		}
-		named = repoRef
+	if named != nil {
 		if tagged, ok := named.(reference.Tagged); ok {
 		if tagged, ok := named.(reference.Tagged); ok {
 			tag = tagged.Tag()
 			tag = tagged.Tag()
+		} else if _, ok := named.(reference.Digested); ok {
+			// If digest is explicitly provided, match all tags
+			allTags = true
 		}
 		}
 	}
 	}
 	for _, ref := range imgs {
 	for _, ref := range imgs {
@@ -282,20 +307,22 @@ func (i *ImageService) getSameReferences(ctx context.Context, img *images.Image,
 					}
 					}
 				} else if named.Name() != repoRef.Name() {
 				} else if named.Name() != repoRef.Name() {
 					continue
 					continue
-				} else if tagged, ok := repoRef.(reference.Tagged); ok {
-					if tag == "" {
-						tag = tagged.Tag()
-					} else if tag != tagged.Tag() {
-						// Same repo, different tag, do not include digest refs
-						digestRefs = nil
+				} else if !allTags {
+					if tagged, ok := repoRef.(reference.Tagged); ok {
+						if tag == "" {
+							tag = tagged.Tag()
+						} else if tag != tagged.Tag() {
+							// Same repo, different tag, do not include digest refs
+							digestRefs = nil
+							continue
+						}
+					} else {
+						if digestRefs != nil {
+							digestRefs = append(digestRefs, ref)
+						}
+						// Add digest refs at end if no other tags in the same name
 						continue
 						continue
 					}
 					}
-				} else {
-					if digestRefs != nil {
-						digestRefs = append(digestRefs, ref)
-					}
-					// Add digest refs at end if no other tags in the same name
-					continue
 				}
 				}
 			} else {
 			} else {
 				// Ignore names which do not parse
 				// Ignore names which do not parse

+ 72 - 4
daemon/containerd/image_delete_test.go

@@ -148,6 +148,76 @@ func TestImageDelete(t *testing.T) {
 				},
 				},
 			},
 			},
 		},
 		},
+		{
+			ref: "repoanddigest@" + digestFor(15).String(),
+			starting: []images.Image{
+				{
+					Name:   "docker.io/library/repoanddigest:latest",
+					Target: desc(15),
+				},
+				{
+					Name:   "docker.io/library/repoanddigest:latest@" + digestFor(15).String(),
+					Target: desc(15),
+				},
+				{
+					Name:   "docker.io/library/someotherrepo:latest",
+					Target: desc(15),
+				},
+			},
+			remaining: []images.Image{
+				{
+					Name:   "docker.io/library/someotherrepo:latest",
+					Target: desc(15),
+				},
+			},
+		},
+		{
+			ref: "repoanddigestothertags@" + digestFor(15).String(),
+			starting: []images.Image{
+				{
+					Name:   "docker.io/library/repoanddigestothertags:v1",
+					Target: desc(15),
+				},
+				{
+					Name:   "docker.io/library/repoanddigestothertags:v1@" + digestFor(15).String(),
+					Target: desc(15),
+				},
+				{
+					Name:   "docker.io/library/repoanddigestothertags:v2",
+					Target: desc(15),
+				},
+				{
+					Name:   "docker.io/library/repoanddigestothertags:v2@" + digestFor(15).String(),
+					Target: desc(15),
+				},
+				{
+					Name:   "docker.io/library/someotherrepo:latest",
+					Target: desc(15),
+				},
+			},
+			remaining: []images.Image{
+				{
+					Name:   "docker.io/library/someotherrepo:latest",
+					Target: desc(15),
+				},
+			},
+		},
+		{
+			ref: "repoanddigestzerocase@" + digestFor(16).String(),
+			starting: []images.Image{
+				{
+					Name:   "docker.io/library/someotherrepo:latest",
+					Target: desc(16),
+				},
+			},
+			remaining: []images.Image{
+				{
+					Name:   "docker.io/library/someotherrepo:latest",
+					Target: desc(16),
+				},
+			},
+			err: dimages.ErrImageDoesNotExist{Ref: nameDigest("repoanddigestzerocase", digestFor(16))},
+		},
 	} {
 	} {
 		tc := tc
 		tc := tc
 		t.Run(tc.ref, func(t *testing.T) {
 		t.Run(tc.ref, func(t *testing.T) {
@@ -173,10 +243,8 @@ func TestImageDelete(t *testing.T) {
 			}
 			}
 
 
 			all, err := service.images.List(ctx)
 			all, err := service.images.List(ctx)
-			if err != nil {
-				t.Fatal(err)
-			}
-			assert.Equal(t, len(tc.remaining), len(all))
+			assert.NilError(t, err)
+			assert.Assert(t, is.Len(tc.remaining, len(all)))
 
 
 			// Order should match
 			// Order should match
 			for i := range all {
 			for i := range all {

+ 12 - 1
daemon/containerd/image_test.go

@@ -98,7 +98,8 @@ func TestLookup(t *testing.T) {
 		{
 		{
 			// Fail to lookup reference with no tag, reference has both tag and digest
 			// Fail to lookup reference with no tag, reference has both tag and digest
 			lookup: "ubuntu@" + ubuntuLatestWithOldDigest.Target.Digest.String(),
 			lookup: "ubuntu@" + ubuntuLatestWithOldDigest.Target.Digest.String(),
-			err:    dockerimages.ErrImageDoesNotExist{Ref: nameDigest("ubuntu", ubuntuLatestWithOldDigest.Target.Digest)},
+			img:    nil,
+			all:    []images.Image{ubuntuLatestWithOldDigest},
 		},
 		},
 		{
 		{
 			// Get all image with both tag and digest
 			// Get all image with both tag and digest
@@ -106,6 +107,16 @@ func TestLookup(t *testing.T) {
 			img:    &ubuntuLatestWithOldDigest,
 			img:    &ubuntuLatestWithOldDigest,
 			all:    []images.Image{ubuntuLatestWithOldDigest},
 			all:    []images.Image{ubuntuLatestWithOldDigest},
 		},
 		},
+		{
+			// Fail to lookup reference with no tag for digest that doesn't exist
+			lookup: "ubuntu@" + digestFor(20).String(),
+			err:    dockerimages.ErrImageDoesNotExist{Ref: nameDigest("ubuntu", digestFor(20))},
+		},
+		{
+			// Fail to lookup reference with nonexistent tag
+			lookup: "ubuntu:nonexistent",
+			err:    dockerimages.ErrImageDoesNotExist{Ref: nameTag("ubuntu", "nonexistent")},
+		},
 		{
 		{
 			// Get abcdef image which also matches short image id
 			// Get abcdef image which also matches short image id
 			lookup: "abcdef",
 			lookup: "abcdef",