Problem referencing aws_nat_gateway within route table - terraform

The nat_gateway_id argument in the last resource below is busted. I've been staring at the sun for too long, though, and the issue isn't jumping out at me.
resource "aws_eip" "nat_gateway_ip" {
for_each = aws_subnet.public
vpc = true
}
}
# Create NAT gateways in public subnets
resource "aws_nat_gateway" "private" {
for_each = aws_subnet.public
allocation_id = aws_eip.nat_gateway_ip[each.key].id
subnet_id = each.value.id
}
# Create Route tables and default routes
resource "aws_route_table" "private" {
for_each = toset([ "test", "prod"])
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.private[each.key].id
}
}
The resulting error is:
nat_gateway_id = aws_nat_gateway.private[each.key].id
│ ├────────────────
│ │ aws_nat_gateway.private is object with 2 attributes
│ │ each.key is "test"
│
│ The given key does not identify an element in this collection value.

Related

Terraform on Azure - I am trying to call the results of a for_each block in one resource in another resource

Terraform on Azure - I am trying to call the results of a for_each block in one resource in another resource.
I have gone through the following & cant seem to be able to wrap my head around the logic.
https://stackoverflow.com/questions/71646136/terraform-reference-a-for-each-resource-from-another-for-each-resource
https://stackoverflow.com/questions/68571073/for-each-loop-in-resource-block-terraform
I have 2 resource blocks [I have added the locals file ]
# Local Variables
#
locals {
# Create a flattened list for resource groups in multiple regions
multi_region_rg = flatten([
for rg_key, rg in var.multiple_resource_groups : [
for region_key, region in var.regions : {
rg_name = var.environment != "" ? format("%s-rg-%s-%s-%s", var.tenant, rg_key, var.environment, region.code) : format("%s-rg-%s-%s", var.tenant, rg_key, region.code)
region_name = region.name
}
]
])
}
# Local Variables
#
locals {
# Create a flattened list for resource groups in multiple regions
multi_region_vnet = flatten([
for vnet_key, vnet in var.multiple_vnets : [
for region_key, region in var.regions : {
vnet_name = var.environment != "" ? format("%s-rg-%s-%s-%s", var.tenant, vnet_key, var.environment, region.code) : format("%s-rg-%s-%s", var.tenant, vnet_key, region.code)
region_name = region.name
virtual_network_address_space = vnet.address_space
}
]
])
}
TFVARS file
# Global tags
global_tags = { "ManagedBy" = "Terraform" }
# Map of regions for deployment of resource groups
regions = {
region1 = { name = "australiaeast", code = "ae" },
region2 = { name = "australiasoutheast", code = "ase" }
}
# Tenant
tenant = "cubem_"
# Map of resource groups
# NOTE: Use short names or aliases for resource groups as the code will generate the resource group name as per the following format:
# <tenant>-rg-<resource_group_alias>-<environment>-<region_code>
multiple_resource_groups = {
connectivity = {
tags = {
CostCenter = "NETWORKS",
Department = "Network Services"
}
}
}
###################################### Vnet Values #######################
# Map of resource groups
# NOTE: Use short names or aliases for resource groups as the code will generate the resource group name as per the following format:
# <tenant>-rg-<resource_group_alias>-<environment>-<region_code>
multiple_vnets = {
vnetx = { address_space = ["10.1.0.0/24"] },
}
# Create 2 resource groups in 2 regions [1 in each region]
#
resource "azurerm_resource_group" "rg" {
for_each = {
for k, v in local.multi_region_rg : v.rg_name => v
}
name = each.key
location = each.value.region_name
}
# Create 2 vnets in 2 regions [1 in each region]
#
resource "azurerm_virtual_network" "vnet" {
for_each = {
for k, v in local.multi_region_vnet : v.vnet_name => v
}
name = each.key
location = each.value.region_name
address_space = each.value.virtual_network_address_space
resource_group_name = azurerm_resource_group.rg[each.key] #Im trying to call the results of the resource group block above
}
These blocks work well when they are run independently but I want to have the ability to run them concurrently so that the vnets gets deployed into the resource groups that are created.
If I use this line resource_group_name = azurerm_resource_group.rg[each.key] its throwing this error
PS C:\PycharmProjects\terraform-projects\my_azure_connectivity_ref_repo\hub-and-spoke\modules\vnet> terraform plan
╷
│ Error: Invalid index
│
│ on main.tf line 69, in resource "azurerm_virtual_network" "vnet":
│ 69: resource_group_name = azurerm_resource_group.rg[each.key]
│ ├────────────────
│ │ azurerm_resource_group.rg is object with 2 attributes
│ │ each.key is "rg-vnetx-ae"
│
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│
│ on main.tf line 69, in resource "azurerm_virtual_network" "vnet":
│ 69: resource_group_name = azurerm_resource_group.rg[each.key]
│ ├────────────────
│ │ azurerm_resource_group.rg is object with 2 attributes
│ │ each.key is "rg-vnetx-ase"
│
│ The given key does not identify an element in this collection value.
╵
PS C:\PycharmProjects\terraform-projects\my_azure_connectivity_ref_repo\hub-and-spoke\modules\vnet>
There are two issues in your resource "azurerm_virtual_network" "vnet" definition in the resource_group_name attribute.
[each.key] in azurerm_resource_group.rg[each.key] expression does not exists. here [each.key] is the vnet name actually coming from for_each = { for k, v in local.multi_region_vnet : v.vnet_name => v} which is not available in the resource azurerm_resource_group.rg
> { for k, v in local.multi_region_vnet : v.vnet_name => v }
{
"test-rg-vnet_key-dev-ae" = {
"region_name" = "australiaeast"
"virtual_network_address_space" = [
"10.1.0.0/24",
]
"vnet_name" = "test-rg-vnet_key-dev-ae"
}
"test-rg-vnet_key-dev-ase" = {
"region_name" = "australiasoutheast"
"virtual_network_address_space" = [
"10.1.0.0/24",
]
"vnet_name" = "test-rg-vnet_key-dev-ase"
}
}
The other issue also lies in the resource_group_name attribute of vnet resource with the reference. in spite of azurerm_resource_group.rg[$(expression)] should be azurerm_resource_group.rg[$(expression)].name
Hence, You have to adjust either of your resources with a standard key which is common for both resource_group and vnet. I have done it in the resource group with location/region as it is required in both of the resources.
Hence the correct code addressing both the above issues would be.
## key is now region_name in azurerm_resource_group ##
resource "azurerm_resource_group" "rg" {
for_each = {
for k, v in local.multi_region_rg : v.region_name => v
}
name = each.value.rg_name
location = each.key
}
resource "azurerm_virtual_network" "vnet" {
for_each = {
for k, v in local.multi_region_vnet : v.vnet_name => v
}
name = each.key
location = each.value.region_name
address_space = each.value.virtual_network_address_space
resource_group_name = azurerm_resource_group.rg[(each.value.region_name)].name
}
Code In Action
➜ random_local_tests git:(main) ✗ terraform apply -auto-approve
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.rg["australiaeast"] will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "australiaeast"
+ name = "test-rg-vnet_key-dev-ae"
}
# azurerm_resource_group.rg["australiasoutheast"] will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "australiasoutheast"
+ name = "test-rg-vnet_key-dev-ase"
}
# azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"] will be created
+ resource "azurerm_virtual_network" "vnet" {
+ address_space = [
+ "10.1.0.0/24",
]
+ dns_servers = (known after apply)
+ guid = (known after apply)
+ id = (known after apply)
+ location = "australiaeast"
+ name = "test-rg-vnet_key-dev-ae"
+ resource_group_name = "test-rg-vnet_key-dev-ae"
+ subnet = (known after apply)
}
# azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"] will be created
+ resource "azurerm_virtual_network" "vnet" {
+ address_space = [
+ "10.1.0.0/24",
]
+ dns_servers = (known after apply)
+ guid = (known after apply)
+ id = (known after apply)
+ location = "australiasoutheast"
+ name = "test-rg-vnet_key-dev-ase"
+ resource_group_name = "test-rg-vnet_key-dev-ase"
+ subnet = (known after apply)
}
Plan: 4 to add, 0 to change, 0 to destroy.
azurerm_resource_group.rg["australiaeast"]: Creating...
azurerm_resource_group.rg["australiasoutheast"]: Creating...
azurerm_resource_group.rg["australiaeast"]: Creation complete after 5s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ae]
azurerm_resource_group.rg["australiasoutheast"]: Creation complete after 6s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ase]
azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"]: Creating...
azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"]: Creating...
azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"]: Still creating... [10s elapsed]
azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"]: Still creating... [10s elapsed]
azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ae"]: Creation complete after 18s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ae/providers/Microsoft.Network/virtualNetworks/test-rg-vnet_key-dev-ae]
azurerm_virtual_network.vnet["test-rg-vnet_key-dev-ase"]: Creation complete after 19s [id=/subscriptions/xxxxxxxxxxxx/resourceGroups/test-rg-vnet_key-dev-ase/providers/Microsoft.Network/virtualNetworks/test-rg-vnet_key-dev-ase]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Hope it helps.
And additional info which is not relevant here too much I have used static values instead of some variables.
rg_name = var.environment != "" ? format("%s-rg-%s-%s-%s", "test", "vnet_key", var.environment, region.code) : format("%s-rg-%s-%s", "test", "vnet_key", region.code)
vnet_name = var.environment != "" ? format("%s-rg-%s-%s-%s", "test", "vnet_key", var.environment, region.code) : format("%s-rg-%s-%s", "test", "vnet_key", region.code)

