From 6f5c5944afd495baa87f6c4c1431bb49b11e1482 Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Thu, 19 Sep 2019 17:19:34 -0400 Subject: [PATCH] manifests: add the module Wrap the pkg/manifests API in a version which knows how to load and save manifest lists and information about where the various instances are found in local storage. Add an ImageSource implementation that can use a manifest list and instance information to create a grab bag of all of the blobs known to all of the instances. Signed-off-by: Nalin Dahyabhai Closes: #1902 Approved by: rhatdan --- manifests/copy.go | 15 ++ manifests/manifests.go | 383 ++++++++++++++++++++++++++++++++++++ manifests/manifests_test.go | 306 ++++++++++++++++++++++++++++ 3 files changed, 704 insertions(+) create mode 100644 manifests/copy.go create mode 100644 manifests/manifests.go create mode 100644 manifests/manifests_test.go diff --git a/manifests/copy.go b/manifests/copy.go new file mode 100644 index 00000000000..7e651a46c02 --- /dev/null +++ b/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/manifests/manifests.go b/manifests/manifests.go new file mode 100644 index 00000000000..cfd141830db --- /dev/null +++ b/manifests/manifests.go @@ -0,0 +1,383 @@ +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 +} + +// 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) + } + }() + + // 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, + } + + // 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 + + manifestBytes, manifestType, err := src.GetManifest(ctx, nil) + if err != nil { + return "", errors.Wrapf(err, "error reading manifest from %q", transports.ImageName(ref)) + } + + if manifest.MIMETypeIsMultiImage(manifestType) { + list, err := manifest.ListFromBlob(manifestBytes, manifestType) + if err != nil { + return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref)) + } + if all { + lists, err := manifests.FromBlob(manifestBytes) + if err != nil { + return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref)) + } + 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 { + instanceDigest, err := list.ChooseInstance(sys) + if err != nil { + return "", errors.Wrapf(err, "error selecting image from manifest list in %q", transports.ImageName(ref)) + } + lists, err := manifests.FromBlob(manifestBytes) + if err != nil { + return "", errors.Wrapf(err, "error parsing 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 + } + } + if instanceInfo.instanceDigest == nil { + 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) + } + 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/manifests/manifests_test.go b/manifests/manifests_test.go new file mode 100644 index 00000000000..5ae070c6246 --- /dev/null +++ b/manifests/manifests_test.go @@ -0,0 +1,306 @@ +package manifests + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/containers/buildah/pkg/manifests" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + digest "github.com/opencontainers/go-digest" + "github.com/stretchr/testify/assert" +) + +var ( + _ List = &list{} + sys = &types.SystemContext{} + amd64sys = &types.SystemContext{ArchitectureChoice: "amd64"} + arm64sys = &types.SystemContext{ArchitectureChoice: "arm64"} + ppc64sys = &types.SystemContext{ArchitectureChoice: "ppc64le"} +) + +const ( + listImageName = "foo" + + otherListImage = "docker://k8s.gcr.io/pause:3.1" + otherListDigest = "sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea" + otherListAmd64Digest = "sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610" + otherListArm64Digest = "sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39" + otherListPpc64Digest = "sha256:bcf9771c0b505e68c65440474179592ffdfa98790eb54ffbf129969c5e429990" + otherListInstanceDigest = "docker://k8s.gcr.io/pause@sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39" +) + +func TestSaveLoad(t *testing.T) { + dir, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dir) + + storeOptions := storage.StoreOptions{ + GraphRoot: filepath.Join(dir, "root"), + RunRoot: filepath.Join(dir, "runroot"), + GraphDriverName: "vfs", + } + store, err := storage.GetStore(storeOptions) + assert.Nilf(t, err, "error opening store") + defer func() { + if _, err := store.Shutdown(true); err != nil { + assert.Nilf(t, err, "error closing store") + } + }() + + list := Create() + assert.NotNil(t, list, "Create() returned nil?") + + image, err := list.SaveToImage(store, "", []string{listImageName}, manifest.DockerV2ListMediaType) + assert.Nilf(t, err, "SaveToImage(1)") + imageReused, err := list.SaveToImage(store, image, nil, manifest.DockerV2ListMediaType) + assert.Nilf(t, err, "SaveToImage(2)") + + _, list, err = LoadFromImage(store, image) + assert.Nilf(t, err, "LoadFromImage(1)") + assert.NotNilf(t, list, "LoadFromImage(1)") + _, list, err = LoadFromImage(store, imageReused) + assert.Nilf(t, err, "LoadFromImage(2)") + assert.NotNilf(t, list, "LoadFromImage(2)") + _, list, err = LoadFromImage(store, listImageName) + assert.Nilf(t, err, "LoadFromImage(3)") + assert.NotNilf(t, list, "LoadFromImage(3)") +} + +func TestAddRemove(t *testing.T) { + ctx := context.TODO() + + ref, err := alltransports.ParseImageName(otherListImage) + assert.Nilf(t, err, "ParseImageName(%q)", otherListImage) + src, err := ref.NewImageSource(ctx, sys) + assert.Nilf(t, err, "NewImageSource(%q)", otherListImage) + defer assert.Nilf(t, src.Close(), "ImageSource.Close()") + m, _, err := src.GetManifest(ctx, nil) + assert.Nilf(t, err, "ImageSource.GetManifest()") + assert.Nilf(t, src.Close(), "ImageSource.GetManifest()") + listDigest, err := manifest.Digest(m) + assert.Nilf(t, err, "manifest.Digest()") + assert.Equalf(t, listDigest.String(), otherListDigest, "digest for image %q changed?", otherListImage) + + l, err := manifests.FromBlob(m) + assert.Nilf(t, err, "manifests.FromBlob()") + assert.NotNilf(t, l, "manifests.FromBlob()") + assert.Equalf(t, len(l.Instances()), 5, "image %q had an arch added?", otherListImage) + + list := Create() + instanceDigest, err := list.Add(ctx, amd64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equal(t, instanceDigest.String(), otherListAmd64Digest) + assert.Equalf(t, len(list.Instances()), 1, "too many instances added") + + list = Create() + instanceDigest, err = list.Add(ctx, arm64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equal(t, instanceDigest.String(), otherListArm64Digest) + assert.Equalf(t, len(list.Instances()), 1, "too many instances added") + + list = Create() + instanceDigest, err = list.Add(ctx, ppc64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equal(t, instanceDigest.String(), otherListPpc64Digest) + assert.Equalf(t, len(list.Instances()), 1, "too many instances added") + + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + assert.Equalf(t, len(list.Instances()), 5, "too many instances added") + + list = Create() + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + assert.Equalf(t, len(list.Instances()), 5, "too many instances added", otherListImage) + + for _, instance := range list.Instances() { + assert.Nilf(t, list.Remove(instance), "error removing instance %q", instance) + } + assert.Equalf(t, len(list.Instances()), 0, "should have removed all instances") + + ref, err = alltransports.ParseImageName(otherListInstanceDigest) + assert.Nilf(t, err, "ParseImageName(%q)", otherListInstanceDigest) + + list = Create() + _, err = list.Add(ctx, sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest) + + list = Create() + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest) +} + +func TestReference(t *testing.T) { + ctx := context.TODO() + + dir, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dir) + + storeOptions := storage.StoreOptions{ + GraphRoot: filepath.Join(dir, "root"), + RunRoot: filepath.Join(dir, "runroot"), + GraphDriverName: "vfs", + } + store, err := storage.GetStore(storeOptions) + assert.Nilf(t, err, "error opening store") + defer func() { + if _, err := store.Shutdown(true); err != nil { + assert.Nilf(t, err, "error closing store") + } + }() + + ref, err := alltransports.ParseImageName(otherListImage) + assert.Nilf(t, err, "ParseImageName(%q)", otherListImage) + + list := Create() + _, err = list.Add(ctx, ppc64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + + listRef, err := list.Reference(store, cp.CopyAllImages, nil) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopyAllImages, nil) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopySystemImage, nil) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest}) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest}) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + _, err = list.SaveToImage(store, "", []string{listImageName}, "") + assert.Nilf(t, err, "SaveToImage") + + listRef, err = list.Reference(store, cp.CopyAllImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySystemImage, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + + listRef, err = list.Reference(store, cp.CopyAllImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySystemImage, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") +} + +func TestPush(t *testing.T) { + ctx := context.TODO() + + dir, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dir) + + storeOptions := storage.StoreOptions{ + GraphRoot: filepath.Join(dir, "root"), + RunRoot: filepath.Join(dir, "runroot"), + GraphDriverName: "vfs", + } + store, err := storage.GetStore(storeOptions) + assert.Nilf(t, err, "error opening store") + defer func() { + if _, err := store.Shutdown(true); err != nil { + assert.Nilf(t, err, "error closing store") + } + }() + + dest, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dest) + + destRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", dest)) + assert.Nilf(t, err, "ParseImageName()") + + ref, err := alltransports.ParseImageName(otherListImage) + assert.Nilf(t, err, "ParseImageName(%q)", otherListImage) + + list := Create() + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + + _, err = list.SaveToImage(store, "", []string{listImageName}, "") + assert.Nilf(t, err, "SaveToImage") + + options := PushOptions{ + Store: store, + SystemContext: sys, + ImageListSelection: cp.CopyAllImages, + Instances: nil, + } + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(all)") + + options.ImageListSelection = cp.CopySystemImage + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(local)") + + options.ImageListSelection = cp.CopySpecificImages + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(none specified)") + + options.Instances = []digest.Digest{otherListAmd64Digest} + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(one specified)") + + options.Instances = append(options.Instances, otherListArm64Digest) + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(two specified)") + + options.Instances = append(options.Instances, otherListPpc64Digest) + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(three specified)") + + options.Instances = append(options.Instances, otherListDigest) + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(four specified)") +}