-
Notifications
You must be signed in to change notification settings - Fork 257
Update spec to include tag history endpoint #606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ef950c1
c97e594
5eaeef9
1d37c35
2516898
7e2fb38
50f747e
0c97df1
a0fa54e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -23,7 +23,7 @@ GET /v2/_oci/ext/discover | |||||
| Repository-level extensions may be discovered with a standard GET as follows. | ||||||
|
|
||||||
| ```HTTP | ||||||
| GET /v2/{name}/_oci/ext/discover | ||||||
| GET /v2/<name>/_oci/ext/discover | ||||||
| ``` | ||||||
|
|
||||||
| The base extension returns an array of supported extensions with details of the endpoints as shown below. | ||||||
|
|
@@ -66,6 +66,157 @@ Content-Type: application/json | |||||
|
|
||||||
| Enumeration of the endpoints provided on this registry (as not all "OPTIONAL" endpoints may be present in all registries) | ||||||
|
|
||||||
| ### Component: `tag-history` | ||||||
|
|
||||||
| This component is for endpoints relating to tag history operations on a repository. | ||||||
|
|
||||||
| This endpoint returns the history of a tag, listing each manifest the tag has pointed to over time and each time the tag was deleted, in descending order (newest first). | ||||||
|
|
||||||
| Tag history MAY be retrieved with a standard `GET` as follows. | ||||||
|
|
||||||
| ```HTTP | ||||||
| GET /v2/<name>/_oci/tag-history/<tag> | ||||||
| ``` | ||||||
|
|
||||||
| `<name>` is the namespace of the repository, and `<tag>` is the name of the tag whose history is being queried. | ||||||
|
|
||||||
| A successful request MUST return a `200 OK` response code. | ||||||
| If the registry does not implement this extension, the registry MUST return a `404 Not Found` response code. | ||||||
| The registry MUST NOT return a `404 Not Found` response code for any other condition. | ||||||
| If the repository does not exist or the tag has no history, the registry MUST return a `200 OK` response code with an empty manifest list. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
the statement is a bit ambiguous, a tag must have a history, at least |
||||||
| Tag history SHOULD remain queryable after the tag is deleted. | ||||||
| Deleted manifests SHOULD NOT be removed from tag history. | ||||||
| Tag history MAY be deleted after the repository is deleted. | ||||||
|
Comment on lines
+83
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from the example below, it seems like each registry operation (push or delete) will only create a single entry in the history. it seems a little ambiguous reading these requirements whether a tag moving between images creates 1 created event or 2 events (deleted/created). can we add a requirement to clarify that a tag move, or a push to an existing tag, will only produce a single "created" event referencing the new image digest, assuming that's the direction we want to go?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I can add clarifying language there - the intent was to do a single "create" event when a tag moves, and "deleted" events only apply if it is specifically removed and no longer referencable in the registry. |
||||||
|
|
||||||
| Upon success, the response body MUST be an OCI Index containing a list of descriptor objects in the following format: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm not sure about image indexes being used to represent content that may/may not exist... the referrers API was a pretty natural use of the image index since it's designed to contain content that's actually in the registry and is going to be pulled, but this feels forced -- any thoughts on using more of a bare bones JSON list, similar to #611? potentially:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We debated this a couple times - ultimately opted to stick with the index approach rather than having to define a new schema/model that everyone needs to copy/import. The data format isn't ideal but at least it is known. Happy to revisit if more people challenge it but that was the consensus before. |
||||||
|
|
||||||
| ```HTTP | ||||||
| 200 OK | ||||||
| Content-Length: <length> | ||||||
| Content-Type: application/vnd.oci.image.index.v1+json | ||||||
|
|
||||||
| { | ||||||
| "schemaVersion": 2, | ||||||
| "mediaType": "application/vnd.oci.image.index.v1+json", | ||||||
| "manifests": [ | ||||||
| { | ||||||
| "mediaType": "application/vnd.oci.image.manifest.v1+json", | ||||||
| "size": 1234, | ||||||
| "digest": "sha256:a1a1a1...", | ||||||
| "annotations": { | ||||||
| "org.opencontainers.distribution.tag.timestamp": "2026-05-04T03:02:01Z", | ||||||
| "org.opencontainers.distribution.tag.event": "created" | ||||||
| } | ||||||
| }, | ||||||
| { | ||||||
| "mediaType": "application/vnd.oci.image.manifest.v1+json", | ||||||
| "size": 1234, | ||||||
| "digest": "sha256:a1a1a1...", | ||||||
| "annotations": { | ||||||
| "org.opencontainers.distribution.tag.timestamp": "2026-02-03T03:02:01Z", | ||||||
| "org.opencontainers.distribution.tag.event": "deleted" | ||||||
| } | ||||||
| }, | ||||||
| { | ||||||
| "mediaType": "application/vnd.oci.image.manifest.v1+json", | ||||||
| "size": 1234, | ||||||
| "digest": "sha256:b2b2b2...", | ||||||
| "annotations": { | ||||||
| "org.opencontainers.distribution.tag.timestamp": "2025-04-03T02:01:00Z", | ||||||
| "org.opencontainers.distribution.tag.event": "created" | ||||||
| } | ||||||
| } | ||||||
| ] | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Results MUST be sorted in descending order by the history entries (i.e. the most recent entry appears first). | ||||||
|
|
||||||
| Each descriptor object in the response MUST be a create or delete entry. | ||||||
| A descriptor for a tag creation event MUST describe the manifest the tag was assigned to. | ||||||
| A descriptor for a tag deletion event MUST describe the manifest the tag pointed to immediately before deletion. | ||||||
|
|
||||||
| Manifest descriptors MUST include the following properties: | ||||||
|
jcarter3 marked this conversation as resolved.
|
||||||
|
|
||||||
| - **`mediaType`** *string*, REQUIRED | ||||||
|
|
||||||
| The media type of the manifest this tag pointed to at that point in time. | ||||||
|
|
||||||
| - **`size`** *integer*, REQUIRED | ||||||
|
|
||||||
| The size in bytes of the manifest. | ||||||
|
|
||||||
| - **`digest`** *string*, REQUIRED | ||||||
|
|
||||||
| The digest of the manifest, in the form `<algorithm>:<encoded>`. | ||||||
|
|
||||||
| - **`annotations`** *map of strings*, REQUIRED | ||||||
|
|
||||||
| Annotations associated with the historical tag entry. Registries MAY include annotations from the underlying manifest or index descriptor. | ||||||
| - When doing so, any existing annotations with keys beginning with `org.opencontainers.distribution` MUST be ignored because this prefix is reserved for annotations generated by the registry. | ||||||
|
|
||||||
| The annotations MUST include both of the following: | ||||||
|
|
||||||
| - **`org.opencontainers.distribution.tag.timestamp`** *string* | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
|
|
||||||
| The RFC 3339 timestamp at which the tag history event occurred. Used as the sort key and as the cursor for time-based pagination. | ||||||
|
|
||||||
| - **`org.opencontainers.distribution.tag.event`** *string* | ||||||
|
|
||||||
| The tag history event type. MUST be either `created` when the tag was assigned to the manifest or `deleted` when the tag was deleted from the manifest. | ||||||
|
|
||||||
| ##### Query Parameters | ||||||
|
|
||||||
| The following query parameters MAY be provided: | ||||||
|
jcarter3 marked this conversation as resolved.
|
||||||
|
|
||||||
| - **`n`** *integer*, OPTIONAL | ||||||
|
|
||||||
| Specifies the maximum number of results to return. | ||||||
| The registry MAY return fewer results than requested if fewer historical entries exist. | ||||||
| When `n` is zero, this endpoint MUST return an OCI Index with an empty manifest list and MUST NOT include a `Link` header, useful for determining whether the extension is supported (`200 OK`) or not (`404 Not Found`). | ||||||
| When `n` is not specified, the registry MAY apply a default limit. | ||||||
|
|
||||||
| - **`before`** *string (RFC 3339 timestamp)*, OPTIONAL | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe from or ot (older than)
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a chance to have two results with the exact same time stamp.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps a registry could consider every update of a tag as a delete followed by a create and so the timestamp would be the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would think the history can be more compact for repush case. Actually two created events are better repressing what acutally happened compared to created/deleted/created.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd love the opposite of this (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated to include a |
||||||
|
|
||||||
| When provided, only entries whose history entry timestamp is strictly less than (i.e. older than) `before` will be returned. | ||||||
| This is used for time-based pagination: pass the `org.opencontainers.distribution.tag.timestamp` value of the last entry returned in the previous response to retrieve the next page. | ||||||
|
|
||||||
| - **`since`** *string (RFC 3339 timestamp)*, OPTIONAL | ||||||
|
|
||||||
| When provided, only entries whose history entry timestamp is strictly greater than (i.e. newer than) `since` will be returned. | ||||||
| The `since` and `before` parameters MAY be combined to return entries within an open time interval. | ||||||
|
|
||||||
| - **`digest`** *string*, OPTIONAL | ||||||
|
|
||||||
| When provided, only history entries for the given digest will be returned. | ||||||
| If the digest does not exist or was not part of the given tag's history, the registry MUST return a `200 OK` response code with an empty manifest list. | ||||||
|
|
||||||
|
jcarter3 marked this conversation as resolved.
|
||||||
| ##### Pagination Example | ||||||
|
|
||||||
| A `Link` header MUST be included in the response when additional history entries are available. | ||||||
| The `Link` header MUST be set according to [RFC 5988](https://www.rfc-editor.org/rfc/rfc5988) with the Relation Type `rel="next"`. | ||||||
| The target URI MAY contain registry-specific query parameters. | ||||||
| Clients MUST use the target URI as provided and MUST NOT modify or interpret registry-specific query parameters. | ||||||
| When a response does not include a `Link` header, the client has reached the end of the history. | ||||||
|
|
||||||
| To fetch the first page of up to 10 results: | ||||||
|
|
||||||
| ```HTTP | ||||||
| GET /v2/<name>/_oci/tag-history/<tag>?n=10 | ||||||
| ``` | ||||||
|
|
||||||
| When constructing the next request without using a `Link` header, pass the history entry timestamp of the last result from the prior response as the `before` parameter: | ||||||
|
|
||||||
| ```HTTP | ||||||
| GET /v2/<name>/_oci/tag-history/<tag>?n=10&before=2026-04-03T02%3A01%3A00Z | ||||||
| ``` | ||||||
|
|
||||||
| To fetch all history entries after a given timestamp: | ||||||
|
|
||||||
| ```HTTP | ||||||
| GET /v2/<name>/_oci/tag-history/<tag>?since=2026-04-01T00%3A00%3A00Z | ||||||
| ``` | ||||||
|
|
||||||
| ## Code representations | ||||||
|
|
||||||
| Golang structures for these JSON structures is available at [`github.com/opencontainers/distribution-spec/specs-go/v1/extensions`](https://github.com/opencontainers/distribution-spec/tree/main/specs-go/v1/extensions/) | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.