Terraform conditional for AWS route tables - amazon-web-services

Using Terraform, I need to create a route table for a public subnet and private subnet. The resource for a 'public' route table is using gateway_id
resource "aws_route_table" "rt-public" {
route {
cidr_block = var.cidr_block
gateway_id = var.gateway_id
}
}
The 'private' route table is using nat_gateway_id:
resource "aws_route_table" "rt-private" {
route {
cidr_block = var.cidr_block
nat_gateway_id = var.gateway_id
}
}
I pass from the main.tf file a variable var.tier that indicates whether I need a 'public' or 'private' route table.
I tried the following (in the module):
resource "aws_route_table" "rt-public" {
count = var.tier == "Public" ? 1 : 0
route {
cidr_block = var.cidr_block
gateway_id = var.gateway_id
}
}
resource "aws_route_table" "rt-private" {
count = var.tier == "Private" ? 1 : 0
route {
cidr_block = var.cidr_block
nat_gateway_id = var.gateway_id
}
}
This works. But in the main.tf I also need to access the route table id. So I created an outputs.tf file as follows:
output "aws_rt_public_id" {
value = aws_route_table.rt-public.id
}
output "aws_rt_private_id" {
value = aws_route_table.rt-private.id
}
The issue is that when a 'public' route table is created, then the 'private' output is empty. I get the error message: aws_route_table.rt-private is empty tuple. The given key does not identify an element in this collection value.
Similar error message for the public version.
How can this be fixed using conditionals?

You can also use your condition, and return only output that is valid:
output "aws_rt_public_id" {
value = var.tier == "Public" ? aws_route_table.rt-public[0].id : null
}
output "aws_rt_private_id" {
value = var.tier == "Private" ? aws_route_table.rt-private[0].id : null
}
In the above the null, when chosen, will result in a given output removed.

Related

AWS Route Table - Terraform | An argument named "gateway_id" is not expected here

I have an internet gateway and two subnets (public and private).
While i can attach my internet gateway to the public one, i can't on the private one. I add this line in the code:
gateway_id = aws_internet_gateway.wally_jeropa_igw.id
but i have this error:
An argument named "gateway_id" is not expected here.
This is my IGW
resource "aws_internet_gateway" "wally_jeropa_igw" {
vpc_id = aws_vpc.wally_jeropa.id
tags = {
Name = "Testn IGW"
}
}
Public subnet
resource "aws_route_table" "wally_jeropa_vpc_public_route_table" {
vpc_id = aws_vpc.wally_jeropa.id
gateway_id = aws_internet_gateway.wally_jeropa_igw.id
route {
cidr_block = "0.0.0.0/0"
}
tags = {
Name = "Public Subnet Route Table."
}
}
Private subnet
resource "aws_route_table" "wally_jeropa_vpc_private_route_table" {
vpc_id = aws_vpc.wally_jeropa.id
gateway_id = aws_internet_gateway.wally_jeropa_igw.id
tags = {
Name = "Private Subnet Route Table."
}
}
The gateway_id attribute must be inside the route block [1]
References:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table#route

Configure Multiple NAT gateways

I have two public & private subnets across two AZs. I want to create a NAT in both public subnets. How do I route my private route table to use both NATs? Case three in this accepted answer. I know it's possible but I'm unsure on how to implement it. Should the destination cidr_block be the public subnet's cidr block? I also tried 0.0.0.0/0 and errored out.
resource "aws_route" "r" {
count = length(aws_nat_gateway.this)
route_table_id = aws_vpc.vpc.default_route_table_id
destination_cidr_block = "aws_subnet.public_subnets.${count.index}.cidr_block"
nat_gateway_id = "aws_nat_gateway.this.${count.index}.id"
depends_on = [aws_vpc.vpc,aws_subnet.public_subnets,aws_nat_gateway.this]
}
When I tried 0.0.0.0/0
error creating Route in Route Table (rtb-059eee10e310ade77) with destination (0.0.0.0/0): RouteAlreadyExists: The route identified by 0.0.0.0/0 already exists.
Short answer: you need to have a separate route table for each private subnet.
Long answer: I would start by defining a map that contains configuration for each availability zone. I avoid count in modern Terraform configs because it's too easy to cause incompatibilities down the road (especially if you're using it to index an array).
locals {
subnets = {
"1a" = { "public_cidr": "172.31.0.0/24", private_cidr: "172.31.1.0/24" }
"1b" = { "public_cidr": "172.31.2.0/24", private_cidr: "172.31.3.0/24" }
}
}
Now, you can use for_each to create public and private subnets for each of these availability zones:
resource "aws_subnet" "public" {
for_each = local.subnets
vpc_id = aws_vpc.example.id
cidr_block = each.value["public_cidr"]
tags = {
Name = "public-${each.key}"
}
}
resource "aws_subnet" "private" {
for_each = local.subnets
vpc_id = aws_vpc.example.id
cidr_block = each.value["private_cidr"]
tags = {
Name = "private-${each.key}"
}
}
You can associate the public subnets with the default route table (which must have a route to the Internet Gateway). Personally, I prefer creating my own public route table, rather than updating the default.
Next, you'll need to create a NAT Gateway in each public subnet:
resource "aws_nat_gateway" "example" {
depends_on = [aws_internet_gateway.example]
for_each = local.subnets
allocation_id = aws_eip.nat_ip[each.key].id
subnet_id = aws_subnet.public[each.key].id
tags = {
Name = "nat-${each.key}"
}
}
And now the important part: each private subnet needs its own route table, which references the NAT Gateway specific to its availability zone.
resource "aws_route_table" "private" {
for_each = local.subnets
vpc_id = aws_vpc.example.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.example[each.key].id
}
tags = {
Name = "private-${each.key}"
}
}
resource "aws_route_table_association" "private" {
for_each = local.subnets
subnet_id = aws_subnet.private[each.key].id
route_table_id = aws_route_table.private[each.key].id
}

