From ba4f68826fb13168778744d0daab035d4e4e214a Mon Sep 17 00:00:00 2001 From: matttrach Date: Tue, 26 Mar 2024 04:40:36 -0500 Subject: [PATCH 1/6] feat: add domains and load balancing, initial Signed-off-by: matttrach --- examples/domain/main.tf | 33 +++++ examples/domain/outputs.tf | 19 +++ examples/domain/variables.tf | 9 ++ examples/domain/versions.tf | 17 +++ examples/loadbalancer/main.tf | 31 +++++ examples/loadbalancer/outputs.tf | 19 +++ examples/loadbalancer/variables.tf | 9 ++ examples/loadbalancer/versions.tf | 17 +++ flake.lock | 6 +- main.tf | 55 +++++++- modules/domain/main.tf | 118 ++++++++++++++++ modules/domain/outputs.tf | 12 ++ modules/domain/variables.tf | 39 ++++++ modules/domain/versions.tf | 17 +++ modules/network_load_balancer/main.tf | 150 +++++++++++++++++++++ modules/network_load_balancer/outputs.tf | 23 ++++ modules/network_load_balancer/variables.tf | 38 ++++++ modules/network_load_balancer/versions.tf | 17 +++ outputs.tf | 42 ++++++ tests/domain_test.go | 34 +++++ tests/loadbalancer_test.go | 34 +++++ variables.tf | 44 +++++- 22 files changed, 776 insertions(+), 7 deletions(-) create mode 100644 examples/domain/main.tf create mode 100644 examples/domain/outputs.tf create mode 100644 examples/domain/variables.tf create mode 100644 examples/domain/versions.tf create mode 100644 examples/loadbalancer/main.tf create mode 100644 examples/loadbalancer/outputs.tf create mode 100644 examples/loadbalancer/variables.tf create mode 100644 examples/loadbalancer/versions.tf create mode 100644 modules/domain/main.tf create mode 100644 modules/domain/outputs.tf create mode 100644 modules/domain/variables.tf create mode 100644 modules/domain/versions.tf create mode 100644 modules/network_load_balancer/main.tf create mode 100644 modules/network_load_balancer/outputs.tf create mode 100644 modules/network_load_balancer/variables.tf create mode 100644 modules/network_load_balancer/versions.tf create mode 100644 tests/domain_test.go create mode 100644 tests/loadbalancer_test.go diff --git a/examples/domain/main.tf b/examples/domain/main.tf new file mode 100644 index 0000000..b2a03cc --- /dev/null +++ b/examples/domain/main.tf @@ -0,0 +1,33 @@ + +provider "aws" { + default_tags { + tags = { + Id = local.identifier + } + } +} +locals { + identifier = var.identifier + name = "tf-dns-${local.identifier}" + key = var.key + key_name = var.key_name +} +# 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" + skip_runner_ip = true + public_ssh_key = local.key + ssh_key_name = local.key_name + load_balancer_name = local.name + skip_lb = false + domain = "${local.name}.${local.identifier}.name" + zone = "${local.identifier}.name." +} diff --git a/examples/domain/outputs.tf b/examples/domain/outputs.tf new file mode 100644 index 0000000..d000b32 --- /dev/null +++ b/examples/domain/outputs.tf @@ -0,0 +1,19 @@ +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 +} + +output "load_balancer" { + value = module.this.load_balancer +} \ No newline at end of file diff --git a/examples/domain/variables.tf b/examples/domain/variables.tf new file mode 100644 index 0000000..7d75481 --- /dev/null +++ b/examples/domain/variables.tf @@ -0,0 +1,9 @@ +variable "key" { + type = string +} +variable "key_name" { + type = string +} +variable "identifier" { + type = string +} \ No newline at end of file diff --git a/examples/domain/versions.tf b/examples/domain/versions.tf new file mode 100644 index 0000000..7c63b5d --- /dev/null +++ b/examples/domain/versions.tf @@ -0,0 +1,17 @@ +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/loadbalancer/main.tf b/examples/loadbalancer/main.tf new file mode 100644 index 0000000..96ad7f0 --- /dev/null +++ b/examples/loadbalancer/main.tf @@ -0,0 +1,31 @@ + +provider "aws" { + default_tags { + tags = { + Id = local.identifier + } + } +} +locals { + identifier = var.identifier + name = "tf-lb-${local.identifier}" + key = var.key + key_name = var.key_name +} +# 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 + skip_lb = false + skip_runner_ip = true +} diff --git a/examples/loadbalancer/outputs.tf b/examples/loadbalancer/outputs.tf new file mode 100644 index 0000000..d000b32 --- /dev/null +++ b/examples/loadbalancer/outputs.tf @@ -0,0 +1,19 @@ +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 +} + +output "load_balancer" { + value = module.this.load_balancer +} \ No newline at end of file diff --git a/examples/loadbalancer/variables.tf b/examples/loadbalancer/variables.tf new file mode 100644 index 0000000..7d75481 --- /dev/null +++ b/examples/loadbalancer/variables.tf @@ -0,0 +1,9 @@ +variable "key" { + type = string +} +variable "key_name" { + type = string +} +variable "identifier" { + type = string +} \ No newline at end of file diff --git a/examples/loadbalancer/versions.tf b/examples/loadbalancer/versions.tf new file mode 100644 index 0000000..7c63b5d --- /dev/null +++ b/examples/loadbalancer/versions.tf @@ -0,0 +1,17 @@ +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/flake.lock b/flake.lock index 0940f1a..d7f08ad 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711106783, - "narHash": "sha256-PDwAcHahc6hEimyrgGmFdft75gmLrJOZ0txX7lFqq+I=", + "lastModified": 1711231723, + "narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a3ed7406349a9335cb4c2a71369b697cecd9d351", + "rev": "e1d501922fd7351da4200e1275dfcf5faaad1220", "type": "github" }, "original": { diff --git a/main.tf b/main.tf index b9de076..53d9d02 100644 --- a/main.tf +++ b/main.tf @@ -22,6 +22,16 @@ locals { 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 + + zone = var.zone # if a zone is given, we will create a zone record, otherwise we will attempt to find it + domain = var.domain # only create a domain record if a load balancer name is given + add_domain = (var.load_balancer_name == "" ? false : true) + + skip_lb = var.skip_lb + load_balancer_name = var.load_balancer_name # if a load balancer name is given, we will create a load balancer + select_lb = var.select_lb # if true we will select a load balancer, otherwise we will create one + create_lb = (local.select_lb ? false : true) # create_lb is the opposite of select_lb + # if a domain and a load balancer name is given, we will create a domain record pointing to the load balancer } data "http" "my_public_ip" { @@ -37,7 +47,10 @@ module "vpc" { } module "subnet" { - count = (local.skip_subnet ? 0 : 1) + depends_on = [ + module.vpc, + ] + count = ((local.skip_subnet || local.skip_vpc) ? 0 : 1) source = "./modules/subnet" name = local.subnet_name cidr = local.subnet_cidr @@ -48,11 +61,15 @@ module "subnet" { } module "security_group" { - count = (local.skip_security_group ? 0 : 1) + depends_on = [ + module.subnet, + module.vpc, + ] + count = ((local.skip_security_group || local.skip_subnet || local.skip_vpc) ? 0 : 1) source = "./modules/security_group" name = local.security_group_name ip = local.ip - cidr = (can(module.subnet[0].cidr) ? module.subnet[0].cidr : "") + cidr = module.subnet[0].cidr owner = local.owner type = local.security_group_type vpc_id = module.vpc[0].id @@ -67,3 +84,35 @@ module "ssh_key" { public_key = local.public_ssh_key owner = local.owner } + +module "network_load_balancer" { + depends_on = [ + module.vpc, + module.subnet, + module.security_group, + ] + count = ((local.skip_lb || local.skip_security_group || local.skip_subnet || local.skip_vpc) ? 0 : 1) + source = "./modules/network_load_balancer" + owner = local.owner + name = local.load_balancer_name + create = local.create_lb + security_group_id = module.security_group[0].id + subnet_id = module.subnet[0].id + vpc_id = module.vpc[0].id +} + +module "domain" { + depends_on = [ + module.vpc, + module.subnet, + module.security_group, + module.network_load_balancer, + ] + count = ((local.domain == "" && local.zone == "") ? 0 : 1) + source = "./modules/domain" + owner = local.owner + create = local.add_domain + content = local.domain + zone = local.zone + alias = (length(module.network_load_balancer) > 0 ? module.network_load_balancer[0].dns_name : "") +} diff --git a/modules/domain/main.tf b/modules/domain/main.tf new file mode 100644 index 0000000..32b4d73 --- /dev/null +++ b/modules/domain/main.tf @@ -0,0 +1,118 @@ +locals { + owner = var.owner + + content = var.content # the domain to register eg. "blah.blah.test.example.com" + alias = var.alias # optional pre-generated domain name eg. "mylb.region.elb.amazonaws.com" + content_id = "${local.zone_id}_${local.content}_A" # used in output + + add_zone = (var.zone == "" ? 0 : 1) # add a zone if given a zone, else select a zone + select_zone = (local.add_zone == 1 ? 0 : 1) # select is the opposite of add + + zone_id = (local.add_zone == 1 ? resource.aws_route53_zone.new[0].id : data.aws_route53_zone.select[0].id) + domain_part_count = length(split(".", local.content)) + domain_parts = split(".", local.content) + top_level_domain = local.domain_parts[(local.domain_part_count - 1)] + next_level_domain = local.domain_parts[(local.domain_part_count - 2)] + find_zone = join(".", [local.next_level_domain, local.top_level_domain]) # extract the zone from the domain eg. "example.com" + zone = (var.zone == "" ? local.find_zone : var.zone) + + create = (var.create ? 1 : 0) # create is always an alias because it can only attached to a load balancer at this point in the project + select = (local.create == 1 ? 0 : 1) # select is the opposite of create + + validation_records = [ + for option in aws_acm_certificate.new[0].domain_validation_options : { + name = option.resource_record_name + record = option.resource_record_value + type = option.resource_record_type + zone_id = local.zone_id + } + ] + # Transform the list of maps into a map using the 'name' and 'type' as the key + validation_records_map = { for record in local.validation_records : "${record.name}_${record.type}" => record } + + +} + +data "aws_route53_zone" "select" { + count = local.select_zone + name = local.find_zone + tags = { + Name = local.find_zone + } +} + +resource "aws_route53_zone" "new" { + count = local.add_zone + name = local.zone + tags = { + Name = local.zone + Owner = local.owner + } +} + +resource "aws_route53domains_registered_domain" "select" { + count = local.select + domain_name = local.content +} + +# alias is a pre-generated "aws" domain eg. mylb.region.elb.amazonaws.com +# this cnames the pre-generated aws domain to the specified domain +resource "aws_route53_record" "new" { + depends_on = [ + aws_route53_zone.new, + data.aws_route53_zone.select, + ] + count = local.create + zone_id = local.zone_id + name = local.content + type = "CNAME" + records = [local.alias] + ttl = 60 +} + +# only generate a certificate if the domain is not already registered +resource "aws_acm_certificate" "new" { + depends_on = [ + aws_route53_zone.new, + data.aws_route53_zone.select, + aws_route53_record.new, + ] + count = local.create + domain_name = local.content + validation_method = "DNS" + tags = { + Name = local.content + Owner = local.owner + } + lifecycle { + create_before_destroy = true + } +} + +resource "aws_route53_record" "cert_validation" { + depends_on = [ + aws_route53_zone.new, + data.aws_route53_zone.select, + aws_route53_record.new, + aws_acm_certificate.new, + ] + count = local.create + zone_id = local.zone_id + name = tolist(aws_acm_certificate.new[0].domain_validation_options)[0].resource_record_name + type = tolist(aws_acm_certificate.new[0].domain_validation_options)[0].resource_record_type + records = [tolist(aws_acm_certificate.new[0].domain_validation_options)[0].resource_record_value] + ttl = 60 +} + +resource "aws_acm_certificate_validation" "certificate_validation" { + depends_on = [ + aws_route53_zone.new, + data.aws_route53_zone.select, + aws_route53_record.new, + aws_acm_certificate.new, + aws_route53_record.cert_validation, + ] + count = local.create + certificate_arn = aws_acm_certificate.new[0].arn + validation_record_fqdns = [aws_route53_record.cert_validation[0].fqdn] +} diff --git a/modules/domain/outputs.tf b/modules/domain/outputs.tf new file mode 100644 index 0000000..861348d --- /dev/null +++ b/modules/domain/outputs.tf @@ -0,0 +1,12 @@ +output "id" { + value = (local.select == 1 ? local.content_id : aws_route53_record.new[0].id) +} +output "zone_id" { + value = (local.select_zone == 1 ? data.aws_route53_zone.select[0].id : aws_route53_zone.new[0].id) +} +output "zone" { + value = (local.select_zone == 1 ? data.aws_route53_zone.select[0] : aws_route53_zone.new[0]) +} +output "domain" { + value = (local.select == 1 ? aws_route53domains_registered_domain.select[0] : aws_route53_record.new[0]) +} diff --git a/modules/domain/variables.tf b/modules/domain/variables.tf new file mode 100644 index 0000000..3f9f05d --- /dev/null +++ b/modules/domain/variables.tf @@ -0,0 +1,39 @@ +variable "owner" { + type = string + description = "The owner to tag the domain with after creation." + default = "terraform" +} + +variable "create" { + type = bool + description = <<-EOT + Set to true to add a domain record. + 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 "alias" { + type = string + description = <<-EOT + An undesireable pre-generated domain name assigned to the project (for instance, from the creation of a network load balancer). + EOT + default = "" +} + +variable "zone" { + type = string + description = <<-EOT + Setting this will create a new zone with the given name. + The zone to add the domain to. + If this is not set we will attempt to find the zone based on the domain name. + EOT + default = "" +} diff --git a/modules/domain/versions.tf b/modules/domain/versions.tf new file mode 100644 index 0000000..7c63b5d --- /dev/null +++ b/modules/domain/versions.tf @@ -0,0 +1,17 @@ +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/network_load_balancer/main.tf b/modules/network_load_balancer/main.tf new file mode 100644 index 0000000..827a29e --- /dev/null +++ b/modules/network_load_balancer/main.tf @@ -0,0 +1,150 @@ +locals { + name = var.name + owner = var.owner + security_group_id = var.security_group_id + subnet_id = var.subnet_id + vpc_id = var.vpc_id + create = (var.create ? 1 : 0) + select = (var.create ? 0 : 1) +} +data "aws_lb" "selected" { + count = local.select + tags = { + Name = local.name + } +} +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_id] + + tags = { + Name = local.name + Owner = local.owner + } +} +# only generate targets if the network load balancer is created +resource "aws_lb_target_group" "port80" { + depends_on = [ + aws_lb.new, + ] + count = local.create + port = 80 + protocol = "TCP" + vpc_id = local.vpc_id + name = "${local.name}-port80" + health_check { + protocol = "HTTP" + path = "/ping" # this is a k8s built in path that returns a 200 status code + interval = 10 + timeout = 6 + healthy_threshold = 3 + unhealthy_threshold = 3 + matcher = "200-399" + } + tags = { + Name = "${local.name}-port80" + Owner = local.owner + } +} +resource "aws_lb_target_group" "port443" { + depends_on = [ + aws_lb.new, + ] + count = local.create + port = 443 + protocol = "TCP" + vpc_id = local.vpc_id + name = "${local.name}-port443" + health_check { + protocol = "HTTP" + path = "/ping" # this is a k8s built in path that returns a 200 status code + interval = 10 + timeout = 6 + healthy_threshold = 3 + unhealthy_threshold = 3 + matcher = "200-399" + } + tags = { + Name = "${local.name}-port443" + Owner = local.owner + } +} +resource "aws_lb_target_group" "port6443" { + depends_on = [ + aws_lb.new, + ] + count = local.create + port = 6443 + protocol = "TCP" + vpc_id = local.vpc_id + name = "${local.name}-port6443" + health_check { + protocol = "HTTP" + path = "/ping" # this is a k8s built in path that returns a 200 status code + interval = 10 + timeout = 6 + healthy_threshold = 3 + unhealthy_threshold = 3 + matcher = "200-399" + } + tags = { + Name = "${local.name}-port6443" + Owner = local.owner + } +} +# only generate listeners if the network load balancer is created +resource "aws_lb_listener" "port80" { + depends_on = [ + aws_lb.new, + ] + count = local.create + load_balancer_arn = aws_lb.new[0].arn + port = "80" + protocol = "TCP" + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.port80[0].arn + } + tags = { + Name = "${local.name}-port80" + Owner = local.owner + } +} +resource "aws_lb_listener" "port443" { + depends_on = [ + aws_lb.new, + ] + count = local.create + load_balancer_arn = aws_lb.new[0].arn + port = "443" + protocol = "TCP" + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.port443[0].arn + } + tags = { + Name = "${local.name}-port443" + Owner = local.owner + } +} +resource "aws_lb_listener" "port6443" { + depends_on = [ + aws_lb.new, + ] + count = local.create + load_balancer_arn = aws_lb.new[0].arn + port = "6443" + protocol = "TCP" + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.port6443[0].arn + } + tags = { + Name = "${local.name}-port6443" + Owner = local.owner + } +} diff --git a/modules/network_load_balancer/outputs.tf b/modules/network_load_balancer/outputs.tf new file mode 100644 index 0000000..397b8d3 --- /dev/null +++ b/modules/network_load_balancer/outputs.tf @@ -0,0 +1,23 @@ +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 "target_group_names" { + value = (local.create == 1 ? [ + aws_lb_target_group.port80[0].tags.Name, + aws_lb_target_group.port443[0].tags.Name, + aws_lb_target_group.port6443[0].tags.Name, + ] : []) +} +output "listener_names" { + value = (local.create == 1 ? [ + aws_lb_listener.port80[0].tags.Name, + aws_lb_listener.port443[0].tags.Name, + aws_lb_listener.port6443[0].tags.Name, + ] : []) +} diff --git a/modules/network_load_balancer/variables.tf b/modules/network_load_balancer/variables.tf new file mode 100644 index 0000000..04f096d --- /dev/null +++ b/modules/network_load_balancer/variables.tf @@ -0,0 +1,38 @@ +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 "owner" { + type = string + description = <<-EOT + The owner of the Load Balancer, this is used as refence in the AWS console. + 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 "create" { + type = bool + description = "Set to false to select a load balancer rather than creating one." +} +variable "security_group_id" { + type = string + description = <<-EOT + The security group id to attach to the Load Balancer. + EOT +} +variable "subnet_id" { + type = string + description = <<-EOT + The subnet id to attach to the Load Balancer. + EOT +} +variable "vpc_id" { + type = string + description = <<-EOT + The VPC id to deploy the load balancer in. + EOT +} diff --git a/modules/network_load_balancer/versions.tf b/modules/network_load_balancer/versions.tf new file mode 100644 index 0000000..7c63b5d --- /dev/null +++ b/modules/network_load_balancer/versions.tf @@ -0,0 +1,17 @@ +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/outputs.tf b/outputs.tf index 2e66d0a..7ddec4e 100644 --- a/outputs.tf +++ b/outputs.tf @@ -89,3 +89,45 @@ output "ssh_key" { The SSH key object from AWS. EOT } + +output "load_balancer" { + value = (can(module.network_load_balancer[0].load_balancer) ? { + 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 = module.network_load_balancer[0].load_balancer.tags + } : { + # no object found, but output types are normal + id = "" + arn = "" + dns_name = "" + zone_id = "" + security_groups = [] + subnets = [] + tags = tomap({ "" = "" }) + }) + description = <<-EOT + The load balancer object from AWS. + EOT +} + +output "domain" { + value = (can(module.domain[0].domain) ? { + id = module.domain[0].domain.id + arn = module.domain[0].domain.arn + domain_name = module.domain[0].domain.domain_name + owner_id = module.domain[0].domain.owner_id + } : { + # no object found, but output types are normal + id = "" + arn = "" + domain_name = "" + owner_id = "" + }) + description = <<-EOT + The domain object from AWS. + EOT +} \ No newline at end of file diff --git a/tests/domain_test.go b/tests/domain_test.go new file mode 100644 index 0000000..a9592f7 --- /dev/null +++ b/tests/domain_test.go @@ -0,0 +1,34 @@ +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 TestDomain(t *testing.T) { + t.Parallel() + uniqueID := os.Getenv("IDENTIFIER") + if uniqueID == "" { + uniqueID = random.UniqueId() + } + directory := "domain" + region := "us-west-1" + + keyPair := ssh.GenerateRSAKeyPair(t, 2048) + keyPairName := fmt.Sprintf("tf-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) + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) +} diff --git a/tests/loadbalancer_test.go b/tests/loadbalancer_test.go new file mode 100644 index 0000000..2b87b9a --- /dev/null +++ b/tests/loadbalancer_test.go @@ -0,0 +1,34 @@ +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 TestLoadbalancer(t *testing.T) { + t.Parallel() + uniqueID := os.Getenv("IDENTIFIER") + if uniqueID == "" { + uniqueID = random.UniqueId() + } + directory := "loadbalancer" + region := "us-west-1" + + keyPair := ssh.GenerateRSAKeyPair(t, 2048) + keyPairName := fmt.Sprintf("tf-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) + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) +} diff --git a/variables.tf b/variables.tf index 77824a1..4102400 100644 --- a/variables.tf +++ b/variables.tf @@ -155,4 +155,46 @@ variable "skip_ssh" { type = bool description = "Skip ssh key generation, use with care." default = false -} \ No newline at end of file +} + +# load balancer +variable "skip_lb" { + type = bool + description = "Set to false to deploy a load balancer." + default = true +} +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 = "" +} +variable "select_lb" { + type = bool + description = "Set to true to select a load balancer rather than creating one." + default = false +} +# domain +variable "domain" { + type = string + description = <<-EOT + The domain name to retrieve or create. + There is no way to generate a domain name without something to attach it to, + so without a load balancer no domain will be created. + If a domain is given, and no load balancer name is given, we will attempt to find the domain. + EOT + default = "" +} +variable "zone" { + type = string + description = <<-EOT + The zone to add the domain to. + If this is specified we will try to generate a zone record. + If this isn't set we will attempt to find the zone based on the domain name. + EOT + default = "" +} From fa9d8239a02c82ddc46ab4c512132cb2a019bdca Mon Sep 17 00:00:00 2001 From: matttrach Date: Thu, 28 Mar 2024 02:01:06 -0500 Subject: [PATCH 2/6] refactor!: rethinking the focus Signed-off-by: matttrach --- examples/domain/main.tf | 19 +- examples/domain/variables.tf | 6 - examples/loadbalancer/main.tf | 18 +- examples/loadbalancer/outputs.tf | 4 - examples/loadbalancer/variables.tf | 6 - flake.lock | 6 +- flake.nix | 1 + main.tf | 146 +++++++------ modules/domain/main.tf | 235 ++++++++++++++------- modules/domain/outputs.tf | 6 +- modules/domain/variables.tf | 30 +-- modules/domain/versions.tf | 12 +- modules/network_load_balancer/main.tf | 156 +++----------- modules/network_load_balancer/outputs.tf | 15 +- modules/network_load_balancer/variables.tf | 33 ++- modules/network_load_balancer/versions.tf | 4 - modules/security_group/main.tf | 71 ++----- modules/security_group/types.tf | 90 ++------ modules/security_group/variables.tf | 50 ++--- modules/security_group/versions.tf | 4 - modules/ssh_key/main.tf | 26 --- modules/ssh_key/outputs.tf | 6 - modules/ssh_key/variables.tf | 20 -- modules/ssh_key/versions.tf | 17 -- modules/subnet/main.tf | 16 +- modules/subnet/outputs.tf | 3 + modules/subnet/variables.tf | 31 ++- modules/subnet/versions.tf | 4 - modules/vpc/main.tf | 5 +- modules/vpc/variables.tf | 14 +- modules/vpc/versions.tf | 4 - notes/README.md | 17 ++ notes/domain.md | 66 ++++++ notes/security_groups.md | 44 ++++ outputs.tf | 89 ++++---- tests/loadbalancer_test.go | 9 +- tests/util_test.go | 1 + variables.tf | 226 ++++++++++---------- versions.tf | 9 +- 39 files changed, 691 insertions(+), 828 deletions(-) delete mode 100644 modules/ssh_key/main.tf delete mode 100644 modules/ssh_key/outputs.tf delete mode 100644 modules/ssh_key/variables.tf delete mode 100644 modules/ssh_key/versions.tf create mode 100644 notes/README.md create mode 100644 notes/domain.md create mode 100644 notes/security_groups.md diff --git a/examples/domain/main.tf b/examples/domain/main.tf index b2a03cc..1a1d623 100644 --- a/examples/domain/main.tf +++ b/examples/domain/main.tf @@ -2,32 +2,23 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier + Owner = local.owner } } } locals { identifier = var.identifier name = "tf-dns-${local.identifier}" - key = var.key - key_name = var.key_name + owner = "terraform-ci@suse.com" } # 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" - skip_runner_ip = true - public_ssh_key = local.key - ssh_key_name = local.key_name + security_group_type = "project" load_balancer_name = local.name - skip_lb = false - domain = "${local.name}.${local.identifier}.name" - zone = "${local.identifier}.name." + domain = "${local.name}.eng.rancher.space" } diff --git a/examples/domain/variables.tf b/examples/domain/variables.tf index 7d75481..379bf39 100644 --- a/examples/domain/variables.tf +++ b/examples/domain/variables.tf @@ -1,9 +1,3 @@ -variable "key" { - type = string -} -variable "key_name" { - type = string -} variable "identifier" { type = string } \ No newline at end of file diff --git a/examples/loadbalancer/main.tf b/examples/loadbalancer/main.tf index 96ad7f0..73f5274 100644 --- a/examples/loadbalancer/main.tf +++ b/examples/loadbalancer/main.tf @@ -2,30 +2,22 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier + Owner = "terraform-ci@suse.com" } } } locals { identifier = var.identifier name = "tf-lb-${local.identifier}" - key = var.key - key_name = var.key_name } # 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 - skip_lb = false - skip_runner_ip = 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 index d000b32..23082d0 100644 --- a/examples/loadbalancer/outputs.tf +++ b/examples/loadbalancer/outputs.tf @@ -10,10 +10,6 @@ output "security_group" { value = module.this.security_group } -output "ssh_key" { - value = module.this.ssh_key -} - output "load_balancer" { value = module.this.load_balancer } \ No newline at end of file diff --git a/examples/loadbalancer/variables.tf b/examples/loadbalancer/variables.tf index 7d75481..379bf39 100644 --- a/examples/loadbalancer/variables.tf +++ b/examples/loadbalancer/variables.tf @@ -1,9 +1,3 @@ -variable "key" { - type = string -} -variable "key_name" { - type = string -} variable "identifier" { type = string } \ No newline at end of file diff --git a/flake.lock b/flake.lock index d7f08ad..fa7a6a5 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711231723, - "narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=", + "lastModified": 1711401922, + "narHash": "sha256-QoQqXoj8ClGo0sqD/qWKFWezgEwUL0SUh37/vY2jNhc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e1d501922fd7351da4200e1275dfcf5faaad1220", + "rev": "07262b18b97000d16a4bdb003418bd2fb067a932", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2afafaf..5aafb76 100644 --- a/flake.nix +++ b/flake.nix @@ -100,6 +100,7 @@ aspellWithDicts bashInteractive curl + dig docker gh git diff --git a/main.tf b/main.tf index 53d9d02..9dbbfcb 100644 --- a/main.tf +++ b/main.tf @@ -1,47 +1,81 @@ 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 - zone = var.zone # if a zone is given, we will create a zone record, otherwise we will attempt to find it - domain = var.domain # only create a domain record if a load balancer name is given - add_domain = (var.load_balancer_name == "" ? false : true) + # domain + domain = var.domain - skip_lb = var.skip_lb - load_balancer_name = var.load_balancer_name # if a load balancer name is given, we will create a load balancer - select_lb = var.select_lb # if true we will select a load balancer, otherwise we will create one - create_lb = (local.select_lb ? false : true) # create_lb is the opposite of select_lb - # if a domain and a load balancer name is given, we will create a domain record pointing to the load balancer + # 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 } @@ -50,14 +84,14 @@ module "subnet" { depends_on = [ module.vpc, ] - count = ((local.skip_subnet || local.skip_vpc) ? 0 : 1) + 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" { @@ -65,24 +99,13 @@ module "security_group" { module.subnet, module.vpc, ] - count = ((local.skip_security_group || local.skip_subnet || local.skip_vpc) ? 0 : 1) - source = "./modules/security_group" - name = local.security_group_name - ip = local.ip - 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 -} - -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 + 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" { @@ -91,13 +114,12 @@ module "network_load_balancer" { module.subnet, module.security_group, ] - count = ((local.skip_lb || local.skip_security_group || local.skip_subnet || local.skip_vpc) ? 0 : 1) + count = local.load_balancer_mod source = "./modules/network_load_balancer" - owner = local.owner + use = local.load_balancer_use_strategy name = local.load_balancer_name - create = local.create_lb security_group_id = module.security_group[0].id - subnet_id = module.subnet[0].id + subnet_ids = [for subnet in module.subnet : subnet.id] vpc_id = module.vpc[0].id } @@ -108,11 +130,9 @@ module "domain" { module.security_group, module.network_load_balancer, ] - count = ((local.domain == "" && local.zone == "") ? 0 : 1) + count = local.domain_mod source = "./modules/domain" - owner = local.owner - create = local.add_domain - content = local.domain - zone = local.zone - alias = (length(module.network_load_balancer) > 0 ? module.network_load_balancer[0].dns_name : "") + use = local.domain_use_strategy + content = lower(local.domain) + ip = module.network_load_balancer[0].public_ip } diff --git a/modules/domain/main.tf b/modules/domain/main.tf index 32b4d73..e928ab1 100644 --- a/modules/domain/main.tf +++ b/modules/domain/main.tf @@ -1,118 +1,195 @@ locals { - owner = var.owner - - content = var.content # the domain to register eg. "blah.blah.test.example.com" - alias = var.alias # optional pre-generated domain name eg. "mylb.region.elb.amazonaws.com" - content_id = "${local.zone_id}_${local.content}_A" # used in output - - add_zone = (var.zone == "" ? 0 : 1) # add a zone if given a zone, else select a zone - select_zone = (local.add_zone == 1 ? 0 : 1) # select is the opposite of add - - zone_id = (local.add_zone == 1 ? resource.aws_route53_zone.new[0].id : data.aws_route53_zone.select[0].id) - domain_part_count = length(split(".", local.content)) - domain_parts = split(".", local.content) - top_level_domain = local.domain_parts[(local.domain_part_count - 1)] - next_level_domain = local.domain_parts[(local.domain_part_count - 2)] - find_zone = join(".", [local.next_level_domain, local.top_level_domain]) # extract the zone from the domain eg. "example.com" - zone = (var.zone == "" ? local.find_zone : var.zone) - - create = (var.create ? 1 : 0) # create is always an alias because it can only attached to a load balancer at this point in the project - select = (local.create == 1 ? 0 : 1) # select is the opposite of create - - validation_records = [ - for option in aws_acm_certificate.new[0].domain_validation_options : { - name = option.resource_record_name - record = option.resource_record_value - type = option.resource_record_type - zone_id = local.zone_id - } - ] - # Transform the list of maps into a map using the 'name' and 'type' as the key - validation_records_map = { for record in local.validation_records : "${record.name}_${record.type}" => record } + 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)], + ]) + zone = join(".", [ + for i in range(1, length(local.content_parts) - 1) : local.content_parts[i] + ]) -data "aws_route53_zone" "select" { - count = local.select_zone - name = local.find_zone - tags = { - Name = local.find_zone - } + # zone + zone_id = data.aws_route53_zone.select.id + + # domain record + create = (local.use == "create" ? 1 : 0) + select = (local.use == "select" ? 1 : 0) } -resource "aws_route53_zone" "new" { - count = local.add_zone - name = local.zone - tags = { - Name = local.zone - Owner = local.owner - } +data "aws_route53_zone" "select" { + name = "${local.zone}." } resource "aws_route53domains_registered_domain" "select" { - count = local.select + count = local.select domain_name = local.content } -# alias is a pre-generated "aws" domain eg. mylb.region.elb.amazonaws.com -# this cnames the pre-generated aws domain to the specified domain resource "aws_route53_record" "new" { depends_on = [ - aws_route53_zone.new, data.aws_route53_zone.select, ] count = local.create zone_id = local.zone_id name = local.content - type = "CNAME" - records = [local.alias] - ttl = 60 + type = "A" + ttl = 30 + records = [local.ip] } -# only generate a certificate if the domain is not already registered -resource "aws_acm_certificate" "new" { +resource "terraform_data" "dig_new_record" { depends_on = [ - aws_route53_zone.new, data.aws_route53_zone.select, aws_route53_record.new, ] - count = local.create - domain_name = local.content - validation_method = "DNS" - tags = { - Name = local.content - Owner = local.owner + count = local.create + triggers_replace = [ + local.content, + local.create, + ] + provisioner "local-exec" { + command = <<-EOT + #!/bin/bash + + DOMAIN='${local.content}' + #DNS_SERVER='${data.aws_route53_zone.select.primary_name_server}' + DNS_SERVER='8.8.8.8 1.1.1.1' + + # Timeout in seconds (5 minutes) + TIMEOUT=300 + + # Start time + START_TIME=$(date +%s) + + # Loop until timeout + while [ $(($(date +%s)-$START_TIME)) -lt $TIMEOUT ]; do + # Query the domain + RESULT="$(dig @$DNS_SERVER $DOMAIN +short)" + + # Check if the domain is available + if [ -n "$RESULT" ]; then + echo "Domain $DOMAIN is available at $DNS_SERVER. IP: $RESULT." + exit 0 + else + echo "Domain $DOMAIN is not available yet. Retrying..." + sleep 30 + fi + done + + # If the loop ends without finding the domain, it's not available + echo "Domain $DOMAIN is not available after 5 minutes." + exit 1 + EOT } - lifecycle { - create_before_destroy = true +} + + +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 "aws_route53_record" "cert_validation" { +resource "acme_certificate" "certificate" { depends_on = [ - aws_route53_zone.new, data.aws_route53_zone.select, aws_route53_record.new, - aws_acm_certificate.new, + terraform_data.dig_new_record, + acme_registration.reg, + tls_private_key.private_key, + tls_private_key.cert_private_key, + tls_cert_request.req, ] - count = local.create - zone_id = local.zone_id - name = tolist(aws_acm_certificate.new[0].domain_validation_options)[0].resource_record_name - type = tolist(aws_acm_certificate.new[0].domain_validation_options)[0].resource_record_type - records = [tolist(aws_acm_certificate.new[0].domain_validation_options)[0].resource_record_value] - ttl = 60 + account_key_pem = acme_registration.reg[0].account_key_pem + certificate_request_pem = tls_cert_request.req[0].cert_request_pem + pre_check_delay = 30 + recursive_nameservers = [ + "${data.aws_route53_zone.select.primary_name_server}:53", + "1.1.1.1", + "8.8.8.8", + ] + disable_complete_propagation = true + dns_challenge { + provider = "route53" + config = { + AWS_PROPAGATION_TIMEOUT = 60, + AWS_POLLING_INTERVAL = 10, + } + } } - -resource "aws_acm_certificate_validation" "certificate_validation" { +resource "terraform_data" "dig_cert_txt" { depends_on = [ - aws_route53_zone.new, data.aws_route53_zone.select, aws_route53_record.new, - aws_acm_certificate.new, - aws_route53_record.cert_validation, + terraform_data.dig_new_record, + acme_registration.reg, + tls_private_key.private_key, + tls_private_key.cert_private_key, + tls_cert_request.req, + #acme_certificate.certificate, # run at same time as certificate + ] + count = local.create + triggers_replace = [ + local.content, + local.create, ] - count = local.create - certificate_arn = aws_acm_certificate.new[0].arn - validation_record_fqdns = [aws_route53_record.cert_validation[0].fqdn] + provisioner "local-exec" { + command = <<-EOT + #!/bin/bash + + # Domain to query + DOMAIN='_acme-challenge.${local.content}' + #DNS_SERVER="${data.aws_route53_zone.select.primary_name_server}" + DNS_SERVER='1.1.1.1 8.8.8.8' + + # Timeout in seconds (5 minutes) + TIMEOUT=300 + + # Start time + START_TIME=$(date +%s) + + # Loop until timeout + while [ $(($(date +%s)-$START_TIME)) -lt $TIMEOUT ]; do + # Query the domain + RESULT="$(dig @$DNS_SERVER $DOMAIN TXT +short)" + + # Check if the domain is available + if [ -n "$RESULT" ]; then + echo "Domain $DOMAIN is available at $DNS_SERVER. TXT record: $RESULT." + exit 0 + else + echo "Domain $DOMAIN is not available yet. Retrying..." + sleep 30 + fi + done + + # If the loop ends without finding the domain, it's not available + echo "Domain $DOMAIN is not available after 5 minutes." + exit 1 + EOT + } } diff --git a/modules/domain/outputs.tf b/modules/domain/outputs.tf index 861348d..9595a73 100644 --- a/modules/domain/outputs.tf +++ b/modules/domain/outputs.tf @@ -1,11 +1,11 @@ output "id" { - value = (local.select == 1 ? local.content_id : aws_route53_record.new[0].id) + value = (local.select == 1 ? aws_route53domains_registered_domain.select[0].id : aws_route53_record.new[0].id) } output "zone_id" { - value = (local.select_zone == 1 ? data.aws_route53_zone.select[0].id : aws_route53_zone.new[0].id) + value = data.aws_route53_zone.select.id } output "zone" { - value = (local.select_zone == 1 ? data.aws_route53_zone.select[0] : aws_route53_zone.new[0]) + value = data.aws_route53_zone.select } output "domain" { value = (local.select == 1 ? aws_route53domains_registered_domain.select[0] : aws_route53_record.new[0]) diff --git a/modules/domain/variables.tf b/modules/domain/variables.tf index 3f9f05d..c0d52cb 100644 --- a/modules/domain/variables.tf +++ b/modules/domain/variables.tf @@ -1,13 +1,12 @@ -variable "owner" { +variable "use" { type = string - description = "The owner to tag the domain with after creation." - default = "terraform" -} - -variable "create" { - type = bool description = <<-EOT - Set to true to add a domain record. + 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 } @@ -20,20 +19,11 @@ variable "content" { EOT } -variable "alias" { - type = string - description = <<-EOT - An undesireable pre-generated domain name assigned to the project (for instance, from the creation of a network load balancer). - EOT - default = "" -} - -variable "zone" { +variable "ip" { type = string description = <<-EOT - Setting this will create a new zone with the given name. - The zone to add the domain to. - If this is not set we will attempt to find the zone based on the domain name. + 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 = "" } diff --git a/modules/domain/versions.tf b/modules/domain/versions.tf index 7c63b5d..511d46c 100644 --- a/modules/domain/versions.tf +++ b/modules/domain/versions.tf @@ -9,9 +9,13 @@ terraform { source = "hashicorp/aws" version = ">= 5.11" } - http = { - source = "hashicorp/http" - version = ">= 3.4" + tls = { + source = "hashicorp/tls" + version = "4.0.5" + } + acme = { + source = "vancluever/acme" + version = ">= 2.0" } } -} \ No newline at end of file +} diff --git a/modules/network_load_balancer/main.tf b/modules/network_load_balancer/main.tf index 827a29e..9dcaacb 100644 --- a/modules/network_load_balancer/main.tf +++ b/modules/network_load_balancer/main.tf @@ -1,150 +1,44 @@ locals { + use = var.use name = var.name - owner = var.owner security_group_id = var.security_group_id - subnet_id = var.subnet_id - vpc_id = var.vpc_id - create = (var.create ? 1 : 0) - select = (var.create ? 0 : 1) + 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 + count = local.create name = local.name internal = false load_balancer_type = "network" security_groups = [local.security_group_id] - subnets = [local.subnet_id] + subnets = local.subnet_ids tags = { - Name = local.name - Owner = local.owner - } -} -# only generate targets if the network load balancer is created -resource "aws_lb_target_group" "port80" { - depends_on = [ - aws_lb.new, - ] - count = local.create - port = 80 - protocol = "TCP" - vpc_id = local.vpc_id - name = "${local.name}-port80" - health_check { - protocol = "HTTP" - path = "/ping" # this is a k8s built in path that returns a 200 status code - interval = 10 - timeout = 6 - healthy_threshold = 3 - unhealthy_threshold = 3 - matcher = "200-399" - } - tags = { - Name = "${local.name}-port80" - Owner = local.owner - } -} -resource "aws_lb_target_group" "port443" { - depends_on = [ - aws_lb.new, - ] - count = local.create - port = 443 - protocol = "TCP" - vpc_id = local.vpc_id - name = "${local.name}-port443" - health_check { - protocol = "HTTP" - path = "/ping" # this is a k8s built in path that returns a 200 status code - interval = 10 - timeout = 6 - healthy_threshold = 3 - unhealthy_threshold = 3 - matcher = "200-399" - } - tags = { - Name = "${local.name}-port443" - Owner = local.owner - } -} -resource "aws_lb_target_group" "port6443" { - depends_on = [ - aws_lb.new, - ] - count = local.create - port = 6443 - protocol = "TCP" - vpc_id = local.vpc_id - name = "${local.name}-port6443" - health_check { - protocol = "HTTP" - path = "/ping" # this is a k8s built in path that returns a 200 status code - interval = 10 - timeout = 6 - healthy_threshold = 3 - unhealthy_threshold = 3 - matcher = "200-399" - } - tags = { - Name = "${local.name}-port6443" - Owner = local.owner - } -} -# only generate listeners if the network load balancer is created -resource "aws_lb_listener" "port80" { - depends_on = [ - aws_lb.new, - ] - count = local.create - load_balancer_arn = aws_lb.new[0].arn - port = "80" - protocol = "TCP" - default_action { - type = "forward" - target_group_arn = aws_lb_target_group.port80[0].arn - } - tags = { - Name = "${local.name}-port80" - Owner = local.owner - } -} -resource "aws_lb_listener" "port443" { - depends_on = [ - aws_lb.new, - ] - count = local.create - load_balancer_arn = aws_lb.new[0].arn - port = "443" - protocol = "TCP" - default_action { - type = "forward" - target_group_arn = aws_lb_target_group.port443[0].arn - } - tags = { - Name = "${local.name}-port443" - Owner = local.owner - } -} -resource "aws_lb_listener" "port6443" { - depends_on = [ - aws_lb.new, - ] - count = local.create - load_balancer_arn = aws_lb.new[0].arn - port = "6443" - protocol = "TCP" - default_action { - type = "forward" - target_group_arn = aws_lb_target_group.port6443[0].arn - } - tags = { - Name = "${local.name}-port6443" - Owner = local.owner + Name = local.name } } diff --git a/modules/network_load_balancer/outputs.tf b/modules/network_load_balancer/outputs.tf index 397b8d3..f7c329d 100644 --- a/modules/network_load_balancer/outputs.tf +++ b/modules/network_load_balancer/outputs.tf @@ -7,17 +7,6 @@ output "dns_name" { output "load_balancer" { value = (local.select == 1 ? data.aws_lb.selected[0] : aws_lb.new[0]) } -output "target_group_names" { - value = (local.create == 1 ? [ - aws_lb_target_group.port80[0].tags.Name, - aws_lb_target_group.port443[0].tags.Name, - aws_lb_target_group.port6443[0].tags.Name, - ] : []) -} -output "listener_names" { - value = (local.create == 1 ? [ - aws_lb_listener.port80[0].tags.Name, - aws_lb_listener.port443[0].tags.Name, - aws_lb_listener.port6443[0].tags.Name, - ] : []) +output "public_ip" { + value = local.public_ip } diff --git a/modules/network_load_balancer/variables.tf b/modules/network_load_balancer/variables.tf index 04f096d..39dd329 100644 --- a/modules/network_load_balancer/variables.tf +++ b/modules/network_load_balancer/variables.tf @@ -1,38 +1,33 @@ -variable "name" { +variable "use" { 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. + 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 "owner" { +variable "name" { type = string description = <<-EOT - The owner of the Load Balancer, this is used as refence in the AWS console. + 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 "create" { - type = bool - description = "Set to false to select a load balancer rather than creating one." -} variable "security_group_id" { type = string description = <<-EOT The security group id to attach to the Load Balancer. EOT + default = "" } -variable "subnet_id" { - type = string - description = <<-EOT - The subnet id to attach to the Load Balancer. - EOT -} -variable "vpc_id" { - type = string +variable "subnet_ids" { + type = list(string) description = <<-EOT - The VPC id to deploy the load balancer in. + 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 index 7c63b5d..24e1e1d 100644 --- a/modules/network_load_balancer/versions.tf +++ b/modules/network_load_balancer/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/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..297c531 --- /dev/null +++ b/notes/domain.md @@ -0,0 +1,66 @@ +# 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 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 7ddec4e..6e9fee5 100644 --- a/outputs.tf +++ b/outputs.tf @@ -65,49 +65,24 @@ output "security_group" { 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 - } : { - # no object found, but output types are normal - id = "" - arn = "" - key_name = "" - key_pair_id = "" - key_type = "" - public_key = "" - tags = tomap({ "" = "" }) - }) - description = <<-EOT - The SSH key object from AWS. - EOT -} - output "load_balancer" { value = (can(module.network_load_balancer[0].load_balancer) ? { - 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 = module.network_load_balancer[0].load_balancer.tags + 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 = module.network_load_balancer[0].load_balancer.tags } : { # no object found, but output types are normal - id = "" - arn = "" - dns_name = "" - zone_id = "" - security_groups = [] - subnets = [] - tags = tomap({ "" = "" }) + id = "" + arn = "" + dns_name = "" + zone_id = "" + security_groups = [] + subnets = [] + tags = tomap({ "" = "" }) }) description = <<-EOT The load balancer object from AWS. @@ -116,18 +91,36 @@ output "load_balancer" { output "domain" { value = (can(module.domain[0].domain) ? { - id = module.domain[0].domain.id - arn = module.domain[0].domain.arn - domain_name = module.domain[0].domain.domain_name - owner_id = module.domain[0].domain.owner_id + 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 = "" - arn = "" - domain_name = "" - owner_id = "" + id = "" + name = "" + zone_id = "" + type = "" + records = [] }) description = <<-EOT The domain object from AWS. EOT -} \ No newline at end of file +} + +# output "domain_zone" { +# value = (can(module.domain[0].zone) ? { +# arn = module.domain[0].zone.arn +# name = module.domain[0].zone.name +# primary_name_server = module.domain[0].zone.primary_name_server +# } : { +# # no object found, but output types are normal +# arn = "" +# name = "" +# primary_name_server = "" +# }) +# description = <<-EOT +# The domain zone object from AWS. +# EOT +# } \ No newline at end of file diff --git a/tests/loadbalancer_test.go b/tests/loadbalancer_test.go index 2b87b9a..3527a95 100644 --- a/tests/loadbalancer_test.go +++ b/tests/loadbalancer_test.go @@ -1,16 +1,13 @@ 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 TestLoadbalancer(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") @@ -18,14 +15,10 @@ func TestLoadbalancer(t *testing.T) { uniqueID = random.UniqueId() } directory := "loadbalancer" - region := "us-west-1" + region := "us-west-2" - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("tf-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/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/variables.tf b/variables.tf index 4102400..d590fe9 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 = "" + default = { "default" = { + cidr = "", # will be generated based on the vpc cidr + availability_zone = "", # just get the first one + public = false, + } } } -variable "subnet_public_ip" { - type = bool - description = <<-EOT - Set this to true to enable the subnet to have public IP addresses. - EOT - default = 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 = "" -} -variable "skip_subnet" { - type = bool - description = "Skip subnet generation, use with care." - default = false + 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'." + } } -# security group variable "security_group_name" { type = string description = <<-EOT @@ -110,58 +125,29 @@ variable "security_group_type" { If specified, must be one of: specific, internal, egress, or public. EOT default = "" -} -variable "security_group_ip" { - 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. - EOT - default = "" -} -variable "skip_security_group" { - type = bool - description = "Skip security group generation, use with care." - default = false -} -variable "skip_runner_ip" { - type = bool - description = "Skip generating ingress security group for the runner's ip" - default = false + 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'." + } } -# ssh key -variable "ssh_key_name" { - 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. - EOT - default = "" -} -variable "public_ssh_key" { +# load balancer +variable "load_balancer_use_strategy" { 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. + 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_ssh" { - type = bool - description = "Skip ssh key generation, use with care." - default = false -} - -# load balancer -variable "skip_lb" { - type = bool - description = "Set to false to deploy a load balancer." - default = true + 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 "load_balancer_name" { type = string @@ -171,30 +157,34 @@ variable "load_balancer_name" { 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 = "" -} -variable "select_lb" { - type = bool - description = "Set to true to select a load balancer rather than creating one." - default = false + default = "" } + # domain -variable "domain" { +variable "domain_use_strategy" { type = string description = <<-EOT - The domain name to retrieve or create. - There is no way to generate a domain name without something to attach it to, - so without a load balancer no domain will be created. - If a domain is given, and no load balancer name is given, we will attempt to find the domain. + 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 "zone" { +variable "domain" { type = string description = <<-EOT - The zone to add the domain to. - If this is specified we will try to generate a zone record. - If this isn't set we will attempt to find the zone based on the domain 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 ids of the load balancer target groups for this purpose. EOT default = "" } diff --git a/versions.tf b/versions.tf index 7c63b5d..466dd05 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" } } +} +provider "acme" { + server_url = "https://acme-staging-v02.api.letsencrypt.org/directory" } \ No newline at end of file From 1d04553754a8f3a9776e67b1ac10494ea416c926 Mon Sep 17 00:00:00 2001 From: matttrach Date: Fri, 29 Mar 2024 23:54:12 -0500 Subject: [PATCH 3/6] feat: add lb domain and certs Signed-off-by: matttrach --- README.md | 11 ++ examples/basic/main.tf | 12 +- examples/basic/outputs.tf | 17 +- examples/basic/variables.tf | 7 +- examples/basic/versions.tf | 4 - examples/domain/main.tf | 5 +- examples/domain/outputs.tf | 8 +- examples/domain/variables.tf | 11 ++ examples/domain/versions.tf | 4 - examples/loadbalancer/versions.tf | 4 - examples/override/main.tf | 19 -- examples/override/outputs.tf | 15 -- examples/override/variables.tf | 9 - examples/override/versions.tf | 17 -- examples/personal/main.tf | 23 --- examples/personal/outputs.tf | 15 -- examples/personal/variables.tf | 9 - examples/project/main.tf | 22 --- examples/project/outputs.tf | 15 -- .../{skipsubnet => securitygroup}/main.tf | 16 +- examples/securitygroup/outputs.tf | 18 ++ .../{project => securitygroup}/variables.tf | 6 +- examples/{sgip => securitygroup}/versions.tf | 4 - examples/sgip/outputs.tf | 15 -- examples/sgip/variables.tf | 3 - examples/skipip/main.tf | 26 --- examples/skipip/outputs.tf | 15 -- examples/skipip/versions.tf | 17 -- examples/skipsecuritygroup/main.tf | 26 --- examples/skipsecuritygroup/outputs.tf | 15 -- examples/skipsecuritygroup/variables.tf | 9 - examples/skipsecuritygroup/versions.tf | 17 -- examples/skipssh/main.tf | 24 --- examples/skipssh/outputs.tf | 15 -- examples/skipssh/versions.tf | 17 -- examples/skipsubnet/outputs.tf | 15 -- examples/skipsubnet/variables.tf | 9 - examples/skipsubnet/versions.tf | 17 -- examples/skipvpc/main.tf | 10 +- examples/skipvpc/variables.tf | 7 +- examples/skipvpc/versions.tf | 4 - examples/specifyip/main.tf | 30 ---- examples/specifyip/outputs.tf | 15 -- examples/specifyip/variables.tf | 12 -- examples/specifyip/versions.tf | 17 -- examples/subnets/main.tf | 37 ++++ examples/subnets/outputs.tf | 18 ++ examples/{skipip => subnets}/variables.tf | 0 examples/{personal => subnets}/versions.tf | 4 - examples/{sgip => vpc}/main.tf | 13 +- examples/vpc/outputs.tf | 18 ++ examples/{skipssh => vpc}/variables.tf | 3 +- examples/{project => vpc}/versions.tf | 4 - flake.lock | 6 +- flake.nix | 1 + main.tf | 3 +- modules/domain/main.tf | 164 +++++++----------- modules/domain/outputs.tf | 10 +- modules/domain/variables.tf | 10 ++ notes/domain.md | 21 +++ outputs.tf | 52 +++--- tests/basic_test.go | 8 +- tests/domain_test.go | 9 +- tests/loadbalancer_test.go | 2 +- tests/override_test.go | 41 ----- ...personal_test.go => securitygroup_test.go} | 14 +- tests/skip_test.go | 110 ------------ tests/specifyip_test.go | 50 ------ tests/{project_test.go => subnets_test.go} | 13 +- tests/{sgip_test.go => vpc_test.go} | 7 +- variables.tf | 11 +- 71 files changed, 322 insertions(+), 913 deletions(-) delete mode 100644 examples/override/main.tf delete mode 100644 examples/override/outputs.tf delete mode 100644 examples/override/variables.tf delete mode 100644 examples/override/versions.tf delete mode 100644 examples/personal/main.tf delete mode 100644 examples/personal/outputs.tf delete mode 100644 examples/personal/variables.tf delete mode 100644 examples/project/main.tf delete mode 100644 examples/project/outputs.tf rename examples/{skipsubnet => securitygroup}/main.tf (51%) create mode 100644 examples/securitygroup/outputs.tf rename examples/{project => securitygroup}/variables.tf (71%) rename examples/{sgip => securitygroup}/versions.tf (75%) delete mode 100644 examples/sgip/outputs.tf delete mode 100644 examples/sgip/variables.tf delete mode 100644 examples/skipip/main.tf delete mode 100644 examples/skipip/outputs.tf delete mode 100644 examples/skipip/versions.tf delete mode 100644 examples/skipsecuritygroup/main.tf delete mode 100644 examples/skipsecuritygroup/outputs.tf delete mode 100644 examples/skipsecuritygroup/variables.tf delete mode 100644 examples/skipsecuritygroup/versions.tf delete mode 100644 examples/skipssh/main.tf delete mode 100644 examples/skipssh/outputs.tf delete mode 100644 examples/skipssh/versions.tf delete mode 100644 examples/skipsubnet/outputs.tf delete mode 100644 examples/skipsubnet/variables.tf delete mode 100644 examples/skipsubnet/versions.tf delete mode 100644 examples/specifyip/main.tf delete mode 100644 examples/specifyip/outputs.tf delete mode 100644 examples/specifyip/variables.tf delete mode 100644 examples/specifyip/versions.tf create mode 100644 examples/subnets/main.tf create mode 100644 examples/subnets/outputs.tf rename examples/{skipip => subnets}/variables.tf (100%) rename examples/{personal => subnets}/versions.tf (75%) rename examples/{sgip => vpc}/main.tf (55%) create mode 100644 examples/vpc/outputs.tf rename examples/{skipssh => vpc}/variables.tf (93%) rename examples/{project => vpc}/versions.tf (75%) delete mode 100644 tests/override_test.go rename tests/{personal_test.go => securitygroup_test.go} (56%) delete mode 100644 tests/skip_test.go delete mode 100644 tests/specifyip_test.go rename tests/{project_test.go => subnets_test.go} (53%) rename tests/{sgip_test.go => vpc_test.go} (81%) 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..d432a19 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -3,26 +3,22 @@ provider "aws" { default_tags { tags = { 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 + domain = 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 index 1a1d623..c8b13a5 100644 --- a/examples/domain/main.tf +++ b/examples/domain/main.tf @@ -11,6 +11,8 @@ locals { identifier = var.identifier name = "tf-dns-${local.identifier}" owner = "terraform-ci@suse.com" + domain = 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" { @@ -20,5 +22,6 @@ module "this" { security_group_name = local.name security_group_type = "project" load_balancer_name = local.name - domain = "${local.name}.eng.rancher.space" + 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 index d000b32..ea86e97 100644 --- a/examples/domain/outputs.tf +++ b/examples/domain/outputs.tf @@ -10,10 +10,10 @@ 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 } \ No newline at end of file diff --git a/examples/domain/variables.tf b/examples/domain/variables.tf index 379bf39..768d5c6 100644 --- a/examples/domain/variables.tf +++ b/examples/domain/variables.tf @@ -1,3 +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/domain/versions.tf b/examples/domain/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/domain/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/loadbalancer/versions.tf b/examples/loadbalancer/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/loadbalancer/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/override/versions.tf b/examples/override/versions.tf deleted file mode 100644 index 7c63b5d..0000000 --- a/examples/override/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/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/skipsubnet/main.tf b/examples/securitygroup/main.tf similarity index 51% rename from examples/skipsubnet/main.tf rename to examples/securitygroup/main.tf index 6c4a5d5..c57627b 100644 --- a/examples/skipsubnet/main.tf +++ b/examples/securitygroup/main.tf @@ -1,26 +1,22 @@ + provider "aws" { default_tags { tags = { Id = local.identifier + Owner = "terraform-ci@suse.com" } } } locals { identifier = var.identifier - name = "tf-skipssh-${local.identifier}" - key = var.key - key_name = var.key_name + name = "tf-securitygroup-${local.identifier}" } -# generate vpc, security group, and ssh key +# 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.0.1" - public_ssh_key = local.key - ssh_key_name = local.key_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/project/variables.tf b/examples/securitygroup/variables.tf similarity index 71% rename from examples/project/variables.tf rename to examples/securitygroup/variables.tf index d1d123b..d7fa9b1 100644 --- a/examples/project/variables.tf +++ b/examples/securitygroup/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/securitygroup/versions.tf similarity index 75% rename from examples/sgip/versions.tf rename to examples/securitygroup/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/sgip/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/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/sgip/variables.tf b/examples/sgip/variables.tf deleted file mode 100644 index 379bf39..0000000 --- a/examples/sgip/variables.tf +++ /dev/null @@ -1,3 +0,0 @@ -variable "identifier" { - type = string -} \ No newline at end of file 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/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..4892717 100644 --- a/examples/skipvpc/main.tf +++ b/examples/skipvpc/main.tf @@ -2,20 +2,14 @@ provider "aws" { default_tags { tags = { 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 + vpc_use_strategy = "skip" # everything depending on vpc is skipped implicitly } 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..db61aca --- /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-subnets-${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/skipip/variables.tf b/examples/subnets/variables.tf similarity index 100% rename from examples/skipip/variables.tf rename to examples/subnets/variables.tf diff --git a/examples/personal/versions.tf b/examples/subnets/versions.tf similarity index 75% rename from examples/personal/versions.tf rename to examples/subnets/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/personal/versions.tf +++ b/examples/subnets/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/vpc/main.tf similarity index 55% rename from examples/sgip/main.tf rename to examples/vpc/main.tf index 10ef52f..16508fd 100644 --- a/examples/sgip/main.tf +++ b/examples/vpc/main.tf @@ -1,23 +1,20 @@ + provider "aws" { default_tags { tags = { Id = local.identifier + Owner = "terraform-ci@suse.com" } } } locals { identifier = var.identifier - name = "tf-sgip-${local.identifier}" + name = "tf-vpc-${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 + 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/skipssh/variables.tf b/examples/vpc/variables.tf similarity index 93% rename from examples/skipssh/variables.tf rename to examples/vpc/variables.tf index 379bf39..d76b092 100644 --- a/examples/skipssh/variables.tf +++ b/examples/vpc/variables.tf @@ -1,3 +1,4 @@ variable "identifier" { type = string -} \ No newline at end of file +} + diff --git a/examples/project/versions.tf b/examples/vpc/versions.tf similarity index 75% rename from examples/project/versions.tf rename to examples/vpc/versions.tf index 7c63b5d..24e1e1d 100644 --- a/examples/project/versions.tf +++ b/examples/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/flake.lock b/flake.lock index fa7a6a5..cfc62a7 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711401922, - "narHash": "sha256-QoQqXoj8ClGo0sqD/qWKFWezgEwUL0SUh37/vY2jNhc=", + "lastModified": 1711624657, + "narHash": "sha256-IViG6BKCJY/I6oRNfAANf/QitYylepQSCzgam0TF+bs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "07262b18b97000d16a4bdb003418bd2fb067a932", + "rev": "72c6ed328aa4e5d9151b1a512f6ad83aca7529fa", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5aafb76..f916711 100644 --- a/flake.nix +++ b/flake.nix @@ -109,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 9dbbfcb..5313a33 100644 --- a/main.tf +++ b/main.tf @@ -63,6 +63,7 @@ locals { # domain domain = var.domain + domain_zone = var.domain_zone # load balancer load_balancer_name = var.load_balancer_name @@ -120,7 +121,6 @@ module "network_load_balancer" { name = local.load_balancer_name security_group_id = module.security_group[0].id subnet_ids = [for subnet in module.subnet : subnet.id] - vpc_id = module.vpc[0].id } module "domain" { @@ -135,4 +135,5 @@ module "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 index e928ab1..173b1b1 100644 --- a/modules/domain/main.tf +++ b/modules/domain/main.tf @@ -9,12 +9,18 @@ locals { local.content_parts[(length(local.content_parts) - 2)], local.content_parts[(length(local.content_parts) - 1)], ]) - zone = join(".", [ - for i in range(1, length(local.content_parts) - 1) : local.content_parts[i] + subdomain = local.content_parts[0] + found_zone = join(".", [ + for part in local.content_parts : part if part != local.subdomain ]) # zone - zone_id = data.aws_route53_zone.select.id + 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) @@ -22,7 +28,27 @@ locals { } data "aws_route53_zone" "select" { - name = "${local.zone}." + 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" { @@ -33,6 +59,7 @@ resource "aws_route53domains_registered_domain" "select" { resource "aws_route53_record" "new" { depends_on = [ data.aws_route53_zone.select, + aws_route53_zone.new, ] count = local.create zone_id = local.zone_id @@ -42,53 +69,6 @@ resource "aws_route53_record" "new" { records = [local.ip] } -resource "terraform_data" "dig_new_record" { - depends_on = [ - data.aws_route53_zone.select, - aws_route53_record.new, - ] - count = local.create - triggers_replace = [ - local.content, - local.create, - ] - provisioner "local-exec" { - command = <<-EOT - #!/bin/bash - - DOMAIN='${local.content}' - #DNS_SERVER='${data.aws_route53_zone.select.primary_name_server}' - DNS_SERVER='8.8.8.8 1.1.1.1' - - # Timeout in seconds (5 minutes) - TIMEOUT=300 - - # Start time - START_TIME=$(date +%s) - - # Loop until timeout - while [ $(($(date +%s)-$START_TIME)) -lt $TIMEOUT ]; do - # Query the domain - RESULT="$(dig @$DNS_SERVER $DOMAIN +short)" - - # Check if the domain is available - if [ -n "$RESULT" ]; then - echo "Domain $DOMAIN is available at $DNS_SERVER. IP: $RESULT." - exit 0 - else - echo "Domain $DOMAIN is not available yet. Retrying..." - sleep 30 - fi - done - - # If the loop ends without finding the domain, it's not available - echo "Domain $DOMAIN is not available after 5 minutes." - exit 1 - EOT - } -} - - resource "tls_private_key" "private_key" { count = local.create algorithm = "RSA" @@ -114,82 +94,66 @@ resource "tls_cert_request" "req" { } } -resource "acme_certificate" "certificate" { +resource "acme_certificate" "new" { depends_on = [ data.aws_route53_zone.select, + aws_route53_zone.new, aws_route53_record.new, - terraform_data.dig_new_record, 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 - pre_check_delay = 30 recursive_nameservers = [ - "${data.aws_route53_zone.select.primary_name_server}:53", - "1.1.1.1", - "8.8.8.8", + "${local.zone_resource.primary_name_server}:53", ] disable_complete_propagation = true dns_challenge { provider = "route53" config = { - AWS_PROPAGATION_TIMEOUT = 60, - AWS_POLLING_INTERVAL = 10, + AWS_PROPAGATION_TIMEOUT = 2400, + AWS_POLLING_INTERVAL = 60, + AWS_HOSTED_ZONE_ID = local.zone_id, } } } -resource "terraform_data" "dig_cert_txt" { + +resource "aws_iam_server_certificate" "new" { depends_on = [ data.aws_route53_zone.select, + aws_route53_zone.new, aws_route53_record.new, - terraform_data.dig_new_record, acme_registration.reg, tls_private_key.private_key, tls_private_key.cert_private_key, tls_cert_request.req, - #acme_certificate.certificate, # run at same time as certificate - ] - count = local.create - triggers_replace = [ - local.content, - local.create, + acme_certificate.new, ] - provisioner "local-exec" { - command = <<-EOT - #!/bin/bash - - # Domain to query - DOMAIN='_acme-challenge.${local.content}' - #DNS_SERVER="${data.aws_route53_zone.select.primary_name_server}" - DNS_SERVER='1.1.1.1 8.8.8.8' - - # Timeout in seconds (5 minutes) - TIMEOUT=300 - - # Start time - START_TIME=$(date +%s) + 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 + } +} - # Loop until timeout - while [ $(($(date +%s)-$START_TIME)) -lt $TIMEOUT ]; do - # Query the domain - RESULT="$(dig @$DNS_SERVER $DOMAIN TXT +short)" - - # Check if the domain is available - if [ -n "$RESULT" ]; then - echo "Domain $DOMAIN is available at $DNS_SERVER. TXT record: $RESULT." - exit 0 - else - echo "Domain $DOMAIN is not available yet. Retrying..." - sleep 30 - fi - done +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, - # If the loop ends without finding the domain, it's not available - echo "Domain $DOMAIN is not available after 5 minutes." - exit 1 - EOT - } + ] + count = local.select + name_prefix = local.content + latest = true } diff --git a/modules/domain/outputs.tf b/modules/domain/outputs.tf index 9595a73..4e06cfb 100644 --- a/modules/domain/outputs.tf +++ b/modules/domain/outputs.tf @@ -2,11 +2,17 @@ output "id" { value = (local.select == 1 ? aws_route53domains_registered_domain.select[0].id : aws_route53_record.new[0].id) } output "zone_id" { - value = data.aws_route53_zone.select.id + value = local.zone_id } output "zone" { - value = data.aws_route53_zone.select + 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 index c0d52cb..d47dc29 100644 --- a/modules/domain/variables.tf +++ b/modules/domain/variables.tf @@ -27,3 +27,13 @@ variable "ip" { 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/notes/domain.md b/notes/domain.md index 297c531..eb0a4e5 100644 --- a/notes/domain.md +++ b/notes/domain.md @@ -64,3 +64,24 @@ ## 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/outputs.tf b/outputs.tf index 6e9fee5..4e1be25 100644 --- a/outputs.tf +++ b/outputs.tf @@ -5,14 +5,14 @@ output "vpc" { cidr_block = module.vpc[0].vpc.cidr_block ipv6_cidr_block = module.vpc[0].vpc.ipv6_cidr_block main_route_table_id = module.vpc[0].vpc.main_route_table_id - tags = module.vpc[0].vpc.tags + tags_all = module.vpc[0].vpc.tags_all } : { id = "" arn = "" cidr_block = "" ipv6_cidr_block = "" main_route_table_id = "" - tags = tomap({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The VPC object from AWS. @@ -28,7 +28,7 @@ output "subnet" { 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 + tags_all = module.subnet[0].subnet.tags_all } : { # no object found, but output types are normal id = "" @@ -38,7 +38,7 @@ output "subnet" { cidr_block = "" ipv6_cidr_block = "" vpc_id = "" - tags = tomap({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The subnet object from AWS. @@ -51,14 +51,14 @@ output "security_group" { 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 + 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({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The security group object from AWS. @@ -73,7 +73,7 @@ output "load_balancer" { 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 = module.network_load_balancer[0].load_balancer.tags + tags_all = module.network_load_balancer[0].load_balancer.tags_all } : { # no object found, but output types are normal id = "" @@ -82,7 +82,7 @@ output "load_balancer" { zone_id = "" security_groups = [] subnets = [] - tags = tomap({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The load balancer object from AWS. @@ -109,18 +109,24 @@ output "domain" { EOT } -# output "domain_zone" { -# value = (can(module.domain[0].zone) ? { -# arn = module.domain[0].zone.arn -# name = module.domain[0].zone.name -# primary_name_server = module.domain[0].zone.primary_name_server -# } : { -# # no object found, but output types are normal -# arn = "" -# name = "" -# primary_name_server = "" -# }) -# description = <<-EOT -# The domain zone object from AWS. -# EOT -# } \ No newline at end of file +output "certificate" { + value = (can(module.domain[0].certificate) ? { + 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 = "" + name = "" + expiration = "" + upload_date = "" + tags_all = tomap({ "" = "" }) + }) + description = <<-EOT + 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/domain_test.go b/tests/domain_test.go index a9592f7..1b86086 100644 --- a/tests/domain_test.go +++ b/tests/domain_test.go @@ -1,12 +1,10 @@ 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" ) @@ -17,17 +15,16 @@ func TestDomain(t *testing.T) { if uniqueID == "" { uniqueID = random.UniqueId() } + domain := os.Getenv("DOMAIN") directory := "domain" region := "us-west-1" - keyPair := ssh.GenerateRSAKeyPair(t, 2048) - keyPairName := fmt.Sprintf("tf-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) defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) diff --git a/tests/loadbalancer_test.go b/tests/loadbalancer_test.go index 3527a95..f3bd4da 100644 --- a/tests/loadbalancer_test.go +++ b/tests/loadbalancer_test.go @@ -15,7 +15,7 @@ func TestLoadbalancer(t *testing.T) { uniqueID = random.UniqueId() } directory := "loadbalancer" - region := "us-west-2" + region := "us-west-1" terraformVars := map[string]interface{}{ "identifier": uniqueID, 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 56% rename from tests/personal_test.go rename to tests/securitygroup_test.go index c2035ec..304d491 100644 --- a/tests/personal_test.go +++ b/tests/securitygroup_test.go @@ -10,24 +10,20 @@ import ( "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 TestBasic(t *testing.T) { t.Parallel() + domain := os.Getenv("DOMAIN") uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "personal" + 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/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/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/project_test.go b/tests/subnets_test.go similarity index 53% rename from tests/project_test.go rename to tests/subnets_test.go index ba5c81d..97dfd0b 100644 --- a/tests/project_test.go +++ b/tests/subnets_test.go @@ -8,24 +8,17 @@ 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) { +func TestBasic(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "project" - region := "us-west-1" - owner := "terraform-ci@suse.com" + directory := "basic" + region := "us-west-2" // This regoin has at least 3 availability zones - keyPair := setupKeyPair(t, directory, region, owner, uniqueID) - defer teardownKeyPair(t, keyPair) terraformVars := map[string]interface{}{ "identifier": uniqueID, - "key_name": keyPair.Name, } terraformOptions := setup(t, directory, region, terraformVars) defer teardown(t, directory) diff --git a/tests/sgip_test.go b/tests/vpc_test.go similarity index 81% rename from tests/sgip_test.go rename to tests/vpc_test.go index 67f1f16..4d88d3a 100644 --- a/tests/sgip_test.go +++ b/tests/vpc_test.go @@ -1,6 +1,7 @@ package test import ( + "fmt" "os" "testing" @@ -8,14 +9,14 @@ import ( "github.com/gruntwork-io/terratest/modules/terraform" ) -// generate a security group without generating a subnet, specifying an ip -func TestSgip(t *testing.T) { +// this test generates all objects, no overrides +func TestBasic(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "sgip" + directory := "basic" region := "us-west-1" terraformVars := map[string]interface{}{ diff --git a/variables.tf b/variables.tf index d590fe9..20c85b6 100644 --- a/variables.tf +++ b/variables.tf @@ -184,7 +184,16 @@ variable "domain" { 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 ids of the load balancer target groups for this purpose. + We output the arn of the load balancer for this purpose. EOT default = "" } +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 From 246ec8facf182bd2ca43f9960c88b2b0d32e046d Mon Sep 17 00:00:00 2001 From: matttrach Date: Fri, 29 Mar 2024 23:55:57 -0500 Subject: [PATCH 4/6] fix: format and lint Signed-off-by: matttrach --- examples/basic/main.tf | 6 ++--- examples/domain/variables.tf | 2 +- examples/securitygroup/main.tf | 12 +++++----- examples/securitygroup/variables.tf | 3 --- examples/skipvpc/main.tf | 4 ++-- examples/subnets/main.tf | 16 +++++++------- examples/vpc/main.tf | 4 ++-- main.tf | 4 ++-- modules/domain/main.tf | 22 +++++++++---------- outputs.tf | 34 ++++++++++++++--------------- 10 files changed, 52 insertions(+), 55 deletions(-) diff --git a/examples/basic/main.tf b/examples/basic/main.tf index d432a19..a44c84f 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -2,7 +2,7 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier Owner = "terraform-ci@suse.com" } } @@ -19,6 +19,6 @@ module "this" { 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 = "egress" - load_balancer_name = local.name - domain = local.domain + load_balancer_name = local.name + domain = local.domain } diff --git a/examples/domain/variables.tf b/examples/domain/variables.tf index 768d5c6..7742e67 100644 --- a/examples/domain/variables.tf +++ b/examples/domain/variables.tf @@ -6,7 +6,7 @@ variable "identifier" { # description = "The domain zone to use for the domain record. eg. example.com for domain 'test.example.com'" # } variable "domain" { - type = string + type = string description = <<-EOT The domain to use for the domain record. eg. 'test.example.com'. This example assumes that the zone already exists. diff --git a/examples/securitygroup/main.tf b/examples/securitygroup/main.tf index c57627b..b57b24a 100644 --- a/examples/securitygroup/main.tf +++ b/examples/securitygroup/main.tf @@ -2,7 +2,7 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier Owner = "terraform-ci@suse.com" } } @@ -13,10 +13,10 @@ locals { } # 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" + 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/variables.tf b/examples/securitygroup/variables.tf index d7fa9b1..e960ce9 100644 --- a/examples/securitygroup/variables.tf +++ b/examples/securitygroup/variables.tf @@ -1,6 +1,3 @@ variable "identifier" { type = string } -variable "domain" { - type = string -} diff --git a/examples/skipvpc/main.tf b/examples/skipvpc/main.tf index 4892717..0913845 100644 --- a/examples/skipvpc/main.tf +++ b/examples/skipvpc/main.tf @@ -1,7 +1,7 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier Owner = "terraform-ci@suse.com" } } @@ -10,6 +10,6 @@ locals { identifier = var.identifier } module "this" { - source = "../../" + source = "../../" vpc_use_strategy = "skip" # everything depending on vpc is skipped implicitly } diff --git a/examples/subnets/main.tf b/examples/subnets/main.tf index db61aca..dc2250f 100644 --- a/examples/subnets/main.tf +++ b/examples/subnets/main.tf @@ -2,7 +2,7 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier Owner = "terraform-ci@suse.com" } } @@ -16,21 +16,21 @@ 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 = { + subnets = { "subnetA" = { - cidr = "10.0.255.0/26" + 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 + public = false # when true AWS will automatically provision public ips for instances in this subnet } "subnetB" = { - cidr = "10.0.255.64/26" + 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 + public = false # when true AWS will automatically provision public ips for instances in this subnet } "subnetC" = { - cidr = "10.0.255.128/26" + 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 + 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/vpc/main.tf b/examples/vpc/main.tf index 16508fd..de85b64 100644 --- a/examples/vpc/main.tf +++ b/examples/vpc/main.tf @@ -2,7 +2,7 @@ provider "aws" { default_tags { tags = { - Id = local.identifier + Id = local.identifier Owner = "terraform-ci@suse.com" } } @@ -16,5 +16,5 @@ 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 + subnet_use_strategy = "skip" # everything depending on subnet is skipped implicitly } diff --git a/main.tf b/main.tf index 5313a33..7ec44b2 100644 --- a/main.tf +++ b/main.tf @@ -62,7 +62,7 @@ locals { security_group_type = var.security_group_type # domain - domain = var.domain + domain = var.domain domain_zone = var.domain_zone # load balancer @@ -135,5 +135,5 @@ module "domain" { use = local.domain_use_strategy content = lower(local.domain) ip = module.network_load_balancer[0].public_ip - zone = local.domain_zone + zone = local.domain_zone } diff --git a/modules/domain/main.tf b/modules/domain/main.tf index 173b1b1..294c8fe 100644 --- a/modules/domain/main.tf +++ b/modules/domain/main.tf @@ -15,11 +15,11 @@ locals { ]) # 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_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 @@ -29,7 +29,7 @@ locals { data "aws_route53_zone" "select" { count = local.zone_select - name = local.zone + name = local.zone } resource "aws_route53_zone" "new" { count = local.zone_create @@ -104,7 +104,7 @@ resource "acme_certificate" "new" { tls_private_key.cert_private_key, tls_cert_request.req, ] - count = local.create + 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 = [ @@ -132,10 +132,10 @@ resource "aws_iam_server_certificate" "new" { 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 + 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 } diff --git a/outputs.tf b/outputs.tf index 4e1be25..4a1fff6 100644 --- a/outputs.tf +++ b/outputs.tf @@ -5,14 +5,14 @@ output "vpc" { cidr_block = module.vpc[0].vpc.cidr_block ipv6_cidr_block = module.vpc[0].vpc.ipv6_cidr_block main_route_table_id = module.vpc[0].vpc.main_route_table_id - tags_all = module.vpc[0].vpc.tags_all + tags_all = module.vpc[0].vpc.tags_all } : { id = "" arn = "" cidr_block = "" ipv6_cidr_block = "" main_route_table_id = "" - tags_all = tomap({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The VPC object from AWS. @@ -28,7 +28,7 @@ output "subnet" { 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_all = module.subnet[0].subnet.tags_all + tags_all = module.subnet[0].subnet.tags_all } : { # no object found, but output types are normal id = "" @@ -38,7 +38,7 @@ output "subnet" { cidr_block = "" ipv6_cidr_block = "" vpc_id = "" - tags_all = tomap({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The subnet object from AWS. @@ -47,18 +47,18 @@ 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_all = module.security_group[0].security_group.tags_all + 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_all = tomap({ "" = "" }) + id = "" + arn = "" + name = "" + vpc_id = "" + tags_all = tomap({ "" = "" }) }) description = <<-EOT The security group object from AWS. @@ -73,7 +73,7 @@ output "load_balancer" { 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 + tags_all = module.network_load_balancer[0].load_balancer.tags_all } : { # no object found, but output types are normal id = "" @@ -82,7 +82,7 @@ output "load_balancer" { zone_id = "" security_groups = [] subnets = [] - tags_all = tomap({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The load balancer object from AWS. @@ -124,7 +124,7 @@ output "certificate" { name = "" expiration = "" upload_date = "" - tags_all = tomap({ "" = "" }) + tags_all = tomap({ "" = "" }) }) description = <<-EOT The certificate object from AWS. From f088932ee7fd6ac00b06ca7c50be8b5f9c7db2b2 Mon Sep 17 00:00:00 2001 From: matttrach Date: Sun, 31 Mar 2024 00:59:14 -0500 Subject: [PATCH 5/6] fix: full tests pass Signed-off-by: matttrach --- examples/basic/main.tf | 4 +-- examples/domain/main.tf | 4 +-- examples/domain/outputs.tf | 13 ++++---- examples/loadbalancer/main.tf | 2 +- examples/loadbalancer/outputs.tf | 15 +++++---- examples/securitygroup/main.tf | 2 +- examples/selectvpc/main.tf | 38 ++++++++++++++++++++++ examples/selectvpc/outputs.tf | 18 ++++++++++ examples/selectvpc/variables.tf | 6 ++++ examples/selectvpc/versions.tf | 17 ++++++++++ examples/skipvpc/outputs.tf | 17 ++++++---- examples/subnets/main.tf | 2 +- examples/vpc/main.tf | 2 +- flake.lock | 6 ++-- outputs.tf | 56 +++++++++++++++++--------------- tests/securitygroup_test.go | 8 ++--- tests/selectvpc_test.go | 30 +++++++++++++++++ tests/skipvpc_test.go | 28 ++++++++++++++++ tests/subnets_test.go | 4 +-- tests/vpc_test.go | 5 ++- variables.tf | 2 +- versions.tf | 6 ++-- 22 files changed, 212 insertions(+), 73 deletions(-) create mode 100644 examples/selectvpc/main.tf create mode 100644 examples/selectvpc/outputs.tf create mode 100644 examples/selectvpc/variables.tf create mode 100644 examples/selectvpc/versions.tf create mode 100644 tests/selectvpc_test.go create mode 100644 tests/skipvpc_test.go diff --git a/examples/basic/main.tf b/examples/basic/main.tf index a44c84f..d01961c 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -9,8 +9,8 @@ provider "aws" { } locals { identifier = var.identifier - name = "tf-basic-${local.identifier}" - domain = var.domain + 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" { diff --git a/examples/domain/main.tf b/examples/domain/main.tf index c8b13a5..b80a7a0 100644 --- a/examples/domain/main.tf +++ b/examples/domain/main.tf @@ -9,9 +9,9 @@ provider "aws" { } locals { identifier = var.identifier - name = "tf-dns-${local.identifier}" + name = "tf-${local.identifier}" owner = "terraform-ci@suse.com" - domain = var.domain + 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) diff --git a/examples/domain/outputs.tf b/examples/domain/outputs.tf index ea86e97..9c7a45e 100644 --- a/examples/domain/outputs.tf +++ b/examples/domain/outputs.tf @@ -1,19 +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 "load_balancer" { value = module.this.load_balancer } - output "domain" { value = module.this.domain -} \ No newline at end of file +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/loadbalancer/main.tf b/examples/loadbalancer/main.tf index 73f5274..f0bf423 100644 --- a/examples/loadbalancer/main.tf +++ b/examples/loadbalancer/main.tf @@ -9,7 +9,7 @@ provider "aws" { } locals { identifier = var.identifier - name = "tf-lb-${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" { diff --git a/examples/loadbalancer/outputs.tf b/examples/loadbalancer/outputs.tf index 23082d0..9c7a45e 100644 --- a/examples/loadbalancer/outputs.tf +++ b/examples/loadbalancer/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 "load_balancer" { value = module.this.load_balancer -} \ No newline at end of file +} +output "domain" { + value = module.this.domain +} +output "certificate" { + value = module.this.certificate +} diff --git a/examples/securitygroup/main.tf b/examples/securitygroup/main.tf index b57b24a..58a9e10 100644 --- a/examples/securitygroup/main.tf +++ b/examples/securitygroup/main.tf @@ -9,7 +9,7 @@ provider "aws" { } locals { identifier = var.identifier - name = "tf-securitygroup-${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" { 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/selectvpc/variables.tf b/examples/selectvpc/variables.tf new file mode 100644 index 0000000..d7fa9b1 --- /dev/null +++ b/examples/selectvpc/variables.tf @@ -0,0 +1,6 @@ +variable "identifier" { + type = string +} +variable "domain" { + type = string +} diff --git a/examples/selectvpc/versions.tf b/examples/selectvpc/versions.tf new file mode 100644 index 0000000..1399985 --- /dev/null +++ b/examples/selectvpc/versions.tf @@ -0,0 +1,17 @@ +terraform { + required_version = ">= 1.5.0, < 1.6" + required_providers { + local = { + source = "hashicorp/local" + version = ">= 2.4" + } + aws = { + source = "hashicorp/aws" + version = ">= 5.11" + } + acme = { + source = "vancluever/acme" + version = ">= 2.0" + } + } +} \ No newline at end of file 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/subnets/main.tf b/examples/subnets/main.tf index dc2250f..a4f34ec 100644 --- a/examples/subnets/main.tf +++ b/examples/subnets/main.tf @@ -9,7 +9,7 @@ provider "aws" { } locals { identifier = var.identifier - name = "tf-subnets-${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" { diff --git a/examples/vpc/main.tf b/examples/vpc/main.tf index de85b64..9ef08f8 100644 --- a/examples/vpc/main.tf +++ b/examples/vpc/main.tf @@ -9,7 +9,7 @@ provider "aws" { } locals { identifier = var.identifier - name = "tf-vpc-${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" { diff --git a/flake.lock b/flake.lock index cfc62a7..40a5b6b 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711624657, - "narHash": "sha256-IViG6BKCJY/I6oRNfAANf/QitYylepQSCzgam0TF+bs=", + "lastModified": 1711715736, + "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "72c6ed328aa4e5d9151b1a512f6ad83aca7529fa", + "rev": "807c549feabce7eddbf259dbdcec9e0600a0660d", "type": "github" }, "original": { diff --git a/outputs.tf b/outputs.tf index 4a1fff6..ccc0f5b 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,44 +1,46 @@ 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 ipv6_cidr_block = module.vpc[0].vpc.ipv6_cidr_block main_route_table_id = module.vpc[0].vpc.main_route_table_id - tags_all = module.vpc[0].vpc.tags_all + tags = module.vpc[0].vpc.tags } : { id = "" arn = "" cidr_block = "" ipv6_cidr_block = "" main_route_table_id = "" - tags_all = tomap({ "" = "" }) + tags = tomap({ "" = "" }) }) description = <<-EOT The VPC object from AWS. 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_all = module.subnet[0].subnet.tags_all - } : { - # no object found, but output types are normal - id = "" - arn = "" - availability_zone = "" - availability_zone_id = "" - cidr_block = "" - ipv6_cidr_block = "" - vpc_id = "" - tags_all = 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,7 +48,7 @@ output "subnet" { } output "security_group" { - value = (can(module.security_group[0].security_group) ? { + 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 @@ -66,7 +68,7 @@ output "security_group" { } output "load_balancer" { - value = (can(module.network_load_balancer[0].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 @@ -90,7 +92,7 @@ output "load_balancer" { } output "domain" { - value = (can(module.domain[0].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 @@ -110,7 +112,7 @@ output "domain" { } output "certificate" { - value = (can(module.domain[0].certificate) ? { + value = (length(module.domain) > 0 ? { id = module.domain[0].certificate.id arn = module.domain[0].certificate.arn name = module.domain[0].certificate.name diff --git a/tests/securitygroup_test.go b/tests/securitygroup_test.go index 304d491..149f8ec 100644 --- a/tests/securitygroup_test.go +++ b/tests/securitygroup_test.go @@ -1,29 +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 generates all objects, no overrides -func TestBasic(t *testing.T) { +func TestSecuritygroup(t *testing.T) { t.Parallel() - domain := os.Getenv("DOMAIN") uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "basic" + directory := "securitygroup" region := "us-west-1" terraformVars := map[string]interface{}{ "identifier": uniqueID, - "domain": domain, } 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/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/subnets_test.go b/tests/subnets_test.go index 97dfd0b..e98bf23 100644 --- a/tests/subnets_test.go +++ b/tests/subnets_test.go @@ -8,13 +8,13 @@ import ( "github.com/gruntwork-io/terratest/modules/terraform" ) -func TestBasic(t *testing.T) { +func TestSubnet(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "basic" + directory := "subnets" region := "us-west-2" // This regoin has at least 3 availability zones terraformVars := map[string]interface{}{ diff --git a/tests/vpc_test.go b/tests/vpc_test.go index 4d88d3a..e501dc4 100644 --- a/tests/vpc_test.go +++ b/tests/vpc_test.go @@ -1,7 +1,6 @@ package test import ( - "fmt" "os" "testing" @@ -10,13 +9,13 @@ import ( ) // this test generates all objects, no overrides -func TestBasic(t *testing.T) { +func TestVpc(t *testing.T) { t.Parallel() uniqueID := os.Getenv("IDENTIFIER") if uniqueID == "" { uniqueID = random.UniqueId() } - directory := "basic" + directory := "vpc" region := "us-west-1" terraformVars := map[string]interface{}{ diff --git a/variables.tf b/variables.tf index 20c85b6..47797e5 100644 --- a/variables.tf +++ b/variables.tf @@ -124,7 +124,7 @@ 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'." diff --git a/versions.tf b/versions.tf index 466dd05..b30206d 100644 --- a/versions.tf +++ b/versions.tf @@ -15,6 +15,6 @@ terraform { } } } -provider "acme" { - server_url = "https://acme-staging-v02.api.letsencrypt.org/directory" -} \ No newline at end of file +# provider "acme" { +# server_url = "https://acme-staging-v02.api.letsencrypt.org/directory" +# } \ No newline at end of file From 16987ac9fc541d71d045d19eced246f5bf325ac2 Mon Sep 17 00:00:00 2001 From: matttrach Date: Sun, 31 Mar 2024 01:04:52 -0500 Subject: [PATCH 6/6] fix: format and lint Signed-off-by: matttrach --- .github/workflows/release.yaml | 1 + outputs.tf | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) 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/outputs.tf b/outputs.tf index ccc0f5b..fc61e5b 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,18 +1,18 @@ output "vpc" { - value = ( length(module.vpc) > 0 ? { + 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 ipv6_cidr_block = module.vpc[0].vpc.ipv6_cidr_block main_route_table_id = module.vpc[0].vpc.main_route_table_id - tags = module.vpc[0].vpc.tags + tags = module.vpc[0].vpc.tags } : { id = "" arn = "" cidr_block = "" ipv6_cidr_block = "" main_route_table_id = "" - tags = tomap({ "" = "" }) + tags = tomap({ "" = "" }) }) description = <<-EOT The VPC object from AWS. @@ -20,8 +20,8 @@ output "vpc" { } output "subnets" { - value = ( length(module.subnet) > 0 ? { - for subnet in module.subnet: subnet.subnet.tags.Name => { + 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 @@ -31,7 +31,7 @@ output "subnets" { vpc_id = subnet.subnet.vpc_id tags_all = subnet.subnet.tags_all } - } : { "empty" = { + } : { "empty" = { id = "" arn = "" availability_zone = ""