191 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			191 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
| 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 [flags] IMAGE",
 | |
| 		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)
 | |
| 	}
 | |
| }
 |