Using the resource ID from a for_each resource block

I've created a Terraform template that creates 2 route tables and 2 subnets using the for_each command. I am trying to associate the route tables to the two subnets, however I am struggling to do so because I don't know how to obtain the ID for the route tables and subnets as the details are not in a variable, and I'm not sure how to get that information and use it. Please may someone provide assistance?
Thank you
Main Template
# SUBNETS DEPLOYMENT
resource "azurerm_subnet" "subnets" {
depends_on = [azurerm_virtual_network.vnet]
for_each = var.subnets
resource_group_name = var.rg.name
virtual_network_name = var.vnet.config.name
name = each.value.subnet_name
address_prefixes = each.value.address_prefixes
}
# ROUTE TABLE DEPLOYMENT
resource "azurerm_route_table" "rt" {
depends_on = [azurerm_virtual_network.vnet]
for_each = var.rt
name = each.value.route_table_name
resource_group_name = var.rg.name
location = var.rg.location
disable_bgp_route_propagation = true
route = [ {
address_prefix = each.value.address_prefix
name = each.value.route_name
next_hop_in_ip_address = each.value.next_hop_ip
next_hop_type = each.value.next_hop_type
} ]
}
# ROUTE TABLE ASSOICATION
resource "azurerm_subnet_route_table_association" "rt_assoication" {
subnet_id = azurerm_subnet.subnets.id
route_table_id = azurerm_route_table.rt.id
}
Variables
# SUBNET VARIBALES
variable "subnets" {
description = "subnet names and address prefixes"
type = map(any)
default = {
subnet1 = {
subnet_name = "snet-001"
address_prefixes = ["172.17.208.0/28"]
}
subnet2 = {
subnet_name = "snet-002"
address_prefixes = ["172.17.208.32/27"]
}
}
}
# ROUTE TABLES VARIABLES
variable "rt" {
description = "variable for route tables."
type = map(any)
default = {
rt1 = {
route_table_name = "rt1"
address_prefix = "0.0.0.0/0"
route_name = "udr-azure-firewall"
next_hop_ip = "10.0.0.0"
next_hop_type = "VirtualAppliance"
}
rt2 = {
route_table_name = "rt2"
address_prefix = "0.0.0.0/0"
route_name = "udr-azure-firewall"
next_hop_ip = "10.0.0.0"
next_hop_type = "VirtualAppliance"
}
}
}
The error I get when I run terraform plan is:
│ Error: Missing resource instance key
│
│ on modules\vnet\main.tf line 74, in resource "azurerm_subnet_route_table_association" "rt_assoication":
│ 74: subnet_id = azurerm_subnet.subnets.id
│
│ Because azurerm_subnet.subnets has "for_each" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ azurerm_subnet.subnets[each.key]
╵
╷
│ Error: Missing resource instance key
│
│ on modules\vnet\main.tf line 75, in resource "azurerm_subnet_route_table_association" "rt_assoication":
│ 75: route_table_id = azurerm_route_table.rt.id
│
│ Because azurerm_route_table.rt has "for_each" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ azurerm_route_table.rt[each.key]
Looks like you are almost there, update the following in the subnet-route table association block, it should work:
# ROUTE TABLE ASSOICATION
resource "azurerm_subnet_route_table_association" "rt_assoication" {
subnet_id = azurerm_subnet.subnets[each.key].id
route_table_id = azurerm_route_table.rt[each.key].id
}

