From 65004e64a4dd53891ffabd14ddde9a6bda36a50c Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Mon, 13 Feb 2023 15:12:47 +0800 Subject: [PATCH] test(e2e): add e2e specs for `oras attach` Signed-off-by: Billy Zha --- .../testdata/foobar}/const.go | 24 +-- test/e2e/internal/utils/file.go | 7 + test/e2e/suite/command/attach.go | 178 ++++++++++++++++++ test/e2e/suite/command/manifest.go | 2 +- test/e2e/suite/scenario/oci_artifact.go | 31 +-- test/e2e/suite/scenario/oci_image.go | 5 +- 6 files changed, 217 insertions(+), 30 deletions(-) rename test/e2e/{suite/scenario => internal/testdata/foobar}/const.go (60%) create mode 100644 test/e2e/suite/command/attach.go diff --git a/test/e2e/suite/scenario/const.go b/test/e2e/internal/testdata/foobar/const.go similarity index 60% rename from test/e2e/suite/scenario/const.go rename to test/e2e/internal/testdata/foobar/const.go index bd65e1917..d347aa8af 100644 --- a/test/e2e/suite/scenario/const.go +++ b/test/e2e/internal/testdata/foobar/const.go @@ -13,30 +13,30 @@ See the License for the specific language governing permissions and limitations under the License. */ -package scenario +package foobar import "oras.land/oras/test/e2e/internal/utils/match" var ( - blobFileNames = []string{ + BlobFileNames = []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]}, + PushFileStateKeys = []match.StateKey{ + {Digest: "2c26b46b68ff", Name: BlobFileNames[0]}, + {Digest: "2c26b46b68ff", Name: BlobFileNames[1]}, + {Digest: "fcde2b2edba5", Name: BlobFileNames[2]}, } - configFileName = "foobar/config.json" - configFileStateKey = match.StateKey{ + ConfigFileName = "foobar/config.json" + ConfigFileStateKey = match.StateKey{ Digest: "46b68ac1696c", Name: "application/vnd.unknown.config.v1+json", } - attachFileName = "foobar/to-be-attached" - attachFileMedia = "test/oras.e2e" - attachFileStateKey = match.StateKey{ - Digest: "d3b29f7d12d9", Name: attachFileName, + AttachFileName = "foobar/to-be-attached" + AttachFileMedia = "test/oras.e2e" + AttachFileStateKey = match.StateKey{ + Digest: "d3b29f7d12d9", Name: AttachFileName, } ) diff --git a/test/e2e/internal/utils/file.go b/test/e2e/internal/utils/file.go index c5cd1b3ca..8a669721b 100644 --- a/test/e2e/internal/utils/file.go +++ b/test/e2e/internal/utils/file.go @@ -30,6 +30,13 @@ import ( var testFileRoot string +// CopyTestDataToTemp copies test data into the temp test folder. +func CopyTestDataToTemp() string { + tempDir := GinkgoT().TempDir() + Expect(CopyTestData(tempDir)).ShouldNot(HaveOccurred()) + return tempDir +} + // CopyTestData copies test data into the temp test folder. func CopyTestData(dstRoot string) error { return filepath.WalkDir(testFileRoot, func(path string, d fs.DirEntry, err error) error { diff --git a/test/e2e/suite/command/attach.go b/test/e2e/suite/command/attach.go new file mode 100644 index 000000000..f19b387d9 --- /dev/null +++ b/test/e2e/suite/command/attach.go @@ -0,0 +1,178 @@ +/* +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 command + +import ( + "encoding/json" + "fmt" + "path/filepath" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras/test/e2e/internal/testdata/foobar" + . "oras.land/oras/test/e2e/internal/utils" + "oras.land/oras/test/e2e/internal/utils/match" +) + +func attachTestRepo(text string) string { + return fmt.Sprintf("command/attach/%d/%s", GinkgoRandomSeed(), text) +} + +var _ = Describe("ORAS beginners:", func() { + When("running attach command", func() { + RunAndShowPreviewInHelp([]string{"attach"}) + + It("should show preview and help doc", func() { + ORAS("attach", "--help").MatchKeyWords("[Preview] Attach", PreviewDesc, ExampleDesc).Exec() + }) + + It("should fail when no subject reference provided", func() { + ORAS("attach", "--artifact-type", "oras.test").ExpectFailure().MatchErrKeyWords("Error:").Exec() + }) + + It("should fail if no file reference or manifest annotation provided", func() { + ORAS("attach", "--artifact-type", "oras.test", Reference(Host, ImageRepo, FoobarImageTag)). + ExpectFailure().MatchErrKeyWords("Error: no blob or manifest annotation are provided").Exec() + }) + }) +}) + +var _ = Describe("Common registry users:", func() { + When("running attach command", 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) + 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() + }) + + It("should attach a file to a subject and export the built manifest", func() { + // prepare + testRepo := attachTestRepo("export-manifest") + tempDir := CopyTestDataToTemp() + exportName := "manifest.json" + subjectRef := Reference(Host, testRepo, FoobarImageTag) + prepare(Reference(Host, ImageRepo, FoobarImageTag), subjectRef) + // test + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--export-manifest", exportName). + WithWorkDir(tempDir). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() + // validate + var index ocispec.Index + bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + fetched := ORAS("manifest", "fetch", Reference(Host, testRepo, index.Manifests[0].Digest.String())).Exec().Out.Contents() + MatchFile(filepath.Join(tempDir, exportName), string(fetched), DefaultTimeout) + }) + 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) + // test + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). + WithWorkDir(tempDir). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() + + // validate + var index ocispec.Index + bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].MediaType).To(Equal(ocispec.MediaTypeImageManifest)) + }) + 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) + // test + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-artifact"). + WithWorkDir(tempDir). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() + + // validate + var index ocispec.Index + bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].MediaType).To(Equal(ocispec.MediaTypeArtifactManifest)) + }) + }) +}) + +var _ = Describe("Fallback registry users:", func() { + When("running attach command", 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) + // test + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). + WithWorkDir(tempDir). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() + + // validate + var index ocispec.Index + bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].MediaType).To(Equal(ocispec.MediaTypeImageManifest)) + }) + + 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) + // test + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). + WithWorkDir(tempDir). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() + + // validate + var index ocispec.Index + bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].MediaType).To(Equal(ocispec.MediaTypeImageManifest)) + }) + + 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) + // 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). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() + + // validate + var index ocispec.Index + bytes := ORAS("discover", subjectRef, "--distribution-spec", "v1.1-referrers-tag", "-o", "json").Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + Expect(index.Manifests[0].MediaType).To(Equal(ocispec.MediaTypeImageManifest)) + }) + }) +}) diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index e6a0f9bc1..fb19e0d36 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -28,7 +28,7 @@ import ( ) func prepare(src string, dst string) { - ORAS("cp", src, dst).Exec() + ORAS("cp", src, dst).WithDescription("prepare test env").Exec() } func validate(repoRef string, tag string, gone bool) { diff --git a/test/e2e/suite/scenario/oci_artifact.go b/test/e2e/suite/scenario/oci_artifact.go index 067215637..5a0139222 100644 --- a/test/e2e/suite/scenario/oci_artifact.go +++ b/test/e2e/suite/scenario/oci_artifact.go @@ -20,6 +20,7 @@ import ( "path/filepath" . "github.com/onsi/ginkgo/v2" + "oras.land/oras/test/e2e/internal/testdata/foobar" . "oras.land/oras/test/e2e/internal/utils" "oras.land/oras/test/e2e/internal/utils/match" ) @@ -39,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", blobFileNames[0], blobFileNames[1], blobFileNames[2], "-v", "--export-manifest", pulledManifest). - MatchStatus(pushFileStateKeys, true, 3). + 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). WithWorkDir(tempDir). WithDescription("push with manifest exported").Exec() @@ -48,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(pushFileStateKeys, true, 3). + MatchStatus(foobar.PushFileStateKeys, true, 3). WithWorkDir(tempDir). WithDescription("pull artFiles with config").Exec() - for _, f := range blobFileNames { + for _, f := range foobar.BlobFileNames { Binary("diff", filepath.Join(f), filepath.Join(pullRoot, f)). WithWorkDir(tempDir). WithDescription("download identical file " + f).Exec() @@ -61,40 +62,40 @@ var _ = Describe("Common OCI artifact users:", Ordered, func() { It("should attach and pull an artifact", func() { subject := Reference(Host, repo, tag) - ORAS("attach", subject, "--artifact-type", "test.artifact1", fmt.Sprint(attachFileName, ":", attachFileMedia), "-v", "--export-manifest", pulledManifest). - MatchStatus([]match.StateKey{attachFileStateKey}, true, 1). + ORAS("attach", subject, "--artifact-type", "test.artifact1", fmt.Sprint(foobar.AttachFileName, ":", foobar.AttachFileMedia), "-v", "--export-manifest", pulledManifest). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, true, 1). WithWorkDir(tempDir). WithDescription("attach with manifest exported").Exec() session := ORAS("discover", subject, "-o", "json").Exec() digest := string(Binary("jq", "-r", ".manifests[].digest").WithInput(session.Out).Exec().Out.Contents()) - fetched := ORAS("manifest", "fetch", Reference(Host, repo, digest)).MatchKeyWords(attachFileMedia).Exec() + fetched := ORAS("manifest", "fetch", Reference(Host, repo, digest)).MatchKeyWords(foobar.AttachFileMedia).Exec() MatchFile(filepath.Join(tempDir, pulledManifest), string(fetched.Out.Contents()), DefaultTimeout) ORAS("pull", Reference(Host, repo, digest), "-v", "-o", pullRoot). - MatchStatus([]match.StateKey{attachFileStateKey}, true, 1). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, true, 1). WithWorkDir(tempDir). WithDescription("pull attached artifact").Exec() - Binary("diff", filepath.Join(attachFileName), filepath.Join(pullRoot, attachFileName)). + Binary("diff", filepath.Join(foobar.AttachFileName), filepath.Join(pullRoot, foobar.AttachFileName)). WithWorkDir(tempDir). - WithDescription("download identical file " + attachFileName).Exec() + WithDescription("download identical file " + foobar.AttachFileName).Exec() - ORAS("attach", subject, "--artifact-type", "test.artifact2", fmt.Sprint(attachFileName, ":", attachFileMedia), "-v", "--export-manifest", pulledManifest). - MatchStatus([]match.StateKey{attachFileStateKey}, true, 1). + ORAS("attach", subject, "--artifact-type", "test.artifact2", fmt.Sprint(foobar.AttachFileName, ":", foobar.AttachFileMedia), "-v", "--export-manifest", pulledManifest). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, true, 1). WithWorkDir(tempDir). WithDescription("attach again with manifest exported").Exec() session = ORAS("discover", subject, "-o", "json", "--artifact-type", "test.artifact2").Exec() digest = string(Binary("jq", "-r", ".manifests[].digest").WithInput(session.Out).Exec().Out.Contents()) - fetched = ORAS("manifest", "fetch", Reference(Host, repo, digest)).MatchKeyWords(attachFileMedia).Exec() + fetched = ORAS("manifest", "fetch", Reference(Host, repo, digest)).MatchKeyWords(foobar.AttachFileMedia).Exec() MatchFile(filepath.Join(tempDir, pulledManifest), string(fetched.Out.Contents()), DefaultTimeout) ORAS("pull", Reference(Host, repo, string(digest)), "-v", "-o", pullRoot, "--include-subject"). - MatchStatus(append(pushFileStateKeys, attachFileStateKey), true, 4). + MatchStatus(append(foobar.PushFileStateKeys, foobar.AttachFileStateKey), true, 4). WithWorkDir(tempDir). WithDescription("pull attached artifact and subject").Exec() - for _, f := range append(blobFileNames, attachFileName) { + for _, f := range append(foobar.BlobFileNames, 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 f34199b0f..26999cb25 100644 --- a/test/e2e/suite/scenario/oci_image.go +++ b/test/e2e/suite/scenario/oci_image.go @@ -19,13 +19,14 @@ import ( "path/filepath" . "github.com/onsi/ginkgo/v2" + "oras.land/oras/test/e2e/internal/testdata/foobar" . "oras.land/oras/test/e2e/internal/utils" ) var _ = Describe("OCI image user:", Ordered, func() { repo := "scenario/oci-image" - files := append([]string{configFileName}, blobFileNames...) - statusKeys := append(pushFileStateKeys, configFileStateKey) + files := append([]string{foobar.ConfigFileName}, foobar.BlobFileNames...) + statusKeys := append(foobar.PushFileStateKeys, foobar.ConfigFileStateKey) When("pushing images and check", func() { tag := "image" var tempDir string