Manual Test Runbook — J1: Log Analytics Workspace
Owner: Sagar | Time: ~10 min (Parts A + B) · +5 min (optional Part C apply) · +15 min (optional Part D feature verification) | Sandbox: snowops-sandbox-01
Promotes J1 (
modules/azure/log-analytics/) from 🟦 Code Complete → 🟩 Shipped. Part C is ~$0 — an empty workspace bills only on ingestion, and only your own query activity ingests. No network, no firewall, no private endpoint.
Prerequisites
- Sandbox subscription access active (PIM activated if required)
-
az logindone;az account showconfirms the sandbox subscription is selected - Identity has Contributor on the sandbox subscription (+ User Access Administrator only if you exercise
*_principal_idsRBAC in Part D) - Local tooling:
terraform >= 1.6,go >= 1.22,az CLI >= 2.50,jq -
SNOWOPS_SANDBOX_SUBSCRIPTION_IDandSNOWOPS_SANDBOX_TENANT_IDenv vars set - Working directory: repo root
Steps
Part A — terraform fmt + validate (offline, ~2 min)
- Confirm formatting + structural validity of the module + example:
terraform -chdir=modules/azure/log-analytics fmt -recursive -check
terraform -chdir=modules/azure/log-analytics init -backend=false -input=false
terraform -chdir=modules/azure/log-analytics validate
terraform -chdir=modules/azure/log-analytics/examples/basic init -backend=false -input=false
terraform -chdir=modules/azure/log-analytics/examples/basic validate
Expected: Success! The configuration is valid. for both.
- Run the J1-relevant offline Terratest cases:
cd tests/terratest
go test -v -timeout 5m ./modules/azure/... \
-run 'TestLogAnalyticsValidate|TestJ1ObservabilityContractConformance'
Expected: 2 top-level tests pass. TestLogAnalyticsValidate exercises the
per-table retention code path offline; TestJ1ObservabilityContractConformance
proves J1's observability_contract output still conforms to
modules/_contracts/observability (the same shape F1 emits).
Part B — full Terratest suite (offline, ~3 min)
- Run the whole offline suite to confirm J1 hasn't regressed anything:
Expected: 25 top-level tests pass across all packages (23 in
modules/azure/ including the two new J1 cases, plus TestNoopHarness and
TestSandboxValidate).
Part C — integration test (real Azure apply + destroy, ~5 min, ~$0)
The fixture sets
enable_delete_lock = falseso the deferredterraform destroytears the workspace down unattended.
- Export sandbox env vars (same as every other module test):
export SNOWOPS_SANDBOX_SUBSCRIPTION_ID="<sandbox-subscription-guid>"
export SNOWOPS_SANDBOX_TENANT_ID="<sandbox-tenant-guid>"
- Run the J1 integration test:
cd tests/terratest
go test -v -tags integration -timeout 30m ./modules/azure/... -run TestLogAnalyticsModule
- Watch for key milestones:
Plan: 3 to add, 0 to change, 0 to destroy.— RG + workspace + self-audit diagnostic setting (the fixture leavestable_retentionempty and the delete lock off, so no table/lock resources).azurerm_log_analytics_workspace.this: Creation complete— typically ~20–40 seconds.- All output assertions PASS, including the
observability_contractshape check (workspace_id,customer_idGUID,retention_days = 90). Destroy complete!— clean teardown.
Workspace-name reuse caution. A destroyed workspace is soft-deleted and recoverable for 14 days; re-using the same name within that window recovers the old workspace rather than creating a fresh one. The integration test uses
random.UniqueId()so this never collides.
Part D — feature verification on a live workspace (optional, ~15 min)
Verifies the three J1 features the integration test doesn't deep-check: per-table ("by category") retention, the delete lock (immutability), and the AAD-only posture. Run a throwaway apply from the example, poke it, destroy.
- Apply the example with the delete lock and per-table retention left at their real-world defaults — but flip the lock on for the immutability check:
cd modules/azure/log-analytics/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 "workspace_name=snowops-j1-rb-$RANDOM"
WS_ID=$(terraform output -raw workspace_id)
WS_NAME=$(echo "$WS_ID" | awk -F/ '{print $NF}')
WS_RG=$(echo "$WS_ID" | awk -F/ '{print $(NF-6)}')
echo "workspace=$WS_NAME rg=$WS_RG"
- Retention by category — confirm the
SigninLogstable carries the override (90d interactive / 365d total) while the workspace default is 90d:
az monitor log-analytics workspace show \
--resource-group "$WS_RG" --workspace-name "$WS_NAME" \
--query "retentionInDays" -o tsv # expect 90
az monitor log-analytics workspace table show \
--resource-group "$WS_RG" --workspace-name "$WS_NAME" \
--name SigninLogs \
--query "{retention:retentionInDays, total:totalRetentionInDays}" -o json
# expect { "retention": 90, "total": 365 }
- AAD-only posture — confirm shared-key local auth is disabled:
az monitor log-analytics workspace show \
--resource-group "$WS_RG" --workspace-name "$WS_NAME" \
--query "features.disableLocalAuth" -o tsv # expect true
-
Immutability (delete lock) — re-apply with the lock ON, then prove a delete is refused:
terraform apply -auto-approve \ -var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \ -var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \ -var "workspace_name=$WS_NAME" # (temporarily set enable_delete_lock = true in the example main.tf for this step) az monitor log-analytics workspace delete \ --resource-group "$WS_RG" --workspace-name "$WS_NAME" --yes 2>&1 | head -3Expected: the delete is rejected with a
ScopeLocked/CanNotDeleteerror. Revert the example'senable_delete_lockback tofalsebefore teardown. -
Self-audit access logs — confirm the diagnostic setting exists and targets the
Auditcategory: -
Teardown:
# If the lock is still on from step 10, the destroy removes it first # (it's a managed resource) provided enable_delete_lock is back to false. terraform destroy -auto-approve \ -var "subscription_id=$SNOWOPS_SANDBOX_SUBSCRIPTION_ID" \ -var "tenant_id=$SNOWOPS_SANDBOX_TENANT_ID" \ -var "workspace_name=$WS_NAME"
Pass criteria
- Part A —
terraform validatepasses for the module + example - Part A —
TestLogAnalyticsValidate+TestJ1ObservabilityContractConformancepass - Part B — full offline Terratest suite passes (25 top-level tests)
- Part C —
TestLogAnalyticsModuleintegration test passes end-to-end - Workspace created at
PerGB2018,retentionInDays = 90 -
customer_id(workspace GUID) populated;observability_contractshape correct - Self-audit diagnostic setting present with the
Auditcategory - (Part D)
SigninLogstable override = 90d interactive / 365d total - (Part D)
disableLocalAuth = true(AAD-only) - (Part D) delete is refused while the CanNotDelete lock is in place
- All
Destroycalls complete without error - No orphaned RGs / soft-deleted workspaces remain (
az group list -o table,az monitor log-analytics workspace list-deleted -o tableif available) - All test resources tagged
ephemeral = true(X7 cleanup safety net)
Teardown
The integration test runs terraform destroy automatically. If a failure
mid-run orphans resources, clean up manually:
Locked workspace. If a workspace was left with an out-of-band CanNotDelete lock (i.e. a lock not in Terraform state), remove it before the RG delete:
Sign-off
- Tester: _ | Date: _ | Result: PASS / FAIL / N/A
- Notes: