Documentation
¶
Index ¶
- Constants
- Variables
- func CheckImmutables(stateData *Data, resolverCtx *resolver.Context, resolvers []*resolver.Resolver) error
- func MergeParameters(saved, cli map[string]any) map[string]any
- func MissingParams(ctx context.Context, config *Config, params map[string]any) []string
- func RequiredParams(ctx context.Context, config *Config) []string
- func ResolveStatePath(path, baseDir string) (string, error)
- func SaveToFile(path, baseDir string, sd *Data) error
- func WithState(ctx context.Context, data *Data) context.Context
- type Backend
- type CommandInfo
- type Config
- type Data
- type FingerprintEntry
- type ImmutableEntry
- type LoadResult
- type Manager
- type Metadata
- type MissingParamsError
- type SolutionMeta
Constants ¶
const (
// SchemaVersionCurrent is the current state file schema version.
SchemaVersionCurrent = 1
)
Variables ¶
var ( // ErrInvalidBackend indicates the configured backend provider lacks CapabilityState. ErrInvalidBackend = errors.New("state backend provider does not have CapabilityState") // ErrKeyNotFound indicates a requested state key does not exist. ErrKeyNotFound = errors.New("state key not found") // ErrImmutableEntry indicates an attempt to overwrite an immutable state entry. ErrImmutableEntry = errors.New("cannot overwrite immutable state entry") // ErrUnsupportedSchemaVersion indicates the state file was written by a newer // version of scafctl and cannot be safely read by this version. ErrUnsupportedSchemaVersion = errors.New("unsupported state schema version") )
Functions ¶
func CheckImmutables ¶
func CheckImmutables(stateData *Data, resolverCtx *resolver.Context, resolvers []*resolver.Resolver) error
CheckImmutables verifies immutable resolver values against state after execution. For each resolver with Immutable: true:
- If no prior value exists in state, the resolved value is saved.
- If a prior value exists and matches, no action is taken.
- If a prior value exists and differs, an error is returned.
Returns the updated state data (with any newly locked immutables).
func MergeParameters ¶
MergeParameters combines saved parameters from state with CLI parameters. CLI parameters take precedence (overwrite existing keys). New CLI keys are added. Returns the effective parameter set for this execution.
func MissingParams ¶ added in v0.28.0
MissingParams returns the subset of RequiredParams that are absent from the supplied params map. Returns nil if all required params are present.
func RequiredParams ¶ added in v0.28.0
RequiredParams extracts the __params keys referenced by the state configuration. These are the CLI parameters (-r flags) that must be supplied for the state backend to resolve its inputs at load time.
It inspects Enabled and Backend.Inputs ValueRefs for:
- CEL expressions: uses Expression.GetVariablesWithPrefix("__params.")
- Go templates: uses GetGoTemplateReferences() and filters for .__params.*
Literal and resolver-ref ValueRefs are skipped (they don't use __params). SaveOverrides are also skipped (they are only evaluated at save time).
Returns a deduplicated, sorted list of parameter names (e.g., ["app_name", "project"]). Errors during parsing are silently ignored (best-effort extraction).
func ResolveStatePath ¶
ResolveStatePath resolves a state file path. Absolute paths are used as-is. Relative paths are resolved against baseDir. Path traversal (../) is rejected.
func SaveToFile ¶
SaveToFile marshals and writes StateData to a JSON file using atomic write. Relative paths are resolved against baseDir.
Types ¶
type Backend ¶
type Backend struct {
// Provider is the name of a registered provider with CapabilityState (e.g., "file").
Provider string `json:"provider" yaml:"provider" doc:"Provider name with CapabilityState" maxLength:"253" example:"file"`
// Inputs are provider-specific inputs. Each value is a ValueRef for dynamic resolution.
//
// CEL expressions use __params for CLI parameters (e.g. __params.project) and _ for
// resolver outputs (available at save time only, not load time).
//
// Go templates spread resolver data at top level (e.g. {{ .name }}) and expose CLI
// parameters under __params (e.g. {{ .__params.project }}).
Inputs map[string]*spec.ValueRef `json:"inputs" yaml:"inputs" doc:"Provider-specific inputs (ValueRef for dynamic resolution)"`
// SaveOverrides are provider-specific inputs resolved only at save time when
// resolver data (_) is available. Keys that overlap with Inputs override them
// at save time. This enables patterns like loading state from a fixed branch
// (via Inputs) and saving to a resolver-derived branch (via SaveOverrides).
//
// At load time, SaveOverrides are completely ignored -- no errors are raised
// for resolver-dependent expressions.
SaveOverrides map[string]*spec.ValueRef `json:"saveOverrides,omitempty" yaml:"saveOverrides,omitempty" doc:"Save-time-only inputs that override Inputs keys"`
}
Backend configures the state persistence backend.
type CommandInfo ¶
type CommandInfo struct {
// Subcommand is the CLI subcommand used (e.g., "run solution").
Subcommand string `json:"subcommand" doc:"CLI subcommand used" maxLength:"100" example:"run solution"`
// Parameters are the key-value pairs from --parameter flags for the most recent run.
Parameters map[string]string `json:"parameters" doc:"Key-value pairs from --parameter flags"`
}
CommandInfo captures the most recent invocation for validation replay. Only the latest invocation is stored -- no history.
type Config ¶
type Config struct {
// Enabled controls whether state persistence is active. Supports literal bool, CEL expression, or template.
// Resolver references (rslvr:) are not supported because state is loaded before resolver execution.
// Use __params to reference CLI parameters (e.g. expr: "__params.enable_state == true").
Enabled *spec.ValueRef `json:"enabled" yaml:"enabled" doc:"Dynamic activation of state persistence"`
// Backend configures which provider handles state persistence.
Backend Backend `json:"backend" yaml:"backend" doc:"Backend provider configuration"`
}
Config is the solution-level state configuration. It is a top-level peer to Spec, Catalog, Bundle, and Compose on the Solution struct.
CLI parameters passed via -r flags are available as __params in CEL expressions and Go templates used in Enabled and Backend.Inputs. This allows dynamic backend configuration without requiring resolver execution (which happens after state load).
Example:
state:
enabled:
expr: "__params.state_enabled == true"
backend:
provider: file
inputs:
path:
expr: "'gcp/' + __params.project + '/state.json'"
type Data ¶
type Data struct {
// SchemaVersion enables forward-compatible format migrations.
SchemaVersion int `json:"schemaVersion" doc:"Format version for migrations"`
// Metadata identifies the solution and tracks timestamps.
Metadata Metadata `json:"metadata" doc:"Solution identity and timestamps"`
// Command captures the most recent invocation for validation replay.
Command CommandInfo `json:"command" doc:"Most recent invocation"`
// Parameters stores the merged parameter set used for replay. On each run,
// CLI parameters are merged into this map (CLI wins on conflict, new keys added).
// When no CLI params are provided, these saved parameters drive replay.
Parameters map[string]any `json:"parameters" doc:"Merged parameter set for replay"`
// Immutables maps resolver names to locked values. Resolvers marked
// immutable: true have their resolved values saved here after first execution.
// Subsequent runs verify the resolver produces the same value.
Immutables map[string]*ImmutableEntry `json:"immutables" doc:"Locked resolver values"`
// Fingerprints stores file and input hashes for action up-to-date checks.
// Keys use the format "__fingerprint:<actionName>:<type>".
Fingerprints map[string]*FingerprintEntry `json:"fingerprints" doc:"Action fingerprint hashes"`
}
Data is the complete persisted state structure. It is serialized as JSON to the backend storage.
func FromContext ¶
FromContext retrieves the StateData from the context. Returns the StateData and true if found, or nil and false if not present.
func LoadFromFile ¶
LoadFromFile reads and unmarshals a StateData JSON file. If the file does not exist, it returns an empty StateData. Relative paths are resolved against baseDir.
type FingerprintEntry ¶
type FingerprintEntry struct {
// Value is the stored hash string.
Value string `json:"value" doc:"Stored hash value"`
// UpdatedAt is when this fingerprint was last written.
UpdatedAt time.Time `json:"updatedAt" doc:"When this fingerprint was last written"`
}
FingerprintEntry stores a fingerprint hash for action up-to-date checks.
type ImmutableEntry ¶
type ImmutableEntry struct {
// Value is the locked resolver value.
Value any `json:"value" doc:"Locked resolver value"`
// Type is the resolver's declared type.
Type string `json:"type" doc:"Resolver declared type" maxLength:"30" example:"string"`
// CreatedAt is when the immutable value was first stored.
CreatedAt time.Time `json:"createdAt" doc:"When the value was first locked"`
}
ImmutableEntry is a locked resolver value persisted in state.
type LoadResult ¶
type LoadResult struct {
// Ctx is the context enriched with state.WithState.
Ctx context.Context
// Data is the loaded (or empty) state data.
Data *Data
// MergedParams is the effective parameter set (saved params merged with CLI params).
// CLI params override saved params; new CLI keys are added.
MergedParams map[string]any
// Skipped is true when state is disabled or the enabled ValueRef is falsy.
Skipped bool
}
LoadResult is returned by Load with the loaded state and merged parameters.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager orchestrates the state lifecycle: pre-execution loading, parameter merging, and post-execution saving. It is called by the CLI command layer before and after resolver execution.
func NewManager ¶
NewManager creates a state manager for the given state configuration.
func (*Manager) Load ¶
func (m *Manager) Load(ctx context.Context, params map[string]any, command CommandInfo) (*LoadResult, error)
Load executes the pre-execution state lifecycle:
- Evaluates the enabled ValueRef using CLI params (available as __params in CEL expressions). No resolver data is available at load time.
- If disabled, returns LoadResult{Skipped: true}.
- Resolves backend inputs with params as __params.
- Calls the backend provider with operation=state_load.
- Merges saved parameters with CLI params (CLI wins on conflict).
- Captures command info.
- Injects state into context via WithState.
func (*Manager) Save ¶
func (m *Manager) Save(ctx context.Context, stateData *Data, resolverCtx *resolver.Context, resolvers []*resolver.Resolver, mergedParams, resolverData map[string]any, solMeta SolutionMeta) error
Save executes the post-execution state lifecycle:
- Saves the merged parameter set.
- Checks immutable resolvers (saves new, verifies existing).
- Updates metadata timestamps.
- Calls the backend provider with operation=state_save.
params are the merged parameters for this execution. resolverData contains resolver outputs, available as _ in CEL expressions for backend inputs.
type Metadata ¶
type Metadata struct {
// Solution is the solution name from metadata.name.
Solution string `json:"solution" doc:"Solution name from metadata.name" maxLength:"253"`
// Version is the solution semver string.
Version string `json:"version" doc:"Solution semver" maxLength:"30"`
// CreatedAt is when the state file was first created.
CreatedAt time.Time `json:"createdAt" doc:"First state file creation"`
// LastUpdatedAt is when the state file was most recently saved.
LastUpdatedAt time.Time `json:"lastUpdatedAt" doc:"Most recent save"`
// ScafctlVersion is the version of scafctl that last wrote the state.
ScafctlVersion string `json:"scafctlVersion" doc:"Version of scafctl that last wrote" maxLength:"30"`
}
Metadata identifies the solution and tracks state lifecycle timestamps.
type MissingParamsError ¶ added in v0.28.0
type MissingParamsError struct {
// Missing is the sorted list of __params keys not found in the supplied params.
Missing []string `json:"missing" yaml:"missing" doc:"Parameter names required by state configuration"`
// Original is the underlying evaluation error.
Original error `json:"-" yaml:"-" doc:"Underlying evaluation error"`
}
MissingParamsError is returned when state load fails because the state configuration references __params keys that were not supplied via CLI -r flags. It wraps the original evaluation error and includes the list of missing parameter names so callers can produce actionable messages.
func (*MissingParamsError) Error ¶ added in v0.28.0
func (e *MissingParamsError) Error() string
func (*MissingParamsError) Unwrap ¶ added in v0.28.0
func (e *MissingParamsError) Unwrap() error
type SolutionMeta ¶
SolutionMeta contains solution identity for state metadata.