Skip to content

Commit

Permalink
Refactor image tree for API usage
Browse files Browse the repository at this point in the history
Signed-off-by: Sascha Grunert <sgrunert@suse.com>
  • Loading branch information
saschagrunert committed Feb 14, 2020
1 parent fb8c494 commit 0f5a902
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 171 deletions.
14 changes: 14 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in

[func ImageSave(options: ImageSaveOptions) MoreResponse](#ImageSave)

[func ImageTree(name: string, whatRequires: bool) string](#ImageTree)

[func ImagesPrune(all: bool, filter: []string) []string](#ImagesPrune)

[func ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) string](#ImportImage)
Expand Down Expand Up @@ -775,6 +777,18 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ImageExists '{"name": "im

method ImageSave(options: [ImageSaveOptions](#ImageSaveOptions)) [MoreResponse](#MoreResponse)</div>
ImageSave allows you to save an image from the local image storage to a tarball
### <a name="ImageTree"></a>func ImageTree
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">

method ImageTree(name: [string](https://godoc.org/builtin#string), whatRequires: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
ImageTree returns the image tree for the provided image name or ID
#### Example
~~~
$ varlink call -m unix:/run/podman/io.podman/io.podman.ImageTree '{"name": "alpine"}'
{
"tree": "Image ID: e7d92cdc71fe\nTags: [docker.io/library/alpine:latest]\nSize: 5.861MB\nImage Layers\n└── ID: 5216338b40a7 Size: 5.857MB Top Layer of: [docker.io/library/alpine:latest]\n"
}
~~~
### <a name="ImagesPrune"></a>func ImagesPrune
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">

Expand Down
97 changes: 2 additions & 95 deletions cmd/podman/tree.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
package main

import (
"context"
"fmt"

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

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

var (
treeCommand cliconfig.TreeValues

Expand Down Expand Up @@ -56,95 +47,11 @@ func treeCmd(c *cliconfig.TreeValues) error {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.DeferredShutdown(false)
imageInfo, layerInfoMap, img, err := runtime.Tree(c.InputArgs[0])
if err != nil {
return err
}
return printTree(imageInfo, layerInfoMap, img, c.WhatRequires)
}

func printTree(imageInfo *image.InfoImage, layerInfoMap map[string]*image.LayerInfo, img *adapter.ContainerImage, whatRequires bool) error {
size, err := img.Size(context.Background())
tree, err := runtime.ImageTree(c.InputArgs[0], c.WhatRequires)
if err != nil {
return err
}

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))
if img.TopLayer() != "" {
fmt.Printf("Image Layers\n")
} else {
fmt.Printf("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
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)
return printImageChildren(layerInfoMap, 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 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.Print(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.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
}
}
fmt.Printf(tree)
return nil
}

// prints the layers info of image
func printImageHierarchy(imageInfo *image.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)
}
}
10 changes: 10 additions & 0 deletions cmd/podman/varlink/io.podman.varlink
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,16 @@ method GetPodsByStatus(statuses: []string) -> (pods: []string)
# ~~~
method ImageExists(name: string) -> (exists: int)

# ImageTree returns the image tree for the provided image name or ID
# #### Example
# ~~~
# $ varlink call -m unix:/run/podman/io.podman/io.podman.ImageTree '{"name": "foobar"}'
# {
# "pod": "1840835294cf076a822e4e12ba4152411f131bd869e7f6a4e8b16df9b0ea5c7f"
# }
# ~~~
method ImageTree(name: string, whatRequires: bool) -> (tree: string)

# ContainerExists takes a full or partial container ID or name and returns an int as to
# whether the container exists in local storage. A result of 0 means the container does
# exists; whereas a result of 1 means it could not be found.
Expand Down
138 changes: 138 additions & 0 deletions libpod/image/tree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,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
}

// NewTree creates an image tree string representation for displaying it to the
// user.
func (i *Image) NewTree(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)
}
}
33 changes: 0 additions & 33 deletions pkg/adapter/images.go

This file was deleted.

31 changes: 0 additions & 31 deletions pkg/adapter/images_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import (
"encoding/json"

iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/inspect"
"github.com/pkg/errors"
)

// Inspect returns returns an ImageData struct from over a varlink connection
Expand All @@ -24,32 +22,3 @@ func (i *ContainerImage) Inspect(ctx context.Context) (*inspect.ImageData, error
}
return &data, nil
}

// Tree ...
func (r *LocalRuntime) Tree(imageOrID string) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) {
layerInfoMap := make(map[string]*image.LayerInfo)
imageInfo := &image.InfoImage{}

img, err := r.NewImageFromLocal(imageOrID)
if err != nil {
return nil, nil, nil, err
}

reply, err := iopodman.GetLayersMapWithImageInfo().Call(r.Conn)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to obtain image layers")
}
if err := json.Unmarshal([]byte(reply), &layerInfoMap); err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to unmarshal image layers")
}

reply, err = iopodman.BuildImageHierarchyMap().Call(r.Conn, imageOrID)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to get build image map")
}
if err := json.Unmarshal([]byte(reply), imageInfo); err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to unmarshal build image map")
}

return imageInfo, layerInfoMap, img, nil
}
9 changes: 9 additions & 0 deletions pkg/adapter/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) {
return &ContainerImage{img}, nil
}

// ImageTree reutnrs an new image.Tree for the provided `imageOrID` and `whatrequires` flag
func (r *LocalRuntime) ImageTree(imageOrID string, whatRequires bool) (string, error) {
img, err := r.Runtime.ImageRuntime().NewFromLocal(imageOrID)
if err != nil {
return "", err
}
return img.NewTree(whatRequires)
}

// LoadFromArchiveReference calls into local storage to load an image from an archive
func (r *LocalRuntime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*ContainerImage, error) {
var containerImages []*ContainerImage
Expand Down
Loading

0 comments on commit 0f5a902

Please sign in to comment.