diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index c4e40abc6..2cbee8fc9 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -13,7 +13,7 @@ name: Bug Report description: File a bug report -labels: [bug] +labels: [bug, triage] body: - type: markdown id: preface diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml index 13603c30c..d6656579e 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yaml +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -13,7 +13,7 @@ name: Feature Request description: File a feature request -labels: [enhancement] +labels: [enhancement, triage] body: - type: markdown id: preface diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7c7a38d6..87928b367 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: ['1.20'] + go-version: ['1.21'] fail-fast: true steps: - name: Checkout diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 152cab650..b9e75f8d6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: security-events: write strategy: matrix: - go-version: ['1.20'] + go-version: ['1.21'] fail-fast: false steps: - name: Checkout repository diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 000000000..052c86175 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,41 @@ +# 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. + +name: golangci-lint + +on: + pull_request: + paths-ignore: + - 'docs/**' + +permissions: + contents: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ['1.21'] + fail-fast: true + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + check-latest: true + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 diff --git a/.github/workflows/release-ghcr.yml b/.github/workflows/release-ghcr.yml index d9ba9575b..e39ae4ae4 100644 --- a/.github/workflows/release-ghcr.yml +++ b/.github/workflows/release-ghcr.yml @@ -35,8 +35,7 @@ jobs: if [[ "${VERSION}" == "${BRANCH_NAME}" ]]; then VERSION=$(git rev-parse --short HEAD) fi - echo ::set-output name=version::${VERSION} - echo ::set-output name=ref::ghcr.io/${{ github.repository }}:${VERSION} + echo "ref=ghcr.io/${{ github.repository }}:${VERSION}" >> $GITHUB_OUTPUT - name: docker login uses: docker/login-action@v2 with: diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml index 26ee00e19..b622fb753 100644 --- a/.github/workflows/release-github.yml +++ b/.github/workflows/release-github.yml @@ -29,7 +29,7 @@ jobs: - name: setup go environment uses: actions/setup-go@v3 with: - go-version: '1.20.5' + go-version: '1.21.0' - name: run goreleaser uses: goreleaser/goreleaser-action@v2 with: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..62f5ff723 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,33 @@ +# 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. + +name: "Close stale issues and PRs" +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days." + stale-pr-message: "This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 30 days." + close-issue-message: "This issue was closed because it has been stalled for 30 days with no activity." + close-pr-message: "This PR was closed because it has been stalled for 30 days with no activity." + days-before-issue-stale: 60 + days-before-pr-stale: 45 + days-before-issue-close: 30 + days-before-pr-close: 30 + exempt-all-milestones: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95d6182fe..f0bcf8e35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,3 @@ # Contributing -Please refer to the [ORAS Contributing guide](https://oras.land/docs/contributing). +Please refer to the [ORAS Contributing guide](https://oras.land/docs/community/contributing_guide). diff --git a/Dockerfile b/Dockerfile index e10ce407a..1525123e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20.5-alpine as builder +FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.0-alpine as builder ARG TARGETPLATFORM RUN apk add git make ENV ORASPKG /oras diff --git a/Makefile b/Makefile index cbf8181c4..df548f223 100644 --- a/Makefile +++ b/Makefile @@ -134,8 +134,7 @@ teste2e-covdata: ## test e2e coverage export GOCOVERDIR=$(CURDIR)/test/e2e/.cover; \ rm -rf $$GOCOVERDIR; \ mkdir -p $$GOCOVERDIR; \ - $(MAKE) teste2e; \ - $(GO_EXE) tool covdata textfmt -i=$$GOCOVERDIR -o "$(CURDIR)/test/e2e/coverage.txt" + $(MAKE) teste2e && $(GO_EXE) tool covdata textfmt -i=$$GOCOVERDIR -o "$(CURDIR)/test/e2e/coverage.txt" .PHONY: help help: ## Display this help diff --git a/README.md b/README.md index cf9831a3f..8059c4785 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ ## Docs Documentation for the ORAS CLI is located on -the project website: [oras.land/cli](https://oras.land/docs/category/cli) +the project website: [oras.land/cli](https://oras.land/docs/category/oras-commands) ## Development Environment Setup -Refer to the [development guide](https://oras.land/docs/CLI/developer_guide) to get started [contributing to ORAS](https://oras.land/docs/contributing). +Refer to the [development guide](https://oras.land/docs/community/developer_guide) to get started [contributing to ORAS](https://oras.land/docs/community/contributing_guide). ## Code of Conduct diff --git a/cmd/oras/internal/display/print.go b/cmd/oras/internal/display/print.go index 35de4ec5a..48374a327 100644 --- a/cmd/oras/internal/display/print.go +++ b/cmd/oras/internal/display/print.go @@ -116,7 +116,7 @@ func (p *tagManifestStatusForRepo) PushReference(ctx context.Context, expected o if p.printHint != nil { p.printHint.Do(func() { ref := p.refPrefix + "@" + expected.Digest.String() - Print("Tagging", ref) + _ = Print("Tagging", ref) }) } if err := p.Repository.PushReference(ctx, expected, content, reference); err != nil { @@ -136,9 +136,10 @@ func (p *tagManifestStatusForTarget) Tag(ctx context.Context, desc ocispec.Descr if p.printHint != nil { p.printHint.Do(func() { ref := p.refPrefix + "@" + desc.Digest.String() - Print("Tagging", ref) + _ = Print("Tagging", ref) }) } + if err := p.Target.Tag(ctx, desc, reference); err != nil { return err } diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 631403ac6..e3c6e4c43 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -16,11 +16,9 @@ limitations under the License. package errors import ( - "errors" "fmt" "oras.land/oras-go/v2/registry" - "oras.land/oras-go/v2/registry/remote" ) // NewErrInvalidReference creates a new error based on the reference string. @@ -32,9 +30,3 @@ func NewErrInvalidReference(ref registry.Reference) error { func NewErrInvalidReferenceStr(ref string) error { return fmt.Errorf("%s: invalid image reference, expecting ", ref) } - -// IsReferrersIndexDelete checks if err is a referrers index delete error. -func IsReferrersIndexDelete(err error) bool { - var re *remote.ReferrersError - return errors.As(err, &re) && re.IsReferrersIndexDelete() -} diff --git a/cmd/oras/internal/option/applier.go b/cmd/oras/internal/option/applier.go index f47aba14b..1d177e96e 100644 --- a/cmd/oras/internal/option/applier.go +++ b/cmd/oras/internal/option/applier.go @@ -29,7 +29,7 @@ type FlagApplier interface { // NOTE: The option argument need to be a pointer to the options, so its value // becomes addressable. func ApplyFlags(optsPtr interface{}, target *pflag.FlagSet) { - rangeFields(optsPtr, func(fa FlagApplier) error { + _ = rangeFields(optsPtr, func(fa FlagApplier) error { fa.ApplyFlags(target) return nil }) diff --git a/cmd/oras/internal/option/packer.go b/cmd/oras/internal/option/packer.go index d32fa62de..ad633dd65 100644 --- a/cmd/oras/internal/option/packer.go +++ b/cmd/oras/internal/option/packer.go @@ -99,7 +99,13 @@ func (opts *Packer) LoadManifestAnnotations() (annotations map[string]map[string } if opts.AnnotationFilePath != "" { if err = decodeJSON(opts.AnnotationFilePath, &annotations); err != nil { - return nil, err + errStr := err.Error() + docLink := " Please refer to the document at https://oras.land/docs/how_to_guides/manifest_annotations." + if !strings.HasSuffix(errStr, ".") { + docLink = "."+docLink + } + return nil, fmt.Errorf("failed to load annotations from %s: %w" + + docLink, opts.AnnotationFilePath, err) } } if len(opts.ManifestAnnotations) != 0 { diff --git a/cmd/oras/internal/option/packer_test.go b/cmd/oras/internal/option/packer_test.go index 5c7470ee5..e0cd51339 100644 --- a/cmd/oras/internal/option/packer_test.go +++ b/cmd/oras/internal/option/packer_test.go @@ -70,7 +70,10 @@ func TestPacker_LoadManifestAnnotations_err(t *testing.T) { func TestPacker_LoadManifestAnnotations_annotationFile(t *testing.T) { testFile := filepath.Join(t.TempDir(), "testAnnotationFile") - os.WriteFile(testFile, []byte(testContent), fs.ModePerm) + err := os.WriteFile(testFile, []byte(testContent), fs.ModePerm) + if err != nil { + t.Fatalf("Error writing %s: %v", testFile, err) + } opts := Packer{AnnotationFilePath: testFile} anno, err := opts.LoadManifestAnnotations() @@ -120,11 +123,12 @@ func TestPacker_LoadManifestAnnotations_annotationFlag(t *testing.T) { t.Fatalf("unexpected error: failed when looking for '$manifest' in annotations") } if !reflect.DeepEqual(annotations, - map[string]map[string]string{"$manifest": { - "Key0": "", - "Key1": "Val", - "Key2": "${env:USERNAME}", - }, + map[string]map[string]string{ + "$manifest": { + "Key0": "", + "Key1": "Val", + "Key2": "${env:USERNAME}", + }, }) { t.Fatalf("unexpected error: %v", errors.New("content not match")) } diff --git a/cmd/oras/internal/option/referrers.go b/cmd/oras/internal/option/referrers.go deleted file mode 100644 index 4920c29d0..000000000 --- a/cmd/oras/internal/option/referrers.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -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 option - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/pflag" - "oras.land/oras-go/v2/registry/remote" -) - -// Referrers option struct. -type Referrers struct { - SkipDeleteReferrers bool -} - -// ApplyFlags applies flags to a command flag set. -func (opts *Referrers) ApplyFlags(fs *pflag.FlagSet) { - fs.BoolVarP(&opts.SkipDeleteReferrers, "skip-delete-referrers", "", false, "skip deleting old referrers index, only work on registry when referrers API is not supported") -} - -// SetReferrersGC sets the referrers GC option for the passed-in target. -func (opts *Referrers) SetReferrersGC(target any, logger logrus.FieldLogger) { - if repo, ok := target.(*remote.Repository); ok { - repo.SkipReferrersGC = opts.SkipDeleteReferrers - } else if opts.SkipDeleteReferrers { - // not a registry, can't skip referrers deletion - logger.Warnln("referrers deletion can only be enforced upon registry") - } -} diff --git a/cmd/oras/internal/option/remote.go b/cmd/oras/internal/option/remote.go index c4261a880..f56b90bf5 100644 --- a/cmd/oras/internal/option/remote.go +++ b/cmd/oras/internal/option/remote.go @@ -25,8 +25,10 @@ import ( "os" "strconv" "strings" + "sync" credentials "github.com/oras-project/oras-credentials-go" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" @@ -53,6 +55,7 @@ type Remote struct { distributionSpec distributionSpec headerFlags []string headers http.Header + warned map[string]*sync.Map } // EnableDistributionSpecFlag set distribution specification flag as applicable. @@ -247,33 +250,55 @@ func (opts *Remote) Credential() auth.Credential { return credential.Credential(opts.Username, opts.Password) } +func (opts *Remote) handleWarning(registry string, logger logrus.FieldLogger) func(warning remote.Warning) { + if opts.warned == nil { + opts.warned = make(map[string]*sync.Map) + } + warned := opts.warned[registry] + if warned == nil { + warned = &sync.Map{} + opts.warned[registry] = warned + } + logger = logger.WithField("registry", registry) + return func(warning remote.Warning) { + if _, loaded := warned.LoadOrStore(warning.WarningValue, struct{}{}); !loaded { + logger.Warn(warning.Text) + } + } +} + // NewRegistry assembles a oras remote registry. -func (opts *Remote) NewRegistry(hostname string, common Common) (reg *remote.Registry, err error) { - reg, err = remote.NewRegistry(hostname) +func (opts *Remote) NewRegistry(registry string, common Common, logger logrus.FieldLogger) (reg *remote.Registry, err error) { + reg, err = remote.NewRegistry(registry) if err != nil { return nil, err } - hostname = reg.Reference.Registry - reg.PlainHTTP = opts.isPlainHttp(hostname) - if reg.Client, err = opts.authClient(hostname, common.Debug); err != nil { + registry = reg.Reference.Registry + reg.PlainHTTP = opts.isPlainHttp(registry) + reg.HandleWarning = opts.handleWarning(registry, logger) + if reg.Client, err = opts.authClient(registry, common.Debug); err != nil { return nil, err } return } // NewRepository assembles a oras remote repository. -func (opts *Remote) NewRepository(reference string, common Common) (repo *remote.Repository, err error) { +func (opts *Remote) NewRepository(reference string, common Common, logger logrus.FieldLogger) (repo *remote.Repository, err error) { repo, err = remote.NewRepository(reference) if err != nil { return nil, err } - hostname := repo.Reference.Registry - repo.PlainHTTP = opts.isPlainHttp(hostname) - if repo.Client, err = opts.authClient(hostname, common.Debug); err != nil { + registry := repo.Reference.Registry + repo.PlainHTTP = opts.isPlainHttp(registry) + repo.HandleWarning = opts.handleWarning(registry, logger) + if repo.Client, err = opts.authClient(registry, common.Debug); err != nil { return nil, err } + repo.SkipReferrersGC = true if opts.distributionSpec.referrersAPI != nil { - repo.SetReferrersCapability(*opts.distributionSpec.referrersAPI) + if err := repo.SetReferrersCapability(*opts.distributionSpec.referrersAPI); err != nil { + return nil, err + } } return } diff --git a/cmd/oras/internal/option/remote_test.go b/cmd/oras/internal/option/remote_test.go index efca51cc2..9c60d939b 100644 --- a/cmd/oras/internal/option/remote_test.go +++ b/cmd/oras/internal/option/remote_test.go @@ -30,6 +30,7 @@ import ( "reflect" "testing" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" "oras.land/oras-go/v2/registry/remote/auth" ) @@ -51,7 +52,9 @@ func TestMain(m *testing.M) { case p == "/v2/" && m == "GET": w.WriteHeader(http.StatusOK) case p == fmt.Sprintf("/v2/%s/tags/list", testRepo) && m == "GET": - json.NewEncoder(w).Encode(testTagList) + if err := json.NewEncoder(w).Encode(testTagList); err != nil { + http.Error(w, "error encoding", http.StatusBadRequest) + } } })) defer ts.Close() @@ -178,7 +181,7 @@ func TestRemote_NewRegistry(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - reg, err := opts.NewRegistry(uri.Host, opts.Common) + reg, err := opts.NewRegistry(uri.Host, opts.Common, logrus.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -206,7 +209,7 @@ func TestRemote_NewRepository(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common) + repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common, logrus.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -233,7 +236,10 @@ func TestRemote_NewRepository_Retry(t *testing.T) { http.Error(w, "error", http.StatusTooManyRequests) return } - json.NewEncoder(w).Encode(testTagList) + err := json.NewEncoder(w).Encode(testTagList) + if err != nil { + http.Error(w, "error encoding", http.StatusBadRequest) + } })) defer ts.Close() opts := struct { @@ -250,7 +256,7 @@ func TestRemote_NewRepository_Retry(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common) + repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common, logrus.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/cmd/oras/internal/option/spec.go b/cmd/oras/internal/option/spec.go index ccbb204fb..837959761 100644 --- a/cmd/oras/internal/option/spec.go +++ b/cmd/oras/internal/option/spec.go @@ -18,35 +18,37 @@ package option import ( "fmt" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/pflag" + "oras.land/oras-go/v2" +) + +const ( + ImageSpecV1_1 = "v1.1" + ImageSpecV1_0 = "v1.0" ) // ImageSpec option struct. type ImageSpec struct { - // Manifest type for building artifact - ManifestMediaType string - - // specFlag should be provided in form of `-` - specFlag string + flag string + PackVersion oras.PackManifestVersion } // Parse parses flags into the option. func (opts *ImageSpec) Parse() error { - switch opts.specFlag { - case "v1.1-image": - opts.ManifestMediaType = ocispec.MediaTypeImageManifest - case "v1.1-artifact": - opts.ManifestMediaType = ocispec.MediaTypeArtifactManifest + switch opts.flag { + case ImageSpecV1_1: + opts.PackVersion = oras.PackManifestVersion1_1_RC4 + case ImageSpecV1_0: + opts.PackVersion = oras.PackManifestVersion1_0 default: - return fmt.Errorf("unknown image specification flag: %q", opts.specFlag) + return fmt.Errorf("unknown image specification flag: %q", opts.flag) } return nil } // ApplyFlags applies flags to a command flag set. func (opts *ImageSpec) ApplyFlags(fs *pflag.FlagSet) { - fs.StringVar(&opts.specFlag, "image-spec", "v1.1-image", "[Experimental] specify manifest type for building artifact. options: v1.1-image, v1.1-artifact") + fs.StringVar(&opts.flag, "image-spec", ImageSpecV1_1, fmt.Sprintf("[Experimental] specify manifest type for building artifact. options: %s, %s", ImageSpecV1_1, ImageSpecV1_0)) } // distributionSpec option struct. diff --git a/cmd/oras/internal/option/target.go b/cmd/oras/internal/option/target.go index 9494e1789..36290204e 100644 --- a/cmd/oras/internal/option/target.go +++ b/cmd/oras/internal/option/target.go @@ -21,7 +21,9 @@ import ( "fmt" "os" "strings" + "sync" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content/oci" @@ -111,7 +113,7 @@ func parseOCILayoutReference(raw string) (path string, ref string, err error) { } // NewTarget generates a new target based on opts. -func (opts *Target) NewTarget(common Common) (oras.GraphTarget, error) { +func (opts *Target) NewTarget(common Common, logger logrus.FieldLogger) (oras.GraphTarget, error) { switch opts.Type { case TargetTypeOCILayout: var err error @@ -121,7 +123,7 @@ func (opts *Target) NewTarget(common Common) (oras.GraphTarget, error) { } return oci.New(opts.Path) case TargetTypeRemote: - repo, err := opts.NewRepository(opts.RawReference, common) + repo, err := opts.NewRepository(opts.RawReference, common, logger) if err != nil { return nil, err } @@ -142,7 +144,7 @@ type ReadOnlyGraphTagFinderTarget interface { } // NewReadonlyTargets generates a new read only target based on opts. -func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common) (ReadOnlyGraphTagFinderTarget, error) { +func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common, logger logrus.FieldLogger) (ReadOnlyGraphTagFinderTarget, error) { switch opts.Type { case TargetTypeOCILayout: var err error @@ -159,7 +161,7 @@ func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common) (ReadO } return oci.NewFromTar(ctx, opts.Path) case TargetTypeRemote: - repo, err := opts.NewRepository(opts.RawReference, common) + repo, err := opts.NewRepository(opts.RawReference, common, logger) if err != nil { return nil, err } @@ -203,6 +205,8 @@ func (opts *BinaryTarget) ApplyFlags(fs *pflag.FlagSet) { // Parse parses user-provided flags and arguments into option struct. func (opts *BinaryTarget) Parse() error { + opts.From.warned = make(map[string]*sync.Map) + opts.To.warned = opts.From.warned // resolve are parsed in array order, latter will overwrite former opts.From.resolveFlag = append(opts.resolveFlag, opts.From.resolveFlag...) opts.To.resolveFlag = append(opts.resolveFlag, opts.To.resolveFlag...) diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index 99ed8ba14..b67ca0c99 100644 --- a/cmd/oras/root/attach.go +++ b/cmd/oras/root/attach.go @@ -19,7 +19,6 @@ import ( "context" "errors" "fmt" - "os" "strings" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -27,7 +26,6 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/content/file" - oerr "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/graph" ) @@ -35,9 +33,7 @@ import ( type attachOptions struct { option.Common option.Packer - option.ImageSpec option.Target - option.Referrers artifactType string concurrency int @@ -55,10 +51,6 @@ func attachCmd() *cobra.Command { Example - Attach file 'hi.txt' with type 'doc/example' to manifest 'hello:v1' in registry 'localhost:5000': oras attach --artifact-type doc/example localhost:5000/hello:v1 hi.txt -Example - Attach file "hi.txt" with specific media type when building the manifest: - oras attach --artifact-type doc/example --image-spec v1.1-image localhost:5000/hello:v1 hi.txt # OCI image - oras attach --artifact-type doc/example --image-spec v1.1-artifact localhost:5000/hello:v1 hi.txt # OCI artifact - Example - Attach file "hi.txt" using a specific method for the Referrers API: oras attach --artifact-type doc/example --distribution-spec v1.1-referrers-api localhost:5000/hello:v1 hi.txt # via API oras attach --artifact-type doc/example --distribution-spec v1.1-referrers-tag localhost:5000/hello:v1 hi.txt # via tag scheme @@ -94,7 +86,7 @@ Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder cmd.Flags().StringVarP(&opts.artifactType, "artifact-type", "", "", "artifact type") cmd.Flags().IntVarP(&opts.concurrency, "concurrency", "", 5, "concurrency level") - cmd.MarkFlagRequired("artifact-type") + _ = cmd.MarkFlagRequired("artifact-type") opts.EnableDistributionSpecFlag() option.ApplyFlags(&opts, cmd.Flags()) return cmd @@ -117,15 +109,13 @@ func runAttach(ctx context.Context, opts attachOptions) error { } defer store.Close() - dst, err := opts.NewTarget(opts.Common) + dst, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } if err := opts.EnsureReferenceNotEmpty(); err != nil { return err } - opts.SetReferrersGC(dst, logger) - subject, err := dst.Resolve(ctx, opts.Reference) if err != nil { return err @@ -136,13 +126,13 @@ func runAttach(ctx context.Context, opts attachOptions) error { } // prepare push - packOpts := oras.PackOptions{ + packOpts := oras.PackManifestOptions{ Subject: &subject, ManifestAnnotations: annotations[option.AnnotationManifest], - PackImageManifest: opts.ManifestMediaType == ocispec.MediaTypeImageManifest, + Layers: descs, } pack := func() (ocispec.Descriptor, error) { - return oras.Pack(ctx, store, opts.artifactType, descs, packOpts) + return oras.PackManifest(ctx, store, oras.PackManifestVersion1_1_RC4, opts.artifactType, packOpts) } graphCopyOptions := oras.DefaultCopyGraphOptions @@ -168,9 +158,6 @@ func runAttach(ctx context.Context, opts attachOptions) error { root, err := pushArtifact(dst, pack, copy) if err != nil { - if oerr.IsReferrersIndexDelete(err) { - fmt.Fprintln(os.Stderr, "attached successfully but failed to remove the outdated referrers index, please use `--skip-delete-referrers` if you want to skip the deletion") - } return err } diff --git a/cmd/oras/root/blob/delete.go b/cmd/oras/root/blob/delete.go index 0f55d044b..e1fbb09ac 100644 --- a/cmd/oras/root/blob/delete.go +++ b/cmd/oras/root/blob/delete.go @@ -71,8 +71,8 @@ Example - Delete a blob and print its descriptor: } func deleteBlob(ctx context.Context, opts deleteBlobOptions) (err error) { - ctx, _ = opts.WithContext(ctx) - repo, err := opts.NewRepository(opts.targetRef, opts.Common) + ctx, logger := opts.WithContext(ctx) + repo, err := opts.NewRepository(opts.targetRef, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/blob/fetch.go b/cmd/oras/root/blob/fetch.go index 7f53873a4..d43ae382d 100644 --- a/cmd/oras/root/blob/fetch.go +++ b/cmd/oras/root/blob/fetch.go @@ -90,9 +90,9 @@ Example - Fetch and print a blob from OCI image layout archive file 'layout.tar' } func fetchBlob(ctx context.Context, opts fetchBlobOptions) (fetchErr error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) var target oras.ReadOnlyTarget - target, err := opts.NewReadonlyTarget(ctx, opts.Common) + target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index 9de6c45fe..ea3bffaee 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -96,9 +96,9 @@ Example - Push blob 'hi.txt' into an OCI image layout folder 'layout-dir': } func pushBlob(ctx context.Context, opts pushBlobOptions) (err error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) - repo, err := opts.NewTarget(opts.Common) + repo, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 20cc2a753..90f7cb090 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -18,7 +18,6 @@ package root import ( "context" "fmt" - "os" "strings" "sync" @@ -26,8 +25,8 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" "oras.land/oras/cmd/oras/internal/display" - oerr "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/graph" ) @@ -36,7 +35,6 @@ type copyOptions struct { option.Common option.Platform option.BinaryTarget - option.Referrers recursive bool concurrency int @@ -102,7 +100,7 @@ func runCopy(ctx context.Context, opts copyOptions) error { ctx, logger := opts.WithContext(ctx) // Prepare source - src, err := opts.From.NewReadonlyTarget(ctx, opts.Common) + src, err := opts.From.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } @@ -111,17 +109,18 @@ func runCopy(ctx context.Context, opts copyOptions) error { } // Prepare destination - dst, err := opts.To.NewTarget(opts.Common) + dst, err := opts.To.NewTarget(opts.Common, logger) if err != nil { return err } - opts.SetReferrersGC(dst, logger) // Prepare copy options committed := &sync.Map{} extendedCopyOptions := oras.DefaultExtendedCopyOptions extendedCopyOptions.Concurrency = opts.concurrency - extendedCopyOptions.FindPredecessors = graph.FindReferrerPredecessors + extendedCopyOptions.FindPredecessors = func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + return graph.Referrers(ctx, src, desc, "") + } extendedCopyOptions.PreCopy = display.StatusPrinter("Copying", opts.Verbose) extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) @@ -171,9 +170,6 @@ func runCopy(ctx context.Context, opts copyOptions) error { } } if err != nil { - if oerr.IsReferrersIndexDelete(err) { - fmt.Fprintln(os.Stderr, "failed to remove the outdated referrers index, please use `--skip-delete-referrers` if you want to skip the deletion") - } return err } diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index d853e77d2..801710ead 100644 --- a/cmd/oras/root/discover.go +++ b/cmd/oras/root/discover.go @@ -91,8 +91,8 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout } func runDiscover(ctx context.Context, opts discoverOptions) error { - ctx, _ = opts.WithContext(ctx) - repo, err := opts.NewReadonlyTarget(ctx, opts.Common) + ctx, logger := opts.WithContext(ctx) + repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } @@ -133,7 +133,7 @@ func runDiscover(ctx context.Context, opts discoverOptions) error { fmt.Println("Digest:", desc.Digest) if len(refs) > 0 { fmt.Println() - printDiscoveredReferrersTable(refs, opts.Verbose) + _ = printDiscoveredReferrersTable(refs, opts.Verbose) } return nil } @@ -171,7 +171,7 @@ func fetchAllReferrers(ctx context.Context, repo oras.ReadOnlyGraphTarget, desc return nil } -func printDiscoveredReferrersTable(refs []ocispec.Descriptor, verbose bool) { +func printDiscoveredReferrersTable(refs []ocispec.Descriptor, verbose bool) error { typeNameTitle := "Artifact Type" typeNameLength := len(typeNameTitle) for _, ref := range refs { @@ -188,9 +188,12 @@ func printDiscoveredReferrersTable(refs []ocispec.Descriptor, verbose bool) { for _, ref := range refs { print(ref.ArtifactType, ref.Digest) if verbose { - printJSON(ref) + if err := printJSON(ref); err != nil { + return fmt.Errorf("error printing JSON: %w", err) + } } } + return nil } // printDiscoveredReferrersJSON prints referrer list in JSON equivalent to the diff --git a/cmd/oras/root/login.go b/cmd/oras/root/login.go index aa862fd4d..d6cc65d5d 100644 --- a/cmd/oras/root/login.go +++ b/cmd/oras/root/login.go @@ -75,7 +75,7 @@ Example - Log in with username and password in an interactive terminal and no TL } func runLogin(ctx context.Context, opts loginOptions) (err error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) // prompt for credential if opts.Password == "" { @@ -108,7 +108,7 @@ func runLogin(ctx context.Context, opts loginOptions) (err error) { if err != nil { return err } - remote, err := opts.Remote.NewRegistry(opts.Hostname, opts.Common) + remote, err := opts.Remote.NewRegistry(opts.Hostname, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index 8ece10400..4458fae77 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -76,8 +76,8 @@ Example - Delete a manifest by digest 'sha256:99e4703fbf30916f549cd6bfa9cdbab614 } func deleteManifest(ctx context.Context, opts deleteOptions) error { - ctx, _ = opts.WithContext(ctx) - repo, err := opts.NewRepository(opts.targetRef, opts.Common) + ctx, logger := opts.WithContext(ctx) + repo, err := opts.NewRepository(opts.targetRef, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/fetch.go b/cmd/oras/root/manifest/fetch.go index 850ac915f..52ec2f80b 100644 --- a/cmd/oras/root/manifest/fetch.go +++ b/cmd/oras/root/manifest/fetch.go @@ -90,9 +90,9 @@ Example - Fetch raw manifest from an OCI layout archive file 'layout.tar': } func fetchManifest(ctx context.Context, opts fetchOptions) (fetchErr error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) - target, err := opts.NewReadonlyTarget(ctx, opts.Common) + target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/fetch_config.go b/cmd/oras/root/manifest/fetch_config.go index a909a4d38..e0f871f34 100644 --- a/cmd/oras/root/manifest/fetch_config.go +++ b/cmd/oras/root/manifest/fetch_config.go @@ -86,9 +86,9 @@ Example - Fetch and print the prettified descriptor of the config: } func fetchConfig(ctx context.Context, opts fetchConfigOptions) (fetchErr error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) - repo, err := opts.NewReadonlyTarget(ctx, opts.Common) + repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index f6391c939..d9e737918 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -29,7 +29,6 @@ import ( "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote" "oras.land/oras/cmd/oras/internal/display" - oerr "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/file" ) @@ -39,7 +38,6 @@ type pushOptions struct { option.Descriptor option.Pretty option.Target - option.Referrers concurrency int extraRefs []string @@ -109,11 +107,10 @@ func pushManifest(ctx context.Context, opts pushOptions) error { ctx, logger := opts.WithContext(ctx) var target oras.Target var err error - target, err = opts.NewTarget(opts.Common) + target, err = opts.NewTarget(opts.Common, logger) if err != nil { return err } - opts.SetReferrersGC(target, logger) if repo, ok := target.(*remote.Repository); ok { target = repo.Manifests() } @@ -154,9 +151,6 @@ func pushManifest(ctx context.Context, opts pushOptions) error { return err } if _, err := oras.TagBytes(ctx, target, mediaType, contentBytes, ref); err != nil { - if oerr.IsReferrersIndexDelete(err) { - fmt.Fprintln(os.Stderr, "pushed successfully but failed to remove the outdated referrers index, please use `--skip-delete-referrers` if you want to skip the deletion") - } return err } if err = display.PrintStatus(desc, "Uploaded ", verbose); err != nil { diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index c10f330a4..6baf93091 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -103,7 +103,7 @@ Example - Pull artifact files from an OCI layout archive 'layout.tar': } func runPull(ctx context.Context, opts pullOptions) error { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) // Copy Options var printed sync.Map copyOptions := oras.DefaultCopyOptions @@ -182,7 +182,7 @@ func runPull(ctx context.Context, opts pullOptions) error { return ret, nil } - target, err := opts.NewReadonlyTarget(ctx, opts.Common) + target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go index 0423df717..c8e10a86c 100644 --- a/cmd/oras/root/push.go +++ b/cmd/oras/root/push.go @@ -71,10 +71,6 @@ Example - Push file "hi.txt" with config type "application/vnd.me.config": Example - Push file "hi.txt" with the custom manifest config "config.json" of the custom media type "application/vnd.me.config": oras push --config config.json:application/vnd.me.config localhost:5000/hello:v1 hi.txt -Example - Push file "hi.txt" with specific media type when building the manifest: - oras push --image-spec v1.1-image localhost:5000/hello:v1 hi.txt # OCI image - oras push --image-spec v1.1-artifact localhost:5000/hello:v1 hi.txt # OCI artifact - Example - Push file to the insecure registry: oras push --insecure localhost:5000/hello:v1 hi.txt @@ -102,15 +98,20 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t opts.RawReference = refs[0] opts.extraRefs = refs[1:] opts.FileRefs = args[1:] - if opts.manifestConfigRef != "" { - if opts.artifactType != "" { - return errors.New("--artifact-type and --config cannot both be provided") + if err := option.Parse(&opts); err != nil { + return err + } + switch opts.PackVersion { + case oras.PackManifestVersion1_0: + if opts.manifestConfigRef != "" && opts.artifactType != "" { + return errors.New("--artifact-type and --config cannot both be provided for 1.0 OCI image") } - if opts.ManifestMediaType == ocispec.MediaTypeArtifactManifest { - return errors.New("cannot build an OCI artifact with manifest config") + case oras.PackManifestVersion1_1_RC4: + if opts.manifestConfigRef == "" && opts.artifactType == "" { + opts.artifactType = oras.MediaTypeUnknownArtifact } } - return option.Parse(&opts) + return nil }, RunE: func(cmd *cobra.Command, args []string) error { return runPush(cmd.Context(), opts) @@ -125,14 +126,14 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t } func runPush(ctx context.Context, opts pushOptions) error { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) annotations, err := opts.LoadManifestAnnotations() if err != nil { return err } // prepare pack - packOpts := oras.PackOptions{ + packOpts := oras.PackManifestOptions{ ConfigAnnotations: annotations[option.AnnotationConfig], ManifestAnnotations: annotations[option.AnnotationManifest], } @@ -152,18 +153,15 @@ func runPush(ctx context.Context, opts pushOptions) error { } desc.Annotations = packOpts.ConfigAnnotations packOpts.ConfigDescriptor = &desc - packOpts.PackImageManifest = true - } - if opts.ManifestMediaType == ocispec.MediaTypeImageManifest { - packOpts.PackImageManifest = true } descs, err := loadFiles(ctx, store, annotations, opts.FileRefs, opts.Verbose) if err != nil { return err } + packOpts.Layers = descs memoryStore := memory.New() pack := func() (ocispec.Descriptor, error) { - root, err := oras.Pack(ctx, memoryStore, opts.artifactType, descs, packOpts) + root, err := oras.PackManifest(ctx, memoryStore, opts.PackVersion, opts.artifactType, packOpts) if err != nil { return ocispec.Descriptor{}, err } @@ -174,7 +172,7 @@ func runPush(ctx context.Context, opts pushOptions) error { } // prepare push - dst, err := opts.NewTarget(opts.Common) + dst, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/repo/ls.go b/cmd/oras/root/repo/ls.go index f2da5e5f3..cea8dd334 100644 --- a/cmd/oras/root/repo/ls.go +++ b/cmd/oras/root/repo/ls.go @@ -69,8 +69,8 @@ Example - List the repositories under the registry that include values lexically } func listRepository(ctx context.Context, opts repositoryOptions) error { - ctx, _ = opts.WithContext(ctx) - reg, err := opts.Remote.NewRegistry(opts.hostname, opts.Common) + ctx, logger := opts.WithContext(ctx) + reg, err := opts.Remote.NewRegistry(opts.hostname, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/repo/tags.go b/cmd/oras/root/repo/tags.go index 2e7bde777..45e37dba9 100644 --- a/cmd/oras/root/repo/tags.go +++ b/cmd/oras/root/repo/tags.go @@ -79,7 +79,7 @@ Example - [Experimental] Show tags associated with a digest: func showTags(ctx context.Context, opts showTagsOptions) error { ctx, logger := opts.WithContext(ctx) - finder, err := opts.NewReadonlyTarget(ctx, opts.Common) + finder, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/tag.go b/cmd/oras/root/tag.go index 35475fec6..4524d4fb9 100644 --- a/cmd/oras/root/tag.go +++ b/cmd/oras/root/tag.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" + "oras.land/oras-go/v2/registry" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/option" ) @@ -58,6 +59,9 @@ Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'l Args: cobra.MinimumNArgs(2), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] + if _, err := registry.ParseReference(opts.RawReference); err != nil { + return fmt.Errorf("unable to add tag for '%s': %w", opts.RawReference, err) + } opts.targetRefs = args[1:] return option.Parse(&opts) }, @@ -72,8 +76,8 @@ Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'l } func tagManifest(ctx context.Context, opts tagOptions) error { - ctx, _ = opts.WithContext(ctx) - target, err := opts.NewTarget(opts.Common) + ctx, logger := opts.WithContext(ctx) + target, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/go.mod b/go.mod index d76346c6e..eb141b539 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,21 @@ module oras.land/oras -go 1.20 +go 1.21 require ( github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc2 + github.com/opencontainers/image-spec v1.1.0-rc4 github.com/oras-project/oras-credentials-go v0.2.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - golang.org/x/term v0.10.0 + golang.org/x/term v0.11.0 gopkg.in/yaml.v3 v3.0.1 - oras.land/oras-go/v2 v2.2.1-0.20230627113607-6b5bd4b4372b + oras.land/oras-go/v2 v2.2.1-0.20230822062013-e6d40b6ff29f ) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/sys v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index ef1f32966..56f34e990 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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= +github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= +github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/oras-project/oras-credentials-go v0.2.0 h1:BvWAXo0e5unWR6Hfxyb0K04mHNHreQz/Zclw6IzCYJo= github.com/oras-project/oras-credentials-go v0.2.0/go.mod h1:JVdg7a5k7hzTrEeeouwag0aCv7OLrS77r7/6w3gVirU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -25,14 +25,14 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.2.1-0.20230627113607-6b5bd4b4372b h1:NYynybpqtG3lLTZMWNlrvUlcyGakCke57tg4TX6w2kA= -oras.land/oras-go/v2 v2.2.1-0.20230627113607-6b5bd4b4372b/go.mod h1:goptA58HogB/6sLN7KV6FgoiurcVxd5QBqv8wMVB0as= +oras.land/oras-go/v2 v2.2.1-0.20230822062013-e6d40b6ff29f h1:2Lx/b6oqh6vZST55SxNYNQJSZmOQZDQckdw34tI2bc4= +oras.land/oras-go/v2 v2.2.1-0.20230822062013-e6d40b6ff29f/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= diff --git a/internal/cache/target_test.go b/internal/cache/target_test.go index cbc7cd0b0..508116774 100644 --- a/internal/cache/target_test.go +++ b/internal/cache/target_test.go @@ -177,7 +177,9 @@ func TestProxy_fetchReference(t *testing.T) { w.WriteHeader(http.StatusOK) // write data to the response if this is the first request if requestCount == 1 { - w.Write(blob) + if _, err := w.Write(blob); err != nil { + t.Errorf("Error writing blobs: %v", err) + } } atomic.AddInt64(&successCount, 1) return diff --git a/internal/credential/store_test.go b/internal/credential/store_test.go new file mode 100644 index 000000000..3a873ab14 --- /dev/null +++ b/internal/credential/store_test.go @@ -0,0 +1,50 @@ +/* +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 credential + +import ( + "os" + "path" + "reflect" + "strings" + "testing" +) + +func TestNewStoreError(t *testing.T) { + tmpDir := t.TempDir() + filename := path.Join(tmpDir, "testfile.txt") + file, err := os.Create(filename) + if err != nil { + t.Errorf("error: cannot create file : %v", err) + } + defer file.Close() + + err = os.Chmod(filename, 000) + if err != nil { + t.Errorf("error: cannot change file permissions: %v", err) + } + credStore, err := NewStore(filename) + if credStore != nil { + t.Errorf("Expected NewStore to return nil but actually returned %v ", credStore) + } + if err != nil { + ok := strings.Contains(err.Error(), "failed to open config file") + reflect.DeepEqual(ok, true) + + } else { + t.Errorf("Expected err to be not nil") + } +} diff --git a/internal/graph/graph.go b/internal/graph/graph.go index bcefdd43d..4269d36df 100644 --- a/internal/graph/graph.go +++ b/internal/graph/graph.go @@ -25,6 +25,34 @@ import ( "oras.land/oras/internal/docker" ) +// MediaTypeArtifactManifest specifies the media type for a content descriptor. +const MediaTypeArtifactManifest = "application/vnd.oci.artifact.manifest.v1+json" + +// Artifact describes an artifact manifest. +// This structure provides `application/vnd.oci.artifact.manifest.v1+json` mediatype when marshalled to JSON. +// +// This manifest type was introduced in image-spec v1.1.0-rc1 and was removed in +// image-spec v1.1.0-rc3. It is not part of the current image-spec and is kept +// here for Go compatibility. +// +// Reference: https://github.com/opencontainers/image-spec/pull/999 +type Artifact struct { + // MediaType is the media type of the object this schema refers to. + MediaType string `json:"mediaType"` + + // ArtifactType is the IANA media type of the artifact this schema refers to. + ArtifactType string `json:"artifactType"` + + // Blobs is a collection of blobs referenced by this manifest. + Blobs []ocispec.Descriptor `json:"blobs,omitempty"` + + // Subject (reference) is an optional link from the artifact to another manifest forming an association between the artifact and the other manifest. + Subject *ocispec.Descriptor `json:"subject,omitempty"` + + // Annotations contains arbitrary metadata for the artifact manifest. + Annotations map[string]string `json:"annotations,omitempty"` +} + // Successors returns the nodes directly pointed by the current node, picking // out subject and config descriptor if applicable. // Returning nil when no subject and config found. @@ -43,18 +71,30 @@ func Successors(ctx context.Context, fetcher content.Fetcher, node ocispec.Descr nodes = manifest.Layers subject = manifest.Subject config = &manifest.Config - case ocispec.MediaTypeArtifactManifest: + case MediaTypeArtifactManifest: var fetched []byte fetched, err = content.FetchAll(ctx, fetcher, node) if err != nil { return } - var manifest ocispec.Artifact + var manifest Artifact if err = json.Unmarshal(fetched, &manifest); err != nil { return } nodes = manifest.Blobs subject = manifest.Subject + case ocispec.MediaTypeImageIndex: + var fetched []byte + fetched, err = content.FetchAll(ctx, fetcher, node) + if err != nil { + return + } + var index ocispec.Index + if err = json.Unmarshal(fetched, &index); err != nil { + return + } + nodes = index.Manifests + subject = index.Subject default: nodes, err = content.Successors(ctx, fetcher, node) } @@ -83,12 +123,12 @@ func Referrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc oc } for _, node := range predecessors { switch node.MediaType { - case ocispec.MediaTypeArtifactManifest: + case MediaTypeArtifactManifest: fetched, err := fetchBytes(ctx, target, node) if err != nil { return nil, err } - var artifact ocispec.Artifact + var artifact Artifact if err := json.Unmarshal(fetched, &artifact); err != nil { return nil, err } @@ -109,39 +149,31 @@ func Referrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc oc if image.Subject == nil || !content.Equal(*image.Subject, desc) { continue } - node.ArtifactType = image.Config.MediaType + node.ArtifactType = image.ArtifactType + if node.ArtifactType == "" { + node.ArtifactType = image.Config.MediaType + } node.Annotations = image.Annotations + case ocispec.MediaTypeImageIndex: + fetched, err := fetchBytes(ctx, target, node) + if err != nil { + return nil, err + } + var index ocispec.Index + if err := json.Unmarshal(fetched, &index); err != nil { + return nil, err + } + if index.Subject == nil || !content.Equal(*index.Subject, desc) { + continue + } + node.ArtifactType = index.ArtifactType + node.Annotations = index.Annotations default: continue } - if node.ArtifactType != "" && (artifactType == "" || artifactType == node.ArtifactType) { - results = append(results, node) - } - } - return results, nil -} - -// FindReferrerPredecessors returns referrer nodes of desc in target. -func FindReferrerPredecessors(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { - var results []ocispec.Descriptor - if repo, ok := src.(registry.ReferrerLister); ok { - // get referrers directly - err := repo.Referrers(ctx, desc, "", func(referrers []ocispec.Descriptor) error { - results = append(results, referrers...) - return nil - }) - if err != nil { - return nil, err - } - return results, nil - } - predecessors, err := src.Predecessors(ctx, desc) - if err != nil { - return nil, err - } - for _, node := range predecessors { - switch node.MediaType { - case ocispec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest: + if artifactType == "" || artifactType == node.ArtifactType { + // the field artifactType in referrers descriptor is allowed to be empty + // https://github.com/opencontainers/distribution-spec/issues/458 results = append(results, node) } } diff --git a/internal/graph/graph_test.go b/internal/graph/graph_test.go index 50eb0e8e0..d33c4ad8c 100644 --- a/internal/graph/graph_test.go +++ b/internal/graph/graph_test.go @@ -39,14 +39,6 @@ func (e *errLister) Referrers(ctx context.Context, desc ocispec.Descriptor, arti return errors.New("") } -type errFinder struct { - oras.ReadOnlyGraphTarget -} - -func (e *errFinder) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) { - return nil, errors.New("") -} - type refLister struct { referrers []ocispec.Descriptor oras.ReadOnlyGraphTarget @@ -89,19 +81,6 @@ func TestReferrers(t *testing.T) { } appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) } - generateArtifact := func(artifactType string, subject *ocispec.Descriptor, annotations map[string]string, blobs ...ocispec.Descriptor) { - manifest := ocispec.Artifact{ - Subject: subject, - Blobs: blobs, - Annotations: annotations, - ArtifactType: artifactType, - } - manifestJSON, err := json.Marshal(manifest) - if err != nil { - t.Fatal(err) - } - appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON) - } generateIndex := func(manifests ...ocispec.Descriptor) { index := ocispec.Index{ Manifests: manifests, @@ -117,11 +96,10 @@ func TestReferrers(t *testing.T) { imgConfig subject image - artifact index ) anno := map[string]string{"test": "foo"} - appendBlob(ocispec.MediaTypeArtifactManifest, []byte("subject content")) + appendBlob(ocispec.MediaTypeImageLayer, []byte("blob")) imageType := "test.image" appendBlob(imageType, []byte("config content")) generateImage(nil, nil, descs[imgConfig], descs[blob]) @@ -129,17 +107,14 @@ func TestReferrers(t *testing.T) { imageDesc := descs[image] imageDesc.Annotations = anno imageDesc.ArtifactType = imageType - artifactType := "test.artifact" - generateArtifact(artifactType, &descs[subject], anno, descs[blob]) generateIndex(descs[subject]) - artifactDesc := descs[artifact] - artifactDesc.Annotations = anno - artifactDesc.ArtifactType = artifactType referrers := []ocispec.Descriptor{descs[image], descs[image]} memory := memory.New() for i := range descs { - memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])) + if err := memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil { + t.Errorf("Error pushing %v\n", err) + } } finder := &predecessorFinder{Store: memory} @@ -161,7 +136,6 @@ func TestReferrers(t *testing.T) { {"should return nil for config node", args{ctx, finder, descs[imgConfig], ""}, nil, false}, {"should return nil for blob/layer node", args{ctx, finder, descs[blob], ""}, nil, false}, {"should find filtered image referrer", args{ctx, finder, descs[subject], imageType}, []ocispec.Descriptor{imageDesc}, false}, - {"should find filtered artifact referrer", args{ctx, finder, descs[subject], artifactType}, []ocispec.Descriptor{artifactDesc}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -177,15 +151,14 @@ func TestReferrers(t *testing.T) { } t.Run("should find referrers in predecessors", func(t *testing.T) { - want1 := []ocispec.Descriptor{artifactDesc, imageDesc} - want2 := []ocispec.Descriptor{imageDesc, artifactDesc} + want := []ocispec.Descriptor{imageDesc} got, err := Referrers(ctx, finder, descs[subject], "") if err != nil { t.Errorf("Referrers() error = %v", err) return } - if !reflect.DeepEqual(got, want1) && !reflect.DeepEqual(got, want2) { - t.Errorf("Referrers() = %v, want %v", got, want1) + if !reflect.DeepEqual(got, want) { + t.Errorf("Referrers() = %v, want %v", got, want) } }) } @@ -214,18 +187,6 @@ func TestSuccessors(t *testing.T) { } appendBlob(mediaType, manifestJSON) } - generateArtifact := func(artifactType string, subject *ocispec.Descriptor, blobs ...ocispec.Descriptor) { - manifest := ocispec.Artifact{ - MediaType: ocispec.MediaTypeArtifactManifest, - Subject: subject, - Blobs: blobs, - } - manifestJSON, err := json.Marshal(manifest) - if err != nil { - t.Fatal(err) - } - appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON) - } generateIndex := func(manifests ...ocispec.Descriptor) { index := ocispec.Index{ Manifests: manifests, @@ -241,21 +202,20 @@ func TestSuccessors(t *testing.T) { config ociImage dockerImage - artifact index ) - appendBlob(ocispec.MediaTypeArtifactManifest, []byte("subject content")) + appendBlob(ocispec.MediaTypeImageLayer, []byte("blob")) imageType := "test.image" appendBlob(imageType, []byte("config content")) generateImage(&descs[subject], ocispec.MediaTypeImageManifest, descs[config]) generateImage(&descs[subject], docker.MediaTypeManifest, descs[config]) - artifactType := "test.artifact" - generateArtifact(artifactType, &descs[subject]) generateIndex(descs[subject]) memory := memory.New() ctx := context.Background() for i := range descs { - memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])) + if err := memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil { + t.Errorf("Error pushing %v\n", err) + } } fetcher := &fetcher{Fetcher: memory} @@ -272,12 +232,10 @@ func TestSuccessors(t *testing.T) { wantConfig *ocispec.Descriptor wantErr bool }{ - {"should failed to get non-existent artifact", args{ctx, fetcher, ocispec.Descriptor{MediaType: ocispec.MediaTypeArtifactManifest}}, nil, nil, nil, true}, {"should failed to get non-existent OCI image", args{ctx, fetcher, ocispec.Descriptor{MediaType: ocispec.MediaTypeImageManifest}}, nil, nil, nil, true}, {"should failed to get non-existent docker image", args{ctx, fetcher, ocispec.Descriptor{MediaType: docker.MediaTypeManifest}}, nil, nil, nil, true}, {"should get success of a docker image", args{ctx, fetcher, descs[dockerImage]}, nil, &descs[subject], &descs[config], false}, {"should get success of an OCI image", args{ctx, fetcher, descs[ociImage]}, nil, &descs[subject], &descs[config], false}, - {"should get success of an artifact", args{ctx, fetcher, descs[artifact]}, nil, &descs[subject], nil, false}, {"should get success of an index", args{ctx, fetcher, descs[index]}, []ocispec.Descriptor{descs[subject]}, nil, nil, false}, } for _, tt := range tests { @@ -299,91 +257,3 @@ func TestSuccessors(t *testing.T) { }) } } - -func TestFindReferrerPredecessors(t *testing.T) { - ctx := context.Background() - var blobs [][]byte - var descs []ocispec.Descriptor - appendBlob := func(mediaType string, blob []byte) { - blobs = append(blobs, blob) - descs = append(descs, ocispec.Descriptor{ - MediaType: mediaType, - Digest: digest.FromBytes(blob), - Size: int64(len(blob)), - }) - } - generateImage := func(subject *ocispec.Descriptor, annotations map[string]string, config ocispec.Descriptor, layers ...ocispec.Descriptor) { - manifest := ocispec.Manifest{ - Subject: subject, - Config: config, - Layers: layers, - Annotations: annotations, - } - manifestJSON, err := json.Marshal(manifest) - if err != nil { - t.Fatal(err) - } - appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) - } - generateIndex := func(manifests ...ocispec.Descriptor) { - index := ocispec.Index{ - Manifests: manifests, - } - manifestJSON, err := json.Marshal(index) - if err != nil { - t.Fatal(err) - } - appendBlob(ocispec.MediaTypeImageIndex, manifestJSON) - } - const ( - subject = iota - imgConfig - image - ) - var anno map[string]string - appendBlob(ocispec.MediaTypeArtifactManifest, []byte("subject content")) - imageType := "test.image" - appendBlob(imageType, []byte("config content")) - generateImage(&descs[subject], anno, descs[imgConfig]) - imageDesc := descs[image] - imageDesc.Annotations = anno - imageDesc.ArtifactType = imageType - generateIndex(descs[subject]) - - referrers := []ocispec.Descriptor{descs[image], descs[image]} - memory := memory.New() - for i := range descs { - memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])) - } - finder := &predecessorFinder{Store: memory} - type args struct { - ctx context.Context - src content.ReadOnlyGraphStorage - desc ocispec.Descriptor - } - tests := []struct { - name string - args args - want []ocispec.Descriptor - wantErr bool - }{ - - {"should failed to get referrers", args{ctx, &errLister{}, ocispec.Descriptor{}}, nil, true}, - {"should failed to get predecessor", args{ctx, &errFinder{}, ocispec.Descriptor{}}, nil, true}, - {"should return referrers when target is a referrer lister", args{ctx, &refLister{referrers: referrers}, ocispec.Descriptor{}}, referrers, false}, - {"should return image for config node", args{ctx, finder, descs[imgConfig]}, []ocispec.Descriptor{descs[image]}, false}, - {"should return image for subject node", args{ctx, finder, descs[subject]}, []ocispec.Descriptor{descs[image]}, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := FindReferrerPredecessors(tt.args.ctx, tt.args.src, tt.args.desc) - if (err != nil) != tt.wantErr { - t.Errorf("FindReferrerPredecessors() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("FindReferrerPredecessors() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/snapcraft.yaml b/snapcraft.yaml index 6fb0ab02d..4a3f47b58 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -42,7 +42,7 @@ parts: - on amd64 to s390x: - TARGET_ARCH: "s390x" build-snaps: - - go/1.20/stable + - go/1.21/stable build-packages: - make stage-packages: diff --git a/test/e2e/README.md b/test/e2e/README.md index f3fa24672..26e7332a8 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -30,7 +30,7 @@ go test oras.land/oras/test/e2e/suite/${suite_name} 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. ### 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), with deletion disabled. +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). You can run scenario test suite against your own registry services via setting `ORAS_REGISTRY_HOST` or `ORAS_REGISTRY_FALLBACK_HOST` environmental variables. diff --git a/test/e2e/go.mod b/test/e2e/go.mod index ef6a4cc4f..20fca6995 100644 --- a/test/e2e/go.mod +++ b/test/e2e/go.mod @@ -1,14 +1,14 @@ module oras.land/oras/test/e2e -go 1.20 +go 1.21 require ( - github.com/onsi/ginkgo/v2 v2.11.0 - github.com/onsi/gomega v1.27.8 + github.com/onsi/ginkgo/v2 v2.12.0 + github.com/onsi/gomega v1.27.10 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.1.0-rc2 + github.com/opencontainers/image-spec v1.1.0-rc4 gopkg.in/yaml.v2 v2.4.0 - oras.land/oras-go/v2 v2.2.1-0.20230627113607-6b5bd4b4372b + oras.land/oras-go/v2 v2.2.1-0.20230822062013-e6d40b6ff29f ) require ( @@ -16,10 +16,10 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.14.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/tools v0.9.3 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.12.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/test/e2e/go.sum b/test/e2e/go.sum index c0cd2933f..52dde5267 100644 --- a/test/e2e/go.sum +++ b/test/e2e/go.sum @@ -14,31 +14,31 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= 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= +github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= +github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 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= @@ -47,5 +47,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.2.1-0.20230627113607-6b5bd4b4372b h1:NYynybpqtG3lLTZMWNlrvUlcyGakCke57tg4TX6w2kA= -oras.land/oras-go/v2 v2.2.1-0.20230627113607-6b5bd4b4372b/go.mod h1:goptA58HogB/6sLN7KV6FgoiurcVxd5QBqv8wMVB0as= +oras.land/oras-go/v2 v2.2.1-0.20230822062013-e6d40b6ff29f h1:2Lx/b6oqh6vZST55SxNYNQJSZmOQZDQckdw34tI2bc4= +oras.land/oras-go/v2 v2.2.1-0.20230822062013-e6d40b6ff29f/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= diff --git a/test/e2e/go.work b/test/e2e/go.work index 602d10f96..01071242e 100644 --- a/test/e2e/go.work +++ b/test/e2e/go.work @@ -1,4 +1,4 @@ -go 1.20 +go 1.21 use ( . diff --git a/test/e2e/internal/testdata/foobar/const.go b/test/e2e/internal/testdata/foobar/const.go index 66a0591f8..0ea5471f0 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" @@ -26,8 +24,6 @@ import ( var ( Tag = "foobar" Digest = "sha256:fd6ed2f36b5465244d5dc86cb4e7df0ab8a9d24adc57825099f522fe009a22bb" - Size = 851 - DescriptorStr = fmt.Sprintf(`{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"%s","size":%d}`, Digest, Size) ManifestStateKey = match.StateKey{Digest: "fd6ed2f36b54", Name: "application/vnd.oci.image.manifest.v1+json"} FileLayerNames = []string{ diff --git a/test/e2e/scripts/common.sh b/test/e2e/scripts/common.sh index e1d40f919..7760a6578 100755 --- a/test/e2e/scripts/common.sh +++ b/test/e2e/scripts/common.sh @@ -15,14 +15,13 @@ help () { echo "Usage" - echo " run-registry " + 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." - echo " delete-enabled if set to true, the registry service will be configured to allow deletion." } # run registry service for testing @@ -62,7 +61,7 @@ run_registry () { 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=$5 \ + --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 \ diff --git a/test/e2e/scripts/e2e.sh b/test/e2e/scripts/e2e.sh index 08d555a77..b110e38c6 100755 --- a/test/e2e/scripts/e2e.sh +++ b/test/e2e/scripts/e2e.sh @@ -49,16 +49,14 @@ run_registry \ ${e2e_root}/testdata/distribution/mount \ ghcr.io/oras-project/registry:v1.0.0-rc.4 \ $oras_container_name \ - $ORAS_REGISTRY_PORT \ - true + $ORAS_REGISTRY_PORT echo " === preparing upstream distribution === " run_registry \ ${e2e_root}/testdata/distribution/mount_fallback \ registry:2.8.1 \ $upstream_container_name \ - $ORAS_REGISTRY_FALLBACK_PORT \ - false + $ORAS_REGISTRY_FALLBACK_PORT echo " === run tests === " if ! ginkgo -r -p --succinct suite; then diff --git a/test/e2e/suite/command/attach.go b/test/e2e/suite/command/attach.go index 499a613da..b0e5fba68 100644 --- a/test/e2e/suite/command/attach.go +++ b/test/e2e/suite/command/attach.go @@ -43,7 +43,6 @@ var _ = Describe("ORAS beginners:", func() { It("should show preview and help doc", func() { out := ORAS("attach", "--help").MatchKeyWords(feature.Preview.Mark+" Attach", feature.Preview.Description, ExampleDesc).Exec() gomega.Expect(out).Should(gbytes.Say("--distribution-spec string\\s+%s", regexp.QuoteMeta(feature.Preview.Mark))) - gomega.Expect(out).Should(gbytes.Say("--image-spec string\\s+%s", regexp.QuoteMeta(feature.Experimental.Mark))) }) It("should fail when no subject reference provided", func() { @@ -65,11 +64,6 @@ var _ = Describe("ORAS beginners:", func() { ORAS("attach", "--artifact-type", "oras.test", RegistryRef(Host, ImageRepo, foobar.Tag), "--distribution-spec", "???"). ExpectFailure().MatchErrKeyWords("unknown distribution specification flag").Exec() }) - - It("should fail if image spec is unkown", func() { - ORAS("attach", "--artifact-type", "oras.test", RegistryRef(Host, ImageRepo, foobar.Tag), "--image-spec", "???"). - ExpectFailure().MatchErrKeyWords("unknown image specification flag").Exec() - }) }) }) @@ -111,10 +105,9 @@ var _ = Describe("Common registry users:", func() { subjectRef := RegistryRef(Host, testRepo, foobar.Tag) prepare(RegistryRef(Host, ImageRepo, foobar.Tag), subjectRef) // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). WithWorkDir(tempDir). MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - // validate var index ocispec.Index bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() @@ -148,24 +141,6 @@ var _ = Describe("Common registry users:", func() { ExpectFailure(). Exec() }) - - It("should attach a file via a OCI Artifact", func() { - testRepo := attachTestRepo("artifact") - tempDir := PrepareTempFiles() - subjectRef := RegistryRef(Host, testRepo, foobar.Tag) - prepare(RegistryRef(Host, ImageRepo, foobar.Tag), subjectRef) - // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-artifact"). - WithWorkDir(tempDir). - MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - - // validate - var index ocispec.Index - bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() - Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) - Expect(len(index.Manifests)).To(Equal(1)) - Expect(index.Manifests[0].MediaType).To(Equal("application/vnd.oci.artifact.manifest.v1+json")) - }) }) }) @@ -177,7 +152,7 @@ var _ = Describe("Fallback registry users:", func() { subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) prepare(RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). WithWorkDir(tempDir). MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() @@ -207,60 +182,13 @@ var _ = Describe("Fallback registry users:", func() { Expect(index.Manifests[0].MediaType).To(Equal("application/vnd.oci.image.manifest.v1+json")) }) - It("should fail to attach again when cleaning referrers index", func() { - testRepo := attachTestRepo("fallback/fail-gc") - tempDir := PrepareTempFiles() - subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) - prepare(RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) - // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). - WithWorkDir(tempDir). - MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - - // validate - var index ocispec.Index - bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() - Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) - Expect(len(index.Manifests)).To(Equal(1)) - Expect(index.Manifests[0].MediaType).To(Equal(ocispec.MediaTypeImageManifest)) - // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "-a", "test.type=another.image"). - WithWorkDir(tempDir). - MatchErrKeyWords("Error: failed to delete dangling referrers index"). - ExpectFailure().Exec() - }) - - It("should attach again and skip cleanning index", func() { - testRepo := attachTestRepo("fallback/skip-gc") - tempDir := PrepareTempFiles() - subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) - prepare(RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) - // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). - WithWorkDir(tempDir). - MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - // validate - var index ocispec.Index - bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() - Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) - Expect(len(index.Manifests)).To(Equal(1)) - // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "-a", "test.type=another.image", "--skip-delete-referrers"). - WithWorkDir(tempDir). - MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - // validate - bytes = ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() - Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) - Expect(len(index.Manifests)).To(Equal(2)) - }) - It("should attach a file via a OCI Image and generate referrer via tag schema", func() { testRepo := attachTestRepo("fallback/tag_schema") tempDir := PrepareTempFiles() subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) prepare(RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image", "--distribution-spec", "v1.1-referrers-tag"). + ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--distribution-spec", "v1.1-referrers-tag"). WithWorkDir(tempDir). MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() @@ -279,7 +207,6 @@ var _ = Describe("OCI image layout users:", func() { prepare := func(root string) { ORAS("cp", RegistryRef(Host, ImageRepo, foobar.Tag), Flags.ToLayout, LayoutRef(root, foobar.Tag)).Exec() } - It("should attach a file to a subject", func() { root := PrepareTempFiles() subjectRef := LayoutRef(root, foobar.Tag) @@ -289,16 +216,6 @@ var _ = Describe("OCI image layout users:", func() { MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() }) - It("should attach and output warning for referrers deletion by default", func() { - root := PrepareTempFiles() - subjectRef := LayoutRef(root, foobar.Tag) - prepare(root) - ORAS("attach", "--artifact-type", "test.attach", "-v", Flags.Layout, subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). - MatchErrKeyWords("referrers deletion can only be enforced upon registry\n"). - WithWorkDir(root). - Exec() - }) - It("should attach a file to a subject and export the built manifest", func() { // prepare root := PrepareTempFiles() @@ -323,7 +240,7 @@ var _ = Describe("OCI image layout users:", func() { subjectRef := LayoutRef(root, foobar.Tag) prepare(root) // test - ORAS("attach", "--artifact-type", "test.attach", Flags.Layout, subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-image"). + ORAS("attach", "--artifact-type", "test.attach", Flags.Layout, subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). WithWorkDir(root). MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() @@ -334,21 +251,5 @@ var _ = Describe("OCI image layout users:", func() { Expect(len(index.Manifests)).To(Equal(1)) Expect(index.Manifests[0].MediaType).To(Equal("application/vnd.oci.image.manifest.v1+json")) }) - It("should attach a file via a OCI Artifact", func() { - root := PrepareTempFiles() - subjectRef := LayoutRef(root, foobar.Tag) - prepare(root) - // test - ORAS("attach", "--artifact-type", "test.attach", subjectRef, Flags.Layout, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--image-spec", "v1.1-artifact"). - WithWorkDir(root). - MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - - // validate - var index ocispec.Index - bytes := ORAS("discover", subjectRef, Flags.Layout, "-o", "json").Exec().Out.Contents() - Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) - Expect(len(index.Manifests)).To(Equal(1)) - Expect(index.Manifests[0].MediaType).To(Equal("application/vnd.oci.artifact.manifest.v1+json")) - }) }) }) diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index 5865ba0cb..4b49187fa 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -31,7 +31,6 @@ import ( "oras.land/oras/test/e2e/internal/testdata/foobar" ma "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" - "oras.land/oras/test/e2e/internal/utils/match" ) func cpTestRepo(text string) string { @@ -246,34 +245,6 @@ var _ = Describe("Common registry users:", func() { var _ = Describe("OCI spec 1.0 registry users:", func() { When("running `cp`", func() { - It("should fail to copy when cleaning referrers index", func() { - testRepo := cpTestRepo("fallback/fail-gc") - tempDir := PrepareTempFiles() - subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) - // prepare - prepare(RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). - WithWorkDir(tempDir). - MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - // test - ORAS("cp", "-r", RegistryRef(FallbackHost, ArtifactRepo, foobar.FallbackSBOMImageReferrer.Digest.String()), RegistryRef(FallbackHost, testRepo, ""), "-v"). - MatchErrKeyWords("Error: failed to delete dangling referrers index"). - ExpectFailure().Exec() - }) - - It("should copy and skip cleaning referrers index", func() { - testRepo := cpTestRepo("fallback/skip-gc") - tempDir := PrepareTempFiles() - subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) - // prepare - prepare(RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef) - ORAS("attach", "--artifact-type", "test.attach", subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia)). - WithWorkDir(tempDir). - MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() - // test - ORAS("cp", "-r", RegistryRef(FallbackHost, ArtifactRepo, foobar.FallbackSBOMImageReferrer.Digest.String()), RegistryRef(FallbackHost, testRepo, ""), "--skip-delete-referrers").Exec() - }) - 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...) @@ -284,7 +255,6 @@ var _ = Describe("OCI spec 1.0 registry users:", func() { ORAS("discover", "-o", "tree", RegistryRef(FallbackHost, repo, foobar.Digest)). WithDescription("discover referrer via subject").MatchKeyWords(foobar.SignatureImageReferrer.Digest.String(), foobar.SBOMImageReferrer.Digest.String()).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, foobar.FallbackImageReferrersStateKeys...), foobar.ImageReferrerConfigStateKeys...) @@ -363,12 +333,6 @@ var _ = Describe("OCI layout users:", func() { Expect(srcManifest).To(Equal(dstManifest)) }) - It("should copy and output verbosed warning for Feferrers deletion by default", func() { - ORAS("cp", RegistryRef(Host, ArtifactRepo, foobar.Tag), GinkgoT().TempDir(), Flags.ToLayout, "-v"). - MatchErrKeyWords("referrers deletion can only be enforced upon registry\n"). - Exec() - }) - It("should copy an image from an OCI image layout to a registry via tag", func() { layoutDir := GinkgoT().TempDir() src := LayoutRef(layoutDir, "copied") diff --git a/test/e2e/suite/command/discover.go b/test/e2e/suite/command/discover.go index c753edfce..22d717d02 100644 --- a/test/e2e/suite/command/discover.go +++ b/test/e2e/suite/command/discover.go @@ -262,7 +262,7 @@ var _ = Describe("OCI image layout users:", func() { When("running discover command with table output", func() { format := "table" - It("should direct referrers of a subject", func() { + It("should get direct referrers of a subject", func() { referrers := []ocispec.Descriptor{foobar.SBOMImageReferrer, foobar.SBOMArtifactReferrer} // prepare subjectRef := LayoutRef(GinkgoT().TempDir(), foobar.Tag) diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index 36d30bf0c..109e4ecc3 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -518,7 +518,9 @@ var _ = Describe("OCI image layout users:", func() { ORAS("manifest", "fetch-config", Flags.Layout, root).ExpectFailure().MatchErrKeyWords("Error:", "invalid image reference").Exec() }) }) +}) +var _ = Describe("OCI image layout users:", func() { When("running `manifest push`", func() { scratchSize := 2 scratchDigest := "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" @@ -545,15 +547,6 @@ var _ = Describe("OCI image layout users:", func() { } descriptor := "{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:f20c43161d73848408ef247f0ec7111b19fe58ffebc0cbcaa0d2c8bda4967268\",\"size\":246}" - It("should push and output verbosed warning for Feferrers deletion by default", func() { - manifestPath := WriteTempFile("manifest.json", manifest) - root := filepath.Dir(manifestPath) - prepare(root) - ORAS("manifest", "push", root, Flags.Layout, manifestPath, "--skip-delete-referrers=false"). - WithWorkDir(root). - MatchErrKeyWords("referrers deletion can only be enforced upon registry\n").Exec() - }) - It("should push a manifest from stdin", func() { root := GinkgoT().TempDir() prepare(root) @@ -562,7 +555,6 @@ var _ = Describe("OCI image layout users:", func() { WithInput(strings.NewReader(manifest)).Exec() validate(root, manifestDigest, "") }) - It("should push a manifest from stdin and tag", func() { tag := "from-stdin" root := GinkgoT().TempDir() @@ -614,40 +606,3 @@ var _ = Describe("OCI image layout users:", func() { }) }) }) - -func pushTestRepo(text string) string { - return fmt.Sprintf("command/push/%d/%s", GinkgoRandomSeed(), text) -} - -var _ = Describe("Fallback registry users:", func() { - When("running `manifest push`", func() { - manifest := fmt.Sprintf(`{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"oras.test","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"subject":%s,"layers":[]}`, foobar.DescriptorStr) - It("should fail to push manifest when cleaning referrers index", func() { - testRepo := pushTestRepo("fallback/fail-gc") - subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) - // prepare - ORAS("cp", RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef, "-r").Exec() - // test - ORAS("manifest", "push", RegistryRef(FallbackHost, testRepo, ""), "-"). - WithInput(strings.NewReader(manifest)). - ExpectFailure(). - MatchErrKeyWords("failed to delete dangling referrers index"). - Exec() - }) - It("should push manifest and skip cleaning referrers index", func() { - testRepo := pushTestRepo("fallback/skip-gc") - subjectRef := RegistryRef(FallbackHost, testRepo, foobar.Tag) - // prepare - ORAS("cp", RegistryRef(FallbackHost, ArtifactRepo, foobar.Tag), subjectRef, "-r").Exec() - // test - ORAS("manifest", "push", RegistryRef(FallbackHost, testRepo, ""), "-", "--skip-delete-referrers"). - WithInput(strings.NewReader(manifest)). - Exec() - // validate - var index ocispec.Index - bytes := ORAS("discover", subjectRef, "-o", "json").Exec().Out.Contents() - Expect(json.Unmarshal(bytes, &index)).ShouldNot(HaveOccurred()) - Expect(len(index.Manifests)).To(Equal(2)) - }) - }) -}) diff --git a/test/e2e/suite/command/push.go b/test/e2e/suite/command/push.go index 17139fed4..0fe788a79 100644 --- a/test/e2e/suite/command/push.go +++ b/test/e2e/suite/command/push.go @@ -47,7 +47,7 @@ var _ = Describe("Remote registry users:", func() { When("pushing to registy without OCI artifact support", func() { repoPrefix := fmt.Sprintf("command/push/%d", GinkgoRandomSeed()) statusKeys := []match.StateKey{ - foobar.ImageConfigStateKey("application/vnd.unknown.config.v1+json"), + foobar.ImageConfigStateKey("application/vnd.oci.empty.v1+json"), foobar.FileBarStateKey, } It("should push files without customized media types", func() { @@ -230,7 +230,7 @@ var _ = Describe("OCI image layout users:", func() { tag := "e2e" When("pushing to registy without OCI artifact support", func() { statusKeys := []match.StateKey{ - foobar.ImageConfigStateKey("application/vnd.unknown.config.v1+json"), + foobar.ImageConfigStateKey("application/vnd.oci.empty.v1+json"), foobar.FileBarStateKey, } diff --git a/test/e2e/suite/command/tag.go b/test/e2e/suite/command/tag.go index 0878a7d7f..d5836ee26 100644 --- a/test/e2e/suite/command/tag.go +++ b/test/e2e/suite/command/tag.go @@ -33,7 +33,11 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail when provided manifest reference is not found", func() { - ORAS("tag", RegistryRef(Host, ImageRepo, "i-dont-think-this-tag-exists")).ExpectFailure().MatchErrKeyWords("Error:").Exec() + ORAS("tag", RegistryRef(Host, ImageRepo, "i-dont-think-this-tag-exists"), "tagged").ExpectFailure().MatchErrKeyWords("Error:").Exec() + }) + + It("should fail when provided invalid reference", func() { + ORAS("tag", "list", "tagged").ExpectFailure().MatchErrKeyWords("Error:", "'list'").Exec() }) }) }) diff --git a/test/e2e/suite/scenario/oci_artifact.go b/test/e2e/suite/scenario/oci_artifact.go index c426b7d79..bbd744e3c 100644 --- a/test/e2e/suite/scenario/oci_artifact.go +++ b/test/e2e/suite/scenario/oci_artifact.go @@ -82,10 +82,12 @@ var _ = Describe("Common OCI artifact users:", Ordered, func() { WithWorkDir(tempDir). WithDescription("attach again with manifest exported").Exec() - session = ORAS("discover", subject, "-o", "json", "--artifact-type", "test.artifact2").Exec() - digest = string(Binary("jq", "-r", ".manifests[].digest").WithInput(session.Out).Exec().Out.Contents()) - fetched = ORAS("manifest", "fetch", RegistryRef(Host, repo, digest)).MatchKeyWords(foobar.AttachFileMedia).Exec() - MatchFile(filepath.Join(tempDir, pulledManifest), string(fetched.Out.Contents()), DefaultTimeout) + // FIXME: oras distribution doesn't support OCI image with artifactType field + // Below test can be run after https://github.com/oras-project/oras/issues/1071 is done. + // session = ORAS("discover", subject, "-o", "json", "--artifact-type", "test.artifact2").Exec() + // digest = string(Binary("jq", "-r", ".manifests[].digest").WithInput(session.Out).Exec().Out.Contents()) + // fetched = ORAS("manifest", "fetch", RegistryRef(Host, repo, digest)).MatchKeyWords(foobar.AttachFileMedia).Exec() + // MatchFile(filepath.Join(tempDir, pulledManifest), string(fetched.Out.Contents()), DefaultTimeout) ORAS("pull", RegistryRef(Host, repo, string(digest)), "-v", "-o", pullRoot, "--include-subject"). MatchStatus(append(foobar.FileStateKeys, foobar.AttachFileStateKey), true, 4).