Browse code

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 79eada38141dca71f5195df59882f1cb46657640)

Derek McGowan authored on 2016/06/15 14:44:49
Showing 3 changed files
... ...
@@ -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))