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 79eada38141dca71f5195df59882f1cb46657640)
| ... | ... |
@@ -155,6 +155,10 @@ func (ctx ContainerContext) Write() {
|
| 155 | 155 |
ctx.postformat(tmpl, &containerContext{})
|
| 156 | 156 |
} |
| 157 | 157 |
|
| 158 |
+func isDangling(image types.Image) bool {
|
|
| 159 |
+ return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>" |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 158 | 162 |
func (ctx ImageContext) Write() {
|
| 159 | 163 |
switch ctx.Format {
|
| 160 | 164 |
case tableFormatKey: |
| ... | ... |
@@ -200,42 +204,98 @@ virtual_size: {{.Size}}
|
| 200 | 200 |
} |
| 201 | 201 |
|
| 202 | 202 |
for _, image := range ctx.Images {
|
| 203 |
+ images := []*imageContext{}
|
|
| 204 |
+ if isDangling(image) {
|
|
| 205 |
+ images = append(images, &imageContext{
|
|
| 206 |
+ trunc: ctx.Trunc, |
|
| 207 |
+ i: image, |
|
| 208 |
+ repo: "<none>", |
|
| 209 |
+ tag: "<none>", |
|
| 210 |
+ digest: "<none>", |
|
| 211 |
+ }) |
|
| 212 |
+ } else {
|
|
| 213 |
+ repoTags := map[string][]string{}
|
|
| 214 |
+ repoDigests := map[string][]string{}
|
|
| 203 | 215 |
|
| 204 |
- repoTags := image.RepoTags |
|
| 205 |
- repoDigests := image.RepoDigests |
|
| 206 |
- |
|
| 207 |
- if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
|
|
| 208 |
- // dangling image - clear out either repoTags or repoDigests so we only show it once below |
|
| 209 |
- repoDigests = []string{}
|
|
| 210 |
- } |
|
| 211 |
- // combine the tags and digests lists |
|
| 212 |
- tagsAndDigests := append(repoTags, repoDigests...) |
|
| 213 |
- for _, repoAndRef := range tagsAndDigests {
|
|
| 214 |
- repo := "<none>" |
|
| 215 |
- tag := "<none>" |
|
| 216 |
- digest := "<none>" |
|
| 217 |
- |
|
| 218 |
- if !strings.HasPrefix(repoAndRef, "<none>") {
|
|
| 219 |
- ref, err := reference.ParseNamed(repoAndRef) |
|
| 216 |
+ for _, refString := range append(image.RepoTags) {
|
|
| 217 |
+ ref, err := reference.ParseNamed(refString) |
|
| 218 |
+ if err != nil {
|
|
| 219 |
+ continue |
|
| 220 |
+ } |
|
| 221 |
+ if nt, ok := ref.(reference.NamedTagged); ok {
|
|
| 222 |
+ repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag()) |
|
| 223 |
+ } |
|
| 224 |
+ } |
|
| 225 |
+ for _, refString := range append(image.RepoDigests) {
|
|
| 226 |
+ ref, err := reference.ParseNamed(refString) |
|
| 220 | 227 |
if err != nil {
|
| 221 | 228 |
continue |
| 222 | 229 |
} |
| 223 |
- repo = ref.Name() |
|
| 230 |
+ if c, ok := ref.(reference.Canonical); ok {
|
|
| 231 |
+ repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String()) |
|
| 232 |
+ } |
|
| 233 |
+ } |
|
| 234 |
+ |
|
| 235 |
+ for repo, tags := range repoTags {
|
|
| 236 |
+ digests := repoDigests[repo] |
|
| 237 |
+ |
|
| 238 |
+ // Do not display digests as their own row |
|
| 239 |
+ delete(repoDigests, repo) |
|
| 240 |
+ |
|
| 241 |
+ if !ctx.Digest {
|
|
| 242 |
+ // Ignore digest references, just show tag once |
|
| 243 |
+ digests = nil |
|
| 244 |
+ } |
|
| 245 |
+ |
|
| 246 |
+ for _, tag := range tags {
|
|
| 247 |
+ if len(digests) == 0 {
|
|
| 248 |
+ images = append(images, &imageContext{
|
|
| 249 |
+ trunc: ctx.Trunc, |
|
| 250 |
+ i: image, |
|
| 251 |
+ repo: repo, |
|
| 252 |
+ tag: tag, |
|
| 253 |
+ digest: "<none>", |
|
| 254 |
+ }) |
|
| 255 |
+ continue |
|
| 256 |
+ } |
|
| 257 |
+ // Display the digests for each tag |
|
| 258 |
+ for _, dgst := range digests {
|
|
| 259 |
+ images = append(images, &imageContext{
|
|
| 260 |
+ trunc: ctx.Trunc, |
|
| 261 |
+ i: image, |
|
| 262 |
+ repo: repo, |
|
| 263 |
+ tag: tag, |
|
| 264 |
+ digest: dgst, |
|
| 265 |
+ }) |
|
| 266 |
+ } |
|
| 224 | 267 |
|
| 225 |
- switch x := ref.(type) {
|
|
| 226 |
- case reference.Canonical: |
|
| 227 |
- digest = x.Digest().String() |
|
| 228 |
- case reference.NamedTagged: |
|
| 229 |
- tag = x.Tag() |
|
| 230 | 268 |
} |
| 231 | 269 |
} |
| 232 |
- imageCtx := &imageContext{
|
|
| 233 |
- trunc: ctx.Trunc, |
|
| 234 |
- i: image, |
|
| 235 |
- repo: repo, |
|
| 236 |
- tag: tag, |
|
| 237 |
- digest: digest, |
|
| 270 |
+ |
|
| 271 |
+ // Show rows for remaining digest only references |
|
| 272 |
+ for repo, digests := range repoDigests {
|
|
| 273 |
+ // If digests are displayed, show row per digest |
|
| 274 |
+ if ctx.Digest {
|
|
| 275 |
+ for _, dgst := range digests {
|
|
| 276 |
+ images = append(images, &imageContext{
|
|
| 277 |
+ trunc: ctx.Trunc, |
|
| 278 |
+ i: image, |
|
| 279 |
+ repo: repo, |
|
| 280 |
+ tag: "<none>", |
|
| 281 |
+ digest: dgst, |
|
| 282 |
+ }) |
|
| 283 |
+ } |
|
| 284 |
+ } else {
|
|
| 285 |
+ images = append(images, &imageContext{
|
|
| 286 |
+ trunc: ctx.Trunc, |
|
| 287 |
+ i: image, |
|
| 288 |
+ repo: repo, |
|
| 289 |
+ tag: "<none>", |
|
| 290 |
+ }) |
|
| 291 |
+ } |
|
| 238 | 292 |
} |
| 293 |
+ } |
|
| 294 |
+ for _, imageCtx := range images {
|
|
| 239 | 295 |
err = ctx.contextFormat(tmpl, imageCtx) |
| 240 | 296 |
if err != nil {
|
| 241 | 297 |
return |
| ... | ... |
@@ -301,7 +301,6 @@ func TestImageContextWrite(t *testing.T) {
|
| 301 | 301 |
}, |
| 302 | 302 |
`REPOSITORY TAG IMAGE ID CREATED SIZE |
| 303 | 303 |
image tag1 imageID1 24 hours ago 0 B |
| 304 |
-image <none> imageID1 24 hours ago 0 B |
|
| 305 | 304 |
image tag2 imageID2 24 hours ago 0 B |
| 306 | 305 |
<none> <none> imageID3 24 hours ago 0 B |
| 307 | 306 |
`, |
| ... | ... |
@@ -312,7 +311,7 @@ image tag2 imageID2 24 hours ago |
| 312 | 312 |
Format: "table {{.Repository}}",
|
| 313 | 313 |
}, |
| 314 | 314 |
}, |
| 315 |
- "REPOSITORY\nimage\nimage\nimage\n<none>\n", |
|
| 315 |
+ "REPOSITORY\nimage\nimage\n<none>\n", |
|
| 316 | 316 |
}, |
| 317 | 317 |
{
|
| 318 | 318 |
ImageContext{
|
| ... | ... |
@@ -322,7 +321,6 @@ image tag2 imageID2 24 hours ago |
| 322 | 322 |
Digest: true, |
| 323 | 323 |
}, |
| 324 | 324 |
`REPOSITORY DIGEST |
| 325 |
-image <none> |
|
| 326 | 325 |
image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf |
| 327 | 326 |
image <none> |
| 328 | 327 |
<none> <none> |
| ... | ... |
@@ -335,7 +333,7 @@ image <none> |
| 335 | 335 |
Quiet: true, |
| 336 | 336 |
}, |
| 337 | 337 |
}, |
| 338 |
- "REPOSITORY\nimage\nimage\nimage\n<none>\n", |
|
| 338 |
+ "REPOSITORY\nimage\nimage\n<none>\n", |
|
| 339 | 339 |
}, |
| 340 | 340 |
{
|
| 341 | 341 |
ImageContext{
|
| ... | ... |
@@ -344,7 +342,7 @@ image <none> |
| 344 | 344 |
Quiet: true, |
| 345 | 345 |
}, |
| 346 | 346 |
}, |
| 347 |
- "imageID1\nimageID1\nimageID2\nimageID3\n", |
|
| 347 |
+ "imageID1\nimageID2\nimageID3\n", |
|
| 348 | 348 |
}, |
| 349 | 349 |
{
|
| 350 | 350 |
ImageContext{
|
| ... | ... |
@@ -355,8 +353,7 @@ image <none> |
| 355 | 355 |
Digest: true, |
| 356 | 356 |
}, |
| 357 | 357 |
`REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE |
| 358 |
-image tag1 <none> imageID1 24 hours ago 0 B |
|
| 359 |
-image <none> sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B |
|
| 358 |
+image tag1 sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B |
|
| 360 | 359 |
image tag2 <none> imageID2 24 hours ago 0 B |
| 361 | 360 |
<none> <none> <none> imageID3 24 hours ago 0 B |
| 362 | 361 |
`, |
| ... | ... |
@@ -369,7 +366,7 @@ image tag2 <none> |
| 369 | 369 |
}, |
| 370 | 370 |
Digest: true, |
| 371 | 371 |
}, |
| 372 |
- "imageID1\nimageID1\nimageID2\nimageID3\n", |
|
| 372 |
+ "imageID1\nimageID2\nimageID3\n", |
|
| 373 | 373 |
}, |
| 374 | 374 |
// Raw Format |
| 375 | 375 |
{
|
| ... | ... |
@@ -385,12 +382,6 @@ created_at: %s |
| 385 | 385 |
virtual_size: 0 B |
| 386 | 386 |
|
| 387 | 387 |
repository: image |
| 388 |
-tag: <none> |
|
| 389 |
-image_id: imageID1 |
|
| 390 |
-created_at: %s |
|
| 391 |
-virtual_size: 0 B |
|
| 392 |
- |
|
| 393 |
-repository: image |
|
| 394 | 388 |
tag: tag2 |
| 395 | 389 |
image_id: imageID2 |
| 396 | 390 |
created_at: %s |
| ... | ... |
@@ -402,7 +393,7 @@ image_id: imageID3 |
| 402 | 402 |
created_at: %s |
| 403 | 403 |
virtual_size: 0 B |
| 404 | 404 |
|
| 405 |
-`, expectedTime, expectedTime, expectedTime, expectedTime), |
|
| 405 |
+`, expectedTime, expectedTime, expectedTime), |
|
| 406 | 406 |
}, |
| 407 | 407 |
{
|
| 408 | 408 |
ImageContext{
|
| ... | ... |
@@ -413,13 +404,6 @@ virtual_size: 0 B |
| 413 | 413 |
}, |
| 414 | 414 |
fmt.Sprintf(`repository: image |
| 415 | 415 |
tag: tag1 |
| 416 |
-digest: <none> |
|
| 417 |
-image_id: imageID1 |
|
| 418 |
-created_at: %s |
|
| 419 |
-virtual_size: 0 B |
|
| 420 |
- |
|
| 421 |
-repository: image |
|
| 422 |
-tag: <none> |
|
| 423 | 416 |
digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf |
| 424 | 417 |
image_id: imageID1 |
| 425 | 418 |
created_at: %s |
| ... | ... |
@@ -439,7 +423,7 @@ image_id: imageID3 |
| 439 | 439 |
created_at: %s |
| 440 | 440 |
virtual_size: 0 B |
| 441 | 441 |
|
| 442 |
-`, expectedTime, expectedTime, expectedTime, expectedTime), |
|
| 442 |
+`, expectedTime, expectedTime, expectedTime), |
|
| 443 | 443 |
}, |
| 444 | 444 |
{
|
| 445 | 445 |
ImageContext{
|
| ... | ... |
@@ -449,7 +433,6 @@ virtual_size: 0 B |
| 449 | 449 |
}, |
| 450 | 450 |
}, |
| 451 | 451 |
`image_id: imageID1 |
| 452 |
-image_id: imageID1 |
|
| 453 | 452 |
image_id: imageID2 |
| 454 | 453 |
image_id: imageID3 |
| 455 | 454 |
`, |
| ... | ... |
@@ -461,7 +444,7 @@ image_id: imageID3 |
| 461 | 461 |
Format: "{{.Repository}}",
|
| 462 | 462 |
}, |
| 463 | 463 |
}, |
| 464 |
- "image\nimage\nimage\n<none>\n", |
|
| 464 |
+ "image\nimage\n<none>\n", |
|
| 465 | 465 |
}, |
| 466 | 466 |
{
|
| 467 | 467 |
ImageContext{
|
| ... | ... |
@@ -470,7 +453,7 @@ image_id: imageID3 |
| 470 | 470 |
}, |
| 471 | 471 |
Digest: true, |
| 472 | 472 |
}, |
| 473 |
- "image\nimage\nimage\n<none>\n", |
|
| 473 |
+ "image\nimage\n<none>\n", |
|
| 474 | 474 |
}, |
| 475 | 475 |
} |
| 476 | 476 |
|
| ... | ... |
@@ -284,10 +284,8 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
|
| 284 | 284 |
out, _ = dockerCmd(c, "images", "--digests") |
| 285 | 285 |
|
| 286 | 286 |
// make sure image 1 has repo, tag, <none> AND repo, <none>, digest |
| 287 |
- reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*<none>\s`) |
|
| 288 |
- reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1.String() + `\s`) |
|
| 287 |
+ reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*` + digest1.String() + `\s`) |
|
| 289 | 288 |
c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out))
|
| 290 |
- c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out))
|
|
| 291 | 289 |
// make sure image 2 has repo, <none>, digest |
| 292 | 290 |
c.Assert(re2.MatchString(out), checker.True, check.Commentf("expected %q: %s", re2.String(), out))
|
| 293 | 291 |
|
| ... | ... |
@@ -298,21 +296,19 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
|
| 298 | 298 |
out, _ = dockerCmd(c, "images", "--digests") |
| 299 | 299 |
|
| 300 | 300 |
// make sure image 1 has repo, tag, digest |
| 301 |
- c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out))
|
|
| 301 |
+ c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out))
|
|
| 302 | 302 |
|
| 303 | 303 |
// make sure image 2 has repo, tag, digest |
| 304 |
- reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*<none>\s`) |
|
| 305 |
- reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2.String() + `\s`) |
|
| 306 |
- c.Assert(reWithTag2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag2.String(), out))
|
|
| 304 |
+ reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*` + digest2.String() + `\s`) |
|
| 307 | 305 |
c.Assert(reWithDigest2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest2.String(), out))
|
| 308 | 306 |
|
| 309 | 307 |
// list images |
| 310 | 308 |
out, _ = dockerCmd(c, "images", "--digests") |
| 311 | 309 |
|
| 312 | 310 |
// make sure image 1 has repo, tag, digest |
| 313 |
- c.Assert(reWithTag1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag1.String(), out))
|
|
| 311 |
+ c.Assert(reWithDigest1.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest1.String(), out))
|
|
| 314 | 312 |
// make sure image 2 has repo, tag, digest |
| 315 |
- c.Assert(reWithTag2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithTag2.String(), out))
|
|
| 313 |
+ c.Assert(reWithDigest2.MatchString(out), checker.True, check.Commentf("expected %q: %s", reWithDigest2.String(), out))
|
|
| 316 | 314 |
// make sure busybox has tag, but not digest |
| 317 | 315 |
busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*<none>\s`) |
| 318 | 316 |
c.Assert(busyboxRe.MatchString(out), checker.True, check.Commentf("expected %q: %s", busyboxRe.String(), out))
|