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 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 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
- type File
- type FileWriter
- type Node
- type Options
- type Record
- type Reporter
- 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 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 IndexOutputs ¶
IndexOutputs decodes each succeeded node's outputs into a map keyed by full node ID ("action.sql.create_merchant" etc.). Failed or output-less nodes are omitted — callers treat their absence as "nothing to tear down". 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.
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
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"`
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"`
}
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) 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.