From aebe850f733186f2b4119089d7b425911663e095 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Mon, 1 Jun 2015 19:10:51 -0700 Subject: [PATCH] Catalog V2 API specification proposal This contains a proposal for a catalog API, provided access to the internal contents of a registry instance. The API endpoint is prefixed with an underscore, which is illegal in images names, to prevent collisions with repositories names. To avoid issues with large result sets, a paginated version of the API is proposed. We make an addition to the tags API to support pagination to ensure the specification is conistent. Signed-off-by: Stephen J Day --- docs/spec/api.md | 331 ++++++++++++++++++++++++++++++++- docs/spec/api.md.tmpl | 184 +++++++++++++++++- registry/api/v2/descriptors.go | 135 ++++++++++++++ registry/api/v2/routes.go | 1 + 4 files changed, 640 insertions(+), 11 deletions(-) diff --git a/docs/spec/api.md b/docs/spec/api.md index 483f15f0..ba4bc1ca 100644 --- a/docs/spec/api.md +++ b/docs/spec/api.md @@ -120,6 +120,16 @@ indicating what is different. Optionally, we may start marking parts of the specification to correspond with the versions enumerated here.
+ +
2.0.4
> +
+
    +
  • Added support for listing registry contents.
  • +
  • Added pagination to tags API.
  • +
  • Added common approach to support pagination.
  • +
