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

Using count index to interpolate other variables? #408

Closed
andyshinn opened this issue Oct 15, 2014 · 16 comments
Closed

Using count index to interpolate other variables? #408

andyshinn opened this issue Oct 15, 2014 · 16 comments

Comments

@andyshinn
Copy link

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?

@knuckolls
Copy link
Contributor

This was also requested in #398. Wrote up my notes in that thread.

@andyshinn
Copy link
Author

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

@mitchellh
Copy link
Contributor

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.

@knuckolls
Copy link
Contributor

@andyshinn yep you're right and I was unaware of the count.index until this thread. thanks!

@rcostanzo
Copy link

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

@rcostanzo
Copy link

I just submitted #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.

@nerd0
Copy link

nerd0 commented Nov 18, 2014

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)

@spyrospph
Copy link

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.

@lamdor
Copy link
Contributor

lamdor commented Nov 24, 2014

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?

@lamdor
Copy link
Contributor

lamdor commented Nov 28, 2014

I fixed my issue with #611

@armon
Copy link
Member

armon commented Nov 30, 2014

Fixed by @rubbish in #611

@rafaelmagu
Copy link

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

@dharmapunk82
Copy link

@rafaelmagu that was the final touch in getting terraform to name instances based on their region variable and count. thank you ever so much!

@ariesyous
Copy link

Anyway to reference data.aws_availability_zones.available.names[0] into this? I think I'm furthering the double interpolation pattern witnessed here

@kpskohli
Copy link

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.

@ghost
Copy link

ghost commented Sep 27, 2019

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 Sep 27, 2019
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