Skip to content

Commit

Permalink
fix: Allow to untag images associated with running or paused containe…
Browse files Browse the repository at this point in the history
…rs by nerdctl rmi -f

In Docker, running `docker rmi -f <Image Names>` on images associated with
running or stopped containers will untag the images, leaving <none> images.

The specific behavior in Docker is as follows.

```
> docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
alpine       latest    91ef0af61f39   6 weeks ago   7.8MB

> docker ps
CONTAINER ID   IMAGE     COMMAND            CREATED         STATUS         PORTS     NAMES
fe4caab5cf42   alpine    "sleep infinity"   4 minutes ago   Up 4 minutes             test

> docker rmi -f alpine
Untagged: alpine:latest
Untagged: alpine@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d

> docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
<none>       <none>    91ef0af61f39   6 weeks ago   7.8MB
```

On the other hand, the same operation described above with nerdctl will
result in the following error.

```
> nerdctl rmi -f alpine
FATA[0000] 1 errors:
conflict: unable to delete alpine (cannot be forced) - image is being used by running container 59261bebc8113ca1ea102203137c32406742c2ec43ca3b108a314e9bfb4657fb
```

This befavior is reported in the following:

- #3454

Therefore, this commit fixes it so that `nerdctl rmi -f <Image Names>` can
be performed on images associated with running or stopped containers.

The behaviour in nerdctl after this modification is as follows.

```
> nerdctl images
REPOSITORY    TAG       IMAGE ID        CREATED          PLATFORM       SIZE       BLOB SIZE
alpine        latest    beefdbd8a1da    5 seconds ago    linux/amd64    8.458MB    3.626MB

> nerdctl ps
CONTAINER ID    IMAGE                              COMMAND             CREATED          STATUS    PORTS    NAMES
28c9db821576    docker.io/library/alpine:latest    "sleep infinity"    6 seconds ago    Up                 alpine-28c9d

> nerdctl rmi -f alpine
Untagged: docker.io/library/alpine:latest
Untagged: sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d

> nerdctl images
REPOSITORY    TAG       IMAGE ID        CREATED          PLATFORM       SIZE       BLOB SIZE
<none>        <none>    beefdbd8a1da    3 seconds ago    linux/amd64    8.458MB    3.626MB
```

Signed-off-by: Hayato Kiwata <[email protected]>
  • Loading branch information
haytok committed Oct 24, 2024
1 parent 4d64f29 commit f6b8222
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 16 deletions.
40 changes: 24 additions & 16 deletions cmd/nerdctl/image/image_remove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -115,29 +119,31 @@ 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 `<none>` image) without deletion; `docker rmi -rf imageID` fails.
// In both cases, `nerdctl rmi -f` will fail.
Require: test.Require(
test.Not(test.Windows),
test.Not(nerdtest.Docker),
),
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "--pull", "always", "-d", "--name", data.Identifier(), testutil.CommonImage, "sleep", "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("<none>"),
})
},
}
Expand Down Expand Up @@ -225,28 +231,30 @@ func TestRemove(t *testing.T) {
Require: test.Require(
test.Not(test.Windows),
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 `<none>` 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", "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("<none>"),
})
},
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/cmd/image/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit f6b8222

Please sign in to comment.