Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

count parameter needs to be able to interpolate variables from modules. #1497

Closed
bobtfish opened this issue Apr 12, 2015 · 60 comments
Closed

Comments

@bobtfish
Copy link
Contributor

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:

  • aws_instance.nat: resource count can't reference module variable: module.vpc.az_count

and if I remove this error from the source (hope springs eternal!), I run into:

  • strconv.ParseInt: parsing "${module.azs.az_count}": invalid syntax

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}"
     ...
}
bobtfish added a commit to bobtfish/terraform-vpc that referenced this issue Apr 13, 2015
bobtfish added a commit to bobtfish/terraform-vpc-nat that referenced this issue Apr 13, 2015
@jtopper
Copy link
Contributor

jtopper commented Apr 14, 2015

👍

Surprised to see I can't do count = ${var.az_count} here.

@progrium
Copy link

+1

@phinze
Copy link
Contributor

phinze commented May 27, 2015

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: #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.

@jtopper
Copy link
Contributor

jtopper commented May 29, 2015

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?

@blalor
Copy link
Contributor

blalor commented Jul 23, 2015

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…

@KoeSystems
Copy link

+1 @bobtfish This will improve the way we can build and reuse terraform modules!
@phinze Any progress with this enhancement ?

@johnhamelink
Copy link

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"
}

@mattupstate
Copy link

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

@maxenglander
Copy link
Contributor

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

@queeno
Copy link

queeno commented Sep 30, 2015

+1

@lamdor
Copy link
Contributor

lamdor commented Oct 12, 2015

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.

@barbarello
Copy link

+1

@mrwilby
Copy link

mrwilby commented Nov 21, 2015

Also just bumped into this when trying to modularize my VPC design. Would be useful to have this support

@antonosmond
Copy link

+1

3 similar comments
@dannietjoh
Copy link

+1

@paprins
Copy link

paprins commented Dec 1, 2015

+1

@jpancoast-kenzan
Copy link

+1

@wricardo
Copy link

wricardo commented Dec 3, 2015

+1

@wgallegos
Copy link

+1

@pporada-gl
Copy link

pporada-gl commented Jan 18, 2016

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.

@jfelixetcetera
Copy link

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.

@myoung34
Copy link
Contributor

myoung34 commented Nov 22, 2016

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

@maxlock
Copy link

maxlock commented Nov 30, 2016

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.

@marranz
Copy link

marranz commented Dec 13, 2016

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.

@thatderek
Copy link

thatderek commented Dec 21, 2016

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!

@odupuy
Copy link

odupuy commented Jan 17, 2017

+1 to fix this

@adampolomski
Copy link

+1

@cfromm1911
Copy link

cfromm1911 commented Jan 24, 2017

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

@mafonso
Copy link

mafonso commented Jan 25, 2017

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.

@cemo
Copy link

cemo commented Jan 25, 2017

👍

1 similar comment
@fisabelle
Copy link

+1

mitchellh added a commit that referenced this issue Jan 28, 2017
This disables the computed value check for `count` during the validation
pass. This enables partial support for #3888 or #1497: as long as the
value is non-computed during the plan, complex values will work in
counts.

**Notably, this allows data source values to be present in counts!**

The "count" value can be disabled during validation safely because we
can treat it as if any field that uses `count.index` is computed for
validation. We then validate a single instance (as if `count = 1`) just
to make sure all required fields are set.
@mitchellh
Copy link
Contributor

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.

@cemo
Copy link

cemo commented Jan 29, 2017

Will this be backported to 0.8.x too?

@mitchellh
Copy link
Contributor

It will not

@mitchellh
Copy link
Contributor

Fixed by #11482! See that issue for caveats, though I think you'll find them acceptable.

dmrzzz added a commit to techservicesillinois/aws-enterprise-vpc that referenced this issue May 10, 2017
…and also that several workarounds formerly attributed to hashicorp/terraform#1497 are actually more closely related to hashicorp/terraform#4149 (and are still necessary for greenfield VPC construction since the value used in count comes from a resource created by Terraform)
@ashb
Copy link
Contributor

ashb commented May 19, 2017

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

@apparentlymart
Copy link
Contributor

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.

@ashb
Copy link
Contributor

ashb commented May 19, 2017

@apparentlymart Thanks, reported with repro as #14677

@ghost
Copy link

ghost commented Apr 8, 2020

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.

@ghost ghost locked and limited conversation to collaborators Apr 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests