How To Access Sub Keys In Nested Maps

Terraform VPC & Subnet Configuration

Terraform: Managing VPCs and Subnets Efficiently

Problem: You have a single VPC, and your var.vpcs map represents a set of three subnets per availability zone for two environments.

🔹 Step 1: Define Variables

Instead of using a map of empty objects, define input variables correctly:


variable "vpc_subnets" {
  type = map(map(object({
    public_cidr_block = string
    app_cidr_block    = string
    db_cidr_block     = string
  })))
}

variable "vpc_cidr_blocks" {
  type = map(string)
}
    

🔹 Step 2: Define CIDR Blocks & Subnets

Example values for environments:


vpc_cidr_blocks = {
  "dev" = "10.1.0.0/16"
  "stg" = "10.1.0.0/16"
}

vpc_subnets = {
  "dev" = {
    "ap-southeast-1a" = {
      public_cidr_block = "10.1.1.0/24",
      app_cidr_block    = "10.1.2.0/24",
      db_cidr_block     = "10.1.3.0/24"
    }
    "ap-southeast-1b" = {
      public_cidr_block = "10.1.4.0/24",
      app_cidr_block    = "10.1.5.0/24",
      db_cidr_block     = "10.1.6.0/24"
    }
  }
  "stg" = {
    "ap-southeast-1a" = {
      public_cidr_block = "10.1.1.0/24",
      app_cidr_block    = "10.1.2.0/24",
      db_cidr_block     = "10.1.3.0/24"
    }
    "ap-southeast-1b" = {
      public_cidr_block = "10.1.4.0/24",
      app_cidr_block    = "10.1.5.0/24",
      db_cidr_block     = "10.1.6.0/24"
    }
  }
}
    

🔹 Step 3: Define VPC Resources


resource "aws_vpc" "vpc" {
  for_each = var.vpc_cidr_blocks

  cidr_block           = each.value
  instance_tenancy     = var.instance_tenancy
  enable_dns_support   = var.enable_dns_support
  enable_dns_hostnames = var.enable_dns_hostnames
}
    

🔹 Step 4: Flatten Subnets for for_each

The var.vpc_subnets structure is not suitable for for_each. Use flatten to transform it:


locals {
  vpc_subnets = flatten([
    for env, azs in var.vpc_subnets : [
      for az, subnets in azs : [
        for attr_name, cidr_block in subnets : {
          env         = env
          az          = az
          type        = trimsuffix(attr_name, "_cidr_block")
          cidr_block  = cidr_block
        }
      ]
    ]
  ])
}
    

🔹 Step 5: Use for_each for Subnets


resource "aws_subnet" "all" {
  for_each = {
    for subnet in local.vpc_subnets :
    "${subnet.env}:${subnet.az}:${subnet.type}" => subnet
  }

  vpc_id                  = aws_vpc.vpc[each.value.env].id
  cidr_block              = each.value.cidr_block
  availability_zone       = each.value.az
  map_public_ip_on_launch = var.map_public_ip_on_launch
}
    

Example Resource Instances:

  • aws_subnet.all[“dev:ap-southeast-1a:public”]
  • aws_subnet.all[“dev:ap-southeast-1a:app”]
  • aws_subnet.all[“dev:ap-southeast-1a:db”]
  • aws_subnet.all[“stg:ap-southeast-1b:public”]
  • aws_subnet.all[“stg:ap-southeast-1b:app”]
  • aws_subnet.all[“stg:ap-southeast-1b:db”]

🔹 Step 6: Split Subnets by Type

Group subnets by type:


locals {
  vpc_subnets_by_type = {
    for subnet in local.vpc_subnets :
    subnet.type => subnet...
  }
}
    

🔹 Step 7: Separate Subnet Resources

Instead of a single resource, use three separate subnet resources for each type.


// Public Subnets
resource "aws_subnet" "public" {
  for_each = {
    for subnet in local.vpc_subnets_by_type["public"] :
    "${subnet.env}:${subnet.az}" => subnet
  }

  vpc_id                  = aws_vpc.vpc[each.value.env].id
  cidr_block              = each.value.cidr_block
  availability_zone       = each.value.az
  map_public_ip_on_launch = var.map_public_ip_on_launch
}

// App Subnets
resource "aws_subnet" "app" {
  for_each = {
    for subnet in local.vpc_subnets_by_type["app"] :
    "${subnet.env}:${subnet.az}" => subnet
  }

  vpc_id                  = aws_vpc.vpc[each.value.env].id
  cidr_block              = each.value.cidr_block
  availability_zone       = each.value.az
  map_public_ip_on_launch = var.map_public_ip_on_launch
}

// DB Subnets
resource "aws_subnet" "db" {
  for_each = {
    for subnet in local.vpc_subnets_by_type["db"] :
    "${subnet.env}:${subnet.az}" => subnet
  }

  vpc_id                  = aws_vpc.vpc[each.value.env].id
  cidr_block              = each.value.cidr_block
  availability_zone       = each.value.az
  map_public_ip_on_launch = var.map_public_ip_on_launch
}
    

🔹 Conclusion

By structuring your variables properly and leveraging for_each with flatten, you can efficiently declare all necessary subnets while keeping your Terraform code clean and maintainable. 🚀

Leave a Comment