1
votes

I am trying to dynamically create some aws resources in terraform making use of count and then build an output variable containing a list of all created resource ids

resource "aws_subnet" "subnet-1" {
  // If subnet_id value is supplied then we set count to 1 to create otherwise skip
  count = "${replace(replace(var.primary_subnet_cidr, "/^.+/", "1"), "/^$/", "0")}"
  tags = {
    Name = "subnet-1"
  }
  cidr_block = "${var.primary_subnet_cidr}"
  vpc_id = "${var.vpc_id}"
  availability_zone = "${var.aws_region}a"
}
resource "aws_subnet" "subnet-2" {
  // If subnet_id value is supplied then we set count to 1 to create otherwise skip
  count = "${replace(replace(var.secondary_subnet_cidr, "/^.+/", "1"),"/^$/", "0")}"
  tags = {
    Name = "subnet-2"
  }
  cidr_block = "${var.secondary_subnet_cidr}"
  vpc_id = "${var.vpc_id}"
  availability_zone = "${var.aws_region}b"
}

output "ids" {
      value = ["${aws_subnet.subnet-1.id}","${aws_subnet.subnet-2.id}"]
}

The idea is to create only the subnet you want by supplying its variable (primary_subnet_cidr or secondary_subnet_cidr).

Creating them dynamically works fine however my issue is with the dynamic list.

When attempting to use this list in something like a aws_db_subnet_group.subnet_ids which takes in a list, it only works if both subnets have been created otherwise it throws me the following error

InvalidSubnet: Subnet IDs are required.

I have also tried this

output "ids" {
      value = ["${aws_subnet.*.id}"]
}

But terraform does not seem to support wildcards on resource level.

I suspect this has to do with the way I generated the output list. Is there a better way to generate that dynamic list so it includes only the created resources?

2

2 Answers

2
votes

You were close, but you need to use the correct Terraform resource syntax of RESOURCE-TYPE.RESOURCE-NAME.PROPERTY_NAME or RESOURCE-TYPE.RESOURCE-NAME.*.PROPERTY_NAME. So, in your example, you would output:

output "ids" {
   value = "${aws_subnet.subnet-1.*.id}"
}

Of course, you may want to join the two lists using the concat interpolation function:

output "ids" {
   value = "${concat(aws_subnet.subnet-1.*.id, aws_subnet.subnet-2.*.id)}"
}

But if your count property is set to 0 on either resource, this might throw an error. To remedy that, you can use some combination of other interpolation functions (e.g. coalescelist()), but I'm afraid I can't remember the exact combination.

Finally, for your count properties, these var definitions are a little tricky to read. Here are some other ideas to try:

// Count = 1 if var.secondary_subnet_cidr is non-empty
resource "aws_subnet" "subnet-1" {
  count = "${signum(length(var.secondary_subnet_cidr))}"
  ...
}

// Define a separate var altogether. Ideally, you could infer this, but sometimes it's the best you can do.
resource "aws_subnet" "subnet-1" {
  count = "${var.should_create_secondary_subnet)}"
  ...
}

Update Jun 6, 2017: Thanks to @MartinAtkins for pointing out that as of Terraform 0.9.x, you no longer need to wrap list outputs in [ and ]. I've updated my answer.

Update Jun 8, 2017: To select only the non-empty list out of two lists where one may be empty, use ${element(concat(aws_subnet.subnet-1.*.id, aws_subnet.subnet-2.*.id), 0)}. The concat() function will accept an empty list, and the element(..., 0) will always returns the first element, which in this case is the non-empty list.

0
votes

I found this question while trying to create a dynamic list, where I had to create a list with a variable number of elements in the list. I was creating a list of strings. I solved the problem by using Terraform's compact method. Basically, this list would filter out blank elements I generated inside my list.

For example suppose I have the following list,

myList = ["foo", barExists ? "bar" : ""]

This will create either ["foo", "bar"] or ["foo", ""]. If you instead need either ["foo", "bar"] or ["foo"], the compact function is your friend.

myList = compact(["foo", barExists ? "bar" : ""])

For such cases, the above line will work.