Documentation
¶
Overview ¶
Package puboci reads a local OCI layout and publishes digest-addressed content and tags.
ReadLayout loads an extracted oci-image/layout directory. Prepare plans tags through StateReader, pushes content through ContentPusher, and signs the published index through Signer. Finalize collects fresh registry state, refuses drift from the prepare observations, and commits tags through TagCommitter only after those checks succeed.
Index ¶
- Constants
- Variables
- func BlobPath(digest rel.Digest) (string, error)
- func CollectState(ctx context.Context, reader StateReader, image Image, version rel.Version, ...) (rel.ChannelState, error)
- func PlanTags(ctx context.Context, reader StateReader, image Image, version rel.Version, ...) (rel.TagPlan, error)
- type AttestationSubject
- type ContentPusher
- type Descriptor
- type DigestRef
- type FinalizeInput
- type FinalizeResult
- type Image
- type Layout
- type OCIPrepareResult
- type Platform
- type PlatformImage
- type PrepareInput
- type Reference
- type Signer
- type SleepFunc
- type StateReader
- type TagCommitter
- type TagObservation
Constants ¶
const (
// FinalizeSchema is the versioned OCI finalize-result identifier.
FinalizeSchema = "release.dev/oci-finalize/v1"
)
const (
// PrepareSchema is the versioned OCI prepare-result identifier.
PrepareSchema = "release.dev/oci-prepare/v1"
)
Variables ¶
var ( // ErrNotAuthoritative reports that the prepare result is a dry-run document. ErrNotAuthoritative = errors.New("prepare result is not authoritative") // ErrStateDrift reports that registry state changed since preparation. ErrStateDrift = errors.New("registry state drifted since preparation") )
Sentinel errors returned by Finalize.
var ( // ErrTagAbsent reports that a tag does not resolve in the registry. ErrTagAbsent = errors.New("registry tag not found") // ErrRetryable reports a transient registry failure. [CollectState] does // not retry; the adapter classifies the failure and the caller decides // whether to try again. ErrRetryable = errors.New("retryable registry error") // ErrCorruptState reports that a present tag's version annotation is // missing or not a stable version. ErrCorruptState = errors.New("corrupt registry state") )
Sentinel errors classified for registry reads.
Functions ¶
func BlobPath ¶
BlobPath returns the fs.FS slash path of digest under blobs/sha256.
The digest must parse as sha256:<64 hex>. The result is valid for fs.ValidPath.
func CollectState ¶
func CollectState( ctx context.Context, reader StateReader, image Image, version rel.Version, digest rel.Digest, ) (rel.ChannelState, error)
CollectState reads the exact tag and every moving channel for version.
A nil context or reader is rejected. A cancelled context fails before the port is called. ErrTagAbsent from StateReader.Resolve is treated as an absent tag. Any other Resolve or Version error is wrapped with the reference and returned. Version is called only when a channel tag is present and its digest differs from digest, so a present differing tag never returns with HasVersion false. Transient failures classified as ErrRetryable are not retried. CollectState performs no registry writes.
func PlanTags ¶
func PlanTags( ctx context.Context, reader StateReader, image Image, version rel.Version, digest rel.Digest, ) (rel.TagPlan, error)
PlanTags collects registry state and decides which tags the candidate may apply.
It is CollectState followed by rel.PlanTags. A planner failure is returned unchanged so callers can inspect the rel sentinels. PlanTags performs no registry writes and does not retry transient failures.
Types ¶
type AttestationSubject ¶
type AttestationSubject struct {
// Platform is the os/architecture pair, such as linux/amd64.
Platform string `json:"platform"`
// Digest is the platform manifest digest.
Digest string `json:"digest"`
}
AttestationSubject is one platform manifest that later attestation names.
type ContentPusher ¶
type ContentPusher interface {
// PushBlob uploads one blob addressed by descriptor.Digest.
//
// An already-present blob is success. content is the blob bytes and is
// consumed at most once. Callers never pass a layer that has been
// buffered into memory.
PushBlob(ctx context.Context, image Image, descriptor Descriptor, content io.Reader) error
// PushManifest uploads one manifest or index addressed by descriptor.Digest.
//
// The registry stores content under descriptor.MediaType. An
// already-present manifest is success. content is the exact encoded
// document and is consumed at most once.
PushManifest(ctx context.Context, image Image, descriptor Descriptor, content io.Reader) error
// Verify resolves ref and requires the registry digest to equal ref.Digest.
//
// Missing content wraps [ErrTagAbsent]. Transient registry failures
// wrap [ErrRetryable]. A different resolved digest is a verification
// failure and is not classified as absent.
Verify(ctx context.Context, ref DigestRef) error
}
ContentPusher writes digest-addressed OCI content and checks that it resolves.
Pushes address content by digest and are idempotent: an already-present blob or manifest is success, not an error. Implementations stream io.Reader content and must not require the caller to buffer a layer. ContentPusher.Verify resolves DigestRef and fails unless the registry returns that same digest. Absent content is classified as ErrTagAbsent. Transient failures are classified as ErrRetryable and are not retried here.
type Descriptor ¶
type Descriptor struct {
// MediaType is the OCI media type of the referenced content.
MediaType string
// Digest is the content digest.
Digest rel.Digest
// Size is the content length in bytes.
Size int64
}
Descriptor is an OCI content descriptor.
func (Descriptor) Validate ¶
func (d Descriptor) Validate() error
Validate reports whether d has a media type, a parsable digest, and a non-negative size.
type DigestRef ¶
type DigestRef struct {
// Image is the untagged repository name.
Image Image
// Digest is the pinned content digest.
Digest rel.Digest
}
DigestRef is an image pinned to a content digest.
type FinalizeInput ¶
type FinalizeInput struct {
// Prepared is the authoritative document produced by [Prepare].
Prepared OCIPrepareResult
// Sleep waits between retryable commit and verification attempts. Nil
// selects a context-aware timer.
Sleep SleepFunc
}
FinalizeInput is a validated prepare document and an optional retry sleeper.
type FinalizeResult ¶
type FinalizeResult struct {
// Schema identifies the finalize-result version and is always [FinalizeSchema].
Schema string `json:"schema"`
// Image is the untagged repository name.
Image string `json:"image"`
// Version is the candidate MAJOR.MINOR.PATCH version.
Version string `json:"version"`
// IndexDigest is the image index digest.
IndexDigest string `json:"index_digest"`
// Applied are the tags written by this run, in plan order.
Applied []string `json:"applied"`
// Accepted are the tags already at the candidate digest, in plan order.
Accepted []string `json:"accepted"`
// Retained are the channels left on a newer release, in plan order.
Retained []string `json:"retained"`
}
FinalizeResult is the versioned document produced by publish oci finalize.
func Finalize ¶
func Finalize( ctx context.Context, input FinalizeInput, state StateReader, committer TagCommitter, ) (FinalizeResult, error)
Finalize collects fresh registry state, refuses drift, and commits planned tags.
It rejects a nil context, state reader, or committer and validates FinalizeInput.Prepared before any registry call. A document with Authoritative false fails with ErrNotAuthoritative. Fresh state is collected through CollectState; a serialized plan is never replayed. Each expected tag is compared against OCIPrepareResult.Observed. An unchanged observation is accepted. A tag whose prepared observation implied create (absent, or present at an older in-line version) and that is now present at the candidate index digest is treated as this publication's own partial progress. A prepared retain or accept that later sits on the candidate digest is ErrStateDrift. Any other change, including a disappeared tag or a tag present in only one set, fails with ErrStateDrift and names the tag, the prepared observation, and the fresh one. Tags are then replanned from the fresh state. TagCommitter.Commit is called once with rel.TagPlan.Apply and skipped entirely when nothing remains to write. Commit and the postcondition reads retry ErrRetryable at most four times with 1s, then 2s, then 4s of backoff. The exact version tag and every applied tag must resolve to the index digest through StateReader, not the committer's word. The result classifies every decision as Applied, Accepted, or Retained in plan order. A nil FinalizeInput.Sleep uses a context-aware timer.
type Image ¶
type Image string
Image is a lowercase untagged registry repository name.
The only constructor is ParseImage. The zero value is invalid.
func ParseImage ¶
ParseImage constructs an Image from a lowercase registry reference.
The grammar is host/path[/path...] with no scheme, tag, or digest. The host is the text before the first slash and may include a port. At least one path element is required. Empty input, uppercase letters, a scheme, a :tag suffix on the last element, an @digest, leading or trailing slashes, empty path elements, and spaces are rejected.
type Layout ¶
type Layout struct {
// Index is the descriptor of the exact index.json bytes.
Index Descriptor
// IndexBytes is the exact index.json contents, retained for push.
IndexBytes []byte
// Platforms are the index manifests in file order.
Platforms []PlatformImage
// Blobs are unique config and layer descriptors in first-seen push order.
Blobs []Descriptor
}
Layout is a validated local OCI image layout rooted at oci-image/layout.
func ReadLayout ¶
ReadLayout loads an extracted oci-image/layout directory from fsys.
fsys is a fs.FS rooted at the layout directory. A regular oci-layout file must exist. index.json is read verbatim; its descriptor digest is SHA-256 over those exact bytes and its size is their length. The index must use schemaVersion 2 and media type ocispec.MediaTypeImageIndex and must list at least one manifest. Each manifest descriptor is validated and must name a platform with a non-empty OS and architecture. Its blob must exist as a regular file of the declared size, and its config and layer blobs are collected the same way. Duplicate digests keep the first descriptor; a later descriptor with a different size or media type is an error. Layer and config blobs are never buffered. index.json and manifests are buffered up to [jsonLimitBytes].
type OCIPrepareResult ¶
type OCIPrepareResult struct {
// Schema identifies the prepare-result version and is always [PrepareSchema].
Schema string `json:"schema"`
// Authoritative is false for --dry-run and true after a real prepare.
Authoritative bool `json:"authoritative"`
// Image is the untagged repository name.
Image string `json:"image"`
// Version is the candidate MAJOR.MINOR.PATCH version.
Version string `json:"version"`
// IndexDigest is the image index digest.
IndexDigest string `json:"index_digest"`
// Platforms are the layout's platform manifests in layout order.
Platforms []AttestationSubject `json:"platforms"`
// Observed is the exact tag, then minor, major, and latest.
Observed []TagObservation `json:"observed"`
}
OCIPrepareResult is the versioned document produced by publish oci prepare.
func NewPrepareResult ¶
func NewPrepareResult( image Image, version rel.Version, index rel.Digest, platforms []PlatformImage, state rel.ChannelState, authoritative bool, ) OCIPrepareResult
NewPrepareResult renders a prepare document from domain values.
Platforms stay in the order supplied by the layout. Observed is the exact tag followed by each channel from rel.ChannelsFor. A channel missing from state.Channels is recorded as absent.
func ParsePrepareResult ¶
func ParsePrepareResult(r io.Reader) (OCIPrepareResult, error)
ParsePrepareResult decodes one prepare document from r and validates it.
Decoding rejects unknown fields. Documents are limited to [jsonLimitBytes].
func Prepare ¶
func Prepare( ctx context.Context, input PrepareInput, state StateReader, pusher ContentPusher, signer Signer, ) (OCIPrepareResult, error)
Prepare reads a local OCI layout, plans tags, and publishes digest-addressed content.
It fails closed before any write when the layout index digest does not match PrepareInput.IndexDigest, when CollectState fails, or when rel.PlanTags reports an immutable-tag or corrupt-channel conflict. A dry run returns NewPrepareResult with Authoritative false and never calls pusher or signer; those ports may be nil only in that mode. A real prepare pushes every layout blob, then each platform manifest, then the index, verifies the index and every platform digest (a deliberate strengthening of the workflow, which verifies only the index), and signs the index recursively. Each push and verification is attempted at most four times. Failures wrapping ErrRetryable wait 1s, then 2s, then 4s, and reopen the layout blob so the stream starts at byte zero. Other errors fail immediately. Context cancellation returns immediately. A nil PrepareInput.Sleep uses a context-aware timer. Errors name the failing step and descriptor digest and wrap the underlying error.
func (OCIPrepareResult) Validate ¶
func (r OCIPrepareResult) Validate() error
Validate reports whether r is a well-formed prepare document.
It rejects a schema other than PrepareSchema, an empty image, an unparsable version or index digest, an empty platform list, a platform subject with an empty platform or an unparsable digest, and an absent tag observation that still carries a digest.
type Platform ¶
type Platform struct {
// OS is the image operating system, such as linux.
OS string
// Architecture is the image CPU architecture, such as amd64.
Architecture string
}
Platform is an OCI image OS and architecture pair.
type PlatformImage ¶
type PlatformImage struct {
// Descriptor is the index's descriptor for this platform manifest.
Descriptor Descriptor
// Platform is the OS and architecture recorded on that descriptor.
Platform Platform
}
PlatformImage is one platform manifest listed by an image index.
type PrepareInput ¶
type PrepareInput struct {
// Image is the untagged repository that will receive the content.
Image Image
// Version is the candidate MAJOR.MINOR.PATCH release.
Version rel.Version
// IndexDigest is the expected index digest, cross-checked against the layout.
IndexDigest rel.Digest
// Layout is a filesystem rooted at the extracted oci-image/layout directory.
Layout fs.FS
// DryRun skips every write, verification, and signature.
DryRun bool
// Sleep waits between retryable publication attempts. Nil selects a
// context-aware timer.
Sleep SleepFunc
}
PrepareInput is the candidate image, layout, and expected index digest.
type Reference ¶
type Reference struct {
// Image is the untagged repository name.
Image Image
// Tag is the exact or channel tag.
Tag rel.Tag
}
Reference is an image together with one tag.
type Signer ¶
type Signer interface {
// SignRecursive signs ref and every referenced platform manifest.
//
// ref is the published index. The call writes signatures only; it does
// not mutate tags.
SignRecursive(ctx context.Context, ref DigestRef) error
}
Signer attaches signatures to a published image index.
Signer.SignRecursive signs the index at ref and every manifest that index references. Implementations invoke `cosign sign --yes --recursive` against image@digest. Sign failures are returned as received; this port does not classify them as ErrRetryable.
type StateReader ¶
type StateReader interface {
// Resolve returns the digest currently pointed at by ref.
//
// An error wrapping [ErrTagAbsent] means the tag is not present.
Resolve(ctx context.Context, ref Reference) (rel.Digest, error)
// Version returns the org.opencontainers.image.version annotation at ref.
//
// Callers invoke Version only when ref is present and resolves to a
// digest other than the candidate. An error wrapping [ErrCorruptState]
// means the annotation is missing or not a stable version.
Version(ctx context.Context, ref Reference) (rel.Version, error)
}
StateReader reads current registry tag state.
Implementations classify not-found as ErrTagAbsent, transient failures as ErrRetryable, and unusable version annotations as ErrCorruptState.
type TagCommitter ¶
type TagCommitter interface {
// Commit applies tags to digest serially, verifying each one.
Commit(ctx context.Context, image Image, digest rel.Digest, tags []rel.Tag) error
}
TagCommitter applies tags to a published digest serially.
TagCommitter.Commit writes each tag in order and verifies that it resolves to digest. Implementations must not apply tags in parallel. Transient failures wrap ErrRetryable. Absent content wraps ErrTagAbsent. Errors must not contain credentials or full URLs.
type TagObservation ¶
type TagObservation struct {
// Tag is the registry tag.
Tag string `json:"tag"`
// Scope is exact, minor, major, or latest.
Scope string `json:"scope"`
// Present reports whether the tag currently resolves.
Present bool `json:"present"`
// Digest is the resolved digest. It is omitted when the tag is absent.
Digest string `json:"digest,omitempty"`
// Version is the annotated version. It is omitted when none was read.
Version string `json:"version,omitempty"`
}
TagObservation is the registry state of one exact or channel tag.