From abbbeacd68b4d4973a55a8d7263bd0a27f5c4e8e Mon Sep 17 00:00:00 2001
From: Brent Baude <bbaude@redhat.com>
Date: Wed, 4 Mar 2020 10:14:07 -0600
Subject: apiv2 addition of manifests

add endpoints for create, add, remove, inspect, and push.  this allows manifests to be managed through the restful interfaces.

also added go-bindings and tests

Signed-off-by: Brent Baude <bbaude@redhat.com>
---
 libpod/image/manifests.go                          | 154 +++++++
 pkg/api/handlers/libpod/manifests.go               | 166 +++++++
 pkg/api/handlers/libpod/swagger.go                 |   9 +
 pkg/api/handlers/types.go                          |   2 +
 pkg/api/server/register_manifest.go                | 145 ++++++
 pkg/api/server/server.go                           |   3 +-
 pkg/api/server/swagger.go                          |   9 +
 pkg/api/tags.yaml                                  |   2 +
 pkg/bindings/manifests/manifests.go                | 126 ++++++
 pkg/bindings/test/manifests_test.go                | 124 ++++++
 .../containers/buildah/manifests/copy.go           |  15 +
 .../containers/buildah/manifests/manifests.go      | 397 +++++++++++++++++
 .../containers/buildah/pkg/manifests/errors.go     |  16 +
 .../containers/buildah/pkg/manifests/manifests.go  | 493 +++++++++++++++++++++
 .../containers/buildah/pkg/supplemented/errors.go  |  17 +
 .../buildah/pkg/supplemented/supplemented.go       | 393 ++++++++++++++++
 vendor/modules.txt                                 |   3 +
 17 files changed, 2073 insertions(+), 1 deletion(-)
 create mode 100644 libpod/image/manifests.go
 create mode 100644 pkg/api/handlers/libpod/manifests.go
 create mode 100644 pkg/api/server/register_manifest.go
 create mode 100644 pkg/bindings/manifests/manifests.go
 create mode 100644 pkg/bindings/test/manifests_test.go
 create mode 100644 vendor/github.com/containers/buildah/manifests/copy.go
 create mode 100644 vendor/github.com/containers/buildah/manifests/manifests.go
 create mode 100644 vendor/github.com/containers/buildah/pkg/manifests/errors.go
 create mode 100644 vendor/github.com/containers/buildah/pkg/manifests/manifests.go
 create mode 100644 vendor/github.com/containers/buildah/pkg/supplemented/errors.go
 create mode 100644 vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go

diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go
new file mode 100644
index 000000000..9dbeb4cc5
--- /dev/null
+++ b/libpod/image/manifests.go
@@ -0,0 +1,154 @@
+package image
+
+import (
+	"context"
+
+	"github.com/containers/buildah/manifests"
+	"github.com/containers/image/v5/manifest"
+	"github.com/containers/image/v5/transports/alltransports"
+	"github.com/containers/image/v5/types"
+	"github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
+)
+
+// Options for adding a manifest
+// swagger:model ManifestAddOpts
+type ManifestAddOpts struct {
+	All        bool              `json:"all"`
+	Annotation map[string]string `json:"annotation"`
+	Arch       string            `json:"arch"`
+	Features   []string          `json:"features"`
+	Images     []string          `json:"images"`
+	OSVersion  string            `json:"os_version"`
+	Variant    string            `json:"variant"`
+}
+
+// InspectManifest returns a dockerized version of the manifest list
+func (i *Image) InspectManifest() (*manifest.Schema2List, error) {
+	list, err := i.getManifestList()
+	if err != nil {
+		return nil, err
+	}
+	return list.Docker(), nil
+}
+
+// RemoveManifest removes the given digest from the manifest list.
+func (i *Image) RemoveManifest(d digest.Digest) (string, error) {
+	list, err := i.getManifestList()
+	if err != nil {
+		return "", err
+	}
+	if err := list.Remove(d); err != nil {
+		return "", err
+	}
+	return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
+}
+
+// getManifestList is a helper to obtain a manifest list
+func (i *Image) getManifestList() (manifests.List, error) {
+	_, list, err := manifests.LoadFromImage(i.imageruntime.store, i.ID())
+	return list, err
+}
+
+// CreateManifestList creates a new manifest list and can optionally add given images
+// to the list
+func CreateManifestList(rt *Runtime, systemContext types.SystemContext, names []string, imgs []string, all bool) (string, error) {
+	list := manifests.Create()
+	opts := ManifestAddOpts{Images: names, All: all}
+	for _, img := range imgs {
+		var ref types.ImageReference
+		newImage, err := rt.NewFromLocal(img)
+		if err == nil {
+			ir, err := newImage.toImageRef(context.Background())
+			if err != nil {
+				return "", err
+			}
+			if ir == nil {
+				return "", errors.New("unable to convert image to ImageReference")
+			}
+			ref = ir.Reference()
+		} else {
+			ref, err = alltransports.ParseImageName(img)
+			if err != nil {
+				return "", err
+			}
+		}
+		list, err = addManifestToList(ref, list, systemContext, opts)
+		if err != nil {
+			return "", err
+		}
+	}
+	return list.SaveToImage(rt.store, "", names, manifest.DockerV2ListMediaType)
+}
+
+func addManifestToList(ref types.ImageReference, list manifests.List, systemContext types.SystemContext, opts ManifestAddOpts) (manifests.List, error) {
+	d, err := list.Add(context.Background(), &systemContext, ref, opts.All)
+	if err != nil {
+		return nil, err
+	}
+	if len(opts.OSVersion) > 0 {
+		if err := list.SetOSVersion(d, opts.OSVersion); err != nil {
+			return nil, err
+		}
+	}
+	if len(opts.Features) > 0 {
+		if err := list.SetFeatures(d, opts.Features); err != nil {
+			return nil, err
+		}
+	}
+	if len(opts.Arch) > 0 {
+		if err := list.SetArchitecture(d, opts.Arch); err != nil {
+			return nil, err
+		}
+	}
+	if len(opts.Variant) > 0 {
+		if err := list.SetVariant(d, opts.Variant); err != nil {
+			return nil, err
+		}
+	}
+	if len(opts.Annotation) > 0 {
+		if err := list.SetAnnotations(&d, opts.Annotation); err != nil {
+			return nil, err
+		}
+	}
+	return list, err
+}
+
+// AddManifest adds a manifest to a given manifest list.
+func (i *Image) AddManifest(systemContext types.SystemContext, opts ManifestAddOpts) (string, error) {
+	var (
+		ref types.ImageReference
+	)
+	newImage, err := i.imageruntime.NewFromLocal(opts.Images[0])
+	if err == nil {
+		ir, err := newImage.toImageRef(context.Background())
+		if err != nil {
+			return "", err
+		}
+		ref = ir.Reference()
+	} else {
+		ref, err = alltransports.ParseImageName(opts.Images[0])
+		if err != nil {
+			return "", err
+		}
+	}
+	list, err := i.getManifestList()
+	if err != nil {
+		return "", err
+	}
+	list, err = addManifestToList(ref, list, systemContext, opts)
+	if err != nil {
+		return "", err
+	}
+	return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
+}
+
+// PushManifest pushes a manifest to a destination
+func (i *Image) PushManifest(dest types.ImageReference, opts manifests.PushOptions) (digest.Digest, error) {
+	list, err := i.getManifestList()
+	if err != nil {
+		return "", err
+	}
+	_, d, err := list.Push(context.Background(), dest, opts)
+	return d, err
+}
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
new file mode 100644
index 000000000..a3d2caba6
--- /dev/null
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -0,0 +1,166 @@
+package libpod
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"github.com/containers/buildah/manifests"
+	copy2 "github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/transports/alltransports"
+	"github.com/containers/libpod/libpod"
+	"github.com/containers/libpod/libpod/image"
+	"github.com/containers/libpod/pkg/api/handlers"
+	"github.com/containers/libpod/pkg/api/handlers/utils"
+	"github.com/gorilla/schema"
+	"github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
+)
+
+func ManifestCreate(w http.ResponseWriter, r *http.Request) {
+	runtime := r.Context().Value("runtime").(*libpod.Runtime)
+	decoder := r.Context().Value("decoder").(*schema.Decoder)
+	query := struct {
+		Name  []string `schema:"name"`
+		Image []string `schema:"image"`
+		All   bool     `schema:"all"`
+	}{
+		// Add defaults here once needed.
+	}
+	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
+	}
+	rtc, err := runtime.GetConfig()
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+	manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All)
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manID})
+}
+
+func ManifestInspect(w http.ResponseWriter, r *http.Request) {
+	runtime := r.Context().Value("runtime").(*libpod.Runtime)
+	name := utils.GetName(r)
+	newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+	if err != nil {
+		utils.ImageNotFound(w, name, err)
+		return
+	}
+	data, err := newImage.InspectManifest()
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	utils.WriteResponse(w, http.StatusOK, data)
+}
+
+func ManifestAdd(w http.ResponseWriter, r *http.Request) {
+	runtime := r.Context().Value("runtime").(*libpod.Runtime)
+	var manifestInput image.ManifestAddOpts
+	if err := json.NewDecoder(r.Body).Decode(&manifestInput); err != nil {
+		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+		return
+	}
+	name := utils.GetName(r)
+	newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+	if err != nil {
+		utils.ImageNotFound(w, name, err)
+		return
+	}
+	rtc, err := runtime.GetConfig()
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+	newID, err := newImage.AddManifest(*sc, manifestInput)
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
+}
+
+func ManifestRemove(w http.ResponseWriter, r *http.Request) {
+	runtime := r.Context().Value("runtime").(*libpod.Runtime)
+	decoder := r.Context().Value("decoder").(*schema.Decoder)
+	query := struct {
+		Digest string `schema:"digest"`
+	}{
+		// Add defaults here once needed.
+	}
+	name := utils.GetName(r)
+	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
+	}
+	newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+	if err != nil {
+		utils.ImageNotFound(w, name, err)
+		return
+	}
+	d, err := digest.Parse(query.Digest)
+	if err != nil {
+		utils.Error(w, "invalid digest", http.StatusBadRequest, err)
+		return
+	}
+	newID, err := newImage.RemoveManifest(d)
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
+}
+func ManifestPush(w http.ResponseWriter, r *http.Request) {
+	runtime := r.Context().Value("runtime").(*libpod.Runtime)
+	decoder := r.Context().Value("decoder").(*schema.Decoder)
+	query := struct {
+		All         bool   `schema:"all"`
+		Destination string `schema:"destination"`
+	}{
+		// Add defaults here once needed.
+	}
+	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
+	}
+	name := utils.GetName(r)
+	newImage, err := runtime.ImageRuntime().NewFromLocal(name)
+	if err != nil {
+		utils.ImageNotFound(w, name, err)
+		return
+	}
+	dest, err := alltransports.ParseImageName(query.Destination)
+	if err != nil {
+		utils.Error(w, "invalid destination parameter", http.StatusBadRequest, errors.Errorf("invalid destination parameter %q", query.Destination))
+		return
+	}
+	rtc, err := runtime.GetConfig()
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
+	opts := manifests.PushOptions{
+		ImageListSelection: copy2.CopySpecificImages,
+		SystemContext:      sc,
+	}
+	if query.All {
+		opts.ImageListSelection = copy2.CopyAllImages
+	}
+	newD, err := newImage.PushManifest(dest, opts)
+	if err != nil {
+		utils.InternalServerError(w, err)
+		return
+	}
+	utils.WriteResponse(w, http.StatusOK, newD.String())
+}
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index aec30ef56..f6a26134b 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -1,8 +1,17 @@
 package libpod
 
