Fixes #7845 and #7801, and a real pain point I had :)
Docker-DCO-1.1-Signed-off-by: Jessica Frazelle <jess@docker.com> (github: jfrazelle)
| ... | ... |
@@ -33,7 +33,6 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine. |
| 33 | 33 |
var ( |
| 34 | 34 |
repoName, tag string |
| 35 | 35 |
tags = []string{}
|
| 36 |
- tagDeleted bool |
|
| 37 | 36 |
) |
| 38 | 37 |
|
| 39 | 38 |
// FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes |
| ... | ... |
@@ -60,9 +59,11 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine. |
| 60 | 60 |
return err |
| 61 | 61 |
} |
| 62 | 62 |
|
| 63 |
+ repos := daemon.Repositories().ByID()[img.ID] |
|
| 64 |
+ |
|
| 63 | 65 |
//If delete by id, see if the id belong only to one repository |
| 64 | 66 |
if repoName == "" {
|
| 65 |
- for _, repoAndTag := range daemon.Repositories().ByID()[img.ID] {
|
|
| 67 |
+ for _, repoAndTag := range repos {
|
|
| 66 | 68 |
parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag) |
| 67 | 69 |
if repoName == "" || repoName == parsedRepo {
|
| 68 | 70 |
repoName = parsedRepo |
| ... | ... |
@@ -83,9 +84,15 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine. |
| 83 | 83 |
return nil |
| 84 | 84 |
} |
| 85 | 85 |
|
| 86 |
- //Untag the current image |
|
| 86 |
+ if len(repos) <= 1 {
|
|
| 87 |
+ if err := daemon.canDeleteImage(img.ID, force); err != nil {
|
|
| 88 |
+ return err |
|
| 89 |
+ } |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ // Untag the current image |
|
| 87 | 93 |
for _, tag := range tags {
|
| 88 |
- tagDeleted, err = daemon.Repositories().Delete(repoName, tag) |
|
| 94 |
+ tagDeleted, err := daemon.Repositories().Delete(repoName, tag) |
|
| 89 | 95 |
if err != nil {
|
| 90 | 96 |
return err |
| 91 | 97 |
} |
| ... | ... |
@@ -99,9 +106,6 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine. |
| 99 | 99 |
tags = daemon.Repositories().ByID()[img.ID] |
| 100 | 100 |
if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
|
| 101 | 101 |
if len(byParents[img.ID]) == 0 {
|
| 102 |
- if err := daemon.canDeleteImage(img.ID, force, tagDeleted); err != nil {
|
|
| 103 |
- return err |
|
| 104 |
- } |
|
| 105 | 102 |
if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
|
| 106 | 103 |
return err |
| 107 | 104 |
} |
| ... | ... |
@@ -125,11 +129,7 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine. |
| 125 | 125 |
return nil |
| 126 | 126 |
} |
| 127 | 127 |
|
| 128 |
-func (daemon *Daemon) canDeleteImage(imgID string, force, untagged bool) error {
|
|
| 129 |
- var message string |
|
| 130 |
- if untagged {
|
|
| 131 |
- message = " (docker untagged the image)" |
|
| 132 |
- } |
|
| 128 |
+func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
|
|
| 133 | 129 |
for _, container := range daemon.List() {
|
| 134 | 130 |
parent, err := daemon.Repositories().LookupImage(container.Image) |
| 135 | 131 |
if err != nil {
|
| ... | ... |
@@ -140,11 +140,11 @@ func (daemon *Daemon) canDeleteImage(imgID string, force, untagged bool) error {
|
| 140 | 140 |
if imgID == p.ID {
|
| 141 | 141 |
if container.IsRunning() {
|
| 142 | 142 |
if force {
|
| 143 |
- return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it%s, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 143 |
+ return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
| 144 | 144 |
} |
| 145 |
- return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it%s, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 145 |
+ return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
| 146 | 146 |
} else if !force {
|
| 147 |
- return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it%s, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 147 |
+ return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID))
|
|
| 148 | 148 |
} |
| 149 | 149 |
} |
| 150 | 150 |
return nil |
| ... | ... |
@@ -20,44 +20,6 @@ func TestImagesEnsureImageIsListed(t *testing.T) {
|
| 20 | 20 |
logDone("images - busybox should be listed")
|
| 21 | 21 |
} |
| 22 | 22 |
|
| 23 |
-func TestCLIImageTagRemove(t *testing.T) {
|
|
| 24 |
- imagesBefore, _, _ := cmd(t, "images", "-a") |
|
| 25 |
- cmd(t, "tag", "busybox", "utest:tag1") |
|
| 26 |
- cmd(t, "tag", "busybox", "utest/docker:tag2") |
|
| 27 |
- cmd(t, "tag", "busybox", "utest:5000/docker:tag3") |
|
| 28 |
- {
|
|
| 29 |
- imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 30 |
- if nLines(imagesAfter) != nLines(imagesBefore)+3 {
|
|
| 31 |
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 32 |
- } |
|
| 33 |
- } |
|
| 34 |
- cmd(t, "rmi", "utest/docker:tag2") |
|
| 35 |
- {
|
|
| 36 |
- imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 37 |
- if nLines(imagesAfter) != nLines(imagesBefore)+2 {
|
|
| 38 |
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 39 |
- } |
|
| 40 |
- |
|
| 41 |
- } |
|
| 42 |
- cmd(t, "rmi", "utest:5000/docker:tag3") |
|
| 43 |
- {
|
|
| 44 |
- imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 45 |
- if nLines(imagesAfter) != nLines(imagesBefore)+1 {
|
|
| 46 |
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 47 |
- } |
|
| 48 |
- |
|
| 49 |
- } |
|
| 50 |
- cmd(t, "rmi", "utest:tag1") |
|
| 51 |
- {
|
|
| 52 |
- imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 53 |
- if nLines(imagesAfter) != nLines(imagesBefore)+0 {
|
|
| 54 |
- t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 55 |
- } |
|
| 56 |
- |
|
| 57 |
- } |
|
| 58 |
- logDone("tag,rmi- tagging the same images multiple times then removing tags")
|
|
| 59 |
-} |
|
| 60 |
- |
|
| 61 | 23 |
func TestImagesOrderedByCreationDate(t *testing.T) {
|
| 62 | 24 |
defer deleteImages("order:test_a")
|
| 63 | 25 |
defer deleteImages("order:test_c")
|
| 64 | 26 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,77 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os/exec" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "testing" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestImageRemoveWithContainerFails(t *testing.T) {
|
|
| 10 |
+ errSubstr := "is using it" |
|
| 11 |
+ |
|
| 12 |
+ // create a container |
|
| 13 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") |
|
| 14 |
+ out, _, err := runCommandWithOutput(runCmd) |
|
| 15 |
+ errorOut(err, t, fmt.Sprintf("failed to create a container: %v %v", out, err))
|
|
| 16 |
+ |
|
| 17 |
+ cleanedContainerID := stripTrailingCharacters(out) |
|
| 18 |
+ |
|
| 19 |
+ // try to delete the image |
|
| 20 |
+ runCmd = exec.Command(dockerBinary, "rmi", "busybox") |
|
| 21 |
+ out, _, err = runCommandWithOutput(runCmd) |
|
| 22 |
+ if err == nil {
|
|
| 23 |
+ t.Fatalf("Container %q is using image, should not be able to rmi: %q", cleanedContainerID, out)
|
|
| 24 |
+ } |
|
| 25 |
+ if !strings.Contains(out, errSubstr) {
|
|
| 26 |
+ t.Fatalf("Container %q is using image, error message should contain %q: %v", cleanedContainerID, errSubstr, out)
|
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ // make sure it didn't delete the busybox name |
|
| 30 |
+ images, _, _ := cmd(t, "images") |
|
| 31 |
+ if !strings.Contains(images, "busybox") {
|
|
| 32 |
+ t.Fatalf("The name 'busybox' should not have been removed from images: %q", images)
|
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ deleteContainer(cleanedContainerID) |
|
| 36 |
+ |
|
| 37 |
+ logDone("rmi- container using image while rmi, should not remove image name")
|
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+func TestImageTagRemove(t *testing.T) {
|
|
| 41 |
+ imagesBefore, _, _ := cmd(t, "images", "-a") |
|
| 42 |
+ cmd(t, "tag", "busybox", "utest:tag1") |
|
| 43 |
+ cmd(t, "tag", "busybox", "utest/docker:tag2") |
|
| 44 |
+ cmd(t, "tag", "busybox", "utest:5000/docker:tag3") |
|
| 45 |
+ {
|
|
| 46 |
+ imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 47 |
+ if nLines(imagesAfter) != nLines(imagesBefore)+3 {
|
|
| 48 |
+ t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+ cmd(t, "rmi", "utest/docker:tag2") |
|
| 52 |
+ {
|
|
| 53 |
+ imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 54 |
+ if nLines(imagesAfter) != nLines(imagesBefore)+2 {
|
|
| 55 |
+ t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ } |
|
| 59 |
+ cmd(t, "rmi", "utest:5000/docker:tag3") |
|
| 60 |
+ {
|
|
| 61 |
+ imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 62 |
+ if nLines(imagesAfter) != nLines(imagesBefore)+1 {
|
|
| 63 |
+ t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ } |
|
| 67 |
+ cmd(t, "rmi", "utest:tag1") |
|
| 68 |
+ {
|
|
| 69 |
+ imagesAfter, _, _ := cmd(t, "images", "-a") |
|
| 70 |
+ if nLines(imagesAfter) != nLines(imagesBefore)+0 {
|
|
| 71 |
+ t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter)
|
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ } |
|
| 75 |
+ logDone("tag,rmi- tagging the same images multiple times then removing tags")
|
|
| 76 |
+} |