AlloyDB read poll support and various usability fixes (#3061)

* AlloyDB read poll support and various usability fixes

* Added support for read poll instances.
* Added support for public (outbound) IPs.
* Added new arguments: `machine_type`, `skip_await_major_version_upgrade`, and `subscription_type`.
* Added outputs for key cluster attributes, including ID and name.
* Improved input variable validations and simplified configurations.
* Fixed bug where AlloyDB user accounts always had null passwords.
* Resolved Terraform state drift issues for `network_config` and `psc_config`.
* Fixed `continuous_backup_config` always being implicitly or explicitly enabled.
* Fixed `automated_backup_policy` and `maintenance_update_policy`, which do not support non-zero minutes, seconds and nanos.
* Fixed various typos.
* Aligned the format of test examples.

* Fixed invalid condition: authorized external networks require enabled public IP, however enabled public IP can have empty authorized external networks

* Formatting

* Fix PSA block

---------

Co-authored-by: Julio Castillo <jccb@google.com>
This commit is contained in:
Viliam Pucik
2025-05-09 13:03:58 +02:00
committed by GitHub
parent ee468514b0
commit 2ce8363812
10 changed files with 641 additions and 347 deletions

View File

@@ -22,7 +22,6 @@ variable "annotations" {
variable "automated_backup_configuration" {
description = "Automated backup settings for cluster."
nullable = false
type = object({
enabled = optional(bool, false)
backup_window = optional(string, "1800s")
@@ -32,44 +31,27 @@ variable "automated_backup_configuration" {
"MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"
])
start_times = optional(object({
hours = optional(number, 23)
minutes = optional(number, 0)
seconds = optional(number, 0)
nanos = optional(number, 0)
hours = optional(number, 23)
}), {})
}), {})
retention_count = optional(number, 7)
retention_period = optional(string, null)
retention_period = optional(string)
})
default = {
enabled = false
backup_window = "1800s"
location = null
weekly_schedule = {
days_of_week = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]
start_times = {
hours = 23
minutes = 0
seconds = 0
nanos = 0
}
}
retention_count = 7
retention_period = null
}
default = {}
nullable = false
validation {
condition = (
var.automated_backup_configuration.enabled ? (
# Maintenance window validation below
# Backup window validation below
!(var.automated_backup_configuration.retention_count != null && var.automated_backup_configuration.retention_period != null) &&
# Maintenance window day validation
length([
for day in var.automated_backup_configuration.weekly_schedule.days_of_week : true
if contains(["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"], day)
]) == length(var.automated_backup_configuration.weekly_schedule.days_of_week)
# Backup window hours below
var.automated_backup_configuration.weekly_schedule.start_times.hours >= 0 &&
var.automated_backup_configuration.weekly_schedule.start_times.hours <= 23 &&
# Backup window day validation
setintersection(["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"], var.automated_backup_configuration.weekly_schedule.days_of_week) == toset(var.automated_backup_configuration.weekly_schedule.days_of_week)
) : true
)
error_message = "Days of week must contains one or more days with the following format 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'. You can only specify retention_count or retention_period."
error_message = "Days of week must contains one or more days with the following format 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'. Backup window hour must be between 0 and 23. You can only specify retention_count or retention_period."
}
}
@@ -85,7 +67,7 @@ variable "client_connection_config" {
require_connectors = optional(bool, false)
ssl_config = optional(object({
ssl_mode = string
}), null)
}))
})
default = null
}
@@ -99,19 +81,17 @@ variable "cluster_display_name" {
variable "cluster_name" {
description = "Name of the primary cluster."
type = string
nullable = false
}
variable "continuous_backup_configuration" {
description = "Continuous backup settings for cluster."
nullable = true
type = object({
enabled = optional(bool, false)
enabled = optional(bool, true)
recovery_window_days = optional(number, 14)
})
default = {
enabled = true
recovery_window_days = 14
}
nullable = false
default = {}
}
variable "cross_region_replication" {
@@ -120,23 +100,29 @@ variable "cross_region_replication" {
enabled = optional(bool, false)
promote_secondary = optional(bool, false)
switchover_mode = optional(bool, false)
region = optional(string, null)
secondary_cluster_display_name = optional(string, null)
secondary_cluster_name = optional(string, null)
secondary_instance_display_name = optional(string, null)
secondary_instance_name = optional(string, null)
region = optional(string)
secondary_cluster_display_name = optional(string)
secondary_cluster_name = optional(string)
secondary_instance_display_name = optional(string)
secondary_instance_name = optional(string)
secondary_machine_config = optional(object({
cpu_count = number
}), null)
cpu_count = number
machine_type = optional(string)
}))
})
default = {}
default = {}
nullable = false
validation {
condition = !var.cross_region_replication.enabled || var.cross_region_replication.enabled && var.cross_region_replication.region != null
error_message = "Region must be available when cross region replication is enabled."
}
validation {
condition = !(var.cross_region_replication.switchover_mode && var.cross_region_replication.promote_secondary)
error_message = "Please choose to either promote secondary cluster or align an existing cluster after swtichover."
error_message = "Please choose to either promote secondary cluster or align an existing cluster after switchover."
}
validation {
condition = contains([2, 4, 8, 16, 32, 64, 96, 128], try(var.cross_region_replication.secondary_machine_config.cpu_count, 2))
error_message = "The number of CPU's in the VM instance must be one of [2, 4, 8, 16, 32, 64, 96, 128]"
}
}
@@ -162,10 +148,9 @@ variable "encryption_config" {
description = "Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'."
type = object({
primary_kms_key_name = string
secondary_kms_key_name = optional(string, null)
secondary_kms_key_name = optional(string)
})
default = null
nullable = true
default = null
}
variable "flags" {
@@ -183,7 +168,7 @@ variable "gce_zone" {
variable "initial_user" {
description = "AlloyDB cluster initial user credentials."
type = object({
user = optional(string, "root")
user = optional(string, "postgres")
password = string
})
default = null
@@ -192,6 +177,7 @@ variable "initial_user" {
variable "instance_name" {
description = "Name of primary instance."
type = string
nullable = false
}
variable "labels" {
@@ -203,16 +189,20 @@ variable "labels" {
variable "location" {
description = "Region or zone of the cluster and instance."
type = string
nullable = false
}
variable "machine_config" {
description = "AlloyDB machine config."
type = object({
cpu_count = optional(number, 2)
cpu_count = optional(number, 2)
machine_type = optional(string)
})
nullable = false
default = {
cpu_count = 2
default = {}
validation {
condition = contains([2, 4, 8, 16, 32, 64, 96, 128], var.machine_config.cpu_count)
error_message = "The number of CPU's in the VM instance must be one of [2, 4, 8, 16, 32, 64, 96, 128]"
}
}
@@ -222,37 +212,22 @@ variable "maintenance_config" {
enabled = optional(bool, false)
day = optional(string, "SUNDAY")
start_time = optional(object({
hours = optional(number, 23)
minutes = optional(number, 0)
seconds = optional(number, 0)
nanos = optional(number, 0)
hours = optional(number, 23)
}), {})
})
default = {
enabled = false
day = "SUNDAY"
start_time = {
hours = 23
minutes = 0
seconds = 0
nanos = 0
}
}
default = {}
validation {
condition = (
var.maintenance_config.enabled ? (
# Maintenance window validation below
var.maintenance_config.start_time.hours >= 0 &&
var.maintenance_config.start_time.hours <= 23 &&
var.maintenance_config.start_time.minutes == 0 &&
var.maintenance_config.start_time.seconds == 0 &&
var.maintenance_config.start_time.nanos == 0 &&
# Maintenance window day validation
contains([
"MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"
], var.maintenance_config.day)) : true
)
error_message = "Maintenance window day must one of 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'. Maintenance window hour must be between 0 and 23 and maintenance window minutes, seconds and nanos should be 0."
error_message = "Maintenance window day must one of 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'. Maintenance window hour must be between 0 and 23."
}
}
@@ -260,28 +235,20 @@ variable "network_config" {
description = "Network configuration for cluster and instance. Only one between psa_config and psc_config can be used."
type = object({
psa_config = optional(object({
network = optional(string)
network = string
allocated_ip_range = optional(string)
authorized_external_networks = optional(list(string), [])
enable_public_ip = optional(bool, false)
enable_outbound_public_ip = optional(bool, false)
}))
psc_config = optional(object({
allowed_consumer_projects = optional(list(string), [])
}), null)
allowed_consumer_projects = list(string)
}))
})
nullable = false
validation {
condition = (
var.network_config.psa_config == null || (
(
try(var.network_config.psa_config.enable_public_ip, false) &&
try(length(var.network_config.psa_config.authorized_external_networks), 0) > 0
) || (
try(length(var.network_config.psa_config.authorized_external_networks), 0) == 0
)
)
)
error_message = "A list of external network authorized to access this instance is required only in case public ip is enabled for the instance."
condition = try(length(var.network_config.psa_config.authorized_external_networks) > 0, false) ? try(var.network_config.psa_config.enable_public_ip, false) : true
error_message = "A list of external network authorized to access this instance is required only in case public IP is enabled for the instance."
}
validation {
condition = (var.network_config.psc_config == null) != (var.network_config.psa_config == null)
@@ -318,12 +285,64 @@ variable "query_insights_config" {
record_client_address = optional(bool, true)
query_plans_per_minute = optional(number, 5)
})
default = {
query_string_length = 1024
record_application_tags = true
record_client_address = true
query_plans_per_minute = 5
default = {}
}
variable "read_pool" {
description = "Map of read pool instances to create in the primary cluster."
type = map(object({
display_name = optional(string)
node_count = optional(number, 1)
flags = optional(map(string))
client_connection_config = optional(object({
require_connectors = optional(bool, false)
ssl_config = optional(object({
ssl_mode = string
}))
}))
machine_config = optional(object({
cpu_count = optional(number, 2)
machine_type = optional(string)
}), {})
network_config = optional(object({
authorized_external_networks = optional(list(string), [])
enable_public_ip = optional(bool, false)
}), {})
query_insights_config = optional(object({
query_string_length = optional(number, 1024)
record_application_tags = optional(bool, true)
record_client_address = optional(bool, true)
query_plans_per_minute = optional(number, 5)
}))
}))
nullable = false
default = {}
validation {
condition = alltrue([
for k, v in var.read_pool :
contains([2, 4, 8, 16, 32, 64, 96, 128], v.machine_config.cpu_count)
])
error_message = "The number of CPU's in the VM instance must be one of [2, 4, 8, 16, 32, 64, 96, 128]"
}
validation {
condition = alltrue([
for k, v in var.read_pool :
try(length(v.network_config.psa_config.authorized_external_networks) > 0, false) ? try(v.network_config.psa_config.enable_public_ip, false) : true
])
error_message = "A list of external network authorized to access this replica pool instance is required only in case public IP is enabled for the replica pool instance."
}
}
variable "skip_await_major_version_upgrade" {
description = "Set to true to skip awaiting on the major version upgrade of the cluster."
type = bool
default = true
}
variable "subscription_type" {
description = "The subscription type of cluster. Possible values are: 'STANDARD' or 'TRIAL'."
type = string
default = "STANDARD"
}
variable "tag_bindings" {
@@ -338,14 +357,15 @@ variable "users" {
type = map(object({
password = optional(string)
roles = optional(list(string), ["alloydbsuperuser"])
type = optional(string)
type = optional(string, "ALLOYDB_BUILT_IN")
}))
default = null
nullable = false
default = {}
validation {
condition = alltrue([
for user in coalesce(var.users, {}) :
try(contains(["ALLOYDB_BUILT_IN", "ALLOYDB_IAM_USER"], user.type), true)
for user in var.users :
contains(["ALLOYDB_BUILT_IN", "ALLOYDB_IAM_USER"], user.type)
])
error_message = "User type must one of 'ALLOYDB_BUILT_IN', 'ALLOYDB_IAM_USER'"
error_message = "User type must one of 'ALLOYDB_BUILT_IN', 'ALLOYDB_IAM_USER'."
}
}