diff --git a/README.md b/README.md index 3bebf95..ecf0d0c 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,22 @@ This module creates an Azure Data Protection Backup Vault with the specified nam **SnapshotStore** is a legacy/compatibility datastore value that appears in provider schemas and older patterns. For newer Azure Backup workflows, Microsoft guidance and examples more often emphasize `OperationalStore` and `VaultStore`, with `OperationalStore` generally preferred over snapshot-style behavior for active operational protection. +## Protecting Resources + +This module can protect multiple resource types. Azure Blob Storage is the first supported resource type, and additional types will be added over time. + +### Azure Blob Storage + +Use `blob_backup_policies` to define one or more Blob backup policy profiles, and `protected_blob_storage_accounts` to map each storage account to a selected policy via `backup_policy_key`. + +For each protected storage account, you can optionally set: + +- `container_names` to protect specific containers (when omitted/null, all containers are included) +- `backup_instance_location` to override backup instance location +- `backup_instance_name` to override backup instance naming + +Note: the `Storage Account Backup Contributor` role assignment for the vault identity should be configured by the caller (for example, using a separate IAM module). + ## Module Inputs, Outputs, and Examples ### Variables @@ -39,6 +55,17 @@ This module creates an Azure Data Protection Backup Vault with the specified nam - `type`: Identity type. Allowed values: `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned`. - `identity_ids`: Optional list of user-assigned identity IDs (required when `type = "UserAssigned"`). - `tags`: A map of tags to apply to the backup vault. +- `protected_blob_storage_accounts`: Map of Blob Storage accounts to protect. Each object supports: + - `id`: Storage account resource ID (required). + - `container_names`: Optional container filter list (null/omitted means all containers). + - `backup_instance_location`: Optional override for backup instance location. + - `backup_instance_name`: Optional override for backup instance name. + - `backup_policy_key`: Optional key selecting an entry from `blob_backup_policies`. +- `blob_backup_policies`: Map of Blob backup policy definitions. Each object supports: + - `name`: Optional policy name. + - `backup_repeating_time_intervals`: Optional schedule list in ISO 8601 repeating interval format. + - `operational_default_retention_duration`: Optional operational retention duration (ISO 8601 duration). + - `vault_default_retention_duration`: Optional vaulted retention duration (ISO 8601 duration). ### Outputs @@ -46,6 +73,8 @@ This module creates an Azure Data Protection Backup Vault with the specified nam - `backup_vault_name`: The name of the created backup vault. - `backup_vault_identity_principal_id`: Principal ID of the assigned managed identity, if configured. - `backup_vault_identity_tenant_id`: Tenant ID of the assigned managed identity, if configured. +- `backup_policy_blob_storage_ids`: Map of Blob backup policy IDs keyed by policy key. +- `backup_instance_blob_storage_ids`: Map of Blob backup instance IDs keyed by protected account key. ### Example Usage diff --git a/main.tf b/main.tf index 7b92ed2..7abec67 100644 --- a/main.tf +++ b/main.tf @@ -7,6 +7,29 @@ locals { var.name : "${coalesce(var.base_name, "")}${substr(md5("${data.azurerm_client_config.current.subscription_id}/${var.rg_name}/${coalesce(var.base_name, "")}"), 0, 6)}" ) + + blob_storage_accounts = var.protected_blob_storage_accounts + blob_backup_enabled = length(local.blob_storage_accounts) > 0 + + default_backup_policies = { + default = { + name = "${local.backup_vault_name}-blob-policy" + backup_repeating_time_intervals = ["R/2026-01-01T01:00:00+00:00/P1D"] + operational_default_retention_duration = "P30D" + vault_default_retention_duration = "P30D" + } + } + + blob_backup_policies = length(var.blob_backup_policies) > 0 ? { + for key, policy in var.blob_backup_policies : key => { + name = coalesce(try(policy.name, null), "${local.backup_vault_name}-${key}-blob-policy") + backup_repeating_time_intervals = coalesce(try(policy.backup_repeating_time_intervals, null), local.default_backup_policies.default.backup_repeating_time_intervals) + operational_default_retention_duration = coalesce(try(policy.operational_default_retention_duration, null), local.default_backup_policies.default.operational_default_retention_duration) + vault_default_retention_duration = coalesce(try(policy.vault_default_retention_duration, null), local.default_backup_policies.default.vault_default_retention_duration) + } + } : local.default_backup_policies + + default_backup_policy_key = contains(keys(local.blob_backup_policies), "default") ? "default" : sort(keys(local.blob_backup_policies))[0] } resource "azurerm_data_protection_backup_vault" "this" { @@ -32,3 +55,26 @@ resource "azurerm_data_protection_backup_vault" "this" { tags = var.tags } + +resource "azurerm_data_protection_backup_policy_blob_storage" "this" { + for_each = local.blob_backup_enabled ? local.blob_backup_policies : {} + + name = each.value.name + vault_id = azurerm_data_protection_backup_vault.this.id + + backup_repeating_time_intervals = each.value.backup_repeating_time_intervals + operational_default_retention_duration = each.value.operational_default_retention_duration + vault_default_retention_duration = each.value.vault_default_retention_duration +} + +resource "azurerm_data_protection_backup_instance_blob_storage" "this" { + for_each = local.blob_storage_accounts + + name = coalesce(try(each.value.backup_instance_name, null), "${local.backup_vault_name}-${each.key}-blob-instance") + vault_id = azurerm_data_protection_backup_vault.this.id + location = coalesce(try(each.value.backup_instance_location, null), var.location) + storage_account_id = each.value.id + backup_policy_id = azurerm_data_protection_backup_policy_blob_storage.this[coalesce(try(each.value.backup_policy_key, null), local.default_backup_policy_key)].id + + storage_account_container_names = try(each.value.container_names, null) +} diff --git a/outputs.tf b/outputs.tf index 9bb04e6..598bd1d 100644 --- a/outputs.tf +++ b/outputs.tf @@ -13,3 +13,17 @@ output "backup_vault_identity_principal_id" { output "backup_vault_identity_tenant_id" { value = try(azurerm_data_protection_backup_vault.this.identity[0].tenant_id, null) } + +output "backup_policy_blob_storage_ids" { + value = { + for key, policy in azurerm_data_protection_backup_policy_blob_storage.this : + key => policy.id + } +} + +output "backup_instance_blob_storage_ids" { + value = { + for key, instance in azurerm_data_protection_backup_instance_blob_storage.this : + key => instance.id + } +} diff --git a/variables.tf b/variables.tf index b216ec9..7226839 100644 --- a/variables.tf +++ b/variables.tf @@ -117,3 +117,48 @@ variable "tags" { type = map(string) default = {} } + +variable "protected_blob_storage_accounts" { + type = map(object({ + id = string + container_names = optional(list(string)) + backup_instance_location = optional(string) + backup_instance_name = optional(string) + backup_policy_key = optional(string) + })) + default = {} + + validation { + condition = ( + length(var.protected_blob_storage_accounts) == 0 || + ( + var.identity != null && + contains([ + "SystemAssigned", + "SystemAssigned, UserAssigned", + ], var.identity.type) + ) + ) + error_message = "When protected_blob_storage_accounts is set, identity.type must include SystemAssigned." + } + + validation { + condition = alltrue([ + for sa in values(var.protected_blob_storage_accounts) : ( + try(sa.backup_policy_key, null) == null || + contains(keys(var.blob_backup_policies), sa.backup_policy_key) + ) + ]) + error_message = "Each protected_blob_storage_accounts[*].backup_policy_key must exist in blob_backup_policies." + } +} + +variable "blob_backup_policies" { + type = map(object({ + name = optional(string) + backup_repeating_time_intervals = optional(list(string)) + operational_default_retention_duration = optional(string) + vault_default_retention_duration = optional(string) + })) + default = {} +}