diff --git a/test/e2e/internal/testdata/foobar/const.go b/test/e2e/internal/testdata/foobar/const.go index d882e899b..c9be1ec5b 100644 --- a/test/e2e/internal/testdata/foobar/const.go +++ b/test/e2e/internal/testdata/foobar/const.go @@ -16,8 +16,6 @@ limitations under the License. package foobar import ( - "fmt" - "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras/test/e2e/internal/utils/match" @@ -60,10 +58,11 @@ var ( {Digest: "2c26b46b68ff", Name: ImageLayerNames[1]}, {Digest: "fcde2b2edba5", Name: ImageLayerNames[2]}, } - ImageConfigName = "config.json" - Foo1BlobDigest = "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae" - Foo1BlobContent = "foo" - Foo1BlobDescriptor = fmt.Sprintf(`{"mediaType":"application/octet-stream","digest":"%s","size":3}`, Foo1BlobDigest) + ImageConfigName = "config.json" + + FooBlobDigest = "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae" + FooBlobContent = "foo" + FooBlobDescriptor = `{"mediaType":"application/octet-stream","digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae","size":3}` ) func ImageConfigStateKey(configName string) match.StateKey { diff --git a/test/e2e/suite/command/blob.go b/test/e2e/suite/command/blob.go index 1acd06a05..8f868f1d7 100644 --- a/test/e2e/suite/command/blob.go +++ b/test/e2e/suite/command/blob.go @@ -27,13 +27,10 @@ import ( ) const ( - pushContent = "test-blob" - pushDigest = "sha256:e1ca41574914ba00e8ed5c8fc78ec8efdfd48941c7e48ad74dad8ada7f2066d8" - invalidDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000" - pushDescFmt = `{"mediaType":"%s","digest":"sha256:e1ca41574914ba00e8ed5c8fc78ec8efdfd48941c7e48ad74dad8ada7f2066d8","size":9}` - deleteDigest = "sha256:fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9" - deleteDescriptor = `{"mediaType":"application/octet-stream","digest":"sha256:fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9","size":3}` - deleteContent = "bar" + pushContent = "test-blob" + pushDigest = "sha256:e1ca41574914ba00e8ed5c8fc78ec8efdfd48941c7e48ad74dad8ada7f2066d8" + invalidDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000" + pushDescFmt = `{"mediaType":"%s","digest":"sha256:e1ca41574914ba00e8ed5c8fc78ec8efdfd48941c7e48ad74dad8ada7f2066d8","size":9}` ) var _ = Describe("ORAS beginners:", func() { @@ -139,14 +136,14 @@ var _ = Describe("ORAS beginners:", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-ref") ORAS("cp", RegistryRef(Host, ImageRepo, foobar.Digest), RegistryRef(Host, dstRepo, foobar.Digest)).Exec() ORAS("blob", "delete").ExpectFailure().Exec() - ORAS("blob", "fetch", RegistryRef(Host, dstRepo, deleteDigest), "--output", "-").MatchContent(deleteContent).Exec() + ORAS("blob", "fetch", RegistryRef(Host, dstRepo, foobar.FooBlobDigest), "--output", "-").MatchContent(foobar.FooBlobContent).Exec() }) It("should fail if no force flag and descriptor flag is provided", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-confirm") ORAS("cp", RegistryRef(Host, ImageRepo, foobar.Digest), RegistryRef(Host, dstRepo, foobar.Digest)).Exec() - ORAS("blob", "delete", RegistryRef(Host, dstRepo, deleteDigest), "--descriptor").ExpectFailure().Exec() - ORAS("blob", "fetch", RegistryRef(Host, dstRepo, deleteDigest), "--output", "-").MatchContent(deleteContent).Exec() + ORAS("blob", "delete", RegistryRef(Host, dstRepo, foobar.FooBlobDigest), "--descriptor").ExpectFailure().Exec() + ORAS("blob", "fetch", RegistryRef(Host, dstRepo, foobar.FooBlobDigest), "--output", "-").MatchContent(foobar.FooBlobContent).Exec() }) It("should fail if the blob reference is not in the form of ", func() { @@ -180,7 +177,7 @@ var _ = Describe("Common registry users:", func() { It("should delete a blob with interactive confirmation", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "prompt-confirmation") ORAS("cp", RegistryRef(Host, ImageRepo, foobar.Digest), RegistryRef(Host, dstRepo, foobar.Digest)).Exec() - toDeleteRef := RegistryRef(Host, dstRepo, deleteDigest) + toDeleteRef := RegistryRef(Host, dstRepo, foobar.FooBlobDigest) ORAS("blob", "delete", toDeleteRef). WithInput(strings.NewReader("y")). MatchKeyWords("Deleted", toDeleteRef).Exec() @@ -194,8 +191,8 @@ 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", RegistryRef(Host, ImageRepo, foobar.Digest), RegistryRef(Host, dstRepo, foobar.Digest)).Exec() - toDeleteRef := RegistryRef(Host, dstRepo, deleteDigest) - ORAS("blob", "delete", toDeleteRef, "--force", "--descriptor").MatchContent(deleteDescriptor).Exec() + toDeleteRef := RegistryRef(Host, dstRepo, foobar.FooBlobDigest) + ORAS("blob", "delete", toDeleteRef, "--force", "--descriptor").MatchContent(foobar.FooBlobDescriptor).Exec() ORAS("blob", "delete", toDeleteRef).WithDescription("validate").ExpectFailure().MatchErrKeyWords("Error:", toDeleteRef, "the specified blob does not exist").Exec() }) @@ -218,7 +215,6 @@ var _ = Describe("Common registry users:", func() { ORAS("blob", "push", RegistryRef(Host, repo, ""), blobPath, "-v"). WithDescription("skip the pushing if the blob already exists in the target repo"). MatchKeyWords("Exists").Exec() - }) It("should push a blob from a stdin and output the descriptor with specific media-type", func() { @@ -233,27 +229,27 @@ var _ = Describe("Common registry users:", func() { When("running `blob fetch`", func() { It("should fetch blob descriptor ", func() { - ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.Foo1BlobDigest), "--descriptor"). - MatchContent(foobar.Foo1BlobDescriptor).Exec() + ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.FooBlobDigest), "--descriptor"). + MatchContent(foobar.FooBlobDescriptor).Exec() }) It("should fetch blob content and output to stdout", func() { - ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.Foo1BlobDigest), "--output", "-"). - MatchContent(foobar.Foo1BlobContent).Exec() + ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.FooBlobDigest), "--output", "-"). + MatchContent(foobar.FooBlobContent).Exec() }) It("should fetch blob content and output to a file", func() { tempDir := GinkgoT().TempDir() contentPath := filepath.Join(tempDir, "fetched") - ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.Foo1BlobDigest), "--output", contentPath). + ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.FooBlobDigest), "--output", contentPath). WithWorkDir(tempDir).Exec() - MatchFile(contentPath, foobar.Foo1BlobContent, DefaultTimeout) + MatchFile(contentPath, foobar.FooBlobContent, DefaultTimeout) }) It("should fetch blob descriptor and output content to a file", func() { tempDir := GinkgoT().TempDir() contentPath := filepath.Join(tempDir, "fetched") - ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.Foo1BlobDigest), "--output", contentPath, "--descriptor"). - MatchContent(foobar.Foo1BlobDescriptor). + ORAS("blob", "fetch", RegistryRef(Host, ImageRepo, foobar.FooBlobDigest), "--output", contentPath, "--descriptor"). + MatchContent(foobar.FooBlobDescriptor). WithWorkDir(tempDir).Exec() - MatchFile(contentPath, foobar.Foo1BlobContent, DefaultTimeout) + MatchFile(contentPath, foobar.FooBlobContent, DefaultTimeout) }) }) }) @@ -266,7 +262,7 @@ var _ = Describe("OCI image layout users:", func() { } When("running `blob delete`", func() { It("should not support deleting a blob", func() { - toDeleteRef := LayoutRef(prepare(RegistryRef(Host, ImageRepo, foobar.Tag)), deleteDigest) + toDeleteRef := LayoutRef(prepare(RegistryRef(Host, ImageRepo, foobar.Tag)), foobar.FooBlobDigest) ORAS("blob", "delete", LayoutFlag, toDeleteRef). WithInput(strings.NewReader("y")). MatchErrKeyWords("Error:", "unknown flag", LayoutFlag). @@ -278,30 +274,30 @@ var _ = Describe("OCI image layout users:", func() { When("running `blob fetch`", func() { It("should fetch blob descriptor", func() { root := prepare(RegistryRef(Host, ImageRepo, foobar.Tag)) - ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.Foo1BlobDigest), "--descriptor"). - MatchContent(foobar.Foo1BlobDescriptor).Exec() + ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.FooBlobDigest), "--descriptor"). + MatchContent(foobar.FooBlobDescriptor).Exec() }) It("should fetch blob content and output to stdout", func() { root := prepare(RegistryRef(Host, ImageRepo, foobar.Tag)) - ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.Foo1BlobDigest), "--output", "-"). - MatchContent(foobar.Foo1BlobContent).Exec() + ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.FooBlobDigest), "--output", "-"). + MatchContent(foobar.FooBlobContent).Exec() }) It("should fetch blob content and output to a file", func() { root := prepare(RegistryRef(Host, ImageRepo, foobar.Tag)) tempDir := GinkgoT().TempDir() contentPath := filepath.Join(tempDir, "fetched") - ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.Foo1BlobDigest), "--output", contentPath). + ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.FooBlobDigest), "--output", contentPath). WithWorkDir(tempDir).Exec() - MatchFile(contentPath, foobar.Foo1BlobContent, DefaultTimeout) + MatchFile(contentPath, foobar.FooBlobContent, DefaultTimeout) }) It("should fetch blob descriptor and output content to a file", func() { root := prepare(RegistryRef(Host, ImageRepo, foobar.Tag)) tempDir := GinkgoT().TempDir() contentPath := filepath.Join(tempDir, "fetched") - ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.Foo1BlobDigest), "--output", contentPath, "--descriptor"). - MatchContent(foobar.Foo1BlobDescriptor). + ORAS("blob", "fetch", LayoutFlag, LayoutRef(root, foobar.FooBlobDigest), "--output", contentPath, "--descriptor"). + MatchContent(foobar.FooBlobDescriptor). WithWorkDir(tempDir).Exec() - MatchFile(contentPath, foobar.Foo1BlobContent, DefaultTimeout) + MatchFile(contentPath, foobar.FooBlobContent, DefaultTimeout) }) }) diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index 3aa11ba10..ca30a3638 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -21,7 +21,6 @@ import ( "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" @@ -58,32 +57,34 @@ var _ = Describe("ORAS beginners:", func() { var foobarStates = append(foobar.ImageLayerStateKeys, foobar.ManifestStateKey, foobar.ImageConfigStateKey(oras.MediaTypeUnknownConfig)) +func CompareRef(src, dst string) { + 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)) +} + var _ = Describe("Common registry users:", func() { When("running `cp`", func() { - validate := func(src, dst string) { - 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 := RegistryRef(Host, ImageRepo, foobar.Tag) dst := RegistryRef(Host, cpTestRepo("tag"), "copiedTag") ORAS("cp", src, dst, "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() - validate(src, dst) + CompareRef(src, dst) }) It("should copy an image to a new repository via digest", func() { src := RegistryRef(Host, ImageRepo, foobar.Digest) dst := RegistryRef(Host, cpTestRepo("digest"), "copiedTag") ORAS("cp", src, dst, "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() - validate(src, dst) + CompareRef(src, dst) }) It("should copy an image to a new repository via tag without tagging", func() { src := RegistryRef(Host, ImageRepo, foobar.Tag) dst := RegistryRef(Host, cpTestRepo("no-tagging"), foobar.Digest) ORAS("cp", src, dst, "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() - validate(src, dst) + CompareRef(src, dst) }) It("should copy an image and its referrers to a new repository", func() { @@ -91,7 +92,7 @@ var _ = Describe("Common registry users:", func() { src := RegistryRef(Host, ArtifactRepo, foobar.Tag) dst := RegistryRef(Host, cpTestRepo("referrers"), foobar.Digest) ORAS("cp", "-r", src, dst, "-v").MatchStatus(stateKeys, true, len(stateKeys)).Exec() - validate(src, dst) + CompareRef(src, dst) }) It("should copy a multi-arch image and its referrers to a new repository via tag", func() { @@ -103,7 +104,7 @@ var _ = Describe("Common registry users:", func() { MatchKeyWords("Digest: " + ma.Digest). Exec() // validate - validate(RegistryRef(Host, ImageRepo, ma.Digest), dst) + CompareRef(RegistryRef(Host, ImageRepo, ma.Digest), dst) var index ocispec.Index bytes := ORAS("discover", dst, "-o", "json"). MatchKeyWords(ma.IndexReferrerDigest). @@ -127,7 +128,7 @@ var _ = Describe("Common registry users:", func() { MatchKeyWords("Digest: " + ma.Digest). Exec() // validate - validate(RegistryRef(Host, ImageRepo, ma.Digest), dst) + CompareRef(RegistryRef(Host, ImageRepo, ma.Digest), dst) var index ocispec.Index bytes := ORAS("discover", dst, "-o", "json"). MatchKeyWords(ma.IndexReferrerDigest). @@ -150,7 +151,7 @@ var _ = Describe("Common registry users:", func() { MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). MatchKeyWords("Digest: " + ma.LinuxAMD64.Digest.String()). Exec() - validate(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), dst) + CompareRef(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), dst) }) It("should copy a certain platform of image to a new repository via digest", func() { @@ -161,7 +162,7 @@ var _ = Describe("Common registry users:", func() { MatchStatus(ma.IndexStateKeys, true, len(ma.IndexStateKeys)). MatchKeyWords("Digest: " + ma.LinuxAMD64.Digest.String()). Exec() - validate(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), RegistryRef(Host, dstRepo, ma.LinuxAMD64.Digest.String())) + CompareRef(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), RegistryRef(Host, dstRepo, ma.LinuxAMD64.Digest.String())) }) It("should copy a certain platform of image and its referrers to a new repository with tag", func() { @@ -173,7 +174,7 @@ var _ = Describe("Common registry users:", func() { MatchKeyWords("Digest: " + ma.LinuxAMD64.Digest.String()). Exec() // validate - validate(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), dst) + CompareRef(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), dst) var index ocispec.Index bytes := ORAS("discover", dst, "-o", "json", "--platform", "linux/amd64"). MatchKeyWords(ma.LinuxAMD64Referrer.Digest.String()). @@ -201,7 +202,7 @@ var _ = Describe("Common registry users:", func() { Exec() // validate dstRef := RegistryRef(Host, dstRepo, ma.LinuxAMD64.Digest.String()) - validate(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), dstRef) + CompareRef(RegistryRef(Host, ImageRepo, ma.LinuxAMD64.Digest.String()), dstRef) var index ocispec.Index bytes := ORAS("discover", dstRef, "-o", "json", "--platform", "linux/amd64"). MatchKeyWords(ma.LinuxAMD64Referrer.Digest.String()). @@ -228,7 +229,7 @@ var _ = Describe("Common registry users:", func() { ORAS("cp", src, dst+":"+strings.Join(tags, ","), "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() for _, tag := range tags { dst := RegistryRef(Host, dstRepo, tag) - validate(src, dst) + CompareRef(src, dst) } }) }) @@ -236,18 +237,13 @@ var _ = Describe("Common registry users:", func() { var _ = Describe("OCI spec 1.0 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)) - } 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, foobar.ImageReferrersStateKeys...), foobar.ImageReferrerConfigStateKeys...) src := RegistryRef(Host, ArtifactRepo, foobar.SignatureImageReferrer.Digest.String()) dst := RegistryRef(FallbackHost, repo, "") ORAS("cp", "-r", src, dst, "-v").MatchStatus(stateKeys, true, len(stateKeys)).Exec() - validate(src, RegistryRef(FallbackHost, repo, foobar.SignatureImageReferrer.Digest.String())) + CompareRef(src, RegistryRef(FallbackHost, repo, foobar.SignatureImageReferrer.Digest.String())) ORAS("discover", "-o", "tree", RegistryRef(FallbackHost, repo, foobar.Digest)). WithDescription("discover referrer via subject").MatchKeyWords(foobar.SignatureImageReferrer.Digest.String(), foobar.SBOMImageReferrer.Digest.String()).Exec() }) @@ -257,7 +253,7 @@ var _ = Describe("OCI spec 1.0 registry users:", func() { src := RegistryRef(FallbackHost, ArtifactRepo, foobar.FallbackSBOMImageReferrer.Digest.String()) dst := RegistryRef(Host, repo, "") ORAS("cp", "-r", src, dst, "-v").MatchStatus(stateKeys, true, len(stateKeys)).Exec() - validate(src, RegistryRef(Host, repo, foobar.FallbackSBOMImageReferrer.Digest.String())) + CompareRef(src, RegistryRef(Host, repo, foobar.FallbackSBOMImageReferrer.Digest.String())) ORAS("discover", "-o", "tree", RegistryRef(Host, repo, foobar.Digest)). WithDescription("discover referrer via subject").MatchKeyWords(foobar.FallbackSignatureImageReferrer.Digest.String(), foobar.FallbackSBOMImageReferrer.Digest.String()).Exec() }) diff --git a/test/e2e/suite/command/customer_header.go b/test/e2e/suite/command/custom_header.go similarity index 99% rename from test/e2e/suite/command/customer_header.go rename to test/e2e/suite/command/custom_header.go index d50aa4eaa..044cf9c46 100644 --- a/test/e2e/suite/command/customer_header.go +++ b/test/e2e/suite/command/custom_header.go @@ -3,7 +3,9 @@ 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. diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index c299658ef..c312e550e 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -18,6 +18,7 @@ package command import ( "fmt" "path/filepath" + "regexp" "strings" . "github.com/onsi/ginkgo/v2" @@ -33,12 +34,13 @@ func prepare(src string, dst string) { ORAS("cp", src, dst).WithDescription("prepare test env").Exec() } -func validate(repoRef string, tag string, gone bool) { +func validateTag(repoRef string, tag string, gone bool) { session := ORAS("repo", "tags", repoRef).Exec() + quoted := regexp.QuoteMeta(tag + "\n") if gone { - Expect(session.Out).NotTo(gbytes.Say(tag)) + Expect(session.Out).NotTo(gbytes.Say(quoted)) } else { - Expect(session.Out).To(gbytes.Say(tag)) + Expect(session.Out).To(gbytes.Say(quoted)) } } @@ -84,7 +86,7 @@ var _ = Describe("ORAS beginners:", func() { prepare(RegistryRef(Host, ImageRepo, foobar.Tag), RegistryRef(Host, dstRepo, tempTag)) ORAS("manifest", "delete", RegistryRef(Host, dstRepo, tempTag)). MatchKeyWords("Operation cancelled.", "Are you sure you want to delete the manifest ", " and all tags associated with it?").Exec() - validate(RegistryRef(Host, dstRepo, ""), tempTag, false) + validateTag(RegistryRef(Host, dstRepo, ""), tempTag, false) }) It("should fail if descriptor flag is provided without confirmation flag", func() { @@ -330,23 +332,23 @@ var _ = Describe("Common registry users:", func() { prepare(RegistryRef(Host, ImageRepo, foobar.Tag), RegistryRef(Host, dstRepo, tempTag)) ORAS("manifest", "delete", RegistryRef(Host, dstRepo, tempTag)). WithInput(strings.NewReader("y")).Exec() - validate(RegistryRef(Host, dstRepo, ""), tempTag, true) + validateTag(RegistryRef(Host, dstRepo, ""), tempTag, true) }) It("should do confirmed deletion via flag", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "confirm-flag") prepare(RegistryRef(Host, ImageRepo, foobar.Tag), RegistryRef(Host, dstRepo, tempTag)) ORAS("manifest", "delete", RegistryRef(Host, dstRepo, tempTag), "-f").Exec() - validate(RegistryRef(Host, dstRepo, ""), tempTag, true) + validateTag(RegistryRef(Host, dstRepo, ""), tempTag, true) }) - It("should do confirmed deletion and output descriptor", func() { + It("should do forced deletion and output descriptor", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "output-descriptor") prepare(RegistryRef(Host, ImageRepo, foobar.Tag), RegistryRef(Host, dstRepo, tempTag)) ORAS("manifest", "delete", RegistryRef(Host, dstRepo, tempTag), "-f", "--descriptor"). MatchContent("{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:fd6ed2f36b5465244d5dc86cb4e7df0ab8a9d24adc57825099f522fe009a22bb\",\"size\":851}"). WithDescription("cancel without confirmation").Exec() - validate(RegistryRef(Host, dstRepo, ""), tempTag, true) + validateTag(RegistryRef(Host, dstRepo, ""), tempTag, true) }) It("should succeed when deleting a non-existent manifest with force flag set", func() { diff --git a/test/e2e/suite/command/pull.go b/test/e2e/suite/command/pull.go index 4e2b166e4..f113251b3 100644 --- a/test/e2e/suite/command/pull.go +++ b/test/e2e/suite/command/pull.go @@ -32,8 +32,6 @@ import ( var _ = Describe("Remote registry users:", func() { When("pulling images from remote registry", func() { var ( - repo = "command/images" - tag = "foobar" configName = "test.config" ) @@ -41,7 +39,7 @@ var _ = Describe("Remote registry users:", func() { pullRoot := "pulled" tempDir := PrepareTempFiles() stateKeys := append(foobar.ImageLayerStateKeys, foobar.ManifestStateKey, foobar.ImageConfigStateKey(configName)) - ORAS("pull", RegistryRef(Host, repo, tag), "-v", "--config", configName, "-o", pullRoot). + ORAS("pull", RegistryRef(Host, ImageRepo, foobar.Tag), "-v", "--config", configName, "-o", pullRoot). MatchStatus(stateKeys, true, len(stateKeys)). WithWorkDir(tempDir).Exec() // check config @@ -57,7 +55,7 @@ var _ = Describe("Remote registry users:", func() { WithWorkDir(tempDir).Exec() } - ORAS("pull", RegistryRef(Host, repo, tag), "-v", "-o", pullRoot, "--keep-old-files"). + ORAS("pull", RegistryRef(Host, ImageRepo, foobar.Tag), "-v", "-o", pullRoot, "--keep-old-files"). ExpectFailure(). WithDescription("fail if overwrite old files are disabled") }) @@ -66,7 +64,7 @@ var _ = Describe("Remote registry users:", func() { pullRoot := "pulled" tempDir := PrepareTempFiles() stateKeys := append(foobar.ImageLayerStateKeys, foobar.ManifestStateKey, foobar.ImageConfigStateKey(oras.MediaTypeUnknownConfig)) - ORAS("pull", RegistryRef(Host, repo, tag), "-v", "--config", fmt.Sprintf("%s:%s", configName, "???"), "-o", pullRoot). + ORAS("pull", RegistryRef(Host, ImageRepo, foobar.Tag), "-v", "--config", fmt.Sprintf("%s:%s", configName, "???"), "-o", pullRoot). MatchStatus(stateKeys, true, len(stateKeys)). WithWorkDir(tempDir).Exec() // check config @@ -80,7 +78,7 @@ var _ = Describe("Remote registry users:", func() { }) It("should pull specific platform", func() { - ORAS("pull", RegistryRef(Host, repo, "multi"), "--platform", "linux/amd64", "-v", "-o", GinkgoT().TempDir()). + ORAS("pull", RegistryRef(Host, ImageRepo, "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 5d1b95f3b..48da66901 100644 --- a/test/e2e/suite/command/repo.go +++ b/test/e2e/suite/command/repo.go @@ -17,6 +17,7 @@ package command import ( "fmt" + "regexp" "strings" . "github.com/onsi/ginkgo/v2" @@ -86,7 +87,8 @@ var _ = Describe("Common registry users:", func() { }) It("should list partial repositories via `last` flag", func() { session := ORAS("repo", "ls", Host, "--last", ImageRepo).Exec() - Expect(session.Out).ShouldNot(gbytes.Say(ImageRepo)) + repoRegex := regexp.QuoteMeta(ImageRepo + "\n") + Expect(session.Out).ShouldNot(gbytes.Say(repoRegex)) }) }) diff --git a/test/e2e/suite/command/resolve_host.go b/test/e2e/suite/command/resolve_host.go new file mode 100644 index 000000000..4a5815a21 --- /dev/null +++ b/test/e2e/suite/command/resolve_host.go @@ -0,0 +1,260 @@ +/* +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" + "os" + "path/filepath" + "strconv" + "strings" + + . "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/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" +) + +type resolveType uint8 + +const ( + resolveFrom = iota + resolveTo + resolveBase +) + +// ResolveFlags generates resolve flags for localhost mapping. +func ResolveFlags(reg string, host string, flagType resolveType) []string { + Expect(reg).To(HavePrefix("localhost:"), fmt.Sprintf("%q is not in format of localhost:", reg)) + _, port, _ := strings.Cut(reg, ":") + resolveFlag := "resolve" + usernameFlag := "username" + passwordFlag := "password" + plainHttpFlag := "plain-http" + fp := "--" + + switch flagType { + case resolveFrom: + resolveFlag = "from-" + resolveFlag + usernameFlag = "from-" + usernameFlag + passwordFlag = "from-" + passwordFlag + plainHttpFlag = "from-" + plainHttpFlag + case resolveTo: + resolveFlag = "to-" + resolveFlag + usernameFlag = "to-" + usernameFlag + passwordFlag = "to-" + passwordFlag + plainHttpFlag = "to-" + plainHttpFlag + } + + return []string{fp + resolveFlag, fmt.Sprintf("%s:80:127.0.0.1:%s", host, port), fp + usernameFlag, Username, fp + passwordFlag, Password, fp + plainHttpFlag} +} + +var _ = Describe("Common registry users:", func() { + if strings.HasPrefix(Host, "localhost:") { + When("custom host is provided", func() { + // mockedHost represents a non-existent host name which + // only can be resolved by custom DNS rule + mockedHost := "oras.e2e.test" + unary := ResolveFlags(Host, mockedHost, resolveBase) + from := ResolveFlags(Host, mockedHost, resolveFrom) + to := ResolveFlags(Host, mockedHost, resolveTo) + testRepo := func(description string) string { + return fmt.Sprintf("command/resolve/%d/%s", GinkgoRandomSeed(), description) + } + + It("should attach a file to a subject via resolve flag", func() { + repo := testRepo("attach") + tempDir := PrepareTempFiles() + prepare(RegistryRef(Host, ImageRepo, foobar.Tag), RegistryRef(Host, repo, foobar.Tag)) + + ORAS(append([]string{"attach", "--artifact-type", "test.attach", RegistryRef(mockedHost, repo, foobar.Tag), fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)}, unary...)...). + WithWorkDir(tempDir). + MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() + // validate + var index ocispec.Index + bytes := ORAS("discover", "-o", "json", RegistryRef(Host, repo, foobar.Tag)).Exec().Out.Contents() + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(len(index.Manifests)).To(Equal(1)) + }) + It("should push a blob from a stdin and output the descriptor with specific media-type", func() { + mediaType := "test.media" + repo := testRepo("blob/push") + ORAS(append([]string{"blob", "push", RegistryRef(mockedHost, repo, pushDigest), "-", "--media-type", mediaType, "--descriptor", "--size", strconv.Itoa(len(pushContent))}, unary...)...). + WithInput(strings.NewReader(pushContent)). + MatchContent(fmt.Sprintf(pushDescFmt, mediaType)).Exec() + ORAS("blob", "fetch", RegistryRef(Host, repo, pushDigest), "--output", "-").MatchContent(pushContent).Exec() + }) + It("should fetch blob descriptor ", func() { + ORAS(append([]string{"blob", "fetch", RegistryRef(mockedHost, ImageRepo, foobar.FooBlobDigest), "--descriptor"}, unary...)...). + MatchContent(foobar.FooBlobDescriptor).Exec() + }) + + It("should delete a blob with force flag and output descriptor", func() { + repo := testRepo("blob/delete") + ORAS("cp", RegistryRef(Host, ImageRepo, foobar.Digest), RegistryRef(Host, repo, foobar.Digest)).Exec() + // test + toDeleteRef := RegistryRef(mockedHost, repo, foobar.FooBlobDigest) + ORAS(append([]string{"blob", "delete", toDeleteRef, "--force", "--descriptor"}, unary...)...). + MatchContent(foobar.FooBlobDescriptor). + Exec() + deletedRef := RegistryRef(Host, repo, foobar.FooBlobDigest) + ORAS("blob", "delete", deletedRef). + WithDescription("validate"). + ExpectFailure(). + MatchErrKeyWords("Error:", deletedRef, "the specified blob does not exist"). + Exec() + }) + + It("should copy an image with source resolve DNS rules", func() { + repo := testRepo("cp/from") + dst := RegistryRef(Host, repo, "copied") + ORAS(append([]string{"cp", RegistryRef(mockedHost, ImageRepo, foobar.Tag), dst, "-v"}, from...)...). + MatchStatus(foobarStates, true, len(foobarStates)). + Exec() + CompareRef(RegistryRef(Host, ImageRepo, foobar.Tag), dst) + }) + + It("should copy an image with destination resolve DNS rules", func() { + src := RegistryRef(Host, ImageRepo, foobar.Tag) + repo := testRepo("cp/to") + tag := "copied" + ORAS(append([]string{"cp", src, RegistryRef(mockedHost, repo, tag), "-v"}, to...)...). + MatchStatus(foobarStates, true, len(foobarStates)). + Exec() + CompareRef(src, RegistryRef(Host, repo, tag)) + }) + + It("should copy an image with base resolve DNS rules", func() { + src := RegistryRef(Host, ImageRepo, foobar.Tag) + repo := testRepo("cp/base") + tag := "copied" + flags := append(to[2:], "--resolve", unary[1]) //hack to chop the destination flags off + ORAS(append([]string{"cp", src, RegistryRef(mockedHost, repo, tag), "-v"}, flags...)...). + MatchStatus(foobarStates, true, len(foobarStates)). + Exec() + CompareRef(src, RegistryRef(Host, repo, tag)) + }) + + It("should copy an image with overridden resolve DNS rules for both source and destination registry", func() { + repo := testRepo("cp/override") + tag := "copied" + flags := append(append(to, from...), "--resolve", mockedHost+":80:1.1.1.1:5000") + ORAS(append([]string{"cp", RegistryRef(mockedHost, ImageRepo, foobar.Tag), RegistryRef(mockedHost, repo, tag), "-v"}, flags...)...). + MatchStatus(foobarStates, true, len(foobarStates)). + Exec() + CompareRef(RegistryRef(Host, ImageRepo, foobar.Tag), RegistryRef(Host, repo, tag)) + }) + + It("should discover direct referrers of a subject", func() { + bytes := ORAS(append([]string{"discover", RegistryRef(mockedHost, ArtifactRepo, foobar.Tag), "-o", "json"}, unary...)...).Exec().Out.Contents() + var index ocispec.Index + Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) + Expect(index.Manifests).To(HaveLen(2)) + Expect(index.Manifests).Should(ContainElement(foobar.SBOMImageReferrer)) + Expect(index.Manifests).Should(ContainElement(foobar.SBOMArtifactReferrer)) + }) + + It("should login", func() { + ORAS(append([]string{"login", mockedHost}, unary...)...).Exec() + }) + + It("should fetch index manifest with digest", func() { + ORAS(append([]string{"manifest", "fetch", RegistryRef(Host, ImageRepo, multi_arch.Tag)}, unary...)...). + MatchContent(multi_arch.Manifest).Exec() + }) + + It("should fetch scratch config", func() { + ORAS(append([]string{"manifest", "fetch-config", RegistryRef(Host, ImageRepo, foobar.Tag)}, unary...)...). + MatchContent("{}").Exec() + }) + + It("should do forced deletion and output descriptor", func() { + repo := testRepo("manifest/delete") + tempTag := "to-delete" + prepare(RegistryRef(Host, ImageRepo, foobar.Tag), RegistryRef(Host, repo, tempTag)) + ORAS(append([]string{"manifest", "delete", RegistryRef(mockedHost, repo, tempTag), "-f", "--descriptor"}, unary...)...). + MatchContent("{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:fd6ed2f36b5465244d5dc86cb4e7df0ab8a9d24adc57825099f522fe009a22bb\",\"size\":851}"). + WithDescription("cancel without confirmation").Exec() + validateTag(RegistryRef(Host, repo, ""), tempTag, true) + }) + + manifest := `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":53},"layers":[]}` + digest := "sha256:a415225c47052151ea6f6f5c14750d25d580b79cc582949bb64814693365c3b4" + It("should push a manifest from stdin without media type flag", func() { + repo := testRepo("manifest/push") + tag := "from-stdin" + prepare(RegistryRef(Host, ImageRepo, foobar.Digest), RegistryRef(Host, repo, foobar.Digest)) + //test + ORAS(append([]string{"manifest", "push", RegistryRef(Host, repo, tag), "-"}, unary...)...). + MatchKeyWords("Pushed", RegistryRef(Host, repo, tag), "Digest:", digest). + WithInput(strings.NewReader(manifest)).Exec() + validateTag(RegistryRef(Host, repo, ""), tag, false) + }) + + It("should pull all files in an image", func() { + pullRoot := "pulled" + configName := "test.config" + tempDir := PrepareTempFiles() + stateKeys := append(foobar.ImageLayerStateKeys, foobar.ManifestStateKey, foobar.ImageConfigStateKey(configName)) + // test + ORAS(append([]string{"pull", RegistryRef(mockedHost, ImageRepo, foobar.Tag), "-v", "--config", configName, "-o", pullRoot}, unary...)...). + MatchStatus(stateKeys, true, len(stateKeys)). + WithWorkDir(tempDir).Exec() + // validate config and layers + 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 foobar.ImageLayerNames { + Binary("diff", filepath.Join(tempDir, "foobar", f), filepath.Join(pullRoot, f)). + WithWorkDir(tempDir). + Exec() + } + }) + + It("should push files without customized media types", func() { + repo := testRepo("push") + tempDir := PrepareTempFiles() + ORAS(append([]string{"push", RegistryRef(mockedHost, repo, foobar.Tag), "-v"}, append(foobar.FileLayerNames, unary...)...)...). + MatchStatus(foobar.FileStateKeys, true, len(foobar.FileStateKeys)). + WithWorkDir(tempDir).Exec() + }) + + It("should list repositories", func() { + ORAS(append([]string{"repository", "list", mockedHost}, unary...)...).MatchKeyWords(ImageRepo).Exec() + }) + + It("should list tags", func() { + ORAS(append([]string{"repository", "show-tags", RegistryRef(mockedHost, ImageRepo, foobar.Digest)}, unary...)...).MatchKeyWords(foobar.Tag).Exec() + }) + + It("should add a tag to an existent manifest when providing tag reference", func() { + repo := testRepo("tag") + prepare(RegistryRef(Host, ImageRepo, foobar.Digest), RegistryRef(Host, repo, foobar.Digest)) + newTag := "tag-via-digest" + ORAS(append([]string{"tag", RegistryRef(mockedHost, repo, foobar.Digest), newTag}, unary...)...).MatchKeyWords(newTag).Exec() + ORAS("repo", "tags", RegistryRef(Host, repo, "")).MatchKeyWords(newTag).Exec() + }) + }) + } +})