How would one get the subnet id if you're using for_each as opposed to count? In my case, I'm doing something like this
resource "aws_instance" "k8s" {
for_each = var.profiles
ami = data.aws_ami.latest-ubuntu.id
instance_type = "t2.medium"
iam_instance_profile = "${each.value}"
subnet_id = ??????????????
vpc_security_group_ids = [var.security_group]
key_name = var.keyname
connection {
type = "ssh"
host = self.public_ip
user = "ubuntu"
private_key = file(var.private_key_path)
}
tags = {
Name = "${each.key}"
}
}
And this is because I'm creating similar instances but need to assign them different instance profiles.
Ideally, I'd have done something like
subnet_id = element(var.subnets, count.index )
to place the instances in different subnets but I don't think count and for_each can be used in the same block definition.
I have to subnets and 4 instances and would like to loop through the subnets, placing each instance in one.
Any ideas?
A good general strategy with resource for_each
is to design the data structure you pass it so that each.value
contains all of the per-instance data you need inside the resource block.
In this case, that means that the for_each
expression for your aws_instance
resource would be a map of objects where each object has both an instance profile and a subnet id.
One way to achieve that would be to write a for
expression that transforms var.profiles
(which is presumably a set(string)
value) into a map of objects that would get the result you want. For example:
resource "aws_instance" "k8s" {
for_each = {
# This assigns a subnet to each of the profiles
# by first sorting them by name to produce a list
# and then selecting subnets based on the order
# of the sort result.
for i, profile_name in sort(var.profiles) : profile_name => {
iam_instance_profile = profile_name
subnet_id = element(var.subnets, i)
}
}
ami = data.aws_ami.latest-ubuntu.id
instance_type = "t2.medium"
iam_instance_profile = each.value.iam_instance_profile
subnet_id = each.value.subnet_id
vpc_security_group_ids = [var.security_group]
key_name = var.keyname
}
Using count.index
with the element
element relied on each item having its own index, but that isn't the case for a set of strings so in the above I used sort
to convert to a list under the assumption that for this situation it doesn't really matter which subnet is assigned to each instance as long as the instances end up roughly evenly distributed between the subnets.
However, there is a big implication of that to keep in mind: if you add a new item to var.profiles
later then it may cause the subnet_id
for existing instances to be reassigned, and would thus require those instances to be recreated. If you don't want that to be true then you'll need to make the selection of subnet per profile more explicit somehow, which could be done by making var.profiles
be a list(string)
instead of a set(string)
and then documenting that new profiles should only be added to the end of that list, or alternatively you could move the decision up into the caller of your module by making var.profiles
itself a map of objects where the caller would then specify one subnet per profile:
variable "profiles" {
type = map(object({
subnet_id = string
}))
}
In that case, your resource block would become simpler because the variable value would already be of a suitable shape:
resource "aws_instance" "k8s" {
for_each = var.profiles
ami = data.aws_ami.latest-ubuntu.id
instance_type = "t2.medium"
iam_instance_profile = each.key
subnet_id = each.value.subnet_id
vpc_security_group_ids = [var.security_group]
key_name = var.keyname
}