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
- Variables
- func CheckOrder(c store.DeployCursor, o store.DeployOrder) error
- func DatabaseRefs(services map[string]spec.Service) []string
- func HoldDeploy(ctx context.Context, st HeldStore, serviceName, image, source string, ...) (string, error)
- func LocalOnlyRef(pin *store.RollbackPin, ref string) bool
- func PreviousKnownGoodImage(attempts []store.DeployAttempt, currentImage string) (image string, ok bool)
- func PruneStaticReleases(ctx context.Context, root, service string, keep int, st StaticRetentionStore) ([]string, error)
- func RemoveStaticTree(root, name string) error
- func RestoreSnapshot(current store.DesiredService, snap store.DeployAttemptSnapshot) store.DesiredService
- func RollbackDesired(current store.DesiredService, target store.DeployAttempt, ...) (svc store.DesiredService, ref string, contentSafe bool)
- func RollbackRef(target store.DeployAttempt, pin *store.RollbackPin) (ref string, contentSafe bool)
- func StaticBuildArgsForTest(env map[string]spec.EnvVar) map[string]string
- func StaticBuildEnvPrefixes() []string
- func StaticKeepReleases() int
- func SupersededReason(err error) (string, bool)
- func ToDesiredService(name, image string, svc spec.Service) (store.DesiredService, error)
- func TriggerImageDeploy(ctx context.Context, st ImageDeployStore, nudger ReconcileNudger, ...) (store.DesiredService, error)
- func ValidateFreezeWindow(w store.DeployFreezeWindow) error
- func WindowActive(w store.DeployFreezeWindow, now time.Time) (bool, time.Time, error)
- type AppStore
- type AttemptRecorder
- type AutoscaleStore
- type BuildCacheProvider
- type BuildMetricsRecorder
- type BuildOnlyRequest
- type BuildSecretSource
- type BuildSecretValues
- type CanaryPolicyStore
- type CancelOutcome
- type CancelRegistry
- func (r *CancelRegistry) Bind(parent context.Context, id string) context.Context
- func (r *CancelRegistry) Cancel(id, by string) CancelOutcome
- func (r *CancelRegistry) Canceled(id string) (by string, ok bool)
- func (r *CancelRegistry) Commit(id string) error
- func (r *CancelRegistry) Register(id string)
- func (r *CancelRegistry) Release(id string)
- type FreezeStatus
- type FreezeStore
- type FrozenError
- type HeldRequest
- type HeldStore
- type ImageBuilder
- type ImageDeployStore
- type ImageResolution
- type ImageTrigger
- type JobStore
- type LoadBalancerStore
- type MaintenanceFreezeStore
- type MultiRequest
- type Option
- func WithAppStore(s AppStore) Option
- func WithAttemptRecorder(r AttemptRecorder) Option
- func WithAutoscaleStore(s AutoscaleStore, ceiling int) Option
- func WithBuildAttest(on bool) Option
- func WithBuildCache(c BuildCacheProvider) Option
- func WithBuildMetricsRecorder(recorder BuildMetricsRecorder) Option
- func WithBuildSecretSource(s BuildSecretSource) Option
- func WithBuildSecretValues(v BuildSecretValues) Option
- func WithCanaryPolicyStore(s CanaryPolicyStore) Option
- func WithImageInspector(i docker.ImageInspector) Option
- func WithImageResolver(r docker.ImageResolver) Option
- func WithJobStore(s JobStore) Option
- func WithLoadBalancerStore(s LoadBalancerStore) Option
- func WithLogger(logger *slog.Logger) Option
- func WithOrderedStore(s OrderedStore) Option
- func WithPublishLocks(l PublishLocks) Option
- func WithRegistryAuthSource(s RegistryAuthSource) Option
- func WithRolloutPolicyStore(s RolloutPolicyStore) Option
- func WithSecretChecker(checker SecretChecker) Option
- func WithSecretScan(h SecretScanHook) Option
- func WithSpendCapStore(s SpendCapStore) Option
- func WithSpendGuard(h SpendGuardHook) Option
- func WithStaticBuildImage(image string) Option
- func WithStaticExtractor(e StaticExtractor) Option
- func WithStaticPublishRecorder(r StaticPublishRecorder) Option
- func WithStaticRootDir(dir string) Option
- func WithStaticRouteLimits(l spec.StaticLimits) Option
- func WithStaticSiteStore(s StaticSiteStore) Option
- func WithSupplyChain(h SupplyChainHook) Option
- func WithVaultConfigChecker(checker VaultConfigChecker) Option
- type OrderedStore
- type Pipeline
- func (p *Pipeline) BuildOnly(ctx context.Context, req BuildOnlyRequest, progress func(build.ProgressEvent)) (string, error)
- func (p *Pipeline) Deploy(ctx context.Context, req Request, progress func(build.ProgressEvent)) (string, error)
- func (p *Pipeline) DeploySpec(ctx context.Context, req MultiRequest, ...) ([]ServiceOutcome, error)
- func (p *Pipeline) SetPortAdmitter(a PortAdmitter)
- func (p *Pipeline) SetStaticPublishHook(h StaticPublishHook)
- type PortAdmitter
- type PublishLocks
- type ReconcileNudger
- type RegistryAuthSource
- type ReleaseHandler
- type Releaser
- type Request
- type RolloutPolicyStore
- type SecretChecker
- type SecretScanHook
- type Sequencer
- type ServiceOutcome
- type ServiceStore
- type SpendCapStore
- type SpendGuardHook
- type StaticExtractor
- type StaticHolds
- type StaticPublishHook
- type StaticPublishRecorder
- type StaticRetentionStore
- type StaticSiteStore
- type StaticTreeStats
- type SupersededError
- type SupplyChainHook
- type VaultConfigChecker
Constants ¶
const ( HeldKindImage = "image" // HeldKindBuild is a queued manual build, replayed from Build. HeldKindBuild = "build" HeldKindGit = "git" // HeldKindPush is a git push parked for approval, replayed from the push mirror. HeldKindPush = "push" )
Held request kinds, each replayed by its own ReleaseHandler.
const DefaultStaticBuildImage = "node:22-bookworm-slim"
DefaultStaticBuildImage is the toolchain image a static build runs in.
const EnvPreviewBuildSecrets = "APP_PREVIEW_BUILD_SECRETS" //nolint:gosec // env var name, not a credential
EnvPreviewBuildSecrets names the env var that, when true, lets preview (pull request) builds use build.secrets. Off by default: a PR author controls app.yaml and the Dockerfile.
const EnvStaticBuildImage = "APP_STATIC_BUILD_IMAGE"
EnvStaticBuildImage overrides DefaultStaticBuildImage.
const MaxFreezeDuration = 31 * 24 * time.Hour
MaxFreezeDuration bounds one freeze window so a typo cannot freeze an app for months.
Variables ¶
var ErrCanceled = errors.New("deploy canceled")
ErrCanceled marks a deploy an operator canceled before it wrote desired state.
ErrFreshImageUnavailable is returned when a deploy requires a fresh registry resolution but no image resolver is available.
var ErrFrozen = errors.New("deploy frozen")
ErrFrozen marks a deploy refused because a freeze window is active.
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.
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.
var ErrUnsafeStaticName = errors.New("deploy: static site name is not a single safe path segment")
ErrUnsafeStaticName is returned when a site name could address a path outside the static root.
var ErrVolumeNameClaimed = fmt.Errorf("deploy: volume name is already claimed by another app")
ErrVolumeNameClaimed means a service's resolved volume name is already held by another service.
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 DatabaseRefs ¶ added in v0.3.0
DatabaseRefs lists the distinct database names the services' { from: ... } env vars point at, so a caller can authorize each before accepting a spec.
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 PruneStaticReleases ¶
func PruneStaticReleases(ctx context.Context, root, service string, keep int, st StaticRetentionStore) ([]string, error)
PruneStaticReleases deletes the oldest release directories of one static site under <root>/<service>, keeping the newest keep (never fewer than 3). It never removes the live release, the previous release, a release used by a queued, running, held or awaiting approval attempt, a release any site still serves (previews and branch deploys included), or a release newer than the live one. It does nothing while a publish lock is set. Safe to repeat.
func RemoveStaticTree ¶
RemoveStaticTree deletes <root>/<name>, the whole published tree of one site. It never touches anything outside root: the name must be one local segment, a symlink at the site path is unlinked rather than followed, and a site path that resolves outside the resolved root is refused. A missing tree is not an error.
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 StaticBuildArgsForTest ¶
StaticBuildArgsForTest exposes the build-arg filter so other packages can prove secrets never reach a build.
func StaticBuildEnvPrefixes ¶
func StaticBuildEnvPrefixes() []string
StaticBuildEnvPrefixes reads APP_STATIC_BUILD_ENV_PREFIXES, the name prefixes of app env vars that reach every framework build (static, Railpack, Dockerfile). Empty after trimming turns the feature off.
func StaticKeepReleases ¶
func StaticKeepReleases() int
StaticKeepReleases reads APP_STATIC_KEEP_RELEASES: how many releases of one static site stay on disk. Unset or invalid gives 10, anything below 3 gives 3.
func SupersededReason ¶ added in v0.2.0
SupersededReason returns the recorded reason for a superseded deploy error.
func ToDesiredService ¶
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
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 AutoscaleStore ¶
type AutoscaleStore interface {
GetAutoscalePolicy(ctx context.Context, app string) (autoscale.Policy, error)
SaveAutoscalePolicy(ctx context.Context, p autoscale.Policy, ev autoscale.Event, now time.Time) error
AutoscaleReplicas(ctx context.Context, app string) (int, bool, error)
}
AutoscaleStore persists a service's autoscale policy and reads the replica count the autoscaler last wrote. *store.DB satisfies this structurally.
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 BuildSecretSource ¶
type BuildSecretSource interface {
BuildSecrets(ctx context.Context, serviceName string) (pkgregistry.Output, error)
}
BuildSecretSource supplies the private package registry config a service's Dockerfile build can mount as BuildKit secrets.
type BuildSecretValues ¶
type BuildSecretValues interface {
Exists(ctx context.Context, serviceName, envKey string) (bool, error)
Resolve(ctx context.Context, serviceName, envKey string) (string, error)
// BuildEnabled reports whether the secret is opted in for build use.
BuildEnabled(ctx context.Context, serviceName, envKey string) (bool, error)
}
BuildSecretValues reads the app's encrypted secrets for build.secrets. internal/secrets.Manager satisfies it.
type CanaryPolicyStore ¶
type CanaryPolicyStore interface {
SetCanaryPolicy(ctx context.Context, p store.CanaryPolicy) error
DeleteCanaryPolicy(ctx context.Context, serviceName string) error
}
CanaryPolicyStore persists a service's saved canary defaults. *store.DB satisfies it.
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
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"`
// BuildArgs are the trigger build args a replay passes to the build again.
BuildArgs map[string]string `json:"build_args,omitempty"`
// PushMirror and HeldRef locate a held push: the mirror and the ref that
// keeps its commit until the hold is decided.
PushMirror string `json:"push_mirror,omitempty"`
HeldRef string `json:"held_ref,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.
func ResolveImageTraced ¶
func ResolveImageTraced(ctx context.Context, tr *deploytrace.Tracer, r docker.ImageResolver, image string, auth *docker.RegistryAuth, requireFresh bool, previous string) (ImageResolution, error)
ResolveImageTraced is ResolveImage recording the digest lookup and the registry auth check on tr. previous is the image the service ran before (to say whether the tag moved); empty when unknown.
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
// RerunOf links the recorded attempt to the one it retries.
RerunOf string
// Locks, when set, holds the deploy instead of making it live while the
// app's publish lock is on. Set it on automatic triggers only.
Locks PublishLocks
}
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 MaintenanceFreezeStore ¶
type MaintenanceFreezeStore interface {
FreezeMaintenanceWindows(ctx context.Context, serviceName string, now time.Time) ([]store.DeployFreezeWindow, error)
}
MaintenanceFreezeStore is the optional source of freeze-enabled maintenance windows; *store.DB satisfies it.
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
// Preview marks a pull request preview build (see Request.Preview).
Preview bool
}
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 ¶
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 WithAutoscaleStore ¶
func WithAutoscaleStore(s AutoscaleStore, ceiling int) Option
WithAutoscaleStore makes a deploy persist the spec's autoscale: block and keep the autoscaler's replica count. ceiling is the global maximum replicas. A spec with no block leaves any dashboard or CLI configured policy alone.
func WithBuildAttest ¶ added in v0.2.0
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 WithBuildSecretSource ¶
func WithBuildSecretSource(s BuildSecretSource) Option
WithBuildSecretSource enables private package registry secret mounts on Dockerfile builds. Without one, builds receive no secrets.
func WithBuildSecretValues ¶
func WithBuildSecretValues(v BuildSecretValues) Option
WithBuildSecretValues enables build.secrets on Dockerfile builds.
func WithCanaryPolicyStore ¶
func WithCanaryPolicyStore(s CanaryPolicyStore) Option
WithCanaryPolicyStore makes a deploy save the spec's canary: block, or clear it when the spec no longer asks for strategy: canary.
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
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 ¶
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 WithPublishLocks ¶
func WithPublishLocks(l PublishLocks) Option
WithPublishLocks makes builds of a locked app finish without becoming live: the would-be desired state is saved as a held build instead.
func WithRegistryAuthSource ¶ added in v0.2.0
func WithRegistryAuthSource(s RegistryAuthSource) Option
WithRegistryAuthSource lets digest resolution authenticate against a private registry.
func WithRolloutPolicyStore ¶
func WithRolloutPolicyStore(s RolloutPolicyStore) Option
WithRolloutPolicyStore makes a deploy save the spec's rollout: block, or clear it when the spec drops the block.
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 WithSecretScan ¶
func WithSecretScan(h SecretScanHook) Option
WithSecretScan enables deploy-time secret scanning.
func WithSpendCapStore ¶
func WithSpendCapStore(s SpendCapStore) Option
WithSpendCapStore makes a deploy persist the spec's budget: block. A spec with no block leaves any dashboard or CLI configured budget alone.
func WithSpendGuard ¶
func WithSpendGuard(h SpendGuardHook) Option
WithSpendGuard enables the block_deploys spend cap guard.
func WithStaticBuildImage ¶
WithStaticBuildImage sets the toolchain image static builds run in.
func WithStaticExtractor ¶
func WithStaticExtractor(e StaticExtractor) Option
WithStaticExtractor enables build.command for static sites.
func WithStaticPublishRecorder ¶
func WithStaticPublishRecorder(r StaticPublishRecorder) Option
WithStaticPublishRecorder enables digest, gate and rollout recording for build.type: static deploys.
func WithStaticRootDir ¶
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 WithStaticRouteLimits ¶
func WithStaticRouteLimits(l spec.StaticLimits) Option
WithStaticRouteLimits sets the caps on a static site's routing rules.
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.
func (*Pipeline) SetPortAdmitter ¶
func (p *Pipeline) SetPortAdmitter(a PortAdmitter)
SetPortAdmitter installs the admitter after construction, since the API router that implements it is built after the pipeline.
func (*Pipeline) SetStaticPublishHook ¶
func (p *Pipeline) SetStaticPublishHook(h StaticPublishHook)
SetStaticPublishHook installs the hook. The API router calls it at boot, after the pipeline exists, because the hook needs the router's credentials.
type PortAdmitter ¶
type PortAdmitter interface {
AdmitPublishedPorts(ctx context.Context, desired store.DesiredService) (release func(), err error)
}
PortAdmitter checks a spec-built service's host ports against every other claim on this server; release runs after the save, like the API's own check.
type PublishLocks ¶
type PublishLocks interface {
GetPublishLock(ctx context.Context, name string) (*store.PublishLock, error)
SaveHeldBuild(ctx context.Context, deployID string, svc store.DesiredService, at time.Time) error
}
PublishLocks is the store surface the pipeline needs to hold a build back from going live while its app is locked.
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
ReleaseDue releases every service whose freeze has ended, returning how many deploys were replayed.
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
// NoCache disables BuildKit's layer cache for a dockerfile build.
NoCache bool
// Preview marks a pull request preview build, which may not use
// build.secrets unless APP_PREVIEW_BUILD_SECRETS is true.
Preview bool
}
Request is one deploy attempt for a single service.
type RolloutPolicyStore ¶
type RolloutPolicyStore interface {
SetRolloutPolicy(ctx context.Context, p store.RolloutPolicy) error
DeleteRolloutPolicy(ctx context.Context, serviceName string) error
}
RolloutPolicyStore persists a service's rollout: block. *store.DB satisfies it.
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 SecretScanHook ¶
type SecretScanHook interface {
BeforeDeploy(ctx context.Context, app, attemptID, root string, env map[string]string) error
}
SecretScanHook scans a deploy's build context and plain env values before anything is built. A non-nil error stops the deploy and the previous release keeps serving.
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 ¶
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 SpendCapStore ¶
type SpendCapStore interface {
SaveSpendCap(ctx context.Context, c spendcap.Cap, ev spendcap.Event, now time.Time) error
}
SpendCapStore persists a service's budget: block from app.yaml. *store.DB satisfies this structurally.
type SpendGuardHook ¶
SpendGuardHook refuses a new deploy of an app held at its spend limit. A non-nil error stops the deploy and the running release keeps serving.
type StaticExtractor ¶
type StaticExtractor interface {
ExtractDir(ctx context.Context, tag, srcPath, destDir string) error
}
StaticExtractor copies a directory out of a built image and discards the image.
type StaticHolds ¶
type StaticHolds interface {
SaveHeldStaticBuild(ctx context.Context, deployID string, site store.StaticSite, at time.Time) error
}
StaticHolds is the extra store surface a locked app's static build needs.
type StaticPublishHook ¶
StaticPublishHook runs after a static site goes live, for example to purge a CDN. Its error is logged and shown in the build log, never returned.
type StaticPublishRecorder ¶
type StaticPublishRecorder interface {
SetDeployAttemptDigest(ctx context.Context, id, digest, reason string) error
SetDeployAttemptRollout(ctx context.Context, id, state string) error
RecordGateEvent(ctx context.Context, e store.DeployGateEvent) error
}
StaticPublishRecorder stamps a static deploy's attempt with what a container deploy gets from the application controller: a content digest, a gate timeline and a serving rollout state. *store.DB satisfies it.
type StaticRetentionStore ¶
type StaticRetentionStore interface {
ListDeployAttempts(ctx context.Context, serviceName string) ([]store.DeployAttempt, error)
ListStaticSites(ctx context.Context) ([]store.StaticSite, error)
GetPublishLock(ctx context.Context, name string) (*store.PublishLock, error)
}
StaticRetentionStore is the read surface pruning needs to know which releases are still in use. *store.DB satisfies it.
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 StaticTreeStats ¶
StaticTreeStats is what hashing a published tree found.
func HashStaticTree ¶
func HashStaticTree(dir string) (StaticTreeStats, error)
HashStaticTree returns a stable digest of every regular file's path and content under dir, so the same tree always hashes the same.
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
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.
Source Files
¶
- autoscale.go
- basedir.go
- build_cache.go
- build_env.go
- build_only.go
- build_secrets.go
- canary_policy.go
- cancel.go
- deploy.go
- digest.go
- external_database.go
- files.go
- freeze.go
- held.go
- jobs.go
- loadbalancer.go
- multi.go
- order.go
- publish_lock.go
- restore.go
- rollback.go
- rollout_policy.go
- secret_scan.go
- spend_guard.go
- spendcap.go
- static.go
- static_build.go
- static_build_env.go
- static_hold.go
- static_publish.go
- static_remove.go
- static_retention.go
- static_routes.go
- supply_chain.go
- trace.go
- translate.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package secretscan finds credentials committed to a build context or stored as plain environment values, and applies a per-app deploy policy.
|
Package secretscan finds credentials committed to a build context or stored as plain environment values, and applies a per-app deploy policy. |