record

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 15 Imported by: 0

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

View Source
const (
	StatusSucceeded = "succeeded"
	StatusFailed    = "failed"
	StatusSkipped   = "skipped"
)

Status values for runs and nodes.

View Source
const (
	KindAction    = "action"
	KindComponent = "component"
)

Kind values for node entries.

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

func DecodeCty(raw map[string]json.RawMessage) (map[string]cty.Value, error)

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

func EncodeCty(values map[string]cty.Value) (map[string]json.RawMessage, error)

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

func Filename(scenario, id string) string

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

func IndexOutputs(r *Record) (map[string]map[string]cty.Value, error)

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

func NewID(now time.Time) string

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

func Save(path string, rec *Record) error

Save writes rec to path through a sibling temp file and a rename, so a partially-flushed record can't appear on disk.

Types

type File

type File struct {
	Path   string
	Record *Record
}

File is a record and the path it was loaded from.

func List

func List(dir string, warn io.Writer) ([]File, error)

List loads every record in dir, newest first. Files that match the record name but can't be loaded (unreadable, not JSON, an unknown format) are skipped with a line on warn.

func Resolve

func Resolve(dir, arg string, warn io.Writer) (File, error)

Resolve finds a record from a command-line argument: a path to an existing file, or else a run ID looked up in dir. Records in dir that can't be loaded are reported on warn, as List does.

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.

func (FileWriter) Path

func (fw FileWriter) Path(r *Record) string

Path is where Write puts r.

func (FileWriter) Write

func (fw FileWriter) Write(r *Record) error

Write implements Writer.

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.

func Load

func Load(path string) (*Record, error)

Load reads and parses a record file from disk, refusing a format other than Format.

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

func NewReporter(opts Options) (*Reporter, error)

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

func (r *Reporter) NodeCompleted(nodeID string, outputs map[string]cty.Value, d time.Duration)

NodeCompleted implements engine.Reporter. Outputs are copied so the caller is free to mutate the map after the call returns.

func (*Reporter) NodeFailed

func (r *Reporter) NodeFailed(nodeID string, err error)

NodeFailed implements engine.Reporter.

func (*Reporter) NodeStarted

func (r *Reporter) NodeStarted(nodeID string)

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

func (r *Reporter) Path() string

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

func (r *Reporter) RunStarted(scenario string)

RunStarted implements engine.Reporter.

type Scenario

type Scenario struct {
	Name string `json:"name"`
	Path string `json:"path"`
}

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.

type Undo

type Undo struct {
	Action string                     `json:"action"`
	Attrs  map[string]json.RawMessage `json:"attrs"`
}

Undo is a recorded provider.Invocation: an action and its evaluated attributes.

type Writer

type Writer interface {
	Write(r *Record) error
}

Writer accepts a rendered Record. The default implementation writes to a file in a configured directory; tests and future alternative backends (remote stores, stdout, etc.) implement it too.

Jump to

Keyboard shortcuts

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