Skip to content

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 login done; az account show confirms the sandbox subscription is selected
  • Identity has Contributor on the sandbox subscription (+ User Access Administrator only if you exercise *_principal_ids RBAC in Part D)
  • Local tooling: terraform >= 1.6, go >= 1.22, az CLI >= 2.50, jq
  • SNOWOPS_SANDBOX_SUBSCRIPTION_ID and SNOWOPS_SANDBOX_TENANT_ID env vars set
  • Working directory: repo root

Steps

Part A — terraform fmt + validate (offline, ~2 min)

  1. 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.

  1. 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)

  1. Run the whole offline suite to confirm J1 hasn't regressed anything:
cd tests/terratest
go test -v -timeout 12m ./...

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 = false so the deferred terraform destroy tears the workspace down unattended.

  1. 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>"
  1. Run the J1 integration test:
cd tests/terratest
go test -v -tags integration -timeout 30m ./modules/azure/... -run TestLogAnalyticsModule
  1. Watch for key milestones:
  2. Plan: 3 to add, 0 to change, 0 to destroy. — RG + workspace + self-audit diagnostic setting (the fixture leaves table_retention empty and the delete lock off, so no table/lock resources).
  3. azurerm_log_analytics_workspace.this: Creation complete — typically ~20–40 seconds.
  4. All output assertions PASS, including the observability_contract shape check (workspace_id, customer_id GUID, retention_days = 90).
  5. 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.

  1. 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"
  1. Retention by category — confirm the SigninLogs table 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 }
  1. 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
  1. 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 -3
    

    Expected: the delete is rejected with a ScopeLocked / CanNotDelete error. Revert the example's enable_delete_lock back to false before teardown.

  2. Self-audit access logs — confirm the diagnostic setting exists and targets the Audit category:

    az monitor diagnostic-settings list --resource "$WS_ID" \
      --query "[].{name:name, logs:logs[?enabled].category}" -o json
    # expect a setting named snowops-law-access-audit with category "Audit"
    
  3. 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 validate passes for the module + example
  • Part A — TestLogAnalyticsValidate + TestJ1ObservabilityContractConformance pass
  • Part B — full offline Terratest suite passes (25 top-level tests)
  • Part C — TestLogAnalyticsModule integration test passes end-to-end
  • Workspace created at PerGB2018, retentionInDays = 90
  • customer_id (workspace GUID) populated; observability_contract shape correct
  • Self-audit diagnostic setting present with the Audit category
  • (Part D) SigninLogs table override = 90d interactive / 365d total
  • (Part D) disableLocalAuth = true (AAD-only)
  • (Part D) delete is refused while the CanNotDelete lock is in place
  • All Destroy calls complete without error
  • No orphaned RGs / soft-deleted workspaces remain (az group list -o table, az monitor log-analytics workspace list-deleted -o table if 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:

az group delete --name "<name_prefix>-law-rg" --yes --no-wait

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:

az lock delete --name snowops-law-delete-lock \
  --resource "<workspace-arm-id>"

Sign-off

  • Tester: _  |  Date: _  |  Result: PASS / FAIL / N/A
  • Notes: