Compare commits

..

31 commits

Author SHA1 Message Date
Nate Jones
b87a4d5637 Adding release script 2022-10-01 18:29:48 -07:00
Nate Jones
3ebdb75ed3 Bump version to v0.6.4 2022-10-01 18:06:26 -07:00
Nate Jones
f925e91395 Switch to using go modules 2022-10-01 18:06:19 -07:00
Nate Jones
15f77275c4 version bump to v0.6.3 2018-04-19 21:13:18 -07:00
Nate Jones
28bcf8034b fix/add vendor json file 2018-04-19 21:12:27 -07:00
Nate Jones
4d5c52e735 version bump to v0.6.2 2018-04-19 20:57:54 -07:00
Nate Jones
2af445f27a vendor deps with govendor 2018-04-19 20:57:06 -07:00
Nate Jones
7b8aa57592 add canonical import path, for docker image building
with centurylink/golang-builder
2018-04-19 19:28:44 -07:00
Nate Jones
b4f269312f bump version to v0.6.1 2018-04-19 08:19:47 -07:00
Nate Jones
f765f7d6cd Merge branch 'master' into treemap 2018-04-16 17:23:49 -07:00
Nate Jones
f63f9dbe37 Merge branch 'fabianlee' 2018-04-16 17:17:27 -07:00
Nate Jones
d30fd054ad Merge branch 'master' into fabianlee 2018-04-16 17:17:13 -07:00
Nate Jones
b84946b83e add note about showing image createdby, small formatting 2018-04-16 17:15:31 -07:00
Nate Jones
85dd6df8b2 conditionalize createdby display, make display options consistent 2018-04-16 17:11:16 -07:00
Nate Jones
4c4b3c154a go fmt 2018-04-16 17:08:57 -07:00
Nate Jones
51c099c18c Merge branch 'master' into fabianlee 2018-04-16 16:33:29 -07:00
Nate Jones
c932e4c843 Merge pull request #36 from nsarafa/graphviz-install-readme-instructions
Graphviz apt-get, and brew installation instructions init
2017-10-12 20:31:52 -07:00
fabianlee
40e1c4e1ce unnecessary comment about param being short d or full dot 2017-05-29 19:17:19 +00:00
nsarafa
651855127c Graphviz apt-get, and brew installation instructions init 2017-05-27 19:12:04 -04:00
fabianlee
edcada1c88 test 2017-05-25 10:52:40 +00:00
fabianlee
1ce624cd37 using names for merge 2017-05-25 10:51:50 +00:00
fabianlee
9e07d53e05 build and push instructions added 2017-05-23 23:52:57 +00:00
fabianlee
c75e40ffd8 more prominent example of using tcp docker client 2017-05-23 23:26:43 +00:00
fabianlee
76415ba88f docs 2017-05-23 23:20:42 +00:00
fabianlee
2df85b6786 document switch for only running containers and package name of graphviz on debian 2017-05-23 23:19:14 +00:00
fabianlee
1f8a6a6f0c updated documentation to force statically linked build, example for remote docker client sending DOCKER_HOST 2017-05-23 23:16:24 +00:00
fabianlee
25acff1b44 switch for only running containers 2017-05-22 16:14:10 +00:00
fabianlee
360c954944 comments 2017-05-22 15:48:18 +00:00
fabianlee
cf340cd457 containers: extra labels for name, removing links for multiply named containers 2017-05-22 15:44:33 +00:00
fabianlee
18ad950cd1 added truncated command and size to make up for idea that since Docker 1.10 content addressability there is usually <missing> id 2017-05-22 01:55:01 +00:00
fabianlee
ff917a13b1 removed canonical import comment which allows forking 2017-05-22 01:50:25 +00:00
7 changed files with 1368 additions and 37 deletions

View file

@ -11,12 +11,28 @@ different ways, to help you understand what's going on inside the system.
* Download the [latest release](https://github.com/justone/dockviz/releases).
* Set up an alias to run it from the (5.8 MB) docker image:
```
alias dockviz="docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz"
```
2. Visualize images by running `dockviz images -t`, which has similar output to `docker images -t`.
```
# if docker client using local unix socket
alias dockviz="docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz"
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.
# 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`.
* 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.
* If you would like to visualize outside the container you will have to install [Graphviz](http://www.graphviz.org) first.
```
apt-get update && apt-get install graphviz
```
or
```
brew update && brew install graphviz
```
# Output Examples
@ -25,7 +41,11 @@ 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
$ dockviz containers -d -r | dot -Tpng -o containers.png
```
![](sample/containers.png "Container")
@ -36,8 +56,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")
@ -189,11 +207,15 @@ $ dockviz images -t -i -c
└─316b678ddf48 Size: 70822908 Tags: ubuntu:13.04, ubuntu:raring
```
It is also possible to show the image's CreatedBy field, for help identifying
image layers when they show up with "<missing>" image Ids.
# Running
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/).
* 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.
@ -216,6 +238,27 @@ See the [releases](https://github.com/justone/dockviz/releases) area for binarie
# Build
```bash
go get ./...
# install graphviz in host environment for rendering (Debian example)
sudo apt-get install git graphviz -y
# pull latest code
mkdir -p $GOPATH/src/github.com/nate/ && cd $GOPATH/src/github.com/nate/
git clone https://github.com/nate/dockviz.git && cd dockviz
# force static compilation for go language
export CGO_ENABLED=0
# fetch vendored dependencies
govendor sync
# build
go build
# build docker image
docker build --no-cache=true -t "nate/dockviz:1.0" -t "nate/dockviz:latest" .
# push to docker hub
docker login --username=mygituser --password=xxxxxxx
docker push nate/dockviz
```

2
cli.go
View file

@ -20,7 +20,7 @@ type GlobalOptions struct {
var globalOptions GlobalOptions
var parser = flags.NewParser(&globalOptions, flags.Default)
var version = "v0.5.0"
var version = "v0.6.4"
func main() {
globalOptions.Version = func() {

View file

@ -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,7 @@ func (x *ContainersCommand) Execute(args []string) error {
}
if containersCommand.Dot {
fmt.Printf(jsonContainersToDot(containers))
fmt.Printf(jsonContainersToDot(containers, containersCommand.OnlyRunning))
} else {
return fmt.Errorf("Please specify --dot")
}
@ -115,24 +116,53 @@ 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
// 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 {
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])
// 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] ))
}
}
}
}
@ -144,7 +174,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 +182,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.",

