automation-tests/common/libimage/image_tree.go

121 lines
3.1 KiB
Go

//go:build !remote
package libimage
import (
"context"
"fmt"
"strings"
"github.com/disiqueira/gotree/v3"
"github.com/docker/go-units"
)
// Tree generates a tree for the specified image and its layers. Use
// `traverseChildren` to traverse the layers of all children. By default, only
// layers of the image are printed.
func (i *Image) Tree(ctx context.Context, traverseChildren bool) (string, error) {
// NOTE: a string builder prevents us from copying to much data around
// and compile the string when and where needed.
sb := &strings.Builder{}
// First print the pretty header for the target image.
size, err := i.Size()
if err != nil {
return "", err
}
repoTags, err := i.RepoTags()
if err != nil {
return "", err
}
fmt.Fprintf(sb, "Image ID: %s\n", i.ID()[:12])
fmt.Fprintf(sb, "Tags: %s\n", repoTags)
fmt.Fprintf(sb, "Size: %v\n", units.HumanSizeWithPrecision(float64(size), 4))
if i.TopLayer() != "" {
fmt.Fprintf(sb, "Image Layers")
} else {
fmt.Fprintf(sb, "No Image Layers")
}
layerTree, err := i.runtime.newFreshLayerTree()
if err != nil {
return "", err
}
imageNode := layerTree.node(i.TopLayer())
// Traverse the entire tree down to all children.
if traverseChildren {
tree := gotree.New(sb.String())
if err := imageTreeTraverseChildren(imageNode, tree); err != nil {
return "", err
}
return tree.Print(), nil
}
// Walk all layers of the image and assemble their data. Note that the
// tree is constructed in reverse order to remain backwards compatible
// with Podman.
contents := []string{}
for parentNode := imageNode; parentNode != nil; parentNode = parentNode.parent {
if parentNode.layer == nil {
break // we're done
}
var tags string
repoTags, err := parentNode.repoTags()
if err != nil {
return "", err
}
if len(repoTags) > 0 {
tags = fmt.Sprintf(" Top Layer of: %s", repoTags)
}
content := fmt.Sprintf("ID: %s Size: %7v%s", parentNode.layer.ID[:12], units.HumanSizeWithPrecision(float64(parentNode.layer.UncompressedSize), 4), tags)
contents = append(contents, content)
}
contents = append(contents, sb.String())
tree := gotree.New(contents[len(contents)-1])
for i := len(contents) - 2; i >= 0; i-- {
tree.Add(contents[i])
}
return tree.Print(), nil
}
func imageTreeTraverseChildren(node *layerNode, parent gotree.Tree) error {
if node.layer == nil {
return nil
}
var tags string
repoTags, err := node.repoTags()
if err != nil {
return err
}
if len(repoTags) > 0 {
tags = fmt.Sprintf(" Top Layer of: %s", repoTags)
}
content := fmt.Sprintf("ID: %s Size: %7v%s", node.layer.ID[:12], units.HumanSizeWithPrecision(float64(node.layer.UncompressedSize), 4), tags)
var newTree gotree.Tree
if node.parent == nil || len(node.parent.children) <= 1 {
// No parent or no siblings, so we can go linear.
parent.Add(content)
newTree = parent
} else {
// Each siblings gets a new tree, so we can branch.
newTree = gotree.New(content)
parent.AddTree(newTree)
}
for i := range node.children {
child := node.children[i]
if err := imageTreeTraverseChildren(child, newTree); err != nil {
return err
}
}
return nil
}