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

  1. Targeted destroy ordering: build a chain (vpc → subnet → db), remove all from desired, and verify deletes run in reverse dependency order (db → subnet → vpc).
  2. State locking: add a lock so two concurrent Applys can't corrupt state; prove with -race.
  3. import: add adoption of an existing live resource into state without recreating it (the real-world onboarding problem).
  4. Plan output as a gate: write a CI check that fails if a plan includes a Delete of a resource tagged protected (a fitness function, pa-10).
  5. Make it a reconcile loop: run Apply on a ticker against a mutable "live" world that drifts; you've now built pa-08's GitOps self-heal.