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 }