From 0e14063e9f567e97e13f4be13a64bb3725ed6f5f Mon Sep 17 00:00:00 2001 From: Adam Janis Date: Sun, 14 Sep 2025 12:23:53 +0100 Subject: [PATCH 1/3] feat(postgres): support read pools --- modules/postgresql/read_replica.tf | 9 +++++++-- modules/postgresql/variables.tf | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/postgresql/read_replica.tf b/modules/postgresql/read_replica.tf index f2c0bae7..6d07018b 100644 --- a/modules/postgresql/read_replica.tf +++ b/modules/postgresql/read_replica.tf @@ -37,6 +37,8 @@ resource "google_sql_database_instance" "replicas" { master_instance_name = google_sql_database_instance.default.name deletion_protection = var.read_replica_deletion_protection encryption_key_name = (join("-", slice(split("-", lookup(each.value, "zone", local.zone)), 0, 2))) == var.region ? null : each.value.encryption_key_name + instance_type = lookup(each.value, "node_count", null) != null ? "READ_POOL_INSTANCE" : "READ_REPLICA_INSTANCE" + node_count = lookup(each.value, "node_count", null) settings { tier = lookup(each.value, "tier", null) == null ? var.tier : lookup(each.value, "tier", null) @@ -98,8 +100,11 @@ resource "google_sql_database_instance" "replicas" { } } - location_preference { - zone = lookup(each.value, "zone", local.zone) + dynamic "location_preference" { + for_each = lookup(each.value, "node_count", null) == null ? ["read_pool_instance"] : [] + content { + zone = lookup(each.value, "zone", local.zone) + } } dynamic "data_cache_config" { diff --git a/modules/postgresql/variables.tf b/modules/postgresql/variables.tf index e0976f8c..9b0e9f78 100644 --- a/modules/postgresql/variables.tf +++ b/modules/postgresql/variables.tf @@ -374,6 +374,7 @@ variable "read_replicas" { }) encryption_key_name = optional(string) data_cache_enabled = optional(bool) + node_count = optional(number) })) default = [] } From 856420bdeef45b8556c98d3f30cb53bea2558b8b Mon Sep 17 00:00:00 2001 From: Adam Janis Date: Thu, 4 Dec 2025 15:34:41 +0000 Subject: [PATCH 2/3] feat: add node_count validation --- modules/postgresql/variables.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/postgresql/variables.tf b/modules/postgresql/variables.tf index 0248f473..8eeb8979 100644 --- a/modules/postgresql/variables.tf +++ b/modules/postgresql/variables.tf @@ -397,6 +397,10 @@ variable "read_replicas" { node_count = optional(number) })) default = [] + validation { + condition = length([for replica in var.read_replicas : false if contains(keys(replica), "node_count") && (replica.node_count < 1 || replica.node_count > 20)]) == 0 + error_message = "node_count for read replica must be between 1 and 20." + } } variable "read_replica_name_suffix" { From 19a44ad00e249040265e43befc5c86775187c11c Mon Sep 17 00:00:00 2001 From: Adam Janis Date: Thu, 4 Dec 2025 15:34:51 +0000 Subject: [PATCH 3/3] chore: update readme --- modules/postgresql/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/postgresql/README.md b/modules/postgresql/README.md index 7c850edc..8c87c03a 100644 --- a/modules/postgresql/README.md +++ b/modules/postgresql/README.md @@ -170,7 +170,7 @@ module "pg" { | read\_replica\_deletion\_protection | Used to block Terraform from deleting replica SQL Instances. | `bool` | `false` | no | | read\_replica\_deletion\_protection\_enabled | Enables protection of replica instance from accidental deletion across all surfaces (API, gcloud, Cloud Console and Terraform). | `bool` | `false` | no | | read\_replica\_name\_suffix | The optional suffix to add to the read instance name | `string` | `""` | no | -| read\_replicas | List of read replicas to create. Encryption key is required for replica in different region. For replica in same region as master set encryption\_key\_name = null |
list(object({
name = string
name_override = optional(string)
tier = optional(string)
edition = optional(string)
availability_type = optional(string)
zone = optional(string)
disk_type = optional(string)
disk_autoresize = optional(bool)
disk_autoresize_limit = optional(number)
disk_size = optional(string)
user_labels = map(string)
connection_pool_config = optional(object({
enabled = optional(bool, false)
flags = optional(list(object({
name = string
value = string
})), [])
}), null)
database_flags = optional(list(object({
name = string
value = string
})), [])
insights_config = optional(object({
query_plans_per_minute = optional(number, 5)
query_string_length = optional(number, 1024)
record_application_tags = optional(bool, false)
record_client_address = optional(bool, false)
}), null)
final_backup_config = optional(object({
enabled = optional(bool, false)
retention_days = optional(number, 1)
}), null)
ip_configuration = object({
authorized_networks = optional(list(map(string)), [])
ipv4_enabled = optional(bool)
private_network = optional(string)
ssl_mode = optional(string)
allocated_ip_range = optional(string)
enable_private_path_for_google_cloud_services = optional(bool, false)
psc_enabled = optional(bool, false)
psc_allowed_consumer_projects = optional(list(string), [])
})
encryption_key_name = optional(string)
data_cache_enabled = optional(bool)
}))
| `[]` | no | +| read\_replicas | List of read replicas to create. Encryption key is required for replica in different region. For replica in same region as master set encryption\_key\_name = null |
list(object({
name = string
name_override = optional(string)
tier = optional(string)
edition = optional(string)
availability_type = optional(string)
zone = optional(string)
disk_type = optional(string)
disk_autoresize = optional(bool)
disk_autoresize_limit = optional(number)
disk_size = optional(string)
user_labels = map(string)
connection_pool_config = optional(object({
enabled = optional(bool, false)
flags = optional(list(object({
name = string
value = string
})), [])
}), null)
database_flags = optional(list(object({
name = string
value = string
})), [])
insights_config = optional(object({
query_plans_per_minute = optional(number, 5)
query_string_length = optional(number, 1024)
record_application_tags = optional(bool, false)
record_client_address = optional(bool, false)
}), null)
final_backup_config = optional(object({
enabled = optional(bool, false)
retention_days = optional(number, 1)
}), null)
ip_configuration = object({
authorized_networks = optional(list(map(string)), [])
ipv4_enabled = optional(bool)
private_network = optional(string)
ssl_mode = optional(string)
allocated_ip_range = optional(string)
enable_private_path_for_google_cloud_services = optional(bool, false)
psc_enabled = optional(bool, false)
psc_allowed_consumer_projects = optional(list(string), [])
})
encryption_key_name = optional(string)
data_cache_enabled = optional(bool)
node_count = optional(number)
}))
| `[]` | no | | region | The region of the Cloud SQL resources | `string` | `"us-central1"` | no | | retain\_backups\_on\_delete | When this parameter is set to true, Cloud SQL retains backups of the instance even after the instance is deleted. The ON\_DEMAND backup will be retained until customer deletes the backup or the project. The AUTOMATED backup will be retained based on the backups retention setting. | `bool` | `false` | no | | root\_password | Initial root password during creation | `string` | `null` | no |