Skip to content

Milestone 2: Real Terraform workflow - #4

Merged
howlcipher merged 6 commits into
mainfrom
milestone-2-real-terraform
Aug 13, 2026
Merged

howlcipher merged 6 commits into
mainfrom
milestone-2-real-terraform

Conversation

@howlcipher

Copy link
Copy Markdown
Owner

What changed

Adds RealTerraformTool (real fmt/init/validate/plan via subprocess) alongside the existing SimulatedTerraformTool, a new devops-learn terraform command with its own vertical-slice workflow, real committed Terraform config for the bundled projects/api_platform sample, Terraform CI checks, three project review subagents, a destructive-command Bash hook, and a project CLAUDE.md.

Why

docs/roadmap.md already scoped this as Milestone 2 (real Terraform depth before Milestone 3's Azure vertical slice). No Azure credentials are available on the dev machine this was built on, so the milestone deliberately stops at a trustworthy plan — everything that doesn't require Azure access is real and tested; plan requires credentials and fails cleanly, with an explanation, without them.

Real Terraform capabilities

  • terraform fmt -check, init -input=false, validate -json, and plan (via plan -out= + show -json) all run for real via RealTerraformTool (src/devops_learn/tools/terraform_tool.py).
  • terraform show -json is parsed into {create, change, replace, destroy} and fed unchanged into the existing validation/terraform_plan_analysis.analyze() risk classifier.
  • Only devops-learn terraform uses RealTerraformTool; analyze --real-tools, local, and review keep SimulatedTerraformTool for the terraform tool name.

What remains simulated

  • apply_approved_plan / destroy_approved_environment are not declared on RealTerraformTool at all — invoking either raises KeyError rather than silently falling back to simulated behavior. They stay on SimulatedTerraformTool until Milestone 3.
  • No real Azure API/resource calls anywhere. No Azure SDK dependency was added.

Safety / approval design

  • Every RealTerraformTool operation is RiskLevel.SAFE, requires_approval=False — no new approval-gated path was introduced this milestone.
  • tools/_subprocess_safety.py adds an explicit timeout and secret redaction (KEY=value-shaped) to every real subprocess call, plus output truncation.
  • plan parsing only ever extracts {address, action} from terraform show -json — never raw before/after attribute values — as the primary defense against leaking sensitive plan data; free-text redaction is defense in depth on top of that.
  • New .claude/hooks/confirm_destructive_command.py (PreToolUse on Bash) forces an explicit "ask" confirmation before terraform apply/destroy, az group/resource delete, git push --force, etc., as a backstop against those commands running directly via Bash outside ToolService.
  • Three read-only review subagents (.claude/agents/{terraform,security,learning}-reviewer.md) were run against this diff during development; their findings (an invalid ACR resource name, a hook crash on malformed payloads, missing learner-profile gating on teaching moments, a dead related_artifact field, and more) are all fixed in this branch.

Credential model

No credentials are used or required for fmt/init/validate. plan needs az login or ARM_CLIENT_ID/ARM_CLIENT_SECRET/ARM_TENANT_ID/ARM_SUBSCRIPTION_ID; without them it fails cleanly and workflows/terraform_flow.py explains why rather than treating it as a bug. Nothing in this PR reads or writes real credentials.

Cost implications

None. No apply path exists yet, so no Azure resource can be created by this code.

Terraform/Azure learning objectives

devops-learn terraform teaches Terraform state, provider, and resource-address concepts just-in-time (gated by learner profile, matching local_flow.py's precedent — a learner already strong in Terraform sees no teaching moments), with docs/terraform-state.md as the fuller reference. A credential-less plan failure is turned into an explicit diagnosis of Azure authentication rather than a hidden error.

Test results

  • pytest: 116 passed
  • flake8 src tests projects/api_platform/app projects/api_platform/tests: clean
  • mypy (platform, strict): clean, 102 files
  • mypy app --strict (demo app): clean, 4 files
  • terraform fmt -check -recursive / terraform init -backend=false / terraform validate against projects/api_platform/infra/terraform/: pass

Manual verification

Ran devops-learn terraform end to end on the dev machine: real fmt/init/validate succeed; plan fails cleanly with a real az login error (no Azure credentials on this machine), correctly diagnosed by the workflow. Verified the learner-profile gating skips both teaching moments when terraform=strong is set. Verified the related_artifact cross-link now renders. Verified the destructive-command hook against sample stdin JSON for all nine target patterns plus edge cases (case-insensitivity, -f short flag, malformed payload shapes).

Known limitations

  • Real Terraform execution only targets the bundled projects/api_platform sample; there's no on-the-fly config generation for arbitrary projects yet.
  • The plan-failure auth-diagnosis heuristic is a coarse substring match ("az login"/"authoriz"/"authent") — it fails safe (falls through to a generic message) but won't catch every possible azurerm auth error phrasing.
  • terraform show -json output is redacted/truncated (20k char cap) before JSON parsing; a plan large enough to exceed that cap would fail to parse. Not a concern at this project's current 3-resource scale.

Next milestone

Milestone 3: first Azure vertical slice (resource group + ACR provisioned for real, apply bound to an approved plan, independent Azure verification via az, a real deploy of the bundled FastAPI app, one troubleshooting exercise, and a safe drift exercise) — per docs/roadmap.md, gated on the user setting up real Azure credentials.

🤖 Generated with Claude Code

howlcipher and others added 6 commits August 12, 2026 16:54
Shells out to a real terraform binary for fmt/init/validate/plan only --
apply/destroy stay on SimulatedTerraformTool until Milestone 3. Adds
tools/_subprocess_safety.py for explicit timeouts and secret redaction on
every real subprocess call, since state/auth output can contain secrets.
Plan parsing extracts only {address, action} from terraform show -json,
never raw attribute values, feeding the existing risk classifier
unchanged. Also renders LearningMoment.related_artifact, which had no
real caller before this milestone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
workflows/terraform_flow.py runs fmt -> init -> validate -> plan -> risk
analysis, teaching provider/state concepts just-in-time (gated by learner
profile, matching local_flow's precedent) and diagnosing an auth failure
on plan as an expected, honest outcome rather than a bug. cli/main.py
wires RealTerraformTool in only for this command, keeping every other
command's terraform tool simulated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
projects/api_platform/infra/terraform/: resource group, container
registry, Log Analytics workspace, tagged for easy identification and
cleanup. Split into providers/variables/main/outputs.tf as a readability
convention (Terraform still merges the whole directory). Fixes an ACR
naming bug (hyphens aren't valid in that resource's name, unlike most
others) caught by live terraform validate during review. .gitignore now
excludes .terraform/ and *.tfstate* while keeping .terraform.lock.hcl
committed. ProjectAnalyzer correctly now detects this real IaC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fmt -check, init -backend=false, and validate against
projects/api_platform/infra/terraform/ -- no Azure credentials needed,
no plan/apply in CI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/terraform-state.md is the reference the in-flow teaching moments
link back to: desired vs. actual vs. Terraform state, provider, resource
address, dependencies, drift, local vs. remote state and locking, state
sensitivity, why deleting state isn't deleting infrastructure, why
hand-editing state is dangerous. Updates README/roadmap/safety/
development docs to describe exactly what's real this milestone (fmt/
init/validate/plan, no apply, no real Azure execution) and documents the
RUN_AZURE_INTEGRATION_TESTS convention Milestone 3 will need.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CLAUDE.md: durable rules on product direction, the Tool/ToolService
gateway, real-vs-simulated labeling, and quality gates. .claude/agents/:
terraform-reviewer, security-reviewer, learning-reviewer -- read-only
review personas used during this milestone's own development.
.claude/hooks/confirm_destructive_command.py: a PreToolUse Bash hook that
forces explicit confirmation before terraform apply/destroy, az group/
resource delete, git push --force, and similar commands run directly via
Bash, as a backstop for when a command bypasses ToolService entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@howlcipher
howlcipher merged commit da6a1a2 into main Aug 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant