diff --git a/LICENSE b/LICENSE index 4b3ce5d..2527dee 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 SIL International +Copyright (c) 2017 SIL International Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 03399ba..e8fa4a1 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,19 @@ -# Terraform module for ... +# Terraform module for AWS VPC -TODO: First https://developer.hashicorp.com/terraform/registry/modules/publish if you have never developed a publishable Terraform module. Then follow those requirements when creating your new module. +This module is used to create a VPC along with the necessary configuration to be useful. It was +previously published at +https://github.com/silinternational/terraform-modules/aws/vpc. -This module ... +## What this does -TODO: Change the path in this link: +- Create VPC named after `app_name` and `app_env` +- Create public and private subnets for each `aws_zones` specified +- Provision a Internet Gateway and configure public subnets to route through it +- Provision a NAT Gateway (or use an existing Transit Gateway) and configure private subnets to route through it +- Create a DB subnet group including all private subnets +- Optionally allocate IPv6 CIDR blocks, egress-only internet gateway, and default IPv6 routes -This module is published in [Terraform Registry](https://registry.terraform.io/modules/silinternational/module-name/provider-name/latest). +This module is published in [Terraform Registry](https://registry.terraform.io/modules/silinternational/vpc/aws/latest). ## Usage Example @@ -14,20 +21,12 @@ TODO: Update the following as a simple, brief representative sample of the modul ```hcl module "this" { - source = "silinternational/module-name/aws" - version = "0.1.0" - - variable_name = "my variable value" + source = "github.com/silinternational/terraform-modules//aws/vpc" + app_name = var.app_name + aws_zones = var.aws_zones } provider "aws" { region = "us-east-1" } ``` - -## Working Example - -TODO: Update or remove this section: - -A working [example](https://github.com/silinternational/terraform-module-name/tree/main/example) usage of this module is included in the source repository. - diff --git a/example/main.tf b/example/main.tf deleted file mode 100644 index 5697465..0000000 --- a/example/main.tf +++ /dev/null @@ -1,12 +0,0 @@ -/* - * TODO: This example folder is a place to provide a fully-functional example, proving any other required - * resources and typical root module outputs with appropriate `sensitive` flags. It can be removed - * entirely if time does not allow for making it complete. - */ - -module "this" { - source = "silinternational/module_name/aws" - version = ">= 0.1.0" - - variable_name = "a variable value" -} diff --git a/example/providers.tf b/example/providers.tf deleted file mode 100644 index 324dc92..0000000 --- a/example/providers.tf +++ /dev/null @@ -1,10 +0,0 @@ -provider "aws" { - region = var.aws_region - - default_tags { - tags = { - managed_by = "terraform" - workspace = terraform.workspace - } - } -} diff --git a/example/variables.tf b/example/variables.tf deleted file mode 100644 index 2cdb1b7..0000000 --- a/example/variables.tf +++ /dev/null @@ -1,3 +0,0 @@ -variable "aws_region" { - default = "us-east-1" -} diff --git a/main.tf b/main.tf index 4236f1d..d02e600 100644 --- a/main.tf +++ b/main.tf @@ -1,7 +1,235 @@ +/* + * Create VPC using app name and env to name it + */ +resource "aws_vpc" "vpc" { + cidr_block = var.vpc_cidr_block + enable_dns_hostnames = var.enable_dns_hostnames + assign_generated_ipv6_cidr_block = var.enable_ipv6 -// TODO: define locals at the top of main.tf + tags = { + Name = "vpc-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } +} + +/* + * Get default security group for reference later + */ +data "aws_security_group" "vpc_default_sg" { + name = "default" + vpc_id = aws_vpc.vpc.id +} + +/* + * Create public and private subnets for each availability zone + */ locals { - a = "a" + ipv6_cidr_block = aws_vpc.vpc.ipv6_cidr_block + public_subnets = var.enable_ipv6 ? cidrsubnets(cidrsubnet(local.ipv6_cidr_block, 4, 0), 4, 4, 4, 4, 4, 4, 4, 4) : [] + private_subnets = var.enable_ipv6 ? cidrsubnets(cidrsubnet(local.ipv6_cidr_block, 4, 1), 4, 4, 4, 4, 4, 4, 4, 4) : [] +} + +resource "aws_subnet" "public_subnet" { + count = length(var.aws_zones) + vpc_id = aws_vpc.vpc.id + availability_zone = element(var.aws_zones, count.index) + cidr_block = element(var.public_subnet_cidr_blocks, count.index) + ipv6_cidr_block = var.enable_ipv6 ? element(local.public_subnets, count.index) : null + + tags = { + Name = "public-${element(var.aws_zones, count.index)}" + app_name = var.app_name + app_env = var.app_env + } +} + +resource "aws_subnet" "private_subnet" { + count = length(var.aws_zones) + vpc_id = aws_vpc.vpc.id + availability_zone = element(var.aws_zones, count.index) + cidr_block = element(var.private_subnet_cidr_blocks, count.index) + ipv6_cidr_block = var.enable_ipv6 ? element(local.private_subnets, count.index) : null + + tags = { + Name = "private-${element(var.aws_zones, count.index)}" + app_name = var.app_name + app_env = var.app_env + } +} + + +/* + * Create internet gateway(s) for VPC + */ +resource "aws_internet_gateway" "internet_gateway" { + vpc_id = aws_vpc.vpc.id + + tags = { + Name = "IGW-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } +} + +resource "aws_egress_only_internet_gateway" "ipv6" { + count = var.enable_ipv6 ? 1 : 0 + + vpc_id = aws_vpc.vpc.id + + tags = { + Name = "egress-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } +} + +resource "aws_route" "private_ipv6" { + count = var.enable_ipv6 ? 1 : 0 + + route_table_id = aws_route_table.private_route_table.id + destination_ipv6_cidr_block = "::/0" + egress_only_gateway_id = one(aws_egress_only_internet_gateway.ipv6[*].id) +} + + +/* + * Create NAT gateway and allocate Elastic IP for it + */ +resource "aws_eip" "gateway_eip" { + tags = { + Name = "EIP-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } + vpc = true +} + +resource "aws_nat_gateway" "nat_gateway" { + count = var.create_nat_gateway ? 1 : 0 + + allocation_id = aws_eip.gateway_eip.id + subnet_id = aws_subnet.public_subnet[0].id + depends_on = [aws_internet_gateway.internet_gateway] + + tags = { + Name = "NAT-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } +} + +/* + * Set use_transit_gateway to true create transit gateway attachments in the private subnets and + * route traffic to the TGW instead of NAT GW. + * Should be used when create_nat_gateway=false. + */ +resource "aws_ec2_transit_gateway_vpc_attachment" "transit_gateway" { + count = var.use_transit_gateway ? 1 : 0 + + subnet_ids = aws_subnet.private_subnet.*.id + transit_gateway_id = var.transit_gateway_id + vpc_id = aws_vpc.vpc.id + transit_gateway_default_route_table_association = var.transit_gateway_default_route_table_association + transit_gateway_default_route_table_propagation = var.transit_gateway_default_route_table_propagation + + tags = { + Name = "TGW-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } +} + +/* + * Routes for private subnets to use NAT or Transit gateway + */ +resource "aws_route_table" "private_route_table" { + vpc_id = aws_vpc.vpc.id + + tags = { + Name = "RT-private-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } +} + +/* + * Refactoring information to note that we renamed aws_route_table.nat_route_table to aws_route_table.private_route_table + * This prevents the resource from being destroyed and recreated. Should be kept at least until the next major revision + * but probably should remain permanently. + */ +moved { + from = aws_route_table.nat_route_table + to = aws_route_table.private_route_table +} + +resource "aws_route" "nat_route" { + count = var.create_nat_gateway ? 1 : 0 + + route_table_id = aws_route_table.private_route_table.id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = one(aws_nat_gateway.nat_gateway[*].id) +} + +resource "aws_route" "transit_gateway" { + count = var.use_transit_gateway ? 1 : 0 + + route_table_id = aws_route_table.private_route_table.id + destination_cidr_block = "0.0.0.0/0" + transit_gateway_id = var.transit_gateway_id +} + +resource "aws_route_table_association" "private_route" { + count = length(var.aws_zones) + subnet_id = element(aws_subnet.private_subnet.*.id, count.index) + route_table_id = aws_route_table.private_route_table.id +} + +/* + * Routes for public subnets to use internet gateway + */ +resource "aws_route_table" "igw_route_table" { + vpc_id = aws_vpc.vpc.id + + tags = { + Name = "RT-public-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } +} + +resource "aws_route" "igw_route" { + route_table_id = aws_route_table.igw_route_table.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.internet_gateway.id +} + +resource "aws_route" "public_ipv6" { + count = var.enable_ipv6 ? 1 : 0 + + route_table_id = aws_route_table.igw_route_table.id + destination_ipv6_cidr_block = "::/0" + gateway_id = aws_internet_gateway.internet_gateway.id +} + +resource "aws_route_table_association" "public_route" { + count = length(var.aws_zones) + subnet_id = element(aws_subnet.public_subnet.*.id, count.index) + route_table_id = aws_route_table.igw_route_table.id +} + +/* + * Create DB Subnet Group for private subnets + */ +resource "aws_db_subnet_group" "db_subnet_group" { + name = "db-subnet-${var.app_name}-${var.app_env}" + subnet_ids = aws_subnet.private_subnet.*.id + + tags = { + Name = "db-subnet-${var.app_name}-${var.app_env}" + app_name = var.app_name + app_env = var.app_env + } } diff --git a/outputs.tf b/outputs.tf index bdd4577..a95bb46 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,5 +1,43 @@ -output "output_name" { - value = var.variable_name - description = "description of the output" - sensitive = true +output "id" { + value = aws_vpc.vpc.id +} + +output "ipv6_association_id" { + value = aws_vpc.vpc.ipv6_association_id +} + +output "ipv6_cidr_block" { + value = aws_vpc.vpc.ipv6_cidr_block +} + +output "vpc_default_sg_id" { + value = data.aws_security_group.vpc_default_sg.id +} + +output "public_subnet_ids" { + value = aws_subnet.public_subnet.*.id +} + +output "public_subnet_cidr_blocks" { + value = aws_subnet.public_subnet.*.cidr_block +} + +output "private_subnet_ids" { + value = aws_subnet.private_subnet.*.id +} + +output "private_subnet_cidr_blocks" { + value = aws_subnet.private_subnet.*.cidr_block +} + +output "db_subnet_group_name" { + value = aws_db_subnet_group.db_subnet_group.name +} + +output "aws_zones" { + value = var.aws_zones +} + +output "nat_gateway_ip" { + value = one(aws_nat_gateway.nat_gateway[*].public_ip) } diff --git a/test/main.tf b/test/main.tf index 939673e..b3c16d0 100644 --- a/test/main.tf +++ b/test/main.tf @@ -9,31 +9,70 @@ module "minimal" { source = "../" - - variable_name = "foo" } module "full" { source = "../" - variable_name = "foo" -} - -output "an_output" { - value = module.minimal.output_name + app_name = "" + app_env = "" + aws_zones = [""] + enable_dns_hostnames = false + create_nat_gateway = false + use_transit_gateway = false + private_subnet_cidr_blocks = [""] + public_subnet_cidr_blocks = [""] + transit_gateway_id = "" + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + vpc_cidr_block = "" + enable_ipv6 = true } provider "aws" { region = "us-east-1" } -terraform { - required_version = ">= 1.0" +output "id" { + value = module.full.id +} + +output "ipv6_association_id" { + value = module.full.ipv6_association_id +} + +output "ipv6_cidr_block" { + value = module.full.ipv6_cidr_block +} + +output "vpc_default_sg_id" { + value = module.full.vpc_default_sg_id +} + +output "public_subnet_ids" { + value = module.full.public_subnet_ids +} + +output "public_subnet_cidr_blocks" { + value = module.full.public_subnet_cidr_blocks +} + +output "private_subnet_ids" { + value = module.full.private_subnet_ids +} + +output "private_subnet_cidr_blocks" { + value = module.full.private_subnet_cidr_blocks +} + +output "db_subnet_group_name" { + value = module.full.db_subnet_group_name +} + +output "aws_zones" { + value = module.full.aws_zones +} - required_providers { - aws = { - source = "hashicorp/aws" - version = "~> 5.0" - } - } +output "nat_gateway_ip" { + value = module.full.nat_gateway_ip } diff --git a/variables.tf b/variables.tf index 61678e4..f2f534e 100644 --- a/variables.tf +++ b/variables.tf @@ -1,6 +1,82 @@ -variable "variable_name" { - description = "Use this order of variable properties: description, type, default, sensitive" +/* + * Required variables + */ +variable "app_name" { + type = string + default = "terraform" +} + +variable "app_env" { + type = string + default = "testing" +} + +variable "aws_zones" { + description = "A list of zones to create subnets (max 8)" + type = list(string) + + default = [ + "us-east-1c", + "us-east-1d", + "us-east-1e", + ] +} + +variable "enable_dns_hostnames" { + type = bool + default = false +} + +variable "create_nat_gateway" { + description = "Set to false to remove NAT gateway and associated route" + type = bool + default = true +} + +variable "use_transit_gateway" { + description = "Set to true to create transit gateway attachments and route traffic to a TGW." + type = bool + default = false +} + +variable "private_subnet_cidr_blocks" { + description = "The CIDR blocks for the private subnets (one per AZ, in order). There must be at least as many private CIDRs as AZs, and they must not overlap the public CIDRs." + type = list(string) + default = ["10.0.11.0/24", "10.0.22.0/24", "10.0.33.0/24", "10.0.44.0/24"] +} + +variable "public_subnet_cidr_blocks" { + description = "The CIDR blocks for the public subnets (one per AZ, in order). There must be at least as many public CIDRs as AZs, and they must not overlap the private CIDRs." + type = list(string) + default = ["10.0.10.0/24", "10.0.20.0/24", "10.0.30.0/24", "10.0.40.0/24"] +} + +variable "transit_gateway_id" { + description = "The id of the transit gateway to attach to. Used in conjuction with use_transit_gateway." type = string default = "" - sensitive = true +} + +variable "transit_gateway_default_route_table_association" { + description = "Whether or not to associate with the default route table of the transit gateway." + type = bool + default = true +} + +variable "transit_gateway_default_route_table_propagation" { + description = "Whether or not to send propagation of this route to the default route table of the transit gateway." + type = bool + default = true +} + +variable "vpc_cidr_block" { + description = "The block of IP addresses (as a CIDR) the VPC should use" + type = string + default = "10.0.0.0/16" +} + +variable "enable_ipv6" { + description = "Add an IPv6 CIDR block to the VPC and IPv6 CIDR blocks to the public and private subnets" + type = bool + default = false } diff --git a/versions.tf b/versions.tf index 5ba3ffe..fab0a2d 100644 --- a/versions.tf +++ b/versions.tf @@ -1,15 +1,11 @@ -terraform { - required_version = ">= 1.0" - /* - * TODO: add any provider version constraints required by provider features - * only available in later versions. - */ +terraform { + required_version = ">= 0.12" required_providers { aws = { - version = ">=5.0.0, <6.0" source = "hashicorp/aws" + version = ">= 4.0.0, < 5.0.0" } } }