#Terraform #OpenTofu #GoogleCloud
It's easy to forget that you can actually still program (a little)
with Terraform. One way I like is to use the count and for_each
meta arguments with resources.
Let's for example create an simple instance in GCP
resource "google_compute_disk" "simple_disk" {
name = "simple_boot_disk"
type = "pd-standard"
zone = "us-central1-a"
size = 20
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
resource "google_compute_instance" "simple_instance" {
name = "simple-instance"
machine_type = "n4-standard-2"
zone = "us-central1-a"
boot_disk {
auto_delete = "true"
device_name = "simple_boot_disk"
mode = "READ_WRITE"
source = google_compute_disk.simple_disk.self_link
}
}
# Terraform resouces that will be created:
google_compute_disk.simple_disk
google_compute_instance.simple_instance
This works! Terraform will create a boot disk and also an instance using that boot disk. But what if we want to create multiples of the same resources? To do this we can use the count meta argument.
resource "google_compute_disk" "simple_disk" {
count = 2
name = "simple_boot_disk_${count.index}"
type = "pd-standard"
zone = "us-central1-a"
size = 20
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
resource "google_compute_instance" "simple_instance" {
count = 2
name = "simple-instance-${count.index}"
machine_type = "n4-standard-2"
zone = "us-central1-a"
boot_disk {
auto_delete = "true"
device_name = "simple_boot_disk"
mode = "READ_WRITE"
source = google_compute_disk.simple_disk[count.index].self_link
}
}
# Terraform resouces that will be created:
google_compute_disk.simple_disk["0"]
google_compute_disk.simple_disk["1"]
google_compute_instance.simple_instance["0"]
google_compute_instance.simple_instance["1"]
Perfect! This is an easy way to tell terraform that we want multiples
of similar resources. Notice that when we need to reference them
in our terraform we have to pass the index like this resource_type.name[index]
to tell terraform which one we want to access.
Now what if we wanted to have multiple resources, but they have
different arguments for some of the settings? While count gets
the job done for simple situations, for_each allows us to iterate
over maps or sets of strings.
Lets try this again but instead setup a map with a local and
iterate over it with for_each.
locals {
simple_servers = {
server1 = {
disk_type = "pd-standard"
disk_size = 20
zone = "us-central1-a"
machine_type = "n4-standard-2"
}
server2 = {
disk_type = "pd-ssd"
disk_size = 100
zone = "us-central1-b"
machine_type = "n4-standard-8"
}
}
}
resource "google_compute_disk" "simple_disk" {
for_each = local.simple_servers
name = "simple_boot_disk_${each.key}"
type = each.value.disk_type
zone = each.value.zone
size = each.value.disk_size
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
resource "google_compute_instance" "simple_instance" {
for_each = local.simple_servers
name = "simple-instance-${each.key}"
machine_type = each.value.machine_type
zone = each.zone
boot_disk {
auto_delete = "true"
device_name = "simple_boot_disk"
mode = "READ_WRITE"
source = google_compute_disk.simple_disk[each.key].self_link
}
}
# Terraform resouces that will be created:
google_compute_disk.simple_disk["server1"]
google_compute_disk.simple_disk["server2"]
google_compute_instance.simple_instance["server1"]
google_compute_instance.simple_instance["server2"]
Awesome! We now have way more flexability while customizing our
resources. By default for_each will set the index of each resource
to the key of the current element being iterated over. So when we
want to reference another resource that is created with our map,
we use the same syntax as before but with each.key as the index.
Since the key for our current resource will match the key of the
referenced resource.
Lastly, in some specific situations you might want to control
the index in our for_each. To do this you use the syntax below.
for_each = {
for item in local.items :
item.name => item
}
This looks a little confusing but we can break it down. We are
creating our own for loop over a map named local.items. Then
we are setting our own index with one of item values item.name
and finally pointing the index at the item in the map.
We are essentially dynamically creating our own map in our
for_each loop! Like I mentioned before this is extreamly
situational but it's good to know how to do this.
Lets update our main code to do this and use the zone as our index.
locals {
simple_servers = [
{
disk_type = "pd-standard"
disk_size = 20
zone = "us-central1-a"
machine_type = "n4-standard-2"
},
{
disk_type = "pd-ssd"
disk_size = 100
zone = "us-central1-b"
machine_type = "n4-standard-8"
}
]
}
resource "google_compute_disk" "simple_disk" {
for_each = {
for server in local.simple_servers :
server.zone => server
}
name = "simple_boot_disk_${each.value.zone}"
type = each.value.disk_type
zone = each.value.zone
size = each.value.disk_size
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
resource "google_compute_instance" "simple_instance" {
for_each = {
for server in local.simple_servers :
server.zone => server
}
name = "simple-instance-${each.value.zone}"
machine_type = each.value.machine_type
zone = each.zone
boot_disk {
auto_delete = "true"
device_name = "simple_boot_disk"
mode = "READ_WRITE"
source = google_compute_disk.simple_disk[each.key].self_link
}
}
# Terraform resouces that will be created:
google_compute_disk.simple_disk["us-central1-a"]
google_compute_disk.simple_disk["us-central1-b"]
google_compute_instance.simple_instance["us-central1-a"]
google_compute_instance.simple_instance["us-central1-b"]
Note: since we set the index to the value of
zone, we can access that value with eithereach.value.zoneoreach.key