Two shops, one recipe
An OpenTofu module is a folder of files that a project can call again and again, each time with its own values. It matters because a fix made once in the module reaches every place that calls it.
A company runs a corner shop and a hill shop with the same set-up. Copying the files works until a fix is needed, and then it is made twice. A module is the recipe written once.
By the end of this lesson
Note
I can call a module twice and read what each copy hands back.
Key words
| Word | What it means | Example |
|---|---|---|
| Module | a folder of files that you can use again | |
| Module call | a block that uses a module and gives it values | module "hill" |
| Output | a value a module hands back | |
| Validation | a rule that refuses a bad input | |
| Module address | the path of a resource, starting with module | module.hill.terraform_data.sign |
The cells students fill in are left blank in this preview.
A module is a folder you call
A module is a folder of OpenTofu files. The project that uses it is the root. The root writes a module call, names the folder in source, and gives each input a value.
Two calls make two separate copies of everything in the folder. The source here is a local path, so nothing is downloaded.
Make the folders and go in
mkdir -p ~/iac-demo/mods/modules/shop && cd ~/iac-demo/modsShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the module
printf '%s\n' 'variable "name" {' ' type = string' ' validation {' ' condition = length(var.name) >= 3 && length(var.name) <= 10' ' error_message = "The name must have 3 to 10 characters."' ' }' '}' '' 'resource "terraform_data" "sign" {' ' input = format("%s: open", var.name)' '}' '' 'output "sign" {' ' value = terraform_data.sign.output' '}' > modules/shop/main.tfShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the root: two calls and one output
printf '%s\n' 'module "corner" {' ' source = "./modules/shop"' ' name = "Corner"' '}' '' 'module "hill" {' ' source = "./modules/shop"' ' name = "Hill"' '}' '' 'output "signs" {' ' value = [module.corner.sign, module.hill.sign]' '}' > main.tfShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Prepare the folder
tofu init -no-color | grep -E "Initializing modules|corner|hill|successfully"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Plan, and look at the addresses
tofu plan -no-color | grep -E "will be created|^Plan"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A module hands back outputs
A resource inside a module has a longer address, such as module.hill.terraform_data.sign. The root cannot read it directly.
Only an output crosses the wall. The root reads it as module.hill.sign: the call name, then the output name.
Apply, so the state holds both copies
tofu apply -auto-approve -no-color | grep "Apply complete"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
List the addresses in the state
tofu state listShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Read the output the root made
tofu output signsShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think the root can read any resource inside a module. It cannot. A module shares only what its outputs name, so a value you want outside needs an output block.
Read a resource that has no output
printf '%s\n' 'output "bad" {' ' value = module.corner.terraform_data.sign' '}' > bad.tf; tofu validate -no-color 2>&1 | grep -E "Error|Unsupported"; rm bad.tfShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A validation rule refuses a bad input
A validation block holds a condition and an error_message. When the condition is false, OpenTofu stops before it plans anything and prints the message.
The module here accepts a name of 3 to 10 characters. Try a name that is too short.
Give the hill shop a short name
sed -i 's/"Hill"/"Hi"/' main.tf; tofu validate -no-color 2>&1 | grep -E "Error|must have"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Put the name back and check it
sed -i 's/"Hi"/"Hill"/' main.tf; tofu validate -no-colorShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Two calls worked out in Python
# Work out the addresses a module call makes.
resources = ["sign", "door"]
calls = {"corner": None, "hill": None}
for call in calls:
for name in resources:
print("module." + call + ".terraform_data." + name)
# Change "hill": None to "hill": ["a", "b"] and look again.
Now do it in the lab
The Runs built a module in a practice folder. The lab has a folder for this too, ~/tofu-lab/shop, and part 7 of its README says what to build. It is not the same module, so you must change what you saw, not copy it.
How it is marked
Your teacher marks it by running a check on your saved lab. The check applies your project, then calls your module again with names and a status you have never seen. A module with the answers typed in cannot follow those, and neither can one with no validation rule.
Going further: a call with count
Add count = 2 to the hill call, and change module.hill.sign in the output to module.hill[0].sign. Run tofu plan -no-color | grep -E "will be|^Plan".
Which addresses does the plan show now, and why did the output line need to change?