package libimage import ( "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(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.layerTree() 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 assemlbe 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 }