Terraform Azure storage management policy one policy per account, rule prefixes, Rust census
One policy per account. Rules own prefixes. Missing policy is not an empty lifecycle.
<!-- hal:authoritative:yaml -->
One management policy resource per storage account. Rules own prefixes and the enabled flag. Missing the policy is not an empty lifecycle.
§I. Frame
Tonight is TF day 75, trio #141. Recent TF Ops sat on AWS and GCP (CloudWatch retention 10-02, Artifact Registry cleanup 09-29, ECR scan 09-26). Rebalance to Azure. The cloud-native referent is Blob lifecycle: azurerm_storage_management_policy on a storage account, plus a read-only Rust census of which accounts have a policy and which rule prefixes are actually declared.
10-01 already counted Service Bus queues with azure_mgmt_servicebus. 09-17 taught Key Vault ephemeral/write-only. Those shelves are adjacent and not this claim.
The problem for today: declare one management policy with two rules (one enabled with prefixes, one disabled), then build a cargo-script census that lists storage accounts, fetches policy name default, and flags missing policies, disabled rules, and empty prefix_match.
§II. Three owners on one account
Policy Cardinality (named technique). Ask how many policy resources exist before asking what a rule deletes.
- Policy belongs to
azurerm_storage_management_policy. ARM stores one management policy per storage account. The Terraform resource maps to that singleton. The ARM name is alwaysdefault. - Rules belong inside the policy. Each rule has
name,enabled,filters(blob_types,prefix_match), andactions(base_blobtier/delete, optional snapshot/version). - Prefixes belong to the rule filter. Microsoft expects prefixes that start with the container name (for example
logs/active/). An emptyprefix_matchmatches broadly; that is a different claim than "no policy".
If you mix up the owners, a green plan can leave an account with no lifecycle while reviewers point at a rule block that never shipped.
§III. The Terraform
This module is the teaching referent. Validate with terraform init -backend=false and terraform validate against hashicorp/azurerm ~> 4.0. Do not apply from this fire.
resource "azurerm_storage_management_policy" "demo" {
storage_account_id = azurerm_storage_account.demo.id
rule {
name = "logs-cool-archive-delete"
enabled = true
filters {
blob_types = ["blockBlob"]
prefix_match = ["logs/active/"]
}
actions {
base_blob {
tier_to_cool_after_days_since_modification_greater_than = 30
tier_to_archive_after_days_since_modification_greater_than = 180
delete_after_days_since_modification_greater_than = 2555
}
}
}
rule {
name = "scratch-delete"
enabled = false
filters {
blob_types = ["blockBlob"]
prefix_match = ["scratch/"]
}
actions {
base_blob {
delete_after_days_since_modification_greater_than = 7
}
}
}
}
Fact one. One azurerm_storage_management_policy per storage account. A second resource against the same account fights the singleton.
Fact two. enabled = false keeps the rule text in ARM and out of active evaluation. Disabled is not deleted.
Fact three. Missing the policy resource is not the same as a policy with zero rules. ARM 404 on managementPolicies/default means no lifecycle policy.
Fact four. blob_types is required in the filter. Valid values are blockBlob and appendBlob. Tiering actions apply to block blobs.
Bundle file: storage-management-policy.tf next to this lesson.
§IV. The Rust census
Automation is a cargo script, not a Python CLI. Client: azure_mgmt_storage 0.21 default tag package_2023_05. Flow matches the 10-01 Service Bus pattern: DefaultAzureCredential, Client::new against https://management.azure.com, then:
storage_accounts_client().list(&sub).into_stream()for accounts.- Parse resource group from the account id.
management_policies_client().get(&rg, &name, &sub, "default").await.- Classify: missing policy (404), disabled rules, empty
prefix_match.
let mut pages = client.storage_accounts_client().list(&sub).into_stream();
// for each account → management_policies_client().get(..., "default")
// print rule rows; flag ATTENTION for missing_policy / disabled / empty_prefix
Full script: storage-management-policy-census.rs (nightly -Zscript). Nix wrapper: storage-management-policy-census.nix (writeBashBin).
Illustrative output (no Azure credentials on the lab Mac as of this fire; the lab must export working credentials before a live census):
rg-app/hedronitesa01 rule=logs-cool-archive-delete enabled=true blob_types=blockBlob prefix=logs/active/
rg-app/hedronitesa01 rule=scratch-delete enabled=false blob_types=blockBlob prefix=scratch/
rg-app/hedronitesa01 ATTENTION disabled=scratch-delete
rg-legacy/oldsa01 ATTENTION missing_policy
summary accounts=2 with_policy=1 missing_policy=1 disabled_rules=1 empty_prefix=0 attention=2
§V. How to run
# Validate the referent (no apply)
terraform init -backend=false
terraform validate
# Census (needs AZURE_SUBSCRIPTION_ID + DefaultAzureCredential)
cargo +nightly -Zscript ./storage-management-policy-census.rs
# or: nix-build -E 'with import <nixpkgs> {}; callPackage ./storage-management-policy-census.nix {}'
If credential resolution fails, stop. Re-auth is a human step on the lab Mac. Do not paste keys into the lesson tree.
§VI. Close
Azure Blob lifecycle is one policy per account. Tonight's Ops claim is cardinality, rule ownership of prefixes and enabled, and a Rust census that reads what ARM reports. Paired Dev parses plan JSON resource_drift / relevant_attributes. Paired Cert covers plan -refresh-only and why terraform refresh is gone.
Related
- Tome: Brikman 3e p.89 (provider credentials via environment) — referenced
- Bootcamp: tfpro Lab 15 (lifecycle as separate intent) — referenced
- Prior Ops: CloudWatch retention 10-02 · Service Bus census 10-01 · Artifact Registry 09-29 · Key Vault ephemeral 09-17