From c651eaf7e12bb3a744daa9c7f86a272c215a584b Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Wed, 15 May 2024 13:06:50 +0200 Subject: [PATCH 01/10] add alloydb module --- modules/alloydb/README.md | 128 ++++++ modules/alloydb/main.tf | 375 ++++++++++++++++++ modules/alloydb/outputs.tf | 76 ++++ modules/alloydb/variables.tf | 303 ++++++++++++++ modules/alloydb/versions.tf | 27 ++ .../examples/cross_region_replication.yaml | 102 +++++ tests/modules/alloydb/examples/simple.yaml | 171 ++++++++ .../examples/alloydb_instance.tfvars | 40 -- .../examples/alloydb_instance.yaml | 103 ----- 9 files changed, 1182 insertions(+), 143 deletions(-) create mode 100644 modules/alloydb/README.md create mode 100644 modules/alloydb/main.tf create mode 100644 modules/alloydb/outputs.tf create mode 100644 modules/alloydb/variables.tf create mode 100644 modules/alloydb/versions.tf create mode 100644 tests/modules/alloydb/examples/cross_region_replication.yaml create mode 100644 tests/modules/alloydb/examples/simple.yaml delete mode 100644 tests/modules/alloydb_instance/examples/alloydb_instance.tfvars delete mode 100644 tests/modules/alloydb_instance/examples/alloydb_instance.yaml diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md new file mode 100644 index 0000000000..274e957162 --- /dev/null +++ b/modules/alloydb/README.md @@ -0,0 +1,128 @@ +# AlloyDB module + +This module manages the creation of an AlloyDB cluster and instance with potential read replicas and an advanced cross region replication setup for disaster recovery scenarios. +It can also create an initial set of users via the `users` parameters. + +Note that this module assumes that some options are the same for both the primary instance and the secondary one in case of cross regional replication configuration. + +*Warning:* if you use the `users` field, you terraform state will contain each user's password in plain text. + + +* [AlloyDB module](#alloydb-module) + * [Examples](#examples) + * [Simple example](#simple-example) + * [Cross region replication](#cross-region-replication) + * [Variables](#variables) + * [Outputs](#outputs) + + +## Examples +### Simple example + +This example shows how to setup a project, VPC and AlloyDB cluster and instance. + +```hcl +module "project" { + source = "./fabric/modules/project" + billing_account = var.billing_account_id + parent = var.folder_id + name = "alloydb-prj" + prefix = var.prefix + services = [ + "servicenetworking.googleapis.com", + "alloydb.googleapis.com", + ] +} + +module "vpc" { + source = "./fabric/modules/net-vpc" + project_id = module.project.project_id + name = "my-network" + # need only one - psa_config or subnets_psc + psa_configs = [{ + ranges = { alloydb = "10.60.0.0/16" } + deletion_policy = "ABANDON" + }] + subnets_psc = [ + { + ip_cidr_range = "10.0.3.0/24" + name = "psc" + region = var.region + } + ] +} + +module "alloydb" { + source = "./fabric/modules/alloydb" + project_id = module.project.project_id + cluster_name = "db" + cluster_network_config = { + network = module.vpc.id + } + name = "db" + location = var.region +} +# tftest modules=3 resources=14 inventory=simple.yaml e2e +``` + +### Cross region replication + +```hcl +module "alloydb" { + source = "./fabric/modules/alloydb" + project_id = var.project_id + cluster_name = "db" + cluster_network_config = { + network = var.vpc.self_link + } + name = "db" + location = "europe-west8" + cross_region_replication = { + enabled = true + region = "europe-west12" + } +} +# tftest modules=1 resources=4 inventory=cross_region_replication.yaml e2e +``` + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [cluster_name](variables.tf#L85) | Name of the primary cluster. | string | ✓ | | +| [cluster_network_config](variables.tf#L90) | Network configuration for the cluster. Only one between cluster_network_config and cluster_psc_config can be used. | object({…}) | ✓ | | +| [location](variables.tf#L196) | Region or zone of the cluster and instance. | string | ✓ | | +| [name](variables.tf#L252) | Name of primary instance. | string | ✓ | | +| [project_id](variables.tf#L267) | The ID of the project where this instances will be created. | string | ✓ | | +| [automated_backup_configuration](variables.tf#L17) | Automated backup settings for cluster. | object({…}) | | {…} | +| [availability_type](variables.tf#L68) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "REGIONAL" | +| [client_connection_config](variables.tf#L74) | Client connection config. | object({…}) | | null | +| [continuous_backup_configuration](variables.tf#L99) | Continuous backup settings for cluster. | object({…}) | | {…} | +| [cross_region_replication](variables.tf#L112) | Cross region replication config. | object({…}) | | {} | +| [database_version](variables.tf#L126) | Database type and version to create. | string | | "POSTGRES_15" | +| [deletion_policy](variables.tf#L132) | AlloyDB cluster and instance deletion policy. | string | | null | +| [encryption_config](variables.tf#L138) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | object({…}) | | null | +| [flags](variables.tf#L148) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | +| [initial_user](variables.tf#L155) | AlloyDB cluster initial user credentials. | object({…}) | | null | +| [insights_config](variables.tf#L164) | Query Insights configuration. Defaults to null which disables Query Insights. | object({…}) | | null | +| [instance_network_config](variables.tf#L175) | Network configuration for the instance. Only one between instance_network_config and instance_psc_config can be used. | object({…}) | | null | +| [labels](variables.tf#L190) | Labels to be attached to all instances. | map(string) | | null | +| [machine_config](variables.tf#L201) | AlloyDB machine config. | object({…}) | | {…} | +| [maintenance_config](variables.tf#L212) | Set maintenance window configuration. | object({…}) | | {…} | +| [prefix](variables.tf#L257) | Optional prefix used to generate instance names. | string | | null | +| [query_insights_config](variables.tf#L272) | Query insights config. | object({…}) | | {…} | +| [users](variables.tf#L288) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [id](outputs.tf#L25) | Fully qualified primary instance id. | | +| [ids](outputs.tf#L30) | Fully qualified ids of all instances. | | +| [instances](outputs.tf#L38) | AlloyDB instance resources. | ✓ | +| [ip](outputs.tf#L44) | IP address of the primary instance. | | +| [ips](outputs.tf#L49) | IP addresses of all instances. | | +| [name](outputs.tf#L56) | Name of the primary instance. | | +| [names](outputs.tf#L61) | Names of all instances. | | +| [user_passwords](outputs.tf#L69) | Map of containing the password of all users created through terraform. | ✓ | + diff --git a/modules/alloydb/main.tf b/modules/alloydb/main.tf new file mode 100644 index 0000000000..6fa18bdc84 --- /dev/null +++ b/modules/alloydb/main.tf @@ -0,0 +1,375 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + prefix = var.prefix == null ? "" : "${var.prefix}-" + # has_replicas = try(length(var.replicas) > 0, false) + is_regional = var.availability_type == "REGIONAL" ? true : false + + users = { + for k, v in coalesce(var.users, {}) : + k => { + name = k + password = try(v.type, "ALLOYDB_BUILT_IN") == "ALLOYDB_BUILT_IN" ? try(random_password.passwords[k].result, v.password) : null + roles = v.roles + type = try(v.type, "ALLOYDB_BUILT_IN") + } + } +} + +resource "google_alloydb_cluster" "primary" { + project = var.project_id + cluster_id = "${local.prefix}${var.cluster_name}" + database_version = var.database_version + deletion_policy = var.deletion_policy + labels = var.labels + location = var.location + + network_config { + network = var.cluster_network_config.network + allocated_ip_range = var.cluster_network_config.allocated_ip_range + } + + dynamic "automated_backup_policy" { + for_each = var.automated_backup_configuration.enabled ? { 1 = 1 } : {} + content { + enabled = true + location = var.automated_backup_configuration.location + backup_window = var.automated_backup_configuration.backup_window + labels = var.labels + + dynamic "encryption_config" { + for_each = var.encryption_config != null ? { 1 = 1 } : {} + content { + kms_key_name = var.encryption_config.primary_kms_key_name + } + } + + weekly_schedule { + days_of_week = var.automated_backup_configuration.weekly_schedule.days_of_week + start_times { + hours = var.automated_backup_configuration.weekly_schedule.start_times.hours + minutes = var.automated_backup_configuration.weekly_schedule.start_times.minutes + seconds = var.automated_backup_configuration.weekly_schedule.start_times.seconds + nanos = var.automated_backup_configuration.weekly_schedule.start_times.nanos + } + } + + dynamic "quantity_based_retention" { + for_each = var.automated_backup_configuration.retention_count != null ? { 1 = 1 } : {} + content { + count = var.automated_backup_configuration.retention_count + } + } + + dynamic "time_based_retention" { + for_each = var.automated_backup_configuration.retention_period != null ? { 1 = 1 } : {} + content { + retention_period = var.automated_backup_configuration.retention_period + } + } + } + } + + dynamic "continuous_backup_config" { + for_each = var.continuous_backup_configuration.enabled ? { 1 = 1 } : {} + content { + enabled = true + recovery_window_days = 14 + dynamic "encryption_config" { + for_each = var.encryption_config != null ? { 1 = 1 } : {} + content { + kms_key_name = var.encryption_config.primary_kms_key_name + } + } + } + } + + dynamic "encryption_config" { + for_each = var.encryption_config != null ? { 1 = 1 } : {} + content { + kms_key_name = var.encryption_config.primary_kms_key_name + } + } + + dynamic "initial_user" { + for_each = var.initial_user != null ? { 1 = 1 } : {} + content { + user = var.initial_user.user + password = var.initial_user.password + } + } + + # TODO manage backup restore + # restore_backup_source { + # backup_name = "" + # } + + # restore_continuous_backup_source { + # cluster = "" + # point_in_time = "" + # } + + dynamic "maintenance_update_policy" { + for_each = var.maintenance_config.enabled ? { 1 = 1 } : {} + content { + maintenance_windows { + day = var.maintenance_config.day + start_time { + hours = var.maintenance_config.start_times.hours + minutes = var.maintenance_config.start_times.minutes + seconds = var.maintenance_config.start_times.seconds + nanos = var.maintenance_config.start_times.nanos + } + } + } + } +} + +resource "google_alloydb_instance" "primary" { + cluster = google_alloydb_cluster.primary.id + instance_id = "${local.prefix}${var.name}" + instance_type = "PRIMARY" + availability_type = var.availability_type + database_flags = var.flags + display_name = "${local.prefix}${var.name}" + gce_zone = local.is_regional ? null : var.location + labels = var.labels + + dynamic "query_insights_config" { + for_each = var.query_insights_config != null ? { 1 = 1 } : {} + content { + query_string_length = var.query_insights_config.query_string_length + record_application_tags = var.query_insights_config.record_application_tags + record_client_address = var.query_insights_config.record_client_address + query_plans_per_minute = var.query_insights_config.query_plans_per_minute + } + } + + dynamic "machine_config" { + for_each = var.machine_config != null ? { 1 = 1 } : {} + content { + cpu_count = var.machine_config.cpu_count + } + } + + dynamic "client_connection_config" { + for_each = var.client_connection_config != null ? { 1 = 1 } : {} + content { + require_connectors = var.client_connection_config.require_connectors + dynamic "ssl_config" { + for_each = var.client_connection_config.ssl_config != null ? { 1 = 1 } : {} + content { + ssl_mode = var.client_connection_config.ssl_config.ssl_mode + } + } + } + } + + dynamic "network_config" { + for_each = var.instance_network_config != null ? { 1 = 1 } : {} + content { + dynamic "authorized_external_networks" { + for_each = var.instance_network_config.authorized_external_networks + content { + cidr_range = authorized_external_networks.value + } + } + enable_public_ip = var.instance_network_config.enable_public_ip + } + } +} + +resource "google_alloydb_cluster" "secondary" { + count = var.cross_region_replication.enabled ? 1 : 0 + project = var.project_id + cluster_id = "${local.prefix}${var.cluster_name}-secondary" + cluster_type = "SECONDARY" + database_version = var.database_version + deletion_policy = "FORCE" + labels = var.labels + location = var.cross_region_replication.region + + network_config { + network = var.cluster_network_config.network + allocated_ip_range = var.cluster_network_config.allocated_ip_range + } + + dynamic "automated_backup_policy" { + for_each = var.automated_backup_configuration.enabled ? { 1 = 1 } : {} + content { + enabled = true + location = var.automated_backup_configuration.location + backup_window = var.automated_backup_configuration.backup_window + labels = var.labels + + dynamic "encryption_config" { + for_each = var.encryption_config != null ? { 1 = 1 } : {} + content { + kms_key_name = var.encryption_config.secondary_kms_key_name + } + } + + weekly_schedule { + days_of_week = var.automated_backup_configuration.weekly_schedule.days_of_week + start_times { + hours = var.automated_backup_configuration.weekly_schedule.start_times.hours + minutes = var.automated_backup_configuration.weekly_schedule.start_times.minutes + seconds = var.automated_backup_configuration.weekly_schedule.start_times.seconds + nanos = var.automated_backup_configuration.weekly_schedule.start_times.nanos + } + } + + dynamic "quantity_based_retention" { + for_each = var.automated_backup_configuration.retention_count != null ? { 1 = 1 } : {} + content { + count = var.automated_backup_configuration.retention_count + } + } + + dynamic "time_based_retention" { + for_each = var.automated_backup_configuration.retention_period != null ? { 1 = 1 } : {} + content { + retention_period = var.automated_backup_configuration.retention_period + } + } + } + } + + dynamic "continuous_backup_config" { + for_each = var.continuous_backup_configuration.enabled ? { 1 = 1 } : {} + content { + enabled = true + recovery_window_days = 14 + dynamic "encryption_config" { + for_each = var.encryption_config != null ? { 1 = 1 } : {} + content { + kms_key_name = var.encryption_config.secondary_kms_key_name + } + } + } + } + + dynamic "encryption_config" { + for_each = var.encryption_config != null ? { 1 = 1 } : {} + content { + kms_key_name = var.encryption_config.secondary_kms_key_name + } + } + + dynamic "initial_user" { + for_each = var.initial_user != null ? { 1 = 1 } : {} + content { + user = var.initial_user.user + password = var.initial_user.password + } + } + + dynamic "maintenance_update_policy" { + for_each = var.maintenance_config.enabled ? { 1 = 1 } : {} + content { + maintenance_windows { + day = var.maintenance_config.day + start_time { + hours = var.maintenance_config.start_times.hours + minutes = var.maintenance_config.start_times.minutes + seconds = var.maintenance_config.start_times.seconds + nanos = var.maintenance_config.start_times.nanos + } + } + } + } + + secondary_config { + primary_cluster_name = google_alloydb_cluster.primary.id + } + + depends_on = [google_alloydb_instance.primary] +} + +resource "google_alloydb_instance" "secondary" { + count = var.cross_region_replication.enabled ? 1 : 0 + cluster = google_alloydb_cluster.secondary.0.id + instance_id = "${local.prefix}${var.name}-secondary" + instance_type = google_alloydb_cluster.secondary.0.cluster_type + availability_type = var.availability_type + database_flags = var.flags + display_name = "${local.prefix}${var.name}" + gce_zone = local.is_regional ? null : var.location + labels = var.labels + + dynamic "query_insights_config" { + for_each = var.query_insights_config != null ? { 1 = 1 } : {} + content { + query_string_length = var.query_insights_config.query_string_length + record_application_tags = var.query_insights_config.record_application_tags + record_client_address = var.query_insights_config.record_client_address + query_plans_per_minute = var.query_insights_config.query_plans_per_minute + } + } + + dynamic "machine_config" { + for_each = var.machine_config != null ? { 1 = 1 } : {} + content { + cpu_count = var.machine_config.cpu_count + } + } + + dynamic "client_connection_config" { + for_each = var.client_connection_config != null ? { 1 = 1 } : {} + content { + require_connectors = var.client_connection_config.require_connectors + dynamic "ssl_config" { + for_each = var.client_connection_config.ssl_config != null ? { 1 = 1 } : {} + content { + ssl_mode = var.client_connection_config.ssl_config.ssl_mode + } + } + } + } + + dynamic "network_config" { + for_each = var.instance_network_config != null ? { 1 = 1 } : {} + content { + dynamic "authorized_external_networks" { + for_each = var.instance_network_config.authorized_external_networks + content { + cidr_range = authorized_external_networks.value + } + } + enable_public_ip = var.instance_network_config.enable_public_ip + } + } +} + +resource "random_password" "passwords" { + for_each = toset([ + for k, v in coalesce(var.users, {}) : + k + if v.password == null + ]) + length = 16 + special = true +} + +resource "google_alloydb_user" "users" { + for_each = local.users + cluster = google_alloydb_cluster.primary.id + user_id = each.value.name + user_type = each.value.type + password = each.value.password + database_roles = each.value.roles +} diff --git a/modules/alloydb/outputs.tf b/modules/alloydb/outputs.tf new file mode 100644 index 0000000000..2062bc4aaf --- /dev/null +++ b/modules/alloydb/outputs.tf @@ -0,0 +1,76 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + # _all_instances = merge( + # { primary = google_alloydb_instance.primary }, + # google_alloydb_instance.replicas + # ) + _all_instances = { primary = google_alloydb_instance.primary } +} + +output "id" { + description = "Fully qualified primary instance id." + value = google_alloydb_instance.primary.id +} + +output "ids" { + description = "Fully qualified ids of all instances." + value = { + for id, instance in local._all_instances : + id => instance.id + } +} + +output "instances" { + description = "AlloyDB instance resources." + value = local._all_instances + sensitive = true +} + +output "ip" { + description = "IP address of the primary instance." + value = google_alloydb_instance.primary.ip_address +} + +output "ips" { + description = "IP addresses of all instances." + value = { + for id, instance in local._all_instances : id => instance.ip_address + } +} + +output "name" { + description = "Name of the primary instance." + value = google_alloydb_instance.primary.name +} + +output "names" { + description = "Names of all instances." + value = { + for id, instance in local._all_instances : + id => instance.name + } +} + +output "user_passwords" { + description = "Map of containing the password of all users created through terraform." + value = { + for name, user in google_alloydb_user.users : + name => user.password + } + sensitive = true +} diff --git a/modules/alloydb/variables.tf b/modules/alloydb/variables.tf new file mode 100644 index 0000000000..f6d6fe8ad5 --- /dev/null +++ b/modules/alloydb/variables.tf @@ -0,0 +1,303 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "automated_backup_configuration" { + description = "Automated backup settings for cluster." + nullable = false + type = object({ + enabled = optional(bool, false) + backup_window = optional(string, "1800s") + location = optional(string) + weekly_schedule = optional(object({ + days_of_week = optional(list(string), [ + "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) + }), {}) + }), {}) + retention_count = optional(number, 7) + retention_period = optional(string, null) + }) + 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 + } + validation { + condition = ( + var.automated_backup_configuration.enabled ? ( + # Maintenance window validation below + !(var.automated_backup_configuration.retention_count != null && var.automated_backup_configuration.retention_period) && + # Maintenance window day validation + contains([ + "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" + ], 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." + } +} + +variable "availability_type" { + description = "Availability type for the primary replica. Either `ZONAL` or `REGIONAL`." + type = string + default = "REGIONAL" +} + +variable "client_connection_config" { + description = "Client connection config." + type = object({ + require_connectors = optional(bool, false) + ssl_config = optional(object({ + ssl_mode = string + }), null) + }) + default = null +} + +variable "cluster_name" { + description = "Name of the primary cluster." + type = string +} + +variable "cluster_network_config" { + description = "Network configuration for the cluster. Only one between cluster_network_config and cluster_psc_config can be used." + type = object({ + network = string + allocated_ip_range = optional(string, null) + }) + nullable = false +} + +variable "continuous_backup_configuration" { + description = "Continuous backup settings for cluster." + nullable = true + type = object({ + enabled = optional(bool, false) + recovery_window_days = optional(number, 14) + }) + default = { + enabled = false + recovery_window_days = 14 + } +} + +variable "cross_region_replication" { + description = "Cross region replication config." + type = object({ + enabled = optional(bool, false) + promote_secondary = optional(bool, false) + region = optional(string, null) + }) + default = {} + 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." + } +} + +variable "database_version" { + description = "Database type and version to create." + type = string + default = "POSTGRES_15" +} + +variable "deletion_policy" { + description = "AlloyDB cluster and instance deletion policy." + type = string + default = null +} + +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) + }) + default = null + nullable = true +} + +variable "flags" { + description = "Map FLAG_NAME=>VALUE for database-specific tuning." + type = map(string) + default = null +} + + +variable "initial_user" { + description = "AlloyDB cluster initial user credentials." + type = object({ + user = optional(string, "root") + password = string + }) + default = null +} + +variable "insights_config" { + description = "Query Insights configuration. Defaults to null which disables Query Insights." + type = object({ + query_string_length = optional(number, 1024) + record_application_tags = optional(bool, false) + record_client_address = optional(bool, false) + query_plans_per_minute = optional(number, 5) + }) + default = null +} + +variable "instance_network_config" { + description = "Network configuration for the instance. Only one between instance_network_config and instance_psc_config can be used." + type = object({ + authorized_external_networks = list(string) + enable_public_ip = bool + }) + default = null + validation { + condition = var.instance_network_config == null ? true : ( + (length(var.instance_network_config.authorized_external_networks) != 0 && var.instance_network_config.enable_public_ip) || !var.instance_network_config.enable_public_ip + ) ? true : false + error_message = "A list of external network authorized to access this instance is required only in case public ip is enabled for the instance." + } +} + +variable "labels" { + description = "Labels to be attached to all instances." + type = map(string) + default = null +} + +variable "location" { + description = "Region or zone of the cluster and instance." + type = string +} + +variable "machine_config" { + description = "AlloyDB machine config." + type = object({ + cpu_count = optional(number, 2) + }) + nullable = false + default = { + cpu_count = 2 + } +} + +variable "maintenance_config" { + description = "Set maintenance window configuration." + type = object({ + 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) + }), {}) + }) + default = { + enabled = false + day = "SUNDAY" + start_time = { + hours = 23 + minutes = 0 + seconds = 0 + nanos = 0 + } + } + 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." + } +} + +variable "name" { + description = "Name of primary instance." + type = string +} + +variable "prefix" { + description = "Optional prefix used to generate instance names." + type = string + default = null + validation { + condition = var.prefix != "" + error_message = "Prefix cannot be empty, please use null instead." + } +} + +variable "project_id" { + description = "The ID of the project where this instances will be created." + type = string +} + +variable "query_insights_config" { + description = "Query insights config." + type = 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) + }) + default = { + query_string_length = 1024 + record_application_tags = true + record_client_address = true + query_plans_per_minute = 5 + } +} + +variable "users" { + description = "Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'." + type = map(object({ + password = optional(string) + roles = optional(list(string), ["alloydbsuperuser"]) + type = optional(string) + })) + default = null + validation { + condition = alltrue([ + for user in coalesce(var.users, {}) : + contains(["ALLOYDB_BUILT_IN", "ALLOYDB_IAM_USER"], user.type) + ]) + error_message = "User type must one of 'ALLOYDB_BUILT_IN', 'ALLOYDB_IAM_USER'" + } +} diff --git a/modules/alloydb/versions.tf b/modules/alloydb/versions.tf new file mode 100644 index 0000000000..bc9986b3c7 --- /dev/null +++ b/modules/alloydb/versions.tf @@ -0,0 +1,27 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_version = ">= 1.7.4" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.26.0, < 6.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 5.26.0, < 6.0.0" # tftest + } + } +} diff --git a/tests/modules/alloydb/examples/cross_region_replication.yaml b/tests/modules/alloydb/examples/cross_region_replication.yaml new file mode 100644 index 0000000000..39bb179ae9 --- /dev/null +++ b/tests/modules/alloydb/examples/cross_region_replication.yaml @@ -0,0 +1,102 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.alloydb.google_alloydb_cluster.primary: + annotations: null + cluster_id: db + cluster_type: PRIMARY + database_version: POSTGRES_15 + deletion_policy: DEFAULT + display_name: null + encryption_config: [] + etag: null + initial_user: [] + labels: null + location: europe-west8 + maintenance_update_policy: [] + network_config: + - allocated_ip_range: null + network: projects/xxx/global/networks/aaa + project: project-id + restore_backup_source: [] + restore_continuous_backup_source: [] + secondary_config: [] + timeouts: null + module.alloydb.google_alloydb_cluster.secondary[0]: + annotations: null + cluster_id: db-secondary + cluster_type: SECONDARY + database_version: POSTGRES_15 + deletion_policy: FORCE + display_name: null + encryption_config: [] + etag: null + initial_user: [] + labels: null + location: europe-west12 + maintenance_update_policy: [] + network_config: + - allocated_ip_range: null + network: projects/xxx/global/networks/aaa + project: project-id + restore_backup_source: [] + restore_continuous_backup_source: [] + secondary_config: + - {} + timeouts: null + module.alloydb.google_alloydb_instance.primary: + annotations: null + availability_type: REGIONAL + display_name: db + gce_zone: null + instance_id: db + instance_type: PRIMARY + labels: null + machine_config: + - cpu_count: 2 + network_config: [] + query_insights_config: + - query_plans_per_minute: 5 + query_string_length: 1024 + record_application_tags: true + record_client_address: true + read_pool_config: [] + timeouts: null + module.alloydb.google_alloydb_instance.secondary[0]: + annotations: null + availability_type: REGIONAL + display_name: db + gce_zone: null + instance_id: db-secondary + instance_type: SECONDARY + labels: null + machine_config: + - cpu_count: 2 + network_config: [] + query_insights_config: + - query_plans_per_minute: 5 + query_string_length: 1024 + record_application_tags: true + record_client_address: true + read_pool_config: [] + timeouts: null + +counts: + google_alloydb_cluster: 2 + google_alloydb_instance: 2 + modules: 1 + resources: 4 + +outputs: {} diff --git a/tests/modules/alloydb/examples/simple.yaml b/tests/modules/alloydb/examples/simple.yaml new file mode 100644 index 0000000000..01b66e5afe --- /dev/null +++ b/tests/modules/alloydb/examples/simple.yaml @@ -0,0 +1,171 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.alloydb.google_alloydb_cluster.primary: + annotations: null + cluster_id: db + cluster_type: PRIMARY + database_version: POSTGRES_15 + deletion_policy: DEFAULT + display_name: null + encryption_config: [] + etag: null + initial_user: [] + labels: null + location: europe-west8 + maintenance_update_policy: [] + network_config: + - allocated_ip_range: null + project: test-alloydb-prj + restore_backup_source: [] + restore_continuous_backup_source: [] + secondary_config: [] + timeouts: null + module.alloydb.google_alloydb_instance.primary: + annotations: null + availability_type: REGIONAL + display_name: db + gce_zone: null + instance_id: db + instance_type: PRIMARY + labels: null + machine_config: + - cpu_count: 2 + network_config: [] + query_insights_config: + - query_plans_per_minute: 5 + query_string_length: 1024 + record_application_tags: true + record_client_address: true + read_pool_config: [] + timeouts: null + module.project.google_project.project[0]: + auto_create_network: false + billing_account: 123456-123456-123456 + folder_id: '1122334455' + labels: null + name: test-alloydb-prj + org_id: null + project_id: test-alloydb-prj + skip_delete: false + timeouts: null + module.project.google_project_iam_member.servicenetworking[0]: + condition: [] + project: test-alloydb-prj + role: roles/servicenetworking.serviceAgent + module.project.google_project_service.project_services["alloydb.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-alloydb-prj + service: alloydb.googleapis.com + timeouts: null + module.project.google_project_service.project_services["servicenetworking.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-alloydb-prj + service: servicenetworking.googleapis.com + timeouts: null + module.project.google_project_service_identity.servicenetworking[0]: + project: test-alloydb-prj + service: servicenetworking.googleapis.com + timeouts: null + module.vpc.google_compute_global_address.psa_ranges["servicenetworking-googleapis-com-alloydb"]: + address: 10.60.0.0 + address_type: INTERNAL + description: null + ip_version: null + name: servicenetworking-googleapis-com-alloydb + prefix_length: 16 + project: test-alloydb-prj + purpose: VPC_PEERING + timeouts: null + module.vpc.google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null + name: my-network + network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL + project: test-alloydb-prj + routing_mode: GLOBAL + timeouts: null + module.vpc.google_compute_network_peering_routes_config.psa_routes["servicenetworking.googleapis.com"]: + export_custom_routes: false + import_custom_routes: false + network: my-network + project: test-alloydb-prj + timeouts: null + module.vpc.google_compute_route.gateway["private-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.8/30 + name: my-network-private-googleapis + network: my-network + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: test-alloydb-prj + tags: null + timeouts: null + module.vpc.google_compute_route.gateway["restricted-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.4/30 + name: my-network-restricted-googleapis + network: my-network + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: test-alloydb-prj + tags: null + timeouts: null + module.vpc.google_compute_subnetwork.psc["europe-west8/psc"]: + description: Terraform-managed subnet for Private Service Connect (PSC NAT). + ip_cidr_range: 10.0.3.0/24 + ipv6_access_type: null + log_config: [] + name: psc + network: my-network + project: test-alloydb-prj + purpose: PRIVATE_SERVICE_CONNECT + region: europe-west8 + role: null + timeouts: null + module.vpc.google_service_networking_connection.psa_connection["servicenetworking.googleapis.com"]: + deletion_policy: ABANDON + reserved_peering_ranges: + - servicenetworking-googleapis-com-alloydb + service: servicenetworking.googleapis.com + timeouts: null + +counts: + google_alloydb_cluster: 1 + google_alloydb_instance: 1 + google_compute_global_address: 1 + google_compute_network: 1 + google_compute_network_peering_routes_config: 1 + google_compute_route: 2 + google_compute_subnetwork: 1 + google_project: 1 + google_project_iam_member: 1 + google_project_service: 2 + google_project_service_identity: 1 + google_service_networking_connection: 1 + modules: 3 + resources: 14 + +outputs: {} diff --git a/tests/modules/alloydb_instance/examples/alloydb_instance.tfvars b/tests/modules/alloydb_instance/examples/alloydb_instance.tfvars deleted file mode 100644 index 03fda4bf80..0000000000 --- a/tests/modules/alloydb_instance/examples/alloydb_instance.tfvars +++ /dev/null @@ -1,40 +0,0 @@ -project_id = "myproject" -cluster_id = "alloydb-cluster-all" -location = "europe-west2" -labels = {} -display_name = "alloydb-cluster-all" -initial_user = { - user = "alloydb-cluster-full", - password = "alloydb-cluster-password" -} -network_self_link = "projects/myproject/global/networks/default" - -automated_backup_policy = null - -primary_instance_config = { - instance_id = "primary-instance-1", - instance_type = "PRIMARY", - machine_cpu_count = 2, - database_flags = {}, - display_name = "alloydb-primary-instance" -} - - -read_pool_instance = [ - { - instance_id = "read-instance-1", - display_name = "read-instancename-1", - instance_type = "READ_POOL", - node_count = 1, - database_flags = {}, - machine_cpu_count = 1 - }, - { - instance_id = "read-instance-2", - display_name = "read-instancename-2", - instance_type = "READ_POOL", - node_count = 1, - database_flags = {}, - machine_cpu_count = 1 - } -] diff --git a/tests/modules/alloydb_instance/examples/alloydb_instance.yaml b/tests/modules/alloydb_instance/examples/alloydb_instance.yaml deleted file mode 100644 index 80ed073883..0000000000 --- a/tests/modules/alloydb_instance/examples/alloydb_instance.yaml +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - values: - google_alloydb_cluster.default: - cluster_id: alloydb-cluster-all - display_name: alloydb-cluster-all - encryption_config: [] - initial_user: - - password: alloydb-cluster-password - user: alloydb-cluster-full - labels: null - location: europe-west2 - network: projects/myproject/global/networks/default - project: myproject - timeouts: null - google_alloydb_instance.primary: - annotations: null - database_flags: null - display_name: alloydb-primary-instance - gce_zone: null - instance_id: primary-instance-1 - instance_type: PRIMARY - labels: null - machine_config: - - cpu_count: 2 - read_pool_config: [] - timeouts: null - google_alloydb_instance.read_pool["read-instance-1"]: - annotations: null - database_flags: null - display_name: null - gce_zone: null - instance_id: read-instance-1 - instance_type: READ_POOL - labels: null - machine_config: - - cpu_count: 1 - read_pool_config: - - node_count: 1 - timeouts: null - google_alloydb_instance.read_pool["read-instance-2"]: - annotations: null - database_flags: null - display_name: null - gce_zone: null - instance_id: read-instance-2 - instance_type: READ_POOL - labels: null - machine_config: - - cpu_count: 1 - read_pool_config: - - node_count: 1 - timeouts: null - google_compute_global_address.private_ip_alloc: - address_type: INTERNAL - description: null - ip_version: null - name: adb-all - prefix_length: 16 - purpose: VPC_PEERING - timeouts: null - google_compute_network.default: - auto_create_subnetworks: true - delete_default_routes_on_create: false - description: null - enable_ula_internal_ipv6: null - name: multiple-readpool - network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL - timeouts: null - google_service_networking_connection.vpc_connection: - reserved_peering_ranges: - - adb-all - service: servicenetworking.googleapis.com - timeouts: null - -counts: - google_alloydb_cluster: 1 - google_alloydb_instance: 3 - google_compute_global_address: 1 - google_compute_network: 1 - google_service_networking_connection: 1 - modules: 0 - resources: 7 - -outputs: - cluster: alloydb-cluster-all - cluster_id: alloydb-cluster-all - primary_instance: alloydb-primary-instance - primary_instance_id: primary-instance-1 - read_pool_instance_ids: - - read-instance-1 - - read-instance-2 From 3eb1100003a1af15fc8dc4d438bd8f6e514059d3 Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Wed, 15 May 2024 13:11:56 +0200 Subject: [PATCH 02/10] added alloydb module in README.md removed unused variable --- modules/README.md | 1 + modules/alloydb/README.md | 21 ++++++++++----------- modules/alloydb/variables.tf | 11 ----------- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/modules/README.md b/modules/README.md index f8c30b7035..37c057107f 100644 --- a/modules/README.md +++ b/modules/README.md @@ -79,6 +79,7 @@ These modules are used in the examples included in this repository. If you are u ## Data +- [AlloyDB](./alloydb) - [Analytics Hub](./analytics-hub) - [BigQuery dataset](./bigquery-dataset) - [Bigtable instance](./bigtable-instance) diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md index 274e957162..ebe06ac0b0 100644 --- a/modules/alloydb/README.md +++ b/modules/alloydb/README.md @@ -91,9 +91,9 @@ module "alloydb" { |---|---|:---:|:---:|:---:| | [cluster_name](variables.tf#L85) | Name of the primary cluster. | string | ✓ | | | [cluster_network_config](variables.tf#L90) | Network configuration for the cluster. Only one between cluster_network_config and cluster_psc_config can be used. | object({…}) | ✓ | | -| [location](variables.tf#L196) | Region or zone of the cluster and instance. | string | ✓ | | -| [name](variables.tf#L252) | Name of primary instance. | string | ✓ | | -| [project_id](variables.tf#L267) | The ID of the project where this instances will be created. | string | ✓ | | +| [location](variables.tf#L185) | Region or zone of the cluster and instance. | string | ✓ | | +| [name](variables.tf#L241) | Name of primary instance. | string | ✓ | | +| [project_id](variables.tf#L256) | The ID of the project where this instances will be created. | string | ✓ | | | [automated_backup_configuration](variables.tf#L17) | Automated backup settings for cluster. | object({…}) | | {…} | | [availability_type](variables.tf#L68) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "REGIONAL" | | [client_connection_config](variables.tf#L74) | Client connection config. | object({…}) | | null | @@ -104,14 +104,13 @@ module "alloydb" { | [encryption_config](variables.tf#L138) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | object({…}) | | null | | [flags](variables.tf#L148) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | | [initial_user](variables.tf#L155) | AlloyDB cluster initial user credentials. | object({…}) | | null | -| [insights_config](variables.tf#L164) | Query Insights configuration. Defaults to null which disables Query Insights. | object({…}) | | null | -| [instance_network_config](variables.tf#L175) | Network configuration for the instance. Only one between instance_network_config and instance_psc_config can be used. | object({…}) | | null | -| [labels](variables.tf#L190) | Labels to be attached to all instances. | map(string) | | null | -| [machine_config](variables.tf#L201) | AlloyDB machine config. | object({…}) | | {…} | -| [maintenance_config](variables.tf#L212) | Set maintenance window configuration. | object({…}) | | {…} | -| [prefix](variables.tf#L257) | Optional prefix used to generate instance names. | string | | null | -| [query_insights_config](variables.tf#L272) | Query insights config. | object({…}) | | {…} | -| [users](variables.tf#L288) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | +| [instance_network_config](variables.tf#L164) | Network configuration for the instance. Only one between instance_network_config and instance_psc_config can be used. | object({…}) | | null | +| [labels](variables.tf#L179) | Labels to be attached to all instances. | map(string) | | null | +| [machine_config](variables.tf#L190) | AlloyDB machine config. | object({…}) | | {…} | +| [maintenance_config](variables.tf#L201) | Set maintenance window configuration. | object({…}) | | {…} | +| [prefix](variables.tf#L246) | Optional prefix used to generate instance names. | string | | null | +| [query_insights_config](variables.tf#L261) | Query insights config. | object({…}) | | {…} | +| [users](variables.tf#L277) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | ## Outputs diff --git a/modules/alloydb/variables.tf b/modules/alloydb/variables.tf index f6d6fe8ad5..3f81dce629 100644 --- a/modules/alloydb/variables.tf +++ b/modules/alloydb/variables.tf @@ -161,17 +161,6 @@ variable "initial_user" { default = null } -variable "insights_config" { - description = "Query Insights configuration. Defaults to null which disables Query Insights." - type = object({ - query_string_length = optional(number, 1024) - record_application_tags = optional(bool, false) - record_client_address = optional(bool, false) - query_plans_per_minute = optional(number, 5) - }) - default = null -} - variable "instance_network_config" { description = "Network configuration for the instance. Only one between instance_network_config and instance_psc_config can be used." type = object({ From bb78c7f44d6ca4f30a62349728496a4e04564d02 Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Wed, 15 May 2024 13:19:13 +0200 Subject: [PATCH 03/10] small fixes --- modules/alloydb/README.md | 4 +++- modules/alloydb/main.tf | 4 ++-- modules/alloydb/outputs.tf | 12 +++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md index ebe06ac0b0..5dc6dd0c90 100644 --- a/modules/alloydb/README.md +++ b/modules/alloydb/README.md @@ -123,5 +123,7 @@ module "alloydb" { | [ips](outputs.tf#L49) | IP addresses of all instances. | | | [name](outputs.tf#L56) | Name of the primary instance. | | | [names](outputs.tf#L61) | Names of all instances. | | -| [user_passwords](outputs.tf#L69) | Map of containing the password of all users created through terraform. | ✓ | +| [secondary_id](outputs.tf#L69) | Fully qualified primary instance id. | | +| [secondary_ip](outputs.tf#L74) | IP address of the primary instance. | | +| [user_passwords](outputs.tf#L79) | Map of containing the password of all users created through terraform. | ✓ | diff --git a/modules/alloydb/main.tf b/modules/alloydb/main.tf index 6fa18bdc84..2a8e978acd 100644 --- a/modules/alloydb/main.tf +++ b/modules/alloydb/main.tf @@ -302,9 +302,9 @@ resource "google_alloydb_cluster" "secondary" { resource "google_alloydb_instance" "secondary" { count = var.cross_region_replication.enabled ? 1 : 0 - cluster = google_alloydb_cluster.secondary.0.id + cluster = google_alloydb_cluster.secondary[0].id instance_id = "${local.prefix}${var.name}-secondary" - instance_type = google_alloydb_cluster.secondary.0.cluster_type + instance_type = google_alloydb_cluster.secondary[0].cluster_type availability_type = var.availability_type database_flags = var.flags display_name = "${local.prefix}${var.name}" diff --git a/modules/alloydb/outputs.tf b/modules/alloydb/outputs.tf index 2062bc4aaf..458200a222 100644 --- a/modules/alloydb/outputs.tf +++ b/modules/alloydb/outputs.tf @@ -19,7 +19,7 @@ locals { # { primary = google_alloydb_instance.primary }, # google_alloydb_instance.replicas # ) - _all_instances = { primary = google_alloydb_instance.primary } + _all_instances = merge({ primary = google_alloydb_instance.primary }, var.cross_region_replication.enabled ? { secondary = google_alloydb_instance.secondary[0] } : {}) } output "id" { @@ -66,6 +66,16 @@ output "names" { } } +output "secondary_id" { + description = "Fully qualified primary instance id." + value = var.cross_region_replication.enabled ? google_alloydb_instance.secondary.id : null +} + +output "secondary_ip" { + description = "IP address of the primary instance." + value = var.cross_region_replication.enabled ? google_alloydb_instance.secondary.ip_address : null +} + output "user_passwords" { description = "Map of containing the password of all users created through terraform." value = { From ea86de5c2f6cf290d204995db5903ec07a09410c Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Wed, 15 May 2024 13:22:13 +0200 Subject: [PATCH 04/10] small fixes --- modules/alloydb/versions.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/alloydb/versions.tf b/modules/alloydb/versions.tf index bc9986b3c7..d7800c6dd1 100644 --- a/modules/alloydb/versions.tf +++ b/modules/alloydb/versions.tf @@ -17,11 +17,11 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = ">= 5.26.0, < 6.0.0" # tftest + version = ">= 5.29.1, < 6.0.0" # tftest } google-beta = { source = "hashicorp/google-beta" - version = ">= 5.26.0, < 6.0.0" # tftest + version = ">= 5.29.1, < 6.0.0" # tftest } } } From 86a39d30de7e4df8bc4d3ec9d2a8cfcd469e926c Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Wed, 15 May 2024 14:28:27 +0200 Subject: [PATCH 05/10] small fixes --- modules/alloydb/outputs.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/alloydb/outputs.tf b/modules/alloydb/outputs.tf index 458200a222..e3f9da1fce 100644 --- a/modules/alloydb/outputs.tf +++ b/modules/alloydb/outputs.tf @@ -68,12 +68,12 @@ output "names" { output "secondary_id" { description = "Fully qualified primary instance id." - value = var.cross_region_replication.enabled ? google_alloydb_instance.secondary.id : null + value = var.cross_region_replication.enabled ? google_alloydb_instance.secondary[0].id : null } output "secondary_ip" { description = "IP address of the primary instance." - value = var.cross_region_replication.enabled ? google_alloydb_instance.secondary.ip_address : null + value = var.cross_region_replication.enabled ? google_alloydb_instance.secondary[0].ip_address : null } output "user_passwords" { From 7dcc7bb2fb300d2ef10025b6b3cae7b2ba2a7523 Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Fri, 17 May 2024 09:54:40 +0200 Subject: [PATCH 06/10] updates --- modules/alloydb/README.md | 98 +++++++++++---- modules/alloydb/main.tf | 136 ++++++++++----------- modules/alloydb/variables.tf | 58 +++++---- tests/modules/alloydb/examples/cmek.yaml | 70 +++++++++++ tests/modules/alloydb/examples/custom.yaml | 102 ++++++++++++++++ tests/modules/alloydb/examples/simple.yaml | 8 +- tools/check_documentation.py | 2 +- 7 files changed, 350 insertions(+), 124 deletions(-) create mode 100644 tests/modules/alloydb/examples/cmek.yaml create mode 100644 tests/modules/alloydb/examples/custom.yaml diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md index 5dc6dd0c90..c5762eea4e 100644 --- a/modules/alloydb/README.md +++ b/modules/alloydb/README.md @@ -56,7 +56,7 @@ module "alloydb" { source = "./fabric/modules/alloydb" project_id = module.project.project_id cluster_name = "db" - cluster_network_config = { + network_config = { network = module.vpc.id } name = "db" @@ -84,33 +84,87 @@ module "alloydb" { } # tftest modules=1 resources=4 inventory=cross_region_replication.yaml e2e ``` + +In a cross-region replication scenario (like in the previous example) this module also supports [promoting the secondary instance](https://cloud.google.com/alloydb/docs/cross-region-replication/work-with-cross-region-replication#promote-secondary-cluster) to become a primary instance via the `var.cross_region_replication.promote_secondary` flag. + +### Custom flags and users definition + +```hcl +module "alloydb" { + source = "./fabric/modules/alloydb" + project_id = var.project_id + cluster_name = "primary" + location = var.region + name = "primary" + flags = { + "alloydb.enable_pgaudit" = "on" + "alloydb.iam_authentication" = "on" + idle_in_transaction_session_timeout = "900000" + timezone = "'UTC'" + } + network_config = { + network = var.vpc.self_link + } + users = { + # generate a password for user1 + user1 = { + password = null + } + # assign a password to user2 + user2 = { + password = "mypassword" + } + } +} +# tftest modules=1 resources=5 inventory=custom.yaml e2e +``` + +### CMEK encryption + +```hcl +module "alloydb" { + source = "./fabric/modules/alloydb" + project_id = var.project_id + cluster_name = "primary" + location = var.region + name = "primary" + network_config = { + network = var.vpc.self_link + } + encryption_config = { + primary_kms_key_name = var.kms_key.id + } +} + +# tftest modules=1 resources=2 inventory=cmek.yaml e2e +``` ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [cluster_name](variables.tf#L85) | Name of the primary cluster. | string | ✓ | | -| [cluster_network_config](variables.tf#L90) | Network configuration for the cluster. Only one between cluster_network_config and cluster_psc_config can be used. | object({…}) | ✓ | | -| [location](variables.tf#L185) | Region or zone of the cluster and instance. | string | ✓ | | -| [name](variables.tf#L241) | Name of primary instance. | string | ✓ | | -| [project_id](variables.tf#L256) | The ID of the project where this instances will be created. | string | ✓ | | +| [cluster_name](variables.tf#L87) | Name of the primary cluster. | string | ✓ | | +| [location](variables.tf#L168) | Region or zone of the cluster and instance. | string | ✓ | | +| [name](variables.tf#L224) | Name of primary instance. | string | ✓ | | +| [network_config](variables.tf#L229) | Network configuration for cluster and instance. Only one between cluster_network_config and cluster_psc_config can be used. | object({…}) | ✓ | | +| [project_id](variables.tf#L254) | The ID of the project where this instances will be created. | string | ✓ | | | [automated_backup_configuration](variables.tf#L17) | Automated backup settings for cluster. | object({…}) | | {…} | -| [availability_type](variables.tf#L68) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "REGIONAL" | -| [client_connection_config](variables.tf#L74) | Client connection config. | object({…}) | | null | -| [continuous_backup_configuration](variables.tf#L99) | Continuous backup settings for cluster. | object({…}) | | {…} | -| [cross_region_replication](variables.tf#L112) | Cross region replication config. | object({…}) | | {} | -| [database_version](variables.tf#L126) | Database type and version to create. | string | | "POSTGRES_15" | -| [deletion_policy](variables.tf#L132) | AlloyDB cluster and instance deletion policy. | string | | null | -| [encryption_config](variables.tf#L138) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | object({…}) | | null | -| [flags](variables.tf#L148) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | -| [initial_user](variables.tf#L155) | AlloyDB cluster initial user credentials. | object({…}) | | null | -| [instance_network_config](variables.tf#L164) | Network configuration for the instance. Only one between instance_network_config and instance_psc_config can be used. | object({…}) | | null | -| [labels](variables.tf#L179) | Labels to be attached to all instances. | map(string) | | null | -| [machine_config](variables.tf#L190) | AlloyDB machine config. | object({…}) | | {…} | -| [maintenance_config](variables.tf#L201) | Set maintenance window configuration. | object({…}) | | {…} | -| [prefix](variables.tf#L246) | Optional prefix used to generate instance names. | string | | null | -| [query_insights_config](variables.tf#L261) | Query insights config. | object({…}) | | {…} | -| [users](variables.tf#L277) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | +| [availability_type](variables.tf#L70) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "REGIONAL" | +| [client_connection_config](variables.tf#L76) | Client connection config. | object({…}) | | null | +| [continuous_backup_configuration](variables.tf#L92) | Continuous backup settings for cluster. | object({…}) | | {…} | +| [cross_region_replication](variables.tf#L105) | Cross region replication config. | object({…}) | | {} | +| [database_version](variables.tf#L119) | Database type and version to create. | string | | "POSTGRES_15" | +| [deletion_policy](variables.tf#L125) | AlloyDB cluster and instance deletion policy. | string | | null | +| [encryption_config](variables.tf#L131) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | object({…}) | | null | +| [flags](variables.tf#L141) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | +| [gce_zone](variables.tf#L147) | The GCE zone that the instance should serve from. This can ONLY be specified for ZONAL instances. If present for a REGIONAL instance, an error will be thrown. | string | | null | +| [initial_user](variables.tf#L153) | AlloyDB cluster initial user credentials. | object({…}) | | null | +| [labels](variables.tf#L162) | Labels to be attached to all instances. | map(string) | | null | +| [machine_config](variables.tf#L173) | AlloyDB machine config. | object({…}) | | {…} | +| [maintenance_config](variables.tf#L184) | Set maintenance window configuration. | object({…}) | | {…} | +| [prefix](variables.tf#L244) | Optional prefix used to generate instance names. | string | | null | +| [query_insights_config](variables.tf#L259) | Query insights config. | object({…}) | | {…} | +| [users](variables.tf#L275) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | ## Outputs diff --git a/modules/alloydb/main.tf b/modules/alloydb/main.tf index 2a8e978acd..f7ace81f01 100644 --- a/modules/alloydb/main.tf +++ b/modules/alloydb/main.tf @@ -18,6 +18,9 @@ locals { prefix = var.prefix == null ? "" : "${var.prefix}-" # has_replicas = try(length(var.replicas) > 0, false) is_regional = var.availability_type == "REGIONAL" ? true : false + # secondary instance type is aligned with cluster type unless apply is targeting a promotion, in that + # case cluster will be 'primary' while instance still 'secondary'. + secondary_instance_type = try(var.cross_region_replication.promote_secondary && google_alloydb_cluster.secondary[0].cluster_type == "SECONDARY" ? "SECONDARY" : google_alloydb_cluster.secondary[0].cluster_type, null) users = { for k, v in coalesce(var.users, {}) : @@ -25,7 +28,7 @@ locals { name = k password = try(v.type, "ALLOYDB_BUILT_IN") == "ALLOYDB_BUILT_IN" ? try(random_password.passwords[k].result, v.password) : null roles = v.roles - type = try(v.type, "ALLOYDB_BUILT_IN") + type = coalesce(v.type, "ALLOYDB_BUILT_IN") } } } @@ -39,15 +42,15 @@ resource "google_alloydb_cluster" "primary" { location = var.location network_config { - network = var.cluster_network_config.network - allocated_ip_range = var.cluster_network_config.allocated_ip_range + network = var.network_config.network + allocated_ip_range = var.network_config.allocated_ip_range } dynamic "automated_backup_policy" { for_each = var.automated_backup_configuration.enabled ? { 1 = 1 } : {} content { enabled = true - location = var.automated_backup_configuration.location + location = try(var.automated_backup_configuration.location, var.location) backup_window = var.automated_backup_configuration.backup_window labels = var.labels @@ -88,7 +91,7 @@ resource "google_alloydb_cluster" "primary" { for_each = var.continuous_backup_configuration.enabled ? { 1 = 1 } : {} content { enabled = true - recovery_window_days = 14 + recovery_window_days = var.continuous_backup_configuration.recovery_window_days dynamic "encryption_config" { for_each = var.encryption_config != null ? { 1 = 1 } : {} content { @@ -113,16 +116,6 @@ resource "google_alloydb_cluster" "primary" { } } - # TODO manage backup restore - # restore_backup_source { - # backup_name = "" - # } - - # restore_continuous_backup_source { - # cluster = "" - # point_in_time = "" - # } - dynamic "maintenance_update_policy" { for_each = var.maintenance_config.enabled ? { 1 = 1 } : {} content { @@ -141,31 +134,14 @@ resource "google_alloydb_cluster" "primary" { resource "google_alloydb_instance" "primary" { cluster = google_alloydb_cluster.primary.id - instance_id = "${local.prefix}${var.name}" - instance_type = "PRIMARY" availability_type = var.availability_type database_flags = var.flags display_name = "${local.prefix}${var.name}" - gce_zone = local.is_regional ? null : var.location + instance_id = "${local.prefix}${var.name}" + instance_type = "PRIMARY" + gce_zone = local.is_regional ? null : var.gce_zone labels = var.labels - dynamic "query_insights_config" { - for_each = var.query_insights_config != null ? { 1 = 1 } : {} - content { - query_string_length = var.query_insights_config.query_string_length - record_application_tags = var.query_insights_config.record_application_tags - record_client_address = var.query_insights_config.record_client_address - query_plans_per_minute = var.query_insights_config.query_plans_per_minute - } - } - - dynamic "machine_config" { - for_each = var.machine_config != null ? { 1 = 1 } : {} - content { - cpu_count = var.machine_config.cpu_count - } - } - dynamic "client_connection_config" { for_each = var.client_connection_config != null ? { 1 = 1 } : {} content { @@ -179,16 +155,33 @@ resource "google_alloydb_instance" "primary" { } } + dynamic "machine_config" { + for_each = var.machine_config != null ? { 1 = 1 } : {} + content { + cpu_count = var.machine_config.cpu_count + } + } + dynamic "network_config" { - for_each = var.instance_network_config != null ? { 1 = 1 } : {} + for_each = var.network_config != null ? { 1 = 1 } : {} content { dynamic "authorized_external_networks" { - for_each = var.instance_network_config.authorized_external_networks + for_each = coalesce(var.network_config.authorized_external_networks, []) content { cidr_range = authorized_external_networks.value } } - enable_public_ip = var.instance_network_config.enable_public_ip + enable_public_ip = var.network_config.enable_public_ip + } + } + + dynamic "query_insights_config" { + for_each = var.query_insights_config != null ? { 1 = 1 } : {} + content { + query_string_length = var.query_insights_config.query_string_length + record_application_tags = var.query_insights_config.record_application_tags + record_client_address = var.query_insights_config.record_client_address + query_plans_per_minute = var.query_insights_config.query_plans_per_minute } } } @@ -197,22 +190,22 @@ resource "google_alloydb_cluster" "secondary" { count = var.cross_region_replication.enabled ? 1 : 0 project = var.project_id cluster_id = "${local.prefix}${var.cluster_name}-secondary" - cluster_type = "SECONDARY" + cluster_type = var.cross_region_replication.promote_secondary ? "PRIMARY" : "SECONDARY" database_version = var.database_version deletion_policy = "FORCE" labels = var.labels location = var.cross_region_replication.region network_config { - network = var.cluster_network_config.network - allocated_ip_range = var.cluster_network_config.allocated_ip_range + network = var.network_config.network + allocated_ip_range = var.network_config.allocated_ip_range } dynamic "automated_backup_policy" { for_each = var.automated_backup_configuration.enabled ? { 1 = 1 } : {} content { enabled = true - location = var.automated_backup_configuration.location + location = var.cross_region_replication.region backup_window = var.automated_backup_configuration.backup_window labels = var.labels @@ -253,7 +246,7 @@ resource "google_alloydb_cluster" "secondary" { for_each = var.continuous_backup_configuration.enabled ? { 1 = 1 } : {} content { enabled = true - recovery_window_days = 14 + recovery_window_days = var.continuous_backup_configuration.recovery_window_days dynamic "encryption_config" { for_each = var.encryption_config != null ? { 1 = 1 } : {} content { @@ -293,8 +286,11 @@ resource "google_alloydb_cluster" "secondary" { } } - secondary_config { - primary_cluster_name = google_alloydb_cluster.primary.id + dynamic "secondary_config" { + for_each = var.cross_region_replication.promote_secondary ? {} : { 1 = 1 } + content { + primary_cluster_name = google_alloydb_cluster.primary.id + } } depends_on = [google_alloydb_instance.primary] @@ -302,32 +298,15 @@ resource "google_alloydb_cluster" "secondary" { resource "google_alloydb_instance" "secondary" { count = var.cross_region_replication.enabled ? 1 : 0 - cluster = google_alloydb_cluster.secondary[0].id - instance_id = "${local.prefix}${var.name}-secondary" - instance_type = google_alloydb_cluster.secondary[0].cluster_type availability_type = var.availability_type - database_flags = var.flags + cluster = google_alloydb_cluster.secondary[0].id + database_flags = var.cross_region_replication.promote_secondary ? var.flags : null display_name = "${local.prefix}${var.name}" - gce_zone = local.is_regional ? null : var.location + gce_zone = local.is_regional ? null : var.gce_zone + instance_id = "${local.prefix}${var.name}-secondary" + instance_type = local.secondary_instance_type labels = var.labels - dynamic "query_insights_config" { - for_each = var.query_insights_config != null ? { 1 = 1 } : {} - content { - query_string_length = var.query_insights_config.query_string_length - record_application_tags = var.query_insights_config.record_application_tags - record_client_address = var.query_insights_config.record_client_address - query_plans_per_minute = var.query_insights_config.query_plans_per_minute - } - } - - dynamic "machine_config" { - for_each = var.machine_config != null ? { 1 = 1 } : {} - content { - cpu_count = var.machine_config.cpu_count - } - } - dynamic "client_connection_config" { for_each = var.client_connection_config != null ? { 1 = 1 } : {} content { @@ -341,16 +320,33 @@ resource "google_alloydb_instance" "secondary" { } } + dynamic "machine_config" { + for_each = var.machine_config != null ? { 1 = 1 } : {} + content { + cpu_count = var.machine_config.cpu_count + } + } + dynamic "network_config" { - for_each = var.instance_network_config != null ? { 1 = 1 } : {} + for_each = var.network_config != null ? { 1 = 1 } : {} content { dynamic "authorized_external_networks" { - for_each = var.instance_network_config.authorized_external_networks + for_each = coalesce(var.network_config.authorized_external_networks, []) content { cidr_range = authorized_external_networks.value } } - enable_public_ip = var.instance_network_config.enable_public_ip + enable_public_ip = var.network_config.enable_public_ip + } + } + + dynamic "query_insights_config" { + for_each = var.query_insights_config != null ? { 1 = 1 } : {} + content { + query_string_length = var.query_insights_config.query_string_length + record_application_tags = var.query_insights_config.record_application_tags + record_client_address = var.query_insights_config.record_client_address + query_plans_per_minute = var.query_insights_config.query_plans_per_minute } } } diff --git a/modules/alloydb/variables.tf b/modules/alloydb/variables.tf index 3f81dce629..d7bd26fe7c 100644 --- a/modules/alloydb/variables.tf +++ b/modules/alloydb/variables.tf @@ -55,11 +55,13 @@ variable "automated_backup_configuration" { condition = ( var.automated_backup_configuration.enabled ? ( # Maintenance window validation below - !(var.automated_backup_configuration.retention_count != null && var.automated_backup_configuration.retention_period) && + !(var.automated_backup_configuration.retention_count != null && var.automated_backup_configuration.retention_period != null) && # Maintenance window day validation - contains([ - "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" - ], var.automated_backup_configuration.weekly_schedule.days_of_week)) : true + 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) + ) : 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." } @@ -87,15 +89,6 @@ variable "cluster_name" { type = string } -variable "cluster_network_config" { - description = "Network configuration for the cluster. Only one between cluster_network_config and cluster_psc_config can be used." - type = object({ - network = string - allocated_ip_range = optional(string, null) - }) - nullable = false -} - variable "continuous_backup_configuration" { description = "Continuous backup settings for cluster." nullable = true @@ -104,7 +97,7 @@ variable "continuous_backup_configuration" { recovery_window_days = optional(number, 14) }) default = { - enabled = false + enabled = true recovery_window_days = 14 } } @@ -151,6 +144,11 @@ variable "flags" { default = null } +variable "gce_zone" { + description = "The GCE zone that the instance should serve from. This can ONLY be specified for ZONAL instances. If present for a REGIONAL instance, an error will be thrown." + type = string + default = null +} variable "initial_user" { description = "AlloyDB cluster initial user credentials." @@ -161,21 +159,6 @@ variable "initial_user" { default = null } -variable "instance_network_config" { - description = "Network configuration for the instance. Only one between instance_network_config and instance_psc_config can be used." - type = object({ - authorized_external_networks = list(string) - enable_public_ip = bool - }) - default = null - validation { - condition = var.instance_network_config == null ? true : ( - (length(var.instance_network_config.authorized_external_networks) != 0 && var.instance_network_config.enable_public_ip) || !var.instance_network_config.enable_public_ip - ) ? true : false - error_message = "A list of external network authorized to access this instance is required only in case public ip is enabled for the instance." - } -} - variable "labels" { description = "Labels to be attached to all instances." type = map(string) @@ -243,6 +226,21 @@ variable "name" { type = string } +variable "network_config" { + description = "Network configuration for cluster and instance. Only one between cluster_network_config and cluster_psc_config can be used." + type = object({ + network = string + allocated_ip_range = optional(string, null) + authorized_external_networks = optional(list(string), null) + enable_public_ip = optional(bool, false) + }) + nullable = false + validation { + condition = (try(length(var.network_config.authorized_external_networks), 0) != 0 && var.network_config.enable_public_ip) || try(length(var.network_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." + } +} + variable "prefix" { description = "Optional prefix used to generate instance names." type = string @@ -285,7 +283,7 @@ variable "users" { validation { condition = alltrue([ for user in coalesce(var.users, {}) : - contains(["ALLOYDB_BUILT_IN", "ALLOYDB_IAM_USER"], user.type) + try(contains(["ALLOYDB_BUILT_IN", "ALLOYDB_IAM_USER"], user.type), true) ]) error_message = "User type must one of 'ALLOYDB_BUILT_IN', 'ALLOYDB_IAM_USER'" } diff --git a/tests/modules/alloydb/examples/cmek.yaml b/tests/modules/alloydb/examples/cmek.yaml new file mode 100644 index 0000000000..f1880e47c2 --- /dev/null +++ b/tests/modules/alloydb/examples/cmek.yaml @@ -0,0 +1,70 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.alloydb.google_alloydb_cluster.primary: + annotations: null + cluster_id: primary + cluster_type: PRIMARY + continuous_backup_config: + - enabled: true + encryption_config: + - kms_key_name: kms_key_self_link + recovery_window_days: 14 + database_version: POSTGRES_15 + deletion_policy: DEFAULT + display_name: null + encryption_config: + - kms_key_name: kms_key_self_link + etag: null + initial_user: [] + labels: null + location: europe-west8 + maintenance_update_policy: [] + network_config: + - allocated_ip_range: null + network: projects/xxx/global/networks/aaa + project: project-id + restore_backup_source: [] + restore_continuous_backup_source: [] + secondary_config: [] + timeouts: null + module.alloydb.google_alloydb_instance.primary: + annotations: null + availability_type: REGIONAL + display_name: primary + gce_zone: null + instance_id: primary + instance_type: PRIMARY + labels: null + machine_config: + - cpu_count: 2 + network_config: + - authorized_external_networks: [] + enable_public_ip: false + query_insights_config: + - query_plans_per_minute: 5 + query_string_length: 1024 + record_application_tags: true + record_client_address: true + read_pool_config: [] + timeouts: null + +counts: + google_alloydb_cluster: 1 + google_alloydb_instance: 1 + modules: 1 + resources: 2 + +outputs: {} diff --git a/tests/modules/alloydb/examples/custom.yaml b/tests/modules/alloydb/examples/custom.yaml new file mode 100644 index 0000000000..cf6417ccda --- /dev/null +++ b/tests/modules/alloydb/examples/custom.yaml @@ -0,0 +1,102 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.alloydb.google_alloydb_cluster.primary: + annotations: null + cluster_id: primary + cluster_type: PRIMARY + continuous_backup_config: + - enabled: true + encryption_config: [] + recovery_window_days: 14 + database_version: POSTGRES_15 + deletion_policy: DEFAULT + display_name: null + encryption_config: [] + etag: null + initial_user: [] + labels: null + location: europe-west8 + maintenance_update_policy: [] + network_config: + - allocated_ip_range: null + network: projects/xxx/global/networks/aaa + project: project-id + restore_backup_source: [] + restore_continuous_backup_source: [] + secondary_config: [] + timeouts: null + module.alloydb.google_alloydb_instance.primary: + annotations: null + availability_type: REGIONAL + database_flags: + alloydb.enable_pgaudit: 'on' + alloydb.iam_authentication: 'on' + idle_in_transaction_session_timeout: '900000' + timezone: '''UTC''' + display_name: primary + gce_zone: null + instance_id: primary + instance_type: PRIMARY + labels: null + machine_config: + - cpu_count: 2 + network_config: + - authorized_external_networks: [] + enable_public_ip: false + query_insights_config: + - query_plans_per_minute: 5 + query_string_length: 1024 + record_application_tags: true + record_client_address: true + read_pool_config: [] + timeouts: null + module.alloydb.google_alloydb_user.users["user1"]: + database_roles: + - alloydbsuperuser + password: null + timeouts: null + user_id: user1 + user_type: ALLOYDB_BUILT_IN + module.alloydb.google_alloydb_user.users["user2"]: + database_roles: + - alloydbsuperuser + password: null + timeouts: null + user_id: user2 + user_type: ALLOYDB_BUILT_IN + module.alloydb.random_password.passwords["user1"]: + keepers: null + length: 16 + lower: true + min_lower: 0 + min_numeric: 0 + min_special: 0 + min_upper: 0 + number: true + numeric: true + override_special: null + special: true + upper: true + +counts: + google_alloydb_cluster: 1 + google_alloydb_instance: 1 + google_alloydb_user: 2 + modules: 1 + random_password: 1 + resources: 5 + +outputs: {} \ No newline at end of file diff --git a/tests/modules/alloydb/examples/simple.yaml b/tests/modules/alloydb/examples/simple.yaml index 01b66e5afe..7d8639ae9c 100644 --- a/tests/modules/alloydb/examples/simple.yaml +++ b/tests/modules/alloydb/examples/simple.yaml @@ -17,6 +17,10 @@ values: annotations: null cluster_id: db cluster_type: PRIMARY + continuous_backup_config: + - enabled: true + encryption_config: [] + recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: DEFAULT display_name: null @@ -43,7 +47,9 @@ values: labels: null machine_config: - cpu_count: 2 - network_config: [] + network_config: + - authorized_external_networks: [] + enable_public_ip: false query_insights_config: - query_plans_per_minute: 5 query_string_length: 1024 diff --git a/tools/check_documentation.py b/tools/check_documentation.py index 965bf1a247..957f18f29e 100755 --- a/tools/check_documentation.py +++ b/tools/check_documentation.py @@ -183,7 +183,7 @@ def main(dirs, exclude_file=None, files=False, show_diffs=False, else: print('Errored modules:') print('\n'.join([e[0] for e in errors])) - + print(errors) raise SystemExit('Errors found.') From 970d47104ed7da65883625c6ed4059e87ad9ee61 Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Fri, 17 May 2024 10:29:24 +0200 Subject: [PATCH 07/10] small fixes --- modules/alloydb/README.md | 12 ++++++---- modules/alloydb/main.tf | 9 ++++---- modules/alloydb/variables.tf | 12 ++++++++++ .../examples/cross_region_replication.yaml | 22 ++++++++++++++----- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md index c5762eea4e..a26616473c 100644 --- a/modules/alloydb/README.md +++ b/modules/alloydb/README.md @@ -12,6 +12,8 @@ Note that this module assumes that some options are the same for both the primar * [Examples](#examples) * [Simple example](#simple-example) * [Cross region replication](#cross-region-replication) + * [Custom flags and users definition](#custom-flags-and-users-definition) + * [CMEK encryption](#cmek-encryption) * [Variables](#variables) * [Outputs](#outputs) @@ -72,11 +74,11 @@ module "alloydb" { source = "./fabric/modules/alloydb" project_id = var.project_id cluster_name = "db" - cluster_network_config = { + location = var.region + name = "db" + network_config = { network = var.vpc.self_link } - name = "db" - location = "europe-west8" cross_region_replication = { enabled = true region = "europe-west12" @@ -164,7 +166,9 @@ module "alloydb" { | [maintenance_config](variables.tf#L184) | Set maintenance window configuration. | object({…}) | | {…} | | [prefix](variables.tf#L244) | Optional prefix used to generate instance names. | string | | null | | [query_insights_config](variables.tf#L259) | Query insights config. | object({…}) | | {…} | -| [users](variables.tf#L275) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | +| [secondary_cluster_name](variables.tf#L275) | Name of secondary cluster instance. | string | | null | +| [secondary_name](variables.tf#L281) | Name of secondary instance. | string | | null | +| [users](variables.tf#L287) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | ## Outputs diff --git a/modules/alloydb/main.tf b/modules/alloydb/main.tf index f7ace81f01..748dfe9367 100644 --- a/modules/alloydb/main.tf +++ b/modules/alloydb/main.tf @@ -20,8 +20,9 @@ locals { is_regional = var.availability_type == "REGIONAL" ? true : false # secondary instance type is aligned with cluster type unless apply is targeting a promotion, in that # case cluster will be 'primary' while instance still 'secondary'. + secondary_cluster_name = coalesce(var.secondary_cluster_name, "${var.cluster_name}-sec") + secondary_instance_name = coalesce(var.secondary_name, "${var.name}-sec") secondary_instance_type = try(var.cross_region_replication.promote_secondary && google_alloydb_cluster.secondary[0].cluster_type == "SECONDARY" ? "SECONDARY" : google_alloydb_cluster.secondary[0].cluster_type, null) - users = { for k, v in coalesce(var.users, {}) : k => { @@ -189,7 +190,7 @@ resource "google_alloydb_instance" "primary" { resource "google_alloydb_cluster" "secondary" { count = var.cross_region_replication.enabled ? 1 : 0 project = var.project_id - cluster_id = "${local.prefix}${var.cluster_name}-secondary" + cluster_id = local.secondary_cluster_name cluster_type = var.cross_region_replication.promote_secondary ? "PRIMARY" : "SECONDARY" database_version = var.database_version deletion_policy = "FORCE" @@ -301,9 +302,9 @@ resource "google_alloydb_instance" "secondary" { availability_type = var.availability_type cluster = google_alloydb_cluster.secondary[0].id database_flags = var.cross_region_replication.promote_secondary ? var.flags : null - display_name = "${local.prefix}${var.name}" + display_name = local.secondary_instance_name gce_zone = local.is_regional ? null : var.gce_zone - instance_id = "${local.prefix}${var.name}-secondary" + instance_id = local.secondary_instance_name instance_type = local.secondary_instance_type labels = var.labels diff --git a/modules/alloydb/variables.tf b/modules/alloydb/variables.tf index d7bd26fe7c..4f7a24ef53 100644 --- a/modules/alloydb/variables.tf +++ b/modules/alloydb/variables.tf @@ -272,6 +272,18 @@ variable "query_insights_config" { } } +variable "secondary_cluster_name" { + description = "Name of secondary cluster instance." + type = string + default = null +} + +variable "secondary_name" { + description = "Name of secondary instance." + type = string + default = null +} + variable "users" { description = "Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'." type = map(object({ diff --git a/tests/modules/alloydb/examples/cross_region_replication.yaml b/tests/modules/alloydb/examples/cross_region_replication.yaml index 39bb179ae9..e048ef892a 100644 --- a/tests/modules/alloydb/examples/cross_region_replication.yaml +++ b/tests/modules/alloydb/examples/cross_region_replication.yaml @@ -17,6 +17,10 @@ values: annotations: null cluster_id: db cluster_type: PRIMARY + continuous_backup_config: + - enabled: true + encryption_config: [] + recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: DEFAULT display_name: null @@ -36,8 +40,12 @@ values: timeouts: null module.alloydb.google_alloydb_cluster.secondary[0]: annotations: null - cluster_id: db-secondary + cluster_id: db-sec cluster_type: SECONDARY + continuous_backup_config: + - enabled: true + encryption_config: [] + recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: FORCE display_name: null @@ -66,7 +74,9 @@ values: labels: null machine_config: - cpu_count: 2 - network_config: [] + network_config: + - authorized_external_networks: [] + enable_public_ip: false query_insights_config: - query_plans_per_minute: 5 query_string_length: 1024 @@ -77,14 +87,16 @@ values: module.alloydb.google_alloydb_instance.secondary[0]: annotations: null availability_type: REGIONAL - display_name: db + display_name: db-sec gce_zone: null - instance_id: db-secondary + instance_id: db-sec instance_type: SECONDARY labels: null machine_config: - cpu_count: 2 - network_config: [] + network_config: + - authorized_external_networks: [] + enable_public_ip: false query_insights_config: - query_plans_per_minute: 5 query_string_length: 1024 From ecba43178931520528c32290780f78b7d171af72 Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Fri, 17 May 2024 11:56:41 +0200 Subject: [PATCH 08/10] updates --- modules/alloydb/README.md | 88 ++++++++++--------- modules/alloydb/main.tf | 79 +++++++++-------- modules/alloydb/outputs.tf | 11 ++- modules/alloydb/variables.tf | 30 +++++++ tests/modules/alloydb/examples/cmek.yaml | 2 +- .../examples/cross_region_replication.yaml | 7 +- tests/modules/alloydb/examples/custom.yaml | 4 +- tests/modules/alloydb/examples/simple.yaml | 2 +- 8 files changed, 134 insertions(+), 89 deletions(-) diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md index a26616473c..ac2083885c 100644 --- a/modules/alloydb/README.md +++ b/modules/alloydb/README.md @@ -5,7 +5,8 @@ It can also create an initial set of users via the `users` parameters. Note that this module assumes that some options are the same for both the primary instance and the secondary one in case of cross regional replication configuration. -*Warning:* if you use the `users` field, you terraform state will contain each user's password in plain text. +> [!WARNING] +> If you use the `users` field, you terraform state will contain each user's password in plain text. * [AlloyDB module](#alloydb-module) @@ -45,13 +46,11 @@ module "vpc" { ranges = { alloydb = "10.60.0.0/16" } deletion_policy = "ABANDON" }] - subnets_psc = [ - { - ip_cidr_range = "10.0.3.0/24" - name = "psc" - region = var.region - } - ] + subnets_psc = [{ + ip_cidr_range = "10.0.3.0/24" + name = "psc" + region = var.region + }] } module "alloydb" { @@ -145,43 +144,48 @@ module "alloydb" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [cluster_name](variables.tf#L87) | Name of the primary cluster. | string | ✓ | | -| [location](variables.tf#L168) | Region or zone of the cluster and instance. | string | ✓ | | -| [name](variables.tf#L224) | Name of primary instance. | string | ✓ | | -| [network_config](variables.tf#L229) | Network configuration for cluster and instance. Only one between cluster_network_config and cluster_psc_config can be used. | object({…}) | ✓ | | -| [project_id](variables.tf#L254) | The ID of the project where this instances will be created. | string | ✓ | | -| [automated_backup_configuration](variables.tf#L17) | Automated backup settings for cluster. | object({…}) | | {…} | -| [availability_type](variables.tf#L70) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "REGIONAL" | -| [client_connection_config](variables.tf#L76) | Client connection config. | object({…}) | | null | -| [continuous_backup_configuration](variables.tf#L92) | Continuous backup settings for cluster. | object({…}) | | {…} | -| [cross_region_replication](variables.tf#L105) | Cross region replication config. | object({…}) | | {} | -| [database_version](variables.tf#L119) | Database type and version to create. | string | | "POSTGRES_15" | -| [deletion_policy](variables.tf#L125) | AlloyDB cluster and instance deletion policy. | string | | null | -| [encryption_config](variables.tf#L131) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | object({…}) | | null | -| [flags](variables.tf#L141) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | -| [gce_zone](variables.tf#L147) | The GCE zone that the instance should serve from. This can ONLY be specified for ZONAL instances. If present for a REGIONAL instance, an error will be thrown. | string | | null | -| [initial_user](variables.tf#L153) | AlloyDB cluster initial user credentials. | object({…}) | | null | -| [labels](variables.tf#L162) | Labels to be attached to all instances. | map(string) | | null | -| [machine_config](variables.tf#L173) | AlloyDB machine config. | object({…}) | | {…} | -| [maintenance_config](variables.tf#L184) | Set maintenance window configuration. | object({…}) | | {…} | -| [prefix](variables.tf#L244) | Optional prefix used to generate instance names. | string | | null | -| [query_insights_config](variables.tf#L259) | Query insights config. | object({…}) | | {…} | -| [secondary_cluster_name](variables.tf#L275) | Name of secondary cluster instance. | string | | null | -| [secondary_name](variables.tf#L281) | Name of secondary instance. | string | | null | -| [users](variables.tf#L287) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | +| [cluster_name](variables.tf#L99) | Name of the primary cluster. | string | ✓ | | +| [location](variables.tf#L186) | Region or zone of the cluster and instance. | string | ✓ | | +| [name](variables.tf#L242) | Name of primary instance. | string | ✓ | | +| [network_config](variables.tf#L247) | Network configuration for cluster and instance. Only one between cluster_network_config and cluster_psc_config can be used. | object({…}) | ✓ | | +| [project_id](variables.tf#L272) | The ID of the project where this instances will be created. | string | ✓ | | +| [annotations](variables.tf#L17) | Map FLAG_NAME=>VALUE for annotations which allow client tools to store small amount of arbitrary data. | map(string) | | null | +| [automated_backup_configuration](variables.tf#L23) | Automated backup settings for cluster. | object({…}) | | {…} | +| [availability_type](variables.tf#L76) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "REGIONAL" | +| [client_connection_config](variables.tf#L82) | Client connection config. | object({…}) | | null | +| [cluster_display_name](variables.tf#L93) | Display name of the primary cluster. | string | | null | +| [continuous_backup_configuration](variables.tf#L104) | Continuous backup settings for cluster. | object({…}) | | {…} | +| [cross_region_replication](variables.tf#L117) | Cross region replication config. | object({…}) | | {} | +| [database_version](variables.tf#L131) | Database type and version to create. | string | | "POSTGRES_15" | +| [deletion_policy](variables.tf#L137) | AlloyDB cluster and instance deletion policy. | string | | null | +| [display_name](variables.tf#L143) | AlloyDB instance display name. | string | | null | +| [encryption_config](variables.tf#L149) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | object({…}) | | null | +| [flags](variables.tf#L159) | Map FLAG_NAME=>VALUE for database-specific tuning. | map(string) | | null | +| [gce_zone](variables.tf#L165) | The GCE zone that the instance should serve from. This can ONLY be specified for ZONAL instances. If present for a REGIONAL instance, an error will be thrown. | string | | null | +| [initial_user](variables.tf#L171) | AlloyDB cluster initial user credentials. | object({…}) | | null | +| [labels](variables.tf#L180) | Labels to be attached to all instances. | map(string) | | null | +| [machine_config](variables.tf#L191) | AlloyDB machine config. | object({…}) | | {…} | +| [maintenance_config](variables.tf#L202) | Set maintenance window configuration. | object({…}) | | {…} | +| [prefix](variables.tf#L262) | Optional prefix used to generate instance names. | string | | null | +| [query_insights_config](variables.tf#L277) | Query insights config. | object({…}) | | {…} | +| [secondary_cluster_display_name](variables.tf#L293) | Display name of secondary cluster instance. | string | | null | +| [secondary_cluster_name](variables.tf#L299) | Name of secondary cluster instance. | string | | null | +| [secondary_display_name](variables.tf#L305) | Display name of secondary instance. | string | | null | +| [secondary_name](variables.tf#L311) | Name of secondary instance. | string | | null | +| [users](variables.tf#L317) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | ## Outputs | name | description | sensitive | |---|---|:---:| -| [id](outputs.tf#L25) | Fully qualified primary instance id. | | -| [ids](outputs.tf#L30) | Fully qualified ids of all instances. | | -| [instances](outputs.tf#L38) | AlloyDB instance resources. | ✓ | -| [ip](outputs.tf#L44) | IP address of the primary instance. | | -| [ips](outputs.tf#L49) | IP addresses of all instances. | | -| [name](outputs.tf#L56) | Name of the primary instance. | | -| [names](outputs.tf#L61) | Names of all instances. | | -| [secondary_id](outputs.tf#L69) | Fully qualified primary instance id. | | -| [secondary_ip](outputs.tf#L74) | IP address of the primary instance. | | -| [user_passwords](outputs.tf#L79) | Map of containing the password of all users created through terraform. | ✓ | +| [id](outputs.tf#L28) | Fully qualified primary instance id. | | +| [ids](outputs.tf#L33) | Fully qualified ids of all instances. | | +| [instances](outputs.tf#L41) | AlloyDB instance resources. | ✓ | +| [ip](outputs.tf#L47) | IP address of the primary instance. | | +| [ips](outputs.tf#L52) | IP addresses of all instances. | | +| [name](outputs.tf#L59) | Name of the primary instance. | | +| [names](outputs.tf#L64) | Names of all instances. | | +| [secondary_id](outputs.tf#L72) | Fully qualified primary instance id. | | +| [secondary_ip](outputs.tf#L77) | IP address of the primary instance. | | +| [user_passwords](outputs.tf#L82) | Map of containing the password of all users created through terraform. | ✓ | diff --git a/modules/alloydb/main.tf b/modules/alloydb/main.tf index 748dfe9367..23cbe66bff 100644 --- a/modules/alloydb/main.tf +++ b/modules/alloydb/main.tf @@ -17,9 +17,11 @@ locals { prefix = var.prefix == null ? "" : "${var.prefix}-" # has_replicas = try(length(var.replicas) > 0, false) - is_regional = var.availability_type == "REGIONAL" ? true : false + is_regional = var.availability_type == "REGIONAL" # secondary instance type is aligned with cluster type unless apply is targeting a promotion, in that # case cluster will be 'primary' while instance still 'secondary'. + primary_cluster_name = "${local.prefix}${var.cluster_name}" + primary_instance_name = "${local.prefix}${var.name}" secondary_cluster_name = coalesce(var.secondary_cluster_name, "${var.cluster_name}-sec") secondary_instance_name = coalesce(var.secondary_name, "${var.name}-sec") secondary_instance_type = try(var.cross_region_replication.promote_secondary && google_alloydb_cluster.secondary[0].cluster_type == "SECONDARY" ? "SECONDARY" : google_alloydb_cluster.secondary[0].cluster_type, null) @@ -36,9 +38,12 @@ locals { resource "google_alloydb_cluster" "primary" { project = var.project_id - cluster_id = "${local.prefix}${var.cluster_name}" + annotations = var.annotations + cluster_id = local.primary_cluster_name + cluster_type = "PRIMARY" database_version = var.database_version deletion_policy = var.deletion_policy + display_name = coalesce(var.cluster_display_name, local.primary_cluster_name) labels = var.labels location = var.location @@ -48,7 +53,7 @@ resource "google_alloydb_cluster" "primary" { } dynamic "automated_backup_policy" { - for_each = var.automated_backup_configuration.enabled ? { 1 = 1 } : {} + for_each = var.automated_backup_configuration.enabled ? [""] : [] content { enabled = true location = try(var.automated_backup_configuration.location, var.location) @@ -56,7 +61,7 @@ resource "google_alloydb_cluster" "primary" { labels = var.labels dynamic "encryption_config" { - for_each = var.encryption_config != null ? { 1 = 1 } : {} + for_each = var.encryption_config != null ? [""] : [] content { kms_key_name = var.encryption_config.primary_kms_key_name } @@ -73,14 +78,14 @@ resource "google_alloydb_cluster" "primary" { } dynamic "quantity_based_retention" { - for_each = var.automated_backup_configuration.retention_count != null ? { 1 = 1 } : {} + for_each = var.automated_backup_configuration.retention_count != null ? [""] : [] content { count = var.automated_backup_configuration.retention_count } } dynamic "time_based_retention" { - for_each = var.automated_backup_configuration.retention_period != null ? { 1 = 1 } : {} + for_each = var.automated_backup_configuration.retention_period != null ? [""] : [] content { retention_period = var.automated_backup_configuration.retention_period } @@ -89,12 +94,12 @@ resource "google_alloydb_cluster" "primary" { } dynamic "continuous_backup_config" { - for_each = var.continuous_backup_configuration.enabled ? { 1 = 1 } : {} + for_each = var.continuous_backup_configuration.enabled ? [""] : [] content { enabled = true recovery_window_days = var.continuous_backup_configuration.recovery_window_days dynamic "encryption_config" { - for_each = var.encryption_config != null ? { 1 = 1 } : {} + for_each = var.encryption_config != null ? [""] : [] content { kms_key_name = var.encryption_config.primary_kms_key_name } @@ -103,14 +108,14 @@ resource "google_alloydb_cluster" "primary" { } dynamic "encryption_config" { - for_each = var.encryption_config != null ? { 1 = 1 } : {} + for_each = var.encryption_config != null ? [""] : [] content { kms_key_name = var.encryption_config.primary_kms_key_name } } dynamic "initial_user" { - for_each = var.initial_user != null ? { 1 = 1 } : {} + for_each = var.initial_user != null ? [""] : [] content { user = var.initial_user.user password = var.initial_user.password @@ -118,7 +123,7 @@ resource "google_alloydb_cluster" "primary" { } dynamic "maintenance_update_policy" { - for_each = var.maintenance_config.enabled ? { 1 = 1 } : {} + for_each = var.maintenance_config.enabled ? [""] : [] content { maintenance_windows { day = var.maintenance_config.day @@ -134,21 +139,22 @@ resource "google_alloydb_cluster" "primary" { } resource "google_alloydb_instance" "primary" { - cluster = google_alloydb_cluster.primary.id + annotations = var.annotations availability_type = var.availability_type + cluster = google_alloydb_cluster.primary.id database_flags = var.flags - display_name = "${local.prefix}${var.name}" - instance_id = "${local.prefix}${var.name}" + display_name = coalesce(var.display_name, local.primary_instance_name) + instance_id = local.primary_instance_name instance_type = "PRIMARY" gce_zone = local.is_regional ? null : var.gce_zone labels = var.labels dynamic "client_connection_config" { - for_each = var.client_connection_config != null ? { 1 = 1 } : {} + for_each = var.client_connection_config != null ? [""] : [] content { require_connectors = var.client_connection_config.require_connectors dynamic "ssl_config" { - for_each = var.client_connection_config.ssl_config != null ? { 1 = 1 } : {} + for_each = var.client_connection_config.ssl_config != null ? [""] : [] content { ssl_mode = var.client_connection_config.ssl_config.ssl_mode } @@ -157,14 +163,14 @@ resource "google_alloydb_instance" "primary" { } dynamic "machine_config" { - for_each = var.machine_config != null ? { 1 = 1 } : {} + for_each = var.machine_config != null ? [""] : [] content { cpu_count = var.machine_config.cpu_count } } dynamic "network_config" { - for_each = var.network_config != null ? { 1 = 1 } : {} + for_each = var.network_config != null ? [""] : [] content { dynamic "authorized_external_networks" { for_each = coalesce(var.network_config.authorized_external_networks, []) @@ -177,7 +183,7 @@ resource "google_alloydb_instance" "primary" { } dynamic "query_insights_config" { - for_each = var.query_insights_config != null ? { 1 = 1 } : {} + for_each = var.query_insights_config != null ? [""] : [] content { query_string_length = var.query_insights_config.query_string_length record_application_tags = var.query_insights_config.record_application_tags @@ -190,10 +196,12 @@ resource "google_alloydb_instance" "primary" { resource "google_alloydb_cluster" "secondary" { count = var.cross_region_replication.enabled ? 1 : 0 project = var.project_id + annotations = var.annotations cluster_id = local.secondary_cluster_name cluster_type = var.cross_region_replication.promote_secondary ? "PRIMARY" : "SECONDARY" database_version = var.database_version deletion_policy = "FORCE" + display_name = coalesce(var.secondary_cluster_display_name, local.secondary_cluster_name) labels = var.labels location = var.cross_region_replication.region @@ -203,7 +211,7 @@ resource "google_alloydb_cluster" "secondary" { } dynamic "automated_backup_policy" { - for_each = var.automated_backup_configuration.enabled ? { 1 = 1 } : {} + for_each = var.automated_backup_configuration.enabled ? [""] : [] content { enabled = true location = var.cross_region_replication.region @@ -211,7 +219,7 @@ resource "google_alloydb_cluster" "secondary" { labels = var.labels dynamic "encryption_config" { - for_each = var.encryption_config != null ? { 1 = 1 } : {} + for_each = var.encryption_config != null ? [""] : [] content { kms_key_name = var.encryption_config.secondary_kms_key_name } @@ -228,14 +236,14 @@ resource "google_alloydb_cluster" "secondary" { } dynamic "quantity_based_retention" { - for_each = var.automated_backup_configuration.retention_count != null ? { 1 = 1 } : {} + for_each = var.automated_backup_configuration.retention_count != null ? [""] : [] content { count = var.automated_backup_configuration.retention_count } } dynamic "time_based_retention" { - for_each = var.automated_backup_configuration.retention_period != null ? { 1 = 1 } : {} + for_each = var.automated_backup_configuration.retention_period != null ? [""] : [] content { retention_period = var.automated_backup_configuration.retention_period } @@ -244,12 +252,12 @@ resource "google_alloydb_cluster" "secondary" { } dynamic "continuous_backup_config" { - for_each = var.continuous_backup_configuration.enabled ? { 1 = 1 } : {} + for_each = var.continuous_backup_configuration.enabled ? [""] : [] content { enabled = true recovery_window_days = var.continuous_backup_configuration.recovery_window_days dynamic "encryption_config" { - for_each = var.encryption_config != null ? { 1 = 1 } : {} + for_each = var.encryption_config != null ? [""] : [] content { kms_key_name = var.encryption_config.secondary_kms_key_name } @@ -258,14 +266,14 @@ resource "google_alloydb_cluster" "secondary" { } dynamic "encryption_config" { - for_each = var.encryption_config != null ? { 1 = 1 } : {} + for_each = var.encryption_config != null ? [""] : [] content { kms_key_name = var.encryption_config.secondary_kms_key_name } } dynamic "initial_user" { - for_each = var.initial_user != null ? { 1 = 1 } : {} + for_each = var.initial_user != null ? [""] : [] content { user = var.initial_user.user password = var.initial_user.password @@ -273,7 +281,7 @@ resource "google_alloydb_cluster" "secondary" { } dynamic "maintenance_update_policy" { - for_each = var.maintenance_config.enabled ? { 1 = 1 } : {} + for_each = var.maintenance_config.enabled ? [""] : [] content { maintenance_windows { day = var.maintenance_config.day @@ -288,7 +296,7 @@ resource "google_alloydb_cluster" "secondary" { } dynamic "secondary_config" { - for_each = var.cross_region_replication.promote_secondary ? {} : { 1 = 1 } + for_each = var.cross_region_replication.promote_secondary ? [""] : [] content { primary_cluster_name = google_alloydb_cluster.primary.id } @@ -299,21 +307,22 @@ resource "google_alloydb_cluster" "secondary" { resource "google_alloydb_instance" "secondary" { count = var.cross_region_replication.enabled ? 1 : 0 + annotations = var.annotations availability_type = var.availability_type cluster = google_alloydb_cluster.secondary[0].id database_flags = var.cross_region_replication.promote_secondary ? var.flags : null - display_name = local.secondary_instance_name + display_name = coalesce(var.secondary_display_name, local.secondary_instance_name) gce_zone = local.is_regional ? null : var.gce_zone instance_id = local.secondary_instance_name instance_type = local.secondary_instance_type labels = var.labels dynamic "client_connection_config" { - for_each = var.client_connection_config != null ? { 1 = 1 } : {} + for_each = var.client_connection_config != null ? [""] : [] content { require_connectors = var.client_connection_config.require_connectors dynamic "ssl_config" { - for_each = var.client_connection_config.ssl_config != null ? { 1 = 1 } : {} + for_each = var.client_connection_config.ssl_config != null ? [""] : [] content { ssl_mode = var.client_connection_config.ssl_config.ssl_mode } @@ -322,14 +331,14 @@ resource "google_alloydb_instance" "secondary" { } dynamic "machine_config" { - for_each = var.machine_config != null ? { 1 = 1 } : {} + for_each = var.machine_config != null ? [""] : [] content { cpu_count = var.machine_config.cpu_count } } dynamic "network_config" { - for_each = var.network_config != null ? { 1 = 1 } : {} + for_each = var.network_config != null ? [""] : [] content { dynamic "authorized_external_networks" { for_each = coalesce(var.network_config.authorized_external_networks, []) @@ -342,7 +351,7 @@ resource "google_alloydb_instance" "secondary" { } dynamic "query_insights_config" { - for_each = var.query_insights_config != null ? { 1 = 1 } : {} + for_each = var.query_insights_config != null ? [""] : [] content { query_string_length = var.query_insights_config.query_string_length record_application_tags = var.query_insights_config.record_application_tags diff --git a/modules/alloydb/outputs.tf b/modules/alloydb/outputs.tf index e3f9da1fce..369a177373 100644 --- a/modules/alloydb/outputs.tf +++ b/modules/alloydb/outputs.tf @@ -19,7 +19,10 @@ locals { # { primary = google_alloydb_instance.primary }, # google_alloydb_instance.replicas # ) - _all_instances = merge({ primary = google_alloydb_instance.primary }, var.cross_region_replication.enabled ? { secondary = google_alloydb_instance.secondary[0] } : {}) + _all_instances = { + primary = google_alloydb_instance.primary + secondary = one(google_alloydb_instance.secondary) + } } output "id" { @@ -31,7 +34,7 @@ output "ids" { description = "Fully qualified ids of all instances." value = { for id, instance in local._all_instances : - id => instance.id + id => try(instance.id, null) } } @@ -49,7 +52,7 @@ output "ip" { output "ips" { description = "IP addresses of all instances." value = { - for id, instance in local._all_instances : id => instance.ip_address + for id, instance in local._all_instances : id => try(instance.ip_address, null) } } @@ -62,7 +65,7 @@ output "names" { description = "Names of all instances." value = { for id, instance in local._all_instances : - id => instance.name + id => try(instance.name, null) } } diff --git a/modules/alloydb/variables.tf b/modules/alloydb/variables.tf index 4f7a24ef53..fac5205be3 100644 --- a/modules/alloydb/variables.tf +++ b/modules/alloydb/variables.tf @@ -14,6 +14,12 @@ * limitations under the License. */ +variable "annotations" { + description = "Map FLAG_NAME=>VALUE for annotations which allow client tools to store small amount of arbitrary data." + type = map(string) + default = null +} + variable "automated_backup_configuration" { description = "Automated backup settings for cluster." nullable = false @@ -84,6 +90,12 @@ variable "client_connection_config" { default = null } +variable "cluster_display_name" { + description = "Display name of the primary cluster." + type = string + default = null +} + variable "cluster_name" { description = "Name of the primary cluster." type = string @@ -128,6 +140,12 @@ variable "deletion_policy" { default = null } +variable "display_name" { + description = "AlloyDB instance display name." + type = string + default = null +} + variable "encryption_config" { description = "Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'." type = object({ @@ -272,12 +290,24 @@ variable "query_insights_config" { } } +variable "secondary_cluster_display_name" { + description = "Display name of secondary cluster instance." + type = string + default = null +} + variable "secondary_cluster_name" { description = "Name of secondary cluster instance." type = string default = null } +variable "secondary_display_name" { + description = "Display name of secondary instance." + type = string + default = null +} + variable "secondary_name" { description = "Name of secondary instance." type = string diff --git a/tests/modules/alloydb/examples/cmek.yaml b/tests/modules/alloydb/examples/cmek.yaml index f1880e47c2..ebc60c1e24 100644 --- a/tests/modules/alloydb/examples/cmek.yaml +++ b/tests/modules/alloydb/examples/cmek.yaml @@ -24,7 +24,7 @@ values: recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: DEFAULT - display_name: null + display_name: primary encryption_config: - kms_key_name: kms_key_self_link etag: null diff --git a/tests/modules/alloydb/examples/cross_region_replication.yaml b/tests/modules/alloydb/examples/cross_region_replication.yaml index e048ef892a..51d9fc5ca3 100644 --- a/tests/modules/alloydb/examples/cross_region_replication.yaml +++ b/tests/modules/alloydb/examples/cross_region_replication.yaml @@ -23,7 +23,7 @@ values: recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: DEFAULT - display_name: null + display_name: db encryption_config: [] etag: null initial_user: [] @@ -48,7 +48,7 @@ values: recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: FORCE - display_name: null + display_name: db-sec encryption_config: [] etag: null initial_user: [] @@ -61,8 +61,7 @@ values: project: project-id restore_backup_source: [] restore_continuous_backup_source: [] - secondary_config: - - {} + secondary_config: [] timeouts: null module.alloydb.google_alloydb_instance.primary: annotations: null diff --git a/tests/modules/alloydb/examples/custom.yaml b/tests/modules/alloydb/examples/custom.yaml index cf6417ccda..a2bcf0b4ef 100644 --- a/tests/modules/alloydb/examples/custom.yaml +++ b/tests/modules/alloydb/examples/custom.yaml @@ -23,7 +23,7 @@ values: recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: DEFAULT - display_name: null + display_name: primary encryption_config: [] etag: null initial_user: [] @@ -99,4 +99,4 @@ counts: random_password: 1 resources: 5 -outputs: {} \ No newline at end of file +outputs: {} diff --git a/tests/modules/alloydb/examples/simple.yaml b/tests/modules/alloydb/examples/simple.yaml index 7d8639ae9c..7f658a69c1 100644 --- a/tests/modules/alloydb/examples/simple.yaml +++ b/tests/modules/alloydb/examples/simple.yaml @@ -23,7 +23,7 @@ values: recovery_window_days: 14 database_version: POSTGRES_15 deletion_policy: DEFAULT - display_name: null + display_name: db encryption_config: [] etag: null initial_user: [] From f732e23e13c9c25b598d7e09846139aeb541b909 Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Mon, 20 May 2024 13:38:46 +0200 Subject: [PATCH 09/10] small fixes --- modules/alloydb/README.md | 4 ++-- modules/alloydb/main.tf | 9 ++++++--- modules/alloydb/outputs.tf | 4 ---- modules/alloydb/variables.tf | 5 ++++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md index ac2083885c..a054ac9558 100644 --- a/modules/alloydb/README.md +++ b/modules/alloydb/README.md @@ -1,7 +1,7 @@ # AlloyDB module -This module manages the creation of an AlloyDB cluster and instance with potential read replicas and an advanced cross region replication setup for disaster recovery scenarios. -It can also create an initial set of users via the `users` parameters. +This module manages the creation of an AlloyDB cluster. It also supports cross-region replication scenario by setting up a secondary cluster. +It can also create an initial set of users via the `users` variable. Note that this module assumes that some options are the same for both the primary instance and the secondary one in case of cross regional replication configuration. diff --git a/modules/alloydb/main.tf b/modules/alloydb/main.tf index 23cbe66bff..f1f06f64b6 100644 --- a/modules/alloydb/main.tf +++ b/modules/alloydb/main.tf @@ -15,8 +15,7 @@ */ locals { - prefix = var.prefix == null ? "" : "${var.prefix}-" - # has_replicas = try(length(var.replicas) > 0, false) + prefix = var.prefix == null ? "" : "${var.prefix}-" is_regional = var.availability_type == "REGIONAL" # secondary instance type is aligned with cluster type unless apply is targeting a promotion, in that # case cluster will be 'primary' while instance still 'secondary'. @@ -24,7 +23,11 @@ locals { primary_instance_name = "${local.prefix}${var.name}" secondary_cluster_name = coalesce(var.secondary_cluster_name, "${var.cluster_name}-sec") secondary_instance_name = coalesce(var.secondary_name, "${var.name}-sec") - secondary_instance_type = try(var.cross_region_replication.promote_secondary && google_alloydb_cluster.secondary[0].cluster_type == "SECONDARY" ? "SECONDARY" : google_alloydb_cluster.secondary[0].cluster_type, null) + secondary_instance_type = try( + var.cross_region_replication.promote_secondary && google_alloydb_cluster.secondary[0].cluster_type == "SECONDARY" + ? "SECONDARY" + : google_alloydb_cluster.secondary[0].cluster_type, null + ) users = { for k, v in coalesce(var.users, {}) : k => { diff --git a/modules/alloydb/outputs.tf b/modules/alloydb/outputs.tf index 369a177373..f77ff87592 100644 --- a/modules/alloydb/outputs.tf +++ b/modules/alloydb/outputs.tf @@ -15,10 +15,6 @@ */ locals { - # _all_instances = merge( - # { primary = google_alloydb_instance.primary }, - # google_alloydb_instance.replicas - # ) _all_instances = { primary = google_alloydb_instance.primary secondary = one(google_alloydb_instance.secondary) diff --git a/modules/alloydb/variables.tf b/modules/alloydb/variables.tf index fac5205be3..793efb131c 100644 --- a/modules/alloydb/variables.tf +++ b/modules/alloydb/variables.tf @@ -254,7 +254,10 @@ variable "network_config" { }) nullable = false validation { - condition = (try(length(var.network_config.authorized_external_networks), 0) != 0 && var.network_config.enable_public_ip) || try(length(var.network_config.authorized_external_networks), 0) == 0 + condition = ( + (try(length(var.network_config.authorized_external_networks), 0) != 0 && var.network_config.enable_public_ip) + || try(length(var.network_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." } } From 6209bdeaf10542b1c0a1c1efb312f900aa2a9577 Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Mon, 20 May 2024 13:41:38 +0200 Subject: [PATCH 10/10] small fixes --- modules/alloydb/README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/modules/alloydb/README.md b/modules/alloydb/README.md index a054ac9558..b0597f9780 100644 --- a/modules/alloydb/README.md +++ b/modules/alloydb/README.md @@ -148,7 +148,7 @@ module "alloydb" { | [location](variables.tf#L186) | Region or zone of the cluster and instance. | string | ✓ | | | [name](variables.tf#L242) | Name of primary instance. | string | ✓ | | | [network_config](variables.tf#L247) | Network configuration for cluster and instance. Only one between cluster_network_config and cluster_psc_config can be used. | object({…}) | ✓ | | -| [project_id](variables.tf#L272) | The ID of the project where this instances will be created. | string | ✓ | | +| [project_id](variables.tf#L275) | The ID of the project where this instances will be created. | string | ✓ | | | [annotations](variables.tf#L17) | Map FLAG_NAME=>VALUE for annotations which allow client tools to store small amount of arbitrary data. | map(string) | | null | | [automated_backup_configuration](variables.tf#L23) | Automated backup settings for cluster. | object({…}) | | {…} | | [availability_type](variables.tf#L76) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "REGIONAL" | @@ -166,26 +166,26 @@ module "alloydb" { | [labels](variables.tf#L180) | Labels to be attached to all instances. | map(string) | | null | | [machine_config](variables.tf#L191) | AlloyDB machine config. | object({…}) | | {…} | | [maintenance_config](variables.tf#L202) | Set maintenance window configuration. | object({…}) | | {…} | -| [prefix](variables.tf#L262) | Optional prefix used to generate instance names. | string | | null | -| [query_insights_config](variables.tf#L277) | Query insights config. | object({…}) | | {…} | -| [secondary_cluster_display_name](variables.tf#L293) | Display name of secondary cluster instance. | string | | null | -| [secondary_cluster_name](variables.tf#L299) | Name of secondary cluster instance. | string | | null | -| [secondary_display_name](variables.tf#L305) | Display name of secondary instance. | string | | null | -| [secondary_name](variables.tf#L311) | Name of secondary instance. | string | | null | -| [users](variables.tf#L317) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | +| [prefix](variables.tf#L265) | Optional prefix used to generate instance names. | string | | null | +| [query_insights_config](variables.tf#L280) | Query insights config. | object({…}) | | {…} | +| [secondary_cluster_display_name](variables.tf#L296) | Display name of secondary cluster instance. | string | | null | +| [secondary_cluster_name](variables.tf#L302) | Name of secondary cluster instance. | string | | null | +| [secondary_display_name](variables.tf#L308) | Display name of secondary instance. | string | | null | +| [secondary_name](variables.tf#L314) | Name of secondary instance. | string | | null | +| [users](variables.tf#L320) | Map of users to create in the primary instance (and replicated to other replicas). Set PASSWORD to null if you want to get an autogenerated password. The user types available are: 'ALLOYDB_BUILT_IN' or 'ALLOYDB_IAM_USER'. | map(object({…})) | | null | ## Outputs | name | description | sensitive | |---|---|:---:| -| [id](outputs.tf#L28) | Fully qualified primary instance id. | | -| [ids](outputs.tf#L33) | Fully qualified ids of all instances. | | -| [instances](outputs.tf#L41) | AlloyDB instance resources. | ✓ | -| [ip](outputs.tf#L47) | IP address of the primary instance. | | -| [ips](outputs.tf#L52) | IP addresses of all instances. | | -| [name](outputs.tf#L59) | Name of the primary instance. | | -| [names](outputs.tf#L64) | Names of all instances. | | -| [secondary_id](outputs.tf#L72) | Fully qualified primary instance id. | | -| [secondary_ip](outputs.tf#L77) | IP address of the primary instance. | | -| [user_passwords](outputs.tf#L82) | Map of containing the password of all users created through terraform. | ✓ | +| [id](outputs.tf#L24) | Fully qualified primary instance id. | | +| [ids](outputs.tf#L29) | Fully qualified ids of all instances. | | +| [instances](outputs.tf#L37) | AlloyDB instance resources. | ✓ | +| [ip](outputs.tf#L43) | IP address of the primary instance. | | +| [ips](outputs.tf#L48) | IP addresses of all instances. | | +| [name](outputs.tf#L55) | Name of the primary instance. | | +| [names](outputs.tf#L60) | Names of all instances. | | +| [secondary_id](outputs.tf#L68) | Fully qualified primary instance id. | | +| [secondary_ip](outputs.tf#L73) | IP address of the primary instance. | | +| [user_passwords](outputs.tf#L78) | Map of containing the password of all users created through terraform. | ✓ |