+import "github.com/containers/image/v5/manifest"
+
 // List Containers
 // swagger:response ListContainers
 type swagInspectPodResponse struct {
 	// in:body
 	Body []ListContainer
 }
+
+// Inspect Manifest
+// swagger:response InspectManifest
+type swagInspectManifestResponse struct {
+	// in:body
+	Body manifest.List
+}
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 2e429dc58..ce4a9957b 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -140,7 +140,9 @@ type VolumeCreateConfig struct {
 	Opts map[string]string `schema:"opts"`
 }
 
+// swagger:model IDResponse
 type IDResponse struct {
+	// ID
 	ID string `json:"id"`
 }
 
diff --git a/pkg/api/server/register_manifest.go b/pkg/api/server/register_manifest.go
new file mode 100644
index 000000000..ccfc2192d
--- /dev/null
+++ b/pkg/api/server/register_manifest.go
@@ -0,0 +1,145 @@
+package server
+
+import (
+	"net/http"
+
+	"github.com/containers/libpod/pkg/api/handlers/libpod"
+	"github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
+	// swagger:operation POST /libpod/manifests/create manifests Create
+	// ---
+	// summary: Create
+	// description: Create a manifest list
+	// produces:
+	// - application/json
+	// parameters:
+	// - in: query
+	//   name: name
+	//   type: string
+	//   description: manifest list name
+	//   required: true
+	// - in: query
+	//   name: image
+	//   type: string
+	//   description: name of the image
+	// - in: query
+	//   name: all
+	//   type: boolean
+	//   description: add all contents if given list
+	// responses:
+	//   200:
+	//     $ref: "#/definitions/IDResponse"
+	//   400:
+	//     $ref: "#/responses/BadParamError"
+	//   404:
+	//     $ref: "#/responses/NoSuchImage"
+	//   500:
+	//     $ref: "#/responses/InternalError"
+	r.Handle(VersionedPath("/libpod/manifests/create"), s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost)
+	// swagger:operation GET /libpod/manifests/{name}/json manifests Inspect
+	// ---
+	// summary: Inspect
+	// description: Display a manifest list
+	// produces:
+	// - application/json
+	// parameters:
+	//  - in: path
+	//    name: name
+	//    type: string
+	//    required: true
+	//    description: the name or ID of the manifest
+	// responses:
+	//   200:
+	//     $ref: "#/responses/InspectManifest"
+	//   404:
+	//     $ref: "#/responses/NoSuchManifest"
+	//   500:
+	//     $ref: "#/responses/InternalError"
+	r.Handle(VersionedPath("/libpod/manifests/{name:.*}/json"), s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet)
+	// swagger:operation POST /libpod/manifests/{name}/add manifests AddManifest
+	// ---
+	// description: Add an image to a manifest list
+	// produces:
+	// - application/json
+	// parameters:
+	//  - in: path
+	//    name: name
+	//    type: string
+	//    required: true
+	//    description: the name or ID of the manifest
+	//  - in: body
+	//    name: options
+	//    description: options for creating a manifest
+	//    schema:
+	//      $ref: "#/definitions/ManifestAddOpts"
+	// responses:
+	//   200:
+	//     $ref: "#/definitions/IDResponse"
+	//   404:
+	//     $ref: "#/responses/NoSuchManifest"
+	//   409:
+	//     $ref: "#/responses/BadParamError"
+	//   500:
+	//     $ref: "#/responses/InternalError"
+	r.Handle(VersionedPath("/libpod/manifests/{name:.*}/add"), s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost)
+	// swagger:operation DELETE /libpod/manifests/{name} manifests RemoveManifest
+	// ---
+	// summary: Remove
+	// description: Remove an image from a manifest list
+	// produces:
+	// - application/json
+	// parameters:
+	//  - in: path
+	//    name: name
+	//    type: string
+	//    required: true
+	//    description: the image associated with the manifest
+	//  - in: query
+	//    name: digest
+	//    type: string
+	//    description: image digest to be removed
+	// responses:
+	//   200:
+	//     $ref: "#/definitions/IDResponse"
+	//   400:
+	//     $ref: "#/responses/BadParamError"
+	//   404:
+	//     $ref: "#/responses/NoSuchManifest"
+	//   500:
+	//     $ref: "#/responses/InternalError"
+	r.Handle(VersionedPath("/libpod/manifests/{name:.*}"), s.APIHandler(libpod.ManifestRemove)).Methods(http.MethodDelete)
+	// swagger:operation POST /libpod/manifests/{name}/push manifests PushManifest
+	// ---
+	// summary: Push
+	// description: Push a manifest list or image index to a registry
+	// produces:
+	// - application/json
+	// parameters:
+	//  - in: path
+	//    name: name
+	//    type: string
+	//    required: true
+	//    description: the name or ID of the manifest
+	//  - in: query
+	//    name: destination
+	//    type: string
+	//    required: true
+	//    description: the destination for the manifest
+	//  - in: query
+	//    name: all
+	//    description: push all images
+	//    type: boolean
+	// responses:
+	//   200:
+	//     $ref: "#/definitions/IDResponse"
+	//   400:
+	//     $ref: "#/responses/BadParamError"
+	//   404:
+	//     $ref: "#/responses/NoSuchManifest"
+	//   500:
+	//     $ref: "#/responses/InternalError"
+	r.Handle(VersionedPath("/libpod/manifests/{name}/push"), s.APIHandler(libpod.ManifestPush)).Methods(http.MethodPost)
+	return nil
+}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index a0addb303..8496cd11c 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -99,11 +99,12 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
 		server.registerAuthHandlers,
 		server.registerContainersHandlers,
 		server.registerDistributionHandlers,
-		server.registerExecHandlers,
 		server.registerEventsHandlers,
+		server.registerExecHandlers,
 		server.registerHealthCheckHandlers,
 		server.registerImagesHandlers,
 		server.registerInfoHandlers,
+		server.registerManifestHandlers,
 		server.registerMonitorHandlers,
 		server.registerPingHandlers,
 		server.registerPluginsHandlers,
diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go
index 011196e5a..bd5c1df10 100644
--- a/pkg/api/server/swagger.go
+++ b/pkg/api/server/swagger.go
@@ -51,6 +51,15 @@ type swagErrNoSuchPod struct {
 	}
 }
 
