Manual Test Runbook — M4: Microsoft Purview Baseline¶
Owner: Sagar | Time: ~7 min (Parts A + B) · +45 min (optional Part C live apply + scan) | Sandbox: snowops-sandbox-01
Promotes M4 (
modules/azure/purview-baseline/) from 🟦 Code Complete → 🟩 Shipped. Parts A + B are offline ($0). Part C costs real money even while idle — the Purview Data Map bills per capacity unit-hour (~$0.4/CU-hour, region-dependent; ~$10/day order of magnitude at the 1-CU floor) plus scan vCore-hours while a scan runs. Apply, verify, destroy promptly.
Prerequisites¶
- Sandbox subscription access active (PIM activated if required)
-
az logindone; sandbox subscription selected - Identity has Owner OR (Contributor + User Access Administrator) on the sandbox sub — UAA is needed because the module grants the Purview identity Reader + Storage Blob Data Reader over the scan scopes
- A sandbox storage account with a few blobs to scan (any test data; a CSV with fake emails/phone numbers makes classifications visible)
-
SNOWOPS_SANDBOX_SUBSCRIPTION_ID+SNOWOPS_SANDBOX_TENANT_IDexported - Local tooling:
terraform >= 1.6,go >= 1.22,az CLI >= 2.50 - Working directory: repo root
Steps¶
Part A — terraform fmt + validate + offline tests (~4 min)¶
- Formatting (offline, no registry access needed):
terraform -chdir=modules/azure/purview-baseline fmt -recursive -check
terraform -chdir=tests/terratest/fixtures/purview-baseline fmt -check
Expected: exit 0, no output.
- Module + example validate:
terraform -chdir=modules/azure/purview-baseline init -backend=false -input=false
terraform -chdir=modules/azure/purview-baseline validate
terraform -chdir=modules/azure/purview-baseline/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/purview-baseline/examples/basic validate
Expected: Success! for both. The example uses a synthetic J1 workspace ID
+ storage scan scope, so validate runs with no cloud creds.
Note:
terraform initneeds Terraform-registry network access to fetch the azurerm provider (the lock files pin 4.76.0). In a network-restricted sandbox init fails at provider download — run Part A from a normal workstation.
- Offline Terratest case + vet:
cd tests/terratest
go vet ./modules/azure/
go test -v -timeout 5m ./modules/azure/... -run TestPurviewBaselineValidate
Expected: vet silent; test PASS — exercises the account (SystemAssigned identity + named managed RG), diagnostics ON against a synthetic J1 ID, and 3 scan scopes × 2 default roles = 6 role assignments through the for_each.
Part B — context check: what M4 is and where the Terraform boundary sits (~3 min)¶
No commands — confirm the README story holds before going live:
-
What Purview is. Microsoft Purview (Data Map / data governance) is the data-classification foundation: a catalog account whose scanner walks registered sources, applies built-in classifiers (credit cards, national IDs, secrets, …), and builds the map of what data exists and where.
-
Where it sits in the M series. M1 (encryption policy), M2 (CMK), M3 (TLS policy), M6 (data residency) enforce how data is protected; M4 discovers + classifies what data exists — the prerequisite for M5 (DLP) and M7 (GDPR/DSAR evidence). "We know where our sensitive data lives" is this asset.
-
The ARM vs data-plane boundary. Terraform (this module) owns the account + access: the Purview account, its system identity, the self-audit diagnostic setting → J1, and the Reader/Storage Blob Data Reader grants over
scan_scopes. Collections, source registration, scans, classification rules, and the glossary are data-plane objects behind the catalog/scan endpoints (governance portal or Purview REST) — that is Part C below, by hand, exactly as the module README states. If anyone claims the module "configures scanning", that is wrong by design. -
Full offline suite still green:
Part C — live apply + scan (sandbox, ~45 min, bills per CU-hour — destroy promptly)¶
- Apply the example with a REAL J1 workspace ID and a REAL sandbox storage account as the scan scope:
cd modules/azure/purview-baseline/examples/basic
terraform init -input=false
WS="<REAL J1 workspace ARM ID>"
SA="/subscriptions/$SNOWOPS_SANDBOX_SUBSCRIPTION_ID/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<name>"
terraform apply -auto-approve \
-var "log_analytics_workspace_id=$WS" \
-var "scan_scope=$SA"
Expected: account + diagnostic setting + 2 role assignments apply clean
(~5-10 min; the account create is slow). terraform output shows the
principal_id, catalog_endpoint, scan_endpoint.
Flake: the role assignments can transiently fail on a freshly-created identity (AAD replication).
principal_typeis pinned, but if an apply 403s/PrincipalNotFounds onazurerm_role_assignment.scan, re-run — the second apply converges.
- Verify the ARM layer landed:
az purview account show -g snowops-governance-rg -n snowops-example-purview \
--query "{state:properties.provisioningState, publicNetwork:properties.publicNetworkAccess, identity:identity.principalId, managedRg:properties.managedResourceGroupName}" -o jsonc
az monitor diagnostic-settings list \
--resource "$(terraform output -raw purview_account_id)" -o table
az role assignment list --assignee "$(terraform output -raw principal_id)" \
--query "[].{role:roleDefinitionName, scope:scope}" -o table
Expected: state: Succeeded; the snowops-purview-diag setting targeting
the J1 workspace; Reader + Storage Blob Data Reader on $SA.
-
Data plane (the part Terraform deliberately does not own). Open the governance portal —
https://web.purview.azure.com(select the account) — then:- create a collection (or use the root collection),
- Data Map → Sources → Register → Azure Blob Storage → pick the
$SAaccount, credential = Purview MSI (this works because of the module's role grants), - New scan on the source → default scope + default rule set
(
AzureStorage_Default) → run once, - wait for the scan to complete (~5-15 min for a small account), then Data Catalog → Browse → the storage assets appear; any seeded PII-looking test data shows classifications on the asset's schema tab.
-
Confirm the scan-status logs reached J1 (allow ~10 min ingestion lag):
az monitor log-analytics query \ --workspace "<J1 customer GUID>" \ --analytics-query "PurviewScanStatusLogs | take 5" -o tableExpected: rows for the scan you just ran (queued → running → succeeded). (Category names → tables vary by Purview platform version; if the table is empty after lag, check
AzureDiagnostics | where ResourceProvider == 'MICROSOFT.PURVIEW'.) -
Destroy promptly (the account bills CU-hours while it sits):
Expected: clean destroy — role assignments, diagnostic setting, account (Azure removes the managed RG + data map with it), holding RG. The scanned storage account and its data are untouched (Purview only ever had read).
Pass criteria¶
- Part A — fmt exit 0; module + example validate;
go vetclean;TestPurviewBaselineValidatepasses - Part B — boundary story confirmed; full offline suite green
- (Part C) account Succeeded; diag setting → J1; Reader + Storage Blob Data Reader on the scan scope; portal scan completes; classifications visible; scan-status logs in J1
- (Part C) destroy clean; managed RG gone; source data untouched
Failure modes¶
- Apply fails: managed RG already exists — Purview must create + own
managed_resource_group_name; pick an unused name or leave it null. - Portal scan fails authentication — RBAC propagation lag on the MSI
grants (retry after a few minutes), or the source isn't covered by
scan_scopes. The module grants Storage roles only; other source types needscan_rolesoverrides or out-of-band data-plane grants (README, Failure modes). - Diagnostic setting apply fails "category not supported" — categories vary
by Purview platform version; list them with
az monitor diagnostic-settings categories list --resource <account-id>and setdiagnostic_log_categoriesto match. - Cost surprise — the Data Map bills per CU-hour even idle. If a sandbox account was left standing, destroy it; the spend mechanism is CU-hours + scan vCore-hours, documented in the README Cost impact.
Cost impact¶
Part C: Data Map ~$0.4/capacity-unit-hour (region-dependent — check the Azure pricing page) for the life of the account, + scan vCore-hours for the one scan (small account ≈ minutes of one vCore). A same-day apply→verify→destroy run is roughly a few dollars; an account forgotten for a month is hundreds. Parts A/B are $0.
Removal path¶
terraform destroy (step 12) removes everything M4 created; Azure deletes the
Purview-managed RG with the account. Verified clean in Part C. The J1 workspace
and the scanned storage account are never touched.
Sign-Off¶
| Field | Value |
|---|---|
| Part A (fmt + validate + vet + Terratest case) | ☐ PASS |
| Part B (context + offline suite) | ☐ PASS |
| Part C (live apply + portal scan + destroy) | ☐ PASS / ☐ skipped |
| Tester | |
| Date | |
| Result | ☐ PASS |