diff --git a/cmd/oras/cp.go b/cmd/oras/cp.go index 4319df6e3..699f85c31 100644 --- a/cmd/oras/cp.go +++ b/cmd/oras/cp.go @@ -21,11 +21,13 @@ import ( "strings" "sync" + "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" "oras.land/oras-go/v2" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/option" + "oras.land/oras/internal/graph" ) type copyOptions struct { @@ -117,6 +119,7 @@ func runCopy(opts copyOptions) error { committed := &sync.Map{} extendedCopyOptions := oras.DefaultExtendedCopyOptions extendedCopyOptions.Concurrency = opts.concurrency + extendedCopyOptions.FindPredecessors = graph.FindReferrerPredecessors extendedCopyOptions.PreCopy = display.StatusPrinter("Copying", opts.Verbose) extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) @@ -131,9 +134,10 @@ func runCopy(opts copyOptions) error { } var desc ocispec.Descriptor - if ref := opts.To.Reference; ref == "" { - // push to the destination with digest only if no tag specified - desc, err = src.Resolve(ctx, opts.From.Reference) + rOpts := oras.DefaultResolveOptions + rOpts.TargetPlatform = opts.Platform.Platform + if dstRef := opts.To.Reference; dstRef == "" { + desc, err = oras.Resolve(ctx, src, opts.From.Reference, rOpts) if err != nil { return err } @@ -144,7 +148,16 @@ func runCopy(opts copyOptions) error { } } else { if opts.recursive { - desc, err = oras.ExtendedCopy(ctx, src, opts.From.Reference, dst, opts.To.Reference, extendedCopyOptions) + srcRef := opts.From.Reference + if rOpts.TargetPlatform != nil { + // resolve source reference to specified platform + desc, err := oras.Resolve(ctx, src, opts.From.Reference, rOpts) + if err != nil { + return err + } + srcRef = desc.Digest.String() + } + desc, err = oras.ExtendedCopy(ctx, src, srcRef, dst, dstRef, extendedCopyOptions) } else { copyOptions := oras.CopyOptions{ CopyGraphOptions: extendedCopyOptions.CopyGraphOptions, @@ -152,13 +165,17 @@ func runCopy(opts copyOptions) error { if opts.Platform.Platform != nil { copyOptions.WithTargetPlatform(opts.Platform.Platform) } - desc, err = oras.Copy(ctx, src, opts.From.Reference, dst, opts.To.Reference, copyOptions) + desc, err = oras.Copy(ctx, src, opts.From.Reference, dst, dstRef, copyOptions) } } if err != nil { return err } + if from, err := digest.Parse(opts.From.Reference); err == nil && from != desc.Digest { + // correct source digest + opts.From.RawReference = fmt.Sprintf("%s@%s", opts.From.Path, desc.Digest.String()) + } fmt.Println("Copied", opts.From.AnnotatedReference(), "=>", opts.To.AnnotatedReference()) if len(opts.extraRefs) != 0 { diff --git a/internal/graph/graph.go b/internal/graph/graph.go index 84d5c9620..bcefdd43d 100644 --- a/internal/graph/graph.go +++ b/internal/graph/graph.go @@ -20,7 +20,6 @@ import ( "encoding/json" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/registry" "oras.land/oras/internal/docker" @@ -63,7 +62,7 @@ func Successors(ctx context.Context, fetcher content.Fetcher, node ocispec.Descr } // Referrers returns referrer nodes of desc in target. -func Referrers(ctx context.Context, target oras.ReadOnlyGraphTarget, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) { +func Referrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) { var results []ocispec.Descriptor if repo, ok := target.(registry.ReferrerLister); ok { // get referrers directly @@ -122,6 +121,33 @@ func Referrers(ctx context.Context, target oras.ReadOnlyGraphTarget, desc ocispe return results, nil } +// FindReferrerPredecessors returns referrer nodes of desc in target. +func FindReferrerPredecessors(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + var results []ocispec.Descriptor + if repo, ok := src.(registry.ReferrerLister); ok { + // get referrers directly + err := repo.Referrers(ctx, desc, "", func(referrers []ocispec.Descriptor) error { + results = append(results, referrers...) + return nil + }) + if err != nil { + return nil, err + } + return results, nil + } + predecessors, err := src.Predecessors(ctx, desc) + if err != nil { + return nil, err + } + for _, node := range predecessors { + switch node.MediaType { + case ocispec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest: + results = append(results, node) + } + } + return results, nil +} + func fetchBytes(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]byte, error) { rc, err := fetcher.Fetch(ctx, desc) if err != nil { diff --git a/internal/graph/graph_test.go b/internal/graph/graph_test.go index be2b4ffdf..1566dc9c6 100644 --- a/internal/graph/graph_test.go +++ b/internal/graph/graph_test.go @@ -26,7 +26,9 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/content/memory" + "oras.land/oras/internal/docker" ) type errLister struct { @@ -37,6 +39,14 @@ func (e *errLister) Referrers(ctx context.Context, desc ocispec.Descriptor, arti return errors.New("") } +type errFinder struct { + oras.ReadOnlyGraphTarget +} + +func (e *errFinder) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) { + return nil, errors.New("") +} + type refLister struct { referrers []ocispec.Descriptor oras.ReadOnlyGraphTarget @@ -50,6 +60,10 @@ type predecessorFinder struct { *memory.Store } +type fetcher struct { + content.Fetcher +} + func TestReferrers(t *testing.T) { ctx := context.Background() var blobs [][]byte @@ -172,3 +186,220 @@ func TestReferrers(t *testing.T) { } }) } + +func TestSuccessors(t *testing.T) { + var blobs [][]byte + var descs []ocispec.Descriptor + appendBlob := func(mediaType string, blob []byte) { + blobs = append(blobs, blob) + descs = append(descs, ocispec.Descriptor{ + MediaType: mediaType, + Digest: digest.FromBytes(blob), + Size: int64(len(blob)), + }) + } + generateImage := func(subject *ocispec.Descriptor, mediaType string, config ocispec.Descriptor, layers ...ocispec.Descriptor) { + manifest := ocispec.Manifest{ + MediaType: mediaType, + Subject: subject, + Config: config, + Layers: layers, + } + manifestJSON, err := json.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + appendBlob(mediaType, manifestJSON) + } + generateArtifact := func(artifactType string, subject *ocispec.Descriptor, blobs ...ocispec.Descriptor) { + manifest := ocispec.Artifact{ + MediaType: ocispec.MediaTypeArtifactManifest, + Subject: subject, + Blobs: blobs, + } + manifestJSON, err := json.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON) + } + generateIndex := func(manifests ...ocispec.Descriptor) { + index := ocispec.Index{ + Manifests: manifests, + } + manifestJSON, err := json.Marshal(index) + if err != nil { + t.Fatal(err) + } + appendBlob(ocispec.MediaTypeImageIndex, manifestJSON) + } + const ( + subject = iota + config + ociImage + dockerImage + artifact + index + ) + appendBlob(ocispec.MediaTypeArtifactManifest, []byte("subject content")) + imageType := "test.image" + appendBlob(imageType, []byte("config content")) + generateImage(&descs[subject], ocispec.MediaTypeImageManifest, descs[config]) + generateImage(&descs[subject], docker.MediaTypeManifest, descs[config]) + artifactType := "test.artifact" + generateArtifact(artifactType, &descs[subject]) + generateIndex(descs[subject]) + memory := memory.New() + ctx := context.Background() + for i := range descs { + memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])) + } + fetcher := &fetcher{Fetcher: memory} + + type args struct { + ctx context.Context + fetcher content.Fetcher + node ocispec.Descriptor + } + tests := []struct { + name string + args args + wantNodes []ocispec.Descriptor + wantSubject *ocispec.Descriptor + wantConfig *ocispec.Descriptor + wantErr bool + }{ + {"should failed to get non-existent artifact", args{ctx, fetcher, ocispec.Descriptor{MediaType: ocispec.MediaTypeArtifactManifest}}, nil, nil, nil, true}, + {"should failed to get non-existent OCI image", args{ctx, fetcher, ocispec.Descriptor{MediaType: ocispec.MediaTypeImageManifest}}, nil, nil, nil, true}, + {"should failed to get non-existent docker image", args{ctx, fetcher, ocispec.Descriptor{MediaType: docker.MediaTypeManifest}}, nil, nil, nil, true}, + {"should get success of a docker image", args{ctx, fetcher, descs[dockerImage]}, nil, &descs[subject], &descs[config], false}, + {"should get success of an OCI image", args{ctx, fetcher, descs[ociImage]}, nil, &descs[subject], &descs[config], false}, + {"should get success of an artifact", args{ctx, fetcher, descs[artifact]}, nil, &descs[subject], nil, false}, + {"should get success of an index", args{ctx, fetcher, descs[index]}, []ocispec.Descriptor{descs[subject]}, nil, nil, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotNodes, gotSubject, gotConfig, err := Successors(tt.args.ctx, tt.args.fetcher, tt.args.node) + if (err != nil) != tt.wantErr { + t.Errorf("Successors() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotNodes, tt.wantNodes) { + t.Errorf("Successors() gotNodes = %v, want %v", gotNodes, tt.wantNodes) + } + if !reflect.DeepEqual(gotSubject, tt.wantSubject) { + t.Errorf("Successors() gotSubject = %v, want %v", gotSubject, tt.wantSubject) + } + if !reflect.DeepEqual(gotConfig, tt.wantConfig) { + t.Errorf("Successors() gotConfig = %v, want %v", gotConfig, tt.wantConfig) + } + }) + } +} + +func TestFindReferrerPredecessors(t *testing.T) { + ctx := context.Background() + var blobs [][]byte + var descs []ocispec.Descriptor + appendBlob := func(mediaType string, blob []byte) { + blobs = append(blobs, blob) + descs = append(descs, ocispec.Descriptor{ + MediaType: mediaType, + Digest: digest.FromBytes(blob), + Size: int64(len(blob)), + }) + } + generateImage := func(subject *ocispec.Descriptor, annotations map[string]string, config ocispec.Descriptor, layers ...ocispec.Descriptor) { + manifest := ocispec.Manifest{ + Subject: subject, + Config: config, + Layers: layers, + Annotations: annotations, + } + manifestJSON, err := json.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) + } + generateArtifact := func(artifactType string, subject *ocispec.Descriptor, annotations map[string]string, blobs ...ocispec.Descriptor) { + manifest := ocispec.Artifact{ + Subject: subject, + Blobs: blobs, + Annotations: annotations, + ArtifactType: artifactType, + } + manifestJSON, err := json.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON) + } + generateIndex := func(manifests ...ocispec.Descriptor) { + index := ocispec.Index{ + Manifests: manifests, + } + manifestJSON, err := json.Marshal(index) + if err != nil { + t.Fatal(err) + } + appendBlob(ocispec.MediaTypeImageIndex, manifestJSON) + } + const ( + subject = iota + imgConfig + image + artifact + ) + var anno map[string]string + appendBlob(ocispec.MediaTypeArtifactManifest, []byte("subject content")) + imageType := "test.image" + appendBlob(imageType, []byte("config content")) + generateImage(&descs[subject], anno, descs[imgConfig]) + imageDesc := descs[image] + imageDesc.Annotations = anno + imageDesc.ArtifactType = imageType + artifactType := "test.artifact" + generateArtifact(artifactType, &descs[subject], anno) + generateIndex(descs[subject]) + artifactDesc := descs[artifact] + artifactDesc.Annotations = anno + artifactDesc.ArtifactType = artifactType + + referrers := []ocispec.Descriptor{descs[image], descs[image]} + memory := memory.New() + for i := range descs { + memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])) + } + finder := &predecessorFinder{Store: memory} + type args struct { + ctx context.Context + src content.ReadOnlyGraphStorage + desc ocispec.Descriptor + } + tests := []struct { + name string + args args + want []ocispec.Descriptor + wantErr bool + }{ + + {"should failed to get referrers", args{ctx, &errLister{}, ocispec.Descriptor{}}, nil, true}, + {"should failed to get predecessor", args{ctx, &errFinder{}, ocispec.Descriptor{}}, nil, true}, + {"should return referrers when target is a referrer lister", args{ctx, &refLister{referrers: referrers}, ocispec.Descriptor{}}, referrers, false}, + {"should return image for config node", args{ctx, finder, descs[imgConfig]}, []ocispec.Descriptor{descs[image]}, false}, + {"should return image and artifact for subject node", args{ctx, finder, descs[subject]}, []ocispec.Descriptor{descs[image], descs[artifact]}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := FindReferrerPredecessors(tt.args.ctx, tt.args.src, tt.args.desc) + if (err != nil) != tt.wantErr { + t.Errorf("FindReferrerPredecessors() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FindReferrerPredecessors() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test/e2e/README.md b/test/e2e/README.md index f19be2d16..5514b1c5a 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -106,6 +106,15 @@ graph TD; D1["test.sbom.file(image)"] -- subject --> C1 D2["test.signature.file(image)"] -- subject --> D1 end + subgraph "file: artifacts_index.tar.gz" + direction TB + F0>tag: multi]-..->F1[oci index] + F1--linux/amd64-->F2[oci image] + F1--linux/arm64-->F3[oci image] + F1--linux/arm/v7-->F4[oci image] + G1["referrer.index(image)"] -- subject --> F1 + G2["referrer.image(image)"] -- subject --> F2 + end end ``` diff --git a/test/e2e/go.mod b/test/e2e/go.mod index b34e47a60..09943c97d 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -3,7 +3,7 @@ module oras.land/oras/test/e2e go 1.20 require ( - github.com/onsi/ginkgo/v2 v2.8.0 + github.com/onsi/ginkgo/v2 v2.8.1 github.com/onsi/gomega v1.26.0 github.com/opencontainers/image-spec v1.1.0-rc2 oras.land/oras-go/v2 v2.0.0 diff --git a/test/e2e/internal/testdata/foobar/const.go b/test/e2e/internal/testdata/foobar/const.go index f8a1a66ca..7e172dda0 100644 --- a/test/e2e/internal/testdata/foobar/const.go +++ b/test/e2e/internal/testdata/foobar/const.go @@ -18,28 +18,75 @@ package foobar import "oras.land/oras/test/e2e/internal/utils/match" var ( - BlobFileNames = []string{ + Tag = "foobar" + Digest = "sha256:fd6ed2f36b5465244d5dc86cb4e7df0ab8a9d24adc57825099f522fe009a22bb" + ManifestStateKey = match.StateKey{Digest: "fd6ed2f36b54", Name: "application/vnd.oci.image.manifest.v1+json"} + + FileLayerNames = []string{ "foobar/foo1", "foobar/foo2", "foobar/bar", } - PushFileStateKeys = []match.StateKey{ - {Digest: "2c26b46b68ff", Name: BlobFileNames[0]}, - {Digest: "2c26b46b68ff", Name: BlobFileNames[1]}, - {Digest: "fcde2b2edba5", Name: BlobFileNames[2]}, + FileConfigName = "foobar/config.json" + FileConfigStateKey = match.StateKey{ + Digest: "46b68ac1696c", Name: "application/vnd.unknown.config.v1+json", + } + FileStateKeys = []match.StateKey{ + {Digest: "2c26b46b68ff", Name: FileLayerNames[0]}, + {Digest: "2c26b46b68ff", Name: FileLayerNames[1]}, + {Digest: "fcde2b2edba5", Name: FileLayerNames[2]}, } - ConfigFileName = "foobar/config.json" - ConfigFileStateKey = match.StateKey{ - Digest: "46b68ac1696c", Name: "application/vnd.unknown.config.v1+json", + ImageLayerNames = []string{ + "foo1", + "foo2", + "bar", } + ImageLayerStateKeys = []match.StateKey{ + {Digest: "2c26b46b68ff", Name: ImageLayerNames[0]}, + {Digest: "2c26b46b68ff", Name: ImageLayerNames[1]}, + {Digest: "fcde2b2edba5", Name: ImageLayerNames[2]}, + } + ImageConfigName = "config.json" + ConfigDesc = "{\"mediaType\":\"application/vnd.unknown.config.v1+json\",\"digest\":\"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\",\"size\":2}" AttachFileName = "foobar/to-be-attached" AttachFileMedia = "test/oras.e2e" AttachFileStateKey = match.StateKey{ Digest: "d3b29f7d12d9", Name: AttachFileName, } +) - Tag = "foobar" - Digest = "sha256:fd6ed2f36b5465244d5dc86cb4e7df0ab8a9d24adc57825099f522fe009a22bb" +func ImageConfigStateKey(configName string) match.StateKey { + return match.StateKey{Digest: "44136fa355b3", Name: configName} +} + +// referrers +var ( + SBOMImageReferrerDigest = "sha256:32b78bd00723cd7d5251d4586f84d252530b7b5fe1c4104532767e6da4e04e47" + SignatureImageReferrerDigest = "sha256:0e007dcb9ded7f49c4dc8e3eed4a446712eb6fdf08a665a4f2352d6d2f8bdf17" + SBOMArtifactReferrerDigest = "sha256:8d7a27ff2662dae183f762d281f46d626ba7b6e56a72cc9959cdbcd91aad7fbc" + SignatureArtifactReferrerDigest = "sha256:0e007dcb9ded7f49c4dc8e3eed4a446712eb6fdf08a665a4f2352d6d2f8bdf17" + ArtifactReferrerStateKeys = []match.StateKey{ + {Digest: "8d7a27ff2662", Name: "application/vnd.oci.artifact.manifest.v1+json"}, + {Digest: "2dbea575a349", Name: "application/vnd.oci.artifact.manifest.v1+json"}, + } + ImageReferrersStateKeys = []match.StateKey{ + {Digest: "0e007dcb9ded", Name: "application/vnd.oci.image.manifest.v1+json"}, + {Digest: "32b78bd00723", Name: "application/vnd.oci.image.manifest.v1+json"}, + } + ImageReferrerConfigStateKeys = []match.StateKey{ + {Digest: "44136fa355b3", Name: "test.signature.file"}, + {Digest: "44136fa355b3", Name: "test.sbom.file"}, + } + FallbackImageReferrersStateKeys = []match.StateKey{ + {Digest: "316405db72cc", Name: "application/vnd.oci.image.manifest.v1+json"}, + {Digest: "8b3f7e000c4a", Name: "application/vnd.oci.image.manifest.v1+json"}, + } +) + +// fallback referrers +var ( + FallbackSignatureImageReferrerDigest = "sha256:8b3f7e000c4a6d32cd6bfcabfe874ed470d470501a09adc65afaf1c342f988ff" + FallbackSBOMImageReferrerDigest = "sha256:316405db72cc8f0212c19db23b498f9af8a456c9cd288f9e33acd1ba9e7cd534" ) diff --git a/test/e2e/internal/testdata/multi_arch/const.go b/test/e2e/internal/testdata/multi_arch/const.go new file mode 100644 index 000000000..408036dbb --- /dev/null +++ b/test/e2e/internal/testdata/multi_arch/const.go @@ -0,0 +1,53 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package multi_arch + +import ( + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras/test/e2e/internal/utils/match" +) + +var ( + Tag = "multi" + Digest = "sha256:e2bfc9cc6a84ec2d7365b5a28c6bc5806b7fa581c9ad7883be955a64e3cc034f" + Manifest = `{"mediaType":"application/vnd.oci.image.index.v1+json","schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458,"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:4f93460061882467e6fb3b772dc6ab72130d9ac1906aed2fc7589a5cd145433c","size":458,"platform":{"architecture":"arm64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:58efe73e78fe043ca31b89007a025c594ce12aa7e6da27d21c7b14b50112e255","size":458,"platform":{"architecture":"arm","os":"linux","variant":"v7"}}]}` + Descriptor = `{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:e2bfc9cc6a84ec2d7365b5a28c6bc5806b7fa581c9ad7883be955a64e3cc034f","size":706}` + IndexReferrerDigest = "sha256:d3cf790759b006e1a2aeee52f9b1ee250bb848fce7e873b992b86bf9408f12d0" + IndexStateKeys = []match.StateKey{ + {Digest: "2ef548696ac7", Name: "hello.tar"}, + {Digest: "fe9dbc99451d", Name: "application/vnd.oci.image.config.v1+json"}, + {Digest: "9d84a5716c66", Name: "application/vnd.oci.image.manifest.v1+json"}, + } +) + +// child images +var ( + LinuxAMD64Manifest = `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:fe9dbc99451d0517d65e048c309f0b5afb2cc513b7a3d456b6cc29fe641386c5","size":53},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:2ef548696ac7dd66ef38aab5cc8fc5cc1fb637dfaedb3a9afc89bf16db9277e1","size":10240,"annotations":{"org.opencontainers.image.title":"hello.tar"}}]}` + LinuxAMD64Digest = "sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1" + LinuxAMD64Desc = `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458}` + LinuxAMD64IndexDesc = `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458,"platform":{"architecture":"amd64","os":"linux"}}` + LinuxAMD64Config = "{\r\n \"architecture\": \"amd64\",\r\n \"os\": \"linux\"\r\n}" + LinuxAMD64ConfigDesc = `{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:fe9dbc99451d0517d65e048c309f0b5afb2cc513b7a3d456b6cc29fe641386c5","size":53}` + LinuxAMD64ReferrerDigest = "sha256:57e6462826c85be15f22f824666f6b467d488fa7bc7e2975f43a2fae27a24ef0" + LayerName = "hello.tar" + LinuxAMD64ReferrerStateKey = match.StateKey{Digest: "57e6462826c8", Name: "application/vnd.oci.image.manifest.v1+json"} + LinuxAMD64ReferrerConfigStateKey = match.StateKey{Digest: "44136fa355b3", Name: "referrer.image"} + ImageStateKeys = []match.StateKey{ + {Digest: "9d84a5716c66", Name: ocispec.MediaTypeImageManifest}, + {Digest: "fe9dbc99451d", Name: ocispec.MediaTypeImageConfig}, + {Digest: "2ef548696ac7", Name: "hello.tar"}, + } +) diff --git a/test/e2e/internal/utils/exec.go b/test/e2e/internal/utils/exec.go index 723952d5b..a7c5acb2b 100644 --- a/test/e2e/internal/utils/exec.go +++ b/test/e2e/internal/utils/exec.go @@ -25,6 +25,7 @@ import ( ginkgo "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" "oras.land/oras/test/e2e/internal/utils/match" ) @@ -192,11 +193,14 @@ func (opts *ExecOption) Exec() *gexec.Session { } // matching result + stdout := session.Out.Contents() for _, s := range opts.stdout { - s.Match(session.Out) + s.Match(gbytes.BufferWithBytes(stdout)) } + + stderr := session.Err.Contents() for _, s := range opts.stderr { - s.Match(session.Err) + s.Match(gbytes.BufferWithBytes(stderr)) } return session diff --git a/test/e2e/internal/utils/testdata.go b/test/e2e/internal/utils/testdata.go index 2729c8c0e..af223ba5b 100644 --- a/test/e2e/internal/utils/testdata.go +++ b/test/e2e/internal/utils/testdata.go @@ -16,27 +16,10 @@ limitations under the License. package utils const ( - PreviewDesc = "** This command is in preview and under development. **" - ExampleDesc = "\nExample - " - ImageRepo = "command/images" - ArtifactRepo = "command/artifacts" - Repo = "command/images" - Namespace = "command" - FoobarImageTag = "foobar" - FoobarConfigDesc = "{\"mediaType\":\"application/vnd.unknown.config.v1+json\",\"digest\":\"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\",\"size\":2}" - MultiImageTag = "multi" - MultiImageDigest = "sha256:e2bfc9cc6a84ec2d7365b5a28c6bc5806b7fa581c9ad7883be955a64e3cc034f" - FoobarImageDigest = "sha256:fd6ed2f36b5465244d5dc86cb4e7df0ab8a9d24adc57825099f522fe009a22bb" - SignatureImageReferrerDigest = "sha256:0e007dcb9ded7f49c4dc8e3eed4a446712eb6fdf08a665a4f2352d6d2f8bdf17" - SBOMImageReferrerDigest = "sha256:32b78bd00723cd7d5251d4586f84d252530b7b5fe1c4104532767e6da4e04e47" - FallbackSignatureImageReferrerDigest = "sha256:8b3f7e000c4a6d32cd6bfcabfe874ed470d470501a09adc65afaf1c342f988ff" - FallbackSBOMImageReferrerDigest = "sha256:316405db72cc8f0212c19db23b498f9af8a456c9cd288f9e33acd1ba9e7cd534" - MultiImageManifest = `{"mediaType":"application/vnd.oci.image.index.v1+json","schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458,"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:4f93460061882467e6fb3b772dc6ab72130d9ac1906aed2fc7589a5cd145433c","size":458,"platform":{"architecture":"arm64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:58efe73e78fe043ca31b89007a025c594ce12aa7e6da27d21c7b14b50112e255","size":458,"platform":{"architecture":"arm","os":"linux","variant":"v7"}}]}` - MultiImageDescriptor = `{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:e2bfc9cc6a84ec2d7365b5a28c6bc5806b7fa581c9ad7883be955a64e3cc034f","size":706}` - LinuxAMD64ImageManifest = `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:fe9dbc99451d0517d65e048c309f0b5afb2cc513b7a3d456b6cc29fe641386c5","size":53},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:2ef548696ac7dd66ef38aab5cc8fc5cc1fb637dfaedb3a9afc89bf16db9277e1","size":10240,"annotations":{"org.opencontainers.image.title":"hello.tar"}}]}` - LinuxAMD64ImageDescriptor = `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458}` - LinuxAMD64ImageIndexDescriptor = `{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458,"platform":{"architecture":"amd64","os":"linux"}}` - LinuxAMD64ImageDigest = "sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1" - LinuxAMD64ImageConfig = "{\r\n \"architecture\": \"amd64\",\r\n \"os\": \"linux\"\r\n}" - LinuxAMD64ImageConfigDescriptor = `{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:fe9dbc99451d0517d65e048c309f0b5afb2cc513b7a3d456b6cc29fe641386c5","size":53}` + PreviewDesc = "** This command is in preview and under development. **" + ExampleDesc = "\nExample - " + ImageRepo = "command/images" + ArtifactRepo = "command/artifacts" + Repo = "command/images" + Namespace = "command" ) diff --git a/test/e2e/suite/command/attach.go b/test/e2e/suite/command/attach.go index f19b387d9..74e7ecc0e 100644 --- a/test/e2e/suite/command/attach.go +++ b/test/e2e/suite/command/attach.go @@ -45,7 +45,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail if no file reference or manifest annotation provided", func() { - ORAS("attach", "--artifact-type", "oras.test", Reference(Host, ImageRepo, FoobarImageTag)). + ORAS("attach", "--artifact-type", "oras.test", Reference(Host, ImageRepo, foobar.Tag)). ExpectFailure().MatchErrKeyWords("Error: no blob or manifest annotation are provided").Exec() }) }) @@ -56,8 +56,8 @@ var _ = Describe("Common registry users:", func() { It("should attach a file to a subject", func() { testRepo := attachTestRepo("simple") tempDir := CopyTestDataToTemp() - subjectRef := Reference(Host, testRepo, FoobarImageTag) - prepare(Reference(Host, ImageRepo, FoobarImageTag), subjectRef) + subjectRef := Reference(Host, testRepo, foobar.Tag) + prepare(Reference(Host, ImageRepo, foobar.Tag), subjectRef) ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). WithWorkDir(tempDir). MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() @@ -68,8 +68,8 @@ var _ = Describe("Common registry users:", func() { testRepo := attachTestRepo("export-manifest") tempDir := CopyTestDataToTemp() exportName := "manifest.json" - subjectRef := Reference(Host, testRepo, FoobarImageTag) - prepare(Reference(Host, ImageRepo, FoobarImageTag), subjectRef) + subjectRef := Reference(Host, testRepo, foobar.Tag) + prepare(Reference(Host, ImageRepo, foobar.Tag), subjectRef) // test ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--export-manifest", exportName). WithWorkDir(tempDir). @@ -85,8 +85,8 @@ var _ = Describe("Common registry users:", func() { It("should attach a file via a OCI Image", func() { testRepo := attachTestRepo("image") tempDir := CopyTestDataToTemp() - subjectRef := Reference(Host, testRepo, FoobarImageTag) - prepare(Reference(Host, ImageRepo, FoobarImageTag), subjectRef) + subjectRef := Reference(Host, testRepo, foobar.Tag) + prepare(Reference(Host, ImageRepo, foobar.Tag), subjectRef) // test ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). WithWorkDir(tempDir). @@ -102,8 +102,8 @@ var _ = Describe("Common registry users:", func() { It("should attach a file via a OCI Artifact", func() { testRepo := attachTestRepo("artifact") tempDir := CopyTestDataToTemp() - subjectRef := Reference(Host, testRepo, FoobarImageTag) - prepare(Reference(Host, ImageRepo, FoobarImageTag), subjectRef) + subjectRef := Reference(Host, testRepo, foobar.Tag) + prepare(Reference(Host, ImageRepo, foobar.Tag), subjectRef) // test ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-artifact"). WithWorkDir(tempDir). @@ -124,8 +124,8 @@ var _ = Describe("Fallback registry users:", func() { It("should attach a file via a OCI Image", func() { testRepo := attachTestRepo("fallback/image") tempDir := CopyTestDataToTemp() - subjectRef := Reference(FallbackHost, testRepo, FoobarImageTag) - prepare(Reference(FallbackHost, ArtifactRepo, FoobarImageTag), subjectRef) + subjectRef := Reference(FallbackHost, testRepo, foobar.Tag) + prepare(Reference(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) // test ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). WithWorkDir(tempDir). @@ -142,8 +142,8 @@ var _ = Describe("Fallback registry users:", func() { It("should attach a file via a OCI Image by default", func() { testRepo := attachTestRepo("fallback/default") tempDir := CopyTestDataToTemp() - subjectRef := Reference(FallbackHost, testRepo, FoobarImageTag) - prepare(Reference(FallbackHost, ArtifactRepo, FoobarImageTag), subjectRef) + subjectRef := Reference(FallbackHost, testRepo, foobar.Tag) + prepare(Reference(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) // test ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). WithWorkDir(tempDir). @@ -160,8 +160,8 @@ var _ = Describe("Fallback registry users:", func() { It("should attach a file via a OCI Image and generate referrer via tag schema", func() { testRepo := attachTestRepo("fallback/tag_schema") tempDir := CopyTestDataToTemp() - subjectRef := Reference(FallbackHost, testRepo, FoobarImageTag) - prepare(Reference(FallbackHost, ArtifactRepo, FoobarImageTag), subjectRef) + subjectRef := Reference(FallbackHost, testRepo, foobar.Tag) + prepare(Reference(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) // test ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image", "--distribution-spec", "v1.1-referrers-tag"). WithWorkDir(tempDir). diff --git a/test/e2e/suite/command/blob.go b/test/e2e/suite/command/blob.go index 86f50345a..3077760f3 100644 --- a/test/e2e/suite/command/blob.go +++ b/test/e2e/suite/command/blob.go @@ -22,6 +22,7 @@ import ( "strings" . "github.com/onsi/ginkgo/v2" + "oras.land/oras/test/e2e/internal/testdata/foobar" . "oras.land/oras/test/e2e/internal/utils" ) @@ -136,14 +137,14 @@ var _ = Describe("ORAS beginners:", func() { It("should fail if no blob reference is provided", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-ref") - ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, foobar.Digest), Reference(Host, dstRepo, foobar.Digest)).Exec() ORAS("blob", "delete").ExpectFailure().Exec() ORAS("blob", "fetch", Reference(Host, dstRepo, deleteDigest), "--output", "-").MatchContent(deleteContent).Exec() }) It("should fail if no force flag and descriptor flag is provided", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-confirm") - ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, foobar.Digest), Reference(Host, dstRepo, foobar.Digest)).Exec() ORAS("blob", "delete", Reference(Host, dstRepo, deleteDigest), "--descriptor").ExpectFailure().Exec() ORAS("blob", "fetch", Reference(Host, dstRepo, deleteDigest), "--output", "-").MatchContent(deleteContent).Exec() }) @@ -178,7 +179,7 @@ var _ = Describe("Common registry users:", func() { When("running `blob delete`", func() { It("should delete a blob with interactive confirmation", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "prompt-confirmation") - ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, foobar.Digest), Reference(Host, dstRepo, foobar.Digest)).Exec() toDeleteRef := Reference(Host, dstRepo, deleteDigest) ORAS("blob", "delete", toDeleteRef). WithInput(strings.NewReader("y")). @@ -192,7 +193,7 @@ var _ = Describe("Common registry users:", func() { It("should delete a blob with force flag and output descriptor", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "flag-confirmation") - ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, foobar.Digest), Reference(Host, dstRepo, foobar.Digest)).Exec() toDeleteRef := Reference(Host, dstRepo, deleteDigest) ORAS("blob", "delete", toDeleteRef, "--force", "--descriptor").MatchContent(deleteDescriptor).Exec() ORAS("blob", "delete", toDeleteRef).WithDescription("validate").ExpectFailure().MatchErrKeyWords("Error:", toDeleteRef, "the specified blob does not exist").Exec() diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index ac3c3c1db..17e842baa 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -16,13 +16,18 @@ limitations under the License. package command import ( + "encoding/json" "fmt" "strings" . "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + . "github.com/onsi/gomega" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras/test/e2e/internal/testdata/foobar" + ma "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" - "oras.land/oras/test/e2e/internal/utils/match" ) func cpTestRepo(text string) string { @@ -42,7 +47,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail when no destination reference provided", func() { - ORAS("cp", Reference(Host, ImageRepo, FoobarImageTag)).ExpectFailure().MatchErrKeyWords("Error:").Exec() + ORAS("cp", Reference(Host, ImageRepo, foobar.Tag)).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) It("should fail when source doesn't exist", func() { @@ -51,89 +56,172 @@ var _ = Describe("ORAS beginners:", func() { }) }) -var ( - foobarStates = []match.StateKey{ - {Digest: "44136fa355b3", Name: "application/vnd.unknown.config.v1+json"}, - {Digest: "fcde2b2edba5", Name: "bar"}, - {Digest: "2c26b46b68ff", Name: "foo1"}, - {Digest: "2c26b46b68ff", Name: "foo2"}, - {Digest: "fd6ed2f36b54", Name: "application/vnd.oci.image.manifest.v1+json"}, - } - foobarReferrersStates = []match.StateKey{ - {Digest: "8d7a27ff2662", Name: "application/vnd.oci.artifact.manifest.v1+json"}, - {Digest: "2dbea575a349", Name: "application/vnd.oci.artifact.manifest.v1+json"}, - } - foobarImageReferrersStates = []match.StateKey{ - {Digest: "0e007dcb9ded", Name: "application/vnd.oci.image.manifest.v1+json"}, - {Digest: "32b78bd00723", Name: "application/vnd.oci.image.manifest.v1+json"}, - } - foobarImageConfigStates = []match.StateKey{ - {Digest: "44136fa355b3", Name: "test.signature.file"}, - {Digest: "44136fa355b3", Name: "test.sbom.file"}, - } - foobarFallbackImageReferrersStates = []match.StateKey{ - {Digest: "316405db72cc", Name: "application/vnd.oci.image.manifest.v1+json"}, - {Digest: "8b3f7e000c4a", Name: "application/vnd.oci.image.manifest.v1+json"}, - } - multiImageStates = []match.StateKey{ - {Digest: "2ef548696ac7", Name: "hello.tar"}, - {Digest: "fe9dbc99451d", Name: "application/vnd.oci.image.config.v1+json"}, - {Digest: "9d84a5716c66", Name: "application/vnd.oci.image.manifest.v1+json"}, - } -) +var foobarStates = append(foobar.ImageLayerStateKeys, foobar.ManifestStateKey, foobar.ImageConfigStateKey(oras.MediaTypeUnknownConfig)) var _ = Describe("Common registry users:", func() { When("running `cp`", func() { validate := func(src, dst string) { - srcManifest := ORAS("manifest", "fetch", src).Exec().Out.Contents() - dstManifest := ORAS("manifest", "fetch", dst).Exec().Out.Contents() - gomega.Expect(srcManifest).To(gomega.Equal(dstManifest)) + srcManifest := ORAS("manifest", "fetch", src).WithDescription("fetch from source for validation").Exec().Out.Contents() + dstManifest := ORAS("manifest", "fetch", dst).WithDescription("fetch from destination for validation").Exec().Out.Contents() + Expect(srcManifest).To(Equal(dstManifest)) } It("should copy an image to a new repository via tag", func() { - src := Reference(Host, ImageRepo, FoobarImageTag) + src := Reference(Host, ImageRepo, foobar.Tag) dst := Reference(Host, cpTestRepo("tag"), "copiedTag") ORAS("cp", src, dst, "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() validate(src, dst) }) It("should copy an image to a new repository via digest", func() { - src := Reference(Host, ImageRepo, FoobarImageDigest) + src := Reference(Host, ImageRepo, foobar.Digest) dst := Reference(Host, cpTestRepo("digest"), "copiedTag") ORAS("cp", src, dst, "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() validate(src, dst) }) It("should copy an image to a new repository via tag without tagging", func() { - src := Reference(Host, ImageRepo, FoobarImageTag) - dst := Reference(Host, cpTestRepo("no-tagging"), FoobarImageDigest) + src := Reference(Host, ImageRepo, foobar.Tag) + dst := Reference(Host, cpTestRepo("no-tagging"), foobar.Digest) ORAS("cp", src, dst, "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() validate(src, dst) }) It("should copy an image and its referrers to a new repository", func() { - stateKeys := append(append(foobarStates, foobarReferrersStates...), foobarImageConfigStates...) - src := Reference(Host, ArtifactRepo, FoobarImageTag) - dst := Reference(Host, cpTestRepo("referrers"), FoobarImageDigest) + stateKeys := append(append(foobarStates, foobar.ArtifactReferrerStateKeys...), foobar.ImageReferrerConfigStateKeys...) + src := Reference(Host, ArtifactRepo, foobar.Tag) + dst := Reference(Host, cpTestRepo("referrers"), foobar.Digest) ORAS("cp", "-r", src, dst, "-v").MatchStatus(stateKeys, true, len(stateKeys)).Exec() validate(src, dst) }) + It("should copy a multi-arch image and its referrers to a new repository via tag", func() { + src := Reference(Host, ArtifactRepo, ma.Tag) + dstRepo := cpTestRepo("index-referrers") + dst := Reference(Host, dstRepo, "copiedTag") + ORAS("cp", src, dst, "-r", "-v"). + MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). + MatchKeyWords("Digest: " + ma.Digest). + Exec() + // validate + validate(Reference(Host, ImageRepo, ma.Digest), dst) + var index ocispec.Index + bytes := ORAS("discover", dst, "-o", "json"). + MatchKeyWords(ma.IndexReferrerDigest). + WithDescription("copy image referrer"). + Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].Digest.String()).To(Equal(ma.IndexReferrerDigest)) + ORAS("manifest", "fetch", Reference(Host, dstRepo, ma.LinuxAMD64ReferrerDigest)). + WithDescription("not copy referrer of successor"). + ExpectFailure(). + Exec() + }) + + It("should copy a multi-arch image and its referrers to a new repository via digest", func() { + src := Reference(Host, ArtifactRepo, ma.Tag) + dstRepo := cpTestRepo("index-referrers-digest") + dst := Reference(Host, dstRepo, ma.Digest) + ORAS("cp", src, dst, "-r", "-v"). + MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). + MatchKeyWords("Digest: " + ma.Digest). + Exec() + // validate + validate(Reference(Host, ImageRepo, ma.Digest), dst) + var index ocispec.Index + bytes := ORAS("discover", dst, "-o", "json"). + MatchKeyWords(ma.IndexReferrerDigest). + WithDescription("copy image referrer"). + Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].Digest.String()).To(Equal(ma.IndexReferrerDigest)) + ORAS("manifest", "fetch", Reference(Host, dstRepo, ma.LinuxAMD64ReferrerDigest)). + WithDescription("not copy referrer of successor"). + ExpectFailure(). + Exec() + }) + It("should copy a certain platform of image to a new repository via tag", func() { - src := Reference(Host, ImageRepo, MultiImageTag) + src := Reference(Host, ImageRepo, ma.Tag) dst := Reference(Host, cpTestRepo("platform-tag"), "copiedTag") - ORAS("cp", src, dst, "--platform", "linux/amd64", "-v").MatchStatus(multiImageStates, true, len(multiImageStates)).Exec() - validate(Reference(Host, ImageRepo, LinuxAMD64ImageDigest), dst) + + ORAS("cp", src, dst, "--platform", "linux/amd64", "-v"). + MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). + MatchKeyWords("Digest: " + ma.LinuxAMD64Digest). + Exec() + validate(Reference(Host, ImageRepo, ma.LinuxAMD64Digest), dst) }) It("should copy a certain platform of image to a new repository via digest", func() { - src := Reference(Host, ImageRepo, MultiImageDigest) - dst := Reference(Host, cpTestRepo("platform-digest"), "copiedTag") - ORAS("cp", src, dst, "--platform", "linux/amd64", "-v").MatchStatus(multiImageStates, true, len(multiImageStates)).Exec() - validate(Reference(Host, ImageRepo, LinuxAMD64ImageDigest), dst) + src := Reference(Host, ImageRepo, ma.Digest) + dstRepo := cpTestRepo("platform-digest") + dst := Reference(Host, dstRepo, "") + ORAS("cp", src, dst, "--platform", "linux/amd64", "-v"). + MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). + MatchKeyWords("Digest: " + ma.LinuxAMD64Digest). + Exec() + validate(Reference(Host, ImageRepo, ma.LinuxAMD64Digest), Reference(Host, dstRepo, ma.LinuxAMD64Digest)) + }) + + It("should copy a certain platform of image and its referrers to a new repository with tag", func() { + src := Reference(Host, ArtifactRepo, ma.Tag) + dstRepo := cpTestRepo("platform-referrers") + dst := Reference(Host, dstRepo, "copiedTag") + ORAS("cp", src, dst, "-r", "--platform", "linux/amd64", "-v"). + MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). + MatchKeyWords("Digest: " + ma.LinuxAMD64Digest). + Exec() + // validate + validate(Reference(Host, ImageRepo, ma.LinuxAMD64Digest), dst) + var index ocispec.Index + bytes := ORAS("discover", dst, "-o", "json", "--platform", "linux/amd64"). + MatchKeyWords(ma.LinuxAMD64ReferrerDigest). + WithDescription("discover amd64 referrers"). + Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].Digest.String()).To(Equal(ma.LinuxAMD64ReferrerDigest)) + ORAS("manifest", "fetch", Reference(Host, dstRepo, ma.Digest)). + WithDescription("not copy index"). + ExpectFailure(). + Exec() + ORAS("manifest", "fetch", Reference(Host, dstRepo, ma.IndexReferrerDigest)). + WithDescription("not copy index referrer"). + ExpectFailure(). + Exec() + }) + + It("should copy a certain platform of image and its referrers to a new repository without tagging", func() { + src := Reference(Host, ArtifactRepo, ma.Tag) + dstRepo := cpTestRepo("platform-referrers-no-tag") + ORAS("cp", src, Reference(Host, dstRepo, ""), "-r", "--platform", "linux/amd64", "-v"). + MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). + MatchKeyWords("Digest: " + ma.LinuxAMD64Digest). + Exec() + // validate + dstRef := Reference(Host, dstRepo, ma.LinuxAMD64Digest) + validate(Reference(Host, ImageRepo, ma.LinuxAMD64Digest), dstRef) + var index ocispec.Index + bytes := ORAS("discover", dstRef, "-o", "json", "--platform", "linux/amd64"). + MatchKeyWords(ma.LinuxAMD64ReferrerDigest). + WithDescription("discover amd64 referrers"). + Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].Digest.String()).To(Equal(ma.LinuxAMD64ReferrerDigest)) + ORAS("manifest", "fetch", Reference(Host, dstRepo, ma.Digest)). + WithDescription("not copy index"). + ExpectFailure(). + Exec() + ORAS("manifest", "fetch", Reference(Host, dstRepo, ma.IndexReferrerDigest)). + WithDescription("not copy index referrer"). + ExpectFailure(). + Exec() }) It("should copy an image to a new repository with multiple tagging", func() { - src := Reference(Host, ImageRepo, FoobarImageDigest) + src := Reference(Host, ImageRepo, foobar.Digest) tags := []string{"tag1", "tag2", "tag3"} dstRepo := cpTestRepo("multi-tagging") dst := Reference(Host, dstRepo, "") @@ -155,23 +243,23 @@ var _ = Describe("OCI spec 1.0 registry users:", func() { } It("should copy an image artifact and its referrers from a registry to a fallback registry", func() { repo := cpTestRepo("to-fallback") - stateKeys := append(append(foobarStates, foobarImageReferrersStates...), foobarImageConfigStates...) - src := Reference(Host, ArtifactRepo, SignatureImageReferrerDigest) + stateKeys := append(append(foobarStates, foobar.ImageReferrersStateKeys...), foobar.ImageReferrerConfigStateKeys...) + src := Reference(Host, ArtifactRepo, foobar.SignatureImageReferrerDigest) dst := Reference(FallbackHost, repo, "") ORAS("cp", "-r", src, dst, "-v").MatchStatus(stateKeys, true, len(stateKeys)).Exec() - validate(src, Reference(FallbackHost, repo, SignatureImageReferrerDigest)) - ORAS("discover", "-o", "tree", Reference(FallbackHost, repo, FoobarImageDigest)). - WithDescription("discover referrer via subject").MatchKeyWords(SignatureImageReferrerDigest, SBOMImageReferrerDigest).Exec() + validate(src, Reference(FallbackHost, repo, foobar.SignatureImageReferrerDigest)) + ORAS("discover", "-o", "tree", Reference(FallbackHost, repo, foobar.Digest)). + WithDescription("discover referrer via subject").MatchKeyWords(foobar.SignatureImageReferrerDigest, foobar.SBOMImageReferrerDigest).Exec() }) It("should copy an image artifact and its referrers from a fallback registry to a registry", func() { repo := cpTestRepo("from-fallback") - stateKeys := append(append(foobarStates, foobarFallbackImageReferrersStates...), foobarImageConfigStates...) - src := Reference(FallbackHost, ArtifactRepo, FallbackSBOMImageReferrerDigest) + stateKeys := append(append(foobarStates, foobar.FallbackImageReferrersStateKeys...), foobar.ImageReferrerConfigStateKeys...) + src := Reference(FallbackHost, ArtifactRepo, foobar.FallbackSBOMImageReferrerDigest) dst := Reference(Host, repo, "") ORAS("cp", "-r", src, dst, "-v").MatchStatus(stateKeys, true, len(stateKeys)).Exec() - validate(src, Reference(Host, repo, FallbackSBOMImageReferrerDigest)) - ORAS("discover", "-o", "tree", Reference(Host, repo, FoobarImageDigest)). - WithDescription("discover referrer via subject").MatchKeyWords(FallbackSignatureImageReferrerDigest, FallbackSBOMImageReferrerDigest).Exec() + validate(src, Reference(Host, repo, foobar.FallbackSBOMImageReferrerDigest)) + ORAS("discover", "-o", "tree", Reference(Host, repo, foobar.Digest)). + WithDescription("discover referrer via subject").MatchKeyWords(foobar.FallbackSignatureImageReferrerDigest, foobar.FallbackSBOMImageReferrerDigest).Exec() }) }) }) diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index fb19e0d36..910a0760b 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -24,6 +24,8 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras/test/e2e/internal/testdata/foobar" + "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" ) @@ -79,7 +81,7 @@ var _ = Describe("ORAS beginners:", func() { tempTag := "to-delete" It("should cancel deletion without confirmation", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-confirm") - prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, foobar.Tag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete", Reference(Host, dstRepo, tempTag)). MatchKeyWords("Operation cancelled.", "Are you sure you want to delete the manifest ", " and all tags associated with it?").Exec() validate(Reference(Host, dstRepo, ""), tempTag, false) @@ -87,7 +89,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail if descriptor flag is provided without confirmation flag", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "descriptor-without-confirm") - prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, foobar.Tag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete", Reference(Host, dstRepo, tempTag), "--descriptor").ExpectFailure().Exec() }) @@ -117,7 +119,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail if no blob reference provided", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-reference") - prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, foobar.Tag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete").ExpectFailure().Exec() }) }) @@ -135,7 +137,7 @@ var _ = Describe("ORAS beginners:", func() { ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, "this-tag-should-not-exist")).ExpectFailure().Exec() }) It("should fail fetching a config of non-image manifest type", func() { - ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, MultiImageTag)).ExpectFailure().Exec() + ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, multi_arch.Tag)).ExpectFailure().Exec() }) }) }) @@ -145,105 +147,105 @@ var _ = Describe("Common registry users:", func() { repoFmt := fmt.Sprintf("command/manifest/%%s/%d/%%s", GinkgoRandomSeed()) When("running `manifest fetch`", func() { It("should fetch manifest list with digest", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag)). - MatchContent(MultiImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Tag)). + MatchContent(multi_arch.Manifest).Exec() }) It("should fetch manifest list with tag", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag)). - MatchContent(MultiImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Tag)). + MatchContent(multi_arch.Manifest).Exec() }) It("should fetch manifest list to stdout", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--output", "-"). - MatchContent(MultiImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Tag), "--output", "-"). + MatchContent(multi_arch.Manifest).Exec() }) It("should fetch manifest to file and output descriptor to stdout", func() { fetchPath := filepath.Join(GinkgoT().TempDir(), "fetchedImage") - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--output", fetchPath, "--descriptor"). - MatchContent(MultiImageDescriptor).Exec() - MatchFile(fetchPath, MultiImageManifest, DefaultTimeout) + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Tag), "--output", fetchPath, "--descriptor"). + MatchContent(multi_arch.Descriptor).Exec() + MatchFile(fetchPath, multi_arch.Manifest, DefaultTimeout) }) It("should fetch manifest via tag with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--platform", "linux/amd64"). - MatchContent(LinuxAMD64ImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Tag), "--platform", "linux/amd64"). + MatchContent(multi_arch.LinuxAMD64Manifest).Exec() }) It("should fetch manifest via digest with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64"). - MatchContent(LinuxAMD64ImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--platform", "linux/amd64"). + MatchContent(multi_arch.LinuxAMD64Manifest).Exec() }) It("should fetch manifest with platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64"). - MatchContent(LinuxAMD64ImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.LinuxAMD64Digest), "--platform", "linux/amd64"). + MatchContent(multi_arch.LinuxAMD64Manifest).Exec() }) It("should fetch descriptor via digest", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--descriptor"). - MatchContent(MultiImageDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--descriptor"). + MatchContent(multi_arch.Descriptor).Exec() }) It("should fetch descriptor via digest with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64", "--descriptor"). - MatchContent(LinuxAMD64ImageIndexDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--platform", "linux/amd64", "--descriptor"). + MatchContent(multi_arch.LinuxAMD64IndexDesc).Exec() }) It("should fetch descriptor via digest with platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--descriptor"). - MatchContent(LinuxAMD64ImageDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.LinuxAMD64Digest), "--platform", "linux/amd64", "--descriptor"). + MatchContent(multi_arch.LinuxAMD64Desc).Exec() }) It("should fetch descriptor via tag", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--descriptor"). - MatchContent(MultiImageDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--descriptor"). + MatchContent(multi_arch.Descriptor).Exec() }) It("should fetch descriptor via tag with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64", "--descriptor"). - MatchContent(LinuxAMD64ImageIndexDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--platform", "linux/amd64", "--descriptor"). + MatchContent(multi_arch.LinuxAMD64IndexDesc).Exec() }) It("should fetch index content with media type assertion", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--media-type", "application/vnd.oci.image.index.v1+json"). - MatchContent(MultiImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--media-type", "application/vnd.oci.image.index.v1+json"). + MatchContent(multi_arch.Manifest).Exec() }) It("should fetch index descriptor with media type assertion", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--media-type", "application/vnd.oci.image.index.v1+json", "--descriptor"). - MatchContent(MultiImageDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--media-type", "application/vnd.oci.image.index.v1+json", "--descriptor"). + MatchContent(multi_arch.Descriptor).Exec() }) It("should fetch image content with media type assertion and platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json"). - MatchContent(LinuxAMD64ImageManifest).Exec() - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). - MatchContent(LinuxAMD64ImageIndexDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Tag), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json"). + MatchContent(multi_arch.LinuxAMD64Manifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). + MatchContent(multi_arch.LinuxAMD64IndexDesc).Exec() }) It("should fetch image descriptor with media type assertion and platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). - MatchContent(LinuxAMD64ImageIndexDescriptor).Exec() - ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). - MatchContent(LinuxAMD64ImageIndexDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Tag), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). + MatchContent(multi_arch.LinuxAMD64IndexDesc).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.Digest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). + MatchContent(multi_arch.LinuxAMD64IndexDesc).Exec() }) It("should fetch image content with media type assertion and platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json"). - MatchContent(LinuxAMD64ImageManifest).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.LinuxAMD64Digest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json"). + MatchContent(multi_arch.LinuxAMD64Manifest).Exec() }) It("should fetch image descriptor with media type assertion and platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json", "--descriptor"). - MatchContent(LinuxAMD64ImageDescriptor).Exec() + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.LinuxAMD64Digest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json", "--descriptor"). + MatchContent(multi_arch.LinuxAMD64Desc).Exec() }) It("should fail to fetch image if media type assertion fails", func() { - ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--media-type", "this.will.not.be.found"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, multi_arch.LinuxAMD64Digest), "--media-type", "this.will.not.be.found"). ExpectFailure(). - MatchErrKeyWords(LinuxAMD64ImageDigest, "error: ", "not found").Exec() + MatchErrKeyWords(multi_arch.LinuxAMD64Digest, "error: ", "not found").Exec() }) }) @@ -291,33 +293,33 @@ var _ = Describe("Common registry users:", func() { When("running `manifest fetch-config`", func() { It("should fetch a config via a tag", func() { - ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, FoobarImageTag)). + ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, foobar.Tag)). MatchContent("{}").Exec() }) It("should fetch a config descriptor via a tag", func() { - ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, ImageRepo, FoobarImageTag)). - MatchContent(FoobarConfigDesc).Exec() + ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, ImageRepo, foobar.Tag)). + MatchContent(foobar.ConfigDesc).Exec() }) It("should fetch a config via digest", func() { - ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, FoobarImageTag)). + ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, foobar.Tag)). MatchContent("{}").Exec() }) It("should fetch a config descriptor via a digest", func() { - ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, ImageRepo, FoobarImageDigest)). - MatchContent(FoobarConfigDesc).Exec() + ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, ImageRepo, foobar.Digest)). + MatchContent(foobar.ConfigDesc).Exec() }) It("should fetch a config of a specific platform", func() { - ORAS("manifest", "fetch-config", "--platform", "linux/amd64", Reference(Host, ImageRepo, MultiImageTag)). - MatchContent(LinuxAMD64ImageConfig).Exec() + ORAS("manifest", "fetch-config", "--platform", "linux/amd64", Reference(Host, ImageRepo, multi_arch.Tag)). + MatchContent(multi_arch.LinuxAMD64Config).Exec() }) It("should fetch a config descriptor of a specific platform", func() { - ORAS("manifest", "fetch-config", "--descriptor", "--platform", "linux/amd64", Reference(Host, ImageRepo, MultiImageTag)). - MatchContent(LinuxAMD64ImageConfigDescriptor).Exec() + ORAS("manifest", "fetch-config", "--descriptor", "--platform", "linux/amd64", Reference(Host, ImageRepo, multi_arch.Tag)). + MatchContent(multi_arch.LinuxAMD64ConfigDesc).Exec() }) }) @@ -325,7 +327,7 @@ var _ = Describe("Common registry users:", func() { tempTag := "to-delete" It("should do confirmed deletion via input", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "confirm-input") - prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, foobar.Tag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete", Reference(Host, dstRepo, tempTag)). WithInput(strings.NewReader("y")).Exec() validate(Reference(Host, dstRepo, ""), tempTag, true) @@ -333,14 +335,14 @@ var _ = Describe("Common registry users:", func() { It("should do confirmed deletion via flag", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "confirm-flag") - prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, foobar.Tag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete", Reference(Host, dstRepo, tempTag), "-f").Exec() validate(Reference(Host, dstRepo, ""), tempTag, true) }) It("should do confirmed deletion and output descriptor", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "output-descriptor") - prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, foobar.Tag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete", Reference(Host, dstRepo, tempTag), "-f", "--descriptor"). MatchContent("{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:fd6ed2f36b5465244d5dc86cb4e7df0ab8a9d24adc57825099f522fe009a22bb\",\"size\":851}"). WithDescription("cancel without confirmation").Exec() diff --git a/test/e2e/suite/command/pull.go b/test/e2e/suite/command/pull.go index 353c5cbca..78a8ab152 100644 --- a/test/e2e/suite/command/pull.go +++ b/test/e2e/suite/command/pull.go @@ -23,48 +23,35 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" + "oras.land/oras/test/e2e/internal/testdata/foobar" + "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" - "oras.land/oras/test/e2e/internal/utils/match" ) var _ = Describe("Remote registry users:", func() { When("pulling images from remote registry", func() { var ( - repo = "command/images" - tag = "foobar" - files = []string{ - "config.json", - "foo1", - "foo2", - "bar", - } + repo = "command/images" + tag = "foobar" + configName = "test.config" ) It("should pull all files in an image to a target folder", func() { pullRoot := "pulled" - tempDir := GinkgoT().TempDir() - if err := CopyTestData(tempDir); err != nil { - panic(err) - } - ORAS("pull", Reference(Host, repo, tag), "-v", "--config", files[0], "-o", pullRoot). - MatchStatus([]match.StateKey{ - {Digest: "fd6ed2f36b54", Name: ocispec.MediaTypeImageManifest}, - {Digest: "44136fa355b3", Name: files[0]}, - {Digest: "2c26b46b68ff", Name: files[1]}, - {Digest: "2c26b46b68ff", Name: files[2]}, - {Digest: "fcde2b2edba5", Name: files[3]}, - }, true, 5). + tempDir := CopyTestDataToTemp() + stateKeys := append(foobar.ImageLayerStateKeys, foobar.ManifestStateKey, foobar.ImageConfigStateKey(configName)) + ORAS("pull", Reference(Host, repo, tag), "-v", "--config", configName, "-o", pullRoot). + MatchStatus(stateKeys, true, len(stateKeys)). WithWorkDir(tempDir).Exec() // check config - configPath := filepath.Join(tempDir, pullRoot, files[0]) + configPath := filepath.Join(tempDir, pullRoot, configName) Expect(configPath).Should(BeAnExistingFile()) f, err := os.Open(configPath) Expect(err).ShouldNot(HaveOccurred()) defer f.Close() Eventually(gbytes.BufferReader(f)).Should(gbytes.Say("{}")) - for _, f := range files[1:] { + for _, f := range foobar.ImageLayerNames { // check layers Binary("diff", filepath.Join(tempDir, "foobar", f), filepath.Join(pullRoot, f)). WithWorkDir(tempDir).Exec() @@ -77,22 +64,14 @@ var _ = Describe("Remote registry users:", func() { It("should skip config if media type not matching", func() { pullRoot := "pulled" - tempDir := GinkgoT().TempDir() - if err := CopyTestData(tempDir); err != nil { - panic(err) - } - ORAS("pull", Reference(Host, repo, tag), "-v", "--config", fmt.Sprintf("%s:%s", files[0], "???"), "-o", pullRoot). - MatchStatus([]match.StateKey{ - {Digest: "fd6ed2f36b54", Name: ocispec.MediaTypeImageManifest}, - {Digest: "44136fa355b3", Name: oras.MediaTypeUnknownConfig}, - {Digest: "2c26b46b68ff", Name: files[1]}, - {Digest: "2c26b46b68ff", Name: files[2]}, - {Digest: "fcde2b2edba5", Name: files[3]}, - }, true, 5). + tempDir := CopyTestDataToTemp() + stateKeys := append(foobar.ImageLayerStateKeys, foobar.ManifestStateKey, foobar.ImageConfigStateKey(oras.MediaTypeUnknownConfig)) + ORAS("pull", Reference(Host, repo, tag), "-v", "--config", fmt.Sprintf("%s:%s", configName, "???"), "-o", pullRoot). + MatchStatus(stateKeys, true, len(stateKeys)). WithWorkDir(tempDir).Exec() // check config - Expect(filepath.Join(pullRoot, files[0])).ShouldNot(BeAnExistingFile()) - for _, f := range files[1:] { + Expect(filepath.Join(pullRoot, configName)).ShouldNot(BeAnExistingFile()) + for _, f := range foobar.ImageLayerNames { // check layers Binary("diff", filepath.Join(tempDir, "foobar", f), filepath.Join(pullRoot, f)). WithWorkDir(tempDir). @@ -101,13 +80,8 @@ var _ = Describe("Remote registry users:", func() { }) It("should pull specific platform", func() { - tempDir := GinkgoT().TempDir() - ORAS("pull", Reference(Host, repo, "multi"), "--platform", "linux/amd64", "-v", "-o", tempDir). - MatchStatus([]match.StateKey{ - {Digest: "9d84a5716c66", Name: ocispec.MediaTypeImageManifest}, - {Digest: "fe9dbc99451d", Name: ocispec.MediaTypeImageConfig}, - {Digest: "2ef548696ac7", Name: "hello.tar"}, - }, true, 3).Exec() + ORAS("pull", Reference(Host, repo, "multi"), "--platform", "linux/amd64", "-v", "-o", GinkgoT().TempDir()). + MatchStatus(multi_arch.ImageStateKeys, true, len(multi_arch.ImageStateKeys)).Exec() }) }) }) diff --git a/test/e2e/suite/command/repo.go b/test/e2e/suite/command/repo.go index 4fd2e25a6..cf7b3f221 100644 --- a/test/e2e/suite/command/repo.go +++ b/test/e2e/suite/command/repo.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "oras.land/oras/test/e2e/internal/testdata/foobar" + "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" ) @@ -72,7 +73,7 @@ var _ = Describe("Common registry users:", func() { It("should not list repositories without a fully matched namespace", func() { repo := "command-draft/images" - ORAS("cp", Reference(Host, Repo, FoobarImageTag), Reference(Host, repo, FoobarImageTag)). + ORAS("cp", Reference(Host, Repo, foobar.Tag), Reference(Host, repo, foobar.Tag)). WithDescription("prepare destination repo: " + repo). Exec() ORAS("repo", "ls", Host).MatchKeyWords(Repo, repo).Exec() @@ -95,15 +96,15 @@ var _ = Describe("Common registry users:", func() { } repoRef := Reference(Host, ImageRepo, "") It("should list tags", func() { - ORAS("repository", "show-tags", repoRef).MatchKeyWords(MultiImageTag, FoobarImageTag).Exec() + ORAS("repository", "show-tags", repoRef).MatchKeyWords(multi_arch.Tag, foobar.Tag).Exec() }) It("should list tags via short command", func() { - ORAS("repo", "tags", repoRef).MatchKeyWords(MultiImageTag, FoobarImageTag).Exec() + ORAS("repo", "tags", repoRef).MatchKeyWords(multi_arch.Tag, foobar.Tag).Exec() }) It("should list partial tags via `last` flag", func() { - session := ORAS("repo", "tags", repoRef, "--last", FoobarImageTag).MatchKeyWords(MultiImageTag).Exec() - Expect(session.Out).ShouldNot(gbytes.Say(FoobarImageTag)) + session := ORAS("repo", "tags", repoRef, "--last", foobar.Tag).MatchKeyWords(multi_arch.Tag).Exec() + Expect(session.Out).ShouldNot(gbytes.Say(foobar.Tag)) }) It("Should list out tags associated to the provided reference", func() { @@ -114,19 +115,19 @@ var _ = Describe("Common registry users:", func() { ORAS("cp", Reference(Host, Repo, foobar.Tag), refWithTags). WithDescription("prepare: copy and create multiple tags to " + refWithTags). Exec() - ORAS("cp", Reference(Host, Repo, MultiImageTag), Reference(Host, Repo, "")). + ORAS("cp", Reference(Host, Repo, multi_arch.Tag), Reference(Host, Repo, "")). WithDescription("prepare: copy tag with different digest"). Exec() // test viaTag := ORAS("repo", "tags", "-v", Reference(Host, repo, foobar.Tag)). MatchKeyWords(tags...). MatchErrKeyWords("Preview", foobar.Digest).Exec().Out - Expect(viaTag).ShouldNot(gbytes.Say(MultiImageTag)) + Expect(viaTag).ShouldNot(gbytes.Say(multi_arch.Tag)) viaDigest := ORAS("repo", "tags", "-v", Reference(Host, repo, foobar.Digest)). MatchKeyWords(tags...). MatchErrKeyWords("Preview", foobar.Digest).Exec().Out - Expect(viaDigest).ShouldNot(gbytes.Say(MultiImageTag)) + Expect(viaDigest).ShouldNot(gbytes.Say(multi_arch.Tag)) }) }) }) diff --git a/test/e2e/suite/command/tag.go b/test/e2e/suite/command/tag.go index 87a3bf829..871ee9356 100644 --- a/test/e2e/suite/command/tag.go +++ b/test/e2e/suite/command/tag.go @@ -17,6 +17,7 @@ package command import ( . "github.com/onsi/ginkgo/v2" + "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" ) @@ -40,16 +41,16 @@ var _ = Describe("Common registry users:", func() { } When("running `tag`", func() { It("should add a tag to an existent manifest when providing tag reference", func() { - tagAndValidate(Host, ImageRepo, MultiImageTag, "tag-via-tag") + tagAndValidate(Host, ImageRepo, multi_arch.Tag, "tag-via-tag") }) It("should add a tag to an existent manifest when providing digest reference", func() { - tagAndValidate(Host, ImageRepo, MultiImageDigest, "tag-via-digest") + tagAndValidate(Host, ImageRepo, multi_arch.Digest, "tag-via-digest") }) It("should add multiple tags to an existent manifest when providing digest reference", func() { - tagAndValidate(Host, ImageRepo, MultiImageDigest, "tag1-via-digest", "tag2-via-digest", "tag3-via-digest") + tagAndValidate(Host, ImageRepo, multi_arch.Digest, "tag1-via-digest", "tag2-via-digest", "tag3-via-digest") }) It("should add multiple tags to an existent manifest when providing tag reference", func() { - tagAndValidate(Host, ImageRepo, MultiImageTag, "tag1-via-tag", "tag1-via-tag", "tag1-via-tag") + tagAndValidate(Host, ImageRepo, multi_arch.Tag, "tag1-via-tag", "tag1-via-tag", "tag1-via-tag") }) }) }) diff --git a/test/e2e/suite/scenario/oci_artifact.go b/test/e2e/suite/scenario/oci_artifact.go index 5a0139222..c0ffbf417 100644 --- a/test/e2e/suite/scenario/oci_artifact.go +++ b/test/e2e/suite/scenario/oci_artifact.go @@ -40,8 +40,8 @@ var _ = Describe("Common OCI artifact users:", Ordered, func() { pulledManifest := "packed.json" pullRoot := "pulled" It("should push and pull an artifact", func() { - ORAS("push", Reference(Host, repo, tag), "--artifact-type", "test-artifact", foobar.BlobFileNames[0], foobar.BlobFileNames[1], foobar.BlobFileNames[2], "-v", "--export-manifest", pulledManifest). - MatchStatus(foobar.PushFileStateKeys, true, 3). + ORAS("push", Reference(Host, repo, tag), "--artifact-type", "test-artifact", foobar.FileLayerNames[0], foobar.FileLayerNames[1], foobar.FileLayerNames[2], "-v", "--export-manifest", pulledManifest). + MatchStatus(foobar.FileStateKeys, true, 3). WithWorkDir(tempDir). WithDescription("push with manifest exported").Exec() @@ -49,11 +49,11 @@ var _ = Describe("Common OCI artifact users:", Ordered, func() { MatchFile(filepath.Join(tempDir, pulledManifest), string(fetched.Out.Contents()), DefaultTimeout) ORAS("pull", Reference(Host, repo, tag), "-v", "-o", pullRoot). - MatchStatus(foobar.PushFileStateKeys, true, 3). + MatchStatus(foobar.FileStateKeys, true, 3). WithWorkDir(tempDir). WithDescription("pull artFiles with config").Exec() - for _, f := range foobar.BlobFileNames { + for _, f := range foobar.FileLayerNames { Binary("diff", filepath.Join(f), filepath.Join(pullRoot, f)). WithWorkDir(tempDir). WithDescription("download identical file " + f).Exec() @@ -91,11 +91,11 @@ var _ = Describe("Common OCI artifact users:", Ordered, func() { MatchFile(filepath.Join(tempDir, pulledManifest), string(fetched.Out.Contents()), DefaultTimeout) ORAS("pull", Reference(Host, repo, string(digest)), "-v", "-o", pullRoot, "--include-subject"). - MatchStatus(append(foobar.PushFileStateKeys, foobar.AttachFileStateKey), true, 4). + MatchStatus(append(foobar.FileStateKeys, foobar.AttachFileStateKey), true, 4). WithWorkDir(tempDir). WithDescription("pull attached artifact and subject").Exec() - for _, f := range append(foobar.BlobFileNames, foobar.AttachFileName) { + for _, f := range append(foobar.FileLayerNames, foobar.AttachFileName) { Binary("diff", filepath.Join(f), filepath.Join(pullRoot, f)). WithWorkDir(tempDir). WithDescription("download identical file " + f).Exec() diff --git a/test/e2e/suite/scenario/oci_image.go b/test/e2e/suite/scenario/oci_image.go index 26999cb25..5859fa0de 100644 --- a/test/e2e/suite/scenario/oci_image.go +++ b/test/e2e/suite/scenario/oci_image.go @@ -25,8 +25,8 @@ import ( var _ = Describe("OCI image user:", Ordered, func() { repo := "scenario/oci-image" - files := append([]string{foobar.ConfigFileName}, foobar.BlobFileNames...) - statusKeys := append(foobar.PushFileStateKeys, foobar.ConfigFileStateKey) + files := append([]string{foobar.FileConfigName}, foobar.FileLayerNames...) + statusKeys := append(foobar.FileStateKeys, foobar.FileConfigStateKey) When("pushing images and check", func() { tag := "image" var tempDir string diff --git a/test/e2e/testdata/distribution/mount/artifacts_index.tar.gz b/test/e2e/testdata/distribution/mount/artifacts_index.tar.gz new file mode 100644 index 000000000..b12f91dde Binary files /dev/null and b/test/e2e/testdata/distribution/mount/artifacts_index.tar.gz differ