Documentation
¶
Overview ¶
Package imgregistry is a minimal Docker Registry v2 HTTP client used to browse the fleet's image registry and resolve tags/digests for the upgrade-picker. Deliberately not named "registry" — the codebase already has jobs.Registry (job-kind registry) and a same-named package here would collide in every import block needing both.
Index ¶
- Constants
- Variables
- func ValidRef(ref string) bool
- func ValidRepoName(repo string) bool
- type Auth
- type CachingClient
- func (c *CachingClient) Catalog(ctx context.Context) ([]string, error)
- func (c *CachingClient) Manifest(ctx context.Context, repo, ref string) (Manifest, error)
- func (c *CachingClient) TagCount(ctx context.Context, repo string) (int, error)
- func (c *CachingClient) Tags(ctx context.Context, repo string) ([]TagGroup, error)
- type Client
- type HTTPClient
- func (c *HTTPClient) Catalog(ctx context.Context) ([]string, error)
- func (c *HTTPClient) Manifest(ctx context.Context, repo, ref string) (Manifest, error)
- func (c *HTTPClient) TagCount(ctx context.Context, repo string) (int, error)
- func (c *HTTPClient) Tags(ctx context.Context, repo string) ([]TagGroup, error)
- type Layer
- type Manifest
- type TagGroup
Constants ¶
const TagsCacheTTL = 5 * time.Minute
TagsCacheTTL bounds how long a CachingClient serves a repo's Tags() result before re-resolving it. Registry contents only change on push (a human or CI action), never spontaneously, so a few minutes of staleness is harmless — an operator who just pushed a new tag and immediately checks the UI sees it within this window, not never. 5 minutes is picked to make the common case (browsing the list, clicking into a couple of repos, going back) free after the first view, while still bounding how long a since-deleted tag would linger in the UI.
Variables ¶
var ErrNotFound = errors.New("not found")
ErrNotFound wraps a registry's genuine 404 response (NAME_UNKNOWN / MANIFEST_UNKNOWN) — the registry answered and said this repo/tag doesn't exist.
var ErrUnreachable = errors.New("registry unreachable")
ErrUnreachable wraps a transport failure, a non-404 non-2xx response, or a response body this client cannot decode — anything that means "we could not get a straight answer from the registry," as distinct from ErrNotFound ("the registry answered and said no"). Callers must not collapse the two: an operator told "not found" for a registry that is simply down gets the exact misleading signal this package exists to prevent.
Functions ¶
func ValidRef ¶
ValidRef reports whether ref is an acceptable manifest reference: either a tag (tagRe) or a content digest (digestRe). Callers must validate ref before passing it to Client.Manifest, which interpolates it unescaped into a registry request path — an unvalidated ref (e.g. containing "../" or "/") would let a caller steer that request to an arbitrary path on the registry host.
func ValidRepoName ¶
ValidRepoName reports whether repo is an acceptable Docker Registry v2 repository name: one or more "/"-separated components, each matching repoComponentRe. Registry v2 repo names legally contain "/" (e.g. "iotready/engine"), unlike the DNS-label-style names (host/template/slug) validated elsewhere by render.ValidName — so this validator is intentionally separate, not a reuse of that one. Shared by internal/api and internal/ui so the two edges cannot drift into accepting different repo shapes.
Types ¶
type CachingClient ¶ added in v1.0.33
type CachingClient struct {
// contains filtered or unexported fields
}
CachingClient wraps a Client and caches its Tags() results for ttl, since Tags is the one call expensive enough to matter — for the fleet's "engine" repo, ~1000 manifest GETs plus 737 config-blob GETs, ~21s on a cold cache. The list page (internal/ui/handlers_registry.go) fires this once per catalog repo, concurrently, on every single page view — without a cache that's the full per-repo cost paid ~26 times on every visit. With it, only the first visit (or the first visit after ttl) pays that cost; every revisit inside the window is instant.
It is a decorator, not a change to HTTPClient itself: HTTPClient stays a pure client with no caching semantics of its own, the cache is independently testable against a fake inner Client, and a caller that wants uncached behavior (e.g. a future admin "force refresh" action, or a test) can simply hold the inner Client instead.
Catalog, TagCount, and Manifest are passed straight through uncached: Catalog and TagCount are already cheap (a single, unresolved tags/catalog list call), and Manifest is one lookup for one digest — caching it would buy little while adding another thing that could go stale.
Only a successful Tags() result is cached (see Tags below) — a transient registry blip must not poison every viewer's list page for the rest of the TTL.
func NewCachingClient ¶ added in v1.0.33
func NewCachingClient(inner Client, ttl time.Duration) *CachingClient
NewCachingClient wraps inner in a Tags()-caching decorator with the given ttl (TagsCacheTTL in production; tests pass a short one to observe expiry).
func (*CachingClient) Catalog ¶ added in v1.0.33
func (c *CachingClient) Catalog(ctx context.Context) ([]string, error)
type Client ¶
type Client interface {
Catalog(ctx context.Context) ([]string, error)
Tags(ctx context.Context, repo string) ([]TagGroup, error)
TagCount(ctx context.Context, repo string) (int, error)
Manifest(ctx context.Context, repo, ref string) (Manifest, error)
}
Client is the read-only surface this package exposes.
type HTTPClient ¶
type HTTPClient struct {
BaseURL string // e.g. "http://100.64.0.23:5000", no trailing slash
Auth Auth
HTTP *http.Client
}
HTTPClient implements Client against a real Registry v2 server.
func NewHTTPClient ¶
func NewHTTPClient(baseURL string, auth Auth) *HTTPClient
NewHTTPClient builds an HTTPClient with a sane default timeout.
func (*HTTPClient) Catalog ¶
func (c *HTTPClient) Catalog(ctx context.Context) ([]string, error)
Catalog lists every repository in the registry, following the Link response header across pages rather than requesting one unbounded page.
func (*HTTPClient) Manifest ¶
Manifest fetches a single manifest by tag or digest and returns its canonical digest (from Docker-Content-Digest) plus size/layer info.
func (*HTTPClient) TagCount ¶ added in v1.0.33
TagCount returns how many tags repo has, without resolving any of them. Deliberately NOT implemented as len(Tags(...)): Tags resolves every tag's manifest and every unique digest's config blob (~21s for the fleet's "engine" repo), while the repo-list page calls this once per repo on a single page load. One tags-list call — paginated, but nothing more — is the whole point.
Note this counts tags, not unique digests, so it can exceed len(Tags(...)) when several tags share a digest. That is the intended meaning for a "how many tags does this repo have" column.
func (*HTTPClient) Tags ¶
Tags lists every tag in repo, resolves each to its manifest digest, and groups tags that share a digest into one TagGroup. Each unique digest's config blob is fetched once (not once per tag) for its Created timestamp. Manifest and blob-created lookups both run with bounded concurrency (tagResolveConcurrency), not sequentially. Groups are sorted newest-Created-first.