oci

package
v1.71.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package oci provides the OCI distribution registry client used by the conformance suite. `New` and the `*HTTPError` envelope are the Step 1 base; Step 1 also adds the Docker Registry HTTP API V2 token-exchange machinery (auth.go). Per-operation methods are introduced starting at S07 Step 2: the blob lifecycle (blob.go). Manifest, tag, and referrers operations follow in later steps.

Spec anchors:

  • S04 §HTTPError block: ResponseBody is truncated to 4 KiB.
  • S04 §per-format Client block: constructor takes baseURL, a Credential, and an *http.Client (nil ⇒ http.DefaultClient).
  • S04 AC #32: `*HTTPError` round-trips through `errors.As`.
  • S04 AC #33: 4 KiB tail truncation marker ends ResponseBody.
  • Plan extra: the Authorization request header is scrubbed via the shared `pkg/conformance/redact` primitives before any error surface or log line is rendered.

Index

Constants

View Source
const (
	// ManifestMediaType is the OCI image manifest media type.
	ManifestMediaType = "application/vnd.oci.image.manifest.v1+json"

	// ConfigMediaType is the OCI image config media type.
	ConfigMediaType = "application/vnd.oci.image.config.v1+json"

	// LayerMediaType is the OCI image layer media type (gzipped tar).
	LayerMediaType = "application/vnd.oci.image.layer.v1.tar+gzip"

	// IndexMediaType is the OCI image index (manifest list) media type.
	IndexMediaType = "application/vnd.oci.image.index.v1+json"
)
View Source
const BlobUploadMediaType = "application/octet-stream"

BlobUploadMediaType is the Content-Type a blob upload carries on the single POST, the chunk PATCH, and the completion PUT (S07 §Common request semantics: "blob `PUT` and `PATCH` set `application/octet-stream`"; §Operation: blob upload single POST). It is fixed regardless of the layer's logical media type: the registry stores blob bytes opaquely and addresses them by digest.

Variables

View Source
var ErrInvalidDigest = errors.New("oci: invalid digest")

ErrInvalidDigest is returned by ParseDigest when the input does not match the `<algorithm>:<hex>` grammar for a supported algorithm.

View Source
var ErrInvalidName = errors.New("oci: invalid repository name")

ErrInvalidName is returned by ValidateName when the input does not match the OCI distribution-spec `<name>` grammar.

View Source
var ErrInvalidTag = errors.New("oci: invalid tag")

ErrInvalidTag is returned by ValidateTag when the input does not match the tag grammar `[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}`.

View Source
var ErrLocationOriginMismatch = errors.New("oci: Location resolved to a different origin than the configured registry")

ErrLocationOriginMismatch is wrapped into the error resolveAndValidateLocation returns when a resolved Location's origin differs from the configured registry's (S07 §Security Considerations).

Functions

func ComputeSHA256

func ComputeSHA256(body []byte) string

ComputeSHA256 returns the canonical `sha256:<hex>` digest of body, where <hex> is the lowercase hex encoding of the SHA-256 sum.

func ParseDigest

func ParseDigest(s string) (algo, encoded string, err error)

ParseDigest splits a digest string of the form `<algorithm>:<hex>` into its algorithm and hex-encoded digest components. It accepts the `sha256` (64 lowercase hex characters) and `sha512` (128 lowercase hex characters) algorithms per S07 §Tag and digest references. On a grammar violation it returns ErrInvalidDigest.

func ReferrersTagSchema added in v1.52.0

func ReferrersTagSchema(subjectDigest string) (string, error)

ReferrersTagSchema builds the OCI referrers tag-schema fallback tag for subjectDigest (S07 §Referrers API (v1.1) / Tag-schema fallback: `<algorithm>-<hex>`), consumed by oci.referrers.tag-schema-fallback (AC #24) when the referrers API itself returns 404. Returns ErrInvalidDigest when subjectDigest does not match the `<algorithm>:<hex>` grammar (digest.go's ParseDigest).

func ValidateName

func ValidateName(name string) error

ValidateName reports whether name matches the OCI distribution-spec `<name>` grammar: one or more lowercase alphanumeric segments separated by `.`, `_`, `__`, or `-`, joined into a path by `/`. It returns ErrInvalidName on a violation. An empty name and an empty path component both fail nameComponentRE, which requires a leading alphanumeric run.

func ValidateTag

func ValidateTag(tag string) error

ValidateTag reports whether tag matches the grammar `[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}` (1-128 characters, first character alphanumeric or underscore) per S07 §Tag and digest references. It returns ErrInvalidTag on a violation.

Types

type BlobResponse added in v1.39.0

type BlobResponse struct {
	// StatusCode is the HTTP status of the accepted (2xx) response
	// (200 full body, 206 partial on a Range request).
	StatusCode int

	// Body is the blob bytes verbatim. Populated by GetBlob; empty for
	// HeadBlob (RFC 9110 §9.3.2).
	Body []byte

	// DockerContentDigest is the `Docker-Content-Digest` response header
	// (`<algorithm>:<hex>`), present on a successful blob GET and HEAD
	// (S07 §`Docker-Content-Digest` response header; AC #5, #6, #26).
	DockerContentDigest string

	// ContentType is the response Content-Type header, verbatim.
	ContentType string

	// ContentLength is the response Content-Length. -1 when the server
	// did not declare one (net/http's sentinel for unknown length).
	ContentLength int64

	// ContentRange is the `Content-Range` response header, present on a
	// 206 Partial Content reply to a Range request (AC #29).
	ContentRange string

	// Digest is the S07 AC #26 Docker-Content-Digest verdict (plan
	// Step 28). For GetBlob, Expected is the digest of the received
	// Body. For HeadBlob, Expected is the digest argument (the caller
	// already supplies it; HeadBlob has no body to hash). For
	// GetBlobRange, Expected is left empty (zero DigestVerdict): a
	// partial (206) response's bytes cannot hash to the full-content
	// digest the header advertises, and AC #26 scopes the always-on GET
	// check to the full body (see DigestVerdict's doc).
	Digest DigestVerdict
}

BlobResponse is the result of a blob GET or HEAD (S07 §Operation: blob GET / HEAD).

type BlobUploadResult added in v1.39.0

type BlobUploadResult struct {
	// StatusCode is the HTTP status of the accepted (2xx) response.
	// Callers inspect it to distinguish 201 (created / mounted) from
	// 202 (fallback to two-phase upload).
	StatusCode int

	// Location is the blob URL on 201, or the upload-session URL on a
	// 202 fallback: always absolute and same-origin with baseURL,
	// whatever form the registry's own Location header took
	// (resolveAndValidateLocation).
	Location string

	// Method is the verb of the request that produced this response,
	// read from the terminal response's own Request and therefore the
	// verb the registry actually answered rather than the one the
	// client sent: a followed 301, 302 or 303 replays a POST or PUT as
	// a GET, and net/http reports that GET here.
	//
	// The conformance layer's seeding cause sort compares
	// SeedResult.Method against SeedSpec.WriteVerb for every format
	// (pkg/conformance/seed.go), so an OCI write callback that left
	// this empty would compare "" against "PUT" and sort every OCI
	// seeding write as transient. OCI is safe by construction on the
	// companion *status* rule, since a GET cannot answer 201, but that
	// is not the same as the sort being able to see the verb.
	Method string
}

BlobUploadResult is the result of an operation that produces a pullable blob or selects an upload path: single-request upload, completion PUT, or cross-repository mount. On 201 Location points at the pullable blob URL (`/v2/<name>/blobs/<digest>`); on a 202 fallback Location is the upload-session URL (S07 §Operation: blob upload single POST / complete / mount).

type BlobUploadSession added in v1.39.0

type BlobUploadSession struct {
	// StatusCode is the HTTP status of the accepted (2xx) response.
	StatusCode int

	// Location is the upload-session URL: always absolute and
	// same-origin with baseURL, whatever form the registry's own
	// Location header took (resolveAndValidateLocation). Used as the
	// target of subsequent PATCH / PUT / GET / DELETE on the session.
	Location string

	// Range is the registry's cumulative write-position header
	// (`0-0` on init, `0-<end>` after a chunk, `0-<offset>` on status).
	Range string
}

BlobUploadSession is the result of an operation that opens or advances an upload session: init (POST .../uploads/), a chunk PATCH, or a status GET. Location is the opaque session URL the caller passes back verbatim; Range echoes the registry's cumulative write position (S07 §Operation: blob upload init / chunk / status).

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the OCI distribution protocol client. Per-operation methods are introduced in S07 Step 2+; Step 1 adds the Docker Registry HTTP API V2 token-exchange machinery (credential dispatch, realm-URL HTTPS pre-check, scope-keyed token cache, single-retry refresh).

func New

func New(baseURL string, cred conformance.Credential, hc *http.Client) *Client

New returns an OCI client. baseURL is the registry root; cred is the user-supplied secret carried into protocol exchanges; hc is the transport (nil ⇒ http.DefaultClient). The constructor does not perform I/O; connection is lazy.

func (*Client) BlobUploadRouteURLs added in v1.69.0

func (c *Client) BlobUploadRouteURLs(name string) (collection, session string)

BlobUploadRouteURLs synthesizes the two `blobs/uploads/*` request targets for name: collection is the upload-collection route `<base>/v2/<name>/blobs/uploads/` that InitBlobUpload posts to, and session is a session-*shaped* path beneath it.

session is synthesized, not derived: a real upload-session location is server-assigned and opaque (resolveAndValidateLocation), so there is no way to address the session route without first opening a session, and a remote repository refuses the verb that would open one. S08 §Write refusal per format asserts status and `Allow` only on this route, because an upload session URL addresses no content coordinate, so a well-formed path to send a verb at is all the assertion needs. The returned session URL must not be passed to PatchBlobChunk, CompleteBlobUpload, GetBlobUploadStatus or CancelBlobUpload expecting a live session: no server ever assigned it.

func (*Client) CancelBlobUpload added in v1.39.0

func (c *Client) CancelBlobUpload(ctx context.Context, location string) *HTTPError

CancelBlobUpload issues `DELETE <location>` to abandon an in-flight session (S07 §Operation: blob upload cancel). On 204 it returns a nil *HTTPError; any non-2xx maps to *HTTPError (AC #31). location is already absolute and same-origin (resolveAndValidateLocation ran when it was first read) and is used as-is.

func (*Client) CompleteBlobUpload added in v1.39.0

func (c *Client) CompleteBlobUpload(ctx context.Context, location, digest string, body []byte) (*BlobUploadResult, *HTTPError)

CompleteBlobUpload issues `PUT <location>?digest=<digest>` with an empty body (after a chunked upload) or the final blob bytes (after init-only) to close the session (S07 §Operation: blob upload complete). On 201 it returns the pullable blob Location; a mismatched digest yields 400 DIGEST_INVALID, mapped to *HTTPError. AC #44 pins this shape on the single-POST path (UploadBlobSingle); the completion PUT's non-2xx mapping is covered by the generic *HTTPError contract (AC #48). location is already absolute and same-origin (resolveAndValidateLocation ran when it was first read); the digest query parameter is appended to it as-is.

func (*Client) CompleteBlobUploadWithContentRange added in v1.62.0

func (c *Client) CompleteBlobUploadWithContentRange(
	ctx context.Context, location, digest string, start, end int64, body []byte,
) (*BlobUploadResult, *HTTPError)

CompleteBlobUploadWithContentRange issues `PUT <location>?digest=<digest>` exactly as CompleteBlobUpload does, but additionally carries a `Content-Range: <start>-<end>` header on the request (mirroring PatchBlobChunk's own Content-Range construction below), for the closing PUT's trailing-chunk shape the upstream OCI distribution-spec's "close the session" operation allows (S07 §Operation: blob upload complete does not itself document the header, but AC #45's second arm requires driving exactly this request shape: "a final PUT <session-url>?digest=<digest> whose Content-Range is mismatched"). On 201 it returns the pullable blob Location; a mismatched Content-Range (a start offset past the registry's current write position) yields 416 BLOB_UPLOAD_INVALID, mapped to *HTTPError (AC #45); a mismatched digest still yields 400 DIGEST_INVALID as CompleteBlobUpload does.

Added as a plan amendment for Step 26e (docs/plans/2026-05-26-oci-format.md): Step 2 pinned CompleteBlobUpload's exact signature with no Content-Range parameter, and that signature is already consumed by Steps 13/14/16's committed call sites, so this is a new, additive sibling rather than a change to CompleteBlobUpload itself, the same "within this plan's own authority" scope Step 26b's WWWAuthenticate() accessor and Step 12's version-check method used, applied here to a new method rather than a new accessor.

func (*Client) DeleteBlob added in v1.39.0

func (c *Client) DeleteBlob(ctx context.Context, name, digest string) *HTTPError

DeleteBlob issues `DELETE /v2/<name>/blobs/<digest>` (S07 §Operation: blob DELETE). On 202 it returns a nil *HTTPError (AC #18); a deletion-disabled registry yields 400 or 405 with UNSUPPORTED and a missing blob yields 404 BLOB_UNKNOWN, both mapped to *HTTPError.

func (*Client) DeleteManifest added in v1.42.0

func (c *Client) DeleteManifest(ctx context.Context, name, reference string) *HTTPError

DeleteManifest issues `DELETE /v2/<name>/manifests/<reference>` (S07 §Operation: manifest DELETE). reference is a digest (AC #16) or a tag (AC #17); both address the same endpoint. On 202 it returns a nil *HTTPError; a deletion-disabled registry yields 400 or 405 with UNSUPPORTED (AC #46) and a delete against a repository with no manifests yields 404 (AC #41), both mapped to *HTTPError.

func (*Client) GetBlob added in v1.39.0

func (c *Client) GetBlob(ctx context.Context, name, digest string) (*BlobResponse, *HTTPError)

GetBlob issues `GET /v2/<name>/blobs/<digest>` for the full blob (S07 §Operation: blob GET). On 200 it returns the blob bytes verbatim with the Docker-Content-Digest, Content-Type, and Content-Length headers (AC #5). A missing blob yields 404 BLOB_UNKNOWN, mapped to *HTTPError (AC #40).

func (*Client) GetBlobRange added in v1.39.0

func (c *Client) GetBlobRange(ctx context.Context, name, digest string, start, end int64) (*BlobResponse, *HTTPError)

GetBlobRange issues `GET /v2/<name>/blobs/<digest>` with `Range: bytes=<start>-<end>` (S07 §Operation: blob GET / `Range` support; AC #29). A registry that honors the range returns 206 with Content-Range and the requested byte window; one that ignores it returns 200 with the full body (both are conformant, so the per-slug oci.blob.range-request layer reads the returned StatusCode to decide pass-vs-skip). A missing blob yields 404 BLOB_UNKNOWN, mapped to *HTTPError.

func (*Client) GetBlobUploadStatus added in v1.39.0

func (c *Client) GetBlobUploadStatus(ctx context.Context, location string) (*BlobUploadSession, *HTTPError)

GetBlobUploadStatus issues `GET <location>` against an in-flight session (S07 §Operation: blob upload status). On 204 it returns the session Location and the `Range: 0-<offset>` header showing the current write position (AC #30); any non-2xx maps to *HTTPError. location is already absolute and same-origin (resolveAndValidateLocation ran when it was first read) and is used as-is.

func (*Client) GetManifest added in v1.42.0

func (c *Client) GetManifest(ctx context.Context, name, reference string, acceptList []string) (*ManifestResponse, *HTTPError)

GetManifest issues `GET /v2/<name>/manifests/<reference>` (S07 §Operation: manifest GET). acceptList is joined into the request `Accept` header per S07 §Common request semantics (a comma-separated list of OCI image-manifest and image-index media types the caller understands); an empty acceptList omits the header. On 200 it returns the manifest bytes verbatim plus the parsed Content-Type and Docker-Content-Digest headers (AC #9, #10, #25, #26, #27, #34); a manifest that was never pushed yields 404 MANIFEST_UNKNOWN, mapped to *HTTPError (AC #39).

func (*Client) HeadBlob added in v1.39.0

func (c *Client) HeadBlob(ctx context.Context, name, digest string) (*BlobResponse, *HTTPError)

HeadBlob issues `HEAD /v2/<name>/blobs/<digest>` (S07 §Operation: blob HEAD). On 200 it returns an empty Body with the Content-Length and Docker-Content-Digest the corresponding GET carries (AC #6); a missing blob yields 404, mapped to *HTTPError.

func (*Client) HeadManifest added in v1.42.0

func (c *Client) HeadManifest(ctx context.Context, name, reference string, acceptList []string) (*ManifestResponse, *HTTPError)

HeadManifest issues `HEAD /v2/<name>/manifests/<reference>` (S07 §Operation: manifest HEAD). reference is a bare tag or digest with no expected-digest parameter; see this file's package doc for the HEAD-by-tag validation split with the test layer. acceptList sets the `Accept` header the same way GetManifest does (S07 §Common request semantics pins `Accept` on both manifest GET and HEAD). On 200 it returns an empty Body with the Content-Length and Docker-Content-Digest the corresponding GET carries (AC #11, #26); a manifest that was never pushed yields 404, mapped to *HTTPError.

func (*Client) InitBlobUpload added in v1.39.0

func (c *Client) InitBlobUpload(ctx context.Context, name string) (*BlobUploadSession, *HTTPError)

InitBlobUpload issues `POST /v2/<name>/blobs/uploads/` to open a monolithic or chunked upload session (S07 §Operation: blob upload init). On 202 it returns the opaque session Location and the `Range: 0-0` header; any non-2xx maps to *HTTPError.

func (*Client) ListReferrers added in v1.52.0

func (c *Client) ListReferrers(ctx context.Context, name, subjectDigest string, artifactTypes []string) (*ReferrersResponse, *HTTPError)

ListReferrers issues `GET /v2/<name>/referrers/<digest>` (S07 §Operation: referrers list (v1.1)). artifactTypes, when non-empty, is sent as one repeated `artifactType=<type>` query parameter per entry; a registry that applies the filter echoes `OCI-Filters-Applied` in the response (AC #23). On 200 the response body decodes into an ImageIndex (AC #22); any other status maps to *HTTPError like every other client method — see this file's package doc for the v1.0-vs-v1.1 distinction callers draw from herr.StatusCode.

func (*Client) ListTags added in v1.46.0

func (c *Client) ListTags(ctx context.Context, name string) (*TagListResponse, *HTTPError)

ListTags issues `GET /v2/<name>/tags/list` with no pagination parameters (S07 §Operation: tag list; AC #12).

func (*Client) ListTagsPaginated added in v1.46.0

func (c *Client) ListTagsPaginated(ctx context.Context, name string, n int, last string) (*TagListResponse, *HTTPError)

ListTagsPaginated issues `GET /v2/<name>/tags/list?n=<n>&last=<last>` (S07 §Operation: tag list / Pagination; AC #13). last is omitted from the query when empty. A registry MAY include an RFC 5988 `Link: <url>; rel="next"` response header when more tags exist beyond the returned page; ListTagsPaginated parses it into NextLink. n=0 returns an empty Tags slice and no NextLink.

func (*Client) MountBlob added in v1.39.0

func (c *Client) MountBlob(ctx context.Context, name, fromName, digest string) (*BlobUploadResult, *HTTPError)

MountBlob issues `POST /v2/<name>/blobs/uploads/?mount=<digest>&from=<fromName>`, constructing the cross-repository mount URL inline (S07 §Operation: cross-repository blob mount). A registry that supports mount returns 201 with the blob Location; one that does not returns 202 with a session Location for fallback. Both are conformant (AC #15); any non-2xx maps to *HTTPError.

func (*Client) PatchBlobChunk added in v1.39.0

func (c *Client) PatchBlobChunk(ctx context.Context, location string, start, end int64, body []byte) (*BlobUploadSession, *HTTPError)

PatchBlobChunk issues `PATCH <location>` with `Content-Range: <start>-<end>` and the chunk bytes (S07 §Operation: blob upload chunk). On 202 it returns the updated session Location and the `Range: 0-<end>` header; an out-of-order range yields 416 with BLOB_UPLOAD_INVALID, mapped to *HTTPError (AC #14, #45). location is already absolute and same-origin (resolveAndValidateLocation ran when it was first read) and is used as-is.

func (*Client) PutManifest added in v1.42.0

func (c *Client) PutManifest(ctx context.Context, name, reference, mediaType string, body []byte) (*PutManifestResponse, *HTTPError)

PutManifest issues `PUT /v2/<name>/manifests/<reference>` with the manifest bytes verbatim, setting `Content-Type: <mediaType>` per S07 §Common request semantics (S07 §Operation: manifest PUT). reference may be a tag or a digest (S07 §Tag and digest references); on a digest reference the request URL's digest MUST equal the digest of body, or the registry returns 400 DIGEST_INVALID (AC #8). On 201 it returns the Location, Docker-Content-Digest, and OCI-Subject (when present) response headers; a manifest exceeding the registry's enforced max yields 413 (AC #28); a manifest referencing missing blobs yields one or more MANIFEST_BLOB_UNKNOWN errors when the registry validates push-time blob references (AC #21, #43); any other non-2xx maps to *HTTPError.

func (*Client) UploadBlobSingle added in v1.39.0

func (c *Client) UploadBlobSingle(ctx context.Context, name, digest string, body []byte) (*BlobUploadResult, *HTTPError)

UploadBlobSingle issues `POST /v2/<name>/blobs/uploads/?digest=<digest>` with the blob body in one request (S07 §Operation: blob upload single POST). A registry that supports the path returns 201 with the blob Location; one that does not returns 202 with a session Location and the caller falls back to two-phase. Both are conformant (AC #3); a body that doesn't hash to the URL digest yields 400 DIGEST_INVALID, mapped to *HTTPError (AC #44); any other non-2xx also maps to *HTTPError.

func (*Client) VersionCheck added in v1.53.0

func (c *Client) VersionCheck(ctx context.Context) *HTTPError

VersionCheck issues `GET /v2/` (S07 §Operation: version check; AC #1). Returns nil on `200 OK`; any other status maps to *HTTPError through the shared do/statusIs path, the same envelope every other operation method in this package uses. The response body is never inspected: AC #1 pins the assertion on status alone.

type DigestVerdict added in v1.54.0

type DigestVerdict struct {
	// Advertised is the raw `Docker-Content-Digest` response header
	// value, exactly as the registry sent it (no normalization).
	Advertised string

	// Expected is the digest the client independently knows for this
	// response (GET: digest of the received body; PUT: digest of the
	// pushed body; HEAD: the digest from the request path), or empty
	// when the client holds none (see type doc).
	Expected string

	// Match reports whether Advertised equals Expected byte-for-byte.
	// Always false when Expected is empty.
	Match bool
}

DigestVerdict is the S07 AC #26 (`oci.http.docker-content-digest`) validation outcome the client computes on every manifest GET/HEAD/PUT and blob GET/HEAD response in scope (S07 §Docker-Content- Digest response header). It is carried on the operation's own response value (`ManifestResponse.Digest`, `PutManifestResponse.Digest`, `BlobResponse.Digest`), never on a shared *Client field, so verdicts from concurrent responses cannot clobber each other (plan Step 28).

Expected is empty and Match is always false when the client holds no independently-known digest to compare Advertised against:

  • HeadManifest by tag: the client signature carries no expected digest for a tag reference (Step 21's per-slug Fn validates that case at the test layer against its own setup-known digest, per this file's manifest.go package doc).
  • GetBlobRange: a partial (206) response's bytes cannot hash to the full-content digest the header advertises, and AC #26 scopes the always-on GET check to the full body; the partial-content shape is AC #29's (`oci.blob.range-request`) concern, not this validator's.

A syntactically-present but unverified Advertised value must never report Match=true: this is a deliberate "unverified pass is worse than no verdict" design (plan Step 28 rationale for HeadManifest).

type HTTPError

type HTTPError struct {
	// Method is the HTTP method of the failing request (e.g., "GET").
	Method string

	// URL is the absolute URL the client attempted.
	URL string

	// StatusCode is the HTTP status code observed. Zero for
	// transport-level failures that never received a status line.
	StatusCode int

	// ResponseBody is the body the server returned, truncated to
	// 4 KiB inclusive of the `... [truncated]` marker when the wire
	// body exceeded that limit. See S04 AC #33.
	ResponseBody string
	// contains filtered or unexported fields
}

HTTPError is returned by client methods when the registry responds with an unexpected status, or when the request fails at the transport layer in a way that yields a partial response. The fields mirror the spec block in `docs/specs/S04-contracts.md` §HTTPError.

ResponseBody must only ever be populated via one of this file's newHTTPErrorFrom* constructors (newHTTPErrorFromRequestError, newHTTPErrorFromResponse, newHTTPErrorFromBody); each of them runs the raw registry body through redact.ScanContent and redact.RedactURLUserinfo before assignment. Constructing an HTTPError directly with an unscrubbed body bypasses that pipeline, and the field itself is then the exposure: it is rendered into failure details verbatim.

The body-derived accessors are NOT a second route to it. Code, HasCode and Codes all read the constructor-computed envelope and never touch ResponseBody, so a directly-constructed value reports no codes at all rather than codes parsed out of an unscrubbed body (TestHTTPError_CodesReportNoVerdictOnDirectConstruction pins this). That is the whole point of computing the verdict in the constructor. Error() is not a body route either — it renders only Method, scrubURL(URL) and StatusCode, so it is an egress route for URL userinfo, not for the body.

func (*HTTPError) Allow added in v1.69.0

func (e *HTTPError) Allow() (value string, ok bool)

Allow reports the `Allow` response header of the response that produced this error, alongside whether the response carried the header at all. The two halves are separate because S08 §Write refusal per format distinguishes them: a refused write on a route that shares its path with a readable resource carries `Allow: GET, HEAD`, while a write-only route carries an `Allow` that is *present and empty*, because the route has no method left to name and RFC 9110 §15.5.6 still obliges the header when a 405 is generated. A value-only accessor collapses that case into "no header", so a target that omits the header entirely would pass the write-only assertion vacuously.

ok is false for an *HTTPError built from a transport failure (newHTTPErrorFromRequestError), which never saw a response, and for one constructed directly.

func (*HTTPError) Code added in v1.54.0

func (e *HTTPError) Code() string

Code returns the first error's `code` field from the OCI error envelope the constructor parsed out of the registry's raw body (see Codes). Returns "" when no constructor computed a verdict, when the body did not parse as that shape, or when it parsed and carried no errors; Codes is the accessor that separates those.

The source is the constructor-computed envelope, NOT ResponseBody: ResponseBody has been through redaction and truncation by the time anyone can read it, and a code parsed back out of it would report the scrub's damage as the registry's shape. A directly-constructed HTTPError therefore reports "" however well its ResponseBody parses.

A protocol-level response parser belongs at this layer, not the test layer (docs/dev/architecture.md §Code placement), so callers compare against the pinned error-code constants here rather than parsing a body themselves.

func (*HTTPError) Codes added in v1.64.0

func (e *HTTPError) Codes() (codes []string, ok bool)

Codes reports every entry's `code` field from the OCI error envelope the registry sent (S07 §Error response format: `{"errors":[{"code":"...","message":"..."}]}`), in order, alongside whether that body decoded as JSON compatible with the shape.

ok is true for any JSON that decodes into the envelope, which includes JSON carrying no `errors` array at all: `{}`, `{"foo":1}`, `{"errors":null}`, and the literal `null` each yield ok == true with an empty, non-nil codes. ok is false wherever encoding/json rejects the body. Only `code` is declared, so an entry whose `message` is not a string still decodes rather than failing the parse: the one classification this method does not inherit from the conformance-layer parser it replaced (plan Step 26a amendment).

Unlike Code and HasCode, which collapse "did not decode" and "decoded but carries no errors[] entry" into the same empty/false result, Codes lets a caller distinguish the two — needed where a spec AC treats a non-JSON body and a JSON-but-empty-envelope body differently (e.g. S07 AC #42's non-JSON carve-out). Plan Step 26a amendment (docs/plans/2026-05-26-oci-format.md).

The verdict is computed by the constructor from the RAW registry body, before redaction and truncation, and this method returns it verbatim. That ordering is what makes ok trustworthy: redact.ScanContent can turn a valid JSON body into an undecodable one (its doc carries the shape), so a verdict taken from ResponseBody would report the harness's own damage as the registry's, and testErrorResponseFormat would pass a non-conformant registry through AC #42's carve-out.

An HTTPError not built by one of the two constructors that parse an envelope — newHTTPErrorFromResponse and newHTTPErrorFromBody — carries no verdict, and this method then reports (nil, false). That includes newHTTPErrorFromRequestError, which wraps a transport failure and has no registry body to classify, as well as a directly-constructed value. It deliberately does NOT fall back to parsing ResponseBody. A fallback would make the verdict's source depend invisibly on how the value was constructed, which is the ambiguity this method exists to remove, and on that path ResponseBody has not been through the scrubbing pipeline, so the codes could carry credential-bearing content (see HTTPError's doc). Build errors through a constructor; ExportNewHTTPErrorFromBody (export_test.go) is the seam for this package's own external tests.

One residual: the body is read under a cap of responseBodyCap plus the truncation marker, so an error envelope larger than that is truncated before the parse and reports ok == false. Raising the read cap is an OOM-guard decision (pkg/client/maven uses a separate, larger framingReadCap for its own verdict) and is not made here.

func (*HTTPError) Error

func (e *HTTPError) Error() string

Error implements the error interface for *HTTPError. The format is stable for end-user display; it must not include any value drawn from the Authorization request header.

func (*HTTPError) HasBearerRealmChallenge added in v1.62.0

func (e *HTTPError) HasBearerRealmChallenge() bool

HasBearerRealmChallenge reports whether the captured WWW-Authenticate header (see WWWAuthenticate above) is a well-formed RFC 6750 Bearer challenge carrying a non-empty realm parameter, reusing this package's own parseWWWAuthenticate (auth.go) rather than making every caller re-parse the header itself. A protocol-level response parser belongs at this layer, not the test layer (docs/dev/architecture.md §Code placement), mirroring Code()'s identical rationale.

func (*HTTPError) HasCode added in v1.58.0

func (e *HTTPError) HasCode(code string) bool

HasCode reports whether any error in the constructor-computed OCI error envelope (see Codes) carries the given code. Unlike Code, which only inspects the first entry, HasCode lets a caller accept a registry that reports multiple errors in one response — needed where a spec AC requires "one or more" errors of a given code rather than pinning the code to a specific position in the list.

Same source and same consequence as Code: the envelope, never ResponseBody, so a directly-constructed HTTPError reports false for every code.

func (*HTTPError) TerminalMethod added in v1.69.0

func (e *HTTPError) TerminalMethod() (value string, ok bool)

TerminalMethod reports the verb of the request that produced this error's response, alongside whether a response arrived at all.

Prefer it over the Method field wherever the question is "what did the origin actually answer", rather than "what did this client ask for". The two differ only when a redirect was followed, and that is the case S08 §Preflight's cause sort has to see: a write replayed as a GET stored nothing whatever status came back.

ok is true only for an error `attempt` built from a response it rejected, which is the one construction site that holds the *http.Response. It is false for the transport envelope (newHTTPErrorFromRequestError), for a value a caller constructed itself, and for newHTTPErrorFromBody, which builds from a body the caller already read and no longer has the request to ask. That last one saw a response, so "absent" here does not mean "no response arrived"; it means this package cannot answer. Its users are the realm exchange and the tag and referrers reads, all GETs whose terminal verb equals the sent one, so a caller falling back to Method gets the same answer either way.

func (*HTTPError) WWWAuthenticate added in v1.62.0

func (e *HTTPError) WWWAuthenticate() string

WWWAuthenticate returns the raw WWW-Authenticate response header value captured from the 401 response that produced this error, or "" when none was captured (a non-401 error, or a 401 whose response carried no such header). Exposed so a caller outside pkg/client/oci can check S07 AC #47's RFC 6750 alternative (`WWW-Authenticate: Bearer realm="…"`) for oci.negative.unauthenticated: unlike Error() and ResponseBody, which deliberately never render this value (see the wwwAuthenticate field doc above), this accessor is the one sanctioned egress route for it. A caller that surfaces the returned value further (a TestCase message, an HTTPDetail) must not re-introduce the channels S07 §Authentication forbids. Plan Step 26b amendment (docs/plans/2026-05-26-oci-format.md).

type ImageIndex added in v1.52.0

type ImageIndex struct {
	// SchemaVersion is the image index schema version (2 for every
	// Phase 1 registry).
	SchemaVersion int `json:"schemaVersion"`

	// MediaType is the image index media type
	// (`application/vnd.oci.image.index.v1+json`).
	MediaType string `json:"mediaType"`

	// Manifests lists every referrer the registry returned. Empty
	// (non-nil after a successful decode of a spec-shaped `[]` body)
	// when the subject digest has no referrers.
	Manifests []ReferrerDescriptor `json:"manifests"`
}

ImageIndex is the OCI image index body a referrers-list response carries (S07 §Referrers API (v1.1); OCI Image Format Specification §Image Index).

type ManifestResponse added in v1.42.0

type ManifestResponse struct {
	// StatusCode is the HTTP status of the accepted (200) response.
	StatusCode int

	// Body is the manifest bytes verbatim, exactly as GetManifest read
	// them off the wire (no JSON re-marshaling), so callers can assert
	// byte-exact storage (AC #34). Empty for HeadManifest (RFC 9110
	// §9.3.2: a HEAD response carries no body).
	Body []byte

	// ContentType is the response Content-Type header, verbatim
	// (S07 §`http.content-type`; AC #27).
	ContentType string

	// ContentLength is the response Content-Length. -1 when the server
	// did not declare one (net/http's sentinel for unknown length).
	ContentLength int64

	// DockerContentDigest is the `Docker-Content-Digest` response header
	// (S07 §`Docker-Content-Digest` response header; AC #10, #11, #26).
	DockerContentDigest string

	// Digest is the S07 AC #26 Docker-Content-Digest verdict (plan
	// Step 28). For GetManifest, Expected is the digest of the received
	// Body. For HeadManifest, Expected is the digest from the request
	// path when reference is a digest, and empty when reference is a
	// tag (HeadManifest carries no expected digest for a tag reference;
	// see this file's package doc and DigestVerdict's doc for the
	// HEAD-by-tag test-layer split).
	Digest DigestVerdict
}

ManifestResponse is the result of a manifest GET or HEAD (S07 §Operation: manifest GET / HEAD).

type PutManifestResponse added in v1.42.0

type PutManifestResponse struct {
	// StatusCode is the HTTP status of the accepted (201) response.
	StatusCode int

	// Location is the canonical `/v2/<name>/manifests/<digest>` URL the
	// registry echoes back, populated on both push-by-tag and
	// push-by-digest.
	Location string

	// DockerContentDigest is the `Docker-Content-Digest` response header;
	// it MUST equal the digest the client computed locally over the
	// pushed bytes (S07 §`Docker-Content-Digest` response header; AC #7,
	// #8, #26).
	DockerContentDigest string

	// OCISubject is the `OCI-Subject` response header, present when the
	// pushed manifest carries a `subject` field and the registry
	// implements OCI v1.1 (S07 §`subject` field (v1.1); AC #19, #20).
	// Empty when the response omits the header (v1.0 registry, or the
	// pushed manifest carried no `subject` field).
	OCISubject string

	// Digest is the S07 AC #26 Docker-Content-Digest verdict: Expected
	// is the digest of the pushed body (the client already has it),
	// compared against the DockerContentDigest header above (plan
	// Step 28; see DigestVerdict's doc for the zero-verdict cases).
	Digest DigestVerdict

	// Method is the verb of the request that produced this response,
	// read from the terminal response's own Request: a followed 301,
	// 302 or 303 replays the manifest PUT as a GET, and net/http
	// reports that GET here. BlobUploadResult.Method carries the same
	// field for the blob-upload writes and states the full rationale.
	Method string
}

PutManifestResponse is the result of a manifest PUT (S07 §Operation: manifest PUT).

type ReferrerDescriptor added in v1.52.0

type ReferrerDescriptor struct {
	// MediaType is the referrer manifest's media type.
	MediaType string `json:"mediaType"`

	// Digest is the referrer manifest's content digest.
	Digest string `json:"digest"`

	// Size is the referrer manifest's byte size.
	Size int64 `json:"size"`

	// ArtifactType is the referrer's artifact type, when the manifest
	// carries one. Empty when absent (S07 AC #23's `artifactType`
	// filter matches against this field).
	ArtifactType string `json:"artifactType,omitempty"`

	// Annotations is the referrer's annotations map, when present. Nil
	// when absent.
	Annotations map[string]string `json:"annotations,omitempty"`
}

ReferrerDescriptor is one entry in an OCI image index's `manifests` array (S07 §Referrers API (v1.1); OCI Image Format Specification §Image Index).

type ReferrersResponse added in v1.52.0

type ReferrersResponse struct {
	// StatusCode is the HTTP status of the accepted (200) response.
	StatusCode int

	// ContentType is the response Content-Type header, verbatim (S07
	// §Referrers API (v1.1) / "Probe to distinguish v1.1 from v1.0": a
	// v1.1 registry's 200 response carries
	// "application/vnd.oci.image.index.v1+json"; callers distinguishing
	// v1.1 from a 200 that isn't actually an image index compare this
	// against client.IndexMediaType.
	ContentType string

	// Index is the decoded image index body (AC #22).
	Index ImageIndex

	// FiltersApplied is the parsed `OCI-Filters-Applied` response
	// header (S07 §Referrers API (v1.1); AC #23): the header value
	// split on `,` with surrounding whitespace trimmed from each
	// entry. Nil when the header is absent (no filter applied, or the
	// registry ignores the filter).
	FiltersApplied []string
}

ReferrersResponse is the result of a referrers-list operation (S07 §Operation: referrers list (v1.1)).

type TagListResponse added in v1.46.0

type TagListResponse struct {
	// Name is the repository name echoed back in the response body.
	Name string

	// Tags is the tag list returned by the registry, in the order the
	// response body carried it. The registry sorts tags lexically per
	// S07 §Tags (AC #12); the client does not re-sort.
	Tags []string

	// NextLink is the target URL of an RFC 5988
	// `Link: <url>; rel="next"` response header, or empty when the
	// header is absent or carries no `rel="next"` entry (S07
	// §Operation: tag list / `Link` header; AC #13).
	NextLink string
}

TagListResponse is the parsed result of a tag-list operation (S07 §Operation: tag list).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL