diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 94ccd6f..211cb3b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,6 +9,7 @@ env: AWS_REGION: us-west-1 AWS_ROLE: arn:aws:iam::270074865685:role/terraform-module-ci-test GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + DOMAIN: ${{secrets.DOMAIN}} permissions: write-all diff --git a/README.md b/README.md index be2917e..00a58db 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,17 @@ ## Recent Changes +- BREAKING CHANGES! + + While adding the loadbalancer and domain to this module it kinda seems like the ssh key shouldn't be included. + I also found a more standardized approach to how to skip or select modules. + When adding a load balancer I discovered that subnets will need to be tied to availability zones. + I also found that it was easier to combine the subnet input to something more complex, but should be easy enough to figure out + 1. No longer managing ssh keys with this module! + 2. The -use-strategy variables now determine how modules are used (create, skip, or select) + 3. Subnets inputs needed to change to incorporate high availability + With this is a massive change in the interface, this is a major break from the previous version, but I believe necessary for its growth. + - Skip Runner IP By default this module will create a security group which allows the ip of the client running terraform ingress and egress access. diff --git a/examples/basic/main.tf b/examples/basic/main.tf index cb8fcc9..d01961c 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -2,27 +2,23 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier + Owner = "terraform-ci@suse.com" } } } locals { identifier = var.identifier - name = "tf-basic-${local.identifier}" - key = var.key - key_name = var.key_name + name = "tf-${local.identifier}" + domain = "${local.identifier}-${var.domain}" } # AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively) module "this" { source = "../../" - owner = "terraform-ci@suse.com" vpc_name = local.name vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 - subnet_name = local.name - subnet_cidr = "10.0.255.224/28" # gives 14 usable addresses from .225 to .238, but AWS reserves .225 to .227 and .238, leaving .227 to .237 - availability_zone = "us-west-1b" # check what availability zones are available in your region before setting this security_group_name = local.name security_group_type = "egress" - public_ssh_key = local.key - ssh_key_name = local.key_name + load_balancer_name = local.name + domain = local.domain } diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf index a8a8e2e..9c7a45e 100644 --- a/examples/basic/outputs.tf +++ b/examples/basic/outputs.tf @@ -1,15 +1,18 @@ output "vpc" { value = module.this.vpc } - -output "subnet" { - value = module.this.subnet +output "subnets" { + value = module.this.subnets } - output "security_group" { value = module.this.security_group } - -output "ssh_key" { - value = module.this.ssh_key +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate } diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf index 7d75481..d7fa9b1 100644 --- a/examples/basic/variables.tf +++ b/examples/basic/variables.tf @@ -1,9 +1,6 @@ -variable "key" { +variable "identifier" { type = string } -variable "key_name" { +variable "domain" { type = string } -variable "identifier" { - type = string -} \ No newline at end of file diff --git a/examples/basic/versions.tf b/examples/basic/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/basic/versions.tf +++ b/examples/basic/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/examples/domain/main.tf b/examples/domain/main.tf new file mode 100644 index 0000000..b80a7a0 --- /dev/null +++ b/examples/domain/main.tf @@ -0,0 +1,27 @@ + +provider "aws" { + default_tags { + tags = { + Id = local.identifier + Owner = local.owner + } + } +} +locals { + identifier = var.identifier + name = "tf-${local.identifier}" + owner = "terraform-ci@suse.com" + domain = "${local.identifier}-${var.domain}" + #zone = var.domain_zone +} +# AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively) +module "this" { + source = "../../" + vpc_name = local.name + vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 + security_group_name = local.name + security_group_type = "project" + load_balancer_name = local.name + domain = local.domain + #domain_zone = local.zone # only specify when creating a new zone +} diff --git a/examples/domain/outputs.tf b/examples/domain/outputs.tf new file mode 100644 index 0000000..9c7a45e --- /dev/null +++ b/examples/domain/outputs.tf @@ -0,0 +1,18 @@ +output "vpc" { + value = module.this.vpc +} +output "subnets" { + value = module.this.subnets +} +output "security_group" { + value = module.this.security_group +} +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/domain/variables.tf b/examples/domain/variables.tf new file mode 100644 index 0000000..7742e67 --- /dev/null +++ b/examples/domain/variables.tf @@ -0,0 +1,14 @@ +variable "identifier" { + type = string +} +# variable "domain_zone" { +# type = string +# description = "The domain zone to use for the domain record. eg. example.com for domain 'test.example.com'" +# } +variable "domain" { + type = string + description = <<-EOT + The domain to use for the domain record. eg. 'test.example.com'. + This example assumes that the zone already exists. + EOT +} \ No newline at end of file diff --git a/examples/personal/versions.tf b/examples/domain/versions.tf similarity index 75% rename from examples/personal/versions.tf rename to examples/domain/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/personal/versions.tf +++ b/examples/domain/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/examples/sgip/main.tf b/examples/loadbalancer/main.tf similarity index 52% rename from examples/sgip/main.tf rename to examples/loadbalancer/main.tf index 10ef52f..f0bf423 100644 --- a/examples/sgip/main.tf +++ b/examples/loadbalancer/main.tf @@ -1,23 +1,23 @@ + provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier + Owner = "terraform-ci@suse.com" } } } locals { identifier = var.identifier - name = "tf-sgip-${local.identifier}" + name = "tf-${local.identifier}" } - +# AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively) module "this" { source = "../../" - owner = "terraform-ci@suse.com" vpc_name = local.name vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 - skip_subnet = true security_group_name = local.name - security_group_type = "specific" - security_group_ip = "192.168.1.1" - skip_ssh = true + security_group_type = "project" + load_balancer_name = local.name + domain_use_strategy = "skip" } diff --git a/examples/loadbalancer/outputs.tf b/examples/loadbalancer/outputs.tf new file mode 100644 index 0000000..9c7a45e --- /dev/null +++ b/examples/loadbalancer/outputs.tf @@ -0,0 +1,18 @@ +output "vpc" { + value = module.this.vpc +} +output "subnets" { + value = module.this.subnets +} +output "security_group" { + value = module.this.security_group +} +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/sgip/variables.tf b/examples/loadbalancer/variables.tf similarity index 100% rename from examples/sgip/variables.tf rename to examples/loadbalancer/variables.tf diff --git a/examples/project/versions.tf b/examples/loadbalancer/versions.tf similarity index 75% rename from examples/project/versions.tf rename to examples/loadbalancer/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/project/versions.tf +++ b/examples/loadbalancer/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/examples/override/main.tf b/examples/override/main.tf deleted file mode 100644 index c3b2896..0000000 --- a/examples/override/main.tf +++ /dev/null @@ -1,19 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} -locals { - identifier = var.identifier - security_group_name = var.security_group_name - key_name = var.key_name -} -module "this" { - source = "../../" - vpc_name = "default" - subnet_name = "default" - security_group_name = local.security_group_name - ssh_key_name = local.key_name -} diff --git a/examples/override/outputs.tf b/examples/override/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/override/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/override/variables.tf b/examples/override/variables.tf deleted file mode 100644 index 70b01e9..0000000 --- a/examples/override/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "key_name" { - type = string -} -variable "security_group_name" { - type = string -} -variable "identifier" { - type = string -} \ No newline at end of file diff --git a/examples/personal/main.tf b/examples/personal/main.tf deleted file mode 100644 index 9f9c397..0000000 --- a/examples/personal/main.tf +++ /dev/null @@ -1,23 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} -locals { - identifier = var.identifier - name = "tf-personal-${local.identifier}" - key = var.key - key_name = var.key_name -} -module "this" { - source = "../../" - owner = "terraform-ci@suse.com" # update this to your email or a group email, the resources will be tagged with this - vpc_name = "default" # select the default vpc - subnet_name = "default" # select the default subnet - security_group_name = local.name - security_group_type = "egress" - public_ssh_key = local.key - ssh_key_name = local.key_name -} diff --git a/examples/personal/outputs.tf b/examples/personal/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/personal/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/personal/variables.tf b/examples/personal/variables.tf deleted file mode 100644 index 7d75481..0000000 --- a/examples/personal/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "key" { - type = string -} -variable "key_name" { - type = string -} -variable "identifier" { - type = string -} \ No newline at end of file diff --git a/examples/project/main.tf b/examples/project/main.tf deleted file mode 100644 index 9733e98..0000000 --- a/examples/project/main.tf +++ /dev/null @@ -1,22 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} -locals { - identifier = var.identifier - name = "tf-project-${local.identifier}" - key_name = var.key_name -} -module "this" { - source = "../../" - owner = "terraform-ci@suse.com" - vpc_name = "default" # select the default vpc - subnet_name = local.name - subnet_cidr = "172.31.254.0/24" # this must be an unused block from the vpc's cidr - security_group_name = local.name - security_group_type = "egress" - ssh_key_name = local.key_name -} diff --git a/examples/project/outputs.tf b/examples/project/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/project/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/securitygroup/main.tf b/examples/securitygroup/main.tf new file mode 100644 index 0000000..58a9e10 --- /dev/null +++ b/examples/securitygroup/main.tf @@ -0,0 +1,22 @@ + +provider "aws" { + default_tags { + tags = { + Id = local.identifier + Owner = "terraform-ci@suse.com" + } + } +} +locals { + identifier = var.identifier + name = "tf-${local.identifier}" +} +# AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively) +module "this" { + source = "../../" + vpc_name = local.name + vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 + security_group_name = local.name + security_group_type = "project" + load_balancer_use_strategy = "skip" # everything depending on load balancer is skipped implicitly +} diff --git a/examples/securitygroup/outputs.tf b/examples/securitygroup/outputs.tf new file mode 100644 index 0000000..9c7a45e --- /dev/null +++ b/examples/securitygroup/outputs.tf @@ -0,0 +1,18 @@ +output "vpc" { + value = module.this.vpc +} +output "subnets" { + value = module.this.subnets +} +output "security_group" { + value = module.this.security_group +} +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/skipip/variables.tf b/examples/securitygroup/variables.tf similarity index 100% rename from examples/skipip/variables.tf rename to examples/securitygroup/variables.tf diff --git a/examples/override/versions.tf b/examples/securitygroup/versions.tf similarity index 75% rename from examples/override/versions.tf rename to examples/securitygroup/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/override/versions.tf +++ b/examples/securitygroup/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/examples/selectvpc/main.tf b/examples/selectvpc/main.tf new file mode 100644 index 0000000..9e261db --- /dev/null +++ b/examples/selectvpc/main.tf @@ -0,0 +1,38 @@ + +provider "aws" { + default_tags { + tags = { + Id = local.identifier + Owner = "terraform-ci@suse.com" + } + } +} +provider "acme" { + server_url = "https://acme-staging-v02.api.letsencrypt.org/directory" +} + +locals { + identifier = var.identifier + name = "tf-${local.identifier}" + domain = "${local.identifier}-${var.domain}" +} + +module "setup" { + source = "../../" + vpc_name = local.name + vpc_cidr = "10.0.255.0/24" + subnet_use_strategy = "skip" +} + +module "this" { + depends_on = [ + module.setup + ] + source = "../../" + vpc_use_strategy = "select" + vpc_name = module.setup.vpc.tags.Name + security_group_name = local.name + security_group_type = "egress" + load_balancer_name = local.name + domain = local.domain +} diff --git a/examples/selectvpc/outputs.tf b/examples/selectvpc/outputs.tf new file mode 100644 index 0000000..9c7a45e --- /dev/null +++ b/examples/selectvpc/outputs.tf @@ -0,0 +1,18 @@ +output "vpc" { + value = module.this.vpc +} +output "subnets" { + value = module.this.subnets +} +output "security_group" { + value = module.this.security_group +} +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/project/variables.tf b/examples/selectvpc/variables.tf similarity index 71% rename from examples/project/variables.tf rename to examples/selectvpc/variables.tf index d1d123b..d7fa9b1 100644 --- a/examples/project/variables.tf +++ b/examples/selectvpc/variables.tf @@ -1,6 +1,6 @@ -variable "key_name" { +variable "identifier" { type = string } -variable "identifier" { +variable "domain" { type = string -} \ No newline at end of file +} diff --git a/examples/sgip/versions.tf b/examples/selectvpc/versions.tf similarity index 76% rename from examples/sgip/versions.tf rename to examples/selectvpc/versions.tf index 7c63b5d..1399985 100644 --- a/examples/sgip/versions.tf +++ b/examples/selectvpc/versions.tf @@ -9,9 +9,9 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" + acme = { + source = "vancluever/acme" + version = ">= 2.0" } } } \ No newline at end of file diff --git a/examples/sgip/outputs.tf b/examples/sgip/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/sgip/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/skipip/main.tf b/examples/skipip/main.tf deleted file mode 100644 index 6e86a13..0000000 --- a/examples/skipip/main.tf +++ /dev/null @@ -1,26 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} - -locals { - identifier = var.identifier - name = "tf-skipip-${local.identifier}" - -} - -module "this" { - source = "../../" - owner = "terraform-ci@suse.com" - vpc_name = local.name - vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 - subnet_name = local.name - subnet_cidr = "10.0.255.224/28" # gives 14 usable addresses from .225 to .238, but AWS reserves .225 to .227 and .238, leaving .227 to .237 - security_group_name = local.name - security_group_type = "internal" # air gapped, internal only and no holes - skip_runner_ip = true # air gapped, internal only and no holes - skip_ssh = true -} diff --git a/examples/skipip/outputs.tf b/examples/skipip/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/skipip/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/skipip/versions.tf b/examples/skipip/versions.tf deleted file mode 100644 index 7c63b5d..0000000 --- a/examples/skipip/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -terraform { - required_version = ">= 1.5.0, < 1.6" - required_providers { - local = { - source = "hashicorp/local" - version = ">= 2.4" - } - aws = { - source = "hashicorp/aws" - version = ">= 5.11" - } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } - } -} \ No newline at end of file diff --git a/examples/skipsecuritygroup/main.tf b/examples/skipsecuritygroup/main.tf deleted file mode 100644 index 21146f1..0000000 --- a/examples/skipsecuritygroup/main.tf +++ /dev/null @@ -1,26 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} -locals { - identifier = var.identifier - name = "tf-skipsecgroup-${local.identifier}" - key = var.key - key_name = var.key_name -} -# vpc, subnet, and ssh key -module "this" { - source = "../../" - owner = "terraform-ci@suse.com" - vpc_name = local.name - vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 - subnet_name = local.name - subnet_cidr = "10.0.255.224/28" # gives 14 usable addresses from .225 to .238, but AWS reserves .225 to .227 and .238, leaving .227 to .237 - availability_zone = "us-west-1b" # check what availability zones are available in your region before setting this - skip_security_group = true - public_ssh_key = local.key - ssh_key_name = local.key_name -} diff --git a/examples/skipsecuritygroup/outputs.tf b/examples/skipsecuritygroup/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/skipsecuritygroup/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/skipsecuritygroup/variables.tf b/examples/skipsecuritygroup/variables.tf deleted file mode 100644 index 7d75481..0000000 --- a/examples/skipsecuritygroup/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "key" { - type = string -} -variable "key_name" { - type = string -} -variable "identifier" { - type = string -} \ No newline at end of file diff --git a/examples/skipsecuritygroup/versions.tf b/examples/skipsecuritygroup/versions.tf deleted file mode 100644 index 7c63b5d..0000000 --- a/examples/skipsecuritygroup/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -terraform { - required_version = ">= 1.5.0, < 1.6" - required_providers { - local = { - source = "hashicorp/local" - version = ">= 2.4" - } - aws = { - source = "hashicorp/aws" - version = ">= 5.11" - } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } - } -} \ No newline at end of file diff --git a/examples/skipssh/main.tf b/examples/skipssh/main.tf deleted file mode 100644 index fefabaa..0000000 --- a/examples/skipssh/main.tf +++ /dev/null @@ -1,24 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} -locals { - identifier = var.identifier - name = "tf-skipssh-${local.identifier}" -} -# generate vpc, subnet, and security group, skip ssh keypair -module "this" { - source = "../../" - owner = "terraform-ci@suse.com" - vpc_name = local.name - vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 - subnet_name = local.name - subnet_cidr = "10.0.255.224/28" # gives 14 usable addresses from .225 to .238, but AWS reserves .225 to .227 and .238, leaving .227 to .237 - availability_zone = "us-west-1b" # check what availability zones are available in your region before setting this - security_group_name = local.name - security_group_type = "egress" - skip_ssh = true -} diff --git a/examples/skipssh/outputs.tf b/examples/skipssh/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/skipssh/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/skipssh/versions.tf b/examples/skipssh/versions.tf deleted file mode 100644 index 7c63b5d..0000000 --- a/examples/skipssh/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -terraform { - required_version = ">= 1.5.0, < 1.6" - required_providers { - local = { - source = "hashicorp/local" - version = ">= 2.4" - } - aws = { - source = "hashicorp/aws" - version = ">= 5.11" - } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } - } -} \ No newline at end of file diff --git a/examples/skipsubnet/main.tf b/examples/skipsubnet/main.tf deleted file mode 100644 index 6c4a5d5..0000000 --- a/examples/skipsubnet/main.tf +++ /dev/null @@ -1,26 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} -locals { - identifier = var.identifier - name = "tf-skipssh-${local.identifier}" - key = var.key - key_name = var.key_name -} -# generate vpc, security group, and ssh key -module "this" { - source = "../../" - owner = "terraform-ci@suse.com" - vpc_name = local.name - vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 - skip_subnet = true - security_group_name = local.name - security_group_type = "specific" - security_group_ip = "192.168.0.1" - public_ssh_key = local.key - ssh_key_name = local.key_name -} diff --git a/examples/skipsubnet/outputs.tf b/examples/skipsubnet/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/skipsubnet/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/skipsubnet/variables.tf b/examples/skipsubnet/variables.tf deleted file mode 100644 index 7d75481..0000000 --- a/examples/skipsubnet/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "key" { - type = string -} -variable "key_name" { - type = string -} -variable "identifier" { - type = string -} \ No newline at end of file diff --git a/examples/skipsubnet/versions.tf b/examples/skipsubnet/versions.tf deleted file mode 100644 index 7c63b5d..0000000 --- a/examples/skipsubnet/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -terraform { - required_version = ">= 1.5.0, < 1.6" - required_providers { - local = { - source = "hashicorp/local" - version = ">= 2.4" - } - aws = { - source = "hashicorp/aws" - version = ">= 5.11" - } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } - } -} \ No newline at end of file diff --git a/examples/skipvpc/main.tf b/examples/skipvpc/main.tf index 3f035bd..0913845 100644 --- a/examples/skipvpc/main.tf +++ b/examples/skipvpc/main.tf @@ -1,21 +1,15 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier + Owner = "terraform-ci@suse.com" } } } locals { identifier = var.identifier - key = var.key - key_name = var.key_name } module "this" { - source = "../../" - owner = "terraform-ci@suse.com" - skip_vpc = true - skip_subnet = true # without a vpc selected or created subnet can't be created - skip_security_group = true # without a vpc selected of created security group can't be created - public_ssh_key = local.key - ssh_key_name = local.key_name + source = "../../" + vpc_use_strategy = "skip" # everything depending on vpc is skipped implicitly } diff --git a/examples/skipvpc/outputs.tf b/examples/skipvpc/outputs.tf index a8a8e2e..9c7a45e 100644 --- a/examples/skipvpc/outputs.tf +++ b/examples/skipvpc/outputs.tf @@ -1,15 +1,18 @@ output "vpc" { value = module.this.vpc } - -output "subnet" { - value = module.this.subnet +output "subnets" { + value = module.this.subnets } - output "security_group" { value = module.this.security_group } - -output "ssh_key" { - value = module.this.ssh_key +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate } diff --git a/examples/skipvpc/variables.tf b/examples/skipvpc/variables.tf index 7d75481..72974fb 100644 --- a/examples/skipvpc/variables.tf +++ b/examples/skipvpc/variables.tf @@ -1,9 +1,4 @@ -variable "key" { - type = string -} -variable "key_name" { - type = string -} + variable "identifier" { type = string } \ No newline at end of file diff --git a/examples/skipvpc/versions.tf b/examples/skipvpc/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/skipvpc/versions.tf +++ b/examples/skipvpc/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/examples/specifyip/main.tf b/examples/specifyip/main.tf deleted file mode 100644 index e348c03..0000000 --- a/examples/specifyip/main.tf +++ /dev/null @@ -1,30 +0,0 @@ -provider "aws" { - default_tags { - tags = { - Id = local.identifier - } - } -} - -locals { - identifier = var.identifier - name = "tf-specifyip-${local.identifier}" - key = var.key - key_name = var.key_name - ip = var.ip -} - -module "this" { - source = "../../" - owner = "terraform-ci@suse.com" - vpc_name = local.name - vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 - subnet_name = local.name - subnet_cidr = "10.0.255.224/28" # gives 14 usable addresses from .225 to .238, but AWS reserves .225 to .227 and .238, leaving .227 to .237 - availability_zone = "us-west-1b" # check what availability zones are available in your region before setting this - security_group_name = local.name - security_group_type = "egress" - security_group_ip = chomp(local.ip) - public_ssh_key = local.key - ssh_key_name = local.key_name -} diff --git a/examples/specifyip/outputs.tf b/examples/specifyip/outputs.tf deleted file mode 100644 index a8a8e2e..0000000 --- a/examples/specifyip/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "vpc" { - value = module.this.vpc -} - -output "subnet" { - value = module.this.subnet -} - -output "security_group" { - value = module.this.security_group -} - -output "ssh_key" { - value = module.this.ssh_key -} diff --git a/examples/specifyip/variables.tf b/examples/specifyip/variables.tf deleted file mode 100644 index ccb7ba4..0000000 --- a/examples/specifyip/variables.tf +++ /dev/null @@ -1,12 +0,0 @@ -variable "key" { - type = string -} -variable "key_name" { - type = string -} -variable "ip" { - type = string -} -variable "identifier" { - type = string -} \ No newline at end of file diff --git a/examples/specifyip/versions.tf b/examples/specifyip/versions.tf deleted file mode 100644 index 7c63b5d..0000000 --- a/examples/specifyip/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -terraform { - required_version = ">= 1.5.0, < 1.6" - required_providers { - local = { - source = "hashicorp/local" - version = ">= 2.4" - } - aws = { - source = "hashicorp/aws" - version = ">= 5.11" - } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } - } -} \ No newline at end of file diff --git a/examples/subnets/main.tf b/examples/subnets/main.tf new file mode 100644 index 0000000..a4f34ec --- /dev/null +++ b/examples/subnets/main.tf @@ -0,0 +1,37 @@ + +provider "aws" { + default_tags { + tags = { + Id = local.identifier + Owner = "terraform-ci@suse.com" + } + } +} +locals { + identifier = var.identifier + name = "tf-${local.identifier}" +} +# AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively) +module "this" { + source = "../../" + vpc_name = local.name + vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 + subnets = { + "subnetA" = { + cidr = "10.0.255.0/26" + availability_zone = "us-west-2a" + public = false # when true AWS will automatically provision public ips for instances in this subnet + } + "subnetB" = { + cidr = "10.0.255.64/26" + availability_zone = "us-west-2b" + public = false # when true AWS will automatically provision public ips for instances in this subnet + } + "subnetC" = { + cidr = "10.0.255.128/26" + availability_zone = "us-west-2c" + public = false # when true AWS will automatically provision public ips for instances in this subnet + } + } + security_group_use_strategy = "skip" # everything depending on security group is skipped implicitly +} diff --git a/examples/subnets/outputs.tf b/examples/subnets/outputs.tf new file mode 100644 index 0000000..9c7a45e --- /dev/null +++ b/examples/subnets/outputs.tf @@ -0,0 +1,18 @@ +output "vpc" { + value = module.this.vpc +} +output "subnets" { + value = module.this.subnets +} +output "security_group" { + value = module.this.security_group +} +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/skipssh/variables.tf b/examples/subnets/variables.tf similarity index 95% rename from examples/skipssh/variables.tf rename to examples/subnets/variables.tf index 379bf39..e960ce9 100644 --- a/examples/skipssh/variables.tf +++ b/examples/subnets/variables.tf @@ -1,3 +1,3 @@ variable "identifier" { type = string -} \ No newline at end of file +} diff --git a/examples/subnets/versions.tf b/examples/subnets/versions.tf new file mode 100644 index 0000000..24e1e1d --- /dev/null +++ b/examples/subnets/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.5.0, < 1.6" + required_providers { + local = { + source = "hashicorp/local" + version = ">= 2.4" + } + aws = { + source = "hashicorp/aws" + version = ">= 5.11" + } + } +} \ No newline at end of file diff --git a/examples/vpc/main.tf b/examples/vpc/main.tf new file mode 100644 index 0000000..9ef08f8 --- /dev/null +++ b/examples/vpc/main.tf @@ -0,0 +1,20 @@ + +provider "aws" { + default_tags { + tags = { + Id = local.identifier + Owner = "terraform-ci@suse.com" + } + } +} +locals { + identifier = var.identifier + name = "tf-${local.identifier}" +} +# AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively) +module "this" { + source = "../../" + vpc_name = local.name + vpc_cidr = "10.0.255.0/24" # gives 256 usable addresses from .1 to .254, but AWS reserves .1 to .4 and .255, leaving .5 to .254 + subnet_use_strategy = "skip" # everything depending on subnet is skipped implicitly +} diff --git a/examples/vpc/outputs.tf b/examples/vpc/outputs.tf new file mode 100644 index 0000000..9c7a45e --- /dev/null +++ b/examples/vpc/outputs.tf @@ -0,0 +1,18 @@ +output "vpc" { + value = module.this.vpc +} +output "subnets" { + value = module.this.subnets +} +output "security_group" { + value = module.this.security_group +} +output "load_balancer" { + value = module.this.load_balancer +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/vpc/variables.tf b/examples/vpc/variables.tf new file mode 100644 index 0000000..d76b092 --- /dev/null +++ b/examples/vpc/variables.tf @@ -0,0 +1,4 @@ +variable "identifier" { + type = string +} + diff --git a/examples/vpc/versions.tf b/examples/vpc/versions.tf new file mode 100644 index 0000000..24e1e1d --- /dev/null +++ b/examples/vpc/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.5.0, < 1.6" + required_providers { + local = { + source = "hashicorp/local" + version = ">= 2.4" + } + aws = { + source = "hashicorp/aws" + version = ">= 5.11" + } + } +} \ No newline at end of file diff --git a/flake.lock b/flake.lock index 0940f1a..40a5b6b 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711106783, - "narHash": "sha256-PDwAcHahc6hEimyrgGmFdft75gmLrJOZ0txX7lFqq+I=", + "lastModified": 1711715736, + "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a3ed7406349a9335cb4c2a71369b697cecd9d351", + "rev": "807c549feabce7eddbf259dbdcec9e0600a0660d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2afafaf..f916711 100644 --- a/flake.nix +++ b/flake.nix @@ -100,6 +100,7 @@ aspellWithDicts bashInteractive curl + dig docker gh git @@ -108,6 +109,7 @@ go jq kubectl + lego less ncurses vim # for easily editing files that are not in this directory structure diff --git a/main.tf b/main.tf index b9de076..7ec44b2 100644 --- a/main.tf +++ b/main.tf @@ -1,69 +1,139 @@ locals { - owner = var.owner + vpc_use_strategy = var.vpc_use_strategy + vpc_mod = ( + local.vpc_use_strategy == "skip" ? 0 : 1 + ) + subnet_use_strategy = var.subnet_use_strategy + subnet_mod = ( + local.subnet_use_strategy == "skip" ? 0 : ( + local.vpc_use_strategy == "skip" ? 0 : 1 # subnet mod requires vpc mod + ) + ) + security_group_use_strategy = var.security_group_use_strategy + security_group_mod = ( + local.security_group_use_strategy == "skip" ? 0 : ( + local.subnet_use_strategy == "skip" ? 0 : ( # security group mod requires subnet mod + local.vpc_use_strategy == "skip" ? 0 : 1 # security group mod requires vpc mod + ) + ) + ) + load_balancer_use_strategy = var.load_balancer_use_strategy + load_balancer_mod = ( + local.load_balancer_use_strategy == "skip" ? 0 : ( + local.security_group_use_strategy == "skip" ? 0 : ( # load balancer mod requires security group mod + local.subnet_use_strategy == "skip" ? 0 : ( # load balancer mod requires subnet mod + local.vpc_use_strategy == "skip" ? 0 : 1 # load balancer mod requires vpc mod + ) + ) + ) + ) + domain_use_strategy = var.domain_use_strategy + domain_mod = ( + local.domain_use_strategy == "skip" ? 0 : ( + local.load_balancer_use_strategy == "skip" ? 0 : ( # domain mod requires load balancer mod + local.security_group_use_strategy == "skip" ? 0 : ( # domain mod requires security group mod + local.subnet_use_strategy == "skip" ? 0 : ( # domain mod requires subnet mod + local.vpc_use_strategy == "skip" ? 0 : 1 # domain mod requires vpc mod + ) + ) + ) + ) + ) + + # vpc vpc_name = var.vpc_name - vpc_cidr = var.vpc_cidr # create when cidr is given, otherwise select with name or skip - skip_vpc = var.skip_vpc # both subnet and security group need a vpc, but vpc is not necessary for ssh key + vpc_cidr = (var.vpc_cidr == "" ? "10.0.255.0/24" : var.vpc_cidr) + + # subnet + subnets = var.subnets + subnet_names = keys(local.subnets) + subnet_count = length(local.subnets) + newbits = (local.subnet_count > 1 ? ceil(log(local.subnet_count, 2)) : 1) + vpc_cidr_split = [for i in range(local.subnet_count) : cidrsubnet(local.vpc_cidr, local.newbits, i)] + potential_regional_subnets = { for i in range(local.subnet_count) : local.subnet_names[i] => local.vpc_cidr_split[i] } - subnet_name = var.subnet_name - subnet_cidr = var.subnet_cidr # create when cidr is given, otherwise select with name or skip - subnet_availability_zone = var.availability_zone # only used when creating - subnet_public_ip = var.subnet_public_ip # set this to true to enable public ip addressing on servers - skip_subnet = var.skip_subnet # if using the "specific" security group type you can skip subnet creation + zones = tolist(data.aws_availability_zones.available.names) + potential_subnet_zones = { for i in range(local.subnet_count) : local.subnet_names[i] => local.zones[i % length(local.zones)] } + # security group security_group_name = var.security_group_name - security_group_type = var.security_group_type # create when type is given, otherwise select with name or skip - security_group_ip = var.security_group_ip - ipinfo_ip = chomp(can(data.http.my_public_ip[0].response_body) ? data.http.my_public_ip[0].response_body : "127.0.0.1") - ip = (local.security_group_ip == "" ? local.ipinfo_ip : local.security_group_ip) - skip_security_group = var.skip_security_group # no objects in this module depend on security group being created, skip if wanted - skip_runner_ip = var.skip_runner_ip - ssh_key_name = var.ssh_key_name - public_ssh_key = var.public_ssh_key # create when public key is given, otherwise select with name - skip_ssh = var.skip_ssh # no objects in this module depend on ssh key being created, skip if wanted + security_group_type = var.security_group_type + + # domain + domain = var.domain + domain_zone = var.domain_zone + + # load balancer + load_balancer_name = var.load_balancer_name } -data "http" "my_public_ip" { - count = (local.security_group_ip == "" ? 1 : 0) - url = "https://ipinfo.io/ip" +data "aws_availability_zones" "available" { + state = "available" } module "vpc" { - count = (local.skip_vpc ? 0 : 1) + count = local.vpc_mod source = "./modules/vpc" + use = local.vpc_use_strategy name = local.vpc_name cidr = local.vpc_cidr } module "subnet" { - count = (local.skip_subnet ? 0 : 1) + depends_on = [ + module.vpc, + ] + for_each = (local.subnet_mod == 1 ? local.subnets : {}) source = "./modules/subnet" - name = local.subnet_name - cidr = local.subnet_cidr + use = local.subnet_use_strategy vpc_id = module.vpc[0].id - owner = local.owner - availability_zone = local.subnet_availability_zone - public_ip = local.subnet_public_ip + name = each.key + cidr = (each.value.cidr == "" ? local.potential_regional_subnets[each.key] : each.value.cidr) + availability_zone = (each.value.availability_zone == "" ? local.potential_subnet_zones[each.key] : each.value.availability_zone) + public = (each.value.public == "" ? false : each.value.public) } module "security_group" { - count = (local.skip_security_group ? 0 : 1) - source = "./modules/security_group" - name = local.security_group_name - ip = local.ip - cidr = (can(module.subnet[0].cidr) ? module.subnet[0].cidr : "") - owner = local.owner - type = local.security_group_type - vpc_id = module.vpc[0].id - vpc_cidr = module.vpc[0].vpc.cidr_block - skip_runner_ip = local.skip_runner_ip + depends_on = [ + module.subnet, + module.vpc, + ] + count = local.security_group_mod + source = "./modules/security_group" + use = local.security_group_use_strategy + name = local.security_group_name + type = local.security_group_type + vpc_id = module.vpc[0].id + vpc_cidr = module.vpc[0].vpc.cidr_block +} + +module "network_load_balancer" { + depends_on = [ + module.vpc, + module.subnet, + module.security_group, + ] + count = local.load_balancer_mod + source = "./modules/network_load_balancer" + use = local.load_balancer_use_strategy + name = local.load_balancer_name + security_group_id = module.security_group[0].id + subnet_ids = [for subnet in module.subnet : subnet.id] } -module "ssh_key" { - count = (local.skip_ssh ? 0 : 1) - source = "./modules/ssh_key" - name = local.ssh_key_name - public_key = local.public_ssh_key - owner = local.owner +module "domain" { + depends_on = [ + module.vpc, + module.subnet, + module.security_group, + module.network_load_balancer, + ] + count = local.domain_mod + source = "./modules/domain" + use = local.domain_use_strategy + content = lower(local.domain) + ip = module.network_load_balancer[0].public_ip + zone = local.domain_zone } diff --git a/modules/domain/main.tf b/modules/domain/main.tf new file mode 100644 index 0000000..294c8fe --- /dev/null +++ b/modules/domain/main.tf @@ -0,0 +1,159 @@ +locals { + + use = var.use + content = lower(var.content) + ip = var.ip + + content_parts = split(".", local.content) + top_level_domain = join(".", [ + local.content_parts[(length(local.content_parts) - 2)], + local.content_parts[(length(local.content_parts) - 1)], + ]) + subdomain = local.content_parts[0] + found_zone = join(".", [ + for part in local.content_parts : part if part != local.subdomain + ]) + + # zone + zone_id = (local.zone_select == 1 ? data.aws_route53_zone.select[0].id : aws_route53_zone.new[0].id) + domain_zone = var.zone + zone = (local.domain_zone == "" ? local.found_zone : local.domain_zone) + zone_create = (local.domain_zone == "" ? 0 : 1) + zone_select = (local.domain_zone == "" ? 1 : 0) + zone_resource = (local.zone_create == 1 ? aws_route53_zone.new[0] : data.aws_route53_zone.select[0]) + + # domain record + create = (local.use == "create" ? 1 : 0) + select = (local.use == "select" ? 1 : 0) +} + +data "aws_route53_zone" "select" { + count = local.zone_select + name = local.zone +} +resource "aws_route53_zone" "new" { + count = local.zone_create + name = local.zone +} +resource "aws_route53_record" "ns" { + count = local.zone_create + allow_overwrite = true + name = local.zone + ttl = 300 + type = "NS" + zone_id = aws_route53_zone.new[0].zone_id + + records = [ + aws_route53_zone.new[0].name_servers[0], + aws_route53_zone.new[0].name_servers[1], + aws_route53_zone.new[0].name_servers[2], + aws_route53_zone.new[0].name_servers[3], + ] +} + +resource "aws_route53domains_registered_domain" "select" { + count = local.select + domain_name = local.content +} + +resource "aws_route53_record" "new" { + depends_on = [ + data.aws_route53_zone.select, + aws_route53_zone.new, + ] + count = local.create + zone_id = local.zone_id + name = local.content + type = "A" + ttl = 30 + records = [local.ip] +} + +resource "tls_private_key" "private_key" { + count = local.create + algorithm = "RSA" +} + +# Warning, this can lead to rate limiting if you are not careful +# make sure you are not creating a new acme_registration for every certificate +resource "acme_registration" "reg" { + count = local.create + account_key_pem = tls_private_key.private_key[0].private_key_pem + email_address = "${local.zone_id}@${local.top_level_domain}" +} + +resource "tls_private_key" "cert_private_key" { + count = local.create + algorithm = "RSA" +} +resource "tls_cert_request" "req" { + count = local.create + private_key_pem = tls_private_key.cert_private_key[0].private_key_pem + subject { + common_name = local.content + } +} + +resource "acme_certificate" "new" { + depends_on = [ + data.aws_route53_zone.select, + aws_route53_zone.new, + aws_route53_record.new, + acme_registration.reg, + tls_private_key.private_key, + tls_private_key.cert_private_key, + tls_cert_request.req, + ] + count = local.create + account_key_pem = acme_registration.reg[0].account_key_pem + certificate_request_pem = tls_cert_request.req[0].cert_request_pem + recursive_nameservers = [ + "${local.zone_resource.primary_name_server}:53", + ] + disable_complete_propagation = true + dns_challenge { + provider = "route53" + config = { + AWS_PROPAGATION_TIMEOUT = 2400, + AWS_POLLING_INTERVAL = 60, + AWS_HOSTED_ZONE_ID = local.zone_id, + } + } +} + +resource "aws_iam_server_certificate" "new" { + depends_on = [ + data.aws_route53_zone.select, + aws_route53_zone.new, + aws_route53_record.new, + acme_registration.reg, + tls_private_key.private_key, + tls_private_key.cert_private_key, + tls_cert_request.req, + acme_certificate.new, + ] + count = local.create + name_prefix = local.content + certificate_body = acme_certificate.new[0].certificate_pem + private_key = tls_private_key.cert_private_key[0].private_key_pem + lifecycle { + create_before_destroy = true + } +} + +data "aws_iam_server_certificate" "select" { + depends_on = [ + data.aws_route53_zone.select, + aws_route53_zone.new, + aws_route53_record.new, + acme_registration.reg, + tls_private_key.private_key, + tls_private_key.cert_private_key, + tls_cert_request.req, + acme_certificate.new, + + ] + count = local.select + name_prefix = local.content + latest = true +} diff --git a/modules/domain/outputs.tf b/modules/domain/outputs.tf new file mode 100644 index 0000000..4e06cfb --- /dev/null +++ b/modules/domain/outputs.tf @@ -0,0 +1,18 @@ +output "id" { + value = (local.select == 1 ? aws_route53domains_registered_domain.select[0].id : aws_route53_record.new[0].id) +} +output "zone_id" { + value = local.zone_id +} +output "zone" { + value = local.zone_resource +} +output "domain" { + value = (local.select == 1 ? aws_route53domains_registered_domain.select[0] : aws_route53_record.new[0]) +} +output "certificate" { + value = (local.select == 1 ? data.aws_iam_server_certificate.select[0] : aws_iam_server_certificate.new[0]) +} +output "certificate_arn" { + value = (local.select == 1 ? data.aws_iam_server_certificate.select[0].arn : aws_iam_server_certificate.new[0].arn) +} diff --git a/modules/domain/variables.tf b/modules/domain/variables.tf new file mode 100644 index 0000000..d47dc29 --- /dev/null +++ b/modules/domain/variables.tf @@ -0,0 +1,39 @@ +variable "use" { + type = string + description = <<-EOT + Strategy for using domain resources: + 'select' to use existing, + or 'create' to generate new domain resources. + The default is 'create'. + When selecting a domain, the content must be provided and a domain with the matching address must exist. + We will extract the zone from the content, this module does not create zones. + EOT +} + +variable "content" { + type = string + description = <<-EOT + A prevetted unique domain name to use for the project. + WARNING! Domains are unique and must be pre-vetted by the project owner. + WARNING! After a domain is generated the account owner must verify their email address within 10 days or it will be deleted. + EOT +} + +variable "ip" { + type = string + description = <<-EOT + The ip address to attach to the domain. + When selecting a domain we won't generate any domain objects, we won't create a cert. + EOT + default = "" +} + +variable "zone" { + type = string + description = <<-EOT + The domain zone to use for the domain record. + eg. example.com for domain 'test.example.com' + Only specify when creating a new zone. + EOT + default = "" +} diff --git a/modules/domain/versions.tf b/modules/domain/versions.tf new file mode 100644 index 0000000..511d46c --- /dev/null +++ b/modules/domain/versions.tf @@ -0,0 +1,21 @@ +terraform { + required_version = ">= 1.5.0, < 1.6" + required_providers { + local = { + source = "hashicorp/local" + version = ">= 2.4" + } + aws = { + source = "hashicorp/aws" + version = ">= 5.11" + } + tls = { + source = "hashicorp/tls" + version = "4.0.5" + } + acme = { + source = "vancluever/acme" + version = ">= 2.0" + } + } +} diff --git a/modules/network_load_balancer/main.tf b/modules/network_load_balancer/main.tf new file mode 100644 index 0000000..9dcaacb --- /dev/null +++ b/modules/network_load_balancer/main.tf @@ -0,0 +1,44 @@ +locals { + use = var.use + name = var.name + security_group_id = var.security_group_id + subnet_ids = var.subnet_ids + create = (local.use == "create" ? 1 : 0) + select = (local.use == "select" ? 1 : 0) + + public_ip = (local.select == 1 ? data.aws_eip.selected[0].public_ip : aws_eip.created[0].public_ip) +} + +data "aws_lb" "selected" { + count = local.select + tags = { + Name = local.name + } +} + +data "aws_eip" "selected" { + count = local.select + filter { + name = "description" + values = ["ELB net/${data.aws_lb.selected[0].name}/*"] + } +} + + +resource "aws_eip" "created" { + count = local.create + domain = "vpc" +} + +resource "aws_lb" "new" { + count = local.create + name = local.name + internal = false + load_balancer_type = "network" + security_groups = [local.security_group_id] + subnets = local.subnet_ids + + tags = { + Name = local.name + } +} diff --git a/modules/network_load_balancer/outputs.tf b/modules/network_load_balancer/outputs.tf new file mode 100644 index 0000000..f7c329d --- /dev/null +++ b/modules/network_load_balancer/outputs.tf @@ -0,0 +1,12 @@ +output "id" { + value = (local.select == 1 ? data.aws_lb.selected[0].id : aws_lb.new[0].id) +} +output "dns_name" { + value = (local.select == 1 ? data.aws_lb.selected[0].dns_name : aws_lb.new[0].dns_name) +} +output "load_balancer" { + value = (local.select == 1 ? data.aws_lb.selected[0] : aws_lb.new[0]) +} +output "public_ip" { + value = local.public_ip +} diff --git a/modules/network_load_balancer/variables.tf b/modules/network_load_balancer/variables.tf new file mode 100644 index 0000000..39dd329 --- /dev/null +++ b/modules/network_load_balancer/variables.tf @@ -0,0 +1,33 @@ +variable "use" { + type = string + description = <<-EOT + Strategy for using load balancer resources: + 'select' to use existing, + or 'create' to generate new load balancer resources. + The default is 'create'. + When selecting a load balancer, the name must be provided and a load balancer with the "Name" tag must exist. + When selecting a load balancer the security group, subnet, and VPC arguments are ignored. + EOT +} +variable "name" { + type = string + description = <<-EOT + The name of the Load Balancer, there must be a 'Name' tag on it to be found. + When generating a load balancer, this will be added as a tag to the resource. + This tag is how we will find it again in the future. + EOT +} +variable "security_group_id" { + type = string + description = <<-EOT + The security group id to attach to the Load Balancer. + EOT + default = "" +} +variable "subnet_ids" { + type = list(string) + description = <<-EOT + The subnet ids to attach to the Load Balancer. + EOT + default = [] +} diff --git a/modules/network_load_balancer/versions.tf b/modules/network_load_balancer/versions.tf new file mode 100644 index 0000000..24e1e1d --- /dev/null +++ b/modules/network_load_balancer/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.5.0, < 1.6" + required_providers { + local = { + source = "hashicorp/local" + version = ">= 2.4" + } + aws = { + source = "hashicorp/aws" + version = ">= 5.11" + } + } +} \ No newline at end of file diff --git a/modules/security_group/main.tf b/modules/security_group/main.tf index 88ffffe..8578d00 100644 --- a/modules/security_group/main.tf +++ b/modules/security_group/main.tf @@ -1,15 +1,12 @@ locals { - name = var.name - select = (var.type == "" ? 1 : 0) # select if no type given - create = (var.type != "" ? 1 : 0) # create if given a type - type = (local.types[(var.type == "" ? "none" : var.type)]) - owner = var.owner - ip = chomp(var.ip) - cidr = var.cidr - vpc_id = var.vpc_id - vpc_cidr = var.vpc_cidr - skip_runner_ip = var.skip_runner_ip - allow_runner = (local.skip_runner_ip ? false : true) # opposite of skip_runner_ip + name = var.name + use = var.use + select = (local.use == "select" ? 1 : 0) + create = (local.use == "create" ? 1 : 0) + type_name = var.type + type = (local.types[var.type]) + vpc_id = var.vpc_id + vpc_cidr = var.vpc_cidr } data "aws_security_group" "selected" { @@ -22,12 +19,11 @@ data "aws_security_group" "selected" { resource "aws_security_group" "new" { count = local.create - description = "security group generated by aws_access module" + description = "access to ${local.type_name} generated by aws_access module" name = local.name vpc_id = local.vpc_id tags = { - Name = local.name - Owner = local.owner + Name = local.name } lifecycle { ignore_changes = [ @@ -37,59 +33,32 @@ resource "aws_security_group" "new" { } } -# this rule allows ingress on any port from the ip specified -resource "aws_vpc_security_group_ingress_rule" "from_ip" { - count = (local.type.specific_ip_ingress && local.allow_runner ? 1 : 0) - ip_protocol = "-1" - cidr_ipv4 = "${local.ip}/32" - security_group_id = aws_security_group.new[0].id -} -# this rule allows egress on any port to the ip specified -resource "aws_vpc_security_group_egress_rule" "to_ip" { - count = (local.type.specific_ip_egress && local.allow_runner ? 1 : 0) - ip_protocol = "-1" - cidr_ipv4 = "${local.ip}/32" - security_group_id = aws_security_group.new[0].id -} - -# this rule allows any ip in the cidr on any port to initiate connections to the server -resource "aws_vpc_security_group_ingress_rule" "internal_ingress" { - count = (local.type.internal_ingress ? 1 : 0) - ip_protocol = "-1" - cidr_ipv4 = local.cidr - security_group_id = aws_security_group.new[0].id -} -# this rule allows the server to initiate connections to any ip in the cidr on any port -resource "aws_vpc_security_group_egress_rule" "internal_egress" { - count = (local.type.internal_egress ? 1 : 0) +# this allows egress between servers on the VPC +resource "aws_vpc_security_group_egress_rule" "project_egress" { + count = (local.type.project_egress ? 1 : 0) ip_protocol = "-1" - cidr_ipv4 = local.cidr + cidr_ipv4 = local.vpc_cidr security_group_id = aws_security_group.new[0].id } -# this rule allows any ip in the cidr on any port to initiate connections to the server +# this allows ingress between servers on the VPC resource "aws_vpc_security_group_ingress_rule" "project_ingress" { count = (local.type.project_ingress ? 1 : 0) ip_protocol = "-1" cidr_ipv4 = local.vpc_cidr security_group_id = aws_security_group.new[0].id } -# this rule allows the server to initiate connections to any ip in the cidr on any port -resource "aws_vpc_security_group_egress_rule" "project_egress" { - count = (local.type.project_egress ? 1 : 0) - ip_protocol = "-1" - cidr_ipv4 = local.vpc_cidr - security_group_id = aws_security_group.new[0].id -} + # this is necessary if you want to update or install anything from the internet -# allows server to initiate connections to anywhere +# allows servers to initiate connections to the public internet resource "aws_vpc_security_group_egress_rule" "external_egress" { count = (local.type.public_egress ? 1 : 0) ip_protocol = "-1" cidr_ipv4 = "0.0.0.0/0" security_group_id = aws_security_group.new[0].id } -# allows anywhere to initiate connections to server -# WARNING! this exposes your server to the public internet + +# allows the public internet to initiate connections to the server +# WARNING! this exposes your entire project to the public internet resource "aws_vpc_security_group_ingress_rule" "external_ingress" { count = (local.type.public_ingress ? 1 : 0) ip_protocol = "-1" diff --git a/modules/security_group/types.tf b/modules/security_group/types.tf index b27e840..f6b7dbb 100644 --- a/modules/security_group/types.tf +++ b/modules/security_group/types.tf @@ -2,84 +2,34 @@ locals { types = { none = { # this will be selected only if no type is given - # this will not create a security group, it will select one based on the name given - specific_ip_ingress = false - specific_ip_egress = false - internal_ingress = false - internal_egress = false - project_ingress = false - project_egress = false - public_ingress = false - public_egress = false - } - specific = { - # allow all ingress and egress, but only from specified ip - # this will require users to figure out how to update and install packages without public internet access - # the server will only be able to egress to specified ip - # specified ip can be outside the vpc - specific_ip_ingress = true - specific_ip_egress = true - internal_ingress = false - internal_egress = false - project_ingress = false - project_egress = false - public_ingress = false - public_egress = false - } - internal = { - # allow all ingress and egress, but only from specified ip and cidr - # this will require users to figure out how to update and install packages without public internet access - # the server will only be able to egress to specified ip or cidr - # specified ip can be outside the vpc, the cidr must be inside the vpc - specific_ip_ingress = true - specific_ip_egress = true - internal_ingress = true - internal_egress = true - project_ingress = false - project_egress = false - public_ingress = false - public_egress = false + # basically no rules will be created + project_egress = false + project_ingress = false + public_egress = false + public_ingress = false } project = { - # allow all ingress and egress, but only from specified ip, cidr, and VPC cidr - # this will require users to figure out how to update and install packages without public internet access - # the server will only be able to egress to specified ip, or any server on a subnet within the VPC internal CIDR - # specified ip can be outside the vpc, the cidr must be inside the vpc, and the vpc cidr must match the vpc - specific_ip_ingress = true - specific_ip_egress = true - internal_ingress = true - internal_egress = true - project_ingress = true - project_egress = true - public_ingress = false - public_egress = false + # this allows communication between servers in the project across regions + project_egress = true + project_ingress = true + public_egress = false + public_ingress = false } egress = { - # allow all ingress and egress, but only from specified ip and vpc cidr - # allow egress to public internet, this enables updates and package installs - # the server will be able to initiate connections to anywhere - # only specified ip and vpc cidr can initiate connections to the server - # specified ip can be outside the vpc, the cidr must be inside the vpc, and the vpc cidr must match the vpc - specific_ip_ingress = true - specific_ip_egress = true - internal_ingress = true - internal_egress = true - project_ingress = true - project_egress = true - public_ingress = false - public_egress = true + # this allows communication between servers in the project and the public internet + # you might want to use this for servers that need to download packages + project_egress = true + project_ingress = true + public_egress = true + public_ingress = false } public = { # allow all ingress and egress, from anywhere # this is the least secure option - specific_ip_ingress = true - specific_ip_egress = true - internal_ingress = true - internal_egress = true - project_ingress = true - project_egress = true - public_ingress = true - public_egress = true + project_egress = true + project_ingress = true + public_egress = true + public_ingress = true } } } \ No newline at end of file diff --git a/modules/security_group/variables.tf b/modules/security_group/variables.tf index 93ee960..45d6485 100644 --- a/modules/security_group/variables.tf +++ b/modules/security_group/variables.tf @@ -1,51 +1,31 @@ - -variable "name" { - type = string - description = <<-EOT - The name of the security group to find or create. - Required. - EOT -} - -# only used when generating a security group -variable "type" { - type = string - description = <<-EOT - The designation from the types.tf of opinionated options to use. - If this value is specified the assumption is that the security group is being generated. - If this value isn't specified the assumption is that the security group is being found. - EOT - default = "" -} -variable "owner" { +variable "use" { type = string description = <<-EOT - The owner to tag the security group with if generated. - Not necessary if the security group is being found. + Strategy for using security group resources: + 'select' to use existing, + or 'create' to generate new security group resources. + When selecting a security group, the name must be provided and a security group with the matching tag Name must exist. EOT - default = "" } -variable "ip" { +variable "name" { type = string description = <<-EOT - The public IP addess to allow ingress from external WAN to the servers in the security group. - Not necessary if the security group is being found. + The name of the security group to find or create. EOT - default = "" } -variable "cidr" { +variable "type" { type = string description = <<-EOT - The cidr of the internal subnet to allow servers access to when generating the security group. - Not necessary if the security group is being found. + The designation from the types.tf of opinionated options to use. + Not necessary if the security group is being selected. EOT - default = "" + default = "none" } variable "vpc_id" { type = string description = <<-EOT The id of the vpc to use when generating the security group. - Not necessary if the security group is being found. + Not necessary if the security group is being selected. EOT default = "" } @@ -53,11 +33,7 @@ variable "vpc_cidr" { type = string description = <<-EOT The CIDR of the VPC, used to allow ingress from the VPC to the servers in the security group. - Not necessary if the security group is being found. + Not necessary if the security group is being selected. EOT default = "" } -variable "skip_runner_ip" { - type = bool - description = "Skip generating ingress security group for the runner's ip" -} \ No newline at end of file diff --git a/modules/security_group/versions.tf b/modules/security_group/versions.tf index 7c63b5d..24e1e1d 100644 --- a/modules/security_group/versions.tf +++ b/modules/security_group/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/modules/ssh_key/main.tf b/modules/ssh_key/main.tf deleted file mode 100644 index 225d6f4..0000000 --- a/modules/ssh_key/main.tf +++ /dev/null @@ -1,26 +0,0 @@ -locals { - select = (var.public_key == "" ? 1 : 0) - create = (var.public_key != "" ? 1 : 0) - name = var.name - public_key = var.public_key - owner = var.owner -} - -data "aws_key_pair" "selected" { - count = local.select - filter { - name = "tag:Name" - values = [local.name] - } - include_public_key = true -} - -resource "aws_key_pair" "new" { - count = local.create - key_name = local.name - public_key = local.public_key - tags = { - Name = local.name - Owner = local.owner - } -} diff --git a/modules/ssh_key/outputs.tf b/modules/ssh_key/outputs.tf deleted file mode 100644 index d393974..0000000 --- a/modules/ssh_key/outputs.tf +++ /dev/null @@ -1,6 +0,0 @@ -output "id" { - value = (local.select == 1 ? data.aws_key_pair.selected[0].id : aws_key_pair.new[0].id) -} -output "ssh_key" { - value = (local.select == 1 ? data.aws_key_pair.selected[0] : aws_key_pair.new[0]) -} \ No newline at end of file diff --git a/modules/ssh_key/variables.tf b/modules/ssh_key/variables.tf deleted file mode 100644 index e93a064..0000000 --- a/modules/ssh_key/variables.tf +++ /dev/null @@ -1,20 +0,0 @@ -# all descriptions should use heredoc blocks -variable "name" { - type = string - description = <<-EOT - The name of the ssh key to find or create. - EOT -} -variable "public_key" { - type = string - description = <<-EOT - The contents of the public key to create. - If this is specified, then a public key object will be created. - EOT - default = "" -} -variable "owner" { - type = string - description = "The owner to tag the public key with after creation." - default = "terraform" -} diff --git a/modules/ssh_key/versions.tf b/modules/ssh_key/versions.tf deleted file mode 100644 index 7c63b5d..0000000 --- a/modules/ssh_key/versions.tf +++ /dev/null @@ -1,17 +0,0 @@ -terraform { - required_version = ">= 1.5.0, < 1.6" - required_providers { - local = { - source = "hashicorp/local" - version = ">= 2.4" - } - aws = { - source = "hashicorp/aws" - version = ">= 5.11" - } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } - } -} \ No newline at end of file diff --git a/modules/subnet/main.tf b/modules/subnet/main.tf index b80f959..52a1d29 100644 --- a/modules/subnet/main.tf +++ b/modules/subnet/main.tf @@ -1,12 +1,12 @@ locals { - select = (var.cidr == "" ? 1 : 0) - create = (var.cidr != "" ? 1 : 0) + use = var.use + select = (local.use == "select" ? 1 : 0) + create = (local.use == "create" ? 1 : 0) name = var.name - cidr = var.cidr vpc_id = var.vpc_id - owner = var.owner + cidr = var.cidr availability_zone = var.availability_zone - public_ip = var.public_ip + public = var.public } data "aws_subnet" "selected" { @@ -16,14 +16,14 @@ data "aws_subnet" "selected" { values = [local.name] } } + resource "aws_subnet" "new" { count = local.create vpc_id = local.vpc_id cidr_block = local.cidr availability_zone = local.availability_zone - map_public_ip_on_launch = local.public_ip + map_public_ip_on_launch = local.public tags = { - Name = local.name - Owner = local.owner + Name = local.name } } diff --git a/modules/subnet/outputs.tf b/modules/subnet/outputs.tf index e72108e..f031e8a 100644 --- a/modules/subnet/outputs.tf +++ b/modules/subnet/outputs.tf @@ -6,4 +6,7 @@ output "cidr" { } output "subnet" { value = (local.select == 1 ? data.aws_subnet.selected[0] : aws_subnet.new[0]) +} +output "availability_zone" { + value = (local.select == 1 ? data.aws_subnet.selected[0].availability_zone : aws_subnet.new[0].availability_zone) } \ No newline at end of file diff --git a/modules/subnet/variables.tf b/modules/subnet/variables.tf index 6b58f37..0a76f45 100644 --- a/modules/subnet/variables.tf +++ b/modules/subnet/variables.tf @@ -1,32 +1,29 @@ - -variable "name" { +variable "use" { type = string description = <<-EOT - The name of the subnet to find or create. + Strategy for using subnet resources: + 'select' to use existing, + or 'create' to generate new subnet resources. + When selecting a subnet, the name must be provided and a subnet with the matching tag Name must exist. EOT } -variable "cidr" { +variable "vpc_id" { type = string description = <<-EOT - The cidr for the subnet to create. - If this is specified a subnet will be created. - If this isn't specified, then the module will attempt to find a subnet with the given name. + The AWS unique id for the VPC which this subnet will be created in. EOT - default = "" } -variable "vpc_id" { +variable "name" { type = string description = <<-EOT - The AWS unique id for the VPC which this subnet will be created in. + The name of the subnet to find or create. EOT - default = "" } -variable "owner" { +variable "cidr" { type = string description = <<-EOT - The owner to tag on the subnet when creating it. + The cidr for the subnet to create. EOT - default = "" } variable "availability_zone" { type = string @@ -35,12 +32,12 @@ variable "availability_zone" { This is the name of the availability zone, not the AWS unique id. For example "us-east-1a" or "us-east-1b" not "use1-az1" or "use1-az2". EOT - default = "" } -variable "public_ip" { +variable "public" { type = bool description = <<-EOT Set this to true to enable the subnet to have public IP addresses. + When this is false you will need to use EIP to assign public IP addresses to servers. + WARNING! there is a 5 IP address limit for EIPs per region. EOT - default = false } \ No newline at end of file diff --git a/modules/subnet/versions.tf b/modules/subnet/versions.tf index 7c63b5d..24e1e1d 100644 --- a/modules/subnet/versions.tf +++ b/modules/subnet/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/modules/vpc/main.tf b/modules/vpc/main.tf index 20cfb65..30c1409 100644 --- a/modules/vpc/main.tf +++ b/modules/vpc/main.tf @@ -1,8 +1,9 @@ locals { name = var.name cidr = var.cidr - select = (var.cidr == "" ? 1 : 0) - create = (var.cidr != "" ? 1 : 0) + use = var.use + select = (local.use == "select" ? 1 : 0) + create = (local.use == "create" ? 1 : 0) } data "aws_vpc" "selected" { diff --git a/modules/vpc/variables.tf b/modules/vpc/variables.tf index d15c9d4..35dc258 100644 --- a/modules/vpc/variables.tf +++ b/modules/vpc/variables.tf @@ -1,15 +1,23 @@ - +variable "use" { + type = string + description = <<-EOT + Strategy for using VPC resources: + 'select' to use existing, + or 'create' to generate new VPC resources + When selecting a VPC, the name must be provided and a VPC with the matching name must exist. + EOT +} variable "name" { type = string description = <<-EOT The name of the VPC, there must be a 'Name' tag on it to be found. + When generating a VPC, this will be added as a tag to the resource. EOT } variable "cidr" { type = string description = <<-EOT The cidr for the VPC to create. - If this is specified a VPC will be created. - If this isn't specified, then the module will attempt to find a VPC with the given name. + This is not required when selecting an existing VPC. EOT } diff --git a/modules/vpc/versions.tf b/modules/vpc/versions.tf index 7c63b5d..24e1e1d 100644 --- a/modules/vpc/versions.tf +++ b/modules/vpc/versions.tf @@ -9,9 +9,5 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" - } } } \ No newline at end of file diff --git a/notes/README.md b/notes/README.md new file mode 100644 index 0000000..f982d96 --- /dev/null +++ b/notes/README.md @@ -0,0 +1,17 @@ +# Dev Notes + +## What is this? + +- A directory to dump my thoughts on design decisions and hopefully give insight to users and future devs on why things exist. + +## This is unconventional and hard to read + +- I agree, but if I don't put it somewhere it might not go anywhere + - my assumption is that more information is better +- this doesn't replace conventional documentation + - it provides a weird mind map of context for interested individuals who would like to understand the ideas and concerns + +## How does this help future developers or maintainers? + +- This is the best way to get the context behind paradigms, assumptions, use cases, and decisions +- With this on the table future developers can question the ideas and paradigms and develop better answers or solutions diff --git a/notes/domain.md b/notes/domain.md new file mode 100644 index 0000000..eb0a4e5 --- /dev/null +++ b/notes/domain.md @@ -0,0 +1,87 @@ +# Domain + +## multi-phase deployments and select when not creating paradigm +- we need to have the ability to select a domain so that we can verify its existence in multi phase deployments + - this means assume that this module has already run, but in a different phase and nothing is in this phase's state +- select options allow us to decide to deploy some resources at earlier phases, while still keeping them in state +- select when not create gives us unified output for modules and the ability to make assumptions about available data and outputs +- select is therefore a special use case, so we should ask the user if that is what they want + +## skip deployments options +- while being able to select reources that were deployed in an earlier phase it is also good to be able to choose to deploy resources later +- skip options work like feature flags +- skip options enable unit testing modules +- skip options allow us to decide to deploy some resources in later phases + - select options allow us to decide to deploy some resources at earlier phases +- skip is a special use case, so we should ask the user if that is what they want + +## how do we know to look for a domain vs creating a domain? +- if creating a zone, we need to create a domain +- can we select a zone, but create a domain? + - no, we want it coupled and encapsulated + - in this situation the user has a zone that maybe serves multiple projects, they want a domain in the zone for this project + - we could suggest that the user create a zone for the project, but it doesn't need to be a top level zone + - the user can have a top level zone "example.com" and a project level zone "project.example.com", + then a project level domain "entry.project.example.com" + - the user can then have a cname for the project level zone pointing to the project level domain "project.example.com -> entry.project.example.com" + - this couples the zone to the domain, and it also gives us a generic domain name to use + - from the user's perspective they have a domain "project.example.com" that works for their project + - while we are actually generating a zone, domain, and cname, the user doesn't need to control this + - selecting a domain is a special use case, so maybe we ask the user if that is what they want +- I think this is addressed with the specialized use case section below + +## specialized use cases +- it seems I have identified a generalized use case for internal modules or features +- maybe every feature should have some argument to either skip or select + - this makes sense since these are not mutually exclusive + - if you want to select a module you don't want to skip it +- this makes the default behavior for any feature to be creation + - this has implecations on new feature additions + - maybe we have an 'experimental' phase where new features are skipped by default + - I think this is too complicated + - then after a period of time we flip the skip default? + - this breaks the interface, something we really don't want to do + - breaking changes should result in major version numbers + - what if we make the default behavior to be skipping a feature + - then users will need to enable each feature individually + - I don't like this because it makes the user interface huge and overwhelming + - I also don't like this because the initial set of features are enabled by default + - then we have 'core' and 'additional' features, which is too complicated + - I think the best compromise is to expect users to pin the major version in ther versions file if they don't want breaking changes + - this is suggested by Hashicorp, so it should be standard practice + - the default posture for features can be create, with the option to skip + - skipping the feature allows you to easily update to the latest major without implecations, but there is some change necessary +- what should we name the argument? + - options: 'skip', 'select', or 'create' defaulting to 'create' + - skip is like 'no', create is like 'yes', select is like 'kinda' + - use_domain, domain_use, domain_usage_level, domain_usage + - https://www.phind.com/search?cache=pxw1uu72n85u69ti4p4v8hmg + - domain_use_strategy per that conversation ^^ + - this will also extrapolate to blah_use_strategy for any feature + - I will need to backfill this argument, but that will have no effect on the current usage since create is the default + - in cases where I already have something I will need to alter the interface which is a breaking change, so there will need to be notes on this + +## TLD +- I don't like the idea of a demo user having to go buy a domain manually for this to work, I would rather generate one for them +- I can't seem to get a valid cert when creating a TLD on our account, so until that works we are stuck with it + +## Kubernetes +- I have the domain and load balancer working +- I decided to generate a cert for the domain +- In order to assign the cert to the load balancer I need to choose a port to listen on +- This gets application specific... which makes me feel like I have made this module too specific to stand on its own +- Kubernetes should be a specialized use of this module not the only way to use it. +- Kubernetes expects a load balancer and domain and cert listening on port 6443 +- maybe we listen on port 443? but then the kubernetes API will need a new listener in the server mod? +- maybe the root module should include any application specific stuff, like a listener on port 6443 +- isn't 443 application specific then? does a kubernetes cluster need 443 exposed? + +## How do I use this thing? +- Getting started with AWS is a hassle, you have to generate a vpc, subnets, an internet gateway, security groups, etc. +- I was thinking, y'know, before I can even start working on a project I have to figure out a lot and do a lot. +- I really wish there was an easy way to get this stuff that didn't lock me into AWS forever +- I don't want to be stuck at AWS in the end. I would like to be able to move my project somewhere else eventually if I need to. +- This module should be the starting point for a project, even if that project has nothing to do with kubernetes +- this lays the groundwork for any project on aws +- it doesn't use any super in-depth or complicated AWS things, just the basics, and with a few tweaks I could use it on any cloud + diff --git a/notes/security_groups.md b/notes/security_groups.md new file mode 100644 index 0000000..3beea86 --- /dev/null +++ b/notes/security_groups.md @@ -0,0 +1,44 @@ +# Security groups + +## specific ip + +- this module was made with the idea that it would be used in creating an rke2 node + - I think the module was too specific in a few places + - I don't think the module should include access for CI or any one entity to the project + - this infers the ssh key and specific ips added to security groups should de removed +- how do we install or manage things when we create them? +- any object further down the line will be able to add a new security group to give access to that object + - we expect to give a project level entry point in the loadbalancer with an eip + - the security group types should reflect how we want to expose the load balancer +- how does a user access their infrastructure? + - any user should be able to add themselves to a project without affecting any of the other parts of the project + - an entity from outside the project could be some client or a user + - a user might need an ssh key, adding them to a server that already exists would require something logging into the server and adding them + - the "something" could be a CI runner, or it could be something like Vault + - that something will need to be able to clean things up if the access changes + - any client will need their IP added to the project (unless it is open to the internet) + +## add client module + +- I think we have identified a new module to help manage a project +- the project_client module would have its own lifecycle, right? +- each client would have its own lifecycle +- maybe it doesn't need its own module? +- would a map of clients in this module work better? + +- lets take a concrete example + - adding the CI to a project is an important client because the CI will provision the basic needs of the servers + - CI keys and IPs are ephemeral, so they need to be created and removed before and after contact + - the CI will need: + - its ip added to the load balancer to access services + - a security group with its ip + - allowing ingress to port 22 for ssh access + - allowing ingress to port 6443 for kubernetes access + - allowing ingress to port 443 for rancher access + - this security group will need to be added to servers + - the idea is not to add the security group to the load balancer + - this is because once it has access to the servers it can access kubernetes directly from one of the control plane nodes + - its ssh key added to servers to enable ssh access + - the server access level of this indicates the need for separation of concerns + - we don't want this module to manage servers because the expectation is that they don't exist yet when this is run + - keeping this lenear progression helps users fit things in their head diff --git a/outputs.tf b/outputs.tf index 2e66d0a..fc61e5b 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,5 +1,5 @@ output "vpc" { - value = (can(module.vpc[0].vpc) ? { + value = (length(module.vpc) > 0 ? { id = module.vpc[0].vpc.id arn = module.vpc[0].vpc.arn cidr_block = module.vpc[0].vpc.cidr_block @@ -19,26 +19,28 @@ output "vpc" { EOT } -output "subnet" { - value = (can(module.subnet[0].subnet) ? { - id = module.subnet[0].subnet.id - arn = module.subnet[0].subnet.arn - availability_zone = module.subnet[0].subnet.availability_zone - availability_zone_id = module.subnet[0].subnet.availability_zone_id - cidr_block = module.subnet[0].subnet.cidr_block - ipv6_cidr_block = module.subnet[0].subnet.ipv6_cidr_block - vpc_id = module.subnet[0].subnet.vpc_id - tags = module.subnet[0].subnet.tags - } : { - # no object found, but output types are normal - id = "" - arn = "" - availability_zone = "" - availability_zone_id = "" - cidr_block = "" - ipv6_cidr_block = "" - vpc_id = "" - tags = tomap({ "" = "" }) +output "subnets" { + value = (length(module.subnet) > 0 ? { + for subnet in module.subnet : subnet.subnet.tags.Name => { + id = subnet.subnet.id + arn = subnet.subnet.arn + availability_zone = subnet.subnet.availability_zone + availability_zone_id = subnet.subnet.availability_zone_id + cidr_block = subnet.subnet.cidr_block + ipv6_cidr_block = subnet.subnet.ipv6_cidr_block + vpc_id = subnet.subnet.vpc_id + tags_all = subnet.subnet.tags_all + } + } : { "empty" = { + id = "" + arn = "" + availability_zone = "" + availability_zone_id = "" + cidr_block = "" + ipv6_cidr_block = "" + vpc_id = "" + tags_all = tomap({ "" = "" }) + } }) description = <<-EOT The subnet object from AWS. @@ -46,46 +48,87 @@ output "subnet" { } output "security_group" { - value = (can(module.security_group[0].security_group) ? { - id = module.security_group[0].security_group.id - arn = module.security_group[0].security_group.arn - name = module.security_group[0].security_group.name - vpc_id = module.security_group[0].security_group.vpc_id - tags = module.security_group[0].security_group.tags + value = (length(module.security_group) > 0 ? { + id = module.security_group[0].security_group.id + arn = module.security_group[0].security_group.arn + name = module.security_group[0].security_group.name + vpc_id = module.security_group[0].security_group.vpc_id + tags_all = module.security_group[0].security_group.tags_all } : { # no object found, but output types are normal - id = "" - arn = "" - name = "" - vpc_id = "" - tags = tomap({ "" = "" }) + id = "" + arn = "" + name = "" + vpc_id = "" + tags_all = tomap({ "" = "" }) }) description = <<-EOT The security group object from AWS. EOT } +output "load_balancer" { + value = (length(module.network_load_balancer) > 0 ? { + id = module.network_load_balancer[0].load_balancer.id + arn = module.network_load_balancer[0].load_balancer.arn + dns_name = module.network_load_balancer[0].load_balancer.dns_name + zone_id = module.network_load_balancer[0].load_balancer.zone_id + security_groups = module.network_load_balancer[0].load_balancer.security_groups + subnets = module.network_load_balancer[0].load_balancer.subnets + tags_all = module.network_load_balancer[0].load_balancer.tags_all + } : { + # no object found, but output types are normal + id = "" + arn = "" + dns_name = "" + zone_id = "" + security_groups = [] + subnets = [] + tags_all = tomap({ "" = "" }) + }) + description = <<-EOT + The load balancer object from AWS. + EOT +} + +output "domain" { + value = (length(module.domain) > 0 ? { + id = module.domain[0].domain.id + name = module.domain[0].domain.name + zone_id = module.domain[0].domain.zone_id + type = module.domain[0].domain.type + records = module.domain[0].domain.records + } : { + # no object found, but output types are normal + id = "" + name = "" + zone_id = "" + type = "" + records = [] + }) + description = <<-EOT + The domain object from AWS. + EOT +} -output "ssh_key" { - value = (can(module.ssh_key[0].ssh_key) ? { - id = module.ssh_key[0].ssh_key.id - arn = module.ssh_key[0].ssh_key.arn - key_name = module.ssh_key[0].ssh_key.key_name - key_pair_id = module.ssh_key[0].ssh_key.key_pair_id - key_type = module.ssh_key[0].ssh_key.key_type - public_key = module.ssh_key[0].ssh_key.public_key - tags = module.ssh_key[0].ssh_key.tags +output "certificate" { + value = (length(module.domain) > 0 ? { + id = module.domain[0].certificate.id + arn = module.domain[0].certificate.arn + name = module.domain[0].certificate.name + expiration = module.domain[0].certificate.expiration + upload_date = module.domain[0].certificate.upload_date + tags_all = module.domain[0].certificate.tags_all } : { # no object found, but output types are normal id = "" arn = "" - key_name = "" - key_pair_id = "" - key_type = "" - public_key = "" - tags = tomap({ "" = "" }) + name = "" + expiration = "" + upload_date = "" + tags_all = tomap({ "" = "" }) }) description = <<-EOT - The SSH key object from AWS. + The certificate object from AWS. EOT } diff --git a/tests/basic_test.go b/tests/basic_test.go index 7e25aee..b72ec71 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -1,18 +1,17 @@ package test import ( - "fmt" "os" "testing" "github.com/gruntwork-io/terratest/modules/random" - "github.com/gruntwork-io/terratest/modules/ssh" "github.com/gruntwork-io/terratest/modules/terraform" ) // this test generates all objects, no overrides func TestBasic(t *testing.T) { t.Parallel() + domain := os.Getenv("DOMAIN") uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() @@ -20,12 +19,9 @@ func TestBasic(t *testing.T) { directory := "basic" region := "us-west-1" - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("terraform-aws-access-test-%s-%s", directory, uniqueID) terraformVars := map[string]interface{}{ "identifier": uniqueID, - "key_name": keyPairName, - "key": keyPair.PublicKey, + "domain": domain, } terraformOptions := setup(t, directory, region, terraformVars) defer teardown(t, directory) diff --git a/tests/project_test.go b/tests/domain_test.go similarity index 56% rename from tests/project_test.go rename to tests/domain_test.go index ba5c81d..1b86086 100644 --- a/tests/project_test.go +++ b/tests/domain_test.go @@ -8,26 +8,23 @@ import ( "github.com/gruntwork-io/terratest/modules/terraform" ) -// this test adds security group and subnet, but overrides vpc and ssh key -// this is a typical experience when you want to start a new project in a new subnet -// and the vpc is outside of your control -func TestProject(t *testing.T) { +// this test generates all objects, no overrides +func TestDomain(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "project" + domain := os.Getenv("DOMAIN") + directory := "domain" region := "us-west-1" - owner := "terraform-ci@suse.com" - keyPair := setupKeyPair(t, directory, region, owner, uniqueID) - defer teardownKeyPair(t, keyPair) terraformVars := map[string]interface{}{ "identifier": uniqueID, - "key_name": keyPair.Name, + "domain": domain, } terraformOptions := setup(t, directory, region, terraformVars) + defer teardown(t, directory) defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) diff --git a/tests/sgip_test.go b/tests/loadbalancer_test.go similarity index 81% rename from tests/sgip_test.go rename to tests/loadbalancer_test.go index 67f1f16..f3bd4da 100644 --- a/tests/sgip_test.go +++ b/tests/loadbalancer_test.go @@ -8,14 +8,13 @@ import ( "github.com/gruntwork-io/terratest/modules/terraform" ) -// generate a security group without generating a subnet, specifying an ip -func TestSgip(t *testing.T) { +func TestLoadbalancer(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "sgip" + directory := "loadbalancer" region := "us-west-1" terraformVars := map[string]interface{}{ diff --git a/tests/override_test.go b/tests/override_test.go deleted file mode 100644 index 65659b1..0000000 --- a/tests/override_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package test - -import ( - "os" - "testing" - - "github.com/gruntwork-io/terratest/modules/random" - "github.com/gruntwork-io/terratest/modules/terraform" -) - -// this test generates no objects, but validates that they exist -// this is a typical experience when you already have all the things you need to access servers in a project -// but you want to make sure the access objects exist before building anything else -func TestOverride(t *testing.T) { - t.Parallel() - uniqueID := os.Getenv("IDENTIFIER") - if uniqueID == "" { - uniqueID = random.UniqueId() - } - directory := "override" - region := "us-west-1" - owner := "terraform-ci@suse.com" - - defer teardown(t, directory) - - keyPair := setupKeyPair(t, directory, region, owner, uniqueID) - defer teardownKeyPair(t, keyPair) - - securityGroupId, securityGroupName := setupSecurityGroup(t, directory, region, owner) - defer teardownSecurityGroup(t, region, securityGroupId) - - terraformVars := map[string]interface{}{ - "identifier": uniqueID, - "key_name": keyPair.Name, - "security_group_name": securityGroupName, - } - terraformOptions := setup(t, directory, region, terraformVars) - - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) -} diff --git a/tests/personal_test.go b/tests/securitygroup_test.go similarity index 50% rename from tests/personal_test.go rename to tests/securitygroup_test.go index c2035ec..149f8ec 100644 --- a/tests/personal_test.go +++ b/tests/securitygroup_test.go @@ -1,33 +1,25 @@ package test import ( - "fmt" "os" "testing" "github.com/gruntwork-io/terratest/modules/random" - "github.com/gruntwork-io/terratest/modules/ssh" "github.com/gruntwork-io/terratest/modules/terraform" ) -// this test adds security group and ssh key, but overrides vpc and subnet -// this is a typical experience when vpc and subnet is managed by a different group -// thus generating only unshared or "personal" objects -func TestPersonal(t *testing.T) { +// this test generates all objects, no overrides +func TestSecuritygroup(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "personal" + directory := "securitygroup" region := "us-west-1" - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("terraform-aws-access-test-%s-%s", directory, uniqueID) terraformVars := map[string]interface{}{ "identifier": uniqueID, - "key_name": keyPairName, - "key": keyPair.PublicKey, } terraformOptions := setup(t, directory, region, terraformVars) defer teardown(t, directory) diff --git a/tests/selectvpc_test.go b/tests/selectvpc_test.go new file mode 100644 index 0000000..492928f --- /dev/null +++ b/tests/selectvpc_test.go @@ -0,0 +1,30 @@ +package test + +import ( + "os" + "testing" + + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +// this test generates all objects, no overrides +func TestSelectvpc(t *testing.T) { + t.Parallel() + domain := os.Getenv("DOMAIN") + uniqueID := os.Getenv("IDENTIFIER") + if uniqueID == "" { + uniqueID = random.UniqueId() + } + directory := "selectvpc" + region := "us-west-1" + + terraformVars := map[string]interface{}{ + "identifier": uniqueID, + "domain": domain, + } + terraformOptions := setup(t, directory, region, terraformVars) + defer teardown(t, directory) + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) +} diff --git a/tests/skip_test.go b/tests/skip_test.go deleted file mode 100644 index 88c69bb..0000000 --- a/tests/skip_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package test - -import ( - "fmt" - "os" - "testing" - - "github.com/gruntwork-io/terratest/modules/random" - "github.com/gruntwork-io/terratest/modules/ssh" - "github.com/gruntwork-io/terratest/modules/terraform" -) - -func TestSkipVpc(t *testing.T) { - t.Parallel() - uniqueID := os.Getenv("IDENTIFIER") - if uniqueID == "" { - uniqueID = random.UniqueId() - } - directory := "skipvpc" - region := "us-west-1" - - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("tf-%s-%s", directory, uniqueID) - terraformVars := map[string]interface{}{ - "identifier": uniqueID, - "key_name": keyPairName, - "key": keyPair.PublicKey, - } - terraformOptions := setup(t, directory, region, terraformVars) - defer teardown(t, directory) - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) -} - -func TestSkipSubnet(t *testing.T) { - t.Parallel() - uniqueID := os.Getenv("IDENTIFIER") - if uniqueID == "" { - uniqueID = random.UniqueId() - } - directory := "skipsubnet" - region := "us-west-1" - - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("tf-%s-%s", directory, uniqueID) - terraformVars := map[string]interface{}{ - "identifier": uniqueID, - "key_name": keyPairName, - "key": keyPair.PublicKey, - } - terraformOptions := setup(t, directory, region, terraformVars) - defer teardown(t, directory) - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) -} -func TestSkipSecurityGroup(t *testing.T) { - t.Parallel() - uniqueID := os.Getenv("IDENTIFIER") - if uniqueID == "" { - uniqueID = random.UniqueId() - } - directory := "skipsecuritygroup" - region := "us-west-1" - - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("tf-%s-%s", directory, uniqueID) - terraformVars := map[string]interface{}{ - "identifier": uniqueID, - "key_name": keyPairName, - "key": keyPair.PublicKey, - } - terraformOptions := setup(t, directory, region, terraformVars) - defer teardown(t, directory) - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) -} -func TestSkipSsh(t *testing.T) { - t.Parallel() - uniqueID := os.Getenv("IDENTIFIER") - if uniqueID == "" { - uniqueID = random.UniqueId() - } - directory := "skipssh" - region := "us-west-1" - - terraformVars := map[string]interface{}{ - "identifier": uniqueID, - } - terraformOptions := setup(t, directory, region, terraformVars) - defer teardown(t, directory) - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) -} -func TestSkipIp(t *testing.T) { - t.Parallel() - uniqueID := os.Getenv("IDENTIFIER") - if uniqueID == "" { - uniqueID = random.UniqueId() - } - directory := "skipip" - region := "us-west-1" - - terraformVars := map[string]interface{}{ - "identifier": uniqueID, - } - terraformOptions := setup(t, directory, region, terraformVars) - defer teardown(t, directory) - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) -} diff --git a/tests/skipvpc_test.go b/tests/skipvpc_test.go new file mode 100644 index 0000000..3d953f9 --- /dev/null +++ b/tests/skipvpc_test.go @@ -0,0 +1,28 @@ +package test + +import ( + "os" + "testing" + + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +// this test generates all objects, no overrides +func TestSkipvpc(t *testing.T) { + t.Parallel() + uniqueID := os.Getenv("IDENTIFIER") + if uniqueID == "" { + uniqueID = random.UniqueId() + } + directory := "skipvpc" + region := "us-west-1" + + terraformVars := map[string]interface{}{ + "identifier": uniqueID, + } + terraformOptions := setup(t, directory, region, terraformVars) + defer teardown(t, directory) + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) +} diff --git a/tests/specifyip_test.go b/tests/specifyip_test.go deleted file mode 100644 index 972d572..0000000 --- a/tests/specifyip_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package test - -import ( - "fmt" - "log" - "net" - "os" - "testing" - - "github.com/gruntwork-io/terratest/modules/random" - "github.com/gruntwork-io/terratest/modules/ssh" - "github.com/gruntwork-io/terratest/modules/terraform" -) - -func TestIp(t *testing.T) { - t.Parallel() - uniqueID := os.Getenv("IDENTIFIER") - if uniqueID == "" { - uniqueID = random.UniqueId() - } - directory := "specifyip" - region := "us-west-1" - ip := GetOutboundIP().String() - - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("terraform-aws-access-%s-%s", directory, uniqueID) - terraformVars := map[string]interface{}{ - "identifier": uniqueID, - "key_name": keyPairName, - "key": keyPair.PublicKey, - "ip": ip, - } - terraformOptions := setup(t, directory, region, terraformVars) - defer teardown(t, directory) - defer terraform.Destroy(t, terraformOptions) - terraform.InitAndApply(t, terraformOptions) -} - -// Get preferred outbound ip of this machine -func GetOutboundIP() net.IP { - conn, err := net.Dial("udp", "8.8.8.8:80") - if err != nil { - log.Fatal(err) - } - defer conn.Close() - - localAddr := conn.LocalAddr().(*net.UDPAddr) - - return localAddr.IP -} diff --git a/tests/subnets_test.go b/tests/subnets_test.go new file mode 100644 index 0000000..e98bf23 --- /dev/null +++ b/tests/subnets_test.go @@ -0,0 +1,27 @@ +package test + +import ( + "os" + "testing" + + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +func TestSubnet(t *testing.T) { + t.Parallel() + uniqueID := os.Getenv("IDENTIFIER") + if uniqueID == "" { + uniqueID = random.UniqueId() + } + directory := "subnets" + region := "us-west-2" // This regoin has at least 3 availability zones + + terraformVars := map[string]interface{}{ + "identifier": uniqueID, + } + terraformOptions := setup(t, directory, region, terraformVars) + defer teardown(t, directory) + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) +} diff --git a/tests/util_test.go b/tests/util_test.go index 5d40b7d..9f658f4 100644 --- a/tests/util_test.go +++ b/tests/util_test.go @@ -43,6 +43,7 @@ func setup(t *testing.T, directory string, region string, terraformVars map[stri // Environment variables to set when running Terraform EnvVars: map[string]string{ "AWS_DEFAULT_REGION": region, + "AWS_REGION": region, }, RetryableTerraformErrors: retryableTerraformErrors, }) diff --git a/tests/vpc_test.go b/tests/vpc_test.go new file mode 100644 index 0000000..e501dc4 --- /dev/null +++ b/tests/vpc_test.go @@ -0,0 +1,28 @@ +package test + +import ( + "os" + "testing" + + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +// this test generates all objects, no overrides +func TestVpc(t *testing.T) { + t.Parallel() + uniqueID := os.Getenv("IDENTIFIER") + if uniqueID == "" { + uniqueID = random.UniqueId() + } + directory := "vpc" + region := "us-west-1" + + terraformVars := map[string]interface{}{ + "identifier": uniqueID, + } + terraformOptions := setup(t, directory, region, terraformVars) + defer teardown(t, directory) + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) +} diff --git a/variables.tf b/variables.tf index 77824a1..47797e5 100644 --- a/variables.tf +++ b/variables.tf @@ -1,29 +1,32 @@ -variable "owner" { +# vpc +variable "vpc_use_strategy" { type = string description = <<-EOT - The name of the owner to tag resources with, usually your email address. - Using your email address for this value allows teammates and other users - to contact you if a resource needs to be removed or if they have questions about it. + Strategy for using vpc resources: + 'skip' to disable, + 'select' to use existing, + or 'create' to generate new vpc resources. + The default is 'create', which requires a vpc_name and vpc_cidr to be provided. + When selecting a vpc, the vpc_name must be provided and a vpc that has a tag "Name with the given name must exist. + When skipping a vpc, the subnet, security group, and load balancer will also be skipped (automatically). EOT - default = "" + default = "create" + validation { + condition = contains(["skip", "select", "create"], var.vpc_use_strategy) + error_message = "The vpc_use_strategy value must be one of 'skip', 'select', or 'create'." + } } -# vpc variable "vpc_name" { type = string description = <<-EOT The name of the VPC to create or select. - This is required. - If a cidr is specified, then a VPC will be created. EOT default = "" } variable "vpc_cidr" { type = string description = <<-EOT - If this value is specified, then a VPC will be created. This value sets the default private IP space for the created VPC. - VPCs generated with this module automatically give Amazon supplied public addresses to ec2 instances via an internet gateway. - Access to the ec2 instances is then controlled by the security group. WARNING: AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively). This means that every VPC has 5 IP addresses that cannot be assigned to subnets, and every subnet assigned has 5 IP addresses that cannot be used. If you attempt to generate a VPC that has no usable addresses you will get an "invalid CIDR" error from AWS. @@ -31,65 +34,77 @@ variable "vpc_cidr" { EOT default = "" } -variable "skip_vpc" { - type = bool - description = "Skip vpc generation, use with care." - default = false -} + # subnet -variable "subnet_name" { +variable "subnet_use_strategy" { type = string description = <<-EOT - The name of the subnet you would like to create or select. - This is required. - If you provide a cidr value, then this module will create a subnet with the given name. - If you do not provide a cidr value, then this module will attempt to find a subnet with the given name. - If you override the VPC creation, but not the subnet creation, - this module will attempt to associate the created subnet to the VPC. - If the subnet is not available within the VPC's default CIDR, this module will fail. - If you override the creation of the VPC and the creation of the subnet, - this module won't attempt to associate the subnet to the VPC. + Strategy for using subnet resources: + 'skip' to disable, + 'select' to use existing, + or 'create' to generate new subnet resources. + The default is 'create', which requires a subnet_name and subnet_cidr to be provided. + When selecting a subnet, the subnet_name must be provided and a subnet with the tag "Name" with the given name must exist. + When skipping a subnet, the security group and load balancer will also be skipped (automatically). EOT - default = "" -} -variable "subnet_cidr" { - type = string + default = "create" + validation { + condition = contains(["skip", "select", "create"], var.subnet_use_strategy) + error_message = "The subnet_use_strategy value must be one of 'skip', 'select', or 'create'." + } +} +variable "subnets" { + type = map(object({ + cidr = string, + availability_zone = string, + public = bool, + })) description = <<-EOT - The cidr of the private subnet you would like to create. - This cidr must be within the IP bounds of the vpc_cidr. - If this is specified, then a subnet will be created. - If this isn't specified, then the module will attempt to find a subnet with the given name. + A map of subnet objects to create or select. + The key is the name of the subnet, and the value is an object with the following keys: + cidr: the cidr of the subnet to create + availability_zone: the availability zone to create the subnet in + public: set this to true to enable the subnet to have public IP addresses WARNING: AWS reserves the first four IP addresses and the last IP address in any CIDR block for its own use (cumulatively). This means that every VPC has 5 IP addresses that cannot be assigned to subnets, and every subnet assigned has 5 IP addresses that cannot be used. If you attempt to generate a subnet that has no usable addresses you will get an "invalid CIDR" error from AWS. If you attempt to generate a subnet that uses one of the addresses reserved by AWS in the VPC's CIDR, you will get an "invalid CIDR" error from AWS. + When skipping a subnet, the security group and load balancer will also be skipped (automatically). + When selecting a subnet: + - the name must be provided and a subnet with the tag "Name" with the given name must exist. + - the values for cidr, availability_zone, and public will be ignored. + When creating subnets, any values not supplied will be generated by the module. + - the name will match the vpc name + - The availability zone will be whatever the default is for your account. + - The cidr will be generated based on the VPC's cidr and the number of subnets you are creating. + - The public flag will be set to false. + If you are expecting high availability, make sure there are at least three availability zones in the region you are deploying to. EOT - default = "" -} -variable "subnet_public_ip" { - type = bool - description = <<-EOT - Set this to true to enable the subnet to have public IP addresses. - EOT - default = false + default = { "default" = { + cidr = "", # will be generated based on the vpc cidr + availability_zone = "", # just get the first one + public = false, + } } } -variable "availability_zone" { + +# security group +variable "security_group_use_strategy" { type = string description = <<-EOT - The availability zone to create the subnet in. - This is the name of the availability zone, not the AWS unique id. - For example "us-east-1a" or "us-east-1b" not "use1-az1" or "use1-az2". - This is required when creating a subnet, but not when selecting a subnet. - Any servers created in this subnet will be created in this availability zone. + Strategy for using security group resources: + 'skip' to disable, + 'select' to use existing, + or 'create' to generate new security group resources. + The default is 'create'. + When selecting a security group, the security_group_name must be provided and a security group with the given name must exist. + When skipping a security group, the load balancer will also be skipped (automatically). EOT - default = "" + default = "create" + validation { + condition = contains(["skip", "select", "create"], var.security_group_use_strategy) + error_message = "The security_group_use_strategy value must be one of 'skip', 'select', or 'create'." + } } -variable "skip_subnet" { - type = bool - description = "Skip subnet generation, use with care." - default = false -} -# security group variable "security_group_name" { type = string description = <<-EOT @@ -109,50 +124,76 @@ variable "security_group_type" { The types are located in ./modules/security_group/types.tf. If specified, must be one of: specific, internal, egress, or public. EOT - default = "" + default = "project" + validation { + condition = contains(["project", "egress", "public"], var.security_group_type) + error_message = "The security_group_type value must be one of 'project', 'egress', or 'public'." + } } -variable "security_group_ip" { + +# load balancer +variable "load_balancer_use_strategy" { type = string description = <<-EOT - When selecting the type of security group to create, you may need to specify an IP address. - If no IP address is specified the module will attempt to discover and use your local IP address. - It is a good idea to specify the IP where Terraform will be run to create servers. + Strategy for using load balancer resources: + 'skip' to disable, + 'select' to use existing, + or 'create' to generate new load balancer resources. + The default is 'create'. + When selecting a load balancer, the load_balancer_name must be provided and a load balancer with the "Name" tag must exist. + When skipping a load balancer, the domain will also be skipped (automatically). EOT - default = "" -} -variable "skip_security_group" { - type = bool - description = "Skip security group generation, use with care." - default = false + default = "create" + validation { + condition = contains(["skip", "select", "create"], var.load_balancer_use_strategy) + error_message = "The load_balancer_use_strategy value must be one of 'skip', 'select', or 'create'." + } } -variable "skip_runner_ip" { - type = bool - description = "Skip generating ingress security group for the runner's ip" - default = false +variable "load_balancer_name" { + type = string + description = <<-EOT + The name of the Load Balancer, there must be a 'Name' tag on it to be found. + When generating a load balancer, this will be added as a tag to the resource. + This tag is how we will find it again in the future. + If a domain and a load balancer name is given, we will create a domain record pointing to the load balancer. + EOT + default = "" } -# ssh key -variable "ssh_key_name" { +# domain +variable "domain_use_strategy" { type = string description = <<-EOT - The name of the ec2 ssh key pair to create or select. - This is required. - If you would like to create an ssh key pair, please specify the public_ssh_key. - If the public_ssh_key variable is not specified, then this module will attempt to find an ssh key with the given name. + Strategy for using domain resources: + 'skip' to disable, + 'select' to use existing, + or 'create' to generate new domain resources. + The default is 'create', which requires a domain name to be provided. + When selecting a domain, the domain must be provided and a domain with the matching name must exist. EOT - default = "" + default = "create" + validation { + condition = contains(["skip", "select", "create"], var.domain_use_strategy) + error_message = "The domain_use_strategy value must be one of 'skip', 'select', or 'create'." + } } -variable "public_ssh_key" { +variable "domain" { type = string description = <<-EOT - The contents of the public ssh key object to create. - If this is specified, then an ssh key will be created. - If this isn't specified, then the module will attempt to find an ssh key with the given name. + The domain name to retrieve or create. + Part of creating the domain is assigning it to the load balancer and generating a tls certificate. + This should enable secure connections for your project. + To make use of this feature, you must generate load balancer target group associations in other further stages. + We output the arn of the load balancer for this purpose. EOT default = "" } -variable "skip_ssh" { - type = bool - description = "Skip ssh key generation, use with care." - default = false +variable "domain_zone" { + type = string + description = <<-EOT + The domain zone to create. + This is only required if you want to create a new domain zone. + If you are using an existing domain zone, you can leave this blank. + EOT + default = "" } \ No newline at end of file diff --git a/versions.tf b/versions.tf index 7c63b5d..b30206d 100644 --- a/versions.tf +++ b/versions.tf @@ -9,9 +9,12 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" + acme = { + source = "vancluever/acme" + version = ">= 2.0" } } -} \ No newline at end of file +} +# provider "acme" { +# server_url = "https://acme-staging-v02.api.letsencrypt.org/directory" +# } \ No newline at end of file