For_each null value

I am currently creating a new Module that creates EC2’s. I have a simple fore_each statement that works nicely. In the module I also need to allow the creation of multiple ENI’s. But these are optional so some EC2’s will not require extra ENI’s.

If I used count I could easily do this:

count = var.enable_dhcp_options ? 1 : 0

But I want to use a for_each within the Resource so cannot use count. Is this possible?

locals {

  is_t_instance_type = replace(var.instance_type, "/^t(2|3|3a){1}\\..*$/", "1") == "1" ? true : false

}

resource "aws_instance" "this" {

  for_each      = var.ec2

  ami           = var.ami

  instance_type = var.instance_type

  

  vpc_security_group_ids               = var.vpc_security_group_ids

  subnet_id                            = each.value["subnet"]

  private_ip                           = each.value["private_ip"]

  #user_data                            = var.user_data

  #user_data_base64                     = var.user_data_base64

  key_name                             = var.key_name

  monitoring                           = var.monitoring

  get_password_data                    = var.get_password_data

  iam_instance_profile                 = var.iam_instance_profile

  associate_public_ip_address          = var.associate_public_ip_address

  ebs_optimized                        = var.ebs_optimized

  disable_api_termination              = var.disable_api_termination

  instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior

  placement_group                      = var.placement_group

  tenancy                              = var.tenancy

    credit_specification {

    cpu_credits = local.is_t_instance_type ? var.cpu_credits : null # move to var?

  }

}

#######

# ENI

#######

resource "aws_network_interface" "mgmt_eth" {

  for_each      = var.ec2

  subnet_id = each.value["subnet_mgt"]

  security_groups = var.mgt_sg 

variables are set :

variable "ec2" {

  type = map(object({

    subnet        = string

    name = string

    private_ip = string

      }))

}

variable "eni_mgt" {

  type = map(object({

    subnet_mgt = string

      }))

}

and module called:

module "ec2" {

  source = "../"

  ami                         = "ami-1234"

  instance_type               = "t3.small"

  vpc_security_group_ids      = ["sg-1234"]

  ec2 = {

    web1 = {

        name = "jpppp1"

        subnet = "subnet-1234"

        private_ip = ""


    },

    web2 = {

        name = "jpppp2"

        subnet = "subnet-5678"

        private_ip = ""


    }

}

eni_mgt = {

    web1 = {

        subnet_mgt = ""

    },

    web2 = {

        subnet_mgt = ""

    }

}

}

Using the above I expect 2 EC2’s (which work) and no mgmt_eth. But it trys to create 2 mgmt_eth.

# module.ec2.aws_network_interface.mgmt_eth["web2"] will be created
  + resource "aws_network_interface" "mgmt_eth" {
      + id                 = (known after apply)
      + ipv6_address_count = (known after apply)
      + ipv6_addresses     = (known after apply)
      + mac_address        = (known after apply)
      + outpost_arn        = (known after apply)
      + private_dns_name   = (known after apply)
      + private_ip         = (known after apply)
      + private_ips        = (known after apply)
      + private_ips_count  = (known after apply)
      + security_groups    = (known after apply)
      + source_dest_check  = true

      + attachment {
          + attachment_id = (known after apply)
          + device_index  = (known after apply)
          + instance      = (known after apply)
        }
    }

I have tried explicitly setting source_dest_check to null or “” but neither work.

Any help would be great.

Please reformat your code properly using triple backticks this helps reading and copy-pasting it for testing.

Your for_each iterates on var.ec2 not evaluating subnet_mgt. If that’s a condition then it has to be added.

Hi @jprouten,

The main requirement for for_each is that you provide a map with one element per instance you want to declare. From that requirement it follows that in order to create zero instances you’d need to provide a map with zero elements.

There are lots of ways to conditionally create a map with zero elements, but one concise way I like is to use a for expression with an if clause that filters out some or all of the elements based on a condition.

In your case it sounds like you either want to include either all of the elements or none of them, and so that would involve an if expression which only refers to globals, and not to anything about the current element:

  for_each = {
    for k, v in var.ec2 : k => v
    if var.enable_ec2
  }

For the above to work you would need to have a variable called enable_ec2 withtype = bool. When that variable is false, if will find its expression returns false for every element and so the for expression will produce an empty map.

Although you don’t need it in this particular case, you might also find it interesting to know that you can use this if clause to filter out only some of the elements of the map, by writing an if clause that includes references to k and/or v to return true only for elements that should be kept:

  for_each = {
    for k, v in var.ec2 : k => v
    if v.name != "forbidden"
  }

The above is a silly, unrealistic example of skipping just instances whose name is "forbidden". I doubt anyone would need to do exactly that, but hopefully you can see from that how you might use this in other situations to use only a subset of the elements of a given map.

1 Like

Thank you for useful examples. I done a lot of HCL code using your answers.