Do not show empty tags for digest references in output

When a repository has a tag and digests, show tag for each digest value.
Do not duplicate rows for the same image name with both tag and digest.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2016-06-14 22:44:49 -07:00
parent 81a85cf448
commit 79eada3814
3 changed files with 103 additions and 64 deletions

View File

@ -155,6 +155,10 @@ func (ctx ContainerContext) Write() {
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() {
switch ctx.Format {
case tableFormatKey:
@ -200,42 +204,98 @@ virtual_size: {{.Size}}
}
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
}
repo = ref.Name()
switch x := ref.(type) {
case reference.Canonical:
digest = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
if nt, ok := ref.(reference.NamedTagged); ok {
repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
}
}
imageCtx := &imageContext{
trunc: ctx.Trunc,
i: image,
repo: repo,
tag: tag,
digest: digest,
for _, refString := range append(image.RepoDigests) {
ref, err := reference.ParseNamed(refString)
if err != nil {
continue
}
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,
})
}
}
}
// 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)
if err != nil {
return

View File

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

View File

@ -284,10 +284,8 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
out, _ = dockerCmd(c, "images", "--digests")
// 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(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out))
// make sure image 2 has repo, <none>, digest
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")
// 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
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))
// list images
out, _ = dockerCmd(c, "images", "--digests")
// 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
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
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))