Terraform outs from a resource called via a for_each - amazon-web-services

I'm wondering if anyone can help me with the following I have a base resource to create aws subnets
resource aws_subnet subnet {
vpc_id = var.vpc_id
cidr_block = var.cidr_block
}
output subnetId {
value = aws_subnet.subnet.id
}
module private_subnet {
source = "linktoresourcedetailedabove"
for_each = var.privateSubnet
vpd.id = var.vpc_id
cidr_block = each.value.cidr_block
}
I have a module which calls using a for_each loop based on a var based in, my question is this resource might be called 10 times and I want to store each id and then access this from another module but I seem to be hitting issues here, I tried updating aws_subnet.subnet.id to aws_subnet.subnet.*.id but am still not having anyluck and can't seem to find anything out there that can help me.

If your private_subnet modules has output
output subnetId {
value = aws_subnet.subnet.id
}
then once you create your private_subnet modules, you can get the list of all subnetId creates as:
values(module.private_subnet)[*].subnetId

Related

How do I retrieve multiple vpc endpoints?

ERROR: no matching VPC Endpoint found
(error referring to data code block)
I am trying to retrieve multiple endpoints from data "aws_vpc_endpoint" resource. I created locals to retrieve service name for multiple endpoints that share the first few characters. Afterwards, the endpoints have unique characters to identify them individually.
I am wanting the data resource to loop through the data and retrieve each endpoint that shares those few characters. Then grab each endpoint id for "aws_route". FYI: The endpoints are being created from resource "aws_networkfirewall_firewall" The main thing to look at in this code snippet is locals, data, and the last line for resource "aws_route" How can I express in locals that the service_name does not end there and the rest of the string is unique to the endpoint without hard coding each service_name?
locals {
endpoints = {
service_name = "com.amazonaws.vpce.us-east-1.vpce-svc-"
}
}
data "aws_vpc_endpoint" "firewall-endpoints" {
for_each = local.endpoints
vpc_id = aws_vpc.vpc.id
service_name = each.value
#filter {
# name = "tag:AWSNetworkFirewallManaged"
# values = [true]
#}
}
resource "aws_route" "tgw_route" {
count = var.number_azs
route_table_id = aws_route_table.tgw_rt[count.index].id
destination_cidr_block = var.tgw_aws_route[0]
vpc_endpoint_id = data.aws_vpc_endpoint.firewall-endpoints["service_name"].id
}
I can't test this, but I think what you want to do is something like this:
resource "aws_route" "tgw_route" {
for_each = aws_networkfirewall_firewall.firewall_status.sync_states
route_table_id = aws_route_table.tgw_rt[???].id
destination_cidr_block = var.tgw_aws_route[0]
vpc_endpoint_id = each.value.attachment.endpoint_id
}
I'm not clear on the structure of the firewall_status output, so that may need to change slightly. The main question is how to get the appropriate route table ID per subnet. Can you access the outputs of the tgw_rt module in some way other than by index? Unfortunately, I have no experience with setting up an AWS firewall, just with Terraform, so I don't know how to solve this part of the puzzle.

Get first element of a tuple in terraform

