Merge branch 'only_labelled'
This commit is contained in:
commit
20d1b3797a
6 changed files with 215 additions and 124 deletions
70
README.md
70
README.md
|
@ -34,10 +34,20 @@ Image info is visualized with lines indicating parent images:
|
|||
|
||||
```
|
||||
$ dockviz images -d | dot -Tpng -o images.png
|
||||
OR
|
||||
$ dockviz images --dot | dot -Tpng -o images.png
|
||||
```
|
||||
|
||||
![](sample/images.png "Image")
|
||||
|
||||
```
|
||||
$ dockviz images -d -l | dot -Tpng -o images.png
|
||||
OR
|
||||
$ dockviz images --dot -only-labeled | dot -Tpng -o images.png
|
||||
```
|
||||
|
||||
![](sample/images_only_labeled.png "Image")
|
||||
|
||||
Or in short form:
|
||||
|
||||
```
|
||||
|
@ -52,35 +62,47 @@ 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
|
||||
| └─5dbd9cb5a02f Virtual Size: 103.9 MB
|
||||
| └─74fe38d11401 Virtual Size: 209.6 MB Tags: ubuntu:12.04, ubuntu:precise
|
||||
|─ef519c9ee91a Virtual Size: 100.9 MB
|
||||
| └─07302703becc Virtual Size: 101.2 MB
|
||||
| └─cf8dc907452c Virtual Size: 101.2 MB
|
||||
| └─a7cf8ae4e998 Virtual Size: 171.3 MB Tags: ubuntu:12.10, ubuntu:quantal
|
||||
| |─e18d8001204e Virtual Size: 171.3 MB
|
||||
| | └─d0525208a46c Virtual Size: 171.3 MB
|
||||
| | └─59dac4bae93b Virtual Size: 242.5 MB
|
||||
| | └─89541b3b35f2 Virtual Size: 511.8 MB
|
||||
| | └─7dac4e98548e Virtual Size: 511.8 MB
|
||||
| | └─341d0cc3fac8 Virtual Size: 511.8 MB
|
||||
| | └─2f96171d2098 Virtual Size: 511.8 MB
|
||||
| | └─67b8b7262a67 Virtual Size: 513.7 MB
|
||||
| | └─0fe9a2bc50fe Virtual Size: 513.7 MB
|
||||
| | └─8c32832f07ba Virtual Size: 513.7 MB
|
||||
| | └─cc4e1358bc80 Virtual Size: 513.7 MB
|
||||
| | └─5c0d04fba9df Virtual Size: 513.7 MB Tags: nate/mongodb:latest
|
||||
| └─398d592f2009 Virtual Size: 242.2 MB
|
||||
| └─0cd8e7f50270 Virtual Size: 243.6 MB
|
||||
| └─594b6f8e6f92 Virtual Size: 243.6 MB
|
||||
| └─f832a63e87a4 Virtual Size: 243.6 MB Tags: redis:latest
|
||||
├─f10ebce2c0e1 Virtual Size: 103.7 MB
|
||||
│ └─82cdea7ab5b5 Virtual Size: 103.9 MB
|
||||
│ └─5dbd9cb5a02f Virtual Size: 103.9 MB
|
||||
│ └─74fe38d11401 Virtual Size: 209.6 MB Tags: ubuntu:12.04, ubuntu:precise
|
||||
├─ef519c9ee91a Virtual Size: 100.9 MB
|
||||
│ └─07302703becc Virtual Size: 101.2 MB
|
||||
│ └─cf8dc907452c Virtual Size: 101.2 MB
|
||||
│ └─a7cf8ae4e998 Virtual Size: 171.3 MB Tags: ubuntu:12.10, ubuntu:quantal
|
||||
│ │─e18d8001204e Virtual Size: 171.3 MB
|
||||
│ │ └─d0525208a46c Virtual Size: 171.3 MB
|
||||
│ │ └─59dac4bae93b Virtual Size: 242.5 MB
|
||||
│ │ └─89541b3b35f2 Virtual Size: 511.8 MB
|
||||
│ │ └─7dac4e98548e Virtual Size: 511.8 MB
|
||||
│ │ └─341d0cc3fac8 Virtual Size: 511.8 MB
|
||||
│ │ └─2f96171d2098 Virtual Size: 511.8 MB
|
||||
│ │ └─67b8b7262a67 Virtual Size: 513.7 MB
|
||||
│ │ └─0fe9a2bc50fe Virtual Size: 513.7 MB
|
||||
│ │ └─8c32832f07ba Virtual Size: 513.7 MB
|
||||
│ │ └─cc4e1358bc80 Virtual Size: 513.7 MB
|
||||
│ │ └─5c0d04fba9df Virtual Size: 513.7 MB Tags: nate/mongodb:latest
|
||||
│ └─398d592f2009 Virtual Size: 242.2 MB
|
||||
│ └─0cd8e7f50270 Virtual Size: 243.6 MB
|
||||
│ └─594b6f8e6f92 Virtual Size: 243.6 MB
|
||||
│ └─f832a63e87a4 Virtual Size: 243.6 MB Tags: redis:latest
|
||||
└─02dae1c13f51 Virtual Size: 98.3 MB
|
||||
└─e7206bfc66aa Virtual Size: 98.5 MB
|
||||
└─cb12405ee8fa Virtual Size: 98.5 MB
|
||||
└─316b678ddf48 Virtual Size: 169.4 MB Tags: ubuntu:13.04, ubuntu:raring
|
||||
```
|
||||
```
|
||||
$ dockviz images -t -l
|
||||
└─511136ea3c5a Virtual Size: 0.0 B
|
||||
├─f10ebce2c0e1 Virtual Size: 103.7 MB
|
||||
│ └─74fe38d11401 Virtual Size: 209.6 MB Tags: ubuntu:12.04, ubuntu:precise
|
||||
├─ef519c9ee91a Virtual Size: 100.9 MB
|
||||
│ └─a7cf8ae4e998 Virtual Size: 171.3 MB Tags: ubuntu:12.10, ubuntu:quantal
|
||||
│ ├─5c0d04fba9df Virtual Size: 513.7 MB Tags: nate/mongodb:latest
|
||||
│ └─f832a63e87a4 Virtual Size: 243.6 MB Tags: redis:latest
|
||||
└─02dae1c13f51 Virtual Size: 98.3 MB
|
||||
└─316b678ddf48 Virtual Size: 169.4 MB Tags: ubuntu:13.04, ubuntu:raring
|
||||
```
|
||||
|
||||
# Running
|
||||
|
||||
|
|
251
images.go
251
images.go
|
@ -21,16 +21,16 @@ type Image struct {
|
|||
}
|
||||
|
||||
type ImagesCommand struct {
|
||||
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."`
|
||||
Short bool `short:"s" long:"short" description:"Show short summary of images (repo name and list of tags)."`
|
||||
NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the image IDs."`
|
||||
Dot bool `short:"d" long:"dot" description:"Show image information as Graphviz dot. You can add a start image id or name -d/--dot [id/name]"`
|
||||
Tree bool `short:"t" long:"tree" description:"Show image information as tree. You can add a start image id or name -t/--tree [id/name]"`
|
||||
Short bool `short:"s" long:"short" description:"Show short summary of images (repo name and list of tags)."`
|
||||
NoTruncate bool `short:"n" long:"no-trunc" description:"Don't truncate the image IDs."`
|
||||
OnlyLabelled bool `short:"l" long:"only-labelled" description:"Print only labelled images/containers."`
|
||||
}
|
||||
|
||||
var imagesCommand ImagesCommand
|
||||
|
||||
func (x *ImagesCommand) Execute(args []string) error {
|
||||
|
||||
var images *[]Image
|
||||
|
||||
stat, err := os.Stdin.Stat()
|
||||
|
@ -82,50 +82,40 @@ func (x *ImagesCommand) Execute(args []string) error {
|
|||
images = &ims
|
||||
}
|
||||
|
||||
if imagesCommand.Dot {
|
||||
fmt.Printf(jsonToDot(images))
|
||||
} else if imagesCommand.Tree {
|
||||
|
||||
var startImage = ""
|
||||
if imagesCommand.Tree || imagesCommand.Dot {
|
||||
var startImage *Image
|
||||
if len(args) > 0 {
|
||||
startImage, err = findStartImage(args[0], images)
|
||||
|
||||
// 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 = image.Id
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(jsonToTree(images, startImage, imagesCommand.NoTruncate))
|
||||
// select the start image of the tree
|
||||
var roots []Image
|
||||
if startImage == nil {
|
||||
roots = collectRoots(images)
|
||||
} else {
|
||||
startImage.ParentId = ""
|
||||
roots = []Image{*startImage}
|
||||
}
|
||||
|
||||
// build helper map (image -> children)
|
||||
imagesByParent := collectChildren(images)
|
||||
|
||||
// filter images
|
||||
if imagesCommand.OnlyLabelled {
|
||||
*images, imagesByParent = filterImages(images, &imagesByParent)
|
||||
}
|
||||
|
||||
if imagesCommand.Tree {
|
||||
fmt.Print(jsonToTree(imagesCommand.NoTruncate, roots, imagesByParent))
|
||||
}
|
||||
if imagesCommand.Dot {
|
||||
fmt.Print(jsonToDot(roots, imagesByParent))
|
||||
}
|
||||
|
||||
} else if imagesCommand.Short {
|
||||
fmt.Printf(jsonToShort(images))
|
||||
} else {
|
||||
|
@ -135,67 +125,137 @@ func (x *ImagesCommand) Execute(args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func jsonToTree(images *[]Image, startImageArg string, noTrunc bool) string {
|
||||
func findStartImage(name string, images *[]Image) (*Image, error) {
|
||||
|
||||
var startImage *Image
|
||||
|
||||
// attempt to find the start image, which can be specified as an
|
||||
// image ID or a repository name
|
||||
startImageArg := name
|
||||
startImageRepo := name
|
||||
|
||||
// if tag is not defined, find by :latest tag
|
||||
if strings.Index(startImageRepo, ":") == -1 {
|
||||
startImageRepo = fmt.Sprintf("%s:latest", startImageRepo)
|
||||
}
|
||||
|
||||
IMAGES:
|
||||
for _, image := range *images {
|
||||
// find by image id
|
||||
if strings.Index(image.Id, startImageArg) == 0 {
|
||||
startImage = &image
|
||||
break IMAGES
|
||||
}
|
||||
|
||||
// find by image name (name and tag)
|
||||
for _, repotag := range image.RepoTags {
|
||||
if repotag == startImageRepo {
|
||||
startImage = &image
|
||||
break IMAGES
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if startImage == nil {
|
||||
return nil, fmt.Errorf("Unable to find image %s = %s.", startImageArg, startImageRepo)
|
||||
}
|
||||
|
||||
return startImage, nil
|
||||
}
|
||||
|
||||
func jsonToTree(noTrunc bool, images []Image, byParent map[string][]Image) 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, "")
|
||||
}
|
||||
jsonToText(&buffer, noTrunc, images, 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)
|
||||
func jsonToDot(roots []Image, byParent map[string][]Image) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString("digraph docker {\n")
|
||||
imagesToDot(&buffer, roots, byParent)
|
||||
buffer.WriteString(" base [style=invisible]\n}\n")
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func collectChildren(images *[]Image) map[string][]Image {
|
||||
var imagesByParent = make(map[string][]Image)
|
||||
for _, image := range *images {
|
||||
if children, exists := imagesByParent[image.ParentId]; exists {
|
||||
imagesByParent[image.ParentId] = append(children, image)
|
||||
} else {
|
||||
imagesByParent[image.ParentId] = []Image{image}
|
||||
}
|
||||
}
|
||||
|
||||
return imagesByParent
|
||||
}
|
||||
|
||||
func collectRoots(images *[]Image) []Image {
|
||||
var roots []Image
|
||||
for _, image := range *images {
|
||||
if image.ParentId == "" {
|
||||
roots = append(roots, image)
|
||||
}
|
||||
}
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
func filterImages(images *[]Image, byParent *map[string][]Image) (filteredImages []Image, filteredChildren map[string][]Image) {
|
||||
for i := 0; i < len(*images); i++ {
|
||||
// image is visible
|
||||
// 1. it has a label
|
||||
// 2. it is root
|
||||
// 3. it is a node
|
||||
var visible bool = (*images)[i].RepoTags[0] != "<none>:<none>" || (*images)[i].ParentId == "" || len((*byParent)[(*images)[i].Id]) > 1
|
||||
if visible {
|
||||
filteredImages = append(filteredImages, (*images)[i])
|
||||
} else {
|
||||
// change childs parent id
|
||||
// if items are filtered with only one child
|
||||
for j := 0; j < len(filteredImages); j++ {
|
||||
if filteredImages[j].ParentId == (*images)[i].Id {
|
||||
filteredImages[j].ParentId = (*images)[i].ParentId
|
||||
}
|
||||
}
|
||||
for j := 0; j < len(*images); j++ {
|
||||
if (*images)[j].ParentId == (*images)[i].Id {
|
||||
(*images)[j].ParentId = (*images)[i].ParentId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filteredChildren = collectChildren(&filteredImages)
|
||||
|
||||
return filteredImages, filteredChildren
|
||||
}
|
||||
|
||||
func jsonToText(buffer *bytes.Buffer, noTrunc bool, images []Image, byParent map[string][]Image, prefix string) {
|
||||
var length = len(images)
|
||||
if length > 1 {
|
||||
for index, image := range images {
|
||||
var nextPrefix string = ""
|
||||
if index+1 == length {
|
||||
PrintTreeNode(buffer, noTrunc, image, prefix+"└─")
|
||||
if subimages, exists := byParent[image.Id]; exists {
|
||||
WalkTree(buffer, noTrunc, subimages, byParent, prefix+" ")
|
||||
}
|
||||
nextPrefix = " "
|
||||
} else {
|
||||
PrintTreeNode(buffer, noTrunc, image, prefix+"├─")
|
||||
if subimages, exists := byParent[image.Id]; exists {
|
||||
WalkTree(buffer, noTrunc, subimages, byParent, prefix+"│ ")
|
||||
}
|
||||
nextPrefix = "│ "
|
||||
}
|
||||
if subimages, exists := byParent[image.Id]; exists {
|
||||
jsonToText(buffer, noTrunc, subimages, byParent, prefix+nextPrefix)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, image := range images {
|
||||
PrintTreeNode(buffer, noTrunc, image, prefix+"└─")
|
||||
if subimages, exists := byParent[image.Id]; exists {
|
||||
WalkTree(buffer, noTrunc, subimages, byParent, prefix+" ")
|
||||
jsonToText(buffer, noTrunc, subimages, byParent, prefix+" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -251,12 +311,8 @@ func parseImagesJSON(rawJSON []byte) (*[]Image, error) {
|
|||
return &images, nil
|
||||
}
|
||||
|
||||
func jsonToDot(images *[]Image) string {
|
||||
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString("digraph docker {\n")
|
||||
|
||||
for _, image := range *images {
|
||||
func imagesToDot(buffer *bytes.Buffer, images []Image, byParent map[string][]Image) {
|
||||
for _, image := range images {
|
||||
if image.ParentId == "" {
|
||||
buffer.WriteString(fmt.Sprintf(" base -> \"%s\" [style=invis]\n", truncate(image.Id)))
|
||||
} else {
|
||||
|
@ -265,11 +321,10 @@ func jsonToDot(images *[]Image) string {
|
|||
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")))
|
||||
}
|
||||
if subimages, exists := byParent[image.Id]; exists {
|
||||
imagesToDot(buffer, subimages, byParent)
|
||||
}
|
||||
}
|
||||
|
||||
buffer.WriteString(" base [style=invisible]\n}\n")
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func jsonToShort(images *[]Image) string {
|
||||
|
|
BIN
images_only_labeled.png
Normal file
BIN
images_only_labeled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -56,7 +56,12 @@ func Test_Dot(t *testing.T) {
|
|||
|
||||
for _, dotTest := range dotTests {
|
||||
im, _ := parseImagesJSON([]byte(dotTest.json))
|
||||
result := jsonToDot(im)
|
||||
byParent := collectChildren(im)
|
||||
roots := collectRoots(im)
|
||||
|
||||
// TODO: test start image limiting
|
||||
|
||||
result := jsonToDot(roots, byParent)
|
||||
|
||||
for _, regexp := range allRegex {
|
||||
if !regexp.MatchString(result) {
|
||||
|
@ -109,7 +114,16 @@ func Test_Tree(t *testing.T) {
|
|||
|
||||
for _, treeTest := range treeTests {
|
||||
im, _ := parseImagesJSON([]byte(treeTest.json))
|
||||
result := jsonToTree(im, treeTest.startImage, treeTest.noTrunc)
|
||||
byParent := collectChildren(im)
|
||||
var roots []Image
|
||||
if len(treeTest.startImage) > 0 {
|
||||
startImage, _ := findStartImage(treeTest.startImage, im)
|
||||
startImage.ParentId = ""
|
||||
roots = []Image{*startImage}
|
||||
} else {
|
||||
roots = collectRoots(im)
|
||||
}
|
||||
result := jsonToTree(treeTest.noTrunc, roots, byParent)
|
||||
|
||||
for _, regexp := range compileRegexps(t, treeTest.regexps) {
|
||||
if !regexp.MatchString(result) {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 88 KiB |
BIN
sample/images_only_labeled.png
Normal file
BIN
sample/images_only_labeled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Loading…
Reference in a new issue