Invalid for_each argument during the aws_lb_target_group_attachment

Hi there,
I have two target_groups - one for port 80 and another for 443. Also have two instances as the members and I need to attach both of the target groups to each instance. So this is the code I’m using, to attach:

// Creates the target-group
resource "aws_lb_target_group" "nlb_target_groups" {
  for_each = {
    for lx in var.nlb_listeners : "${lx.protocol}:${lx.target_port}" => lx
  }
  name                 = "${var.vpc_names[var.idx]}-tgr-${each.value.target_port}"
  deregistration_delay = var.deregistration_delay
  port                 = each.value.target_port
  protocol             = each.value.protocol
  vpc_id               = var.vpc_ids[var.idx]
  proxy_protocol_v2    = true

  health_check {
    port                = each.value.health_port
    protocol            = each.value.protocol
    interval            = var.health_check_interval
    healthy_threshold   = var.healthy_threshold
    unhealthy_threshold = var.unhealthy_threshold
  }
}

// Attach the target groups to the instance(s)
resource "aws_lb_target_group_attachment" "tgr_attachment" {
  for_each = {
    for pair in setproduct(keys(aws_lb_target_group.nlb_target_groups), var.elb_members.ids) : "${pair[0]}:${pair[1]}" => {
      target_group = aws_lb_target_group.nlb_target_groups[pair[0]]
      instance_id  = pair[1]
    }
  }
  target_group_arn = each.value.target_group.arn
  target_id        = each.value.instance_id
  port             = each.value.target_group.port
  #target_id       = [for tid in range(var.inst_count) : data.aws_instances.nlb_insts.ids[tid]]
}

where var.nlb_listeners is like this:

nlb_listeners = [
  {
    protocol    = "TCP"
    target_port = "80"
    health_port = "1936"
  },
  {
    protocol    = "TCP"
    target_port = "443"
    health_port = "1936"
  }
]

and var.elb_members.ids is like this:

"ids" = [
    "i-015604f88xxxxxx42",
    "i-0e4defceexxxxxxe5",
  ]

but I’m getting Invalid for_each argument error:

Error: Invalid for_each argument

  on ../../modules/elb/balencer.tf line 46, in resource "aws_lb_target_group_attachment" "tgr_attachment":
  46:   for_each = {
  47:     for pair in setproduct(keys(aws_lb_target_group.nlb_target_groups), var.elb_members.ids) : "${pair[0]}:${pair[1]}" => {
  48:       target_group = aws_lb_target_group.nlb_target_groups[pair[0]]
  49:       instance_id  = pair[1]
  50:     }
  51:   }

The "for_each" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the for_each depends on.

I cannot figure out why it’s either invalid or how this for_each cannot determine the values. Any idea what’s am I doing wrong here? Seriously got stuck in the middle and would really appreciate any help to put me to the right direction.

-S

Am I the first one experiencing that? Any suggestions from any one at all?

-S

Okay, I have figure out in my way, which is working fine for now. In my ec2 child-module, I have a data-source like this:

ec2/data.tf

// List of instance attributes by role
data "aws_instance" "by_role" {
  for_each = {
    for ic in range(var.inst_count): "${var.inst_role}0${ic+1}" => ic
  }
  instance_tags  = {
    Name = "${var.vpc_names[var.idx]}${each.key}"
  }
  instance_id    = aws_instance.inst[substr(each.key,4,2)-1].id
}
// 
output "inst_by_role" {
  value = data.aws_instance.by_role
}

That returns a map like this:

inst_info = {
  "asa01" = {
    "ami" = "ami-06a5087891b6d8eb8"
    "arn" = "arn:aws:ec2:us-east-1:619xx:instance/i-0939b2db7xx"
    "associate_public_ip_address" = false
    ..........
  }
  "asa02" = {
    "ami" = "ami-06a5087891b6d8eb8"
    "arn" = "arn:aws:ec2:us-east-1:619xx:instance/i-0ab3f8dae7xx"
    "associate_public_ip_address" = false
    .........
  }
}

and in the nlb child-module:

nlb/listener.tf

variable "inst_info" {
  #type       = map
  description = "List of attributes of the NLB members"
}
// Attach the target groups to the instance(s)
resource "aws_lb_target_group_attachment" "tgr_attachment" {
  for_each = {
    for pair in setproduct(keys(aws_lb_target_group.nlb_target_groups), keys(var.inst_info)):
    "${pair[0]}:${pair[1]}" => {
      target_group = aws_lb_target_group.nlb_target_groups[pair[0]]
      inst_name    = var.inst_info[pair[1]]
    }
  }
  target_group_arn = each.value.target_group.arn
  target_id        = each.value.inst_name.id
  port             = each.value.target_group.port
}

then feed the data in the root-module:

module "asa-nlb" {
  source    = "../../modules/nlb"
  inst_role = "asa"
  inst_info = module.dfw.inst_by_role
  .......
}

this reply from @apparentlymart helped me a lot to look for a alternative way to get it working.

-San

I am looking to solve a similar problem with your code but I hit a wall and I can’t seem to get it to work. Any input appreciated.

My ec2 module:

data "aws_instance" "ec2info" {
  count = var.instance_count
  instance_id = aws_instance.this[count.index].id
}

resource "aws_instance" "this" {
  count = var.instance_count
  ...

My elb module:

resource "aws_lb_target_group_attachment" "tgr_attachment" {
  for_each = {
    for pair in setproduct(keys(aws_lb_target_group.lb_target_groups), keys(var.instance)) :
    "${pair[0]}:${pair[1]}" => {
      target_group = aws_lb_target_group.lb_target_groups[pair[0]]
      instance_id  = var.instance[pair[1]]
    }
  }

  target_group_arn              = each.value.target_group.arn
  target_id                     = each.value.instance_id.id
  port                          = each.value.target_group.port

variable "instance" {}

The main module:

module "elb" {
 instance              = module.stacka.ec2info
 ...

The error:

Error: Invalid function argument

  on ../../modules/networking/elb/main.tf line 77, in resource "aws_lb_target_group_attachment" "tgr_attachment":
  77:     for pair in setproduct(keys(aws_lb_target_group.lb_target_groups), keys(var.instance)) :
    |----------------
    | var.instance is tuple with 3 elements

Invalid value for "inputMap" parameter: must have map or object type.

The 3 elements would be the 3 instances but I dont understand why its not treating it as a map like for you?

…I am guessing it’s because my set has no keys? :thinking:

this is what the var.instance returns

ec2info = [
  {
    "ami" = "ami-02a1f2e85b6e06219"
    "arn" = "arn:aws:ec2:us-east-1:802655918742:instance/i-0b2389e70a3bba640"
  ...
  },
  {
   "ami" = "ami-02a1f2e85b6e06219"
   "arn" = "arn:aws:ec2:us-east-1:802655918742:instance/i-024511f8f330fae64"
  ...
  },
]

Hi @chomezski,

Indeed, the keys function only works with maps, not with sets. To make it work like you were intended you’ll need to either change that var.instance value to be a map itself, or write an expression inside your module to project it into being a map with a suitable key:

locals {
  ec2_instances = {
    for inst in var.instance.ec2info : inst.arn = inst
  }
}

You could then use local.ec2_instances to access the map. For the sake of example here I used the arn attribute value for the unique key, because that was the only unique value you showed in your example, but note that instance ids or ARNs are generally not a suitable value to use in instance keys because they too are assigned only after an instance is created, and so Terraform can’t predict at plan time what the final value would be.

Appreciate the response @apparentlymart!

I managed to get it working by iterating using a for loop instead of count like @dsantanu did. I don’t understand what specifically changes the output to a map though. :slight_smile:

data "aws_instance" "ec2info" {
  for_each = {
     for ic in range(var.instance_count): ic => ic
  }
  instance_tags = {
    Name = "${var.name}-${each.key+1}"
  }
  instance_id = aws_instance.this[each.key].id
}

if you output the result from that data source, something like:

output "ec2info" {
    value = data.aws_instance.ec2info
}

and run terraform apply, what do you get?

-S