diff --git a/daemon/containerd/image.go b/daemon/containerd/image.go index 65c268e060..de6e0797f7 100644 --- a/daemon/containerd/image.go +++ b/daemon/containerd/image.go @@ -261,6 +261,24 @@ func (i *ImageService) resolveImage(ctx context.Context, refOrID string) (contai return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed} } + // If reference is both Named and Digested, make sure we don't match + // images with a different repository even if digest matches. + // For example, busybox@sha256:abcdef..., shouldn't match asdf@sha256:abcdef... + if parsedNamed, ok := parsed.(reference.Named); ok { + for _, img := range imgs { + imgNamed, err := reference.ParseNormalizedNamed(img.Name) + if err != nil { + log.G(ctx).WithError(err).WithField("image", img.Name).Warn("image with invalid name encountered") + continue + } + + if parsedNamed.Name() == imgNamed.Name() { + return img, nil + } + } + return containerdimages.Image{}, images.ErrImageDoesNotExist{Ref: parsed} + } + return imgs[0], nil } diff --git a/integration/image/remove_test.go b/integration/image/remove_test.go index 11d8141da3..6401313427 100644 --- a/integration/image/remove_test.go +++ b/integration/image/remove_test.go @@ -6,9 +6,11 @@ import ( "testing" "github.com/docker/docker/api/types" + "github.com/docker/docker/errdefs" "github.com/docker/docker/integration/internal/container" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" + "gotest.tools/v3/skip" ) func TestRemoveImageOrphaning(t *testing.T) { @@ -57,3 +59,36 @@ func TestRemoveImageOrphaning(t *testing.T) { _, _, err = client.ImageInspectWithRaw(ctx, commitResp2.ID) assert.Check(t, is.ErrorContains(err, "No such image:")) } + +func TestRemoveByDigest(t *testing.T) { + skip.If(t, !testEnv.UsingSnapshotter(), "RepoDigests doesn't include tags when using graphdrivers") + + defer setupTest(t)() + ctx := context.Background() + client := testEnv.APIClient() + + err := client.ImageTag(ctx, "busybox", "test-remove-by-digest:latest") + assert.NilError(t, err) + + inspect, _, err := client.ImageInspectWithRaw(ctx, "test-remove-by-digest") + assert.NilError(t, err) + + id := "" + for _, ref := range inspect.RepoDigests { + if strings.Contains(ref, "test-remove-by-digest") { + id = ref + break + } + } + assert.Assert(t, id != "") + + t.Logf("removing %s", id) + _, err = client.ImageRemove(ctx, id, types.ImageRemoveOptions{}) + assert.NilError(t, err) + + inspect, _, err = client.ImageInspectWithRaw(ctx, "busybox") + assert.Check(t, err, "busybox image got deleted") + + inspect, _, err = client.ImageInspectWithRaw(ctx, "test-remove-by-digest") + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) +}