Attempting to use list of stacks output as module output - terraform

I have a module that creates a variable number of CloudFormation Stacks. This works just fine but I am having problems attempting to use the Stack output as output in the module. The stack creates a subnet and I specify the created subnet id as an output of the stack. Then I want to return a list of all subnet ids as part of module output. This is what I think my output should look like:
output "subnets" {
value = ["${aws_cloudformation_stack.subnets.*.outputs["Subnet"]}"]
}
I get an integer parse error when I do that. Terraform seems to be treating outputs as a list instead of a map. Any way to get this to work?
Edit: Here is where I declare the stacks:
resource "aws_cloudformation_stack" "subnets" {
count = "${local.num_zones}"
name = "Subnet-${element(local.availability_zones, count.index)}"
on_failure = "DELETE"
template_body = "${file("${path.module}/templates/subnet.yaml")}"
parameters {
CIDR = "${cidrsubnet(var.cidr,ceil(log(local.num_zones * 2, 2)), count.index)}"
AZ = "${element(local.availability_zones, count.index)}"
VPC = "${aws_cloudformation_stack.vpc.outputs["VPCId"]}"
}
}
Then there is a Stack output in subnet.yaml that is has key Subnet and is the id of the subnet that was created.
The stacks are all created successfully but I can't seem to get exporting all the created subnet ids from my terraform module. Not sure why terraform is treating *.outputs as list vs keeping *.outputs["Subnet"] as the list. I'm guessing *.outputs is getting converted to a list of maps but I need a list of a specific key (Subnet) in the map.
I've got a non list example working for stack and using output from stack as terraform module output:
resource "aws_cloudformation_stack" "vpc" {
name = "${var.name_prefix}-VPC"
on_failure = "DELETE"
template_body = "${file("${path.module}/templates/vpc.yaml")}"
parameters {
CIDR = "${var.cidr}"
}
}
output "vpc" {
value = "${aws_cloudformation_stack.vpc.outputs["VPCId"]}"
}
I was able to work around the issue by declaring data to lookup the subnets after creation. It's not ideal but gets me passed being stuck. Let me know if anyone knows how to do what I was originally trying to do. Here is what I came up with:
data "aws_subnet_ids" "subnets" {
depends_on = ["aws_cloudformation_stack.subnets"]
vpc_id = "${aws_cloudformation_stack.vpc.outputs["VPCId"]}"
}
output "subnets" {
value = "${data.aws_subnet_ids.subnets.ids}"
}

Related

How to select the value of a specific resource in tuple based on a property value?

Hello im struggling a little to solve this challenge and hope someone can provide insights. I need to pick a specific resource out of a tuple of those resources based on a property on that resource. How would one go about achieving something like that?
Code looks something like:
resource "aws_network_interface" "network_interface" {
for_each = var.counter_of_2
// stuff
}
resource "aws_network_interface_attachment" "currently_used_eni" {
instance_id = var.instance_id
network_interface_id = <the aws_network_interface with tag.Name = "thisone">
device_index = 0
}
Because tag is not ID of resource something like that would be impossible. Imagine if you have two network interfaces with same tag. Which one should it take?
But your case might be not lost. (yet it might... depends on case)
I assume that if you want to find specific aws_network_interface then tags are unique. And if they are unique then every network interface have different tag. So you can simply use something like that:
aws_network_interface.network_interface["value_from_for_each"]
For simplicity (and assuming that one network interface = one tag) I'll make this for_each collection to be set of tag names.
variable "interface_tags" {
type = set(string)
default = ["tag1", "tag2", "thisone"]
}
resource "aws_network_interface" "network_interface" {
for_each = var.interface_tags
// stuff
}
resource "aws_network_interface_attachment" "currently_used_eni" {
instance_id = var.instance_id
network_interface_id = aws_network_interface.network_interface["thisone"].id
device_index = 0
}
And this should work (and I hope it can be used in your case)

Cannot assign variable from data.tf to variables.tf file

New to terraform, and have been building out the infrastructure recently.
I am trying to pull secrets from azure key vault and assign the keys to the variables.tf file depending on the environment(dev.tfvars, test.tfvars, etc). However when I execute the plan with the tfvar file as the parameter, I get an error with the following message:
Error: Variables not allowed
Here are the files and the relevant contents of it.
variables.tf:
variable "user_name" {
type = string
sensitive = true
}
data.tf (referencing the azure key vault):
data "azurerm_key_vault" "test" {
name = var.key_vault_name
resource_group_name = var.resource_group
}
data "azurerm_key_vault_secret" "test" {
name = "my-key-vault-key-name"
key_vault_id = data.azurerm_key_vault.test.id
}
test.tfvars:
user_name = "${data.azurerm_key_vault_secret.test.value}" # Where the error occurrs
Can anyone point out what I'm doing wrong here? And if so is there another way to achieve such a thing?
In Terraform a variable can be used for user input only. You can not assign to them anything dynamically computed from your code. They are like read-only arguments, for more info see Input Variables from the doc.
If you want to assign a value to something for later use, you must use locals. For example:
locals {
user_name = data.azurerm_key_vault_secret.test.value
}
Local values can be changed dynamically during execution. For more info, see Local Values.
You can't create dynamic variables. All variables must have known values before execution of your code. The only thing you could do is to use local, instead of variabile:
locals {
user_name = data.azurerm_key_vault_secret.test.value
}
and then refer to it as local.user_name.

How to create multiple resources in a for loop with terraform?

I have looked at several bits of documentation as well as a udemy course on terraform and I do not understand how to do the thing that I want to do. I want to create a for loop and in it I want to create an S3 event notification, create an Sns topic that listens to that notification, create an Sqs queue and then subscribe the queue to the sns topic. It seems like for loops in terraform are not advanced enough to do this. Am I wrong, is there any documentation or examples that explain how to use for loops for this use case?
Thanks in advance.
An example to create AWS VPC subnets then give them to AWS EC2 instances.
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidr_blocks)
vpc_id = var.vpc_id
cidr_block = var.public_subnet_cidr_blocks[count.index]
}
resource "aws_instance" "public_ec2" {
count = length(var.public_subnet_ids)
subnet_id = var.public_subnet_ids[count.index]
ami = var.ami_id
instance_type = "t2.micro"
tags = {
Name = "PublicEC2${count.index}}"
}
provisioner "local-exec" {
command = <<EOF
echo "Public EC2 ${count.index} ID is ${self.id}"
EOF
}
}
There is no syntax like below to create resources.
[ for name in var.names:
aws_s3_bucket {...}
aws_sns_topic {...}
]
For expression is basically for values, not for creating resources.
for Expressions
A for expression creates a complex type value by transforming another complex type value.
To create multiple resources, as below in the document use for_each or count.
for_each: Multiple Resource Instances Defined By a Map, or Set of Strings
By default, a resource block configures one real infrastructure object. However, sometimes you want to manage several similar objects, such as a fixed pool of compute instances. Terraform has two ways to do this: count and for_each.
The for_each example looks like the following:
variable names {
type = list(string)
description = "List of names"
}
resoure "aws_foo" "bar" {
for_each = toset(var.names)
name = each.key
}
This will create N resources (length of names), using the each.key to specify the value to assign to name each time

Iterate over map with lists in terraform 0.12

I am using terraform 0.12.8 and I am trying to write a resource which would iterate over the following variable structure:
variable "applications" {
type = map(string)
default = {
"app1" = "test,dev,prod"
"app2" = "dev,prod"
}
}
My resource:
resource "aws_iam_user" "custom" {
for_each = var.applications
name = "circleci-${var.tags["ServiceType"]}-user-${var.tags["Environment"]}-${each.key}"
path = "/"
}
So, I can iterate over my map. However, I can't figure out how to verify that var.tags["Environment"] is enabled for specific app e.g. app1.
Basically, I want to ensure that the resource is created for each application as long as the Environment variable is in the list referencing app name in the applications map.
Could someone help me out here?
Please note that I am happy to go with a different variable structure if you have something to propose that would accomplish my goal.

How to display public ip of ec2_instance after terraform creation

I would like to display the public_ip of aws_instance created by terraform after terraform execution.
However, only the global IP of the first server appears.
My source code is as follows.
resource "aws_instance" "main" {
count = "3"
...
}
output "ec2_global_ips" {
value = "${aws_instance.main.*.public_ip}"
}
Is something wrong grammar?
Let me know if you have to know how to display array values as output in terraform.
I believe this will work:
output "ec2_global_ips" {
value = ["${aws_instance.main.*.public_ip}"]
}

Resources