Browse code

Removing an image that fails, also removes the image name/tag.

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)

Jessica Frazelle authored on 2014/09/10 09:32:14
Showing 3 changed files
... ...
@@ -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
+}