Infrastructure Automation — Terraform¶
Overview¶
Wrap terraform/tofu CLI safely from Python for fmt, validate, and plan summaries, inspect state cautiously, and know when CDK for Terraform (cdktf) fits.
Python rarely replaces HCL — it orchestrates Terraform in CI: run checks, parse plan JSON, fail pipelines, and report. Never auto-apply from a tutorial cron.
Complete Kubernetes Python Client first. Diagrams use Excalidraw only.
This is a core tutorial in Module 19 · Infrastructure of the REBASH Academy Python for Cloud & DevOps Engineers series — written for Cloud, DevOps, Platform, and SRE engineers.
Prerequisites¶
Required¶
- Linux Automation — subprocess
- Optional:
terraformortofuonPATH
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Run fmt/validate/plan via subprocess with timeouts
- Capture plan JSON for reporting
- Explain state inspection risks
- Summarise cdktf’s role vs HCL
- Default to no apply
Architecture¶
This topic’s control points and relationships are shown below.
Theory¶
What it is¶
Infrastructure as Code (IaC) with Terraform is usually authored in HashiCorp Configuration Language (HCL). Python’s role in this course is orchestration around the CLI: format, validate, plan, parse machine-readable plan JSON, and gate Continuous Integration (CI) on policy — not reinventing providers. Optionally, CDK for Terraform (CDKTF) lets teams define stacks in Python that synthesise to Terraform JSON when language reuse matters.
Why it matters¶
Click-ops drift and unreviewed apply cause outages. Wrapping Terraform in Python (or any language) standardises timeouts, exit codes, artefact upload of tfplan, and checks such as “no unexpected destroys.” Platform teams build these wrappers once so every pipeline behaves the same. Understanding the boundary — HCL owns resources; Python owns process and policy — keeps both layers honest.
How it works¶
Prefer a fixed sequence: terraform fmt -check, validate, plan -out=tfplan, then terraform show -json tfplan for analysis. Invoke with subprocess.run([...], timeout=..., capture_output=True, text=True) — never shell=True with interpolated paths. CI parses the JSON for create/update/delete counts and fails when policy forbids deletes. State backends hold secrets and real inventory: protect them; never commit terraform.tfstate. state list / state show are read-sensitive operations.
terraform fmt -check -recursive
terraform validate
terraform plan -out=tfplan
terraform show -json tfplan
Key concepts and comparisons¶
| Approach | Role |
|---|---|
| HCL + CLI wrappers | Default for most ops teams |
| CDKTF (Python/TS) | Synthesise Terraform; language reuse |
| Pure cloud SDKs | Bypass Terraform — different trade-offs |
| Command | Tutorial default |
|---|---|
| fmt / validate / plan | Allowed |
| apply / destroy | Require --apply + extra guard |
Common pitfalls¶
- Running apply from a laptop against shared state without a lock or ticket.
- Parsing human
plantext instead of-json. - Committing state or provider credentials.
- Skipping
timeout=so stuck providers hang CI forever. - Treating CDKTF as “no need to understand Terraform state” — state still exists.
Hands-on Lab¶
Focus: practise the core workflow for Infrastructure Automation — Terraform
mkdir -p ~/rebash-python/module-19
cd ~/rebash-python/module-19
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
command -v terraform || command -v tofu || echo "CLI optional — fixture path available"
Step 1 – Mini module + wrapper¶
cd ~/rebash-python/module-19
source .venv/bin/activate
mkdir -p tf
cat > tf/main.tf << 'EOF'
terraform {
required_version = ">= 1.5.0"
}
output "greeting" {
value = "rebash-module-19"
}
EOF
cat > tf_wrap.py << 'EOF'
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path
def terraform_bin() -> str:
for name in ("terraform", "tofu"):
path = shutil.which(name)
if path:
return path
raise RuntimeError("terraform/tofu not found on PATH")
def run(bin_: str, args: list[str], cwd: Path) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[bin_, *args],
cwd=cwd,
capture_output=True,
text=True,
timeout=120,
)
def main() -> int:
p = argparse.ArgumentParser(description="Terraform wrapper")
p.add_argument("--chdir", type=Path, default=Path("tf"))
p.add_argument("--apply", action="store_true", help="forbidden in tutorial path")
p.add_argument("--fixture-plan", type=Path, help="skip CLI; read plan JSON")
args = p.parse_args()
if args.apply:
print("error: --apply disabled in this tutorial", file=sys.stderr)
return 2
if args.fixture_plan:
data = json.loads(args.fixture_plan.read_text(encoding="utf-8"))
print(json.dumps({"resource_changes": len(data.get("resource_changes", []))}))
return 0
try:
bin_ = terraform_bin()
except RuntimeError as exc:
print(f"error: {exc}", file=sys.stderr)
print("hint: use --fixture-plan fixtures/plan.json", file=sys.stderr)
return 1
init = run(bin_, ["init", "-backend=false", "-input=false"], args.chdir)
if init.returncode != 0:
sys.stderr.write(init.stderr)
return init.returncode
val = run(bin_, ["validate", "-json"], args.chdir)
sys.stdout.write(val.stdout or "")
if val.returncode != 0:
sys.stderr.write(val.stderr)
return val.returncode
if __name__ == "__main__":
raise SystemExit(main())
EOF
mkdir -p fixtures
cat > fixtures/plan.json << 'EOF'
{
"resource_changes": [
{"address": "null_resource.a", "change": {"actions": ["create"]}}
]
}
EOF
python tf_wrap.py --fixture-plan fixtures/plan.json
Step 2 – Live validate (optional)¶
Step 3 – Policy sketch¶
Write: fail CI if plan contains delete on production workspaces unless change ticket present.
Validation¶
- Lab commands run under
~/rebash-python/module-19/ - You can explain each Theory section in your own words
- You used modern tooling where it applies to this topic
- You can describe one production failure mode for this topic
Code Walkthrough¶
Production practice for Infrastructure Automation — Terraform always combines:
- Inspect before you change (status, plan, logs, dry-run)
- Prefer reversible, documented changes (Git, IaC, drop-ins, version pins)
- Capture evidence (command output, pipeline logs) for handovers
- Prefer current tools and APIs over legacy shortcuts
- Least privilege — escalate credentials only when required
Keep runbooks short enough to follow under pressure. Automate checks; keep humans for judgement.
Security Considerations¶
- Treat credentials and tokens for python as privileged — never commit them
- Prefer short-lived auth (OIDC, roles, SSO) over long-lived keys
- Validate blast radius before apply/deploy/delete operations
- Restrict who can approve production changes
- Collect audit logs; limit who can read sensitive traces
Common Mistakes¶
Running apply from a laptop against shared state without a lock or ticket.
Validate assumptions against the Theory section and official docs before changing production.
Parsing human plan text instead of -json.
Lab shortcuts (open security groups, admin roles, skip approvals) must not ship unchanged.
Changing production without a rollback path
Always know how to revert (previous artefact, prior release, state rollback, DNS failback).
Best Practices¶
- Encode Infrastructure Automation — Terraform changes as code and review them in pull requests
- Pin versions (images, modules, actions, provider plugins)
- Separate environments with clear promotion gates
- Alert on symptoms with runbooks attached
- Destroy lab resources; tag everything with owner and expiry where possible
Troubleshooting¶
| Symptom | Likely cause | What to do |
|---|---|---|
| Provider download fails | Network in CI | Cache plugins; -backend=false for validate-only |
| State locked | Concurrent apply | Don’t apply from wrappers casually |
| Wrong binary | tofu vs terraform | Detect both |
Summary¶
- Python orchestrates Terraform CLI; HCL stays source of truth
- fmt/validate/plan in CI; apply is gated
- cdktf is an alternative authoring path
- Lab: Terraform Wrapper
Interview Questions¶
- How does Infrastructure Automation — Terraform show up when operating Cloud or production platforms?
- What would you check first if this area misbehaves in production?
- Which modern tools or APIs replace older equivalents here?
- What security control should accompany this capability?
- How would you automate verification of this topic in CI?
Sample answer — question 2
Start with blast radius and recent changes, gather evidence (logs, status, plan/diff), then fix forward with a known rollback path — not guesswork.