implement --tree
This commit is contained in:
parent
a009c9ab2a
commit
2e787004ab
2 changed files with 147 additions and 7 deletions
109
images.go
109
images.go
|
@ -21,6 +21,7 @@ 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]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{}
|
||||||
|
|
Loading…
Reference in a new issue