浏览代码

Merge pull request #23549 from dmcgowan/reference-ui-updates

Do not show empty tags for digest references in output
Arnaud Porterie 9 年之前
父节点
当前提交
16a6987a00
共有 3 个文件被更改,包括 102 次插入63 次删除
  1. 88 28
      api/client/formatter/formatter.go
  2. 9 26
      api/client/formatter/formatter_test.go
  3. 5 9
      integration-cli/docker_cli_by_digest_test.go

+ 88 - 28
api/client/formatter/formatter.go

@@ -155,6 +155,10 @@ func (ctx ContainerContext) Write() {
 	ctx.postformat(tmpl, &containerContext{})
 	ctx.postformat(tmpl, &containerContext{})
 }
 }
 
 
+func isDangling(image types.Image) bool {
+	return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
+}
+
 func (ctx ImageContext) Write() {
 func (ctx ImageContext) Write() {
 	switch ctx.Format {
 	switch ctx.Format {
 	case tableFormatKey:
 	case tableFormatKey:
@@ -200,42 +204,98 @@ virtual_size: {{.Size}}
 	}
 	}
 
 
 	for _, image := range ctx.Images {
 	for _, image := range ctx.Images {
+		images := []*imageContext{}
+		if isDangling(image) {
+			images = append(images, &imageContext{
+				trunc:  ctx.Trunc,
+				i:      image,
+				repo:   "<none>",
+				tag:    "<none>",
+				digest: "<none>",
+			})
+		} else {
+			repoTags := map[string][]string{}
+			repoDigests := map[string][]string{}
 
 
-		repoTags := image.RepoTags
-		repoDigests := image.RepoDigests
-
-		if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
-			// dangling image - clear out either repoTags or repoDigests so we only show it once below
-			repoDigests = []string{}
-		}
-		// combine the tags and digests lists
-		tagsAndDigests := append(repoTags, repoDigests...)
-		for _, repoAndRef := range tagsAndDigests {
-			repo := "<none>"
-			tag := "<none>"
-			digest := "<none>"
-
-			if !strings.HasPrefix(repoAndRef, "<none>") {
-				ref, err := reference.ParseNamed(repoAndRef)
+			for _, refString := range append(image.RepoTags) {
+				ref, err := reference.ParseNamed(refString)
+				if err != nil {
+					continue
+				}
+				if nt, ok := ref.(reference.NamedTagged); ok {
+					repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
+				}
+			}
+			for _, refString := range append(image.RepoDigests) {
+				ref, err := reference.ParseNamed(refString)
 				if err != nil {
 				if err != nil {
 					continue
 					continue
 				}
 				}
-				repo = ref.Name()
+				if c, ok := ref.(reference.Canonical); ok {
+					repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
+				}
+			}
+
+			for repo, tags := range repoTags {
+				digests := repoDigests[repo]
+
+				// Do not display digests as their own row
+				delete(repoDigests, repo)
+
+				if !ctx.Digest {
+					// Ignore digest references, just show tag once
+					digests = nil
+				}
+
+				for _, tag := range tags {
+					if len(digests) == 0 {
+						images = append(images, &imageContext{
+							trunc:  ctx.Trunc,
+							i:      image,
+							repo:   repo,
+							tag:    tag,
+							digest: "<none>",
+						})
+						continue
+					}
+					// Display the digests for each tag
+					for _, dgst := range digests {
+						images = append(images, &imageContext{
+							trunc:  ctx.Trunc,
+							i:      image,
+							repo:   repo,
+							tag:    tag,
+							digest: dgst,
+						})
+					}
 
 
-				switch x := ref.(type) {
-				case reference.Canonical:
-					digest = x.Digest().String()
-				case reference.NamedTagged:
-					tag = x.Tag()
 				}
 				}
 			}
 			}
-			imageCtx := &imageContext{
-				trunc:  ctx.Trunc,
-				i:      image,
-				repo:   repo,
-				tag:    tag,
-				digest: digest,
+
+			// Show rows for remaining digest only references
+			for repo, digests := range repoDigests {
+				// If digests are displayed, show row per digest
+				if ctx.Digest {
+					for _, dgst := range digests {
+						images = append(images, &imageContext{
+							trunc:  ctx.Trunc,
+							i:      image,
+							repo:   repo,
+							tag:    "<none>",
+							digest: dgst,
+						})
+					}
+				} else {
+					images = append(images, &imageContext{
+						trunc: ctx.Trunc,
+						i:     image,
+						repo:  repo,
+						tag:   "<none>",
+					})
+				}
 			}
 			}
+		}
+		for _, imageCtx := range images {
 			err = ctx.contextFormat(tmpl, imageCtx)
 			err = ctx.contextFormat(tmpl, imageCtx)
 			if err != nil {
 			if err != nil {
 				return
 				return

+ 9 - 26
api/client/formatter/formatter_test.go

@@ -301,7 +301,6 @@ func TestImageContextWrite(t *testing.T) {
 			},
 			},
 			`REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 			`REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
 image               tag1                imageID1            24 hours ago        0 B
 image               tag1                imageID1            24 hours ago        0 B
-image               <none>              imageID1            24 hours ago        0 B
 image               tag2                imageID2            24 hours ago        0 B
 image               tag2                imageID2            24 hours ago        0 B
 <none>              <none>              imageID3            24 hours ago        0 B
 <none>              <none>              imageID3            24 hours ago        0 B
 `,
 `,
@@ -312,7 +311,7 @@ image               tag2                imageID2            24 hours ago
 					Format: "table {{.Repository}}",
 					Format: "table {{.Repository}}",
 				},
 				},
 			},
 			},
-			"REPOSITORY\nimage\nimage\nimage\n<none>\n",
+			"REPOSITORY\nimage\nimage\n<none>\n",
 		},
 		},
 		{
 		{
 			ImageContext{
 			ImageContext{
@@ -322,7 +321,6 @@ image               tag2                imageID2            24 hours ago
 				Digest: true,
 				Digest: true,
 			},
 			},
 			`REPOSITORY          DIGEST
 			`REPOSITORY          DIGEST
-image               <none>
 image               sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 image               sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 image               <none>
 image               <none>
 <none>              <none>
 <none>              <none>
@@ -335,7 +333,7 @@ image               <none>
 					Quiet:  true,
 					Quiet:  true,
 				},
 				},
 			},
 			},
