Documentation
¶
Overview ¶
Package iac turns multi-document YAML resource files into a plan of changes against a control plane and applies it through the ordinary REST API, so per-resource authorization applies to every item.
Index ¶
- Constants
- Variables
- func Build(docs []*Document, opts Options) ([]*Resource, []Issue)
- func IsDenied(err error) bool
- func IsFeatureOff(err error) bool
- func IsNotFound(err error) bool
- func IsSettingWeaken(c Change) bool
- func IsUnavailable(err error) bool
- func LeakedSecret(docs []byte, secrets map[string]string) (string, bool)
- func LedgerConflicts(p Plan) []string
- func MassPruneBlocked(p Plan) bool
- func ParseDocuments(sources []Source) ([]*Document, []Issue)
- func PlanValueMax() int
- func SchemaJSON() ([]byte, error)
- func ValidateStackLevel(scope StackLevel, project, environment string) error
- func ValidateStackName(name string) error
- func WithAllowWeaken(ctx context.Context) context.Context
- func WithLedger(ctx context.Context, l *Ledger) context.Context
- func WithStackScope(ctx context.Context, s StackScope) context.Context
- type Action
- type ApplyResult
- type Change
- type Document
- type Doer
- type DriftPolicy
- type ExportFile
- type ExportOptions
- type ExportResult
- type FieldChange
- type GitMeta
- type Issue
- type ItemResult
- type Kind
- type Ledger
- type LedgerEntry
- type LedgerRef
- type LedgerUpdate
- type Options
- type Plan
- type PruneGuard
- type Resource
- type Revision
- type RevisionStatus
- type Source
- type Stack
- type StackDeleteMode
- type StackLevel
- type StackLock
- type StackScope
- type StackSpec
- type State
- type StatusError
- type Summary
Constants ¶
const ( StatusApplied = "applied" StatusFailed = "failed" StatusDenied = "denied" StatusSkipped = "skipped" StatusNoop = "noop" )
Item outcomes.
const ( OpAdd = "add" OpChange = "change" OpRemove = "remove" OpKeep = "keep" )
Field operations shown in a plan.
const DefaultDriftPolicy = DriftWarn
DefaultDriftPolicy is warn so hand edits keep working until an owner opts in.
const (
LabelManagedBy = "managed-by"
)
Labels with a meaning of their own.
const NodeTaintWeakenReason = "weakens a protected taint; allowed by allow_weaken"
NodeTaintWeakenReason starts the reason of a Node change that drops or loosens a protected taint under allow_weaken, so reviewers can find it.
const SchemaVersion = 1
SchemaVersion is the only resource document version this package accepts.
const SettingWeakenReason = "weakens a security protection; allowed by allow_weaken"
SettingWeakenReason starts the reason of every Setting change that weakens a protection under allow_weaken, so reviewers and the audit can find it.
Variables ¶
var ( ErrStackPinned = errors.New("stack is pinned; only a rollback to the pinned revision may apply") ErrStackLocked = errors.New("stack is locked by another apply") ErrLedgerConflict = errors.New("resource is owned by another stack") ErrPruneNotAllowed = errors.New("prune needs both the manifest request and the stack allow_prune setting") )
Sentinel errors the store and API map to 409 or 422 responses.
var ErrPlanChanged = errors.New("the plan changed since it was reviewed; plan again")
ErrPlanChanged means live state moved between the plan a user reviewed and the apply that followed.
var Kinds = []Kind{KindProject, KindEnvironment, KindTag, KindNode, KindSetting, KindDatabase, KindApp, KindDomain, KindLoadBalancer, KindPipeline, KindAlertRule}
Kinds lists every kind in the order changes are applied.
Functions ¶
func Build ¶
Build validates every document (schema first, then semantics) and turns them into canonical resources. It returns no resources when any issue exists, so nothing is planned from a partly valid input.
func IsFeatureOff ¶
IsFeatureOff reports whether err is the 404 a gated endpoint answers while its beta feature is disabled.
func IsSettingWeaken ¶
IsSettingWeaken reports whether c is a Setting change that weakens a protection.
func IsUnavailable ¶
IsUnavailable reports whether err says the feature is not configured.
func LeakedSecret ¶
LeakedSecret returns the name of a secret whose value appears in docs, so a revision can be refused before it is stored. Empty values are skipped.
func LedgerConflicts ¶
LedgerConflicts returns the keys a plan refused for ownership reasons.
func MassPruneBlocked ¶
MassPruneBlocked reports whether the guard held back any delete.
func ParseDocuments ¶
ParseDocuments splits sources into documents and decodes each envelope. It reports YAML syntax errors and missing kind or name, nothing deeper; Validate does the schema and semantic checks.
func PlanValueMax ¶
func PlanValueMax() int
PlanValueMax is the longest before or after value a plan shows.
func SchemaJSON ¶
SchemaJSON returns the published JSON Schema: the resource envelope schema with the app.yaml schema definitions merged in unchanged, except that an env value may also be a { secretRef } or a { from: { database, field } } object.
func ValidateStackLevel ¶
func ValidateStackLevel(scope StackLevel, project, environment string) error
ValidateStackLevel checks the project and environment fit the scope.
func ValidateStackName ¶
ValidateStackName checks the stack name shape.
func WithAllowWeaken ¶
WithAllowWeaken lets the Setting plan confirm a weakening change. Only the API sets it, after it has checked the caller is a human root session.
func WithLedger ¶
WithLedger makes Load and the planner enforce l. It is a server-side value; nothing a client sends can set it.
func WithStackScope ¶
func WithStackScope(ctx context.Context, s StackScope) context.Context
WithStackScope returns ctx carrying the stack apply scope.
Types ¶
type ApplyResult ¶
type ApplyResult struct {
Plan Plan `json:"plan"`
Results []ItemResult `json:"results"`
Applied int `json:"applied"`
Failed int `json:"failed"`
Skipped int `json:"skipped"`
}
ApplyResult is the outcome of an apply.
func Apply ¶
func Apply(ctx context.Context, d Doer, res []*Resource, opts Options, expectedHash string) (ApplyResult, error)
Apply plans and executes resources through doer. Validation and reading happen before any write; a plan holding an error item applies nothing unless ContinueOnError is set. Items run in dependency order and each goes through the ordinary endpoint, so a denied write is reported on its own item and the rest proceed only when ContinueOnError is set.
func (ApplyResult) OK ¶
func (r ApplyResult) OK() bool
OK reports whether every item that had to change did.
type Change ¶
type Change struct {
Kind Kind `json:"kind"`
Name string `json:"name"`
Scope string `json:"scope,omitempty"`
Action Action `json:"action"`
Fields []FieldChange `json:"fields,omitempty"`
Kept []FieldChange `json:"kept,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Reason string `json:"reason,omitempty"`
// DependsOn lists the resources this item reads from, applied first.
DependsOn []string `json:"dependsOn,omitempty"`
Denied bool `json:"denied,omitempty"`
// LiveID is the live identifier a ledger records; it never leaves the process.
LiveID string `json:"-"`
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
}
Change is one line of a plan.
type Document ¶
type Document struct {
File string
Line int
Kind Kind
Name string
Labels map[string]string
Root *yaml.Node
SpecNode *yaml.Node
}
Document is one parsed YAML document with its envelope decoded.
func FilterProject ¶
FilterProject keeps the documents that belong to one project: the project itself, its environments, apps and databases, the domains, load balancers, pipelines and alert rules of those apps, and the tags those apps use.
type Doer ¶
Doer performs one JSON request against the control plane REST API. The CLI implements it over HTTP with the caller's token; the server implements it in process with the caller's own credentials, so per resource authorization applies to every call either way.
type DriftPolicy ¶
type DriftPolicy string
DriftPolicy says what happens when live state diverges from the stack.
const ( DriftWarn DriftPolicy = "warn" DriftLock DriftPolicy = "lock" )
Drift policies; revert is deliberately not offered.
func ParseDriftPolicy ¶
func ParseDriftPolicy(s string) (DriftPolicy, error)
ParseDriftPolicy maps an empty value to the default and rejects unknown ones.
type ExportFile ¶
type ExportFile struct {
Name string `json:"name"`
Kind Kind `json:"kind"`
Content string `json:"content"`
}
ExportFile is one exported document.
type ExportOptions ¶
type ExportOptions struct {
Project string
App string
// Database and Node each export that one resource alone.
Database string
Node string
// Settings exports only the saved, non-sensitive runtime settings.
Settings bool
// IncludeEnvValues writes literal env values; when false every value
// becomes a ${{ env.NAME }} placeholder.
IncludeEnvValues bool
}
ExportOptions select what to export.
type ExportResult ¶
type ExportResult struct {
Files []ExportFile `json:"files"`
Warnings []string `json:"warnings,omitempty"`
}
ExportResult is the exported documents in stable order plus warnings for live settings the documents cannot express.
func Export ¶
func Export(ctx context.Context, d Doer, opts ExportOptions) (ExportResult, error)
Export reads live state and renders it as documents. Output is stable: no timestamps or generated ids, sorted keys and a fixed document order.
func (ExportResult) Join ¶
func (r ExportResult) Join() string
Join concatenates exported files into one multi-document stream.
type FieldChange ¶
type FieldChange struct {
Path string `json:"path"`
Op string `json:"op"`
Old string `json:"old,omitempty"`
New string `json:"new,omitempty"`
}
FieldChange is one leaf difference. Values under env are never shown.
type GitMeta ¶
type GitMeta struct {
Repo string `json:"git_repo,omitempty"`
Ref string `json:"git_ref,omitempty"`
SHA string `json:"git_sha,omitempty"`
CIRunURL string `json:"ci_run_url,omitempty"`
}
GitMeta links a revision to the commit and CI run that produced it.
type Issue ¶
type Issue struct {
File string `json:"file,omitempty"`
Path string `json:"path,omitempty"`
Line int `json:"line"`
Message string `json:"message"`
}
Issue is one validation problem, located by file, line and dotted path.
type ItemResult ¶
type ItemResult struct {
Change
Status string `json:"status"`
Error string `json:"error,omitempty"`
}
ItemResult is one plan item's outcome.
type Kind ¶
type Kind string
Kind names a resource document type.
const ( KindProject Kind = "Project" KindEnvironment Kind = "Environment" KindTag Kind = "Tag" KindNode Kind = "Node" KindSetting Kind = "Setting" KindDatabase Kind = "Database" KindApp Kind = "App" KindDomain Kind = "Domain" KindLoadBalancer Kind = "LoadBalancer" KindPipeline Kind = "Pipeline" KindAlertRule Kind = "AlertRule" )
Resource kinds, in dependency order.
const KindStack Kind = "Stack"
KindStack is the optional stack document. It is not in Kinds because it configures the apply instead of describing a resource.
type Ledger ¶
type Ledger struct {
StackID string
StackName string
// Scope is the stack scope; empty means project.
Scope StackLevel
// ScopeProject is the stack's own project; an admin bound it to the stack,
// so declaring it claims it without an explicit adopt.
ScopeProject string
// Owned maps resource keys this stack owns to their entries.
Owned map[string]LedgerEntry
// Foreign maps keys owned by some other stack to that stack's id.
Foreign map[string]string
// Adopt lets the stack claim existing resources no stack owns.
Adopt bool
// AllowMassPrune confirms a prune above the guard thresholds.
AllowMassPrune bool
Guard PruneGuard
}
Ledger is the stack ownership view a plan is checked against (ADR 042). With a ledger, ownership and prune come from it alone, never from tags.
func NewInstallLedger ¶
func NewInstallLedger(stackID, stackName string, owned []LedgerEntry) *Ledger
NewInstallLedger builds the view for an install stack, which may own only Node and Setting documents and has no project to claim.
func NewLedger ¶
func NewLedger(stackID, stackName, scopeProject string, owned []LedgerEntry) *Ledger
NewLedger builds the view for one stack from its own ledger rows.
type LedgerEntry ¶
type LedgerEntry struct {
StackID string `json:"stack_id"`
Kind Kind `json:"kind"`
Key string `json:"key"`
LiveID string `json:"live_id"`
SinceRevision int `json:"since_revision"`
}
LedgerEntry records the single stack that owns a resource. Key is the resourceKey (kind/scope/name), unique per kind across all stacks.
type LedgerRef ¶
LedgerRef is one (kind, key) whose owner a caller must look up.
func LedgerRefs ¶
LedgerRefs lists every key the plan checks ownership of: each resource plus the parent app of Domain, LoadBalancer, Pipeline and AlertRule documents.
type LedgerUpdate ¶
type LedgerUpdate struct {
Claim []LedgerEntry
Release []LedgerEntry
}
LedgerUpdate is what one apply changes in the ledger.
func LedgerChanges ¶
func LedgerChanges(stackID string, rev int, res []*Resource, out ApplyResult) LedgerUpdate
LedgerChanges derives claims and releases from an apply. Only declared resources are claimed, and nothing is claimed from a plan refused for errors.
type Options ¶
type Options struct {
// Vars resolve ${{ env.NAME }} placeholders.
Vars map[string]string
// Source names this apply source; apps it creates carry a managed-by
// tag and only those are ever pruned.
Source string
// Secrets maps "NAME" or "app/NAME" to a value to store at apply time.
// Values never appear in a plan, result or export.
Secrets map[string]string
// Project limits prune to managed apps of this project, matching the
// project filter applied to the files.
Project string
// Prune deletes managed resources absent from the files.
Prune bool
// NoDeploy skips the restart that applies an env change to a running app.
NoDeploy bool
// ContinueOnError keeps applying after an item fails.
ContinueOnError bool
}
Options carry apply-time inputs that never live in a resource file.
type Plan ¶
type Plan struct {
Source string `json:"source,omitempty"`
Prune bool `json:"prune,omitempty"`
Changes []Change `json:"changes"`
Summary Summary `json:"summary"`
Hash string `json:"hash"`
}
Plan is the ordered set of changes needed to reach the desired state.
type PruneGuard ¶
PruneGuard bounds how much of a stack one prune may delete without an explicit confirmation.
func PruneGuardFromEnv ¶
func PruneGuardFromEnv() PruneGuard
PruneGuardFromEnv reads APP_STACK_PRUNE_MAX_PERCENT and APP_STACK_PRUNE_MAX_COUNT; zero is valid and means any prune needs the flag.
type Resource ¶
type Resource struct {
Kind Kind
Name string
Scope string
Doc *Document
Fields map[string]any
// SecretRefs are app env names backed by the secrets store.
SecretRefs []string
// ExistingSecrets are SecretRefs that must already be in the store.
ExistingSecrets []string
Warnings []string
}
Resource is one desired object in canonical form, ready to compare with live state.
type Revision ¶
type Revision struct {
StackID string `json:"stack_id"`
Number int `json:"number"`
Status RevisionStatus `json:"status"`
PlanHash string `json:"plan_hash"`
PlanJSON []byte `json:"plan,omitempty"`
RenderedDocs []byte `json:"rendered_docs,omitempty"`
RollbackOf int `json:"rollback_of,omitempty"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
Actor string `json:"actor"`
TokenID string `json:"token_id,omitempty"`
Git GitMeta `json:"git"`
CreatedAt time.Time `json:"created_at"`
FinishedAt *time.Time `json:"finished_at,omitempty"`
ResultJSON []byte `json:"result,omitempty"`
}
Revision is one numbered apply of a stack. RenderedDocs holds documents after env substitution only; secret values are never part of it.
type RevisionStatus ¶
type RevisionStatus string
RevisionStatus is the lifecycle of one stack apply.
const ( RevisionPendingApproval RevisionStatus = "pending_approval" RevisionApplying RevisionStatus = "applying" RevisionApplied RevisionStatus = "applied" RevisionPartial RevisionStatus = "partial" RevisionFailed RevisionStatus = "failed" RevisionSuperseded RevisionStatus = "superseded" )
Revision statuses, in lifecycle order.
func (RevisionStatus) CanTransition ¶
func (s RevisionStatus) CanTransition(next RevisionStatus) bool
CanTransition reports whether a revision may move from s to next.
func (RevisionStatus) Terminal ¶
func (s RevisionStatus) Terminal() bool
Terminal statuses never change again, except applied becoming superseded.
func (RevisionStatus) Valid ¶
func (s RevisionStatus) Valid() bool
Valid reports whether s is a known status.
type Stack ¶
type Stack struct {
ID string `json:"id"`
Name string `json:"name"`
ProjectID string `json:"project_id"`
EnvironmentID string `json:"environment_id,omitempty"`
Scope StackLevel `json:"scope,omitempty"`
AllowPrune bool `json:"allow_prune"`
DriftPolicy DriftPolicy `json:"drift_policy"`
PinnedRevision int `json:"pinned_revision,omitempty"` // 0 means not pinned
RequireApproval bool `json:"require_approval"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by"`
}
Stack is the server-side stack object. Settings here are admin-owned; a manifest can request but never grant them.
func (*Stack) CheckPin ¶
CheckPin returns ErrStackPinned unless the apply is a rollback to the pin. rollbackOf is 0 for an ordinary apply.
func (*Stack) PruneAllowed ¶
PruneAllowed needs both sides so a manifest cannot grant itself prune.
type StackDeleteMode ¶
type StackDeleteMode string
StackDeleteMode picks what deleting a stack does to its resources.
const ( // StackForget releases the ledger and leaves every resource in place. StackForget StackDeleteMode = "forget" // StackDestroy deletes ledger apps, and databases only with delete_data; never from CI. StackDestroy StackDeleteMode = "destroy" )
func ParseStackDeleteMode ¶
func ParseStackDeleteMode(s string) (StackDeleteMode, error)
ParseStackDeleteMode defaults to forget, the mode that cannot lose data.
type StackLevel ¶
type StackLevel string
StackLevel says whether a stack belongs to one project or to the install.
const ( LevelProject StackLevel = "project" LevelInstall StackLevel = "install" )
Stack scopes; an empty scope means project.
func ParseStackLevel ¶
func ParseStackLevel(s string) (StackLevel, error)
ParseStackLevel maps an empty value to project and rejects unknown ones.
type StackLock ¶
type StackLock struct {
StackID string `json:"stack_id"`
Holder string `json:"holder"`
Revision int `json:"revision"`
ExpiresAt time.Time `json:"expires_at"`
}
StackLock is the per-stack lease. Its length comes from APP_STACK_LOCK_TTL.
type StackScope ¶
StackScope is the apply context a stack handler attaches to loopback sub-requests. IAM trusts only this server-side value, never a header.
func StackScopeFrom ¶
func StackScopeFrom(ctx context.Context) (StackScope, bool)
StackScopeFrom returns the stack apply scope, if the request runs inside one.
type StackSpec ¶
type StackSpec struct {
Scope StackLevel `json:"scope,omitempty" yaml:"scope,omitempty"`
Project string `json:"project,omitempty" yaml:"project,omitempty"`
Environment string `json:"environment,omitempty" yaml:"environment,omitempty"`
Prune bool `json:"prune,omitempty" yaml:"prune,omitempty"`
Drift DriftPolicy `json:"drift,omitempty" yaml:"drift,omitempty"`
}
StackSpec is the spec of a kind: Stack document.
type State ¶
type State struct {
// contains filtered or unexported fields
}
State is the live control plane data a plan needs, read through the API with the caller's own permissions. A read the caller may not perform is recorded in denied instead of failing the whole plan.
func (*State) ManagedApps ¶
ManagedApps lists the apps this apply owns: the ledger's apps for a stack, otherwise those carrying the source's managed-by tag.
type StatusError ¶
StatusError is a non-2xx API answer.
func (*StatusError) Error ¶
func (e *StatusError) Error() string