registry

package
v0.1.0 Latest Latest
Warning

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

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

Documentation

Overview

Package registry resolves tags to digests and lists tags, behind an ordered, explicit credential chain.

It is the "Container registry" surface of docs/repo-map.md: read-only, and the only place registry credentials exist (R-002). A credential is obtained from one source at a time — env, keychain, cluster, op, in the caller's order, then anonymous — and tried against the registry; the first source whose request succeeds is remembered for the life of the Client and reported by name through AuthSourceUsed ("cluster secret app-staging/ghcr-pull"). No input, output, error or report carries a token, a password or a username read from a source: registry errors are reduced to their status and error codes (never the response body), transport errors go through pkg/redact, and every credential value the chain has seen is scrubbed from every message as a second guard.

The op source runs a program (`op read <ref>`). It runs only when the caller configured an OpRef and listed op in the order; an unconfigured source is skipped and reported as "not configured", never guessed at (AGENTS.md §4.3).

Head returns the digest a pull by tag would pin: for a multi-arch image that is the image index digest, which is also what the container runtime reports in a pod's imageID for the same tag (containerd and Docker both record the digest of the manifest the tag resolved to). TestHeadMultiArchReturnsIndexDigest pins the choice.

Index

Constants

This section is empty.

Variables

DefaultAuthOrder is the chain when the caller states none.

Functions

This section is empty.

Types

type AuthConfig

type AuthConfig struct {
	// Order is the chain; empty means DefaultAuthOrder.
	Order []AuthSource
	// ClusterSecret is "namespace/name" of the pull secret the cluster source reads.
	// Empty means the cluster source is skipped.
	ClusterSecret string
	// OpRef is the 1Password reference the op source reads. Empty means op is skipped and
	// nothing is executed.
	OpRef string
	// Cluster reads ClusterSecret; required when ClusterSecret is set.
	Cluster k8s.Cluster
	// Keychain is the keychain source; nil means go-containerregistry's DefaultKeychain
	// (Docker config and credential helpers). Tests substitute one.
	Keychain authn.Keychain
	// Transport is the HTTP transport; nil means go-containerregistry's default. Tests
	// point it at an in-memory registry.
	Transport http.RoundTripper
}

AuthConfig is how a Client obtains credentials. It names sources; it holds no secret.

type AuthReporter

type AuthReporter interface {
	AuthSourceUsed() string
	// Consulted reports whether at least one request was attempted — whether or not it
	// succeeded. A caller that only checks AuthSourceUsed cannot tell "never asked" from
	// "asked, every source failed" apart: both report "".
	Consulted() bool
}

AuthReporter is implemented by a Registry that can say which credential source authenticated, by name only, and whether the registry was asked at all.

type AuthSource

type AuthSource string

AuthSource names one link of the credential chain.

const (
	AuthEnv      AuthSource = "env"      // HOIST_GHCR_TOKEN, then GHCR_TOKEN; ghcr.io only
	AuthKeychain AuthSource = "keychain" // ~/.docker/config.json and credential helpers
	AuthCluster  AuthSource = "cluster"  // a kubernetes.io/dockerconfigjson Secret, opt-in
	AuthOp       AuthSource = "op"       // `op read <ref>`, opt-in
)

The credential sources, in their default order.

func ParseAuthOrder

func ParseAuthOrder(names []string) ([]AuthSource, error)

ParseAuthOrder turns a list of source names into an order, refusing unknown names and duplicates.

type Client

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

Client is Registry over go-containerregistry with the credential chain of AuthConfig.

func New

func New(cfg AuthConfig) (*Client, error)

New validates cfg and returns a Client. Nothing is contacted or executed here.

func (*Client) AuthSourceUsed

func (c *Client) AuthSourceUsed() string

AuthSourceUsed implements AuthReporter: the winning link per registry host, by name, or "" when no request has succeeded yet. With one host it is just the link ("cluster secret app-staging/ghcr-pull"); with several, "host: link; host: link".

func (*Client) Config

func (c *Client) Config(ctx context.Context, ref image.Ref) (ImageMeta, error)

Config implements the Registry interface's AGENTS.md M6 addition: per-digest image metadata read from the image's config blob, never the index. For a multi-arch image, the index itself carries no Created/Labels — those live only on the platform-specific child manifest's config blob — so Config resolves ref to configPlatform's child first: Get's remote.WithPlatform option plus Descriptor.Image() (called through desc.Image() below) do that resolution for a single round of "fetch, is it an index, walk to the matching child" this package does not have to reimplement; called on a reference that is already a single-platform image manifest, Image() just returns it unchanged, so this same code path handles both shapes.

