Loop with list/map

With this variable:

variable "slo_groups" {
  description = "Groups and values"
  default = {
    "group1" = { sev = ["10", "20", "30"], group = "group1" }
    "group2" = { sev = ["10", "20", "40"], group = "group2" }
  }
}

I would like to use one resource declaration to create 6 resources, 3 for group1, 3 for group2. With a local variable named slo_pairs, a resouce would look a bit like:

resource "my_resource" "iop_mttr_slos" {
  for_each  = local.slo_pairs
  query     =  "... ${each.key}}) ...  ${each.value} "
}

I’ve been trying to use a nested loop to create a list of maps like:

group1 = 10
group1 = 20
gruop1 = 30
...

If I use this:

locals {
  slo_pairs = flatten([
    for assignment_group in var.slo_groups : [
      for sev in assignment_group.sev : {
        group      = assignment_group.group
        slo_target = assignment_group.sev
      }
    ]
  ])

  group_slo_list = {
    for k in local.slo_pairs : k.group => k.slo_target...
  }
}

I end up with a tuple, I think like:

{
group1 = [10, 20, 30]
gruop2 = [10,20,30]
}

in which case my tf plan only tries to create 2, rather than 6 values & fails with:

    | each.value is tuple with 3 elements

without the elipses, the tf-plan returns duplicate key errors:
Error: Duplicate object key

Seems like what I want to do is possible, but I just don’t see the path. I want to hand the resource a list of pairs. Is this possible?

I’d be happy to work with a more simple variable as well, like:

variable "slo_groups" {
  default = {
    "group1" =  ["10", "20", "30"]
    "group2" =  ["10", "20", "30"]
}

but trying to work with mapping has seemed to overcomplicate things.

Hi @mlindes,

I think I see two things in your example that I’d suggest changing. The first is that in your slo_pairs expression you seem to be using the whole list of sev values for the slo_target each object, rather than the individual sev string. To address that, we can change the slo_target expression to be sev rather than assignment_group.sev:

locals {
  slo_pairs = flatten([
    for assignment_group in var.slo_groups : [
      for sev in assignment_group.sev : {
        group      = assignment_group.group
        slo_target = sev
      }
    ]
  ])
}

This should produce a list like this:

[
  { group = "group1", sev = "10" },
  { group = "group1", sev = "20" },
  { group = "group1", sev = "30" },
  { group = "group2", sev = "10" },
  { group = "group2", sev = "20" },
  { group = "group2", sev = "30" },
]

The other thing we need to take care of is that when we convert this to a map we need to make sure all of the elements have unique keys, which requires including both the group and sev values in the key strings.

resource "my_resource" "iop_mttr_slos" {
  for_each  = { for p in local.slo_pairs : "${p.group}-${p.sev}" => p }
  # ...
}

This meets the two important rules for for_each: there’s one element in the map for each instance you want to create, and each element of the map has a key that contains sufficient information to make it unique.

Inside that resource block you can use each.value.group and each.value.sev to access the group and sev strings. You wouldn’t use each.key in here because it’s serving only as a compound unique identifier, and so its value is unlikely to be useful for anything else.

The result of this should be six resource instances with the following addresses:

  • my_resource.iop_mttr_slos["group1-10"]
  • my_resource.iop_mttr_slos["group1-20"]
  • my_resource.iop_mttr_slos["group1-30"]
  • my_resource.iop_mttr_slos["group2-10"]
  • my_resource.iop_mttr_slos["group2-20"]
  • my_resource.iop_mttr_slos["group2-30"]
3 Likes

Thank you so much. This solved my problem exactly… minus one slip on the list produced.
Using slo_target = sev in the locals declaration ends up with a list like:

[
  { group = "group1", slo_target = "10" },
  { group = "group1", slo_target = "20" },
  { group = "group1", slo_target = "30" },
  ...

so that the for_each loop needs to reference slo_target rather than sev:

resource "my_resource" "iop_mttr_slos" {
  for_each  = { for p in local.slo_pairs : "${p.group}-${p.slo_target}" => p }
  # ...
}

Thanks so much for the clarification. This highlights a few key concepts well.

Oh yes! When working with a configuration that contains a bunch of terms that are meaningless to me it’s easy to mix them up sometimes… :slight_smile: I’m sorry and glad you figured out what I meant!