Create multiple resources in for_each context

Hello,

I’m currently stuck in finding a way to combine a count in a for_each ( yes, I know it’s not possible :slight_smile: )
I have a list of objects representing VM settings:

vms = [
  { 
    purpose = "webfront"
    size = "Standard"
    ...
  },
  { 
     purpose = "app"
     size = "large"
     ...
  }
]

no problem with that, it can be managed pretty easily with a count or a for_each.
Now I would like to add the possibility to create multiple VM of a kind.
For instance:

vms = [
  { 
    number_to_create = 1 
    purpose = "webfront"
    size = "Standard"
    ...
  },
  {  
     number_to_create = 2
     purpose = "app"
     size = "large"
     ...
  }
]

I tried to do it by using the sum function over the number_to_create values to count the total number of VM to create, but the problem is that considering how lists are created, the result is not consistent. For example, with the above case I will have 2 webfront and 1 app because the list is probably created recursively.

count = 3 => [ "webfront","app","webfront" ]

What I would need is to “count” the number_to_create in “for-each” vm …

Do you have any idea on how I could achieve this ?

Thank you for your precious help

Hi @philhenrihector,

To get the result you want you will need to write an expression that transforms your list of objects representing groups of VMs into a map of objects that each represent just one VM.

Some Terraform language tools for that include:

To elaborate on that a little, the approach I would take to solve this would be:

  1. Use two nested levels of for expression to transform your list of VM groups into a list of lists of individual VMs, with the inner for expression using range(vm_group.number_to_create) to reinterpret the number of instances into a list of that length.

    In this step you’ll probably want to produce a list of a new object type that has the number_to_create attribute replaced with index, so that you can use the indices to distinguish the multiple instances in each group.

  2. Use flatten to collapse that list of lists into a single flat list of instances, no longer grouped.

  3. Use one more for expression to project the flattened list into a map that’s suitable for use in for_each. In particular, you’ll need to decide on a suitable map key scheme so that each VM has a unique key.

    For example, if you generated VM objects that have both purpose and index attributes then the unique map keys could be generated as "${vm.purpose}:${vm.index}".

Something to watch out for is that since you are starting with a list you won’t have any guarantee that the purpose strings are unique and so if two VM groups have the same purpose then the above identifier scheme will generate a collision, and thus the final for expression will fail with an error. I assume that such a failure would be acceptable, since it would then prompt the person who made that change to correct it.

Thank you very much mate !
I missed the range function :smiley:

locals {
    vm = [
        {
            number = 3
            purpose = "app"
        },
        {
            number = 2
            purpose = "database"
        }
    ]
}

output result {
    value = { for key,vm in local.vm : vm.purpose => flatten([
            for i in range(vm.number) : {
                name = format("%s%s",vm.purpose,i+1)
            }
        ])
    }
}

gave me

result = {
      + app      = [
          + {
              + name = "app1"
            },
          + {
              + name = "app2"
            },
          + {
              + name = "app3"
            },
        ]
      + database = [
          + {
              + name = "database1"
            },
          + {
              + name = "database2"
            },
        ]
    }

Thank you again, you made me saved a lot of time and a clearer code !

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.