Terraform AWS VCPe Route Creation Endpoint Not Found

When I create a route to an endpoint I get the following error:
InvalidVpcEndpointId.NotFound: The vpcEndpoint ID 'vpce-044e0beXXXXXXXX' does not exist.
But further up in the output (and on the console) I can see the endpoint is created:
module.sec.aws_vpc_endpoint.s3[0]: Creation complete after 6s [id=vpce-044e0beXXXXXXXXX]
This is what I have to create the route:
resource "aws_route" "s3_route" {
count = length(var.s3_routes)
route_table_id = aws_route_table.main.id
destination_cidr_block = var.s3_routes[count.index]
vpc_endpoint_id = var.s3_endpoint_ID[0]
}
This module is an example of one that calls it:
module "sec-route-NATGW-ifw-a" {
source = "./route"
depends_on = [module.i-dmz, module.t-dmz, module.cde, module.tgw-core, module.tgw-ifw]
vpc_id = module.sec.vpc_id
subnet_association = [for s in range(0, length(module.sec.natgw_subnet_IDs)) : module.sec.natgw_subnet_IDs[s] if module.sec.natgw_subnet_AZs[s] == "${local.region}a"]
s3_endpoint_ID = module.sec.s3_endpoint_ID
s3_routes = local.s3_ips
}
And this is the output for the ID:
output "s3_endpoint_ID" {
value = aws_vpc_endpoint.s3[*].id
description = "ID for S3 Endpoint"
}
And the resource to create the endpoint:
resource "aws_vpc_endpoint" "s3" {
count = var.s3_servicename == "" ? 0 : 1
vpc_id = aws_vpc.SEC.id
service_name = var.s3_servicename
}
FYI VPCE ID has been changed from script.
The issue was the configuration on my route resource, I replaced it with the following:
resource "aws_vpc_endpoint_route_table_association" "s3" {
count = length(var.s3_endpoint_ID) == 0 ? 0 : 1
route_table_id = aws_route_table.main.id
vpc_endpoint_id = tostring(var.s3_endpoint_ID[0])
}
I don't think the tostring() is needed and I changed how count worked slightly, but adding the destination_cidr_block argument was the issue.

Terraform how to fix attribute "route": set of object required

