diff --git a/.github/workflows/commitmsg-conform.yml b/.github/workflows/commitmsg-conform.yml new file mode 100644 index 0000000..4940385 --- /dev/null +++ b/.github/workflows/commitmsg-conform.yml @@ -0,0 +1,14 @@ +name: Commit Message Conformance + +on: + pull_request: {} + +permissions: + statuses: write + checks: write + contents: read + pull-requests: read + +jobs: + commitmsg-conform: + uses: actionsforge/actions/.github/workflows/commitmsg-conform.yml@main diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 0000000..034b809 --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,14 @@ +name: Markdown Lint + +on: + pull_request: {} + +permissions: + statuses: write + checks: write + contents: read + pull-requests: read + +jobs: + markdown-lint: + uses: actionsforge/actions/.github/workflows/markdown-lint.yml@main diff --git a/.github/workflows/terraform-docs.yml b/.github/workflows/terraform-docs.yml new file mode 100644 index 0000000..56aa648 --- /dev/null +++ b/.github/workflows/terraform-docs.yml @@ -0,0 +1,13 @@ +name: Generate terraform docs + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + terraform-docs: + uses: actionsforge/actions/.github/workflows/terraform-docs.yml@main diff --git a/.github/workflows/terraform-lint-validate.yml b/.github/workflows/terraform-lint-validate.yml new file mode 100644 index 0000000..915f136 --- /dev/null +++ b/.github/workflows/terraform-lint-validate.yml @@ -0,0 +1,13 @@ +name: Terraform Lint & Validate + +on: + pull_request: {} +permissions: + statuses: write + checks: write + contents: read + pull-requests: read + +jobs: + terraform-lint-validate: + uses: actionsforge/actions/.github/workflows/terraform-lint-validate.yml@main diff --git a/.github/workflows/terraform-tag-and-release.yml b/.github/workflows/terraform-tag-and-release.yml new file mode 100644 index 0000000..069a79e --- /dev/null +++ b/.github/workflows/terraform-tag-and-release.yml @@ -0,0 +1,12 @@ +name: Terraform Tag and Release +on: + workflow_run: + workflows: ["Generate terraform docs"] + types: + - completed + +permissions: + contents: write +jobs: + terraform-tag-and-release: + uses: actionsforge/actions/.github/workflows/terraform-tag-and-release.yml@main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e37ca17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Local .terraform directories +**/.terraform/ + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore transient lock info files created by terraform apply +.terraform.tfstate.lock.info + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..bd4b6f1 --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,45 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.10.0" + constraints = ">= 5.0.0" + hashes = [ + "h1:ToB7wJhFPmcX1/0vbx5NgZAnP+cJ7Rti5NpNPyEJpxI=", + "zh:3c92efebaf635372bf7283e04fc667d59b0ff3cf1aacd011fc484a11f70954d9", + "zh:404b2a1d360851e63f25945406f2d0c2cb9c20b361552ce01bf7fe3df516a5bf", + "zh:523b1640e2b9e2b548876a1dccc627c290f342255d727568fe4becfd9a8f5689", + "zh:697adf10c76384195303650555229129d64135f5be3abf95da0bf4b6de742054", + "zh:69d6177e3e106518844373871d4e6377003336761aab884da32f66b034229b5c", + "zh:6a41899ce8ab9cdd6f706160fd350951e5f3fc1432a37e638d3576a780c686fd", + "zh:6e8fd28299d6bf0ab6922cf987757e578f357a45ac45abc312688580dbde3bee", + "zh:7ca4bfb5a8f89586dd0c8dd9c1e638a03bc7c6f456bcc29be57cfb7bdc90fc30", + "zh:8fe1f6e0a2718318bae3f53a4fb77bc9eaef0fc4131145996f48482b135830c6", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b221cfbc9f19ad30719b773f05f45571e88b124c15c35ac230021df1bb1110f5", + "zh:b458c357b5f38092e374957e51827d9113447696deccf0cb01f5684d976e7725", + "zh:b7fbb1b05972d73d72af58a2179ac124c6d69a4f0392aa2ce4dc855e78f52268", + "zh:d95da0dc45df0f30005e17c5206addbd62b0471c265d9855fe8039bf6f2adef7", + "zh:db5dd4120c6ab6ae13df67353a9bc902ac34d01c1d297812d628ebf61dc6f681", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = ">= 3.0.0" + hashes = [ + "h1:356j/3XnXEKr9nyicLUufzoF4Yr6hRy481KIxRVpK0c=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..8d4eadd --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "davidanson.vscode-markdownlint", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "foxundermoon.shell-format", + "Gruntfuggly.todo-tree", + "hashicorp.terraform", + "mhutchie.git-graph", + "streetsidesoftware.code-spell-checker", + "usernamehw.errorlens", + "vscode-icons-team.vscode-icons" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e081787 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,52 @@ +{ + "files.associations": { + "*.dockerfile": "dockerfile", + "*.sh.tpl": "shellscript", + "docker-compose*.yml": "yaml", + "Dockerfile*": "dockerfile" + }, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "__debug_bin": true, + "vendor/": true, + "go.sum": true + }, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, + "remote.extensionKind": { + "ms-azuretools.vscode-docker": "ui", + "ms-vscode-remote.remote-containers": "ui" + }, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "prettier.requireConfig": true, + "workbench.iconTheme": "vscode-icons", + "[css]": { + "editor.defaultFormatter": "vscode.css-language-features", + "editor.foldingStrategy": "indentation" + }, + "[dockerfile]": { + "editor.defaultFormatter": "ms-azuretools.vscode-docker" + }, + "[html]": { + "editor.defaultFormatter": "vscode.html-language-features" + }, + "[json]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[shellscript]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + }, +"[terraform]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "hashicorp.terraform", + "editor.tabSize": 2, + "editor.insertSpaces": true +} +} diff --git a/README.md b/README.md index 57fb32c..766ba54 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # terraform-aws-aurora-postgresql + Terraform module for deploying Aurora PostgreSQL cluster + + + diff --git a/examples/standard/.terraform.lock.hcl b/examples/standard/.terraform.lock.hcl new file mode 100644 index 0000000..47d79ac --- /dev/null +++ b/examples/standard/.terraform.lock.hcl @@ -0,0 +1,45 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.10.0" + constraints = ">= 5.0.0, >= 6.0.0" + hashes = [ + "h1:ToB7wJhFPmcX1/0vbx5NgZAnP+cJ7Rti5NpNPyEJpxI=", + "zh:3c92efebaf635372bf7283e04fc667d59b0ff3cf1aacd011fc484a11f70954d9", + "zh:404b2a1d360851e63f25945406f2d0c2cb9c20b361552ce01bf7fe3df516a5bf", + "zh:523b1640e2b9e2b548876a1dccc627c290f342255d727568fe4becfd9a8f5689", + "zh:697adf10c76384195303650555229129d64135f5be3abf95da0bf4b6de742054", + "zh:69d6177e3e106518844373871d4e6377003336761aab884da32f66b034229b5c", + "zh:6a41899ce8ab9cdd6f706160fd350951e5f3fc1432a37e638d3576a780c686fd", + "zh:6e8fd28299d6bf0ab6922cf987757e578f357a45ac45abc312688580dbde3bee", + "zh:7ca4bfb5a8f89586dd0c8dd9c1e638a03bc7c6f456bcc29be57cfb7bdc90fc30", + "zh:8fe1f6e0a2718318bae3f53a4fb77bc9eaef0fc4131145996f48482b135830c6", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:b221cfbc9f19ad30719b773f05f45571e88b124c15c35ac230021df1bb1110f5", + "zh:b458c357b5f38092e374957e51827d9113447696deccf0cb01f5684d976e7725", + "zh:b7fbb1b05972d73d72af58a2179ac124c6d69a4f0392aa2ce4dc855e78f52268", + "zh:d95da0dc45df0f30005e17c5206addbd62b0471c265d9855fe8039bf6f2adef7", + "zh:db5dd4120c6ab6ae13df67353a9bc902ac34d01c1d297812d628ebf61dc6f681", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = ">= 3.0.0" + hashes = [ + "h1:356j/3XnXEKr9nyicLUufzoF4Yr6hRy481KIxRVpK0c=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} diff --git a/examples/standard/README.md b/examples/standard/README.md new file mode 100644 index 0000000..cff6ccf --- /dev/null +++ b/examples/standard/README.md @@ -0,0 +1,50 @@ +# Aurora PostgreSQL Example + +This example demonstrates a complete Aurora PostgreSQL setup using the custom module with VPC infrastructure. + +## Features + +- Creates a new VPC with public/private subnets in ap-southeast-2 +- Deploys Aurora PostgreSQL 15 cluster with 2 instances (Multi-AZ) +- Enables storage encryption with KMS +- Sets up enhanced monitoring and Performance Insights +- Creates custom parameter groups with SSL enforcement +- Uses private subnets with NAT Gateway for internet access + +## Usage + +```bash +cd examples/standard +terraform init +terraform plan +terraform apply +``` + +## Configuration + +The example creates: + +- **VPC**: 10.0.0.0/16 with 3 AZs +- **Aurora**: 2x db.r6g.large instances +- **Security**: Encrypted storage, monitoring, CloudWatch logs +- **Networking**: Private subnets, security groups, NAT Gateway + +## Requirements + +- Terraform >= 1.0 +- AWS provider configured +- Access to ap-southeast-2 region + +## Notes + +- This is a test/demo configuration +- Uses db.r6g.large instances (~$250/month) +- Consider db.t3.medium for cost savings (~$100/month) +- Multi-AZ setup for learning purposes + +## Cleanup + +```bash +terraform destroy +rm -rf .terraform .terraform.lock.hcl terraform.tfstate* +``` diff --git a/examples/standard/main.tf b/examples/standard/main.tf new file mode 100644 index 0000000..969209c --- /dev/null +++ b/examples/standard/main.tf @@ -0,0 +1,119 @@ +# Example usage of the Aurora PostgreSQL module +# This example creates a complete setup with VPC, subnets, and security groups + +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} + +provider "aws" { + region = "ap-southeast-2" +} + +locals { + name = "example" + vpc_cidr = "10.0.0.0/16" + azs = ["ap-southeast-2a", "ap-southeast-2b", "ap-southeast-2c"] + public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + private_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"] + database_subnets = ["10.0.7.0/24", "10.0.8.0/24", "10.0.9.0/24"] + + tags = { + Name = "example" + Environment = "dev" + } +} + +module "vpc" { + source = "cloudbuildlab/vpc/aws" + + vpc_name = local.name + vpc_cidr = local.vpc_cidr + availability_zones = local.azs + + public_subnet_cidrs = local.public_subnets + private_subnet_cidrs = local.private_subnets + + # Enable Internet Gateway & NAT Gateway + create_igw = true + nat_gateway_type = "single" + + tags = local.tags +} + +# Create security group for Aurora +resource "aws_security_group" "aurora" { + name_prefix = "aurora-postgresql-" + vpc_id = module.vpc.vpc_id + + ingress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + cidr_blocks = [local.vpc_cidr] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = merge(local.tags, { + Name = "aurora-postgresql-sg" + }) +} + +# Use the Aurora PostgreSQL module +module "aurora_postgresql" { + source = "../../" + + cluster_name = local.name + subnet_ids = module.vpc.database_subnet_ids + vpc_security_group_ids = [aws_security_group.aurora.id] + + # Database configuration + database_name = "example_db" + master_username = "postgres" + create_random_password = true + + # Instance configuration + instance_count = 2 + instance_class = "db.t3.medium" + + # Security settings + storage_encrypted = true + deletion_protection = false + skip_final_snapshot = true + + # Backup settings + backup_retention_period = 7 + + # Enhanced monitoring + monitoring_enabled = true + create_monitoring_role = true + monitoring_interval = 60 + + # Performance Insights + performance_insights_enabled = true + performance_insights_retention_period = 7 + + # CloudWatch Logs + enabled_cloudwatch_logs_exports = ["postgresql"] + + # Parameter Group + create_db_cluster_parameter_group = true + parameter_group_family = "aurora-postgresql15" + cluster_parameter_group_parameters = { + "rds.force_ssl" = "1" + "log_min_duration_statement" = "1000" + } + + tags = local.tags +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..ed1583c --- /dev/null +++ b/main.tf @@ -0,0 +1,189 @@ +######################## +# Locals +######################## +locals { + # KMS key logic + kms_key_arn = var.storage_encrypted ? (var.kms_key_arn != "" ? var.kms_key_arn : aws_kms_key.this[0].arn) : null + + # Final snapshot logic + effective_final_snapshot_id = var.skip_final_snapshot ? null : (var.final_snapshot_identifier != "" ? var.final_snapshot_identifier : "${var.cluster_name}-final") + + # IAM role logic + monitoring_role_arn = var.monitoring_enabled ? (var.create_monitoring_role ? aws_iam_role.monitoring[0].arn : var.monitoring_role_arn) : null + + # Parameter group logic + parameter_group_name = var.create_db_cluster_parameter_group ? aws_rds_cluster_parameter_group.this[0].name : (var.db_cluster_parameter_group_name != "" ? var.db_cluster_parameter_group_name : null) + + # Password logic - use generated password if create_random_password is true, otherwise use provided password + master_password = var.create_random_password ? random_password.master[0].result : var.master_password +} + +######################## +# Random Password +######################## +resource "random_password" "master" { + count = var.create_random_password ? 1 : 0 + length = 20 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +######################## +# KMS Key (optional) +######################## +resource "aws_kms_key" "this" { + count = var.storage_encrypted && var.kms_key_arn == "" ? 1 : 0 + description = "KMS key for Aurora PostgreSQL ${var.cluster_name}" + enable_key_rotation = true + deletion_window_in_days = 7 + tags = merge(var.tags, { Name = "${var.cluster_name}-kms" }) +} + +######################## +# IAM Role (Enhanced Monitoring) +######################## +data "aws_iam_policy_document" "monitoring_assume" { + statement { + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["monitoring.rds.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "monitoring" { + count = var.monitoring_enabled && var.create_monitoring_role ? 1 : 0 + name = "${var.cluster_name}-aurora-monitoring" + assume_role_policy = data.aws_iam_policy_document.monitoring_assume.json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "monitoring" { + count = var.monitoring_enabled && var.create_monitoring_role ? 1 : 0 + role = aws_iam_role.monitoring[0].name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole" +} + +######################## +# DB Subnet Group +######################## +resource "aws_db_subnet_group" "this" { + name = "${var.cluster_name}-subnets" + subnet_ids = var.subnet_ids + description = "Subnet group for Aurora PostgreSQL ${var.cluster_name}" + tags = merge(var.tags, { Name = "${var.cluster_name}-subnet-group" }) +} + +######################## +# DB Cluster Parameter Group (optional) +######################## +resource "aws_rds_cluster_parameter_group" "this" { + count = var.create_db_cluster_parameter_group ? 1 : 0 + name = "${var.cluster_name}-cluster-pg" + family = var.parameter_group_family + description = "Custom cluster parameter group for ${var.cluster_name}" + + dynamic "parameter" { + for_each = var.cluster_parameter_group_parameters + iterator = pg + content { + name = pg.key + value = pg.value + } + } + + tags = var.tags +} + +######################## +# Aurora PostgreSQL Cluster +######################## +resource "aws_rds_cluster" "this" { + # Basic Configuration + cluster_identifier = var.cluster_name + engine = "aurora-postgresql" + engine_version = var.engine_version + database_name = var.database_name + + # Authentication + master_username = var.snapshot_identifier == "" ? var.master_username : null + master_password = var.snapshot_identifier == "" ? local.master_password : null + + # Storage Configuration + storage_encrypted = var.storage_encrypted + kms_key_id = local.kms_key_arn + + # Network Configuration + db_subnet_group_name = aws_db_subnet_group.this.name + vpc_security_group_ids = var.vpc_security_group_ids + + # Parameter Group + db_cluster_parameter_group_name = local.parameter_group_name + + # Backup Configuration + backup_retention_period = var.backup_retention_period + preferred_backup_window = var.preferred_backup_window + copy_tags_to_snapshot = var.copy_tags_to_snapshot + + # Maintenance Configuration + preferred_maintenance_window = var.preferred_maintenance_window + + # Snapshot Configuration + skip_final_snapshot = var.skip_final_snapshot + final_snapshot_identifier = local.effective_final_snapshot_id + deletion_protection = var.deletion_protection + + # Snapshot restore + snapshot_identifier = var.snapshot_identifier != "" ? var.snapshot_identifier : null + + # CloudWatch Logs + enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs_exports + + tags = merge(var.tags, { Name = var.cluster_name }) + + lifecycle { + create_before_destroy = true + ignore_changes = [ + storage_encrypted, + performance_insights_retention_period, + ] + } +} + +######################## +# Aurora PostgreSQL Instances +######################## +resource "aws_rds_cluster_instance" "this" { + count = var.instance_count + + # Basic Configuration + identifier = "${var.cluster_name}-${count.index + 1}" + cluster_identifier = aws_rds_cluster.this.id + instance_class = var.instance_class + engine = aws_rds_cluster.this.engine + engine_version = aws_rds_cluster.this.engine_version + + # Network Configuration + publicly_accessible = var.publicly_accessible + + # Monitoring Configuration + monitoring_interval = var.monitoring_enabled ? var.monitoring_interval : 0 + monitoring_role_arn = local.monitoring_role_arn + + # Performance Insights + performance_insights_enabled = var.performance_insights_enabled + performance_insights_retention_period = var.performance_insights_enabled ? var.performance_insights_retention_period : null + + # Maintenance Configuration + auto_minor_version_upgrade = var.auto_minor_version_upgrade + + tags = merge(var.tags, { Name = "${var.cluster_name}-${count.index + 1}" }) + + lifecycle { + create_before_destroy = true + ignore_changes = [ + performance_insights_retention_period, + ] + } +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..5b69c8f --- /dev/null +++ b/outputs.tf @@ -0,0 +1,87 @@ +######################## +# Cluster Outputs +######################## +output "cluster_arn" { + description = "The ARN of the Aurora PostgreSQL cluster" + value = aws_rds_cluster.this.arn +} + +output "cluster_endpoint" { + description = "The cluster endpoint" + value = aws_rds_cluster.this.endpoint +} + +output "cluster_id" { + description = "The ID of the Aurora PostgreSQL cluster" + value = aws_rds_cluster.this.id +} + +output "cluster_port" { + description = "The port on which the DB accepts connections" + value = aws_rds_cluster.this.port +} + +output "cluster_reader_endpoint" { + description = "The cluster reader endpoint" + value = aws_rds_cluster.this.reader_endpoint +} + +######################## +# Database Outputs +######################## +output "database_name" { + description = "The name of the database" + value = aws_rds_cluster.this.database_name +} + +output "master_password" { + description = "The master password (only if create_random_password is true)" + value = var.create_random_password ? random_password.master[0].result : null + sensitive = true +} + +output "master_username" { + description = "The master username for the database" + value = aws_rds_cluster.this.master_username +} + +######################## +# Instance Outputs +######################## +output "instance_endpoints" { + description = "List of Aurora instance endpoints" + value = aws_rds_cluster_instance.this[*].endpoint +} + +output "instance_ids" { + description = "List of Aurora instance IDs" + value = aws_rds_cluster_instance.this[*].id +} + +######################## +# Infrastructure Outputs +######################## +output "kms_key_arn" { + description = "The ARN of the KMS key used for encryption" + value = var.storage_encrypted ? (var.kms_key_arn != "" ? var.kms_key_arn : aws_kms_key.this[0].arn) : null +} + +output "monitoring_role_arn" { + description = "The ARN of the IAM role used for enhanced monitoring" + value = var.monitoring_enabled ? (var.create_monitoring_role ? aws_iam_role.monitoring[0].arn : var.monitoring_role_arn) : null +} + +output "parameter_group_name" { + description = "The name of the DB cluster parameter group" + value = var.create_db_cluster_parameter_group ? aws_rds_cluster_parameter_group.this[0].name : var.db_cluster_parameter_group_name +} + +output "subnet_group_arn" { + description = "The ARN of the DB subnet group" + value = aws_db_subnet_group.this.arn +} + +output "subnet_group_name" { + description = "The name of the DB subnet group" + value = aws_db_subnet_group.this.name +} diff --git a/tests/aurora_postgres.tftest.hcl b/tests/aurora_postgres.tftest.hcl new file mode 100644 index 0000000..a1deb32 --- /dev/null +++ b/tests/aurora_postgres.tftest.hcl @@ -0,0 +1,88 @@ +# Test file for Aurora PostgreSQL module +# This test validates the module configuration using plan only + +variables { + cluster_name = "test-aurora-postgresql" + subnet_ids = ["subnet-12345678", "subnet-87654321"] + master_password = "test-password-123!" +} + +run "validate_resource_configuration" { + command = plan + + # Test that resources are properly configured + assert { + condition = aws_rds_cluster.this.engine == "aurora-postgresql" + error_message = "Cluster engine should be aurora-postgresql" + } + + assert { + condition = aws_rds_cluster.this.storage_encrypted == true + error_message = "Storage should be encrypted by default" + } + + assert { + condition = aws_rds_cluster.this.backup_retention_period == 7 + error_message = "Backup retention period should be 7 days by default" + } + + assert { + condition = aws_db_subnet_group.this.name != null + error_message = "Subnet group should have a name" + } +} + +run "validate_instance_configuration" { + command = plan + + # Test instance configuration + assert { + condition = length(aws_rds_cluster_instance.this) == 2 + error_message = "Should create 2 instances by default" + } + + assert { + condition = aws_rds_cluster_instance.this[0].instance_class == "db.r6g.large" + error_message = "Instance class should be db.r6g.large by default" + } + + assert { + condition = aws_rds_cluster_instance.this[0].engine == "aurora-postgresql" + error_message = "Instance engine should be aurora-postgresql" + } +} + +run "validate_enhanced_features" { + command = plan + + # Test enhanced features + assert { + condition = aws_db_subnet_group.this.subnet_ids != null + error_message = "Subnet group should have subnet IDs" + } + + assert { + condition = aws_rds_cluster.this.storage_encrypted == true + error_message = "Storage encryption should be enabled" + } +} + +run "validate_variables" { + command = plan + + # Test that variables are properly set + assert { + condition = var.cluster_name == "test-aurora-postgresql" + error_message = "Cluster name variable should be set correctly" + } + + assert { + condition = length(var.subnet_ids) == 2 + error_message = "Subnet IDs variable should have 2 subnets" + } + + assert { + condition = var.master_password != "" + error_message = "Master password variable should be set" + } +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..cce80a7 --- /dev/null +++ b/variables.tf @@ -0,0 +1,202 @@ +variable "cluster_name" { + description = "Name of the Aurora PostgreSQL cluster" + type = string +} + +variable "subnet_ids" { + description = "List of subnet IDs for the DB subnet group" + type = list(string) +} + +variable "vpc_security_group_ids" { + description = "List of VPC security group IDs to associate with the cluster" + type = list(string) + default = [] +} + +variable "engine_version" { + description = "Aurora PostgreSQL engine version" + type = string + default = "15.4" +} + +variable "database_name" { + description = "Name of the database to create when the cluster is created" + type = string + default = "postgres" +} + +variable "master_username" { + description = "Master username for the database" + type = string + default = "postgres" +} + +variable "master_password" { + description = "Master password for the database" + type = string + sensitive = true + default = "" +} + +variable "create_random_password" { + description = "Whether to create a random password for the master user" + type = bool + default = false +} + +variable "instance_count" { + description = "Number of Aurora instances in the cluster" + type = number + default = 2 +} + +variable "instance_class" { + description = "Instance class for Aurora instances" + type = string + default = "db.r6g.large" +} + +variable "skip_final_snapshot" { + description = "Whether to skip final snapshot when destroying the cluster" + type = bool + default = false +} + +variable "final_snapshot_identifier" { + description = "Name of the final snapshot when destroying the cluster" + type = string + default = "" +} + +variable "deletion_protection" { + description = "Whether to enable deletion protection for the cluster" + type = bool + default = false +} + +variable "storage_encrypted" { + description = "Whether to enable storage encryption" + type = bool + default = true +} + +variable "kms_key_arn" { + description = "ARN of the KMS key to use for encryption. If not provided and storage_encrypted is true, a new KMS key will be created" + type = string + default = "" +} + +variable "backup_retention_period" { + description = "Number of days to retain backups" + type = number + default = 7 +} + +variable "preferred_backup_window" { + description = "Preferred backup window in UTC" + type = string + default = "03:00-04:00" +} + +variable "copy_tags_to_snapshot" { + description = "Whether to copy tags to snapshots" + type = bool + default = true +} + +variable "preferred_maintenance_window" { + description = "Preferred maintenance window in UTC" + type = string + default = "sun:04:00-sun:05:00" +} + +variable "publicly_accessible" { + description = "Whether the instances are publicly accessible" + type = bool + default = false +} + +variable "auto_minor_version_upgrade" { + description = "Whether to enable auto minor version upgrade" + type = bool + default = true +} + +variable "monitoring_enabled" { + description = "Whether to enable enhanced monitoring" + type = bool + default = false +} + +variable "monitoring_interval" { + description = "The interval, in seconds, between points when Enhanced Monitoring metrics are collected" + type = number + default = 60 +} + +variable "create_monitoring_role" { + description = "Whether to create a monitoring IAM role" + type = bool + default = true +} + +variable "monitoring_role_arn" { + description = "ARN of the IAM role for enhanced monitoring" + type = string + default = "" +} + +variable "performance_insights_enabled" { + description = "Whether to enable Performance Insights" + type = bool + default = false +} + +variable "performance_insights_retention_period" { + description = "Amount of time in days to retain Performance Insights data" + type = number + default = 7 +} + +variable "enabled_cloudwatch_logs_exports" { + description = "List of log types to export to CloudWatch" + type = list(string) + default = [] +} + +variable "create_db_cluster_parameter_group" { + description = "Whether to create a DB cluster parameter group" + type = bool + default = false +} + +variable "db_cluster_parameter_group_name" { + description = "Name of the DB cluster parameter group to use" + type = string + default = "" +} + +variable "parameter_group_family" { + description = "Family of the DB cluster parameter group" + type = string + default = "aurora-postgresql15" +} + +variable "cluster_parameter_group_parameters" { + description = "Map of cluster parameter group parameters" + type = map(string) + default = {} +} + +variable "snapshot_identifier" { + description = "Identifier of the snapshot to restore from" + type = string + default = "" +} + +variable "tags" { + description = "Additional tags for the resources" + type = map(string) + default = {} +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..d7b00bc --- /dev/null +++ b/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + random = { + source = "hashicorp/random" + version = ">= 3.0" + } + } +}