Last active
March 6, 2026 02:07
-
-
Save sqlxpert/2b1dc6724f081bdccef268cbae6283cb to your computer and use it in GitHub Desktop.
Allocate VPC subnet private IP address ranges from an IPAM resource planning pool (new in Terraform AWS Provider v6.29)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # AWS IPAM resource planning pool for VPC subnets, in Terraform | |
| # Copyright Paul Marcelin. Licensed under GPLv3. | |
| # https://gist.github.com/sqlxpert/2b1dc6724f081bdccef268cbae6283cb | |
| # | |
| # Related: https://docs.aws.amazon.com/vpc/latest/ipam/tutorials-subnet-planning.html | |
| # | |
| # Try it in CloudShell! | |
| # https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html | |
| # | |
| # | |
| # sudo dnf install --assumeyes 'dnf-command(config-manager)' | |
| # sudo dnf config-manager --add-repo 'https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo' | |
| # sudo dnf install --assumeyes terraform | |
| # | |
| # terraform init | |
| # terraform apply -target='aws_vpc_ipam_pool_cidr_allocation.sample_vpc_subnets' | |
| # terraform apply | |
| # | |
| # # terraform apply -destroy # Takes loooong! | |
| # | |
| # | |
| # Pre-allocate to work around an error in cloudposse/dynamic-subnets/aws | |
| # count = local.need_vpc_data ? 1 : 0 | |
| # The "count" value depends on resource attributes that cannot be determined | |
| terraform { | |
| backend "local" {} | |
| required_version = ">= 1.10.0" | |
| required_providers { | |
| aws = { | |
| source = "hashicorp/aws" | |
| version = ">= 6.29.0" | |
| # https://github.com/hashicorp/terraform-provider-aws/issues/34615 | |
| # https://github.com/hashicorp/terraform-provider-aws/pull/44705 | |
| } | |
| } | |
| } | |
| data "aws_caller_identity" "current" {} | |
| data "aws_region" "current" {} | |
| resource "aws_vpc_ipam" "sample_vpc" { | |
| description = "Sample VPC" | |
| operating_regions { | |
| region_name = data.aws_region.current.region | |
| } | |
| } | |
| resource "aws_vpc_ipam_pool" "sample_vpc" { | |
| description = "Sample VPC private addresses" | |
| locale = data.aws_region.current.region | |
| ipam_scope_id = aws_vpc_ipam.sample_vpc.private_default_scope_id | |
| address_family = "ipv4" | |
| allocation_default_netmask_length = 21 | |
| auto_import = false | |
| } | |
| resource "aws_vpc_ipam_pool_cidr" "sample_vpc" { | |
| ipam_pool_id = aws_vpc_ipam_pool.sample_vpc.id | |
| lifecycle { | |
| ignore_changes = [ | |
| cidr, | |
| ] | |
| } | |
| cidr = "10.11.0.0/21" | |
| } | |
| module "sample_vpc" { | |
| source = "cloudposse/vpc/aws" | |
| version = "3.0.0" | |
| name = "Sample" | |
| depends_on = [ | |
| aws_vpc_ipam_pool_cidr.sample_vpc, | |
| ] | |
| ipv4_primary_cidr_block_association = { | |
| ipv4_ipam_pool_id = aws_vpc_ipam_pool.sample_vpc.id | |
| ipv4_netmask_length = aws_vpc_ipam_pool.sample_vpc.allocation_default_netmask_length | |
| } | |
| assign_generated_ipv6_cidr_block = false | |
| default_security_group_deny_all = true | |
| } | |
| resource "aws_vpc_ipam_pool" "sample_vpc_subnets" { | |
| description = "Sample VPC subnet private addresses" | |
| locale = data.aws_region.current.region | |
| source_ipam_pool_id = aws_vpc_ipam_pool.sample_vpc.id | |
| ipam_scope_id = aws_vpc_ipam.sample_vpc.private_default_scope_id | |
| address_family = "ipv4" | |
| allocation_default_netmask_length = 24 | |
| auto_import = false | |
| source_resource { | |
| resource_type = "vpc" | |
| resource_region = data.aws_region.current.region | |
| resource_owner = provider::aws::arn_parse(data.aws_caller_identity.current.arn)["account_id"] | |
| resource_id = module.sample_vpc.vpc_id | |
| } | |
| } | |
| resource "aws_vpc_ipam_pool_cidr" "sample_vpc_subnets" { | |
| ipam_pool_id = aws_vpc_ipam_pool.sample_vpc_subnets.id | |
| lifecycle { | |
| ignore_changes = [ | |
| cidr, | |
| ] | |
| } | |
| cidr = module.sample_vpc.vpc_cidr_block | |
| } | |
| locals { | |
| subnet_scope_to_keys = { | |
| for subnet_scope in ["private", "public"] : | |
| subnet_scope => [ | |
| for subnet_index in range(3) : | |
| join(":", [subnet_scope, subnet_index]) | |
| ] | |
| } | |
| subnet_keys_set = toset(flatten(values(local.subnet_scope_to_keys))) | |
| } | |
| resource "aws_vpc_ipam_pool_cidr_allocation" "sample_vpc_subnets" { | |
| for_each = local.subnet_keys_set | |
| description = "Sample VPC ${each.key} subnet private addresses" | |
| depends_on = [ | |
| aws_vpc_ipam_pool_cidr.sample_vpc_subnets, | |
| ] | |
| lifecycle { | |
| ignore_changes = [ | |
| description, | |
| cidr, | |
| ] | |
| } | |
| ipam_pool_id = aws_vpc_ipam_pool.sample_vpc_subnets.id | |
| } | |
| module "sample_vpc_subnets" { | |
| source = "cloudposse/dynamic-subnets/aws" | |
| version = "3.1.1" | |
| name = "Sample" | |
| vpc_id = module.sample_vpc.vpc_id | |
| igw_id = [module.sample_vpc.igw_id] | |
| ipv4_cidrs = [{ | |
| for subnet_scope, subnet_keys in local.subnet_scope_to_keys : | |
| subnet_scope => sort([ | |
| for subnet_key in subnet_keys : | |
| aws_vpc_ipam_pool_cidr_allocation.sample_vpc_subnets[subnet_key].cidr | |
| ]) | |
| }] | |
| # Maximum subnets per type (public or private), not overall! | |
| max_subnet_count = 3 | |
| private_subnets_per_az_count = 1 | |
| public_subnets_per_az_count = 1 | |
| public_route_table_enabled = true | |
| public_route_table_per_subnet_enabled = false | |
| private_route_table_enabled = false | |
| nat_gateway_enabled = false | |
| max_nats = 0 | |
| private_open_network_acl_enabled = false | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For a LinkedIn comment encouraging early use of AWS idioms