From c59127d9021f64cbd18ab987f0f5960204f371af Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Tue, 13 May 2014 07:06:20 -0700 Subject: [PATCH] viz for containers --- containers.go | 105 +++++++++++++++++++++++++++++++++++++++++ images.go | 7 ++- images_test.go | 6 +-- sample/containers.json | 42 +++++++++++++++++ 4 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 containers.go create mode 100644 sample/containers.json diff --git a/containers.go b/containers.go new file mode 100644 index 0000000..ca131dc --- /dev/null +++ b/containers.go @@ -0,0 +1,105 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" +) + +type Container struct { + Id string + Image string + Names []string + Ports []map[string]interface{} + Created int64 + Status string + Command string +} + +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."` +} + +var containersCommand ContainersCommand + +func (x *ContainersCommand) Execute(args []string) error { + + // read in stdin + stdin, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("error reading all input", err) + } + + containers, err := parseContainersJSON(stdin) + if err != nil { + return err + } + + if containersCommand.Dot { + fmt.Printf(jsonContainersToDot(containers)) + } else { + return fmt.Errorf("Please specify --dot") + } + + return nil +} + +func parseContainersJSON(rawJSON []byte) (*[]Container, error) { + + var containers []Container + err := json.Unmarshal(rawJSON, &containers) + + if err != nil { + return nil, fmt.Errorf("Error reading JSON: ", err) + } + + return &containers, nil +} + +func jsonContainersToDot(containers *[]Container) string { + + var buffer bytes.Buffer + buffer.WriteString("digraph docker {\n") + + for _, container := range *containers { + + var containerName string + + 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])) + + } + } + + var containerBackground string + if strings.Contains(container.Status, "Exited") { + containerBackground = "lightgrey" + } else { + containerBackground = "paleturquoise" + } + + buffer.WriteString(fmt.Sprintf(" \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"%s\",style=\"filled,rounded\"];\n", containerName, containerName, truncate(container.Id), containerBackground)) + } + + buffer.WriteString("}\n") + + return buffer.String() +} + +func init() { + parser.AddCommand("containers", + "Visualize docker containers.", + "", + &containersCommand) +} diff --git a/images.go b/images.go index 39ad648..46800a5 100644 --- a/images.go +++ b/images.go @@ -34,7 +34,10 @@ func (x *ImagesCommand) Execute(args []string) error { return fmt.Errorf("error reading all input", err) } - images, err := parseJSON(stdin) + images, err := parseImagesJSON(stdin) + if err != nil { + return err + } if imagesCommand.Dot { fmt.Printf(jsonToDot(images)) @@ -157,7 +160,7 @@ func truncate(id string) string { return id[0:12] } -func parseJSON(rawJSON []byte) (*[]Image, error) { +func parseImagesJSON(rawJSON []byte) (*[]Image, error) { var images []Image err := json.Unmarshal(rawJSON, &images) diff --git a/images_test.go b/images_test.go index 5a6dace..29b6323 100644 --- a/images_test.go +++ b/images_test.go @@ -18,7 +18,7 @@ type TreeTest struct { } func Test_BadJSON(t *testing.T) { - _, err := parseJSON([]byte(` "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ ":" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`)) + _, err := parseImagesJSON([]byte(` "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ ":" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`)) if err == nil { t.Error("invalid json did not cause an error") @@ -50,7 +50,7 @@ func Test_Dot(t *testing.T) { } for _, dotTest := range dotTests { - im, _ := parseJSON([]byte(dotTest.json)) + im, _ := parseImagesJSON([]byte(dotTest.json)) result := jsonToDot(im) for _, regexp := range allRegex { @@ -103,7 +103,7 @@ func Test_Tree(t *testing.T) { } for _, treeTest := range treeTests { - im, _ := parseJSON([]byte(treeTest.json)) + im, _ := parseImagesJSON([]byte(treeTest.json)) result := jsonToTree(im, treeTest.startImage, treeTest.noTrunc) for _, regexp := range compileRegexps(t, treeTest.regexps) { diff --git a/sample/containers.json b/sample/containers.json new file mode 100644 index 0000000..a29fdd3 --- /dev/null +++ b/sample/containers.json @@ -0,0 +1,42 @@ +[ + { + "Status": "Exited (0) 9 seconds ago", + "Ports": [], + "Names": [ + "/application2" + ], + "Image": "ubuntu:12.10", + "Id": "878602c44611115d52118edeb768fc62de8cfed8c3bdb8c5cd2e149cb1c20afa", + "Created": 1399985983, + "Command": "/bin/bash" + }, + { + "Status": "Up 2 minutes", + "Ports": [], + "Names": [ + "/application" + ], + "Image": "ubuntu:12.10", + "Id": "6a2fa6a3c2d43738a1b850a17b3da212970efce83d119da2707177d1e506567f", + "Created": 1399985012, + "Command": "/bin/bash" + }, + { + "Status": "Up 4 minutes", + "Ports": [ + { + "Type": "tcp", + "PublicPort": 6379 + } + ], + "Names": [ + "/application/db", + "/application2/db", + "/redis" + ], + "Image": "redis:latest", + "Id": "5d7e818a4ea3cf01bdf5e1fdaebf645e11469ba0e55a506cde31834732766421", + "Created": 1399984760, + "Command": "/usr/bin/redis-server" + } +]