mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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:
parent
81a85cf448
commit
79eada3814
3 changed files with 103 additions and 64 deletions
|
@ -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
|
for _, refString := range append(image.RepoTags) {
|
||||||
repoDigests := image.RepoDigests
|
ref, err := reference.ParseNamed(refString)
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
repo = ref.Name()
|
if nt, ok := ref.(reference.NamedTagged); ok {
|
||||||
|
repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
|
||||||
switch x := ref.(type) {
|
|
||||||
case reference.Canonical:
|
|
||||||
digest = x.Digest().String()
|
|
||||||
case reference.NamedTagged:
|
|
||||||
tag = x.Tag()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imageCtx := &imageContext{
|
for _, refString := range append(image.RepoDigests) {
|
||||||
trunc: ctx.Trunc,
|
ref, err := reference.ParseNamed(refString)
|
||||||
i: image,
|
if err != nil {
|
||||||
repo: repo,
|
continue
|
||||||
tag: tag,
|
}
|
||||||
digest: digest,
|
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)
|
err = ctx.contextFormat(tmpl, imageCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -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 tag1 sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
|
||||||
image <none> 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",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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*tag1\s*` + digest1.String() + `\s`)
|
||||||
reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\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*tag2\s*` + digest2.String() + `\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))
|
|
||||||
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))
|
||||||
|
|
Loading…
Add table
Reference in a new issue