plan¶
Package plan provides plan generation and persistence for mooncake configurations.
Index¶
- Variables
- func HashInputFiles(paths []string) (string, error)
- func IsStaleError(err error) bool
- func SavePlanToFile(p *Plan, filePath string) (err error)
- func ValidateForApply(p *Plan, opts ValidateOptions) error
- type ExpansionContext
- type HostFacts
- type IncludeFrame
- type Plan
- func LoadPlanFromFile(filePath string) (*Plan, error)
- type Planner
- func NewPlanner() (*Planner, error)
- func (p Planner) BuildPlan(cfg PlannerConfig) (Plan, error)
- func (p *Planner) ExpandStepsWithContext(steps []config.Step, variables map[string]interface{}, currentDir string) ([]config.Step, error)
- type PlannerConfig
- type StaleError
- func (e *StaleError) Error() string
- type StaleReason
- func ValidateForApplyWithReasons(p *Plan, opts ValidateOptions) ([]StaleReason, error)
- type StepInspection
- type UnresolvedRef
- func CheckPlanStrict(p *Plan) []UnresolvedRef
- type ValidateOptions
Variables¶
ErrInputFileMissing is returned by HashInputFiles when one of the recorded input files no longer exists at apply time. Surfaces as part of the stale-plan policy.
func HashInputFiles¶
HashInputFiles computes a deterministic hash over the contents of the given files. Used at both plan time (to record what the plan was built from) and apply time (to detect that the source files have changed since).
The hash mixes the file path AND content so renames are detected (different path → different hash even with identical content).
Returns ErrInputFileMissing if any path is unreadable; callers should treat that as a stale-plan condition.
func IsStaleError¶
IsStaleError reports whether err is a stale-plan rejection.
func SavePlanToFile¶
SavePlanToFile saves a plan to a file in JSON or YAML format.
Before writing, the marshalled bytes go through redactSecretMarkers which rewrites any in-memory `\x00__MOONCAKE_SECRET__:env:FOO` sentinel back to a human-readable `!secret env:FOO` form. This is the spec-23 §3 plan-output redaction: the real secret value never makes it to disk (the marker carries only the *ref*, never the resolved value), but the marker itself looks like a control character so we rewrite it before serialization for readability.
func ValidateForApply¶
ValidateForApply is the convenience shim around ValidateForApplyWithReasons that drops the per-check reason list. Existing callers that only care about pass/fail keep working.
type ExpansionContext¶
ExpansionContext holds the context during plan expansion
type ExpansionContext struct {
Variables map[string]interface{}
CurrentDir string
Tags []string
// SkipTags excludes steps whose tags intersect this list
// (MT-58 `--skip-tags`). Composes with Tags via AND.
SkipTags []string
// Names is the spec-50 step-name filter. When non-empty, a step is
// only kept (step.Skipped=false) when its name matches one of the
// entries. Untagged steps still run on a tag filter; unnamed steps
// are dropped on a name filter (see utils.MatchesNames).
Names []string
}
type HostFacts¶
HostFacts captures the minimum set of facts needed to detect a stale plan being applied on the wrong host. Spec 16's stale-plan policy compares these at apply time and refuses on mismatch unless --allow-stale is set.
Deliberately small. Hostname, kernel version, package versions etc. are too strict and would make plans annoyingly unportable across similar dev machines.
type HostFacts struct {
OsFamily string `json:"os_family,omitempty" yaml:"os_family,omitempty"`
Arch string `json:"arch,omitempty" yaml:"arch,omitempty"`
DistroFamily string `json:"distro_family,omitempty" yaml:"distro_family,omitempty"`
}
type IncludeFrame¶
IncludeFrame tracks a frame in the include stack for cycle detection and origin tracking
type Plan¶
Plan represents a fully expanded, deterministic execution plan.
Spec 16 adds: - Inspections: per-step state predictions - GeneratedOn: host facts subset for stale-plan detection - InputFiles + InputFilesHash: source-file integrity check so `apply --from-plan` refuses to run plans that no longer match the YAML they were built from.
type Plan struct {
Version string `json:"version" yaml:"version"`
GeneratedAt time.Time `json:"generated_at" yaml:"generated_at"`
GeneratedOn HostFacts `json:"generated_on,omitempty" yaml:"generated_on,omitempty"`
RootFile string `json:"root_file" yaml:"root_file"`
InputFiles []string `json:"input_files,omitempty" yaml:"input_files,omitempty"`
InputFilesHash string `json:"input_files_hash,omitempty" yaml:"input_files_hash,omitempty"`
Steps []config.Step `json:"steps" yaml:"steps"`
Inspections []StepInspection `json:"inspections,omitempty" yaml:"inspections,omitempty"`
InitialVars map[string]interface{} `json:"initial_vars,omitempty" yaml:"initial_vars,omitempty"`
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
// Modules carries the playbook's `modules:` alias map (spec-67) so the
// executor can resolve `use: <alias>` references at apply time.
Modules map[string]string `json:"modules,omitempty" yaml:"modules,omitempty"`
// UnresolvedTemplates lists `{{ root }}` references whose root
// identifier is not present in initial_vars, not produced by a
// prior step's `as:` register, and not a recognized loop/env name.
// Populated by CheckPlanStrict after expansion; surfaced by the
// `validate` and `plan` commands.
UnresolvedTemplates []UnresolvedRef `json:"unresolved_templates,omitempty" yaml:"unresolved_templates,omitempty"`
}
func LoadPlanFromFile¶
LoadPlanFromFile loads a plan from a JSON or YAML file
type Planner¶
Planner builds deterministic execution plans from config files
func NewPlanner¶
NewPlanner creates a new Planner instance. Returns an error if template renderer initialization fails.
func (*Planner) BuildPlan¶
BuildPlan generates a deterministic execution plan from a config file
func (*Planner) ExpandStepsWithContext¶
func (p *Planner) ExpandStepsWithContext(steps []config.Step, variables map[string]interface{}, currentDir string) ([]config.Step, error)
ExpandStepsWithContext expands a list of steps with the given context. This is useful for expanding preset steps which may contain includes, loops, etc. Returns the expanded steps ready for execution.
type PlannerConfig¶
PlannerConfig holds configuration for building a plan
type PlannerConfig struct {
ConfigPath string
Variables map[string]interface{}
Tags []string
// SkipTags excludes steps whose tags intersect this list (MT-58).
SkipTags []string
// Names is the spec-50 step-name filter; propagated into
// ExpansionContext so per-step skip evaluation can consult it.
Names []string
// TaskName, when non-empty, selects a named task from the config's
// `tasks:` block instead of the top-level `steps:` list. The
// planner replaces RunConfig.Steps with the task's Steps and
// layers the task's Vars between file-level vars (lowest) and the
// caller-supplied Variables (highest). An unknown task name is an
// error from BuildPlan.
TaskName string
}
type StaleError¶
StaleError describes a stale-plan rejection. Callers compare Reason to StaleReason constants; the human Message is suitable for direct display.
func (*StaleError) Error¶
type StaleReason¶
StaleReason identifies why a plan was rejected as stale at apply time. Returned via StaleError so callers can present specific messages or honor a typed --allow-stale override.
const (
StaleReasonHostMismatch StaleReason = "host_mismatch"
StaleReasonHashMismatch StaleReason = "input_files_changed"
StaleReasonFileMissing StaleReason = "input_file_missing"
StaleReasonAgeExceeded StaleReason = "max_age_exceeded"
)
func ValidateForApplyWithReasons¶
ValidateForApplyWithReasons checks that a plan loaded from disk is safe to apply against the current host and returns BOTH:
- reasons: every stale-check that would have rejected the plan,
populated regardless of AllowStale. Callers running with
`--allow-stale` use this to surface "we allowed apply despite
X / Y" so the operator sees what was overridden.
- err: the first *StaleError that fired (when AllowStale is
false), wrapped via standard `errors.Is/As`. nil when no
check failed OR when AllowStale demoted them all.
Checks (in order):
- The host facts subset (os_family, arch, distro_family) matches the values captured at plan time. 2. The on-disk contents of every input file (root config + all includes) hash to the value captured at plan time. Detects unrelated edits to the YAML between plan and apply. 3. If opts.MaxAge is set, the plan must be younger than that.
Hash I/O errors that aren't "file missing" (perm-denied, EIO, …) short-circuit immediately and return as the raw wrap — they aren't stale-plan conditions, they're system errors.
type StepInspection¶
StepInspection is the result of running a handler in ModePlan against a single step. One inspection per Plan.Steps entry (matched by StepID).
type StepInspection struct {
StepID string `json:"step_id" yaml:"step_id"`
ActionType string `json:"action_type,omitempty" yaml:"action_type,omitempty"`
WouldChange bool `json:"would_change" yaml:"would_change"`
Checkable bool `json:"checkable" yaml:"checkable"`
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
// Skipped reflects when/tag filtering decisions made at plan time.
// Skipped steps have WouldChange=false and Reason explains why.
Skipped bool `json:"skipped,omitempty" yaml:"skipped,omitempty"`
// Detail carries action-specific plan data (e.g. effects.ContentDiff for file writes).
Detail any `json:"detail,omitempty" yaml:"detail,omitempty"`
// Diff is the spec-22 structural per-step delta when the handler
// implements actions.Differ. nil for handlers that haven't opted
// in (assert, shell, cmd, etc.) — we deliberately don't synthesize
// a default-Differ Diff here because the coarse "Operation=update,
// Resource.Kind=other" fallback would appear on every non-Differ
// step and add noise to JSON output without information.
//
// Consumers wanting structured information about steps that don't
// implement Differ should look at Reason + Detail instead.
Diff *actions.Diff `json:"diff,omitempty" yaml:"diff,omitempty"`
// Cost is the spec-22 phase 6 informational cost estimate when
// the handler implements actions.Coster. nil for handlers that
// haven't opted in. Surfaced in `mooncake plan --format json`
// per step so cost-aware tooling (transaction risk gates, agent
// safety prompts) can read it without re-running.
Cost *actions.CostEstimate `json:"cost,omitempty" yaml:"cost,omitempty"`
}
type UnresolvedRef¶
UnresolvedRef describes a single `{{ root }}` reference whose root identifier is not in scope at the step's plan-time position.
type UnresolvedRef struct {
StepID string `json:"step_id,omitempty" yaml:"step_id,omitempty"`
StepName string `json:"step_name,omitempty" yaml:"step_name,omitempty"`
Field string `json:"field,omitempty" yaml:"field,omitempty"`
Root string `json:"root" yaml:"root"`
Origin *config.Origin `json:"origin,omitempty" yaml:"origin,omitempty"`
}
func CheckPlanStrict¶
CheckPlanStrict scans the expanded plan for unresolved root identifiers. Returns a deterministic list (steps in order, refs per step in field declaration order, deduplicated by root).
type ValidateOptions¶
ValidateOptions controls which checks ValidateForApply runs and what overrides the caller has explicitly enabled.
type ValidateOptions struct {
// MaxAge, when non-zero, rejects plans older than this duration.
MaxAge time.Duration
// AllowStale, when true, demotes all stale-plan rejections to a
// best-effort warning (returned as nil error). The caller is
// responsible for logging the reasons separately if desired.
AllowStale bool
}
Generated by gomarkdoc