diff --git a/examples/postgresql_flexible_servers.tfvars b/examples/postgresql_flexible_servers.tfvars new file mode 100644 index 00000000..435bb244 --- /dev/null +++ b/examples/postgresql_flexible_servers.tfvars @@ -0,0 +1,138 @@ +postgresql_flexible_servers = { + flex_postgres_server_test = { + resource_group_ref = "rg_test" + name = "pg-flex-example" + administrator_login = "pgadmin" + administrator_password = "SuperSecurePassword123!" + sku_name = "B_Standard_B1ms" + version = "14" + zone = "1" + + storage_mb = 32768 + storage_tier = "P15" + auto_grow_enabled = true + + backup_retention_days = 7 + geo_redundant_backup_enabled = false + + public_network_access_enabled = false + subnet_ref = "vnet_test/snet_sqlmi" + dnszone_ref = "pdns_zone_test" + + identity = { + type = "SystemAssigned" + identity_ids_ref = ["mi_test"] + } + + authentication = { + active_directory_auth_enabled = true + password_auth_enabled = true + } + + customer_managed_key = { + kvkey_ref = "kvkey_test1" + } + + high_availability = { + mode = "ZoneRedundant" + standby_availability_zone = "2" + } + + maintenance_window = { + day_of_week = 1 + start_hour = 2 + start_minute = 30 + } + } +} + +# pre-requisites +resource_groups = { + rg_test = { + name = "rg-test-01" + location = "northeurope" + tags = { + DeployDate = "11/07/2025", + DeadLine = "11/07/2026", + Owner = "Test Test", + Project = "Test", + } + } +} + +virtual_networks = { + vnet_test = { + name = "vnet-test-dv-ne-01" + resource_group_ref = "rg_test" + cidr = ["10.10.10.0/24"] + subnets = { + snet_sqlmi = { + name = "snet-sql-managed-instance" + cidr = ["10.10.10.0/25"] + delegation = "sql_managed_instance" + } + snet_private_endpoints = { + name = "snet-private-endpoints" + cidr = ["10.10.10.128/25"] + service_endpoints = ["Microsoft.Storage", "Microsoft.KeyVault"] + } + } + } +} + +private_dns_zones = { + pdns_zone_test = { + name = "privatelink.postgres.database.azure.com" + resource_group_ref = "rg_test" + vnet_ref = ["vnet_test"] + } +} + +managed_identities = { + mi_test = { + name = "id-test-dv-ne-01" + resource_group_ref = "rg_test" + } +} + +key_vault_keys = { + kvkey_test1 = { + name = "generated-certificate" + key_vault_ref = "kv_test" + key_type = "RSA" + key_size = "2048" + + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] + + rotation_policy = { + expire_after = "P90D" + notify_before_expiry = "P29D" + automatic = { + time_before_expiry = "P30D" + } + } + } +} + +keyvaults = { + kv_test = { + name = "kv-test-dv-ne-01" + resource_group_ref = "rg_test" + network_rules = { + default_action = "Deny" + allowed_ips = ["10.10.10.10", "20.20.20.20"] + subnets = { + allow_app1 = { + subnet_ref = "vnet_test/snet_private_endpoints" + } + } + } + } +} diff --git a/src/_outputs.tf b/src/_outputs.tf index 5ed17337..4cb2771f 100644 --- a/src/_outputs.tf +++ b/src/_outputs.tf @@ -86,6 +86,10 @@ output "postgres" { value = module.postgres } +output "postgresql_flexible_servers" { + value = module.postgres +} + output "container_registries" { value = module.container_registries } diff --git a/src/_variables.resources.tf b/src/_variables.resources.tf index 8a7b8885..b6979a30 100644 --- a/src/_variables.resources.tf +++ b/src/_variables.resources.tf @@ -40,6 +40,8 @@ variable "public_ips" { default = {} } variable "postgres" { default = {} } +variable "postgresql_flexible_servers" { default = {} } + variable "keyvaults" { default = {} } variable "storage_accounts" { default = {} } diff --git a/src/modules/postgresql_flexible_server/_locals.tf b/src/modules/postgresql_flexible_server/_locals.tf new file mode 100644 index 00000000..458b7be9 --- /dev/null +++ b/src/modules/postgresql_flexible_server/_locals.tf @@ -0,0 +1,99 @@ +locals { + resource_group = var.resources[ + try(var.settings.lz_key, var.client_config.landingzone_key) + ].resource_groups[var.settings.resource_group_ref] + resource_group_name = local.resource_group.name + location = local.resource_group.location + + delegated_subnet_id = try( + var.resources[ + try(var.settings.vnet_lz_key, var.client_config.landingzone_key) + ].virtual_networks[ + split("/", var.settings.subnet_ref)[0] + ].subnets[ + split("/", var.settings.subnet_ref)[1] + ].id, + var.settings.delegated_subnet_id, + null + ) + + private_dns_zone_id = try( + var.resources[ + try(var.settings.dnszone_lz_key, var.client_config.landingzone_key) + ].private_dns_zones[ + var.settings.dnszone_ref + ].id, + var.settings.private_dns_zone_id, + null + ) + + key_vault_key_id = try( + var.resources[ + try(var.settings.customer_managed_key.kvkey_lz_key, var.client_config.landingzone_key) + ].key_vault_keys[ + var.settings.customer_managed_key.kvkey_ref + ].versionless_id, + var.settings.customer_managed_key.key_vault_key_id + ) + + geo_backup_key_vault_key_id = try( + var.resources[ + try(var.settings.customer_managed_key.geo_kvkey_lz_key, var.client_config.landingzone_key) + ].key_vault_keys[ + var.settings.customer_managed_key.geo_kvkey_ref + ].versionless_id, + var.settings.customer_managed_key.geo_backup_key_vault_key_id, + null + ) + + primary_user_assigned_identity_id = try( + var.resources[ + try(var.settings.customer_managed_key.prime_mi_lz_key, var.client_config.landingzone_key) + ].managed_identities[ + var.settings.customer_managed_key.prime_mi_ref + ].id, + var.settings.customer_managed_key.primary_user_assigned_identity_id, + null + ) + + geo_backup_user_assigned_identity_id = try( + var.resources[ + try(var.settings.customer_managed_key.geo_mi_lz_key, var.client_config.landingzone_key) + ].managed_identities[ + var.settings.customer_managed_key.geo_mi_ref + ].id, + var.settings.customer_managed_key.geo_backup_user_assigned_identity_id, + null + ) + + identity_ids = [ + for id_ref in try(var.settings.identity.identity_ids_ref, []) : + var.resources[ + try(var.settings.identity.mi_lz_key, var.client_config.landingzone_key) + ].managed_identities[id_ref].id + ] + + administrator_password = try( + ( + try(length(trimspace(var.settings.key_vault_ref)) > 0, false) + ? random_password.admin[0].result + : try(var.settings.administrator_password, random_password.admin[0].result) + ), + null + ) + + key_vault_id = try( + var.resources[ + try(var.settings.key_vault_lz_key, var.client_config.landingzone_key) + ].keyvaults[ + var.settings.key_vault_ref + ].id, + null + ) + + tags = merge( + var.global_settings.tags, + var.global_settings.inherit_resource_group_tags ? local.resource_group.tags : {}, + try(var.settings.tags, {}) + ) +} diff --git a/src/modules/postgresql_flexible_server/_outputs.tf b/src/modules/postgresql_flexible_server/_outputs.tf new file mode 100644 index 00000000..69137758 --- /dev/null +++ b/src/modules/postgresql_flexible_server/_outputs.tf @@ -0,0 +1,7 @@ +output "id" { + value = azurerm_postgresql_flexible_server.main.id +} + +output "fqdn" { + value = azurerm_postgresql_flexible_server.main.fqdn +} diff --git a/src/modules/postgresql_flexible_server/_variables.tf b/src/modules/postgresql_flexible_server/_variables.tf new file mode 100644 index 00000000..bb7a9d86 --- /dev/null +++ b/src/modules/postgresql_flexible_server/_variables.tf @@ -0,0 +1,18 @@ +variable "global_settings" { + description = "Global settings for tinycaf" +} + +variable "settings" { + description = "All the configuration for this resource" +} + +variable "resources" { + description = "All required resources" +} + +variable "client_config" { + description = "Client config such as current landingzone key" + type = object({ + landingzone_key = string + }) +} diff --git a/src/modules/postgresql_flexible_server/main.tf b/src/modules/postgresql_flexible_server/main.tf new file mode 100644 index 00000000..4acb059a --- /dev/null +++ b/src/modules/postgresql_flexible_server/main.tf @@ -0,0 +1,85 @@ +resource "azurerm_postgresql_flexible_server" "main" { + name = var.settings.name + resource_group_name = local.resource_group_name + location = local.location + tags = local.tags + delegated_subnet_id = local.delegated_subnet_id + private_dns_zone_id = local.private_dns_zone_id + administrator_login = try(var.settings.administrator_login, "${var.settings.name}-pgadim") + administrator_password = local.administrator_password + backup_retention_days = try(var.settings.backup_retention_days, null) + geo_redundant_backup_enabled = try(var.settings.geo_redundant_backup_enabled, null) + create_mode = try(var.settings.create_mode, null) + public_network_access_enabled = try(var.settings.public_network_access_enabled, null) + point_in_time_restore_time_in_utc = try(var.settings.point_in_time_restore_time_in_utc, null) + replication_role = try(var.settings.replication_role, null) + sku_name = try(var.settings.sku_name, null) + source_server_id = try(var.settings.source_server_id, null) + auto_grow_enabled = try(var.settings.auto_grow_enabled, null) + storage_mb = try(var.settings.storage_mb, null) + storage_tier = try(var.settings.storage_tier, null) + version = try(var.settings.version, null) + zone = try(var.settings.zone, null) + + dynamic "identity" { + for_each = can(var.settings.identity) ? [1] : [] + content { + type = var.settings.identity.type + identity_ids = try(local.identity_ids, null) + } + } + + dynamic "authentication" { + for_each = can(var.settings.authentication) ? [1] : [] + content { + active_directory_auth_enabled = try(var.settings.authentication.active_directory_auth_enabled, null) + password_auth_enabled = try(var.settings.authentication.password_auth_enabled, null) + tenant_id = try(var.settings.authentication.tenant_id, null) + } + } + + dynamic "customer_managed_key" { + for_each = can(var.settings.customer_managed_key) ? [1] : [] + content { + key_vault_key_id = local.key_vault_key_id + primary_user_assigned_identity_id = local.primary_user_assigned_identity_id + geo_backup_user_assigned_identity_id = local.geo_backup_user_assigned_identity_id + geo_backup_key_vault_key_id = local.geo_backup_key_vault_key_id + } + } + + dynamic "high_availability" { + for_each = can(var.settings.high_availability) ? [1] : [] + content { + mode = var.settings.high_availability.mode + standby_availability_zone = try(var.settings.high_availability.standby_availability_zone, null) + } + } + + dynamic "maintenance_window" { + for_each = can(var.settings.maintenance_window) ? [1] : [] + content { + day_of_week = try(var.settings.maintenance_window.day_of_week, null) + start_hour = try(var.settings.maintenance_window.start_hour, null) + start_minute = try(var.settings.maintenance_window.start_minute, null) + } + } +} + +resource "random_password" "admin" { + count = try(length(trimspace(var.settings.key_vault_ref)) > 0, false) ? 1 : 0 + length = try(var.settings.password_settings.length, 20) + min_upper = try(var.settings.password_settings.min_upper, 2) + min_lower = try(var.settings.password_settings.min_lower, 2) + min_special = try(var.settings.password_settings.min_special, 2) + numeric = try(var.settings.password_settings.numeric, true) + special = try(var.settings.password_settings.special, true) + override_special = try(var.settings.password_settings.override_special, "!@#$%&") +} + +resource "azurerm_key_vault_secret" "admin_password" { + count = try(length(trimspace(var.settings.key_vault_ref)) > 0, false) ? 1 : 0 + name = try(var.settings.custom_secret_name, "${var.settings.name}-${var.settings.administrator_login}") + value = random_password.admin[0].result + key_vault_id = local.key_vault_id +} diff --git a/src/postgresql_flexible_servers.tf b/src/postgresql_flexible_servers.tf new file mode 100644 index 00000000..5868f3e5 --- /dev/null +++ b/src/postgresql_flexible_servers.tf @@ -0,0 +1,26 @@ +module "postgresql_flexible_servers" { + source = "./modules/postgresql_flexible_server" + for_each = var.postgresql_flexible_servers + settings = each.value + global_settings = local.global_settings + + resources = merge( + { + (var.landingzone.key) = { + resource_groups = module.resource_groups + managed_identities = module.managed_identities + virtual_networks = module.virtual_networks + private_dns_zones = module.private_dns_zones + key_vault_keys = module.key_vault_keys + keyvaults = module.keyvaults + } + }, + { + for k, v in module.remote_states : k => v.outputs + } + ) + + client_config = { + landingzone_key = var.landingzone.key + } +}