From c8e3dd8a9ce7ba7948a5db88608a57de07599c7b Mon Sep 17 00:00:00 2001
From: baude <bbaude@redhat.com>
Date: Mon, 7 Jan 2019 15:08:26 -0600
Subject: [PATCH] remote-client support for images

Signed-off-by: baude <bbaude@redhat.com>
---
 .../podman/imagefilters}/filters.go           |  23 +--
 cmd/podman/images.go                          |  56 +++----
 cmd/podman/varlink/io.podman.varlink          |   3 +-
 libpod/adapter/images_remote.go               |  17 ++
 libpod/adapter/runtime.go                     |  29 ++++
 libpod/adapter/runtime_remote.go              | 151 +++++++++++++++++-
 libpod/image/image.go                         |   6 +-
 libpod/image/parts.go                         |  36 +++--
 libpod/image/parts_test.go                    |   4 +-
 libpod/image/pull.go                          |   4 +-
 libpod/image/utils.go                         |   6 +-
 pkg/varlinkapi/images.go                      |   5 +
 12 files changed, 273 insertions(+), 67 deletions(-)
 rename {libpod/image => cmd/podman/imagefilters}/filters.go (74%)
 create mode 100644 libpod/adapter/images_remote.go

diff --git a/libpod/image/filters.go b/cmd/podman/imagefilters/filters.go
similarity index 74%
rename from libpod/image/filters.go
rename to cmd/podman/imagefilters/filters.go
index d0c3adfb5f..3665102022 100644
--- a/libpod/image/filters.go
+++ b/cmd/podman/imagefilters/filters.go
@@ -1,25 +1,26 @@
-package image
+package imagefilters
 
 import (
 	"context"
 	"strings"
 	"time"
 
+	"github.com/containers/libpod/libpod/adapter"
 	"github.com/containers/libpod/pkg/inspect"
 )
 
 // ResultFilter is a mock function for image filtering
-type ResultFilter func(*Image) bool
+type ResultFilter func(*adapter.ContainerImage) bool
 
 // Filter is a function to determine whether an image is included in
 // command output. Images to be outputted are tested using the function. A true
 // return will include the image, a false return will exclude it.
