How to For_Each Through a List(Objects) in Terraform 0.12

I need to deploy a list of GCP compute instances. How do I loop for_each through the "vms" in a list of objects like this:

    "gcp_zone": "us-central1-a",
    "image_name": "centos-cloud/centos-7",
    "vms": [
      {
        "hostname": "test1-srfe",
        "cpu": 1,
        "ram": 4,
        "hdd": 15,
        "log_drive": 300,
        "template": "Template-New",
        "service_types": [
          "sql",
          "db01",
          "db02"
        ]
      },
      {
        "hostname": "test1-second",
        "cpu": 1,
        "ram": 4,
        "hdd": 15,
        "template": "APPs-Template",
        "service_types": [
          "configs"
        ]
      }
    ]    
}
3

9 Answers

I work a lot with iterators in Terraform, they always gave me bad headaches. Therefore I identified five of the most common iterator patterns (code examples are given below), which helped me construct a lot of nice modules (source).

  1. Using for_each on a list of strings
  2. Using for_each on a list of objects
  3. Using for_each to combine two lists
  4. Using for_each in a nested block
  5. Using for_each as a conditional

Using for_each and a list of strings is the easiest to understand, you can always use the toset() function. When working with a list of objects you need to convert it to a map where the key is a unique value. The alternative is to put a map inside your Terraform configuration. Personally, I think it looks cleaner to have a list of objects instead of a map in your configuration. The key usually doesn't have a purpose other than to identify unique items in a map, which can thus be constructed dynamically. I also use iterators to conditionally deploy a resource or resource block, especially when constructing more complex modules.

1. Using for_each on a list of strings

locals {
  ip_addresses = ["10.0.0.1", "10.0.0.2"]
}

resource "example" "example" {
  for_each   = toset(local.ip_addresses)
  ip_address = each.key
}

2. Using for_each on a list of objects

locals {
  virtual_machines = [
    {
      ip_address = "10.0.0.1"
      name       = "vm-1"
    },
    {
      ip_address = "10.0.0.1"
      name       = "vm-2"
    }
  ]
}    

resource "example" "example" {
  for_each   = {
    for index, vm in local.virtual_machines:
    vm.name => vm # Perfect, since VM names also need to be unique
    # OR: index => vm (unique but not perfect, since index will change frequently)
    # OR: uuid() => vm (do NOT do this! gets recreated everytime)
  }
  name       = each.value.name
  ip_address = each.value.ip_address
}

3. Using for_each to combine two lists

locals {
  domains = [
    "",
    ""
  ]
  paths = [
    "/one",
    "/two",
    "/three"
  ]
}
    
resource "example" "example" {
  # Loop over both lists and flatten the result
  urls = flatten([
    for domain in local.domains : [
      for path in local.paths : {
        domain = domain
        path   = path
      }
    ]
  ]))
}

4. Using for_each on a nested block

# Using the optional() keyword makes fields null if not present
variable "routes" {
  type = list(
    name   = string
    path   = string
    config = optional(object({
      cache_enabled = bool
      https_only    = bool
    }))
  default = []
}

resource "example" "example" {
  name = ...
  
  dynamic "route" {
    for_each = {
      for route in var.routes :
      route.name => route
    }
    content {
      # Note: <top_level_block>.value.<object_key>
      name = route.value.name
    }
    
    dynamic "configuration" {
      # Note: <top_level_block>.value.<optional_object_key>
      for_each = route.value.config != null ? [1] : []
      content {
        cache_enabled = route.value.config.cache_enabled
        https_only    = route.value.config.https_only
      }
    }
  }

5. Using for_each as a conditional

variable "deploy_example" {
  type        = bool
  description = "Indicates whether to deploy something."
  default     = true
}

# Using count and a conditional, for_each is also possible here.
# See the next solution using a for_each with a conditional.
resource "example" "example" {
  count      = var.deploy_example ? 0 : 1
  name       = ...
  ip_address = ...
}

variable "enable_logs" {
  type        = bool
  description = "Indicates whether to enable something."
  default     = false
}

resource "example" "example" {
  name       = ...
  ip_address = ...

  # Note: dynamic blocks cannot use count!
  # Using for_each with an empty list and list(1) as a readable alternative. 
  dynamic "logs" {
    for_each = var.enable_logs ? [] : [1]
    content {
      name     = "logging"
    }
  }
}
5

Seem's like I found what to do. If you pass not the maps of maps but the list of maps you can use such code

resource "google_compute_instance" "node" {
    for_each = {for vm in var.vms:  vm.hostname => vm}

    name         = "${each.value.hostname}"
    machine_type = "custom-${each.value.cpu}-${each.value.ram*1024}"
    zone         = "${var.gcp_zone}"

    boot_disk {
        initialize_params {
        image = "${var.image_name}"
        size = "${each.value.hdd}"
        }
    }

    network_interface {
        network = "${var.network}"
    }

    metadata = {
        env_id = "${var.env_id}"
        service_types = "${join(",",each.value.service_types)}"
  }
}

It will create actual number of instance and when you remove for example middle one of three(if you create three:)), terraform will remove what we asked.

3

From Terraform 1.3, you can use the for_each and objects with modules like the following:

modules/google_compute_instance/variables.tf

variable "hosts" {
    type = map(object({
        cpu           = optional(number, 1)
        ram           = optional(number, 4)
        hdd           = optional(number, 15)
        log_drive     = optional(number, 300)
        template      = optional(string, "Template-New")
        service_types = list(string)
      }))
    }
Maya Lin-Takahashi

Maya Lin-Takahashi

Consumer Tech & Gadget Reviewer

Maya is a hardware enthusiast who tests and reviews smart home devices, smartphones, wearables, and audio gear. She focuses on practical consumer value and build quality.