Skip to content

Commit 07cb00b

Browse files
authored
Merge pull request #3519 from haytok/issue_3016
fix: Allow to delete images when names of images are short digest ids…
2 parents fad0285 + c3627e1 commit 07cb00b

File tree

3 files changed

+84
-17
lines changed

3 files changed

+84
-17
lines changed

cmd/nerdctl/image/image_remove_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ package image
1818

1919
import (
2020
"errors"
21+
"strings"
2122
"testing"
2223

24+
"gotest.tools/v3/assert"
25+
2326
"github.com/containerd/nerdctl/v2/pkg/imgutil"
2427
"github.com/containerd/nerdctl/v2/pkg/testutil"
2528
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
@@ -302,3 +305,49 @@ func TestRemove(t *testing.T) {
302305

303306
testCase.Run(t)
304307
}
308+
309+
// TestIssue3016 tests https://github.com/containerd/nerdctl/issues/3016
310+
func TestIssue3016(t *testing.T) {
311+
testCase := nerdtest.Setup()
312+
313+
const (
314+
tagIDKey = "tagID"
315+
)
316+
317+
testCase.SubTests = []*test.Case{
318+
{
319+
Description: "Issue #3016 - Tags created using the short digest ids of container images cannot be deleted using the nerdctl rmi command.",
320+
Setup: func(data test.Data, helpers test.Helpers) {
321+
helpers.Ensure("pull", testutil.CommonImage)
322+
helpers.Ensure("pull", testutil.NginxAlpineImage)
323+
324+
img := nerdtest.InspectImage(helpers, testutil.NginxAlpineImage)
325+
repoName, _ := imgutil.ParseRepoTag(testutil.NginxAlpineImage)
326+
tagID := strings.TrimPrefix(img.RepoDigests[0], repoName+"@sha256:")[0:8]
327+
328+
helpers.Ensure("tag", testutil.CommonImage, tagID)
329+
330+
data.Set(tagIDKey, tagID)
331+
},
332+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
333+
return helpers.Command("rmi", data.Get(tagIDKey))
334+
},
335+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
336+
return &test.Expected{
337+
ExitCode: 0,
338+
Errors: []error{},
339+
Output: func(stdout string, info string, t *testing.T) {
340+
helpers.Command("images", data.Get(tagIDKey)).Run(&test.Expected{
341+
ExitCode: 0,
342+
Output: func(stdout string, info string, t *testing.T) {
343+
assert.Equal(t, len(strings.Split(stdout, "\n")), 2)
344+
},
345+
})
346+
},
347+
}
348+
},
349+
},
350+
}
351+
352+
testCase.Run(t)
353+
}

pkg/cmd/image/remove.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,18 @@ func Remove(ctx context.Context, client *containerd.Client, args []string, optio
6565
walker := &imagewalker.ImageWalker{
6666
Client: client,
6767
OnFound: func(ctx context.Context, found imagewalker.Found) error {
68-
// if found multiple images, return error unless in force-mode and
69-
// there is only 1 unique image.
70-
if found.MatchCount > 1 && !(options.Force && found.UniqueImages == 1) {
71-
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
68+
if found.NameMatchIndex == -1 {
69+
// if found multiple images, return error unless in force-mode and
70+
// there is only 1 unique image.
71+
if found.MatchCount > 1 && !(options.Force && found.UniqueImages == 1) {
72+
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
73+
}
74+
} else if found.NameMatchIndex != found.MatchIndex {
75+
// when there is an image with a name matching the argument but the argument is a digest short id,
76+
// the deletion process is not performed.
77+
return nil
7278
}
79+
7380
if cid, ok := runningImages[found.Image.Name]; ok {
7481
return fmt.Errorf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", found.Req, cid)
7582
}

pkg/idutil/imagewalker/imagewalker.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ import (
3131
)
3232

3333
type Found struct {
34-
Image images.Image
35-
Req string // The raw request string. name, short ID, or long ID.
36-
MatchIndex int // Begins with 0, up to MatchCount - 1.
37-
MatchCount int // 1 on exact match. > 1 on ambiguous match. Never be <= 0.
38-
UniqueImages int // Number of unique images in all found images.
34+
Image images.Image
35+
Req string // The raw request string. name, short ID, or long ID.
36+
MatchIndex int // Begins with 0, up to MatchCount - 1.
37+
MatchCount int // 1 on exact match. > 1 on ambiguous match. Never be <= 0.
38+
UniqueImages int // Number of unique images in all found images.
39+
NameMatchIndex int // Image index with a name matching the argument for `nerdctl rmi`.
3940
}
4041

4142
type OnFound func(ctx context.Context, found Found) error
@@ -50,8 +51,12 @@ type ImageWalker struct {
5051
// Returns the number of the found entries.
5152
func (w *ImageWalker) Walk(ctx context.Context, req string) (int, error) {
5253
var filters []string
53-
if parsedReference, err := referenceutil.Parse(req); err == nil {
54-
filters = append(filters, fmt.Sprintf("name==%s", parsedReference.String()))
54+
var parsedReferenceStr string
55+
56+
parsedReference, err := referenceutil.Parse(req)
57+
if err == nil {
58+
parsedReferenceStr = parsedReference.String()
59+
filters = append(filters, fmt.Sprintf("name==%s", parsedReferenceStr))
5560
}
5661
filters = append(filters,
5762
fmt.Sprintf("name==%s", req),
@@ -68,17 +73,23 @@ func (w *ImageWalker) Walk(ctx context.Context, req string) (int, error) {
6873
// to handle the `rmi -f` case where returned images are different but
6974
// have the same short prefix.
7075
uniqueImages := make(map[digest.Digest]bool)
71-
for _, image := range images {
76+
nameMatchIndex := -1
77+
for i, image := range images {
7278
uniqueImages[image.Target.Digest] = true
79+
// to get target image index for `nerdctl rmi <short digest ids of another images>`.
80+
if (parsedReferenceStr != "" && image.Name == parsedReferenceStr) || image.Name == req {
81+
nameMatchIndex = i
82+
}
7383
}
7484

7585
for i, img := range images {
7686
f := Found{
77-
Image: img,
78-
Req: req,
79-
MatchIndex: i,
80-
MatchCount: matchCount,
81-
UniqueImages: len(uniqueImages),
87+
Image: img,
88+
Req: req,
89+
MatchIndex: i,
90+
MatchCount: matchCount,
91+
UniqueImages: len(uniqueImages),
92+
NameMatchIndex: nameMatchIndex,
8293
}
8394
if e := w.OnFound(ctx, f); e != nil {
8495
return -1, e

0 commit comments

Comments
 (0)