Terraform: How to use multiple locals and Variables inside "for_each"

11,437

The configuration you shared in your question is asking Terraform to manage four instances, each of which has four network interfaces associated with it. That's problematic in two different ways:

  • All for of the network interfaces on each instance are configured with the same device_index, which is invalid and is what the error message here is reporting.
  • Even if you were to fix that, it would then try to attach the same four network interfaces to four different EC2 instances, which is invalid: each network interface can be attached to only one instance at a time.

To address that and get the behavior you wanted, you only need one network_interface block, whose content is different for each of the instances:

locals {
  instance_ami = {
    A = "ami-11111"
    B = "ami-22222"
    C = "ami-33333"
    D = "ami-4444"
  }
}

variable "instance_eni" {
  description = "Pre created Network Interfaces"
  default = [
    {
      name = "A"
      id   = "eni-0a15890a6f567f487"
    },
    {
      name = "B"
      id   = "eni-089a68a526af5775b"
    },
    {
      name = "C"
      id   = "eni-09ec8ad891c8e9d91"
    },
    {
      name = "D"
      id   = "eni-0fd5ca23d3af654a9"
    }
  ]
}

locals {
  # This expression is transforming the instance_eni
  # value into a more convenient shape: a map from
  # instance key to network interface id. You could
  # also choose to just change directly the
  # definition of variable "instance_eni" to already
  # be such a map, but I did it this way to preserve
  # your module interface as given.
  instance_network_interfaces = {
    for ni in var.instance_eni : ni.name => ni.id
  }
}

resource "aws_instance" "instance" {
  for_each = local.instance_ami

  ami           = each.value
  instance_type = var.instance_type
  key_name      = var.keypair

  root_block_device {
    delete_on_termination = true
    volume_size           = 80
    volume_type           = "gp2"
  }

  network_interface {
    device_index          = 0
    network_interface_id  = local.instance_network_interfaces[each.key]
    delete_on_termination = false
  }
}

Now each instance has only one network interface, with each one attaching to the corresponding ENI ID given in your input variable. Referring to each.key and each.value is how we can create differences between each of the instances declared when using resource for_each; we don't need any other repetition constructs inside unless we want to create nested repetitions, like having a dynamic number of network interfaces for each instance.

Share:
11,437
Prashant Shetage
Author by

Prashant Shetage

Cloud and DevOps Engineer.

Updated on June 15, 2022

Comments

  • Prashant Shetage
    Prashant Shetage almost 2 years

    I have a terraform template that creates multiple EC2 instances. I then create a few Elastic Network interfaces in the AWS console and added them as locals in the terraform template. Now, I want to map the appropriate ENI to the instance hence I added locals and variables as below.

    locals {
      instance_ami = {
        A  = "ami-11111"
        B = "ami-22222"
        C  = "ami-33333"
        D   = "ami-4444"
      }
    }
    
    variable "instance_eni" {
      description = "Pre created Network Interfaces"
      default = [
        {
          name = "A"
          id   = "eni-0a15890a6f567f487"
        },
        {
          name = "B"
          id   = "eni-089a68a526af5775b"
        },
        {
          name = "C"
          id   = "eni-09ec8ad891c8e9d91"
        },
        {
          name = "D"
          id   = "eni-0fd5ca23d3af654a9"
        }
      ]
    }
    
    
    resource "aws_instance" "instance" {
      for_each = local.instance_ami
    
      ami           = each.value
      instance_type = var.instance_type
      key_name      = var.keypair
    
      root_block_device {
        delete_on_termination = true
        volume_size           = 80
        volume_type           = "gp2"
      }
    
    
      dynamic "network_interface" {
        for_each = [for eni in var.instance_eni : {
          eni_id = eni.id
        }]
    
        content {
          device_index          = 0
          network_interface_id  = network_interface.value.eni_id
          delete_on_termination = false
        }
      }
    }
    

    I am getting below error:

    Error: Error launching source instance: InvalidParameterValue: Each network interface requires a unique device index. status code: 400, request id: 4a482753-bddc-4fc3-90f4-2f1c5e2472c7

    I think terraform is tyring to attach all 4 ENI's to single instance only. What should be done to attach ENI's to an individual instance?

  • Prashant Shetage
    Prashant Shetage about 4 years
    Hi Martin, getting below error with the above configuration. "Error: Invalid index network_interface_id = local.instance_network_interfaces[each.key] |---------------- | each.key is "A" | local.instance_network_interfaces is object with 4 attributes The given key does not identify an element in this collection value." However, I am able to achieve the goal with: network_interface_id = lookup(var.instance_eni, each.key)
  • Prashant Shetage
    Prashant Shetage about 4 years
    For the above, I had updated the instance_eni variable as below. variable "instance_eni" { default = { A = "eni-0a15890a6f567f487" B = "eni-089a68a526af5775b" C = "eni-09ec8ad891c8e9d91" D = "eni-0fd5ca23d3af654a9" } }