diff --git a/examples/optimized_ipv4_subnets/.header.md b/examples/optimized_ipv4_subnets/.header.md new file mode 100644 index 0000000..6d38660 --- /dev/null +++ b/examples/optimized_ipv4_subnets/.header.md @@ -0,0 +1,6 @@ +# Creating subnets with sorted CIDR ranges + +This example shows how you can use this module to efficiently use the VPC CIDR range. Before calculating CIDR ranges, subnets are sorted from largest to smallest. + +* The VPC module creates the following: + * Four sets of subnets (*public*, *private*, *database*, and *infrastructure*) diff --git a/examples/optimized_ipv4_subnets/.terraform-docs.yaml b/examples/optimized_ipv4_subnets/.terraform-docs.yaml new file mode 100644 index 0000000..6dc99de --- /dev/null +++ b/examples/optimized_ipv4_subnets/.terraform-docs.yaml @@ -0,0 +1,21 @@ +formatter: markdown +header-from: .header.md +settings: + anchor: true + color: true + default: true + escape: true + html: true + indent: 2 + required: true + sensitive: true + type: true + lockfile: false + +sort: + enabled: true + by: required + +output: + file: README.md + mode: replace diff --git a/examples/optimized_ipv4_subnets/README.md b/examples/optimized_ipv4_subnets/README.md new file mode 100644 index 0000000..13c5458 --- /dev/null +++ b/examples/optimized_ipv4_subnets/README.md @@ -0,0 +1,46 @@ + +# Creating subnets with sorted CIDR ranges + +This example shows how you can use this module to efficiently use the VPC CIDR range. Before calculating CIDR ranges, subnets are sorted from largest to smallest. + +* The VPC module creates the following: + * Four sets of subnets (*public*, *private*, *database*, and *infrastructure*) + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | ~> 1.9 | +| [aws](#requirement\_aws) | ~> 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 5.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [vpc](#module\_vpc) | aws-ia/vpc/aws | ~> 4.4 | + +## Resources + +| Name | Type | +|------|------| +| [aws_availability_zones.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_region](#input\_aws\_region) | AWS Region to create resources in. | `string` | `"eu-west-2"` | no | +| [vpc\_cidr](#input\_vpc\_cidr) | VPC cidr range | `string` | `"10.0.0.0/22"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [private\_subnets\_tags\_length](#output\_private\_subnets\_tags\_length) | Count of private subnet tags for a single az. | + \ No newline at end of file diff --git a/examples/optimized_ipv4_subnets/main.tf b/examples/optimized_ipv4_subnets/main.tf new file mode 100644 index 0000000..c68c3f4 --- /dev/null +++ b/examples/optimized_ipv4_subnets/main.tf @@ -0,0 +1,32 @@ +data "aws_availability_zones" "current" { + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +module "vpc" { + source = "aws-ia/vpc/aws" + version = "~> 4.4" + + name = "sorted-subnets" + cidr_block = var.vpc_cidr + az_count = 3 + optimize_subnet_cidr_ranges = true + + subnets = { + private = { + netmask = 24 + } + database = { + netmask = 27 + } + public = { + netmask = 28 + nat_gateway_configuration = "single_az" + } + infrastructure = { + netmask = 28 + } + } +} diff --git a/examples/optimized_ipv4_subnets/outputs.tf b/examples/optimized_ipv4_subnets/outputs.tf new file mode 100644 index 0000000..6678e99 --- /dev/null +++ b/examples/optimized_ipv4_subnets/outputs.tf @@ -0,0 +1,6 @@ +## Used for Testing, do not delete + +output "private_subnets_tags_length" { + description = "Count of private subnet tags for a single az." + value = try(length(module.vpc.private_subnet_attributes_by_az["private/${data.aws_availability_zones.current.names[0]}"].tags), null) +} diff --git a/examples/optimized_ipv4_subnets/providers.tf b/examples/optimized_ipv4_subnets/providers.tf new file mode 100644 index 0000000..9bca1d9 --- /dev/null +++ b/examples/optimized_ipv4_subnets/providers.tf @@ -0,0 +1,15 @@ +terraform { + required_version = "~> 1.9" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# Provider definition +provider "aws" { + region = var.aws_region +} + diff --git a/examples/optimized_ipv4_subnets/variables.tf b/examples/optimized_ipv4_subnets/variables.tf new file mode 100644 index 0000000..4f792b6 --- /dev/null +++ b/examples/optimized_ipv4_subnets/variables.tf @@ -0,0 +1,11 @@ +variable "aws_region" { + description = "AWS Region to create resources in." + type = string + default = "eu-west-2" +} + +variable "vpc_cidr" { + type = string + description = "VPC cidr range" + default = "10.0.0.0/22" +} diff --git a/main.tf b/main.tf index 5bf91a7..528633a 100644 --- a/main.tf +++ b/main.tf @@ -7,7 +7,8 @@ module "calculate_subnets" { cidr = local.cidr_block azs = local.azs - subnets = var.subnets + subnets = var.subnets + optimize_subnet_cidr_ranges = var.optimize_subnet_cidr_ranges } module "calculate_subnets_ipv6" { @@ -521,4 +522,4 @@ resource "aws_vpclattice_service_network_vpc_association" "vpc_lattice_service_n module.tags.tags_aws, module.vpc_lattice_tags.tags_aws ) -} \ No newline at end of file +} diff --git a/modules/calculate_subnets/main.tf b/modules/calculate_subnets/main.tf index 14d4604..436173b 100644 --- a/modules/calculate_subnets/main.tf +++ b/modules/calculate_subnets/main.tf @@ -16,6 +16,30 @@ locals { } ]]) + # map of subnet names to netmask values for looking up netmask by name + netmasks_for_subnets = { for subnet in local.calculated_subnet_objects : subnet.name => subnet.netmask } + + # sorted list of netmasks from largest to smallest so we can efficiently use the ip address space + sorted_subnet_netmasks = reverse(distinct(sort([ + for subnet in local.calculated_subnet_objects : format("%05d", subnet.netmask) + ]))) + + # list of subnet names sorted based on their netmask value (large to small) + sorted_subnet_names = compact(flatten([ + for netmask in local.sorted_subnet_netmasks : [ + for subnet in local.calculated_subnet_objects : + subnet.name if subnet.netmask == tonumber(netmask) + ] + ])) + + # list of subnet the original calculated subnet objects, but sorted based on their netmask value (large to small) + sorted_subnet_objects = [ + for name in local.sorted_subnet_names : { + name = name + netmask = local.netmasks_for_subnets[name] + } + ] + # map of explicit cidrs to az explicit_cidrs_grouped = { for _, type in local.types_with_explicit : type => zipmap(var.azs, var.subnets[type].cidrs[*]) } } @@ -27,6 +51,5 @@ module "subnet_calculator" { version = "1.0.2" base_cidr_block = var.cidr - networks = local.calculated_subnet_objects + networks = var.optimize_subnet_cidr_ranges ? local.sorted_subnet_objects : local.calculated_subnet_objects } - diff --git a/modules/calculate_subnets/variables.tf b/modules/calculate_subnets/variables.tf index 9bba577..ddceae8 100644 --- a/modules/calculate_subnets/variables.tf +++ b/modules/calculate_subnets/variables.tf @@ -12,3 +12,9 @@ variable "cidr" { description = "CIDR value to use as base for calculating IP address prefixes." type = string } + +variable "optimize_subnet_cidr_ranges" { + description = "Sort subnets to calculate by their netmask to efficiently use IP space." + type = bool + default = false +} diff --git a/test/examples_optimized_ipv4_subnets_test.go b/test/examples_optimized_ipv4_subnets_test.go new file mode 100644 index 0000000..b39e368 --- /dev/null +++ b/test/examples_optimized_ipv4_subnets_test.go @@ -0,0 +1,21 @@ +package test + +import ( + "testing" + + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/likexian/gokit/assert" +) + +func TestExamplesOptimizedSubnets(t *testing.T) { + terraformOptions := &terraform.Options{ + TerraformDir: "../examples/optimized_ipv4_subnets", + } + + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) + terraform.ApplyAndIdempotent(t, terraformOptions) + + privateTagsLength := terraform.Output(t, terraformOptions, "private_subnets_tags_length") + assert.Equal(t, "1", privateTagsLength) +} diff --git a/variables.tf b/variables.tf index 95c9936..ebe617a 100644 --- a/variables.tf +++ b/variables.tf @@ -250,6 +250,12 @@ EOF } } +variable "optimize_subnet_cidr_ranges" { + description = "Sort subnets to calculate by their netmask to efficiently use IP space." + type = bool + default = false +} + variable "tags" { description = "Tags to apply to all resources." type = map(string)