Joseph D. Marhee
Aug 6 · 3 min read

Recent additions to Terraform 0.12.x include the use of a for_each keyword, which has been a long-awaited addition, and one with a lot of great uses for structures in Terraform like map .

I had a project recently that could benefit immensely from passing a map of locations for my resources, and in the past, I would need to instantiate a module for each location, and let Terraform take it from there.

However, because each of these module instances didn’t need to exist discretely, and in fact, needed to be identical, it made sense to manage all instances of this configuration as a single module (with an option to continue to instantiate modules to segregate resources, etc.) with a map of locations I’d like the modules to spin-up in.

Let’s say I wanted to create a module, modules/cluster_pool , and I wanted to create a server, a network, attach the network, and then cluster the servers (for example a Kubernetes cluster). Previously, iterating a list of facilities in which to deploy to, the quickest, most durable way to do this would’ve been to do something like:

module "cluster_name_nrt1" {
source = "modules/cluster_pool"

cluster_name = "your_cluster_name"
count = "${var.count}"
plan_primary = "${var.plan_primary}"
plan_node = "${var.plan_node}"
facility = "nrt1"
auth_token = "${var.auth_token}"
project_id = "${var.project_id}"
}

in ./main.tf for example, and in the cluster_pool module, I’d define these resources, using that single facility value passed through:

...
variable "facility" {}
...

resource "packet_reserved_ip_block" "packet-k3s" {
...
facility = "${var.facility}"
...
}

...
resource "packet_device" "k3s_primary" {
...
facilities = ["${var.facility}"]
...
}

resource "packet_bgp_session" "test" {
device_id = "${packet_device.k3s_primary.id}"
address_family = "ipv4"
}

resource "packet_ip_attachment" "kubernetes_lb_block" {
device_id = "${packet_device.k3s_primary.id}"
cidr_notation = "${packet_reserved_ip_block.packet-k3s.cidr_notation}"
}

...

resource "packet_device" "arm_node" {
...
facilities = ["${var.facility}"]
...
}

rinse and repeat for additional clusters, where you might apply doing something like:

terraform apply -target=module.cluster_pool_somefacility

for each instance.

However, this was not ideal for my use case (a lot of disposable, homogenized clusters), and this new feature in Terraform allows me to create a cluster per facility in my facilities map, so if I define in my vars a map like:

variable "facilities" {                                                                                                                                                                                                                                                                                                                                                                                                                      type = "map"                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          default = {                                                                                                                                                                                                                                                                                                                                                                                                                                   newark  = "ewr1"                                                                                                                                                                                                                                                                                                                                                                                                                           narita  = "nrt1"                                                                                                                                                                                                                                                                                                                                                                                                                           sanjose = "sjc1"                                                                                                                                                                                                                                                                                                                                                                                                                          }                                                                                                                                                                                                                                                                                                                                                                                                                                        }

and then in modules/cluster_pool/main.tf update each resource to parse this map, then I can add and remove facilities by updating the map and then applying (and using the new lifecycle features, how that impacts the rest of the resources is more flexible now, as well!).

In our case, we are passing facilities in the module definition above as a map (instead of a list), so in our module, we’ll see resource definitions like this:

resource "packet_reserved_ip_block" "packet-k3s" {                                                                                                                                                                                                                                                                                                                                                                                           ...                                                                                                                                                                                                                                                                                                                                                                                                          for_each   = var.facilities                                                                                                                                                                                                                                                                                                                                                                                                                facility = "${each.value}"                                                                                                                                                                                                                                                                                                                                                                                                                 ...                                                                                                                                                                                                                                                                                                                                                                                                                   }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     data "template_file" "controller" {                                                                                                                                                                                                                                                                                                                                                                                                          template = "${file("${path.module}/controller.tpl")}"                                                                                                                                                                                                                                                                                                                                                                                      for_each = var.facilities                                                                                                                                                                                                                                                                                                                                                                                                                  vars = {                                                                                                                                                                                                                                                                                                                                                                                                                                     packet_network_cidr = "${packet_reserved_ip_block.packet-k3s[each.key].cidr_notation}"                                                                                                                                                                                                                                                                                                                                                     ...                                                                                                                                                                                                                                                                                                                                                                                              }                                                                                                                                                                                                                                                                                                                                                                                                                                        }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     resource "packet_device" "k3s_primary" {                                                                                                                                                                                                                                                                                                                                                                                                     ...                                                                                                                                                                                                                                                                                                                                                                                                for_each = var.facilities                                                                                                                                                                                                                                                                                                                                                                                                                  facilities = ["${var.facilities[each.key]}"]                                                                                                                                                                                                                                                                                                                                                                                               ...                                                                                                                                                                                                                                      }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     billing_cycle = "hourly"                                                                                                                                                                                                                                                                                                                                                                                                                   project_id    = "${var.project_id}"                                                                                                                                                                                                                                                                                                                                                                                                      }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     resource "packet_bgp_session" "test" {                                                                                                                                                                                                                                                                                                                                                                                                       for_each = var.facilities                                                                                                                                                                                                                                                                                                                                                                                                                  device_id      = "${packet_device.k3s_primary[each.key].id}"                                                                                                                                                                                                                                                                                                                                                                               ...                                                                                                                                                                                                                                                                                                                                                                                                                 }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     resource "packet_ip_attachment" "kubernetes_lb_block" {                                                                                                                                                                                                                                                                                                                                                                                      for_each = var.facilities                                                                                                                                                                                                                                                                                                                                                                                                                  device_id     = "${packet_device.k3s_primary[each.key].id}"                                                                                                                                                                                                                                                                                                                                                                                cidr_notation = "${packet_reserved_ip_block.packet-k3s[each.key].cidr_notation}"                                                                                                                                                                                                                                                                                                                                                         }

So, as shown here, you’re able to map the resources to those other resources in the same manner as before, but using the facilities map to locate them per-facility (so the IP attachment in ewr1 for example, can find the corresponding server, which can also locate the corresponding network you provisioned for that facility).

Like maps in any other context, you can reference the data by key name (which works best when locating resources linked by that map, as we saw above), or if you need to use that data stored in the map, itself, you can reference the value :

for_each   = var.facilities                                                                                                                                                                                                                                                                                                                                                                                                                facility = "${each.value}"

Using this pattern allows you to further generalize how your state is generated, tracked, and applied, and more tightly control how your infrastructure interacts and is mapped to each other; this feature in particular, in my case, adds immense value in allowing me to test better, and deploy more efficiently by folding, what used to be, multiple instances into a single module (creating a pool of cluster pools — where the map indicates the size of the pool-, for example!).

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade