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
+}