From 6f0e3ea62047df68842e5f47b668f77f92ef16be Mon Sep 17 00:00:00 2001 From: Xiaoxuan Wang <103478229+wangxiaoxuan273@users.noreply.github.com> Date: Fri, 4 Nov 2022 17:41:24 +0800 Subject: [PATCH] feat!: implement the referrers route in the v2 package (#32) Implement the `referrers` route in the `registry/api/v2` package. Path with filtering on `ArtifactType` is also implemented. Unit tests are included. The next pr will implement `referrersHandler` but probably will not implement filtering yet. Part 5 of #21 Signed-off-by: wangxiaoxuan273 --- registry/api/v2/descriptors.go | 128 +++++++++++++++++++++++++++++++++ registry/api/v2/routes.go | 1 + registry/api/v2/routes_test.go | 8 +++ registry/api/v2/urls.go | 12 ++++ registry/api/v2/urls_test.go | 20 ++++++ 5 files changed, 169 insertions(+) diff --git a/registry/api/v2/descriptors.go b/registry/api/v2/descriptors.go index fce0ccda4f..6f09b26e87 100644 --- a/registry/api/v2/descriptors.go +++ b/registry/api/v2/descriptors.go @@ -1595,6 +1595,134 @@ var routeDescriptors = []RouteDescriptor{ }, }, }, + { + Name: RouteNameReferrers, + Path: "/v2/{name:" + reference.NameRegexp.String() + "}/referrers/{digest:" + digest.DigestRegexp.String() + "}", + Entity: "Referrers", + Description: `Retrieve information about referrers.`, + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Fetch the referrers of the artifact identified by `digest`.", + Requests: []RequestDescriptor{ + { + Name: "Referrers", + Description: "Request an unabridged list of referrers.", + Successes: []ResponseDescriptor{ + { + Description: "Returns an image index containing all referrers as a json response.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + linkHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/vnd.oci.image.index.v1+json", + Format: `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + , + ... + ] +}`, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The registry does not support referrers API.", + StatusCode: http.StatusNotFound, + }, + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `digest`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json", + Format: errorsBody, + }, + }, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + { + // may need to change to accommodate multiple filters applied. + // spec is not clear regarding applying multiple filters + Name: "referrers with filtering", + Description: "Request a list of referrers filtered on artifact type.", + QueryParameters: []ParameterDescriptor{ + { + Name: "artifactType", + Type: "string", + Description: "This is the artifact type to be appied on the filter.", + Format: "", + Required: false, + }, + }, + Successes: []ResponseDescriptor{ + { + Description: "Returns an image index containing all referrers as a json response with the filter applied.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + linkHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/vnd.oci.image.index.v1+json", + Format: `{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + , + ... + ], + "annotations": { + "org.opencontainers.referrers.filtersApplied": , + ... + } +}`, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The registry does not support referrers API.", + StatusCode: http.StatusNotFound, + }, + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `digest`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []errcode.ErrorCode{ + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json", + Format: errorsBody, + }, + }, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, + tooManyRequestsDescriptor, + }, + }, + }, + }, + }, + }, } var routeDescriptorsMap map[string]RouteDescriptor diff --git a/registry/api/v2/routes.go b/registry/api/v2/routes.go index 74bf15e159..aa3ddb3ce6 100644 --- a/registry/api/v2/routes.go +++ b/registry/api/v2/routes.go @@ -16,6 +16,7 @@ const ( RouteNameBlobUpload = "blob-upload" RouteNameBlobUploadChunk = "blob-upload-chunk" RouteNameCatalog = "catalog" + RouteNameReferrers = "referrers" ) var ( diff --git a/registry/api/v2/routes_test.go b/registry/api/v2/routes_test.go index 6c77e28155..92431203ec 100644 --- a/registry/api/v2/routes_test.go +++ b/registry/api/v2/routes_test.go @@ -170,6 +170,14 @@ func TestRouter(t *testing.T) { "reference": "tag", }, }, + { + RouteName: RouteNameReferrers, + RequestURI: "/v2/foo/referrers/sha256:abcdef0919234", + Vars: map[string]string{ + "name": "foo", + "digest": "sha256:abcdef0919234", + }, + }, } checkTestRouter(t, testCases, "", true) diff --git a/registry/api/v2/urls.go b/registry/api/v2/urls.go index c4fdf41536..c765f44c7a 100644 --- a/registry/api/v2/urls.go +++ b/registry/api/v2/urls.go @@ -162,6 +162,18 @@ func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) { return manifestURL.String(), nil } +// BuildReferrersURL constructs the url to fetch a list of referrers +func (ub *URLBuilder) BuildReferrersURL(ref reference.Canonical, values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameReferrers) + + referrersURL, err := route.URL("name", ref.Name(), "digest", ref.Digest().String()) + if err != nil { + return "", err + } + + return appendValuesURL(referrersURL, values...).String(), nil +} + // BuildBlobURL constructs the url for the blob identified by name and dgst. func (ub *URLBuilder) BuildBlobURL(ref reference.Canonical) (string, error) { route := ub.cloneRoute(RouteNameBlob) diff --git a/registry/api/v2/urls_test.go b/registry/api/v2/urls_test.go index 66fb4fd04f..791bb11eae 100644 --- a/registry/api/v2/urls_test.go +++ b/registry/api/v2/urls_test.go @@ -118,6 +118,26 @@ func makeURLBuilderTestCases(urlBuilder *URLBuilder) []urlBuilderTestCase { }) }, }, + { + description: "build referrers url", + expectedPath: "/v2/foo/bar/referrers/sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5", + expectedErr: nil, + build: func() (string, error) { + ref, _ := reference.WithDigest(fooBarRef, "sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5") + return urlBuilder.BuildReferrersURL(ref) + }, + }, + { + description: "build referrers url with artifact type parameter", + expectedPath: "/v2/foo/bar/referrers/sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5?artifactType=example.test.type", + expectedErr: nil, + build: func() (string, error) { + ref, _ := reference.WithDigest(fooBarRef, "sha256:3b3692957d439ac1928219a83fac91e7bf96c153725526874673ae1f2023f8d5") + return urlBuilder.BuildReferrersURL(ref, url.Values{ + "artifactType": []string{"example.test.type"}, + }) + }, + }, } }