Manual Test Runbook — U5: Cost Anomaly Detection¶
Owner: CO | Time: ~6 min (Parts A + B offline) · +10 min (optional Part C live apply) | Sandbox: snowops-sandbox-01
Promotes U5 (
modules/azure/cost-anomaly-alert/) from 🟦 Code Complete → 🟩 Shipped. Parts A + B are offline ($0). Part C creates a REALazurerm_cost_anomaly_alerton the sandbox subscription and confirms it exists viaaz costmanagement/ the portal. U5 is the detection complement to U1 budget alerts: U1 = "you crossed a threshold", U5 = "your spend pattern changed unexpectedly". Anomaly alerts are free Cost Management objects (~$0).
Prerequisites¶
- Sandbox subscription access active (PIM activated if required)
-
az logindone; sandbox subscription selected - Identity has Cost Management Contributor (or Contributor) on the subscription
-
SNOWOPS_SANDBOX_SUBSCRIPTION_ID+SNOWOPS_SANDBOX_TENANT_IDexported - Local tooling:
terraform >= 1.6,go >= 1.22,az CLI >= 2.50,jq - Working directory: repo root
Steps¶
Part A — terraform fmt + validate + go vet (offline, ~3 min)¶
- Module + example formatting and validate:
terraform -chdir=modules/azure/cost-anomaly-alert fmt -recursive -check
terraform -chdir=modules/azure/cost-anomaly-alert init -backend=false -input=false
terraform -chdir=modules/azure/cost-anomaly-alert validate
terraform -chdir=modules/azure/cost-anomaly-alert/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/cost-anomaly-alert/examples/basic validate
Expected: fmt -check clean (exit 0); Success! for both validate calls.
The example uses a synthetic subscription_id/tenant_id, so validate runs
with no cloud creds.
Sandbox note: the Terraform registry network is blocked in the CI sandbox, so
init(which downloads the azurerm provider) fails there identically for every module — confirm against any existing sibling fixture (e.g.terraform -chdir=tests/terratest/fixtures/budget-alert init) and treat it as environmental, not a U5 defect.fmt -checkandgo vetrun fully offline and ARE the offline gate.
- The test compiles (go vet):
Expected: no output, exit 0 — TestCostAnomalyAlertValidate compiles.
- Offline Terratest case (where registry access is available):
Expected: PASS — exercises the for_each over two alerts, the module-wide vs
per-alert sender/scope fallbacks, the message field, and the variable
validations + preconditions, offline.
To eyeball a rejection, temporarily set an
email_subjectlonger than 70 chars in the fixture and re-run validate — it must fail with the "email_subject must be 1–70 characters" message. Revert after.
Part B — full Terratest suite (offline, ~3 min)¶
bash cd tests/terratest && go test -count=1 -timeout 15m ./...
Expected: the full suite green (the new TestCostAnomalyAlertValidate
included).
Part C — live apply against the sandbox subscription (sandbox, ~10 min, ~$0)¶
The point of Part C is to prove a real azurerm_cost_anomaly_alert is created
and recognised by Cost Management. There is no per-resource charge — anomaly
alerts are free, so this window costs effectively $0.
- Apply the example against the sandbox subscription:
cd modules/azure/cost-anomaly-alert/examples/basic
terraform init -input=false
terraform apply -auto-approve \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \
-var 'email_addresses=["your.name@example.com"]'
Expected: applies clean. terraform output cost_anomaly_alert_ids shows the
ARM ID of the created alert; terraform output email_recipients echoes your
recipient list.
- Confirm the anomaly alert exists. The
azurerm_cost_anomaly_alertis a Cost Management scheduled-action of kindInsightAlert. List it via the REST API (theaz costmanagementextension does not expose anomaly alerts directly):
SUB="$SNOWOPS_SANDBOX_SUBSCRIPTION_ID"
az rest --method get \
--url "https://management.azure.com/subscriptions/$SUB/providers/Microsoft.CostManagement/scheduledActions?api-version=2023-11-01&\$filter=properties/scope eq 'subscriptions/$SUB'" \
--query "value[?kind=='InsightAlert'].{name:name, displayName:properties.displayName, status:properties.status}" -o jsonc
Expected: the snowops-example-anomaly alert is listed with its
displayName and an enabled status.
Portal alternative: Cost Management > Cost alerts > Anomaly alerts (or Cost analysis > … > Anomaly detection settings) shows the created alert and its recipients.
-
Detection latency note (expected, not a failure): Azure needs roughly 10 days of cost history to model the subscription's normal spend before it can flag anomalies. A freshly created alert (or a new subscription) stays quiet until the model warms up — do not expect a test email on apply. Confirming the alert object exists (step 6) is the Part C pass criterion; actual firing is validated organically once history accrues.
-
Destroy:
cd modules/azure/cost-anomaly-alert/examples/basic
terraform destroy -auto-approve \
-var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \
-var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID"
Expected: clean destroy — the anomaly alert is removed. Nothing else on the subscription is touched (detection is stateless config; no data to clean up).
Pass criteria¶
- Part A —
fmt -checkclean; module + example validate (or registry-block noted as environmental);go vet ./modules/azure/clean;TestCostAnomalyAlertValidatepasses - Part B — full offline suite passes
- (Part C) apply creates the alert;
az rest … scheduledActions(or portal) confirms theInsightAlert; destroy removes it cleanly - All test changes reverted
Failure modes¶
email_subjectrejected at plan — over 70 chars. Variable validation + precondition, by design (Azure caps the subject at 70).email_addressesrejected at plan — 0 or > 20 entries. Azure requires 1–20 recipients; variable validation + precondition enforce it.messagerejected at plan — over 250 chars (Azure limit).- No anomaly email after apply — expected for the first ~10 days while Azure builds cost history. Not a U5 defect; documented in the README.
initfails downloading the provider in CI — registry network is blocked in the sandbox; environmental, identical for every module.fmt -check+go vetare the offline gate.
Cost impact¶
The cost-anomaly alert is a free Cost Management object — no consumption charge. The only spend it generates is the notification emails on detection. Part C costs effectively $0. No dollar figure is asserted.
Removal path¶
terraform destroy (Part C step 8) removes each azurerm_cost_anomaly_alert.
Detection is stateless subscription config — no orphaned resources and no data to
purge. Verified clean in Part C.
Sign-Off¶
| Field | Value |
|---|---|
| Part A (fmt + validate + go vet + Terratest case) | ☐ PASS |
| Part B (offline suite) | ☐ PASS |
| Part C (live apply + alert confirmed + destroy) | ☐ PASS / ☐ skipped |
| Tester | |
| Date | |
| Result | ☐ PASS |