How to add security group rule to multiple security groups

I'm creating one security group rule and want to attach it to multiple security groups. How can I do it? For example:
resource "aws_security_group" "test-sg-1" {
name = "Test SG 1"
description = "Test Security Group one"
vpc_id = aws_vpc.test_vpc.id
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "test-sg-2" {
name = "Test SG 2"
description = "Test Security Group two"
vpc_id = aws_vpc.test_vpc.id
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group_rule" "egress_all" {
from_port = 0
protocol = "-1"
security_group_id = [aws_security_group.test-sg-1.id, aws_security_group.test-sg-2.id]
to_port = 0
type = "egress"
cidr_blocks = ["0.0.0.0/0"]
}
I'm getting error if I try this above way of using a list.
│ Error: Incorrect attribute value type
│
│ on main.tf line 76, in resource "aws_security_group_rule" "egress_all":
│ 76: security_group_id = [aws_security_group.test-sg-1.id, aws_security_group.test-sg-2.id]
│ ├────────────────
│ │ aws_security_group.test-sg-1.id will be known only after apply
│ │ aws_security_group.test-sg-2.id will be known only after apply
│
│ Inappropriate value for attribute "security_group_id": string required.
In this case using the for_each meta-argument [1] might be a good idea to avoid code repetition. So this is what I would do:
locals {
sg_names = ["Test SG 1", "Test SG 2"]
}
resource "aws_security_group" "test_sg" {
for_each = toset(local.sg_names)
name = each.value
description = each.value
vpc_id = aws_vpc.test_vpc.id
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group_rule" "egress_all" {
for_each = aws_security_group.test_sg
from_port = 0
protocol = "-1"
security_group_id = each.value.id
to_port = 0
type = "egress"
cidr_blocks = ["0.0.0.0/0"]
}
Here the resource chaining is used. You can read more in [2].
[1] https://www.terraform.io/language/meta-arguments/for_each#basic-syntax
[2] https://www.terraform.io/language/meta-arguments/for_each#chaining-for_each-between-resources

Need to pass values for nsg rules in terraform resource block from a map type variable

I am trying a create a resource block for azurerm_network_security_rule such that it can be mapped with subnet variable being used in resource block as well so that subnet association with nsg would be easy.
for eg. variable is like
variable "subnets" {
description = "A list of subnets inside the VPC"
type = map(any)
default = {
subnet = {
name = "test"
address_prefix = ["x.x.x.x/x"]
attached_nat = "false"
inbound_rules = [
# [name, priority, direction, access, protocol, destination_port_range, source_address_prefix, destination_address_prefix]
["http", "100", "Inbound", "Allow", "Tcp", "80", "*", "0.0.0.0/0"],
]
outbound_rules = [
# [name, priority, direction, access, protocol, destination_port_range, source_address_prefix, destination_address_prefix]
["ntp_out", "103", "Outbound", "Allow", "Udp", "123", "", "0.0.0.0/0"],
]
}
}
}
Getting error
**Error: Invalid for_each argument
│
│ on main.tf line 120, in resource "azurerm_network_security_rule" "subnet":
│ 120: for_each = concat([for k, v in var.subnets: {"inbound_rules"=lookup(v,"inbound_rules",[])}], [for k, v in var.subnets: {"outbound_rules"=lookup(v,"outbound_rules",[])}])
│ ├────────────────
│ │ var.subnets is map of object with 1 element
│
│ The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type │ tuple.╵**
my resource block is like -
resource "azurerm_network_security_group" "subnet" {
for_each = var.subnets
name = format("%s-${each.value.name}")
location = var.location
resource_group_name = var.resource_group_name
}
resource "azurerm_network_security_rule" "subnet" {
for_each = concat([for k, v in var.subnets: {"inbound_rules"=lookup(v,"inbound_rules",[])}], [for k, v in var.subnets: {"outbound_rules"=lookup(v,"outbound_rules",[])}])
name = each.value[0]
priority = each.value[1]
direction = each.value[2]
access = each.value[3]
protocol = each.value[4]
source_port_range = "*"
destination_port_range = each.value[5]
source_address_prefix = each.value[6]
destination_address_prefix = each.value[7]
description = "${each.value[2]}_Port_${each.value[5]}"
resource_group_name = var.resource_group_name
network_security_group_name = azurerm_network_security_group.subnet[each.key].name
}
If someone can help figuring out the loop to put in for_each to achieve it.

How to validate acm certificate created via terraform

I'm trying to create 3 certificates for 3 services named test,valid,jsc and using an exiting hosted zone.But terraform is failing in cert validation stating that certificate is in pending_validation state
variable "service-names" {
type = list(any)
default = ["valid", "jsc", "test"]
}
resource "aws_acm_certificate" "cert" {
for_each = toset(var.service-names)
domain_name = "www.us.${each.value}.xxxxx.cloud"
validation_method = "DNS"
tags = {
Name = "www.us.${each.value}.xxxxx.cloud"
}
lifecycle {
create_before_destroy = true
}
}
data "aws_route53_zone" "ecomzoneinfo" {
name = "xxxxx.cloud"
tags = {
"Name" = "www.us.${each.value}.xxxxx.cloud"
}
}
locals {
dns-record-list = flatten([
for svc in var.service-names : [
for dvo in aws_acm_certificate.cert[svc].domain_validation_options : {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
service = svc
}
]
])
}
resource "aws_route53_record" "ecom-route" {
for_each = { for idx,dns in local.dns-record-list : "${dns.service}" => dns }
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.ecomzoneinfo.zone_id
}
resource "aws_acm_certificate_validation" "ecom" {
for_each = toset(var.service-names)
certificate_arn = aws_acm_certificate.cert[each.value].arn
validation_record_fqdns = [aws_route53_record.ecom-route[each.value].fqdn]
}
Getting Error stating as certificate is in pending validation and yes i'm able to the certs created by terraform aws_acm_certification resource are in pending status.Even i'm able to see the cname records are added under the zone xxxxx.cloud hosted zone.So i'm not sure what is wrong here
Error: Error describing created certificate: Expected certificate to be issued but was in state PENDING_VALIDATION
│
│ with aws_acm_certificate_validation.ecom["jsc"],
│ on main.tf line 449, in resource "aws_acm_certificate_validation" "ecom":
│ 449: resource "aws_acm_certificate_validation" "ecom" {
│
╵
╷
│ Error: Error describing created certificate: Expected certificate to be issued but was in state PENDING_VALIDATION
│
│ with aws_acm_certificate_validation.ecom["test"],
│ on main.tf line 449, in resource "aws_acm_certificate_validation" "ecom":
│ 449: resource "aws_acm_certificate_validation" "ecom" {
│
╵
╷
│ Error: Error describing created certificate: Expected certificate to be issued but was in state PENDING_VALIDATION
│
│ with aws_acm_certificate_validation.ecom["valid"],
│ on main.tf line 449, in resource "aws_acm_certificate_validation" "ecom":
│ 449: resource "aws_acm_certificate_validation" "ecom" {
As per the documentation provided.
resource "aws_acm_certificate" "certificate" {
domain_name = "mydomain.com"
subject_alternative_names = [
"*.mydomain.com"
]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "acm-records" {
for_each = {
for dvo in aws_acm_certificate.certificate.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [
each.value.record
]
ttl = 60
type = each.value.type
zone_id = aws_route53_zone.mydomain.zone_id
}
resource "aws_acm_certificate_validation" "acm-validation" {
certificate_arn = aws_acm_certificate.certificate.arn
validation_record_fqdns = [for record in aws_route53_record.acm-records : record.fqdn]
}

Resources