Manages one
azurerm_data_protection_backup_policy_data_lake_storageβ the retention and schedule contract a Data Lake backup instance points at. Targetshashicorp/azurerm ~> 4.0.
- π― Creates one Data Lake Storage backup policy inside a Backup Vault.
- π§ Every argument is force-new and there is no update function β nothing about this policy can ever be edited in place.
- ποΈ Vaulted backup only β a single required retention duration, not the two optional tiers the blob storage policy carries.
- π Validates the schedule (1 to 5 intervals) and the time zone (a closed set of 142) β both of which its blob storage sibling leaves unchecked.
- π« Carries no
tagsβ the resource exposes none.
π‘ Why it matters: this looks like a clone of the blob storage backup policy and is not. There is one retention duration and it is required; the schedule is required, validated and capped at five; the retention rule's fields are flat, with no
criteriaorlife_cycleblock and nopriorityordata_store_typeat all; there is nodays_of_month; andtime_zoneis checked against a closed set rather than merely being non-empty. A configuration ported across does not fit, and several parts of it would be wrong in ways that only surface at apply.
If this module saves you time:
- β Star the repository
- πΌ Connect on LinkedIn
- β Buy me a coffee
flowchart TB
RG["azurerm_resource_group"]
VAULT["azurerm_data_protection_backup_vault"]
SA["azurerm_storage_account with HIERARCHICAL NAMESPACE"]
MI["the vault system-assigned identity"]
ROLE["Storage Account Backup Contributor assignment"]
POL["backup_policy_data_lake_storage"]
INS["backup_instance_data_lake_storage"]
RG -->|"resource group"| VAULT
VAULT -->|"data_protection_backup_vault_id, FORCE-NEW"| POL
VAULT -->|"data_protection_backup_vault_id, FORCE-NEW"| INS
POL -->|"backup_policy_data_lake_storage_id, updates IN PLACE"| INS
SA -->|"storage_account_id, FORCE-NEW"| INS
SA -->|"location, taken on trust and never verified"| INS
VAULT -->|"system-assigned identity"| MI
MI -->|"principal_id"| ROLE
ROLE -.->|"on the storage account, BEFORE the instance"| INS
SA -.->|"is_hns_enabled required, checked by NOTHING"| INS
classDef me fill:#0078D4,stroke:#004578,color:#ffffff
classDef key fill:#004578,stroke:#002b47,color:#ffffff
classDef ext fill:#F0F3F6,stroke:#9AA5B1,color:#1F2933
class POL,INS me
class VAULT key
class RG,SA,MI,ROLE ext
This module is one half of a pair. The solid edge from the policy to the instance is the only link between them, and it is the one edge in the diagram that updates in place β which is what makes the create-new-then-repoint sequence possible at all.
flowchart TB
VIN["name, data_protection_backup_vault_id"]
VDUR["default_retention_duration, REQUIRED -- one tier only"]
VSCH["backup_schedule, REQUIRED, 1 to 5 intervals, VALIDATED"]
VRULE["retention_rule map -- flat fields, no priority, no data_store_type"]
VTZ["time_zone, a CLOSED set of 142 identifiers"]
ALL["EVERY argument is FORCE-NEW, and there is NO update function"]
R["azurerm_data_protection_backup_policy_data_lake_storage.this"]
OID["id -- the value a backup instance references"]
OVAULT["this_resource_backs_up_to_the_vault_only"]
OREP["rules_narrowing_an_absolute_criteria, REPORTED not refused"]
ONEW["changing_retention_means_a_new_policy_not_an_edit"]
VIN --> ALL
VDUR --> ALL
VSCH --> ALL
VRULE --> ALL
VTZ --> ALL
ALL --> R
R --> OID
R --> OVAULT
R --> OREP
R --> ONEW
classDef me fill:#0078D4,stroke:#004578,color:#ffffff
classDef key fill:#004578,stroke:#002b47,color:#ffffff
classDef ext fill:#F0F3F6,stroke:#9AA5B1,color:#1F2933
class R key
class ALL,ONEW me
class VIN,VDUR,VSCH,VRULE,VTZ,OID,OVAULT,OREP ext
| Resource | Count | Role |
|---|---|---|
azurerm_data_protection_backup_policy_data_lake_storage |
1 (this) |
The keystone, and the only resource here. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None here. The caller configures the provider, its authentication and its features {} block. |
Schema notes that bite, each verified against the provider source rather than inferred:
- π΄ There is no update function. Create, read and delete only. This is why the
timeoutsblock has three keys β a fourth is rejected by Terraform, not silently ignored. - π΄ Every single argument is force-new.
force_new_fieldslists all six.data_protection_backup_vault_idtakes its marker from a shared helper, so a reading of the resource file alone shows one fewer. - π΄ A cross-field rule is enforced inside the CREATE function. Every retention rule needs at least one of
absolute_criteriaanddays_of_week, and the provider's own refusal therefore arrives at apply, after a plan has been approved. This module checks the same rule offline. - π΄
backup_scheduleis required, validated, and capped at five intervals. Minimum one, maximum five, each parsed as a repeating interval. Neither bound appears in the exported schema. The blob storage policy's equivalent is optional with no validator at all. - π΄
time_zoneis a closed, case-sensitive set of 142 Windows identifiers. The blob storage policy checks only that the string is non-empty, so it takes a wrong identifier silently. - π΄ The retention rule's fields are FLAT β no
criteriablock, nolife_cycleblock, nopriority, nodata_store_type. The blob storage policy has all four. - There is no
days_of_month. Month-end must go throughweeks_of_month = ["Last"]with a day selector. - Four selector lists carry a minimum of one item, invisible in the exported schema. An empty list is refused with an error naming neither the field nor the reason.
- The name pattern requires a leading letter:
^[a-zA-Z][-a-zA-Z0-9]{2,149}$. The blob storage policy's does not. - The requires-import guard can be disabled by a provider feature, which would let a create overwrite an existing policy.
- No
CustomizeDiff, no version gate, notags.
| Role | Scope | Why |
|---|---|---|
Backup Contributor (or a custom role with Microsoft.DataProtection/backupVaults/backupPolicies/*) |
the Backup Vault | Create, read and delete backup policies. There is no update permission to need, because there is no update. |
| Reader | the Backup Vault | Sufficient for terraform plan to refresh an existing policy. |
βΉοΈ This policy is not a grant. It names no storage account and no identity. The permission that decides whether backup actually works β
Storage Account Backup Contributor, held by the vault's system-assigned identity on the protected account β belongs to the instance, not here.
- The
Microsoft.DataProtectionresource provider registered in the subscription. - An existing Backup Vault. The policy is created inside it and cannot be moved between vaults.
- A retention decision taken before the first apply β changing it later replaces the policy.
- The caller's
provider "azurerm" { features {} }block. The module declares none.
terraform-azurerm-data-protection-backup-policy-data-lake-storage/
βββ providers.tf # required_version + the azurerm ~> 4.0 pin. No provider block.
βββ variables.tf # 7 variables, 18 validations, every enum mirrored from the provider's own source
βββ main.tf # one keystone `this`, a dynamic retention_rule over a keyed map
βββ outputs.tf # 40 outputs; id first, then name, then the facts
βββ README.md # this file
βββ SCOPE.md # the cross-module contract
βββ LICENSE # MIT
βββ .gitignore
provider "azurerm" {
features {}
}
module "data_lake_backup_policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-daily-30d"
data_protection_backup_vault_id = module.backup_vault.id
# Both are REQUIRED on this resource. Data Lake backup is vaulted only, so there
# is one retention duration and a schedule is always needed.
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
}π The caller configures the provider, its authentication and its
features {}block. This module declares none of them.
Consumes
| Input | Type | Source |
|---|---|---|
data_protection_backup_vault_id |
Resource ID | terraform-azurerm-data-protection-backup-vault β id |
name |
string, required |
caller |
default_retention_duration |
ISO 8601 duration, required | caller |
backup_schedule |
list(string), required, 1β5 |
caller |
retention_rule |
map(object) keyed by rule name |
caller |
time_zone, timeouts |
see Inputs | caller |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
The policy's Resource ID | β¦-backup-instance-data-lake-storage β backup_policy_data_lake_storage_id |
this_resource_backs_up_to_the_vault_only |
The tier model | review |
rules_narrowing_an_absolute_criteria |
Reported, not refused | review |
force_new_fields |
All six arguments | change review |
1 Β· The smallest real policy
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-daily-30d"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
}βΉοΈ There is no smaller call. Both the duration and the schedule are Required β unlike the blob storage backup policy, where the schedule is optional because its operational tier is continuous. Data Lake backup is vaulted only, so a schedule is always needed.
2 Β· Vaulted only β there is no operational tier here
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-vaulted"
data_protection_backup_vault_id = module.backup_vault.id
# ONE duration. There is no `operational_default_retention_duration` on this
# resource and no `vault_default_retention_duration` either -- just this.
default_retention_duration = "P365D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
}
output "vault_only" {
value = module.policy.this_resource_backs_up_to_the_vault_only
}π‘ This is the good case, not a limitation. Vaulted backup copies data into the Backup Vault, so it survives loss of the storage account. The blob storage policy's operational tier keeps data on the account and does not.
3 Β· Several schedule intervals β and the cap at five
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-four-times-daily"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
# Four start instants, each repeating daily. A FIFTH is legal; a SIXTH is not.
backup_schedule = [
"R/2026-09-01T00:00:00+00:00/P1D",
"R/2026-09-01T06:00:00+00:00/P1D",
"R/2026-09-01T12:00:00+00:00/P1D",
"R/2026-09-01T18:00:00+00:00/P1D",
]
}
β οΈ Minimum one, maximum five, and neither bound appears in the exported schema. The provider's own refusal for a sixth is a bare "too many list items" that names no reason; this module reports the limit before the provider does.
4 Β· The schedule is validated here β unlike the blob policy
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-shapes"
data_protection_backup_vault_id = module.backup_vault.id
# A BARE duration.
default_retention_duration = "P30D"
# A REPEATING INTERVAL: "R/", a start instant, then the period. On THIS resource
# the provider parses it. On the blob storage policy it applies no validator at
# all, so the same mistake there reaches Azure.
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
}π Swapping the two is the likeliest misconfiguration, because both are called "ISO 8601" and both are strings. Writing
"P1D"intobackup_scheduleis refused here by the provider and by this module. Seea_repeating_interval_is_not_a_durationandthe_schedule_is_validated_here_unlike_the_blob_policy.
5 Β· A retention rule β note the FLAT fields
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-weekly"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
retention_rule = {
"keep-weekly" = {
# `duration` sits DIRECTLY on the rule. There is no `life_cycle` block here,
# and no `criteria` block either -- the selectors are flat too.
duration = "P12W"
absolute_criteria = "FirstOfWeek"
}
}
}π΄ A rule written for the blob storage backup policy will not parse here. That resource nests
durationinsidelife_cycleand the selectors insidecriteria, and additionally requires apriority. This one has none of those. Seethe_criteria_fields_are_flat_on_this_resource.
6 Β· Several rules, and no priority to order them
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-tiered"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
retention_rule = {
"keep-weekly" = {
duration = "P12W"
absolute_criteria = "FirstOfWeek"
}
"keep-monthly" = {
duration = "P24M"
absolute_criteria = "FirstOfMonth"
}
"keep-yearly" = {
duration = "P7Y"
absolute_criteria = "FirstOfYear"
}
}
}βΉοΈ There is no
priorityfield on this resource, so there is nothing to order and nothing to keep unique β the blob storage backup policy requires one on every rule, and this module's counterpart there enforces uniqueness. Neither applies here.
7 Β· Every rule needs absolute_criteria or days_of_week
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-selectors"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
retention_rule = {
# Selects by absolute_criteria.
"weekly" = {
duration = "P12W"
absolute_criteria = "FirstOfWeek"
}
# Selects by days_of_week instead. Either satisfies the rule; both together
# are legal too.
"sundays" = {
duration = "P8W"
days_of_week = ["Sunday"]
}
}
}π΄ This check exists in the provider β but inside its CREATE function. Its refusal therefore arrives at apply, after a plan has been reviewed and approved. Both fields sit in one object here, so this module runs the same check offline. It is an earlier failure, not a stricter rule. See
every_rule_needs_absolute_criteria_or_days_of_week.
8 Β· All seven weekdays, Wednesday included
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-midweek"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
retention_rule = {
"midweek" = {
duration = "P8W"
days_of_week = ["Wednesday"]
}
}
}
β οΈ Worth stating because of a defect in the neighbouring documentation. The provider's registry page for the blob storage backup policy lists the legal days as "Monday, Tuesday, Thursday, Friday, Saturday and Sunday" β omitting Wednesday β while every validator in this family accepts all seven. A reader carrying that list across would refuse a legal value.all_seven_weekdays_are_legalemits the real set.
9 Β· Month-end, without a days_of_month field
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-month-end"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
retention_rule = {
"month-end" = {
duration = "P36M"
# There is NO days_of_month on this resource. Month-end is expressed as the
# LAST week plus a day selector.
days_of_week = ["Sunday"]
weeks_of_month = ["Last"]
}
}
}π΄ The blob storage backup policy has a
days_of_monthfield that encodes month-end as0. This resource has no such field at all, so that idiom does not transfer. Seethere_is_no_days_of_month_on_this_resource.
10 Β· Narrowing with weeks_of_month and months_of_year
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-quarterly"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
retention_rule = {
"quarterly-first-sunday" = {
duration = "P5Y"
days_of_week = ["Sunday"]
weeks_of_month = ["First"]
months_of_year = ["January", "April", "July", "October"]
}
}
}
β οΈ weeks_of_monthandmonths_of_yearNARROW a selection; they do not make one. A rule that sets either while selecting only byabsolute_criteriais legal and does something, but reads as though it selected the backups by itself.rules_narrowing_an_absolute_criterianames those rules so the intent can be confirmed rather than assumed β reported, never refused.
11 Β· scheduled_backup_times, and the empty-list trap
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-pinned"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P60D"
backup_schedule = ["R/2026-09-01T03:00:00+00:00/P1D"]
retention_rule = {
"pinned-times" = {
duration = "P90D"
days_of_week = ["Sunday"]
scheduled_backup_times = ["2026-09-06T03:00:00Z"]
# Do NOT write `months_of_year = []`. Omit the field instead -- the provider
# puts a minimum of one item on all four selector lists, and its error names
# neither the field nor the reason. This module sends an empty list as null,
# so omitting and clearing produce the same plan either way.
}
}
}
β οΈ Four selector lists carry an invisible minimum of one item βdays_of_week,months_of_year,weeks_of_monthandscheduled_backup_times. None of those minimums appears in the schema Terraform exports. Seethe_selector_lists_reject_an_empty_list.
12 Β· time_zone β a closed set of 142, checked
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-eastern"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00-05:00/P1D"]
# A WINDOWS identifier from the provider's own closed set. Not "America/New_York",
# and not a near-miss like "Eastern Time" -- both are refused.
time_zone = "Eastern Standard Time"
}π This is a real check here and not on the sibling. The provider validates
time_zoneagainst 142 identifiers, case-sensitively; the blob storage backup policy accepts any non-empty string and therefore takes a wrong identifier silently. BothUTCandCoordinated Universal Timeare members, as are offset forms such asUTC-02andUTC+13.
βΉοΈ Note the interaction: the start instants in
backup_schedulealready carry their own UTC offset.
13 Β· Three timeout keys, not four
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-timeouts"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P30D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
timeouts = {
create = "30m"
read = "5m"
delete = "30m"
}
}π There is no
updatekey, and that is correct. The resource has no update function, so no update timeout is declared and Terraform rejects the key outright β worth stating because object-type conversion normally silently discards an undeclared key.
βΉοΈ On this resource each operation declares its own timeout beside its own function, so none can bound an operation other than the one it names. That is a property of the provider's typed framework rather than luck; the defect where a read silently runs on the create timeout is possible in the older framework and impossible here.
14 Β· Changing retention β create the new policy, then repoint
# THE POLICY CANNOT BE EDITED. Every argument is force-new and there is no update
# function, so raising retention from 30 to 365 days is a migration, not an edit.
#
# 1. create a NEW policy alongside the old one
# 2. point the instance at it -- an IN-PLACE update, no data disturbed
# 3. remove the old policy, once nothing references it
module "policy_v2" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-365d"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P365D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
}
module "instance" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-instance-data-lake-storage.git?ref=v1.0.0"
name = "bi-adls-prod"
location = module.storage.location
data_protection_backup_vault_id = module.backup_vault.id
storage_account_id = module.storage.id
storage_container_names = ["data"]
# The in-place update.
backup_policy_data_lake_storage_id = module.policy_v2.id
}π΄ Do not reverse the order. Editing the old policy in place destroys it, and any instance still referencing the old ID goes with it β nothing in Terraform records which instances use a policy, because the reference is held on the instance. See
destroying_this_policy_breaks_every_instance_using_it.
15 Β· ποΈ End-to-end composition
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-backup-eastus"
location = "eastus"
}
module "storage" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-storage-account.git?ref=v1.0.0"
name = "stadlsprod01"
resource_group_name = module.rg.name
location = module.rg.location
# π΄ THIS IS WHAT MAKES IT DATA LAKE STORAGE GEN2, and backup here requires it.
# Nothing in the backup resources checks it -- an account without it fails when
# protection is configured.
is_hns_enabled = true
containers = {
"data" = {}
}
}
module "backup_vault" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-vault.git?ref=v1.0.0"
name = "bv-corp"
resource_group_name = module.rg.name
location = module.rg.location
datastore_type = "VaultStore"
identity = {
type = "SystemAssigned"
}
}
# THIS MODULE.
module "policy" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-policy-data-lake-storage.git?ref=v1.0.0"
name = "bp-adls-hybrid"
data_protection_backup_vault_id = module.backup_vault.id
default_retention_duration = "P365D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
time_zone = "UTC"
retention_rule = {
"keep-monthly" = {
duration = "P24M"
absolute_criteria = "FirstOfMonth"
}
}
}
# The grant the INSTANCE needs, on the STORAGE ACCOUNT, held by the VAULT's
# identity. Sequence it ahead of the instance.
module "vault_grant" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-role-assignments.git?ref=v1.0.0"
scope = module.storage.id
role_assignments = {
"vault-backup" = {
role_definition_name = "Storage Account Backup Contributor"
principal_id = module.backup_vault.identity_principal_id
}
}
}
module "instance" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-data-protection-backup-instance-data-lake-storage.git?ref=v1.0.0"
name = "bi-adls-prod"
data_protection_backup_vault_id = module.backup_vault.id
# The STORAGE ACCOUNT's region, from the account itself -- nothing verifies it.
location = module.storage.location
storage_account_id = module.storage.id
backup_policy_data_lake_storage_id = module.policy.id
storage_container_names = ["data"]
depends_on = [module.vault_grant]
}
output "policy_id" {
value = module.policy.id
}
output "vault_only" {
value = module.policy.this_resource_backs_up_to_the_vault_only
}
output "protection_state" {
value = module.instance.protection_state
}π΄ Two things here are doing quiet work.
is_hns_enabled = trueis the Data Lake Gen2 requirement that nothing in the backup resources checks, anddepends_onon the grant orders a permission that nothing in the instance's arguments references β without it Terraform may create the instance first and block on a protection poll waiting for access it does not have.
Required: name, data_protection_backup_vault_id, default_retention_duration, backup_schedule.
Optional: retention_rule, time_zone, timeouts.
Full schemas
variable "retention_rule" {
type = map(object({
duration = string
absolute_criteria = optional(string)
days_of_week = optional(list(string))
months_of_year = optional(list(string))
weeks_of_month = optional(list(string))
scheduled_backup_times = optional(list(string))
}))
default = {}
# Flat -- no `criteria` block, no `life_cycle` block, no `priority`,
# no `data_store_type`, no `days_of_month`.
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
delete = optional(string)
})
default = null
# Provider defaults: create 30m, read 5m, delete 30m. There is no update.
}| Output | Description | Notes |
|---|---|---|
id |
Policy Resource ID | Emitted first. The value an instance references. |
name, data_protection_backup_vault_id, vault_name, resource_group_name, subscription_id |
Identity and owning vault | Parsed from the vault ID. |
default_retention_duration, backup_schedule, schedule_count |
Retention and schedule | Known at plan. |
retention_rule_names, retention_rule_count, rules_using_absolute_criteria, rules_using_days_of_week |
The rules and how each selects | Known at plan. |
rules_narrowing_an_absolute_criteria |
Reported, not refused | Known at plan. |
the_absolute_criteria_set_is_closed_and_case_sensitive, all_seven_weekdays_are_legal, the_week_number_set |
The real enum sets, as values | Taken from the provider's SDK constants. |
force_new_fields |
All six arguments | β |
this_policy_can_never_be_updated, changing_retention_means_a_new_policy_not_an_edit, destroying_this_policy_breaks_every_instance_using_it |
The lifecycle facts | Constant. |
this_resource_backs_up_to_the_vault_only, the_criteria_fields_are_flat_on_this_resource, there_is_no_days_of_month_on_this_resource |
Differences from the blob storage policy | Constant. |
the_schedule_is_capped_at_five_intervals, the_schedule_is_validated_here_unlike_the_blob_policy, a_repeating_interval_is_not_a_duration, the_selector_lists_reject_an_empty_list |
Schedule and selector traps | Constant. |
every_rule_needs_absolute_criteria_or_days_of_week |
A provider check moved earlier | Constant. |
time_zone, time_zone_was_left_to_the_service, the_time_zone_set_is_closed_and_case_sensitive |
Time zone | β |
the_provider_declares_no_update_timeout_and_that_is_correct, the_timeout_wiring_defect_class_cannot_occur_here, no_customize_diff_guards_this_resource |
Where checks do and do not fire | Constant. |
this_resource_supports_no_azure_resource_tags |
Why there is no tags tail |
Constant. |
import_address, the_import_guard_names_this_resource_correctly, the_import_check_can_be_disabled_by_a_provider_feature |
Import | The guard is correct by construction. |
lifecycle_prevent_destroy_is_not_available_to_a_module_caller |
A caller cannot add prevent_destroy |
Constant. |
No output is a secret, and nothing here is marked
sensitiveβ there is nothing sensitive to mark.
The absence of an update function is the whole story, exactly as on the blob storage backup policy. All six arguments are force-new, and even if one were not, there is no update function to call. Treat this module's inputs as a version, not as settings: change any of them and you are provisioning a new policy. The safe sequence β create, repoint, remove β depends on a fact that lives in a different resource, so both modules state it.
Everything else about this resource differs from that sibling, and that is the point of most of the documentation here. Data Lake Storage backup is vaulted only, so there is one required retention duration instead of two optional tiers under an at-least-one rule; the schedule is required rather than optional, and validated rather than unchecked, and capped at five intervals; the retention rule's fields are flat, with no criteria block, no life_cycle block, no priority and no data_store_type; there is no days_of_month; the name pattern demands a leading letter; and even the vault argument is spelled differently. The two resources look like clones and behave like different products, so this module emits the differences as outputs rather than only describing them β a reader porting a configuration needs them where they will be seen.
One provider check is enforced in the wrong place, and this module moves it. Every retention rule needs at least one of absolute_criteria and days_of_week. The provider agrees β but it applies the rule inside its create function, so its refusal arrives at apply, after a plan has been reviewed and approved. Both fields sit in one object here, so the same check runs offline. It is worth being precise about what that does and does not mean: the module adds no constraint, it only makes an existing one fail earlier.
Two constraints are invisible in the schema Terraform exports and both produce unhelpful errors when hit. backup_schedule has a floor of one interval and a ceiling of five; the four selector lists each have a floor of one item. The provider's messages for these name neither the field nor the reason, so this module checks both and says why. The selector lists are additionally sent as null when empty, so omitting a selector and clearing it produce the same plan.
The 142 time zones and the four enum sets are mirrored from the provider's own source rather than transcribed. That matters more than it sounds: the SDK's enum helpers return constant names rather than string literals, so the values have to be resolved rather than assumed equal β and this library has previously found constant names that differ from their values with no rule. The module's lists are asserted equal to that extraction.
lifecycle is not valid inside a module block, so a caller cannot add prevent_destroy here. Where deletion protection matters, put a CanNotDelete management lock on the vault β noting that a lock prevents deletion, not replacement, which is the failure mode that actually applies to this resource.
| Concern | This module's default | Opt-out |
|---|---|---|
| Retention | Required β the provider gives no choice | n/a |
| Schedule | Required, 1β5 intervals, validated | n/a |
| The at-least-one-of criteria rule | Enforced offline, because the provider enforces it at apply | n/a |
weeks_of_month / months_of_year narrowing |
Reported through an output | n/a β never refused |
| Enum sets | Mirrored exactly from the provider's own source | n/a |
| Empty selector list | Sent as null, so omit and clear plan alike |
n/a |
retention_rule keying |
A map keyed by rule name, so nothing re-indexes | n/a |
tags |
Not offered β the resource exposes none | tag the vault instead |
| Secrets | None accepted, none emitted | n/a |
βΉοΈ Secure-by-default has nothing to act on here, and this module says so rather than implying otherwise. Every security-relevant argument on this resource is Required and none of them is permissive β there is no empty call to make safe and no risky value to make the caller type. The risk here is an unnoticed replacement, so the defaults are chosen to make the lifecycle visible.
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module at a tag β ?ref=v1.0.0 β never a branch. This library is plan-only: a human applies from CI.
| Check | Covered by | Needs credentials? |
|---|---|---|
| HCL parses; types are consistent | terraform validate |
No |
| Formatting | terraform fmt -check |
No |
This module's own validation {} blocks β the duration and interval shapes, the 1β5 schedule bound, the anchored vault ID, the four enum sets, the 142 time zones, the at-least-one-of criteria rule |
terraform plan on a configuration that calls the module |
No |
| The provider's own schema-level checks β the name pattern, the enum sets, the selector minimums | terraform plan |
No |
| π΄ The provider's at-least-one-of criteria rule in its own right | terraform apply β it lives in the create function |
Yes |
| Whether Azure accepts the schedule and the retention combination | terraform apply |
Yes |
β οΈ terraform validaterun against a configuration that calls this module evaluates none of the module's variable values, so neither this module'svalidation {}blocks nor the provider's schema checks are reached β validate reports success. The refusal lands atterraform plan, which needs no credentials for these checks.
$ terraform output
id = "/subscriptions/.../resourceGroups/rg-backup-eastus/providers/Microsoft.DataProtection/backupVaults/bv-corp/backupPolicies/bp-adls-hybrid"
name = "bp-adls-hybrid"
vault_name = "bv-corp"
default_retention_duration = "P365D"
backup_schedule = ["R/2026-09-01T23:00:00+00:00/P1D"]
schedule_count = 1
retention_rule_names = ["keep-monthly"]
retention_rule_count = 1
rules_using_absolute_criteria = ["keep-monthly"]
rules_using_days_of_week = []
rules_narrowing_an_absolute_criteria = []
time_zone = "UTC"
time_zone_was_left_to_the_service = false
this_resource_backs_up_to_the_vault_only = true
this_policy_can_never_be_updated = true
force_new_fields = [
"name",
"data_protection_backup_vault_id",
"backup_schedule",
"default_retention_duration",
"retention_rule",
"time_zone",
]| Symptom | Cause | Fix |
|---|---|---|
| Plan shows the policy being replaced after a one-character change | Every argument is force-new and there is no update function | Expected. Create a new policy, repoint the instances, then remove the old one β see example 14. |
| A backup instance fails after a policy change | The policy was replaced and the instance still referenced the old ID | Sequence it: new policy, then the instance's backup_policy_data_lake_storage_id, then remove the old policy. |
| A retention rule copied from a blob storage policy will not parse | That resource nests duration in life_cycle and the selectors in criteria, and requires priority |
Flatten it. This resource has none of those blocks or fields. |
every retention_rule must set at least one of absolute_criteria and days_of_week |
A rule with neither selector | Add one. The provider enforces the same rule, but only at apply. |
backup_schedule must contain between 1 and 5 intervals |
Six or more intervals, or none | Both bounds are the provider's own and invisible in the exported schema. |
every backup_schedule entry must be an ISO 8601 REPEATING interval beginning "R/" |
A bare duration such as P1D put in the schedule |
Use R/<start instant>/P<period>. |
time_zone must be one of the 142 Windows time zone identifiers |
An IANA name, a near-miss, or wrong casing | Use e.g. UTC or Eastern Standard Time. The provider's set is closed and case-sensitive here, unlike on the blob storage policy. |
| A "not enough list items" error naming no field | An empty days_of_week / months_of_year / weeks_of_month / scheduled_backup_times |
Omit the field rather than passing []. |
name must be β¦ and START WITH A LETTER |
A name beginning with a digit or hyphen | Legal on the blob storage policy, refused here. |
| A rule appears to select nothing | weeks_of_month or months_of_year narrows a selection, it does not make one |
Check rules_narrowing_an_absolute_criteria in the plan output. |
Unsupported argument: update on timeouts |
The resource has no update function | Remove the key. Three are supported: create, read, delete. |
| A create silently overwrote an existing policy | The provider's skip-import-check feature is enabled in the caller's provider block | That is provider configuration, not module configuration. See the_import_check_can_be_disabled_by_a_provider_feature. |
azurerm_data_protection_backup_policy_data_lake_storageazurerm_data_protection_backup_instance_data_lake_storageβ the consumer of this module'sid- Azure Backup for Azure Data Lake Storage
- Sibling modules:
terraform-azurerm-data-protection-backup-instance-data-lake-storage,terraform-azurerm-data-protection-backup-vault,terraform-azurerm-storage-account,terraform-azurerm-role-assignments - This module's
SCOPE.md
π "Infrastructure as Code should be standardized, consistent, and secure."