Using some loop with count and for_each. Availability zones and server count

Im trying to do a module for a module build. I have a count statement for the vm build but now I need to use a for each to rotate through a list.

locals {
zone = toset([“1”,“2”,“3”])
}
resource “azurerm_windows_virtual_machine” “main” {

for_each = locals.zones

zone = each.value

count = var.itemCount

and the Module would look like this:

module “webservers” {

source = “./module”

itemcount = “3”


The problem is terraform does not allow count and for_each in the same module / block. I need this because I’m doing a count for the server number and a for_each to stick the vms in a availability zone.

Help please!!!

Hi @tluv2006,

When working with for_each our task is to construct a collection that has one element for each of the instances we want to create. In your case, it seems like you want three instances per each element in your collection. A different way to say that is that you want one instance for each unique combination of elements from local.zone and elements from the sequence [0, 1, 2].

We can construct a sequence like [0, 1, 2] using the range function:

> range(3)
[0, 1, 2]

To combine that with your set of zones we can use nested for expressions along with flatten so that the result is just a flat list, rather than a list of lists:

locals {
  # (I renamed the zones to letters so that they'd
  # be more distinct from the instance indices.)
  zones         = ["a", "b", "c"]
  instance_idxs = range(var.itemcount)

  instances = flatten([
    for z in local.zones : [
      for i in local.instance_idxs : {
        zone  = z
        index = i
      }
    ]
  ])
}

The local.instances value will now look something like this:

[
  { zone = "a", index = 0 },
  { zone = "a", index = 1 },
  { zone = "a", index = 2 },
  { zone = "b", index = 0 },
  { zone = "b", index = 1 },
  { zone = "b", index = 2 },
  { zone = "c", index = 0 },
  { zone = "c", index = 1 },
  { zone = "c", index = 2 },
]

That list contains one element per instance of azurerm_windows_virtual_machine you want to create, so all that remains is to project that into a map in the for_each expression so that the map keys can be used to track each individual instance:

resource "azurerm_windows_virtual_machine" "main" {
  for_each = { for inst in local.instances : "${i.zone}_${i.index}" => i }

  name = "example_${each.key}"
  zone = each.value.zone
  # ...
}

The above tells Terraform to track these instances using addresses like this:

  • azurerm_windows_virtual_machine.main["a_0"]
  • azurerm_windows_virtual_machine.main["a_1"]
  • azurerm_windows_virtual_machine.main["a_2"]
  • azurerm_windows_virtual_machine.main["b_0"]
  • azurerm_windows_virtual_machine.main["b_1"]

This means that if you add a new item d to local.zones, Terraform will understand that as a request to create three new instances and leave the others undisturbed:

  • azurerm_windows_virtual_machine.main["d_0"]
  • azurerm_windows_virtual_machine.main["d_1"]
  • azurerm_windows_virtual_machine.main["d_2"]

…and if you decrease var.itemcount to 2 then Terraform will understand that as a request to delete all of the ones with index 2, leaving indices 0 and 1 intact:

  • azurerm_windows_virtual_machine.main["a_2"]
  • azurerm_windows_virtual_machine.main["b_2"]
  • azurerm_windows_virtual_machine.main["c_2"]
  • azurerm_windows_virtual_machine.main["d_2"]

For this reason, it’s important to ensure that the keys in the for_each map capture your intended unique identifier for each instance: Terraform will use the keys to understand how to interpret a change to the input as a planned change to the remote objects you’re managing with Terraform.

2 Likes