implement --tree

This commit is contained in:
Nate Jones 2014-05-04 14:22:25 -07:00
parent a009c9ab2a
commit 2e787004ab
2 changed files with 147 additions and 7 deletions

113
images.go
View file

@ -19,8 +19,9 @@ type Image struct {
} }
type ImagesCommand struct { type ImagesCommand struct {
Dot bool `short:"d" long:"dot" description:"Show image information as Graphviz dot."` 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."` Tree bool `short:"t" long:"tree" description:"Show image information as tree."`
NoTruncate bool `short:"n" long:"notrunc" description:"Don't truncate the image IDs."`
} }
var imagesCommand ImagesCommand var imagesCommand ImagesCommand
@ -38,12 +39,118 @@ func (x *ImagesCommand) Execute(args []string) error {
if imagesCommand.Dot { if imagesCommand.Dot {
fmt.Printf(jsonToDot(images)) fmt.Printf(jsonToDot(images))
} else if imagesCommand.Tree { } else if imagesCommand.Tree {
fmt.Println("Tree output not implemented yet.")
var startImageArg = ""
if len(args) > 0 {
startImageArg = args[0]
}
fmt.Printf(jsonToTree(images, startImageArg, imagesCommand.NoTruncate))
} }
return nil return nil
} }
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 {
PrintTreeNode(buffer, noTrunc, image, prefix+"|─")
if subimages, exists := byParent[image.Id]; exists {
WalkTree(buffer, noTrunc, subimages, byParent, prefix+"| ")
}
}
}
} 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])
}
func truncate(id string) string { func truncate(id string) string {
return id[0:12] return id[0:12]
} }

View file

@ -5,11 +5,18 @@ import (
"testing" "testing"
) )
type RunTest struct { type DotTest struct {
json string json string
regexps []string regexps []string
} }
type TreeTest struct {
json string
startImage string
noTrunc bool
regexps []string
}
func Test_BadJSON(t *testing.T) { func Test_BadJSON(t *testing.T) {
_, err := parseJSON([]byte(` "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`)) _, err := parseJSON([]byte(` "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`))
@ -25,14 +32,14 @@ func Test_Dot(t *testing.T) {
} }
allRegex := compileRegexps(t, allMatch) allRegex := compileRegexps(t, allMatch)
dotTests := []RunTest{ dotTests := []DotTest{
RunTest{ DotTest{
json: `[{ "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`, json: `[{ "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`,
regexps: []string{ regexps: []string{
`base -> "4c1208b690c6"`, `base -> "4c1208b690c6"`,
}, },
}, },
RunTest{ DotTest{
json: `[{ "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "foo:latest" ], "ParentId": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Id": "c87be8e5e697c735f5db5626147582d2ae3f2088574c5faaf8d4d1bccab99470", "Created": 1386142123 },{ "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "<none>:<none>" ], "ParentId": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Id": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Created": 1386142123 },{ "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`, json: `[{ "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "foo:latest" ], "ParentId": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Id": "c87be8e5e697c735f5db5626147582d2ae3f2088574c5faaf8d4d1bccab99470", "Created": 1386142123 },{ "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "<none>:<none>" ], "ParentId": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Id": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Created": 1386142123 },{ "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`,
regexps: []string{ regexps: []string{
`base -> "4c1208b690c6"`, `base -> "4c1208b690c6"`,
@ -60,6 +67,32 @@ func Test_Dot(t *testing.T) {
} }
} }
func Test_Tree(t *testing.T) {
treeTests := []TreeTest{
TreeTest{
json: `[{ "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "foo:latest" ], "ParentId": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Id": "c87be8e5e697c735f5db5626147582d2ae3f2088574c5faaf8d4d1bccab99470", "Created": 1386142123 },{ "VirtualSize": 662553464, "Size": 0, "RepoTags": [ "<none>:<none>" ], "ParentId": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Id": "735f5db5626147582d2ae3f2c87be8e5e697c088574c5faaf8d4d1bccab99470", "Created": 1386142123 },{ "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`,
startImage: "",
noTrunc: false,
regexps: []string{
`(?m)└─4c1208b690c6`,
`(?m) └─735f5db56261`,
`(?m) └─c87be8e5e697`,
},
},
}
for _, treeTest := range treeTests {
im, _ := parseJSON([]byte(treeTest.json))
result := jsonToTree(im, treeTest.startImage, treeTest.noTrunc)
for _, regexp := range compileRegexps(t, treeTest.regexps) {
if !regexp.MatchString(result) {
t.Fatalf("images tree content '%s' did not match regexp '%s'", result, regexp)
}
}
}
}
func compileRegexps(t *testing.T, regexpStrings []string) []*regexp.Regexp { func compileRegexps(t *testing.T, regexpStrings []string) []*regexp.Regexp {
compiledRegexps := []*regexp.Regexp{} compiledRegexps := []*regexp.Regexp{}