From 66ea3dfe71ad48ea573f337af5c3f1251cacbe49 Mon Sep 17 00:00:00 2001 From: "Lixia (Sylvia) Lei" Date: Tue, 23 Jan 2024 21:23:12 +0800 Subject: [PATCH] refactor: upgrade to `distribution-spec v1.1.0-rc4` (#690) 1. If the `OCI-Subject` header is not set in the response of manifest push, do not ping the Referrers API (see `repository.go`) 2. Update tests accordingly 3. Update the spec version in comments Resolve: #685 Signed-off-by: Lixia (Sylvia) Lei --- content/oci/oci.go | 6 +- registry/reference.go | 4 +- registry/remote/errcode/errors.go | 6 +- registry/remote/example_test.go | 1 + registry/remote/referrers.go | 2 +- registry/remote/registry.go | 2 +- registry/remote/repository.go | 67 +++--- registry/remote/repository_test.go | 325 ----------------------------- registry/remote/url.go | 2 +- registry/remote/warning.go | 4 +- registry/repository.go | 6 +- 11 files changed, 52 insertions(+), 373 deletions(-) diff --git a/content/oci/oci.go b/content/oci/oci.go index b7494b4e..3a47a929 100644 --- a/content/oci/oci.go +++ b/content/oci/oci.go @@ -14,7 +14,7 @@ limitations under the License. */ // Package oci provides access to an OCI content store. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc6/image-layout.md package oci import ( @@ -43,7 +43,7 @@ import ( // Store implements `oras.Target`, and represents a content store // based on file system with the OCI-Image layout. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc6/image-layout.md type Store struct { // AutoSaveIndex controls if the OCI store will automatically save the index // file when needed. @@ -228,7 +228,7 @@ func (s *Store) delete(ctx context.Context, target ocispec.Descriptor) ([]ocispe // Tag tags a descriptor with a reference string. // reference should be a valid tag (e.g. "latest"). -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc5/image-layout.md#indexjson-file +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc6/image-layout.md#indexjson-file func (s *Store) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { s.sync.RLock() defer s.sync.RUnlock() diff --git a/registry/reference.go b/registry/reference.go index fe5eddf6..a8ad16f2 100644 --- a/registry/reference.go +++ b/registry/reference.go @@ -34,13 +34,13 @@ var ( // // References: // - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53 - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pulling-manifests + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pulling-manifests repositoryRegexp = regexp.MustCompile(`^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$`) // tagRegexp checks the tag name. // The docker and OCI spec have the same regular expression. // - // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pulling-manifests + // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pulling-manifests tagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) ) diff --git a/registry/remote/errcode/errors.go b/registry/remote/errcode/errors.go index fb192aa8..78c6557a 100644 --- a/registry/remote/errcode/errors.go +++ b/registry/remote/errcode/errors.go @@ -24,7 +24,7 @@ import ( ) // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 const ( ErrorCodeBlobUnknown = "BLOB_UNKNOWN" @@ -45,7 +45,7 @@ const ( // Error represents a response inner error returned by the remote // registry. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 type Error struct { Code string `json:"code"` @@ -73,7 +73,7 @@ func (e Error) Error() string { // Errors represents a list of response inner errors returned by the remote // server. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 type Errors []Error diff --git a/registry/remote/example_test.go b/registry/remote/example_test.go index 372ba2ac..fc5f017b 100644 --- a/registry/remote/example_test.go +++ b/registry/remote/example_test.go @@ -153,6 +153,7 @@ func TestMain(m *testing.M) { case p == fmt.Sprintf("/v2/%s/manifests/%s", exampleRepositoryName, ManifestDigest) && m == "PUT": w.WriteHeader(http.StatusCreated) case p == fmt.Sprintf("/v2/%s/manifests/%s", exampleRepositoryName, ReferenceManifestDigest) && m == "PUT": + w.Header().Set("OCI-Subject", "sha256:a3f9d449466b9b7194c3a76ca4890d792e11eb4e62e59aa8b4c3cce0a56f129d") w.WriteHeader(http.StatusCreated) case p == fmt.Sprintf("/v2/%s/manifests/%s", exampleRepositoryName, exampleSignatureManifestDescriptor.Digest) && m == "GET": w.Header().Set("Content-Type", spec.MediaTypeArtifactManifest) diff --git a/registry/remote/referrers.go b/registry/remote/referrers.go index 191db9d1..45c738a0 100644 --- a/registry/remote/referrers.go +++ b/registry/remote/referrers.go @@ -102,7 +102,7 @@ func (e *ReferrersError) IsReferrersIndexDelete() bool { // buildReferrersTag builds the referrers tag for the given manifest descriptor. // Format: - -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#unavailable-referrers-api +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#unavailable-referrers-api func buildReferrersTag(desc ocispec.Descriptor) string { alg := desc.Digest.Algorithm().String() encoded := desc.Digest.Encoded() diff --git a/registry/remote/registry.go b/registry/remote/registry.go index d1334042..9af3f3a4 100644 --- a/registry/remote/registry.go +++ b/registry/remote/registry.go @@ -94,7 +94,7 @@ func (r *Registry) do(req *http.Request) (*http.Response, error) { // // References: // - https://docs.docker.com/registry/spec/api/#base -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#api +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#api func (r *Registry) Ping(ctx context.Context) error { url := buildRegistryBaseURL(r.PlainHTTP, r.Reference) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) diff --git a/registry/remote/repository.go b/registry/remote/repository.go index d67240f2..def5f56b 100644 --- a/registry/remote/repository.go +++ b/registry/remote/repository.go @@ -53,7 +53,7 @@ const ( // // References: // - https://docs.docker.com/registry/spec/api/#digest-header - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pull + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pull headerDockerContentDigest = "Docker-Content-Digest" // headerOCIFiltersApplied is the "OCI-Filters-Applied" header. @@ -61,7 +61,7 @@ const ( // applied filters. // // Reference: - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers headerOCIFiltersApplied = "OCI-Filters-Applied" // headerOCISubject is the "OCI-Subject" header. @@ -74,7 +74,7 @@ const ( // referrers. // // References: -// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers // - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers const filterTypeArtifactType = "artifactType" @@ -118,7 +118,7 @@ type Repository struct { // ReferrerListPageSize specifies the page size when invoking the Referrers // API. // If zero, the page size is determined by the remote registry. - // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers + // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers ReferrerListPageSize int // MaxMetadataBytes specifies a limit on how many response bytes are allowed @@ -133,16 +133,16 @@ type Repository struct { // is successfully uploaded. // - If true, the old referrers index is kept. // By default, it is disabled (set to false). See also: - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#referrers-tag-schema - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#deleting-manifests + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#referrers-tag-schema + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pushing-manifests-with-subject + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#deleting-manifests SkipReferrersGC bool // HandleWarning handles the warning returned by the remote server. // Callers SHOULD deduplicate warnings from multiple associated responses. // // References: - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#warnings + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#warnings // - https://www.rfc-editor.org/rfc/rfc7234#section-5.5 HandleWarning func(warning Warning) @@ -212,9 +212,9 @@ func (r *Repository) clone() *Repository { // SetReferrersCapability returns ErrReferrersCapabilityAlreadySet if the // Referrers API capability has been already set. // - When the capability is set to true, the Referrers() function will always -// request the Referrers API. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// request the Referrers API. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers // - When the capability is set to false, the Referrers() function will always -// request the Referrers Tag. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#referrers-tag-schema +// request the Referrers Tag. Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#referrers-tag-schema // - When the capability is not set, the Referrers() function will automatically // determine which API to use. func (r *Repository) SetReferrersCapability(capable bool) error { @@ -388,7 +388,7 @@ func (r *Repository) ParseReference(reference string) (registry.Reference, error // of the Tags list. // // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#content-discovery +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#content-discovery // - https://docs.docker.com/registry/spec/api/#tags func (r *Repository) Tags(ctx context.Context, last string, fn func(tags []string) error) error { ctx = auth.AppendRepositoryScope(ctx, r.Reference, auth.ActionPull) @@ -447,7 +447,7 @@ func (r *Repository) tags(ctx context.Context, last string, fn func(tags []strin // Predecessors returns the descriptors of image or artifact manifests directly // referencing the given manifest descriptor. // Predecessors internally leverages Referrers. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers func (r *Repository) Predecessors(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { var res []ocispec.Descriptor if err := r.Referrers(ctx, desc, "", func(referrers []ocispec.Descriptor) error { @@ -466,7 +466,7 @@ func (r *Repository) Predecessors(ctx context.Context, desc ocispec.Descriptor) // If artifactType is not empty, only referrers of the same artifact type are // fed to fn. // -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers func (r *Repository) Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error { state := r.loadReferrersState() if state == referrersStateUnsupported { @@ -565,7 +565,7 @@ func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string referrers := index.Manifests if artifactType != "" { // check both filters header and filters annotations for compatibility - // latest spec for filters header: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers + // latest spec for filters header: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers // older spec for filters annotations: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers filtersHeader := resp.Header.Get(headerOCIFiltersApplied) filtersAnnotation := index.Annotations[spec.AnnotationReferrersFiltersApplied] @@ -587,7 +587,7 @@ func (r *Repository) referrersPageByAPI(ctx context.Context, artifactType string // referencing the given manifest descriptor by requesting referrers tag. // fn is called for the referrers result. If artifactType is not empty, // only referrers of the same artifact type are fed to fn. -// reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#backwards-compatibility +// reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#backwards-compatibility func (r *Repository) referrersByTagSchema(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error { referrersTag := buildReferrersTag(desc) _, referrers, err := r.referrersFromIndex(ctx, referrersTag) @@ -801,7 +801,7 @@ func (s *blobStore) Mount(ctx context.Context, desc ocispec.Descriptor, fromRepo // push it. If the caller has provided a getContent function, we // can use that, otherwise pull the content from the source repository. // - // [spec]: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#mounting-a-blob-from-another-repository + // [spec]: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#mounting-a-blob-from-another-repository var r io.ReadCloser if getContent != nil { @@ -836,7 +836,7 @@ func (s *blobStore) sibling(otherRepoName string) *blobStore { // References: // - https://docs.docker.com/registry/spec/api/#pushing-an-image // - https://docs.docker.com/registry/spec/api/#initiate-blob-upload -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-a-blob-monolithically +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pushing-a-blob-monolithically func (s *blobStore) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error { // start an upload // pushing usually requires both pull and push actions. @@ -1146,7 +1146,7 @@ func (s *manifestStore) deleteWithIndexing(ctx context.Context, target ocispec.D // on manifest delete. // // References: -// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#deleting-manifests +// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#deleting-manifests // - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#deleting-manifests func (s *manifestStore) indexReferrersForDelete(ctx context.Context, desc ocispec.Descriptor, manifestJSON []byte) error { var manifest struct { @@ -1332,13 +1332,19 @@ func (s *manifestStore) push(ctx context.Context, expected ocispec.Descriptor, c // checkOCISubjectHeader checks the "OCI-Subject" header in the response and // sets referrers capability accordingly. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pushing-manifests-with-subject func (s *manifestStore) checkOCISubjectHeader(resp *http.Response) { - // Referrers capability is not set to false when the subject header is not - // present, as the server may still conform to an older version of the spec + // If the "OCI-Subject" header is set, it indicates that the registry + // supports the Referrers API and has processed the subject of the manifest. if subjectHeader := resp.Header.Get(headerOCISubject); subjectHeader != "" { s.repo.SetReferrersCapability(true) } + + // If the "OCI-Subject" header is NOT set, it means that either the manifest + // has no subject OR the referrers API is NOT supported by the registry. + // + // Since we don't know whether the pushed manifest has a subject or not, + // we do not set the referrers capability to false at here. } // pushWithIndexing pushes the manifest content matching the expected descriptor, @@ -1363,6 +1369,8 @@ func (s *manifestStore) pushWithIndexing(ctx context.Context, expected ocispec.D } // check referrers API availability again after push if state := s.repo.loadReferrersState(); state == referrersStateSupported { + // the subject has been processed the registry, no client-side + // indexing needed return nil } return s.indexReferrersForPush(ctx, expected, manifestJSON) @@ -1375,7 +1383,7 @@ func (s *manifestStore) pushWithIndexing(ctx context.Context, expected ocispec.D // on manifest push. // // References: -// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject +// - Latest spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pushing-manifests-with-subject // - Compatible spec: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pushing-manifests-with-subject func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec.Descriptor, manifestJSON []byte) error { var subject ocispec.Descriptor @@ -1423,22 +1431,17 @@ func (s *manifestStore) indexReferrersForPush(ctx context.Context, desc ocispec. return nil } - ok, err := s.repo.pingReferrers(ctx) - if err != nil { - return err - } - if ok { - // referrers API is available, no client-side indexing needed - return nil - } + // if the manifest has a subject but the remote registry does not process it, + // it means that the Referrers API is not supported by the registry. + s.repo.SetReferrersCapability(false) return s.updateReferrersIndex(ctx, subject, referrerChange{desc, referrerOperationAdd}) } // updateReferrersIndex updates the referrers index for desc referencing subject // on manifest push and manifest delete. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pushing-manifests-with-subject -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#deleting-manifests +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#pushing-manifests-with-subject +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#deleting-manifests func (s *manifestStore) updateReferrersIndex(ctx context.Context, subject ocispec.Descriptor, change referrerChange) (err error) { referrersTag := buildReferrersTag(subject) diff --git a/registry/remote/repository_test.go b/registry/remote/repository_test.go index 583f9e81..a3dc77f9 100644 --- a/registry/remote/repository_test.go +++ b/registry/remote/repository_test.go @@ -3502,154 +3502,6 @@ func Test_ManifestStore_Push_ReferrersAPIAvailable(t *testing.T) { } } -func Test_ManifestStore_Push_ReferrersAPIAvailable_NoSubjectHeader(t *testing.T) { - // generate test content - subject := []byte(`{"layers":[]}`) - subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject) - artifact := spec.Artifact{ - MediaType: spec.MediaTypeArtifactManifest, - Subject: &subjectDesc, - } - artifactJSON, err := json.Marshal(artifact) - if err != nil { - t.Fatalf("failed to marshal manifest: %v", err) - } - artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) - manifest := ocispec.Manifest{ - MediaType: ocispec.MediaTypeImageManifest, - Subject: &subjectDesc, - } - manifestJSON, err := json.Marshal(manifest) - if err != nil { - t.Fatalf("failed to marshal manifest: %v", err) - } - manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) - index := ocispec.Index{ - MediaType: ocispec.MediaTypeImageIndex, - Subject: &subjectDesc, - } - indexJSON, err := json.Marshal(index) - if err != nil { - t.Fatalf("failed to marshal manifest: %v", err) - } - indexDesc := content.NewDescriptorFromBytes(manifest.MediaType, indexJSON) - - var gotManifest []byte - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch { - case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactDesc.Digest.String(): - if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType { - w.WriteHeader(http.StatusBadRequest) - break - } - buf := bytes.NewBuffer(nil) - if _, err := buf.ReadFrom(r.Body); err != nil { - t.Errorf("fail to read: %v", err) - } - gotManifest = buf.Bytes() - w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String()) - w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestDesc.Digest.String(): - if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType { - w.WriteHeader(http.StatusBadRequest) - break - } - buf := bytes.NewBuffer(nil) - if _, err := buf.ReadFrom(r.Body); err != nil { - t.Errorf("fail to read: %v", err) - } - gotManifest = buf.Bytes() - w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) - w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexDesc.Digest.String(): - if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType { - w.WriteHeader(http.StatusBadRequest) - break - } - buf := bytes.NewBuffer(nil) - if _, err := buf.ReadFrom(r.Body); err != nil { - t.Errorf("fail to read: %v", err) - } - gotManifest = buf.Bytes() - w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String()) - w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - result := ocispec.Index{ - Versioned: specs.Versioned{ - SchemaVersion: 2, // historical value. does not pertain to OCI or docker version - }, - MediaType: ocispec.MediaTypeImageIndex, - Manifests: []ocispec.Descriptor{}, - } - w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex) - if err := json.NewEncoder(w).Encode(result); err != nil { - t.Errorf("failed to write response: %v", err) - } - default: - t.Errorf("unexpected access: %s %s", r.Method, r.URL) - w.WriteHeader(http.StatusNotFound) - } - })) - defer ts.Close() - uri, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("invalid test http server: %v", err) - } - ctx := context.Background() - - // test pushing artifact with subject - repo, err := NewRepository(uri.Host + "/test") - if err != nil { - t.Fatalf("NewRepository() error = %v", err) - } - repo.PlainHTTP = true - if state := repo.loadReferrersState(); state != referrersStateUnknown { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) - } - err = repo.Push(ctx, artifactDesc, bytes.NewReader(artifactJSON)) - if err != nil { - t.Fatalf("Manifests.Push() error = %v", err) - } - if !bytes.Equal(gotManifest, artifactJSON) { - t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON)) - } - if state := repo.loadReferrersState(); state != referrersStateSupported { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) - } - - // test pushing image manifest with subject - repo, err = NewRepository(uri.Host + "/test") - if err != nil { - t.Fatalf("NewRepository() error = %v", err) - } - repo.PlainHTTP = true - if state := repo.loadReferrersState(); state != referrersStateUnknown { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) - } - err = repo.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON)) - if err != nil { - t.Fatalf("Manifests.Push() error = %v", err) - } - if !bytes.Equal(gotManifest, manifestJSON) { - t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON)) - } - if state := repo.loadReferrersState(); state != referrersStateSupported { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) - } - - // test pushing image index with subject - err = repo.Push(ctx, indexDesc, bytes.NewReader(indexJSON)) - if err != nil { - t.Fatalf("Manifests.Push() error = %v", err) - } - if !bytes.Equal(gotManifest, indexJSON) { - t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexJSON)) - } - if state := repo.loadReferrersState(); state != referrersStateSupported { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) - } -} - func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { // generate test content subject := []byte(`{"layers":[]}`) @@ -3700,8 +3552,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -3779,8 +3629,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(emptyIndexJSON) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -3883,8 +3731,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_1) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -3955,8 +3801,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_2) default: @@ -4039,8 +3883,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_2) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -4149,8 +3991,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable_SkipReferrersGC(t *testing. gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -4227,8 +4067,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable_SkipReferrersGC(t *testing. gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(emptyIndexJSON) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -4323,8 +4161,6 @@ func Test_ManifestStore_Push_ReferrersAPIUnavailable_SkipReferrersGC(t *testing. gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_1) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -5884,159 +5720,6 @@ func Test_ManifestStore_PushReference_ReferrersAPIAvailable(t *testing.T) { } } -func Test_ManifestStore_PushReference_ReferrersAPIAvailable_NoSubjectHeader(t *testing.T) { - // generate test content - subject := []byte(`{"layers":[]}`) - subjectDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, subject) - artifact := spec.Artifact{ - MediaType: spec.MediaTypeArtifactManifest, - Subject: &subjectDesc, - } - artifactJSON, err := json.Marshal(artifact) - if err != nil { - t.Fatalf("failed to marshal manifest: %v", err) - } - artifactDesc := content.NewDescriptorFromBytes(artifact.MediaType, artifactJSON) - artifactRef := "foo" - - manifest := ocispec.Manifest{ - MediaType: ocispec.MediaTypeImageManifest, - Subject: &subjectDesc, - } - manifestJSON, err := json.Marshal(manifest) - if err != nil { - t.Fatalf("failed to marshal manifest: %v", err) - } - manifestDesc := content.NewDescriptorFromBytes(manifest.MediaType, manifestJSON) - manifestRef := "bar" - - index := ocispec.Index{ - MediaType: ocispec.MediaTypeImageIndex, - Subject: &subjectDesc, - } - indexJSON, err := json.Marshal(index) - if err != nil { - t.Fatalf("failed to marshal manifest: %v", err) - } - indexDesc := content.NewDescriptorFromBytes(index.MediaType, indexJSON) - indexRef := "baz" - - var gotManifest []byte - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch { - case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+artifactRef: - if contentType := r.Header.Get("Content-Type"); contentType != artifactDesc.MediaType { - w.WriteHeader(http.StatusBadRequest) - break - } - buf := bytes.NewBuffer(nil) - if _, err := buf.ReadFrom(r.Body); err != nil { - t.Errorf("fail to read: %v", err) - } - gotManifest = buf.Bytes() - w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String()) - w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+manifestRef: - if contentType := r.Header.Get("Content-Type"); contentType != manifestDesc.MediaType { - w.WriteHeader(http.StatusBadRequest) - break - } - buf := bytes.NewBuffer(nil) - if _, err := buf.ReadFrom(r.Body); err != nil { - t.Errorf("fail to read: %v", err) - } - gotManifest = buf.Bytes() - w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) - w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+indexRef: - if contentType := r.Header.Get("Content-Type"); contentType != indexDesc.MediaType { - w.WriteHeader(http.StatusBadRequest) - break - } - buf := bytes.NewBuffer(nil) - if _, err := buf.ReadFrom(r.Body); err != nil { - t.Errorf("fail to read: %v", err) - } - gotManifest = buf.Bytes() - w.Header().Set("Docker-Content-Digest", indexDesc.Digest.String()) - w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - result := ocispec.Index{ - Versioned: specs.Versioned{ - SchemaVersion: 2, // historical value. does not pertain to OCI or docker version - }, - MediaType: ocispec.MediaTypeImageIndex, - Manifests: []ocispec.Descriptor{}, - } - w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex) - if err := json.NewEncoder(w).Encode(result); err != nil { - t.Errorf("failed to write response: %v", err) - } - default: - t.Errorf("unexpected access: %s %s", r.Method, r.URL) - w.WriteHeader(http.StatusNotFound) - } - })) - defer ts.Close() - uri, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("invalid test http server: %v", err) - } - ctx := context.Background() - - // test pushing artifact with subject - repo, err := NewRepository(uri.Host + "/test") - if err != nil { - t.Fatalf("NewRepository() error = %v", err) - } - repo.PlainHTTP = true - if state := repo.loadReferrersState(); state != referrersStateUnknown { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) - } - err = repo.PushReference(ctx, artifactDesc, bytes.NewReader(artifactJSON), artifactRef) - if err != nil { - t.Fatalf("Manifests.Push() error = %v", err) - } - if !bytes.Equal(gotManifest, artifactJSON) { - t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(artifactJSON)) - } - if state := repo.loadReferrersState(); state != referrersStateSupported { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) - } - - // test pushing image manifest with subject - repo, err = NewRepository(uri.Host + "/test") - if err != nil { - t.Fatalf("NewRepository() error = %v", err) - } - repo.PlainHTTP = true - if state := repo.loadReferrersState(); state != referrersStateUnknown { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateUnknown) - } - err = repo.PushReference(ctx, manifestDesc, bytes.NewReader(manifestJSON), manifestRef) - if err != nil { - t.Fatalf("Manifests.Push() error = %v", err) - } - if !bytes.Equal(gotManifest, manifestJSON) { - t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(manifestJSON)) - } - if state := repo.loadReferrersState(); state != referrersStateSupported { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) - } - - // test pushing image index with subject - err = repo.PushReference(ctx, indexDesc, bytes.NewReader(indexJSON), indexRef) - if err != nil { - t.Fatalf("Manifests.Push() error = %v", err) - } - if !bytes.Equal(gotManifest, indexJSON) { - t.Errorf("Manifests.Push() = %v, want %v", string(gotManifest), string(indexJSON)) - } - if state := repo.loadReferrersState(); state != referrersStateSupported { - t.Errorf("Repository.loadReferrersState() = %v, want %v", state, referrersStateSupported) - } -} - func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { // generate test content subject := []byte(`{"layers":[]}`) @@ -6088,8 +5771,6 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", artifactDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -6187,8 +5868,6 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_1) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: @@ -6259,8 +5938,6 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", manifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_2) default: @@ -6344,8 +6021,6 @@ func Test_ManifestStore_PushReference_ReferrersAPIUnavailable(t *testing.T) { gotManifest = buf.Bytes() w.Header().Set("Docker-Content-Digest", indexManifestDesc.Digest.String()) w.WriteHeader(http.StatusCreated) - case r.Method == http.MethodGet && r.URL.Path == "/v2/test/referrers/"+zeroDigest: - w.WriteHeader(http.StatusNotFound) case r.Method == http.MethodGet && r.URL.Path == "/v2/test/manifests/"+referrersTag: w.Write(indexJSON_2) case r.Method == http.MethodPut && r.URL.Path == "/v2/test/manifests/"+referrersTag: diff --git a/registry/remote/url.go b/registry/remote/url.go index 74258de7..903ee088 100644 --- a/registry/remote/url.go +++ b/registry/remote/url.go @@ -101,7 +101,7 @@ func buildRepositoryBlobMountURL(plainHTTP bool, ref registry.Reference, d diges // buildReferrersURL builds the URL for querying the Referrers API. // Format: :///v2//referrers/?artifactType= -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers func buildReferrersURL(plainHTTP bool, ref registry.Reference, artifactType string) string { var query string if artifactType != "" { diff --git a/registry/remote/warning.go b/registry/remote/warning.go index ff8f9c02..5eb98e78 100644 --- a/registry/remote/warning.go +++ b/registry/remote/warning.go @@ -43,7 +43,7 @@ var errUnexpectedWarningFormat = errors.New("unexpected warning format") // WarningValue represents the value of the Warning header. // // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#warnings +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#warnings // - https://www.rfc-editor.org/rfc/rfc7234#section-5.5 type WarningValue struct { // Code is the warn-code. @@ -58,7 +58,7 @@ type WarningValue struct { // other information related to the warning. // // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#warnings +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#warnings // - https://www.rfc-editor.org/rfc/rfc7234#section-5.5 type Warning struct { // WarningValue is the value of the warning header. diff --git a/registry/repository.go b/registry/repository.go index 394a9aa8..90c26905 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -87,7 +87,7 @@ type ReferenceFetcher interface { } // ReferrerLister provides the Referrers API. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers type ReferrerLister interface { Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error } @@ -109,7 +109,7 @@ type TagLister interface { // specification. // // References: - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#content-discovery + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#content-discovery // - https://docs.docker.com/registry/spec/api/#tags // See also `Tags()` in this package. Tags(ctx context.Context, last string, fn func(tags []string) error) error @@ -143,7 +143,7 @@ func Tags(ctx context.Context, repo TagLister) ([]string, error) { // Referrers lists the descriptors of image or artifact manifests directly // referencing the given manifest descriptor. // -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc4/spec.md#listing-referrers func Referrers(ctx context.Context, store content.ReadOnlyGraphStorage, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) { if !descriptor.IsManifest(desc) { return nil, fmt.Errorf("the descriptor %v is not a manifest: %w", desc, errdef.ErrUnsupported)