registry

package
v0.1.0-alpha.8 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package registry implements Zattera's embedded OCI image registry (spec §3.5). T-31 covers the content-addressed blob store and the OCI push protocol (blob uploads); manifests, tags, pull, auth and GC arrive in T-32.

Blob storage is content-addressed by digest, so identical layers — shared across the architectures of a multi-arch image, or across repos — are stored exactly once. Nothing here is keyed by repository or platform; the push path is media-type agnostic (an image index is just bytes at PUT time, its validation is T-32's job).

Index

Constants

View Source
const (
	MediaTypeOCIManifest    = "application/vnd.oci.image.manifest.v1+json"
	MediaTypeOCIIndex       = "application/vnd.oci.image.index.v1+json"
	MediaTypeDockerManifest = "application/vnd.docker.distribution.manifest.v2+json"
	MediaTypeDockerList     = "application/vnd.docker.distribution.manifest.list.v2+json"
)

OCI / Docker media types we accept and produce. Manifest lists and image indexes are the multi-arch descriptors; a single-arch image is a plain manifest.

Variables

View Source
var (
	ErrBlobUnknown         = errors.New("registry: blob unknown")
	ErrUploadUnknown       = errors.New("registry: upload unknown")
	ErrDigestInvalid       = errors.New("registry: invalid digest")
	ErrDigestMismatch      = errors.New("registry: digest mismatch")
	ErrRangeNotSatisfiable = errors.New("registry: range not satisfiable")
)

Sentinel errors returned by the store and upload manager. The HTTP layer maps these onto OCI error codes.

View Source
var (
	ErrManifestUnknown   = errors.New("registry: manifest unknown")
	ErrManifestInvalid   = errors.New("registry: manifest invalid")
	ErrManifestBlobUnkn  = errors.New("registry: referenced blob unknown")
	ErrManifestChildUnkn = errors.New("registry: referenced child manifest unknown")
)

Manifest-layer sentinel errors, mapped to OCI error codes by the HTTP layer.

Functions

func RemotePlatforms

func RemotePlatforms(ctx context.Context, imageRef string) ([]string, error)

RemotePlatforms best-effort inspects an EXTERNAL image reference (docker hub, ghcr, …) and returns the OCI platforms it can run on: an index/manifest list's child platforms, or the single platform read from a plain manifest's config. It speaks anonymous distribution-API auth (bearer token challenge). Callers treat ANY error as "unknown" — a deploy must never fail over manifest inspection (T-88).

func SplitRepoRef

func SplitRepoRef(path string) (repo, ref string)

SplitRepoRef splits a host-less image path ("proj/app:tag", "proj/app@sha256:…") into repository and tag-or-digest ("latest" when bare).

Types

type AuthFunc

type AuthFunc func(username, password string) bool

AuthFunc adapts a plain function to the Authenticator interface.

func (AuthFunc) Authenticate

func (f AuthFunc) Authenticate(username, password string) bool

Authenticate implements Authenticator.

type Authenticator

type Authenticator interface {
	// Authenticate reports whether the username/password pair is valid.
	Authenticate(username, password string) bool
}

Authenticator verifies registry HTTP basic-auth credentials. The registry package stays decoupled from cluster state: the daemon supplies an implementation backed by node registry credentials (KV registry/creds/<id>, created at join in T-17) and user personal access tokens (zpat_… as the password). A nil Authenticator disables auth (dev / tests).

type BlobStore

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

BlobStore keeps immutable, digest-addressed blobs on disk. Layout:

<root>/blobs/sha256/<first2>/<hex>   committed blobs
<root>/uploads/<id>                  in-progress upload temp files

Writes go to a temp file, get digest-verified, then atomically rename into place — a crash mid-upload can never surface as a corrupt blob.

func NewBlobStore

func NewBlobStore(root string) (*BlobStore, error)

NewBlobStore prepares the blob and upload directories under root (which is typically <data-dir>/registry).

func (*BlobStore) Delete

func (s *BlobStore) Delete(dgst string) error

Delete removes a blob by digest. A missing blob is not an error (GC calls this idempotently).

func (*BlobStore) Has

func (s *BlobStore) Has(dgst string) bool

Has reports whether a committed blob exists for the digest.

func (*BlobStore) Open

func (s *BlobStore) Open(dgst string) (*os.File, error)

Open returns a read handle to a committed blob. ErrBlobUnknown if absent. The caller closes the returned file.

func (*BlobStore) Stat

func (s *BlobStore) Stat(dgst string) (int64, error)

Stat returns the byte size of a committed blob. ErrBlobUnknown if absent.

func (*BlobStore) Write

func (s *BlobStore) Write(r io.Reader) (string, int64, error)

Write streams r into the blob store, computing its digest as it goes, and commits it under that digest. Used for content whose digest we do not know in advance (manifests and image indexes, which are addressed by the sha256 of their own bytes). Returns the resulting digest and byte size.

type Fetcher

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

Fetcher pulls blobs and manifests from peer control nodes on demand.

func NewFetcher

func NewFetcher(peers PeerSource, creds PeerCredentials, client *http.Client) *Fetcher

NewFetcher builds a pull-through fetcher. A nil PeerSource (or one that returns no peers) makes every method a no-op miss, which is exactly what a single-node cluster wants.

func (*Fetcher) FetchBlob

func (f *Fetcher) FetchBlob(ctx context.Context, store *BlobStore, repo, dgst string) error

FetchBlob fetches dgst from the first peer that has it and commits it to store. It returns nil when the blob is now present locally. Callers re-read from the local store afterwards: committing first and serving from disk keeps the write path atomic (BlobStore.Write digest-verifies and renames) and avoids half-serving a blob whose transfer failed midway.

func (*Fetcher) FetchManifest

func (f *Fetcher) FetchManifest(ctx context.Context, man *Manifests, store *BlobStore, repo, ref string) error

FetchManifest fetches a manifest from a peer and registers it locally so the tag index and refcount graph stay correct (a manifest is a blob plus bookkeeping — copying only the bytes would leave GC blind to its children).

ref may be a tag or a digest. A tag is resolved on the peer first and the local tag is bound only to the digest the peer actually served, so a stale or racing tag on one node cannot silently repoint another node's tag.

type Handler

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

Handler serves the OCI distribution API: the version probe and blob upload flow (T-31) plus manifests, tags, pull and delete (T-32). A nil manifests store leaves the manifest routes disabled (the T-31-only surface); a nil Authenticator disables auth.

func NewHandler

func NewHandler(store *BlobStore, uploads *Uploads, log *slog.Logger) *Handler

NewHandler wires the blob/upload push surface (T-31) with no manifest store or auth. Use newHandler (or registry.New) for the full T-32 surface.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type Manifests

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

Manifests stores image manifests and indexes (as content-addressed blobs) plus the tag index and a reference-counted object graph for GC.

func NewManifests

func NewManifests(dir string, blobs *BlobStore, clk clock.Clock) (*Manifests, error)

NewManifests opens (creating if needed) the registry metadata db under dir and prepares its buckets.

func (*Manifests) Close

func (m *Manifests) Close() error

Close releases the metadata db.

func (*Manifests) DeleteManifest

func (m *Manifests) DeleteManifest(repo, digest string) error

DeleteManifest removes a manifest by digest from repo: it untags every tag in the repo pointing at it and releases those references (cascading GC). Absent manifests return ErrManifestUnknown.

func (*Manifests) GetManifest

func (m *Manifests) GetManifest(repo, ref string) (body []byte, mediaType, digest string, err error)

GetManifest returns the stored bytes, media type and digest for a reference.

func (*Manifests) Platforms

func (m *Manifests) Platforms(repo, ref string) ([]string, error)

Platforms lists the OCI platform strings a reference can run on: an index's child platforms, or a single-element list for a plain manifest read from its config blob (empty when unknown). Used by T-88 to populate Release.platforms.

func (*Manifests) PutManifest

func (m *Manifests) PutManifest(repo, ref, contentType string, body []byte) (string, error)

PutManifest validates and stores a manifest or image index under repo, then (when ref is a tag) points the tag at it. Returns the manifest digest.

Validation: an image manifest requires its config and every layer blob to already exist; an index requires every referenced child manifest to already exist in this repo (children are pushed before the index in OCI push order).

func (*Manifests) Resolve

func (m *Manifests) Resolve(repo, ref string) (string, error)

Resolve turns a tag or digest reference into a concrete manifest digest that exists in repo. ErrManifestUnknown when absent.

func (*Manifests) ResolveManifest

func (m *Manifests) ResolveManifest(repo, ref, platform string) (digest, mediaType string, err error)

ResolveManifest maps (repo, ref, platform) to the concrete manifest digest a client should pull for that platform: for an index it returns the matching child; for a plain manifest (or empty platform) it returns the reference itself. This lets the control plane learn a release's arch without a docker client (T-88).

func (*Manifests) Tags

func (m *Manifests) Tags(repo string) ([]string, error)

Tags lists the tags defined in repo (sorted by bbolt key order).

func (*Manifests) UntagAndSweep

func (m *Manifests) UntagAndSweep(repo, tag string) error

UntagAndSweep removes a tag and reclaims anything it solely kept alive. This is the retention hook (T-38): drop an old release's image tag and let GC free its blobs.

type Peer

type Peer struct {
	// NodeID identifies the peer (logging only).
	NodeID string
	// BaseURL is its registry root, e.g. "https://10.90.0.2:5000".
	BaseURL string
}

Peer is one other control node's registry endpoint.

type PeerCredentials

type PeerCredentials struct {
	Username string
	Password string
}

PeerCredentials is the identity a node presents to a peer's registry. In production this is the node's own "node-<id>" credential (already minted at join and validated by the registry authenticator) over the mesh with the cluster CA — never anonymous, never a user PAT.

type PeerSource

type PeerSource interface {
	Peers() []Peer
}

PeerSource lists the OTHER control-node registries this node may fetch from. It is resolved per call from cluster state (never a static list) so it follows joins, removals and cordons without restarts. Returning nil disables pull-through — which is the single-node and dev case.

type PeerSourceFunc

type PeerSourceFunc func() []Peer

PeerSourceFunc adapts a plain function to PeerSource.

func (PeerSourceFunc) Peers

func (f PeerSourceFunc) Peers() []Peer

Peers implements PeerSource.

type Registry

type Registry struct {
	Blobs     *BlobStore
	Uploads   *Uploads
	Manifests *Manifests
	// contains filtered or unexported fields
}

Registry bundles the embedded OCI registry's storage (blobs, upload sessions, manifests) behind a single http.Handler mounted on :5000 by the daemon on control nodes.

func New

func New(dir string, clk clock.Clock, auth Authenticator, log *slog.Logger) (*Registry, error)

New opens the registry rooted at dir (typically <data-dir>/registry). auth may be nil to disable authentication (dev / tests).

func (*Registry) Close

func (rg *Registry) Close() error

Close releases the manifest metadata database.

func (*Registry) EnablePullThrough

func (rg *Registry) EnablePullThrough(peers PeerSource, creds PeerCredentials, client *http.Client)

EnablePullThrough makes this registry fetch blobs and manifests it lacks from peer control nodes (T-101). Safe to leave unset: without it a miss is simply a miss, which is the single-node and dev behaviour.

func (*Registry) Handler

func (rg *Registry) Handler() http.Handler

Handler returns the OCI distribution HTTP handler.

func (*Registry) ReapUploads

func (rg *Registry) ReapUploads() int

ReapUploads expires idle upload sessions; call it periodically from a janitor.

type Upload

type Upload struct {
	ID      string
	Started time.Time
	// contains filtered or unexported fields
}

Upload is one in-progress, resumable blob upload. Bytes are streamed to a temp file while a running sha256 is maintained, so finalize can verify the client-declared digest without re-reading the file. Uploads are sequential: a PATCH must continue exactly where the previous chunk ended.

func (*Upload) Offset

func (u *Upload) Offset() int64

Offset returns the number of bytes accepted so far (the next expected Content-Range start).

type Uploads

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

Uploads tracks live upload sessions and owns their lifecycle against the backing BlobStore.

func NewUploads

func NewUploads(store *BlobStore, clk clock.Clock) *Uploads

NewUploads constructs the upload manager. The clock drives session expiry.

func (*Uploads) Append

func (m *Uploads) Append(id string, r io.Reader, start int64) (int64, error)

Append writes r to the session's temp file. When start >= 0 it is a declared Content-Range start and MUST equal the session's current offset (uploads are strictly sequential); start < 0 means "append at the current offset" (a monolithic PATCH with no Content-Range). Returns the new offset.

func (*Uploads) Cancel

func (m *Uploads) Cancel(id string) error

Cancel aborts an upload session, deleting its temp file.

func (*Uploads) Finalize

func (m *Uploads) Finalize(id, expected string, trailing io.Reader) (string, error)

Finalize appends any trailing bytes, verifies the running digest against the client-declared expected digest, then commits the blob. On success the session is closed and removed. On a digest mismatch the session is discarded (its temp file deleted) so a bad upload can never linger or corrupt a blob.

func (*Uploads) Get

func (m *Uploads) Get(id string) (*Upload, bool)

Get returns a live session by id.

func (*Uploads) Ingest

func (m *Uploads) Ingest(r io.Reader, expected string) (string, error)

Ingest performs a one-shot monolithic upload (POST ?digest= or a PUT with the full body): start → append → finalize in a single call.

func (*Uploads) Reap

func (m *Uploads) Reap() int

Reap deletes sessions idle longer than uploadTTL and returns how many were removed. Intended to be called periodically by a janitor.

func (*Uploads) Start

func (m *Uploads) Start() (*Upload, error)

Start opens a new upload session with an empty temp file.

Jump to

Keyboard shortcuts

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