diff --git a/cmd/nerdctl/image/image_remove_test.go b/cmd/nerdctl/image/image_remove_test.go index 5771ac4c07c..ff3d33cbb71 100644 --- a/cmd/nerdctl/image/image_remove_test.go +++ b/cmd/nerdctl/image/image_remove_test.go @@ -32,6 +32,10 @@ import ( func TestRemove(t *testing.T) { testCase := nerdtest.Setup() + const ( + imgShortIDKey = "imgShortID" + ) + repoName, _ := imgutil.ParseRepoTag(testutil.CommonImage) nginxRepoName, _ := imgutil.ParseRepoTag(testutil.NginxAlpineImage) // NOTES: @@ -112,28 +116,30 @@ func TestRemove(t *testing.T) { { Description: "Remove image with running container - with -f", NoParallel: true, - // FIXME: nerdctl is broken - // https://github.com/containerd/nerdctl/issues/3454 - // If an image is associated with a running/paused containers, `docker rmi -f imageName` - // untags `imageName` (left a `` image) without deletion; `docker rmi -rf imageID` fails. - // In both cases, `nerdctl rmi -f` will fail. Require: test.Require( test.Not(nerdtest.Docker), ), Setup: func(data test.Data, helpers test.Helpers) { helpers.Ensure("run", "--pull", "always", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", nerdtest.Infinity) + + img := nerdtest.InspectImage(helpers, testutil.CommonImage) + repoName, _ := imgutil.ParseRepoTag(testutil.CommonImage) + imgShortID := strings.TrimPrefix(img.RepoDigests[0], repoName+"@sha256:")[0:8] + + data.Set(imgShortIDKey, imgShortID) }, Cleanup: func(data test.Data, helpers test.Helpers) { helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Get(imgShortIDKey)) }, Command: test.Command("rmi", "-f", testutil.CommonImage), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 1, - Errors: []error{errors.New("image is being used")}, + ExitCode: 0, + Errors: []error{}, Output: func(stdout string, info string, t *testing.T) { helpers.Command("images").Run(&test.Expected{ - Output: test.Contains(repoName), + Output: test.Contains(""), }) }, } @@ -219,28 +225,30 @@ func TestRemove(t *testing.T) { NoParallel: true, Require: test.Require( nerdtest.CGroup, - // FIXME: nerdctl is broken - // https://github.com/containerd/nerdctl/issues/3454 - // If an image is associated with a running/paused containers, `docker rmi -f imageName` - // untags `imageName` (left a `` image) without deletion; `docker rmi -rf imageID` fails. - // In both cases, `nerdctl rmi -f` will fail. test.Not(nerdtest.Docker), ), Setup: func(data test.Data, helpers test.Helpers) { helpers.Ensure("run", "--pull", "always", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", nerdtest.Infinity) helpers.Ensure("pause", data.Identifier()) + + img := nerdtest.InspectImage(helpers, testutil.CommonImage) + repoName, _ := imgutil.ParseRepoTag(testutil.CommonImage) + imgShortID := strings.TrimPrefix(img.RepoDigests[0], repoName+"@sha256:")[0:8] + + data.Set(imgShortIDKey, imgShortID) }, Cleanup: func(data test.Data, helpers test.Helpers) { helpers.Anyhow("rm", "-f", data.Identifier()) + helpers.Anyhow("rmi", "-f", data.Get(imgShortIDKey)) }, Command: test.Command("rmi", "-f", testutil.CommonImage), Expected: func(data test.Data, helpers test.Helpers) *test.Expected { return &test.Expected{ - ExitCode: 1, - Errors: []error{errors.New("image is being used")}, + ExitCode: 0, + Errors: []error{}, Output: func(stdout string, info string, t *testing.T) { helpers.Command("images").Run(&test.Expected{ - Output: test.Contains(repoName), + Output: test.Contains(""), }) }, } diff --git a/pkg/cmd/image/remove.go b/pkg/cmd/image/remove.go index 3afa2bff80b..8075a928659 100644 --- a/pkg/cmd/image/remove.go +++ b/pkg/cmd/image/remove.go @@ -78,6 +78,19 @@ func Remove(ctx context.Context, client *containerd.Client, args []string, optio } if cid, ok := runningImages[found.Image.Name]; ok { + if options.Force { + if err = is.Delete(ctx, found.Image.Name); err != nil { + return err + } + fmt.Fprintf(options.Stdout, "Untagged: %s\n", found.Image.Name) + fmt.Fprintf(options.Stdout, "Untagged: %s\n", found.Image.Target.Digest.String()) + + found.Image.Name = ":" + if _, err = is.Create(ctx, found.Image); err != nil { + return err + } + return nil + } return fmt.Errorf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", found.Req, cid) } if cid, ok := usedImages[found.Image.Name]; ok && !options.Force {