+
+
2.0.3
  • Allow repository name components to be one character.
  • @@ -131,7 +141,6 @@ specification to correspond with the versions enumerated here.
  • Added section covering digest format.
  • Added more clarification that manifest cannot be deleted by tag.
  • -
    2.0.1
      @@ -745,7 +754,9 @@ each unknown blob. The response format is as follows: ] } -#### Listing Image Tags + + +### Listing Image Tags It may be necessary to list all of the tags under a given repository. The tags for an image repository can be retrieved with the following request: @@ -766,8 +777,166 @@ The response will be in the following format: } For repositories with a large number of tags, this response may be quite -large, so care should be taken by the client when parsing the response to -reduce copying. +large. If such a response is expected, one should use the pagination. + +#### Pagination + +Paginated tag results can be retrieved by adding the appropriate pagination +parameters. Starting a paginated flow may begin as follows: + +``` +GET /v2//tags/list?n= +``` + +The above specifies that a tags response should be returned, from the start of +the result set, ordered lexically, limiting the number of results to `n`. The +response to such a request would look as follows: + +``` +200 OK +Content-Type: application/json + +{ + "name": , + "tags": [ + , + ... + ] + "next": ?n=&last= +} +``` + +> __TODO(stevvooe):__ Consider using a Header here, rather than a body parameter. A +header would allow one to issue the next request before parsing the response +body. + +To get the next result set, a client would issue the request as follows, using +the value of "next" from the response body: + +``` +GET /v2//tags/list?n=&last= +``` + +The above process should then be repeated until the `next` parameter is no +longer set in the response. + +The behavior of `last` is quite simple and can be demonstrated with an +example. Let's say the repository has the following tags: + +``` +a +b +c +d +``` + +If the value of `n` is 2, _a_ and _b_ will be returned on the first response. +The `next` url within the respone will have `n` set to 2 and last set to _b_: + +``` +"next": ?n=2&last=b +``` + +The client can then issue the response, receiving the values _c_ and _d_. Note +that n may change on second to last response or be omitted fully, if the +server may so choose. + +### Listing Repositories + +Images are stored in collections, known as a _repository_, which is keyed by a +`name`, as seen throughout the API specification. A registry instance may +contain several repositories. The list of available repositories, or image +names, is made available through the _catalog_. + +The catalog for a given registry can be retrived with the following request: + +``` +GET /v2/_catalog +``` + +The response will be in the following format: + +``` +200 OK +Content-Type: application/json + +{ + "repositories": [ + , + ... + ] +} +``` + +For registries with a large number of repositories, this response may be quite +large. If such a response is expected, one should use the pagination. + +#### Pagination + +Paginated repository results can be retrieved by adding the appropriate +pagination parameters, which are similar to those available in the tag API. +Starting a paginated flow may begin as follows: + +``` +GET /v2/_catalog?n= +``` + +The above specifies that a catalog response should be returned, from the start of +the result set, ordered lexically, limiting the number of results to `n`. The +response to such a request would look as follows: + +``` +200 OK +Content-Type: application/json + +{ + "repositories": [ + , + ... + ] + "next": ?n=&last= +} +``` + +> __TODO(stevvooe):__ Consider using a Header here, rather than a body parameter. A +header would allow one to issue the next request before parsing the response +body. + +To get the next result set, a client would issue the request as follows, using +the value of "next" from the response body: + +``` +GET /v2/_catalog?n=&last= +``` + +The above process should then be repeated until the `next` parameter is no +longer set in the response. + +The result set of repository names is represented abstractly as a lexically +sorted list, where the position in that list can be specified by the query +term `last`. The entries in the response start _after_ the term specified by +`last`, up to `n` entries. + +The behavior of `last` is quite simple when demonstrated with an example. +Let's say the registry has the following repositories: + +``` +a +b +c +d +``` + +If the value of `n` is 2, _a_ and _b_ will be returned on the first response. +The `next` url within the respone will have `n` set to 2 and last set to _b_: + +``` +"next": ?n=2&last=b +``` + +The client can then issue the request with above value of `next`, receiving +the values _c_ and _d_. Note that n may change on second to last response or +be omitted fully, if the server may so choose. ### Deleting an Image @@ -817,6 +986,7 @@ A list of methods and URIs are covered in the table below: | PATCH | `/v2//blobs/uploads/` | Blob Upload | Upload a chunk of data for the specified upload. | | PUT | `/v2//blobs/uploads/` | Blob Upload | Complete the upload specified by `uuid`, optionally appending the body as the final chunk. | | DELETE | `/v2//blobs/uploads/` | Blob Upload | Cancel outstanding upload processes, releasing associated resources. If this is not called, the unfinished uploads will eventually timeout. | +| GET | `/v2/_catalog` | Catalog | Retrieve a sorted, json list of repositories available in the registry. | The detail for each endpoint is covered in the following sections. @@ -886,7 +1056,6 @@ The API implements V2 protocol and is accessible. - ###### On Failure: Unauthorized ``` @@ -1056,6 +1225,57 @@ The error codes that may be included in the response body are enumerated below: +``` +GET /v2//tags/list?n=last= +``` + +Return a portion of the tags for the specified repository. + + +The following parameters should be specified on the request: + +|Name|Kind|Description| +|----|----|-----------| +|`name`|path|Name of the target repository.| +|`n`|query|Limit the number of entries in each response. It not present, all entries will be returned.| +|`last`|query|Result set will include values lexically after last.| + + + + +###### On Success: OK + +``` +200 OK +Content-Length: +Content-Type: application/json; charset=utf-8 + +{ + "name": , + "tags": [ + , + ... + ], + "next": "?last=&n=" +} +``` + +A list of tags for the named repository. +The following fields may be returned in the response body: + +|Name|Description| +|----|-----------| +|`next`|Provides the URL to get the next set of results, if available.| + +The following headers will be returned with the response: + +|Name|Description| +|----|-----------| +|`Content-Length`|Length of the JSON response body.| + + + + ### Manifest @@ -1453,7 +1673,6 @@ The following parameters should be specified on the request: - ###### On Failure: Invalid Name or Reference ``` @@ -2907,3 +3126,103 @@ The error codes that may be included in the response body are enumerated below: +### Catalog + +List a set of available repositories in the local registry cluster. Does not provide any indication of what may be available upstream. Applications can only determine if a repository is available but not if it is not available. + + + +#### GET Catalog + +Retrieve a sorted, json list of repositories available in the registry. + + +##### Catalog Fetch Complete + +``` +GET /v2/_catalog +``` + +Request an unabridged list of repositories available. + + + + + +###### On Success: OK + +``` +200 OK +Content-Length: +Content-Type: application/json; charset=utf-8 + +{ + "repositories": [ + , + ... + ] +} +``` + +Returns the unabridged list of repositories as a json response. + +The following headers will be returned with the response: + +|Name|Description| +|----|-----------| +|`Content-Length`|Length of the JSON response body.| + + + +##### Catalog Fetch Paginated + +``` +GET /v2/_catalog?n=last= +``` + +Return the specified portion of repositories. + + +The following parameters should be specified on the request: + +|Name|Kind|Description| +|----|----|-----------| +|`n`|query|Limit the number of entries in each response. It not present, all entries will be returned.| +|`last`|query|Result set will include values lexically after last.| + + + + +###### On Success: OK + +``` +200 OK +Content-Length: +Content-Type: application/json; charset=utf-8 + +{ + "repositories": [ + , + ... + ] + "next": "?last=&n=" +} +``` + + +The following fields may be returned in the response body: + +|Name|Description| +|----|-----------| +|`next`|Provides the URL to get the next set of results, if available.| + +The following headers will be returned with the response: + +|Name|Description| +|----|-----------| +|`Content-Length`|Length of the JSON response body.| + + + + + diff --git a/docs/spec/api.md.tmpl b/docs/spec/api.md.tmpl index bc6d6f92..218f30c9 100644 --- a/docs/spec/api.md.tmpl +++ b/docs/spec/api.md.tmpl @@ -120,6 +120,16 @@ indicating what is different. Optionally, we may start marking parts of the specification to correspond with the versions enumerated here.
      + +
      2.0.4
      > +
      +
        +
      • Added support for listing registry contents.
      • +
      • Added pagination to tags API.
      • +
      • Added common approach to support pagination.
      • +
      +
      +
      2.0.3
    • Allow repository name components to be one character.
    • @@ -131,7 +141,6 @@ specification to correspond with the versions enumerated here.
    • Added section covering digest format.
    • Added more clarification that manifest cannot be deleted by tag.
    • -
      2.0.1
        @@ -745,7 +754,9 @@ each unknown blob. The response format is as follows: ] } -#### Listing Image Tags + + +### Listing Image Tags It may be necessary to list all of the tags under a given repository. The tags for an image repository can be retrieved with the following request: @@ -766,8 +777,166 @@ The response will be in the following format: } For repositories with a large number of tags, this response may be quite -large, so care should be taken by the client when parsing the response to -reduce copying. +large. If such a response is expected, one should use the pagination. + +#### Pagination + +Paginated tag results can be retrieved by adding the appropriate pagination +parameters. Starting a paginated flow may begin as follows: + +``` +GET /v2//tags/list?n= +``` + +The above specifies that a tags response should be returned, from the start of +the result set, ordered lexically, limiting the number of results to `n`. The +response to such a request would look as follows: + +``` +200 OK +Content-Type: application/json + +{ + "name": , + "tags": [ + , + ... + ] + "next": ?n=&last= +} +``` + +> __TODO(stevvooe):__ Consider using a Header here, rather than a body parameter. A +header would allow one to issue the next request before parsing the response +body. + +To get the next result set, a client would issue the request as follows, using +the value of "next" from the response body: + +``` +GET /v2//tags/list?n=&last= +``` + +The above process should then be repeated until the `next` parameter is no +longer set in the response. + +The behavior of `last` is quite simple and can be demonstrated with an +example. Let's say the repository has the following tags: + +``` +a +b +c +d +``` + +If the value of `n` is 2, _a_ and _b_ will be returned on the first response. +The `next` url within the respone will have `n` set to 2 and last set to _b_: + +``` +"next": ?n=2&last=b +``` + +The client can then issue the response, receiving the values _c_ and _d_. Note +that n may change on second to last response or be omitted fully, if the +server may so choose. + +### Listing Repositories + +Images are stored in collections, known as a _repository_, which is keyed by a +`name`, as seen throughout the API specification. A registry instance may +contain several repositories. The list of available repositories, or image +names, is made available through the _catalog_. + +The catalog for a given registry can be retrived with the following request: + +``` +GET /v2/_catalog +``` + +The response will be in the following format: + +``` +200 OK +Content-Type: application/json + +{ + "repositories": [ + , + ... + ] +} +``` + +For registries with a large number of repositories, this response may be quite +large. If such a response is expected, one should use the pagination. + +#### Pagination + +Paginated repository results can be retrieved by adding the appropriate +pagination parameters, which are similar to those available in the tag API. +Starting a paginated flow may begin as follows: + +``` +GET /v2/_catalog?n= +``` + +The above specifies that a catalog response should be returned, from the start of +the result set, ordered lexically, limiting the number of results to `n`. The +response to such a request would look as follows: + +``` +200 OK +Content-Type: application/json + +{ + "repositories": [ + , + ... + ] + "next": ?n=&last= +} +``` + +> __TODO(stevvooe):__ Consider using a Header here, rather than a body parameter. A +header would allow one to issue the next request before parsing the response +body. + +To get the next result set, a client would issue the request as follows, using +the value of "next" from the response body: + +``` +GET /v2/_catalog?n=&last= +``` + +The above process should then be repeated until the `next` parameter is no +longer set in the response. + +The result set of repository names is represented abstractly as a lexically +sorted list, where the position in that list can be specified by the query +term `last`. The entries in the response start _after_ the term specified by +`last`, up to `n` entries. + +The behavior of `last` is quite simple when demonstrated with an example. +Let's say the registry has the following repositories: + +``` +a +b +c +d +``` + +If the value of `n` is 2, _a_ and _b_ will be returned on the first response. +The `next` url within the respone will have `n` set to 2 and last set to _b_: + +``` +"next": ?n=2&last=b +``` + +The client can then issue the request with above value of `next`, receiving +the values _c_ and _d_. Note that n may change on second to last response or +be omitted fully, if the server may so choose. ### Deleting an Image @@ -867,8 +1036,13 @@ Content-Type: {{.Body.ContentType}}{{end}}{{if .Body.Format}} ``` {{.Description}} +{{if .Fields}}The following fields may be returned in the response body: -{{if .Headers}}The following headers will be returned with the response: +|Name|Description| +|----|-----------| +{{range .Fields}}|`{{.Name}}`|{{.Description}}| +{{end}}{{end}}{{if .Headers}} +The following headers will be returned with the response: |Name|Description| |----|-----------| diff --git a/registry/api/v2/descriptors.go b/registry/api/v2/descriptors.go index f2551ffe..4eec6492 100644 --- a/registry/api/v2/descriptors.go +++ b/registry/api/v2/descriptors.go @@ -87,6 +87,23 @@ var ( Format: "", } + paginationParameters = []ParameterDescriptor{ + { + Name: "n", + Type: "integer", + Description: "Limit the number of entries in each response. It not present, all entries will be returned.", + Format: "", + Required: false, + }, + { + Name: "last", + Type: "string", + Description: "Result set will include values lexically after last.", + Format: "", + Required: false, + }, + } + unauthorizedResponse = ResponseDescriptor{ Description: "The client does not have access to the repository.", StatusCode: http.StatusUnauthorized, @@ -269,6 +286,9 @@ type ResponseDescriptor struct { // Headers covers any headers that may be returned from the response. Headers []ParameterDescriptor + // Fields describes any fields that may be present in the response. + Fields []ParameterDescriptor + // ErrorCodes enumerates the error codes that may be returned along with // the response. ErrorCodes []errcode.ErrorCode @@ -427,6 +447,44 @@ var routeDescriptors = []RouteDescriptor{ }, }, }, + { + Description: "Return a portion of the tags for the specified repository.", + PathParameters: []ParameterDescriptor{nameParameterDescriptor}, + QueryParameters: paginationParameters, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Description: "A list of tags for the named repository.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Fields: []ParameterDescriptor{ + { + Name: "next", + Type: "url", + Description: "Provides the URL to get the next set of results, if available.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "name": , + "tags": [ + , + ... + ], + "next": "?last=&n=" +}`, + }, + }, + }, + }, }, }, }, @@ -1320,6 +1378,83 @@ var routeDescriptors = []RouteDescriptor{ }, }, }, + { + Name: RouteNameCatalog, + Path: "/v2/_catalog", + Entity: "Catalog", + Description: "List a set of available repositories in the local registry cluster. Does not provide any indication of what may be available upstream. Applications can only determine if a repository is available but not if it is not available.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Retrieve a sorted, json list of repositories available in the registry.", + Requests: []RequestDescriptor{ + { + Name: "Catalog Fetch Complete", + Description: "Request an unabridged list of repositories available.", + Successes: []ResponseDescriptor{ + { + Description: "Returns the unabridged list of repositories as a json response.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "repositories": [ + , + ... + ] +}`, + }, + }, + }, + }, + { + Name: "Catalog Fetch Paginated", + Description: "Return the specified portion of repositories.", + QueryParameters: paginationParameters, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "repositories": [ + , + ... + ] + "next": "?last=&n=" +}`, + }, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Fields: []ParameterDescriptor{ + { + Name: "next", + Type: "url", + Description: "Provides the URL to get the next set of results, if available.", + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + }, } var routeDescriptorsMap map[string]RouteDescriptor diff --git a/registry/api/v2/routes.go b/registry/api/v2/routes.go index 69f9d901..d18860f5 100644 --- a/registry/api/v2/routes.go +++ b/registry/api/v2/routes.go @@ -11,6 +11,7 @@ const ( RouteNameBlob = "blob" RouteNameBlobUpload = "blob-upload" RouteNameBlobUploadChunk = "blob-upload-chunk" + RouteNameCatalog = "catalog" ) var allEndpoints = []string{