diff --git a/API.md b/API.md
index cfbe394de2..2460de09fd 100644
--- a/API.md
+++ b/API.md
@@ -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)
@@ -775,6 +777,18 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ImageExists '{"name": "im
method ImageSave(options: [ImageSaveOptions](#ImageSaveOptions)) [MoreResponse](#MoreResponse)
ImageSave allows you to save an image from the local image storage to a tarball
+### func ImageTree
+
+
+method ImageTree(name: [string](https://godoc.org/builtin#string), whatRequires: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)
+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"
+}
+~~~
### func ImagesPrune
diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go
index 69b42639de..28c770f0ce 100644
--- a/cmd/podman/tree.go
+++ b/cmd/podman/tree.go
@@ -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
@@ -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)
- }
-}
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index a0227c48c8..e9792fa8f0 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -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.
diff --git a/libpod/image/tree.go b/libpod/image/tree.go
new file mode 100644
index 0000000000..c7c69462ff
--- /dev/null
+++ b/libpod/image/tree.go
@@ -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
+}
+
+// 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)
+ }
+}
diff --git a/pkg/adapter/images.go b/pkg/adapter/images.go
deleted file mode 100644
index 762f1a6565..0000000000
--- a/pkg/adapter/images.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// +build !remoteclient
-
-package adapter
-
-import (
- "github.com/containers/libpod/libpod/image"
- "github.com/pkg/errors"
-)
-
-// Tree ...
-func (r *LocalRuntime) Tree(imageOrID string) (*image.InfoImage, map[string]*image.LayerInfo, *ContainerImage, error) {
- img, err := r.NewImageFromLocal(imageOrID)
- if err != nil {
- return nil, nil, nil, err
- }
-
- // Fetch map of image-layers, which is used for printing output.
- layerInfoMap, err := image.GetLayersMapWithImageInfo(r.Runtime.ImageRuntime())
- if err != nil {
- return nil, nil, nil, errors.Wrapf(err, "error while retrieving layers of image %q", img.InputName)
- }
-
- // Create an imageInfo and fill the image and layer info
- imageInfo := &image.InfoImage{
- ID: img.ID(),
- Tags: img.Names(),
- }
-
- if err := image.BuildImageHierarchyMap(imageInfo, layerInfoMap, img.TopLayer()); err != nil {
- return nil, nil, nil, err
- }
- return imageInfo, layerInfoMap, img, nil
-}
diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go
index 1d4997d9a8..e7b38dccc8 100644
--- a/pkg/adapter/images_remote.go
+++ b/pkg/adapter/images_remote.go
@@ -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
@@ -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
-}
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go
index 40089797dd..dfe6b7f071 100644
--- a/pkg/adapter/runtime.go
+++ b/pkg/adapter/runtime.go
@@ -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
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index c908358ff2..220d4cf75e 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -344,6 +344,10 @@ func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authf
return newImage, nil
}
+func (r *LocalRuntime) ImageTree(imageOrID string, whatRequires bool) (string, error) {
+ return iopodman.ImageTree().Call(r.Conn, imageOrID, whatRequires)
+}
+
// IsParent goes through the layers in the store and checks if i.TopLayer is
// the parent of any other layer in store. Double check that image with that
// layer exists as well.
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index bcbe4977e7..eac0e4dad8 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -46,17 +46,34 @@ func ImageExists(w http.ResponseWriter, r *http.Request) {
}
func ImageTree(w http.ResponseWriter, r *http.Request) {
- // tree is a bit of a mess ... logic is in adapter and therefore not callable from here. needs rework
-
- // name := utils.GetName(r)
- // _, layerInfoMap, _, err := s.Runtime.Tree(name)
- // if err != nil {
- // Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name))
- // return
- // }
- // it is not clear to me how to deal with this given all the processing of the image
- // is in main. we need to discuss how that really should be and return something useful.
- handlers.UnsupportedHandler(w, r)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ name := utils.GetName(r)
+
+ img, err := runtime.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
+ return
+ }
+
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ WhatRequires bool `schema:"whatrequires"`
+ }{
+ WhatRequires: false,
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ tree, err := img.GenerateTree(query.WhatRequires)
+ if err != nil {
+ utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, tree)
}
func GetImage(w http.ResponseWriter, r *http.Request) {
@@ -72,8 +89,8 @@ func GetImage(w http.ResponseWriter, r *http.Request) {
return
}
utils.WriteResponse(w, http.StatusOK, inspect)
-
}
+
func GetImages(w http.ResponseWriter, r *http.Request) {
images, err := utils.GetImages(w, r)
if err != nil {
diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go
index 10525bfc70..4ba123ba9d 100644
--- a/pkg/api/handlers/swagger.go
+++ b/pkg/api/handlers/swagger.go
@@ -136,3 +136,12 @@ type swagInspectVolumeResponse struct {
libpod.InspectVolumeData
}
}
+
+// Image tree response
+// swagger:response LibpodImageTreeResponse
+type swagImageTreeResponse struct {
+ // in:body
+ Body struct {
+ ImageTreeResponse
+ }
+}
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index f082c5fec7..4706a7c69f 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -578,6 +578,31 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/{name}/exists"), APIHandler(s.Context, libpod.ImageExists))
+ // swagger:operation POST /libpod/images/{name}/tree libpod libpodImageTree
+ // ---
+ // tags:
+ // - images
+ // summary: Image tree
+ // description: Retrieve the image tree for the provided image name or ID
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name or ID of the container
+ // - in: query
+ // name: whatrequires
+ // type: boolean
+ // description: show all child images and layers of the specified image
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: '#/responses/LibpodImageTreeResponse'
+ // 401:
+ // $ref: '#/responses/NoSuchImage'
+ // 500:
+ // $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/{name}/tree"), APIHandler(s.Context, libpod.ImageTree))
// swagger:operation GET /libpod/images/{name}/history libpod libpodImageHistory
// ---
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index b144bfa5ef..c4809f16b8 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -1016,3 +1016,17 @@ func (i *LibpodAPI) BuildImageHierarchyMap(call iopodman.VarlinkCall, name strin
}
return call.ReplyBuildImageHierarchyMap(string(b))
}
+
+// ImageTree returns the image tree string for the provided image name or ID
+func (i *LibpodAPI) ImageTree(call iopodman.VarlinkCall, nameOrID string, whatRequires bool) error {
+ img, err := i.Runtime.ImageRuntime().NewFromLocal(nameOrID)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+
+ tree, err := img.GenerateTree(whatRequires)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyImageTree(tree)
+}