Documentation
¶
Overview ¶
Package record defines the on-disk run record Orchard writes after each `orchard run`. A record captures what a scenario did well enough that future tooling — starting with `orchard teardown` — can reason about it without re-executing the scenario.
The format is JSON, pretty-printed for inspectability. cty values (variables, node outputs, scenario outputs) serialise through pkg/protocol/ctyjson so they round-trip cleanly with the engine's own type system.
This is deliberately a simpler model than Terraform's state: no remote backends, no locking, no versioning, one file per run in the user's working directory. That keeps the surface small while the teardown story is being prototyped. A records-based teardown can grow sophistication when real-world use demands it.
Index ¶
- Constants
- func ConfigDrift(r *Record, configs map[string]map[string]cty.Value) ([]string, error)
- func DecodeCty(raw map[string]json.RawMessage) (map[string]cty.Value, error)
- func EncodeCty(values map[string]cty.Value) (map[string]json.RawMessage, error)
- func Filename(scenario, id string) string
- func HashConfig(runID string, configs map[string]map[string]cty.Value) (map[string]map[string]string, error)
- func HashValue(runID, scope, name string, v cty.Value) (string, error)
- func IndexComponentVars(r *Record) (map[string]map[string]cty.Value, error)
- func IndexOutputs(r *Record) (map[string]map[string]cty.Value, error)
- func IndexUndos(r *Record) (map[string]provider.Invocation, error)
- func NewID(now time.Time) string
- func Save(path string, rec *Record) error
- func SensitiveDrift(r *Record, vars map[string]cty.Value) ([]string, error)
- type File
- type FileWriter
- type Node
- type Options
- type Record
- type Reporter
- func (r *Reporter) ComponentVars(componentKey string, vars map[string]cty.Value)
- func (r *Reporter) NodeCompleted(nodeID string, outputs map[string]cty.Value, d time.Duration)
- func (r *Reporter) NodeFailed(nodeID string, err error)
- func (r *Reporter) NodeStarted(nodeID string)
- func (r *Reporter) NodeUndo(nodeID string, undo provider.Invocation)
- func (r *Reporter) Path() string
- func (r *Reporter) RunCompleted(summary engine.RunSummary)
- func (r *Reporter) RunStarted(scenario string)
- type Scenario
- type Undo
- type Writer
Constants ¶
const ( StatusSucceeded = "succeeded" StatusFailed = "failed" StatusSkipped = "skipped" )
Status values for runs and nodes.
const ( KindAction = "action" KindComponent = "component" )
Kind values for node entries.
const Format = "1"
Format is a monotonically-increasing record format version. Bump only for breaking changes to the on-disk shape: Load refuses any other format, so additive fields (omitempty, absent in older records) stay on the current one.
Variables ¶
This section is empty.
Functions ¶
func ConfigDrift ¶ added in v0.2.0
ConfigDrift lists, sorted, the "<provider>.<attribute>" configs that differ between the run r recorded and configs: changed, added or removed. Providers the run didn't record are left out, and a record without hashes reports no drift.
func DecodeCty ¶
DecodeCty is the inverse of EncodeCty: turn a map of JSON-encoded cty values back into cty values. Types are inferred from the JSON structure (DynamicPseudoType) since the record doesn't carry a type hint per field — numbers become numbers, lists become tuples, objects become objects. Close enough for teardown expressions that typically read scalars and occasionally traverse an object.
func EncodeCty ¶
EncodeCty serialises a map of cty values using ctyjson, preserving type information across the JSON round-trip. Returns nil for an empty input so omitempty fires and the serialised record stays compact.
func Filename ¶
Filename returns the on-disk filename for a record with the given scenario name and run ID. The scenario name is sanitised — anything outside [A-Za-z0-9_-] becomes an underscore so operators can still find records with familiar shell globs even if the scenario block uses spaces, slashes, or other metacharacters.
func HashConfig ¶ added in v0.2.0
func HashConfig(runID string, configs map[string]map[string]cty.Value) (map[string]map[string]string, error)
HashConfig fingerprints evaluated provider configs per attribute with HashValue.
func HashValue ¶ added in v0.2.0
HashValue fingerprints v as HMAC-SHA256 keyed by the run ID, so equal values in different records hash differently. scope and name say what v is ("var" and the variable, or a provider and its attribute), so equal values in one record differ too.
func IndexComponentVars ¶ added in v0.2.0
IndexComponentVars decodes the record's component variables, keyed by component key, for engine.Teardown.
func IndexOutputs ¶
IndexOutputs decodes each succeeded node's outputs into a map keyed by full node ID ("action.sql.create_merchant" etc.). Every succeeded node is present, with an empty map when it had no outputs (a DDL statement), since callers treat absence as "nothing to tear down". Failed and skipped nodes are omitted. Designed as the canonical way to hand a record off to engine.Teardown.
func IndexUndos ¶
func IndexUndos(r *Record) (map[string]provider.Invocation, error)
IndexUndos decodes each succeeded node's undo invocation, keyed by full node ID, for engine.Teardown. Nodes without one are omitted.
func NewID ¶
NewID returns a run identifier of the form YYYYMMDDTHHMMSSZ_<8hex>. The timestamp prefix ensures lexicographic sort matches chronological order; the hex suffix handles collisions within the same second. UTC is always used so the ID is stable across machines.
8 hex characters give 2^32 values — ample for "same second" collision avoidance without bloating filenames.
func Save ¶
Save writes rec to path through a sibling temp file and a rename, so a partially-flushed record can't appear on disk.
func SensitiveDrift ¶ added in v0.2.0
SensitiveDrift checks vars, the variables teardown loaded, against the hashes of the run's sensitive variables. It returns, sorted, the names whose value differs, and an error naming any the scenario no longer declares.
Types ¶
type File ¶
File is a record and the path it was loaded from.
type FileWriter ¶
type FileWriter struct {
Dir string
}
FileWriter persists records to the filesystem. Dir is where the file lands; the filename is derived from the scenario name and run id. Writes go through a temp file + atomic rename so a partially-flushed record can't appear on disk.
type Node ¶
type Node struct {
ID string `json:"id"`
Kind string `json:"kind"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at"`
Status string `json:"status"`
Outputs map[string]json.RawMessage `json:"outputs,omitempty"`
Error string `json:"error,omitempty"`
// Undo is how the node's provider says to reverse it (a typed
// insert: delete the row by its primary key). `orchard teardown`
// runs it when the action has no explicit teardown block. Records
// from before undo existed simply don't have it.
Undo *Undo `json:"undo,omitempty"`
// TornDown marks a node whose teardown succeeded, so re-running a
// partly failed teardown skips it.
TornDown bool `json:"torn_down,omitempty"`
}
Node captures one graph node's execution. Outputs is only populated for succeeded nodes; Error is only populated for failed ones. Skipped nodes (upstream of a failure) have neither.
type Options ¶
type Options struct {
Inner engine.Reporter
Writer Writer
Scenario Scenario
Variables map[string]cty.Value // resolved scenario variables
Seed uint64 // the run's random seed
// ProviderConfigs are the evaluated provider configs, recorded as
// hashes (see HashConfig).
ProviderConfigs map[string]map[string]cty.Value
// Sensitive names the scenario variables recorded as a hash
// rather than a value (ParsedScenario.Sensitive).
Sensitive map[string]bool
LogOutput io.Writer // where write failures surface; defaults to os.Stderr
}
Options configures a Reporter. Most callers only need Writer and scenario context; LogOutput is where a write failure surfaces.
type Record ¶
type Record struct {
Format string `json:"format"`
ID string `json:"id"`
Scenario Scenario `json:"scenario"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
Variables map[string]json.RawMessage `json:"variables,omitempty"`
// Seed is the run's random seed; `orchard run --seed` with it
// draws the same random values again. Absent in older records.
Seed *uint64 `json:"seed,omitempty"`
// ComponentVariables are each component's resolved inputs (its
// var.* namespace), keyed by component key ("merchant.acme",
// "merchant.acme[0]"), for its actions' teardown bodies. Absent
// in older records.
ComponentVariables map[string]map[string]json.RawMessage `json:"component_variables,omitempty"`
Nodes []Node `json:"nodes"`
Outputs map[string]json.RawMessage `json:"outputs,omitempty"`
// TornDownAt is set once `orchard teardown` has finished every
// node's teardown; such a record is refused by a later teardown.
TornDownAt *time.Time `json:"torn_down_at,omitempty"`
// ConfigHashes fingerprints each provider config attribute the run
// used (provider -> attribute -> HashConfig), so teardown can
// refuse a config that now points somewhere else without the
// record holding DSNs or keys. Absent in older records.
ConfigHashes map[string]map[string]string `json:"config_hashes,omitempty"`
// SensitiveVariables holds, for each scenario variable declared
// `sensitive = true`, a hash of its value (see HashValue) in place
// of the value, which Variables leaves out. Teardown needs the
// value passed again and checks it against the hash.
SensitiveVariables map[string]string `json:"sensitive_variables,omitempty"`
// SensitiveComponentVariables names, per component key, the
// component variables ComponentVariables leaves out because they
// hold a sensitive value. Teardown evaluates them again.
SensitiveComponentVariables map[string][]string `json:"sensitive_component_variables,omitempty"`
}
Record is the top-level document persisted per run.
type Reporter ¶
type Reporter struct {
// WriteErr is set if the record could not be persisted.
WriteErr error
// contains filtered or unexported fields
}
Reporter wraps another engine.Reporter and accumulates enough state to assemble a Record at the end of the run. Writer is called exactly once, from RunCompleted. WriteErr (if the write fails) is surfaced through the reporter's own Out writer so the user sees it, and the error is retained on the reporter so the caller can choose to fail the run.
Wrap a presentation reporter like so:
base := reporter.Select(reporter.Options{Out: os.Stdout})
rec := record.NewReporter(base, scenarioMeta, variables, record.FileWriter{Dir: "."}, os.Stderr)
engine.SetReporter(rec)
The inner reporter receives every event first; the recorder accumulates its own copy. This keeps presentation identical to a no-record run.
func NewReporter ¶
NewReporter constructs a Reporter from options. The scenario variables are encoded up front so a later failure during the run doesn't lose the inputs that produced it.
func (*Reporter) ComponentVars ¶ added in v0.2.0
ComponentVars implements engine.ComponentVarsReporter. Variables holding a sensitive value (marked parser.SensitiveMark) are named in the record but left out.
func (*Reporter) NodeCompleted ¶
NodeCompleted implements engine.Reporter. Outputs are copied so the caller is free to mutate the map after the call returns.
func (*Reporter) NodeFailed ¶
NodeFailed implements engine.Reporter.
func (*Reporter) NodeStarted ¶
NodeStarted implements engine.Reporter.
func (*Reporter) NodeUndo ¶
func (r *Reporter) NodeUndo(nodeID string, undo provider.Invocation)
NodeUndo implements engine.UndoReporter: it attaches the undo invocation to the node that just completed.
func (*Reporter) Path ¶
Path is the file the record was written to, or "" if it wasn't written (no writer, a failed write, or a writer that isn't a FileWriter).
func (*Reporter) RunCompleted ¶
func (r *Reporter) RunCompleted(summary engine.RunSummary)
RunCompleted implements engine.Reporter. This is where the record is assembled and persisted.
func (*Reporter) RunStarted ¶
RunStarted implements engine.Reporter.
type Scenario ¶
Scenario is the subset of scenario metadata worth persisting. Path is absolute so a record is self-describing even when the consumer runs from a different cwd than the original `orchard run`.
func ScenarioFromParser ¶
func ScenarioFromParser(ps *parser.ParsedScenario, path string) Scenario
ScenarioFromParser extracts the bits of scenario metadata the record needs from a ParsedScenario + its source path. Keeps the caller from reaching into parser internals.