wfctlhelpers

package
v0.21.1 Latest Latest
Warning

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

Go to latest
Published: May 5, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package wfctlhelpers hosts the wfctl-side dispatch helper for v2 IaC plugins. wfctl calls ApplyPlan when a plugin manifest declares iacProvider.computePlanVersion: v2 (see plugin/sdk.IaCProvider). The helper iterates plan.Actions, fetches the matching ResourceDriver from the provider, and dispatches each action to a per-action sub-function (doCreate, doUpdate, doReplace, doDelete).

Lifecycle inside W-3a:

  • T3.1 (this file's ApplyPlan + dispatch + skeleton sub-functions)
  • T3.1.5 — wraps ApplyPlan with the input-drift postcondition
  • T3.2 — fills doCreate with UpsertSupporter recovery
  • T3.3 — fills doUpdate + doDelete (the latent doDelete bug fix)
  • T3.4 — fills doReplace and populates ApplyResult.ReplaceIDMap

Until W-3b lands the cmd/wfctl dispatch wiring, ApplyPlan has no in-tree caller — the helper ships in W-3a as foundation only and is exercised solely by this package's tests.

Per-action error-prefix policy

Sub-functions follow a "decompose-then-prefix" rule for the strings recorded in interfaces.ApplyResult.Errors[].Error:

  • doCreate, doUpdate, doDelete pass driver errors through unchanged. The ActionError struct already carries Resource + Action context fields, so a per-kind prefix would be redundant.
  • doCreate's upsert recovery path prefixes "upsert: " (e.g., "upsert: read after conflict: ...") because the failure is specifically about the recovery flow, not the original Create.
  • doReplace prefixes "replace: delete: " or "replace: create: " because a Replace decomposes into two driver calls — without the prefix, an operator reading result.Errors couldn't tell which sub-step failed.

Tests in apply_update_delete_test.go and apply_replace_test.go lock this contract via exact-string assertions; future refactors that drop or rename a prefix fail loudly.

Index

Constants

View Source
const DispatchVersionV2 = "v2"

DispatchVersionV2 is the manifest value that routes apply through wfctlhelpers.ApplyPlan. Exported so callers don't string-literal it at every dispatch site.

Variables

This section is empty.

Functions

func ApplyPlan

ApplyPlan dispatches each plan action to the matching ResourceDriver on the provider. Per-action errors are recorded on result.Errors and do NOT abort the loop — apply best-effort across actions, surface every failure for the operator to triage. Context cancellation between actions IS respected: when ctx is canceled or its deadline expires, the loop stops at the next iteration boundary and returns ctx.Err() as the top-level error so a long apply terminates promptly on Ctrl-C / SIGTERM.

At entry ApplyPlan captures result.InitialInputSnapshot by fingerprinting every name listed in plan.InputSnapshot through the OS env. After the dispatch loop completes — successfully or not — a deferred postcondition computes result.InputDriftReport against an apply-time snapshot taken through inputsnapshot.NewTolerantEnvProvider (sub-action env unsets are preserved, not flagged as drift). The postcondition is wrapped in recover() so a buggy env-provider closure cannot corrupt apply results; on panic, InputDriftReport is reset to nil and a warning is logged.

The function is concurrency-safe with respect to its inputs: result is owned by ApplyPlan for the duration of the call and is not shared with the provider or driver implementations.

T3.1 ships the dispatch skeleton; T3.1.5 added the postcondition above; T3.2/T3.3/T3.4 fill the per-action sub-functions with their full bodies.

func DispatchVersionFor

func DispatchVersionFor(p any) string

DispatchVersionFor returns the apply-time dispatch version for p. Providers that don't implement ComputePlanVersionDeclarer, or that return anything other than "v2", get "v1" (the legacy provider.Apply path). Centralizing the type assertion + default keeps the dispatch decision in one place — call sites pass the raw provider value (typed as interfaces.IaCProvider or any concrete provider type) rather than type-asserting at every dispatch site.

Param is `any` rather than interfaces.IaCProvider so this package stays import-free of the engine's interfaces package (and so non-engine call sites such as tests can pass concrete provider stubs without an extra adapter). The contract is identical: pass the loaded provider; receive "v1" or "v2".

func ValidateAllowReplaceProtected

func ValidateAllowReplaceProtected(plan interfaces.IaCPlan, allow map[string]struct{}) error

ValidateAllowReplaceProtected gates dispatch on the per-resource `protected: true` annotation. It walks every replace or delete action in plan and aggregates ALL blockers (resources protected and not in `allow`) into a single error before returning, so the operator sees the full set of names in one apply attempt and can authorize them with one round-trip via the pre-formatted --allow-replace=<csv> value.

Format (W-6/T6.2):

plan would require destructive action on N protected resource(s):
  <name1> (replace)
  <name2> (delete)
  ...
to authorize, re-run with:
  --allow-replace=<name1>,<name2>,...

Both names and the csv preserve plan-action declaration order so the output is deterministic across runs.

`protected: true` is sourced from PlanAction.Resource.Config for replace actions (where Resource carries the desired spec) and from PlanAction.Current.AppliedConfig for delete actions (where platform.differ leaves Resource.Config empty and the protected status is preserved on the previously-applied state).

Promoted from cmd/wfctl in W-7/T7.9 so the iac/conformance suite can exercise the gate contract without importing package main. The cmd/wfctl validateAllowReplaceProtected wrapper now delegates here; behavior and error format are byte-identical with the pre-W-7 implementation, so all existing tests in cmd/wfctl continue to pass without modification.

Types

type ComputePlanVersionDeclarer

type ComputePlanVersionDeclarer interface {
	ComputePlanVersion() string
}

ComputePlanVersionDeclarer is the optional interface a loaded interfaces.IaCProvider satisfies when wfctl has read its plugin.json SDK manifest's iacProvider.computePlanVersion field. wfctl's apply path type-asserts this interface to choose between the v1 (legacy in-provider Apply) and v2 (wfctlhelpers.ApplyPlan + drift postcondition) dispatch paths.

The dispatch contract is rev2/rev3-locked: there is NO WFCTL_USE_V2_APPLY env var, NO operator-flippable gate. The v1/v2 routing is plugin-author-controlled via the manifest field. A provider that does not satisfy this interface defaults to v1 (legacy dispatch); a provider that returns "v2" routes through ApplyPlan; any other return value is treated as "v1" so a typo in the manifest silently degrades to the safe legacy path.

NOTE on validation: when the manifest is loaded via plugin/sdk.ParseManifest, schema validation rejects unknown values at parse time. However, some loader paths in wfctl (e.g. cmd/wfctl/deploy_providers.go's findIaCPluginDir) currently use a minimal json.Unmarshal without schema validation, so unknown values CAN reach DispatchVersionFor at runtime. The default-to-v1 behavior is the safety net for those paths — DO NOT rely on the manifest-validation guarantee in callers.

Jump to

Keyboard shortcuts

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