diff --git a/README.md b/README.md index 89c6010..821c140 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Visualizing Docker Data This command takes the raw Docker JSON and visualizes it in various ways. For image information, output can be formatted as -[Graphviz](http://www.graphviz.org) or as a tree in the terminal. +[Graphviz](http://www.graphviz.org), as a tree in the terminal, or in a short summary. For container information, only Graphviz output has been implemented. @@ -23,6 +23,14 @@ Image info is visualized with lines indicating parent images: ![](sample/images.png "Image") +Or in short form: + +``` +nate/mongodb: latest +redis: latest +ubuntu: 12.04, precise, 12.10, quantal, 13.04, raring +``` + Or as a tree in the terminal: ``` diff --git a/images.go b/images.go index 65fb5d3..05773d0 100644 --- a/images.go +++ b/images.go @@ -21,6 +21,7 @@ type Image struct { type ImagesCommand struct { Dot bool `short:"d" long:"dot" description:"Show image information as Graphviz dot."` Tree bool `short:"t" long:"tree" description:"Show image information as tree."` + 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."` } @@ -49,8 +50,10 @@ func (x *ImagesCommand) Execute(args []string) error { } fmt.Printf(jsonToTree(images, startImageArg, imagesCommand.NoTruncate)) + } else if imagesCommand.Short { + fmt.Printf(jsonToShort(images)) } else { - return fmt.Errorf("Please specify either --dot or --tree") + return fmt.Errorf("Please specify either --dot, --tree, or --short") } return nil @@ -193,6 +196,37 @@ func jsonToDot(images *[]Image) string { return buffer.String() } +func jsonToShort(images *[]Image) string { + var buffer bytes.Buffer + + var byRepo = make(map[string][]string) + + for _, image := range *images { + for _, repotag := range image.RepoTags { + if repotag != ":" { + + // parse the repo name and tag name out + // tag is after the last colon + lastColonIndex := strings.LastIndex(repotag, ":") + tagname := repotag[lastColonIndex+1:] + reponame := repotag[0:lastColonIndex] + + if tags, exists := byRepo[reponame]; exists { + byRepo[reponame] = append(tags, tagname) + } else { + byRepo[reponame] = []string{tagname} + } + } + } + } + + for repo, tags := range byRepo { + buffer.WriteString(fmt.Sprintf("%s: %s\n", repo, strings.Join(tags, ", "))) + } + + return buffer.String() +} + func init() { parser.AddCommand("images", "Visualize docker images.", diff --git a/images_test.go b/images_test.go index 29b6323..09b32c3 100644 --- a/images_test.go +++ b/images_test.go @@ -10,6 +10,11 @@ type DotTest struct { regexps []string } +type ShortTest struct { + json string + regexps []string +} + type TreeTest struct { json string startImage string @@ -114,6 +119,31 @@ func Test_Tree(t *testing.T) { } } +func Test_Short(t *testing.T) { + shortJSON := `[ { "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "foo:latest" ], "ParentId": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Id": "c87be8e5e697c735f5db5626147582d2ae3f2088574c5faaf8d4d1bccab99470", "Created": 1386142123 }, { "VirtualSize": 682553464, "Size": 0, "RepoTags": [ "foo:1.0" ], "ParentId": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Id": "626147582d2ae3735f5db5f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Created": 1386142123 }, { "VirtualSize": 712553464, "Size": 0, "RepoTags": [ "foo:2.0" ], "ParentId": "626147582d2ae3735f5db5f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Id": "574c5faaf8d4d1bccab994626147582d2ae3735f5db5f2c87be8e5e697c08870", "Created": 1386142123 }, { "VirtualSize": 752553464, "Size": 0, "RepoTags": [ "private.repo.com:5000:latest" ], "ParentId": "574c5faaf8d4d1bccab994626147582d2ae3735f5db5f2c87be8e5e697c08870", "Id": "aaf8d4d1bccab994574c5f626147582d2ae3735f5db5f2c87be8e5e697c08870", "Created": 1386142123 }, { "VirtualSize": 662553464, "Size": 0, "RepoTags": [ ":" ], "ParentId": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Id": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Created": 1386142123 }, { "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ ":" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 } ]` + + shortTests := []ShortTest{ + ShortTest{ + json: shortJSON, + regexps: []string{ + `(?m)foo: latest, 1.0, 2.0`, + `(?m)private.repo.com:5000: latest`, + }, + }, + } + + for _, shortTest := range shortTests { + im, _ := parseImagesJSON([]byte(shortTest.json)) + result := jsonToShort(im) + + for _, regexp := range compileRegexps(t, shortTest.regexps) { + if !regexp.MatchString(result) { + t.Fatalf("images short content '%s' did not match regexp '%s'", result, regexp) + } + } + } +} + func compileRegexps(t *testing.T, regexpStrings []string) []*regexp.Regexp { compiledRegexps := []*regexp.Regexp{}