deploy

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package deploy is the build integration: it connects internal/spec's build declaration to internal/build's BuildKit client, and a successful build's output to internal/store's desired state, closing the loop the application controller (internal/reconcile/ application) reads from on every reconcile.

Index

Constants

View Source
const (
	HeldKindImage = "image"
	// HeldKindBuild is a queued manual build, replayed from Build.
	HeldKindBuild = "build"
	HeldKindGit   = "git"
)

Held request kinds, each replayed by its own ReleaseHandler.

View Source
const MaxFreezeDuration = 31 * 24 * time.Hour

MaxFreezeDuration bounds one freeze window so a typo cannot freeze an app for months.

Variables

View Source
var ErrCanceled = errors.New("deploy canceled")

ErrCanceled marks a deploy an operator canceled before it wrote desired state.

View Source
var ErrFreshImageUnavailable = errors.New("image resolver unavailable, cannot resolve a fresh image")

ErrFreshImageUnavailable is returned when a deploy requires a fresh registry resolution but no image resolver is available.

View Source
var ErrFrozen = errors.New("deploy frozen")

ErrFrozen marks a deploy refused because a freeze window is active.

View Source
var ErrNoServices = errors.New("deploy: no services to deploy")

ErrNoServices is returned by DeploySpec when req.Services is empty: there is nothing to fan out, and proceeding would create an App row that owns nothing.

View Source
var ErrSuperseded = errors.New("deploy superseded")

ErrSuperseded marks a deploy that was not applied because a newer one already was. Match it with errors.Is.

Functions

func CheckOrder added in v0.2.0

func CheckOrder(c store.DeployCursor, o store.DeployOrder) error

CheckOrder rejects an automatic deploy that would overwrite newer desired state. Manual deploys and explicit rollbacks always pass.

func HoldDeploy added in v0.2.0

func HoldDeploy(ctx context.Context, st HeldStore, serviceName, image, source string, req HeldRequest, status FreezeStatus) (string, error)

HoldDeploy records an automatic deploy parked by a freeze window.

func LocalOnlyRef added in v0.2.0

func LocalOnlyRef(pin *store.RollbackPin, ref string) bool

LocalOnlyRef reports whether ref is a rollback pin tag, which exists in no registry and so must never be pulled.

func PreviousKnownGoodImage added in v0.2.0

func PreviousKnownGoodImage(attempts []store.DeployAttempt, currentImage string) (image string, ok bool)

PreviousKnownGoodImage returns the newest succeeded attempt's image that differs from currentImage, scanning attempts newest first. ok is false when there is nothing to roll back to.

func RestoreSnapshot added in v0.2.0

func RestoreSnapshot(current store.DesiredService, snap store.DeployAttemptSnapshot) store.DesiredService

RestoreSnapshot restores snap's env literals, port, health, replicas, strategy, resources and labels onto current. Secret references, domains, volumes and host ports stay current: rolling them back can cut traffic or data.

func RollbackDesired added in v0.2.0

func RollbackDesired(current store.DesiredService, target store.DeployAttempt, pin *store.RollbackPin) (svc store.DesiredService, ref string, contentSafe bool)

RollbackDesired is current restored to target's config, plus the image reference to deploy it with. A pinned local tag never exists in any registry, so an always-pull policy is cleared when the pin is used.

func RollbackRef added in v0.2.0

func RollbackRef(target store.DeployAttempt, pin *store.RollbackPin) (ref string, contentSafe bool)

RollbackRef is the image reference that redeploys exactly target's content: its registry digest when it had one, else its rollback pin, else the bare tag (the caller must verify a bare tag still matches).

func SupersededReason added in v0.2.0

func SupersededReason(err error) (string, bool)

SupersededReason returns the recorded reason for a superseded deploy error.

func ToDesiredService

func ToDesiredService(name, image string, svc spec.Service) (store.DesiredService, error)

ToDesiredService exports toDesiredService for internal/webhook's own legacy single-app path (webhook.Handler.beginDeployAttempt), which has a static spec.Service from its Config but no store.DesiredService to read one back from: it needs the identical spec.Service -> desired state translation this package already does for every real deploy, to build that attempt's store.DeployAttemptSnapshot.

func TriggerImageDeploy added in v0.2.0

func TriggerImageDeploy(ctx context.Context, st ImageDeployStore, nudger ReconcileNudger, existing store.DesiredService, image, source string, logger *slog.Logger) (store.DesiredService, error)

TriggerImageDeploy is ImageTrigger without digest resolution or ordering, for callers deploying an image already taken from deploy history.

func ValidateFreezeWindow added in v0.2.0

func ValidateFreezeWindow(w store.DeployFreezeWindow) error

ValidateFreezeWindow checks a window before it is stored.

func WindowActive added in v0.2.0

func WindowActive(w store.DeployFreezeWindow, now time.Time) (bool, time.Time, error)

WindowActive reports whether w covers now and, if so, when it ends. The cron expression is evaluated on the wall clock of w.Timezone.

Types

type AppStore

type AppStore interface {
	SaveApp(ctx context.Context, a store.App) error
	GetAppByName(ctx context.Context, name string) (store.App, error)
	UpdateServiceApp(ctx context.Context, serviceName, appID string) error
}

AppStore is the narrow surface DeploySpec needs to create-or-reuse a store.App and link each fanned-out service to it. *store.DB satisfies this structurally. Optional: nil (the default) means DeploySpec itself is unavailable (see its own doc comment), not that a caller still expecting single-service Deploy calls needs to change.

type AttemptRecorder added in v0.2.0

type AttemptRecorder interface {
	SetDeployAttemptDigest(ctx context.Context, id, digest, reason string) error
}

AttemptRecorder stamps a deploy attempt with the content it resolved to.

type BuildCacheProvider added in v0.2.0

type BuildCacheProvider interface {
	S3Cache(ctx context.Context, app string) (*build.S3Cache, error)
	RecordBuild(ctx context.Context, app, warning string)
}

BuildCacheProvider resolves an app's BuildKit remote cache and records how the build went. internal/objectstore.BuildCache satisfies it.

type BuildMetricsRecorder

type BuildMetricsRecorder interface {
	RecordBuildDuration(ctx context.Context, serviceName string, d time.Duration, at time.Time) error
}

BuildMetricsRecorder is the narrow surface this package needs to record the build-duration metric. *telemetry.DB satisfies this structurally; not imported directly to avoid a dependency this package doesn't otherwise need, the same reasoning ImageBuilder/ ServiceStore/SecretChecker above already establish.

type BuildOnlyRequest added in v0.2.0

type BuildOnlyRequest struct {
	// Type is "dockerfile" (default) or "railpack".
	Type          string
	SourceDir     string
	BaseDirectory string
	Dockerfile    string
	Tag           string
	Args          map[string]string
}

BuildOnlyRequest builds one image without touching desired state.

type CancelOutcome added in v0.2.0

type CancelOutcome int

CancelOutcome is what CancelRegistry.Cancel found.

const (
	// CancelUnknown means no in-process deploy is registered under that id.
	CancelUnknown CancelOutcome = iota
	// CancelRequested means the deploy was stopped before it could commit.
	CancelRequested
	// CancelTooLate means the deploy already committed desired state.
	CancelTooLate
)

type CancelRegistry added in v0.2.0

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

CancelRegistry tracks in-flight deploys so an operator can stop one. A deploy is cancelable until it calls Commit, right before it writes desired state, so a cancel can never leave desired state half-applied.

func NewCancelRegistry added in v0.2.0

func NewCancelRegistry() *CancelRegistry

NewCancelRegistry returns an empty registry.

func (*CancelRegistry) Bind added in v0.2.0

func (r *CancelRegistry) Bind(parent context.Context, id string) context.Context

Bind derives the context the deploy runs under, canceled by Cancel. An unregistered id gets parent back unchanged.

func (*CancelRegistry) Cancel added in v0.2.0

func (r *CancelRegistry) Cancel(id, by string) CancelOutcome

Cancel stops deploy id unless it already committed. by is recorded for Canceled.

func (*CancelRegistry) Canceled added in v0.2.0

func (r *CancelRegistry) Canceled(id string) (by string, ok bool)

Canceled reports whether deploy id was canceled, and by whom.

func (*CancelRegistry) Commit added in v0.2.0

func (r *CancelRegistry) Commit(id string) error

Commit marks the point of no return. It fails with ErrCanceled when the deploy was canceled first; after it succeeds Cancel reports CancelTooLate.

func (*CancelRegistry) Register added in v0.2.0

func (r *CancelRegistry) Register(id string)

Register starts tracking deploy id.

func (*CancelRegistry) Release added in v0.2.0

func (r *CancelRegistry) Release(id string)

Release stops tracking deploy id and frees its context.

type FreezeStatus added in v0.2.0

type FreezeStatus struct {
	Frozen bool
	// Until is when the latest-ending active window closes.
	Until    time.Time
	Reason   string
	WindowID string
}

FreezeStatus is whether automatic deploys to a service are held now.

func ActiveFreeze added in v0.2.0

func ActiveFreeze(windows []store.DeployFreezeWindow, now time.Time) FreezeStatus

ActiveFreeze folds windows into one status at now. A window that fails to evaluate is treated as active: a broken freeze must fail closed.

func CheckFreeze added in v0.2.0

func CheckFreeze(ctx context.Context, fs FreezeStore, serviceName string, now time.Time) (FreezeStatus, error)

CheckFreeze returns serviceName's freeze status from its own and the global windows.

type FreezeStore added in v0.2.0

type FreezeStore interface {
	ListDeployFreezeWindows(ctx context.Context, scopes ...string) ([]store.DeployFreezeWindow, error)
}

FreezeStore lists freeze windows by scope. *store.DB satisfies this.

type FrozenError added in v0.2.0

type FrozenError struct{ Status FreezeStatus }

FrozenError is ErrFrozen with the window that caused it.

func (*FrozenError) Error added in v0.2.0

func (e *FrozenError) Error() string

func (*FrozenError) Is added in v0.2.0

func (e *FrozenError) Is(target error) bool

Is lets errors.Is(err, ErrFrozen) match.

type HeldRequest added in v0.2.0

type HeldRequest struct {
	Kind        string    `json:"kind"`
	Image       string    `json:"image,omitempty"`
	CheckoutRef string    `json:"checkout_ref,omitempty"`
	CommitLabel string    `json:"commit_label,omitempty"`
	CommitSHA   string    `json:"commit_sha,omitempty"`
	Before      string    `json:"before,omitempty"`
	CommitAt    time.Time `json:"commit_at,omitempty"`
	// Branch lets a newer queued deploy supersede older ones of the same branch.
	Branch string `json:"branch,omitempty"`
	// FreezeNote is set when a manual deploy overrode a freeze window, so the
	// queue does not hold it behind that window again.
	FreezeNote string `json:"freeze_note,omitempty"`
	// Build is a queued manual build's original request body.
	Build json.RawMessage `json:"build,omitempty"`
	// AllowPrivateRepoAuth records the requester's ability to mint repo tokens.
	AllowPrivateRepoAuth bool `json:"allow_private_repo_auth,omitempty"`
}

HeldRequest is what a held deploy needs to be replayed after its freeze.

type HeldStore added in v0.2.0

type HeldStore interface {
	SaveDeployAttempt(ctx context.Context, a store.DeployAttempt) error
	ListHeldDeployAttempts(ctx context.Context) ([]store.DeployAttempt, error)
	TransitionDeployAttempt(ctx context.Context, id, from, to, reason string) (bool, error)
}

HeldStore is the store surface holding and releasing needs.

type ImageBuilder

type ImageBuilder interface {
	Build(ctx context.Context, req build.Request, progress func(build.ProgressEvent)) (*build.Result, error)

	// BuildRailpack builds a service whose build.Type is
	// spec.BuildRailpack: Railpack's own provider detection replaces a
	// user-authored Dockerfile, scoped to node and golang only (see
	// deployRailpack). A separate method, not a build.Request field,
	// because internal/build's own solve path is genuinely different
	// (Definition-based, not dockerfile.v0-frontend-based; see
	// internal/build/railpack.go's package doc comment).
	BuildRailpack(ctx context.Context, req build.RailpackRequest, progress func(build.ProgressEvent)) (*build.Result, error)
}

ImageBuilder is the narrow surface this package needs from internal/build, so tests can fake it without a real daemon or BuildKit connection. *build.Client satisfies this.

type ImageDeployStore added in v0.2.0

type ImageDeployStore interface {
	SaveDesiredService(ctx context.Context, svc store.DesiredService) error
	SaveDeployAttempt(ctx context.Context, a store.DeployAttempt) error
	FinishDeployAttempt(ctx context.Context, id, status string, finishedAt time.Time, errMsg string) error
}

ImageDeployStore is the narrow store surface an image deploy trigger needs. *store.DB satisfies this structurally.

type ImageResolution added in v0.2.0

type ImageResolution struct {
	// Image is the reference to write as desired state: pinned to a
	// registry digest whenever one is known.
	Image string
	// Digest is the content identity recorded in deploy history.
	Digest string
	// LocalID is a local image ID, set only when no registry digest exists.
	LocalID string
	// Reason is one of the store.DigestReason* constants.
	Reason string
	// Note carries the registry error behind a fallback, for the operator.
	Note string
}

ImageResolution is what a deploy decided an image reference means.

func ResolveImage added in v0.2.0

func ResolveImage(ctx context.Context, r docker.ImageResolver, image string, auth *docker.RegistryAuth, requireFresh bool) (ImageResolution, error)

ResolveImage turns image into content. A nil resolver keeps the image as given. requireFresh fails instead of falling back to a cached image.

type ImageTrigger added in v0.2.0

type ImageTrigger struct {
	Store    ImageDeployStore
	Nudger   ReconcileNudger
	Resolver docker.ImageResolver
	// Ordered and Sequencer together enable the stale-deploy guard.
	Ordered   OrderedStore
	Sequencer Sequencer
	Logger    *slog.Logger
	Source    string
	// RequireFresh fails instead of deploying a cached image (deploy --pull).
	RequireFresh bool
	// Automatic subjects the deploy to the stale guard; manual ones are exempt.
	Automatic bool
	// Auth authenticates digest resolution against a private registry.
	Auth *docker.RegistryAuth
	// Reason is recorded on the attempt, e.g. a freeze override.
	Reason string
}

ImageTrigger points a service at an image with no build step: manual redeploys and rollbacks, pipeline deploy steps and crashloop rollback. Every optional field degrades to the pre-digest behavior when nil.

func (ImageTrigger) Deploy added in v0.2.0

func (t ImageTrigger) Deploy(ctx context.Context, existing store.DesiredService, image string) (store.DesiredService, error)

Deploy resolves image, writes it as existing's desired image, records a deploy attempt carrying the digest, and nudges the reconciler.

type JobStore added in v0.2.0

type JobStore interface {
	SyncSpecScheduledTasks(ctx context.Context, serviceName string, jobs []store.ScheduledTask, now time.Time, newID func() (string, error)) error
}

JobStore persists a service's spec-declared scheduled jobs. *store.DB satisfies it structurally.

type LoadBalancerStore added in v0.2.0

type LoadBalancerStore interface {
	SetServiceLoadBalancer(ctx context.Context, service, configJSON string) error
}

LoadBalancerStore persists a service's loadbalancer: block from app.yaml. *store.DB satisfies this structurally.

type MultiRequest

type MultiRequest struct {
	// AppName identifies the store.App every fanned-out service is
	// linked to (created if it doesn't exist yet, reused if it does: see
	// DeploySpec's own doc comment). Each service's own DesiredService
	// name is "<AppName>-<serviceKey>", the flat naming convention
	// internal/api/apps_group.go's own doc comment establishes.
	AppName string
	// Services is app.yaml's services: map, keyed by service name (e.g.
	// "web", "worker").
	Services map[string]spec.Service
	// SourceDir is the local checkout root, shared by every service's
	// build: one git checkout produces every service's image.
	SourceDir string
	CommitSHA string
	// ImageRepoBase is the image name without a tag or per-service
	// suffix, e.g. "levelrail/myapp"; each service tags as
	// "<ImageRepoBase>-<serviceKey>:<CommitSHA>".
	ImageRepoBase string
	// SingleServiceName, when set, replaces "<AppName>-<serviceKey>" as
	// the sole service's name. DeploySpec rejects it alongside more than
	// one service. Set only by "apps create --file"'s build.type: static
	// path (internal/api/apps_multi.go), so the deployed site is named
	// after the app itself rather than a redundant "<app>-<service>"
	// suffix; every other caller leaves it empty.
	SingleServiceName string
}

MultiRequest is one deploy attempt for N services under one app, fanned out from a single app.yaml's Spec.Services map. Mirrors Request's own fields, minus ServiceName/Service/ImageRepo (one per service key instead of one for the whole request) and plus AppName and ImageRepoBase.

type Option

type Option func(*Pipeline)

Option configures optional Pipeline behavior.

func WithAppStore

func WithAppStore(s AppStore) Option

WithAppStore enables DeploySpec, the multi-service fan-out entry point (see that method's own doc comment). Without one configured (the default), DeploySpec fails loudly rather than fanning out services with no way to link them to an app row.

func WithAttemptRecorder added in v0.2.0

func WithAttemptRecorder(r AttemptRecorder) Option

WithAttemptRecorder records resolved digests on the request's attempt.

func WithBuildAttest added in v0.2.0

func WithBuildAttest(on bool) Option

WithBuildAttest asks BuildKit for an SBOM and minimal provenance attestation on Dockerfile builds.

func WithBuildCache added in v0.2.0

func WithBuildCache(c BuildCacheProvider) Option

WithBuildCache enables the object storage BuildKit cache for Dockerfile builds. A resolution failure downgrades to no cache with a warning; it never fails the deploy.

func WithBuildMetricsRecorder

func WithBuildMetricsRecorder(recorder BuildMetricsRecorder) Option

WithBuildMetricsRecorder enables recording build.Result.Duration as the build_duration_seconds metric after a successful build. Without one configured (the default), a deploy still succeeds exactly as before, it just isn't measured: a metrics-store outage must never block a real deploy.

func WithImageInspector added in v0.2.0

func WithImageInspector(i docker.ImageInspector) Option

WithImageInspector lets a build record the local image ID it produced.

func WithImageResolver added in v0.2.0

func WithImageResolver(r docker.ImageResolver) Option

WithImageResolver makes image deploys resolve floating tags to digests. Without one, image references are written exactly as given.

func WithJobStore added in v0.2.0

func WithJobStore(s JobStore) Option

WithJobStore makes a deploy keep the service's spec-declared jobs in step with its jobs: list. A spec with no jobs: key leaves jobs created in the dashboard or CLI alone.

func WithLoadBalancerStore added in v0.2.0

func WithLoadBalancerStore(s LoadBalancerStore) Option

WithLoadBalancerStore makes a deploy persist the spec's loadbalancer: block. A spec with no block leaves any UI or CLI configured balancer alone.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger overrides the logger used to report a non-fatal metrics recording failure. Defaults to slog.Default().

func WithOrderedStore added in v0.2.0

func WithOrderedStore(s OrderedStore) Option

WithOrderedStore enables the stale-deploy guard for requests that carry an Order.

func WithRegistryAuthSource added in v0.2.0

func WithRegistryAuthSource(s RegistryAuthSource) Option

WithRegistryAuthSource lets digest resolution authenticate against a private registry.

func WithSecretChecker

func WithSecretChecker(checker SecretChecker) Option

WithSecretChecker enables { secret: true } env vars to pass through deployment instead of being rejected outright. Without one configured (the default), a service declaring any secret-backed env var fails to deploy with an explicit error, the same as before envelope-encrypted secret storage existed: an operator running Levelrail without a master key configured should get a clear failure, not a container silently missing a variable it declared as required.

func WithStaticRootDir

func WithStaticRootDir(dir string) Option

WithStaticRootDir sets the local filesystem directory static site output is copied into, under which every static service gets its own "<ServiceName>/<CommitSHA>" subdirectory. Without one configured (the default, empty string), build.type: static fails its deploy loudly: this package deliberately does not invent a fallback location (e.g. os.TempDir()) for content the ingress controller and Caddy need to keep serving after this process's own temp-file lifecycle might have cleaned it up. The caller (cmd/levelrail) is expected to pass a path under the control plane's own data directory, matching every other on-disk state this codebase keeps there (internal/ingress's WithStorageDir doc comment makes the identical point for Caddy's own certificate storage).

func WithStaticSiteStore

func WithStaticSiteStore(s StaticSiteStore) Option

WithStaticSiteStore enables build.type: static deploys. Without one configured (the default), a service declaring build.type: static fails its deploy loudly rather than silently discarding the copied files, the same "fail loudly without configuration" shape WithSecretChecker already establishes for { secret: true } env vars.

func WithSupplyChain added in v0.2.0

func WithSupplyChain(h SupplyChainHook) Option

WithSupplyChain enables SBOM storage and the scan gate for built images.

func WithVaultConfigChecker added in v0.2.0

func WithVaultConfigChecker(checker VaultConfigChecker) Option

WithVaultConfigChecker enables { vault: { path, key } } env vars to pass through deployment instead of being rejected outright. Without one configured (the default), a service declaring any vault-backed env var fails to deploy with an explicit error, the same "fail loudly without config" shape WithSecretChecker's own absence already produces for { secret: true }.

type OrderedStore added in v0.2.0

type OrderedStore interface {
	SaveDesiredServiceOrdered(ctx context.Context, svc store.DesiredService, order store.DeployOrder, check store.DeployOrderCheck) error
}

OrderedStore saves desired state guarded by deploy ordering.

type Pipeline

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

Pipeline builds a service (when its build type requires a build) and writes the result as that service's new desired state.

func New

func New(builder ImageBuilder, svcStore ServiceStore, opts ...Option) *Pipeline

New builds a Pipeline.

func (*Pipeline) BuildOnly added in v0.2.0

func (p *Pipeline) BuildOnly(ctx context.Context, req BuildOnlyRequest, progress func(build.ProgressEvent)) (string, error)

BuildOnly runs the same builder deploys use and returns the built tag, leaving desired state alone so a pipeline can build, test, and deploy as separate steps.

func (*Pipeline) Deploy

func (p *Pipeline) Deploy(ctx context.Context, req Request, progress func(build.ProgressEvent)) (string, error)

Deploy runs req's build (if its build.Type needs one) and saves the resulting desired state. For every build.Type except spec.BuildStatic, that means the full image reference the application controller converges to on its next reconcile, also what's returned: a freshly built one for spec.BuildDockerfile/spec.BuildRailpack, or, for spec.BuildImage, req.Service.Build.Image passed straight through with no build at all (see deployImage). spec.BuildStatic has no image and no container for the application controller to converge to at all (see static.go's package doc comment): its desired state is a store.StaticSite instead of a store.DesiredService, consumed directly by internal/reconcile/ingress rather than the application controller, and the string this returns is the local directory now being served, not an image reference.

progress, if non-nil, receives build progress as it happens; see build.ProgressEvent and build.SlogProgress. A static deploy never invokes it: there is no build step to report progress on.

func (*Pipeline) DeploySpec

func (p *Pipeline) DeploySpec(ctx context.Context, req MultiRequest, progress func(serviceKey string, ev build.ProgressEvent)) ([]ServiceOutcome, error)

DeploySpec fans out req into N independent Deploy calls, one per service key, under one store.App (created if AppName has none yet, reused otherwise: p.apps.GetAppByName then SaveApp, App.ID == App.Name by this method's own convention, matching migrations/0039_apps.sql's backfill convention for a pre-existing single-service app). The app row is created before any service's build starts, so a partially failed fan-out still leaves every successfully-deployed sibling linked to a real AppID.

Each service key's build failure is captured in that key's own ServiceOutcome.Err and does not block any other key's build or deploy, matching the reconciler's own "one broken resource must not block convergence of everything else" principle (internal/reconcile/engine.go's ReconcileAll doc comment): a caller gets back one outcome per service key, in deterministic (sorted key) order, and must inspect each Err individually rather than treating this call's own returned error as "did it work." The returned error is non-nil only for a failure that blocks every service alike (req.Services empty, or the app row itself couldn't be created or reused); once fan-out begins, DeploySpec itself always returns a nil error and lets each ServiceOutcome carry its own.

progress, if non-nil, is called with each service key's own build.ProgressEvent stream, so a caller can tell which service a given progress line belongs to.

type ReconcileNudger added in v0.2.0

type ReconcileNudger interface {
	Nudge()
}

ReconcileNudger wakes the reconcile loop after a desired-state write.

type RegistryAuthSource added in v0.2.0

type RegistryAuthSource interface {
	RegistryAuth(ctx context.Context, credentialID string) (*docker.RegistryAuth, error)
}

RegistryAuthSource resolves a registry credential ID into pull auth.

type ReleaseHandler added in v0.2.0

type ReleaseHandler func(ctx context.Context, attempt store.DeployAttempt, req HeldRequest) error

ReleaseHandler replays one held request of its kind.

type Releaser added in v0.2.0

type Releaser struct {
	Store    HeldStore
	Freeze   FreezeStore
	Handlers map[string]ReleaseHandler
	Logger   *slog.Logger
	Now      func() time.Time
}

Releaser replays held deploys once their service is no longer frozen. Only the newest held deploy per service runs; older ones are superseded.

func (Releaser) ReleaseDue added in v0.2.0

func (r Releaser) ReleaseDue(ctx context.Context) (int, error)

ReleaseDue releases every service whose freeze has ended, returning how many deploys were replayed.

func (Releaser) Run added in v0.2.0

func (r Releaser) Run(ctx context.Context, interval time.Duration)

Run calls ReleaseDue every interval until ctx ends.

type Request

type Request struct {
	// ServiceName identifies the service, matching the name the
	// application controller (1.3) reconciles under.
	ServiceName string
	// Service is the app.yaml service block: build config, port, env,
	// resources, health.
	Service spec.Service
	// SourceDir is the local checkout root, the build context.
	SourceDir string
	// CommitSHA tags the built image, and is what a future rollback
	// command points desired.Image back at.
	CommitSHA string
	// ImageRepo is the image name without a tag, e.g. "levelrail/thesvg".
	// Naming policy (namespacing, registry prefix) is the caller's
	// decision, not this package's.
	ImageRepo string

	// AttemptID, when set, receives the resolved digest (WithAttemptRecorder).
	AttemptID string
	// Order enables the stale-deploy guard (WithOrderedStore); nil skips it.
	Order *store.DeployOrder
	// RequireFreshImage fails an image deploy whose registry is unreachable
	// instead of falling back to the cached image.
	RequireFreshImage bool
	// Commit, when set, runs right before desired state is written and can
	// veto it (ErrCanceled), the last point a deploy is cancelable.
	Commit func() error
}

Request is one deploy attempt for a single service.

type SecretChecker

type SecretChecker interface {
	Exists(ctx context.Context, serviceName, envKey string) (bool, error)
}

SecretChecker is the narrow surface this package needs from internal/secrets.Manager: whether a value has been set for a { secret: true } env var, never the value itself. A deploy never needs to see a secret's plaintext, only confirm one exists (or doesn't, for a required one) before proceeding.

type Sequencer added in v0.2.0

type Sequencer interface {
	NextDeploySequence(ctx context.Context, serviceName string) (int64, error)
}

Sequencer hands out per-service trigger order.

type ServiceOutcome

type ServiceOutcome struct {
	ServiceKey  string
	ServiceName string
	Image       string
	Err         error
}

ServiceOutcome is one service's own fan-out result: Image is set on success, Err is set on failure, never both.

type ServiceStore

type ServiceStore interface {
	SaveDesiredService(ctx context.Context, svc store.DesiredService) error
	// GetRegistryCredentialByName resolves build.type: image's optional
	// registryCredential field (a name, spec.Build.RegistryCredential's
	// own doc comment on why not an ID) at deploy time, in deployImage.
	GetRegistryCredentialByName(ctx context.Context, name string) (store.RegistryCredential, error)
	// GetDesiredDatabase resolves a { from: "<database>.<field>" } env
	// var's database at deploy time (validateEnv), so an app referencing
	// an unknown database fails the deploy itself rather than only
	// surfacing later as a reconcile failure.
	GetDesiredDatabase(ctx context.Context, name string) (*store.DesiredDatabase, error)
}

ServiceStore is the narrow surface this package needs from internal/store. *store.DB satisfies this.

type StaticSiteStore

type StaticSiteStore interface {
	SaveStaticSite(ctx context.Context, site store.StaticSite) error
}

StaticSiteStore is the narrow surface this package needs from internal/store for build.type: static, so tests can fake it without a real database, the same narrow-interface pattern ServiceStore already establishes for container services. *store.DB satisfies this.

type SupersededError added in v0.2.0

type SupersededError struct {
	// Reason is store.DeployReasonSuperseded or store.DeployReasonStale.
	Reason  string
	Message string
}

SupersededError says why a deploy was not applied.

func (*SupersededError) Error added in v0.2.0

func (e *SupersededError) Error() string

func (*SupersededError) Is added in v0.2.0

func (e *SupersededError) Is(target error) bool

Is lets errors.Is(err, ErrSuperseded) match.

type SupplyChainHook added in v0.2.0

type SupplyChainHook interface {
	AfterBuild(ctx context.Context, app, attemptID string, att build.Attestations) error
}

SupplyChainHook stores a build's SBOM and may veto the release. A non-nil error stops the deploy before desired state changes, so the previous release keeps serving. *supplychain.Service satisfies it.

type VaultConfigChecker added in v0.2.0

type VaultConfigChecker interface {
	VaultConfigured(ctx context.Context) (bool, error)
}

VaultConfigChecker is the narrow surface this package needs to check, at deploy validation time, whether an operator has actually turned on Vault integration before accepting a { vault: { path, key } } env var. Deliberately not the resolver itself (internal/vault.Manager's Resolve): reaching out to a live Vault server on every deploy validation would make an unrelated deploy's success depend on Vault's own uptime, so this only checks configuration, the same "confirm existence, never fetch the value" boundary SecretChecker draws above. Real Vault reachability is still checked, just later: the application controller's own resolveEnv step fails the deploy loudly at container-create time if the configured Vault is unreachable.

Jump to

Keyboard shortcuts

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