Objective
By the end of this lab, you will have:
- Created a module with a clear interface.
- Called the module from a root configuration.
- Refactored the module call to use module inputs.
- Verified the refactor does not destroy the real-world resource.
Requirements
- A Linux or macOS workstation with shell access.
- The Terraform CLI 1.9.x or later installed.
Scenario
You have a configuration that creates a file. The configuration has grown. The next refactor is to extract the file into a module so the same pattern can be reused.
Tasks
Task 1: Create the working directory
mkdir -p ~/rb-modules-lab/root
mkdir -p ~/rb-modules-lab/modules/greeting
cd ~/rb-modules-lab
Task 2: Create the module
Create modules/greeting/variables.tf:
variable "filename" {
type = string
description = "The path to the greeting file."
}
variable "content" {
type = string
description = "The content of the greeting file."
default = "Hello from Terraform!\n"
}
Create modules/greeting/main.tf:
terraform {
required_version = ">= 1.9.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
resource "local_file" "greeting" {
filename = var.filename
content = var.content
}
output "filename" {
value = local_file.greeting.filename
description = "The path to the greeting file."
}
output "size" {
value = local_file.greeting.content_length
description = "The size of the greeting file in bytes."
}
Create modules/greeting/outputs.tf:
output "filename" {
value = local_file.greeting.filename
description = "The path to the greeting file."
}
output "size" {
value = local_file.greeting.content_length
description = "The size of the greeting file in bytes."
}
Task 3: Create the root configuration
Create root/main.tf:
terraform {
required_version = ">= 1.9.0"
}
module "greeting" {
source = "../modules/greeting"
filename = "${path.module}/greeting.txt"
content = "Hello from the modules lab!\n"
}
Create root/outputs.tf:
output "greeting_file" {
value = module.greeting.filename
}
output "greeting_size" {
value = module.greeting.size
}
Task 4: Initialise and apply
cd ~/rb-modules-lab/root
terraform init
terraform apply
Verify:
cat ~/rb-modules-lab/root/greeting.txt
The file is created.
Task 5: Inspect the state
terraform state list
Expected:
module.greeting.local_file.greeting
The state has the module resource.
Task 6: Refactor the root to use a variable
Edit root/main.tf:
terraform {
required_version = ">= 1.9.0"
}
variable "greeting_content" {
type = string
default = "Hello from the modules lab!\n"
}
module "greeting" {
source = "../modules/greeting"
filename = "${path.module}/greeting.txt"
content = var.greeting_content
}
Run the plan:
terraform plan
The plan may or may not propose changes. If the content is the same, the plan is empty. If the content is different, the plan proposes to update the file.
Task 7: Move the module to a deeper path
Suppose the module is renamed from greeting to hello. This
is the production test of module refactoring.
Edit root/main.tf:
module "hello" {
source = "../modules/greeting"
filename = "${path.module}/greeting.txt"
content = var.greeting_content
}
Add a moved block:
moved {
from = module.greeting
to = module.hello
}
module "hello" {
source = "../modules/greeting"
filename = "${path.module}/greeting.txt"
content = var.greeting_content
}
Run the plan:
terraform plan
The plan is empty (or close to empty). The state has been moved
from module.greeting.local_file.greeting to
module.hello.local_file.greeting.
Task 8: Verify the state
terraform state list
The state has the new module path.
Task 9: Verify the file is unchanged
cat ~/rb-modules-lab/root/greeting.txt
The file content is unchanged.
Task 10: Remove the moved block
The moved block is one-shot. After the refactor, it can be removed.
Validation
The lab is successful if:
- The module was created with a clear interface.
- The root configuration called the module.
- The refactor renamed the module without destroying the real-world resource.
- The plan is empty after the refactor.
Expected Outcome
At the end of the lab:
+---------------------------------+
| ~/rb-modules-lab/ |
| modules/ |
| └── greeting/ |
| ├── main.tf |
| ├── outputs.tf |
| └── variables.tf |
| root/ |
| ├── greeting.txt |
| └── main.tf |
+---------------------------------+
The greeting.txt file is created. The state has the new
module path. The plan is empty.
Cleanup
cd ~/rb-modules-lab/root
terraform destroy
rm -rf .terraform .terraform.lock.hcl terraform.tfstate*
The module and root configurations are the artefacts worth keeping.
What You Learned
You learned the module pattern:
- The module has a clear interface. Inputs are variables; outputs are the results.
- The root configuration calls the module. The modules interface is the contract.
- The moved block preserves the state. Renaming a module does not destroy the real-world resources.
- The plan is empty after the refactor. The refactor is invisible to the apply.