19

Terraform Plan, State, Modules, and Physical Effects

Follow Terraform from reviewed configuration through state and AWS APIs to real compute, memory, storage, and network effects.

Run it in the public monorepo

This course is built around the public Zheta Kubernetes Lab monorepo. The excerpt below is runnable source, not pseudocode.

Source: infra/stacks/prod/main.tf

provider "aws" {
  region              = var.region
  allowed_account_ids = [var.account_id]
  assume_role {
    role_arn = var.deployer_role_arn
  }

Code to reality

Declared intent
Bind production infrastructure changes to one expected AWS account and an explicit deployer role.
Interpreter
Terraform configures the AWS provider, assumes the role, and rejects credentials for any other account.
Software effect
The plan is scoped to the production state and account boundary before resource reconciliation begins.
Hardware effect
Only an authorized apply can allocate billable AWS networking, control-plane, compute, and data resources.
Observable evidence
Caller identity, reviewed plan, remote state lock, and AWS resource tags agree on the production account.

Start with the people and the result they need

The source tables below remain the detailed contract. Begin with these customer paths:

  • D19-UC-01
    • Person: Infrastructure engineer
    • Job: Change dev EKS capacity through a reusable reviewed module
    • Observable result: Approved plan creates or updates the expected AWS resources and Zheta Forge remains reachable
  • D19-UC-02
    • Person: Incident commander
    • Job: Reconcile Terraform state with independently observed AWS reality
    • Observable result: Team chooses refresh, import, recreate, or last-resort state removal based on exact resource identity

Turn each customer job into a testable story

Now turn each customer job into a story with a result that an engineer can check:

  • D19-US-01
    • Story: As an infrastructure engineer, I want a fresh saved plan reviewed before apply, so that replacement, cost, availability, and physical effects are…
    • Observable acceptance: Commit, module/provider versions, state version, plan checksum, policy, approver, identity, resource IDs, environment, run, and probes are recorded
  • D19-US-02
    • Story: As an incident commander, I want state repair gated by independent provider inspection, so that removing a record cannot orphan a real paid resource
    • Observable acceptance: Exact address and AWS identity are resolved, empty or ambiguous matches fail, chosen repair is recorded, replan converges, and positive control passes

Add real state and observable proof

Finally trace each story through the system that owns its state and the evidence that proves the outcome:

  • D19-FLOW-01
    • Trigger: Engineer opens a dev capacity pull request
    • Responsible systems: Terraform CLI/runner, module registry or Git source, policy gate, approval service, AWS provider and APIs, probe runner
    • Authoritative state: Git for intent; remote backend for mapping; AWS APIs for resource reality
    • Owned record: TerraformRoot
    • Observable evidence: Actor, commit, plan checksum, state version, approval, expected and observed resources, environment, timestamps, AWS IDs, run, cluster, and trace IDs
    • Failure signal: Stale plan, unlocked state, unpinned provider, replacement surprise, API partial failure, or failed customer probe
  • D19-FLOW-02
    • Trigger: Plan references a resource absent from the expected AWS inventory
    • Responsible systems: Read-only state reader, AWS inventory, audit logs, repair workflow, Terraform runner
    • Authoritative state: Versioned backend for state history; AWS APIs and audit logs for existence and attribution
    • Owned record: SavedPlan
    • Observable evidence: Provider inventory, state backup/version, decision, denied unsafe attempt, repaired plan, new or adopted AWS identity, customer probes, environment,…
    • Failure signal: Wrong account or region, ambiguous target, live resource removed from state, failed import, or repeated drift

The enterprise problem and today’s slice

Enterprise problem: A small Terraform edit—Terraform is a stateful Infrastructure as Code (IaC) engine—can replace an Elastic Kubernetes Service (EKS) node group, expose a secret through state, race another operator, or leave paid AWS resources partly changed while Zheta Forge remains unavailable. Whole-course context: The incoming evidence assigns one owner to infrastructure, cluster lifecycle, Kubernetes workloads, and GitOps; today examines Terraform as the IaC implementation for the infrastructure boundary. Today’s slice: We connect HashiCorp Configuration Language (HCL), modules, plan, approval, locked remote state, providers, AWS APIs, physical effects, drift, import, and guarded state repair. End-of-day evidence: A dev run binds commit, fresh plan, policy, approval, state version, execution identity, AWS resource IDs, cluster observation, customer probe, denied unsafe change, environment, timestamp, run, and trace IDs. Still unsolved: Separate AWS account foundations and production Identity and Access Management (IAM) guardrails remain deferred.

Customer use cases

Terraform is safe only when reviewers can connect an abstract diff to actual resource and customer effects. These use cases cover planned infrastructure change and recovery from stale mapping.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D19-UC-01Infrastructure engineerChange dev EKS capacity through a reusable reviewed moduleApproved plan creates or updates the expected AWS resources and Zheta Forge remains reachablePolicy denies public exposure, broad replacement, plaintext secret, or production-scoped identity before apply
D19-UC-02Incident commanderReconcile Terraform state with independently observed AWS realityTeam chooses refresh, import, recreate, or last-resort state removal based on exact resource identityAmbiguous or still-existing resource blocks state removal; replanning proves the repaired mapping and unaffected control

Actor-centred user stories

State is not the infrastructure itself; it is Terraform’s durable mapping between resource addresses and provider objects. These stories keep that mapping recoverable and auditable.

Story IDUse case IDsUser storyObservable acceptance conditions
D19-US-01D19-UC-01As an infrastructure engineer, I want a fresh saved plan reviewed before apply, so that replacement, cost, availability, and physical effects are visibleCommit, module/provider versions, state version, plan checksum, policy, approver, identity, resource IDs, environment, run, and probes are recorded
D19-US-02D19-UC-02As an incident commander, I want state repair gated by independent provider inspection, so that removing a record cannot orphan a real paid resourceExact address and AWS identity are resolved, empty or ambiguous matches fail, chosen repair is recorded, replan converges, and positive control passes

End-to-end product flows

Running apply directly hides the proposal and can use stale assumptions, so a safe flow separates read, plan, approval, execution, and observation. State repair requires stronger proof because it changes Terraform’s memory without changing AWS.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D19-FLOW-01D19-UC-01HappyEngineer opens a dev capacity pull request1. Initialise pinned providers and locked backend. 2. Validate module inputs. 3. Refresh provider observations. 4. Save plan. 5. Review replacements and physical effects. 6. Approve exact checksum. 7. Apply with bounded identity. 8. Observe AWS, Kubernetes, and customer path.Actor, commit, plan checksum, state version, approval, expected and observed resources, environment, timestamps, AWS IDs, run, cluster, and trace IDs
D19-FLOW-02D19-UC-02RecoveryPlan references a resource absent from the expected AWS inventory1. Resolve exact state address. 2. Inspect expected account and region independently. 3. Reject empty, ambiguous, wrong-owner, or still-existing targets. 4. Prefer refresh or import when reality exists. 5. Remove state only when absence is proven. 6. Replan and observe recreation or adoption. 7. Probe affected and unaffected apps.Provider inventory, state backup/version, decision, denied unsafe attempt, repaired plan, new or adopted AWS identity, customer probes, environment, time, and run ID

The engineer proposes or repairs one infrastructure change and must finish with both AWS reality and a customer observation. Terraform exit status alone is not the outcome.

System design derived from the flows

Modules reduce repeated design decisions, but a shared module must not collapse environment state or credentials. Each environment root invokes reusable modules through a separately locked backend and execution role.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D19-UC-01Environment-root pull requestTerraform CLI/runner, module registry or Git source, policy gate, approval service, AWS provider and APIs, probe runnerGit for intent; remote backend for mapping; AWS APIs for resource realityStale plan, unlocked state, unpinned provider, replacement surprise, API partial failure, or failed customer probe
D19-UC-02State discrepancy incidentRead-only state reader, AWS inventory, audit logs, repair workflow, Terraform runnerVersioned backend for state history; AWS APIs and audit logs for existence and attributionWrong account or region, ambiguous target, live resource removed from state, failed import, or repeated drift

Terraform owns cloud foundation and a minimal Argo CD bootstrap, not GitOps-managed Zheta Forge Deployments. AWS APIs own live resource reality, while state stores Terraform’s mapping; generated-app data and roles remain in their own plane.

Data model and ownership

State may contain sensitive values and resource identities, so treating it as an ordinary build artifact creates security and concurrency failures. The model separates intent, proposed change, state version, and provider observation.

Generated-application database: Not created in this slice — Terraform manages infrastructure records and evidence; generated-application databases remain independently owned workloads on that infrastructure.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
TerraformRootGit repository, owned by Infrastructure Platformcommit_sha plus root_pathPinned module and provider refsAWS account and environment IDOne root, backend key, and execution identity per environment; plaintext secrets forbiddenPropose, review, apply, supersede, retire after dependantsD19-UC-01, D19-UC-02
SavedPlanEncrypted run store, owned by IaC Deliveryplan_checksumOpaque root commit, state version, identity, and policy refsAWS account and environment IDApply inputs must exactly match approved plan context; plan expires on any input changeGenerate, approve or deny, apply once, expire, retain metadata for auditD19-UC-01
TerraformStateVersionEncrypted locked backend, owned by Infrastructure Platformbackend_key plus version_idAWS provider resource IDsAWS account and environment IDLock serialises writers; access is least-privilege; state is never committed to GitCreate versions on apply/import/repair, recover prior version, delete after environment retentionD19-UC-01, D19-UC-02
ResourceObservationAWS inventory evidence, owned by Operationsaws_resource_arn plus observed_atOpaque Terraform address and run refsAWS account and environment IDAccount, region, immutable owner labels, role, and freshness are mandatoryCapture read-only, seal with run, expire by evidence policyD19-UC-01, D19-UC-02

The state mapping informs an explicit lifecycle decision, but AWS observation constrains it. The resulting evidence spans software records, physical allocation, Kubernetes readiness, and the Zheta Forge customer path.

Trace HCL to physical reality

Changing a node-group shape is not merely editing text: the provider may replace virtual machines, drain pods, allocate memory, and move network traffic. Review the plan for these effects before execution.

module "environment" {
  source              = "../../modules/environment"
  environment         = "dev"
  expected_account_id = var.account_id
  deployer_role_arn   = var.deployer_role_arn
  monthly_budget_usd  = 250
  region              = var.region
  vpc_cidr            = "10.10.0.0/16"
  kubernetes_version  = var.kubernetes_version
  node_instance_types = ["m7i.large"]
  node_min_size       = 2
  node_max_size       = 5
  deletion_protection = false
}
Effect fieldWhat happens
Declared intentDev EKS should have bounded worker capacity through a reusable environment module
InterpreterTerraform evaluates HCL; AWS provider translates resource changes into AWS API calls
Software effectEKS node-group configuration, instances, kubelets, runtime processes, and scheduled pods may change
Hardware effectAWS supplies vCPU, DRAM-backed memory, disks, NICs, addresses, and network paths
EvidenceSaved plan, state version, AWS resource IDs/events, ready nodes, pod rollout, and customer response

Repair state only after proving reality

terraform state rm does not delete AWS infrastructure; it only removes Terraform’s mapping and can therefore orphan a live resource. Inspect exact identity first, back up versioned state, and prefer import when the resource exists.

: "${AWS_PROFILE:?set the approved AWS profile}" "${AWS_REGION:?set the intended AWS region}" "${EXPECTED_ACCOUNT_ID:?set the intended 12-digit AWS account}"
actual_account="$(aws --profile "$AWS_PROFILE" --region "$AWS_REGION" sts get-caller-identity --query Account --output text)"
[ "$actual_account" = "$EXPECTED_ACCOUNT_ID" ] || { echo "AWS account mismatch" >&2; exit 1; }
terraform -chdir=infra/stacks/dev state show 'module.environment.aws_eks_cluster.this'
aws --profile "$AWS_PROFILE" --region "$AWS_REGION" eks describe-cluster --name zheta-forge-dev --region eu-west-2
terraform -chdir=infra/stacks/dev plan -out=verified.tfplan

Do not run a removal command from this lesson. The guarded decision is: if AWS returns the exact expected resource, refresh or import; if independent account-and-region inventory proves it absent, use the approved repair workflow, then re-plan and verify the recreated or adopted identity.

Effect fieldWhat happens
Declared intentCompare one exact Terraform address with one expected AWS cluster before choosing a repair
InterpreterTerraform reads locked state; AWS CLI reads the named account, region, and EKS API
Software effectNo state or AWS resource changes; a saved plan and inspection evidence are created
Hardware effectControl-plane APIs consume small CPU, storage, and network capacity to answer reads
EvidenceState address, AWS cluster identity, account, region, fresh plan, caller identity, timestamp, and run ID

Key takeaways

Terraform connects configuration to physical resources through a reviewed plan, state mapping, provider, and observable AWS effects.

  • State is sensitive durable mapping, not the infrastructure itself.
  • Modules reuse design while separate roots preserve environment isolation.
  • State surgery is last-resort record repair after exact independent inspection.

Checklist

An apply is complete only when its real effects and customer outcome are known.

  • [ ] Plan is fresh, saved, policy-checked, and approved by checksum
  • [ ] Backend is encrypted, locked, versioned, and environment-scoped
  • [ ] Review names replacement, capacity, cost, and availability effects
  • [ ] AWS, Kubernetes, customer, negative, and unaffected evidence are recorded