diff --git a/daemon/image_delete.go b/daemon/image_delete.go index a44eb1bfa6..ece33a3c78 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -30,6 +30,7 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi repoName, tag string tags = []string{} ) + repoAndTags := make(map[string][]string) // FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes repoName, tag = parsers.ParseRepositoryTag(name) @@ -68,19 +69,25 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi if repoName == "" || repoName == parsedRepo { repoName = parsedRepo if parsedTag != "" { - tags = append(tags, parsedTag) + repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag) } } else if repoName != parsedRepo && !force && first { // the id belongs to multiple repos, like base:latest and user:test, // in that case return conflict return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name) + } else { + //the id belongs to multiple repos, with -f just delete all + repoName = parsedRepo + if parsedTag != "" { + repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag) + } } } } else { - tags = append(tags, tag) + repoAndTags[repoName] = append(repoAndTags[repoName], tag) } - if !first && len(tags) > 0 { + if !first && len(repoAndTags) > 0 { return nil } @@ -91,16 +98,18 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi } // Untag the current image - for _, tag := range tags { - tagDeleted, err := daemon.Repositories().Delete(repoName, tag) - if err != nil { - return err - } - if tagDeleted { - *list = append(*list, types.ImageDelete{ - Untagged: utils.ImageReference(repoName, tag), - }) - daemon.EventsService.Log("untag", img.ID, "") + for repoName, tags := range repoAndTags { + for _, tag := range tags { + tagDeleted, err := daemon.Repositories().Delete(repoName, tag) + if err != nil { + return err + } + if tagDeleted { + *list = append(*list, types.ImageDelete{ + Untagged: utils.ImageReference(repoName, tag), + }) + daemon.EventsService.Log("untag", img.ID, "") + } } } tags = daemon.Repositories().ByID()[img.ID] diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 79f59f5f2d..b9f506a6c8 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1794,6 +1794,21 @@ before the image is removed. Untagged: test:latest Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 +If you use the `-f` flag and specify the image's short or long ID, then this +command untags and removes all images that match the specified ID. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED SIZE + test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + test2 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + + $ docker rmi -f fd484f19954f + Untagged: test1:latest + Untagged: test:latest + Untagged: test2:latest + Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 + An image pulled by digest has no tag associated with it: $ docker images --digests diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 277004d2ec..7161a09228 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -77,6 +77,43 @@ func TestRmiTag(t *testing.T) { logDone("rmi - tag,rmi - tagging the same images multiple times then removing tags") } +func TestRmiImgIDForce(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf("failed to create a container:%s, %v", out, err) + } + containerID := strings.TrimSpace(out) + runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-test") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) + } + + imagesBefore, _, _ := dockerCmd(t, "images", "-a") + dockerCmd(t, "tag", "busybox-test", "utest:tag1") + dockerCmd(t, "tag", "busybox-test", "utest:tag2") + dockerCmd(t, "tag", "busybox-test", "utest/docker:tag3") + dockerCmd(t, "tag", "busybox-test", "utest:5000/docker:tag4") + { + imagesAfter, _, _ := dockerCmd(t, "images", "-a") + if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 { + t.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) + } + } + out, _, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") + imgID := strings.TrimSpace(out) + dockerCmd(t, "rmi", "-f", imgID) + { + imagesAfter, _, _ := dockerCmd(t, "images", "-a") + if strings.Contains(imagesAfter, imgID[:12]) { + t.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) + } + + } + logDone("rmi - imgID,rmi -f imgID delete all tagged repos of specific imgID") +} + func TestRmiTagWithExistingContainers(t *testing.T) { defer deleteAllContainers()