From d806336734c27aa9d2ba83c6546c522b3cc650a3 Mon Sep 17 00:00:00 2001 From: Matthew Baggett <matthew@baggett.me> Date: Thu, 26 Dec 2024 21:56:31 +0100 Subject: [PATCH] Now available in non-serverless flavour! --- cloud/aws/rds/aws.tf | 5 +++ cloud/aws/rds/debug.tf | 61 ++++++++++++++++++++++++++++ cloud/aws/rds/inputs.tf | 68 ++++++++++++++++++++++++++++++++ cloud/aws/rds/outputs.tf | 16 ++++++++ cloud/aws/rds/rds.certificate.tf | 13 ++++++ cloud/aws/rds/rds.tf | 68 ++++++++++++++++++++++++++++++++ cloud/aws/rds/security-groups.tf | 24 +++++++++++ cloud/aws/rds/subnets.tf | 22 +++++++++++ cloud/aws/rds/tenants.tf | 12 ++++++ cloud/aws/rds/terraform.tf | 26 ++++++++++++ 10 files changed, 315 insertions(+) create mode 100644 cloud/aws/rds/aws.tf create mode 100644 cloud/aws/rds/debug.tf create mode 100644 cloud/aws/rds/inputs.tf create mode 100644 cloud/aws/rds/outputs.tf create mode 100644 cloud/aws/rds/rds.certificate.tf create mode 100644 cloud/aws/rds/rds.tf create mode 100644 cloud/aws/rds/security-groups.tf create mode 100644 cloud/aws/rds/subnets.tf create mode 100644 cloud/aws/rds/tenants.tf create mode 100644 cloud/aws/rds/terraform.tf diff --git a/cloud/aws/rds/aws.tf b/cloud/aws/rds/aws.tf new file mode 100644 index 0000000..3be5cfc --- /dev/null +++ b/cloud/aws/rds/aws.tf @@ -0,0 +1,5 @@ +data "aws_region" "current" {} +data "aws_caller_identity" "current" {} +data "aws_vpc" "current" { + id = data.aws_security_group.source.vpc_id +} \ No newline at end of file diff --git a/cloud/aws/rds/debug.tf b/cloud/aws/rds/debug.tf new file mode 100644 index 0000000..b53e3cc --- /dev/null +++ b/cloud/aws/rds/debug.tf @@ -0,0 +1,61 @@ +resource "local_file" "debug" { + content = nonsensitive(jsonencode({ + rds = { + instance_name = var.instance_name, + sanitised_name = local.sanitised_name + tennants = var.tenants, + application_arn = try(var.application.arn, null), + application_name = try(var.application.name, null), + engine = { + requested = { + engine = var.engine, + version = var.engine_version + } + resolved = { + engine = data.aws_rds_engine_version.latest.engine, + version = data.aws_rds_engine_version.latest.version, + match = data.aws_rds_engine_version.latest, + } + } + #endpoints = { + # write = aws_rds_cluster_endpoint.endpoint["write"].endpoint, + # read = aws_rds_cluster_endpoint.endpoint["read"].endpoint + #} + admin = { + username = module.admin_identity.username + password = nonsensitive(module.admin_identity.password) + } + } + tenants = var.tenants + })) + filename = "${path.root}/.debug/aws/rds/${var.instance_name}.provided.json" + file_permission = "0600" +} +resource "local_file" "debug_result" { + content = nonsensitive(jsonencode({ + rds = { + instance_name = var.instance_name, + tennants = var.tenants, + application_arn = try(var.application.arn, null), + application_name = try(var.application.name, null), + engine = { + requested = { + engine = var.engine, + version = var.engine_version + } + resolved = { + engine = data.aws_rds_engine_version.latest.engine, + version = data.aws_rds_engine_version.latest.version, + match = data.aws_rds_engine_version.latest, + } + } + endpoints = aws_db_instance.instance.endpoint + } + tenants = merge({ admin = { + username = module.admin_identity.username + password = nonsensitive(module.admin_identity.password) + } }, local.output_tenants) + })) + filename = "${path.root}/.debug/aws/rds/${var.instance_name}.result.json" + file_permission = "0600" +} \ No newline at end of file diff --git a/cloud/aws/rds/inputs.tf b/cloud/aws/rds/inputs.tf new file mode 100644 index 0000000..c41cc25 --- /dev/null +++ b/cloud/aws/rds/inputs.tf @@ -0,0 +1,68 @@ +variable "instance_name" { + type = string + description = "The name of the RDS serverless instance" + default = "serverless-multitennant" +} +locals { + sanitised_name = replace(replace(lower(var.instance_name), "[^a-z0-9A-Z-]", "-"), " ", "-") +} +variable "tenants" { + type = map(object({ + username = string + database = string + active = optional(bool, true) + })) + default = null +} +variable "application" { + description = "The AWS myApplication to be associated with this cluster" + type = object({ + arn = string + name = string + description = string + application_tag = map(string) + }) + default = null +} +variable "engine" { + type = string + description = "The database engine to use. This must be either aurora-mysql or aurora-postgresql" + default = "mysql" + validation { + error_message = "Must be either ${join(" or ", local.supported_engines)}." + condition = contains(local.supported_engines, var.engine) + } +} +locals { + supported_engines = ["mysql", "postgresql", "mariadb", ] + is_mysql = var.engine == "mysql" + is_postgres = var.engine == "postgresql" + is_mariadb = var.engine == "mariadb" +} +variable "engine_version" { + type = string + default = null +} +variable "backup_window" { + type = string + description = "The daily time range during which automated backups are created if automated backups are enabled." + default = "03:00-05:00" +} +variable "backup_retention_period_days" { + type = number + default = 30 + validation { + error_message = "backup_retention_period_days must be between 1 and 35." + condition = var.backup_retention_period_days >= 1 && var.backup_retention_period_days <= 35 + } +} +variable "skip_final_snapshot" { + type = bool + description = "Determines whether a final DB snapshot is created before the DB cluster is deleted." + default = false +} +variable "enable_performance_insights" { + type = bool + default = false +} + diff --git a/cloud/aws/rds/outputs.tf b/cloud/aws/rds/outputs.tf new file mode 100644 index 0000000..2148d2a --- /dev/null +++ b/cloud/aws/rds/outputs.tf @@ -0,0 +1,16 @@ +locals { + output_tenants = { + #for key, tenant in module.tenants : key => { + # username = tenant.username + # database = tenant.database + # password = tenant.password + # connection_string = tenant.connection_string + #} + } +} +output "tenants" { + value = local.output_tenants +} +output "admin" { + value = module.admin_identity +} \ No newline at end of file diff --git a/cloud/aws/rds/rds.certificate.tf b/cloud/aws/rds/rds.certificate.tf new file mode 100644 index 0000000..35fb4f5 --- /dev/null +++ b/cloud/aws/rds/rds.certificate.tf @@ -0,0 +1,13 @@ +data "aws_rds_certificate" "default" { + id = aws_db_instance.instance.ca_cert_identifier + latest_valid_till = true +} +data "http" "cert_data" { + url = "https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem" +} +output "cert" { + value = data.aws_rds_certificate.default +} +output "cert_data" { + value = data.http.cert_data.response_body +} \ No newline at end of file diff --git a/cloud/aws/rds/rds.tf b/cloud/aws/rds/rds.tf new file mode 100644 index 0000000..e5cfd66 --- /dev/null +++ b/cloud/aws/rds/rds.tf @@ -0,0 +1,68 @@ +data "aws_rds_engine_version" "latest" { + engine = var.engine + version = var.engine_version + latest = true +} +resource "aws_kms_key" "db_key" { + description = "RDS ${var.instance_name} Encryption Key" + tags = merge( + try(var.application.application_tag, {}), + { + TerraformSecretType = "RDSMasterEncryptionKey" + } + ) +} +variable "instance_class" { + type = string + description = "The instance class to use for the RDS instance" + default = "db.t4g.small" +} +module "admin_identity" { + source = "../../../utils/identity" + username_words = 2 +} +variable "allocated_storage_gb" { + type = number + default = 5 + description = "The storage size for the RDS instance, measured in GB." +} +variable "max_allocated_storage_gb" { + type = number + default = 100 + description = "The maximum storage size for the RDS instance, measured in GB." +} +resource "aws_db_instance" "instance" { + identifier_prefix = "${local.sanitised_name}-" + instance_class = var.instance_class + engine = data.aws_rds_engine_version.latest.engine + engine_version = data.aws_rds_engine_version.latest.version + storage_encrypted = true + kms_key_id = aws_kms_key.db_key.arn + publicly_accessible = false + apply_immediately = true + db_subnet_group_name = aws_db_subnet_group.sg.name + vpc_security_group_ids = [aws_security_group.rds.id] + skip_final_snapshot = var.skip_final_snapshot + username = module.admin_identity.username + password = module.admin_identity.password + backup_window = var.backup_window + backup_retention_period = var.backup_retention_period_days + allocated_storage = var.allocated_storage_gb + max_allocated_storage = var.max_allocated_storage_gb + + performance_insights_enabled = var.enable_performance_insights + performance_insights_retention_period = var.enable_performance_insights ? 7 : null + performance_insights_kms_key_id = var.enable_performance_insights ? aws_kms_key.db_key.arn : null + + lifecycle { + create_before_destroy = false + } + + tags = merge( + try(var.application.application_tag, {}), + {} + ) +} +output "endpoints" { + value = aws_db_instance.instance.endpoint +} diff --git a/cloud/aws/rds/security-groups.tf b/cloud/aws/rds/security-groups.tf new file mode 100644 index 0000000..4f58f7e --- /dev/null +++ b/cloud/aws/rds/security-groups.tf @@ -0,0 +1,24 @@ +resource "aws_security_group" "rds" { + name = join("-", [var.instance_name, "rds"]) + description = "RDS Security Group for ${var.instance_name}" + vpc_id = data.aws_vpc.current.id + tags = merge( + try(var.application.application_tag, {}), + {} + ) +} +variable "source_security_group_id" { + type = string + description = "The security group ID to allow access to the RDS instance" +} +data "aws_security_group" "source" { + id = var.source_security_group_id +} +resource "aws_security_group_rule" "sgr" { + security_group_id = aws_security_group.rds.id + type = "ingress" + protocol = "tcp" + from_port = var.engine == "aurora-postgres" ? 5432 : 3306 + to_port = var.engine == "aurora-postgres" ? 5432 : 3306 + source_security_group_id = var.source_security_group_id +} \ No newline at end of file diff --git a/cloud/aws/rds/subnets.tf b/cloud/aws/rds/subnets.tf new file mode 100644 index 0000000..08cc9f8 --- /dev/null +++ b/cloud/aws/rds/subnets.tf @@ -0,0 +1,22 @@ +variable "aws_subnets" { + description = "Pass an aws_subnets data object to the module" + type = object({ + ids = list(string) + }) +} +data "aws_subnets" "subnets" { + filter { + name = "subnet-id" + values = var.aws_subnets.ids + } +} +resource "aws_db_subnet_group" "sg" { + name = lower(join("-", [var.instance_name, "subnet-group"])) + subnet_ids = data.aws_subnets.subnets.ids + tags = merge( + try(var.application.application_tag, {}), + { + Name = "${var.instance_name} Subnet Group" + } + ) +} \ No newline at end of file diff --git a/cloud/aws/rds/tenants.tf b/cloud/aws/rds/tenants.tf new file mode 100644 index 0000000..a773132 --- /dev/null +++ b/cloud/aws/rds/tenants.tf @@ -0,0 +1,12 @@ +/*module "tenants" { + depends_on = [aws_db_instance.instance] + for_each = var.tenants + source = "./tenant" + username = each.value.username + database = each.value.database + vpc_id = data.aws_vpc.current.id + cluster_id = aws_rds_cluster.cluster.id + engine = data.aws_rds_engine_version.latest.engine + admin_username = module.admin_identity.username + admin_password = module.admin_identity.password +}*/ \ No newline at end of file diff --git a/cloud/aws/rds/terraform.tf b/cloud/aws/rds/terraform.tf new file mode 100644 index 0000000..287a7c2 --- /dev/null +++ b/cloud/aws/rds/terraform.tf @@ -0,0 +1,26 @@ +terraform { + required_version = "~> 1.6" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + local = { + source = "hashicorp/local" + version = "~> 2.0" + } + null = { + source = "hashicorp/null" + version = "~> 3.0" + } + ssh = { + source = "matthewbaggett/ssh" + version = "~> 0.1" + } + } +}