-			"REPOSITORY\nimage\nimage\nimage\n<none>\n",
+			"REPOSITORY\nimage\nimage\n<none>\n",
 		},
 		},
 		{
 		{
 			ImageContext{
 			ImageContext{
@@ -344,7 +342,7 @@ image               <none>
 					Quiet:  true,
 					Quiet:  true,
 				},
 				},
 			},
 			},
-			"imageID1\nimageID1\nimageID2\nimageID3\n",
+			"imageID1\nimageID2\nimageID3\n",
 		},
 		},
 		{
 		{
 			ImageContext{
 			ImageContext{
@@ -355,8 +353,7 @@ image               <none>
 				Digest: true,
 				Digest: true,
 			},
 			},
 			`REPOSITORY          TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
 			`REPOSITORY          TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
-image               tag1                <none>                                                                    imageID1            24 hours ago        0 B
-image               <none>              sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf   imageID1            24 hours ago        0 B
+image               tag1                sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf   imageID1            24 hours ago        0 B
 image               tag2                <none>                                                                    imageID2            24 hours ago        0 B
 image               tag2                <none>                                                                    imageID2            24 hours ago        0 B
 <none>              <none>              <none>                                                                    imageID3            24 hours ago        0 B
 <none>              <none>              <none>                                                                    imageID3            24 hours ago        0 B
 `,
 `,
@@ -369,7 +366,7 @@ image               tag2                <none>
 				},
 				},
 				Digest: true,
 				Digest: true,
 			},
 			},
