From 4e3a39978a94995683f297fbdb662320f16c9f8e Mon Sep 17 00:00:00 2001 From: bruzzechesse Date: Wed, 15 May 2024 13:06:50 +0200 Subject: [PATCH] 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