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)
(cherry picked from commit 79eada3814
)
This commit is contained in:
parent
b3387e96ed
commit
56b253fb3c
3 changed files with 103 additions and 64 deletions
|
@ -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()
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
if c, ok := ref.(reference.Canonical); ok {
|
||||
repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
|
||||
}
|
||||
}
|
||||
|
||||
switch x := ref.(type) {
|
||||
case reference.Canonical:
|
||||
digest = x.Digest().String()
|
||||
case reference.NamedTagged:
|
||||
tag = x.Tag()
|
||||
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
|
||||
}
|
||||
}
|
||||
imageCtx := &imageContext{
|
||||
|
||||
for _, tag := range tags {
|
||||
if len(digests) == 0 {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: digest,
|
||||
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
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue