Documentation
¶
Overview ¶
Package controlplane is the Burrow control plane: the product. It runs deploy orchestration, rollout and rollback, logs and status, scaling, the safety guardrails, and the record of who deployed what. It holds the cluster credentials and is the only layer that talks to Kubernetes.
This is the public API surface of the control plane — kept importable by a separate private module (the managed product). It currently holds the domain types (App, Release, Policy) and the seam interfaces (Clock, Kubernetes, Registry, Database) that core logic is written against (ADR-0010); the deploy engine and its constructor land in later phases (docs/PLAN.md). The implementation guts live in controlplane/internal, and in-memory fakes of the seams live in controlplane/internal/fake for tests.
This package is licensed Apache-2.0 (see LICENSING.md and ADR-0033).
Index ¶
- Constants
- Variables
- func AppSecretName(app string) string
- func BackupPath(app, id string) string
- func BuildFitsState(state ClusterResourceState) (fits bool, verdict string)
- func ContextWithClientVersion(ctx context.Context, version string) context.Context
- func EnvScopable(code GuardrailCode) bool
- func HumanCPU(millis int64) string
- func HumanMemory(bytes int64) string
- func ImagePullIssue(image, reason, message string) string
- func IsImagePullReason(reason string) bool
- func KnownGuardrail(code GuardrailCode) bool
- func RegistryHost(image string) string
- func ResolveAutoDeploy(current string, available []string, level AutoDeployLevel) (target string, upgrade string)
- type AddDomainRequest
- type AddProviderRequest
- type AddonInfo
- type AddonSpec
- type AddonType
- type AmbiguousEnvironmentError
- type App
- type AppEnvRef
- type AttachResult
- type AuditEntry
- type AuditFilter
- type AuditOutcome
- type AutoDeployConfig
- type AutoDeployLevel
- type AutoDeployPoller
- type AutoDeployStatus
- type AutoscaleResult
- type AutoscaleSpec
- type Backup
- type BackupResult
- type BackupStatus
- type BuildRequest
- type BuildResult
- type Builder
- type Capability
- type CapacityProber
- type CapacityReport
- type CertManagerCapability
- type Clock
- type ClusterCapabilities
- type ClusterProber
- type ClusterResourceState
- type ConnectBackend
- type Consumer
- type Credentials
- type DNSCapability
- type DNSFactory
- type DNSProvider
- type DNSRecord
- type DNSRecordType
- type Database
- type DatabaseProvisioner
- type DeployRequest
- type DeployResult
- type Deps
- type Disposition
- type DomainResult
- type Engine
- func (e *Engine) AddDomain(ctx context.Context, req AddDomainRequest) (DomainResult, error)
- func (e *Engine) AddEnvironment(ctx context.Context, name, namespace string) (Environment, error)
- func (e *Engine) AddProvider(ctx context.Context, req AddProviderRequest) (Provider, error)
- func (e *Engine) AttachAddon(ctx context.Context, t AddonType, app string) (AttachResult, error)
- func (e *Engine) Audit(ctx context.Context, filter AuditFilter) ([]AuditEntry, error)
- func (e *Engine) AutoDeploy(ctx context.Context, app, env string) (AutoDeployLevel, error)
- func (e *Engine) AutoDeployStatus(ctx context.Context, app, env string) (AutoDeployStatus, error)
- func (e *Engine) Autoscale(ctx context.Context, app, env string, spec AutoscaleSpec, confirm bool) (AutoscaleResult, error)
- func (e *Engine) BackupAddon(ctx context.Context, t AddonType, app string) (BackupResult, error)
- func (e *Engine) Build(ctx context.Context, req BuildRequest) (BuildResult, error)
- func (e *Engine) ClusterCapabilities(ctx context.Context) (ClusterCapabilities, error)
- func (e *Engine) ClusterCapacity(ctx context.Context) (CapacityReport, error)
- func (e *Engine) ConnectAddon(ctx context.Context, backend, endpoint, secretKey, token string) (AddonInfo, error)
- func (e *Engine) DeleteApp(ctx context.Context, app, env string, confirm bool) error
- func (e *Engine) Deploy(ctx context.Context, req DeployRequest) (DeployResult, error)
- func (e *Engine) DetachAddon(ctx context.Context, t AddonType, app string, confirm bool) error
- func (e *Engine) DisableAutoscale(ctx context.Context, app, env string, confirm bool) error
- func (e *Engine) Expose(ctx context.Context, req ExposeRequest) (ExposeResult, error)
- func (e *Engine) Guardrails(ctx context.Context, env string) ([]GuardrailInfo, error)
- func (e *Engine) History(ctx context.Context, app, env string) ([]Release, error)
- func (e *Engine) InstallAddon(ctx context.Context, t AddonType, confirm bool) (AddonInfo, error)
- func (e *Engine) ListAddons(ctx context.Context) ([]AddonInfo, error)
- func (e *Engine) ListApps(ctx context.Context, env string) ([]WorkloadStatus, error)
- func (e *Engine) ListBackups(ctx context.Context, t AddonType, app string) ([]Backup, error)
- func (e *Engine) ListConfig(ctx context.Context, app, env string) (map[string]string, error)
- func (e *Engine) ListEnvironments(ctx context.Context) ([]Environment, error)
- func (e *Engine) ListSecrets(ctx context.Context, app, env string) ([]string, error)
- func (e *Engine) Logs(ctx context.Context, app, env string, opts LogOptions) ([]LogLine, error)
- func (e *Engine) NewAutoDeployPoller(cfg AutoDeployConfig) *AutoDeployPoller
- func (e *Engine) NextTag(ctx context.Context, app, env string) (NextTagResult, error)
- func (e *Engine) Providers(ctx context.Context) ([]Provider, error)
- func (e *Engine) QueryLogs(ctx context.Context, query string, limit int, backend string) ([]LogEntry, error)
- func (e *Engine) QueryMetrics(ctx context.Context, query string, backend string) ([]MetricSample, error)
- func (e *Engine) Reachability(ctx context.Context, app, env string) (ReachabilityResult, error)
- func (e *Engine) RemoveAddon(ctx context.Context, name string, confirm bool) error
- func (e *Engine) RemoveDomain(ctx context.Context, req RemoveDomainRequest) (DomainResult, error)
- func (e *Engine) RestoreAddon(ctx context.Context, t AddonType, app, backupID string, confirm bool) error
- func (e *Engine) Rollback(ctx context.Context, app, env string, confirm bool) (RollbackResult, error)
- func (e *Engine) Run(ctx context.Context, req RunRequest) (RunResult, error)
- func (e *Engine) Scale(ctx context.Context, app, env string, replicas int32, confirm bool) (ScaleResult, error)
- func (e *Engine) SetAutoDeploy(ctx context.Context, app, env string, level AutoDeployLevel) error
- func (e *Engine) SetConfig(ctx context.Context, app, env, key, value string, noRestart bool) error
- func (e *Engine) SetGuardrail(ctx context.Context, env string, code GuardrailCode, d Disposition) error
- func (e *Engine) SetSecret(ctx context.Context, app, env, key, value string, noRestart bool) error
- func (e *Engine) Status(ctx context.Context, app, env string) (StatusResult, error)
- func (e *Engine) Unexpose(ctx context.Context, app, env string) error
- func (e *Engine) UnsetConfig(ctx context.Context, app, env, key string, noRestart bool) error
- func (e *Engine) UnsetSecret(ctx context.Context, app, env, key string, noRestart bool) error
- type Environment
- type ExposeRequest
- type ExposeResult
- type ExposeSpec
- type ExposureStatus
- type GuardrailCode
- type GuardrailError
- type GuardrailInfo
- type IDSource
- type IngressCapability
- type Kubernetes
- type LoadBalancerCapability
- type LogEntry
- type LogLine
- type LogOptions
- type LogsQuerier
- type MetricSample
- type MetricsQuerier
- type MetricsServerCapability
- type MissingPrerequisitesError
- type NextTagResult
- type NextTags
- type NodeAllocatable
- type NodeCapacity
- type PodRequest
- type Policy
- type Prerequisite
- type Provider
- type ProviderCapability
- type ProviderType
- type ReachabilityResult
- type RegistryAuth
- type RegistryClient
- type Release
- type ReleaseStatus
- type ReleaseTrigger
- type RemoveDomainRequest
- type Resolver
- type RollbackResult
- type RunRequest
- type RunResult
- type RunSpec
- type ScaleResult
- type SourceCredential
- type SourceRef
- type StatusResult
- type StorageCapability
- type WorkloadKind
- type WorkloadSpec
- type WorkloadStatus
Constants ¶
const ( // ReasonImagePullBackOff is the kubelet's back-off state after repeated pull failures. ReasonImagePullBackOff = "ImagePullBackOff" // ReasonErrImagePull is the kubelet's first pull failure, before it backs off. ReasonErrImagePull = "ErrImagePull" )
Blocking image-pull waiting reasons a container reports when the cluster cannot fetch the image. These are the only reasons Burrow surfaces as an Issue: a private registry with no pull credentials is the common, human-fixable cause (ADR-0017). Transient reasons like ContainerCreating or PodInitializing are deliberately excluded — they resolve on their own.
const DefaultAutoDeployBackoff = time.Hour
DefaultAutoDeployBackoff is how long the watcher holds off after a tag's deploy fails (or after a registry rate-limits it with no Retry-After hint): a failed tag is not re-attempted before this, so a bad image cannot become a redeploy crash-loop (ADR-0052 §5). A strictly newer tag is always tried; the hold applies only to re-attempting the exact tag that failed.
const DefaultAutoDeployInterval = 5 * time.Minute
DefaultAutoDeployInterval is the conservative default poll cadence (ADR-0052 §7): ~5 minutes. The watcher is deliberately not a low-latency channel, because it need not be — the explicit deploy is always the immediate path — so a conservative interval protects registry rate limits without a latency complaint.
const DefaultAutoDeployLevel = AutoDeployOff
DefaultAutoDeployLevel is the level that applies to every app, new or already deployed, until an operator sets one: off, so auto-deploy is opt-in — the watcher never polls or moves an app that has not deliberately opted in (ADR-0058, revising ADR-0052 §2's on-by-default default). This keeps a pre-existing app from being silently polled the moment a cluster is upgraded to a version carrying the poller; set a level (patch/minor/major) to turn auto-deploy on.
const DefaultEnvironment = "default"
DefaultEnvironment is the name of the implicit environment that always exists: the app namespace burrowd already runs against (BURROW_NAMESPACE), behaving exactly like before environments were introduced (ADR-0035 phase 2). It is reserved — it cannot be created with `burrow env add`, because it is synthesized rather than stored — and it is always listed first.
const ReleaseAnnotation = "burrow.cloud/release"
ReleaseAnnotation is the pod-template annotation Burrow stamps with the release ID so every deploy of a new release rolls the workload, even when the image reference is unchanged. A re-deploy of the same image is a new release with a new ID, so the pod template differs and the server-side apply triggers a rollout (replacing pods whose per-creation state is stale — e.g. an imagePullSecret bound before `burrow config registry login` fixed the pull credential).
const RestartedAtAnnotation = "burrow.cloud/restarted-at"
RestartedAtAnnotation is the pod-template annotation Burrow bumps to a fresh timestamp to force a rolling update when something the pod reads only at start changes — notably a secret value under an existing key, which does not otherwise mutate the template (ADR-0028).
Variables ¶
var ( // ErrInvalid marks a malformed request — a bad app name, an empty image // reference, a negative replica count. The caller must fix the request; retrying // it unchanged will fail the same way. ErrInvalid = errors.New("invalid request") // ErrNotImplemented marks an operation whose backing adapter is not wired in this // build yet (e.g. the cluster adapter before it ships). It is an honest-status // signal (ADR-0009), distinct from a malformed request or a system failure. ErrNotImplemented = errors.New("not implemented") // ErrNotFound marks a requested record or resource that does not exist. The seams // (seams.go) return it — possibly wrapped — so engine logic can branch on absence // with errors.Is without depending on a particular adapter. ErrNotFound = errors.New("not found") )
The control plane classifies its failures with these sentinels so a front end (the HTTP API, the MCP server) can map them to the right status without parsing prose. They complement the typed GuardrailError (a deliberate policy refusal).
Functions ¶
func AppSecretName ¶
AppSecretName is the per-app Kubernetes Secret that holds an app's secret env (ADR-0028): one object per app in the app namespace, keys = env-var names, values = secret values. The values live only here — never inlined into the Deployment spec, never written to Postgres, and never carried over MCP (ADR-0029/0004). A value may transit burrowd's authenticated control-plane API on `secret set`, which writes it here. The name is derived from the app (a DNS-1123 label, so the result is always a valid Secret name), so every layer computes it the same way rather than passing it around.
func BackupPath ¶
BackupPath is the on-PVC path of app's dump for id, recorded in the backup row and used by the kube Job builder so both sides agree on the layout without the engine importing the kube package (ADR-0032). It is a path on a volume burrowd does not mount — never a credential.
func BuildFitsState ¶ added in v0.13.0
func BuildFitsState(state ClusterResourceState) (fits bool, verdict string)
BuildFitsState reports whether a typical in-cluster build (buildRequestCPUMillis / buildRequestMemBytes — a quarter CPU and 512 MB) would currently schedule on the given resource state, and returns the short plain-language verdict to surface when it would not. It runs the same pure headroom math as ClusterCapacity, so the build pre-flight (issue #274) and the capacity report (issue #275) can never disagree. The kube build adapter calls it before creating a build Job to fail fast with an actionable message instead of leaving a Job stuck Pending.
func ContextWithClientVersion ¶ added in v0.10.0
ContextWithClientVersion returns a context carrying the acting client's release version for the audit record (ADR-0039). The API layer sets it from the X-Burrow-Client-Version header; the engine reads it back via clientVersionFromContext when it records a guarded operation. An empty version leaves the context unchanged, so a pre-handshake client records no version.
func EnvScopable ¶ added in v0.7.0
func EnvScopable(code GuardrailCode) bool
EnvScopable reports whether a guardrail can be scoped to a named environment (ADR-0035 phase 2c). The app-level guardrails (app.*) gate per-app operations that always carry an environment, so they can be locked down per environment — strict prod, permissive staging. The cluster-level guardrails (addon.*, dns.*) gate cluster-wide operations that are not env-scoped (installing an add-on or writing DNS affects the whole cluster), so they are only ever set globally.
func HumanCPU ¶ added in v0.13.0
HumanCPU renders a milli-CPU amount in plain language — "½ a CPU", "¼ of a CPU", "a tenth of a CPU", "2 CPUs" — never machine units like "500m" (issue #275/#277). Sub-CPU amounts snap to the nearest familiar fraction (a tenth, ¼, ⅓, ½, ⅔, ¾); amounts of one CPU or more show the whole count with a fractional glyph (e.g. "1½ CPUs"). Amounts that do not land on a named fraction are prefixed "about", so an arbitrary reading (e.g. a DOKS node's ~620m of system overhead) still reads as "about ⅔ of a CPU". This is the plain-language convention the README uses.
func HumanMemory ¶ added in v0.13.0
HumanMemory renders a byte count in MB or GB (issue #275/#277). It divides by the binary MiB/GiB (so a 512Mi request reads as "512 MB" and 2Gi as "2 GB", matching how the manifests and README state these) and trims a trailing ".0". Amounts below a gigabyte show as MB; a gigabyte or more as GB with up to one decimal.
func ImagePullIssue ¶ added in v0.8.0
ImagePullIssue builds the actionable Issue message for a workload whose pod cannot pull its image. burrowd no longer resolves the image before deploy (ADR-0040), so a bad tag or a missing credential surfaces here, asynchronously, as the kubelet's pull failure. When the kubelet's waiting message clearly reports the image is not present (a "manifest unknown" / "not found"), the Issue names the tag as the likely fix; otherwise it defaults to the common, human-fixable cause — a private registry with no pull credentials (ADR-0017) — and names the exact `burrow config registry login` command. The credential step is human- and CLI-only and never crosses MCP (ADR-0017), so the message points at the user's terminal.
func IsImagePullReason ¶ added in v0.8.0
IsImagePullReason reports whether reason is a blocking image-pull failure Burrow surfaces as an actionable Issue. A pod waiting for any other reason (still creating, initializing, …) is not reported, so Status never flags a transient state as a problem.
func KnownGuardrail ¶
func KnownGuardrail(code GuardrailCode) bool
KnownGuardrail reports whether code names a configurable guardrail.
func RegistryHost ¶ added in v0.8.0
RegistryHost returns the registry host of an image reference following the Docker convention: the first "/"-separated component is the host when it looks like one (it contains a "." or a ":", or is "localhost"); otherwise the reference is an implicit Docker Hub name and the host is "docker.io". Examples: "ghcr.io/org/app:1" -> "ghcr.io", "library/nginx" -> "docker.io", "registry.example.com:5000/app:1" -> "registry.example.com:5000".
func ResolveAutoDeploy ¶ added in v0.13.0
func ResolveAutoDeploy(current string, available []string, level AutoDeployLevel) (target string, upgrade string)
ResolveAutoDeploy decides what an app on `current` should auto-deploy to, given the tags `available` in the registry and the auto-deploy `level`. It returns the tag to deploy (target, "" if none) and the highest version that exists ABOVE the level's cap (upgrade, "" if none) for surfacing as an available upgrade (ADR-0052 §2/§3). Upgrades only; only semver tags count.
The returned strings are the ORIGINAL tags as they appeared in `available` (e.g. "v1.2.7" stays "v1.2.7"), so the caller deploys the exact tag rather than a normalized form.
Non-semver tags in `available` (latest, a git SHA, a date) are ignored — they cannot be classified patch/minor/major, so the watcher never chases them (ADR-0052 §4). Prerelease versions (e.g. 1.3.0-rc1) are excluded: only stable releases are eligible for auto-deploy or for surfacing as an available upgrade. If `current` does not parse as semver there is no basis to compare, so the result is ("", "").
Types ¶
type AddDomainRequest ¶
type AddDomainRequest struct {
Host string `json:"host"`
Provider string `json:"provider"`
// Address is the external IP or hostname to point Host at. Optional when App is set.
Address string `json:"address,omitempty"`
// App is an exposed application whose ingress external address Host should point at, used
// when Address is empty so the agent need not look the address up itself.
App string `json:"app,omitempty"`
// Confirm acknowledges the dns.write guardrail so the operation proceeds past it.
Confirm bool `json:"confirm,omitempty"`
}
AddDomainRequest points a host at an address through a configured DNS provider (ADR-0018). The address is the cluster's external entry point — the ingress controller's IP or hostname. Supply it explicitly with Address, or name an exposed App and the control plane reads the controller-assigned address from that app's Ingress (the value `burrow app reachability` reports).
type AddProviderRequest ¶
type AddProviderRequest struct {
// Name identifies the provider; empty defaults to the type.
Name string `json:"name,omitempty"`
// Type is the vendor this provider talks to.
Type ProviderType `json:"type"`
// SecretKey is the key in burrow-credentials the token is written under; empty defaults to Name.
SecretKey string `json:"secret_key,omitempty"`
// Token is the vendor API token VALUE. It is written to burrow-credentials after validation and
// is never logged, stored in Postgres, or echoed back (ADR-0030).
Token string `json:"token,omitempty"`
}
AddProviderRequest registers a vendor credential (ADR-0023, ADR-0030). The token VALUE travels in this request over burrowd's authenticated, TLS-protected control-plane API; burrowd validates it and then writes it into the burrow-credentials Secret. The value is never logged, never stored in Postgres, never returned in a response, and still never carried over MCP — provider add is a human/CLI operation, not an agent one.
type AddonInfo ¶
type AddonInfo struct {
Name string `json:"name"`
Type AddonType `json:"type"`
// Mode is how the backend is provided: "installed" (Burrow deployed it) or "connected"
// (an existing backend the user runs). Installed-only for now; connect lands later (ADR-0026).
Mode string `json:"mode"`
// Backend is the concrete adapter implementation backing this add-on (e.g. "victorialogs").
Backend string `json:"backend,omitempty"`
Image string `json:"image,omitempty"`
Endpoint string `json:"endpoint"` // in-cluster host:port the app or agent reaches it on
Capabilities []string `json:"capabilities"`
// SecretKey is the non-secret key under which this add-on's bearer token lives in the
// burrow-credentials Secret (ADR-0023). Empty means the backend is unauthenticated; the
// token itself never travels here — only the key (ADR-0004).
SecretKey string `json:"secret_key,omitempty"`
// CreatedAt is when the add-on was registered, read from the injected clock.
CreatedAt time.Time `json:"created_at,omitempty"`
// Ready is a live property — whether the backing Deployment is available. It is probed
// from the cluster at list time and never persisted in the registry.
Ready bool `json:"ready"`
}
AddonInfo is one installed add-on instance, as seen by `addon list` and the agent. It carries no secret — when an add-on needs a credential it lives in a cluster Secret, never here.
type AddonSpec ¶
type AddonSpec struct {
Type AddonType
// Backend is the concrete adapter implementation that backs this add-on (e.g.
// "victorialogs"), recorded in the registry so the agent knows which adapter serves it.
Backend string
// Image is the pinned container image for the backing service.
Image string
// Port is the service port the app (or the agent, for an observability add-on) reaches it on.
Port int32
// StorageGi requests a persistent volume of this size in GiB; 0 is ephemeral. Stateful
// stores (logs, metrics) persist so data survives a restart.
StorageGi int
// Capabilities are what the agent can query this add-on for (e.g. "logs"). For an
// installed default it is fixed; a connected backend may derive or probe its own (ADR-0026).
Capabilities []string
// Summary is a one-line description for the catalog listing.
Summary string
}
AddonSpec is a catalog entry: how to deploy and reach one vetted backing service. The catalog is curated and permissively licensed (Apache / MIT / BSD) so Burrow can bundle it without copyleft friction (ADR-0025) — which is why logs is VictoriaLogs (Apache), not AGPL Loki.
func AddonCatalog ¶
func AddonCatalog() []AddonSpec
AddonCatalog returns the catalog entries in a stable order.
func LookupAddon ¶
LookupAddon returns the catalog spec for t, or false if t is not a known add-on type.
type AddonType ¶
type AddonType string
AddonType identifies a building-block backing service in the curated catalog (ADR-0025).
const ( // AddonLogs is a log-aggregation store (VictoriaLogs, Apache-2.0). AddonLogs AddonType = "logs" // AddonMetrics is a metrics store (VictoriaMetrics single-node, Apache-2.0) paired with a // vmagent scraper that collects app-pod metrics and remote-writes them into it. AddonMetrics AddonType = "metrics" // AddonCache is an in-memory cache (ValKey, BSD-3) the agent wires an app to — a backing // service the app connects to, not one the agent queries, so it has no query seam. AddonCache AddonType = "cache" // AddonPostgres is a cluster-shared PostgreSQL instance (the official postgres image, PostgreSQL // License) the agent attaches an app to — Burrow provisions a database and login role per app // inside the one instance and writes the app's DATABASE_URL into its per-app Secret (ADR-0031). AddonPostgres AddonType = "postgres" )
type AmbiguousEnvironmentError ¶ added in v0.11.0
type AmbiguousEnvironmentError struct {
// Environments are the registered environments the caller must choose among (the implicit
// `default` first, then the named ones in name order, as ListEnvironments returns them).
Environments []Environment
}
AmbiguousEnvironmentError reports that a mutating operation arrived with no environment named while more than one environment is registered, so burrowd refuses to pick one rather than silently defaulting to the implicit `default` environment (ADR-0047 §1). It is a structured outcome, not a system failure: the request was understood, but its target is ambiguous — an unanswered question, not a held operation. It lists the registered environments (the implicit default first, then the named ones) with their namespaces so the caller re-issues the operation naming an explicit environment, rather than letting a state change land on the default by accident. The check is on registration, not reachability: ambiguity is a static fact about how many environments exist, with no network probe (ADR-0047 §1). Callers distinguish it with AsAmbiguousEnvironment; the HTTP API maps it to a 4xx with the machine-readable "ambiguous_environment" code.
func AsAmbiguousEnvironment ¶ added in v0.11.0
func AsAmbiguousEnvironment(err error) (*AmbiguousEnvironmentError, bool)
AsAmbiguousEnvironment reports whether err is (or wraps) an AmbiguousEnvironmentError and returns it, mirroring AsGuardrail and AsMissingPrerequisites so a front end (the HTTP API, the MCP server) can surface the structured refusal without parsing prose.
func (*AmbiguousEnvironmentError) Error ¶ added in v0.11.0
func (e *AmbiguousEnvironmentError) Error() string
type App ¶
type App struct {
// Name identifies the app within the control plane and names its Kubernetes
// workload. It must be a DNS-1123 label (lowercase alphanumerics and '-').
Name string
}
App is a deployable application: the logical unit an agent operates. A single App has a history of Releases; the most recent successful one is what is running.
type AppEnvRef ¶ added in v0.13.0
AppEnvRef names one (app, environment) pair — a unit the pull-based watcher reconciles (ADR-0052 Phase 4b). Env is the canonical environment name (the reserved "default" for the implicit default environment).
type AttachResult ¶
type AttachResult struct {
App string `json:"app"`
Addon AddonType `json:"addon"`
// SecretKey is the env-var name under which the generated connection string was written into
// the app's per-app Secret. The value is never returned (ADR-0029/0031).
SecretKey string `json:"secret_key"`
}
AttachResult is the outcome of attaching an app to an add-on (ADR-0031). It carries the KEY NAME the connection string was written under (e.g. "DATABASE_URL") — never the value, which lives only in the app's Kubernetes Secret.
type AuditEntry ¶
type AuditEntry struct {
// ID is the store-assigned row identity (0 before it is persisted). Newest rows have the
// largest IDs.
ID int64 `json:"id,omitempty"`
// Timestamp comes from the injected clock, never ambient time (ADR-0010).
Timestamp time.Time `json:"timestamp"`
// Operation is the logical operation: deploy, scale, rollback, app_delete, expose,
// dns_write, dns_delete, addon_install, addon_remove.
Operation string `json:"operation"`
// Target is what the operation acted on: an app, a host, or an add-on name. It may be
// empty for an operation that names nothing.
Target string `json:"target,omitempty"`
// Args is the redacted, per-operation allowlist of salient parameters. Never secret values.
Args map[string]string `json:"args,omitempty"`
// GuardrailCode is the guardrail that applied (e.g. app.expose_public). Empty on an execution
// row, which is the second half of a trail whose decision row already named the guardrail.
GuardrailCode string `json:"guardrail_code,omitempty"`
// Disposition is the guardrail's effective disposition (allow/confirm/deny) at decision time.
Disposition string `json:"disposition,omitempty"`
// Outcome is the categorical result (see AuditOutcome).
Outcome AuditOutcome `json:"outcome"`
// Result is the execution result: empty on success, or a short error summary on a failed
// outcome. It never carries a secret value.
Result string `json:"result,omitempty"`
// Caller is the authenticated caller. Identity is coarse until an auth model exists
// (ADR-0027): today it is a constant naming the control-plane boundary.
Caller string `json:"caller,omitempty"`
// Principal is the acting identity — the actor behind the operation, distinct from Caller
// (the control-plane boundary that authenticated the request). Today every agent shares one
// ServiceAccount, so it is a constant ("shared-agent"); the principal seam (ADR-0038) is
// where a future TokenReview→SSO identity resolution fills in a real per-user value, so
// attribution becomes additive rather than a migration of past rows' meaning.
Principal string `json:"principal,omitempty"`
// ClientVersion is the release version of the client (CLI or MCP server) that drove the
// operation, read from the X-Burrow-Client-Version handshake header (ADR-0039). It records which
// client acted, next to the principal (who) and the server's own version — so version skew is
// legible after the fact. Empty for a pre-handshake client that sent no version. The json tag must
// match the client-side AuditEntry tag exactly (the struct crosses the API), or the field would
// silently drop.
ClientVersion string `json:"client_version,omitempty"`
}
AuditEntry is one append-only record of a guarded mutating operation and the guardrail decision that applied (ADR-0027). It is written at the control-plane boundary — the single choke point that holds both the credentials and the decision.
Args is redacted by construction: it carries only safe metadata (app/host/addon name, image reference, replica count, env/secret key NAMES) and never an env value or any secret value. Callers build it through the per-operation allowlist, never by serializing a raw request.
type AuditFilter ¶
type AuditFilter struct {
// App restricts to rows whose target is this app/host/addon name; empty matches any.
App string
// Operation restricts to one operation name; empty matches any.
Operation string
// Outcome restricts to one outcome; empty matches any.
Outcome AuditOutcome
// Limit caps the number of rows returned, newest first. Zero or negative applies the
// store's default cap.
Limit int
}
AuditFilter narrows an audit query. A zero value matches everything (subject to Limit).
type AuditOutcome ¶
type AuditOutcome string
AuditOutcome is what happened to a guarded mutating operation once the guardrail had its say (ADR-0027). It is the categorical fact a reviewer reads off the trail.
const ( // AuditAllowed: the guardrail allowed the operation (allow disposition, or a confirm // disposition the caller confirmed). It is recorded before the operation executes; the // execution outcome is a second row. AuditAllowed AuditOutcome = "allowed" // AuditHeld: a confirm-disposition guardrail held the operation for confirmation and the // caller did not confirm. The operation did NOT execute. A later confirmed run is a // separate row. AuditHeld AuditOutcome = "held" // AuditDenied: a deny-disposition guardrail refused the operation outright. It did NOT // execute. AuditDenied AuditOutcome = "denied" // AuditExecuted: the operation was allowed-or-confirmed and ran to success. AuditExecuted AuditOutcome = "executed" // AuditFailed: the operation was allowed-or-confirmed and ran, but its execution errored. // The error is summarized in AuditEntry.Result; a distinct outcome keeps "it ran and // broke" readable apart from "it ran and worked" (ADR-0027). AuditFailed AuditOutcome = "failed" )
type AutoDeployConfig ¶ added in v0.13.0
type AutoDeployConfig struct {
// Interval is the base poll cadence. Zero or negative applies DefaultAutoDeployInterval.
Interval time.Duration
// Backoff is how long a failed tag (or a hint-less rate-limit) is held off. Zero applies
// DefaultAutoDeployBackoff.
Backoff time.Duration
// Jitter returns the actual wait before the next cycle, given the base interval, so many
// pollers do not stampede the registry in lockstep (ADR-0052 §7). Nil applies a default of
// the base interval +/- up to 10%. It is injected so a test drives the cadence deterministically.
Jitter func(base time.Duration) time.Duration
// After returns a channel that fires after d — the seam the run loop waits on between cycles.
// Nil applies time.After. It is injected (rather than reading the wall clock) so a test drives
// the loop with no real sleeping and no ambient time (ADR-0010).
After func(d time.Duration) <-chan time.Time
}
AutoDeployConfig configures the pull-based watcher (ADR-0052 Phase 4b). The zero value is valid: every field falls back to a safe default, so cmd/burrowd can start the poller with an empty config, and a test can drive it deterministically by supplying Interval, Jitter, and After.
type AutoDeployLevel ¶ added in v0.13.0
type AutoDeployLevel string
AutoDeployLevel is how far the pull-based passive watcher may move an app on its own (ADR-0052): it caps auto-deploy to patch, minor, or major upgrades, or turns it off. The level is set per app and per environment; the watcher only ever moves an app forward, to the highest semver version within the level's cap that is greater than the running release.
const ( // AutoDeployOff disables auto-deploy: only an explicit CLI or agent deploy ships a release // (ADR-0052 §2). Setting off is also how a version is pinned. It is the default: auto-deploy // is opt-in, so an app is never polled until an operator sets a level (ADR-0058). AutoDeployOff AutoDeployLevel = "off" // AutoDeployPatch auto-deploys patches within the current minor only (e.g. 1.2.6, 1.2.7 for // an app on 1.2.5); crossing to a new minor is a manual step (ADR-0052 §2). AutoDeployPatch AutoDeployLevel = "patch" // AutoDeployMinor auto-deploys any patch or minor upgrade within the current major (e.g. // 1.2.6, 1.3.0, 1.4.0 for an app on 1.2.5), never a major (ADR-0052 §2). AutoDeployMinor AutoDeployLevel = "minor" // AutoDeployMajor auto-deploys anything newer, including a breaking major (e.g. 2.0.0) — // fully hands-off updates for the operator who accepts the risk (ADR-0052 §2). AutoDeployMajor AutoDeployLevel = "major" )
func ParseAutoDeployLevel ¶ added in v0.13.0
func ParseAutoDeployLevel(s string) (AutoDeployLevel, error)
ParseAutoDeployLevel parses an auto-deploy level from a CLI argument, rejecting unknown values with a message that lists the valid ones (ADR-0052 §6). It is case-insensitive and tolerates surrounding whitespace.
func (AutoDeployLevel) Valid ¶ added in v0.13.0
func (l AutoDeployLevel) Valid() bool
Valid reports whether l is a known auto-deploy level.
type AutoDeployPoller ¶ added in v0.13.0
type AutoDeployPoller struct {
// contains filtered or unexported fields
}
AutoDeployPoller is the pull-based passive-deploy watcher (ADR-0052): on a bounded, jittered cadence it lists each app's image tags and, when a newer tag within the app's auto-deploy level exists, drives the SAME guarded deploy an explicit call would — same rollout, deploy record, rollback handle, and audit entry, distinguished only by its auto provenance. It is outbound-only (it never accepts an inbound connection) and fail-soft: one app's registry error or deploy failure is logged and never stops the loop or affects another app (ADR-0040). It reads no ambient time or randomness — the cadence comes from the injected After/Jitter seams and every backoff deadline from the engine's injected clock (ADR-0010).
func (*AutoDeployPoller) Run ¶ added in v0.13.0
func (p *AutoDeployPoller) Run(ctx context.Context)
Run polls until ctx is cancelled, reconciling every candidate (app, env) on each cycle. It runs a reconcile pass immediately, then waits a jittered interval before the next, honoring cancellation promptly (ADR-0052 §7). With no registry seam wired it logs and returns rather than spinning — the watcher is optional and degrades to off when the outbound registry client is absent (ADR-0040).
type AutoDeployStatus ¶ added in v0.13.0
type AutoDeployStatus struct {
App string `json:"app"`
Env string `json:"env"`
Level AutoDeployLevel `json:"level"`
Repository string `json:"repository,omitempty"` // current image reference with the tag stripped (e.g. "ghcr.io/user/app"), for building the deploy hint
Current string `json:"current,omitempty"` // current running semver tag, when known
Target string `json:"target,omitempty"` // tag auto-deploy would move to within the level, "" if none / already current
Upgrade string `json:"upgrade,omitempty"` // highest version above the level's cap, surfaced as an available upgrade, "" if none
Checked bool `json:"checked"` // whether the registry upgrade check actually ran
Note string `json:"note,omitempty"` // when Checked is false, a short human reason
// DisabledReason is why auto-deploy is off, when it was turned off by the safety stop rather than
// by a human (ADR-0052 §5): "disabled by rollback" or "disabled by downgrade". Empty when the
// level was human-set or is not off.
DisabledReason string `json:"disabled_reason,omitempty"`
}
AutoDeployStatus is the enriched, read-only view of an app's auto-deploy configuration in one environment (ADR-0052 §2/§3): the level plus, when the registry could be listed, the current running version, the tag auto-deploy would move to within the level, and the highest version above the level's cap surfaced as an available upgrade. When the upgrade check could not run — the registry is unreachable or needs credentials, the current tag is not semver, there is no running release, or the check is not wired — Checked is false and Note carries a short human reason; the level is still reported. The check is deliberately best-effort: a registry failure never errors the show, keeping this path independent of registry reachability (ADR-0040).
type AutoscaleResult ¶ added in v0.8.0
type AutoscaleResult struct {
App string `json:"app"`
Env string `json:"env,omitempty"`
MinReplicas int32 `json:"min_replicas"`
MaxReplicas int32 `json:"max_replicas"`
CPUPercent int32 `json:"cpu_percent"`
MemoryPercent int32 `json:"memory_percent,omitempty"`
MetricsAvailable bool `json:"metrics_available"`
Warning string `json:"warning,omitempty"`
}
AutoscaleResult reports the outcome of configuring autoscaling: the effective spec that was applied, the app and environment it acted in, and whether metrics-server is present. When it is absent, MetricsAvailable is false and Warning explains that the autoscaler is set but will not scale until metrics-server is installed — the HPA is applied regardless (its creation does not need metrics-server; only its scaling does).
type AutoscaleSpec ¶ added in v0.8.0
type AutoscaleSpec struct {
// MinReplicas is the floor the autoscaler will not scale below. Must be at least 1.
MinReplicas int32 `json:"min_replicas"`
// MaxReplicas is the ceiling the autoscaler will not scale above. Must be >= MinReplicas and is
// itself bounded by the replica-ceiling guardrail (Policy.MaxReplicas).
MaxReplicas int32 `json:"max_replicas"`
// CPUPercent is the target average CPU utilization (1..100) the autoscaler holds the app at.
CPUPercent int32 `json:"cpu_percent"`
// MemoryPercent is the target average memory utilization (1..100), or 0 to add no memory metric.
MemoryPercent int32 `json:"memory_percent,omitempty"`
}
AutoscaleSpec is the desired autoscaling shape for an app's Deployment (ADR-0006): the replica band the HorizontalPodAutoscaler moves within and the resource-utilization targets it scales on. It is code-free metadata that travels over the API. CPUPercent is required; MemoryPercent is optional (0 means no memory metric).
type Backup ¶
type Backup struct {
// ID is the backup identifier, minted from the IDs seam — also the dump filename stem.
ID string `json:"id"`
// App is the application whose database was dumped.
App string `json:"app"`
// CreatedAt is when the backup was recorded, read from the injected clock.
CreatedAt time.Time `json:"created_at"`
// Path is the on-PVC location of the dump (e.g. /backups/<app>/<id>.dump). It is a path on a
// volume burrowd does not mount — never a credential.
Path string `json:"path,omitempty"`
// SizeBytes is the dump's size in bytes, or 0 when unknown.
SizeBytes int64 `json:"size_bytes,omitempty"`
// Status is the lifecycle state of this backup.
Status BackupStatus `json:"status"`
}
Backup is one recorded per-app database backup (ADR-0032): the control-plane index row for a logical dump written to the backup PVC. It names the app, the on-PVC path, the byte size (0 when unknown), and the lifecycle status — never a credential or a connection string. burrowd is not mounted to the backup PVC, so this row, not the volume, is what `addon backups` lists.
type BackupResult ¶
type BackupResult struct {
Backup Backup `json:"backup"`
}
BackupResult reports the outcome of an on-demand backup (ADR-0032): the recorded backup row. It carries the backup id, the app, the path, the size, and the status — never a secret value.
type BackupStatus ¶
type BackupStatus string
BackupStatus is the lifecycle state of a recorded Postgres backup (ADR-0032). A backup is recorded pending when its Job is started and moves to completed on the Job's success or failed on its failure.
const ( // BackupPending is a backup whose Job has been started but has not yet completed. BackupPending BackupStatus = "pending" // BackupCompleted is a backup whose Job finished successfully; the dump is on the PVC. BackupCompleted BackupStatus = "completed" // BackupFailed is a backup whose Job did not complete successfully. BackupFailed BackupStatus = "failed" )
func (BackupStatus) Valid ¶
func (s BackupStatus) Valid() bool
Valid reports whether s is a known BackupStatus.
type BuildRequest ¶ added in v0.13.0
type BuildRequest struct {
App string `json:"app"`
// Env is the environment to deploy the built image into (ADR-0035): empty or "default" targets the
// implicit default environment, a registered name targets that environment. An unregistered name
// is an error, surfaced by the deploy the build hands off to.
Env string `json:"env,omitempty"`
// Source is the git reference the builder clones and checks out inside the cluster.
Source SourceRef `json:"source"`
// TargetImage is the pullable repo:tag reference the built image is pushed to; the resulting deploy
// pins the digest the builder returns. In this phase it is supplied explicitly; the optional
// in-cluster registry as the zero-config default push target (ADR-0053 §5) is a later phase.
TargetImage string `json:"target_image"`
// Confirm acknowledges the app.deploy guardrail whose disposition is confirm, letting the deploy
// the build hands off to proceed past it (ADR-0020). It has no effect on a guardrail set to deny.
Confirm bool `json:"confirm,omitempty"`
}
BuildRequest is the code-free description of an in-cluster build-then-deploy (ADR-0053): the app, the git source to clone and build inside the cluster, and the target image reference the built image is pushed to. No code travels here (ADR-0004) — only the git reference and the target reference; the builder clones the source from git inside the cluster. On success the built image rejoins the existing guarded deploy path (ADR-0053 §4), so a build is a front-end that ends where deploy begins.
type BuildResult ¶ added in v0.13.0
type BuildResult struct {
// Digest is the content digest of the built image (e.g. "sha256:..."), the immutable identity the
// deployed release pins.
Digest string `json:"digest"`
// Deploy is the guarded deploy the built image flowed into (ADR-0053 §4).
Deploy DeployResult `json:"deploy"`
}
BuildResult reports the outcome of a successful build-then-deploy (ADR-0053 §4): the digest of the image the builder produced and the deploy that shipped it. Because the build ends where deploy begins, Deploy carries the same new release, rollback handle, and hints an explicit deploy returns — downstream cannot tell a built image apart from an externally-built one except in provenance.
type Builder ¶ added in v0.13.0
type Builder interface {
// Build clones source inside the cluster, builds an image, pushes it to targetImage (a pullable
// repo:tag reference), and returns the resulting image content digest (e.g. "sha256:..."). Only
// the git reference and the target reference cross into the builder — never source bytes; the
// builder clones the actual code from git inside the cluster (ADR-0004/0053 §3). It returns an
// error on any clone, build, or push failure; the caller surfaces that structurally and does NOT
// touch the deploy path. On success the returned digest is the immutable identity the resulting
// guarded deploy pins (ADR-0053 §4).
//
// insecure marks the push target as a plain-HTTP registry the push must not verify TLS against —
// set only for the in-cluster registry, which serves plain HTTP in-cluster (ADR-0054 §5). The
// engine is the single place that knows this: it sets insecure when it defaults the target to the
// in-cluster registry, and leaves it false for a caller-supplied external target, which is pushed
// over TLS. The base-image pull during the build always uses TLS regardless — insecure applies
// only to the push to targetImage.
//
// cred is the resolved source-provider credential (ADR-0057). When it carries a token the builder
// authenticates the clone to a PRIVATE git source and the buildah push/pull to the provider's
// registry with it, by mounting it into the build Job — never as a Job env var or command-line
// argument. The zero value (IsZero) is the public-source, credential-free path. The token is a
// secret: an implementation must not log it, echo it, or place it in an error.
Build(ctx context.Context, source SourceRef, targetImage string, insecure bool, cred SourceCredential) (digest string, err error)
}
Builder builds a container image from a git source reference inside the user's own cluster and pushes it to a target registry (ADR-0053). It is a seam — a real adapter (a Kubernetes build Job) and a fake — like every other Burrow dependency that touches the cluster, the registry, the clock, or the database. The interface is deliberately MINIMAL (ADR-0053 §6): it takes a source reference and a target image reference and returns the resulting image digest or an error — nothing more. Isolation and sandboxing are expressed INSIDE an implementation, never as interface knobs, so the separate commercial multi-tenant product can supply a hardened, sandboxed executor behind this same seam without the OSS interface having to anticipate its needs (ADR-0053 §6/§7). Building is code execution; in the single-tenant OSS path the user owns both the cluster and the source, so no sandbox is required (ADR-0053 §7). It is an OPTIONAL seam: nil is allowed, and the build path errors cleanly (ErrNotImplemented) when it is not wired.
type Capability ¶
type Capability string
Capability is a service Burrow can drive a provider to perform on the user's behalf (ADR-0023). A capability is served by a chosen provider — DNS is the first; compute and registry capabilities follow. Selecting which provider serves a capability is explicit for now (the operation names a --provider); auto-detection comes later.
const ( // CapabilityDNS is managing DNS records for a domain the user has delegated to Burrow. CapabilityDNS Capability = "dns" // CapabilitySource is authenticating to a source provider (GitHub, GitLab, …): the in-cluster // build uses the provider's token to clone a PRIVATE git source and — where the same provider // hosts a registry — to authenticate the build's image push/pull (ADR-0057). One provider token // covers both, so a single credential serves the clone and the provider registry. CapabilitySource Capability = "source" )
type CapacityProber ¶ added in v0.13.0
type CapacityProber interface {
// ReadResourceState reads node allocatable and pod requests read-only. It never writes.
ReadResourceState(ctx context.Context) (ClusterResourceState, error)
}
CapacityProber reads the cluster's scheduling-capacity facts read-only over the Kubernetes API: each node's allocatable and every pod's summed requests, with no metrics-server required (issue #275). It is the seam over those reads so the engine's headroom/verdict logic stays pure and unit-testable against a fake. The production adapter (controlplane/kube) wraps a client-go clientset. It is an OPTIONAL seam — nil is allowed, and a capacity read errors cleanly (ErrNotImplemented) when it is not wired. It never writes.
type CapacityReport ¶ added in v0.13.0
type CapacityReport struct {
// Nodes is the per-node breakdown, one entry per schedulable node.
Nodes []NodeCapacity `json:"nodes"`
// Cluster is the cluster-wide total (its Name is empty).
Cluster NodeCapacity `json:"cluster"`
// TopCPU lists the pods requesting the most CPU, most first; TopMemory the most memory.
TopCPU []Consumer `json:"top_cpu"`
TopMemory []Consumer `json:"top_memory"`
// BuildCPUMillis / BuildMemBytes are the resource request the verdict pre-flights (a typical
// in-cluster build: a quarter of a CPU, 512 MB).
BuildCPUMillis int64 `json:"build_cpu_millis"`
BuildMemBytes int64 `json:"build_mem_bytes"`
// BuildFits is true when at least one node has enough free CPU AND memory at once to schedule
// that build. A build pod lands on a single node, so headroom split across nodes does not count.
BuildFits bool `json:"build_fits"`
// BuildFitsNode names the node the build would schedule on when BuildFits is true.
BuildFitsNode string `json:"build_fits_node,omitempty"`
// Verdict is the short, plain-language recommendation (plain CPU units, memory in MB/GB).
Verdict string `json:"verdict"`
// UtilizationNote records that this is scheduling headroom only and what metrics-server would add.
UtilizationNote string `json:"utilization_note"`
}
CapacityReport answers "is my cluster at capacity, do I need to scale, and what is using the most CPU/memory?" from the Kubernetes API alone (issue #275). It reports, per node and cluster-total, allocatable / committed (sum of requests) / free headroom; the top consumers by CPU and by memory request; and a short plain-language verdict — whether a typical in-cluster build would currently schedule and whether another node is needed. It is SCHEDULING headroom (requests vs allocatable), which is what determines whether a pod schedules and needs no metrics-server; live utilization is a separate layer (see UtilizationNote, issue #276).
type CertManagerCapability ¶
type CertManagerCapability struct {
Present bool `json:"present"`
}
CertManagerCapability reports whether cert-manager is installed. Present is true when the cert-manager.io API group is served — i.e. its CRDs are installed — detected via API-group discovery, which needs no RBAC.
type Clock ¶
Clock is the control plane's only source of time. Injecting it keeps core logic deterministic: a release's CreatedAt and any timeouts come from here, never from time.Now.
type ClusterCapabilities ¶
type ClusterCapabilities struct {
// Ingress is the ingress-controller situation: whether a controller is actually running, and
// which IngressClass(es) exist.
Ingress IngressCapability `json:"ingress"`
// Storage is the default-StorageClass situation: whether a default exists and its name.
Storage StorageCapability `json:"storage"`
// LoadBalancer is whether Service type=LoadBalancer is likely supported, detected from whatever
// services LoadBalancers — a cloud provider, k3s's servicelb, or MetalLB.
LoadBalancer LoadBalancerCapability `json:"load_balancer"`
// CertManager is whether cert-manager is installed, detected via its API group (its CRDs).
CertManager CertManagerCapability `json:"cert_manager"`
// MetricsServer is whether metrics-server is serving the Kubernetes Metrics API (metrics.k8s.io),
// detected via API-group discovery. It powers `kubectl top`, HPA CPU/memory autoscaling, and the
// utilization layer of capacity reporting; Burrow auto-ensures it as a baseline (ADR-0054 §1).
MetricsServer MetricsServerCapability `json:"metrics_server"`
// Provider is the detected cloud provider, inferred from node labels / providerID.
Provider ProviderCapability `json:"provider"`
// DNS is whether a DNS provider is configured in the registry (ADR-0023) — a control-plane
// fact, not a cluster read. It is filled by the engine, not the cluster probe.
DNS DNSCapability `json:"dns"`
}
ClusterCapabilities is a neutral, read-only report of what a cluster can do (ADR-0034): an ingress controller and its IngressClass, a default StorageClass, LoadBalancer support, whether cert-manager is installed, the cloud provider, and whether a DNS provider is configured. It is the low-trust agent entry point — the agent can survey a cluster and explain its state before anything is changed. It replaces the cluster-type picker: Burrow observes the cluster rather than asking the user to classify it. Each capability reports present / absent / inferred, never an opinion about whether the cluster "should" have it.
type ClusterProber ¶
type ClusterProber interface {
// DetectCapabilities reads the cluster's capabilities read-only. It never writes.
DetectCapabilities(ctx context.Context) (ClusterCapabilities, error)
}
ClusterProber detects a cluster's capabilities read-only (ADR-0034): it reads IngressClasses, ingress-nginx controller Deployments, StorageClasses, and Nodes, and uses API-group discovery to detect cert-manager, then detects LoadBalancer support from whatever services LoadBalancers (a cloud provider, servicelb, or MetalLB). It is the seam over those reads so the engine stays unit-testable against a fake; the production adapter (controlplane/kube) wraps a client-go clientset, and the same detection runs whether driven by the kubeconfig client (install) or burrowd's in-cluster client. It returns only the cluster-derived capabilities; the DNS field is filled by the engine from the providers registry. It is an optional seam — present only when wired; ClusterCapabilities errors cleanly (ErrNotImplemented) when it is nil.
type ClusterResourceState ¶ added in v0.13.0
type ClusterResourceState struct {
Nodes []NodeAllocatable
Pods []PodRequest
}
ClusterResourceState is the raw scheduling-capacity facts read from the Kubernetes API alone (node allocatable and the per-pod sum of resource requests) — no metrics-server involved. The engine turns it into a CapacityReport; keeping the seam's output raw keeps the headroom, top consumers, and verdict math pure and unit-testable against a fake.
type ConnectBackend ¶
type ConnectBackend struct {
// Name is the backend identifier (e.g. "loki"), used as the add-on Backend and as the
// querier key the engine dispatches on.
Name string
// Capabilities are what the agent can query a connected instance of this backend for. They
// are derived from the backend, not declared by the user (ADR-0026): a single-capability
// backend like Loki implies "logs".
Capabilities []string
// Summary is a one-line description for the connectable-backend listing.
Summary string
}
ConnectBackend is a catalog entry for an existing backend the user already runs that Burrow can register and query (ADR-0026). Unlike an AddonSpec it carries no image or storage: connect is registration-only — Burrow never deploys a connected backend, it queries the one already there.
func ConnectCatalog ¶
func ConnectCatalog() []ConnectBackend
ConnectCatalog returns the connectable backends in a stable name order.
func LookupConnectBackend ¶
func LookupConnectBackend(name string) (ConnectBackend, bool)
LookupConnectBackend returns the catalog entry for name, or false if name is not a known connectable backend.
type Consumer ¶ added in v0.13.0
type Consumer struct {
Namespace string `json:"namespace"`
Name string `json:"name"`
Node string `json:"node,omitempty"`
CPUMillis int64 `json:"cpu_millis"`
MemBytes int64 `json:"mem_bytes"`
}
Consumer is one pod's contribution to the committed total, for the top-consumers lists. CPUMillis is milli-CPU; MemBytes is bytes. It is a request figure, not live usage.
type Credentials ¶
type Credentials interface {
// Token returns the token stored under key in burrow-credentials, or ErrNotFound when
// the Secret or the key is absent.
Token(ctx context.Context, key string) (string, error)
// SetToken upserts key=value into burrow-credentials, creating the Secret if absent
// (ADR-0030). The value arrives over burrowd's authenticated, TLS-protected control-plane
// API — never over MCP — and is written straight to the Secret: it is NEVER logged, never
// stored in Postgres, and never returned in a response. Any error names the key only, never
// the value.
SetToken(ctx context.Context, key, value string) error
}
Credentials is the seam over the one burrow-credentials Secret that holds every vendor token (ADR-0023, ADR-0030). The control plane reads a provider's token through it at call time, so a rotation is picked up with no restart, and writes a token value it received over its authenticated control-plane API. It is the only path by which the control plane reads or writes a Secret's contents, and the production adapter is scoped to that single object — burrowd's least-privilege Role grants `get` and `update` on exactly burrow-credentials and nothing else.
type DNSCapability ¶
type DNSCapability struct {
Configured bool `json:"configured"`
Providers []string `json:"providers,omitempty"`
}
DNSCapability reports whether a DNS provider is configured in the registry (ADR-0023). Configured is true when at least one provider serves the DNS capability; Providers names them.
type DNSFactory ¶
type DNSFactory interface {
DNS(t ProviderType, token string) (DNSProvider, error)
}
DNSFactory builds a DNSProvider for a vendor type and token (ADR-0023). It is the seam that lets the engine reach a vendor without importing its adapter: production maps each ProviderType to its adapter (controlplane/dns), and tests substitute a fake. It returns ErrNotImplemented for a type no adapter serves.
type DNSProvider ¶
type DNSProvider interface {
// VerifyAccess confirms the token authenticates and can manage DNS, with a cheap read
// call against the vendor. It returns ErrInvalid when the vendor rejects the token.
VerifyAccess(ctx context.Context) error
// EnsureRecord creates or updates the record so r.Name resolves to r.Value (ADR-0018). It
// is idempotent: re-applying the same record is a no-op. It returns ErrNotFound when no
// zone the provider manages covers r.Name.
EnsureRecord(ctx context.Context, r DNSRecord) error
// DeleteRecord removes the A/CNAME record(s) the provider holds for host. It returns
// ErrNotFound when no managed zone covers host or no such record exists.
DeleteRecord(ctx context.Context, host string) error
}
DNSProvider is the seam over a single vendor's DNS API, holding one provider's token (ADR-0018, ADR-0023). burrowd is the only thing that talks to the vendor — the agent never holds the token and never calls the API directly. Writes are scoped to the zones the provider manages: an operation on a host no managed zone covers returns ErrNotFound.
type DNSRecord ¶
type DNSRecord struct {
// Type is A or CNAME.
Type DNSRecordType
// Name is the fully-qualified host, e.g. app.example.com.
Name string
// Value is the target: an IPv4 address for an A record, a hostname for a CNAME.
Value string
// TTL is the record's time to live in seconds; 0 means the provider's default.
TTL int
}
DNSRecord is one record the control plane manages on the user's behalf.
type DNSRecordType ¶
type DNSRecordType string
DNSRecordType is the kind of DNS record the control plane manages (ADR-0018). A host is pointed at an IPv4 address with an A record or at another hostname with a CNAME; the engine chooses based on the address it is given.
const ( RecordA DNSRecordType = "A" RecordCNAME DNSRecordType = "CNAME" )
type Database ¶
type Database interface {
// SaveRelease persists r. An existing release with the same ID is overwritten
// (releases are immutable in meaning; this supports status transitions during a
// single rollout).
SaveRelease(ctx context.Context, r Release) error
// Release returns the release with the given ID, or ErrNotFound.
Release(ctx context.Context, id string) (Release, error)
// LatestRelease returns the most recently saved release for app in env, or ErrNotFound if
// the app has no releases there. Releases are keyed per (app, environment) (ADR-0052 Phase 4a):
// env is the canonical environment name (the reserved "default" for the implicit default
// environment).
LatestRelease(ctx context.Context, app, env string) (Release, error)
// Releases returns all releases for app in env, oldest first, keyed per (app, environment).
// An app with no releases there yields an empty slice and no error.
Releases(ctx context.Context, app, env string) ([]Release, error)
// ListReleases returns all releases for app in env, NEWEST first — the deploy timeline the
// history surface reads (the same rows deploys already write, read the other way round from
// Releases). Releases are keyed per (app, environment) (ADR-0052 Phase 4a). An app with no
// releases there yields an empty slice and no error.
ListReleases(ctx context.Context, app, env string) ([]Release, error)
// DeleteReleases removes all release records for app across every environment — the durable
// side of an app teardown, which removes the whole app. Deleting the releases of an app that
// has none is a no-op, not an error.
DeleteReleases(ctx context.Context, app string) error
// AppEnv returns the non-secret environment store for app: the app-global current
// config rendered into the workload at apply time (ADR-0028). An app with no env yields
// an empty map and no error.
AppEnv(ctx context.Context, app string) (map[string]string, error)
// SetAppEnv upserts one env key for app in the store.
SetAppEnv(ctx context.Context, app, key, value string) error
// UnsetAppEnv removes one env key for app from the store. Removing a key that is not set
// is a no-op, not an error.
UnsetAppEnv(ctx context.Context, app, key string) error
// Policy returns the current guardrail policy: the stored guardrail dispositions
// overlaid on the built-in defaults (DefaultPolicy), so a store with nothing set
// returns DefaultPolicy and newly-added guardrails get a sensible default (ADR-0020).
Policy(ctx context.Context) (Policy, error)
// SetGuardrail persists the disposition for one guardrail — the write behind
// `guard set`. It rejects an invalid disposition.
SetGuardrail(ctx context.Context, code GuardrailCode, d Disposition) error
// AutoDeployLevel returns the auto-deploy level configured for app in the named environment
// (ADR-0052 §2). A missing configuration resolves to DefaultAutoDeployLevel (off): auto-deploy is
// opt-in, so an app with no stored row is off and is never polled (ADR-0058). env is the canonical
// environment name (the reserved "default" for the implicit default environment).
AutoDeployLevel(ctx context.Context, app, env string) (AutoDeployLevel, error)
// SetAutoDeployLevel upserts the auto-deploy level for app in the named environment — the write
// behind `burrow app auto-deploy <app> <level>`. It rejects an invalid level. It CLEARS any
// stored disable reason: a human setting the level is the deliberate re-enable action that
// removes a rollback or downgrade note (ADR-0052 §5).
SetAutoDeployLevel(ctx context.Context, app, env string, level AutoDeployLevel) error
// DisableAutoDeploy sets app's level to off in the named environment AND records why (e.g.
// "disabled by rollback") — the safety stop of ADR-0052 §5, so the watcher does not fight a
// deliberate downgrade. It upserts, overwriting any prior level and reason.
DisableAutoDeploy(ctx context.Context, app, env, reason string) error
// AutoDeployCandidates returns the distinct (app, environment) pairs the pull-based watcher may
// reconcile: every app that has a recorded release, paired with the environment it was released
// into (ADR-0052 Phase 4b). Candidacy is "has a running release" — the set the poller can compare
// a registry tag against — not "has a stored level row"; the poller reads each pair's level and
// skips those that are off, which is the default (ADR-0058), so an app that never opted in is read
// and skipped before any registry call. None yields an empty slice and no error.
AutoDeployCandidates(ctx context.Context) ([]AppEnvRef, error)
// AutoDeployReason returns the stored disable reason for app in the named environment, or ""
// when the level was human-set or is the default (no stored override) — the reason surfaced
// next to an off level (ADR-0052 §5).
AutoDeployReason(ctx context.Context, app, env string) (string, error)
// SaveProvider upserts a provider in the registry by name (ADR-0023). It stores only
// the non-secret registry entry — type, capabilities, and the key under which the token
// lives in the burrow-credentials Secret — never the token itself.
SaveProvider(ctx context.Context, p Provider) error
// Provider returns the provider with the given name, or ErrNotFound.
Provider(ctx context.Context, name string) (Provider, error)
// Providers returns all configured providers, name order. None yields an empty slice
// and no error.
Providers(ctx context.Context) ([]Provider, error)
// SaveAddon upserts an add-on in the registry by name (ADR-0025). It stores the non-secret
// registry entry — type, mode, backend, endpoint, and capabilities — never the live
// readiness, which is probed from the cluster.
SaveAddon(ctx context.Context, a AddonInfo) error
// Addon returns the add-on with the given name, or ErrNotFound.
Addon(ctx context.Context, name string) (AddonInfo, error)
// Addons returns all registered add-ons, name order. None yields an empty slice and no
// error. The returned entries carry no live readiness.
Addons(ctx context.Context) ([]AddonInfo, error)
// DeleteAddon removes the named add-on from the registry, or ErrNotFound if absent.
DeleteAddon(ctx context.Context, name string) error
// AppendAudit appends one audit row (ADR-0027). The log is append-only: there is no
// update or delete path. The store assigns the row identity and orders rows by it.
AppendAudit(ctx context.Context, entry AuditEntry) error
// Audit returns audit rows matching filter, newest first, capped by filter.Limit (a
// store default when unset). No matches yields an empty slice and no error.
Audit(ctx context.Context, filter AuditFilter) ([]AuditEntry, error)
// RecordBackup persists a new backup row (ADR-0032). burrowd records it pending before
// starting the backup Job. The row names the app, the on-PVC path, and the status — never a
// credential. An existing row with the same ID is overwritten.
RecordBackup(ctx context.Context, b Backup) error
// SetBackupStatus updates a recorded backup's status (and, when known, its size) — the
// completed/failed transition burrowd writes when the Job finishes. Setting the status of an
// unknown backup id returns ErrNotFound.
SetBackupStatus(ctx context.Context, id string, status BackupStatus, sizeBytes int64) error
// ListBackups returns recorded backups, newest first. An empty app lists every app's backups;
// a non-empty app restricts to that app. No matches yields an empty slice and no error.
ListBackups(ctx context.Context, app string) ([]Backup, error)
// GetBackup returns the backup with the given id, or ErrNotFound.
GetBackup(ctx context.Context, id string) (Backup, error)
// CreateEnvironment registers a named environment mapping name to namespace (ADR-0035 phase 2).
// It rejects a duplicate name (the name is the primary key) with an ErrInvalid-wrapped error.
// The reserved `default` environment is never stored here — it is synthesized by the engine.
CreateEnvironment(ctx context.Context, name, namespace string) error
// ListEnvironments returns the registered environments ordered by name. None yields an empty
// slice and no error. The synthesized `default` environment is not included; the engine
// prepends it.
ListEnvironments(ctx context.Context) ([]Environment, error)
// GetEnvironment returns the registered environment with the given name, or ErrNotFound.
GetEnvironment(ctx context.Context, name string) (Environment, error)
}
Database is the seam over the control plane's own durable state (Postgres in production): the deploy records that form the history and the rollback handles (ADR-0007). It stores domain Releases; it is independent of cluster state.
type DatabaseProvisioner ¶
type DatabaseProvisioner interface {
// EnsureAppDatabase idempotently provisions an isolated database and login role for app on the
// shared instance and returns the app's DATABASE_URL (a postgres:// connection string carrying
// a freshly generated role password). It rotates the role password on every call, so a re-attach
// returns a fresh, working URL with no orphaned state. The app name is validated against a strict
// identifier pattern and every SQL identifier is quoted BEFORE any SQL runs, so a name can never
// carry SQL. The returned string is a secret value — the caller writes it straight into the app's
// Secret and never logs, audits, or returns it.
EnsureAppDatabase(ctx context.Context, app string) (databaseURL string, err error)
// DropAppDatabase removes app's database and login role from the shared instance — the
// destructive side of detach. Dropping a database/role that is already absent is a no-op, not an
// error. The app name is validated before any SQL, exactly as in EnsureAppDatabase.
DropAppDatabase(ctx context.Context, app string) error
}
DatabaseProvisioner is the seam over the installed Postgres add-on's admin surface (ADR-0031). burrowd connects to the shared instance as the superuser and gives each app its own database and login role inside it; the engine calls this on attach/detach. It is an optional seam — present only when the Postgres add-on path is wired; the engine errors cleanly (ErrNotImplemented) on an attach when it is nil. The connection string it returns is a secret VALUE: it is handed only to SetSecretValue and never logged, audited, returned, or carried over MCP (ADR-0029/0031).
type DeployRequest ¶
type DeployRequest struct {
App string `json:"app"`
// Env is the environment to deploy into (ADR-0035 phase 2b): empty or "default" targets the
// implicit default environment's namespace, a registered name targets that environment's
// namespace. An unregistered name is an error.
Env string `json:"env,omitempty"`
Image string `json:"image"`
Command []string `json:"command,omitempty"`
// MetricsPort, when positive, annotates the deployed pod so the metrics add-on scrapes
// /metrics on this container port (ADR-0026). Zero adds no annotations.
MetricsPort int32 `json:"metrics_port,omitempty"`
Replicas int32 `json:"replicas"`
// Confirm acknowledges a guardrail whose disposition is confirm, letting the operation
// proceed past it (ADR-0020). It has no effect on a guardrail set to deny.
Confirm bool `json:"confirm,omitempty"`
}
DeployRequest is the small, code-free description of a deploy: a pullable image plus metadata (ADR-0004). No code travels here. Env is deliberately absent: an app's non-secret config is an independently-managed, app-global store, sourced at apply time rather than passed per deploy (ADR-0028) — set it with SetConfig before deploying.
type DeployResult ¶
type DeployResult struct {
// Release is the new release that is now running.
Release Release `json:"release"`
// SupersededReleaseID is the release this deploy replaced, or "" if it was the
// first deploy of the app. It is the handle a rollback would return to.
SupersededReleaseID string `json:"superseded_release_id,omitempty"`
// Hints are non-blocking notes about the deploy the agent can reason over (ADR-0052 §8): today,
// a nudge toward semver when the deployed tag cannot be classified for auto-update. They never
// gate or fail the deploy — any reference deploys (ADR-0007). Empty when there is nothing to note.
Hints []string `json:"hints,omitempty"`
}
DeployResult reports the outcome of a successful deploy.
type Deps ¶
type Deps struct {
Kubernetes Kubernetes
Database Database
Clock Clock
IDs IDSource
Resolver Resolver
// Credentials reads vendor tokens from the burrow-credentials Secret (ADR-0023).
Credentials Credentials
// DNS builds a DNSProvider for a vendor type and token (ADR-0023).
DNS DNSFactory
// Logs maps a backend id (e.g. "victorialogs", "loki") to the querier serving an installed
// or connected logs add-on. Optional — an empty or nil map is allowed, and the engine errors
// cleanly on a logs query when no querier is wired for the add-on's backend (ADR-0026).
Logs map[string]LogsQuerier
// Metrics maps a backend id (e.g. "prometheus", "victoriametrics") to the querier serving an
// installed or connected metrics add-on. Optional — an empty or nil map is allowed, and the
// engine errors cleanly on a metrics query when no querier is wired for the add-on's backend
// (ADR-0026).
Metrics map[string]MetricsQuerier
// DatabaseProvisioner provisions a per-app database and role on the installed Postgres add-on
// (ADR-0031). Optional — nil is allowed, and the engine errors cleanly (ErrNotImplemented) on a
// Postgres attach when it is not wired.
DatabaseProvisioner DatabaseProvisioner
// ClusterProber detects the cluster's read-only capabilities (ADR-0034). Optional — nil is
// allowed, and the engine errors cleanly (ErrNotImplemented) on a capabilities read when it is
// not wired.
ClusterProber ClusterProber
// CapacityProber reads the cluster's scheduling-capacity facts (node allocatable, pod requests)
// for the headroom surface (issue #275). Optional — nil is allowed, and the engine errors cleanly
// (ErrNotImplemented) on a capacity read when it is not wired.
CapacityProber CapacityProber
// RegistryClient lists an image repository's tags for the auto-deploy read/watch (ADR-0052).
// Optional — nil is allowed, and the auto-deploy show degrades to reporting the level alone
// when it is not wired. It is OUTBOUND-only and never used on the core deploy path (ADR-0040).
RegistryClient RegistryClient
// Builder builds an image from a git source reference inside the cluster for the optional
// in-cluster build path (ADR-0053). Optional — nil is allowed, and the engine errors cleanly
// (ErrNotImplemented) on a build when it is not wired.
Builder Builder
// BuildRegistry is the in-cluster registry reference host:port the in-cluster build defaults its
// push target to when the caller supplies none — the zero-config default push target for a build
// (ADR-0053 §5). Optional — an empty value means a build with no explicit target errors, and a
// caller-supplied target always overrides it (external registries stay fully supported). burrowd
// sets it from BURROW_BUILD_REGISTRY, which `burrow cluster registry install` wires to the
// in-cluster registry it deploys.
BuildRegistry string
// BuildPublicRegistry is the PUBLIC registry host the in-cluster build's resulting deploy
// references so the node pulls through the ingress over TLS, distinct from BuildRegistry (the
// internal push endpoint) — ADR-0054 §5. Optional — an empty value falls back to referencing the
// internal push endpoint. burrowd sets it from BURROW_BUILD_PUBLIC_REGISTRY, which
// `burrow cluster registry install --host` wires to the registry's public ingress hostname.
BuildPublicRegistry string
// AppNamespace is the namespace burrowd deploys apps into (BURROW_NAMESPACE) — the namespace of
// the implicit `default` environment (ADR-0035 phase 2). Optional — an empty value defaults to
// "default", matching the kube Adapter.
AppNamespace string
}
Deps are the dependencies an Engine needs. All seams are required. The guardrail policy is not a dependency here: the engine reads the live policy from the Database seam on each guarded operation (ADR-0020), so a `guard set` takes effect without restarting.
type Disposition ¶
type Disposition string
Disposition is how the control plane enforces a guardrail when an operation trips it: allow it silently, hold it for explicit confirmation, or deny it outright (ADR-0020).
const ( DispositionAllow Disposition = "allow" DispositionConfirm Disposition = "confirm" DispositionDeny Disposition = "deny" )
func (Disposition) Valid ¶
func (d Disposition) Valid() bool
Valid reports whether d is a known disposition.
type DomainResult ¶
type DomainResult struct {
Host string `json:"host"`
Provider string `json:"provider"`
Type string `json:"type,omitempty"`
Address string `json:"address,omitempty"`
}
DomainResult reports the DNS record a domain operation created, updated, or removed.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the control plane's deploy orchestrator: the product. It turns an agent's deploy / status / logs / rollback / scale requests into guarded operations against the cluster, records every deploy, and returns structured results (ADR-0002, ADR-0006). It owns no global state and reads no ambient time or randomness — every external dependency is an injected seam (ADR-0010), so the engine is deterministic and unit-testable against fakes.
func New ¶
New constructs an Engine, validating that every seam is supplied and the policy is coherent. It returns an error rather than panicking so wiring mistakes surface at startup.
func (*Engine) AddDomain ¶
func (e *Engine) AddDomain(ctx context.Context, req AddDomainRequest) (DomainResult, error)
AddDomain creates or updates a DNS record pointing Host at an address, through the named provider (ADR-0018). The address is given explicitly (req.Address) or derived from an exposed app's ingress (req.App) so the agent need not look it up. It is a guarded operation: pointing a public hostname at the cluster is a blast-radius change, so it trips the dns.write guardrail (confirm by default). The record is an A record when the address is an IPv4 address, a CNAME otherwise. The provider must be configured and serve DNS; burrowd holds the token and is the only thing that calls the vendor.
func (*Engine) AddEnvironment ¶ added in v0.7.0
AddEnvironment registers a named environment mapping name to namespace (ADR-0035 phase 2). It validates name as a DNS-1123-label-safe lowercase token and rejects the reserved `default` (which is implicit), then records it. The namespace and burrowd's Role there are created kubeconfig-side by `burrow env add` before this call — burrowd holds only namespaced Roles and cannot create namespaces or RBAC itself (least privilege), so the engine only records the registry entry. A duplicate name is rejected by the store.
func (*Engine) AddProvider ¶
AddProvider records a vendor credential in the registry, after validating the token and writing it into the burrow-credentials Secret (ADR-0023, ADR-0030). The token VALUE arrives in req over burrowd's authenticated control-plane API; burrowd is the only thing that holds it. The order is validate-then-write: build the vendor adapter with the passed token and make a cheap authenticated call, and only on success write the token to the Secret and record the registry entry. A rejected token returns an error and writes NOTHING — no rollback needed, since nothing is written until validation passes. The token is never logged, never stored in Postgres, and never returned. The provider's capabilities are derived from its type.
func (*Engine) AttachAddon ¶
AttachAddon gives app its own database on the installed Postgres add-on and wires it into the app (ADR-0031). burrowd provisions an isolated database + login role on the shared instance, generates the DATABASE_URL server-side, writes it into the app's per-app Secret via the SetSecretValue path (ADR-0029), and restarts the app so envFrom picks it up. Attach provisions and destroys nothing, so it is allowed by default (no guardrail) and is safe over MCP: no secret value crosses MCP — the agent supplies only the app name; burrowd generates the value and never returns it. The audit row records {addon, app} only — never the URL.
func (*Engine) Audit ¶
func (e *Engine) Audit(ctx context.Context, filter AuditFilter) ([]AuditEntry, error)
Audit returns audit rows matching filter, newest first (ADR-0027). It is a read-only, guarded control-plane operation: the agent and the operator may review the trail, but no API path writes to or alters it. The rows never carry a secret value — they were redacted at write time.
func (*Engine) AutoDeploy ¶ added in v0.13.0
AutoDeploy returns the auto-deploy level configured for app in env (ADR-0052 §2). A missing configuration resolves to the built-in default (DefaultAutoDeployLevel, off): auto-deploy is opt-in, so an app with no stored level is off and is never polled (ADR-0058). The environment is resolved so an unknown name is a clear error, and the level is keyed by the canonical environment name. This is a read: the agent may observe it over burrow-agent, but only a human sets it (§6).
func (*Engine) AutoDeployStatus ¶ added in v0.13.0
AutoDeployStatus returns the enriched, read-only auto-deploy view for app in env (ADR-0052 §2/§3). It reads the level exactly as AutoDeploy does, then — when the registry seam is wired — lists the tags of the app's current image and computes what auto-deploy would take within the level and any higher upgrade above its cap. The registry listing is anonymous in this phase (RegistryAuth{}): public GHCR (the reference registry), public Docker Hub, DO, and GCR-token registries all list anonymously. Authenticated private-repo listing — reading the client-side burrow-registry pull secret — needs a deliberate burrowd RBAC grant withheld today under the least-privilege boundary (ADR-0017/ADR-0040); it lands with the Phase 4 poller, for which the seam is already ready via RegistryAuth. Any registry failure (unreachable, a private-repo 401, a non-semver current tag, or an absent seam) degrades to Checked=false with a Note and no error; only a validation or environment error returns a non-nil error.
func (*Engine) Autoscale ¶ added in v0.8.0
func (e *Engine) Autoscale(ctx context.Context, app, env string, spec AutoscaleSpec, confirm bool) (AutoscaleResult, error)
Autoscale configures autoscaling for an app: it applies an autoscaling/v2 HorizontalPodAutoscaler on the app's Deployment with the requested replica band and utilization targets (ADR-0006). It is guarded twice — the app.autoscale guardrail gates the operation (allow by default), and the app.replica_ceiling guardrail bounds the requested max the same way it bounds a manual scale, so a max above the ceiling is denied exactly like scaling above it. The HPA is applied even when metrics-server is absent (creating it needs no metrics); the result then carries a Warning that it will not scale until metrics-server is installed.
func (*Engine) BackupAddon ¶
BackupAddon backs up app's database on the installed Postgres add-on (ADR-0032): burrowd records a pending backup, runs an in-cluster Job that pg_dumps the database to the backup PVC, and marks the backup completed (or failed). The backup is recorded in the control-plane database — burrowd is not mounted to the backup PVC, so the database, not the volume, is the index of backups. It moves no secret value: the Job reads the superuser password only via secretKeyRef, and the audit row and the returned result name the add-on, app, backup id, path, and size — never a credential. Backup is allowed by default (it destroys nothing) and safe over MCP.
func (*Engine) Build ¶ added in v0.13.0
func (e *Engine) Build(ctx context.Context, req BuildRequest) (BuildResult, error)
Build builds an app's image from a git source reference inside the user's own cluster and, on success, hands the resulting image into the existing guarded deploy path (ADR-0053). It is the optional in-cluster build path, never the deploy spine: Burrow stays client-build-first and deploy stays by image reference (ADR-0007), so a build is a front-end that ends where deploy begins.
Only metadata crosses into the builder — the git reference and the target image reference; the builder clones the actual source from git inside the cluster, so no code travels over the control channel (ADR-0004, ADR-0053 §3). On a successful build the resulting digest-pinned reference flows into the same rollout, deploy record (the rollback handle), and audit entry an explicit deploy runs (ADR-0053 §4), so guardrails and the deploy history are never bypassed. On a builder error nothing is deployed: the error is surfaced structurally and the deploy path is not touched.
func (*Engine) ClusterCapabilities ¶
func (e *Engine) ClusterCapabilities(ctx context.Context) (ClusterCapabilities, error)
ClusterCapabilities reports what the cluster can do, read live so out-of-band changes are always reflected (ADR-0034). It runs the cluster probe through the ClusterProber seam and fills the DNS capability from the providers registry (ADR-0023) — a control-plane fact, not a cluster read. It is read-only: it changes nothing in the cluster or the registry.
func (*Engine) ClusterCapacity ¶ added in v0.13.0
func (e *Engine) ClusterCapacity(ctx context.Context) (CapacityReport, error)
ClusterCapacity reports the cluster's scheduling capacity and headroom (issue #275): per node and cluster-total allocatable / committed / free, the top CPU and memory consumers, and a verdict on whether a typical in-cluster build fits and whether another node is needed — all from the Kubernetes API alone, no metrics-server. It runs the read through the CapacityProber seam and computes the report purely, so it changes nothing in the cluster.
func (*Engine) ConnectAddon ¶
func (e *Engine) ConnectAddon(ctx context.Context, backend, endpoint, secretKey, token string) (AddonInfo, error)
ConnectAddon registers an existing backend the user already runs (e.g. an in-cluster Loki) as a queryable add-on, recording its endpoint and derived capabilities in the registry (ADR-0026). Unlike install it deploys nothing and is not guarded — connect is registration-only. Connecting the same backend twice upserts, updating the endpoint. secretKey is the (non-secret) key under which a bearer token for an authenticated backend lives in the burrow-credentials Secret; "" means the backend is unauthenticated. token is the bearer token VALUE for an authenticated backend: it arrives over burrowd's authenticated control-plane API and is written into burrow-credentials under secretKey (ADR-0030). The value is never logged, never stored in Postgres, never returned, and never carried over MCP — only the key is recorded in the registry (ADR-0004/0023). A token without a secretKey is invalid. The registry entry that crosses the API holds only the key.
func (*Engine) DeleteApp ¶
DeleteApp removes an app entirely: its workload, its routing (Service/Ingress), and its release history, so the app disappears from the apps listing and from status. It is guarded by app.delete, which holds the destructive teardown for confirmation by default (ADR-0020). The app must exist — it has either recorded releases or a live workload; an app unknown to both is ErrNotFound. Teardown tolerates an already-absent piece: an ErrNotFound from the workload or routing delete means that piece is already gone, not a failure.
func (*Engine) Deploy ¶
func (e *Engine) Deploy(ctx context.Context, req DeployRequest) (DeployResult, error)
Deploy rolls out an image by reference (ADR-0007). It validates the request, applies the guardrails, records a new release, applies it to the cluster, and records the outcome — superseding the previously running release on success. burrowd never contacts the registry: the workload is applied by image reference and the kubelet resolves and pulls it with the imagePullSecret (ADR-0040). The image bytes never pass through here; only the reference does (ADR-0004).
Deploy is the explicit, human- or agent-triggered path (ADR-0007): it records a manual provenance. The Phase 4b pull-based watcher (ADR-0052) will drive the same rollout through the unexported deploy with an auto provenance.
func (*Engine) DetachAddon ¶
DetachAddon removes app's DATABASE_URL and, behind the addon.detach confirm guardrail (it destroys data), drops app's database and role from the cluster-shared Postgres instance (ADR-0031). The audit row records {addon, app} only.
func (*Engine) DisableAutoscale ¶ added in v0.8.0
DisableAutoscale turns autoscaling off for an app by removing its HorizontalPodAutoscaler (ADR-0006). It is guarded by the same app.autoscale guardrail and audited. It is idempotent: removing autoscaling from an app that has none succeeds without error.
func (*Engine) Expose ¶
func (e *Engine) Expose(ctx context.Context, req ExposeRequest) (ExposeResult, error)
Expose makes an app reachable at a hostname through an Ingress (ADR-0018). It is a guarded operation: public exposure trips the app.expose_public guardrail, which holds for confirmation by default. The app must already be deployed.
func (*Engine) Guardrails ¶
Guardrails returns the guardrail policy as a list for inspection (ADR-0020). With an empty or "default" env it returns the global policy; with a named environment it returns that environment's effective policy under the env to global to default fallback, each entry marking whether its disposition is env-specific or inherited (ADR-0035 phase 2c). A named environment must be registered; an unknown one is a clear ErrNotFound.
func (*Engine) History ¶ added in v0.13.0
History returns an app's deploy timeline: the releases recorded for it, newest first — what versions the app has been rolled to, when, and whether each landed (the Release.Status conveys success or failure). It is a read-only view over the deploy records the deploy engine already writes (ADR-0007); it records nothing and touches no cluster state, so it is safe over any channel. It resolves and validates env exactly as Status does — an unknown environment is a clean error — and reads the releases for that environment: releases are keyed per (app, environment) (ADR-0052 Phase 4a), so history is the app's timeline in the selected environment.
Each entry carries what the deploy record holds: the image reference (the version), the creation timestamp, the lifecycle status, and the deploy's provenance (ADR-0052 §5) — whether it was a manual or an unattended auto-update, and the level and tag an auto-update ran under. The record carries those fields, so history renders whatever they hold with no per-field logic here.
func (*Engine) InstallAddon ¶
InstallAddon deploys the vetted backing service for the named add-on type and registers it as a queryable capability (ADR-0025/0026). It is guarded by addon.install.
func (*Engine) ListAddons ¶
ListAddons returns the registered add-on instances from the registry, with live readiness probed from the cluster for installed ones (ADR-0025). A readiness probe failure leaves an entry not-ready rather than failing the whole listing.
func (*Engine) ListApps ¶
Status returns the combined control-plane and cluster view of an app: the most recent ListApps returns the workload status of every Burrow-managed app, for an apps listing. It reads the cluster — the source of truth for what is running.
func (*Engine) ListBackups ¶
ListBackups returns recorded backups, newest first, from the control-plane database (ADR-0032). An empty app lists every app's backups; a non-empty app restricts to that app. Read-only and safe over MCP — it names the app, size, time, and on-PVC path, never a credential.
func (*Engine) ListConfig ¶ added in v0.7.0
ListConfig returns the app's non-secret config store (ADR-0028). An app with no config yields an empty map and no error.
func (*Engine) ListEnvironments ¶ added in v0.7.0
func (e *Engine) ListEnvironments(ctx context.Context) ([]Environment, error)
ListEnvironments returns the environments the cluster's burrowd knows about (ADR-0035 phase 2): the implicit `default` environment first (the app namespace burrowd runs against, behaving like today), followed by the registered environments in name order. The default is synthesized rather than stored, so multi-environment is opt-in with no regression.
func (*Engine) ListSecrets ¶
ListSecrets returns the env-var KEYS in an app's per-app Secret, sorted, never the values (ADR-0028/0004). Secret values live only in the Kubernetes Secret and never cross the API or MCP, so this read returns keys only. An app with no secrets yields an empty slice.
func (*Engine) NewAutoDeployPoller ¶ added in v0.13.0
func (e *Engine) NewAutoDeployPoller(cfg AutoDeployConfig) *AutoDeployPoller
NewAutoDeployPoller builds the watcher bound to this engine. It reads the engine's registry, database, and clock seams, and drives the same unexported guarded deploy path an explicit call uses, stamped with an auto provenance (ADR-0052 §5). Constructing it does not start it — call Run.
func (*Engine) NextTag ¶ added in v0.13.0
NextTag suggests the next semver release tags for app in env, read from its current running tag (ADR-0052 §8). It reads the current tag exactly as AutoDeployStatus does (the latest release's image), and when that tag parses as stable semver returns the next patch, minor, and major tags. A missing release or a non-semver current tag degrades to a Note with no suggestion and no error, so the guidance is best-effort and never blocks the agent (ADR-0040). It changes nothing.
func (*Engine) Providers ¶
Providers returns the configured providers, name order (ADR-0023). It reports the non-secret registry only; no token is read.
func (*Engine) QueryLogs ¶
func (e *Engine) QueryLogs(ctx context.Context, query string, limit int, backend string) ([]LogEntry, error)
QueryLogs runs query against the installed logs add-on and returns up to limit records. It is the read path behind the agent's logs-query tool: it locates the add-on advertising the "logs" capability and queries it through the LogsQuerier seam (ADR-0026). An empty backend picks the first logs add-on; a non-empty backend targets a specific one (by its concrete backend or its registry name) when more than one serves logs.
func (*Engine) QueryMetrics ¶
func (e *Engine) QueryMetrics(ctx context.Context, query string, backend string) ([]MetricSample, error)
QueryMetrics runs an instant PromQL query against the connected metrics add-on and returns the matching samples. It is the read path behind the agent's metrics-query tool: it locates the add-on advertising the "metrics" capability and queries it through the MetricsQuerier seam (ADR-0026). An empty backend picks the first metrics add-on; a non-empty backend targets a specific one (by its concrete backend or its registry name) when more than one serves metrics.
func (*Engine) Reachability ¶
Reachability reports, link by link, whether an app is reachable at its hostname (ADR-0018): deployed and ready, exposed, given an external address by an ingress controller, and DNS pointing the host at that address. It returns a structured chain plus a one-line plain summary for a non-expert; it never errors on a missing link — that is the answer.
func (*Engine) RemoveAddon ¶
RemoveAddon removes the named add-on instance. It is guarded by addon.remove (removing a backing service can break dependent apps).
func (*Engine) RemoveDomain ¶
func (e *Engine) RemoveDomain(ctx context.Context, req RemoveDomainRequest) (DomainResult, error)
RemoveDomain deletes the DNS record the provider holds for Host (ADR-0018). It trips the dns.delete guardrail (confirm by default) — it is the destructive side of DNS management.
func (*Engine) RestoreAddon ¶
func (e *Engine) RestoreAddon(ctx context.Context, t AddonType, app, backupID string, confirm bool) error
RestoreAddon restores app's database from a recorded backup, overwriting its live contents (ADR-0032). It is behind the addon.restore confirm guardrail (it destroys live data), runs an in-cluster Job that pg_restores the named dump, and records the restore in the audit log. The Job reads the superuser password only via secretKeyRef; the audit row records {addon, app, backup} only — never a credential.
func (*Engine) Rollback ¶
func (e *Engine) Rollback(ctx context.Context, app, env string, confirm bool) (RollbackResult, error)
Rollback restores the app's previously running release by redeploying its reference (ADR-0007). It finds the current running release, re-applies the release that one superseded, and records the rollback as a new release. It returns ErrNotFound when there is nothing to roll back from or to.
A rollback disables auto-deploy in the target environment (ADR-0052 §5): once landed it sets the app's level to off with the reason "disabled by rollback", so the pull-based watcher does not fight the deliberate downgrade by re-applying the version just backed away from. Re-enabling is a deliberate human action (`burrow app auto-deploy <app> <level>`).
func (*Engine) Run ¶ added in v0.12.0
Run executes a caller-provided one-off command inside the app's OWN current image and environment as a short-lived Kubernetes Job, waits for it to finish, and returns the captured output and exit code as a structured result (ADR-0048). It is the primitive behind migrations, seeds, backfills, and console-style tasks.
The command runs in the app's currently-deployed release image — resolved the same way deploy and rollback resolve it — with the app's config env and per-app Secret injected via envFrom, so the command sees exactly the runtime, dependencies, config, and secrets the running app sees (ADR-0048 §2). No secret value crosses the API or MCP: the caller supplies only the command (ADR-0029). The operation is synchronous — launch, wait, capture, return (ADR-0048 §3): a non-zero exit is a normal structured outcome the agent reasons over, not a transport error.
It is gated by the app.run guardrail (confirm by default), which gates WHETHER the command runs, not what it does — the opaque-command limitation is honest and stated (ADR-0048 §5). A held run returns a *GuardrailError needing confirmation; the agent surfaces the exact command to the human and re-invokes with Confirm only on explicit approval — it never self-confirms (ADR-0020). Every run — held, denied, or executed — is recorded in the audit log with the command (ADR-0027).
func (*Engine) Scale ¶
func (e *Engine) Scale(ctx context.Context, app, env string, replicas int32, confirm bool) (ScaleResult, error)
Scale changes an app's replica count, guarded against scale-to-zero and the policy ceiling (ADR-0006). It does not create a new release: scaling adjusts the running workload, while a release records a deploy.
func (*Engine) SetAutoDeploy ¶ added in v0.13.0
SetAutoDeploy sets the auto-deploy level for app in env (ADR-0052 §2, §6). Choosing the level is a governance decision, so it is a human operator action exposed only through the `burrow` CLI and never to the agent — what deploys unattended stays a human decision (ADR-0038). It rejects an invalid level as ErrInvalid and an unknown or ambiguous environment like every other per-app mutation, and stores the level under the canonical environment name.
func (*Engine) SetConfig ¶ added in v0.7.0
SetConfig upserts one non-secret config var for an app in the config store (ADR-0028). The store is the single source of truth for the app's config. By default the change re-applies the running workload so it rolls and the running app picks the value up; with noRestart the value is only persisted and lands on the next deploy. An app with no running release simply persists and skips the apply — not an error. Config vars are non-secret, so there is no guardrail.
func (*Engine) SetGuardrail ¶
func (e *Engine) SetGuardrail(ctx context.Context, env string, code GuardrailCode, d Disposition) error
SetGuardrail sets one guardrail's disposition (ADR-0020). It rejects an unknown guardrail or an invalid disposition as ErrInvalid. This is the operator's lever — exposed via the CLI, never as an MCP tool, so the agent cannot change its own guardrails.
With an empty or "default" env it sets the global disposition for code (today's behavior). With a named environment it stores the env-prefixed code (e.g. prod.app.delete) so the environment's policy can diverge from the global one (ADR-0035 phase 2c). A named environment must be registered (an unknown one is ErrNotFound, catching typos), and only the app-level guardrails are env-scopable: a cluster-level guardrail (addon.*, dns.*) gates a cluster-wide operation and can only be set globally, so env-scoping one is rejected as ErrInvalid.
func (*Engine) SetSecret ¶
SetSecret upserts one key=value into an app's per-app Secret and, unless noRestart, rolls the running workload so it picks the value up (ADR-0029). The value arrives over burrowd's authenticated control-plane API and is written here through the Kubernetes seam; it is NEVER logged, never audited, never stored in Postgres, and never carried over MCP — only its KEY name appears in any error (the value is never formatted into one). Setting a value still cannot be done over MCP: there is no secret-set MCP tool. An app with no running workload just writes the Secret; the change lands on the next deploy.
func (*Engine) Status ¶
recorded release and the live workload state. It returns ErrNotFound only when the app is unknown to both.
func (*Engine) Unexpose ¶
Unexpose removes an app's exposure (its Service and Ingress). It does not affect the workload. Unexposing an app that was never exposed returns ErrNotFound.
func (*Engine) UnsetConfig ¶ added in v0.7.0
UnsetConfig removes one config var for an app from the config store (ADR-0028). Like SetConfig it re-applies the running workload by default so the running app drops the value, or only persists with noRestart. An app with no running release simply persists and skips the apply.
func (*Engine) UnsetSecret ¶
UnsetSecret removes one key from an app's per-app Secret and, unless noRestart, rolls the running workload so it drops the value (ADR-0028). Removing a key carries no value, and this is MCP-allowed. An app with no running workload just updates the Secret; the change lands on the next deploy. Removing an absent key succeeds.
type Environment ¶ added in v0.7.0
type Environment struct {
// Name is the environment handle, a DNS-1123 label (e.g. "staging", "prod").
Name string `json:"name"`
// Namespace is the Kubernetes namespace this environment's apps deploy into.
Namespace string `json:"namespace"`
// Default reports whether this is the implicit `default` environment (the app namespace
// burrowd already runs against). Registered environments are never default.
Default bool `json:"default"`
// CreatedAt is when the environment was registered, read from the injected clock. It is the
// zero time for the synthesized default environment.
CreatedAt time.Time `json:"created_at,omitempty"`
}
Environment is a named app environment for namespace-per-environment (ADR-0035 phase 2): one cluster, several app namespaces, one per environment. Name is the operator-facing handle (a DNS-1123 label); Namespace is the Kubernetes namespace the environment's apps deploy into. Default marks the implicit `default` environment, which is synthesized from the engine's app namespace rather than registered.
type ExposeRequest ¶
type ExposeRequest struct {
App string `json:"app"`
// Env is the environment whose namespace the app lives in (ADR-0035 phase 2b): empty or
// "default" targets the default environment, a registered name targets that environment.
Env string `json:"env,omitempty"`
Host string `json:"host"`
Port int32 `json:"port"`
// TLS requests an HTTPS certificate for Host via cert-manager; Issuer names the
// ClusterIssuer to use.
TLS bool `json:"tls,omitempty"`
Issuer string `json:"issuer,omitempty"`
// Confirm acknowledges the app.expose_public guardrail so the operation proceeds past it.
Confirm bool `json:"confirm,omitempty"`
}
ScaleResult reports the outcome of a scale. ExposeRequest describes making an app reachable at a hostname (ADR-0018).
type ExposeResult ¶
type ExposeResult struct {
App string `json:"app"`
Host string `json:"host"`
Port int32 `json:"port"`
URL string `json:"url"`
}
ExposeResult reports the outcome of exposing an app.
type ExposeSpec ¶
type ExposeSpec struct {
// App is the application to expose; its workload provides the Service's backends.
App string
// Host is the external hostname to route, e.g. app.example.com.
Host string
// Port is the app's container port the Service forwards to. Must be positive.
Port int32
// TLS requests an HTTPS certificate for Host via cert-manager (the Ingress is annotated
// for the Issuer ClusterIssuer, and a TLS Secret is named for cert-manager to fill).
TLS bool
// Issuer is the cert-manager ClusterIssuer to request the certificate from when TLS.
Issuer string
}
ExposeSpec describes how to make an app reachable at a hostname (ADR-0018). v0.2 routes HTTP to the app's Service via an Ingress, optionally with TLS issued by cert-manager.
type ExposureStatus ¶
type ExposureStatus struct {
Exposed bool
Host string
Address string
// TLS reports whether the Ingress requests a certificate (its spec has a TLS entry).
TLS bool
// CertReady reports whether the requested TLS certificate has been issued (its Secret holds a
// certificate). It is meaningful only when TLS is true.
CertReady bool
}
ExposureStatus is the observed state of an app's exposure, for the reachability surface (ADR-0018). Address is the controller-assigned external IP or hostname, read from the Ingress's status; it is empty until an ingress controller assigns one.
type GuardrailCode ¶
type GuardrailCode string
GuardrailCode identifies a guardrail: it is both the key a Policy configures the guardrail's disposition under and the machine-readable reason it appears in a refusal, so an agent can branch on the cause rather than parse prose (ADR-0006, ADR-0020).
const ( // GuardrailAppDeploy: the operation would deploy a new release of an app. Deploy is the // core action, so it is allowed by default and gated only if an operator opts in — set it // to confirm to require sign-off before a deploy (e.g. in prod), or to deny to freeze // deploys entirely, per environment. Realizes ADR-0007: the explicit deploy call is where // the guardrails live. GuardrailAppDeploy GuardrailCode = "app.deploy" // GuardrailReplicaCeiling: the requested replica count exceeds Policy.MaxReplicas. GuardrailReplicaCeiling GuardrailCode = "app.replica_ceiling" // GuardrailScaleToZero: the operation would scale to zero replicas. GuardrailScaleToZero GuardrailCode = "app.scale_to_zero" // GuardrailExposePublic: the operation would make an app reachable from outside the // cluster (expose it at a hostname). GuardrailExposePublic GuardrailCode = "app.expose_public" // GuardrailDNSWrite: the operation would create or update a public DNS record at a // configured provider (ADR-0018). GuardrailDNSWrite GuardrailCode = "dns.write" // GuardrailDNSDelete: the operation would delete a public DNS record at a configured // provider — the destructive side of DNS management (ADR-0018). GuardrailDNSDelete GuardrailCode = "dns.delete" // GuardrailAddonInstall: the operation would install a building-block backing service // (a vetted add-on like logs or metrics) onto the cluster (ADR-0025). GuardrailAddonInstall GuardrailCode = "addon.install" // GuardrailAddonRemove: the operation would remove an installed add-on — the destructive // side, since dependent apps may rely on it (ADR-0025). GuardrailAddonRemove GuardrailCode = "addon.remove" // GuardrailAddonDetach: the operation would detach an app from an add-on — for Postgres, // dropping the app's database and role and destroying its data (ADR-0031). Held for // confirmation by default. (Attach is not guarded: it provisions, it destroys nothing.) GuardrailAddonDetach GuardrailCode = "addon.detach" // GuardrailAddonRestore: the operation would restore an app's database from a backup, // overwriting its live contents (ADR-0032). Held for confirmation by default, like detach and // app delete. (Backup and list are not guarded: they destroy nothing.) GuardrailAddonRestore GuardrailCode = "addon.restore" // GuardrailAppDelete: the operation would delete an app entirely — its workload, routing, // and release history — so it disappears from the apps listing. The destructive teardown // of a deployed application. GuardrailAppDelete GuardrailCode = "app.delete" // GuardrailRollback: the operation would roll an app back to its previous release. A // production mutation, but a recovery one — allowed by default so an agent can restore a // broken app quickly; an operator can set it to confirm or deny to require sign-off for // server-side, agent-independent enforcement (ADR-0020). GuardrailRollback GuardrailCode = "app.rollback" // GuardrailAutoscale: the operation would configure (or turn off) autoscaling for an app — apply // a HorizontalPodAutoscaler on its Deployment. Allowed by default: autoscaling is helpful and // non-destructive, and the autoscaler's max is independently bounded by the replica ceiling // (GuardrailReplicaCeiling). An operator can raise it to confirm or deny per environment, e.g. // deny in prod so only a human sets the scaling shape there. GuardrailAutoscale GuardrailCode = "app.autoscale" // GuardrailAppRun: the operation would run a caller-provided one-off command inside the app's own // current image and environment (ADR-0048) — a migration, seed, backfill, or maintenance script. // Held for confirmation by default: a command runs opaquely and may make destructive changes, so // the human sees and approves the exact command before it runs, and prod is the environment to // keep gated. The guardrail gates whether the command runs, not what it does (ADR-0048 §5). GuardrailAppRun GuardrailCode = "app.run" )
type GuardrailError ¶
type GuardrailError struct {
// Operation is the operation that was gated (e.g. "deploy", "scale").
Operation string
// Code is the machine-readable guardrail that tripped.
Code GuardrailCode
// Message is a human-readable explanation.
Message string
// Requested is the value the caller asked for (e.g. the replica count).
Requested int32
// Limit is the relevant policy limit, when the code involves one.
Limit int32
// NeedsConfirmation is true when the operation was not refused outright but requires
// explicit confirmation to proceed (disposition confirm). A plain deny leaves it false.
NeedsConfirmation bool
}
GuardrailError is returned when the control plane declines a dangerous operation or holds it for confirmation. It is a structured outcome, not a system failure: the operation was understood and deliberately gated. Callers distinguish it with AsGuardrail.
func AsGuardrail ¶
func AsGuardrail(err error) (*GuardrailError, bool)
AsGuardrail reports whether err is (or wraps) a GuardrailError and returns it.
func (*GuardrailError) Error ¶
func (e *GuardrailError) Error() string
type GuardrailInfo ¶
type GuardrailInfo struct {
Code GuardrailCode `json:"code"`
Disposition Disposition `json:"disposition"`
Description string `json:"description"`
// Source reports where the effective disposition came from when the guardrail is inspected for a
// named environment (ADR-0035 phase 2c): "env" for an environment-specific override, "global" for
// the global policy, or "default" for the built-in default. It is empty for the global listing.
Source string `json:"source,omitempty"`
}
GuardrailInfo describes a guardrail and its current disposition, for inspection through `guard list` and the read-only MCP guard tool (ADR-0020).
type IDSource ¶
type IDSource interface {
// NewID returns a fresh release identifier.
NewID() string
}
IDSource mints release identifiers. It is a seam (ADR-0010): the engine never reads ambient randomness or time to make an ID, so a test can supply a deterministic counter while production supplies a UUID minter. Implementations must return a non-empty, unique string on each call.
type IngressCapability ¶
type IngressCapability struct {
// Present is true only when a ready ingress controller is running — the signal that an expose
// will actually get an external address and admission webhook. It is not implied by Classes.
Present bool `json:"present"`
// Classes are the IngressClass names that exist, sorted. A class may be present while Present is
// false (an orphan class whose controller was removed).
Classes []string `json:"classes,omitempty"`
}
IngressCapability reports the cluster's ingress-controller situation. Present — the "you can expose" signal — is true only when an ingress controller is actually running (a ready ingress-nginx controller Deployment), NOT merely when an IngressClass exists: an IngressClass is cluster-scoped and can outlive the controller that created it (deleting the ingress-nginx release and its namespace leaves the "nginx" class orphaned), and an orphan class routes nothing. Classes are the IngressClass names found, sorted; they are reported independently of Present because binding an Ingress still needs the class name (e.g. while the controller is being reinstalled).
type Kubernetes ¶
type Kubernetes interface {
// WithNamespace returns a view of this seam whose per-app resource operations (deploy,
// status, logs, scale, delete, expose/unexpose, and the per-app Secret) act in ns instead
// of the configured app namespace — the mechanism that routes an operation to a named
// environment's namespace (ADR-0035 phase 2). Add-on operations are unaffected: add-ons live
// in their own namespace. An empty ns, or ns equal to the configured app namespace, returns a
// view equivalent to the receiver, so default-environment behavior is identical to before
// environments existed.
WithNamespace(ns string) Kubernetes
// ApplyWorkload creates or updates the workload for spec.App to match spec.
ApplyWorkload(ctx context.Context, spec WorkloadSpec) error
// WorkloadStatus returns the observed state of app's workload, or ErrNotFound if
// no workload exists for it.
WorkloadStatus(ctx context.Context, app string) (WorkloadStatus, error)
// ListWorkloads returns the observed state of every Burrow-managed workload in the
// namespace (for an apps listing). No workloads is an empty slice, not an error.
ListWorkloads(ctx context.Context) ([]WorkloadStatus, error)
// DeployAddon installs a building-block backing service per spec — a workload, a
// ClusterIP Service, and a persistent volume when the spec asks for one — and returns
// the instance's connection info (ADR-0025). Installing an already-installed add-on is
// idempotent.
DeployAddon(ctx context.Context, spec AddonSpec) (AddonInfo, error)
// AddonReady reports whether the named add-on's backing Deployment is available. It is a
// cheap single-Deployment readiness probe — readiness is a live property, not stored in the
// registry. A missing Deployment is reported as not ready (false, nil), not an error.
AddonReady(ctx context.Context, name string) (bool, error)
// DeleteAddon removes the named add-on instance and its resources. Removing an add-on
// that is not installed returns ErrNotFound.
DeleteAddon(ctx context.Context, name string) error
// ScaleWorkload sets the desired replica count for app's workload.
ScaleWorkload(ctx context.Context, app string, replicas int32) error
// ApplyAutoscaler creates or updates an autoscaling/v2 HorizontalPodAutoscaler named after app,
// targeting app's Deployment, per spec — the replica band and the CPU (and optional memory)
// utilization targets (ADR-0006). It is create-or-update: re-applying adjusts the existing HPA.
// Creating the HPA does not require metrics-server; only its scaling does.
ApplyAutoscaler(ctx context.Context, app string, spec AutoscaleSpec) error
// DeleteAutoscaler removes app's HorizontalPodAutoscaler. Deleting an absent HPA is a no-op, not
// an error, so turning autoscaling off is idempotent.
DeleteAutoscaler(ctx context.Context, app string) error
// AutoscalerActive reports whether app has an active HorizontalPodAutoscaler owning its replica
// count. A workload apply consults it so a deploy (or rollback, or config/secret reapply) leaves
// the HPA-managed count untouched rather than resetting it. A missing HPA is reported as inactive
// (false, nil), not an error.
AutoscalerActive(ctx context.Context, app string) (bool, error)
// MetricsAPIAvailable reports whether the metrics.k8s.io API group is served (metrics-server is
// installed), so the engine can warn that an applied HPA will not scale until it is. It is
// best-effort by contract: the engine treats an error as "absent" and warns rather than failing,
// so a discovery hiccup never blocks applying the HPA.
MetricsAPIAvailable(ctx context.Context) (bool, error)
// Logs returns recent log lines for app's workload.
Logs(ctx context.Context, app string, opts LogOptions) ([]LogLine, error)
// DeleteWorkload removes app's workload. Deleting a missing workload returns
// ErrNotFound.
DeleteWorkload(ctx context.Context, app string) error
// Expose makes app reachable at a hostname by creating (or updating) a Service and an
// Ingress that routes the host to it (ADR-0018). It does not create the workload —
// Deploy does — and whether the host is actually reachable also depends on an ingress
// controller and DNS, which the reachability surface reports on.
Expose(ctx context.Context, spec ExposeSpec) error
// Unexpose removes the Service and Ingress created by Expose. Unexposing an app that
// was never exposed returns ErrNotFound.
Unexpose(ctx context.Context, app string) error
// ExposureStatus reports whether app is exposed, at what host, and the external address
// the ingress controller assigned its Ingress (read from the Ingress's
// status.loadBalancer — empty until a controller processes it). A never-exposed app
// returns a zero ExposureStatus and no error.
ExposureStatus(ctx context.Context, app string) (ExposureStatus, error)
// SecretKeys returns the env-var names held in app's per-app Secret
// (burrow-app-<app>-secrets), sorted, never the values (ADR-0028/0004). A missing
// Secret yields an empty slice and no error — an app with no secrets set.
SecretKeys(ctx context.Context, app string) ([]string, error)
// SetSecretValue upserts one key=value into app's per-app Secret, creating the
// Secret if absent (ADR-0029). The value arrives over burrowd's authenticated
// control-plane API and is written here to the Kubernetes Secret — it is NEVER
// logged, never audited (the audit log records the key name only), never stored in
// Postgres, and never carried over MCP. Any error this returns must name the app and
// key only, never the value.
SetSecretValue(ctx context.Context, app, key, value string) error
// UnsetSecretKey removes one key from app's per-app Secret. A missing Secret or a
// missing key is a no-op, not an error — unsetting what is already absent succeeds.
// The value never crosses this seam: only the key name does.
UnsetSecretKey(ctx context.Context, app, key string) error
// RestartWorkload triggers a rolling update of app's Deployment by bumping the
// pod-template annotation burrow.cloud/restarted-at to at. It is how a secret change
// (read only at pod start via envFrom) forces the running app to pick it up. A missing
// Deployment returns ErrNotFound; the caller treats that as "nothing running to roll".
RestartWorkload(ctx context.Context, app string, at time.Time) error
// RunBackupJob runs a one-shot Job in the add-on namespace that pg_dumps app's database on the
// installed Postgres instance to /<backup-pvc>/<app>/<backupID>.dump (custom format), ensuring
// the backup PVC first (ADR-0032). The Job connects as the superuser, reading the password from
// the burrow-postgres Secret via secretKeyRef env — never a CLI argument, never logged. It
// blocks until the Job completes, returns an error if the Job fails or times out, and reaps the
// Job on success. It returns the dump's size in bytes when the dump container reported it (the
// pod's terminated-state message), or 0 when unknown. app is validated before any Job is built.
RunBackupJob(ctx context.Context, app, backupID string) (sizeBytes int64, err error)
// RunRestoreJob runs a one-shot Job in the add-on namespace that pg_restores
// /<backup-pvc>/<app>/<backupID>.dump into app's database (--clean --if-exists, so it replaces
// current contents). Like RunBackupJob it reads the superuser password only via secretKeyRef,
// blocks until the Job completes, errors on failure or timeout, and reaps the Job on success.
// app is validated before any Job is built.
RunRestoreJob(ctx context.Context, app, backupID string) error
// RunJob runs spec.Command as a one-shot Job in the app namespace (this seam view's namespace),
// built from the app's own current image (spec.Image) and its config env plus per-app Secret via
// envFrom, so DATABASE_URL and every secret resolve as the app sees them (ADR-0048 §2). It blocks
// until the Job finishes, then captures the pod's output and the container's exit code into a
// RunResult and returns it. A non-zero exit is a NORMAL structured outcome, not an error: the
// error return is reserved for a launch, poll, or timeout failure (ADR-0048 §3). The finished Job
// is garbage-collected by Kubernetes' native ttlSecondsAfterFinished, set from spec.TTLSeconds, so
// there is no imperative reap (ADR-0048 §7). spec.App is validated before any Job is built.
RunJob(ctx context.Context, spec RunSpec) (RunResult, error)
}
Kubernetes is the seam over the target cluster: the only path from the control plane to the runtime. It is deliberately narrow — the v0.1 operations (deploy, status, logs, scale, and the delete that supports teardown) and nothing more.
type LoadBalancerCapability ¶
type LoadBalancerCapability struct {
Supported bool `json:"supported"`
Inferred bool `json:"inferred"`
Provider string `json:"provider,omitempty"`
}
LoadBalancerCapability reports whether Service type=LoadBalancer is likely supported, and by what (ADR-0043). Supported is true when any LoadBalancer provider is present: a recognized cloud provider (a billable cloud load balancer), k3s's built-in servicelb, or MetalLB. Provider names which one — a cloud id (e.g. "digitalocean"), "servicelb", or "metallb" — empty when none is detected; only a cloud provider is billable. Inferred is always true: this recognizes a provider, not a direct probe (provisioning a LoadBalancer is the real test).
type LogEntry ¶
type LogEntry struct {
Time string `json:"time,omitempty"`
Message string `json:"message"`
Pod string `json:"pod,omitempty"`
}
LogEntry is one record returned by a logs query.
type LogLine ¶
type LogLine struct {
Pod string `json:"pod"`
Timestamp time.Time `json:"timestamp"`
Message string `json:"message"`
}
LogLine is a single line of application log output.
type LogOptions ¶
type LogOptions struct {
// TailLines bounds how many of the most recent lines to return. Zero means an
// adapter-defined default.
TailLines int
}
LogOptions selects which log lines to return.
type LogsQuerier ¶
type LogsQuerier interface {
// QueryLogs runs query against the logs store reachable at endpoint (an in-cluster
// host:port) and returns up to limit matching records, most recent first. token is a bearer
// credential for an authenticated backend; an empty token means unauthenticated and no
// Authorization header is sent.
QueryLogs(ctx context.Context, endpoint, query string, limit int, token string) ([]LogEntry, error)
}
LogsQuerier queries a logs backing service (an installed or connected add-on) for records matching a query, so the agent can answer "what happened? / why is it slow?" (ADR-0026). It is an optional seam — present only when logs querying is wired; the engine errors cleanly if not.
type MetricSample ¶
type MetricSample struct {
Labels map[string]string `json:"labels,omitempty"`
Value string `json:"value"`
Time string `json:"time,omitempty"`
}
MetricSample is one sample returned by a metrics query. Value is the metric's value as a string so PromQL's exact numeric formatting (precision, NaN/Inf) is preserved rather than lost to a float round-trip.
type MetricsQuerier ¶
type MetricsQuerier interface {
// QueryMetrics runs an instant PromQL query against the Prometheus-API-compatible store reachable
// at endpoint (an in-cluster host:port) and returns the matching samples. token is a bearer
// credential for an authenticated backend; an empty token means unauthenticated and no
// Authorization header is sent.
QueryMetrics(ctx context.Context, endpoint, query string, token string) ([]MetricSample, error)
}
MetricsQuerier runs an instant PromQL query against a Prometheus-API-compatible metrics store (an installed or connected add-on) so the agent can answer "how is my app performing? / what's the CPU, memory, or error rate?" (ADR-0026). It is an optional seam — present only when metrics querying is wired; the engine errors cleanly if not.
type MetricsServerCapability ¶ added in v0.13.0
type MetricsServerCapability struct {
Present bool `json:"present"`
}
MetricsServerCapability reports whether metrics-server is serving the Kubernetes Metrics API. Present is true when the metrics.k8s.io API group is served — i.e. a metrics-server (or a vendor's equivalent on k3s, GKE, or AKS) is registered — detected via API-group discovery, which needs no RBAC.
type MissingPrerequisitesError ¶ added in v0.8.0
type MissingPrerequisitesError struct {
// Host is the hostname the exposure targeted.
Host string
// TLS reports whether the exposure asked for HTTPS (so TLS prerequisites applied).
TLS bool
// Missing enumerates the prerequisites that are absent, each with its fix.
Missing []Prerequisite
}
MissingPrerequisitesError reports that an expose request cannot be satisfied because the cluster is not set up for the public, optionally TLS-terminated reachability it asked for. It is a structured outcome, not a system failure: the request was understood, but the cluster lacks one or more prerequisites. It enumerates each missing prerequisite and the burrow command that resolves it, so an agent can guide the user back onto Burrow's path rather than falling back to raw kubectl to diagnose the cluster (ADR-0006). Callers distinguish it with AsMissingPrerequisites.
func AsMissingPrerequisites ¶ added in v0.8.0
func AsMissingPrerequisites(err error) (*MissingPrerequisitesError, bool)
AsMissingPrerequisites reports whether err is (or wraps) a MissingPrerequisitesError and returns it, mirroring AsGuardrail so a front end (the HTTP API, the MCP server) can surface the structured checklist without parsing prose.
func (*MissingPrerequisitesError) Error ¶ added in v0.8.0
func (e *MissingPrerequisitesError) Error() string
type NextTagResult ¶ added in v0.13.0
type NextTagResult struct {
App string `json:"app"`
Env string `json:"env"`
Current string `json:"current,omitempty"`
Next *NextTags `json:"next,omitempty"`
Note string `json:"note,omitempty"`
}
NextTagResult is the read-only suggestion of an app's next semver release tags in one environment (ADR-0052 §8). Current is the app's running tag; Next carries the suggested patch/minor/major tags when that tag is stable semver. When there is no running release or the current tag is not semver, Next is nil and Note carries a short human reason — this degrades gracefully rather than erroring (ADR-0040), matching how AutoDeployStatus reports Checked=false with a Note.
type NextTags ¶ added in v0.13.0
type NextTags struct {
Patch string `json:"patch"`
Minor string `json:"minor"`
Major string `json:"major"`
}
NextTags are the suggested next release tags after a current semver tag (ADR-0052 §8): the next patch, minor, and major version, each preserving the current tag's leading "v" style. They turn "please use semver" into concrete numbers the agent applies to its build.
type NodeAllocatable ¶ added in v0.13.0
NodeAllocatable is one node's schedulable capacity, read straight from its .status.allocatable (the Kubernetes API's own scheduling budget). CPUMillis is in milli-CPU (1000 = one core); MemBytes is bytes. It carries no live-usage figure — allocatable is what the scheduler divides among pod requests, and needs no metrics-server.
type NodeCapacity ¶ added in v0.13.0
type NodeCapacity struct {
Name string `json:"name,omitempty"`
Pods int `json:"pods"`
AllocCPUMillis int64 `json:"alloc_cpu_millis"`
UsedCPUMillis int64 `json:"committed_cpu_millis"`
FreeCPUMillis int64 `json:"free_cpu_millis"`
AllocMemBytes int64 `json:"alloc_mem_bytes"`
UsedMemBytes int64 `json:"committed_mem_bytes"`
FreeMemBytes int64 `json:"free_mem_bytes"`
}
NodeCapacity is the allocatable / committed / free-headroom breakdown for one node, or — when Name is empty in CapacityReport.Cluster — the cluster-wide total. Committed is the sum of the resource requests of the pods scheduled here; Free is Allocatable minus Committed, the room the scheduler still has to place a new pod. All CPU figures are milli-CPU, memory figures bytes.
type PodRequest ¶ added in v0.13.0
PodRequest is one pod's summed resource requests and the node it is scheduled on. It is what the scheduler actually reserves against a node's allocatable — not live usage. Node is empty for a pod that has not been scheduled (e.g. Pending for lack of room); such a pod reserves nothing yet but still shows as a consumer of demand. CPUMillis is milli-CPU; MemBytes is bytes.
type Policy ¶
type Policy struct {
// Dispositions configures how each guardrail is enforced — allow, confirm, or deny
// (ADR-0020), keyed by GuardrailCode. A guardrail with no entry here defaults to deny:
// the safe default.
Dispositions map[GuardrailCode]Disposition
// MaxReplicas is the largest replica count permitted before the app.replica_ceiling
// guardrail's disposition applies. Must be positive.
MaxReplicas int32
}
Policy is the guardrail configuration the control plane evaluates dangerous operations against (ADR-0006). This type carries the limits; the evaluation that gates, constrains, or refuses an operation against them is the deploy engine's job.
func DefaultPolicy ¶
func DefaultPolicy() Policy
DefaultPolicy returns the conservative starting guardrail policy (ADR-0020): a modest replica ceiling that denies oversized scale-ups, and scale-to-zero held for confirmation — recoverable with an explicit confirm rather than silently allowed or hard-denied. The operator can relax or tighten any of these with `guard set`.
func (Policy) Guardrails ¶
func (p Policy) Guardrails() []GuardrailInfo
Guardrails returns each known guardrail with its effective disposition under the global policy (ADR-0020). Use GuardrailsFor to inspect a named environment's effective policy.
func (Policy) GuardrailsFor ¶ added in v0.7.0
func (p Policy) GuardrailsFor(env string) []GuardrailInfo
GuardrailsFor returns each known guardrail with its effective disposition for the named environment (ADR-0035 phase 2c): the disposition under the env-prefixed override, falling back to the global override, then the built-in default. Each entry's Source records where the effective disposition came from ("env", "global", or "default") so `guard list --env` can show which guardrails are env-specific and which are inherited. An empty or "default" env reproduces the global policy exactly (and leaves Source unset, as for Guardrails).
func (Policy) With ¶
func (p Policy) With(code GuardrailCode, d Disposition) Policy
With returns a copy of the policy with code's disposition set, leaving the receiver unchanged — the basis for `guard set` and for composing policies.
type Prerequisite ¶ added in v0.8.0
type Prerequisite struct {
// Name is the missing piece (e.g. "ingress controller", "cert-manager", "DNS provider").
Name string `json:"name"`
// Detail says what is missing and why the exposure needs it.
Detail string `json:"detail"`
// Fix is the burrow command that provisions it.
Fix string `json:"fix"`
}
Prerequisite names one cluster prerequisite a public, optionally TLS-terminated exposure needs but which is missing, paired with the exact burrow command that provisions it (ADR-0006, ADR-0034). It is the structured unit an agent reads to fix the cluster's setup without inspecting it directly with kubectl.
type Provider ¶
type Provider struct {
// Name identifies the provider within the registry. It defaults to the type but can be
// set explicitly so a user can register two providers of the same type. It must be a
// DNS-1123 label.
Name string `json:"name"`
// Type is the vendor this provider talks to.
Type ProviderType `json:"type"`
// Capabilities are the services this provider serves, derived from its type.
Capabilities []Capability `json:"capabilities"`
// SecretKey is the key under which this provider's token lives in burrow-credentials.
SecretKey string `json:"secret_key"`
// CreatedAt is when the provider was registered, read from the injected clock.
CreatedAt time.Time `json:"created_at"`
}
Provider is a configured vendor credential in the registry (ADR-0023): a name, the vendor type, the capabilities it serves, and the key in the burrow-credentials Secret that holds its token. The token itself never appears here — the registry is the non-secret structure, stored in the database; the control plane reads the token from the Secret at call time, so a rotation needs no restart.
func (Provider) Serves ¶
func (p Provider) Serves(c Capability) bool
Serves reports whether the provider serves capability c.
type ProviderCapability ¶
type ProviderCapability struct {
Cloud string `json:"cloud,omitempty"`
Name string `json:"name,omitempty"`
}
ProviderCapability reports the detected cloud provider. Cloud is the provider id (e.g. "digitalocean", "aws"), empty when unknown or bare-metal; Name is a human label (e.g. "DigitalOcean").
type ProviderType ¶
type ProviderType string
ProviderType identifies a vendor Burrow knows how to talk to. The type implies the capabilities it can serve; the adapter that actually makes the API calls plugs in per capability (ADR-0023). A credential is held per provider, so a user can split services across vendors (e.g. compute on DigitalOcean, DNS on Cloudflare).
const ( // ProviderDigitalOcean is DigitalOcean (DNS for v0.2; compute later). ProviderDigitalOcean ProviderType = "digitalocean" // ProviderCloudflare is Cloudflare (DNS). ProviderCloudflare ProviderType = "cloudflare" // ProviderGitHub is GitHub: a source provider whose one token covers both github.com private-git // clones and ghcr.io image pulls (ADR-0057). ProviderGitHub ProviderType = "github" // ProviderGitLab is GitLab: a source provider whose one token covers both gitlab.com private-git // clones and its container registry (ADR-0057). ProviderGitLab ProviderType = "gitlab" )
func SourceProviderForHost ¶ added in v0.13.0
func SourceProviderForHost(host string) (ProviderType, bool)
SourceProviderForHost returns the source provider that serves the given git host (e.g. "github.com" -> ProviderGitHub) and whether one is known. It is how the in-cluster build maps a clone URL's host to the provider whose credential authenticates it (ADR-0057 §1).
func SupportedProviderTypes ¶
func SupportedProviderTypes() []ProviderType
SupportedProviderTypes returns the provider types Burrow supports, sorted — for help text and the message shown when an unsupported type is requested.
func (ProviderType) Capabilities ¶
func (t ProviderType) Capabilities() []Capability
Capabilities returns the capabilities a provider of type t serves, as a fresh slice so callers cannot mutate the registry's knowledge.
func (ProviderType) GitHost ¶ added in v0.13.0
func (t ProviderType) GitHost() string
GitHost returns the git host a source provider clones from (e.g. "github.com"), or "" for a non-source provider.
func (ProviderType) GitUser ¶ added in v0.13.0
func (t ProviderType) GitUser() string
GitUser returns the HTTPS basic-auth username the clone presents alongside a source provider's token, or "" for a non-source provider.
func (ProviderType) RegistryHost ¶ added in v0.13.0
func (t ProviderType) RegistryHost() string
RegistryHost returns the registry host a source provider's token also authenticates (e.g. "ghcr.io"), or "" for a non-source provider (ADR-0057 §1).
func (ProviderType) Valid ¶
func (t ProviderType) Valid() bool
Valid reports whether t is a provider type Burrow supports.
type ReachabilityResult ¶
type ReachabilityResult struct {
App string `json:"app"`
Deployed bool `json:"deployed"`
Ready bool `json:"ready"`
Exposed bool `json:"exposed"`
Host string `json:"host,omitempty"`
Address string `json:"address,omitempty"` // controller-assigned external address
TLS bool `json:"tls"` // the Ingress requests an HTTPS certificate
CertReady bool `json:"cert_ready"` // the requested TLS certificate has been issued
DNSPointsAtCluster bool `json:"dns_points_at_cluster"`
DNSAddresses []string `json:"dns_addresses,omitempty"`
// Reachable is the converged verdict: every link in the chain is green and the app is live.
Reachable bool `json:"reachable"`
// URL is where the app is live when Reachable (https when TLS was requested, else http); it
// is empty until Reachable.
URL string `json:"url,omitempty"`
// BlockedOn names the first unready link when not Reachable (e.g. "ingress controller",
// "tls certificate", "dns"); it is empty when Reachable. It is the one link to fix next.
BlockedOn string `json:"blocked_on,omitempty"`
Summary string `json:"summary"`
}
ReachabilityResult reports whether an app is reachable at its hostname, link by link, for the reachability surface (ADR-0018, ADR-0022). Summary is a one-line, plain-English verdict for a non-expert; the fields are the full chain for advanced users and the agent.
type RegistryAuth ¶ added in v0.13.0
RegistryAuth carries optional basic-auth credentials for listing a private repo's tags (ADR-0052 §7). The zero value lists anonymously — the case for public GHCR/Docker Hub.
type RegistryClient ¶ added in v0.13.0
type RegistryClient interface {
// ListTags returns the tags available in the repository named by imageRef (a reference
// like "ghcr.io/user/app:1.2.3" or the bare repo). auth carries optional basic-auth
// credentials for a private repo; the zero value lists anonymously. It follows the Docker
// Registry HTTP API v2 tag-list pagination and the standard Bearer-token auth flow.
ListTags(ctx context.Context, imageRef string, auth RegistryAuth) ([]string, error)
}
RegistryClient lists the tags of a container image repository so burrowd can see which versions exist and compute what auto-deploy would take (ADR-0052). It is OUTBOUND-only and used only for the optional auto-deploy read/watch — never on the core deploy path, which stays independent of registry reachability (ADR-0040). It is an OPTIONAL seam: when it is not wired the auto-deploy show degrades to reporting the level alone.
type Release ¶
type Release struct {
// ID is the control-plane-assigned identifier for this release. It is minted by
// the deploy engine, not here, so the domain type stays free of ambient identity.
ID string `json:"id"`
// App is the name of the App this release belongs to.
App string `json:"app"`
// Environment is the canonical environment this release was deployed into (ADR-0052 §5):
// a named environment, or "default" for the implicit default environment. Releases are
// keyed per (app, environment), so an app can have an independent history in each.
Environment string `json:"environment,omitempty"`
// Image is the pullable container image reference the deploy named (ADR-0007).
Image string `json:"image"`
// Digest is the content digest of the running Image, when known (e.g. "sha256:...").
// burrowd never resolves it against the registry (ADR-0040); it is an observed value
// read back from Kubernetes, so it is empty until observed. Read-back is a deferred
// follow-up (ADR-0040 §3), so for now it is left unset.
Digest string `json:"digest,omitempty"`
// Env is the environment passed to the workload.
Env map[string]string `json:"env,omitempty"`
// Command overrides the image's default command, when set.
Command []string `json:"command,omitempty"`
// MetricsPort, when positive, is the container port the app serves Prometheus metrics on.
// The deploy annotates the pod (prometheus.io/scrape, /port, /path) so the metrics add-on's
// scraper discovers and scrapes /metrics on it. Zero means no metrics annotations (ADR-0026).
MetricsPort int32 `json:"metrics_port,omitempty"`
// Replicas is the desired replica count.
Replicas int32 `json:"replicas"`
// Status is the lifecycle state of this release.
Status ReleaseStatus `json:"status"`
// Supersedes is the ID of the release this one replaced, if any — the chain that
// lets rollback walk back to a prior known-good release.
Supersedes string `json:"supersedes,omitempty"`
// Trigger is how this deploy was triggered (ADR-0052 §5): "manual" for an explicit CLI or
// agent deploy — the default for every deploy today — or "auto" for the pull-based passive
// watcher (Phase 4b). Empty on rows written before provenance existed.
Trigger ReleaseTrigger `json:"trigger,omitempty"`
// AutoLevel is the auto-deploy level that applied when the watcher took this release
// (ADR-0052 §5). Set only for an auto trigger; empty for a manual deploy.
AutoLevel AutoDeployLevel `json:"auto_level,omitempty"`
// AutoTag is the resolved tag the watcher moved to when it took this release (ADR-0052 §5).
// Set only for an auto trigger; empty for a manual deploy.
AutoTag string `json:"auto_tag,omitempty"`
// CreatedAt is when the control plane recorded this release, read from the
// injected clock (never from ambient time).
CreatedAt time.Time `json:"created_at"`
}
Release is one immutable deploy of an App: the unit recorded in the deploy history and the handle rollback redeploys. It captures exactly what was asked for — the pullable image, the resolved digest, and the small metadata that travels over MCP (env, command, replica count) — never any code (ADR-0004).
type ReleaseStatus ¶
type ReleaseStatus string
ReleaseStatus is the lifecycle state of a Release.
const ( // ReleasePending is a release that has been recorded but not yet rolled out. ReleasePending ReleaseStatus = "pending" // ReleaseDeployed is a release that rolled out successfully and is (or was) running. ReleaseDeployed ReleaseStatus = "deployed" // ReleaseFailed is a release whose rollout did not succeed. ReleaseFailed ReleaseStatus = "failed" // ReleaseSuperseded is a release replaced by a newer one (deploy or rollback). ReleaseSuperseded ReleaseStatus = "superseded" )
func (ReleaseStatus) Valid ¶
func (s ReleaseStatus) Valid() bool
Valid reports whether s is a known ReleaseStatus.
type ReleaseTrigger ¶ added in v0.13.0
type ReleaseTrigger string
ReleaseTrigger records how a deploy was triggered — its provenance (ADR-0052 §5), so the deploy record and audit log distinguish an explicit deploy from an unattended auto-update.
const ( // TriggerManual is an explicit deploy driven by a human or an agent through the CLI or the // control-plane API. It is the default for every deploy today: nothing auto-deploys until the // Phase 4b watcher lands. TriggerManual ReleaseTrigger = "manual" // TriggerAuto is an unattended deploy the pull-based passive watcher performed on its own // (ADR-0052 §1). An auto release also records the level that applied and the tag it took. TriggerAuto ReleaseTrigger = "auto" )
func (ReleaseTrigger) Valid ¶ added in v0.13.0
func (t ReleaseTrigger) Valid() bool
Valid reports whether t is a known release trigger.
type RemoveDomainRequest ¶
type RemoveDomainRequest struct {
Host string `json:"host"`
Provider string `json:"provider"`
// Confirm acknowledges the dns.delete guardrail so the operation proceeds past it.
Confirm bool `json:"confirm,omitempty"`
}
RemoveDomainRequest removes the DNS record a provider holds for a host (ADR-0018).
type Resolver ¶
type Resolver interface {
// LookupHost returns the IP addresses host resolves to, or an error (e.g. NXDOMAIN).
LookupHost(ctx context.Context, host string) ([]string, error)
}
Resolver is the control plane's DNS lookups, injected so reachability checks stay deterministic in tests (ADR-0018). It reports the addresses a hostname resolves to.
type RollbackResult ¶
type RollbackResult struct {
// Release is the new release created by the rollback (carrying the prior
// reference) and now running.
Release Release `json:"release"`
// RolledBackToReleaseID is the prior release whose reference was restored.
RolledBackToReleaseID string `json:"rolled_back_to_release_id"`
// SupersededReleaseID is the release that was running before the rollback.
SupersededReleaseID string `json:"superseded_release_id"`
}
RollbackResult reports the outcome of a rollback. A rollback is itself a forward deploy of a prior reference (ADR-0007), so it produces a new Release.
type RunRequest ¶ added in v0.12.0
type RunRequest struct {
App string `json:"app"`
// Env is the environment whose namespace the command runs in (ADR-0035): empty or "default"
// targets the default environment, a registered name targets that environment.
Env string `json:"env,omitempty"`
// Command is the command and its arguments to run, as an argv. It must be non-empty.
Command []string `json:"command"`
// TTLSeconds overrides how long the finished Job lingers before Kubernetes garbage-collects it
// (ttlSecondsAfterFinished; ADR-0048 §7). Nil applies the default (one hour); a value — including
// 0, delete as soon as the output is captured — overrides it. A negative value is rejected.
TTLSeconds *int32 `json:"ttl_seconds,omitempty"`
// Confirm acknowledges the app.run guardrail whose disposition is confirm, letting the command
// proceed past it (ADR-0020). It has no effect on a guardrail set to deny.
Confirm bool `json:"confirm,omitempty"`
}
RunRequest is a one-off command to run in an app's own current image and environment (ADR-0048). It carries only the command (and its arguments) — the image, config, and secrets come from the app, resolved server-side. No code travels here (ADR-0004): the command names an entrypoint that already exists in the app's image.
type RunResult ¶ added in v0.12.0
type RunResult struct {
App string `json:"app"`
ExitCode int `json:"exit_code"`
Stdout string `json:"stdout,omitempty"`
Stderr string `json:"stderr,omitempty"`
// TimedOut reports the command did not finish within the run window (10 minutes).
TimedOut bool `json:"timed_out,omitempty"`
}
RunResult reports the outcome of a one-off command (ADR-0048 §3). A non-zero ExitCode is a normal structured outcome the agent reasons over, not a transport failure. Stdout carries the command's captured output — Kubernetes returns a pod's stdout and stderr as one interleaved stream, so the output lands in Stdout; Stderr is reserved for a future separation.
type RunSpec ¶ added in v0.12.0
type RunSpec struct {
App string
// ID is the unique run identifier the Job name derives from (burrow-run-<ID>), minted by the
// engine's ID seam so the Job name is deterministic in tests.
ID string
// Image is the app's currently-deployed image the command runs in (ADR-0048 §2).
Image string
// Command is the command and its arguments run as the container's command.
Command []string
// Env is the app's non-secret config, sourced as container env alongside the per-app Secret.
Env map[string]string
// TTLSeconds is the Job's ttlSecondsAfterFinished — how long the finished Job lingers before
// Kubernetes garbage-collects it (ADR-0048 §7). Zero deletes it as soon as it finishes.
TTLSeconds int32
}
RunSpec is the desired one-off command Job the kube seam builds for RunJob (ADR-0048): the app's own current image and config env, plus the caller's command and the finished-Job TTL. The per-app Secret is injected by the adapter via envFrom, not carried here, so no secret value crosses this seam. No code travels here (ADR-0004) — the command names an entrypoint already in the image.
type ScaleResult ¶
type SourceCredential ¶ added in v0.13.0
type SourceCredential struct {
// Provider is the source provider the token authenticates (github, gitlab), which fixes the git
// host and the registry host the token also covers (ADR-0057 §1).
Provider ProviderType
// Token is the provider token VALUE (an OSS Personal Access Token). It lives in memory in burrowd
// only long enough to be written into the build Job's mounted Secret.
Token string
}
SourceCredential is a resolved source-provider credential the in-cluster build uses to authenticate a PRIVATE git clone and, where the provider hosts a registry, the build's image push/pull (ADR-0057). The engine resolves it from the guarded credential store just before a build and hands it to the Builder seam, which mounts it into the build Job — the token is never a Job env var, a command-line argument, or anything the agent sees. The zero value (empty Token) means public-source, credential-free — the pre-ADR-0057 behavior. The Token is a SECRET: it is never logged, never placed in an error or an API response, and never stored in Postgres.
func (SourceCredential) IsZero ¶ added in v0.13.0
func (c SourceCredential) IsZero() bool
IsZero reports whether the credential carries no token — the public-source, credential-free path.
type SourceRef ¶ added in v0.13.0
type SourceRef struct {
// Repo is the git repository URL to clone (e.g. "https://github.com/user/app").
Repo string
// Ref is the commit SHA or tag to check out. A build pins an exact reference, so it is required.
Ref string
}
SourceRef names the git source an in-cluster build clones and checks out inside the user's own cluster (ADR-0053 §3): a repository URL plus the commit or tag to build. It is the ONLY thing a build carries over the control channel — never source bytes (ADR-0004). The builder clones the actual code from git inside the cluster; the agent never carries a tarball, a diff, or any code.
type StatusResult ¶
type StatusResult struct {
App string `json:"app"`
// HasRelease reports whether the control plane has any release recorded for the
// app; Release holds the most recent one when true.
HasRelease bool `json:"has_release"`
Release Release `json:"release,omitempty"`
// Running reports whether a workload currently exists in the cluster; Workload
// holds its observed state when true.
Running bool `json:"running"`
Workload WorkloadStatus `json:"workload,omitempty"`
}
StatusResult is the combined control-plane and cluster view of an app.
type StorageCapability ¶
type StorageCapability struct {
DefaultPresent bool `json:"default_present"`
DefaultClass string `json:"default_class,omitempty"`
Classes []string `json:"classes,omitempty"`
}
StorageCapability reports the cluster's persistent-storage situation. DefaultPresent is true when a StorageClass carries the default-class annotation; DefaultClass is its name; Classes are all StorageClass names found, sorted.
type WorkloadKind ¶
type WorkloadKind string
WorkloadKind names the Kubernetes resource a workload maps to (ADR-0011). The seam speaks in workloads rather than a single resource type so new kinds are additive. The empty value means WorkloadDeployment.
const ( // WorkloadDeployment is a stateless Deployment — the only kind v0.1 uses. WorkloadDeployment WorkloadKind = "Deployment" // WorkloadStatefulSet is a stateful StatefulSet, for workloads needing stable // identity, persistent volumes, or ordered rollout. Not used in v0.1; reserved so // adding it later is additive, not a rename. WorkloadStatefulSet WorkloadKind = "StatefulSet" )
type WorkloadSpec ¶
type WorkloadSpec struct {
App string
Kind WorkloadKind
Image string
Env map[string]string
Command []string
// MetricsPort, when positive, is the container port the app serves Prometheus metrics on.
// buildDeployment annotates the pod template (prometheus.io/scrape, /port, /path) so the
// metrics add-on's scraper discovers and scrapes /metrics on it. Zero adds no annotations.
MetricsPort int32
Replicas int32
// ReleaseID is the release this workload is applying. It is stamped on the pod template (under
// ReleaseAnnotation) so a new release always rolls the workload, even when the image reference
// is unchanged; re-applying the same release reuses the same ID, so the apply stays idempotent.
// Empty adds no annotation.
ReleaseID string
}
WorkloadSpec is the desired state of one App's Kubernetes workload — the small, code-free description a deploy turns into (ADR-0004): a kind, a pullable image, and metadata.
type WorkloadStatus ¶
type WorkloadStatus struct {
App string `json:"app"`
Kind WorkloadKind `json:"kind"`
Image string `json:"image"`
DesiredReplicas int32 `json:"desired_replicas"`
ReadyReplicas int32 `json:"ready_replicas"`
UpdatedReplicas int32 `json:"updated_replicas"`
// Available reports whether the workload currently meets its availability
// condition (enough ready replicas to serve).
Available bool `json:"available"`
// Issue is a human- and agent-actionable explanation of why an unavailable workload is
// blocked, when the cluster reports a genuinely blocking pod condition — e.g. a pull
// failure that names the image, the registry host, and the `burrow config registry login`
// fix (ADR-0006). It is best-effort enrichment: empty when the workload is healthy or
// when no blocking condition was observed, so it never becomes a required field.
Issue string `json:"issue,omitempty"`
// IssueReason is the raw, machine-usable Kubernetes reason behind Issue (e.g.
// "ImagePullBackOff" or "ErrImagePull"), for an agent that wants to branch on the cause
// rather than parse the prose. Empty whenever Issue is empty.
IssueReason string `json:"issue_reason,omitempty"`
}
WorkloadStatus is the observed state of an App's workload, as reported by the cluster.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package api is the control plane's HTTP front end: it exposes the deploy engine's operations over JSON and authenticates its callers with a bearer token (ADR-0005).
|
Package api is the control plane's HTTP front end: it exposes the deploy engine's operations over JSON and authenticates its callers with a bearer token (ADR-0005). |
|
Package dns is the production controlplane.DNSFactory and the per-vendor DNS adapters (ADR-0018, ADR-0023).
|
Package dns is the production controlplane.DNSFactory and the per-vendor DNS adapters (ADR-0018, ADR-0023). |
|
Package e2e holds end-to-end tests that drive the deploy engine through its real adapters — the client-go Kubernetes adapter against a live cluster and the registry resolver against a real registry — proving the whole vertical slice composes (ADR-0010).
|
Package e2e holds end-to-end tests that drive the deploy engine through its real adapters — the client-go Kubernetes adapter against a live cluster and the registry resolver against a real registry — proving the whole vertical slice composes (ADR-0010). |
|
Package internal holds the control plane's implementation guts: the deploy state machine, the Kubernetes/registry/database seam adapters, and the guardrail policy.
|
Package internal holds the control plane's implementation guts: the deploy state machine, the Kubernetes/registry/database seam adapters, and the guardrail policy. |
|
fake
Package fake provides in-memory implementations of the control-plane seams (controlplane.Clock, Kubernetes, Registry, Database) for tests.
|
Package fake provides in-memory implementations of the control-plane seams (controlplane.Clock, Kubernetes, Registry, Database) for tests. |
|
Package kube is the production controlplane.Kubernetes adapter, built on the official client-go SDK (ADR-0011).
|
Package kube is the production controlplane.Kubernetes adapter, built on the official client-go SDK (ADR-0011). |
|
Package logs holds the production controlplane.LogsQuerier adapters — the seam burrowd uses to query an installed or connected logs backing service (ADR-0026).
|
Package logs holds the production controlplane.LogsQuerier adapters — the seam burrowd uses to query an installed or connected logs backing service (ADR-0026). |
|
Package metrics holds the adapters that query a metrics backing service (an installed or connected add-on) for the agent's metrics-query path (ADR-0026).
|
Package metrics holds the adapters that query a metrics backing service (an installed or connected add-on) for the agent's metrics-query path (ADR-0026). |
|
Package postgres is the production controlplane.Database adapter, backed by Postgres running in the cluster (ADR-0012).
|
Package postgres is the production controlplane.Database adapter, backed by Postgres running in the cluster (ADR-0012). |
|
Package registry holds the adapter that lists a container image repository's tags over the Docker Registry HTTP API v2, so burrowd can see which versions exist and compute what auto-deploy would take (ADR-0052).
|
Package registry holds the adapter that lists a container image repository's tags over the Docker Registry HTTP API v2, so burrowd can see which versions exist and compute what auto-deploy would take (ADR-0052). |
|
Package sys holds the production implementations of the control plane's system seams — the wall Clock, a crypto/rand ID source, and the DNS Resolver — the concrete values cmd/burrowd injects in place of the test fakes (ADR-0010).
|
Package sys holds the production implementations of the control plane's system seams — the wall Clock, a crypto/rand ID source, and the DNS Resolver — the concrete values cmd/burrowd injects in place of the test fakes (ADR-0010). |