-type Filter func(*Image, *inspect.ImageData) bool
+type Filter func(*adapter.ContainerImage, *inspect.ImageData) bool
 
 // CreatedBeforeFilter allows you to filter on images created before
 // the given time.Time
 func CreatedBeforeFilter(createTime time.Time) ResultFilter {
-	return func(i *Image) bool {
+	return func(i *adapter.ContainerImage) bool {
 		return i.Created().Before(createTime)
 	}
 }
@@ -27,14 +28,14 @@ func CreatedBeforeFilter(createTime time.Time) ResultFilter {
 // CreatedAfterFilter allows you to filter on images created after
 // the given time.Time
 func CreatedAfterFilter(createTime time.Time) ResultFilter {
-	return func(i *Image) bool {
+	return func(i *adapter.ContainerImage) bool {
 		return i.Created().After(createTime)
 	}
 }
 
 // DanglingFilter allows you to filter images for dangling images
 func DanglingFilter() ResultFilter {
-	return func(i *Image) bool {
+	return func(i *adapter.ContainerImage) bool {
 		return i.Dangling()
 	}
 }
@@ -42,7 +43,7 @@ func DanglingFilter() ResultFilter {
 // LabelFilter allows you to filter by images labels key and/or value
 func LabelFilter(ctx context.Context, labelfilter string) ResultFilter {
 	// We need to handle both label=key and label=key=value
-	return func(i *Image) bool {
+	return func(i *adapter.ContainerImage) bool {
 		var value string
 		splitFilter := strings.Split(labelfilter, "=")
 		key := splitFilter[0]
@@ -61,15 +62,15 @@ func LabelFilter(ctx context.Context, labelfilter string) ResultFilter {
 }
 
 // OutputImageFilter allows you to filter by an a specific image name
-func OutputImageFilter(userImage *Image) ResultFilter {
-	return func(i *Image) bool {
+func OutputImageFilter(userImage *adapter.ContainerImage) ResultFilter {
+	return func(i *adapter.ContainerImage) bool {
 		return userImage.ID() == i.ID()
 	}
 }
 
 // FilterImages filters images using a set of predefined filter funcs
-func FilterImages(images []*Image, filters []ResultFilter) []*Image {
-	var filteredImages []*Image
+func FilterImages(images []*adapter.ContainerImage, filters []ResultFilter) []*adapter.ContainerImage {
+	var filteredImages []*adapter.ContainerImage
 	for _, image := range images {
 		include := true
 		for _, filter := range filters {
diff --git a/cmd/podman/images.go b/cmd/podman/images.go
index 522863b1b5..8b8ce78bd1 100644
--- a/cmd/podman/images.go
+++ b/cmd/podman/images.go
@@ -2,6 +2,8 @@ package main
 
 import (
 	"context"
+	"github.com/containers/libpod/cmd/podman/imagefilters"
+	"github.com/containers/libpod/libpod/adapter"
 	"reflect"
 	"sort"
 	"strings"
@@ -9,11 +11,9 @@ import (
 	"unicode"
 
 	"github.com/containers/libpod/cmd/podman/formats"
-	"github.com/containers/libpod/cmd/podman/libpodruntime"
-	"github.com/containers/libpod/libpod"
 	"github.com/containers/libpod/libpod/image"
 	"github.com/docker/go-units"
-	digest "github.com/opencontainers/go-digest"
+	"github.com/opencontainers/go-digest"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/urfave/cli"
@@ -145,20 +145,20 @@ var (
 
 func imagesCmd(c *cli.Context) error {
 	var (
-		filterFuncs []image.ResultFilter
-		newImage    *image.Image
+		filterFuncs []imagefilters.ResultFilter
+		newImage    *adapter.ContainerImage
 	)
 	if err := validateFlags(c, imagesFlags); err != nil {
 		return err
 	}
 
-	runtime, err := libpodruntime.GetRuntime(c)
+	localRuntime, err := adapter.GetRuntime(c)
 	if err != nil {
 		return errors.Wrapf(err, "Could not get runtime")
 	}
-	defer runtime.Shutdown(false)
+	defer localRuntime.Runtime.Shutdown(false)
 	if len(c.Args()) == 1 {
-		newImage, err = runtime.ImageRuntime().NewFromLocal(c.Args().Get(0))
+		newImage, err = localRuntime.NewImageFromLocal(c.Args().Get(0))
 		if err != nil {
 			return err
 		}
@@ -171,7 +171,7 @@ func imagesCmd(c *cli.Context) error {
 	ctx := getContext()
 
 	if len(c.StringSlice("filter")) > 0 || newImage != nil {
-		filterFuncs, err = CreateFilterFuncs(ctx, runtime, c, newImage)
+		filterFuncs, err = CreateFilterFuncs(ctx, localRuntime, c, newImage)
 		if err != nil {
 			return err
 		}
@@ -195,20 +195,20 @@ func imagesCmd(c *cli.Context) error {
 		children to the image once built. until buildah supports caching builds,
 		it will not generate these intermediate images.
 	*/
-	images, err := runtime.ImageRuntime().GetImages()
+	images, err := localRuntime.GetImages()
 	if err != nil {
 		return errors.Wrapf(err, "unable to get images")
 	}
 
-	var filteredImages []*image.Image
-	// filter the images
+	var filteredImages []*adapter.ContainerImage
+	//filter the images
 	if len(c.StringSlice("filter")) > 0 || newImage != nil {
-		filteredImages = image.FilterImages(images, filterFuncs)
+		filteredImages = imagefilters.FilterImages(images, filterFuncs)
 	} else {
 		filteredImages = images
 	}
 
-	return generateImagesOutput(ctx, runtime, filteredImages, opts)
+	return generateImagesOutput(ctx, filteredImages, opts)
 }
 
 func (i imagesOptions) setOutputFormat() string {
@@ -263,7 +263,7 @@ func sortImagesOutput(sortBy string, imagesOutput imagesSorted) imagesSorted {
 }
 
 // getImagesTemplateOutput returns the images information to be printed in human readable format
-func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image, opts imagesOptions) (imagesOutput imagesSorted) {
+func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) (imagesOutput imagesSorted) {
 	for _, img := range images {
 		// If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent
 		// to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag
@@ -319,7 +319,7 @@ func getImagesTemplateOutput(ctx context.Context, runtime *libpod.Runtime, image
 }
 
 // getImagesJSONOutput returns the images information in its raw form
-func getImagesJSONOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image) (imagesOutput []imagesJSONParams) {
+func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) (imagesOutput []imagesJSONParams) {
 	for _, img := range images {
 		size, err := img.Size(ctx)
 		if err != nil {
@@ -339,7 +339,7 @@ func getImagesJSONOutput(ctx context.Context, runtime *libpod.Runtime, images []
 
 // generateImagesOutput generates the images based on the format provided
 
-func generateImagesOutput(ctx context.Context, runtime *libpod.Runtime, images []*image.Image, opts imagesOptions) error {
+func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) error {
 	if len(images) == 0 {
 		return nil
 	}
@@ -347,10 +347,10 @@ func generateImagesOutput(ctx context.Context, runtime *libpod.Runtime, images [
 
 	switch opts.format {
 	case formats.JSONString:
-		imagesOutput := getImagesJSONOutput(ctx, runtime, images)
+		imagesOutput := getImagesJSONOutput(ctx, images)
 		out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)}
 	default:
-		imagesOutput := getImagesTemplateOutput(ctx, runtime, images, opts)
+		imagesOutput := getImagesTemplateOutput(ctx, images, opts)
 		out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: imagesOutput[0].HeaderMap()}
 	}
 	return formats.Writer(out).Out()
@@ -375,34 +375,34 @@ func (i *imagesTemplateParams) HeaderMap() map[string]string {
 
 // CreateFilterFuncs returns an array of filter functions based on the user inputs
 // and is later used to filter images for output
-func CreateFilterFuncs(ctx context.Context, r *libpod.Runtime, c *cli.Context, img *image.Image) ([]image.ResultFilter, error) {
-	var filterFuncs []image.ResultFilter
+func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, c *cli.Context, img *adapter.ContainerImage) ([]imagefilters.ResultFilter, error) {
+	var filterFuncs []imagefilters.ResultFilter
 	for _, filter := range c.StringSlice("filter") {
 		splitFilter := strings.Split(filter, "=")
 		switch splitFilter[0] {
 		case "before":
-			before, err := r.ImageRuntime().NewFromLocal(splitFilter[1])
+			before, err := r.NewImageFromLocal(splitFilter[1])
 			if err != nil {
 				return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1])
 			}
-			filterFuncs = append(filterFuncs, image.CreatedBeforeFilter(before.Created()))
+			filterFuncs = append(filterFuncs, imagefilters.CreatedBeforeFilter(before.Created()))
 		case "after":
-			after, err := r.ImageRuntime().NewFromLocal(splitFilter[1])
+			after, err := r.NewImageFromLocal(splitFilter[1])
 			if err != nil {
 				return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1])
 			}
-			filterFuncs = append(filterFuncs, image.CreatedAfterFilter(after.Created()))
+			filterFuncs = append(filterFuncs, imagefilters.CreatedAfterFilter(after.Created()))
 		case "dangling":
-			filterFuncs = append(filterFuncs, image.DanglingFilter())
+			filterFuncs = append(filterFuncs, imagefilters.DanglingFilter())
 		case "label":
 			labelFilter := strings.Join(splitFilter[1:], "=")
-			filterFuncs = append(filterFuncs, image.LabelFilter(ctx, labelFilter))
+			filterFuncs = append(filterFuncs, imagefilters.LabelFilter(ctx, labelFilter))
 		default:
 			return nil, errors.Errorf("invalid filter %s ", splitFilter[0])
 		}
 	}
 	if img != nil {
-		filterFuncs = append(filterFuncs, image.OutputImageFilter(img))
+		filterFuncs = append(filterFuncs, imagefilters.OutputImageFilter(img))
 	}
 	return filterFuncs, nil
 }
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index 4e8b69faf3..b7972a9189 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -37,7 +37,8 @@ type ImageInList (
   size: int,
   virtualSize: int,
   containers: int,
-  labels: [string]string
+  labels: [string]string,
+  isParent: bool
 )
 
 # ImageHistory describes the returned structure from ImageHistory.
diff --git a/libpod/adapter/images_remote.go b/libpod/adapter/images_remote.go
new file mode 100644
index 0000000000..77b0629a70
--- /dev/null
+++ b/libpod/adapter/images_remote.go
@@ -0,0 +1,17 @@
+// +build remoteclient
+
+package adapter
+
+import (
+	"github.com/containers/libpod/libpod"
+)
+
+// Images returns information for the host system and its components
+func (r RemoteRuntime) Images() ([]libpod.InfoData, error) {
+	conn, err := r.Connect()
+	if err != nil {
+		return nil, err
+	}
+	_ = conn
+	return nil, nil
+}
diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go
index b6db510714..13141f8865 100644
--- a/libpod/adapter/runtime.go
+++ b/libpod/adapter/runtime.go
@@ -5,6 +5,7 @@ package adapter
 import (
 	"github.com/containers/libpod/cmd/podman/libpodruntime"
 	"github.com/containers/libpod/libpod"
+	"github.com/containers/libpod/libpod/image"
 	"github.com/urfave/cli"
 )
 
@@ -14,6 +15,11 @@ type LocalRuntime struct {
 	Remote  bool
 }
 
+// ContainerImage ...
+type ContainerImage struct {
+	*image.Image
+}
+
 // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
 func GetRuntime(c *cli.Context) (*LocalRuntime, error) {
 	runtime, err := libpodruntime.GetRuntime(c)
@@ -24,3 +30,26 @@ func GetRuntime(c *cli.Context) (*LocalRuntime, error) {
 		Runtime: runtime,
 	}, nil
 }
+
+// GetImages returns a slice of images in containerimages
+func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
+	var containerImages []*ContainerImage
+	images, err := r.Runtime.ImageRuntime().GetImages()
+	if err != nil {
+		return nil, err
+	}
+	for _, i := range images {
+		containerImages = append(containerImages, &ContainerImage{i})
+	}
+	return containerImages, nil
+
+}
+
+// NewImageFromLocal returns a containerimage representation of a image from local storage
+func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) {
+	img, err := r.Runtime.ImageRuntime().NewFromLocal(name)
+	if err != nil {
+		return nil, err
+	}
+	return &ContainerImage{img}, nil
+}
diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go
index 715728d211..2f22dd36be 100644
--- a/libpod/adapter/runtime_remote.go
+++ b/libpod/adapter/runtime_remote.go
@@ -2,23 +2,43 @@
 
 package adapter
 
-import "github.com/urfave/cli"
+import (
+	"context"
+	"fmt"
+	"github.com/containers/libpod/cmd/podman/varlink"
+	"github.com/containers/libpod/libpod/image"
+	"github.com/opencontainers/go-digest"
+	"github.com/urfave/cli"
+	"github.com/varlink/go/varlink"
+	"strings"
+	"time"
+)
+
+// ImageRuntime is wrapper for image runtime
+type RemoteImageRuntime struct{}
 
 // RemoteRuntime describes a wrapper runtime struct
-type RemoteRuntime struct{}
+type RemoteRuntime struct {
+}
 
 // LocalRuntime describes a typical libpod runtime
 type LocalRuntime struct {
 	Runtime *RemoteRuntime
 	Remote  bool
+	Conn    *varlink.Connection
 }
 
 // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it
 func GetRuntime(c *cli.Context) (*LocalRuntime, error) {
 	runtime := RemoteRuntime{}
+	conn, err := runtime.Connect()
+	if err != nil {
+		return nil, err
+	}
 	return &LocalRuntime{
 		Runtime: &runtime,
 		Remote:  true,
+		Conn:    conn,
 	}, nil
 }
 
@@ -26,3 +46,130 @@ func GetRuntime(c *cli.Context) (*LocalRuntime, error) {
 func (r RemoteRuntime) Shutdown(force bool) error {
 	return nil
 }
+
+// ContainerImage
+type ContainerImage struct {
+	remoteImage
+}
+
+type remoteImage struct {
+	ID          string
+	Labels      map[string]string
+	RepoTags    []string
+	RepoDigests []string
+	Parent      string
+	Size        int64
+	Tag         string
+	Repository  string
+	Created     time.Time
+	InputName   string
+	Names       []string
+	Digest      digest.Digest
+	isParent    bool
+}
+
+// GetImages returns a slice of containerimages over a varlink connection
+func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
+	var newImages []*ContainerImage
+	images, err := iopodman.ListImages().Call(r.Conn)
+	if err != nil {
+		return nil, err
+	}
+	for _, i := range images {
+		name := i.Id
+		if len(i.RepoTags) > 1 {
+			name = i.RepoTags[0]
+		}
+		newImage, err := imageInListToContainerImage(i, name)
+		if err != nil {
+			return nil, err
+		}
+		newImages = append(newImages, newImage)
+	}
+	return newImages, nil
+}
+
+func imageInListToContainerImage(i iopodman.ImageInList, name string) (*ContainerImage, error) {
+	imageParts, err := image.DecomposeString(name)
+	if err != nil {
+		return nil, err
+	}
+	created, err := splitStringDate(i.Created)
+	if err != nil {
+		return nil, err
+	}
+	ri := remoteImage{
+		InputName:   name,
+		ID:          i.Id,
+		Labels:      i.Labels,
+		RepoTags:    i.RepoTags,
+		RepoDigests: i.RepoTags,
+		Parent:      i.ParentId,
+		Size:        i.Size,
+		Created:     created,
+		Tag:         imageParts.Tag,
+		Repository:  imageParts.Registry,
+		Names:       i.RepoTags,
+		isParent:    i.IsParent,
+	}
+	return &ContainerImage{ri}, nil
+}
+
+// NewImageFromLocal returns a container image representation of a image over varlink
+func (r *LocalRuntime) NewImageFromLocal(name string) (*ContainerImage, error) {
+	img, err := iopodman.GetImage().Call(r.Conn, name)
+	if err != nil {
+		return nil, err
+	}
+	return imageInListToContainerImage(img, name)
+
+}
+
+func splitStringDate(d string) (time.Time, error) {
+	fields := strings.Fields(d)
+	t := fmt.Sprintf("%sT%sZ", fields[0], fields[1])
+	return time.ParseInLocation(time.RFC3339Nano, t, time.UTC)
+}
+
+// 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.
+func (ci *ContainerImage) IsParent() (bool, error) {
+	return ci.remoteImage.isParent, nil
+}
+
+// ID returns the image ID as a string
+func (ci *ContainerImage) ID() string {
+	return ci.remoteImage.ID
+}
+
+// Names returns a string array of names associated with the image
+func (ci *ContainerImage) Names() []string {
+	return ci.remoteImage.Names
+}
+
+// Created returns the time the image was created
+func (ci *ContainerImage) Created() time.Time {
+	return ci.remoteImage.Created
+}
+
+// Size returns the size of the image
+func (ci *ContainerImage) Size(ctx context.Context) (*uint64, error) {
+	usize := uint64(ci.remoteImage.Size)
+	return &usize, nil
+}
+
+// Digest returns the image's digest
+func (ci *ContainerImage) Digest() digest.Digest {
+	return ci.remoteImage.Digest
+}
+
+// Labels returns a map of the image's labels
+func (ci *ContainerImage) Labels(ctx context.Context) (map[string]string, error) {
+	return ci.remoteImage.Labels, nil
+}
+
+// Dangling returns a bool if the image is "dangling"
+func (ci *ContainerImage) Dangling() bool {
+	return len(ci.Names()) == 0
+}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 2e12adb706..dda7533853 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -460,7 +460,7 @@ func normalizeTag(tag string) (string, error) {
 	}
 	// If the input does not have a tag, we need to add one (latest)
 	if !decomposedTag.isTagged {
-		tag = fmt.Sprintf("%s:%s", tag, decomposedTag.tag)
+		tag = fmt.Sprintf("%s:%s", tag, decomposedTag.Tag)
 	}
 	// If the input doesn't specify a registry, set the registry to localhost
 	if !decomposedTag.hasRegistry {
@@ -937,7 +937,7 @@ func (i *Image) MatchRepoTag(input string) (string, error) {
 		if err != nil {
 			return "", err
 		}
-		if dcRepoName.registry == dcImage.registry && dcImage.registry != "" {
+		if dcRepoName.Registry == dcImage.Registry && dcImage.Registry != "" {
 			count++
 		}
 		if dcRepoName.name == dcImage.name && dcImage.name != "" {
@@ -945,7 +945,7 @@ func (i *Image) MatchRepoTag(input string) (string, error) {
 		} else if splitString(dcRepoName.name) == splitString(dcImage.name) {
 			count++
 		}
-		if dcRepoName.tag == dcImage.tag {
+		if dcRepoName.Tag == dcImage.Tag {
 			count++
 		}
 		results[count] = append(results[count], repoName)
diff --git a/libpod/image/parts.go b/libpod/image/parts.go
index 9adf26fb93..b2a69f26c2 100644
--- a/libpod/image/parts.go
+++ b/libpod/image/parts.go
@@ -7,12 +7,12 @@ import (
 	"github.com/containers/image/docker/reference"
 )
 
-// imageParts describes the parts of an image's name
-type imageParts struct {
+// Parts describes the parts of an image's name
+type Parts struct {
 	transport   string
-	registry    string
+	Registry    string
 	name        string
-	tag         string
+	Tag         string
 	isTagged    bool
 	hasRegistry bool
 }
@@ -34,10 +34,16 @@ func GetImageBaseName(input string) (string, error) {
 	return splitImageName[len(splitImageName)-1], nil
 }
 
+// DecomposeString decomposes a string name into imageParts description. This
+// is a wrapper for decompose
+func DecomposeString(input string) (Parts, error) {
+	return decompose(input)
+}
+
 // decompose breaks an input name into an imageParts description
-func decompose(input string) (imageParts, error) {
+func decompose(input string) (Parts, error) {
 	var (
-		parts       imageParts
+		parts       Parts
 		hasRegistry bool
 		tag         string
 	)
@@ -56,7 +62,7 @@ func decompose(input string) (imageParts, error) {
 	}
 	registry := reference.Domain(imgRef.(reference.Named))
 	imageName := reference.Path(imgRef.(reference.Named))
-	// Is this a registry or a repo?
+	// Is this a Registry or a repo?
 	if isRegistry(registry) {
 		hasRegistry = true
 	} else {
@@ -65,27 +71,27 @@ func decompose(input string) (imageParts, error) {
 			registry = ""
 		}
 	}
-	return imageParts{
-		registry:    registry,
+	return Parts{
+		Registry:    registry,
 		hasRegistry: hasRegistry,
 		name:        imageName,
-		tag:         tag,
+		Tag:         tag,
 		isTagged:    isTagged,
 		transport:   DefaultTransport,
 	}, nil
 }
 
 // assemble concatenates an image's parts into a string
-func (ip *imageParts) assemble() string {
-	spec := fmt.Sprintf("%s:%s", ip.name, ip.tag)
+func (ip *Parts) assemble() string {
+	spec := fmt.Sprintf("%s:%s", ip.name, ip.Tag)
 
-	if ip.registry != "" {
-		spec = fmt.Sprintf("%s/%s", ip.registry, spec)
+	if ip.Registry != "" {
+		spec = fmt.Sprintf("%s/%s", ip.Registry, spec)
 	}
 	return spec
 }
 
 // assemble concatenates an image's parts with transport into a string
-func (ip *imageParts) assembleWithTransport() string {
+func (ip *Parts) assembleWithTransport() string {
 	return fmt.Sprintf("%s%s", ip.transport, ip.assemble())
 }
diff --git a/libpod/image/parts_test.go b/libpod/image/parts_test.go
index 518538f0b8..1e01c85d37 100644
--- a/libpod/image/parts_test.go
+++ b/libpod/image/parts_test.go
@@ -55,9 +55,9 @@ func TestDecompose(t *testing.T) {
 		} else {
 			assert.NoError(t, err, c.input)
 			assert.Equal(t, c.transport, parts.transport, c.input)
-			assert.Equal(t, c.registry, parts.registry, c.input)
+			assert.Equal(t, c.registry, parts.Registry, c.input)
 			assert.Equal(t, c.name, parts.name, c.input)
-			assert.Equal(t, c.tag, parts.tag, c.input)
+			assert.Equal(t, c.tag, parts.Tag, c.input)
 			assert.Equal(t, c.isTagged, parts.isTagged, c.input)
 			assert.Equal(t, c.hasRegistry, parts.hasRegistry, c.input)
 			assert.Equal(t, c.assembled, parts.assemble(), c.input)
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index 09935fe7c2..203e943106 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -76,7 +76,7 @@ func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string)
 	decomposedDest, err := decompose(destName)
 	if err == nil && !decomposedDest.hasRegistry {
 		// If the image doesn't have a registry, set it as the default repo
-		decomposedDest.registry = DefaultLocalRegistry
+		decomposedDest.Registry = DefaultLocalRegistry
 		decomposedDest.hasRegistry = true
 		destName = decomposedDest.assemble()
 	}
@@ -317,7 +317,7 @@ func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullG
 	}
 	var refPairs []pullRefPair
 	for _, registry := range searchRegistries {
-		decomposedImage.registry = registry
+		decomposedImage.Registry = registry
 		imageName := decomposedImage.assembleWithTransport()
 		if hasShaInInputName(inputName) {
 			imageName = fmt.Sprintf("%s%s/%s", decomposedImage.transport, registry, inputName)
diff --git a/libpod/image/utils.go b/libpod/image/utils.go
index b944de1bb2..135b470081 100644
--- a/libpod/image/utils.go
+++ b/libpod/image/utils.go
@@ -16,7 +16,7 @@ import (
 
 // findImageInRepotags takes an imageParts struct and searches images' repotags for
 // a match on name:tag
-func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, error) {
+func findImageInRepotags(search Parts, images []*Image) (*storage.Image, error) {
 	var results []*storage.Image
 	for _, image := range images {
 		for _, name := range image.Names() {
@@ -25,12 +25,12 @@ func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, er
 			if err != nil {
 				continue
 			}
-			if d.name == search.name && d.tag == search.tag {
+			if d.name == search.name && d.Tag == search.Tag {
 				results = append(results, image.image)
 				continue
 			}
 			// account for registry:/somedir/image
-			if strings.HasSuffix(d.name, search.name) && d.tag == search.tag {
+			if strings.HasSuffix(d.name, search.name) && d.Tag == search.Tag {
 				results = append(results, image.image)
 				continue
 			}
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index 8f8934025d..370fd091a9 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -47,6 +47,10 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
 		}
 
 		size, _ := image.Size(getContext())
+		isParent, err := image.IsParent()
+		if err != nil {
+			return call.ReplyErrorOccurred(err.Error())
+		}
 
 		i := iopodman.ImageInList{
 			Id:          image.ID(),
@@ -58,6 +62,7 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
 			VirtualSize: image.VirtualSize,
 			Containers:  int64(len(containers)),
 			Labels:      labels,
+			IsParent:    isParent,
 		}
 		imageList = append(imageList, i)
 	}