registry

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Mar 16, 2026 License: AGPL-3.0, AGPL-3.0-only Imports: 20 Imported by: 0

Documentation

Overview

Package registry provides a platform-agnostic abstraction over container registries (Docker Hub, GitLab, GHCR, Quay, JFrog, Harbor, Gitea). Every registry operation (list tags, delete tags) goes through the Registry interface so StageFreight's retention engine works identically regardless of where images are hosted.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CanonicalProvider

func CanonicalProvider(provider string) (string, error)

CanonicalProvider normalizes a provider string to its canonical form and validates it. Returns the canonical name or an error for unknown values.

func CheckManifestDigest

func CheckManifestDigest(ctx context.Context, host, path, tag string, credResolver func(string) (string, string), credRef string) (string, error)

CheckManifestDigest performs a HEAD (fallback GET) on the OCI manifest endpoint. Returns the Docker-Content-Digest header value if available. Exported for cross-client digest verification (shadow write detection).

func DeriveRawBase

func DeriveRawBase(linkBase string) string

DeriveRawBase auto-derives a raw file URL base from a link_base URL. Supports GitHub, GitLab, and Gitea URL patterns.

func DiscoverAllArtifacts

func DiscoverAllArtifacts(ctx context.Context, images []build.PublishedImage, credResolver func(string) (string, string)) map[string]ArtifactLinks

DiscoverAllArtifacts runs DiscoverArtifacts concurrently for multiple images. Deduplicates by host/path@digest to avoid redundant lookups.

func HasSection

func HasSection(content, name string) bool

HasSection reports whether content contains markers for the named section.

func InsertAboveMatch

func InsertAboveMatch(content, pattern, text string) (string, bool)

InsertAboveMatch inserts text before the first line matching the regex pattern. Returns the updated content and whether a match was found.

func InsertAboveMatchInSection

func InsertAboveMatchInSection(content, sectionName, pattern, text string) (string, bool)

InsertAboveMatchInSection finds a regex match within a named section and inserts text above the matched line. The section markers are preserved.

func InsertAboveSection

func InsertAboveSection(content, name, text string) (string, bool)

InsertAboveSection inserts text before the opening <!-- sf:<name> --> marker. Returns the updated content and whether the section was found.

func InsertBelowMatch

func InsertBelowMatch(content, pattern, text string) (string, bool)

InsertBelowMatch inserts text after the first line matching the regex pattern. Returns the updated content and whether a match was found.

func InsertBelowMatchInSection

func InsertBelowMatchInSection(content, sectionName, pattern, text string) (string, bool)

InsertBelowMatchInSection finds a regex match within a named section and inserts text below the matched line. The section markers are preserved.

func InsertBelowSection

func InsertBelowSection(content, name, text string) (string, bool)

InsertBelowSection inserts text after the closing <!-- /sf:<name> --> marker. Returns the updated content and whether the section was found.

func IsForbidden

func IsForbidden(err error) bool

IsForbidden returns true if the error chain contains an HTTP 403 response.

func NormalizeHost

func NormalizeHost(h string) string

NormalizeHost strips scheme prefixes and trailing slashes from a registry host. Prevents config variations like "https://ghcr.io" from producing broken URLs.

func NormalizeProvider

func NormalizeProvider(p string) string

NormalizeProvider maps provider aliases to their canonical platform names. Canonical names are the platform brand: docker, github, gitlab, quay, jfrog, harbor, gitea. Legacy aliases (dockerhub, ghcr) are accepted and mapped to canonical forms.

func PlaceContent

func PlaceContent(content, sectionName, position, matchPattern, text string, inline, plain bool) string

PlaceContent applies a placement operation to inject content into a document. Handles all combinations: section, match, section+match, top, bottom. The position parameter should already be normalized (above/below/replace/top/bottom). If inline is true, first-insertion wraps inline; if plain is true, no markers.

func ReplaceBetween

