summaryrefslogtreecommitdiff
path: root/cmd/podman/tree.go
blob: ebda18cdb9098a9bb2f83878ca6b3c8a4891ba7b (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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package main

import (
	"context"
	"fmt"

	"github.com/containers/libpod/cmd/podman/cliconfig"
	"github.com/containers/libpod/cmd/podman/libpodruntime"
	"github.com/containers/libpod/libpod/image"
	units "github.com/docker/go-units"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

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

var (
	treeCommand cliconfig.TreeValues

	treeDescription = "Prints layer hierarchy of an image in a tree format"
	_treeCommand    = &cobra.Command{
		Use:   "tree",
		Short: treeDescription,
		Long:  treeDescription,
		RunE: func(cmd *cobra.Command, args []string) error {
			treeCommand.InputArgs = args
			treeCommand.GlobalFlags = MainGlobalOpts
			return treeCmd(&treeCommand)
		},
		Example: "podman image tree alpine:latest",
	}
)

func init() {
	treeCommand.Command = _treeCommand
	treeCommand.SetUsageTemplate(UsageTemplate())
	treeCommand.Flags().BoolVar(&treeCommand.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image")
}

// infoImage keep information of Image along with all associated layers
type infoImage struct {
	// id of image
	id string
	// tags of image
	tags []string
	// layers stores all layers of image.
	layers []image.LayerInfo
}

func treeCmd(c *cliconfig.TreeValues) error {
	args := c.InputArgs
	if len(args) == 0 {
		return errors.Errorf("an image name must be specified")
	}
	if len(args) > 1 {
		return errors.Errorf("you must provide at most 1 argument")
	}

	runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
	if err != nil {
		return errors.Wrapf(err, "error creating libpod runtime")
	}
	defer runtime.Shutdown(false)

	img, err := runtime.ImageRuntime().NewFromLocal(args[0])
	if err != nil {
		return err
	}

	// Fetch map of image-layers, which is used for printing output.
	layerInfoMap, err := image.GetLayersMapWithImageInfo(runtime.ImageRuntime())
	if err != nil {
		return errors.Wrapf(err, "error while retriving layers of image %q", img.InputName)
	}

	// Create an imageInfo and fill the image and layer info
	imageInfo := &infoImage{
		id:   img.ID(),
		tags: img.Names(),
	}

	size, err := img.Size(context.Background())
	if err != nil {
		return errors.Wrapf(err, "error while retriving image size")
	}
	fmt.Printf("Image ID: %s\n", imageInfo.id[:12])
	fmt.Printf("Tags:\t %s\n", imageInfo.tags)
	fmt.Printf("Size:\t %v\n", units.HumanSizeWithPrecision(float64(*size), 4))
	fmt.Printf(fmt.Sprintf("Image Layers\n"))

	if !c.WhatRequires {
		// fill imageInfo with layers associated with image.
		// the layers will be filled such that
		// (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
		err := buildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer())
		if err != nil {
			return err
		}
		// Build output from imageInfo into buffer
		printImageHierarchy(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)
		err := printImageChildren(layerInfoMap, img.TopLayer(), "", true)
		if err != nil {
			return err
		}
	}

	return nil
}

// Stores hierarchy of images such that all parent layers using which image is built are stored in imageInfo
// Layers are added such that  (Start)RootLayer->...intermediate Parent Layer(s)-> TopLayer(End)
func buildImageHierarchyMap(imageInfo *infoImage, layerMap map[string]*image.LayerInfo, layerID string) error {
	if layerID == "" {
		return nil
	}
	ll, ok := layerMap[layerID]
	if !ok {
		return fmt.Errorf("lookup error: layerid  %s not found", layerID)
	}
	if err := buildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil {
		return err
	}

	imageInfo.layers = append(imageInfo.layers, *ll)
	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 printImageChildren(layerMap map[string]*image.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.Printf(prefix)

	//initialize intend with middleItem to reduce middleItem checks.
	intend := middleItem
	if !last {
		// add continueItem i.e. '|' for next iteration prefix
		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 childern.
		// If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├──
		intend = lastItem
		prefix = prefix + " "
	}

	var tags string
	if len(ll.RepoTags) > 0 {
		tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags)
	}
	fmt.Printf("%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 := printImageChildren(layerMap, childID, prefix, (count == len(ll.ChildID)-1)); err != nil {
			return err
		}
	}
	return nil
}

// prints the layers info of image
func 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.Printf("%s ID: %s Size: %7v%s\n", intend, l.ID[:12], units.HumanSizeWithPrecision(float64(l.Size), 4), tags)
	}
}