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 ¶
- Variables
- type AuthConfig
- type AuthReporter
- type AuthSource
- type Client
- func (c *Client) AuthSourceUsed() string
- func (c *Client) Config(ctx context.Context, ref image.Ref) (ImageMeta, error)
- func (c *Client) Consulted() bool
- func (c *Client) Head(ctx context.Context, ref image.Ref) (string, error)
- func (c *Client) Tags(ctx context.Context, repo string) ([]string, error)
- type Fake
- type ImageMeta
- type PerRepo
- type Registry
Constants ¶
This section is empty.
Variables ¶
var DefaultAuthOrder = []AuthSource{AuthEnv, AuthKeychain, AuthCluster, AuthOp}
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 ¶
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 ¶
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.
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 ¶
AuthSourceUsed implements AuthReporter.
func (*Fake) Consulted ¶
Consulted implements AuthReporter: whether Head or Tags was called at all, win or lose.
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.