func ReplaceBetween(content, startMarker, endMarker, replacement string) (updated string, found bool)

ReplaceBetween finds arbitrary start/end markers and replaces the content between them. Markers themselves are preserved. Works like ReplaceSection but with user-specified markers instead of the standard sf:NAME pattern. Whitespace detection: if markers are inline (no newline between), no padding.

func ReplaceMatch

func ReplaceMatch(content, pattern, text string) (string, bool)

ReplaceMatch replaces lines matching the regex pattern with the provided text. All contiguous matching lines are replaced as a block. Returns the updated content and whether a match was found.

func ReplaceMatchInSection

func ReplaceMatchInSection(content, sectionName, pattern, text string) (string, bool)

ReplaceMatchInSection finds a regex match within a named section and replaces matched lines. The section markers are preserved.

func ReplaceSection

func ReplaceSection(content, name, replacement string) (updated string, found bool)

ReplaceSection finds the markers for the named section and replaces the content between them. Markers themselves are preserved. Returns the updated content and whether the section was found.

Whitespace-aware: if both markers are on the same line in the source (inline section), replacement is inserted without newline padding. If markers are on separate lines (block section), replacement gets newline padding for readability. The source document declares the intent by how the markers are placed.

If the section is not found, content is returned unchanged with found=false so the caller can decide where to insert a new section.

func SectionContent

func SectionContent(content, name string) (string, bool)

SectionContent extracts the content between section markers. Returns the content and whether the section was found.

func SectionEnd

func SectionEnd(name string) string

SectionEnd returns the closing marker for a named managed section.

func SectionStart

func SectionStart(name string) string

SectionStart returns the opening marker for a named managed section.

func ValidateCredentials

func ValidateCredentials(prefix string) error

ValidateCredentials checks that a credential prefix is a valid env var name.

func ValidateImagePath

func ValidateImagePath(path string) error

ValidateImagePath checks that a repository/image path conforms to OCI spec.

func ValidatePattern

func ValidatePattern(pattern string) error

ValidatePattern checks that a regex pattern compiles.

func ValidateProvider

func ValidateProvider(provider string) error

ValidateProvider normalizes and checks that the provider is a known value.

func ValidateRegistryConfig

func ValidateRegistryConfig(url, path string, tags []string, credentials, provider string, branches, gitTags []string, retention config.RetentionPolicy) []error

ValidateRegistryConfig runs all validation checks against a registry config entry. Returns all errors found (not just the first).

func ValidateRegistryURL

func ValidateRegistryURL(u string) error

ValidateRegistryURL checks that a registry URL is well-formed. Rejects strings with spaces, control characters, or invalid structure.

func ValidateRetention

func ValidateRetention(r config.RetentionPolicy) error

ValidateRetention checks that all retention policy values are non-negative.

func ValidateTag

func ValidateTag(tag string) error

ValidateTag checks that a resolved tag conforms to OCI spec.

func ValidateTagTemplate

func ValidateTagTemplate(tmpl string) error

ValidateTagTemplate checks that an unresolved tag template is structurally valid. Allows {var} and {var:param} syntax. Rejects unclosed braces, spaces, control chars.

func WrapSection

func WrapSection(name, content string) string

WrapSection wraps content in named section markers (block mode with newlines).

func WrapSectionInline

func WrapSectionInline(name, content string) string

WrapSectionInline wraps content in named section markers (inline, no newlines).

Types

type ArtifactLinks struct {
	SBOM       string // digest ref for SBOM artifact (host/path@sha256:...)
	Provenance string // digest ref for provenance artifact
	Signature  string // digest ref for signature artifact
}

ArtifactLinks holds discovered OCI referrer artifact links for an image.

func DiscoverArtifacts

func DiscoverArtifacts(ctx context.Context, img build.PublishedImage, credResolver func(string) (string, string)) (ArtifactLinks, error)

DiscoverArtifacts queries the OCI referrers API for a verified image digest. Returns links to SBOM, provenance, and signature artifacts if present. Best-effort: returns empty ArtifactLinks (no error) if referrers API unsupported.

type DockerHub

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

DockerHub implements the Registry interface for Docker Hub. Uses hub.docker.com v2 API for listing, deleting tags, and updating descriptions. Authenticates via /v2/auth/token (supports both PATs and passwords).

func NewDockerHub

func NewDockerHub(user, pass string) *DockerHub

func (*DockerHub) DeleteTag

func (d *DockerHub) DeleteTag(ctx context.Context, repo string, tag string) error

func (*DockerHub) ListTags

func (d *DockerHub) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*DockerHub) Provider

func (d *DockerHub) Provider() string

func (*DockerHub) UpdateDescription

func (d *DockerHub) UpdateDescription(ctx context.Context, repo, short, full string) error

func (*DockerHub) Warnings

func (d *DockerHub) Warnings() []string

type GHCR

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

GHCR implements the Registry interface for GitHub Container Registry (ghcr.io). Uses the GitHub REST API for package version management. Requires a PAT with read:packages and delete:packages scopes.

func NewGHCR

func NewGHCR(user, pass string) *GHCR

func (*GHCR) DeleteTag

func (g *GHCR) DeleteTag(ctx context.Context, repo string, tag string) error

func (*GHCR) ListTags

func (g *GHCR) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*GHCR) Provider

func (g *GHCR) Provider() string

func (*GHCR) UpdateDescription

func (g *GHCR) UpdateDescription(_ context.Context, _, _, _ string) error

type GitLabRegistry

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

GitLabRegistry implements the Registry interface for GitLab Container Registry. Uses the GitLab REST API (projects/:id/registry/repositories) for tag management. Requires either a CI_JOB_TOKEN (in CI) or a personal/project access token.

func NewGitLab

func NewGitLab(registryURL, user, pass string) *GitLabRegistry

func (*GitLabRegistry) DeleteTag

func (g *GitLabRegistry) DeleteTag(ctx context.Context, repo string, tag string) error

func (*GitLabRegistry) ListTags

func (g *GitLabRegistry) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*GitLabRegistry) Provider

func (g *GitLabRegistry) Provider() string

func (*GitLabRegistry) UpdateDescription

func (g *GitLabRegistry) UpdateDescription(_ context.Context, _, _, _ string) error

type Gitea

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

Gitea implements the Registry interface for Gitea/Forgejo container package registries. Uses the Gitea package API (/api/v1/packages/:owner). Requires a token with package:read and package:delete scopes.

func NewGitea

func NewGitea(registryURL, user, pass string) *Gitea

func (*Gitea) DeleteTag

func (g *Gitea) DeleteTag(ctx context.Context, repo string, tag string) error

func (*Gitea) ListTags

func (g *Gitea) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*Gitea) Provider

func (g *Gitea) Provider() string

func (*Gitea) UpdateDescription

func (g *Gitea) UpdateDescription(_ context.Context, _, _, _ string) error

type HTTPError

type HTTPError struct {
	StatusCode int
	Method     string
	URL        string
	Body       string
}

HTTPError represents an HTTP response with a non-success status code.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Harbor

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

Harbor implements the Registry interface for Harbor v2 container registry. Uses the Harbor REST API v2.0 (/api/v2.0/projects/:project/repositories/:repo/artifacts). Requires a user with push+delete permissions on the target project.

func NewHarbor

func NewHarbor(registryURL, user, pass string) *Harbor

func (*Harbor) DeleteTag

func (h *Harbor) DeleteTag(ctx context.Context, repo string, tag string) error

func (*Harbor) ListTags

func (h *Harbor) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*Harbor) Provider

func (h *Harbor) Provider() string

func (*Harbor) UpdateDescription

func (h *Harbor) UpdateDescription(ctx context.Context, repo, short, full string) error

