releasewait

package
v8.82.0 Latest Latest
Warning

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

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

Documentation

Overview

Package releasewait waits until a tag's images and charts are pullable: what `devctl release wait` does. A tag and a GitHub Release exist about a minute after a merge; the artifacts come from the CircleCI pipeline the tag triggers, minutes later, under names the repository's CI decides. The package reads those names from the sources that define them and never from the repository name: for generated CI from the team-file entry the generator renders (the `image.name` override included), for hand-written CI from the push jobs of the tag pipeline matched with the tag's CircleCI configuration. When the sources disagree, or yield nothing while a Dockerfile exists at the tag, the wait ends with the disagreement instead of a guess.

Availability is a digest: every image and chart resolves to one in the registry, the public registry probed anonymously (a stale docker login cannot produce a false UNAUTHORIZED), the private one with the docker keychain. A failed or cancelled workflow of the tag pipeline, the newest run per workflow name, ends the wait as the tag's CI failure with the failed jobs; a repository without CircleCI is judged by the Actions runs the tag triggered. A repository without image and chart is waited for through its published release and the tag's workflows.

Index

Constants

View Source
const (
	IntervalFloor   = 15 * time.Second
	IntervalCeiling = 60 * time.Second
)

The poll interval's bounds at scale 1; between them it follows GitHub's rate-limit headers.

View Source
const (
	// ReleaseModelAutoRelease tags the merge commit of every push to main
	// that warrants a bump, from conventional commits.
	ReleaseModelAutoRelease = "auto-release"
	// ReleaseModelLegacy tags from a release branch and a release pull
	// request; the tag is not on the merge commit of a feature pull request.
	ReleaseModelLegacy = "legacy"
)

The release models: how a repository turns a merge into a tag.

View Source
const (
	// CIModelGenerated: devctl renders .circleci/config.yml and workflows.yml
	// from the team-file entry; the entry names the artifacts.
	CIModelGenerated = "generated"
	// CIModelHandWritten: the repository maintains its CircleCI
	// configuration; its push jobs name the artifacts.
	CIModelHandWritten = "hand-written"
	// CIModelNone: no CircleCI; the tag is judged by its Actions runs.
	CIModelNone = "none"
)

The CI models: where the names of the artifacts come from.

View Source
const (
	KindImage        = "image"
	KindChart        = "chart"
	KindReleaseAsset = "release-asset"
)

The artifact kinds.

View Source
const (
	StateAvailable = "available"
	StateMissing   = "missing"
)

The artifact states.

View Source
const Command = "release wait"

Command is the envelope's command name.

View Source
const DefaultCatalogURL = "https://giantswarm.github.io"

DefaultCatalogURL is where the catalog indexes are published: <DefaultCatalogURL>/<catalog>/index.yaml.

View Source
const DefaultTimeout = 30 * time.Minute

DefaultTimeout bounds a wait that gets no --timeout.

View Source
const EnvCatalogURL = "DEVCTL_CATALOG_URL"

EnvCatalogURL points --catalog at another host for the catalog indexes; the default is the GitHub Pages host of the catalogs.

Variables

This section is empty.

Functions

func CatalogURLFromEnv

func CatalogURLFromEnv() string

CatalogURLFromEnv is the base URL the environment selects.

func EntryGeneratesCI

func EntryGeneratesCI(entry *reposetup.Fields) bool

EntryGeneratesCI: the entry opts into devctl-generated CircleCI configuration.

func EntryReleaseModel

func EntryReleaseModel(entry *reposetup.Fields) string

EntryReleaseModel is the release model a team-file entry declares: releaseWorkflow when set, else auto-release with gen.ci.generate and legacy without. An entry without a gen block declares nothing ("").

Types

type ActionsRun

type ActionsRun struct {
	Name       string `json:"name"`
	RunID      int64  `json:"runId"`
	Status     string `json:"status"`
	Conclusion string `json:"conclusion"`
	URL        string `json:"url"`
}

ActionsRun is one GitHub Actions run the tag triggered, for a repository without CircleCI.

type Artifact

type Artifact struct {
	// Kind is image, chart or release-asset.
	Kind string `json:"kind"`
	// Reference is the pullable reference (registry/name:tag) or, for a
	// release asset, its download URL.
	Reference string `json:"reference"`
	// Digest is set once the artifact is available.
	Digest string `json:"digest"`
	// State is available or missing.
	State string `json:"state"`
	// contains filtered or unexported fields
}

