Objective
By the end of this lab, you will have:
- Written a configuration that produces several different plan outputs.
- Interpreted each plan correctly.
- Identified the action types (create, update, replace, destroy).
- Recognised known-after-apply values.
- Investigated an unexpected plan.
The lab produces several plans; the reader must interpret each correctly before applying.
Architecture
A single Terraform configuration with three resources that have different change types:
+------------------------+
| Terraform |
| ↓ |
| random_pet.name |
| local_file.greeting |
| local_file.tags |
| ↓ |
| ~/rb-plan-review-lab/ |
| ├── greeting.txt |
| └── tags.txt |
+------------------------+
The local_file.tags resource is the focus: it has a custom
lifecycle that triggers replacement on tag changes.
Requirements
- A Linux or macOS workstation with shell access.
- The Terraform CLI 1.9.x or later installed.
Scenario
You maintain a small Terraform configuration. The configuration manages two files. You are about to make a change. The change is supposed to be a simple content update. The plan tells a different story.
The lab walks through several scenarios. Each scenario asks you to interpret the plan and decide what to do.
Tasks
Task 1: Create the working directory and configuration
mkdir -p ~/rb-plan-review-lab
cd ~/rb-plan-review-lab
Create main.tf:
terraform {
required_version = ">= 1.9.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
resource "random_pet" "name" {
length = 1
}
resource "local_file" "greeting" {
filename = "${path.module}/greeting.txt"
content = "Hello, ${random_pet.name.id}!\n"
}
resource "local_file" "tags" {
filename = "${path.module}/tags.txt"
content = "Initial content.\n"
file_permission = "0644"
lifecycle {
create_before_destroy = true
}
}
Task 2: Initialise and apply
terraform init
terraform apply
Verify the files were created:
ls -la ~/rb-plan-review-lab
cat ~/rb-plan-review-lab/greeting.txt
cat ~/rb-plan-review-lab/tags.txt
Task 3: Verify the empty plan
terraform plan
Expected output:
No changes. Your infrastructure matches the configuration.
The plan is empty. The configuration matches the state.
Task 4: Scenario A — A simple in-place update
Edit main.tf to change the greeting content:
resource "local_file" "greeting" {
filename = "${path.module}/greeting.txt"
content = "Hello, ${random_pet.name.id}! Welcome.\n"
}
Run the plan:
terraform plan
Question A.1: What is the action?
Answer
The action is update in-place. The content attribute
changes; the other attributes are unchanged.
Plan: 0 to add, 1 to change, 0 to destroy.Apply:
terraform apply
Verify:
cat ~/rb-plan-review-lab/greeting.txt
Task 5: Scenario B — A no-op due to random_pet
The random_pet resource generates a name. The name is in
state. Editing the length does not affect the name.
Edit main.tf to change the random_pet length:
resource "random_pet" "name" {
length = 2
}
Run the plan:
terraform plan
Question B.1: What is the action?
Answer
The plan proposes to update the random_pet resource in place.
The length attribute changes from 1 to 2. The id attribute
(the generated name) is unchanged because the already-generated
name is preserved.
Plan: 0 to add, 1 to change, 0 to destroy.The random_pet provider is sensitive to the length change
but does not change the resulting name until a new resource is
created. The length change is mostly cosmetic.
Task 6: Scenario C — A replace due to replacement-triggering attributes
The tags.txt resource has a file_permission of 0644. The
local_file providers schema determines whether changing
file_permission triggers replacement.
Edit main.tf to change the file permission:
resource "local_file" "tags" {
filename = "${path.module}/tags.txt"
content = "Initial content.\n"
file_permission = "0600"
lifecycle {
create_before_destroy = true
}
}
Run the plan:
terraform plan
Question C.1: What is the action?
Answer
The action depends on the local_file providers schema. In
the current version of the provider, changing file_permission
does trigger replacement because the provider does not have
an in-place update API for file permissions.
The plan shows:
Plan: 0 to add, 1 to change, 0 to destroy.But the change is actually a replace. The replacement is
visible in the will be updated in-place or must be replaced
annotation. Look for the attribute-level annotation.
In the actual plan output, the change is annotated with
# forces replacement. The provider has marked the
file_permission change as a replacement.
The lifecycle.create_before_destroy = true ensures the new
file is created before the old file is destroyed.
Task 7: Scenario D — A destroy for a removed resource
Edit main.tf to remove the tags resource:
# Delete the local_file.tags block entirely
Run the plan:
terraform plan
Question D.1: What is the action?
Answer
The plan proposes to destroy the tags.txt file.
Plan: 0 to add, 0 to change, 1 to destroy.The destroyed resource is the local_file.tags resource. The
file is removed from disk during the apply.
The destroy is recoverable in this lab (the file is disposable) but is not recoverable in production. The plan is the opportunity to question the destroy.
Task 8: Scenario E — The unexpected plan
The “production plan test” scenario. The plan shows changes that were not intended.
Edit main.tf to add a new resource:
resource "local_file" "extra" {
filename = "${path.module}/extra.txt"
content = "Extra file.\n"
}
Run the plan:
terraform plan
The plan should show the new resource as 1 to add, the
greeting as 0 to change (no changes were made), and the
tags as 0 to destroy (wait — the tags block was removed in
Task 7!).
Question E.1: What is the actual plan?
Answer
The plan shows:
Plan: 1 to add, 0 to change, 0 to destroy.The plan creates the extra resource. The greeting and
name resources are unchanged. The tags resource was
already removed in Task 7; the state was updated then.
The plan is the expected plan. The configuration matches the state matches the real world.
Task 9: The unexpected plan (advanced)
Now the test: introduce a drift. Modify the greeting.txt file
manually:
echo "Manual change." > ~/rb-plan-review-lab/greeting.txt
Run the plan:
terraform plan
Question E.2: What does the plan show?
Answer
The plan refreshes the state and detects the drift. The plan output reports:
# local_file.greeting will be updated in-place
~ resource "local_file" "greeting" {
~ content = "Hello, ...! Welcome.\n" -> "Manual change.\n"The provider refresh updates the state to reflect the manual
change. The plan now proposes to reconcile the configuration
back to the configuration. The change is from Manual change.\n
to the configured value.
This is drift. The drift is detected. The plan is the recovery procedure.
Task 10: Accept the drift
terraform apply
The apply restores the file content to the configuration value.
Verify:
cat ~/rb-plan-review-lab/greeting.txt
Validation
The lab is successful if:
- The plans in each scenario were interpreted correctly.
- The plan output matched the expectation.
- The apply did not produce unexpected changes.
Expected Outcome
At the end of the lab:
+---------------------------------+
| ~/rb-plan-review-lab/ |
| .terraform/ |
| .terraform.lock.hcl |
| extra.txt |
| greeting.txt |
| main.tf |
| tags.txt (deleted) |
+---------------------------------+
The greeting.txt content is the configured value. The tags.txt
is deleted. The extra.txt is created. The state is consistent
with the configuration.
Cleanup
cd ~/rb-plan-review-lab
terraform destroy
rm -rf .terraform .terraform.lock.hcl terraform.tfstate*
What You Learned
You learned the plan-review workflow:
- Read the summary line. The summary is the operational artefact.
- For each change, identify the action type. Create, update, replace, destroy. Each has different operational implications.
- For each replace, find the trigger. The plan shows
# forces replacementfor the attribute that triggers the replacement. - Investigate unexpected plans. A plan that differs from your expectation is evidence of a problem.
- Drift is detected by the plan. A manually-modified
resource appears in the plan as
update in-placeto restore the configuration.