Create resources iterating through the values of a map rather key

Hi All,

I couldn’t figure out how I can loop through the sum of values of the below map.

variable "images" {
  default = {
    "rhel-8-factory-os-ready" = {
       "availability_zone" = "eu-fra-1ah"
       "flavor" = 4
       "instance_count" = 2
       "image_name" = "rhel-8-factory-os-ready"
    },
    "rhel-7-factory-os-ready" = {
       "availability_zone" = "eu-fra-1ai"
       "instance_count" = 3
       "flavor" = 3
       "image_name" = "rhel-7-factory-os-ready"
    },
    "rhel-6-factory-os-ready" = {
       "availability_zone" = "eu-fra-1ah"
       "instance_count" = 3
       "flavor" = 3
       "image_name" = "rhel-6-factory-os-ready"
    }
  }
}

Here, I’ve to iterate through the sum of instance_count attribute of the all the keys & create instances based on the instance_count.

I could calculate the sum of instance_count, with below inbuilt functions.

locals {
  list_sum = length(flatten([for i in var.images: range(i["instance_count"])]))
}

How can I iterate through the list_sum variable & create the resources based on instance_count?

I created below lists to create resources ::

locals {
  list_images = tolist(keys(var.images))
  list_instance_count = [for i in var.images: i["instance_count"]]
  list_flavors = [for i in var.images: i["flavor"]]
  list_image_names = [for i in var.images: i["image_name"]]
  list_availability_zones = [for i in var.images: i["availability_zone"]]
}

My resource ::

resource "openstack_compute_instance_v2" "instance" {
  count = local.list_sum
  image_name = element(local.list_image_names, count.index +1 )
  flavor_id = element(local.list_flavors, (count.index + 1) )
  name = element(local.list_image_names, (count.index + 1) )
  security_groups = var.security_group
  availability_zone = element(local.list_availability_zones, (count.index + 1) )
  key_pair = "foptst"
  network {
    name = var.network_name
  }
}

By now, you may be knowing that my iteration is incorrect. My resource block has to create the number of resources based on instance_count var ie., 2 instances of rhel-8-factory-os-ready, 3 instances of rhel-7-factory-os-ready and 3 instances of rhel-6-factory-os-ready.

Because of incorrect looping, I couldn’t get it. It would be great if someone could help me how to iterate properly to create resources as expected.

Many Thanks in advance,
Harsha

1 Like

Hi @harshavmb,

The goal when working with for_each or count is always to transform your input value into a collection that has one element per instance you want to create, which commonly involves the flatten function. So in this case, we need one element per instance_count per image, I think.

locals {
  # A list of objects with one object per instance.
  instances = flatten([
    for image_key, image in var.images : [
      for index in range(image.instance_count) : {
        availability_zone = image.availability_zone
        flavor            = image.flavor
        instance_index    = index
        image_key         = image_key
        image_name        = image.image_name
      }
    ]
  ])
}

The above works by initially constructing a list of lists. The first level of lists represents the images, and the second level represents the instances for each image, created using range. The above is a combination of the example in the flatten docs and the example in the range docs.

We can then use local.instances as part of the for_each argument in the resource block:

resource "openstack_compute_instance_v2" "instance" {
  for_each = {
    # Generate a unique string identifier for each instance
    for inst in local.instances : format("%s-%02d", inst.image_key, inst.instance_index + 1) => inst
  }

  image_name        = each.value.image_name
  flavor_id         = each.value.flavor
  name              = each.key
  security_groups   = var.security_groups
  availability_zone = each.value.availability_zones
  key_pair          = "foptst"
  network {
    name = var.network_name
  }
}

This uses for_each instead of count, because count is for creating a number of equivalent instances, where all are redundantly performing the same function. In this case, the instances are differentiated by the image they are created from and so using for_each allows us to customize the tracking keys to include the image keys, giving instance addresses like this:

  • openstack_compute_instance_v2.instance["rhel-8-factory-os-ready-01"]
  • openstack_compute_instance_v2.instance["rhel-8-factory-os-ready-02"]
  • openstack_compute_instance_v2.instance["rhel-7-factory-os-ready-01"]
  • openstack_compute_instance_v2.instance["rhel-7-factory-os-ready-02"]
  • openstack_compute_instance_v2.instance["rhel-7-factory-os-ready-03"]
  • openstack_compute_instance_v2.instance["rhel-6-factory-os-ready-01"]
  • openstack_compute_instance_v2.instance["rhel-6-factory-os-ready-02"]
  • openstack_compute_instance_v2.instance["rhel-6-factory-os-ready-03"]

If you were to decrease instance_count for rhel-7-factory-os-ready on a subsequent run, Terraform would plan to destroy openstack_compute_instance_v2.instance["rhel-7-factory-os-ready-03"] and leave all of the other instances untouched, because it can see that there is no longer an element with key "rhel-7-factory-os-ready-03" in the for_each map.

3 Likes

Awesome. This is what I was expecting. I’m no more using count. for_each is great.
Many Thanks :slight_smile: