Browse code

Merge pull request #19170 from aaronlehmann/delete-prune-digests

Prune digest references when deleting by tag

David Calavera authored on 2016/01/13 08:15:36
Showing 4 changed files
... ...
@@ -90,8 +90,34 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
90 90
 		daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
91 91
 		records = append(records, untaggedRecord)
92 92
 
93
-		// If has remaining references then untag finishes the remove
94
-		if len(repoRefs) > 1 {
93
+		repoRefs = daemon.referenceStore.References(imgID)
94
+
95
+		// If this is a tag reference and all the remaining references
96
+		// to this image are digest references, delete the remaining
97
+		// references so that they don't prevent removal of the image.
98
+		if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical {
99
+			foundTagRef := false
100
+			for _, repoRef := range repoRefs {
101
+				if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical {
102
+					foundTagRef = true
103
+					break
104
+				}
105
+			}
106
+			if !foundTagRef {
107
+				for _, repoRef := range repoRefs {
108
+					if _, err := daemon.removeImageRef(repoRef); err != nil {
109
+						return records, err
110
+					}
111
+
112
+					untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
113
+					records = append(records, untaggedRecord)
114
+				}
115
+				repoRefs = []reference.Named{}
116
+			}
117
+		}
118
+
119
+		// If it has remaining references then the untag finished the remove
120
+		if len(repoRefs) > 0 {
95 121
 			return records, nil
96 122
 		}
97 123
 
... ...
@@ -19,8 +19,9 @@ parent = "smn_cli"
19 19
       --no-prune           Do not delete untagged parents
20 20
 
21 21
 You can remove an image using its short or long ID, its tag, or its digest. If
22
-an image has one or more tag or digest reference, you must remove all of them
23
-before the image is removed.
22
+an image has one or more tag referencing it, you must remove all of them before
23
+the image is removed. Digest references are removed automatically when an image
24
+is removed by tag.
24 25
 
25 26
     $ docker images
26 27
     REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
... ...
@@ -395,6 +395,35 @@ func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C)
395 395
 	dockerCmd(c, "rmi", imageID)
396 396
 }
397 397
 
398
+func (s *DockerRegistrySuite) TestDeleteImageWithDigestAndTag(c *check.C) {
399
+	pushDigest, err := setupImage(c)
400
+	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
401
+
402
+	// pull from the registry using the <name>@<digest> reference
403
+	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
404
+	dockerCmd(c, "pull", imageReference)
405
+
406
+	imageID, err := inspectField(imageReference, "Id")
407
+	c.Assert(err, checker.IsNil, check.Commentf("error inspecting image id"))
408
+
409
+	repoTag := repoName + ":sometag"
410
+	repoTag2 := repoName + ":othertag"
411
+	dockerCmd(c, "tag", imageReference, repoTag)
412
+	dockerCmd(c, "tag", imageReference, repoTag2)
413
+
414
+	dockerCmd(c, "rmi", repoTag2)
415
+
416
+	// rmi should have deleted only repoTag2, because there's another tag
417
+	_, err = inspectField(repoTag, "Id")
418
+	c.Assert(err, checker.IsNil, check.Commentf("repoTag should not have been removed"))
419
+
420
+	dockerCmd(c, "rmi", repoTag)
421
+
422
+	// rmi should have deleted the tag, the digest reference, and the image itself
423
+	_, err = inspectField(imageID, "Id")
424
+	c.Assert(err, checker.NotNil, check.Commentf("image should have been deleted"))
425
+}
426
+
398 427
 // TestPullFailsWithAlteredManifest tests that a `docker pull` fails when
399 428
 // we have modified a manifest blob and its digest cannot be verified.
400 429
 // This is the schema2 version of the test.
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"fmt"
5 5
 	"io/ioutil"
6 6
 	"os/exec"
7
+	"strings"
7 8
 	"time"
8 9
 
9 10
 	"github.com/docker/docker/pkg/integration/checker"
... ...
@@ -200,3 +201,55 @@ func (s *DockerTrustSuite) TestTrustedOfflinePull(c *check.C) {
200 200
 	c.Assert(err, check.IsNil, check.Commentf(out))
201 201
 	c.Assert(string(out), checker.Contains, "Tagging", check.Commentf(out))
202 202
 }
203
+
204
+func (s *DockerTrustSuite) TestTrustedPullDelete(c *check.C) {
205
+	repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, "trusted-pull-delete")
206
+	// tag the image and upload it to the private registry
207
+	_, err := buildImage(repoName, `
208
+                    FROM busybox
209
+                    CMD echo trustedpulldelete
210
+                `, true)
211
+
212
+	pushCmd := exec.Command(dockerBinary, "push", repoName)
213
+	s.trustedCmd(pushCmd)
214
+	out, _, err := runCommandWithOutput(pushCmd)
215
+	if err != nil {
216
+		c.Fatalf("Error running trusted push: %s\n%s", err, out)
217
+	}
218
+	if !strings.Contains(string(out), "Signing and pushing trust metadata") {
219
+		c.Fatalf("Missing expected output on trusted push:\n%s", out)
220
+	}
221
+
222
+	if out, status := dockerCmd(c, "rmi", repoName); status != 0 {
223
+		c.Fatalf("Error removing image %q\n%s", repoName, out)
224
+	}
225
+
226
+	// Try pull
227
+	pullCmd := exec.Command(dockerBinary, "pull", repoName)
228
+	s.trustedCmd(pullCmd)
229
+	out, _, err = runCommandWithOutput(pullCmd)
230
+
231
+	c.Assert(err, check.IsNil, check.Commentf(out))
232
+
233
+	matches := digestRegex.FindStringSubmatch(out)
234
+	c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out))
235
+	pullDigest := matches[1]
236
+
237
+	imageID, err := inspectField(repoName, "Id")
238
+	c.Assert(err, checker.IsNil, check.Commentf("error inspecting image id"))
239
+
240
+	imageByDigest := repoName + "@" + pullDigest
241
+	byDigestID, err := inspectField(imageByDigest, "Id")
242
+	c.Assert(err, checker.IsNil, check.Commentf("error inspecting image id"))
243
+
244
+	c.Assert(byDigestID, checker.Equals, imageID)
245
+
246
+	// rmi of tag should also remove the digest reference
247
+	dockerCmd(c, "rmi", repoName)
248
+
249
+	_, err = inspectField(imageByDigest, "Id")
250
+	c.Assert(err, checker.NotNil, check.Commentf("digest reference should have been removed"))
251
+
252
+	_, err = inspectField(imageID, "Id")
253
+	c.Assert(err, checker.NotNil, check.Commentf("image should have been deleted"))
254
+}