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?
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.
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