type JFrog

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

JFrog implements the Registry interface for JFrog Artifactory / JFrog Container Registry. Uses the Artifactory REST API for Docker registry management. Requires an admin token or user with delete permissions on the target repository.

func NewJFrog

func NewJFrog(registryURL, user, pass string) *JFrog

func (*JFrog) DeleteTag

func (j *JFrog) DeleteTag(ctx context.Context, repo string, tag string) error

func (*JFrog) ListTags

func (j *JFrog) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*JFrog) Provider

func (j *JFrog) Provider() string

func (*JFrog) UpdateDescription

func (j *JFrog) UpdateDescription(_ context.Context, _, _, _ string) error

type Local

type Local struct{}

Local implements the Registry interface for the local Docker daemon. Uses `docker` CLI commands to list and remove images — no remote API calls. This enables retention for local dev builds loaded with --load.

func NewLocal

func NewLocal() *Local

func (*Local) DeleteTag

func (l *Local) DeleteTag(ctx context.Context, repo string, tag string) error

func (*Local) ListTags

func (l *Local) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*Local) Provider

func (l *Local) Provider() string

func (*Local) UpdateDescription

func (l *Local) UpdateDescription(_ context.Context, _, _, _ string) error

type Quay

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

Quay implements the Registry interface for Quay.io (and self-hosted Quay/Red Hat Quay). Uses the Quay REST API (/api/v1/repository). Requires an OAuth token or robot account token with repo:admin scope.

func NewQuay

func NewQuay(registryURL, user, pass string) *Quay

func (*Quay) DeleteTag

func (q *Quay) DeleteTag(ctx context.Context, repo string, tag string) error

func (*Quay) ListTags

func (q *Quay) ListTags(ctx context.Context, repo string) ([]TagInfo, error)

func (*Quay) Provider

func (q *Quay) Provider() string

func (*Quay) UpdateDescription

func (q *Quay) UpdateDescription(ctx context.Context, repo, short, full string) error

type ReadmeContent

type ReadmeContent struct {
	Short string // max 100 chars
	Full  string // full markdown
}

ReadmeContent holds the processed README ready for pushing to registries.

func PrepareReadmeFromFile

func PrepareReadmeFromFile(file, description, linkBase, rootDir string) (*ReadmeContent, error)

PrepareReadmeFromFile loads a README file and returns processed content ready for registry sync. Takes individual fields for maximum flexibility — callers resolve config to args.

type Registry

type Registry interface {
	// Provider returns the registry vendor name.
	Provider() string

	// ListTags returns all tags for a repository, sorted by creation time descending.
	ListTags(ctx context.Context, repo string) ([]TagInfo, error)

	// DeleteTag removes a single tag from a repository.
	DeleteTag(ctx context.Context, repo string, tag string) error

	// UpdateDescription pushes short and full descriptions to the registry.
	// Returns nil for providers that don't support description APIs.
	UpdateDescription(ctx context.Context, repo, short, full string) error
}

Registry is the interface every container registry provider implements.

func NewRegistry

func NewRegistry(provider, registryURL, credentialPrefix string) (Registry, error)

NewRegistry creates a registry client for the given provider. Credentials are resolved from environment variables using the prefix:

prefix: "DOCKER" → DOCKER_USER / DOCKER_PASS
prefix: "GHCR_ORG" → GHCR_ORG_USER / GHCR_ORG_PASS

The registryURL is the base URL (e.g., "docker.io", "ghcr.io").

type ResolvedRegistryTarget

type ResolvedRegistryTarget struct {
	Provider string   // canonical provider: docker, github, gitlab, quay, jfrog, harbor, gitea, generic
	Host     string   // normalized registry host (e.g., "docker.io", "registry.gitlab.com")
	Path     string   // resolved repo/image path (e.g., "prplanit/stagefreight")
	Tags     []string // fully resolved publish tags
}

