I'm attempting to create 3 subnets, each in different availability zones:
resource "aws_subnet" "coreospub0" {
vpc_id = "${aws_vpc.coreos.id}"
cidr_block = "172.64.0.0/22"
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
}
resource "aws_subnet" "coreospub1" {
vpc_id = "${aws_vpc.coreos.id}"
cidr_block = "172.64.4.0/22"
availability_zone = "${var.aws_region}b"
map_public_ip_on_launch = true
}
resource "aws_subnet" "coreospub2" {
vpc_id = "${aws_vpc.coreos.id}"
cidr_block = "172.64.8.0/22"
availability_zone = "${var.aws_region}c"
map_public_ip_on_launch = true
}
I was then trying to simplify the aws_instance
resource duplication by using count
and interpolating subnet_id
:
resource "aws_instance" "coreos" {
ami = "${lookup(var.aws_amis, var.aws_region)}"
instance_type = "${var.aws_instance_type}"
count = 3
key_name = "${var.aws_key_name}"
security_groups = ["${aws_security_group.ssh.id}", "${aws_security_group.self.id}"]
subnet_id = "${aws_subnet.coreospub${count.index}.id}"
}
But I end up with a plan that tries to reference the string and not the actual subnet_id
:
+ aws_instance.coreos.2
...
subnet_id: "" => "${aws_subnet.coreospub2.id}"
Is there any way to accomplish this yet?
This was also requested in #398. Wrote up my notes in that thread.
Hmm, I'm not sure if it is the same yet. For static things (like a hostname) you can use ${count.index}
as of 0.3.0. Are you trying to use count.index
as a reference to other interpolated variables like I am? The count.index
does work and the variable I want to interpolate is listed as the subnet_id
. But I think it is just a double interpolation problem (${aws_subnet.coreospub2.id}
needs to be interpolated again).
I think we need to document how to do this. You should use map structures for this (map variable types) with the lookup
function. I've tagged as docs.
@andyshinn yep you're right and I was unaware of the count.index until this thread. thanks!
@mitchellh Using the lookup function works if the values are static. But if they're dynamic (like the subnet ids in this example), you can't reference them in a map since variable values are required to be static. Is there another way to accomplish this now?
I just submitted https://github.com/hashicorp/terraform/pull/554 which helps solve this problem in a slightly different way. There's a new "element" interpolation function which you can use to get a specific index from a splat. So for the example above, if you use the count = 3 to generate your subnets as a multi-value variable like:
variable "zones" {
default = {
zone0 = "us-west-2a"
zone1 = "us-west-2b"
zone2 = "us-west-2c"
}
}
variable "cidr_blocks" {
default = {
zone0 = "172.64.0.0/22"
zone1 = "172.64.0.4/22"
zone2 = "172.64.0.8/22"
}
}
resource "aws_subnet" "coreospub" {
vpc_id = "${aws_vpc.coreos.id}"
cidr_block = "${lookup(var.cidr_blocks, concat("zone", count.index))}"
availability_zone = "${lookup(var.zones, concat("zone", count.index))}"
map_public_ip_on_launch = true
count = 3
}
You then get a aws_subnet.coreospub.*.id splat to play around with. To use that when creating instances with the new element function, it'd look like:
resource "aws_instance" "coreos" {
ami = "${lookup(var.aws_amis, var.aws_region)}"
instance_type = "${var.aws_instance_type}"
count = 3
key_name = "${var.aws_key_name}"
security_groups = ["${aws_security_group.ssh.id}", "${aws_security_group.self.id}"]
subnet_id = "${element(aws_subnet.coreospub.*.id, count.index)}"
}
Note that I made the element function wrap if the index is greater than the splat length, so if you do a count = 6, for example, you'll end up with 2 instances per AZ/subnet pair.
Curious when will we be able to using count.index to reference a resource already created just as @andyshinn pointed. I ran into the same problem
resource "aws_subnet" "public" { vpc_id = "${aws_vpc.default.id}" cidr_block = "${concat(var.vpc_cidr_block_base, ".", count.index ,".0/24")}" availability_zone = "${concat(var.region, lookup(var.vpc_availability_zone, concat("zone_", count.index)))}" count = "${var.zone_count}" depends_on = ["aws_vpc.default", "aws_internet_gateway.default"] } # Routing table for public subnets resource "aws_route_table" "public" { vpc_id = "${aws_vpc.default.id}" route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.default.id}" } depends_on = ["aws_vpc.default", "aws_internet_gateway.default"] } resource "aws_route_table_association" "public" { subnet_id = "${aws_subnet.public.${count.index}.id}" route_table_id = "${aws_route_table.public.id}" count = "${var.zone_count}" depends_on = ["aws_vpc.default", "aws_internet_gateway.default"] }Output
module.vpc.aws_route_table_association.public.1: Creating... route_table_id: "" => "rtb-0668bf63" subnet_id: "" => "${aws_subnet.public.1.id}" module.vpc.aws_route_table_association.public.0: Creating... route_table_id: "" => "rtb-0668bf63" subnet_id: "" => "${aws_subnet.public.0.id}" module.vpc.aws_route_table_association.public.0: Error: The subnet ID '${aws_subnet.public.0.id}' does not exist (InvalidSubnetID.NotFound)
Using the way @rcostanzo suggested above (i.e. using the element function) did the job for me.
However I believe a way that provides more flexibility needs to be found and the only way I can imagine is eventually allowing for maps to use interpolated variables.
I just tried to use the new element
function as @rcostanzo added, however I ran into an issue. It works fine when ran as part of the main tf file
i.e.
variable "instance_count" {
default = "1"
}
resource "aws_instance" "base" {
ami = "ami-b66ed3de"
instance_type = "m3.medium"
count = "${var.instance_count}"
}
resource "aws_route53_record" "base_internal_dns" {
name = "${concat("test", count.index, ".", "test.com")}"
count = "${var.instance_count}"
zone_id = "test.com"
type = "A"
ttl = "1"
records = ["${element(aws_instance.base.*.private_ip, count.index)}"]
}
works fine.
However, when I try and use that as a module i.e.
outside of the file above:
module "base" {
source = "./base"
instance_count = "2"
}
I run into
Error running plan: Resource 'aws_instance.base' not found for variable 'aws_instance.base.*.private_ip'
Am I not referring to the aws_instance.base.*.private_ip
correctly? Is it a module scoping issue?
I fixed my issue with #611
Fixed by @rubbish in #611
Just in case someone lands here trying to achieve this with 0.7 or later, the way to do this without relying on concat
is:
lookup(var.vpc_availability_zone, "zone_${count.index}")
Or, in my case:
lookup(var.vpc_availability_zone, "zone_${count.index % 3}")
@rafaelmagu that was the final touch in getting terraform to name instances based on their region variable and count. thank you ever so much!
Anyway to reference data.aws_availability_zones.available.names[0] into this? I think I'm furthering the double interpolation pattern witnessed here
I tried similar code above to create 3 instances but public IP is not getting attached to all three instances. It attaches it to only 1st instance:
terraform code:
resource "aws_subnet" "public_subnet" {
count = "3"
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "${cidrsubnet("${var.vpc_cidr}", 4, count.index)}" #count.index is 3 it creates 3 subnets
availability_zone = "${element(var.lst_azs, count.index)}"
map_public_ip_on_launch = "true"
}
Above code creates 3 subnets and 3 different instances are attached to these 3 subnets. But only 1st instance is getting public ip. 2nd and 3rd are not getting public ip's.
I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
Most helpful comment
Just in case someone lands here trying to achieve this with 0.7 or later, the way to do this without relying on
concat
is:Or, in my case: