2014-04-21 18:30:55 +02:00
package main
2014-04-22 06:04:38 +02:00
import (
2015-03-08 23:28:01 +01:00
"github.com/fsouza/go-dockerclient"
2014-04-25 06:09:44 +02:00
"bytes"
2014-04-22 06:04:38 +02:00
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
)
type Image struct {
Id string
ParentId string ` json:",omitempty" `
RepoTags [ ] string ` json:",omitempty" `
VirtualSize int64
Size int64
Created int64
}
2014-04-21 18:30:55 +02:00
type ImagesCommand struct {
2014-05-04 23:22:25 +02:00
Dot bool ` short:"d" long:"dot" description:"Show image information as Graphviz dot." `
Tree bool ` short:"t" long:"tree" description:"Show image information as tree." `
2015-02-27 06:06:24 +01:00
Short bool ` short:"s" long:"short" description:"Show short summary of images (repo name and list of tags)." `
2014-05-13 05:30:39 +02:00
NoTruncate bool ` short:"n" long:"no-trunc" description:"Don't truncate the image IDs." `
2014-04-21 18:30:55 +02:00
}
var imagesCommand ImagesCommand
func ( x * ImagesCommand ) Execute ( args [ ] string ) error {
2015-03-08 23:28:01 +01:00
var images * [ ] Image
stat , err := os . Stdin . Stat ( )
2014-04-22 06:04:38 +02:00
if err != nil {
2015-03-08 23:28:01 +01:00
return fmt . Errorf ( "error reading stdin stat" , err )
2014-04-22 06:04:38 +02:00
}
2015-03-08 23:28:01 +01:00
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 {
2015-05-21 21:48:06 +02:00
client , err := connect ( )
2015-05-21 23:33:45 +02:00
if err != nil {
return err
}
2015-03-08 23:28:01 +01:00
clientImages , err := client . ListImages ( docker . ListImagesOptions { All : true } )
if err != nil {
2015-08-18 15:33:22 +02:00
if in_docker := os . Getenv ( "IN_DOCKER" ) ; len ( in_docker ) > 0 {
return fmt . Errorf ( "Unable to access Docker socket, please run like this:\n docker run --rm -v /var/run/docker.sock:/var/run/docker.sock nate/dockviz images <args>\nFor more help, run 'dockviz help'" )
} else {
return fmt . Errorf ( "Unable to connect: %s\nFor help, run 'dockviz help'" , err )
}
2015-03-08 23:28:01 +01:00
}
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
2014-05-13 16:06:20 +02:00
}
2014-04-22 06:04:38 +02:00
2014-04-21 18:30:55 +02:00
if imagesCommand . Dot {
2014-04-25 06:09:44 +02:00
fmt . Printf ( jsonToDot ( images ) )
2014-04-21 18:30:55 +02:00
} else if imagesCommand . Tree {
2014-05-04 23:22:25 +02:00
2015-08-02 05:29:29 +02:00
var startImage = ""
2014-05-04 23:22:25 +02:00
if len ( args ) > 0 {
2015-08-02 05:29:29 +02:00
// attempt to find the start image, which can be specified as an
// image ID or a repository name
startImageArg := args [ 0 ]
startImageRepo := args [ 0 ]
// in case a repo name was specified, append ":latest" if it isn't
// already there
if ! strings . HasSuffix ( startImageRepo , ":latest" ) {
startImageRepo = fmt . Sprintf ( "%s:latest" , startImageRepo )
}
IMAGES :
for _ , image := range * images {
// check if the start image arg matches an image id
if strings . Index ( image . Id , startImageArg ) == 0 {
startImage = startImageArg
break IMAGES
}
// check if the start image arg matches an repository name
if image . RepoTags [ 0 ] != "<none>:<none>" {
for _ , repotag := range image . RepoTags {
if repotag == startImageRepo {
startImage = image . Id
break IMAGES
}
}
}
}
if startImage == "" {
return fmt . Errorf ( "Unable to find image %s." , startImageArg )
}
2014-05-04 23:22:25 +02:00
}
2015-08-02 05:29:29 +02:00
fmt . Printf ( jsonToTree ( images , startImage , imagesCommand . NoTruncate ) )
2015-02-27 06:06:24 +01:00
} else if imagesCommand . Short {
fmt . Printf ( jsonToShort ( images ) )
2014-05-13 05:30:39 +02:00
} else {
2015-02-27 06:06:24 +01:00
return fmt . Errorf ( "Please specify either --dot, --tree, or --short" )
2014-04-21 18:30:55 +02:00
}
return nil
}
2014-05-04 23:22:25 +02:00
func jsonToTree ( images * [ ] Image , startImageArg string , noTrunc bool ) string {
var buffer bytes . Buffer
var startImage Image
var roots [ ] Image
var byParent = make ( map [ string ] [ ] Image )
for _ , image := range * images {
if image . ParentId == "" {
roots = append ( roots , image )
} else {
if children , exists := byParent [ image . ParentId ] ; exists {
byParent [ image . ParentId ] = append ( children , image )
} else {
byParent [ image . ParentId ] = [ ] Image { image }
}
}
if startImageArg != "" {
if startImageArg == image . Id || startImageArg == truncate ( image . Id ) {
startImage = image
}
for _ , repotag := range image . RepoTags {
if repotag == startImageArg {
startImage = image
}
}
}
}
if startImageArg != "" {
WalkTree ( & buffer , noTrunc , [ ] Image { startImage } , byParent , "" )
} else {
WalkTree ( & buffer , noTrunc , roots , byParent , "" )
}
return buffer . String ( )
}
func WalkTree ( buffer * bytes . Buffer , noTrunc bool , images [ ] Image , byParent map [ string ] [ ] Image , prefix string ) {
if len ( images ) > 1 {
length := len ( images )
for index , image := range images {
if index + 1 == length {
PrintTreeNode ( buffer , noTrunc , image , prefix + "└─" )
if subimages , exists := byParent [ image . Id ] ; exists {
WalkTree ( buffer , noTrunc , subimages , byParent , prefix + " " )
}
} else {
2014-07-28 18:52:42 +02:00
PrintTreeNode ( buffer , noTrunc , image , prefix + "├─" )
2014-05-04 23:22:25 +02:00
if subimages , exists := byParent [ image . Id ] ; exists {
2014-07-28 18:52:42 +02:00
WalkTree ( buffer , noTrunc , subimages , byParent , prefix + "│ " )
2014-05-04 23:22:25 +02:00
}
}
}
} else {
for _ , image := range images {
PrintTreeNode ( buffer , noTrunc , image , prefix + "└─" )
if subimages , exists := byParent [ image . Id ] ; exists {
WalkTree ( buffer , noTrunc , subimages , byParent , prefix + " " )
}
}
}
}
func PrintTreeNode ( buffer * bytes . Buffer , noTrunc bool , image Image , prefix string ) {
var imageID string
if noTrunc {
imageID = image . Id
} else {
imageID = truncate ( image . Id )
}
buffer . WriteString ( fmt . Sprintf ( "%s%s Virtual Size: %s" , prefix , imageID , humanSize ( image . VirtualSize ) ) )
if image . RepoTags [ 0 ] != "<none>:<none>" {
buffer . WriteString ( fmt . Sprintf ( " Tags: %s\n" , strings . Join ( image . RepoTags , ", " ) ) )
} else {
buffer . WriteString ( fmt . Sprintf ( "\n" ) )
}
}
func humanSize ( raw int64 ) string {
sizes := [ ] string { "B" , "KB" , "MB" , "GB" , "TB" }
rawFloat := float64 ( raw )
ind := 0
for {
if rawFloat < 1000 {
break
} else {
rawFloat = rawFloat / 1000
ind = ind + 1
}
}
return fmt . Sprintf ( "%.01f %s" , rawFloat , sizes [ ind ] )
}
2014-04-22 06:04:38 +02:00
func truncate ( id string ) string {
return id [ 0 : 12 ]
}
2014-05-13 16:06:20 +02:00
func parseImagesJSON ( rawJSON [ ] byte ) ( * [ ] Image , error ) {
2014-04-25 06:09:44 +02:00
var images [ ] Image
err := json . Unmarshal ( rawJSON , & images )
if err != nil {
return nil , fmt . Errorf ( "Error reading JSON: " , err )
}
return & images , nil
}
func jsonToDot ( images * [ ] Image ) string {
var buffer bytes . Buffer
buffer . WriteString ( "digraph docker {\n" )
for _ , image := range * images {
if image . ParentId == "" {
buffer . WriteString ( fmt . Sprintf ( " base -> \"%s\" [style=invis]\n" , truncate ( image . Id ) ) )
} else {
buffer . WriteString ( fmt . Sprintf ( " \"%s\" -> \"%s\"\n" , truncate ( image . ParentId ) , truncate ( image . Id ) ) )
}
if image . RepoTags [ 0 ] != "<none>:<none>" {
buffer . WriteString ( fmt . Sprintf ( " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n" , truncate ( image . Id ) , truncate ( image . Id ) , strings . Join ( image . RepoTags , "\\n" ) ) )
}
}
buffer . WriteString ( " base [style=invisible]\n}\n" )
return buffer . String ( )
}
2015-02-27 06:06:24 +01:00
func jsonToShort ( images * [ ] Image ) string {
var buffer bytes . Buffer
var byRepo = make ( map [ string ] [ ] string )
for _ , image := range * images {
for _ , repotag := range image . RepoTags {
if repotag != "<none>:<none>" {
// parse the repo name and tag name out
// tag is after the last colon
lastColonIndex := strings . LastIndex ( repotag , ":" )
tagname := repotag [ lastColonIndex + 1 : ]
reponame := repotag [ 0 : lastColonIndex ]
if tags , exists := byRepo [ reponame ] ; exists {
byRepo [ reponame ] = append ( tags , tagname )
} else {
byRepo [ reponame ] = [ ] string { tagname }
}
}
}
}
for repo , tags := range byRepo {
buffer . WriteString ( fmt . Sprintf ( "%s: %s\n" , repo , strings . Join ( tags , ", " ) ) )
}
return buffer . String ( )
}
2014-04-21 18:30:55 +02:00
func init ( ) {
parser . AddCommand ( "images" ,
"Visualize docker images." ,
"" ,
& imagesCommand )
}