ResolvedRegistryTarget is a fully resolved registry target with enough information to both push images and generate deterministic UI URLs. If StageFreight can publish to this target, it can generate correct URLs.

func (ResolvedRegistryTarget) DisplayName

func (r ResolvedRegistryTarget) DisplayName() string

DisplayName returns the human-friendly registry label.

func (ResolvedRegistryTarget) ImageRef

func (r ResolvedRegistryTarget) ImageRef() string

ImageRef returns the canonical image reference (host/path).

func (ResolvedRegistryTarget) RepoURL

func (r ResolvedRegistryTarget) RepoURL() string

RepoURL returns the web UI URL for this image's repository page. Every supported provider returns a deterministic URL. Generic provider returns https://{host}/{path} (the user's configured URL). Panics on unknown providers — if StageFreight can publish, it must resolve URLs.

func (ResolvedRegistryTarget) TagURL

func (r ResolvedRegistryTarget) TagURL(tag string) string

TagURL returns the web UI URL for a specific tag on this image. Every supported provider returns a deterministic URL. Generic provider returns https://{host}/{path} (best available reference). Panics on unknown providers — if StageFreight can publish, it must resolve URLs.

type RetentionResult

type RetentionResult struct {
	Provider string
	Repo     string
	Matched  int      // tags matching the pattern set
	Kept     int      // tags kept by policy
	Deleted  []string // tags successfully deleted
	Skipped  []string // tags skipped (digest shared with protected tag)
	Errors   []error  // errors from individual deletes
}

RetentionResult captures what the retention engine did.

func ApplyRetention

func ApplyRetention(ctx context.Context, reg Registry, repo string, tagPatterns []string, policy config.RetentionPolicy) (*RetentionResult, error)

ApplyRetention lists all tags on the registry, filters them by the given tag patterns (using config.MatchPatterns with full !/OR/AND semantics), sorts by creation time descending, and applies restic-style retention policies to decide which tags to keep.

Tags whose digest is shared with a protected tag are skipped during deletion to prevent breaking rolling tags like latest-dev.

Policies are additive — a tag survives if ANY policy wants to keep it:

  • keep_last N: keep the N most recent
  • keep_daily N: keep one per day for the last N days
  • keep_weekly N: keep one per week for the last N weeks
  • keep_monthly N: keep one per month for the last N months
  • keep_yearly N: keep one per year for the last N years

tagPatterns uses the same syntax as branches/git_tags in the config:

["^dev-"]              → only tags starting with "dev-"
["^dev-", "!^dev-keep"]→ dev- tags, excluding dev-keep*
[]                     → ALL tags are candidates (dangerous, use with care)

type SkippedError

type SkippedError struct {
	Tag string
}

SkippedError is a sentinel indicating a tag was skipped because its digest is shared with a protected tag.

func (*SkippedError) Error

func (e *SkippedError) Error() string

func (*SkippedError) IsSkipped

func (e *SkippedError) IsSkipped() bool

type TagInfo

type TagInfo struct {
	Name      string
	Digest    string
	CreatedAt time.Time
}

TagInfo describes a single tag in a container registry.

type VerificationResult

type VerificationResult struct {
	Image    build.PublishedImage
	Verified bool
	Digest   string // remote digest if available
	Err      error
}

VerificationResult tracks the outcome of verifying a single published image.

func VerifyImages

func VerifyImages(ctx context.Context, images []build.PublishedImage, credResolver func(string) (string, string)) ([]VerificationResult, error)

VerifyImages checks each published image against its remote registry. Uses OCI Distribution API HEAD (fallback GET) manifest request. Concurrent (max 8 workers), retries with exponential backoff. Digest mismatch is a verification failure.

type Warner

type Warner interface {
	Warnings() []string
}

Warner is an optional interface for registries that emit warnings (e.g., credential hygiene nudges). Callers can type-assert to check.

Jump to

Keyboard shortcuts

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