I have this terraform module route_table.tf I need to use. It looks like below:
resource "aws_route_table" "aws_route_table" {
# route {
# cidr_block = "0.0.0.0/0"
# gateway_id = var.GATEWAY_ID
# }
route = var.ROUTE
tags = var.ROUTE_TABLE_TAGS
vpc_id = var.VPC_ID
}
and I have defined the variable ROUTE as below in the inputs.tf:
variable "ROUTE" {
type = object({ cidr_block=string, gateway_id=string })
}
And I am passing those values in the main.tf as below:
module "route_tables_public" {
source = "./modules/route_tables"
ROUTE = {
cidr_block = "0.0.0.0/0"
gateway_id = var.GATEWAY_ID
}
ROUTE_TABLE_TAGS = { "Name" : "mlb-rt-public" , "Project" : "mlb"}
VPC_ID = module.ecs_vpc.vpc_id
}
But I am getting this error:
Inappropriate value for attribute "route": set of object required.
Can someone help me on this?
Your var.ROUTE is a single object, but it should be list of objects. So you can try:
variable "ROUTE" {
type = list(object({ cidr_block=string, gateway_id=string }))
}
and then
module "route_tables_public" {
source = "./modules/route_tables"
ROUTE = [{
cidr_block = "0.0.0.0/0"
gateway_id = var.GATEWAY_ID
}]
ROUTE_TABLE_TAGS = { "Name" : "mlb-rt-public" , "Project" : "mlb"}
VPC_ID = module.ecs_vpc.vpc_id
}
UPDATE
Your aws_route_table should be:
resource "aws_route_table" "aws_route_table" {
dynamic "route" {
for_each = var.ROUTE
content {
cidr_block = route.value.cidr_block
gateway_id = route.value.gateway_id
}
}
tags = var.ROUTE_TABLE_TAGS
vpc_id = var.VPC_ID
}
I would recommend against directly assigning variable values to complex resource type arguments like this, because if the schema of the route argument were to grow to include additional attributes in the future then your module would fail validation.
Instead, better to let the variable's type be independent of the route block schema and translate explicitly between them, which should then allow this to work as long as a new version of the provider doesn't introduce a new required argument for that block.
variable "route" {
type = object({
cidr_block = string
gateway_id = string
})
}
resource "aws_route_table" "aws_route_table" {
# ...
route {
cidr_block = var.route.cidr_block
gateway_id = var.route.gateway_id
}
}
This can work because it's constructing a new object that conforms to the route block schema using two attributes of your object, whereas your example fails because you tried to assign the input object directly to that argument, which would require that you match the target type exactly.
Please note that naming variables all in uppercase is not idiomatic Terraform style, so in the above example I renamed the variable to be route instead. ROUTE would also work, but would be an unusual way to name a Terraform variable.

Terraform using output from module

I just started with Terraform infrastructure. Trying to create a vpc module that will contain code for vpc, subnets, internet gateway, rout table. Also creating a separate tf file for rds , which will refer to the vpc module and utilize the private subnets declared in vpc module.
Created a vpc module that has vpc.tf with following
provider "aws" {
region = var.region
}
terraform {
backend "s3" {}
}
resource "aws_vpc" "production-vpc" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
tags = {
Name = "Dev-VPC"
}
}
// Private Subnets
resource "aws_subnet" "private-subnet-1" {
cidr_block = var.private_subnet_1_cidr
vpc_id = aws_vpc.production-vpc.id
availability_zone = "us-east-1a"
tags = {
Name = "Private-Subnet-1"
}
}
resource "aws_subnet" "private-subnet-2" {
cidr_block = var.private_subnet_2_cidr
vpc_id = aws_vpc.production-vpc.id
availability_zone = "us-east-1b"
tags = {
Name = "Private-Subnet-2"
}
}
The output.tf has following
output "private-subnet1-id" {
description = "Private Subnet1 Id"
value = aws_subnet.private-subnet-1.*.id
}
output "private-subnet2-id" {
description = "Private Subnet2 Id"
value = aws_subnet.private-subnet-2.*.id
}
The file is saved in folder \module\vpc folder
Created rds.tf as follows in folder \rds
provider "aws" {
region = var.region
}
terraform {
backend "s3" {}
}
module "vpc" {
source = "../module/vpc"
}
resource "aws_db_subnet_group" "subnetgrp" {
name = "dbsubnetgrp"
subnet_ids = [module.vpc.private-subnet1-id.id, module.vpc.private-subnet2-id.id]
}
When I run terraform plan , I get following error
Error: Unsupported attribute
on rds.tf line 16, in resource "aws_db_subnet_group" "subnetgrp":
16: subnet_ids = [module.vpc.private-subnet1-id.id, module.vpc.private-subnet2-id.id]
|----------------
| module.vpc.private-subnet1-id is tuple with 1 element
This value does not have any attributes.
Error: Unsupported attribute
on rds.tf line 16, in resource "aws_db_subnet_group" "subnetgrp":
16: subnet_ids = [module.vpc.private-subnet1-id.id, module.vpc.private-subnet2-id.id]
|----------------
| module.vpc.private-subnet2-id is tuple with 1 element
This value does not have any attributes.
You don't need the splat expression in the output.tf. Try the following,
output "private-subnet1-id" {
description = "Private Subnet1 Id"
value = aws_subnet.private-subnet-1.id
}
output "private-subnet2-id" {
description = "Private Subnet2 Id"
value = aws_subnet.private-subnet-2.id
}