skip to Main Content

I am trying to create target group attachment to the instances. e.g.
if there are 3 target groups and 2 instances, would like to attach 3 target groups to the 2 instances.

Here is my code

instances

resource "aws_instance" "web_servers" {
  ami                         = "ami-1234"
  instance_type               = "t3a.small"
  subnet_id                   = module.vpc.private_subnets[0]
  key_name                    = aws_key_pair.keypair.key_name
  count                       = 2
  root_block_device {
    volume_type           = "gp3"
    volume_size           = var.ws_storage
    encrypted             = true
    delete_on_termination = true
  }

  tags = merge(
    { Name = "${var.project}-${var.env}-ws-${count.index}" },
    var.tags
  )
}

Target group attachment local variable

locals {
  tg_attachments = distinct(flatten([
    for tg in var.lb : [
      for instance in aws_instance.web_servers : {
        tg_name = tg.tg_name
        tg_port = tg.listener_port
        tg_protocol = tg.protocol
        tg_instance = instance.id
      }
    ]
  ]))
}

target group attachment

resource "aws_lb_target_group_attachment" "ws_tg_attachement" {
  depends_on = [
    time_sleep.wait_30_seconds,
    aws_lb_listener.listener,
    aws_instance.web_servers,
    aws_lb_target_group.tg
  ]
  for_each = { for entry in local.tg_attachments: "${entry.tg_name}-${entry.tg_port}-${entry.tg_instance}" => entry }
  target_group_arn = aws_lb_target_group.tg["${each.value.tg_name}-${each.value.tg_port}"].arn
  target_id        = each.value.tg_instance
  port             = each.value.tg_port
}

ERROR

for_each = { for entry in local.tg_attachments: "${entry.tg_name}-${entry.tg_port}-${entry.tg_instance}" => entry }
│     ├────────────────
│     │ local.tg_attachments is a list of object, known only after apply
│
│ 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.

3

Answers


  1. Chosen as BEST ANSWER

    I am able to resolve the issue writing the code as follows.

    Targert Group attachment

    resource "aws_lb_target_group_attachment" "ws_tg_attachement" {
      
      for_each = {
        for pair in setproduct(range(length(var.lb)), range(var.web_server.count)) : "${pair[0]} ${pair[1]}" => {
          target_group_arn = "${var.lb[pair[0]].tg_name}-${var.lb[pair[0]].listener_port}"
          port = var.lb[pair[0]].listener_port
          target_id = pair[1]
        }
      }
    
      target_group_arn = aws_lb_target_group.tg[each.value.target_group_arn].arn
      target_id = aws_instance.web_servers[each.value.target_id].id
      
      port             = each.value.port
    }
    

    lb variable

    lb = [
        {
            tg_name = "SSH"
            listener_port = 22
            protocol = "HTTP"
            cidr = ["0.0.0.0/0"]
        },
        {
            tg_name = "nginx"
            listener_port = 80
            protocol = "HTTP"
            cidr = ["0.0.0.0/0"]
        }
    ]
    

    web_server variable

    web_server = {
        name = "web"
        ami = "ami-09d3b3274b6c5d4aa"
        type = "t2.micro"
        storage = "20"
        count = 2
    }
    

  2. As the error suggest, you can’t create resources based on list or maps with unknown arguments at plan time. In your case:

     tg_instance = instance.id
    

    will only be known after apply (not before), and that’s why TF errors out. You either have to refactor your code to have all values known at plan time (change instance.id with something that you know at plan time), or use -target to create instances first:

    1. First create instances only:
    terraform apply -target aws_instance.web_servers
    
    1. then create the rest of your resources:
    terraform apply
    
    Login or Signup to reply.
  3. Your local value result is a wholly-unknown list because you’ve used the distinct function. In order to decide the result of that function Terraform must compare all of the values in the list, which includes comparing the instance ids. Because the instance IDs aren’t known yet, Terraform also cannot predict the result of the distinct function.

    However, if you use the instance index instead of the instance ID then Terraform will always know that value during planning, because instance IDs are a Terraform concept rather than a remote API concept:

    locals {
      tg_attachments = distinct(flatten([
        for tg in var.lb : [
          for instance_idx, instance in aws_instance.web_servers : {
            tg_name         = tg.tg_name
            tg_port         = tg.listener_port
            tg_protocol     = tg.protocol
            tg_instance_idx = instance_idx
          }
        ]
      ]))
    }
    

    The index of a list element is the "key" of that element, so you can use the two-symbol form of for as shown above to get an instance_idx for each element. That’ll set tg_instance_idx to 0, 1, 2… depending on the count value for aws_instance.web_servers. Those index values will always be known during the planning phase, because Terraform always knows the count value for a resource during planning.

    This means that when you set target_id you will then need to finally cross-reference with the original resource to get the actual instance ID, like this:

    resource "aws_lb_target_group_attachment" "ws_tg_attachement" {
      for_each = { for entry in local.tg_attachments: "${entry.tg_name}-${entry.tg_port}-${entry.tg_instance_idx}" => entry }
    
      target_group_arn = aws_lb_target_group.tg["${each.value.tg_name}-${each.value.tg_port}"].arn
      target_id        = aws_instance.web_servers[each.value.tg_instance_idx].id
      port             = each.value.tg_port
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search