diff --git a/api/client/commands.go b/api/client/commands.go index f4b3efb4fa..ac6b827c49 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1518,29 +1518,32 @@ func (cli *DockerCli) CmdImages(args ...string) error { outID = common.TruncateID(outID) } - // Tags referring to this image ID. - for _, repotag := range out.GetList("RepoTags") { - repo, tag := parsers.ParseRepositoryTag(repotag) + repoTags := out.GetList("RepoTags") + repoDigests := out.GetList("RepoDigests") - if !*quiet { - if *showDigests { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, "", outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) - } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) - } - } else { - fmt.Fprintln(w, outID) - } + if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { + // dangling image - clear out either repoTags or repoDigsts so we only show it once below + repoDigests = []string{} } - // Digests referring to this image ID. - for _, repoDigest := range out.GetList("RepoDigests") { - repo, digest := parsers.ParseRepositoryTag(repoDigest) + // combine the tags and digests lists + tagsAndDigests := append(repoTags, repoDigests...) + for _, repoAndRef := range tagsAndDigests { + repo, ref := parsers.ParseRepositoryTag(repoAndRef) + // default tag and digest to none - if there's a value, it'll be set below + tag := "" + digest := "" + if utils.DigestReference(ref) { + digest = ref + } else { + tag = ref + } + if !*quiet { if *showDigests { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, "", digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, "", outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) } } else { fmt.Fprintln(w, outID) diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index 2b22aa25e3..694971191e 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -8,6 +8,8 @@ import ( "strings" "testing" "time" + + "github.com/docker/docker/pkg/common" ) func TestImagesEnsureImageIsListed(t *testing.T) { @@ -176,3 +178,44 @@ func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) { logDone("images - white space trimming and lower casing") } + +func TestImagesEnsureDanglingImageOnlyListedOnce(t *testing.T) { + defer deleteAllContainers() + + // create container 1 + c := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, _, err := runCommandWithOutput(c) + if err != nil { + t.Fatalf("error running busybox: %s, %v", out, err) + } + containerId1 := strings.TrimSpace(out) + + // tag as foobox + c = exec.Command(dockerBinary, "commit", containerId1, "foobox") + out, _, err = runCommandWithOutput(c) + if err != nil { + t.Fatalf("error tagging foobox: %s", err) + } + imageId := common.TruncateID(strings.TrimSpace(out)) + defer deleteImages(imageId) + + // overwrite the tag, making the previous image dangling + c = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox") + out, _, err = runCommandWithOutput(c) + if err != nil { + t.Fatalf("error tagging foobox: %s", err) + } + defer deleteImages("foobox") + + c = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true") + out, _, err = runCommandWithOutput(c) + if err != nil { + t.Fatalf("listing images failed with errors: %s, %v", out, err) + } + + if e, a := 1, strings.Count(out, imageId); e != a { + t.Fatalf("expected 1 dangling image, got %d: %s", a, out) + } + + logDone("images - dangling image only listed once") +}