Terraform v0.11.10
+ provider.aws v1.21.0
resource "aws_cloudformation_stack" "example" {
count = "2"
name = "example-${count.index}"
template = <<STACK
{
"Resources" : {
"MyVPC": {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : "10.0.0.0/16",
"Tags" : [
{"Key": "Name", "Value": "Primary_CF_VPC"}
]
}
}
},
"Outputs" : {
"VpcID" : {
"Description": "The VPC ID",
"Value" : { "Ref" : "MyVPC" }
}
}
}
STACK
}
output "VpcID" {
value = "${aws_cloudformation_stack.example.*.outputs["VpcID"]}"
}
* module.example.output.DefaultDNSTarget: __builtin_StringToInt: strconv.ParseInt: parsing "VpcID": invalid syntax in:
${aws_cloudformation_stack.example.*.outputs["VpcID"]}
Hi @skinofstars! Sorry for this strange behavior.
The reason for the result you saw here is that the splat operator .* only consumes attribute access operations after it, and is interpreting ["VpcID"] as an index into the resulting list of output maps, rather than as a key into each of the maps.
In other words, it's evaluating aws_cloudformation_stack.example.*.outputs first to produce a list of maps and then applying ["VpcID"] to that list, whereas you wanted it to apply the full .outputs["VpcID"] traversal to each item, producing a list of strings.
The easiest way to get what you wanted here in Terraform 0.11 is to use attribute syntax instead:
value = "${aws_cloudformation_stack.example.*.outputs.VpcID}"
The forthcoming Terraform 0.12 release will introduce a new form of splat expression that will consume index operations too, so once that is released you'd be able to alternatively write this as:
value = "${aws_cloudformation_stack.example[*].outputs["VpcID"]}"
However, in this sort of situation we usually prefer to use the attribute syntax anyway because it expresses the same idea more concisely; the index syntax is intended for situations where the key itself is a dynamic value.
Hi @apparentlymart, thanks for getting back.
Thanks for explaining the reason. Unfortunately your proposed solution, to use attribute syntax, doesn't work for aws_cloudformation_stack (perhaps a different bug?). It results in the following
Error: Error running plan: 1 error(s) occurred:
* module.example.output.VpcID: Resource 'aws_cloudformation_stack.example' does not have attribute 'outputs.VpcID' for variable 'aws_cloudformation_stack.example.*.outputs.VpcID'
I'm seeing the same issue as above when using count for a cloud formation template. Is it not possible to use count?
Just to add some flavor to the above comment:
Here is my stack instantiation:
# Cloudformation template
resource "aws_cloudformation_stack" "window" {
count = "${length(var.days) * length(var.time)}"
name = "vpc-maintenance-window"
parameters {
window_name = "mw-AutomatedPatching-runson-${var.day[count.index % local.counter["day"]]}-at-${var.time[count.index / local.counter["days"]]}hours-EST"
window_schedule = "cron(0 ${var.time[count.index / local.counter["days"]]} ? * ${var.days[count.index % local.counter["days"]]} *)"
window_duration = 2
window_cutoff = 0
}
template_body = "${file("${path.module}/window.json")}"
}
#output "CFWindow_ID" {
# value = "${aws_cloudformation_stack.window.*.outputs.CFWindow_ID}"
#}
#output "CFWindow_Name" {
# value = "${aws_cloudformation_stack.window.*.outputs.CFWindow_Name}"
#}
Now with the above outputs commented out, a Terraform plan will run without issue.
removing the comments from the output results in:
* aws_ssm_maintenance_window_target.target_install[2]: Resource 'aws_cloudformation_stack.window' not found for variable 'aws_cloudformation_stack.window.outputs'
* aws_ssm_maintenance_window_target.target_install[3]: Resource 'aws_cloudformation_stack.window' not found for variable 'aws_cloudformation_stack.window.outputs'
* aws_ssm_maintenance_window_target.target_install[4]: Resource 'aws_cloudformation_stack.window' not found for variable 'aws_cloudformation_stack.window.outputs'
* aws_ssm_maintenance_window_target.target_install[5]: Resource 'aws_cloudformation_stack.window' not found for variable 'aws_cloudformation_stack.window.outputs'
* aws_ssm_maintenance_window_target.target_install[0]: Resource 'aws_cloudformation_stack.window' not found for variable 'aws_cloudformation_stack.window.outputs'
* aws_ssm_maintenance_window_target.target_install[1]: Resource 'aws_cloudformation_stack.window' not found for variable 'aws_cloudformation_stack.window.outputs'
with calling the following ssm_maintenance_window_target:
resource "aws_ssm_maintenance_window_target" "target_install" {
count = "${length(var.days) * length(var.time)}"
window_id = "${aws_cloudformation_stack.window.outputs.CFWindow_ID}"
resource_type = "INSTANCE"
targets {
key = "tag:maintenancewindow"
values = ["${var.day[count.index % local.counter["day"]]}@${var.time[count.index / local.counter["days"]]}"]
}
When using count, you'll need to use coalescelist to access cloudformation output.
So for example, we have a cloudformation module called foo, with template returning output BAR and BAZ.
locals {
empty_map = {
BAR = ""
BAZ = ""
}
tmp_list = "${coalescelist(aws_cloudformation_stack.foo.*.outputs, list(local.empty_map))}"
tmp_map = "${local.tmp_list[0]}"
cf_outputs_BAR = "${lookup(local.tmp_map, "BAR", "")}"
cf_outputs_BAZ = "${lookup(local.tmp_map, "BAZ", "")}"
}
output "BAR" {
value = "${local.cf_outputs_BAR}"
}
output "BAZ" {
value = "${local.cf_outputs_BAZ}"
}
Huge thanks to @maartenvanderhoef for providing this answer in Gitter
I'm glad to hear you found a (working) workaround! I'm going to close this issue, but please feel free to open another issue if needed.
@skinofstars i am a little bit confused on how to use your solution.
How can i use this with count in the same module ?
I want to reference output ARN from cloudformation and set it as replication task target endpoint.
I want to do something like this
target_endpoint_arn = "${element(aws_cloudformation_stack.s3_endpoint_cf.*.outputs.ARN, count.index)}"
@AleksandarTokarev I believe they key aspect of this is to use coalescelist. As I said, this solution was provided by someone else, so I'm not the expert on this.
Maybe you need something like the following... This assumes an outputs map an ARN list, so you can see some different ways of interacting with results.
resource "aws_cloudformation_stack" "my_stack" {
name = "my_stack"
template_body = "${file("${path.module}/my_stack.json")}"
}
locals {
policy_arn_list = "${coalescelist(aws_iam_policy.my_stack.*.arn, list("", ""))}"
outputs_list = "${coalescelist(aws_cloudformation_stack.my_stack.*.outputs, list(map("VPCID", "")))}"
map = "${local.outputs_list[0]}"
vpc_id = "${lookup(local.map, "VPCID", "")}"
}
resource "aws_iam_user_policy_attachment" "my_iam_user_policy" {
user = "${var.iam_user_name}"
policy_arn = "${local.policy_arn_list[0]}"
}
resource "aws_security_group" "subnet" {
vpc_id = "${local.vpc_id}"
}
Hmm my problem is the same as your original post, where i have a count with cloudformation, and i am getting multiple cloudformation templates each one with 1 output, so my question is whether it is possible to achieve this with the thing u had posted?
@AleksandarTokarev Note that the above example is doing:
map = "${local.outputs_list[0]}"
What you can do is run the lookup inside your resource, though it's a little harder to read:
resource aws_network_interface_sg_attachment payg_f5 {
count = "${aws_cloudformation_stack.payg_f5.count}"
security_group_id = "${aws_security_group.f5.id}"
network_interface_id = "${lookup(local.payg_outputs_list[count.index], "Bigip1subnet1Az1Interface", "")}"
}
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
When using count, you'll need to use coalescelist to access cloudformation output.
So for example, we have a cloudformation module called
foo, with template returning outputBARandBAZ.Huge thanks to @maartenvanderhoef for providing this answer in Gitter