From 7aace0ee2a504988c867e45e431c3400ae92907e Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Sun, 8 Mar 2015 15:28:01 -0700 Subject: [PATCH 1/8] start of implementing direct client access --- images.go | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/images.go b/images.go index 05773d0..53235e8 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,52 @@ 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 { + + // grab directly from docker daemon + client, err := docker.NewClient("unix:///var/run/docker.sock") + 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 { From 8c98858fcbdfcfd0ea1a3944605612408da3fcaa Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Thu, 30 Apr 2015 21:17:26 -0700 Subject: [PATCH 2/8] support boot2docker via environment variables --- images.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/images.go b/images.go index 53235e8..ad01cb9 100644 --- a/images.go +++ b/images.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "os" + "path" "strings" ) @@ -53,9 +54,25 @@ func (x *ImagesCommand) Execute(args []string) error { } else { // grab directly from docker daemon - client, err := docker.NewClient("unix:///var/run/docker.sock") - if err != nil { - return err + endpoint := os.Getenv("DOCKER_HOST") + if len(endpoint) == 0 { + endpoint = "unix:///var/run/docker.sock" + } + + var client *docker.Client + 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 err + } + } else { + client, err = docker.NewClient(endpoint) + if err != nil { + return err + } } clientImages, err := client.ListImages(docker.ListImagesOptions{All: true}) From 837d45a0c37941fdbacf5674d84f66053bba3a76 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Thu, 21 May 2015 12:48:06 -0700 Subject: [PATCH 3/8] extract dockerclient connection --- images.go | 23 +---------------------- util.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 util.go diff --git a/images.go b/images.go index ad01cb9..2140533 100644 --- a/images.go +++ b/images.go @@ -8,7 +8,6 @@ import ( "fmt" "io/ioutil" "os" - "path" "strings" ) @@ -53,27 +52,7 @@ func (x *ImagesCommand) Execute(args []string) error { } else { - // grab directly from docker daemon - endpoint := os.Getenv("DOCKER_HOST") - if len(endpoint) == 0 { - endpoint = "unix:///var/run/docker.sock" - } - - var client *docker.Client - 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 err - } - } else { - client, err = docker.NewClient(endpoint) - if err != nil { - return err - } - } + client, err := connect() clientImages, err := client.ListImages(docker.ListImagesOptions{All: true}) if err != nil { diff --git a/util.go b/util.go new file mode 100644 index 0000000..c1f84a0 --- /dev/null +++ b/util.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.com/fsouza/go-dockerclient" + + "os" + "path" +) + +func connect() (*docker.Client, error) { + + // grab directly from docker daemon + endpoint := os.Getenv("DOCKER_HOST") + if len(endpoint) == 0 { + endpoint = "unix:///var/run/docker.sock" + } + + var client *docker.Client + var err error + 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 { + client, err = docker.NewClient(endpoint) + if err != nil { + return nil, err + } + } + return client, nil +} From 56ab2b992bf0a0c113a4e59918061319ff7f9259 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Thu, 21 May 2015 13:12:10 -0700 Subject: [PATCH 4/8] support for direct client connect in containers too --- containers.go | 60 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/containers.go b/containers.go index ca131dc..e537b48 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,47 @@ 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() + + 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 +82,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 From 8053390d939b0dd4512d9b1caa7aec02e29fa574 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Thu, 21 May 2015 14:33:45 -0700 Subject: [PATCH 5/8] support command line parameters for tls --- cli.go | 5 ++++- images.go | 3 +++ util.go | 29 ++++++++++++++++++++--------- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/cli.go b/cli.go index 7a93567..d1e88c9 100644 --- a/cli.go +++ b/cli.go @@ -7,7 +7,10 @@ import ( ) 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"` } var globalOptions GlobalOptions diff --git a/images.go b/images.go index 2140533..c2bc14b 100644 --- a/images.go +++ b/images.go @@ -53,6 +53,9 @@ func (x *ImagesCommand) Execute(args []string) error { } else { client, err := connect() + if err != nil { + return err + } clientImages, err := client.ListImages(docker.ListImagesOptions{All: true}) if err != nil { diff --git a/util.go b/util.go index c1f84a0..2f7571e 100644 --- a/util.go +++ b/util.go @@ -1,10 +1,11 @@ package main import ( - "github.com/fsouza/go-dockerclient" - + "errors" "os" "path" + + "github.com/fsouza/go-dockerclient" ) func connect() (*docker.Client, error) { @@ -17,13 +18,23 @@ func connect() (*docker.Client, error) { var client *docker.Client var err error - 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 + 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) From 5fc5050be2b6d85f3170c8197834fb83eba4d9e0 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Thu, 21 May 2015 14:42:53 -0700 Subject: [PATCH 6/8] support specifying docker host endpoint --- cli.go | 1 + containers.go | 3 +++ util.go | 9 +++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cli.go b/cli.go index d1e88c9..aab4dc9 100644 --- a/cli.go +++ b/cli.go @@ -11,6 +11,7 @@ type GlobalOptions struct { 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"` } var globalOptions GlobalOptions diff --git a/containers.go b/containers.go index e537b48..2029a42 100644 --- a/containers.go +++ b/containers.go @@ -51,6 +51,9 @@ func (x *ContainersCommand) Execute(args []string) error { } else { client, err := connect() + if err != nil { + return err + } clientContainers, err := client.ListContainers(docker.ListContainersOptions{All: true}) if err != nil { diff --git a/util.go b/util.go index 2f7571e..728baea 100644 --- a/util.go +++ b/util.go @@ -11,8 +11,13 @@ import ( func connect() (*docker.Client, error) { // grab directly from docker daemon - endpoint := os.Getenv("DOCKER_HOST") - if len(endpoint) == 0 { + 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" } From 2decb499f2cd0f5ee5774fa85e6e4f242f0393ba Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Thu, 21 May 2015 22:20:45 -0700 Subject: [PATCH 7/8] update docs for direct client use --- README.md | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) 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 ``` - From 961ed9e35f0f9d75993151a14c72ffa66b8b439f Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Thu, 21 May 2015 22:33:11 -0700 Subject: [PATCH 8/8] add version flag --- cli.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cli.go b/cli.go index aab4dc9..e5e72fc 100644 --- a/cli.go +++ b/cli.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "github.com/jessevdk/go-flags" @@ -12,12 +13,19 @@ type GlobalOptions struct { 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) }