Documentation
¶
Overview ¶
Package runtime is the execution engine linked into every compiled factory binary.
Owns:
- DAG construction (implicit deps via reference; explicit via @depends-on)
- Plan computation: refresh + drift + change detection + replace-because chains
- Apply execution: parallelism cap (default 10), per-resource state writes, apply error UX
- State model (snapshots, content-addressed, encrypted at rest)
- Action semantics (triggered with @trigger; 'always' literal; @lock; @timeout)
Companion packages:
- pkg/sdk/state - Backend contract that provider libraries implement
- pkg/state/local and pkg/state/s3 - the filesystem and S3 backends
- pkg/runner - the factory CLI that invokes runtime entry points
Index ¶
- Constants
- Variables
- func ApplyBindings(ctx *EvalContext, binds []lang.EachBinding)
- func Changed[T any](prior, current T) bool
- func CoreFunctionSigs() map[string]typecheck.FuncSig
- func Decode(v any, inputs map[string]any) error
- func DirectParent(addr string) string
- func DotPathString(p *lang.DotPath) string
- func EncodePlan(p *Plan) ([]byte, error)
- func Eval(e lang.Expr, ctx *EvalContext) (any, error)
- func InputNames(f *lang.File) map[string]bool
- func InternalConfigurationNames(f *lang.File) map[string]map[string]bool
- func RefAddress(p *lang.DotPath) string
- func Refs(e lang.Expr) []string
- func ScopeRef(ref, callSite string) string
- func SealPlan(p *Plan, enc encrypt.Encrypter) ([]byte, error)
- func SplitInstanceAddress(addr string) (template, key string)
- type ActionRegistration
- type ApplyError
- type ApplyEvent
- type ApplyStage
- type CompositeType
- type ConfigRef
- type DAG
- type DataSourceRegistration
- type Decision
- type EvalContext
- type ExecResult
- type Executor
- type FactoryRef
- type FunctionType
- type Library
- type LibrarySchema
- type MigrationState
- type Migrator
- type Node
- type NodeKind
- type PanicError
- type PendingValue
- type Plan
- type PlanFile
- type PlanStep
- type Prior
- type RefreshResult
- type ResourceRegistration
- type StateRef
- type StepNode
- type TriggerDecision
- type TypeSchema
- type TypedAction
- type TypedDataSource
- type TypedResource
Constants ¶
const DefaultParallelism = 10
DefaultParallelism is the in-flight cap apply uses when no explicit value is given on the Executor or in the plan file.
const PlanFormatVersion = 1
PlanFormatVersion is the schema version this package reads and writes for plan files.
const TriggerAlways = "always"
TriggerAlways is the literal an action uses to opt into running every time, regardless of stored state.
Variables ¶
var ErrEvalNotFound = errors.New("not found")
ErrEvalNotFound is returned by Eval when an address or field cannot be resolved in the current scope. Plan callers may treat it as "known after apply"; apply re-evaluates against the live scope and surfaces a real failure when the reference truly is invalid.
var ErrInstanceGone = errors.New("instance no longer in iterable")
ErrInstanceGone is returned by ensureCompositeScope when a per- instance composite scope is requested for a key that the boundary's `@for-each` iterable no longer yields. Plan-time seeding of prior state treats this as a signal to skip rather than fail; orphan destroy steps for the missing instance still emit through the usual orphan path.
var ErrInterrupted = errors.New("apply: interrupted")
ErrInterrupted is returned by ApplyPlan when the executor's Drain channel was closed before all steps could be dispatched. The returned snapshot still reflects every step that completed before the drain, so re-plan plus apply will pick up the remainder.
var ErrNotFound = errors.New("resource not found")
ErrNotFound is returned by a resource's Read method when the resource is absent in the cloud. The runtime treats it as a request to recreate.
Functions ¶
func ApplyBindings ¶ added in v0.6.0
func ApplyBindings(ctx *EvalContext, binds []lang.EachBinding)
ApplyBindings copies a constraint's iteration bindings onto the context so @each and any chained level name resolve during the element's evaluation.
func Changed ¶
Changed reports whether a field differs between its prior and current value. It compares by value, so a pointer field compares what it points at, and a state round trip that re-decodes an equal value is not a false positive.
func CoreFunctionSigs ¶ added in v0.6.0
CoreFunctionSigs returns each @core function's signature, keyed by name, for compile-time existence, arity, and type checking.
func Decode ¶
Decode fills v's exported fields from the inputs map using `ub` struct tags. A field's key is the tag's name, or the kebab-cased field name when the tag has no name (or no tag at all). String values like "30s" decode into time.Duration fields. v must be a non-nil pointer to a struct.
func DirectParent ¶
DirectParent returns the substring before the last `/` in addr, or the empty string when addr has no `/`. Unlike templateAddress, DirectParent preserves `['key']` segments so the result names a per-instance composite call site when one is present.
func DotPathString ¶ added in v0.6.0
dotPathString renders a dotted reference back to its source form. Named segments are joined with `.`; indexed segments preserve the `['<key>']` form when the index is a string literal, and otherwise collapse to `[...]` so the path stays readable.
func EncodePlan ¶
EncodePlan renders a plan as JSON bytes for on-disk storage.
func Eval ¶
func Eval(e lang.Expr, ctx *EvalContext) (any, error)
Eval reduces a parsed expression to a Go value. Supported are literals, bare identifiers (as their name string); array and object literals (recursive); and the `var.X[.Y...]` address form.
func InputNames ¶ added in v0.6.0
InputNames returns the set of input names a file declares.
func InternalConfigurationNames ¶ added in v0.6.0
InternalConfigurationNames returns the configuration names a factory defines internally, keyed by import alias. The runner consults it when loading config.ub so an operator entry cannot collide with a name the factory owns.
func RefAddress ¶ added in v0.6.0
func Refs ¶
Refs returns the addresses an expression depends on, in source order with duplicates removed. Each returned address is the canonical form of another node:
var.<name> resource.<alias>.<type>.<name> data.<alias>.<type>.<name> action.<alias>.<type>.<name>
Field segments past the node address (`.id`, `['key'].arn`) and `@each.X` bindings are skipped.
func ScopeRef ¶ added in v0.6.0
scopeRef rewrites a reference into a composite internal address. `resource.aws.vpc.this` under call site `resource.net.cluster.web` becomes `resource.net.cluster.web/resource.aws.vpc.this`; every segment keeps its own kind root, so resource, data, and action refs all join the same way, matching what `composeAddress` produces. Var refs and unsupported kinds pass through unchanged so toposort skips them. An empty callSite means the ref is already in its target scope (a top-level boundary's body refs, or a no-op when walking up past the outermost scope) and the ref returns unchanged.
func SealPlan ¶
SealPlan encodes p and seals the body in the shared state.Envelope, ready for atomic write. The envelope records the encrypter's own description, whether the operator wrote an encryption block or the resolver chose a default.
func SplitInstanceAddress ¶
SplitInstanceAddress separates a `<template>['<key>']` address into its template part and the instance key. Non-instance addresses return unchanged with an empty key.
Types ¶
type ActionRegistration ¶
type ActionRegistration interface {
NewReceiver() any
Run(ctx context.Context, receiver, cfg any) (any, error)
OutputType() reflect.Type
}
ActionRegistration is the type-erased registration for actions.
func MakeAction ¶
func MakeAction[T any, Out any, PT actionPtr[T, Out]]() ActionRegistration
MakeAction produces an ActionRegistration that wraps a TypedAction[Out] implemented by *T.
func MakeActionWith ¶
func MakeActionWith[T any, Out any, PT actionPtr[T, Out]]( construct func() *T, ) ActionRegistration
MakeActionWith is the variant of MakeAction that captures external state through the constructor.
type ApplyError ¶
type ApplyError struct {
Address string
Kind NodeKind
Decision Decision
Library string
Elapsed time.Duration
Err error
SkippedCount int
SucceededCount int
}
ApplyError is the structured failure value runApplySchedule returns when a step's CRUD or action call reports an error. The original error is available via Unwrap so callers can use errors.Is and errors.As; the runner uses the structured fields to print a multi line report that names the failing address, its decision and library, the elapsed time, and the counts of steps that were skipped or completed alongside it.
func (*ApplyError) Error ¶
func (e *ApplyError) Error() string
func (*ApplyError) Unwrap ¶
func (e *ApplyError) Unwrap() error
type ApplyEvent ¶
type ApplyEvent struct {
Address string
Kind NodeKind
// Composite marks an event for a composite call site (a boundary).
// A boundary's Kind is its own resource/data/action kind, so this
// is what tells a boundary apart from a leaf of that kind.
Composite bool
Decision Decision
Stage ApplyStage
Time time.Time
Elapsed time.Duration
Err error
}
ApplyEvent is one observation the scheduler hands to the optional Executor.Events channel during a run. The renderer in the runner consumes these to print live per-step progress on stderr or to emit one JSON object per event under --json.
type ApplyStage ¶
type ApplyStage string
ApplyStage tags one moment in a step's apply lifecycle.
const ( // StageStart fires when the scheduler hands the step to a worker. StageStart ApplyStage = "start" // StageDone fires when the worker reports a successful result. StageDone ApplyStage = "done" // StageFail fires when the worker returns an error. Apply will // halt further dispatch but already-running siblings still emit // their own done or fail events. StageFail ApplyStage = "fail" )
type CompositeType ¶
type CompositeType struct {
Name string
Kind NodeKind
Body *lang.File
Libraries map[string]*Library
}
CompositeType registers a UB-implemented type under a library. Body is the parsed body file for the composite (same shape as a stack minus `configurations:`). The runtime expands a call site into sub-DAG nodes by walking Body's `resources:`, `actions:`, and `data:` blocks under the call site's address prefix.
Libraries is the resolved import table for this composite's body, keyed by the alias declared in the body's `imports:` block. The runtime looks up composite-internal nodes against this table, not the stack root's, so a composite can be reused without the caller importing every library it transitively uses. A nil Libraries falls back to the executor's root Libraries table for backward compatibility with composites built directly in tests.
type ConfigRef ¶
ConfigRef names a particular configuration on an import. `@configuration: aws.east2` parses to {Alias: "aws", Configuration: "east2"}.
type DAG ¶
DAG is a stack's runtime dependency graph: every addressable node indexed by its address, and the list of node addresses each one depends on, collected from references in the body and from any `@depends-on` meta key.
func BuildDAG ¶
BuildDAG walks a parsed stack file and returns its dependency graph. The file is assumed to be validated. libs is the imported-library table; passed to ExtractNodes so composite call sites are expanded before edges are computed.
func (*DAG) ConfigurationSelections ¶ added in v0.6.0
ConfigurationSelections returns every configuration selection the graph's leaves resolve to, explicit or implicit default, keyed by the resolved alias and limited to libraries that declare a configuration. The factory's help output uses it to say which names an operator must supply.
func (*DAG) TopologicalOrder ¶
TopologicalOrder returns the DAG's nodes in dependency order: every node appears after the nodes it references. Edges to non-node addresses such as `var.X` are skipped, since vars are bound from inputs and not block execution. Returns an error naming the involved addresses when the graph contains a cycle.
func (*DAG) UnderForEachComposite ¶ added in v0.6.0
UnderForEachComposite reports whether any composite call site in n's ancestry is itself a `@for-each` template.
type DataSourceRegistration ¶
type DataSourceRegistration interface {
NewReceiver() any
Read(ctx context.Context, receiver, cfg any) (any, error)
OutputType() reflect.Type
}
DataSourceRegistration is the type-erased registration for data sources.
func MakeDataSource ¶
func MakeDataSource[T any, Out any, PT dataSourcePtr[T, Out]]() DataSourceRegistration
MakeDataSource produces a DataSourceRegistration that wraps a TypedDataSource[Out] implemented by *T.
func MakeDataSourceWith ¶
func MakeDataSourceWith[T any, Out any, PT dataSourcePtr[T, Out]]( construct func() *T, ) DataSourceRegistration
MakeDataSourceWith is the variant of MakeDataSource that captures external state through the constructor.
type Decision ¶
type Decision string
Decision tags one node's planned action.
const ( DecisionCreate Decision = "create" DecisionUpdate Decision = "update" DecisionReplace Decision = "replace" DecisionDestroy Decision = "destroy" DecisionNoOp Decision = "no-op" DecisionRerun Decision = "rerun" DecisionSkip Decision = "skip" DecisionRead Decision = "read" DecisionEval Decision = "eval" )
type EvalContext ¶
type EvalContext struct {
Vars map[string]any
Resources map[string]any
Data map[string]any
Actions map[string]any
Libraries map[string]*Library
Bindings map[string]any
// Each holds named iteration bindings, @each for a @for-each body
// and declared names like @rule for a chained constraint form,
// each a key/value record read as @name.key and @name.value.
Each map[string]lang.EachValue
// MissingAsNull makes path navigation yield null instead of
// ErrEvalNotFound, or a hard error, when a key is absent or a parent
// is itself null. The constraint checkers set it so a predicate over
// an unset optional input, including a nested one, reduces to a
// boolean rather than collapsing the whole expression to null or
// failing on a null parent. Navigating into a non-null scalar is
// still an error. It stays false everywhere else, because the planner
// relies on ErrEvalNotFound to detect forward references to upstreams
// that have not run yet.
MissingAsNull bool
// contains filtered or unexported fields
}
EvalContext supplies the values that addresses resolve against. Vars is the validated `inputs:` map after `config.ub` and `UB_VAR_*` env overrides. Resources, Data, and Actions hold the outputs of nodes that have already executed, indexed by their source address path. Libraries is the import table the scope's `<alias>.<func>(...)` calls resolve against; nil disables library-qualified calls. Bindings holds comprehension-bound names, which resolve as bare values and as dot-path roots ahead of the reserved roots; validation keeps the names distinct across nesting.
func NewEvalContext ¶ added in v0.7.0
func NewEvalContext(f *lang.File) *EvalContext
NewEvalContext returns an EvalContext whose local.<name> references resolve against f's locals: block. Callers fill the remaining fields for their scope. A nil file, or one without locals, yields a context where any local reference reports the local as not declared.
type ExecResult ¶
type ExecResult struct {
Outputs map[string]any
Actions map[string]any
Data map[string]any
WrittenRev string
}
ExecResult is what the Executor produces: the outputs map, the Action and Data tables populated during the run, and the rev of the snapshot written (empty when no Store was configured).
type Executor ¶
type Executor struct {
DAG *DAG
Libraries map[string]*Library
Inputs map[string]any
// Source is the parsed stack file. Static analysis passes (e.g.
// sensitivity propagation at plan time) consult its top-level
// blocks for declarations the DAG alone does not carry. May be
// nil in test setups; analyses that need it degrade to no-op.
Source *lang.File
// Configurations is keyed first by the library's import alias and
// then by the configuration alias declared in config.ub. Entries
// are the value returned by cfg.ConfigurationType.New populated
// by cfg.Decode. A nil map disables config routing and every CRUD
// call sees a nil cfg argument.
Configurations map[string]map[string]any
Store state.Backend
Factory state.FactoryInfo
// Parallelism caps the number of in-flight resource, data, and
// action steps during ApplyPlan. Zero or negative falls back to
// DefaultParallelism.
Parallelism int
// Destroy makes Plan compute a teardown: every resource in prior
// state is planned for destroy and no outputs are evaluated. The
// source is still parsed and its configurations still resolve, so
// the deletes use the right credentials.
Destroy bool
// Drain, when non-nil, lets the caller ask the scheduler to stop
// dispatching new steps without canceling the apply context. The
// runner closes this channel on SIGINT so in-flight CRUD calls
// finish and their state writes commit; SIGTERM cancels the
// context directly. A nil channel disables the drain signal.
Drain <-chan struct{}
// Events, when non-nil, receives one ApplyEvent per step stage
// during ApplyPlan: start when the scheduler hands the step to a
// worker, done or fail when the worker returns. The caller owns
// the channel and is responsible for sizing the buffer and
// closing it after ApplyPlan returns. A nil channel disables
// event emission.
Events chan<- ApplyEvent
// contains filtered or unexported fields
}
Executor wires together the parsed DAG, the imported libraries, the caller's inputs, and a state backend. It exposes three lifecycle methods: Plan computes a PlanStep slice against prior state without running any CRUD, ApplyPlan executes a previously computed plan, and Refresh reads each prior-state resource and writes back observed outputs. Store and Stack must always be set.
func (*Executor) ApplyPlan ¶
ApplyPlan executes a previously computed PlanFile against the Executor's libraries, store, and parsed source. The DAG passed on the Executor is used for output expressions, while resource and action bodies come from the plan. The plan's stack identity must match the Executor's, and the prior state's rev must match what the plan was computed against. The stack's lock is held for the duration.
func (*Executor) CheckConfigurations ¶ added in v0.6.0
CheckConfigurations walks the DAG and reports every `@configuration:` or `@configurations:` reference that cannot resolve against the Executor's decoded configurations. It is called at the start of Plan and Refresh so a typo or stale reference fails fast instead of reaching a CRUD call with a nil cfg.
func (*Executor) Plan ¶
Plan walks the DAG against prior state and returns the planned actions per node. Resources with prior state get their Read method invoked so the plan can report drift; no CRUD methods (Create, Update, Replace, Delete) are called and no actions run. Inputs that reference outputs of nodes about to run are evaluated against the prior state where available.
func (*Executor) Refresh ¶
func (e *Executor) Refresh(ctx context.Context) (*RefreshResult, error)
Refresh reads every resource recorded in prior state and writes a fresh snapshot whose leaf outputs reflect the observation. Resources that are no longer present are dropped. Action and library-call entries, plus stack-level outputs, carry forward unchanged. No resource writes happen. The stack's lock is held for the duration.
type FactoryRef ¶
type FactoryRef struct {
Name string `json:"name"`
Version string `json:"version"`
ContentRevision string `json:"content-revision"`
}
FactoryRef identifies the stack a plan was computed against.
type FunctionType ¶
type FunctionType struct {
Name string
Description string
ArgCount int
Variadic bool
Func func(args []any) (any, error)
}
FunctionType registers a callable function under a Go library. Functions take pre-evaluated argument values and return a single value or an error. They run inline during expression evaluation and have no DAG node or state of their own.
ArgCount and Variadic declare the argument count the compiler enforces at each call site: a non-variadic function takes exactly ArgCount arguments, a variadic one takes ArgCount or more. A call's argument count is fixed in the source, so this check is compile-time only and the function body may assume it already holds.
func MakeFunc ¶ added in v0.6.0
func MakeFunc(name, description string, fn any) FunctionType
MakeFunc adapts a plain typed Go function into a FunctionType, the way text/template adapts a FuncMap entry. fn may take any number of parameters, variadic included, and must return (value, error); each parameter must be a type the evaluator's values convert to exactly: bool, int64, float64, string, any, or a slice or string-keyed map of those. Evaluated arguments convert to the parameter types on the way in and the result converts back to evaluator values on the way out, so the implementation stays plain typed Go and the argument count cannot disagree with the declaration.
MakeFunc panics on a fn that does not fit. Registration runs when a factory binary starts and when a library's own tests construct it; the compiler rejects the same signatures from source, so a malformed registration fails the importing factory's compile first.
type Library ¶
type Library struct {
Name string
Description string
Configuration *cfg.ConfigurationType
Actions map[string]ActionRegistration
Resources map[string]ResourceRegistration
DataSources map[string]DataSourceRegistration
// Composites are kept in one map per kind, mirroring the
// Resources / DataSources / Actions Go-type maps. resource, data,
// and action are distinct namespaces, so a library may declare
// resource.foo and data.foo as separate composites.
ResourceComposites map[string]*CompositeType
DataComposites map[string]*CompositeType
ActionComposites map[string]*CompositeType
Functions map[string]FunctionType
// Schema carries the library's resource, data source, and action
// output field sets. Populated by the dev CLI from a fetched Go
// library's source for compile-time reference checking; nil at
// runtime since the stack binary does not need it.
Schema *LibrarySchema
// Constraints holds each Go type's cross-field constraints in the
// embeddable spec form, keyed by "<kind>.<type>" (e.g. "resource.vpc")
// since resource, data, and action are distinct namespaces. codegen
// sets it in the generated main.go from the constraints goschema
// derived from the library's source; the plan checks a node against
// Constraints[node.Kind + "." + node.Type]. UB composites carry their
// own constraints in their bodies, so this stays empty for UB libraries.
Constraints map[string][]lang.ConstraintSpec
// Defaults holds each Go type's declared input defaults, keyed by
// "<kind>.<type>" like Constraints and set the same way by codegen.
// The runtime fills a Value default into a body's inputs wherever a
// field is left out or null, before constraints, triggers, and
// decode read them; an Optional marker fills nothing.
Defaults map[string][]lang.DefaultSpec
}
Library is the registration record a library exports for its types, actions, and data sources. Go libraries supply Resources, Actions, and DataSources via the generic helpers (`MakeResource` and friends); UB libraries - compiled to Go packages by `unobin compile` - contribute Composites whose bodies are parsed AST literals built into the binary. The compiler links each imported library's record and aggregates them under the alias the calling source assigned to the import.
func (*Library) AddComposite ¶
func (l *Library) AddComposite(ct *CompositeType)
AddComposite stores ct in the map for its kind, creating the map on first use.
type LibrarySchema ¶
type LibrarySchema struct {
Resources map[string]*TypeSchema
DataSources map[string]*TypeSchema
Actions map[string]*TypeSchema
// Functions maps each function name the library exports to its
// declared signature. The implementations live in the compiled Go
// and cannot run at compile time, but the signature lets the
// reference checker reject a call to an unknown function or one
// given the wrong number of arguments, and the inferrer check each
// argument's type and use the result type. A function registered
// without declared types reads as all-Unknown, which counts
// arguments but checks no types.
Functions map[string]typecheck.FuncSig
// Configuration describes the fields of the library's Configuration
// struct, keyed by kebab-case field name. Nil when the library
// declares no configuration or when the struct behind the
// ConfigurationType's New function cannot be read from source.
// HasConfiguration distinguishes the two: it is true whenever the
// library declares a configuration, readable or not, so checks can
// tell "no configuration" from "fields unknowable".
Configuration map[string]typecheck.Type
HasConfiguration bool
}
LibrarySchema describes a Go library's registered resource, data source, and action types as the dev CLI sees them at compile time. Each entry is keyed by the type's kebab-case name (the same name used in stack source).
func (*LibrarySchema) ForType ¶ added in v0.6.0
func (s *LibrarySchema) ForType(kind NodeKind, typ string) *TypeSchema
ForType returns the schema for a node kind's type, or nil when the kind is not a resource, data, or action or the type is absent.
type MigrationState ¶ added in v0.6.0
MigrationState is the pair of persisted maps a Migrator upgrades: the inputs the body evaluated to on the last apply and the outputs the resource returned then. Observed (see Prior) is plan-time only and is never persisted, so a migration covers inputs and outputs alone.
type Migrator ¶
type Migrator interface {
Migrate(oldVersion int, prior MigrationState) (MigrationState, error)
}
Migrator is an optional add-on a TypedResource may implement when its SchemaVersion has incremented past 1 and an older state entry needs upgrading. Migrate receives the whole recorded entry at the version it was written and returns it at the current version. Upgrading both halves together keeps the entry's single SchemaVersion stamp correct: were only the outputs upgraded, the entry would be stamped current while its inputs stayed at the old version, and a later input migration would never run.
type Node ¶
type Node struct {
Address string
Kind NodeKind
Alias string
Type string
Name string
Body lang.Expr
Composite string
CompositeBody *lang.File
Libraries map[string]*Library
ForEach lang.Expr
// Configuration names the configuration selected under the node's
// import (Alias) that the runtime hands to CRUD calls. Empty falls
// back to "default" at lookup time.
Configuration string
// LockName is the value of a node body's `@lock:` field. Two nodes
// sharing a non-empty LockName cannot run in parallel under apply's
// scheduler, even on unrelated DAG branches. Empty means the node is
// not under a named lock. It applies to any kind; since the
// scheduler only runs nodes in parallel at apply, a lock has no
// effect on a data source whose inputs are known and read at plan.
LockName string
// Timeout is the parsed value of a node body's `@timeout:` field: a
// limit on how long the node's apply step may run. Zero means no
// limit. On expiry the step's context is cancelled and the step
// fails like any other apply error. Like @lock it only bites at
// apply, so it does not bound a data source read at plan.
Timeout time.Duration
// ConfigurationsRemap is set only on a composite boundary. It maps an
// inner import alias to the (alias, configuration) of the
// configuration that backs that import inside the call. The
// runtime walks the composite chain at lookup time and the
// validator enforces that the right-hand-side alias matches
// the key.
ConfigurationsRemap map[string]ConfigRef
}
Node is one addressable element of a stack: a single resource instance, data source, action, output, or composite call site. Address is the canonical dotted form the language uses to reference the node from elsewhere such as `resource.aws.vpc.main` or `output.cluster-arn`. Body is the source expression: an ObjectLit for resources/data/actions/ composites, any Expr for outputs.
A node inside a composite carries the call site address in Composite so the runtime evaluates its body against the composite's scope rather than the root. Its address looks like `resource.<call site>/<alias>.<type>.<name>`, with the call site as a prefix joined by a single `/`. For a composite that itself calls another composite the chain continues: `resource.<outer>/<inner-rel>/<deepest-rel>`, and each node's Composite names its direct enclosing call site.
CompositeBody and Libraries are set only on a composite boundary (the call site node), and IsComposite reports that case. CompositeBody points to the composite type's full body so the runtime can evaluate the `outputs:` block once the internals complete. Libraries is the composite's resolved import table; the runtime resolves composite-internal node lookups against this map rather than the stack root's, so a composite can be reused without the caller importing every library it transitively uses.
func ExtractNodes ¶
ExtractNodes walks a parsed stack or exported-type file and returns every addressable node in source order. The file's shape is assumed to be validated. Malformed subtrees are skipped silently rather than reported as they should be validated with `lang.ValidateFile` first.
libs is the imported-library table keyed by alias. It is consulted to distinguish primitive resource call sites from composite call sites; composites expand into a boundary node plus internal nodes. A nil or empty libs skips the composite check, in which case every node in `resources:` is treated as a primitive.
func (*Node) IsComposite ¶
IsComposite reports whether the node is a composite call site (a boundary) rather than a primitive leaf. A boundary has its own Kind (the call site's resource/data/action kind) just like a leaf; what sets it apart is the CompositeBody it expands, which extractKind populates only on boundaries.
type PanicError ¶ added in v0.6.0
PanicError reports that code the runtime called panicked. Every call into a library - a resource, action, data source, or function - is recovered at the boundary, so a defect there fails the step like any other error instead of crashing the process. A panic in unobin's own @core functions is recovered the same way but attributed to unobin rather than to a library.
Op names what was running when the panic happened. Library is the import alias to blame, filled in where the failing node is known and left empty when the runtime cannot place it. Value is whatever was passed to panic. Stack is the goroutine stack captured at the moment of recovery, kept for a verbose report.
func (*PanicError) Error ¶ added in v0.6.0
func (e *PanicError) Error() string
type PendingValue ¶ added in v0.6.0
type PendingValue struct {
Refs []string
}
PendingValue marks a position in a plan step's recorded inputs where a forward reference kept the value from resolving at plan time. It holds the upstream source addresses the expression reads, so the plan renderer shows `<resource.X.field>` at the value's real position, including inside a list or object, rather than collapsing the whole field to one marker. It is recorded for display only: withoutPending resets every unresolved field for the plan walk's decisions, and knownFields removes it before seeding or the apply premise check, so a PendingValue never reaches a resource.
type Plan ¶
type Plan struct {
Factory state.FactoryInfo
Stack string
StateRev string
Inputs map[string]any
RawConfigurations map[string]map[string]any
Steps []*PlanStep
// Backend names the state backend the plan was computed against,
// so apply can reconstruct the same backend without re-reading
// config.ub. A nil value means the resolver's default (the local
// backend under .unobin/state) was used.
Backend *StateRef
// Parallelism is the in-flight cap apply should honor. Zero means
// the runtime's DefaultParallelism applies.
Parallelism int
// Destroy marks a teardown plan: every step is a destroy and apply
// evaluates no outputs.
Destroy bool
}
Plan is the readonly result of computing what an apply would do. StateRev is the snapshot rev the plan was computed against. Apply rejects the plan when the current rev no longer matches. Inputs captures the validated root inputs so apply can rebuild the same eval scope without re-reading config.ub. RawConfigurations carries the raw per-library configuration maps (keyed by import alias then alias name) so apply re-decodes them through the same code path rather than re-reading the config file.
type PlanFile ¶
type PlanFile struct {
FormatVersion int `json:"format-version"`
Factory FactoryRef `json:"factory"`
Stack string `json:"stack"`
StateRev string `json:"state-rev"`
GeneratedAt time.Time `json:"generated-at"`
Inputs map[string]any `json:"inputs,omitempty"`
RawConfigurations map[string]map[string]any `json:"configurations,omitempty"`
Backend *StateRef `json:"backend,omitempty"`
Parallelism int `json:"parallelism,omitempty"`
Destroy bool `json:"destroy,omitempty"`
Steps []PlanStep `json:"steps"`
}
PlanFile is the on-disk shape of a plan. Steps in the file mirror the in-memory `Plan.Steps` but use `json` tags for stable serialization. Inputs carries the validated root inputs the plan was computed against, so apply can seed them into its eval scope without re-reading config.ub.
func DecodePlan ¶
DecodePlan parses a plan file from JSON bytes. JSON has no native distinction between int and float, so the decoder reads numbers as json.Number and coerceNumbers walks the result, restoring int64 for values whose text has no decimal point or exponent and float64 for everything else. This preserves the typing that Plan emitted: a re-evaluation at apply time gets int64 back where the schema said integer.
func OpenPlan ¶
func OpenPlan( b []byte, resolveEnc func(*StateRef) (encrypt.Encrypter, error), ) (*PlanFile, error)
OpenPlan opens a sealed plan envelope and returns its inner PlanFile. resolveEnc builds an encrypter from the envelope's ref; pass a function that consults the caller's resolver chain. resolveEnc receives nil when the envelope has no encrypter ref. A plan file may name its own encrypter because the decrypted content is exactly what apply executes, so the ref grants the file no authority its content does not already have.
type PlanStep ¶
type PlanStep struct {
Address string `json:"address"`
Kind NodeKind `json:"kind"`
// Composite marks a step whose apply finalizes a composite call
// site (a boundary) rather than a primitive leaf. A boundary's Kind
// is its own resource/data/action kind, so the runtime cannot
// tell it from a leaf by Kind alone; on a Node that distinction is
// the expanded CompositeBody (Node.IsComposite), but a step has no
// body, so the bit is stored explicitly in the plan file.
Composite bool `json:"composite,omitempty"`
Decision Decision `json:"decision"`
Inputs map[string]any `json:"inputs,omitempty"`
UnresolvedInputs map[string][]string `json:"unresolved-inputs,omitempty"`
// DeferredRead names the configuration selection (alias.name form)
// whose pending evaluation kept this node's read from running at
// plan. The stored state is taken as current for the decision;
// apply and the next plan see real values.
DeferredRead string `json:"deferred-read,omitempty"`
// PriorInputs is the body the last apply evaluated, recorded so the plan
// can show a changed field as `old -> new` rather than the new value
// alone. Nil for a create, where there is no prior to compare against.
PriorInputs map[string]any `json:"prior-inputs,omitempty"`
PriorOutputs map[string]any `json:"prior-outputs,omitempty"`
ObservedOutputs map[string]any `json:"observed-outputs,omitempty"`
TriggerHash string `json:"trigger-hash,omitempty"`
// ReplaceTriggers names the replace-forcing fields whose value changed,
// the reason a step replaces rather than updates. The renderer tags each
// with `(forces replacement)`. Empty unless Decision is replace.
ReplaceTriggers []string `json:"replace-triggers,omitempty"`
// Configuration is the step's library configuration ref
// ("<alias>.<configuration>"). A destroy step records it from prior
// state, so apply deletes against the same credentials the resource
// was created with. A live step records it only when the selection
// names an internal configuration still pending this plan, which is
// how the renderer knows to show the selection on the step.
Configuration string `json:"configuration,omitempty"`
// DependsOn carries a destroy step's recorded dependencies from
// prior state. Apply reverses these edges so a resource is deleted
// before the resources it depended on.
DependsOn []string `json:"depends-on,omitempty"`
// AlreadyGone marks a destroy step whose resource was already
// absent when the plan read it. Apply drops the entry from state
// without calling Delete.
AlreadyGone bool `json:"already-gone,omitempty"`
// SensitiveInputs names the input fields whose value expression
// reads from any sensitive source. Renderers replace the value
// with a placeholder rather than printing the secret.
SensitiveInputs []string `json:"sensitive-inputs,omitempty"`
// SensitiveOutputs names the output fields of this step that are
// sensitive. For a primitive resource/action it comes from the
// library schema's tagged fields; for a composite call site it
// comes from the composite type's `@sensitive` markers and from
// propagation through its body.
SensitiveOutputs []string `json:"sensitive-outputs,omitempty"`
// contains filtered or unexported fields
}
PlanStep records one node's planned action. For resources, Inputs is the evaluated body. PriorOutputs is what state holds (nil for create or destroy of a resource that is not found). ObservedOutputs is what Resource.Read returned at plan time; it differs from PriorOutputs when the resource has drifted out of band. For actions, TriggerHash is the hash that determines whether to rerun or skip.
UnresolvedInputs names the input fields whose plan-time evaluation hit a forward reference (an upstream node with no prior state). Each entry maps the field name to the source-side dot paths the body reads from. Apply re-evaluates these against the live scope.
type Prior ¶
type Prior[In, Out any] struct { Inputs In Outputs Out Observed Out }
Prior is everything known before Update acts: the inputs the body evaluated to on the last apply, the outputs the resource returned then, and the reality a plan-time Read last saw. Compare current inputs against Inputs with Changed to decide what to reconcile, read Outputs for the prior handle (an id, an arn) the update acts against, and read Observed to patch from current reality rather than the recorded result when the two have drifted apart.
Inputs is the input struct by value, so it is never nil and needs no guard; a prior whose recorded inputs no longer decode into the current struct degrades to the zero value, which reads as "every field changed". Outputs and Observed keep the same pointer type the rest of the contract uses, so a resource with no recorded result or no plan-time read still passes nil.
Observed is plan-time, not apply-time: apply does not re-Read before Update, so between plan and apply reality can move further. A resource that needs apply-time truth must Read itself.
type RefreshResult ¶
RefreshResult reports what Refresh did. Refreshed counts leaf entries whose outputs were updated to match what was observed; Dropped counts leaves whose Read returned ErrNotFound and were removed from state.
type ResourceRegistration ¶
type ResourceRegistration interface {
SchemaVersion() int
Migrate(oldVersion int, prior MigrationState) (MigrationState, error)
NewReceiver() any
Create(ctx context.Context, receiver, cfg any) (any, error)
Read(ctx context.Context, receiver, cfg, prior any) (any, error)
Update(ctx context.Context, receiver, cfg, priorInputs, priorOutputs, observed any) (any, error)
Delete(ctx context.Context, receiver, cfg, prior any) error
ReplaceFields(receiver any) []string
OutputType() reflect.Type
}
ResourceRegistration is the type-erased registration the runtime's resource map holds. A library author produces one via MakeResource; the runtime calls the methods on it to dispatch CRUD work without caring about the typed Out parameter.
func MakeResource ¶
func MakeResource[T any, Out any, PT resourcePtr[T, Out]]() ResourceRegistration
MakeResource produces a ResourceRegistration that wraps a TypedResource[Out] implemented by *T. Use as `runtime.MakeResource[Vpc, *VpcOutput]()`. Each receiver is zero-constructed via new(T) when the runtime asks for one.
func MakeResourceWith ¶
func MakeResourceWith[T any, Out any, PT resourcePtr[T, Out]]( construct func() *T, ) ResourceRegistration
MakeResourceWith is the variant of MakeResource for callers that need each receiver to capture external state. The constructor runs once per instance the runtime needs; Decode then fills it from the inputs.
type StateRef ¶
StateRef is an alias for state.Ref, the resolver reference that both the plan envelope and the state-snapshot envelope use to name a backend or encrypter. The type lives in pkg/sdk/state so a state backend can use it without importing runtime; the alias keeps the runtime spelling for the plan body and the resolver code.
type StepNode ¶ added in v0.7.0
type StepNode struct {
Address string `json:"address"`
Kind NodeKind `json:"kind"`
Composite bool `json:"composite,omitempty"`
Decision Decision `json:"decision"`
DependsOn []string `json:"depends-on,omitempty"`
}
StepNode is one plan step in the instance-form dependency graph PlanGraph returns: the step's identity plus the addresses it waits for under apply's scheduler.
func PlanGraph ¶ added in v0.7.0
PlanGraph returns the dependency graph apply schedules pf with: one node per plan step, in plan order, each listing the instance-form addresses it waits for. The edges are the scheduler's own, so a destroy step already waits on the reversal of its recorded dependencies. Each node's DependsOn is sorted.
type TriggerDecision ¶
TriggerDecision is what the runtime computes for an action node before dispatching it: a hash to compare against state and a flag for the always-rerun escape hatch.
func ComputeTrigger ¶
func ComputeTrigger(n *Node, inputs map[string]any, ec *EvalContext) (TriggerDecision, error)
ComputeTrigger evaluates the action node's `@trigger` meta key (if any) and returns the resulting decision. Without `@trigger`, the hash is taken over the action's evaluated inputs so any visible change to the body causes a rerun. With `@trigger: 'always'`, AlwaysRerun is true and the hash is empty.
type TypeSchema ¶
type TypeSchema struct {
Inputs map[string]typecheck.Type
Outputs map[string]typecheck.Type
SensitiveInputs []string
SensitiveOutputs []string
// Constraints holds the type's cross-field constraints, derived from
// its Constraints method at compile time, in the embeddable string
// form. A check parses them with lang.ParseSpecs and runs them through
// lang.CheckConstraintEntries, the same path UB constraints take.
Constraints []lang.ConstraintSpec
// Defaults holds the type's declared input defaults, derived from
// its Defaults method at compile time. A field with a Value default
// is filled in when a body leaves it out; an Optional marker only
// declares that absence is fine. Any other input is required.
Defaults []lang.DefaultSpec
}
TypeSchema describes the input and output fields of one resource, data source, or action. Each map keys a kebab-case field name (the form stack source uses) to that field's semantic Type. Inputs lists the receiver type's exported fields; Outputs lists the output struct's. The walker that builds this schema (goschema) recursively expands named struct types so nested object fields can be type-checked too.
SensitiveInputs and SensitiveOutputs hold the kebab-case names of fields a library marked sensitive via a `ub:",sensitive"` struct tag. Both are top-level only; sensitivity does not descend into nested object fields.
type TypedAction ¶
TypedAction is the typed contract for actions. Out names the action's output struct.
type TypedDataSource ¶
TypedDataSource is the typed contract for read-only data sources.
type TypedResource ¶
type TypedResource[In, Out any] interface { SchemaVersion() int Create(ctx context.Context, cfg any) (Out, error) Read(ctx context.Context, cfg any, prior Out) (Out, error) Update(ctx context.Context, cfg any, prior Prior[In, Out]) (Out, error) Delete(ctx context.Context, cfg any, prior Out) error ReplaceFields() []string }
TypedResource is the typed contract a library author implements for one primitive resource type. In names the input struct, which is the method receiver; Out names the output struct, usually a pointer (e.g. *VpcOutput) so a "no prior state" call passes nil. Update receives a Prior bundling the last apply's inputs and outputs.
Source Files
¶
- apply_error.go
- apply_events.go
- apply_graph.go
- apply_plan.go
- apply_reconcile.go
- apply_schedule.go
- check_configs.go
- corefns.go
- dag.go
- decode.go
- defaults.go
- doc.go
- eval.go
- executor.go
- funcs.go
- library.go
- locals.go
- nodes.go
- panic.go
- plan.go
- plan_envelope.go
- plan_for_each.go
- plan_graph.go
- plan_io.go
- refresh.go
- refs.go
- schema.go
- sensitivity.go
- state_ref.go
- trigger.go
- typed.go