2 — Resources, State Mechanics & Backends¶
Code: 01-basics/1-create-local-file/ · 02-gcp-terraform/z-core-gcp-resources/backend.tf · Quiz: Resources, State & Drift
Architect's Points to Keep in Mind¶
1. Resource Block Anatomy & Ownership¶
- First label (
google_service_account): fixed by the provider plugin. - Second label (
gsa): local identifier chosen by you, unique per resource type within the module. - Together,
google_service_account.gsais the resource address — the primary key interraform.tfstate.
2. Why terraform.tfstate Must Exist¶
- Identity Mapping (Load-Bearing): Cloud APIs have no native concept of "this bucket belongs to this
.tffile." State maps HCL addresses (google_service_account.gsa) to real cloud IDs (projects/.../serviceAccounts/...). - Performance: Caching attributes avoids querying every cloud resource when unnecessary and prevents API rate-limiting at scale.
- Dependency & Metadata: Stores resource dependency edges (necessary for correct destroy ordering even after a block is deleted from HCL) and provider-computed values.
3. terraform apply is Non-Atomic (Per-Resource Commit)¶
- Interview Highlight: Terraform
applyis not transactional. There is no automatic rollback if resource #7 of 7 fails. - As each individual resource finishes creation, Terraform immediately records it in state.
- If a 7-resource apply fails on #7 (e.g., a transient network error or unreachable Consul endpoint), the first 6 resources are live and durably tracked in state. Re-running
terraform planshows only1 to add(idempotent convergence).
4. Missing Resource vs. Missing State (Provider-Specific Failure Modes)¶
- Delete the real resource, keep state: During
plan, the refresh phase queries the provider, sees the resource is gone, and proposes recreating it (+ create). - Delete the state file, keep the real resource: Terraform has complete amnesia. It does not scan the cloud to rediscover orphans; it plans to create everything fresh.
- File-based providers (
local_file): silently overwrite/clobber the existing file on disk. - Cloud APIs (
google_service_account,google_storage_bucket): reject duplicate identifiers withError 409: AlreadyExists. Recovery requiresterraform import.
- File-based providers (
- Empty State File Trap: A
terraform.tfstatefile existing on disk does not prove resources are tracked — an initialized or destroyed root still has a valid JSON state file with"resources": []. Always checkterraform state list.
5. Remote Backends (GCS) & The Chicken-and-Egg Problem¶
Moving state from local disk to a remote backend (backend "gcs") provides:
- State Locking: prevents concurrent
applyruns from corrupting state. - Encryption at Rest: Google-managed encryption or CMEK (local state is always unencrypted plaintext JSON).
- Versioning & Recovery: GCS object versioning allows rolling back accidental state corruption.
Two Backend Constraints to Remember:
- Bootstrap Chicken-and-Egg:
terraform initconnects to the backend before parsing anyresourceblocks. Therefore, a Terraform configuration cannot create the GCS bucket that stores its own state. Create the state bucket out-of-band (gcloud storage buckets create/gsutil mb) or via a dedicatedbootstraproot. - No Variables in
backend {}: Backend blocks cannot interpolatevar.*orlocal.*. Use a partial backend configuration (backend "gcs" {}) and pass-backend-config="bucket=..." -backend-config="prefix=..."duringterraform init.- Gotcha: Omitting
-backend-configon a partialgcsbackend does not error with "missing bucket argument" — Terraform queries GCS with an empty bucket name and returnsstorage: bucket doesn't exist ... 404.
- Gotcha: Omitting
6. The plan Read-Access Security Constraint¶
- Running
terraform planrequires read access to the state file. - Because state stores every attribute in plaintext (including secrets marked
sensitive = true), anyone who has IAM permissions to runterraform planagainst a state file can read every secret inside that state (terraform state pull). - This is the primary architectural reason to move
plan/applyoff developer laptops and into isolated CI/CD runners.
Code Reference¶
resource "local_file" "pet" {
filename = "/tmp/hello.txt"
content = "Hello! Let the terraform learning commence...."
file_permission = "0700"
}
# Experiment 1: Drift recovery (state intact, file deleted)
terraform init && terraform apply -auto-approve
rm /tmp/hello.txt
terraform plan # Refresh detects missing file -> plans 1 to add
# Experiment 2: State amnesia (file intact, state deleted)
rm terraform.tfstate*
terraform plan # No memory of file -> plans 1 to add (silently clobbers on apply)
# backend.tf — Partial configuration (no hardcoded bucket names in Git)
terraform {
backend "gcs" {}
}
# resources.tf — First managed cloud resource
resource "google_service_account" "gsa" {
account_id = "acme-web-app"
display_name = "My Test App"
}
# 1. Bootstrap bucket out-of-band (globally unique name, versioning enabled)
gsutil mb -l us-central1 gs://YOUR-UNIQUE-BUCKET-tfstate
gsutil versioning set on gs://YOUR-UNIQUE-BUCKET-tfstate
# 2. Initialize & migrate local state to GCS
terraform init \
-backend-config="bucket=YOUR-UNIQUE-BUCKET-tfstate" \
-backend-config="prefix=core-gcp-resources/state"
# 3. Verify state ledger
terraform state list
terraform state show google_service_account.gsa