Config first asks Head for ref's own digest (a cheap manifest HEAD, reusing this Client's same credential chain and cached winner) and checks the on-disk cache for that digest before ever fetching a config blob — see cache.go's doc comment for why a digest-keyed cache is always safe to trust with no invalidation policy. Only a cache miss reaches the network for the config blob itself.

func (*Client) Consulted

func (c *Client) Consulted() bool

Consulted implements AuthReporter.

func (*Client) Head

func (c *Client) Head(ctx context.Context, ref image.Ref) (string, error)

Head implements Registry.

func (*Client) Tags

func (c *Client) Tags(ctx context.Context, repo string) ([]string, error)

Tags implements Registry.

type Fake

type Fake struct {
	Digests  map[string]string
	TagLists map[string][]string
	Configs  map[string]ImageMeta
	Err      error
	Auth     string
	Calls    []string
	// contains filtered or unexported fields
}

Fake is an in-memory Registry for tests in other packages. Digests maps "repo:tag" to the digest Head returns; TagLists maps repo to Tags' answer; Configs maps ref.String() (either "repo:tag" or "repo@digest", whichever a test's caller asks Config with) to what Config returns for it. Err, when set, is returned by every call. Auth is what AuthSourceUsed reports once a call has succeeded. Calls records every call as "Head <ref>", "Tags <repo>" or "Config <ref>".

func (*Fake) AuthSourceUsed

func (f *Fake) AuthSourceUsed() string

AuthSourceUsed implements AuthReporter.

func (*Fake) Config

func (f *Fake) Config(_ context.Context, ref image.Ref) (ImageMeta, error)

Config implements Registry.

func (*Fake) Consulted

func (f *Fake) Consulted() bool

Consulted implements AuthReporter: whether Head or Tags was called at all, win or lose.

func (*Fake) Head

func (f *Fake) Head(_ context.Context, ref image.Ref) (string, error)

Head implements Registry.

func (*Fake) Tags

func (f *Fake) Tags(_ context.Context, repo string) ([]string, error)

Tags implements Registry.

type ImageMeta

type ImageMeta struct {
	// Digest is the top-level digest Head would also return for this ref: the image index
	// digest for a multi-arch tag, never one platform manifest's (see the package doc's note
	// on Head for why that is the digest a pod's imageID reports). This is deliberately not
	// the digest of whatever platform-specific manifest Created/Labels were actually read
	// from — see configPlatform's doc comment — so that a cache keyed by ImageMeta.Digest
	// (cache.go) is keyed by exactly the same value every other digest-keyed thing in this
	// codebase uses (Head, a pod's imageID, image.Ref.Digest).
	Digest  string
	Created time.Time
	Labels  map[string]string
}

ImageMeta is what Config reads from one image's config blob: the digest a pull by tag would pin (the same value Head returns for the same ref — see this field's own note below), when the image was built, and its OCI/Docker config labels. Nothing here is guessed or derived from a tag; every field is read fresh from the registry unless a cache entry answers it first (cache.go).

type PerRepo

type PerRepo interface {
	Registry
	// ForRepo returns the Registry scoped to repo, or nil when repo has no configured
	// registry at all (a repo with no registries[] entry still gets a Registry — a
	// default chain with no cluster or op link — so nil is rare, not the common case of
	// "unmatched").
	ForRepo(repo string) Registry
}

PerRepo is implemented by a Registry that keeps a separate underlying Registry — its own credentials — per image repo (F4): several registries[] entries, each scoped to its own prefix, so a caller resolving many repos through one Registry value never sends one entry's credential (its op ref, its cluster secret) to a repo covered by a different entry, or by none. resolve.Resolve calls ForRepo before every registry request when the Registry it was given implements this; a Registry that answers every repo alike (most fakes, and a single-entry chain) does not need to.

type Registry

type Registry interface {
	// Head returns the digest of ref (repo:tag, or repo@digest which is returned as is
	// after the registry confirms it). See the package doc for multi-arch images.
	Head(ctx context.Context, ref image.Ref) (digest string, err error)
	// Tags lists the tags of repo, sorted; registries return them in no useful order
	// (AGENTS.md §6.1).
	Tags(ctx context.Context, repo string) ([]string, error)
	// Config returns ref's image metadata — digest, Created, Labels — read from its config
	// blob (config.go). Added for M6's tag picker: a picker column needs more than Head's
	// bare digest, and needs it per candidate tag without a second, hand-rolled client.
	Config(ctx context.Context, ref image.Ref) (ImageMeta, error)
}

Registry is what the resolver needs from a registry.

Jump to

Keyboard shortcuts

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