Artifact is one thing the release ships and where it stands.

func GeneratedArtifacts

func GeneratedArtifacts(entry reposetup.Fields, repo, version string, content TagContent, privateRepo bool, endpoints agentcli.Endpoints) ([]Artifact, error)

GeneratedArtifacts are the artifacts devctl's CircleCI generator emits for a team-file entry, the way the generator derives them: an image when the tag has a root Dockerfile or the entry names one elsewhere, called gen.ci.image.name or <owner>/<repo>; a chart for the app flavour of a non-template repository, called gen.ci.chartName or the repository, in gen.ci.appCatalog or the default catalog. The private registry holds a private-only image and the artifacts of a private repository that does not force them public.

func HandWrittenArtifacts

func HandWrittenArtifacts(ctx context.Context, gh GitHub, owner, repo, sha, version string, content TagContent, pipelineJobs map[string]bool, privateRepo bool, endpoints agentcli.Endpoints) ([]Artifact, error)

HandWrittenArtifacts are the artifacts the tag pipeline of a hand-written configuration publishes: the push jobs of the configuration files at the tag that the pipeline runs (pipelineJobs are the job names CircleCI lists for it), each naming its image or chart. A Dockerfile at the tag with no image among them is a disagreement between the sources, reported as such.

func (Artifact) Private

func (a Artifact) Private() bool

Private says whether the artifact lives in the private registry.

type CatalogIndex

type CatalogIndex struct {
	// BaseURL is the host of the indexes; empty means DefaultCatalogURL.
	BaseURL string
	// HTTPClient sends the requests; nil means the default client.
	HTTPClient *http.Client
}

CatalogIndex reads a catalog's index.yaml over HTTP.

func (CatalogIndex) Lists

func (c CatalogIndex) Lists(ctx context.Context, catalog, chart, version string) (bool, error)

Lists implements CatalogReader: the index of catalog has an entry of chart at version. The index is fetched anew on every call, so a stale copy never answers.

type CatalogReader

type CatalogReader interface {
	Lists(ctx context.Context, catalog, chart, version string) (bool, error)
}

CatalogReader says whether a catalog's index lists a chart version.

type CircleCI

type CircleCI interface {
	FindPipelineByTag(ctx context.Context, org, repo, tag string) (*circleciclient.Pipeline, error)
	ListPipelineWorkflows(ctx context.Context, pipelineID string) ([]circleciclient.Workflow, error)
	ListWorkflowJobs(ctx context.Context, workflowID string) ([]circleciclient.Job, error)
}

CircleCI is what the wait reads from CircleCI; *circleciclient.Client is one.

type Config

type Config struct {
	// Owner and Repo name the repository. Required.
	Owner, Repo string
	// Version is the release to wait for, vX.Y.Z or X.Y.Z; empty with PR.
	Version string
	// PR is the merged pull request whose tag is waited for; 0 with Version.
	PR int
	// Timeout bounds the wait; zero means DefaultTimeout.
	Timeout time.Duration
	// Catalog also waits for the catalog index to list every chart.
	Catalog bool

	// GitHub is required. Entries and Registry are required; CircleCI is
	// called once when the tag carries a .circleci/config.yml, so a
	// repository without CircleCI needs no CircleCI token. CatalogIndex is
	// required with Catalog.
	GitHub       GitHub
	Entries      EntryFinder
	CircleCI     func(ctx context.Context) (CircleCI, error)
	Registry     Prober
	CatalogIndex CatalogReader

	// Endpoints name the registries. Zero means production.
	Endpoints agentcli.Endpoints
	// Clock paces the polls; zero means the wall clock at scale 1.
	Clock agentcli.Clock
	// Rate is the rate limit of the last GitHub response, the interval's
	// input; nil keeps the floor.
	Rate func() githubclient.RateLimit
	// Progress receives one line per step; nil is silent.
	Progress *agentcli.Progress
}

Config configures a Waiter.

type Document

type Document struct {
	agentcli.Envelope
	Result
}

Document is the command's JSON: the envelope and the result.

type EntryFinder

type EntryFinder interface {
	FindEntry(ctx context.Context, owner, repo string) (*reposetup.Fields, bool, error)
}

