diff --git a/terraform/aws/rds-replica/README.md b/terraform/aws/rds-replica/README.md new file mode 100644 index 0000000..f06dfa9 --- /dev/null +++ b/terraform/aws/rds-replica/README.md @@ -0,0 +1,148 @@ +# RDS Replica Module + +A Terraform module for creating a standalone AWS RDS read replica from an existing RDS primary instance, enabling horizontal scaling of read workloads across availability zones. + +## Features + +- Standalone Read Replica from any existing RDS primary instance +- Cross-AZ Placement for improved read performance and fault isolation +- Encryption configuration with optional override +- Security Group Management with configurable ingress rules +- Enhanced Monitoring via CloudWatch and Performance Insights +- Storage Autoscaling with configurable upper bounds +- Deletion Protection to prevent accidental removal +- Independent Instance Sizing to right-size replica compute and storage + +## Quick Start + +```hcl +module "rds_replica" { + source = "github.com/llamandcoco/infra-modules//terraform/aws/rds-replica?ref=" + + identifier = "myapp-db-replica" + source_db_instance_identifier = "myapp-db" + instance_class = "db.t3.micro" + + vpc_id = var.vpc_id +} +``` + +**Note:** Use commit SHA instead of version tags (e.g., `?ref=abc123def`) until a release policy is established. + +## Examples + +Complete, tested configurations in [`tests/`](tests/): + +| Example | Directory | +|---------|-----------| +| Basic Read Replica | [`tests/basic/main.tf`](tests/basic/main.tf) | + +**Usage:** +```bash +# View example +cat tests/basic/main.tf + +# Copy and adapt +cp -r tests/basic/ my-project/ +``` + +## Testing + +```bash +cd tests/basic && terraform init && terraform plan +``` + +
+Terraform Documentation + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | ~> 5.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 5.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_db_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_vpc_security_group_egress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.cidr](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allocated\_storage](#input\_allocated\_storage) | The allocated storage in GiB for the replica. Must be >= the source instance's storage. If null, inherits from source. | `number` | `null` | no | +| [allowed\_cidr\_blocks](#input\_allowed\_cidr\_blocks) | List of CIDR blocks allowed to access the replica. Use sparingly for security reasons. | `list(string)` | `[]` | no | +| [allowed\_security\_groups](#input\_allowed\_security\_groups) | Map of security group IDs allowed to access the replica. Key is a description, value is the security group ID. | `map(string)` | `{}` | no | +| [apply\_immediately](#input\_apply\_immediately) | Whether to apply changes immediately or during the next maintenance window. | `bool` | `false` | no | +| [auto\_minor\_version\_upgrade](#input\_auto\_minor\_version\_upgrade) | Whether to automatically upgrade minor engine versions during maintenance windows. | `bool` | `true` | no | +| [availability\_zone](#input\_availability\_zone) | The AZ for the replica instance. | `string` | `null` | no | +| [backup\_retention\_period](#input\_backup\_retention\_period) | Number of days to retain automated backups for the replica. Set based on your recovery requirements. | `number` | `7` | no | +| [copy\_tags\_to\_snapshot](#input\_copy\_tags\_to\_snapshot) | Whether to copy all instance tags to snapshots. | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Whether to create a security group for the RDS replica. | `bool` | `true` | no | +| [deletion\_protection](#input\_deletion\_protection) | Whether to enable deletion protection. Prevents accidental deletion of the replica. | `bool` | `true` | no | +| [egress\_cidr\_blocks](#input\_egress\_cidr\_blocks) | List of CIDR blocks for egress traffic. Set to empty list to disable egress rules. | `list(string)` | `[]` | no | +| [final\_snapshot\_identifier](#input\_final\_snapshot\_identifier) | Custom identifier for the final snapshot when skip\_final\_snapshot is false. If null, a default is generated. | `string` | `null` | no | +| [identifier](#input\_identifier) | The name of the RDS replica instance. Must be unique within the AWS account and region. | `string` | n/a | yes | +| [instance\_class](#input\_instance\_class) | The instance type of the RDS replica instance. Examples:
- db.t3.micro, db.t3.small: Burstable performance (dev/test)
- db.t4g.micro, db.t4g.small: ARM-based burstable (cost-optimized)
- db.m5.large, db.m5.xlarge: General purpose (production)
- db.r5.large, db.r5.xlarge: Memory optimized (high-performance)
See: https://aws.amazon.com/rds/instance-types/ | `string` | n/a | yes | +| [iops](#input\_iops) | The amount of provisioned IOPS. Required when storage\_type is io1 or io2. | `number` | `null` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | ARN of the KMS key to use for replica encryption. If not specified, uses the default RDS KMS key. | `string` | `null` | no | +| [max\_allocated\_storage](#input\_max\_allocated\_storage) | The upper limit of storage (GiB) for autoscaling. Set to 0 to disable storage autoscaling. | `number` | `0` | no | +| [monitoring\_interval](#input\_monitoring\_interval) | The interval in seconds between Enhanced Monitoring metric collection.
Valid values: 0, 1, 5, 10, 15, 30, 60. Set to 0 to disable.
Requires monitoring\_role\_arn when enabled. | `number` | `0` | no | +| [monitoring\_role\_arn](#input\_monitoring\_role\_arn) | ARN of the IAM role for Enhanced Monitoring. Required when monitoring\_interval > 0. | `string` | `null` | no | +| [performance\_insights\_enabled](#input\_performance\_insights\_enabled) | Whether to enable Performance Insights on the replica. | `bool` | `false` | no | +| [performance\_insights\_kms\_key\_id](#input\_performance\_insights\_kms\_key\_id) | ARN of the KMS key to encrypt Performance Insights data. | `string` | `null` | no | +| [performance\_insights\_retention\_period](#input\_performance\_insights\_retention\_period) | Amount of time in days to retain Performance Insights data. Valid values: 7, 731 (2 years). | `number` | `7` | no | +| [port](#input\_port) | The port on which the replica accepts connections. Inherits from source if null. | `number` | `null` | no | +| [publicly\_accessible](#input\_publicly\_accessible) | Whether the replica is publicly accessible. Set to false for production databases. | `bool` | `false` | no | +| [skip\_final\_snapshot](#input\_skip\_final\_snapshot) | Whether to skip the final snapshot when the replica is deleted. | `bool` | `false` | no | +| [source\_db\_instance\_identifier](#input\_source\_db\_instance\_identifier) | The identifier of the source RDS instance to replicate. Must have automated backups enabled (backup\_retention\_period > 0). | `string` | n/a | yes | +| [storage\_encrypted](#input\_storage\_encrypted) | Whether to enable storage encryption on the replica. If null, uses AWS default behavior for replicas. | `bool` | `null` | no | +| [storage\_throughput](#input\_storage\_throughput) | Storage throughput value for gp3 storage type in MB/s. Valid range: 125-1000. | `number` | `null` | no | +| [storage\_type](#input\_storage\_type) | Storage type for the replica. Valid values:
- gp2: General Purpose SSD
- gp3: General Purpose SSD (baseline 3000 IOPS, configurable)
- io1: Provisioned IOPS SSD
- io2: Provisioned IOPS SSD (higher durability)
- standard: Magnetic storage (legacy) | `string` | `"gp3"` | no | +| [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | VPC ID where the replica will be created. Required if create\_security\_group is true. | `string` | `null` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of VPC security group IDs to associate with the replica. If null and create\_security\_group is true, a security group will be created. | `list(string)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [db\_instance\_address](#output\_db\_instance\_address) | The hostname of the replica. Use this as the host in database connection strings. | +| [db\_instance\_allocated\_storage](#output\_db\_instance\_allocated\_storage) | The allocated storage in GiB for the replica. | +| [db\_instance\_arn](#output\_db\_instance\_arn) | The ARN of the RDS replica instance. | +| [db\_instance\_availability\_zone](#output\_db\_instance\_availability\_zone) | The availability zone of the replica. | +| [db\_instance\_ca\_cert\_identifier](#output\_db\_instance\_ca\_cert\_identifier) | The identifier of the CA certificate for the replica. | +| [db\_instance\_endpoint](#output\_db\_instance\_endpoint) | The connection endpoint for the replica in address:port format. | +| [db\_instance\_engine](#output\_db\_instance\_engine) | The database engine type of the replica. | +| [db\_instance\_engine\_version](#output\_db\_instance\_engine\_version) | The running version of the database engine. | +| [db\_instance\_hosted\_zone\_id](#output\_db\_instance\_hosted\_zone\_id) | The canonical hosted zone ID of the replica (for Route53 alias records). | +| [db\_instance\_id](#output\_db\_instance\_id) | The RDS replica instance identifier. | +| [db\_instance\_monitoring\_interval](#output\_db\_instance\_monitoring\_interval) | The Enhanced Monitoring collection interval in seconds. | +| [db\_instance\_performance\_insights\_enabled](#output\_db\_instance\_performance\_insights\_enabled) | Whether Performance Insights is enabled on the replica. | +| [db\_instance\_port](#output\_db\_instance\_port) | The port number on which the replica accepts connections. | +| [db\_instance\_resource\_id](#output\_db\_instance\_resource\_id) | The unique resource ID of the replica. Used for CloudWatch metrics and Performance Insights. | +| [db\_instance\_status](#output\_db\_instance\_status) | The current status of the RDS replica instance. | +| [db\_instance\_storage\_encrypted](#output\_db\_instance\_storage\_encrypted) | Whether the replica storage is encrypted. | +| [db\_instance\_storage\_type](#output\_db\_instance\_storage\_type) | The storage type of the replica. | +| [security\_group\_arn](#output\_security\_group\_arn) | The ARN of the security group created for the replica (if create\_security\_group was true). | +| [security\_group\_id](#output\_security\_group\_id) | The ID of the security group created for the replica (if create\_security\_group was true). | +| [tags](#output\_tags) | All tags applied to the replica. | + +
diff --git a/terraform/aws/rds-replica/main.tf b/terraform/aws/rds-replica/main.tf new file mode 100644 index 0000000..c7bbea5 --- /dev/null +++ b/terraform/aws/rds-replica/main.tf @@ -0,0 +1,171 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# Security Group for RDS Replica +# Controls inbound and outbound traffic to the replica instance +resource "aws_security_group" "this" { + count = var.create_security_group ? 1 : 0 + + name_prefix = "${var.identifier}-rds-replica-" + description = "Security group for RDS replica instance ${var.identifier}" + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { + Name = "${var.identifier}-rds-replica-sg" + } + ) + + lifecycle { + create_before_destroy = true + } +} + +# Security Group Rules +resource "aws_vpc_security_group_ingress_rule" "this" { + for_each = var.create_security_group ? var.allowed_security_groups : {} + + security_group_id = aws_security_group.this[0].id + + referenced_security_group_id = each.value + from_port = var.port + to_port = var.port + ip_protocol = "tcp" + + tags = merge( + var.tags, + { + Name = "${var.identifier}-replica-ingress-${each.key}" + } + ) +} + +resource "aws_vpc_security_group_ingress_rule" "cidr" { + for_each = var.create_security_group && length(var.allowed_cidr_blocks) > 0 ? toset(var.allowed_cidr_blocks) : toset([]) + + security_group_id = aws_security_group.this[0].id + + cidr_ipv4 = each.value + from_port = var.port + to_port = var.port + ip_protocol = "tcp" + + tags = merge( + var.tags, + { + Name = "${var.identifier}-replica-ingress-cidr-${replace(each.value, "/", "-")}" + } + ) +} + +resource "aws_vpc_security_group_egress_rule" "this" { + for_each = var.create_security_group && length(var.egress_cidr_blocks) > 0 ? toset(var.egress_cidr_blocks) : toset([]) + + security_group_id = aws_security_group.this[0].id + + cidr_ipv4 = each.value + ip_protocol = "-1" + + tags = merge( + var.tags, + { + Name = "${var.identifier}-replica-egress-${replace(each.value, "/", "-")}" + } + ) +} + +# RDS Read Replica Instance +# Creates a read replica of an existing RDS instance for scaling read workloads +resource "aws_db_instance" "this" { + identifier = var.identifier + + # Replica source + replicate_source_db = var.source_db_instance_identifier + + # Compute + instance_class = var.instance_class + + # Storage Configuration + allocated_storage = var.allocated_storage + storage_type = var.storage_type + storage_encrypted = var.storage_encrypted + kms_key_id = var.kms_key_id + iops = var.iops + storage_throughput = var.storage_throughput + + max_allocated_storage = var.max_allocated_storage + + # Network Configuration + vpc_security_group_ids = var.vpc_security_group_ids != null ? var.vpc_security_group_ids : (var.create_security_group ? [aws_security_group.this[0].id] : null) + publicly_accessible = var.publicly_accessible + port = var.port + availability_zone = var.availability_zone + + # Monitoring & Logging + monitoring_interval = var.monitoring_interval + monitoring_role_arn = var.monitoring_role_arn + performance_insights_enabled = var.performance_insights_enabled + performance_insights_kms_key_id = var.performance_insights_kms_key_id + performance_insights_retention_period = var.performance_insights_retention_period + + # Additional Settings + backup_retention_period = var.backup_retention_period + auto_minor_version_upgrade = var.auto_minor_version_upgrade + apply_immediately = var.apply_immediately + deletion_protection = var.deletion_protection + skip_final_snapshot = var.skip_final_snapshot + final_snapshot_identifier = var.skip_final_snapshot ? null : coalesce(var.final_snapshot_identifier, "${var.identifier}-final-snapshot") + copy_tags_to_snapshot = var.copy_tags_to_snapshot + + tags = merge( + var.tags, + { + Name = var.identifier + } + ) + + lifecycle { + precondition { + condition = var.max_allocated_storage == 0 || var.allocated_storage == null ? true : var.max_allocated_storage >= var.allocated_storage + error_message = "max_allocated_storage must be 0 or greater than or equal to allocated_storage." + } + + precondition { + condition = var.monitoring_interval == 0 || var.monitoring_role_arn != null + error_message = "monitoring_role_arn is required when monitoring_interval is greater than 0." + } + + precondition { + condition = var.create_security_group || try(length(var.vpc_security_group_ids), 0) > 0 + error_message = "Set create_security_group=true or provide at least one vpc_security_group_ids entry." + } + + precondition { + condition = var.vpc_security_group_ids == null || try(length(var.vpc_security_group_ids), 0) > 0 + error_message = "If vpc_security_group_ids is provided, it must contain at least one security group ID." + } + + precondition { + condition = !var.create_security_group || var.vpc_id != null + error_message = "vpc_id is required when create_security_group is true." + } + + precondition { + condition = !var.create_security_group || (length(var.allowed_security_groups) == 0 && length(var.allowed_cidr_blocks) == 0) || var.port != null + error_message = "port must be set when create_security_group is true and allowed_security_groups or allowed_cidr_blocks are configured." + } + + ignore_changes = [ + password, + ] + } +} diff --git a/terraform/aws/rds-replica/outputs.tf b/terraform/aws/rds-replica/outputs.tf new file mode 100644 index 0000000..060db9b --- /dev/null +++ b/terraform/aws/rds-replica/outputs.tf @@ -0,0 +1,127 @@ +# ----------------------------------------------------------------------------- +# Replica Instance Identification Outputs +# ----------------------------------------------------------------------------- + +output "db_instance_id" { + description = "The RDS replica instance identifier." + value = aws_db_instance.this.id +} + +output "db_instance_arn" { + description = "The ARN of the RDS replica instance." + value = aws_db_instance.this.arn +} + +output "db_instance_resource_id" { + description = "The unique resource ID of the replica. Used for CloudWatch metrics and Performance Insights." + value = aws_db_instance.this.resource_id +} + +# ----------------------------------------------------------------------------- +# Connection Information Outputs +# ----------------------------------------------------------------------------- + +output "db_instance_endpoint" { + description = "The connection endpoint for the replica in address:port format." + value = aws_db_instance.this.endpoint +} + +output "db_instance_address" { + description = "The hostname of the replica. Use this as the host in database connection strings." + value = aws_db_instance.this.address +} + +output "db_instance_port" { + description = "The port number on which the replica accepts connections." + value = aws_db_instance.this.port +} + +output "db_instance_hosted_zone_id" { + description = "The canonical hosted zone ID of the replica (for Route53 alias records)." + value = aws_db_instance.this.hosted_zone_id +} + +# ----------------------------------------------------------------------------- +# Configuration Outputs +# ----------------------------------------------------------------------------- + +output "db_instance_engine" { + description = "The database engine type of the replica." + value = aws_db_instance.this.engine +} + +output "db_instance_engine_version" { + description = "The running version of the database engine." + value = aws_db_instance.this.engine_version_actual +} + +output "db_instance_availability_zone" { + description = "The availability zone of the replica." + value = aws_db_instance.this.availability_zone +} + +# ----------------------------------------------------------------------------- +# Storage Outputs +# ----------------------------------------------------------------------------- + +output "db_instance_storage_encrypted" { + description = "Whether the replica storage is encrypted." + value = aws_db_instance.this.storage_encrypted +} + +output "db_instance_storage_type" { + description = "The storage type of the replica." + value = aws_db_instance.this.storage_type +} + +output "db_instance_allocated_storage" { + description = "The allocated storage in GiB for the replica." + value = aws_db_instance.this.allocated_storage +} + +# ----------------------------------------------------------------------------- +# Monitoring Outputs +# ----------------------------------------------------------------------------- + +output "db_instance_monitoring_interval" { + description = "The Enhanced Monitoring collection interval in seconds." + value = aws_db_instance.this.monitoring_interval +} + +output "db_instance_performance_insights_enabled" { + description = "Whether Performance Insights is enabled on the replica." + value = aws_db_instance.this.performance_insights_enabled +} + +# ----------------------------------------------------------------------------- +# Network Outputs +# ----------------------------------------------------------------------------- + +output "security_group_id" { + description = "The ID of the security group created for the replica (if create_security_group was true)." + value = var.create_security_group ? aws_security_group.this[0].id : null +} + +output "security_group_arn" { + description = "The ARN of the security group created for the replica (if create_security_group was true)." + value = var.create_security_group ? aws_security_group.this[0].arn : null +} + +# ----------------------------------------------------------------------------- +# Additional Metadata Outputs +# ----------------------------------------------------------------------------- + +output "db_instance_status" { + description = "The current status of the RDS replica instance." + value = aws_db_instance.this.status +} + +output "db_instance_ca_cert_identifier" { + description = "The identifier of the CA certificate for the replica." + value = aws_db_instance.this.ca_cert_identifier +} + +output "tags" { + description = "All tags applied to the replica." + value = aws_db_instance.this.tags_all +} diff --git a/terraform/aws/rds-replica/tests/basic/main.tf b/terraform/aws/rds-replica/tests/basic/main.tf new file mode 100644 index 0000000..67c81ba --- /dev/null +++ b/terraform/aws/rds-replica/tests/basic/main.tf @@ -0,0 +1,68 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# Mock AWS provider for testing without credentials +provider "aws" { + region = "us-east-1" + skip_credentials_validation = true + skip_metadata_api_check = true + skip_requesting_account_id = true + skip_region_validation = true + + access_key = "test" + secret_key = "test" +} + +# Test the module with a basic read replica configuration +module "test_rds_replica" { + source = "../../" + + # Required variables + identifier = "test-mysql-replica" + source_db_instance_identifier = "test-mysql-primary" + instance_class = "db.t3.micro" + + # Network configuration + vpc_id = "vpc-12345678" + + # Storage + storage_type = "gp3" + + # Deletion settings for testing + skip_final_snapshot = true + + tags = { + Environment = "test" + ManagedBy = "terraform" + Purpose = "module-testing" + } +} + +# Test outputs to verify module behavior +output "db_instance_endpoint" { + value = module.test_rds_replica.db_instance_endpoint +} + +output "db_instance_arn" { + value = module.test_rds_replica.db_instance_arn +} + +output "db_instance_id" { + value = module.test_rds_replica.db_instance_id +} + +output "security_group_id" { + value = module.test_rds_replica.security_group_id +} + +output "db_instance_storage_encrypted" { + value = module.test_rds_replica.db_instance_storage_encrypted +} diff --git a/terraform/aws/rds-replica/variables.tf b/terraform/aws/rds-replica/variables.tf new file mode 100644 index 0000000..2c70165 --- /dev/null +++ b/terraform/aws/rds-replica/variables.tf @@ -0,0 +1,269 @@ +# ----------------------------------------------------------------------------- +# Required Variables +# ----------------------------------------------------------------------------- + +variable "identifier" { + description = "The name of the RDS replica instance. Must be unique within the AWS account and region." + type = string + + validation { + condition = can(regex("^[a-z][a-z0-9-]*$", var.identifier)) + error_message = "Identifier must start with a letter and contain only lowercase letters, numbers, and hyphens." + } + + validation { + condition = length(var.identifier) >= 1 && length(var.identifier) <= 63 + error_message = "Identifier must be between 1 and 63 characters long." + } +} + +variable "source_db_instance_identifier" { + description = "The identifier of the source RDS instance to replicate. Must have automated backups enabled (backup_retention_period > 0)." + type = string +} + +variable "instance_class" { + description = <<-EOT + The instance type of the RDS replica instance. Examples: + - db.t3.micro, db.t3.small: Burstable performance (dev/test) + - db.t4g.micro, db.t4g.small: ARM-based burstable (cost-optimized) + - db.m5.large, db.m5.xlarge: General purpose (production) + - db.r5.large, db.r5.xlarge: Memory optimized (high-performance) + See: https://aws.amazon.com/rds/instance-types/ + EOT + type = string +} + +# ----------------------------------------------------------------------------- +# Network Configuration +# ----------------------------------------------------------------------------- + +variable "vpc_id" { + description = "VPC ID where the replica will be created. Required if create_security_group is true." + type = string + default = null +} + +variable "vpc_security_group_ids" { + description = "List of VPC security group IDs to associate with the replica. If null and create_security_group is true, a security group will be created." + type = list(string) + default = null +} + +variable "create_security_group" { + description = "Whether to create a security group for the RDS replica." + type = bool + default = true +} + +variable "allowed_security_groups" { + description = "Map of security group IDs allowed to access the replica. Key is a description, value is the security group ID." + type = map(string) + default = {} +} + +variable "allowed_cidr_blocks" { + description = "List of CIDR blocks allowed to access the replica. Use sparingly for security reasons." + type = list(string) + default = [] +} + +variable "egress_cidr_blocks" { + description = "List of CIDR blocks for egress traffic. Set to empty list to disable egress rules." + type = list(string) + default = [] +} + +variable "publicly_accessible" { + description = "Whether the replica is publicly accessible. Set to false for production databases." + type = bool + default = false +} + +variable "port" { + description = "The port on which the replica accepts connections. Inherits from source if null." + type = number + default = null +} + +variable "availability_zone" { + description = "The AZ for the replica instance." + type = string + default = null +} + +# ----------------------------------------------------------------------------- +# Storage Configuration +# ----------------------------------------------------------------------------- + +variable "allocated_storage" { + description = "The allocated storage in GiB for the replica. Must be >= the source instance's storage. If null, inherits from source." + type = number + default = null + + validation { + condition = var.allocated_storage == null ? true : var.allocated_storage >= 5 + error_message = "Allocated storage must be at least 5 GiB." + } +} + +variable "max_allocated_storage" { + description = "The upper limit of storage (GiB) for autoscaling. Set to 0 to disable storage autoscaling." + type = number + default = 0 +} + +variable "storage_type" { + description = <<-EOT + Storage type for the replica. Valid values: + - gp2: General Purpose SSD + - gp3: General Purpose SSD (baseline 3000 IOPS, configurable) + - io1: Provisioned IOPS SSD + - io2: Provisioned IOPS SSD (higher durability) + - standard: Magnetic storage (legacy) + EOT + type = string + default = "gp3" + + validation { + condition = contains(["gp2", "gp3", "io1", "io2", "standard"], var.storage_type) + error_message = "Storage type must be one of: gp2, gp3, io1, io2, standard." + } +} + +variable "kms_key_id" { + description = "ARN of the KMS key to use for replica encryption. If not specified, uses the default RDS KMS key." + type = string + default = null +} + +variable "storage_encrypted" { + description = "Whether to enable storage encryption on the replica. If null, uses AWS default behavior for replicas." + type = bool + default = null +} + +variable "iops" { + description = "The amount of provisioned IOPS. Required when storage_type is io1 or io2." + type = number + default = null +} + +variable "storage_throughput" { + description = "Storage throughput value for gp3 storage type in MB/s. Valid range: 125-1000." + type = number + default = null +} + +# ----------------------------------------------------------------------------- +# Monitoring & Logging +# ----------------------------------------------------------------------------- + +variable "monitoring_interval" { + description = <<-EOT + The interval in seconds between Enhanced Monitoring metric collection. + Valid values: 0, 1, 5, 10, 15, 30, 60. Set to 0 to disable. + Requires monitoring_role_arn when enabled. + EOT + type = number + default = 0 + + validation { + condition = contains([0, 1, 5, 10, 15, 30, 60], var.monitoring_interval) + error_message = "Monitoring interval must be one of: 0, 1, 5, 10, 15, 30, 60." + } +} + +variable "monitoring_role_arn" { + description = "ARN of the IAM role for Enhanced Monitoring. Required when monitoring_interval > 0." + type = string + default = null +} + +variable "performance_insights_enabled" { + description = "Whether to enable Performance Insights on the replica." + type = bool + default = false +} + +variable "performance_insights_kms_key_id" { + description = "ARN of the KMS key to encrypt Performance Insights data." + type = string + default = null +} + +variable "performance_insights_retention_period" { + description = "Amount of time in days to retain Performance Insights data. Valid values: 7, 731 (2 years)." + type = number + default = 7 + + validation { + condition = contains([7, 731], var.performance_insights_retention_period) + error_message = "Performance Insights retention period must be either 7 or 731 days." + } +} + +# ----------------------------------------------------------------------------- +# Maintenance & Upgrade Configuration +# ----------------------------------------------------------------------------- + +variable "backup_retention_period" { + description = "Number of days to retain automated backups for the replica. Set based on your recovery requirements." + type = number + default = 7 + + validation { + condition = var.backup_retention_period >= 1 && var.backup_retention_period <= 35 + error_message = "backup_retention_period must be between 1 and 35 days." + } +} + +variable "auto_minor_version_upgrade" { + description = "Whether to automatically upgrade minor engine versions during maintenance windows." + type = bool + default = true +} + +variable "apply_immediately" { + description = "Whether to apply changes immediately or during the next maintenance window." + type = bool + default = false +} + +# ----------------------------------------------------------------------------- +# Deletion Protection +# ----------------------------------------------------------------------------- + +variable "deletion_protection" { + description = "Whether to enable deletion protection. Prevents accidental deletion of the replica." + type = bool + default = true +} + +variable "skip_final_snapshot" { + description = "Whether to skip the final snapshot when the replica is deleted." + type = bool + default = false +} + +variable "final_snapshot_identifier" { + description = "Custom identifier for the final snapshot when skip_final_snapshot is false. If null, a default is generated." + type = string + default = null +} + +variable "copy_tags_to_snapshot" { + description = "Whether to copy all instance tags to snapshots." + type = bool + default = true +} + +# ----------------------------------------------------------------------------- +# General Variables +# ----------------------------------------------------------------------------- + +variable "tags" { + description = "A map of tags to add to all resources." + type = map(string) + default = {} +}