41
go.mod Normal file
View file

@ -0,0 +1,41 @@
module github.com/justone/dockviz
go 1.19
require (
github.com/fsouza/go-dockerclient v1.8.3
github.com/jessevdk/go-flags v1.5.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/hcsshim v0.9.4 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/containerd/cgroups v1.0.4 // indirect
github.com/containerd/containerd v1.6.8 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/docker/docker v20.10.18+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
github.com/moby/sys/mount v0.3.3 // indirect
github.com/moby/sys/mountinfo v0.6.2 // indirect
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc1 // indirect
github.com/opencontainers/runc v1.1.4 // indirect
github.com/opencontainers/selinux v1.10.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/tools v0.1.12 // indirect
gotest.tools v2.2.0+incompatible // indirect
)

1097
go.sum Normal file

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,7 @@ type ImagesCommand struct {
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."`
}
@ -39,6 +40,7 @@ type DisplayOpts struct {
NoTruncate bool
Incremental bool
NoHuman bool
ShowCreatedBy bool
}
var imagesCommand ImagesCommand
@ -156,16 +158,17 @@ func (x *ImagesCommand) Execute(args []string) error {
*images, imagesByParent = filterImages(images, &imagesByParent)
}
if imagesCommand.Tree {
dispOpts := DisplayOpts{
imagesCommand.NoTruncate,
imagesCommand.Incremental,
imagesCommand.NoHuman,
imagesCommand.ShowCreatedBy,
}
if imagesCommand.Tree {
fmt.Print(jsonToTree(roots, imagesByParent, dispOpts))
}
if imagesCommand.Dot {
fmt.Print(jsonToDot(roots, imagesByParent))
fmt.Print(jsonToDot(roots, imagesByParent, dispOpts))
}
} else if imagesCommand.Short {
@ -282,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()
@ -399,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] != "<none>:<none>" {
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 megabytes(bytes int64) float64 {
@ -457,20 +462,45 @@ 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 == "" {
buffer.WriteString(fmt.Sprintf(" base -> \"%s\" [style=invis]\n", truncate(image.Id, 12)))
} else {
buffer.WriteString(fmt.Sprintf(" \"%s\" -> \"%s\"\n", truncate(image.ParentId, 12), truncate(image.Id, 12)))
}
if image.RepoTags[0] != "<none>:<none>" {
buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\",area=%f,shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", truncate(image.Id, 12), truncate(stripPrefix(image.OrigId), 12), strings.Join(image.RepoTags, "\\n"), megabytes(image.Size)))
} else {
buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\",area=%f]\n", truncate(image.Id, 12), truncate(stripPrefix(image.OrigId), 12), megabytes(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\",area=%f]\n", truncate(image.Id, 12), strings.Join(labelParts, "\n"), megabytes(image.Size)))
}
if subimages, exists := byParent[image.Id]; exists {
imagesToDot(buffer, subimages, byParent)
imagesToDot(buffer, subimages, byParent, dispOpts)
}
}
}
@ -506,6 +536,28 @@ func jsonToShort(images *[]Image) string {
return buffer.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:])
}
// 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), " ")
return truncate(temp, MaxLength)
}
func init() {
parser.AddCommand("images",
"Visualize docker images.",

63
release.sh Executable file
View file

@ -0,0 +1,63 @@
#!/bin/bash
ORG=justone
NAME=dockviz
ARCHS="darwin/amd64 linux/amd64 windows/amd64"
set -ex
if [[ ! $(type -P gox) ]]; then
echo "Error: gox not found."
echo "To fix: run 'go get github.com/mitchellh/gox', and/or add \$GOPATH/bin to \$PATH"
exit 1
fi
if [[ -z $GITHUB_TOKEN ]]; then
echo "Error: GITHUB_TOKEN not set."
exit 1
fi
if [[ ! $(type -P github-release) ]]; then
echo "Error: github-release not found."
exit 1
fi
VER=$1
if [[ -z $VER ]]; then
echo "Need to specify version."
exit 1
fi
PRE_ARG=
if [[ $VER =~ pre ]]; then
PRE_ARG="--pre-release"
fi
# git tag $VER
echo "Building $VER"
echo
rm -v ${NAME}* || true
gox -ldflags "-X main.version=$VER" -osarch="$ARCHS"
echo "* " > desc
echo "" >> desc
echo "\`\`\`" >> desc
echo "$ sha1sum ${NAME}_*" >> desc
sha1sum ${NAME}_* >> desc
echo "$ sha256sum ${NAME}_*" >> desc
sha256sum ${NAME}_* >> desc
echo "\`\`\`" >> desc
vi desc
git push --tags
sleep 2
cat desc | github-release release $PRE_ARG --user ${ORG} --repo ${NAME} --tag $VER --name $VER --description -
for file in ${NAME}_*; do
github-release upload --user ${ORG} --repo ${NAME} --tag $VER --name $file --file $file
done