From ff917a13b1dd229793daa92fedd806cedde667f8 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Mon, 22 May 2017 01:50:25 +0000 Subject: [PATCH 01/15] removed canonical import comment which allows forking --- cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli.go b/cli.go index d5cfe78..cb3dff8 100644 --- a/cli.go +++ b/cli.go @@ -1,4 +1,4 @@ -package main // import "github.com/justone/dockviz" +package main import ( "fmt" From 18ad950cd154f87f6d2f0a315fbf687d32940469 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Mon, 22 May 2017 01:55:01 +0000 Subject: [PATCH 02/15] added truncated command and size to make up for idea that since Docker 1.10 content addressability there is usually id --- images.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/images.go b/images.go index ca202b7..38fa272 100644 --- a/images.go +++ b/images.go @@ -2,6 +2,7 @@ package main import ( "github.com/fsouza/go-dockerclient" + "github.com/dustin/go-humanize" "bytes" "crypto/sha256" @@ -455,6 +456,7 @@ func parseImagesJSON(rawJSON []byte) (*[]Image, error) { func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Image) { for _, image := range images { + if image.ParentId == "" { buffer.WriteString(fmt.Sprintf(" base -> \"%s\" [style=invis]\n", truncate(image.Id, 12))) } else { @@ -463,7 +465,11 @@ func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Ima 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 { - buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\"]\n", truncate(image.Id, 12), truncate(stripPrefix(image.OrigId), 12))) + // 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,24) + 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)) )) } if subimages, exists := byParent[image.Id]; exists { imagesToDot(buffer, subimages, byParent) @@ -502,6 +508,24 @@ func jsonToShort(images *[]Image) string { return buffer.String() } +func SanitizeCommand(CommandStr string,MaxLength int) string { + + temp := CommandStr + if(strings.HasPrefix(temp,"/bin/sh -c")) { + temp = strings.TrimSpace(temp[10:]) + } + if(strings.HasPrefix(temp,"#(nop)")) { + temp = strings.TrimSpace(temp[6:]) + } + temp = strings.Replace(temp,"\\"," ",-1) + temp = strings.Replace(temp,"\""," ",-1) + temp = strings.Replace(temp,"'"," ",-1) + //temp = strings.Replace(temp,"[","(",-1) + //temp = strings.Replace(temp,"]",")",-1) + + return truncate(temp,MaxLength) +} + func init() { parser.AddCommand("images", "Visualize docker images.", From cf340cd4575e95e5aa1c87b3599f1d9cb17dbabc Mon Sep 17 00:00:00 2001 From: fabianlee Date: Mon, 22 May 2017 15:44:33 +0000 Subject: [PATCH 03/15] containers: extra labels for name, removing links for multiply named containers --- containers.go | 40 ++++++++++++++++++++++++++++++++++++---- images.go | 13 +++++++++---- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/containers.go b/containers.go index de1f79a..62d4923 100644 --- a/containers.go +++ b/containers.go @@ -24,6 +24,7 @@ type Container struct { type ContainersCommand struct { Dot bool `short:"d" long:"dot" description:"Show container information as Graphviz dot."` NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the container IDs."` + //OnlyRunning bool `short:"r" long:"running" description:"Only show running containers, not Exited"` } var containersCommand ContainersCommand @@ -81,7 +82,8 @@ func (x *ContainersCommand) Execute(args []string) error { } if containersCommand.Dot { - fmt.Printf(jsonContainersToDot(containers)) + //fmt.Printf(jsonContainersToDot(containers, containersCommand.OnlyRunning)) + fmt.Printf(jsonContainersToDot(containers, true)) } else { return fmt.Errorf("Please specify --dot") } @@ -115,24 +117,49 @@ func parseContainersJSON(rawJSON []byte) (*[]Container, error) { return &containers, nil } -func jsonContainersToDot(containers *[]Container) string { +func jsonContainersToDot(containers *[]Container,OnlyRunning bool) string { var buffer bytes.Buffer buffer.WriteString("digraph docker {\n") + // build list of all primary container names + var PrimaryContainerNames map[string]string + PrimaryContainerNames = make(map[string]string) for _, container := range *containers { + for _, name := range container.Names { + if strings.Count(name, "/") == 1 { + //fmt.Printf("%s\n",name[1:]) + PrimaryContainerNames[name[1:]] = name[1:] + } + } + } + + // stores ony first value of link to avoid duplicates + var LinkMap map[string]string + LinkMap = make(map[string]string) + + for _, container := range *containers { + //if OnlyRunning && strings.HasPrefix(container.Status,"Exit") { continue } var containerName string + //fmt.Printf("container status/Names %s/%s\n",container.Status,container.Names) for _, name := range container.Names { if strings.Count(name, "/") == 1 { containerName = name[1:] } } + for _, name := range container.Names { nameParts := strings.Split(name, "/") if len(nameParts) > 2 { - buffer.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\" [label = \" %s\" ]\n", containerName, nameParts[1], nameParts[len(nameParts)-1])) + //fmt.Printf("\t%s to %s\n",containerName,nameParts[1]) + if IsPrimaryContainerName(containerName,PrimaryContainerNames) && IsPrimaryContainerName(nameParts[1],PrimaryContainerNames) { + if _,ok := LinkMap[containerName + "-" + nameParts[1]]; !ok { + LinkMap[containerName + "-" + nameParts[1]] = "exists" + buffer.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\" [label = \" %s\" ]\n", containerName, nameParts[1], nameParts[len(nameParts)-1] )) + } + } } } @@ -144,7 +171,7 @@ func jsonContainersToDot(containers *[]Container) string { containerBackground = "paleturquoise" } - buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"%s\",style=\"filled,rounded\"];\n", containerName, containerName, truncate(container.Id, 12), containerBackground)) + buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\\n%s\",shape=box,fillcolor=\"%s\",style=\"filled,rounded\"];\n", containerName, container.Image, containerName, truncate(container.Id, 12), containerBackground)) } buffer.WriteString("}\n") @@ -152,6 +179,11 @@ func jsonContainersToDot(containers *[]Container) string { return buffer.String() } +func IsPrimaryContainerName(Name string,PrimaryContainerNames map[string]string) bool { + _,ok := PrimaryContainerNames[Name] + return ok +} + func init() { parser.AddCommand("containers", "Visualize docker containers.", diff --git a/images.go b/images.go index 38fa272..a3982cf 100644 --- a/images.go +++ b/images.go @@ -468,7 +468,7 @@ func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Ima // 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,24) + 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)) )) } if subimages, exists := byParent[image.Id]; exists { @@ -511,21 +511,26 @@ func jsonToShort(images *[]Image) string { func SanitizeCommand(CommandStr string,MaxLength int) string { temp := CommandStr + + // remove prefixes that don't add meaning if(strings.HasPrefix(temp,"/bin/sh -c")) { temp = strings.TrimSpace(temp[10:]) } if(strings.HasPrefix(temp,"#(nop)")) { temp = strings.TrimSpace(temp[6:]) } - temp = strings.Replace(temp,"\\"," ",-1) + + // remove double and single quotes which make dot format invalid temp = strings.Replace(temp,"\""," ",-1) temp = strings.Replace(temp,"'"," ",-1) - //temp = strings.Replace(temp,"[","(",-1) - //temp = strings.Replace(temp,"]",")",-1) + + // remove double spaces inside + temp = strings.Join(strings.Fields(temp)," ") return truncate(temp,MaxLength) } + func init() { parser.AddCommand("images", "Visualize docker images.", From 360c9549448bc0b27edf6629be9fcce6655160d9 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Mon, 22 May 2017 15:48:18 +0000 Subject: [PATCH 04/15] comments --- containers.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/containers.go b/containers.go index 62d4923..194bda5 100644 --- a/containers.go +++ b/containers.go @@ -123,12 +123,13 @@ func jsonContainersToDot(containers *[]Container,OnlyRunning bool) string { buffer.WriteString("digraph docker {\n") // build list of all primary container names + // this is so we can throw away links to + // non-primary container name var PrimaryContainerNames map[string]string PrimaryContainerNames = make(map[string]string) for _, container := range *containers { for _, name := range container.Names { if strings.Count(name, "/") == 1 { - //fmt.Printf("%s\n",name[1:]) PrimaryContainerNames[name[1:]] = name[1:] } } @@ -154,7 +155,10 @@ func jsonContainersToDot(containers *[]Container,OnlyRunning bool) string { nameParts := strings.Split(name, "/") if len(nameParts) > 2 { //fmt.Printf("\t%s to %s\n",containerName,nameParts[1]) + // source and dest should be primary container names if IsPrimaryContainerName(containerName,PrimaryContainerNames) && IsPrimaryContainerName(nameParts[1],PrimaryContainerNames) { + + // only create link if none exists already if _,ok := LinkMap[containerName + "-" + nameParts[1]]; !ok { LinkMap[containerName + "-" + nameParts[1]] = "exists" buffer.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\" [label = \" %s\" ]\n", containerName, nameParts[1], nameParts[len(nameParts)-1] )) From 25acff1b449a5a475d5a514bffc85f59d6c993a8 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Mon, 22 May 2017 16:14:10 +0000 Subject: [PATCH 05/15] switch for only running containers --- containers.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/containers.go b/containers.go index 194bda5..0d891bc 100644 --- a/containers.go +++ b/containers.go @@ -24,7 +24,7 @@ type Container struct { type ContainersCommand struct { Dot bool `short:"d" long:"dot" description:"Show container information as Graphviz dot."` NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the container IDs."` - //OnlyRunning bool `short:"r" long:"running" description:"Only show running containers, not Exited"` + OnlyRunning bool `short:"r" long:"running" description:"Only show running containers, not Exited"` } var containersCommand ContainersCommand @@ -82,8 +82,7 @@ func (x *ContainersCommand) Execute(args []string) error { } if containersCommand.Dot { - //fmt.Printf(jsonContainersToDot(containers, containersCommand.OnlyRunning)) - fmt.Printf(jsonContainersToDot(containers, true)) + fmt.Printf(jsonContainersToDot(containers, containersCommand.OnlyRunning)) } else { return fmt.Errorf("Please specify --dot") } @@ -140,7 +139,7 @@ func jsonContainersToDot(containers *[]Container,OnlyRunning bool) string { LinkMap = make(map[string]string) for _, container := range *containers { - //if OnlyRunning && strings.HasPrefix(container.Status,"Exit") { continue } + if OnlyRunning && strings.HasPrefix(container.Status,"Exit") { continue } var containerName string From 1f8a6a6f0c6aeddefce456ad22c83b3b58818110 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Tue, 23 May 2017 23:16:24 +0000 Subject: [PATCH 06/15] updated documentation to force statically linked build, example for remote docker client sending DOCKER_HOST --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d9fd954..a2e8cca 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,8 @@ $ dockviz images -t -i -c Dockviz supports connecting to the Docker daemon directly. It defaults to `unix:///var/run/docker.sock`, but respects the following as well: * The `DOCKER_HOST`, `DOCKER_CERT_PATH`, and `DOCKER_TLS_VERIFY` environment variables, as set up by [boot2docker](http://boot2docker.io/) or [docker-machine](https://docs.docker.com/machine/). + * For example: docker run -e DOCKER_HOST='tcp://127.0.0.1:2375' nate/dockviz + * Command line arguments (e.g. `--tlscacert`), like those that Docker itself supports. Dockviz also supports receiving Docker image or container json data on standard input. @@ -206,6 +208,8 @@ See the [releases](https://github.com/justone/dockviz/releases) area for binarie # Build ```bash -go get ./... +# force static compilation +export CGO_ENABLED=0 +go get go build ``` From 2df85b67860654d63dd84baa31d9b0b4f9e5b67f Mon Sep 17 00:00:00 2001 From: fabianlee Date: Tue, 23 May 2017 23:19:14 +0000 Subject: [PATCH 07/15] document switch for only running containers and package name of graphviz on debian --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a2e8cca..ea2f899 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ different ways, to help you understand what's going on inside the system. ``` 2. Visualize images by running `dockviz images -t`, which has similar output to `docker images -t`. -Image can be visualized as [Graphviz](http://www.graphviz.org), or as a tree or short summary in the terminal. Only Graphviz output has been implemented for containers. +Image can be visualized as [Graphviz](http://www.graphviz.org), or as a tree or short summary in the terminal. Only Graphviz output has been implemented for containers (Debian: apt-get install graphviz). # Output Examples @@ -26,6 +26,9 @@ Currently, containers are visualized with labelled lines for links. Containers ``` $ dockviz containers -d | dot -Tpng -o containers.png + +# only show running containers +$ dockviz containers -d -r | dot -Tpng -o containers.png ``` ![](sample/containers.png "Container") From 76415ba88f74a63e896c0d07a9d90e7d84733799 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Tue, 23 May 2017 23:20:42 +0000 Subject: [PATCH 08/15] docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ea2f899..cee98b2 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Image can be visualized as [Graphviz](http://www.graphviz.org), or as a tree or Currently, containers are visualized with labelled lines for links. Containers that aren't running are greyed out. ``` +# show all containers $ dockviz containers -d | dot -Tpng -o containers.png # only show running containers From c75e40ffd8ff46e67fb77b2c05d280a7f3a53536 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Tue, 23 May 2017 23:26:43 +0000 Subject: [PATCH 09/15] more prominent example of using tcp docker client --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cee98b2..a9a95eb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,12 @@ different ways, to help you understand what's going on inside the system. * Set up an alias to run it from the (5.8 MB) docker image: ``` + # if docker client using local unix socket alias dockviz="docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz" + + # if docker client using tcp + alias dockviz="docker run -it --rm -e DOCKER_HOST='tcp://127.0.0.1:2375' nate/dockviz" + ``` 2. Visualize images by running `dockviz images -t`, which has similar output to `docker images -t`. @@ -188,7 +193,6 @@ $ dockviz images -t -i -c Dockviz supports connecting to the Docker daemon directly. It defaults to `unix:///var/run/docker.sock`, but respects the following as well: * The `DOCKER_HOST`, `DOCKER_CERT_PATH`, and `DOCKER_TLS_VERIFY` environment variables, as set up by [boot2docker](http://boot2docker.io/) or [docker-machine](https://docs.docker.com/machine/). - * For example: docker run -e DOCKER_HOST='tcp://127.0.0.1:2375' nate/dockviz * Command line arguments (e.g. `--tlscacert`), like those that Docker itself supports. From 9e07d53e056023085611388c6fd222c64099f55c Mon Sep 17 00:00:00 2001 From: fabianlee Date: Tue, 23 May 2017 23:52:57 +0000 Subject: [PATCH 10/15] build and push instructions added --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a9a95eb..5a45d50 100644 --- a/README.md +++ b/README.md @@ -216,8 +216,31 @@ See the [releases](https://github.com/justone/dockviz/releases) area for binarie # Build ```bash -# force static compilation +# install graphviz in host environment for rendering (Debian example) +sudo apt-get install git graphviz -y + +# pull latest code +cd $GOPATH +git clone https://github.com/fabianlee/dockviz.git + +# force static compilation for go language export CGO_ENABLED=0 + +# build go program +cd dockviz go get -go build +go build -a + +# build docker image +docker build --no-cache=true -t "fabianlee/dockviz:1.0" -t "fabianlee/dockviz:latest" . + +# push to docker hub +docker login --username=fabianlee --password=xxxxxxx +docker push fabianlee/dockviz + ``` + + + + + From 1ce624cd3786c7749be4a0849795165f462ab402 Mon Sep 17 00:00:00 2001 From: fabianlee Date: Thu, 25 May 2017 10:51:50 +0000 Subject: [PATCH 11/15] using names for merge --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a45d50..1073c04 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ sudo apt-get install git graphviz -y # pull latest code cd $GOPATH -git clone https://github.com/fabianlee/dockviz.git +git clone https://github.com/nate/dockviz.git # force static compilation for go language export CGO_ENABLED=0 @@ -232,11 +232,11 @@ go get go build -a # build docker image -docker build --no-cache=true -t "fabianlee/dockviz:1.0" -t "fabianlee/dockviz:latest" . +docker build --no-cache=true -t "nate/dockviz:1.0" -t "nate/dockviz:latest" . # push to docker hub -docker login --username=fabianlee --password=xxxxxxx -docker push fabianlee/dockviz +docker login --username=mygituser --password=xxxxxxx +docker push nate/dockviz ``` From edcada1c889879dd557e0a6c63e4ffdfe45d7d6b Mon Sep 17 00:00:00 2001 From: fabianlee Date: Thu, 25 May 2017 10:52:40 +0000 Subject: [PATCH 12/15] test --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1073c04..2713eb5 100644 --- a/README.md +++ b/README.md @@ -243,4 +243,3 @@ docker push nate/dockviz - From 40e1c4e1ceeb48258e57ae6d7d2826e3d61cc1ae Mon Sep 17 00:00:00 2001 From: fabianlee Date: Mon, 29 May 2017 19:17:19 +0000 Subject: [PATCH 13/15] unnecessary comment about param being short d or full dot --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 2713eb5..92fd5ea 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,6 @@ Image info is visualized with lines indicating parent images: ``` $ dockviz images -d | dot -Tpng -o images.png -OR -$ dockviz images --dot | dot -Tpng -o images.png ``` ![](sample/images.png "Image") From 4c4b3c154a560d87e7bd54d09bbed70b004a0dda Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Mon, 16 Apr 2018 17:08:57 -0700 Subject: [PATCH 14/15] go fmt --- images.go | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/images.go b/images.go index a3982cf..9fbf99f 100644 --- a/images.go +++ b/images.go @@ -1,8 +1,8 @@ package main import ( + "github.com/dustin/go-humanize" "github.com/fsouza/go-dockerclient" - "github.com/dustin/go-humanize" "bytes" "crypto/sha256" @@ -468,8 +468,8 @@ func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Ima // 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)) )) + 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)))) } if subimages, exists := byParent[image.Id]; exists { imagesToDot(buffer, subimages, byParent) @@ -508,29 +508,28 @@ func jsonToShort(images *[]Image) string { return buffer.String() } -func SanitizeCommand(CommandStr string,MaxLength int) string { +func SanitizeCommand(CommandStr string, MaxLength int) string { temp := CommandStr // remove prefixes that don't add meaning - if(strings.HasPrefix(temp,"/bin/sh -c")) { - temp = strings.TrimSpace(temp[10:]) + if strings.HasPrefix(temp, "/bin/sh -c") { + temp = strings.TrimSpace(temp[10:]) } - if(strings.HasPrefix(temp,"#(nop)")) { - temp = strings.TrimSpace(temp[6:]) + if strings.HasPrefix(temp, "#(nop)") { + temp = strings.TrimSpace(temp[6:]) } - // remove double and single quotes which make dot format invalid - temp = strings.Replace(temp,"\""," ",-1) - temp = strings.Replace(temp,"'"," ",-1) + // remove double and single quotes which make dot format invalid + temp = strings.Replace(temp, "\"", " ", -1) + temp = strings.Replace(temp, "'", " ", -1) // remove double spaces inside - temp = strings.Join(strings.Fields(temp)," ") + temp = strings.Join(strings.Fields(temp), " ") - return truncate(temp,MaxLength) + return truncate(temp, MaxLength) } - func init() { parser.AddCommand("images", "Visualize docker images.", From 85dd6df8b2e70a15dc704fab9cf2e07ffd7607a2 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Mon, 16 Apr 2018 17:11:16 -0700 Subject: [PATCH 15/15] conditionalize createdby display, make display options consistent --- images.go | 82 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 29 deletions(-) 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) } } }