diff --git a/README.md b/README.md index c157d4f0..bd18ba81 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,7 @@ Available targets: | [aws_rds_cluster_activity_stream.primary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_activity_stream) | resource | | [aws_rds_cluster_instance.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_instance) | resource | | [aws_rds_cluster_parameter_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_parameter_group) | resource | +| [aws_rds_reserved_instance.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_reserved_instance) | resource | | [aws_security_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group_rule.egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | | [aws_security_group_rule.egress_ipv6](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | @@ -364,6 +365,7 @@ Available targets: | [random_pet.instance](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | | [aws_iam_policy_document.enhanced_monitoring](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_rds_reserved_instance_offering.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/rds_reserved_instance_offering) | data source | ## Inputs @@ -445,6 +447,8 @@ Available targets: | [publicly\_accessible](#input\_publicly\_accessible) | Set to true if you want your cluster to be publicly accessible (such as via QuickSight) | `bool` | `false` | no | | [rds\_monitoring\_interval](#input\_rds\_monitoring\_interval) | The interval, in seconds, between points when enhanced monitoring metrics are collected for the DB instance. To disable collecting Enhanced Monitoring metrics, specify 0. The default is 0. Valid Values: 0, 1, 5, 10, 15, 30, 60 | `number` | `0` | no | | [rds\_monitoring\_role\_arn](#input\_rds\_monitoring\_role\_arn) | The ARN for the IAM role that permits RDS to send enhanced monitoring metrics to CloudWatch Logs | `string` | `null` | no | +| [rds\_ri\_duration](#input\_rds\_ri\_duration) | The number of years to reserve the instance. Values can be 1 or 3 (or in seconds, 31536000 or 94608000) | `number` | `1` | no | +| [rds\_ri\_offering\_type](#input\_rds\_ri\_offering\_type) | Offering type of reserved DB instances. Valid values are 'No Upfront', 'Partial Upfront', 'All Upfront'. | `string` | `""` | no | | [reader\_dns\_name](#input\_reader\_dns\_name) | Name of the reader endpoint CNAME record to create in the parent DNS zone specified by `zone_id`. If left empty, the name will be auto-asigned using the format `replicas.var.name` | `string` | `""` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | | [replication\_source\_identifier](#input\_replication\_source\_identifier) | ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica | `string` | `""` | no | @@ -465,6 +469,7 @@ Available targets: | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | | [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | | [timeouts\_configuration](#input\_timeouts\_configuration) | List of timeout values per action. Only valid actions are `create`, `update` and `delete` |
list(object({
create = string
update = string
delete = string
}))
| `[]` | no | +| [use\_reserved\_instances](#input\_use\_reserved\_instances) | WARNING: Observe your plans and applies carefully when using this feature.
It has potential to be very expensive if not used correctly.

Whether to use reserved instances. | `bool` | `false` | no | | [vpc\_id](#input\_vpc\_id) | VPC ID to create the cluster in (e.g. `vpc-a22222ee`) | `string` | n/a | yes | | [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | Additional security group IDs to apply to the cluster, in addition to the provisioned default security group with ingress traffic from existing CIDR blocks and existing security groups | `list(string)` | `[]` | no | | [zone\_id](#input\_zone\_id) | Route53 DNS Zone ID as list of string (0 or 1 items). If empty, no custom DNS name will be published.
If the list contains a single Zone ID, a custom DNS name will be pulished in that zone.
Can also be a plain string, but that use is DEPRECATED because of Terraform issues. | `any` | `[]` | no | @@ -486,6 +491,7 @@ Available targets: | [master\_username](#output\_master\_username) | Username for the master DB user | | [reader\_endpoint](#output\_reader\_endpoint) | A read-only endpoint for the Aurora cluster, automatically load-balanced across replicas | | [replicas\_host](#output\_replicas\_host) | Replicas hostname | +| [reserved\_instance](#output\_reserved\_instance) | All information about the reserved instance(s) if created. | | [security\_group\_arn](#output\_security\_group\_arn) | Security Group ARN | | [security\_group\_id](#output\_security\_group\_id) | Security Group ID | | [security\_group\_name](#output\_security\_group\_name) | Security Group name | diff --git a/docs/terraform.md b/docs/terraform.md index 18889aeb..0f4f344c 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -40,6 +40,7 @@ | [aws_rds_cluster_activity_stream.primary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_activity_stream) | resource | | [aws_rds_cluster_instance.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_instance) | resource | | [aws_rds_cluster_parameter_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster_parameter_group) | resource | +| [aws_rds_reserved_instance.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_reserved_instance) | resource | | [aws_security_group.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group_rule.egress](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | | [aws_security_group_rule.egress_ipv6](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | @@ -50,6 +51,7 @@ | [random_pet.instance](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | | [aws_iam_policy_document.enhanced_monitoring](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_rds_reserved_instance_offering.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/rds_reserved_instance_offering) | data source | ## Inputs @@ -131,6 +133,8 @@ | [publicly\_accessible](#input\_publicly\_accessible) | Set to true if you want your cluster to be publicly accessible (such as via QuickSight) | `bool` | `false` | no | | [rds\_monitoring\_interval](#input\_rds\_monitoring\_interval) | The interval, in seconds, between points when enhanced monitoring metrics are collected for the DB instance. To disable collecting Enhanced Monitoring metrics, specify 0. The default is 0. Valid Values: 0, 1, 5, 10, 15, 30, 60 | `number` | `0` | no | | [rds\_monitoring\_role\_arn](#input\_rds\_monitoring\_role\_arn) | The ARN for the IAM role that permits RDS to send enhanced monitoring metrics to CloudWatch Logs | `string` | `null` | no | +| [rds\_ri\_duration](#input\_rds\_ri\_duration) | The number of years to reserve the instance. Values can be 1 or 3 (or in seconds, 31536000 or 94608000) | `number` | `1` | no | +| [rds\_ri\_offering\_type](#input\_rds\_ri\_offering\_type) | Offering type of reserved DB instances. Valid values are 'No Upfront', 'Partial Upfront', 'All Upfront'. | `string` | `""` | no | | [reader\_dns\_name](#input\_reader\_dns\_name) | Name of the reader endpoint CNAME record to create in the parent DNS zone specified by `zone_id`. If left empty, the name will be auto-asigned using the format `replicas.var.name` | `string` | `""` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | | [replication\_source\_identifier](#input\_replication\_source\_identifier) | ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica | `string` | `""` | no | @@ -151,6 +155,7 @@ | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | | [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | | [timeouts\_configuration](#input\_timeouts\_configuration) | List of timeout values per action. Only valid actions are `create`, `update` and `delete` |
list(object({
create = string
update = string
delete = string
}))
| `[]` | no | +| [use\_reserved\_instances](#input\_use\_reserved\_instances) | WARNING: Observe your plans and applies carefully when using this feature.
It has potential to be very expensive if not used correctly.

Whether to use reserved instances. | `bool` | `false` | no | | [vpc\_id](#input\_vpc\_id) | VPC ID to create the cluster in (e.g. `vpc-a22222ee`) | `string` | n/a | yes | | [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | Additional security group IDs to apply to the cluster, in addition to the provisioned default security group with ingress traffic from existing CIDR blocks and existing security groups | `list(string)` | `[]` | no | | [zone\_id](#input\_zone\_id) | Route53 DNS Zone ID as list of string (0 or 1 items). If empty, no custom DNS name will be published.
If the list contains a single Zone ID, a custom DNS name will be pulished in that zone.
Can also be a plain string, but that use is DEPRECATED because of Terraform issues. | `any` | `[]` | no | @@ -172,6 +177,7 @@ | [master\_username](#output\_master\_username) | Username for the master DB user | | [reader\_endpoint](#output\_reader\_endpoint) | A read-only endpoint for the Aurora cluster, automatically load-balanced across replicas | | [replicas\_host](#output\_replicas\_host) | Replicas hostname | +| [reserved\_instance](#output\_reserved\_instance) | All information about the reserved instance(s) if created. | | [security\_group\_arn](#output\_security\_group\_arn) | Security Group ARN | | [security\_group\_id](#output\_security\_group\_id) | Security Group ID | | [security\_group\_name](#output\_security\_group\_name) | Security Group name | diff --git a/main.tf b/main.tf index 73df8cd1..f75dd30e 100644 --- a/main.tf +++ b/main.tf @@ -7,6 +7,8 @@ locals { is_regional_cluster = var.cluster_type == "regional" is_serverless = var.engine_mode == "serverless" ignore_admin_credentials = var.replication_source_identifier != "" || var.snapshot_identifier != null + reserved_instance_engine = split("-", var.engine)[1] + use_reserved_instances = var.use_reserved_instances && !local.is_serverless && contains(["mysql", "postgresql"], local.reserved_instance_engine) } data "aws_partition" "current" { @@ -88,6 +90,28 @@ resource "aws_security_group_rule" "egress_ipv6" { security_group_id = join("", aws_security_group.default[*].id) } +data "aws_rds_reserved_instance_offering" "default" { + count = local.use_reserved_instances ? 1 : 0 + db_instance_class = var.db_cluster_instance_class + duration = var.rds_ri_duration + multi_az = local.is_regional_cluster + offering_type = var.rds_ri_offering_type + product_description = local.reserved_instance_engine +} + +# Note: I'm not sure what will happen when the db reservation expires, and this is not easy to test. +# It will either be recreated or will require manual intervention to recreate. +resource "aws_rds_reserved_instance" "default" { + count = local.use_reserved_instances ? 1 : 0 + + offering_id = data.aws_rds_reserved_instance_offering.default[0].id + instance_count = local.cluster_instance_count + lifecycle { + # Once created, we want to avoid any case of accidentally re-creating. + prevent_destroy = true + } +} + # The name "primary" is poorly chosen. We actually mean standalone or regional. # The primary cluster of a global database is actually created with the "secondary" cluster resource below. resource "aws_rds_cluster" "primary" { diff --git a/outputs.tf b/outputs.tf index 6a359c6b..d52e9c50 100644 --- a/outputs.tf +++ b/outputs.tf @@ -78,3 +78,8 @@ output "activity_stream_name" { value = join("", aws_rds_cluster_activity_stream.primary[*].kinesis_stream_name) description = "Activity Stream Name" } + +output "reserved_instance" { + value = join("", aws_rds_reserved_instance.default[*]) + description = "All information about the reserved instance(s) if created." +} diff --git a/variables.tf b/variables.tf index 5b54c723..d0b73bbf 100644 --- a/variables.tf +++ b/variables.tf @@ -542,4 +542,27 @@ variable "network_type" { type = string default = "IPV4" description = "The network type of the cluster. Valid values: IPV4, DUAL." -} \ No newline at end of file +} + +variable "use_reserved_instances" { + description = <<-EOT + WARNING: Observe your plans and applies carefully when using this feature. + It has potential to be very expensive if not used correctly. + + Whether to use reserved instances. + EOT + type = bool + default = false +} + +variable "rds_ri_offering_type" { + description = "Offering type of reserved DB instances. Valid values are 'No Upfront', 'Partial Upfront', 'All Upfront'." + type = string + default = "" +} + +variable "rds_ri_duration" { + description = "The number of years to reserve the instance. Values can be 1 or 3 (or in seconds, 31536000 or 94608000)" + type = number + default = 1 +}