Manual Test Runbook — F12: Brownfield Import Library
Owner: Sagar | Time: ~6 min (Parts A + B offline) / +15–30 min per module (optional Part C live adoption) | Sandbox: snowops-sandbox-01
Overview
F12 (modules/azure/import-blocks/) is the SnowOps brownfield import library:
config-driven Terraform import {} blocks that adopt pre-existing Azure resources
into the F-modules, one <module>.tf per module. It makes the "Brownfield-Safe by
Default" principle executable.
Covered modules (this drop): F1 baseline · F2 network-hub · F3 aks-secure · F4 acr · F5 key-vault · F6 state-backend · J1 log-analytics · J2 policy-diagnostics · J6 audit-log-archive.
The directory double-duties as its own offline test harness: each file pairs the
import blocks with a placeholder module call so terraform validate proves
every to = address resolves against a real module resource. Parts A + B verify
that offline; Part C is an optional live adoption of one real sandbox resource.
Part A — Offline validate + fmt (no cloud, ~3 min)
terraform -chdir=modules/azure/import-blocks init -backend=false -input=false
terraform -chdir=modules/azure/import-blocks validate
terraform -chdir=modules/azure/import-blocks fmt -check -recursive
Expect: Success! The configuration is valid. and clean fmt. A failure here
means an import { to = … } references an address that no longer exists in a
module (e.g. a resource was renamed) — the gate's whole purpose.
Part B — Terratest + tflint (offline, ~3 min)
cd tests/terratest
go test -v -timeout 5m ./modules/azure/... -run TestImportBlocksValidate
go test -count=1 -timeout 15m ./... # full offline suite still green
( cd ../../modules/azure/import-blocks && tflint )
Expect: TestImportBlocksValidate PASS; full suite green; tflint exit 0.
Part C — Live adoption drill (sandbox, optional, ~15–30 min)
Proves a real resource can be adopted with a zero-change plan. state-backend (F6) is the cheapest target. Use an ephemeral sandbox RG tagged
ephemeral=true.
- Create a throwaway resource outside Terraform (e.g. a storage account):
az group create -n snowops-import-drill-rg -l eastus --tags ephemeral=true
az storage account create -n snoimportdrill$RANDOM -g snowops-import-drill-rg \
-l eastus --sku Standard_LRS
-
In a scratch root module, call
module "state_backend"with inputs matching the live resource, then copy the relevantimport {}blocks frommodules/azure/import-blocks/state-backend.tf, replacing the placeholder IDs with the real ones (az storage account show --ids …). -
terraform plan→ review the Plan: N to import, 0 to add, 0 to change, 0 to destroy line (after the first adopt-and-reconcile pass).terraform applyadopts into state. Re-runterraform plan→ No changes is the success criterion. -
Teardown:
Pass criteria
- Part A —
validatesucceeds;fmt -checkclean - Part B —
TestImportBlocksValidatepasses; full offline suite green; tflint exit 0 - (Part C) a real resource adopts with a subsequent zero-change plan; drill RG removed
Per-module key-scheme cheatsheet
| Module | for_each key scheme |
|---|---|
| F1 baseline | sub policy assignment = subscription GUID; Defender pricing = service name |
| F2 network-hub | hub subnet/NSG = subnet name; spoke vNet/RT/peering = spoke name; spoke subnet/NSG = "<spoke>/<subnet>"; DNS zone/hub link = zone name; spoke DNS link = "<zone>/<spoke>"; flow log = the NSG's key |
| F3 aks-secure | user node pool = pool name |
| F4 acr | AcrPull role assignment = principal object ID |
| F5 key-vault | role assignment = "<Role Name>/<principal_id>" |
| F6 state-backend | container / immutability policy = container name |
| J1 log-analytics | table = table name; role assignment = "<Role Name>/<principal_id>" |
| J2 policy-diagnostics | remediation role assignment = role definition name |
| J6 audit-log-archive | archive-reader role assignment = principal object ID |
Counted (
count) resources use[0]; the import gate proves the base address resolves but not the key — follow these schemes when replicating blocks.
Sign-off
- Tester: _ | Date: _ | Result: PASS / FAIL / N/A
- Notes: