implement --tree
This commit is contained in:
parent
a009c9ab2a
commit
2e787004ab
2 changed files with 147 additions and 7 deletions
113
images.go
113
images.go
|
@ -19,8 +19,9 @@ 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."`
|
||||
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."`
|
||||
NoTruncate bool `short:"n" long:"notrunc" description:"Don't truncate the image IDs."`
|
||||
}
|
||||
|
||||
var imagesCommand ImagesCommand
|
||||
|
@ -38,12 +39,118 @@ func (x *ImagesCommand) Execute(args []string) error {
|
|||
if imagesCommand.Dot {
|
||||
fmt.Printf(jsonToDot(images))
|
||||
} 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
|
||||
}
|
||||
|
||||
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 {
|
||||
return id[0:12]
|
||||
}
|
||||
|
|
|
@ -5,11 +5,18 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
type RunTest struct {
|
||||
type DotTest struct {
|
||||
json string
|
||||
regexps []string
|
||||
}
|
||||
|
||||
type TreeTest struct {
|
||||
json string
|
||||
startImage string
|
||||
noTrunc bool
|
||||
regexps []string
|
||||
}
|
||||
|
||||
func Test_BadJSON(t *testing.T) {
|
||||
_, 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)
|
||||
|
||||
dotTests := []RunTest{
|
||||
RunTest{
|
||||
dotTests := []DotTest{
|
||||
DotTest{
|
||||
json: `[{ "VirtualSize": 662553464, "Size": 662553464, "RepoTags": [ "<none>:<none>" ], "ParentId": "", "Id": "4c1208b690c68af3476b437e7bc2bcc460f062bda2094d2d8f21a7e70368d358", "Created": 1386114144 }]`,
|
||||
regexps: []string{
|
||||
`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 }]`,
|
||||
regexps: []string{
|
||||
`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 {
|
||||
|
||||
compiledRegexps := []*regexp.Regexp{}
|
||||
|
|
Loading…
Reference in a new issue