Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.Print(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": "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"
# }
# ~~~
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to somewhere in pkg/? It doesn't seem much like an API call, given it only returns a string.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to wait for @mtrmac's new package. We may be able to integrate it there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "wait" I mean we could merge it as is now and let Miloslav pick it up in his new package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. It's just libpod/image, not libpod itself, so sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely don’t wait for me, I’m still mostly in learning phase, and I can adjust for whatever happens here.

(Jumping into what I’m sure was a well-considered decision, I’d instinctively prefer to primarily have a well-typed API that returns the structure, and keep the text formatter in the CLI (allowing, at least in principle, a nice GUI to be built on top, for example). OTOH, I guess that exposing the complete tree structure over Varlink/whatever would be a lot of work and pain — and for no benefit at least now that the only consumer is the CLI.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's my big hold-up as well - I'd love to return a data structure that represents the tree, with a .String() that returns the output. I'm willing to defer that to followup work, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Honestly, though, I'd be fine keeping the tree local-only, and exposing only a literal output string over the API. There's precedence for this with things like podman top)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Or was it podman stats... I forget which)


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)
}
}
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.GenerateTree(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