-			"imageID1\nimageID1\nimageID2\nimageID3\n",
+			"imageID1\nimageID2\nimageID3\n",
 		},
 		},
 		// Raw Format
 		// Raw Format
 		{
 		{
@@ -384,12 +381,6 @@ image_id: imageID1
 created_at: %s
 created_at: %s
 virtual_size: 0 B
 virtual_size: 0 B
 
 
-repository: image
-tag: <none>
-image_id: imageID1
-created_at: %s
-virtual_size: 0 B
-
 repository: image
 repository: image
 tag: tag2
 tag: tag2
 image_id: imageID2
 image_id: imageID2
@@ -402,7 +393,7 @@ image_id: imageID3
 created_at: %s
 created_at: %s
 virtual_size: 0 B
 virtual_size: 0 B
 
 
-`, expectedTime, expectedTime, expectedTime, expectedTime),
+`, expectedTime, expectedTime, expectedTime),
 		},
 		},
 		{
 		{
 			ImageContext{
 			ImageContext{
@@ -413,13 +404,6 @@ virtual_size: 0 B
 			},
 			},
 			fmt.Sprintf(`repository: image
 			fmt.Sprintf(`repository: image
 tag: tag1
 tag: tag1
-digest: <none>
-image_id: imageID1
-created_at: %s
-virtual_size: 0 B
-
-repository: image
-tag: <none>
 digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 image_id: imageID1
 image_id: imageID1
 created_at: %s
 created_at: %s
@@ -439,7 +423,7 @@ image_id: imageID3
 created_at: %s
 created_at: %s
 virtual_size: 0 B
 virtual_size: 0 B
 
 
-`, expectedTime, expectedTime, expectedTime, expectedTime),
+`, expectedTime, expectedTime, expectedTime),
 		},
 		},
 		{
 		{
 			ImageContext{
 			ImageContext{
@@ -449,7 +433,6 @@ virtual_size: 0 B
 				},
 				},
 			},
 			},
 			`image_id: imageID1
 			`image_id: imageID1
-image_id: imageID1
 image_id: imageID2
 image_id: imageID2
 image_id: imageID3
 image_id: imageID3
 `,
 `,
@@ -461,7 +444,7 @@ image_id: imageID3
 					Format: "{{.Repository}}",
 					Format: "{{.Repository}}",
 				},
 				},
 			},
 			},
-			"image\nimage\nimage\n<none>\n",
+			"image\nimage\n<none>\n",
 		},
 		},
 		{
 		{
 			ImageContext{
 			ImageContext{
@@ -470,7 +453,7 @@ image_id: imageID3
 				},
 				},
 				Digest: true,
 				Digest: true,
 			},
 			},
-			"image\nimage\nimage\n<none>\n",
+			"image\nimage\n<none>\n",
 		},
 		},
 	}
 	}
 
 

+ 5 - 9
integration-cli/docker_cli_by_digest_test.go

@@ -284,10 +284,8 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
 	out, _ = dockerCmd(c, "images", "--digests")
 	out, _ = dockerCmd(c, "images", "--digests")
 
 
 	// make sure image 1 has repo, tag, <none> AND repo, <none>, digest
 	// make sure image 1 has repo, tag, <none> AND repo, <none>, digest
-	reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*<none>\s`)
-	reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`)
+	reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*` + digest1.String() + `\s`)
 	c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out))
 	c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out))
-	c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out))
 	// make sure image 2 has repo, <none>, digest
 	// make sure image 2 has repo, <none>, digest
 	c.Assert(re2.MatchString(out), checker.True, check.Commentf("expected %q: %s", re2.String(), out))
 	c.Assert(re2.MatchString(out), checker.True, check.Commentf("expected %q: %s", re2.String(), out))
 
 
@@ -298,21 +296,19 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
 	out, _ = dockerCmd(c, "images", "--digests")
 	out, _ = dockerCmd(c, "images", "--digests")
 
 
 	// make sure image 1 has repo, tag, digest
 	// make sure image 1 has repo, tag, digest
-	c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out))
+	c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out))
 
 
 	// make sure image 2 has repo, tag, digest
 	// make sure image 2 has repo, tag, digest
-	reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*<none>\s`)
-	reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`)
-	c.Assert(reWithTag2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag2.String(), out))
+	reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*` + digest2.String() + `\s`)
 	c.Assert(reWithDigest2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest2.String(), out))
 	c.Assert(reWithDigest2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest2.String(), out))
 
 
 	// list images
 	// list images
 	out, _ = dockerCmd(c, "images", "--digests")
 	out, _ = dockerCmd(c, "images", "--digests")
 
 
 	// make sure image 1 has repo, tag, digest
 	// make sure image 1 has repo, tag, digest
-	c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out))
+	c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out))
 	// make sure image 2 has repo, tag, digest
 	// make sure image 2 has repo, tag, digest
-	c.Assert(reWithTag2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag2.String(), out))
+	c.Assert(reWithDigest2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest2.String(), out))
 	// make sure busybox has tag, but not digest
 	// make sure busybox has tag, but not digest
 	busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*<none>\s`)
 	busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*<none>\s`)
 	c.Assert(busyboxRe.MatchString(out), checker.True, check.Commentf("expected %q: %s", busyboxRe.String(), out))
 	c.Assert(busyboxRe.MatchString(out), checker.True, check.Commentf("expected %q: %s", busyboxRe.String(), out))