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>
This commit is contained in:
Derek McGowan 2023-12-11 22:12:00 -08:00
parent 87c87bccb5
commit 0c6e9121b2
No known key found for this signature in database
GPG key ID: F58C5D0A4405ACDB
4 changed files with 139 additions and 29 deletions

View file

@ -483,7 +483,12 @@ func (i *ImageService) resolveAllReferences(ctx context.Context, refOrID string)
if !cerrdefs.IsNotFound(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 {
img = &cimg
if dgst != "" && img.Target.Digest != dgst {
@ -492,7 +497,6 @@ func (i *ImageService) resolveAllReferences(ctx context.Context, refOrID string)
}
dgst = img.Target.Digest
}
}
// Lookup up all associated images and check for consistency with first reference

View file

@ -12,6 +12,7 @@ import (
"github.com/docker/docker/api/types/events"
imagetypes "github.com/docker/docker/api/types/image"
"github.com/docker/docker/container"
dimages "github.com/docker/docker/daemon/images"
"github.com/docker/docker/image"
"github.com/docker/docker/internal/compatcontext"
"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}
}
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 {
return nil, err
}
if len(sameRef) == 0 && named != nil {
return nil, dimages.ErrImageDoesNotExist{Ref: named}
}
if len(sameRef) == len(all) && !force {
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 {
imgID = image.ID(img.Target.Digest)
explicitDanglingRef := strings.HasPrefix(imageRef, imageNameDanglingPrefix) && isDanglingImage(*img)
@ -89,7 +116,7 @@ func (i *ImageService) ImageDelete(ctx context.Context, imageRef string, force,
return nil, err
}
sameRef, err := i.getSameReferences(ctx, img, all)
sameRef, err := i.getSameReferences(ctx, parsedRef, all)
if err != nil {
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
// 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 (
named reference.Named
tag string
sameRef []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 {
tag = tagged.Tag()
} else if _, ok := named.(reference.Digested); ok {
// If digest is explicitly provided, match all tags
allTags = true
}
}
for _, ref := range imgs {
@ -282,20 +307,22 @@ func (i *ImageService) getSameReferences(ctx context.Context, img *images.Image,
}
} else if named.Name() != repoRef.Name() {
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
}
} else {
if digestRefs != nil {
digestRefs = append(digestRefs, ref)
}
// Add digest refs at end if no other tags in the same name
continue
}
} else {
// Ignore names which do not parse

View file

@ -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
t.Run(tc.ref, func(t *testing.T) {
@ -173,10 +243,8 @@ func TestImageDelete(t *testing.T) {
}
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
for i := range all {

View file

@ -98,7 +98,8 @@ func TestLookup(t *testing.T) {
{
// Fail to lookup reference with no tag, reference has both tag and digest
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
@ -106,6 +107,16 @@ func TestLookup(t *testing.T) {
img: &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
lookup: "abcdef",