I'm trying to deploy my EKS nodes with only one subnet but I dont know how to give to the resource only one. I show you my code:
resource "aws_eks_node_group" "managed_workers" {
for_each = var.nodegroups[terraform.workspace]
cluster_name = aws_eks_cluster.cluster.name
node_group_name = each.value.Name
node_role_arn = aws_iam_role.managed_workers.arn
subnet_ids = aws_subnet.private.*.id
On the other hand I have a normal task to create the subnets and give the ouput to all my code:
resource "aws_subnet" "private" {
count = length(local.subnet_priv)
vpc_id = var.vpc_id[terraform.workspace]
cidr_block = local.subnet_priv[count.index]
availability_zone = element(lookup(var.availability_zones, terraform.workspace), count.index)
map_public_ip_on_launch = false
So.. I don't know how to get from my subnet_ids argument only the first subnet of the tuple. Now, as you can see, I'm getting all of them but I tried different ways to do but with no success (aws_subnet.private[0].*.id , aws_subnet.private[0].id, etc)
Any idea?
Thanks a lot!
EKS node group subnet_ids arguments expects a tuple. In the original example subnet_ids = aws_subnet.private.*.id the splat operator is used. The spear operator (*) essentially creates a tuple with all the available resources, in our case all the available subnets.
If we want to pass only one subnet from all the available ones, we have to create a tuple with a single element. We could do that by taking the first element from all the existing ones, for example:
subnet_ids = [aws_subnet.private[0].id]
Although, this might work, I personally don't really consider it to be elegant solution. Certainly a better way to accomplish the same result is to modify the local.subnet_priv tuple to contain only one subnet id.

Terraform - Launch in specific availability zones in any region

I am looking to launch a subnet in the 'a' or 'b' availability zone of any region I specify - is there a way I can allow terraform to do this?
Something along the lines of this:
variable "az" {
default = {
a = 1
b = 2
}
}
resource "aws_subnet" "example" {
vpc_id = aws_vpc.vpc.id
availability_zone = var.az.(places subnets in either of the specified AZs)
}
The accepted answer is a fine answer for the question as posed. I'm writing this answer to show an alternative that doesn't directly solve the problem as posed but would achieve an equivalent result in any region where you have access to both the a and b AZs:
data "aws_availability_zones" "all" {
}
locals {
sorted_availability_zones = sort(data.aws_availability_zones.all.names)
selected_availability_zones = toset([
local.sorted_availability_zones[0],
local.sorted_availability_zones[1],
])
}
resource "aws_subnet" "example" {
for_each = local.selected_availability_zones
vpc_id = aws_vpc.vpc.id
availability_zone = each.value
}
The above uses the aws_availability_zones data source to find all of the zones your account has access to in the current region, then sorts them lexically so that they will be in a predictable order, and finally takes the first two items from that sorted list. Assuming you have access to the a and b zones this will always pick those two, because (per the current AZ naming scheme) they will always sort first in the list.
You could simply take the current region name, and append the a or b characters to it, like so:
data "aws_region" "current" {}
locals {
az1 = "${data.aws_region.current.name}a"
az2 = "${data.aws_region.current.name}b"
}

Terraform: length() on data source cannot be determined until apply?

I am trying to dynamically declare multiple aws_nat_gateway data sources by retrieving the list of public subnets through the aws_subnet_ids data source. However, when I try to set the count parameter to be equal to the length of the subnet IDs, I get an error saying The "count" value depends on resource attributes that cannot be determined until apply....
This is almost in direct contradiction to the example in their documentation!. How do I fix this? Is their documentation wrong?
I am using Terraform v0.12.
data "aws_vpc" "environment_vpc" {
id = var.vpc_id
}
data "aws_subnet_ids" "public_subnet_ids" {
vpc_id = data.aws_vpc.environment_vpc.id
tags = {
Tier = "public"
}
depends_on = [data.aws_vpc.environment_vpc]
}
data "aws_nat_gateway" "nat_gateway" {
count = length(data.aws_subnet_ids.public_subnet_ids.ids) # <= Error
subnet_id = data.aws_subnet_ids.public_subnet_ids.ids.*[count.index]
depends_on = [data.aws_subnet_ids.public_subnet_ids]
}
I expect to be able to apply this template successfully, but I am getting the following error:
Error: Invalid count argument
on ../src/variables.tf line 78, in data "aws_nat_gateway" "nat_gateway":
78: count = "${length(data.aws_subnet_ids.public_subnet_ids.ids)}"
The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.
It seems you are trying to fetch subnets that weren't created yet or they couldn't be determinated, the terraform cmd output suggests you add -target flag to create the VPC and subnets or do another task first, after that, you'll apply the nat_gateway resource. I suggest you use the AZs list instead of subnets ids, I'll add a simple example below.
variable "vpc_azs_list" {
default = [
"us-east-1d",
"us-east-1e"
]
}
resource "aws_nat_gateway" "nat" {
count = var.enable_nat_gateways ? length(var.azs_list) : 0
allocation_id = "xxxxxxxxx"
subnet_id = "xxxxxxxxx"
depends_on = [
aws_internet_gateway.main,
aws_eip.nat_eip,
]
tags = {
"Name" = "nat-gateway-name"
"costCenter" = "xxxxxxxxx"
"owner" = "xxxxxxxxx"
}
}
I hope will be useful to you and other users.

'Not a valid output for module' when using output variable with terraform

I'm trying to setup some IaC for a new project using Hashicorp Terraform on AWS. I'm using modules because I want to be able to reuse stuff across multiple environments (staging, prod, dev, etc.)
I'm struggling to understand where I have to set an output variable within a module, and how I then use that in another module. Any pointers to this would be greatly appreciated!
I need to use some things created in my VPC module (subnet IDs) when creating EC2 machines. My understanding is that you can't reference something from one module in another, so I am trying to use an output variable from the VPC module.
I have the following in my site main.tf
module "myapp-vpc" {
source = "dev/vpc"
aws_region = "${var.aws_region}"
}
module "myapp-ec2" {
source = "dev/ec2"
aws_region = "${var.aws_region}"
subnet_id = "${module.vpc.subnetid"}
}
dev/vpc simply sets some values and uses my vpc module:
module "vpc" {
source = "../../modules/vpc"
aws_region = "${var.aws_region}"
vpc-cidr = "10.1.0.0/16"
public-subnet-cidr = "10.1.1.0/24"
private-subnet-cidr = "10.1.2.0/24"
}
In my vpc main.tf, I have the following at the very end, after the aws_vpc and aws_subnet resources (showing subnet resource):
resource "aws_subnet" "public" {
vpc_id = "${aws_vpc.main.id}"
map_public_ip_on_launch = true
availability_zone = "${var.aws_region}a"
cidr_block = "${var.public-subnet-cidr}"
}
output "subnetid" {
value = "${aws_subnet.public.id}"
}
When I run terraform plan I get the following error message:
Error: module 'vpc': "subnetid" is not a valid output for module "vpc"
Outputs need to be passed up through each module explicitly each time.
For example if you wanted to output a variable to the screen from a module nested below another module you would need something like this:
child-module.tf
output "child_foo" {
value = "foobar"
}
parent-module.tf
module "child" {
source = "path/to/child"
}
output "parent_foo" {
value = "${module.child.child_foo}"
}
main.tf
module "parent" {
source = "path/to/parent"
}
output "main_foo" {
value = "${module.parent.parent_foo}"
}