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:
parent
87c87bccb5
commit
0c6e9121b2
4 changed files with 139 additions and 29 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue