I've been trying to extend my Terraform examples to support multiple AZs by default.
As such, I have an _az_count_ variable output by my module to detect the AZs you have available:
https://github.com/terraform-community-modules/tf_aws_availability_zones#outputs
And I then want to reuse it for instances, for example here:
https://github.com/bobtfish/terraform-vpc/blob/master/main.tf#L37
My example (https://github.com/bobtfish/terraform-example-vpc/tree/master/eucentral1-demo) crashes out with the error:
and if I remove this error from the source (hope springs eternal!), I run into:
This inability to interpolate count variables is a blocker for me being able to write region independent modules - as (for example) I want to be able to allocate one subnet per AZ, writing code like:
module "azs" {
source = "github.com/terraform-community-modules/tf_aws_availability_zones"
account = "${var.account}"
region = "${var.region}"
}
resource "aws_subnet" "front" {
count = "${module.vpcs.az_count}"
...
}
Even better, I'd like to be able to interpolate variables out of one module, and into the user variables of another, for example:
module "azs" {
source = "github.com/terraform-community-modules/tf_aws_availability_zones"
account = "${var.account}"
region = "${var.region}"
}
variable "az_count" {
default = "${module.azs.az_count}"
}
resource "aws_subnet" "front" {
count = "${var.az_count}"
...
}
:+1:
Surprised to see I can't do count = ${var.az_count}
here.
+1
We should definitely do this, the tricky part comes from the fact that count expansion is currently done statically, before the primary graph walk, which means we can't support "computed" counts right now. (A "computed" value in TF is one that's flagged as not known until all its dependencies are calculated.)
Related: https://github.com/hashicorp/terraform/pull/354
So we'd need to rework resource count expansion to occur "just in time" to support this. Probably the right thing to do in the long run, but that's what makes this nontrivial to fix.
Sounds like I might need to preprocess these .tf files for a little while then. I don't suppose you have a sense of where in the roadmap this rework might happen?
I have a pretty kludgy workaround for this; I really want a way to store intermediate variables somehow…
variable "az_mapping" {
default = {
"us-west-1" = "a,c"
}
}
resource "aws_subnet" "management" {
vpc_id = "${aws_vpc.default.id}"
count = "${length(split(",", lookup(var.az_mapping, var.region)))}"
availability_zone = "${var.region}${element(split(",", lookup(var.az_mapping, var.region)), count.index)}"
cidr_block = "172.31.${count.index}.0/24"
map_public_ip_on_launch = true
tags {
Name = "management-${element(split(",", lookup(var.az_mapping, var.region)), count.index)}"
}
}
resource "aws_instance" "consul" {
count = "${length(split(",", lookup(var.az_mapping, var.region)))}"
subnet_id = "${element(aws_subnet.management.*.id, count.index)}"
ami = "${lookup(var.coreos_amis, var.region)}"
instance_type = "t2.small"
vpc_security_group_ids = [
"${aws_security_group.default.id}",
"${aws_security_group.ssh_boston.id}",
]
tags {
Name = "consul-${format("%03d", count.index)}.${var.env_name}"
}
}
Basically, the number of availability zones are determined by the az_mapping
variable (because Amazon in their infinite wisdom won't allow me to create instances in us-west-2b).
I mean, this is really kludgy, but so far it's working in the demo I'm hacking together today…
+1 @bobtfish This will improve the way we can build and reuse terraform modules!
@phinze Any progress with this enhancement ?
Another usecase I have, is that I want to take advantage of route53's nameserver/load balancer integration, but I use cloudflare for everything else. So what I want to do is to create a new cloudflare NS record for each route53 nameserver. Luckily we know there are 4 nameservers in the list that route53 gives back, so you can hardcode the count in like so:
# Forwards to Route53 Nameserver for the API endpoint so
# we can take advantage of automatically connecting the
# nameserver to the ELB.
resource "cloudflare_record" "new_record" {
domain = "example.com"
# This is hardcoded to counteract https://github.com/hashicorp/terraform/issues/1497.
# Luckily, we know there are 4 records, so this doesn't immediately affect us.
count = 4
name = "api2"
value = "${lookup(module.app.route53_nameservers, count.index)}"
type = "NS"
}
:+1: I've been trying to pass counts down to modules either directly or through length(split(",", some_list))
within the module and probably experiencing the same issue described here. More specifically I'm getting strange cycle errors that are difficult to understand.
Am blocked by this as well. I want to be able to have AWS "datacenter_a" and "datacenter_b" modules parameterized by AWS region, and set count to the number of availability zones in the provided region. Currently hitting the "strconv.ParseInt" error and am forced to simply hardcode the count to "2".
+1
I ran into this issue this morning and our previous hack doesn't seem to work anymore.
Our hack had consisted of what we called "priming" the counts. We had a module which outputted a count which we then used to input into another module for a resource's count. We'd only run into this issue on clean state, that is, nothing yet created. So what we'd do is create an override with the outputs defaulted to zero, run a terraform plan, remove the override and things were ok. It'd then continue to use the module output correctly as it was updated, but that no longer works.
For now, I believe we're going to find another workaround in which we'll probably just have to set the count in two different places, but it would be great to have this resolved.
+1
Also just bumped into this when trying to modularize my VPC design. Would be useful to have this support
+1
+1
+1
+1
+1
+1
This will help me fix about 20 lines in my terraform project that are essentially
// AGAIN this issue
//subnet_id = "${element(split(",", var.data_passed_in_from_a_module), count.index)}"
subnet_id = "${element(split(",","HARDCODED1,HARDCODED2"), count.index)}"
Edit: This was a result of me not realizing I was writing the wrong variable names.
Like @johnhamelink's issue above, computed counts would help when setting up Mailgun.
The Mailgun provider returns DNS records that you're supposed to create, and it would be nice to be able to directly create those after the Mailgun domain is set up. Right now I just have to hard-code that receiving_records
returns 2 records and sending_records
returns 3. Though if Mailgun ever changes what they return, this will obviously break.
I think I found another trigger for this bug, but the error it throws is different. I was trying to build a "string list" ie: "id1,id2,id3,id4" that I would then split on it to get the length. ie:
count = "${length(split(",", var.string_from_another_module))}"
This causes a Cycle error, though I suspect it's the same issue mentioned here.
Using terraform 0.6.15.
+1
Have had to hardcode the value for the AWS az_count as 4 for us-east-1, will have to remember to change it on my other deployments.
Saying that I feel much better after seeing it was a bug. Spent the last 10 hours questioning my sanity and feeling seriously stupid
I'm running in to this same problem when using data resources in 0.7.0. The docs here give the use case of creating subnets in each availability zone using the data resource to get the available zones and using the length of that in the count for the subnet resource. If I actually try the example in the docs it fails: aws_subnet.public: resource count can't reference resource variable: data.aws_availability_zones.zones.instance
+1
I think fixing this would help a lot of people watching #1604. Conditional resources could be implemented by setting count=0 in your interpolation.
:+1:
Over a year and this is still a huge problem. :(
My workaround is to export the count from the module I'm referencing but that's really inelegant.
EDIT: and even that doesn't work. Does anyone have a method to figure out the number of objects a module created and create complementary objects? In my use case, I'm modifying an aws_route_table
via aws_route
.
+1
Have an ELB module that can't be reused for cases where the instance attachments aren't known ahead of time or are expected to change, such as autoscaling groups. Would be great if I could do something like:
resource "aws_elb_attachment" "elb-attachment" {
count = "${length(compact(split(",", var.instance_ids)))}"
elb = "${aws_elb.elb.id}"
instance = ["${element(split(",", var.instance_ids), count.index)}"]
}
That would enable me to reuse my ELB module with my ASG module. Instead, I'm going to have to duplicate the majority of the code, leaving out the instance attachments.
Something like #4149 would enable us to implement this. I started prototyping for this core improvement over in #8521. My plan is to get the "partial apply" concept implemented first, and then once we have that we can rework the count
implementation. I think we'd permit any variable we can resolve from the state during a dynamic expand, and defer any resource nodes whose count cannot yet be resolved.
+1
I believe this issue is also related to not just modules, but data sources as well.
count = "${length(split(",", data.terraform_remote_state.foo.some_list))}"
You'll get the error message as:
Error running plan: 1 error(s) occurred:
* Resource 'data.terraform_remote_state.foo.some_list' does not have attribute
'some_list' for variable 'data.terraform_remote_state.foo.some_list'
+1
+1
Also happens when the variable being passed is a list:
variable "private_route_table_ids" {
type = "list"
}
resource "aws_route" "private_peering" {
count = "${length(var.private_route_table_ids)}"
route_table_id = "${element(var.private_route_table_ids, count.index)}"
destination_cidr_block = "${var.peer_vpc_block}"
vpc_peering_connection_id = "${aws_vpc_peering_connection.peering.id}"
depends_on = ["aws_vpc_peering_connection.peering"]
}
I have the same error.. and i really can't find any workaround.. 👎
count = "${length(split(",", data.terraform_remote_state.foo.some_list))}"
You'll get the error message as:
Error running plan: 1 error(s) occurred:
Yep, I'm hitting this today too.
EDIT: Hitting this _again_ today, since apparently I commented on this bug ages ago :)
All, we kept running into this, too, so we wrote a blog post that includes explicit mention of it. CTRL-F for "Count has limitations" to see the relevant part. Basically, we just hardcode the length of any list, which works in most cases. Obviously, it's not an ideal solution workaround.
Edit: Clarified this is a workaround, not a solution based on comment by @ChadScott.
@josh-padnick that's a workaround not a solution.
For instance, if I want precisely one instance/container/whatever in each AZ, there's no way to do that dynamically. us-east-1 has more AZs than us-west-2, so I'm having to maintain a hard-coded count.
It's terrible.
@ChadScott , as long as there are no resource/data sources in count, any interpolation functions work fine with count
. You'll probably have list of AZs hardcoded in your per-envrionement tfvar file, taking length() of it works fine with count
@redbaron that's no longer a safe assumption: https://www.terraform.io/docs/providers/aws/d/availability_zones.html
For me who is just evaluating Terraform this is a somewhat weird issue. Being able to dynamically request the availability zones without the possibility to dynamically interpolate them in dependent resources seems like a huge flaw in the design. This should actually get a higher priority than an "enahncement" TBH...
@redbaron no, I don't, I pull it dynamically, which is the whole point. If I want to spin up a new region, I don't want to have to hard-code a ton of variables to make it work.
Still a bug as of 2016-11-16 01:20:45+0000:
resource "aws_route" "vpn" {
count = "${length(var.route_table_ids)}"
# ...
}
yields:
Errors:
* strconv.ParseInt: parsing "${length(var.route_table_ids)}": invalid syntax
Still a problem in 0.7.9
* aws_route_table_association.prod-private: resource count can't reference module variable: module.prod_vpc.private_subnets
Another example of this being an issue is when trying to split the results of data.aws_ip_ranges into groups of 50 or less to be distibuted amongst a number of security groups. having to resort to hard coded lists. not happy.
i'm getting this error. The variable referenced in the count property is '0' or '1'. (checked via output)
when the value is set manually (=0 or =1) everything works properly.
Using terraform v0.7.13
data "terraform_remote_state" "rds" {
backend = "s3"
config {
bucket = "xxxx-state-xxxx"
key = "xxxxx-rds-play-deploy/terraform.tfstate"
region = "xxxx"
}
}
data "template_file" "user-data-standalone" {
template = "${file("templates/user-data.tpl")}"
count = "${data.terraform_remote_state.rds.standalone_rds}"
vars = {
db_endpoint = "${data.terraform_remote_state.rds.db_endpoint_standalone}"
}
}
So it seems that the problem is not only when interpolating from modules, also from external state.
Yeah, modules are very limited by this at the moment. For instance, I have a vpc module with public and private subnets that I'd like to be deployable in any aws-region. Users of the module are allowed to have as many subnets as they would like; accordingly, the module takes the number specified and iterates over the az data resource. Later when creating nat instances for the private subnets, that part of the module should be able to take for count something like "${length(distinct(aws_subnet.public.*.availability_zone))}"
, but this currently errors.
Alas, these are cool problems to have. Keep up the good work!
+1 to fix this
+1
+1
Would very much love to hear an update on this issue. The "count bug" as I call it is limiting me all over my TF build.
count = "${length(split("|", lookup(zipmap(split(";", element(split(":", data.consul_keys.asg.var.app_sg),0)), split(";", element(split(":", data.consul_keys.asg.var.app_sg),1))), "app")))}"
resource count can't reference resource variable
count = "${length(split(",", var.model_sg_groups))}"
value of 'count' cannot be computed
Over and Over.
Got into this again. I would like to be able to do
data "aws_route_table" "requester" {
count = "${length(var.subnets)}"
subnet_id = "${element(var.subnets,count.index)}"
}
resource "aws_route" "route" {
count = "${length(data.aws_route_table.requester.*.id)}"
route_table_id = "${element(data.aws_route_table.requester.*.id,count.index)}"
destination_cidr_block = "${data.aws_vpc.accepter.cidr_block}"
vpc_peering_connection_id = "${aws_vpc_peering_connection.peer.id}"
depends_on = ["aws_vpc_peering_connection.peer"]
}
It works fine if I declare count to be e.g 3
This is a a limitation to build fully dynamic modules driven by data source resources.
👍
+1
This should be fixed by #11482.
Blanket support for computed values is still not allowed since it doesn't allow a deterministic "plan" to happen. However, you can now use data source values as an input to count (which was one of the reasons for this in the first place) with #11482.
There are still plans to improve this further and potentially allow any computed value in "count" by splitting plan/apply into multiple distinct steps. However, I think that allowing data sources will be a big big step forward and bring a lot more functionality to count.
Will this be backported to 0.8.x too?
It will not
Fixed by #11482! See that issue for caveats, though I think you'll find them acceptable.
I think I just came across an example that I would have expected to work under this (as to my mind it is deterministic but I get the "value of 'count' cannot be computed" error:
If I have this in a module:
variable "subnet_ids" { type = "list" }
data "aws_subnet" "subnets" {
count = "${length(var.subnet_ids)}"
id = "${element(var.subnet_ids, count.index)}"
}
And then the call site:
module "nat" {
source = "..."
subnet_ids = ["${module.vpc.public_subnets}"]
}
Even If I change it to use an extra subnets_count
variable and change the module instantiation to be:
subnet_ids = ["${module.vpc.public_subnets}"]
subnets_count = "${length(module.vpc.public_subnets)}"
I get the same error. The only way I could make it work was to remove one level of indirection:
subnet_ids = ["${module.vpc.public_subnets}"]
subnets_count = "${length(data.aws_availability_zones.available.names)}"
(The public_subnet from the vpc module creates one subnet per AZ given.)
Hi @ashb,
That does seem strange. Would you mind opening a new top-level issue for that? It seems like something more subtle than what this issue was about. If you do open a new issue, it would be helpful to also see the relevant contents of your vpc
module, and in particular how its public_subnets
output is populated.
@apparentlymart Thanks, reported with repro as #14677
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
For me who is just evaluating Terraform this is a somewhat weird issue. Being able to dynamically request the availability zones without the possibility to dynamically interpolate them in dependent resources seems like a huge flaw in the design. This should actually get a higher priority than an "enahncement" TBH...