From b2793efa0f31bb88958f81369974a456ee1b30de Mon Sep 17 00:00:00 2001 From: Andriy Knysh Date: Thu, 9 Nov 2017 09:55:13 -0500 Subject: [PATCH] terraform-aws-multi-az-subnets (#1) * Initial commit * Update `README` * Rename tag to `AZ` * Update `README` * Update `README` * Update `README` * Add `locals` * Add `enabled` flag * Add NAT Gateway per AZ * Update `README` * Add Map of AZ names to NAT Gateway IDs * Update `README` * Update `README` * Use `var.availability_zones` and `var.az_ngw_ids` in private subnets * Update private subnets * terraform fmt * Set `max_subnets` to 6 * Use `var.vpc_id` * Add `var.az_ngw_count` * Add examples * Update `README` --- .gitignore | 8 + .travis.yml | 16 ++ LICENSE | 2 +- Makefile | 6 + README.md | 179 ++++++++++++++++++ examples/only-private-subnets/.gitignore | 2 + examples/only-private-subnets/main.tf | 14 ++ examples/only-private-subnets/outputs.tf | 11 ++ examples/only-private-subnets/variables.tf | 24 +++ examples/only-public-subnets/.gitignore | 2 + examples/only-public-subnets/main.tf | 16 ++ examples/only-public-subnets/outputs.tf | 11 ++ examples/only-public-subnets/variables.tf | 30 +++ .../.gitignore | 2 + .../main.tf | 28 +++ .../outputs.tf | 23 +++ .../variables.tf | 30 +++ .../.gitignore | 2 + .../main.tf | 37 ++++ .../outputs.tf | 23 +++ .../variables.tf | 30 +++ outputs.tf | 11 ++ private.tf | 74 ++++++++ public.tf | 105 ++++++++++ variables.tf | 162 ++++++++++++++++ 25 files changed, 847 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 examples/only-private-subnets/.gitignore create mode 100644 examples/only-private-subnets/main.tf create mode 100644 examples/only-private-subnets/outputs.tf create mode 100644 examples/only-private-subnets/variables.tf create mode 100644 examples/only-public-subnets/.gitignore create mode 100644 examples/only-public-subnets/main.tf create mode 100644 examples/only-public-subnets/outputs.tf create mode 100644 examples/only-public-subnets/variables.tf create mode 100644 examples/public-and-private-subnets-no-nat-gateways/.gitignore create mode 100644 examples/public-and-private-subnets-no-nat-gateways/main.tf create mode 100644 examples/public-and-private-subnets-no-nat-gateways/outputs.tf create mode 100644 examples/public-and-private-subnets-no-nat-gateways/variables.tf create mode 100644 examples/public-and-private-subnets-with-nat-gateways/.gitignore create mode 100644 examples/public-and-private-subnets-with-nat-gateways/main.tf create mode 100644 examples/public-and-private-subnets-with-nat-gateways/outputs.tf create mode 100644 examples/public-and-private-subnets-with-nat-gateways/variables.tf create mode 100644 outputs.tf create mode 100644 private.tf create mode 100644 public.tf create mode 100644 variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0dd221 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Compiled files +*.tfstate +*.tfstate.backup + +# Module directory +.terraform +.idea +*.iml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0bca29a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +addons: + apt: + packages: + - git + - make + - curl + +install: + - make init + +script: + - make terraform:install + - make terraform:get-plugins + - make terraform:get-modules + - make terraform:lint + - make terraform:validate diff --git a/LICENSE b/LICENSE index 8dada3e..6b9d898 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2017 Cloud Posse, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0f7470 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +SHELL := /bin/bash + +-include $(shell curl -sSL -o .build-harness "https://git.io/build-harness"; echo .build-harness) + +lint: + $(SELF) terraform:install terraform:get-modules terraform:get-plugins terraform:lint terraform:validate diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd41eb8 --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +# terraform-aws-multi-az-subnets [![Build Status](https://travis-ci.org/cloudposse/terraform-aws-multi-az-subnets.svg)](https://travis-ci.org/cloudposse/terraform-aws-multi-az-subnets) + +Terraform module for multi-AZ [`subnets`](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html) provisioning. + +The module creates private or public subnets in the provided Availability Zones. + +The public subnets are routed to the Internet Gateway specified by `var.igw_id`. + +`nat_gateway_enabled` flag controls the creation of NAT Gateways in public subnets. + +The private subnets are routed to the NAT Gateways provided in the `var.az_ngw_ids` map. + + +## Usage + +```hcl +module "vpc" { + source = "git::https://github.com/cloudposse/terraform-aws-vpc.git?ref=master" + namespace = "${var.namespace}" + name = "vpc" + stage = "${var.stage}" + cidr_block = "${var.cidr_block}" +} + +locals { + public_cidr_block = "${cidrsubnet(module.vpc.vpc_cidr_block, 1, 0)}" + private_cidr_block = "${cidrsubnet(module.vpc.vpc_cidr_block, 1, 1)}" +} + +module "public_subnets" { + source = "git::https://github.com/cloudposse/terraform-aws-multi-az-subnets.git?ref=master" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${module.vpc.vpc_id}" + cidr_block = "${local.public_cidr_block}" + type = "public" + igw_id = "${module.vpc.igw_id}" + nat_gateway_enabled = "true" +} + +module "private_subnets" { + source = "git::https://github.com/cloudposse/terraform-aws-multi-az-subnets.git?ref=master" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${module.vpc.vpc_id}" + cidr_block = "${local.private_cidr_block}" + type = "private" + + # Map of AZ names to NAT Gateway IDs that was created in "public_subnets" module + az_ngw_ids = "${module.public_subnets.az_ngw_ids}" + + # Need to explicitly provide the count since Terraform currently can't use dynamic count on computed resources from different modules + # https://github.com/hashicorp/terraform/issues/10857 + # https://github.com/hashicorp/terraform/issues/12125 + # https://github.com/hashicorp/terraform/issues/4149 + az_ngw_count = 3 +} +``` + + +# Inputs + +| Name | Default | Description | Required | +|:------------------------------|:---------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:| +| `namespace` | `` | Namespace (_e.g._ `cp` or `cloudposse`) | Yes | +| `stage` | `` | Stage (_e.g._ `prod`, `dev`, `staging`) | Yes | +| `name` | `` | Application or solution name (_e.g._ `myapp`) | Yes | +| `delimiter` | `-` | Delimiter to use between `name`, `namespace`, `stage`, `attributes` | No | +| `attributes` | `[]` | Additional attributes (_e.g._ `policy` or `role`) | No | +| `tags` | `{}` | Additional tags (_e.g._ `map("BusinessUnit","XYZ")` | No | +| `max_subnets` | `16` | Maximum number of subnets that can be created. This variable is used for CIDR blocks calculation. MUST be greater than the length of `availability_zones` list | Yes | +| `availability_zones` | [] | List of Availability Zones (e.g. `["us-east-1a", "us-east-1b", "us-east-1c"]`) | Yes | +| `type` | `private` | Type of subnets to create (`private` or `public`) | Yes | +| `vpc_id` | `` | VPC ID where subnets are created (_e.g._ `vpc-aceb2723`) | Yes | +| `cidr_block` | `` | Base CIDR block which is divided into subnet CIDR blocks (_e.g._ `10.0.0.0/24`) | No | +| `igw_id` | `` | Only for public subnets. Internet Gateway ID which is used as a default route when creating public subnets (_e.g._ `igw-9c26a123`) | Yes | +| `public_network_acl_id` | `` | ID of Network ACL which is added to the public subnets. If empty, a new ACL will be created | No | +| `private_network_acl_id` | `` | ID of Network ACL which is added to the private subnets. If empty, a new ACL will be created | No | +| `public_network_acl_egress` | see [variables.tf](https://github.com/cloudposse/terraform-aws-multi-az-subnets/blob/master/variables.tf) | Egress rules which are added to the new Public Network ACL | No | +| `public_network_acl_ingress` | see [variables.tf](https://github.com/cloudposse/terraform-aws-multi-az-subnets/blob/master/variables.tf) | Ingress rules which are added to the new Public Network ACL | No | +| `private_network_acl_egress` | see [variables.tf](https://github.com/cloudposse/terraform-aws-multi-az-subnets/blob/master/variables.tf) | Egress rules which are added to the new Private Network ACL | No | +| `private_network_acl_ingress` | see [variables.tf](https://github.com/cloudposse/terraform-aws-multi-az-subnets/blob/master/variables.tf) | Ingress rules which are added to the new Private Network ACL | No | +| `enabled` | `true` | Set to `false` to prevent the module from creating any resources | No | +| `nat_gateway_enabled` | `true` | Flag to enable/disable NAT Gateways creation in public subnets | No | +| `az_ngw_ids` | {} | Map of AZ names to NAT Gateway IDs which are used as default routes when creating private subnets. Only for private subnets | No | +| `az_ngw_count` | 0 | Count of items in the `az_ngw_ids` map. Needs to be explicitly provided since Terraform currently can't use dynamic count on computed resources from different modules. https://github.com/hashicorp/terraform/issues/10857 | No | + + +## Outputs + +| Name | Description | +|:--------------------------|:---------------------------------------------------------------| +| az_subnet_ids | Map of AZ names to subnet IDs | +| az_route_table_ids | Map of AZ names to Route Table IDs | +| az_ngw_ids | Map of AZ names to NAT Gateway IDs (only for public subnets) | + + +Given the following configuration + +```hcl +module "vpc" { + source = "git::https://github.com/cloudposse/terraform-aws-vpc.git?ref=master" + namespace = "${var.namespace}" + name = "vpc" + stage = "${var.stage}" + cidr_block = "${var.cidr_block}" +} + +locals { + public_cidr_block = "${cidrsubnet(module.vpc.vpc_cidr_block, 1, 0)}" + private_cidr_block = "${cidrsubnet(module.vpc.vpc_cidr_block, 1, 1)}" +} + +module "public_subnets" { + source = "git::https://github.com/cloudposse/terraform-aws-multi-az-subnets.git?ref=master" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${module.vpc.vpc_id}" + cidr_block = "${local.public_cidr_block}" + type = "public" + igw_id = "${module.vpc.igw_id}" + nat_gateway_enabled = "true" +} + +module "private_subnets" { + source = "git::https://github.com/cloudposse/terraform-aws-multi-az-subnets.git?ref=master" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${module.vpc.vpc_id}" + cidr_block = "${local.private_cidr_block}" + type = "private" + az_ngw_ids = "${module.public_subnets.az_ngw_ids}" + az_ngw_count = 3 +} + +output "private_az_subnet_ids" { + value = "${module.private_subnets.az_subnet_ids}" +} + +output "public_az_subnet_ids" { + value = "${module.public_subnets.az_subnet_ids}" +} +``` + +the output Maps of AZ names to subnet IDs look like these + +```hcl +public_az_subnet_ids = { + us-east-1a = subnet-ea58d78e + us-east-1b = subnet-556ee131 + us-east-1c = subnet-6f54db0b +} +private_az_subnet_ids = { + us-east-1a = subnet-376de253 + us-east-1b = subnet-9e53dcfa + us-east-1c = subnet-a86fe0cc +} +``` + +and the created subnet IDs could be found by the AZ names using `map["key"]` or [`lookup(map, key, [default])`](https://www.terraform.io/docs/configuration/interpolation.html#lookup-map-key-default-), + +for example: + +`public_az_subnet_ids["us-east-1a"]` + +`lookup(private_az_subnet_ids, "us-east-1b")` + + +## License + +Apache 2 License. See [`LICENSE`](LICENSE) for full details. diff --git a/examples/only-private-subnets/.gitignore b/examples/only-private-subnets/.gitignore new file mode 100644 index 0000000..e2bf8bc --- /dev/null +++ b/examples/only-private-subnets/.gitignore @@ -0,0 +1,2 @@ +*.tfstate +*.tfstate.backup diff --git a/examples/only-private-subnets/main.tf b/examples/only-private-subnets/main.tf new file mode 100644 index 0000000..87249a9 --- /dev/null +++ b/examples/only-private-subnets/main.tf @@ -0,0 +1,14 @@ +locals { + private_cidr_block = "${cidrsubnet(var.cidr_block, 1, 0)}" +} + +module "private_subnets" { + source = "../../" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${var.vpc_id}" + cidr_block = "${local.private_cidr_block}" + type = "private" +} diff --git a/examples/only-private-subnets/outputs.tf b/examples/only-private-subnets/outputs.tf new file mode 100644 index 0000000..24ed121 --- /dev/null +++ b/examples/only-private-subnets/outputs.tf @@ -0,0 +1,11 @@ +output "private_az_subnet_ids" { + value = "${module.private_subnets.az_subnet_ids}" +} + +output "private_az_ngw_ids" { + value = "${module.private_subnets.az_ngw_ids}" +} + +output "private_az_route_table_ids" { + value = "${module.private_subnets.az_route_table_ids}" +} diff --git a/examples/only-private-subnets/variables.tf b/examples/only-private-subnets/variables.tf new file mode 100644 index 0000000..37a272c --- /dev/null +++ b/examples/only-private-subnets/variables.tf @@ -0,0 +1,24 @@ +variable "namespace" { + description = "Namespace (e.g. `cp` or `cloudposse`)" + type = "string" +} + +variable "stage" { + description = "Stage (e.g. `prod`, `dev`, `staging`)" + type = "string" +} + +variable "name" { + type = "string" + description = "Application or solution name" +} + +variable "vpc_id" { + type = "string" + description = "VPC ID" +} + +variable "cidr_block" { + type = "string" + description = "Base CIDR block which is divided into subnet CIDR blocks (e.g. `10.0.0.0/16`)" +} diff --git a/examples/only-public-subnets/.gitignore b/examples/only-public-subnets/.gitignore new file mode 100644 index 0000000..e2bf8bc --- /dev/null +++ b/examples/only-public-subnets/.gitignore @@ -0,0 +1,2 @@ +*.tfstate +*.tfstate.backup diff --git a/examples/only-public-subnets/main.tf b/examples/only-public-subnets/main.tf new file mode 100644 index 0000000..aedaf0c --- /dev/null +++ b/examples/only-public-subnets/main.tf @@ -0,0 +1,16 @@ +locals { + public_cidr_block = "${cidrsubnet(var.cidr_block, 1, 0)}" +} + +module "public_subnets" { + source = "../../" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${var.vpc_id}" + cidr_block = "${local.public_cidr_block}" + type = "public" + igw_id = "${var.igw_id}" + nat_gateway_enabled = "false" +} diff --git a/examples/only-public-subnets/outputs.tf b/examples/only-public-subnets/outputs.tf new file mode 100644 index 0000000..5558dc3 --- /dev/null +++ b/examples/only-public-subnets/outputs.tf @@ -0,0 +1,11 @@ +output "public_az_subnet_ids" { + value = "${module.public_subnets.az_subnet_ids}" +} + +output "public_az_ngw_ids" { + value = "${module.public_subnets.az_ngw_ids}" +} + +output "public_az_route_table_ids" { + value = "${module.public_subnets.az_route_table_ids}" +} diff --git a/examples/only-public-subnets/variables.tf b/examples/only-public-subnets/variables.tf new file mode 100644 index 0000000..d0feb59 --- /dev/null +++ b/examples/only-public-subnets/variables.tf @@ -0,0 +1,30 @@ +variable "namespace" { + description = "Namespace (e.g. `cp` or `cloudposse`)" + type = "string" +} + +variable "stage" { + description = "Stage (e.g. `prod`, `dev`, `staging`)" + type = "string" +} + +variable "name" { + type = "string" + description = "Application or solution name" +} + +variable "vpc_id" { + type = "string" + description = "VPC ID" +} + +variable "cidr_block" { + type = "string" + description = "Base CIDR block which is divided into subnet CIDR blocks (e.g. `10.0.0.0/16`)" +} + +variable "igw_id" { + type = "string" + description = "Internet Gateway ID that is used as a default route when creating public subnets (e.g. `igw-9c26a123`)" + default = "" +} diff --git a/examples/public-and-private-subnets-no-nat-gateways/.gitignore b/examples/public-and-private-subnets-no-nat-gateways/.gitignore new file mode 100644 index 0000000..e2bf8bc --- /dev/null +++ b/examples/public-and-private-subnets-no-nat-gateways/.gitignore @@ -0,0 +1,2 @@ +*.tfstate +*.tfstate.backup diff --git a/examples/public-and-private-subnets-no-nat-gateways/main.tf b/examples/public-and-private-subnets-no-nat-gateways/main.tf new file mode 100644 index 0000000..a8e7ade --- /dev/null +++ b/examples/public-and-private-subnets-no-nat-gateways/main.tf @@ -0,0 +1,28 @@ +locals { + public_cidr_block = "${cidrsubnet(var.cidr_block, 1, 0)}" + private_cidr_block = "${cidrsubnet(var.cidr_block, 1, 1)}" +} + +module "public_subnets" { + source = "../../" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${var.vpc_id}" + cidr_block = "${local.public_cidr_block}" + type = "public" + igw_id = "${var.igw_id}" + nat_gateway_enabled = "false" +} + +module "private_subnets" { + source = "../../" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${var.vpc_id}" + cidr_block = "${local.private_cidr_block}" + type = "private" +} diff --git a/examples/public-and-private-subnets-no-nat-gateways/outputs.tf b/examples/public-and-private-subnets-no-nat-gateways/outputs.tf new file mode 100644 index 0000000..6cdde33 --- /dev/null +++ b/examples/public-and-private-subnets-no-nat-gateways/outputs.tf @@ -0,0 +1,23 @@ +output "private_az_subnet_ids" { + value = "${module.private_subnets.az_subnet_ids}" +} + +output "public_az_subnet_ids" { + value = "${module.public_subnets.az_subnet_ids}" +} + +output "private_az_ngw_ids" { + value = "${module.private_subnets.az_ngw_ids}" +} + +output "public_az_ngw_ids" { + value = "${module.public_subnets.az_ngw_ids}" +} + +output "private_az_route_table_ids" { + value = "${module.private_subnets.az_route_table_ids}" +} + +output "public_az_route_table_ids" { + value = "${module.public_subnets.az_route_table_ids}" +} diff --git a/examples/public-and-private-subnets-no-nat-gateways/variables.tf b/examples/public-and-private-subnets-no-nat-gateways/variables.tf new file mode 100644 index 0000000..d0feb59 --- /dev/null +++ b/examples/public-and-private-subnets-no-nat-gateways/variables.tf @@ -0,0 +1,30 @@ +variable "namespace" { + description = "Namespace (e.g. `cp` or `cloudposse`)" + type = "string" +} + +variable "stage" { + description = "Stage (e.g. `prod`, `dev`, `staging`)" + type = "string" +} + +variable "name" { + type = "string" + description = "Application or solution name" +} + +variable "vpc_id" { + type = "string" + description = "VPC ID" +} + +variable "cidr_block" { + type = "string" + description = "Base CIDR block which is divided into subnet CIDR blocks (e.g. `10.0.0.0/16`)" +} + +variable "igw_id" { + type = "string" + description = "Internet Gateway ID that is used as a default route when creating public subnets (e.g. `igw-9c26a123`)" + default = "" +} diff --git a/examples/public-and-private-subnets-with-nat-gateways/.gitignore b/examples/public-and-private-subnets-with-nat-gateways/.gitignore new file mode 100644 index 0000000..e2bf8bc --- /dev/null +++ b/examples/public-and-private-subnets-with-nat-gateways/.gitignore @@ -0,0 +1,2 @@ +*.tfstate +*.tfstate.backup diff --git a/examples/public-and-private-subnets-with-nat-gateways/main.tf b/examples/public-and-private-subnets-with-nat-gateways/main.tf new file mode 100644 index 0000000..c323f55 --- /dev/null +++ b/examples/public-and-private-subnets-with-nat-gateways/main.tf @@ -0,0 +1,37 @@ +locals { + public_cidr_block = "${cidrsubnet(var.cidr_block, 1, 0)}" + private_cidr_block = "${cidrsubnet(var.cidr_block, 1, 1)}" +} + +module "public_subnets" { + source = "../../" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${var.vpc_id}" + cidr_block = "${local.public_cidr_block}" + type = "public" + igw_id = "${var.igw_id}" + nat_gateway_enabled = "true" +} + +module "private_subnets" { + source = "../../" + namespace = "${var.namespace}" + stage = "${var.stage}" + name = "${var.name}" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + vpc_id = "${var.vpc_id}" + cidr_block = "${local.private_cidr_block}" + type = "private" + + # Map of AZ names to NAT Gateway IDs that was created in "public_subnets" module + az_ngw_ids = "${module.public_subnets.az_ngw_ids}" + + # Need to explicitly provide the count since Terraform currently can't use dynamic count on computed resources from different modules + # https://github.com/hashicorp/terraform/issues/10857 + # https://github.com/hashicorp/terraform/issues/12125 + # https://github.com/hashicorp/terraform/issues/4149 + az_ngw_count = 3 +} diff --git a/examples/public-and-private-subnets-with-nat-gateways/outputs.tf b/examples/public-and-private-subnets-with-nat-gateways/outputs.tf new file mode 100644 index 0000000..6cdde33 --- /dev/null +++ b/examples/public-and-private-subnets-with-nat-gateways/outputs.tf @@ -0,0 +1,23 @@ +output "private_az_subnet_ids" { + value = "${module.private_subnets.az_subnet_ids}" +} + +output "public_az_subnet_ids" { + value = "${module.public_subnets.az_subnet_ids}" +} + +output "private_az_ngw_ids" { + value = "${module.private_subnets.az_ngw_ids}" +} + +output "public_az_ngw_ids" { + value = "${module.public_subnets.az_ngw_ids}" +} + +output "private_az_route_table_ids" { + value = "${module.private_subnets.az_route_table_ids}" +} + +output "public_az_route_table_ids" { + value = "${module.public_subnets.az_route_table_ids}" +} diff --git a/examples/public-and-private-subnets-with-nat-gateways/variables.tf b/examples/public-and-private-subnets-with-nat-gateways/variables.tf new file mode 100644 index 0000000..d0feb59 --- /dev/null +++ b/examples/public-and-private-subnets-with-nat-gateways/variables.tf @@ -0,0 +1,30 @@ +variable "namespace" { + description = "Namespace (e.g. `cp` or `cloudposse`)" + type = "string" +} + +variable "stage" { + description = "Stage (e.g. `prod`, `dev`, `staging`)" + type = "string" +} + +variable "name" { + type = "string" + description = "Application or solution name" +} + +variable "vpc_id" { + type = "string" + description = "VPC ID" +} + +variable "cidr_block" { + type = "string" + description = "Base CIDR block which is divided into subnet CIDR blocks (e.g. `10.0.0.0/16`)" +} + +variable "igw_id" { + type = "string" + description = "Internet Gateway ID that is used as a default route when creating public subnets (e.g. `igw-9c26a123`)" + default = "" +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..fdd34c8 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,11 @@ +output "az_subnet_ids" { + value = "${zipmap(var.availability_zones, matchkeys(coalescelist(aws_subnet.private.*.id, aws_subnet.public.*.id), coalescelist(aws_subnet.private.*.tags.AZ, aws_subnet.public.*.tags.AZ), var.availability_zones))}" +} + +output "az_route_table_ids" { + value = "${zipmap(var.availability_zones, matchkeys(coalescelist(aws_route_table.private.*.id, aws_route_table.public.*.id), coalescelist(aws_route_table.private.*.tags.AZ, aws_route_table.public.*.tags.AZ), var.availability_zones))}" +} + +output "az_ngw_ids" { + value = "${zipmap(var.availability_zones, matchkeys(aws_nat_gateway.public.*.id, aws_nat_gateway.public.*.tags.AZ, var.availability_zones))}" +} diff --git a/private.tf b/private.tf new file mode 100644 index 0000000..687e39f --- /dev/null +++ b/private.tf @@ -0,0 +1,74 @@ +locals { + private_count = "${var.enabled == "true" && var.type == "private" ? length(var.availability_zones) : 0}" + private_route_count = "${var.enabled == "true" && var.type == "private" ? length(var.az_ngw_count) : 0}" +} + +module "private_label" { + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.0" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + delimiter = "${var.delimiter}" + tags = "${var.tags}" + attributes = ["${compact(concat(var.attributes, list("private")))}"] + enabled = "${var.enabled}" +} + +resource "aws_subnet" "private" { + count = "${local.private_count}" + vpc_id = "${var.vpc_id}" + availability_zone = "${element(var.availability_zones, count.index)}" + cidr_block = "${cidrsubnet(var.cidr_block, ceil(log(var.max_subnets, 2)), count.index)}" + + tags = "${ + merge( + module.private_label.tags, + map( + "Name", "${module.private_label.id}${var.delimiter}${element(var.availability_zones, count.index)}", + "AZ", "${element(var.availability_zones, count.index)}", + "Type", "${var.type}" + ) + ) + }" +} + +resource "aws_network_acl" "private" { + count = "${var.enabled == "true" && var.type == "private" && signum(length(var.private_network_acl_id)) == 0 ? 1 : 0}" + vpc_id = "${var.vpc_id}" + subnet_ids = ["${aws_subnet.private.*.id}"] + egress = "${var.private_network_acl_egress}" + ingress = "${var.private_network_acl_ingress}" + tags = "${module.private_label.tags}" + depends_on = ["aws_subnet.private"] +} + +resource "aws_route_table" "private" { + count = "${local.private_count}" + vpc_id = "${var.vpc_id}" + + tags = "${ + merge( + module.private_label.tags, + map( + "Name", "${module.private_label.id}${var.delimiter}${element(var.availability_zones, count.index)}", + "AZ", "${element(var.availability_zones, count.index)}", + "Type", "${var.type}" + ) + ) + }" +} + +resource "aws_route_table_association" "private" { + count = "${local.private_count}" + subnet_id = "${element(aws_subnet.private.*.id, count.index)}" + route_table_id = "${element(aws_route_table.private.*.id, count.index)}" + depends_on = ["aws_subnet.private", "aws_route_table.private"] +} + +resource "aws_route" "default" { + count = "${local.private_route_count}" + route_table_id = "${lookup(zipmap(var.availability_zones, matchkeys(aws_route_table.private.*.id, aws_route_table.private.*.tags.AZ, var.availability_zones)), element(keys(var.az_ngw_ids), count.index))}" + nat_gateway_id = "${lookup(var.az_ngw_ids, element(keys(var.az_ngw_ids), count.index))}" + destination_cidr_block = "0.0.0.0/0" + depends_on = ["aws_route_table.private"] +} diff --git a/public.tf b/public.tf new file mode 100644 index 0000000..e6ed4d6 --- /dev/null +++ b/public.tf @@ -0,0 +1,105 @@ +locals { + public_count = "${var.enabled == "true" && var.type == "public" ? length(var.availability_zones) : 0}" + public_nat_gateways_count = "${var.enabled == "true" && var.type == "public" && var.nat_gateway_enabled == "true" ? length(var.availability_zones) : 0}" +} + +module "public_label" { + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.0" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + delimiter = "${var.delimiter}" + tags = "${var.tags}" + attributes = ["${compact(concat(var.attributes, list("public")))}"] + enabled = "${var.enabled}" +} + +resource "aws_subnet" "public" { + count = "${local.public_count}" + vpc_id = "${var.vpc_id}" + availability_zone = "${element(var.availability_zones, count.index)}" + cidr_block = "${cidrsubnet(var.cidr_block, ceil(log(var.max_subnets, 2)), count.index)}" + + tags = "${ + merge( + module.public_label.tags, + map( + "Name", "${module.public_label.id}${var.delimiter}${element(var.availability_zones, count.index)}", + "AZ", "${element(var.availability_zones, count.index)}", + "Type", "${var.type}" + ) + ) + }" +} + +resource "aws_network_acl" "public" { + count = "${var.enabled == "true" && var.type == "public" && signum(length(var.public_network_acl_id)) == 0 ? 1 : 0}" + vpc_id = "${var.vpc_id}" + subnet_ids = ["${aws_subnet.public.*.id}"] + egress = "${var.public_network_acl_egress}" + ingress = "${var.public_network_acl_ingress}" + tags = "${module.public_label.tags}" + depends_on = ["aws_subnet.public"] +} + +resource "aws_route_table" "public" { + count = "${local.public_count}" + vpc_id = "${var.vpc_id}" + + tags = "${ + merge( + module.public_label.tags, + map( + "Name", "${module.public_label.id}${var.delimiter}${element(var.availability_zones, count.index)}", + "AZ", "${element(var.availability_zones, count.index)}", + "Type", "${var.type}" + ) + ) + }" +} + +resource "aws_route" "public" { + count = "${local.public_count}" + route_table_id = "${element(aws_route_table.public.*.id, count.index)}" + gateway_id = "${var.igw_id}" + destination_cidr_block = "0.0.0.0/0" + depends_on = ["aws_route_table.public"] +} + +resource "aws_route_table_association" "public" { + count = "${local.public_count}" + subnet_id = "${element(aws_subnet.public.*.id, count.index)}" + route_table_id = "${element(aws_route_table.public.*.id, count.index)}" + depends_on = ["aws_subnet.public", "aws_route_table.public"] +} + +resource "aws_eip" "public" { + count = "${local.public_nat_gateways_count}" + vpc = true + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_nat_gateway" "public" { + count = "${local.public_nat_gateways_count}" + allocation_id = "${element(aws_eip.public.*.id, count.index)}" + subnet_id = "${element(aws_subnet.public.*.id, count.index)}" + depends_on = ["aws_subnet.public"] + + lifecycle { + create_before_destroy = true + } + + tags = "${ + merge( + module.public_label.tags, + map( + "Name", "${module.public_label.id}${var.delimiter}${element(var.availability_zones, count.index)}", + "AZ", "${element(var.availability_zones, count.index)}", + "Type", "${var.type}" + ) + ) + }" +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..dfaffbb --- /dev/null +++ b/variables.tf @@ -0,0 +1,162 @@ +variable "namespace" { + description = "Namespace (e.g. `cp` or `cloudposse`)" + type = "string" +} + +variable "stage" { + description = "Stage (e.g. `prod`, `dev`, `staging`)" + type = "string" +} + +variable "name" { + type = "string" + description = "Application or solution name" +} + +variable "delimiter" { + type = "string" + default = "-" + description = "Delimiter to be used between `name`, `namespace`, `stage`, `attributes`" +} + +variable "attributes" { + type = "list" + default = [] + description = "Additional attributes (e.g. `policy` or `role`)" +} + +variable "tags" { + type = "map" + default = {} + description = "Additional tags (e.g. map(`BusinessUnit`,`XYZ`)" +} + +variable "availability_zones" { + type = "list" + default = [] + description = "List of Availability Zones (e.g. `['us-east-1a', 'us-east-1b', 'us-east-1c']`)" +} + +variable "max_subnets" { + default = "6" + description = "Maximum number of subnets that can be created. The variable is used for CIDR blocks calculation" +} + +variable "type" { + type = "string" + default = "private" + description = "Type of subnets to create (`private` or `public`)" +} + +variable "vpc_id" { + type = "string" + description = "VPC ID" +} + +variable "cidr_block" { + type = "string" + description = "Base CIDR block which is divided into subnet CIDR blocks (e.g. `10.0.0.0/16`)" +} + +variable "igw_id" { + type = "string" + description = "Internet Gateway ID that is used as a default route when creating public subnets (e.g. `igw-9c26a123`)" + default = "" +} + +variable "az_ngw_ids" { + type = "map" + description = "Only for private subnets. Map of AZ names to NAT Gateway IDs that are used as default routes when creating private subnets" + default = {} +} + +variable "public_network_acl_id" { + type = "string" + description = "Network ACL ID that is added to the public subnets. If empty, a new ACL will be created" + default = "" +} + +variable "private_network_acl_id" { + type = "string" + description = "Network ACL ID that is added to the private subnets. If empty, a new ACL will be created" + default = "" +} + +variable "public_network_acl_egress" { + description = "Egress network ACL rules" + type = "list" + + default = [ + { + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + protocol = "-1" + }, + ] +} + +variable "public_network_acl_ingress" { + description = "Egress network ACL rules" + type = "list" + + default = [ + { + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + protocol = "-1" + }, + ] +} + +variable "private_network_acl_egress" { + description = "Egress network ACL rules" + type = "list" + + default = [ + { + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + protocol = "-1" + }, + ] +} + +variable "private_network_acl_ingress" { + description = "Egress network ACL rules" + type = "list" + + default = [ + { + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + protocol = "-1" + }, + ] +} + +variable "enabled" { + description = "Set to false to prevent the module from creating any resources" + default = "true" +} + +variable "nat_gateway_enabled" { + description = "Flag to enable/disable NAT Gateways creation in public subnets" + default = "true" +} + +variable "az_ngw_count" { + description = "Count of items in the `az_ngw_ids` map. Needs to be explicitly provided since Terraform currently can't use dynamic count on computed resources from different modules. https://github.com/hashicorp/terraform/issues/10857" + default = 0 +}