+// No such manifest
+// swagger:response NoSuchManifest
+type swagErrNoSuchManifest struct {
+	// in:body
+	Body struct {
+		utils.ErrorModel
+	}
+}
+
 // Internal server error
 // swagger:response InternalError
 type swagInternalError struct {
diff --git a/pkg/api/tags.yaml b/pkg/api/tags.yaml
index 571f49e44..5b5d9f5bb 100644
--- a/pkg/api/tags.yaml
+++ b/pkg/api/tags.yaml
@@ -6,6 +6,8 @@ tags:
     - name: images
       description: Actions related to images
     - name: pods
+      description: Actions related to manifests
+    - name: manifests
       description: Actions related to pods
     - name: volumes
       description: Actions related to volumes
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
new file mode 100644
index 000000000..a8d1e6ca3
--- /dev/null
+++ b/pkg/bindings/manifests/manifests.go
@@ -0,0 +1,126 @@
+package manifests
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+
+	"github.com/containers/image/v5/manifest"
+	"github.com/containers/libpod/libpod/image"
+	"github.com/containers/libpod/pkg/api/handlers"
+	"github.com/containers/libpod/pkg/bindings"
+	jsoniter "github.com/json-iterator/go"
+)
+
+// Create creates a manifest for the given name.  Optional images to be associated with
+// the new manifest can also be specified.  The all boolean specifies to add all entries
+// of a list if the name provided is a manifest list.  The ID of the new manifest list
+// is returned as a string.
+func Create(ctx context.Context, names, images []string, all *bool) (string, error) {
+	var idr handlers.IDResponse
+	conn, err := bindings.GetClient(ctx)
+	if err != nil {
+		return "", err
+	}
+	if len(names) < 1 {
+		return "", errors.New("creating a manifest requires at least one name argument")
+	}
+	params := url.Values{}
+	if all != nil {
+		params.Set("all", strconv.FormatBool(*all))
+	}
+	for _, name := range names {
+		params.Add("name", name)
+	}
+	for _, i := range images {
+		params.Add("image", i)
+	}
+
+	response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params)
+	if err != nil {
+		return "", err
+	}
+	return idr.ID, response.Process(&idr)
+}
+
+// Inspect returns a manifest list for a given name.
+func Inspect(ctx context.Context, name string) (*manifest.Schema2List, error) {
+	var list manifest.Schema2List
+	conn, err := bindings.GetClient(ctx)
+	if err != nil {
+		return nil, err
+	}
+	response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, name)
+	if err != nil {
+		return nil, err
+	}
+	return &list, response.Process(&list)
+}
+
+// Add adds a manifest to a given manifest list.  Additional options for the manifest
+// can also be specified.  The ID of the new manifest list is returned as a string
+func Add(ctx context.Context, name string, options image.ManifestAddOpts) (string, error) {
+	var idr handlers.IDResponse
+	conn, err := bindings.GetClient(ctx)
+	if err != nil {
+		return "", err
+	}
+	optionsString, err := jsoniter.MarshalToString(options)
+	if err != nil {
+		return "", err
+	}
+	stringReader := strings.NewReader(optionsString)
+	response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, name)
+	if err != nil {
+		return "", err
+	}
+	return idr.ID, response.Process(&idr)
+}
+
+// Remove deletes a manifest entry from a manifest list.  Both name and the digest to be
+// removed are mandatory inputs.  The ID of the new manifest list is returned as a string.
+func Remove(ctx context.Context, name, digest string) (string, error) {
+	var idr handlers.IDResponse
+	conn, err := bindings.GetClient(ctx)
+	if err != nil {
+		return "", err
+	}
+	params := url.Values{}
+	params.Set("digest", digest)
+	response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, name)
+	if err != nil {
+		return "", err
+	}
+	return idr.ID, response.Process(&idr)
+}
+
+// Push takes a manifest list and pushes to a destination.  If the destination is not specified,
+// the name will be used instead.  If the optional all boolean is specified, all images specified
+// in the list will be pushed as well.
+func Push(ctx context.Context, name string, destination *string, all *bool) (string, error) {
+	var (
+		idr handlers.IDResponse
+	)
+	dest := name
+	conn, err := bindings.GetClient(ctx)
+	if err != nil {
+		return "", err
+	}
+	params := url.Values{}
+	params.Set("image", name)
+	if destination != nil {
+		dest = name
+	}
+	params.Set("destination", dest)
+	if all != nil {
+		params.Set("all", strconv.FormatBool(*all))
+	}
+	response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, name)
+	if err != nil {
+		return "", err
+	}
+	return idr.ID, response.Process(&idr)
+}
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
new file mode 100644
index 000000000..23c3d8194
--- /dev/null
+++ b/pkg/bindings/test/manifests_test.go
@@ -0,0 +1,124 @@
+package test_bindings
+
+import (
+	"net/http"
+	"time"
+
+	"github.com/containers/libpod/libpod/image"
+	"github.com/containers/libpod/pkg/bindings"
+	"github.com/containers/libpod/pkg/bindings/images"
+	"github.com/containers/libpod/pkg/bindings/manifests"
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman containers ", func() {
+	var (
+		bt *bindingTest
+		s  *gexec.Session
+	)
+
+	BeforeEach(func() {
+		bt = newBindingTest()
+		bt.RestoreImagesFromCache()
+		s = bt.startAPIService()
+		time.Sleep(1 * time.Second)
+		err := bt.NewConnection()
+		Expect(err).To(BeNil())
+	})
+
+	AfterEach(func() {
+		s.Kill()
+		bt.cleanup()
+	})
+
+	It("create manifest", func() {
+		// create manifest list without images
+		id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+		Expect(err).To(BeNil())
+		list, err := manifests.Inspect(bt.conn, id)
+		Expect(err).To(BeNil())
+		Expect(len(list.Manifests)).To(BeZero())
+
+		// creating a duplicate should fail as a 500
+		_, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+		Expect(err).ToNot(BeNil())
+		code, _ := bindings.CheckResponseCode(err)
+		Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+
+		_, err = images.Remove(bt.conn, id, nil)
+		Expect(err).To(BeNil())
+
+		// create manifest list with images
+		id, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
+		Expect(err).To(BeNil())
+		list, err = manifests.Inspect(bt.conn, id)
+		Expect(err).To(BeNil())
+		Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+	})
+
+	It("inspect bogus manifest", func() {
+		_, err := manifests.Inspect(bt.conn, "larry")
+		Expect(err).ToNot(BeNil())
+		code, _ := bindings.CheckResponseCode(err)
+		Expect(code).To(BeNumerically("==", http.StatusNotFound))
+	})
+
+	It("add manifest", func() {
+		// add to bogus should 404
+		_, err := manifests.Add(bt.conn, "foobar", image.ManifestAddOpts{})
+		Expect(err).ToNot(BeNil())
+		code, _ := bindings.CheckResponseCode(err)
+		Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+		id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+		Expect(err).To(BeNil())
+		opts := image.ManifestAddOpts{Images: []string{alpine.name}}
+		_, err = manifests.Add(bt.conn, id, opts)
+		Expect(err).To(BeNil())
+		list, err := manifests.Inspect(bt.conn, id)
+		Expect(err).To(BeNil())
+		Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+
+		// add bogus name to existing list should fail
+		opts.Images = []string{"larry"}
+		_, err = manifests.Add(bt.conn, id, opts)
+		Expect(err).ToNot(BeNil())
+		code, _ = bindings.CheckResponseCode(err)
+		Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+	})
+
+	It("remove manifest", func() {
+		// removal on bogus manifest list should be 404
+		_, err := manifests.Remove(bt.conn, "larry", "1234")
+		Expect(err).ToNot(BeNil())
+		code, _ := bindings.CheckResponseCode(err)
+		Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+		id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
+		Expect(err).To(BeNil())
+		data, err := manifests.Inspect(bt.conn, id)
+		Expect(err).To(BeNil())
+		Expect(len(data.Manifests)).To(BeNumerically("==", 1))
+
+		// removal on a good manifest list with a bad digest should be 400
+		_, err = manifests.Remove(bt.conn, id, "!234")
+		Expect(err).ToNot(BeNil())
+		code, _ = bindings.CheckResponseCode(err)
+		Expect(code).To(BeNumerically("==", http.StatusBadRequest))
+
+		digest := data.Manifests[0].Digest.String()
+		_, err = manifests.Remove(bt.conn, id, digest)
+		Expect(err).To(BeNil())
+
+		// removal on good manifest with good digest should work
+		data, err = manifests.Inspect(bt.conn, id)
+		Expect(err).To(BeNil())
+		Expect(len(data.Manifests)).To(BeZero())
+	})
+
+	It("push manifest", func() {
+		Skip("TODO")
+	})
+})
diff --git a/vendor/github.com/containers/buildah/manifests/copy.go b/vendor/github.com/containers/buildah/manifests/copy.go
new file mode 100644
index 000000000..7e651a46c
--- /dev/null
+++ b/vendor/github.com/containers/buildah/manifests/copy.go
@@ -0,0 +1,15 @@
+package manifests
+
+import (
+	"github.com/containers/image/v5/signature"
+)
+
+var (
+	// storageAllowedPolicyScopes overrides the policy for local storage
+	// to ensure that we can read images from it.
+	storageAllowedPolicyScopes = signature.PolicyTransportScopes{
+		"": []signature.PolicyRequirement{
+			signature.NewPRInsecureAcceptAnything(),
+		},
+	}
+)
diff --git a/vendor/github.com/containers/buildah/manifests/manifests.go b/vendor/github.com/containers/buildah/manifests/manifests.go
new file mode 100644
index 000000000..0fe7e477b
--- /dev/null
+++ b/vendor/github.com/containers/buildah/manifests/manifests.go
@@ -0,0 +1,397 @@
+package manifests
+
+import (
+	"context"
+	"encoding/json"
+	stderrors "errors"
+	"io"
+
+	"github.com/containers/buildah/pkg/manifests"
+	"github.com/containers/buildah/pkg/supplemented"
+	cp "github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/docker/reference"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/manifest"
+	"github.com/containers/image/v5/signature"
+	is "github.com/containers/image/v5/storage"
+	"github.com/containers/image/v5/transports"
+	"github.com/containers/image/v5/transports/alltransports"
+	"github.com/containers/image/v5/types"
+	"github.com/containers/storage"
+	digest "github.com/opencontainers/go-digest"
+	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+)
+
+const instancesData = "instances.json"
+
+// ErrListImageUnknown is returned when we attempt to create an image reference
+// for a List that has not yet been saved to an image.
+var ErrListImageUnknown = stderrors.New("unable to determine which image holds the manifest list")
+
+type list struct {
+	manifests.List
+	instances map[digest.Digest]string
+}
+
+// List is a manifest list or image index, either created using Create(), or
+// loaded from local storage using LoadFromImage().
+type List interface {
+	manifests.List
+	SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error)
+	Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error)
+	Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error)
+	Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error)
+}
+
+// PushOptions includes various settings which are needed for pushing the
+// manifest list and its instances.
+type PushOptions struct {
+	Store              storage.Store
+	SystemContext      *types.SystemContext  // github.com/containers/image/types.SystemContext
+	ImageListSelection cp.ImageListSelection // set to either CopySystemImage, CopyAllImages, or CopySpecificImages
+	Instances          []digest.Digest       // instances to copy if ImageListSelection == CopySpecificImages
+	ReportWriter       io.Writer             // will be used to log the writing of the list and any blobs
+	SignBy             string                // fingerprint of GPG key to use to sign images
+	RemoveSignatures   bool                  // true to discard signatures in images
+	ManifestType       string                // the format to use when saving the list - possible options are oci, v2s1, and v2s2
+}
+
+// Create creates a new list containing information about the specified image,
+// computing its manifest's digest, and retrieving OS and architecture
+// information from its configuration blob.  Returns the new list, and the
+// instanceDigest for the initial image.
+func Create() List {
+	return &list{
+		List:      manifests.Create(),
+		instances: make(map[digest.Digest]string),
+	}
+}
+
+// LoadFromImage reads the manifest list or image index, and additional
+// information about where the various instances that it contains live, from an
+// image record with the specified ID in local storage.
+func LoadFromImage(store storage.Store, image string) (string, List, error) {
+	img, err := store.Image(image)
+	if err != nil {
+		return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image)
+	}
+	manifestBytes, err := store.ImageBigData(img.ID, storage.ImageDigestManifestBigDataNamePrefix)
+	if err != nil {
+		return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image)
+	}
+	manifestList, err := manifests.FromBlob(manifestBytes)
+	if err != nil {
+		return "", nil, err
+	}
+	list := &list{
+		List:      manifestList,
+		instances: make(map[digest.Digest]string),
+	}
+	instancesBytes, err := store.ImageBigData(img.ID, instancesData)
+	if err != nil {
+		return "", nil, errors.Wrapf(err, "error locating image %q for loading instance list", image)
+	}
+	if err := json.Unmarshal(instancesBytes, &list.instances); err != nil {
+		return "", nil, errors.Wrapf(err, "error decoding instance list for image %q", image)
+	}
+	list.instances[""] = img.ID
+	return img.ID, list, err
+}
+
+// SaveToImage saves the manifest list or image index as the manifest of an
+// Image record with the specified names in local storage, generating a random
+// image ID if none is specified.  It also stores information about where the
+// images whose manifests are included in the list can be found.
+func (l *list) SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error) {
+	manifestBytes, err := l.List.Serialize(mimeType)
+	if err != nil {
+		return "", err
+	}
+	instancesBytes, err := json.Marshal(&l.instances)
+	if err != nil {
+		return "", err
+	}
+	img, err := store.CreateImage(imageID, names, "", "", &storage.ImageOptions{})
+	if err == nil || errors.Cause(err) == storage.ErrDuplicateID {
+		created := (err == nil)
+		if created {
+			imageID = img.ID
+			l.instances[""] = img.ID
+		}
+		err := store.SetImageBigData(imageID, storage.ImageDigestManifestBigDataNamePrefix, manifestBytes, manifest.Digest)
+		if err != nil {
+			if created {
+				if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
+					logrus.Errorf("error deleting image %q after failing to save manifest for it", img.ID)
+				}
+			}
+			return "", errors.Wrapf(err, "error saving manifest list to image %q", imageID)
+		}
+		err = store.SetImageBigData(imageID, instancesData, instancesBytes, nil)
+		if err != nil {
+			if created {
+				if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
+					logrus.Errorf("error deleting image %q after failing to save instance locations for it", img.ID)
+				}
+			}
+			return "", errors.Wrapf(err, "error saving instance list to image %q", imageID)
+		}
+		return imageID, nil
+	}
+	return "", errors.Wrapf(err, "error creating image to hold manifest list")
+}
+
+// Reference returns an image reference for the composite image being built
+// in the list, or an error if the list has never been saved to a local image.
+func (l *list) Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error) {
+	if l.instances[""] == "" {
+		return nil, errors.Wrap(ErrListImageUnknown, "error building reference to list")
+	}
+	s, err := is.Transport.ParseStoreReference(store, l.instances[""])
+	if err != nil {
+		return nil, errors.Wrapf(err, "error creating ImageReference from image %q", l.instances[""])
+	}
+	references := make([]types.ImageReference, 0, len(l.instances))
+	whichInstances := make([]digest.Digest, 0, len(l.instances))
+	switch multiple {
+	case cp.CopyAllImages, cp.CopySystemImage:
+		for instance := range l.instances {
+			if instance != "" {
+				whichInstances = append(whichInstances, instance)
+			}
+		}
+	case cp.CopySpecificImages:
+		for instance := range l.instances {
+			for _, allowed := range instances {
+				if instance == allowed {
+					whichInstances = append(whichInstances, instance)
+				}
+			}
+		}
+	}
+	for _, instance := range whichInstances {
+		imageName := l.instances[instance]
+		ref, err := alltransports.ParseImageName(imageName)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error creating ImageReference from image %q", imageName)
+		}
+		references = append(references, ref)
+	}
+	return supplemented.Reference(s, references, multiple, instances), nil
+}
+
+// Push saves the manifest list and whichever blobs are needed to a destination location.
+func (l *list) Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) {
+	// Load the system signing policy.
+	pushPolicy, err := signature.DefaultPolicy(options.SystemContext)
+	if err != nil {
+		return nil, "", errors.Wrapf(err, "error obtaining default signature policy")
+	}
+
+	// Override the settings for local storage to make sure that we can always read the source "image".
+	pushPolicy.Transports[is.Transport.Name()] = storageAllowedPolicyScopes
+
+	policyContext, err := signature.NewPolicyContext(pushPolicy)
+	if err != nil {
+		return nil, "", errors.Wrapf(err, "error creating new signature policy context")
+	}
+	defer func() {
+		if err2 := policyContext.Destroy(); err2 != nil {
+			logrus.Errorf("error destroying signature policy context: %v", err2)
+		}
+	}()
+
+	// If we were given a media type that corresponds to a multiple-images
+	// type, reset it to a valid corresponding single-image type, since we
+	// already expect the image library to infer the list type from the
+	// image type that we're telling it to force.
+	singleImageManifestType := options.ManifestType
+	switch singleImageManifestType {
+	case v1.MediaTypeImageIndex:
+		singleImageManifestType = v1.MediaTypeImageManifest
+	case manifest.DockerV2ListMediaType:
+		singleImageManifestType = manifest.DockerV2Schema2MediaType
+	}
+
+	// Build a source reference for our list and grab bag full of blobs.
+	src, err := l.Reference(options.Store, options.ImageListSelection, options.Instances)
+	if err != nil {
+		return nil, "", err
+	}
+	copyOptions := &cp.Options{
+		ImageListSelection:    options.ImageListSelection,
+		Instances:             options.Instances,
+		SourceCtx:             options.SystemContext,
+		DestinationCtx:        options.SystemContext,
+		ReportWriter:          options.ReportWriter,
+		RemoveSignatures:      options.RemoveSignatures,
+		SignBy:                options.SignBy,
+		ForceManifestMIMEType: singleImageManifestType,
+	}
+
+	// Copy whatever we were asked to copy.
+	manifestBytes, err := cp.Image(ctx, policyContext, dest, src, copyOptions)
+	if err != nil {
+		return nil, "", err
+	}
+	manifestDigest, err := manifest.Digest(manifestBytes)
+	if err != nil {
+		return nil, "", err
+	}
+	return nil, manifestDigest, nil
+}
+
+// Add adds information about the specified image to the list, computing the
+// image's manifest's digest, retrieving OS and architecture information from
+// the image's configuration, and recording the image's reference so that it
+// can be found at push-time.  Returns the instanceDigest for the image.  If
+// the reference points to an image list, either all instances are added (if
+// "all" is true), or the instance which matches "sys" (if "all" is false) will
+// be added.
+func (l *list) Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error) {
+	src, err := ref.NewImageSource(ctx, sys)
+	if err != nil {
+		return "", errors.Wrapf(err, "error setting up to read manifest and configuration from %q", transports.ImageName(ref))
+	}
+	defer src.Close()
+
+	type instanceInfo struct {
+		instanceDigest                       *digest.Digest
+		OS, Architecture, OSVersion, Variant string
+		Features, OSFeatures, Annotations    []string
+		Size                                 int64
+	}
+	var instanceInfos []instanceInfo
+	var manifestDigest digest.Digest
+
+	primaryManifestBytes, primaryManifestType, err := src.GetManifest(ctx, nil)
+	if err != nil {
+		return "", errors.Wrapf(err, "error reading manifest from %q", transports.ImageName(ref))
+	}
+
+	if manifest.MIMETypeIsMultiImage(primaryManifestType) {
+		lists, err := manifests.FromBlob(primaryManifestBytes)
+		if err != nil {
+			return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref))
+		}
+		if all {
+			for i, instance := range lists.OCIv1().Manifests {
+				platform := instance.Platform
+				if platform == nil {
+					platform = &v1.Platform{}
+				}
+				instanceDigest := instance.Digest
+				instanceInfo := instanceInfo{
+					instanceDigest: &instanceDigest,
+					OS:             platform.OS,
+					Architecture:   platform.Architecture,
+					OSVersion:      platform.OSVersion,
+					Variant:        platform.Variant,
+					Features:       append([]string{}, lists.Docker().Manifests[i].Platform.Features...),
+					OSFeatures:     append([]string{}, platform.OSFeatures...),
+					Size:           instance.Size,
+				}
+				instanceInfos = append(instanceInfos, instanceInfo)
+			}
+		} else {
+			list, err := manifest.ListFromBlob(primaryManifestBytes, primaryManifestType)
+			if err != nil {
+				return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref))
+			}
+			instanceDigest, err := list.ChooseInstance(sys)
+			if err != nil {
+				return "", errors.Wrapf(err, "error selecting image from manifest list in %q", transports.ImageName(ref))
+			}
+			added := false
+			for i, instance := range lists.OCIv1().Manifests {
+				if instance.Digest != instanceDigest {
+					continue
+				}
+				platform := instance.Platform
+				if platform == nil {
+					platform = &v1.Platform{}
+				}
+				instanceInfo := instanceInfo{
+					instanceDigest: &instanceDigest,
+					OS:             platform.OS,
+					Architecture:   platform.Architecture,
+					OSVersion:      platform.OSVersion,
+					Variant:        platform.Variant,
+					Features:       append([]string{}, lists.Docker().Manifests[i].Platform.Features...),
+					OSFeatures:     append([]string{}, platform.OSFeatures...),
+					Size:           instance.Size,
+				}
+				instanceInfos = append(instanceInfos, instanceInfo)
+				added = true
+			}
+			if !added {
+				instanceInfo := instanceInfo{
+					instanceDigest: &instanceDigest,
+				}
+				instanceInfos = append(instanceInfos, instanceInfo)
+			}
+		}
+	} else {
+		instanceInfo := instanceInfo{
+			instanceDigest: nil,
+		}
+		instanceInfos = append(instanceInfos, instanceInfo)
+	}
+
+	for _, instanceInfo := range instanceInfos {
+		if instanceInfo.OS == "" || instanceInfo.Architecture == "" {
+			img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, instanceInfo.instanceDigest))
+			if err != nil {
+				return "", errors.Wrapf(err, "error reading configuration blob from %q", transports.ImageName(ref))
+			}
+			config, err := img.OCIConfig(ctx)
+			if err != nil {
+				return "", errors.Wrapf(err, "error reading info about config blob from %q", transports.ImageName(ref))
+			}
+			if instanceInfo.OS == "" {
+				instanceInfo.OS = config.OS
+			}
+			if instanceInfo.Architecture == "" {
+				instanceInfo.Architecture = config.Architecture
+			}
+		}
+		manifestBytes, manifestType, err := src.GetManifest(ctx, instanceInfo.instanceDigest)
+		if err != nil {
+			return "", errors.Wrapf(err, "error reading manifest from %q, instance %q", transports.ImageName(ref), instanceInfo.instanceDigest)
+		}
+		if instanceInfo.instanceDigest == nil {
+			manifestDigest, err = manifest.Digest(manifestBytes)
+			if err != nil {
+				return "", errors.Wrapf(err, "error computing digest of manifest from %q", transports.ImageName(ref))
+			}
+			instanceInfo.instanceDigest = &manifestDigest
+			instanceInfo.Size = int64(len(manifestBytes))
+		} else {
+			if manifestDigest == "" {
+				manifestDigest = *instanceInfo.instanceDigest
+			}
+		}
+		err = l.List.AddInstance(*instanceInfo.instanceDigest, instanceInfo.Size, manifestType, instanceInfo.OS, instanceInfo.Architecture, instanceInfo.OSVersion, instanceInfo.OSFeatures, instanceInfo.Variant, instanceInfo.Features, instanceInfo.Annotations)
+		if err != nil {
+			return "", errors.Wrapf(err, "error adding instance with digest %q", *instanceInfo.instanceDigest)
+		}
+		if _, ok := l.instances[*instanceInfo.instanceDigest]; !ok {
+			l.instances[*instanceInfo.instanceDigest] = transports.ImageName(ref)
+		}
+	}
+
+	return manifestDigest, nil
+}
+
+// Remove filters out any instances in the list which match the specified digest.
+func (l *list) Remove(instanceDigest digest.Digest) error {
+	err := l.List.Remove(instanceDigest)
+	if err == nil {
+		if _, needToDelete := l.instances[instanceDigest]; needToDelete {
+			delete(l.instances, instanceDigest)
+		}
+	}
+	return err
+}
diff --git a/vendor/github.com/containers/buildah/pkg/manifests/errors.go b/vendor/github.com/containers/buildah/pkg/manifests/errors.go
new file mode 100644
index 000000000..8398d7efc
--- /dev/null
+++ b/vendor/github.com/containers/buildah/pkg/manifests/errors.go
@@ -0,0 +1,16 @@
+package manifests
+
+import (
+	"errors"
+)
+
+var (
+	// ErrDigestNotFound is returned when we look for an image instance
+	// with a particular digest in a list or index, and fail to find it.
+	ErrDigestNotFound = errors.New("no image instance matching the specified digest was found in the list or index")
+	// ErrManifestTypeNotSupported is returned when we attempt to parse a
+	// manifest with a known MIME type as a list or index, or when we attempt
+	// to serialize a list or index to a manifest with a MIME type that we
+	// don't know how to encode.
+	ErrManifestTypeNotSupported = errors.New("manifest type not supported")
+)
diff --git a/vendor/github.com/containers/buildah/pkg/manifests/manifests.go b/vendor/github.com/containers/buildah/pkg/manifests/manifests.go
new file mode 100644
index 000000000..ea9495ee7
--- /dev/null
+++ b/vendor/github.com/containers/buildah/pkg/manifests/manifests.go
@@ -0,0 +1,493 @@
+package manifests
+
+import (
+	"encoding/json"
+	"os"
+
+	"github.com/containers/image/v5/manifest"
+	digest "github.com/opencontainers/go-digest"
+	imgspec "github.com/opencontainers/image-spec/specs-go"
+	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/pkg/errors"
+)
+
+// List is a generic interface for manipulating a manifest list or an image
+// index.
+type List interface {
+	AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, os, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error
+	Remove(instanceDigest digest.Digest) error
+
+	SetURLs(instanceDigest digest.Digest, urls []string) error
+	URLs(instanceDigest digest.Digest) ([]string, error)
+
+	SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error
+	Annotations(instanceDigest *digest.Digest) (map[string]string, error)
+
+	SetOS(instanceDigest digest.Digest, os string) error
+	OS(instanceDigest digest.Digest) (string, error)
+
+	SetArchitecture(instanceDigest digest.Digest, arch string) error
+	Architecture(instanceDigest digest.Digest) (string, error)
+
+	SetOSVersion(instanceDigest digest.Digest, osVersion string) error
+	OSVersion(instanceDigest digest.Digest) (string, error)
+
+	SetVariant(instanceDigest digest.Digest, variant string) error
+	Variant(instanceDigest digest.Digest) (string, error)
+
+	SetFeatures(instanceDigest digest.Digest, features []string) error
+	Features(instanceDigest digest.Digest) ([]string, error)
+
+	SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error
+	OSFeatures(instanceDigest digest.Digest) ([]string, error)
+
+	Serialize(mimeType string) ([]byte, error)
+	Instances() []digest.Digest
+	OCIv1() *v1.Index
+	Docker() *manifest.Schema2List
+
+	findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error)
+	findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error)
+}
+
+type list struct {
+	docker manifest.Schema2List
+	oci    v1.Index
+}
+
+// OCIv1 returns the list as a Docker schema 2 list.  The returned structure should NOT be modified.
+func (l *list) Docker() *manifest.Schema2List {
+	return &l.docker
+}
+
+// OCIv1 returns the list as an OCI image index.  The returned structure should NOT be modified.
+func (l *list) OCIv1() *v1.Index {
+	return &l.oci
+}
+
+// Create creates a new list.
+func Create() List {
+	return &list{
+		docker: manifest.Schema2List{
+			SchemaVersion: 2,
+			MediaType:     manifest.DockerV2ListMediaType,
+		},
+		oci: v1.Index{
+			Versioned: imgspec.Versioned{SchemaVersion: 2},
+		},
+	}
+}
+
+// AddInstance adds an entry for the specified manifest digest, with assorted
+// additional information specified in parameters, to the list or index.
+func (l *list) AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, osName, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error {
+	if err := l.Remove(manifestDigest); err != nil && !os.IsNotExist(errors.Cause(err)) {
+		return err
+	}
+
+	schema2platform := manifest.Schema2PlatformSpec{
+		Architecture: architecture,
+		OS:           osName,
+		OSVersion:    osVersion,
+		OSFeatures:   osFeatures,
+		Variant:      variant,
+		Features:     features,
+	}
+	l.docker.Manifests = append(l.docker.Manifests, manifest.Schema2ManifestDescriptor{
+		Schema2Descriptor: manifest.Schema2Descriptor{
+			MediaType: manifestType,
+			Size:      manifestSize,
+			Digest:    manifestDigest,
+		},
+		Platform: schema2platform,
+	})
+
+	ociv1platform := v1.Platform{
+		Architecture: architecture,
+		OS:           osName,
+		OSVersion:    osVersion,
+		OSFeatures:   osFeatures,
+		Variant:      variant,
+	}
+	l.oci.Manifests = append(l.oci.Manifests, v1.Descriptor{
+		MediaType: manifestType,
+		Size:      manifestSize,
+		Digest:    manifestDigest,
+		Platform:  &ociv1platform,
+	})
+
+	return nil
+}
+
+// Remove filters out any instances in the list which match the specified digest.
+func (l *list) Remove(instanceDigest digest.Digest) error {
+	err := errors.Wrapf(os.ErrNotExist, "no instance matching digest %q found in manifest list", instanceDigest)
+	newDockerManifests := make([]manifest.Schema2ManifestDescriptor, 0, len(l.docker.Manifests))
+	for i := range l.docker.Manifests {
+		if l.docker.Manifests[i].Digest != instanceDigest {
+			newDockerManifests = append(newDockerManifests, l.docker.Manifests[i])
+		} else {
+			err = nil
+		}
+	}
+	l.docker.Manifests = newDockerManifests
+	newOCIv1Manifests := make([]v1.Descriptor, 0, len(l.oci.Manifests))
+	for i := range l.oci.Manifests {
+		if l.oci.Manifests[i].Digest != instanceDigest {
+			newOCIv1Manifests = append(newOCIv1Manifests, l.oci.Manifests[i])
+		} else {
+			err = nil
+		}
+	}
+	l.oci.Manifests = newOCIv1Manifests
+	return err
+}
+
+func (l *list) findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error) {
+	for i := range l.docker.Manifests {
+		if l.docker.Manifests[i].Digest == instanceDigest {
+			return &l.docker.Manifests[i], nil
+		}
+	}
+	return nil, errors.Wrapf(ErrDigestNotFound, "no Docker manifest matching digest %q was found in list", instanceDigest.String())
+}
+
+func (l *list) findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error) {
+	for i := range l.oci.Manifests {
+		if l.oci.Manifests[i].Digest == instanceDigest {
+			return &l.oci.Manifests[i], nil
+		}
+	}
+	return nil, errors.Wrapf(ErrDigestNotFound, "no OCI manifest matching digest %q was found in list", instanceDigest.String())
+}
+
+// SetURLs sets the URLs where the manifest might also be found.
+func (l *list) SetURLs(instanceDigest digest.Digest, urls []string) error {
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return err
+	}
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return err
+	}
+	oci.URLs = append([]string{}, urls...)
+	docker.URLs = append([]string{}, urls...)
+	return nil
+}
+
+// URLs retrieves the locations from which this object might possibly be downloaded.
+func (l *list) URLs(instanceDigest digest.Digest) ([]string, error) {
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return nil, err
+	}
+	return append([]string{}, oci.URLs...), nil
+}
+
+// SetAnnotations sets annotations on the image index, or on a specific manifest.
+// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
+func (l *list) SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error {
+	a := &l.oci.Annotations
+	if instanceDigest != nil {
+		oci, err := l.findOCIv1(*instanceDigest)
+		if err != nil {
+			return err
+		}
+		a = &oci.Annotations
+	}
+	(*a) = make(map[string]string)
+	for k, v := range annotations {
+		(*a)[k] = v
+	}
+	return nil
+}
+
+// Annotations retrieves the annotations which have been set on the image index, or on one instance.
+// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
+func (l *list) Annotations(instanceDigest *digest.Digest) (map[string]string, error) {
+	a := l.oci.Annotations
+	if instanceDigest != nil {
+		oci, err := l.findOCIv1(*instanceDigest)
+		if err != nil {
+			return nil, err
+		}
+		a = oci.Annotations
+	}
+	annotations := make(map[string]string)
+	for k, v := range a {
+		annotations[k] = v
+	}
+	return annotations, nil
+}
+
+// SetOS sets the OS field in the platform information associated with the instance with the specified digest.
+func (l *list) SetOS(instanceDigest digest.Digest, os string) error {
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return err
+	}
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return err
+	}
+	docker.Platform.OS = os
+	oci.Platform.OS = os
+	return nil
+}
+
+// OS retrieves the OS field in the platform information associated with the instance with the specified digest.
+func (l *list) OS(instanceDigest digest.Digest) (string, error) {
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return "", err
+	}
+	return oci.Platform.OS, nil
+}
+
+// SetArchitecture sets the Architecture field in the platform information associated with the instance with the specified digest.
+func (l *list) SetArchitecture(instanceDigest digest.Digest, arch string) error {
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return err
+	}
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return err
+	}
+	docker.Platform.Architecture = arch
+	oci.Platform.Architecture = arch
+	return nil
+}
+
+// Architecture retrieves the Architecture field in the platform information associated with the instance with the specified digest.
+func (l *list) Architecture(instanceDigest digest.Digest) (string, error) {
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return "", err
+	}
+	return oci.Platform.Architecture, nil
+}
+
+// SetOSVersion sets the OSVersion field in the platform information associated with the instance with the specified digest.
+func (l *list) SetOSVersion(instanceDigest digest.Digest, osVersion string) error {
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return err
+	}
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return err
+	}
+	docker.Platform.OSVersion = osVersion
+	oci.Platform.OSVersion = osVersion
+	return nil
+}
+
+// OSVersion retrieves the OSVersion field in the platform information associated with the instance with the specified digest.
+func (l *list) OSVersion(instanceDigest digest.Digest) (string, error) {
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return "", err
+	}
+	return oci.Platform.OSVersion, nil
+}
+
+// SetVariant sets the Variant field in the platform information associated with the instance with the specified digest.
+func (l *list) SetVariant(instanceDigest digest.Digest, variant string) error {
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return err
+	}
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return err
+	}
+	docker.Platform.Variant = variant
+	oci.Platform.Variant = variant
+	return nil
+}
+
+// Variant retrieves the Variant field in the platform information associated with the instance with the specified digest.
+func (l *list) Variant(instanceDigest digest.Digest) (string, error) {
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return "", err
+	}
+	return oci.Platform.Variant, nil
+}
+
+// SetFeatures sets the features list in the platform information associated with the instance with the specified digest.
+// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes.
+func (l *list) SetFeatures(instanceDigest digest.Digest, features []string) error {
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return err
+	}
+	docker.Platform.Features = append([]string{}, features...)
+	// no OCI equivalent
+	return nil
+}
+
+// Features retrieves the features list from the platform information associated with the instance with the specified digest.
+// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes.
+func (l *list) Features(instanceDigest digest.Digest) ([]string, error) {
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return nil, err
+	}
+	return append([]string{}, docker.Platform.Features...), nil
+}
+
+// SetOSFeatures sets the OS features list in the platform information associated with the instance with the specified digest.
+func (l *list) SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error {
+	docker, err := l.findDocker(instanceDigest)
+	if err != nil {
+		return err
+	}
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return err
+	}
+	docker.Platform.OSFeatures = append([]string{}, osFeatures...)
+	oci.Platform.OSFeatures = append([]string{}, osFeatures...)
+	return nil
+}
+
+// OSFeatures retrieves the OS features list from the platform information associated with the instance with the specified digest.
+func (l *list) OSFeatures(instanceDigest digest.Digest) ([]string, error) {
+	oci, err := l.findOCIv1(instanceDigest)
+	if err != nil {
+		return nil, err
+	}
+	return append([]string{}, oci.Platform.OSFeatures...), nil
+}
+
+// FromBlob builds a list from an encoded manifest list or image index.
+func FromBlob(manifestBytes []byte) (List, error) {
+	manifestType := manifest.GuessMIMEType(manifestBytes)
+	list := &list{
+		docker: manifest.Schema2List{
+			SchemaVersion: 2,
+			MediaType:     manifest.DockerV2ListMediaType,
+		},
+		oci: v1.Index{
+			Versioned: imgspec.Versioned{SchemaVersion: 2},
+		},
+	}
+	switch manifestType {
+	default:
+		return nil, errors.Wrapf(ErrManifestTypeNotSupported, "unable to load manifest list: unsupported format %q", manifestType)
+	case manifest.DockerV2ListMediaType:
+		if err := json.Unmarshal(manifestBytes, &list.docker); err != nil {
+			return nil, errors.Wrapf(err, "unable to parse Docker manifest list from image")
+		}
+		for _, m := range list.docker.Manifests {
+			list.oci.Manifests = append(list.oci.Manifests, v1.Descriptor{
+				MediaType: m.Schema2Descriptor.MediaType,
+				Size:      m.Schema2Descriptor.Size,
+				Digest:    m.Schema2Descriptor.Digest,
+				Platform: &v1.Platform{
+					Architecture: m.Platform.Architecture,
+					OS:           m.Platform.OS,
+					OSVersion:    m.Platform.OSVersion,
+					OSFeatures:   m.Platform.OSFeatures,
+					Variant:      m.Platform.Variant,
+				},
+			})
+		}
+	case v1.MediaTypeImageIndex:
+		if err := json.Unmarshal(manifestBytes, &list.oci); err != nil {
+			return nil, errors.Wrapf(err, "unable to parse OCIv1 manifest list")
+		}
+		for _, m := range list.oci.Manifests {
+			platform := m.Platform
+			if platform == nil {
+				platform = &v1.Platform{}
+			}
+			list.docker.Manifests = append(list.docker.Manifests, manifest.Schema2ManifestDescriptor{
+				Schema2Descriptor: manifest.Schema2Descriptor{
+					MediaType: m.MediaType,
+					Size:      m.Size,
+					Digest:    m.Digest,
+				},
+				Platform: manifest.Schema2PlatformSpec{
+					Architecture: platform.Architecture,
+					OS:           platform.OS,
+					OSVersion:    platform.OSVersion,
+					OSFeatures:   platform.OSFeatures,
+					Variant:      platform.Variant,
+				},
+			})
+		}
+	}
+	return list, nil
+}
+
+func (l *list) preferOCI() bool {
+	// If we have any data that's only in the OCI format, use that.
+	for _, m := range l.oci.Manifests {
+		if len(m.URLs) > 0 {
+			return true
+		}
+		if len(m.Annotations) > 0 {
+			return true
+		}
+	}
+	// If we have any data that's only in the Docker format, use that.
+	for _, m := range l.docker.Manifests {
+		if len(m.Platform.Features) > 0 {
+			return false
+		}
+	}
+	// If we have no manifests, remember that the Docker format is
+	// explicitly typed, so use that.  Otherwise, default to using the OCI
+	// format.
+	return len(l.docker.Manifests) != 0
+}
+
+// Serialize encodes the list using the specified format, or by selecting one
+// which it thinks is appropriate.
+func (l *list) Serialize(mimeType string) ([]byte, error) {
+	var manifestBytes []byte
+	switch mimeType {
+	case "":
+		if l.preferOCI() {
+			manifest, err := json.Marshal(&l.oci)
+			if err != nil {
+				return nil, errors.Wrapf(err, "error marshalling OCI image index")
+			}
+			manifestBytes = manifest
+		} else {
+			manifest, err := json.Marshal(&l.docker)
+			if err != nil {
+				return nil, errors.Wrapf(err, "error marshalling Docker manifest list")
+			}
+			manifestBytes = manifest
+		}
+	case v1.MediaTypeImageIndex:
+		manifest, err := json.Marshal(&l.oci)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error marshalling OCI image index")
+		}
+		manifestBytes = manifest
+	case manifest.DockerV2ListMediaType:
+		manifest, err := json.Marshal(&l.docker)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error marshalling Docker manifest list")
+		}
+		manifestBytes = manifest
+	default:
+		return nil, errors.Wrapf(ErrManifestTypeNotSupported, "serializing list to type %q not implemented", mimeType)
+	}
+	return manifestBytes, nil
+}
+
+// Instances returns the list of image instances mentioned in this list.
+func (l *list) Instances() []digest.Digest {
+	instances := make([]digest.Digest, 0, len(l.oci.Manifests))
+	for _, instance := range l.oci.Manifests {
+		instances = append(instances, instance.Digest)
+	}
+	return instances
+}
diff --git a/vendor/github.com/containers/buildah/pkg/supplemented/errors.go b/vendor/github.com/containers/buildah/pkg/supplemented/errors.go
new file mode 100644
index 000000000..6de679b50
--- /dev/null
+++ b/vendor/github.com/containers/buildah/pkg/supplemented/errors.go
@@ -0,0 +1,17 @@
+package supplemented
+
+import (
+	"errors"
+
+	"github.com/containers/buildah/pkg/manifests"
+)
+
+var (
+	// ErrDigestNotFound is returned when we look for an image instance
+	// with a particular digest in a list or index, and fail to find it.
+	ErrDigestNotFound = manifests.ErrDigestNotFound
+	// ErrBlobNotFound is returned when try to figure out which supplemental
+	// image we should ask for a blob with the specified characteristics,
+	// based on the information in each of the supplemental images' manifests.
+	ErrBlobNotFound = errors.New("location of blob could not be determined")
+)
diff --git a/vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go b/vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go
new file mode 100644
index 000000000..9dd47a0e2
--- /dev/null
+++ b/vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go
@@ -0,0 +1,393 @@
+package supplemented
+
+import (
+	"container/list"
+	"context"
+	"io"
+
+	cp "github.com/containers/image/v5/copy"
+	"github.com/containers/image/v5/image"
+	"github.com/containers/image/v5/manifest"
+	"github.com/containers/image/v5/transports"
+	"github.com/containers/image/v5/types"
+	multierror "github.com/hashicorp/go-multierror"
+	digest "github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+)
+
+// supplementedImageReference groups multiple references together.
+type supplementedImageReference struct {
+	types.ImageReference
+	references []types.ImageReference
+	multiple   cp.ImageListSelection
+	instances  []digest.Digest
+}
+
+// supplementedImageSource represents an image, plus all of the blobs of other images.
+type supplementedImageSource struct {
+	types.ImageSource
+	reference                 types.ImageReference
+	manifest                  []byte                              // The manifest list or image index.
+	manifestType              string                              // The MIME type of the manifest list or image index.
+	sourceDefaultInstances    map[types.ImageSource]digest.Digest // The default manifest instances of open ImageSource objects.
+	sourceInstancesByInstance map[digest.Digest]types.ImageSource // A map from manifest instance digests to open ImageSource objects.
+	instancesByBlobDigest     map[digest.Digest]digest.Digest     // A map from blob digests to manifest instance digests.
+}
+
+// Reference groups one reference and some number of additional references
+// together as a group.  The first reference's default instance will be treated
+// as the default instance of the resulting reference, with the other
+// references' instances made available as instances for their respective
+// digests.
+func Reference(ref types.ImageReference, supplemental []types.ImageReference, multiple cp.ImageListSelection, instances []digest.Digest) types.ImageReference {
+	if len(instances) > 0 {
+		i := make([]digest.Digest, len(instances))
+		copy(i, instances)
+		instances = i
+	}
+	return &supplementedImageReference{
+		ImageReference: ref,
+		references:     append([]types.ImageReference{}, supplemental...),
+		multiple:       multiple,
+		instances:      instances,
+	}
+}
+
+// NewImage returns a new higher-level view of the image.
+func (s *supplementedImageReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
+	src, err := s.NewImageSource(ctx, sys)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error building a new Image using an ImageSource")
+	}
+	return image.FromSource(ctx, sys, src)
+}
+
+// NewImageSource opens the referenced images, scans their manifests for
+// instances, and builds mappings from each blob mentioned in them to their
+// instances.
+func (s *supplementedImageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (iss types.ImageSource, err error) {
+	sources := make(map[digest.Digest]types.ImageSource)
+	defaultInstances := make(map[types.ImageSource]digest.Digest)
+	instances := make(map[digest.Digest]digest.Digest)
+	var sis *supplementedImageSource
+
+	// Open the default instance for reading.
+	top, err := s.ImageReference.NewImageSource(ctx, sys)
+	if err != nil {
+		return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(s.ImageReference))
+	}
+
+	defer func() {
+		if err != nil {
+			if iss != nil {
+				// The composite source has been created.  Use its Close method.
+				if err2 := iss.Close(); err2 != nil {
+					logrus.Errorf("error opening image: %v", err2)
+				}
+			} else if top != nil {
+				// The composite source has not been created, but the top was already opened.  Close it.
+				if err2 := top.Close(); err2 != nil {
+					logrus.Errorf("error opening image: %v", err2)
+				}
+			}
+		}
+	}()
+
+	var addSingle, addMulti func(manifestBytes []byte, manifestType string, src types.ImageSource) error
+	type manifestToRead struct {
+		src      types.ImageSource
+		instance *digest.Digest
+	}
+	manifestsToRead := list.New()
+
+	addSingle = func(manifestBytes []byte, manifestType string, src types.ImageSource) error {
+		// Mark this instance as being associated with this ImageSource.
+		manifestDigest, err := manifest.Digest(manifestBytes)
+		if err != nil {
+			return errors.Wrapf(err, "error computing digest over manifest %q", string(manifestBytes))
+		}
+		sources[manifestDigest] = src
+
+		// Parse the manifest as a single image.
+		man, err := manifest.FromBlob(manifestBytes, manifestType)
+		if err != nil {
+			return errors.Wrapf(err, "error parsing manifest %q", string(manifestBytes))
+		}
+
+		// Log the config blob's digest and the blobs of its layers as associated with this manifest.
+		config := man.ConfigInfo()
+		if config.Digest != "" {
+			instances[config.Digest] = manifestDigest
+			logrus.Debugf("blob %q belongs to %q", config.Digest, manifestDigest)
+		}
+
+		layers := man.LayerInfos()
+		for _, layer := range layers {
+			instances[layer.Digest] = manifestDigest
+			logrus.Debugf("layer %q belongs to %q", layer.Digest, manifestDigest)
+		}
+
+		return nil
+	}
+
+	addMulti = func(manifestBytes []byte, manifestType string, src types.ImageSource) error {
+		// Mark this instance as being associated with this ImageSource.
+		manifestDigest, err := manifest.Digest(manifestBytes)
+		if err != nil {
+			return errors.Wrapf(err, "error computing manifest digest")
+		}
+		sources[manifestDigest] = src
+
+		// Parse the manifest as a list of images.
+		list, err := manifest.ListFromBlob(manifestBytes, manifestType)
+		if err != nil {
+			return errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(manifestBytes), manifestType)
+		}
+
+		// Figure out which of its instances we want to look at.
+		var chaseInstances []digest.Digest
+		switch s.multiple {
+		case cp.CopySystemImage:
+			instance, err := list.ChooseInstance(sys)
+			if err != nil {
+				return errors.Wrapf(err, "error selecting appropriate instance from list")
+			}
+			chaseInstances = []digest.Digest{instance}
+		case cp.CopySpecificImages:
+			chaseInstances = s.instances
+		case cp.CopyAllImages:
+			chaseInstances = list.Instances()
+		}
+
+		// Queue these manifest instances for reading from this
+		// ImageSource later, if we don't stumble across them somewhere
+		// else first.
+		for _, instanceIterator := range chaseInstances {
+			instance := instanceIterator
+			next := &manifestToRead{
+				src:      src,
+				instance: &instance,
+			}
+			if src == top {
+				// Prefer any other source.
+				manifestsToRead.PushBack(next)
+			} else {
+				// Prefer this source over the first ("main") one.
+				manifestsToRead.PushFront(next)
+			}
+		}
+		return nil
+	}
+
+	visitedReferences := make(map[types.ImageReference]struct{})
+	for i, ref := range append([]types.ImageReference{s.ImageReference}, s.references...) {
+		if _, visited := visitedReferences[ref]; visited {
+			continue
+		}
+		visitedReferences[ref] = struct{}{}
+
+		// Open this image for reading.
+		var src types.ImageSource
+		if ref == s.ImageReference {
+			src = top
+		} else {
+			src, err = ref.NewImageSource(ctx, sys)
+			if err != nil {
+				return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(ref))
+			}
+		}
+
+		// Read the default manifest for the image.
+		manifestBytes, manifestType, err := src.GetManifest(ctx, nil)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error reading default manifest from image %q", transports.ImageName(ref))
+		}
+
+		// If this is the first image, mark it as our starting point.
+		if i == 0 {
+			sources[""] = src
+
+			sis = &supplementedImageSource{
+				ImageSource:               top,
+				reference:                 s,
+				manifest:                  manifestBytes,
+				manifestType:              manifestType,
+				sourceDefaultInstances:    defaultInstances,
+				sourceInstancesByInstance: sources,
+				instancesByBlobDigest:     instances,
+			}
+			iss = sis
+		}
+
+		// Record the digest of the ImageSource's default instance's manifest.
+		manifestDigest, err := manifest.Digest(manifestBytes)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error computing digest of manifest from image %q", transports.ImageName(ref))
+		}
+		sis.sourceDefaultInstances[src] = manifestDigest
+
+		// If the ImageSource's default manifest is a list, parse each of its instances.
+		if manifest.MIMETypeIsMultiImage(manifestType) {
+			if err = addMulti(manifestBytes, manifestType, src); err != nil {
+				return nil, errors.Wrapf(err, "error adding multi-image %q", transports.ImageName(ref))
+			}
+		} else {
+			if err = addSingle(manifestBytes, manifestType, src); err != nil {
+				return nil, errors.Wrapf(err, "error adding single image %q", transports.ImageName(ref))
+			}
+		}
+	}
+
+	// Parse the rest of the instances.
+	for manifestsToRead.Front() != nil {
+		front := manifestsToRead.Front()
+		value := front.Value
+		manifestToRead, ok := value.(*manifestToRead)
+		if !ok {
+			panic("bug: wrong type looking for *manifestToRead in list?")
+		}
+		manifestsToRead.Remove(front)
+
+		// If we already read this manifest, no need to read it again.
+		if _, alreadyRead := sources[*manifestToRead.instance]; alreadyRead {
+			continue
+		}
+
+		// Read the instance's manifest.
+		manifestBytes, manifestType, err := manifestToRead.src.GetManifest(ctx, manifestToRead.instance)
+		if err != nil {
+			// if errors.Cause(err) == storage.ErrImageUnknown || os.IsNotExist(errors.Cause(err)) {
+			// Trust that we either don't need it, or that it's in another reference.
+			// continue
+			// }
+			return nil, errors.Wrapf(err, "error reading manifest for instance %q", manifestToRead.instance)
+		}
+
+		if manifest.MIMETypeIsMultiImage(manifestType) {
+			// Add the list's contents.
+			if err = addMulti(manifestBytes, manifestType, manifestToRead.src); err != nil {
+				return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance)
+			}
+		} else {
+			// Add the single image's contents.
+			if err = addSingle(manifestBytes, manifestType, manifestToRead.src); err != nil {
+				return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance)
+			}
+		}
+	}
+
+	return iss, nil
+}
+
+func (s *supplementedImageReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
+	return errors.Errorf("deletion of images not implemented")
+}
+
+func (s *supplementedImageSource) Close() error {
+	var returnErr *multierror.Error
+	closed := make(map[types.ImageSource]struct{})
+	for _, sourceInstance := range s.sourceInstancesByInstance {
+		if _, closed := closed[sourceInstance]; closed {
+			continue
+		}
+		if err := sourceInstance.Close(); err != nil {
+			returnErr = multierror.Append(returnErr, err)
+		}
+		closed[sourceInstance] = struct{}{}
+	}
+	return returnErr.ErrorOrNil()
+}
+
+func (s *supplementedImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
+	requestInstanceDigest := instanceDigest
+	if instanceDigest == nil {
+		return s.manifest, s.manifestType, nil
+	}
+	if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
+		if *instanceDigest == s.sourceDefaultInstances[sourceInstance] {
+			requestInstanceDigest = nil
+		}
+		return sourceInstance.GetManifest(ctx, requestInstanceDigest)
+	}
+	return nil, "", errors.Wrapf(ErrDigestNotFound, "error getting manifest for digest %q", *instanceDigest)
+}
+
+func (s *supplementedImageSource) GetBlob(ctx context.Context, blob types.BlobInfo, bic types.BlobInfoCache) (io.ReadCloser, int64, error) {
+	sourceInstance, ok := s.instancesByBlobDigest[blob.Digest]
+	if !ok {
+		return nil, -1, errors.Wrapf(ErrBlobNotFound, "error blob %q in known instances", blob.Digest)
+	}
+	src, ok := s.sourceInstancesByInstance[sourceInstance]
+	if !ok {
+		return nil, -1, errors.Wrapf(ErrDigestNotFound, "error getting image source for instance %q", sourceInstance)
+	}
+	return src.GetBlob(ctx, blob, bic)
+}
+
+func (s *supplementedImageSource) HasThreadSafeGetBlob() bool {
+	checked := make(map[types.ImageSource]struct{})
+	for _, sourceInstance := range s.sourceInstancesByInstance {
+		if _, checked := checked[sourceInstance]; checked {
+			continue
+		}
+		if !sourceInstance.HasThreadSafeGetBlob() {
+			return false
+		}
+		checked[sourceInstance] = struct{}{}
+	}
+	return true
+}
+
+func (s *supplementedImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
+	var src types.ImageSource
+	requestInstanceDigest := instanceDigest
+	if instanceDigest == nil {
+		if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok {
+			src = sourceInstance
+		}
+	} else {
+		if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
+			src = sourceInstance
+		}
+		if *instanceDigest == s.sourceDefaultInstances[src] {
+			requestInstanceDigest = nil
+		}
+	}
+	if src != nil {
+		return src.GetSignatures(ctx, requestInstanceDigest)
+	}
+	return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to read signatures", *instanceDigest)
+}
+
+func (s *supplementedImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
+	var src types.ImageSource
+	requestInstanceDigest := instanceDigest
+	if instanceDigest == nil {
+		if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok {
+			src = sourceInstance
+		}
+	} else {
+		if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
+			src = sourceInstance
+		}
+		if *instanceDigest == s.sourceDefaultInstances[src] {
+			requestInstanceDigest = nil
+		}
+	}
+	if src != nil {
+		blobInfos, err := src.LayerInfosForCopy(ctx, requestInstanceDigest)
+		if err != nil {
+			return nil, errors.Wrapf(err, "error reading layer infos for copy from instance %q", instanceDigest)
+		}
+		var manifestDigest digest.Digest
+		if instanceDigest != nil {
+			manifestDigest = *instanceDigest
+		}
+		for _, blobInfo := range blobInfos {
+			s.instancesByBlobDigest[blobInfo.Digest] = manifestDigest
+		}
+		return blobInfos, nil
+	}
+	return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to copy layers", *instanceDigest)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f6b0e9b40..b6f075400 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -68,13 +68,16 @@ github.com/containers/buildah/bind
 github.com/containers/buildah/chroot
 github.com/containers/buildah/docker
 github.com/containers/buildah/imagebuildah
+github.com/containers/buildah/manifests
 github.com/containers/buildah/pkg/blobcache
 github.com/containers/buildah/pkg/chrootuser
 github.com/containers/buildah/pkg/cli
 github.com/containers/buildah/pkg/formats
+github.com/containers/buildah/pkg/manifests
 github.com/containers/buildah/pkg/overlay
 github.com/containers/buildah/pkg/parse
 github.com/containers/buildah/pkg/secrets
+github.com/containers/buildah/pkg/supplemented
 github.com/containers/buildah/pkg/umask
 github.com/containers/buildah/util
 # github.com/containers/common v0.4.2
-- 
cgit v1.2.3-54-g00ecf