diff --git a/spec.md b/spec.md index 956d9e6f..3848c999 100644 --- a/spec.md +++ b/spec.md @@ -49,10 +49,11 @@ Several terms are used frequently in this document and warrant basic definitions - **Push**: the act of uploading blobs and manifests to a registry - **Pull**: the act of downloading blobs and manifests from a registry - **Blob**: the binary form of content that is stored by a registry, addressable by a digest -- **Manifest**: a JSON document which defines an artifact +- **Manifest**: a JSON document which defines an artifact. Manifests are defined under the [OCI Image Spec](https://github.com/opencontainers/image-spec/blob/master/manifest.md) - **Config**: a section in the manifest (and associated blob) which contains artifact metadata - **Artifact**: one conceptual piece of content stored as blobs with an accompanying manifest containing a config -- **Digest**: a unique identifier created from a cryptographic hash of a blob's content +- **Digest**: a unique identifier created from a cryptographic hash of a blob's content. Digests are defined under the [OCI Image Spec](https://github.com/opencontainers/image-spec/blob/b6e51fa50549ee0bd5188494912a7f4c382cb0d4/descriptor.md#digests) +- **Tag**: a custom, human-readable manifest identifier ## Conformance @@ -79,19 +80,349 @@ Registry providers can self-certify by submitting conformance results to [openco #### Pull -TODO: describe the Pull category and the high-level details +##### Pulling Blobs + +To pull a blob, perform a `GET` request to a url in the following form: +[2a](#Endpoints) `/v2//blobs/` + +`` is the namespace of the repository, and `` is the blob's digest. + +A GET request to an existing blob URL MUST provide the expected blob, with a reponse code that MUST be `200 OK`. + +If the blob is not found in the registry, the response code MUST be `404 Not Found`. + +##### Pulling manifests + +To pull a manifest, perform a `GET` request to a url in the following form: +[3a](#Endpoints) `/v2//manifests/` + +`` refers to the namespace of the repository. `` MUST be either (a) the digest of the manifest or (b) a tag name. + +The `` MUST NOT be in any other format. + +A GET request to an existing manifest URL MUST provide the expected manifest, with a response code that MUST be `200 OK`. + +If the manifest is not found in the registry, the response code MUST be `404 Not Found`. #### Push -TODO: describe the Push category and the high-level details +##### Pushing blobs + +There are two ways to push blobs: chunked or monolithic. + +##### Pushing a blob monolithically + +There are two ways to push a blob monolithically: +1. A single `POST` request +2. A `POST` request followed by a `PUT` request + +--- + +To push a blob monolithically by using a single POST request, perform a `POST` request to a URL in the following form, and with the following headers and body: + +[4b](#Endpoints) `/v2//blobs/uploads/?digest=` +``` +Content-Length: +Content-Type: application/octet-stream +``` +``` + +``` + +Here, `` is the repository's namespace, `` is the blob's digest, and `` is the size (in bytes) of the blob. + +The `Content-Length` header MUST match the blob's actual content length. Likewise, the `` MUST match the blob's digest. + +Successful completion of the request MUST return a `201 Created`, and MUST include the following header: + +``` +Location: +``` + +With `` being a pullable blob URL. + +--- + +To push a blob monolithically by using a POST request followed by a PUT request, there are two steps: +1. Obtain a session id (upload URL) +2. Upload the blob to said URL + +To obtain a session ID, perform a `POST` request to a URL in the following format: + +[4a](#Endpoints) `/v2//blobs/uploads/` + +Here, `` refers to the namespace of the repository. Upon success, the response MUST have a code of `202 Accepted`, and MUST include the following header: + +``` +Location: +``` + +The `` MUST contain a UUID representing a unique session ID for the upload to follow. + +Optionally, the location MAY be absolute (containing the protocol and/or hostname), or it MAY be relative (containing just the URL path). + +Once the `` has been obtained, perform the upload proper by making a `PUT` request to the following URL path, and with the following headers and body: + +[6a](#Endpoints) `?digest=` +``` +Content-Length: +Content-Type: aplication/octet-stream +``` +``` + +``` + +The `` MAY contain critical query parameters. Additionally, it SHOULD match exactly the `` obtained from the `POST` request. It SHOULD NOT be assembled manually by clients except where absolute/relative conversion is necessary. + +Here, `` is the digest of the blob being uploaded, and `` is its size in bytes. + +Upon successful completion of the request, the response MUST have code `201 Created` and MUST have the following header: + +``` +Location: +``` + +With `` being a pullable blob URL. + +##### Pushing a blob in chunks + +A chunked blob upload is accomplished in three phases: +1. Obtain a session ID (upload URL) (`POST`) +2. Upload the chunks (`PATCH`) +3. Close the session (`PUT`) + +For information on obtaining a session ID, reference the above section on pushing a blob monolithically via the `POST`/`PUT` method. The process remains unchanged for chunked upload, except that the post request MUST include the following header: + +``` +Content-Length: 0 +``` + +Please reference the above section for restrictions on the ``. + +--- +To upload a chunk, issue a `PATCH` request to a URL path in the following format, and with the following headers and body: + +URL path: [5a](#Endpoints) `` +``` +Content-Type: application/octet-stream +Content-Range: +Content-Length: +``` +``` + +``` + +The `` refers to the URL obtained from the preceding `POST` request. + +The `` refers to the byte range of the chunk, and MUST be inclusive on both ends. The first chunk's range MUST begin with `0`. It MUST match the following regular expression: + +```regexp +^[0-9]+-[0-9]+$ +``` + +The `` is the content-length, in bytes, of the current chunk. + +Each successful chunk upload MUST have a `202 Accepted` response code, and MUST have the following header: + +``` +Location +``` + +Each consecutive chunk upload SHOULD use the `` provided in the response to the previous chunk upload. + +Chunks MUST be uploaded in order, with the first byte of a chunk being the last chunk's `` plus one. If a chunk is uploaded out of order, the registry MUST respond with a `416 Requested Range Not Satisfiable` code. + +The final chunk MAY be uploaded using a `PATCH` request or it MAY be uploaded in the closing `PUT` request. Regardless of how the final chunk is uploaded, the session MUST be closed with a `PUT` request. + +--- + +To close the session, issue a `PUT` request to a url in the following format, and with the following headers (and optional body, depending on whether or not the final chunk was uploaded already via a `PATCH` request): + +`?digest=` +``` +Content-Length: +Content-Range: +Content-Type: application/octet-stream +``` +``` +OPTIONAL: +``` + +The closing `PUT` request MUST include the `` of the whole blob (not the final chunk) as a query parameter. + +The response to a successful closing of the session MUST be `201 Created`, and MUST contain the following header: +``` +Location: +``` + +Here, `` is a pullable blob URL. + + +##### Pushing Manifests + +To push a manifest, perform a `PUT` request to a path in the following format, and with the following headers +and body: +[7a](#Endpoints) `/v2//manifests/` +``` +Content-Type: application/vnd.oci.image.manifest.v1+json +``` +``` + +``` + +`` is the namespace of the repository, and the `` MUST be either a) a digest or b) a tag. + +The uploaded manifest MUST reference any layers that make up the artifact. However, the layers field MAY +be empty. Upon a successful upload, the registry MUST return response code `201 Created`, and MUST have the +following header: + +``` +Location: +``` + +The `` is a pullable manifest URL. + +An attempt to pull a nonexistent repository MUST return response code `404 Not Found` #### Content Discovery -TODO: describe the Content Discovery category and the high-level details +Currently, the only functionality provided by this workflow is the ability to discover tags. + +To fetch the list of tags, perform a `GET` request to a path in the following format: +[8a](#Endpoints) `/v2//tags/list` + +`` is the namespace of the repository. Assuming a repository is found, this request MUST return a +`200 OK` response code. The list of tags MAY be empty, if there are no tags on the repository. If the list is not empty, +the tags MUST be in lexical order (i.e. case-insensitive alphanumeric order). + +Upon success, the response MUST be a json body in the following format: +```json +{ + "name": "", + "tags": [ + "", + "", + "" + ] +} +``` + +`` is the namespace of the repository, and ``, ``, and `` are each tags on the repository. + +In addition to fetching the whole list of tags, a subset of the tags can be fetched by providing the `n` query parameter. +In this case, the path will look like the following: +[8b](#Endpoints) `/v2//tags/list?n=` + +`` is the namespace of the repository, and `` is an integer specifying the number of tags requested. The response +to such a request MAY return fewer than `` results, but only when the total number of tags attached to the repository +is less than ``. Otherwise, the response MUST include `` results. Without the `last` query parameter (described +next), the list returned will start at the beginning of the list and include `` results. As above, the tags MUST be +in lexical order. + +The `last` query parameter provides further means for limiting the number of tags. It is used exclusively in combination with the +`n` parameter: +[8b](#Endpoints) `/v2//tags/list?n=&last=` + +`` is the namespace of the repository, `` is the number of tags requested, and `` is the *value* of +the last tag. `` MUST NOT be a numerical index, but rather it MUST be a proper tag. A request of this sort will return +up to `` tags, beginning non-inclusively with ``. That is to say, `` will not be included in the +results, but up to `` tags *after* `` will be returned. The tags MUST be in lexical order. #### Content Management +Content management refers to the deletion of blobs, tags, and manifests. Registries MAY implement deletion or they MAY +disable it. Similarly, a registry MAY implement tag deletion, while others MAY allow deletion only by manifest. + +##### Deleting tags +`` is the namespace of the repository, and `` is the name of the tag to be deleted. Upon success, the registry +MUST respond with a `202 Accepted` code. If tag deletion is disabled, the registry MUST respond with either a +`400 Bad Request` or a `405 Method Not Allowed`. + +To delete a tag, perform a `DELETE` request to a path in the following format: +[9a](#Endpoints) `/v2//manifests/` + +##### Deleting Manifests +To delete a manifest, perform a `DELETE` request to a path in the following format: +[9a](#Endpoints) `/v2//manifests/` + +`` is the namespace of the repository, and `` is the digest of the manifest to be deleted. Upon success, the registry +MUST respond with a `202 Accepted` code. If the repository does not exist, the response MUST return `404 Not Found`. + +##### Deleting Blobs +To delete a blob, perform a `DELETE` request to a path in the following format: +[10a](#Endpoints) `/v2//blobs/` + +`` is the namespace of the repository, and `` is the digest of the blob to be deleted. Upon success, the +registry MUST respond with code `202 Accepted`. If the blob is not found, a `404 Not Found` code MUST be returned. + +### API +The API operates over HTTP. Below is a summary of the endpoints used by the API. + +#### Determining Support +To check whether or not the registry implements this specification, perform a `GET` request to the following endpoint: +[1a](#Endpoints) `/v2/`. -TODO: describe the Content Management category and the high-level details +If the response is `200 OK`, then the registry implements this specification. + +This endpoint MAY be used for authentication/authorization purposes, but this is out of the purview +of this specification. + +#### Endpoints +| ID | Method | API endpoint | Accepted Successful Response Codes | Accepted Failure Response Codes | +| ---| --- | ---|---|---| +| 1a | `GET` | `/v2/` | `200` | `404`/`401` | +| 2a | `GET` | `/v2//blobs/` | `200` | `404` | +| 3a | `GET` | `/v2//manifests/` | `200` | `404` | +| 4a | `POST` | `/v2//blobs/uploads/` | `202` | `404` | +| 4b | `POST` | `/v2//blobs/uploads/?digest=` | `201` | `404`/`400` | +| 5a | `PATCH` | `/v2//blobs/uploads/` | `202` | `404`/`416` | +| 6a | `PUT` | `/v2//blobs/uploads/?digest=` | `201` | `404`/`400` | +| 7a | `PUT` | `/v2//manifests/` | `201` | `404` | +| 8a | `GET` | `/v2//tags/list` | `200` | `404` | +| 8b | `GET` | `/v2//tags/list?n=&last=` | `200` | `404` | +| 9a | `DELETE` | `/v2//manifests/` | `202` | `404`/`400`/`405` | +| 10a | `DELETE` | `/v2//blobs/` | `202` | `404`/`405` | + +#### Error Codes + +A `4XX` response code from the registry MAY return a body in any format. If the response body is in JSON format, it MUST +have the following format: + +```json + { + "errors": [ + { + "code": "", + "message": "", + "detail": "" + }, + ... + ] + } +``` + +The `code` field MUST be a unique identifier, containing only uppercase alphabetic characters and underscores. The +`message` field is OPTIONAL, and if present, it SHOULD be a human readable string or MAY be empty. The `detail` field is +OPTIONAL and MAY contain arbitrary JSON data providing information the client can use to resolve the issue. + +The `code` field MUST be one of the following: + +| Code | Description | +|-------------------------|------------------------------------------------| +| `BLOB_UNKNOWN` | blob unknown to registry | +| `BLOB_UPLOAD_INVALID` | blob upload invalid | +| `BLOB_UPLOAD_UNKNOWN` | blob upload unknown to registry | +| `DIGEST_INVALID` | provided digest did not match uploaded content | +| `MANIFEST_BLOB_UNKNOWN` | blob unknown to registry | +| `MANIFEST_INVALID` | manifest invalid | +| `MANIFEST_UNKNOWN` | manifest unknown | +| `MANIFEST_UNVERIFIED` | manifest failed signature verification | +| `NAME_INVALID` | invalid repository name | +| `NAME_UNKNOWN` | repository name not known to registry | +| `SIZE_INVALID` | provided length did not match content length | +| `TAG_INVALID` | manifest tag did not match URI | +| `UNAUTHORIZED` | authentication required | +| `DENIED` | requested access to the resource is denied | +| `UNSUPPORTED` | the operation is unsupported | ## Scope @@ -480,7 +811,7 @@ Range: bytes=0-0 To get the status of an upload, issue a GET request to the upload URL: ```HTTP -GET /v2//blobs/uploads/ +GET /v2//globs/uploads/ Host: ```