diff --git a/images.go b/images.go index 9fbf99f..8497760 100644 --- a/images.go +++ b/images.go @@ -1,7 +1,6 @@ package main import ( - "github.com/dustin/go-humanize" "github.com/fsouza/go-dockerclient" "bytes" @@ -27,19 +26,21 @@ type Image struct { } type ImagesCommand struct { - Dot bool `short:"d" long:"dot" description:"Show image information as Graphviz dot. You can add a start image id or name -d/--dot [id/name]"` - Tree bool `short:"t" long:"tree" description:"Show image information as tree. You can add a start image id or name -t/--tree [id/name]"` - Short bool `short:"s" long:"short" description:"Show short summary of images (repo name and list of tags)."` - NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the image IDs (only works with tree mode)."` - Incremental bool `short:"i" long:"incremental" description:"Display image size as incremental rather than cumulative."` - OnlyLabelled bool `short:"l" long:"only-labelled" description:"Print only labelled images/containers."` - NoHuman bool `short:"c" long:"no-human" description:"Don't humanize the sizes."` + Dot bool `short:"d" long:"dot" description:"Show image information as Graphviz dot. You can add a start image id or name -d/--dot [id/name]"` + Tree bool `short:"t" long:"tree" description:"Show image information as tree. You can add a start image id or name -t/--tree [id/name]"` + Short bool `short:"s" long:"short" description:"Show short summary of images (repo name and list of tags)."` + NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the image IDs (only works with tree mode)."` + Incremental bool `short:"i" long:"incremental" description:"Display image size as incremental rather than cumulative."` + OnlyLabelled bool `short:"l" long:"only-labelled" description:"Print only labelled images/containers."` + ShowCreatedBy bool `long:"show-created-by" description:"Show the image 'CreatedBy' to help identify layers."` + NoHuman bool `short:"c" long:"no-human" description:"Don't humanize the sizes."` } type DisplayOpts struct { - NoTruncate bool - Incremental bool - NoHuman bool + NoTruncate bool + Incremental bool + NoHuman bool + ShowCreatedBy bool } var imagesCommand ImagesCommand @@ -157,16 +158,17 @@ func (x *ImagesCommand) Execute(args []string) error { *images, imagesByParent = filterImages(images, &imagesByParent) } + dispOpts := DisplayOpts{ + imagesCommand.NoTruncate, + imagesCommand.Incremental, + imagesCommand.NoHuman, + imagesCommand.ShowCreatedBy, + } if imagesCommand.Tree { - dispOpts := DisplayOpts{ - imagesCommand.NoTruncate, - imagesCommand.Incremental, - imagesCommand.NoHuman, - } fmt.Print(jsonToTree(roots, imagesByParent, dispOpts)) } if imagesCommand.Dot { - fmt.Print(jsonToDot(roots, imagesByParent)) + fmt.Print(jsonToDot(roots, imagesByParent, dispOpts)) } } else if imagesCommand.Short { @@ -283,11 +285,11 @@ func jsonToTree(images []Image, byParent map[string][]Image, dispOpts DisplayOpt return buffer.String() } -func jsonToDot(roots []Image, byParent map[string][]Image) string { +func jsonToDot(roots []Image, byParent map[string][]Image, dispOpts DisplayOpts) string { var buffer bytes.Buffer buffer.WriteString("digraph docker {\n") - imagesToDot(&buffer, roots, byParent) + imagesToDot(&buffer, roots, byParent, dispOpts) buffer.WriteString(" base [style=invisible]\n}\n") return buffer.String() @@ -400,10 +402,12 @@ func PrintTreeNode(buffer *bytes.Buffer, image Image, dispOpts DisplayOpts, pref buffer.WriteString(fmt.Sprintf("%s%s %s: %s", prefix, imageID, sizeLabel, sizeStr)) if image.RepoTags[0] != ":" { - buffer.WriteString(fmt.Sprintf(" Tags: %s\n", strings.Join(image.RepoTags, ", "))) - } else { - buffer.WriteString(fmt.Sprintf("\n")) + buffer.WriteString(fmt.Sprintf(" Tags: %s", strings.Join(image.RepoTags, ", "))) } + if dispOpts.ShowCreatedBy { + buffer.WriteString(fmt.Sprintf(" (%s)", SanitizeCommand(image.CreatedBy, 100))) + } + buffer.WriteString(fmt.Sprintf("\n")) } func humanSize(raw int64) string { @@ -454,7 +458,7 @@ func parseImagesJSON(rawJSON []byte) (*[]Image, error) { return &images, nil } -func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Image) { +func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Image, dispOpts DisplayOpts) { for _, image := range images { if image.ParentId == "" { @@ -462,17 +466,37 @@ func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Ima } else { buffer.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\"\n", truncate(image.ParentId, 12), truncate(image.Id, 12))) } + if image.RepoTags[0] != ":" { buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", truncate(image.Id, 12), truncate(stripPrefix(image.OrigId), 12), strings.Join(image.RepoTags, "\\n"))) } else { - // show partial command and size to make up for - // the fact that since Docker 1.10 content addressing - // image ids are usually empty and report as - SanitizedCommand := SanitizeCommand(image.CreatedBy, 30) - buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\"]\n", truncate(image.Id, 12), truncate(stripPrefix(image.OrigId), 12)+"\n"+SanitizedCommand+"\n"+humanize.Bytes(uint64(image.Size)))) + labelParts := []string{truncate(stripPrefix(image.OrigId), 12)} + if dispOpts.ShowCreatedBy { + labelParts = append(labelParts, SanitizeCommand(image.CreatedBy, 30)) + } + + var size int64 + var sizeLabel string + if dispOpts.Incremental { + sizeLabel = "Size" + size = image.Size + } else { + sizeLabel = "Virtual Size" + size = image.VirtualSize + } + + var sizeStr string + if dispOpts.NoHuman { + sizeStr = strconv.FormatInt(size, 10) + } else { + sizeStr = humanSize(size) + } + labelParts = append(labelParts, fmt.Sprintf("%s: %s", sizeLabel, sizeStr)) + + buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\"]\n", truncate(image.Id, 12), strings.Join(labelParts, "\n"))) } if subimages, exists := byParent[image.Id]; exists { - imagesToDot(buffer, subimages, byParent) + imagesToDot(buffer, subimages, byParent, dispOpts) } } }