EntryFinder returns the team-file entry that declares a repository, and false when no team file does.

type GitHub

type GitHub interface {
	GetPullRequestMerge(ctx context.Context, owner, repo string, number int) (githubclient.PullRequestMerge, error)
	GetTagSHA(ctx context.Context, owner, repo, tag string) (string, error)
	FindTagForCommit(ctx context.Context, owner, repo, sha string) (string, error)
	GetReleaseByTag(ctx context.Context, owner, repo, tag string) (githubclient.Release, error)
	ListWorkflowRunsForSHA(ctx context.Context, owner, repo, sha string) ([]githubclient.WorkflowRun, error)
	ListDirectory(ctx context.Context, owner, repo, path, ref string) ([]string, error)
	GetFile(ctx context.Context, owner, repo, path, ref string) (githubclient.RepositoryFile, error)
	IsPrivateRepository(ctx context.Context, owner, repo string) (bool, error)
}

GitHub is what the wait reads from GitHub; *githubclient.Client is one.

type Models

type Models struct {
	Release string
	CI      string
}

Models are the settled release and CI models.

func ResolveModels

func ResolveModels(entry *reposetup.Fields, content TagContent) (Models, error)

ResolveModels settles the models from the team-file entry (nil when no team file declares the repository) cross-checked against the tag's files. A disagreement between the two is an error, never a guess; a source that says nothing leaves the decision to the other; neither saying anything about the release model is an error too.

type Pipeline

type Pipeline struct {
	ID     string `json:"id"`
	Number int64  `json:"number"`
	URL    string `json:"url"`
	// Workflows are the newest run of every workflow name.
	Workflows []PipelineWorkflow `json:"workflows"`
	// FailedJobs are workflow/job of every failed job when the tag's CI
	// failed; empty otherwise.
	FailedJobs []string `json:"failedJobs"`
}

Pipeline is the tag pipeline on CircleCI as the document reports it.

type PipelineWorkflow

type PipelineWorkflow struct {
	Name   string `json:"name"`
	Status string `json:"status"`
}

PipelineWorkflow is one workflow of the tag pipeline.

type Prober

type Prober interface {
	Probe(ctx context.Context, artifact Artifact) (digest string, found bool, err error)
}

Prober answers whether an artifact is in its registry: the digest when it is, found false when the registry knows no such manifest, and an error for every other answer, which is a tooling failure and not a slow pipeline.

type PushJob

type PushJob struct {
	// Name is the job's name in the workflow: its name parameter, else the
	// orb job's name.
	Name string
	// Kind is image or chart.
	Kind string
	// Image is the image parameter; empty means the orb's default,
	// <owner>/<repo>.
	Image string
	// Chart and Catalog are the chart and app_catalog parameters.
	Chart, Catalog string
	// Push is false for a build-only job (push: false, or a chart job that
	// pushes to neither the catalog nor the registry).
	Push bool
	// PrivateOnly: registries-data names the private registry only.
	PrivateOnly bool
	// ForcePublic pushes a private repository's artifact to the public
	// registry.
	ForcePublic bool
}

PushJob is a push job of a hand-written CircleCI configuration: an architect push-to-registries (or push-to-docker) job naming an image, or a push-to-app-catalog job naming a chart.

func ParsePushJobs

func ParsePushJobs(config []byte) ([]PushJob, error)

ParsePushJobs reads the push jobs of every workflow in a CircleCI configuration. Jobs the configuration defines itself are opaque and not returned; the orb's push jobs are recognised whatever the orb is called.

type RegistryAnswerError

type RegistryAnswerError struct {
	Reference string
	Status    int
	Code      string
	Hint      string
}

RegistryAnswerError is a registry answer that is neither a digest nor "manifest unknown": the probe's failure, which ends the wait at once.

func (*RegistryAnswerError) Error

func (e *RegistryAnswerError) Error() string

type RegistryProber

type RegistryProber struct {
	// Endpoints name the registries and whether they speak plain HTTP.
	Endpoints agentcli.Endpoints
	// Keychain answers the private registry; nil means the docker keychain.
	Keychain authn.Keychain
	// Transport sends the requests; nil means the default.
	Transport http.RoundTripper
}

RegistryProber probes OCI registries with go-containerregistry: the public registry anonymously, so a stale docker login cannot produce a false UNAUTHORIZED for a public artifact; the private registry with the docker keychain, the credentials `docker login` stored.

