diff --git a/README.md b/README.md index 821c140..42547cf 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,41 @@ -# dockviz +# dockviz: Visualizing Docker Data -Visualizing Docker Data +This command takes Docker image and container information and presents in +different ways, to help you understand what's going on inside the system. -This command takes the raw Docker JSON and visualizes it in various ways. +# Quick Start -For image information, output can be formatted as -[Graphviz](http://www.graphviz.org), as a tree in the terminal, or in a short summary. +1. Download the [latest release](https://github.com/justone/dockviz/releases). +2. Visualize images by running `dockviz images -t`, which has similar output to `docker images -t`. -For container information, only Graphviz output has been implemented. +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. -# Examples +# Output Examples ## Containers Currently, containers are visualized with labeled lines for links. Containers that aren't running are greyed out. +``` +$ dockviz containers -d | dot -Tpng -o containers.png +``` + ![](sample/containers.png "Container") ## Images Image info is visualized with lines indicating parent images: +``` +$ dockviz images -d | dot -Tpng -o images.png +``` + ![](sample/images.png "Image") Or in short form: ``` +$ dockviz images -s nate/mongodb: latest redis: latest ubuntu: 12.04, precise, 12.10, quantal, 13.04, raring @@ -34,6 +44,7 @@ ubuntu: 12.04, precise, 12.10, quantal, 13.04, raring Or as a tree in the terminal: ``` +$ dockviz images -t └─511136ea3c5a Virtual Size: 0.0 B |─f10ebce2c0e1 Virtual Size: 103.7 MB | └─82cdea7ab5b5 Virtual Size: 103.9 MB @@ -67,29 +78,21 @@ Or as a tree in the terminal: # Running -## TCP +Dockviz supports connecting to the Docker daemon directly. It defaults to `unix:///var/run/docker.sock`, but respects the following as well: -When docker is listening on the TCP port: +* 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. ``` $ curl -s http://localhost:4243/images/json?all=1 | dockviz images --tree $ curl -s http://localhost:4243/containers/json?all=1 | dockviz containers --dot | dot -Tpng -o containers.png -``` - -## Socket - -When docker is listening on the socket: - -``` $ echo -e "GET /images/json?all=1 HTTP/1.0\r\n" | nc -U /var/run/docker.sock | tail -n +5 | dockviz images --tree $ echo -e "GET /containers/json?all=1 HTTP/1.0\r\n" | nc -U /var/run/docker.sock | tail -n +5 | dockviz containers --dot | dot -Tpng -o containers.png ``` -GNU netcat doesn't support `-U` (UNIX socket) flag, so OpenBSD variant can be used. - -## Direct from Docker - -Someday soon the Docker command line will allow dumping the image and container JSON directly. +Note: GNU netcat doesn't support `-U` (UNIX socket) flag, so OpenBSD variant can be used. # Binaries @@ -101,4 +104,3 @@ See the [releases](https://github.com/justone/dockviz/releases) area for binarie go get ./... go build ``` - diff --git a/cli.go b/cli.go index 7a93567..e5e72fc 100644 --- a/cli.go +++ b/cli.go @@ -1,19 +1,31 @@ package main import ( + "fmt" "os" "github.com/jessevdk/go-flags" ) type GlobalOptions struct { - // no options yet + TLSCaCert string `long:"tlscacert" value-name:"~/.docker/ca.pem" description:"Trust certs signed only by this CA"` + TLSCert string `long:"tlscert" value-name:"~/.docker/cert.pem" description:"Path to TLS certificate file"` + TLSKey string `long:"tlskey" value-name:"~/.docker/key.pem" description:"Path to TLS key file"` + TLSVerify bool `long:"tlsverify" description:"Use TLS and verify the remote"` + Host string `long:"host" short:"H" value-name:"unix:///var/run/docker.sock" description:"Docker host to connect to"` + Version func() `long:"version" short:"v" description:"Display version information."` } var globalOptions GlobalOptions var parser = flags.NewParser(&globalOptions, flags.Default) +var version = "v0.2" + func main() { + globalOptions.Version = func() { + fmt.Println("dockviz", version) + os.Exit(0) + } if _, err := parser.Parse(); err != nil { os.Exit(1) } diff --git a/containers.go b/containers.go index ca131dc..2029a42 100644 --- a/containers.go +++ b/containers.go @@ -1,6 +1,8 @@ package main import ( + "github.com/fsouza/go-dockerclient" + "bytes" "encoding/json" "fmt" @@ -28,15 +30,50 @@ var containersCommand ContainersCommand func (x *ContainersCommand) Execute(args []string) error { - // read in stdin - stdin, err := ioutil.ReadAll(os.Stdin) + var containers *[]Container + + stat, err := os.Stdin.Stat() if err != nil { - return fmt.Errorf("error reading all input", err) + return fmt.Errorf("error reading stdin stat", err) } - containers, err := parseContainersJSON(stdin) - if err != nil { - return err + if (stat.Mode() & os.ModeCharDevice) == 0 { + // 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 + } + } else { + + client, err := connect() + if err != nil { + return err + } + + clientContainers, err := client.ListContainers(docker.ListContainersOptions{All: true}) + if err != nil { + return err + } + + var conts []Container + for _, container := range clientContainers { + conts = append(conts, Container{ + container.ID, + container.Image, + container.Names, + apiPortToMap(container.Ports), + container.Created, + container.Status, + container.Command, + }) + } + + containers = &conts } if containersCommand.Dot { @@ -48,6 +85,20 @@ func (x *ContainersCommand) Execute(args []string) error { return nil } +func apiPortToMap(ports []docker.APIPort) []map[string]interface{} { + result := make([]map[string]interface{}, 2) + for _, port := range ports { + intPort := map[string]interface{}{ + "IP": port.IP, + "Type": port.Type, + "PrivatePort": port.PrivatePort, + "PublicPort": port.PublicPort, + } + result = append(result, intPort) + } + return result +} + func parseContainersJSON(rawJSON []byte) (*[]Container, error) { var containers []Container diff --git a/images.go b/images.go index 05773d0..c2bc14b 100644 --- a/images.go +++ b/images.go @@ -1,6 +1,8 @@ package main import ( + "github.com/fsouza/go-dockerclient" + "bytes" "encoding/json" "fmt" @@ -29,15 +31,51 @@ var imagesCommand ImagesCommand func (x *ImagesCommand) Execute(args []string) error { - // read in stdin - stdin, err := ioutil.ReadAll(os.Stdin) + var images *[]Image + + stat, err := os.Stdin.Stat() if err != nil { - return fmt.Errorf("error reading all input", err) + return fmt.Errorf("error reading stdin stat", err) } - images, err := parseImagesJSON(stdin) - if err != nil { - return err + if (stat.Mode() & os.ModeCharDevice) == 0 { + // read in stdin + stdin, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("error reading all input", err) + } + + images, err = parseImagesJSON(stdin) + if err != nil { + return err + } + + } else { + + client, err := connect() + if err != nil { + return err + } + + clientImages, err := client.ListImages(docker.ListImagesOptions{All: true}) + if err != nil { + return err + } + + var ims []Image + for _, image := range clientImages { + // fmt.Println(image) + ims = append(ims, Image{ + image.ID, + image.ParentID, + image.RepoTags, + image.VirtualSize, + image.Size, + image.Created, + }) + } + + images = &ims } if imagesCommand.Dot { diff --git a/util.go b/util.go new file mode 100644 index 0000000..728baea --- /dev/null +++ b/util.go @@ -0,0 +1,51 @@ +package main + +import ( + "errors" + "os" + "path" + + "github.com/fsouza/go-dockerclient" +) + +func connect() (*docker.Client, error) { + + // grab directly from docker daemon + var endpoint string + if env_endpoint := os.Getenv("DOCKER_HOST"); len(env_endpoint) > 0 { + endpoint = env_endpoint + } else if len(globalOptions.Host) > 0 { + endpoint = globalOptions.Host + } else { + // assume local socket + endpoint = "unix:///var/run/docker.sock" + } + + var client *docker.Client + var err error + dockerTlsVerifyEnv := os.Getenv("DOCKER_TLS_VERIFY") + if dockerTlsVerifyEnv == "1" || globalOptions.TLSVerify { + if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); len(dockerCertPath) > 0 { + cert := path.Join(dockerCertPath, "cert.pem") + key := path.Join(dockerCertPath, "key.pem") + ca := path.Join(dockerCertPath, "ca.pem") + client, err = docker.NewTLSClient(endpoint, cert, key, ca) + if err != nil { + return nil, err + } + } else if len(globalOptions.TLSCert) > 0 && len(globalOptions.TLSKey) > 0 && len(globalOptions.TLSCaCert) > 0 { + client, err = docker.NewTLSClient(endpoint, globalOptions.TLSCert, globalOptions.TLSKey, globalOptions.TLSCaCert) + if err != nil { + return nil, err + } + } else { + return nil, errors.New("TLS Verification requested but certs not specified") + } + } else { + client, err = docker.NewClient(endpoint) + if err != nil { + return nil, err + } + } + return client, nil +}