summaryrefslogtreecommitdiff
path: root/libpod/image/tree.go
blob: c7c69462ff471f2a65374f312b5d7439d5c19196 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package image

import (
	"context"
	"fmt"
	"strings"

	"github.com/docker/go-units"
	"github.com/pkg/errors"
)

const (
	middleItem   = "├── "
	continueItem = "│   "
	lastItem     = "└── "
)

type tree struct {
	img       *Image
	imageInfo *InfoImage
	layerInfo map[string]*LayerInfo
	sb        *strings.Builder
}

// GenerateTree creates an image tree string representation for displaying it
// to the user.
func (i *Image) GenerateTree(whatRequires bool) (string, error) {
	// Fetch map of image-layers, which is used for printing output.
	layerInfo, err := GetLayersMapWithImageInfo(i.imageruntime)
	if err != nil {
		return "", errors.Wrapf(err, "error while retrieving layers of image %q", i.InputName)
	}

	// Create an imageInfo and fill the image and layer info
	imageInfo := &InfoImage{
		ID:   i.ID(),
		Tags: i.Names(),
	}

	if err := BuildImageHierarchyMap(imageInfo, layerInfo, i.TopLayer()); err != nil {
		return "", err
	}
	sb := &strings.Builder{}
	tree := &tree{i, imageInfo, layerInfo, sb}
	if err := tree.print(whatRequires); err != nil {
		return "", err
	}
	return tree.string(), nil
}

func (t *tree) string() string {
	return t.sb.String()
}

func (t *tree) print(whatRequires bool) error {
	size, err := t.img.Size(context.Background())
	if err != nil {
		return err
	}

	fmt.Fprintf(t.sb, "Image ID: %s\n", t.imageInfo.ID[:12])
	fmt.Fprintf(t.sb, "Tags:     %s\n", t.imageInfo.Tags)
	fmt.Fprintf(t.sb, "Size:     %v\n", units.HumanSizeWithPrecision(float64(*size), 4))
	if t.img.TopLayer() != "" {
		fmt.Fprintf(t.sb, "Image Layers\n")
	} else {
		fmt.Fprintf(t.sb, "No Image Layers\n")
	}

	if !whatRequires {
		// fill imageInfo with layers associated with image.
		// the layers will be filled such that
		// (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
		// Build output from imageInfo into buffer
		t.printImageHierarchy(t.imageInfo)
	} else {
		// fill imageInfo with layers associated with image.
		// the layers will be filled such that
		// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
		//     (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
		return t.printImageChildren(t.layerInfo, t.img.TopLayer(), "", true)
	}
	return nil
}

// Stores all children layers which are created using given Image.
// Layers are stored as follows
// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End)
//             (Forks)... intermediate Child Layer(s) -> Child Top Layer(End)
func (t *tree) printImageChildren(layerMap map[string]*LayerInfo, layerID string, prefix string, last bool) error {
	if layerID == "" {
		return nil
	}
	ll, ok := layerMap[layerID]
	if !ok {
		return fmt.Errorf("lookup error: layerid  %s, not found", layerID)
	}
	fmt.Fprint(t.sb, prefix)

	//initialize intend with middleItem to reduce middleItem checks.
	intend := middleItem
	if !last {
		// add continueItem i.e. '|' for next iteration prefix
		prefix += continueItem
	} else if len(ll.ChildID) > 1 || len(ll.ChildID) == 0 {
		// The above condition ensure, alignment happens for node, which has more then 1 children.
		// If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├──
		intend = lastItem
		prefix += " "
	}

	var tags string
	if len(ll.RepoTags) > 0 {
		tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags)
	}
	fmt.Fprintf(t.sb, "%sID: %s Size: %7v%s\n", intend, ll.ID[:12], units.HumanSizeWithPrecision(float64(ll.Size), 4), tags)
	for count, childID := range ll.ChildID {
		if err := t.printImageChildren(layerMap, childID, prefix, count == len(ll.ChildID)-1); err != nil {
			return err
		}
	}
	return nil
}

// prints the layers info of image
func (t *tree) printImageHierarchy(imageInfo *InfoImage) {
	for count, l := range imageInfo.Layers {
		var tags string
		intend := middleItem
		if len(l.RepoTags) > 0 {
			tags = fmt.Sprintf(" Top Layer of: %s", l.RepoTags)
		}
		if count == len(imageInfo.Layers)-1 {
			intend = lastItem
		}
		fmt.Fprintf(t.sb, "%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags)
	}
}