func (RegistryProber) Probe

func (p RegistryProber) Probe(ctx context.Context, artifact Artifact) (string, bool, error)

Probe implements Prober.

type Result

type Result struct {
	Repository   string `json:"repository"`
	Tag          string `json:"tag"`
	SHA          string `json:"sha"`
	ReleaseModel string `json:"releaseModel"`
	CIModel      string `json:"ciModel"`
	// Artifacts are the expected images and charts, or the release assets
	// of a repository that ships neither.
	Artifacts []Artifact `json:"artifacts"`
	// Pipeline is the tag pipeline; null for a repository without CircleCI.
	Pipeline *Pipeline `json:"pipeline"`
	// Actions are the runs the tag triggered; empty with CircleCI.
	Actions []ActionsRun `json:"actions"`
}

Result is the command's document below the envelope.

func NewResult

func NewResult(repository string) Result

NewResult is the result before anything is known: the repository, empty arrays, no pipeline.

type TagContent

type TagContent struct {
	// SHA the content was read at.
	SHA string
	// Root are the names at the repository root.
	Root []string
	// Workflows are the file names under .github/workflows.
	Workflows []string
	// CircleCI are the file names under .circleci.
	CircleCI []string
}

TagContent is what the tag carries of the files the models and the artifacts are read from: the root listing, the workflow files and the CircleCI directory.

func ReadTagContent

func ReadTagContent(ctx context.Context, gh GitHub, owner, repo, sha string) (*TagContent, error)

ReadTagContent lists the three directories at sha; a missing directory is an empty list.

func (TagContent) CIModel

func (c TagContent) CIModel() string

CIModel is the CI model the files show.

func (TagContent) GeneratedCircleCI

func (c TagContent) GeneratedCircleCI() bool

GeneratedCircleCI: the configuration is devctl's, recognised by the workflows.yml the generator writes beside config.yml.

func (TagContent) HasCircleCI

func (c TagContent) HasCircleCI() bool

HasCircleCI: the repository has a CircleCI project to read.

func (TagContent) HasCustomCircleCI

func (c TagContent) HasCustomCircleCI() bool

HasCustomCircleCI: a repo-owned custom.yml is merged into the generated workflows.

func (TagContent) HasDockerfile

func (c TagContent) HasDockerfile() bool

HasDockerfile: a Dockerfile at the repository root, the signal that turns the generated image pipeline on and the reason a hand-written pipeline without a push job is a disagreement.

func (TagContent) ReleaseModel

func (c TagContent) ReleaseModel() (string, error)

ReleaseModel is the release model the workflow files show: auto-release for an auto-release workflow, legacy for the create-release workflows, "" when neither is there. Both at once is an error: the files contradict each other.

type TeamFileEntries

type TeamFileEntries struct {
	// GitHub is the go-github client the team files are read with.
	GitHub *github.Client
	// Owner is the organisation whose team files are searched; empty means
	// the default one.
	Owner string
}

TeamFileEntries finds a repository's declaration in the team files of the organisation's github repository, read as the caller. Only the organisation the team files describe is searched; a repository of another owner has no entry.

func (TeamFileEntries) FindEntry

func (t TeamFileEntries) FindEntry(ctx context.Context, owner, repo string) (*reposetup.Fields, bool, error)

FindEntry implements EntryFinder.

type Version

type Version struct {
	// Given is the argument as typed.
	Given string
	// Bare is the version without a leading v: the tag of the image and the
	// chart.
	Bare string
}

Version is a release version as given on the command line.

func ParseVersion

func ParseVersion(s string) (Version, error)

ParseVersion accepts vX.Y.Z and X.Y.Z, with a pre-release suffix.

func (Version) Tags

func (v Version) Tags() []string

Tags are the tag names the version may exist under, the given spelling first: a repository tags vX.Y.Z or X.Y.Z, and the caller need not know which.

type Waiter

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

Waiter runs one wait.

func New

func New(config Config) (*Waiter, error)

New validates config and returns a Waiter.

func (*Waiter) Wait

func (w *Waiter) Wait(ctx context.Context, result *Result) error

Wait runs the wait and fills result as it learns; the error is the outcome of the exit-code table, nil when every artifact is available. result is partially filled when the error is not nil: what was known when the wait ended.

Jump to

Keyboard shortcuts

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