One block can make many things
In OpenTofu, a variable is a named value that a project reads, so the same files can be used with different values. Two more tools let one block make several things: count makes numbered copies and for_each makes one copy for each name in a list.
A market with ten lanes and six stalls does not need sixteen blocks.
By the end of this lesson
Note
I can make several of a thing from a variable.
Key words
| Word | What it means | Example |
|---|---|---|
| Variable | a named value given to a project | var.lanes |
| Output | a value a project prints when it finishes | |
| count | makes that many copies, numbered from 0 | |
| for_each | makes one copy for each name | |
| Instance | one copy made by count or for_each | |
| Address | the unique name of one instance | terraform_data.lane[0] |
The cells students fill in are left blank in this preview.
A variable is a value set from outside
A variable block gives a value a name, a type and, usually, a default. The rest of the project reads it as var.name.
You can replace the default with -var name=value on the command line. An output block prints a value when the apply finishes, so you can read the result without opening the state.
A project with two variables and an output
# main.tf (the three blocks are shown in the order the file holds them)
variable "market" {
type = string
default = "Saturday market"
}
variable "lanes" {
type = number
default = 2
}
resource "terraform_data" "lane" {
count = var.lanes
input = format("%s lane %d", var.market, count.index + 1)
}
output "first_lane" {
value = terraform_data.lane[0].output
}
Make a folder for the market
mkdir -p ~/iac-demo/marketShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Go into it
cd ~/iac-demo/marketShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the file shown above
printf '%s\n' 'variable "market" {' ' type = string' ' default = "Saturday market"' '}' '' 'variable "lanes" {' ' type = number' ' default = 2' '}' '' 'resource "terraform_data" "lane" {' ' count = var.lanes' ' input = format("%s lane %d", var.market, count.index + 1)' '}' '' 'output "first_lane" {' ' value = terraform_data.lane[0].output' '}' > 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 successfullyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Apply with the default of two lanes
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.
Read the output
tofu output -no-colorShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
count makes numbered copies
Setting count = var.lanes makes that many copies of the block. Inside it, count.index is the copy's position, and it starts at 0.
Each copy has its own address, with the position in square brackets. Growing the count adds copies at the end. Shrinking it removes copies from the end.
Worked example: lanes set to 3
| count.index | Address | Input it gets |
|---|---|---|
| 0 | terraform_data.lane[0] | Saturday market lane 1 |
| 1 | terraform_data.lane[1] | Saturday market lane 2 |
| 2 | terraform_data.lane[2] | Saturday market lane 3 |
The cells students fill in are left blank in this preview.
Plan four lanes instead of two
tofu plan -no-color -var lanes=4 | grep -E "will be|^Plan"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Plan one lane instead of two
tofu plan -no-color -var lanes=1 | grep -E "will be|^Plan"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
for_each makes one copy for each name
for_each takes a set of names, or a map, and makes one copy for each. Inside the block, each.key is that copy's name.
The address carries the name in quotes and square brackets. A set has no repeats and OpenTofu puts the names in order, so the order you type them does not matter.
Add named stalls to the same file
printf '%s\n' '' 'variable "stalls" {' ' type = set(string)' ' default = ["bread", "cakes"]' '}' '' 'resource "terraform_data" "stall" {' ' for_each = var.stalls' ' input = format("%s: %s", var.market, each.key)' '}' >> main.tfShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Plan: two stalls to create
tofu plan -no-color | grep -E "will be|^Plan"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Apply
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 every address 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.
A common mistake
Warning
Many people think count and for_each are two spellings of one tool. They are not. count gives copies a position, so [0] means the first. for_each gives copies a name, so ["bread"] always means the bread stall. Use names whenever the things have names.
Expanding both into addresses
# Expand count and for_each into the names OpenTofu uses.
lanes = 3
stalls = ["cakes", "bread", "cakes"]
print("count:")
for i in range(lanes):
print(" terraform_data.lane[" + str(i) + "]")
# A set has no repeats, and OpenTofu puts the keys in order.
keys = []
for name in stalls:
if name not in keys:
keys.append(name)
keys.sort()
print("for_each:")
for key in keys:
print(' terraform_data.stall["' + key + '"]')
# Change lanes to 5 and add "fruit" to stalls, then run it again.