pa-07 — The Hitchhiker's Guide to Infrastructure as Code
Companion to CONCEPTS.md, with the runnable engine in
src/go/iac/. Build the Terraform/Pulumi kernel and the whole declarative-infra world demystifies.
bash scripts/verify.sh runs the lifecycle:
plan (empty state): create vpc, create subnet, create db
apply (dependency order): create vpc -> create subnet -> create db
re-apply identical config: create=0 update=0 delete=0 <- idempotent no-op
change db size small->large: update db [size: "small" -> "large"]
drift (out-of-band resize): drift on: [db] <- next apply would revert
That's the engine behind every IaC tool, end to end.
1. Plan = the diff (engine.go)
Plan(desired, state) is the read-only heart: validate deps exist, reject
cycles, then per resource emit Create (TestPlanCreate), Update
with a diff (TestPlanUpdate), Delete for resources dropped from
desired (TestPlanDelete), or NoOp. Plan never touches the world —
that preview is the safety feature ("this will destroy the prod DB"
before it does). In CI you gate apply on a reviewed plan.
2. Apply = converge, in dependency order (engine.go)
Apply runs the plan, executing creates/updates in topological order
(deps first) and deletes in reverse, then writes the new state.
TestApplyTopoOrder proves vpc → subnet → db even when declared out of
order — because topoSort (Kahn's algorithm, deterministic) orders by
DependsOn. TestCycleError and TestMissingDependencyError show the
two ways a config is rejected. This ordering is the same correctness
concern as ADS in xDS (gw-08) and build order in pa-01.
3. Idempotency = safe to re-run (engine.go)
TestIdempotentReapply: apply a config, then apply it again → zero
creates/updates/deletes. Because the second plan diffs desired against a
state that already equals it, everything is a NoOp. Idempotency is what
lets you run IaC in CI, on a cron, or after a crash without fear — the
exact property shared by the reconcile loops in gw-08/gw-10/pa-08. It's
also the difference between IaC and an imperative script (which breaks or
duplicates on re-run).
4. State and drift (engine.go)
State is the last-applied snapshot the next plan diffs against — the
engine's memory of the world, and (in real tools) its most fragile,
load-bearing artifact. Drift(state, live) compares state to the actual
world: TestDriftDetection shows a hand-edited DB (size: manually-resized) and an unmanaged "rogue" resource both reported.
Managed drift is what the next apply reverts — and what GitOps
self-heal (pa-08) does continuously and automatically. Lose or corrupt
state and the engine plans the wrong delta (recreating live infra),
which is why production uses remote state + locking + backups.
5. The architect's view
The code is the kernel; the leverage is what it enables:
- Reproducible, reviewable platforms — environments in git, diffed before apply, rebuildable from scratch (vs unauditable click-ops).
- The universal reconcile loop — desired vs actual, converge. IaC (here), Kubernetes (gw-10), GitOps (pa-08), and xDS (gw-08) are the same idea; recognizing that is the synthesis an architect brings.
- The operational hazards to design around — state locking,
blast-radius of a bad apply (plan review + targeted applies), drift,
and adopting existing resources (
import).
6. Hands-on
cd src/go
bash ../scripts/verify.sh
go run ./cmd/iacsim
7. Exercises
- Targeted destroy ordering: build a chain (vpc → subnet → db), remove all from desired, and verify deletes run in reverse dependency order (db → subnet → vpc).
- State locking: add a lock so two concurrent
Applys can't corrupt state; prove with-race. import: add adoption of an existing live resource into state without recreating it (the real-world onboarding problem).- Plan output as a gate: write a CI check that fails if a plan
includes a
Deleteof a resource taggedprotected(a fitness function, pa-10). - Make it a reconcile loop: run
Applyon a ticker against a mutable "live" world that drifts; you've now built pa-08's GitOps self-heal.