2014-05-13 16:06:20 +02:00
package main
import (
2015-05-21 22:12:10 +02:00
"github.com/fsouza/go-dockerclient"
2014-05-13 16:06:20 +02:00
"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." `
2017-05-22 18:14:10 +02:00
OnlyRunning bool ` short:"r" long:"running" description:"Only show running containers, not Exited" `
2014-05-13 16:06:20 +02:00
}
var containersCommand ContainersCommand
func ( x * ContainersCommand ) Execute ( args [ ] string ) error {
2015-05-21 22:12:10 +02:00
var containers * [ ] Container
stat , err := os . Stdin . Stat ( )
2014-05-13 16:06:20 +02:00
if err != nil {
2015-05-21 22:12:10 +02:00
return fmt . Errorf ( "error reading stdin stat" , err )
2014-05-13 16:06:20 +02:00
}
2016-12-08 21:18:12 +01:00
if globalOptions . Stdin && ( stat . Mode ( ) & os . ModeCharDevice ) == 0 {
2015-05-21 22:12:10 +02:00
// 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 ( )
2015-05-21 23:42:53 +02:00
if err != nil {
return err
}
2015-05-21 22:12:10 +02:00
clientContainers , err := client . ListContainers ( docker . ListContainersOptions { All : true } )
if err != nil {
2015-08-18 15:33:22 +02:00
if in_docker := os . Getenv ( "IN_DOCKER" ) ; len ( in_docker ) > 0 {
2016-06-14 05:48:21 +02:00
return fmt . Errorf ( "Unable to access Docker socket, please run like this:\n docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz containers <args>\nFor more help, run 'dockviz help'" )
2015-08-18 15:33:22 +02:00
} else {
return fmt . Errorf ( "Unable to connect: %s\nFor help, run 'dockviz help'" , err )
}
2015-05-21 22:12:10 +02:00
}
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
2014-05-13 16:06:20 +02:00
}
if containersCommand . Dot {
2017-05-22 18:14:10 +02:00
fmt . Printf ( jsonContainersToDot ( containers , containersCommand . OnlyRunning ) )
2014-05-13 16:06:20 +02:00
} else {
return fmt . Errorf ( "Please specify --dot" )
}
return nil
}
2015-05-21 22:12:10 +02:00
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
}
2014-05-13 16:06:20 +02:00
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
}
2017-05-22 17:44:33 +02:00
func jsonContainersToDot ( containers * [ ] Container , OnlyRunning bool ) string {
2014-05-13 16:06:20 +02:00
var buffer bytes . Buffer
buffer . WriteString ( "digraph docker {\n" )
2017-05-22 17:44:33 +02:00
// build list of all primary container names
2017-05-22 17:48:18 +02:00
// this is so we can throw away links to
// non-primary container name
2017-05-22 17:44:33 +02:00
var PrimaryContainerNames map [ string ] string
PrimaryContainerNames = make ( map [ string ] string )
2014-05-13 16:06:20 +02:00
for _ , container := range * containers {
2017-05-22 17:44:33 +02:00
for _ , name := range container . Names {
if strings . Count ( name , "/" ) == 1 {
PrimaryContainerNames [ name [ 1 : ] ] = name [ 1 : ]
}
}
}
// stores ony first value of link to avoid duplicates
var LinkMap map [ string ] string
LinkMap = make ( map [ string ] string )
for _ , container := range * containers {
2017-05-22 18:14:10 +02:00
if OnlyRunning && strings . HasPrefix ( container . Status , "Exit" ) { continue }
2014-05-13 16:06:20 +02:00
var containerName string
2017-05-22 17:44:33 +02:00
//fmt.Printf("container status/Names %s/%s\n",container.Status,container.Names)
2014-05-13 16:06:20 +02:00
for _ , name := range container . Names {
if strings . Count ( name , "/" ) == 1 {
containerName = name [ 1 : ]
}
}
2017-05-22 17:44:33 +02:00
2014-05-13 16:06:20 +02:00
for _ , name := range container . Names {
nameParts := strings . Split ( name , "/" )
if len ( nameParts ) > 2 {
2017-05-22 17:44:33 +02:00
//fmt.Printf("\t%s to %s\n",containerName,nameParts[1])
2017-05-22 17:48:18 +02:00
// source and dest should be primary container names
2017-05-22 17:44:33 +02:00
if IsPrimaryContainerName ( containerName , PrimaryContainerNames ) && IsPrimaryContainerName ( nameParts [ 1 ] , PrimaryContainerNames ) {
2017-05-22 17:48:18 +02:00
// only create link if none exists already
2017-05-22 17:44:33 +02:00
if _ , ok := LinkMap [ containerName + "-" + nameParts [ 1 ] ] ; ! ok {
LinkMap [ containerName + "-" + nameParts [ 1 ] ] = "exists"
buffer . WriteString ( fmt . Sprintf ( " \"%s\" -> \"%s\" [label = \" %s\" ]\n" , containerName , nameParts [ 1 ] , nameParts [ len ( nameParts ) - 1 ] ) )
}
}
2014-05-13 16:06:20 +02:00
}
}
var containerBackground string
if strings . Contains ( container . Status , "Exited" ) {
containerBackground = "lightgrey"
} else {
containerBackground = "paleturquoise"
}
2017-05-22 17:44:33 +02:00
buffer . WriteString ( fmt . Sprintf ( " \"%s\" [label=\"%s\\n%s\\n%s\",shape=box,fillcolor=\"%s\",style=\"filled,rounded\"];\n" , containerName , container . Image , containerName , truncate ( container . Id , 12 ) , containerBackground ) )
2014-05-13 16:06:20 +02:00
}
buffer . WriteString ( "}\n" )
return buffer . String ( )
}
2017-05-22 17:44:33 +02:00
func IsPrimaryContainerName ( Name string , PrimaryContainerNames map [ string ] string ) bool {
_ , ok := PrimaryContainerNames [ Name ]
return ok
}
2014-05-13 16:06:20 +02:00
func init ( ) {
parser . AddCommand ( "containers" ,
"Visualize docker containers." ,
"" ,
& containersCommand )
}