state

package
v0.26.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// SchemaVersionCurrent is the current state file schema version.
	SchemaVersionCurrent = 1
)

Variables

View Source
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

func MergeParameters(saved, cli map[string]any) map[string]any

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 ResolveStatePath

func ResolveStatePath(path, baseDir string) (string, error)

ResolveStatePath resolves a state file path. Absolute paths are used as-is. Relative paths are resolved against baseDir. Path traversal (../) is rejected.

func SaveToFile

func SaveToFile(path, baseDir string, sd *Data) error

SaveToFile marshals and writes StateData to a JSON file using atomic write. Relative paths are resolved against baseDir.

func WithState

func WithState(ctx context.Context, data *Data) context.Context

WithState returns a new context with the StateData stored in it.

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

func FromContext(ctx context.Context) (*Data, bool)

FromContext retrieves the StateData from the context. Returns the StateData and true if found, or nil and false if not present.

func LoadFromFile

func LoadFromFile(path, baseDir string) (*Data, error)

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.

func NewData

func NewData() *Data

NewData returns an initialized empty StateData with the current schema version.

func NewMockData

func NewMockData(solution, version string, params map[string]any) *Data

NewMockData creates a StateData populated with test data. Use this in tests that need a pre-populated state.

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

func NewManager(config *Config, registry *provider.Registry, version string) *Manager

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:

  1. Evaluates the enabled ValueRef using CLI params (available as __params in CEL expressions). No resolver data is available at load time.
  2. If disabled, returns LoadResult{Skipped: true}.
  3. Resolves backend inputs with params as __params.
  4. Calls the backend provider with operation=state_load.
  5. Merges saved parameters with CLI params (CLI wins on conflict).
  6. Captures command info.
  7. 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:

  1. Saves the merged parameter set.
  2. Checks immutable resolvers (saves new, verifies existing).
  3. Updates metadata timestamps.
  4. 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 SolutionMeta

type SolutionMeta struct {
	Name    string
	Version string
}

SolutionMeta contains solution identity for state metadata.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL