I am trying to use subnet IDs, created by the resource "aws_subnet" "public" {...} block, in to the resource "aws_route_table_association" "public" {...} block, but it is giving the error:
Error: Invalid for_each argument
on main.tf line 69, in resource "aws_route_table_association" "public":
69: for_each = toset(tolist([for subnet in aws_subnet.public : subnet.id]))
├────────────────
│ aws_subnet.public is object with 3 attributes
The "for_each" set includes values derived from resource attributes
that cannot be determined until apply, and so Terraform cannot determine
the full set of keys that will identify the instances of this resource.
When working with unknown values in for_each, it's better to use a map
value where the keys are defined statically in your configuration and
where only the values contain apply-time results.
Alternatively, you could use the -target planning option to first apply
only the resources that the for_each value depends on, and then apply a
second time to fully converge.
As showing in my terraform configuration file below, I used depends_on = [aws_subnet.public] in the resource "aws_route_table_association" "public", expecting that the subnet IDs will be created before associations trigger.
It works, as suggested in the error message, if I run in two steps like:
terraform apply -target=aws_subnet.public -target=aws_subnet.private -auto-approve
terraform apply -auto-approve
The mapping suggestions, I’m not clear about, but I think will require to map using the AWS region name, which I do not want to use, as I need to run this on any region. If I can use the mapping with the aws_region variable, I am OK with this.
How can I accomplish this with one run?
Configuration file main.tf:
// provider
provider "aws" {
region = var.aws_region
shared_credentials_files = ["~/.aws/credentials"]
#profile = var.aws_profile # using `default` profile
}
// data
data "aws_availability_zones" "available" {
state = "available"
filter {
name = "zone-type"
values = ["availability-zone"]
}
}
//variables
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "environment" {
type = string
description = "Deployment Environment (e.g. dev, prod, etc.)"
default = "dev"
}
variable "vpc_cidr" {
type = string
description = "AWS VPC CIDR"
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
// vpc
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
tags = {
Name = "${var.environment}-vpc"
}
}
// public subnets, public route table, public subnet associations
resource "aws_subnet" "public" {
for_each = toset(var.public_subnet_cidrs)
cidr_block = each.value
vpc_id = aws_vpc.this.id
availability_zone = data.aws_availability_zones.available.names[index(var.public_subnet_cidrs, each.value)]
tags = {
Name = "Public-Subnet-${index(var.public_subnet_cidrs, each.value) + 1}"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
tags = {
Name = "Public-RouteTable"
}
}
resource "aws_route_table_association" "public" {
for_each = toset(tolist([for subnet in aws_subnet.public : subnet.id])) # touples --> list --> set
route_table_id = aws_route_table.public.id
subnet_id = each.value
depends_on = [aws_subnet.public]
}
>Solution :
You can just use for_each = aws_subnet.public:
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
route_table_id = aws_route_table.public.id
subnet_id = each.value.id
}