From 4484599b7c942d54a56def920151892e9f228c9d Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 15 Feb 2023 10:28:33 +0800 Subject: [PATCH] test(e2e): add e2e specs for `oras cp` (#749) This PR - Uses docker registry a fallback registry service to cover fallback scenarios for OCI artifacts - Adds command specs for `oras copy` - Scriptizes E2E setup and execution Resolves #646, resolves #568 Signed-off-by: Billy Zha --- .github/workflows/build.yml | 19 +- .gitignore | 3 +- test/e2e/README.md | 167 +++++++++-------- test/e2e/go.mod | 13 +- test/e2e/go.sum | 26 +-- test/e2e/internal/utils/exec.go | 2 + test/e2e/internal/utils/file.go | 2 + test/e2e/internal/utils/init.go | 20 +- test/e2e/internal/utils/match/content.go | 2 + test/e2e/internal/utils/match/keywords.go | 2 + test/e2e/internal/utils/match/matchable.go | 2 + test/e2e/internal/utils/match/status.go | 8 +- test/e2e/internal/utils/preview.go | 2 + test/e2e/internal/utils/registry.go | 2 + test/e2e/internal/utils/testdata.go | 42 +++-- test/e2e/scripts/common.sh | 80 ++++++++ test/e2e/scripts/e2e.sh | 59 ++++++ test/e2e/suite/auth/auth.go | 2 + test/e2e/suite/auth/auth_test.go | 2 + test/e2e/suite/command/blob.go | 32 ++-- test/e2e/suite/command/cp.go | 177 ++++++++++++++++++ test/e2e/suite/command/manifest.go | 96 +++++----- test/e2e/suite/command/pull.go | 2 + test/e2e/suite/command/push.go | 2 + test/e2e/suite/command/repo.go | 14 +- test/e2e/suite/command/tag.go | 12 +- test/e2e/suite/scenario/oci_image.go | 2 + test/e2e/suite/scenario/scenario_test.go | 2 + .../distribution/mount/artifacts.tar.gz | Bin 0 -> 2294 bytes .../mount/artifacts_fallback.tar.gz | Bin 0 -> 1449 bytes .../mount_fallback/artifacts.tar.gz | Bin 0 -> 4378 bytes .../distribution/mount_fallback/passwd_bcrypt | 1 + 32 files changed, 586 insertions(+), 209 deletions(-) create mode 100755 test/e2e/scripts/common.sh create mode 100755 test/e2e/scripts/e2e.sh create mode 100644 test/e2e/suite/command/cp.go create mode 100644 test/e2e/testdata/distribution/mount/artifacts.tar.gz create mode 100644 test/e2e/testdata/distribution/mount/artifacts_fallback.tar.gz create mode 100644 test/e2e/testdata/distribution/mount_fallback/artifacts.tar.gz create mode 100644 test/e2e/testdata/distribution/mount_fallback/passwd_bcrypt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 184adc66c..f7bb92c74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,26 +43,9 @@ jobs: run: make test - name: Run E2E Tests run: | - cd $GITHUB_WORKSPACE/test/e2e - go install github.com/onsi/ginkgo/v2/ginkgo - mnt_root="$GITHUB_WORKSPACE/test/e2e/testdata/distribution/mount" - rm -rf $mnt_root/docker - for layer in $(ls $mnt_root/*.tar.gz); do - tar -xvzf $layer -C $mnt_root - done - trap 'docker kill oras-e2e || true' ERR - docker run --pull always -d -p 5000:5000 --rm --name oras-e2e \ - --env REGISTRY_STORAGE_DELETE_ENABLED=true \ - --env REGISTRY_AUTH_HTPASSWD_REALM=test-basic \ - --env REGISTRY_AUTH_HTPASSWD_PATH=/etc/docker/registry/passwd \ - --mount type=bind,source=$mnt_root/docker,target=/var/lib/registry/docker \ - --mount type=bind,source=$mnt_root/passwd_bcrypt,target=/etc/docker/registry/passwd \ - ghcr.io/oras-project/registry:v1.0.0-rc.3 - ginkgo -r -p --succinct suite - docker kill oras-e2e || true + sh $GITHUB_WORKSPACE/test/e2e/scripts/e2e.sh $GITHUB_WORKSPACE --clean env: ORAS_PATH: bin/linux/amd64/oras - ORAS_REGISTRY_HOST: localhost:5000 - name: Check Version run: bin/linux/amd64/oras version - name: Upload coverage to codecov.io diff --git a/.gitignore b/.gitignore index 3c923ef60..7b8e4c7e0 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ _dist/ .DS_Store # Distribution storage files for local E2E testing -test/e2e/testdata/distribution/mount/docker/ +test/e2e/testdata/distribution/mount/docker/ +test/e2e/testdata/distribution/mount_fallback/docker/ diff --git a/test/e2e/README.md b/test/e2e/README.md index 602726835..f19be2d16 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -1,83 +1,52 @@ # ORAS End-to-End Testing Dev Guide **KNOWN LIMITATION**: E2E tests are designed to run in the CI and currently only support running on linux platform. -## Setting up -Minimal setup: Run the script in **step 3** +## Prerequisites +Install [git](https://git-scm.com/download/linux), [docker](https://docs.docker.com/desktop/install/linux-install), [go](https://go.dev/doc/install). -### 1. Clone Source Code of ORAS CLI +## Run E2E Script ```shell -git clone https://github.com/oras-project/oras.git +$REPO_ROOT/test/e2e/scripts/e2e.sh $REPO_ROOT --clean # REPO_ROOT is root folder of oras CLI code ``` -### 2. _[Optional]_ Install Ginkgo -This will enable you use `ginkgo` directly in CLI. +If the tests fails with errors like `ginkgo: not found`, use below command to add GOPATH into the PATH variable ```shell -go install github.com/onsi/ginkgo/v2/ginkgo@latest +PATH+=:$(go env GOPATH)/bin ``` -If you skip step 2, you can only run tests via `go test`. -### 3. Run Distribution -The backend of E2E test is an [oras-distribution](https://github.com/oras-project/distribution). -```shell -PORT=5000 -docker run -dp $PORT:5000 --rm --name oras-e2e \ - --env STORAGE_DELETE_ENABLED=true \ - ghcr.io/oras-project/registry:v1.0.0-rc.2 -``` +## Development +### 1. Using IDE +Since E2E test suites are added as an nested module, the module file and checksum file are separated from oras CLI. To develop E2E tests, it's better to set the working directory to `$REPO_ROOT/test/e2e/` or open your IDE at it. -### 4. _[Optional]_ Customize Port for Distribution +### 2. Testing pre-built ORAS Binary +By default, Gomega builds a temp binary every time before running e2e tests, which makes sure that latest code changes in the working directory are covered. If you are making changes to E2E test code only, set `ORAS_PATH` towards your pre-built ORAS binary to skip building and speed up the test. + +### 3. Debugging via `go test` +E2E specs can be ran natively without `ginkgo`: ```shell -export ORAS_REGISTRY_HOST="localhost:$PORT" -# for PowerShell, use $env:ORAS_REGISTRY_HOST = "localhost:$PORT" +# run below command in the target test suite folder +go test oras.land/oras/test/e2e/suite/${suite_name} ``` -If you skipped step 4, E2E test will look for distribution ran in `localhost:5000` +This is super handy when you want to do step-by-step debugging from command-line or via an IDE. If you need to debug certain specs, use [focused specs](https://onsi.github.io/ginkgo/#focused-specs) but don't check it in. -### 5. _[Optional]_ Setup ORAS Binary for Testing -```bash -# Set REPO_ROOT as root folder of oras CLI code -cd $REPO_ROOT -make build -``` -### 6. _[Optional]_ Setup Pre-Built Binary -You need to setup below environmental variables to debug a pre-built ORAS binary: -```bash -export ORAS_PATH="bin/linux/amd64/oras" # change target platform if needed -export GITHUB_WORKSPACE=$REPO_ROOT -``` -If you skipped step 5 or 6, Gomega will build a temp binary, which will include all the CLI code changes in the working directory. +### 4. Testing Registry Services +The backend of E2E tests are two registry services: [oras-distribution](https://github.com/oras-project/distribution) and [upstream distribution](https://github.com/distribution/distribution). The former is expected to support image and artifact media types and referrer API; The latter is expected to only support image media type with subject and provide referrers via [tag schema](https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#referrers-tag-schema). -### 7. _[Optional]_ Mount Test Data -If you want to run command suite, you need to decompress the registry storage files and mount to the distribution. `$REPO_ROOT` points to the root folder of cloned oras CLI code. -```shell -mnt_root=${REPO_ROOT}/test/e2e/testdata/distribution/mount -for layer in $(ls ${mnt_root}/*.tar.gz); do - tar -xvzf $layer -C ${mnt_root} -done - -PORT=5000 -docker run -dp ${PORT}:5000 --rm --name oras-e2e \ - --env STORAGE_DELETE_ENABLED=true \ - --mount type=bind,source=${mnt_root}/docker,target=/opt/data/registry-root-dir/docker \ - ghcr.io/oras-project/registry:v1.0.0-rc.2 -``` -Skipping step 7 you will not be able to run specs in Command suite. +You can run scenario test suite against your own registry services via setting `ORAS_REGISTRY_HOST` or `ORAS_REGISTRY_FALLBACK_HOST` environmental variables. -## Development -### 1. Constant Build & Watch -This is a good choice if you want to debug certain re-runnable specs -```bash +### 5. Constant Build & Watch +This is a good choice if you want to debug certain re-runnable specs: +```shell cd $REPO_ROOT/test/e2e ginkgo watch -r ``` -### 2. Debugging -Since E2E test suites are added to a sub-module, you need to run `go test` from `$REPO_ROOT/test/e2e/`. If you need to debug a certain spec, use [focused spec](https://onsi.github.io/ginkgo/#focused-specs) but don't check it in. - -### 3. Trouble-shooting CLI -Executed command should be shown in the ginkgo logs after `[It]`, +### 6. Trouble-shooting CLI +The executed commands should be shown in the ginkgo logs after `[It]`, with full execution output in the E2E log. -### 4. Adding New Tests -Two suites will be maintained for E2E testing: +### 7. Adding New Tests +Three suites will be maintained for E2E testing: - command: contains test specs for single oras command execution +- auth: contains test specs similar to command specs but specific to auth. It cannot be ran in parallel with command suite specs - scenario: contains featured scenarios with several oras commands execution Inside a suite, please follow below model when building the hierarchical collections of specs: @@ -89,34 +58,72 @@ Describe: Expect: (detailed checks for execution results) ``` -### 5. Adding New Test Data +### 8. Adding New Test Data + +#### 8.1 Command Suite +Command suite uses pre-baked test data, which is a bunch of layered archive files compressed from registry storage. Test data are all stored in `$REPO_ROOT/test/e2e/testdata/distribution/` but separated in different sub-folders: oras distribution uses `mount` and upstream distribution uses `mount_fallback`. -#### 5.1 Command Suite -Command suite uses pre-baked registry data for testing. The repository name should be `command/$repo_suffix`. To add a new layer, compress the `docker` folder from the root directory of your distribution storage and copy it to `$REPO_ROOT/test/e2e/testdata/distribution/mount` folder. +For both registries, the repository name should follow the convention of `command/$repo_suffix`. To add a new layer to the test data, use the below command to compress the `docker` folder from the root directory of the registry storage and copy it to the corresponding subfolder in `$REPO_ROOT/test/e2e/testdata/distribution/mount`. ```shell tar -cvzf ${repo_suffix}.tar.gz --owner=0 --group=0 docker/ ``` -Currently we have below OCI images: + +##### Test Data for ORAS-Distribution ```mermaid graph TD; - subgraph images.tar.gz - A0>tag: multi]-..->A1[oci index] - A1--linux/amd64-->A2[oci image] - A1--linux/arm64-->A3[oci image] - A1--linux/arm/v7-->A4[oci image] - A2-->A5[config1] - A3-->A6[config2] - A4-->A7[config3] - A2-- hello.tar -->A8[blob] - A3-- hello.tar -->A8[blob] - A4-- hello.tar -->A8[blob] - - B0>tag: foobar]-..->B1[oci image] - B1-- foo1 -->B2[blob1] - B1-- foo2 -->B2[blob1] - B1-- bar -->B3[blob2] + subgraph "repository: command/images" + subgraph "file: images.tar.gz" + direction TB + A0>tag: multi]-..->A1[oci index] + A1--linux/amd64-->A2[oci image] + A1--linux/arm64-->A3[oci image] + A1--linux/arm/v7-->A4[oci image] + A2-->A5(config1) + A3-->A6(config2) + A4-->A7(config3) + A2-- hello.tar -->A8(blob) + A3-- hello.tar -->A8(blob) + A4-- hello.tar -->A8(blob) + + B0>tag: foobar]-..->B1[oci image] + B1-- foo1 -->B2(blob1) + B1-- foo2 -->B2(blob1) + B1-- bar -->B3(blob2) + end + end + + subgraph "repository: command/artifacts" + subgraph "file: artifacts.tar.gz" + direction TB + C0>tag: foobar]-..->C1[oci image] + + direction TB + E1["test.sbom.file(artifact)"] -- subject --> C1 + E2["test.signature.file(artifact)"] -- subject --> E1 + end + subgraph "file: artifacts_fallback.tar.gz" + direction TB + D1["test.sbom.file(image)"] -- subject --> C1 + D2["test.signature.file(image)"] -- subject --> D1 + end end ``` -#### 5.2 Scenario Suite +##### Test Data for Upstream Distribution +```mermaid +graph TD; + subgraph "repository: command/artifacts" + subgraph "file: artifacts_fallback.tar.gz" + direction TB + A0>tag: foobar]-..->A1[oci image] + A1-- foo1 -->A2(blob1) + A1-- foo2 -->A2(blob1) + A1-- bar -->A3(blob2) + + E1["test.sbom.file(image)"] -- subject --> A1 + E2["test.signature.file(image)"] -- subject --> E1 + end + end +``` +#### 8.2 Scenario Suite Test files used by scenario-based specs are placed in `$REPO_ROOT/test/e2e/testdata/files`. \ No newline at end of file diff --git a/test/e2e/go.mod b/test/e2e/go.mod index cc1f1aab4..b34e47a60 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -3,18 +3,19 @@ module oras.land/oras/test/e2e go 1.20 require ( - github.com/onsi/ginkgo/v2 v2.1.6 - github.com/onsi/gomega v1.20.2 + github.com/onsi/ginkgo/v2 v2.8.0 + github.com/onsi/gomega v1.26.0 github.com/opencontainers/image-spec v1.1.0-rc2 oras.land/oras-go/v2 v2.0.0 ) require ( - github.com/google/go-cmp v0.5.8 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/net v0.5.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/test/e2e/go.sum b/test/e2e/go.sum index af7602d5a..fd89ee311 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -1,22 +1,24 @@ +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= -github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= +github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/test/e2e/internal/utils/exec.go b/test/e2e/internal/utils/exec.go index 5e328cf5e..723952d5b 100644 --- a/test/e2e/internal/utils/exec.go +++ b/test/e2e/internal/utils/exec.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/internal/utils/file.go b/test/e2e/internal/utils/file.go index e18473bfe..c5cd1b3ca 100644 --- a/test/e2e/internal/utils/file.go +++ b/test/e2e/internal/utils/file.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/internal/utils/init.go b/test/e2e/internal/utils/init.go index 0c47352de..712e16561 100644 --- a/test/e2e/internal/utils/init.go +++ b/test/e2e/internal/utils/init.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. @@ -31,19 +33,32 @@ var ORASPath string // Host points to the registry service where E2E specs will be run against. var Host string +// Host points to the registry service where fallback E2E specs will be run against. +var FallbackHost string + func init() { Host = os.Getenv("ORAS_REGISTRY_HOST") if Host == "" { Host = "localhost:5000" fmt.Fprintln(os.Stderr, "cannot find host name in ORAS_REGISTRY_HOST, using", Host, "instead") } - ref := registry.Reference{ Registry: Host, } if err := ref.ValidateRegistry(); err != nil { panic(err) } + + FallbackHost = os.Getenv("ORAS_REGISTRY_FALLBACK_HOST") + if FallbackHost == "" { + FallbackHost = "localhost:6000" + fmt.Fprintln(os.Stderr, "cannot find fallback host name in ORAS_REGISTRY_FALLBACK_HOST, using", FallbackHost, "instead") + } + ref.Registry = FallbackHost + if err := ref.ValidateRegistry(); err != nil { + panic(err) + } + // setup test data pwd, err := os.Getwd() if err != nil { @@ -68,7 +83,10 @@ func init() { fmt.Printf("Testing based on temp binary locates in %q\n", ORASPath) } + // Login cmd := exec.Command(ORASPath, "login", Host, "-u", Username, "-p", Password) gomega.Expect(cmd.Run()).ShouldNot(gomega.HaveOccurred()) + cmd = exec.Command(ORASPath, "login", FallbackHost, "-u", Username, "-p", Password) + gomega.Expect(cmd.Run()).ShouldNot(gomega.HaveOccurred()) }) } diff --git a/test/e2e/internal/utils/match/content.go b/test/e2e/internal/utils/match/content.go index e70d6cc69..15f84af84 100644 --- a/test/e2e/internal/utils/match/content.go +++ b/test/e2e/internal/utils/match/content.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/internal/utils/match/keywords.go b/test/e2e/internal/utils/match/keywords.go index 6f3831f1f..bdd4e8a0f 100644 --- a/test/e2e/internal/utils/match/keywords.go +++ b/test/e2e/internal/utils/match/keywords.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/internal/utils/match/matchable.go b/test/e2e/internal/utils/match/matchable.go index d25ac587c..4d06dcdaa 100644 --- a/test/e2e/internal/utils/match/matchable.go +++ b/test/e2e/internal/utils/match/matchable.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/internal/utils/match/status.go b/test/e2e/internal/utils/match/status.go index 637d38a64..ebbdd0fd4 100644 --- a/test/e2e/internal/utils/match/status.go +++ b/test/e2e/internal/utils/match/status.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. @@ -69,6 +71,10 @@ func newStateMachine(cmd string) *stateMachine { sm.addPath("Downloading", "Processing", "Downloaded") sm.addPath("Skipped") sm.addPath("Restored") + case "copy", "cp": + sm.addPath("Copying", "Copied") + sm.addPath("Skipped") + sm.addPath("Exists") default: panic("Unrecognized cmd name " + cmd) } @@ -134,7 +140,7 @@ func (s *statusMatcher) switchState(st status, key StateKey) { // find next e := findState(now, s.edges[st]) - gomega.Expect(e).NotTo(gomega.BeNil(), fmt.Sprintf("should state node not matching for %v, %v", st, key)) + gomega.Expect(e).NotTo(gomega.BeNil(), fmt.Sprintf("state node not matching for %v, %v", st, key)) // switch s.states[key] = e.to diff --git a/test/e2e/internal/utils/preview.go b/test/e2e/internal/utils/preview.go index 19e0906d5..a7684cf9c 100644 --- a/test/e2e/internal/utils/preview.go +++ b/test/e2e/internal/utils/preview.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/internal/utils/registry.go b/test/e2e/internal/utils/registry.go index 1e1765d5b..c3c0022ca 100644 --- a/test/e2e/internal/utils/registry.go +++ b/test/e2e/internal/utils/registry.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/internal/utils/testdata.go b/test/e2e/internal/utils/testdata.go index 123078255..2729c8c0e 100644 --- a/test/e2e/internal/utils/testdata.go +++ b/test/e2e/internal/utils/testdata.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. @@ -14,21 +16,27 @@ limitations under the License. package utils const ( - PreviewDesc = "** This command is in preview and under development. **" - ExampleDesc = "\nExample - " - 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" - 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" + 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}` ) diff --git a/test/e2e/scripts/common.sh b/test/e2e/scripts/common.sh new file mode 100755 index 000000000..7760a6578 --- /dev/null +++ b/test/e2e/scripts/common.sh @@ -0,0 +1,80 @@ +#!/bin/sh -e + +# 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. + +help () { + echo "Usage" + echo " run-registry " + echo "" + echo "Arguments" + echo " mount-root root mounting directory for pre-baked registry storage files." + echo " image-name image name of the registry." + echo " container-name container name of the registry service." + echo " container-port port to export the registry service." +} + +# run registry service for testing +run_registry () { + # check arguments + mnt_root=$1 + if [ -z "$mnt_root" ]; then + echo "mount root directory path is not provided." + help + exit 1 + fi + mnt_root=$(realpath --canonicalize-existing ${mnt_root}) + img_name=$2 + if [ -z "$img_name" ]; then + echo "distribution image name is not provided." + help + exit 1 + fi + ctr_name=$3 + if [ -z "$ctr_name" ]; then + echo "container name is not provided." + help + exit 1 + fi + ctr_port=$4 + if [ -z "$ctr_port" ]; then + echo "distribution port is not provided." + help + exit 1 + fi + + rm -rf $mnt_root/docker + for layer in $(ls -rt $mnt_root/*.tar.gz); do + tar -xvzf $layer -C $mnt_root + done + + try_clean_up $ctr_name + docker run --pull always -d -p $ctr_port:5000 --rm --name $ctr_name \ + -u $(id -u $(whoami)) \ + --env REGISTRY_STORAGE_DELETE_ENABLED=true \ + --env REGISTRY_AUTH_HTPASSWD_REALM=test-basic \ + --env REGISTRY_AUTH_HTPASSWD_PATH=/etc/docker/registry/passwd \ + --mount type=bind,source=$mnt_root/docker,target=/var/lib/registry/docker \ + --mount type=bind,source=$mnt_root/passwd_bcrypt,target=/etc/docker/registry/passwd \ + $img_name +} + + +# clean up +try_clean_up () { + echo " === stopping below containers ===" + for ctr_name in "$@" + do + docker kill ${ctr_name} || true + done +} \ No newline at end of file diff --git a/test/e2e/scripts/e2e.sh b/test/e2e/scripts/e2e.sh new file mode 100755 index 000000000..bf9c69b2c --- /dev/null +++ b/test/e2e/scripts/e2e.sh @@ -0,0 +1,59 @@ +#!/bin/sh -e + +# 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. + +export ORAS_REGISTRY_PORT="5000" +export ORAS_REGISTRY_HOST="localhost:${ORAS_REGISTRY_PORT}" +export ORAS_REGISTRY_FALLBACK_PORT="6000" +export ORAS_REGISTRY_FALLBACK_HOST="localhost:${ORAS_REGISTRY_FALLBACK_PORT}" + +repo_root=$1 +if [ -z "${repo_root}" ]; then + echo "repository root path is not provided." + echo "Usage" + echo " e2e.sh [--clean]" + exit 1 +fi +clean_up=$2 + +echo " === installing ginkgo === " +repo_root=$(realpath --canonicalize-existing ${repo_root}) +cwd=$(pwd) +cd ${repo_root}/test/e2e && go install github.com/onsi/ginkgo/v2/ginkgo@latest +trap "cd $cwd" EXIT + +# start registries +. ${repo_root}/test/e2e/scripts/common.sh + +if [ "$clean_up" = '--clean' ]; then + echo " === setting deferred clean up jobs === " + trap "try_clean_up oras-e2e oras-e2e-fallback" EXIT +fi + +echo " === preparing oras distribution === " +run_registry \ + ${repo_root}/test/e2e/testdata/distribution/mount \ + ghcr.io/oras-project/registry:v1.0.0-rc.4 \ + oras-e2e \ + $ORAS_REGISTRY_PORT + +echo " === preparing upstream distribution === " +run_registry \ + ${repo_root}/test/e2e/testdata/distribution/mount_fallback \ + registry:2.8.1 \ + oras-e2e-fallback \ + $ORAS_REGISTRY_FALLBACK_PORT + +echo " === run tests === " +ginkgo -r -p --succinct suite \ No newline at end of file diff --git a/test/e2e/suite/auth/auth.go b/test/e2e/suite/auth/auth.go index 04066d259..55ee99fb8 100644 --- a/test/e2e/suite/auth/auth.go +++ b/test/e2e/suite/auth/auth.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/auth/auth_test.go b/test/e2e/suite/auth/auth_test.go index 734d459e8..35ed9169e 100644 --- a/test/e2e/suite/auth/auth_test.go +++ b/test/e2e/suite/auth/auth_test.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/blob.go b/test/e2e/suite/command/blob.go index 9ba7e754a..86f50345a 100644 --- a/test/e2e/suite/command/blob.go +++ b/test/e2e/suite/command/blob.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. @@ -104,22 +106,22 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail if neither output path nor descriptor flag are not provided", func() { - ORAS("blob", "fetch", Reference(Host, Repo, "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae")). + ORAS("blob", "fetch", Reference(Host, ImageRepo, "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae")). ExpectFailure().Exec() }) It("should fail if no digest provided", func() { - ORAS("blob", "fetch", Reference(Host, Repo, "")). + ORAS("blob", "fetch", Reference(Host, ImageRepo, "")). ExpectFailure().Exec() }) It("should fail if provided digest doesn't existed", func() { - ORAS("blob", "fetch", Reference(Host, Repo, "sha256:2aaa2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a")). + ORAS("blob", "fetch", Reference(Host, ImageRepo, "sha256:2aaa2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a")). ExpectFailure().Exec() }) It("should fail if output path points to stdout and descriptor flag is provided", func() { - ORAS("blob", "fetch", Reference(Host, Repo, ""), "--descriptor", "--output", "-"). + ORAS("blob", "fetch", Reference(Host, ImageRepo, ""), "--descriptor", "--output", "-"). ExpectFailure().Exec() }) @@ -134,14 +136,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, Repo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).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, Repo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() ORAS("blob", "delete", Reference(Host, dstRepo, deleteDigest), "--descriptor").ExpectFailure().Exec() ORAS("blob", "fetch", Reference(Host, dstRepo, deleteDigest), "--output", "-").MatchContent(deleteContent).Exec() }) @@ -154,7 +156,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail to delete a non-existent blob without force flag set", func() { - toDeleteRef := Reference(Host, Repo, invalidDigest) + toDeleteRef := Reference(Host, ImageRepo, invalidDigest) ORAS("blob", "delete", toDeleteRef). ExpectFailure(). MatchErrKeyWords(toDeleteRef, "the specified blob does not exist"). @@ -162,7 +164,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail to delete a non-existent blob and output descriptor, with force flag set", func() { - toDeleteRef := Reference(Host, Repo, invalidDigest) + toDeleteRef := Reference(Host, ImageRepo, invalidDigest) ORAS("blob", "delete", toDeleteRef, "--force", "--descriptor"). ExpectFailure(). MatchErrKeyWords(toDeleteRef, "the specified blob does not exist"). @@ -176,7 +178,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, Repo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() toDeleteRef := Reference(Host, dstRepo, deleteDigest) ORAS("blob", "delete", toDeleteRef). WithInput(strings.NewReader("y")). @@ -190,14 +192,14 @@ 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, Repo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).Exec() + ORAS("cp", Reference(Host, ImageRepo, FoobarImageDigest), Reference(Host, dstRepo, FoobarImageDigest)).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() }) It("should return success when deleting a non-existent blob with force flag set", func() { - toDeleteRef := Reference(Host, Repo, invalidDigest) + toDeleteRef := Reference(Host, ImageRepo, invalidDigest) ORAS("blob", "delete", toDeleteRef, "--force"). MatchKeyWords("Missing", toDeleteRef). Exec() @@ -233,24 +235,24 @@ var _ = Describe("Common registry users:", func() { var blobDescriptor = `{"mediaType":"application/octet-stream","digest":"sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae","size":3}` When("running `blob fetch`", func() { It("should fetch blob descriptor ", func() { - ORAS("blob", "fetch", Reference(Host, Repo, blobDigest), "--descriptor"). + ORAS("blob", "fetch", Reference(Host, ImageRepo, blobDigest), "--descriptor"). MatchContent(blobDescriptor).Exec() }) It("should fetch blob content and output to stdout", func() { - ORAS("blob", "fetch", Reference(Host, Repo, blobDigest), "--output", "-"). + ORAS("blob", "fetch", Reference(Host, ImageRepo, blobDigest), "--output", "-"). MatchContent(blobContent).Exec() }) It("should fetch blob content and output to a file", func() { tempDir := GinkgoT().TempDir() contentPath := filepath.Join(tempDir, "fetched") - ORAS("blob", "fetch", Reference(Host, Repo, blobDigest), "--output", contentPath). + ORAS("blob", "fetch", Reference(Host, ImageRepo, blobDigest), "--output", contentPath). WithWorkDir(tempDir).Exec() MatchFile(contentPath, blobContent, DefaultTimeout) }) It("should fetch blob descriptor and output content to a file", func() { tempDir := GinkgoT().TempDir() contentPath := filepath.Join(tempDir, "fetched") - ORAS("blob", "fetch", Reference(Host, Repo, blobDigest), "--output", contentPath, "--descriptor"). + ORAS("blob", "fetch", Reference(Host, ImageRepo, blobDigest), "--output", contentPath, "--descriptor"). MatchContent(blobDescriptor). WithWorkDir(tempDir).Exec() MatchFile(contentPath, blobContent, DefaultTimeout) diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go new file mode 100644 index 000000000..ac3c3c1db --- /dev/null +++ b/test/e2e/suite/command/cp.go @@ -0,0 +1,177 @@ +/* +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 ( + "fmt" + "strings" + + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + . "oras.land/oras/test/e2e/internal/utils" + "oras.land/oras/test/e2e/internal/utils/match" +) + +func cpTestRepo(text string) string { + return fmt.Sprintf("command/copy/%d/%s", GinkgoRandomSeed(), text) +} + +var _ = Describe("ORAS beginners:", func() { + When("running cp command", func() { + RunAndShowPreviewInHelp([]string{"copy"}) + + It("should show preview and help doc", func() { + ORAS("cp", "--help").MatchKeyWords("[Preview] Copy", PreviewDesc, ExampleDesc).Exec() + }) + + It("should fail when no reference provided", func() { + ORAS("cp").ExpectFailure().MatchErrKeyWords("Error:").Exec() + }) + + It("should fail when no destination reference provided", func() { + ORAS("cp", Reference(Host, ImageRepo, FoobarImageTag)).ExpectFailure().MatchErrKeyWords("Error:").Exec() + }) + + It("should fail when source doesn't exist", func() { + ORAS("cp", Reference(Host, ImageRepo, "i-dont-think-this-tag-exists"), Reference(Host, cpTestRepo("nonexistent-source"), "")).ExpectFailure().MatchErrKeyWords("Error:").Exec() + }) + }) +}) + +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 _ = 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)) + } + It("should copy an image to a new repository via tag", func() { + src := Reference(Host, ImageRepo, FoobarImageTag) + 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) + 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) + 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) + ORAS("cp", "-r", src, dst, "-v").MatchStatus(stateKeys, true, len(stateKeys)).Exec() + validate(src, dst) + }) + + It("should copy a certain platform of image to a new repository via tag", func() { + src := Reference(Host, ImageRepo, MultiImageTag) + 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) + }) + + 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) + }) + + It("should copy an image to a new repository with multiple tagging", func() { + src := Reference(Host, ImageRepo, FoobarImageDigest) + tags := []string{"tag1", "tag2", "tag3"} + dstRepo := cpTestRepo("multi-tagging") + dst := Reference(Host, dstRepo, "") + ORAS("cp", src, dst+":"+strings.Join(tags, ","), "-v").MatchStatus(foobarStates, true, len(foobarStates)).Exec() + for _, tag := range tags { + dst := Reference(Host, dstRepo, tag) + validate(src, dst) + } + }) + }) +}) + +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, foobarImageReferrersStates...), foobarImageConfigStates...) + src := Reference(Host, ArtifactRepo, 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() + }) + 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) + 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() + }) + }) +}) diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index d1c445031..e6a0f9bc1 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.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. @@ -77,7 +79,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, Repo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, FoobarImageTag), 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) @@ -85,12 +87,12 @@ 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, Repo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete", Reference(Host, dstRepo, tempTag), "--descriptor").ExpectFailure().Exec() }) It("should fail to delete a non-existent manifest via digest without force flag set", func() { - toDeleteRef := Reference(Host, Repo, invalidDigest) + toDeleteRef := Reference(Host, ImageRepo, invalidDigest) ORAS("manifest", "delete", toDeleteRef). ExpectFailure(). MatchErrKeyWords(toDeleteRef, "the specified manifest does not exist"). @@ -98,7 +100,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail to delete a non-existent manifest and output descriptor via digest, with force flag set", func() { - toDeleteRef := Reference(Host, Repo, invalidDigest) + toDeleteRef := Reference(Host, ImageRepo, invalidDigest) ORAS("manifest", "delete", toDeleteRef, "--force", "--descriptor"). ExpectFailure(). MatchErrKeyWords(toDeleteRef, "the specified manifest does not exist"). @@ -106,7 +108,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail to delete a non-existent manifest and output descriptor via tag, without force flag set", func() { - toDeleteRef := Reference(Host, Repo, "this.tag.should-not.be-existed") + toDeleteRef := Reference(Host, ImageRepo, "this.tag.should-not.be-existed") ORAS("manifest", "delete", toDeleteRef, "--force", "--descriptor"). ExpectFailure(). MatchErrKeyWords(toDeleteRef, "the specified manifest does not exist"). @@ -115,7 +117,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, Repo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete").ExpectFailure().Exec() }) }) @@ -130,10 +132,10 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail if provided reference does not exist", func() { - ORAS("manifest", "fetch-config", Reference(Host, Repo, "this-tag-should-not-exist")).ExpectFailure().Exec() + 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, Repo, MultiImageTag)).ExpectFailure().Exec() + ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, MultiImageTag)).ExpectFailure().Exec() }) }) }) @@ -143,103 +145,103 @@ 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, Repo, MultiImageTag)). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag)). MatchContent(MultiImageManifest).Exec() }) It("should fetch manifest list with tag", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageTag)). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag)). MatchContent(MultiImageManifest).Exec() }) It("should fetch manifest list to stdout", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageTag), "--output", "-"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--output", "-"). MatchContent(MultiImageManifest).Exec() }) It("should fetch manifest to file and output descriptor to stdout", func() { fetchPath := filepath.Join(GinkgoT().TempDir(), "fetchedImage") - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageTag), "--output", fetchPath, "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--output", fetchPath, "--descriptor"). MatchContent(MultiImageDescriptor).Exec() MatchFile(fetchPath, MultiImageManifest, DefaultTimeout) }) It("should fetch manifest via tag with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageTag), "--platform", "linux/amd64"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageTag), "--platform", "linux/amd64"). MatchContent(LinuxAMD64ImageManifest).Exec() }) It("should fetch manifest via digest with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageDigest), "--platform", "linux/amd64"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64"). MatchContent(LinuxAMD64ImageManifest).Exec() }) It("should fetch manifest with platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, LinuxAMD64ImageDigest), "--platform", "linux/amd64"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64"). MatchContent(LinuxAMD64ImageManifest).Exec() }) It("should fetch descriptor via digest", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageDigest), "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--descriptor"). MatchContent(MultiImageDescriptor).Exec() }) It("should fetch descriptor via digest with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageDigest), "--platform", "linux/amd64", "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64", "--descriptor"). MatchContent(LinuxAMD64ImageIndexDescriptor).Exec() }) It("should fetch descriptor via digest with platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--descriptor"). MatchContent(LinuxAMD64ImageDescriptor).Exec() }) It("should fetch descriptor via tag", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageDigest), "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--descriptor"). MatchContent(MultiImageDescriptor).Exec() }) It("should fetch descriptor via tag with platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageDigest), "--platform", "linux/amd64", "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--platform", "linux/amd64", "--descriptor"). MatchContent(LinuxAMD64ImageIndexDescriptor).Exec() }) It("should fetch index content with media type assertion", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageDigest), "--media-type", "application/vnd.oci.image.index.v1+json"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--media-type", "application/vnd.oci.image.index.v1+json"). MatchContent(MultiImageManifest).Exec() }) It("should fetch index descriptor with media type assertion", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageDigest), "--media-type", "application/vnd.oci.image.index.v1+json", "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, MultiImageDigest), "--media-type", "application/vnd.oci.image.index.v1+json", "--descriptor"). MatchContent(MultiImageDescriptor).Exec() }) It("should fetch image content with media type assertion and platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageTag), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json"). + 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, Repo, MultiImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). + 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() }) It("should fetch image descriptor with media type assertion and platform selection", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, MultiImageTag), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). + 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, Repo, MultiImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json", "--descriptor"). + 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() }) It("should fetch image content with media type assertion and platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json"). MatchContent(LinuxAMD64ImageManifest).Exec() }) It("should fetch image descriptor with media type assertion and platform validation", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json", "--descriptor"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--platform", "linux/amd64", "--media-type", "application/vnd.oci.image.manifest.v1+json", "--descriptor"). MatchContent(LinuxAMD64ImageDescriptor).Exec() }) It("should fail to fetch image if media type assertion fails", func() { - ORAS("manifest", "fetch", Reference(Host, Repo, LinuxAMD64ImageDigest), "--media-type", "this.will.not.be.found"). + ORAS("manifest", "fetch", Reference(Host, ImageRepo, LinuxAMD64ImageDigest), "--media-type", "this.will.not.be.found"). ExpectFailure(). MatchErrKeyWords(LinuxAMD64ImageDigest, "error: ", "not found").Exec() }) @@ -252,14 +254,14 @@ var _ = Describe("Common registry users:", func() { It("should push a manifest from stdin without media type flag", func() { tag := "from-stdin" - ORAS("manifest", "push", Reference(Host, Repo, tag), "-"). - MatchKeyWords("Pushed", Reference(Host, Repo, tag), "Digest:", digest). + ORAS("manifest", "push", Reference(Host, ImageRepo, tag), "-"). + MatchKeyWords("Pushed", Reference(Host, ImageRepo, tag), "Digest:", digest). WithInput(strings.NewReader(manifest)).Exec() }) It("should push a manifest and output descriptor", func() { tag := "from-stdin" - ORAS("manifest", "push", Reference(Host, Repo, tag), "-", "--descriptor"). + ORAS("manifest", "push", Reference(Host, ImageRepo, tag), "-", "--descriptor"). MatchContent(descriptor). WithInput(strings.NewReader(manifest)).Exec() }) @@ -267,8 +269,8 @@ var _ = Describe("Common registry users:", func() { It("should push a manifest from file", func() { manifestPath := WriteTempFile("manifest.json", manifest) tag := "from-file" - ORAS("manifest", "push", Reference(Host, Repo, tag), manifestPath, "--media-type", ocispec.MediaTypeImageManifest). - MatchKeyWords("Pushed", Reference(Host, Repo, tag), "Digest:", digest). + ORAS("manifest", "push", Reference(Host, ImageRepo, tag), manifestPath, "--media-type", ocispec.MediaTypeImageManifest). + MatchKeyWords("Pushed", Reference(Host, ImageRepo, tag), "Digest:", digest). WithInput(strings.NewReader(manifest)).Exec() }) @@ -276,11 +278,11 @@ var _ = Describe("Common registry users:", func() { manifest := `{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:fe9dbc99451d0517d65e048c309f0b5afb2cc513b7a3d456b6cc29fe641386c5","size":53}}` digest := "sha256:0c2ae2c73c5dde0a42582d328b2e2ea43f36ba20f604fa8706f441ac8b0a3445" tag := "mediatype-flag" - ORAS("manifest", "push", Reference(Host, Repo, tag), "-", "--media-type", ocispec.MediaTypeImageManifest). - MatchKeyWords("Pushed", Reference(Host, Repo, tag), "Digest:", digest). + ORAS("manifest", "push", Reference(Host, ImageRepo, tag), "-", "--media-type", ocispec.MediaTypeImageManifest). + MatchKeyWords("Pushed", Reference(Host, ImageRepo, tag), "Digest:", digest). WithInput(strings.NewReader(manifest)).Exec() - ORAS("manifest", "push", Reference(Host, Repo, ""), "-"). + ORAS("manifest", "push", Reference(Host, ImageRepo, ""), "-"). WithInput(strings.NewReader(manifest)). ExpectFailure(). WithDescription("fail if no media type flag provided").Exec() @@ -289,32 +291,32 @@ 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, Repo, FoobarImageTag)). + ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, FoobarImageTag)). MatchContent("{}").Exec() }) It("should fetch a config descriptor via a tag", func() { - ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, Repo, FoobarImageTag)). + ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, ImageRepo, FoobarImageTag)). MatchContent(FoobarConfigDesc).Exec() }) It("should fetch a config via digest", func() { - ORAS("manifest", "fetch-config", Reference(Host, Repo, FoobarImageTag)). + ORAS("manifest", "fetch-config", Reference(Host, ImageRepo, FoobarImageTag)). MatchContent("{}").Exec() }) It("should fetch a config descriptor via a digest", func() { - ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, Repo, FoobarImageDigest)). + ORAS("manifest", "fetch-config", "--descriptor", Reference(Host, ImageRepo, FoobarImageDigest)). MatchContent(FoobarConfigDesc).Exec() }) It("should fetch a config of a specific platform", func() { - ORAS("manifest", "fetch-config", "--platform", "linux/amd64", Reference(Host, Repo, MultiImageTag)). + ORAS("manifest", "fetch-config", "--platform", "linux/amd64", Reference(Host, ImageRepo, MultiImageTag)). MatchContent(LinuxAMD64ImageConfig).Exec() }) It("should fetch a config descriptor of a specific platform", func() { - ORAS("manifest", "fetch-config", "--descriptor", "--platform", "linux/amd64", Reference(Host, Repo, MultiImageTag)). + ORAS("manifest", "fetch-config", "--descriptor", "--platform", "linux/amd64", Reference(Host, ImageRepo, MultiImageTag)). MatchContent(LinuxAMD64ImageConfigDescriptor).Exec() }) }) @@ -323,7 +325,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, Repo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) ORAS("manifest", "delete", Reference(Host, dstRepo, tempTag)). WithInput(strings.NewReader("y")).Exec() validate(Reference(Host, dstRepo, ""), tempTag, true) @@ -331,14 +333,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, Repo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, FoobarImageTag), 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, Repo, FoobarImageTag), Reference(Host, dstRepo, tempTag)) + prepare(Reference(Host, ImageRepo, FoobarImageTag), 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() @@ -346,7 +348,7 @@ var _ = Describe("Common registry users:", func() { }) It("should succeed when deleting a non-existent manifest with force flag set", func() { - toDeleteRef := Reference(Host, Repo, invalidDigest) + toDeleteRef := Reference(Host, ImageRepo, invalidDigest) ORAS("manifest", "delete", toDeleteRef, "--force"). MatchKeyWords("Missing", toDeleteRef). Exec() diff --git a/test/e2e/suite/command/pull.go b/test/e2e/suite/command/pull.go index d62c2c906..353c5cbca 100644 --- a/test/e2e/suite/command/pull.go +++ b/test/e2e/suite/command/pull.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/push.go b/test/e2e/suite/command/push.go index d520da2de..dfb2843e7 100644 --- a/test/e2e/suite/command/push.go +++ b/test/e2e/suite/command/push.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/repo.go b/test/e2e/suite/command/repo.go index bbc47a8ae..72ed52e56 100644 --- a/test/e2e/suite/command/repo.go +++ b/test/e2e/suite/command/repo.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. @@ -49,7 +51,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail listing repositories if wrong registry provided", func() { ORAS("repo", "tags").ExpectFailure().MatchErrKeyWords("Error:").Exec() ORAS("repo", "tags", Host).ExpectFailure().MatchErrKeyWords("Error:").Exec() - ORAS("repo", "tags", Reference(Host, Repo, "some-tag")).ExpectFailure().MatchErrKeyWords("Error:").Exec() + ORAS("repo", "tags", Reference(Host, ImageRepo, "some-tag")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) }) }) @@ -58,7 +60,7 @@ var _ = Describe("ORAS beginners:", func() { var _ = Describe("Common registry users:", func() { When("running `repo ls`", func() { It("should list repositories", func() { - ORAS("repository", "list", Host).MatchKeyWords(Repo).Exec() + ORAS("repository", "list", Host).MatchKeyWords(ImageRepo).Exec() }) It("should list repositories under provided namespace", func() { ORAS("repo", "ls", Reference(Host, Namespace, "")).MatchKeyWords(Repo[len(Namespace)+1:]).Exec() @@ -75,15 +77,15 @@ var _ = Describe("Common registry users:", func() { }) It("should list repositories via short command", func() { - ORAS("repo", "ls", Host).MatchKeyWords(Repo).Exec() + ORAS("repo", "ls", Host).MatchKeyWords(ImageRepo).Exec() }) It("should list partial repositories via `last` flag", func() { - session := ORAS("repo", "ls", Host, "--last", Repo).Exec() - Expect(session.Out).ShouldNot(gbytes.Say(Repo)) + session := ORAS("repo", "ls", Host, "--last", ImageRepo).Exec() + Expect(session.Out).ShouldNot(gbytes.Say(ImageRepo)) }) }) When("running `repo tags`", func() { - repoRef := Reference(Host, Repo, "") + repoRef := Reference(Host, ImageRepo, "") It("should list tags", func() { ORAS("repository", "show-tags", repoRef).MatchKeyWords(MultiImageTag, FoobarImageTag).Exec() }) diff --git a/test/e2e/suite/command/tag.go b/test/e2e/suite/command/tag.go index 2c0de569e..87a3bf829 100644 --- a/test/e2e/suite/command/tag.go +++ b/test/e2e/suite/command/tag.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. @@ -26,7 +28,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail when provided manifest reference is not found", func() { - ORAS("tag", Reference(Host, Repo, "i-dont-think-this-tag-exists")).ExpectFailure().MatchErrKeyWords("Error:").Exec() + ORAS("tag", Reference(Host, ImageRepo, "i-dont-think-this-tag-exists")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) }) }) @@ -38,16 +40,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, Repo, MultiImageTag, "tag-via-tag") + tagAndValidate(Host, ImageRepo, MultiImageTag, "tag-via-tag") }) It("should add a tag to an existent manifest when providing digest reference", func() { - tagAndValidate(Host, Repo, MultiImageDigest, "tag-via-digest") + tagAndValidate(Host, ImageRepo, MultiImageDigest, "tag-via-digest") }) It("should add multiple tags to an existent manifest when providing digest reference", func() { - tagAndValidate(Host, Repo, MultiImageDigest, "tag1-via-digest", "tag2-via-digest", "tag3-via-digest") + tagAndValidate(Host, ImageRepo, MultiImageDigest, "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, Repo, MultiImageTag, "tag1-via-tag", "tag1-via-tag", "tag1-via-tag") + tagAndValidate(Host, ImageRepo, MultiImageTag, "tag1-via-tag", "tag1-via-tag", "tag1-via-tag") }) }) }) diff --git a/test/e2e/suite/scenario/oci_image.go b/test/e2e/suite/scenario/oci_image.go index 5bc72421b..f34199b0f 100644 --- a/test/e2e/suite/scenario/oci_image.go +++ b/test/e2e/suite/scenario/oci_image.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/scenario/scenario_test.go b/test/e2e/suite/scenario/scenario_test.go index b3814a6b6..f58e393ae 100644 --- a/test/e2e/suite/scenario/scenario_test.go +++ b/test/e2e/suite/scenario/scenario_test.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/testdata/distribution/mount/artifacts.tar.gz b/test/e2e/testdata/distribution/mount/artifacts.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..aee352e7e6557be856c08c343674f4875b3d1cc2 GIT binary patch literal 2294 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 D1iBV< literal 0 HcmV?d00001 diff --git a/test/e2e/testdata/distribution/mount_fallback/artifacts.tar.gz b/test/e2e/testdata/distribution/mount_fallback/artifacts.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e0ca94ab56c12a5c57b333dfa28b8bded5e4fb4c GIT binary patch literal 4378 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 literal 0 HcmV?d00001 diff --git a/test/e2e/testdata/distribution/mount_fallback/passwd_bcrypt b/test/e2e/testdata/distribution/mount_fallback/passwd_bcrypt new file mode 100644 index 000000000..71e4a0113 --- /dev/null +++ b/test/e2e/testdata/distribution/mount_fallback/passwd_bcrypt @@ -0,0 +1 @@ +hello:$2y$05$7VA2NX15rMmt6Y14Ep7wReDybGvD8e7ko9tsxmvZQwnC4wBfG9Thy