viz for containers
This commit is contained in:
parent
5f566e0780
commit
c59127d902
4 changed files with 155 additions and 5 deletions
105
containers.go
Normal file
105
containers.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -34,7 +34,10 @@ func (x *ImagesCommand) Execute(args []string) error {
|
||||||
return fmt.Errorf("error reading all input", err)
|
return fmt.Errorf("error reading all input", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
images, err := parseJSON(stdin)
|
images, err := parseImagesJSON(stdin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if imagesCommand.Dot {
|
if imagesCommand.Dot {
|
||||||
fmt.Printf(jsonToDot(images))
|
fmt.Printf(jsonToDot(images))
|
||||||
|
@ -157,7 +160,7 @@ func truncate(id string) string {
|
||||||
return id[0:12]
|
return id[0:12]
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseJSON(rawJSON []byte) (*[]Image, error) {
|
func parseImagesJSON(rawJSON []byte) (*[]Image, error) {
|
||||||
|
|
||||||
var images []Image
|
var images []Image
|
||||||
err := json.Unmarshal(rawJSON, &images)
|
err := json.Unmarshal(rawJSON, &images)
|
||||||
|
|
|
@ -18,7 +18,7 @@ type TreeTest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BadJSON(t *testing.T) {
|
func Test_BadJSON(t *testing.T) {
|
||||||
_, err := parseJSON([]byte(` "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`))
|
_, err := parseImagesJSON([]byte(` "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`))
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("invalid json did not cause an error")
|
t.Error("invalid json did not cause an error")
|
||||||
|
@ -50,7 +50,7 @@ func Test_Dot(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dotTest := range dotTests {
|
for _, dotTest := range dotTests {
|
||||||
im, _ := parseJSON([]byte(dotTest.json))
|
im, _ := parseImagesJSON([]byte(dotTest.json))
|
||||||
result := jsonToDot(im)
|
result := jsonToDot(im)
|
||||||
|
|
||||||
for _, regexp := range allRegex {
|
for _, regexp := range allRegex {
|
||||||
|
@ -103,7 +103,7 @@ func Test_Tree(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, treeTest := range treeTests {
|
for _, treeTest := range treeTests {
|
||||||
im, _ := parseJSON([]byte(treeTest.json))
|
im, _ := parseImagesJSON([]byte(treeTest.json))
|
||||||
result := jsonToTree(im, treeTest.startImage, treeTest.noTrunc)
|
result := jsonToTree(im, treeTest.startImage, treeTest.noTrunc)
|
||||||
|
|
||||||
for _, regexp := range compileRegexps(t, treeTest.regexps) {
|
for _, regexp := range compileRegexps(t, treeTest.regexps) {
|
||||||
|
|
42
sample/containers.json
Normal file
42
sample/containers.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in a new issue