github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/image/tree.go (about)

     1  package image
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/docker/go-units"
     9  	"github.com/pkg/errors"
    10  )
    11  
    12  const (
    13  	middleItem   = "├── "
    14  	continueItem = "│   "
    15  	lastItem     = "└── "
    16  )
    17  
    18  type tree struct {
    19  	img       *Image
    20  	imageInfo *InfoImage
    21  	layerInfo map[string]*LayerInfo
    22  	sb        *strings.Builder
    23  }
    24  
    25  // GenerateTree creates an image tree string representation for displaying it
    26  // to the user.
    27  func (i *Image) GenerateTree(whatRequires bool) (string, error) {
    28  	// Fetch map of image-layers, which is used for printing output.
    29  	layerInfo, err := GetLayersMapWithImageInfo(i.imageruntime)
    30  	if err != nil {
    31  		return "", errors.Wrapf(err, "error while retrieving layers of image %q", i.InputName)
    32  	}
    33  
    34  	// Create an imageInfo and fill the image and layer info
    35  	imageInfo := &InfoImage{
    36  		ID:   i.ID(),
    37  		Tags: i.Names(),
    38  	}
    39  
    40  	if err := BuildImageHierarchyMap(imageInfo, layerInfo, i.TopLayer()); err != nil {
    41  		return "", err
    42  	}
    43  	sb := &strings.Builder{}
    44  	tree := &tree{i, imageInfo, layerInfo, sb}
    45  	if err := tree.print(whatRequires); err != nil {
    46  		return "", err
    47  	}
    48  	return tree.string(), nil
    49  }
    50  
    51  func (t *tree) string() string {
    52  	return t.sb.String()
    53  }
    54  
    55  func (t *tree) print(whatRequires bool) error {
    56  	size, err := t.img.Size(context.Background())
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	fmt.Fprintf(t.sb, "Image ID: %s\n", t.imageInfo.ID[:12])
    62  	fmt.Fprintf(t.sb, "Tags:     %s\n", t.imageInfo.Tags)
    63  	fmt.Fprintf(t.sb, "Size:     %v\n", units.HumanSizeWithPrecision(float64(*size), 4))
    64  	if t.img.TopLayer() != "" {
    65  		fmt.Fprintf(t.sb, "Image Layers\n")
    66  	} else {
    67  		fmt.Fprintf(t.sb, "No Image Layers\n")
    68  	}
    69  
    70  	if !whatRequires {
    71  		// fill imageInfo with layers associated with image.
    72  		// the layers will be filled such that
    73  		// (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
    74  		// Build output from imageInfo into buffer
    75  		t.printImageHierarchy(t.imageInfo)
    76  	} else {
    77  		// fill imageInfo with layers associated with image.
    78  		// the layers will be filled such that
    79  		// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
    80  		//     (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
    81  		return t.printImageChildren(t.layerInfo, t.img.TopLayer(), "", true)
    82  	}
    83  	return nil
    84  }
    85  
    86  // Stores all children layers which are created using given Image.
    87  // Layers are stored as follows
    88  // (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
    89  //             (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
    90  func (t *tree) printImageChildren(layerMap map[string]*LayerInfo, layerID string, prefix string, last bool) error {
    91  	if layerID == "" {
    92  		return nil
    93  	}
    94  	ll, ok := layerMap[layerID]
    95  	if !ok {
    96  		return fmt.Errorf("lookup error: layerid  %s, not found", layerID)
    97  	}
    98  	fmt.Fprint(t.sb, prefix)
    99  
   100  	//initialize intend with middleItem to reduce middleItem checks.
   101  	intend := middleItem
   102  	if !last {
   103  		// add continueItem i.e. '|' for next iteration prefix
   104  		prefix += continueItem
   105  	} else if len(ll.ChildID) > 1 || len(ll.ChildID) == 0 {
   106  		// The above condition ensure, alignment happens for node, which has more then 1 children.
   107  		// If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├──
   108  		intend = lastItem
   109  		prefix += " "
   110  	}
   111  
   112  	var tags string
   113  	if len(ll.RepoTags) > 0 {
   114  		tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags)
   115  	}
   116  	fmt.Fprintf(t.sb, "%sID: %s Size: %7v%s\n", intend, ll.ID[:12], units.HumanSizeWithPrecision(float64(ll.Size), 4), tags)
   117  	for count, childID := range ll.ChildID {
   118  		if err := t.printImageChildren(layerMap, childID, prefix, count == len(ll.ChildID)-1); err != nil {
   119  			return err
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  // prints the layers info of image
   126  func (t *tree) printImageHierarchy(imageInfo *InfoImage) {
   127  	for count, l := range imageInfo.Layers {
   128  		var tags string
   129  		intend := middleItem
   130  		if len(l.RepoTags) > 0 {
   131  			tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags)
   132  		}
   133  		if count == len(imageInfo.Layers)-1 {
   134  			intend = lastItem
   135  		}
   136  		fmt.Fprintf(t.sb, "%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags)
   137  	}
   138  }