Brownfield & Multi-CI Adoption — Handover
Milestone: M3 · Scenario: Day-30 / Day-90 clients with partial IaC, existing Azure resources, or Azure DevOps.
Ownership: [CO]/[CA] — SnowOps configures; you operate. Nothing requires re-provisioning your existing estate.
This guide is the milestone-level handover for brownfield clients. For the deep mechanics of layering modules onto existing Terraform configs, read its companion: Brownfield In-Place Compliance Strategy.
The Promise
You do not have to rebuild your infrastructure to adopt SnowOps. M3 makes every Baseline capability usable against your existing Terraform-managed or click-ops Azure resources, on GitHub Actions or Azure DevOps, with stable module versioning.
| M3 capability | Asset | What it unlocks |
|---|---|---|
| Self-service prerequisite check | B6 | Run in your own tenant before engagement — validates tooling, auth, RBAC, providers |
| Brownfield import library | F12 | Adopt existing Azure resources into SnowOps modules with zero destroy/recreate |
| Azure DevOps pipelines | C5 | C1/C2/C3/D2 equivalents for ADO (pipelines/azure-devops/) |
| Ticketing adapters | E7 | Drift/incident/change tickets into GitHub Issues, Jira, Linear, or ADO Boards |
| Module versioning | F11 | Pin module versions; controlled upgrade path |
| Client offboarding | W4 | Clean exit / handover procedure (see §Offboarding below) |
Step 0 — Validate Prerequisites (B6)
Before anything touches your environment, run the self-service bootstrap checker in your own tenant:
It reads a read-only EnvironmentSnapshot and reports READY (exit 0) or NOT READY (exit 2) with a remediation list. It checks:
- Tooling:
az≥ 2.50,terraform≥ 1.6 - Auth: you're signed in
- Permissions: you can assign RBAC (Owner or User Access Administrator — Contributor is not sufficient, it cannot write role assignments), create Entra apps + SPs, and required resource providers are registered
- Licensing: Entra ID P2 for PIM (warning only — not a day-zero blocker)
It is read-only — it creates nothing. Resolve any blockers it lists before proceeding.
Choosing Your Adoption Path
There are three ways to bring SnowOps in, from lightest to deepest touch. Full examples are in Brownfield In-Place; the summary:
| Path | Touch | When to use |
|---|---|---|
| C — OPA gate only | Zero code changes; a new CI step | You want compliance enforced on future changes without restructuring anything. Start here. |
| A — Add modules in-place | module {} blocks in your existing configs |
You want SnowOps' hardened supporting resources (audit logging, backup, alerting) alongside your infra. |
| B — Import existing resources (F12) | Import blocks + module call | You want an existing resource (e.g. a Key Vault) fully managed by a SnowOps module with drift detection. |
Recommended sequence (also in the in-place guide):
Day 1: Run B6 prerequisite check → wire G-series discovery for a posture baseline
Day 1–3: Wire E0 + S2 → compliance dashboard (read-only, zero code changes)
Week 1: Add OPA/conftest gate to existing CI (Path C) + D5 waivers for existing violations
Week 2: Wire S1 drift detection against existing state backends
Week 2–4: Add Deny-policy modules (M1/M3/N5/U2) — no existing resources touched
Week 4–8: Add real-resource modules (log-analytics J1, policy-diagnostics J2,
audit-log-archive J6, backup-policy L1, budget-alert U1)
Week 8+: Use F12 import blocks to adopt resources you want fully module-managed
Adopting Existing Resources (F12)
The import library at modules/azure/import-blocks/ provides config-driven import {} blocks for the F-modules — covering F1 baseline, F2 network-hub, F3 aks-secure, F4 acr, F5 key-vault, F6 state-backend, plus J1 log-analytics, J2 policy-diagnostics, and J6 audit-log-archive (9 modules).
Procedure (full version in docs/runbooks/import/F12.md):
- Copy the relevant import block from
modules/azure/import-blocks/<module>.tf. - Add the matching
module {}call to your config, pinned to a semver tag. terraform plan— expect0 to add, 0 to change, 0 to destroyplus imports.- If the plan shows
changeactions, the module's hardening defaults differ from your resource (e.g.purge_protection_enabled: false → true). Review each — most are improvements. Apply deliberately. terraform applyto complete the import. The resource is now module-managed and drift-detected (S1).
Caveat:
terraform validateproves the import address resolves but does not evaluatefor_each/countkey validity. Key schemes are derived from the module source and documented per file. The runbook covers this.
Azure DevOps Clients (C5)
If you run Azure DevOps instead of (or alongside) GitHub Actions, the C5 templates in pipelines/azure-devops/ mirror the GitHub workflows:
| ADO template | GitHub equivalent | Purpose |
|---|---|---|
terraform-plan-apply.yml |
C1 | Plan-on-PR, apply-on-merge with OPA gate + stage approval |
container-build-sign.yml |
C2 | Build → ACR → Notation sign → Grype scan |
aks-deploy.yml |
C3 | ArgoCD deploy → smoke → rollback drill |
quality-gates.yml |
D2 | PR-blocking quality/security gates |
The underlying tools are identical — only the CI wrapper changes. Callers reference the templates via resources: repositories:. Stage approval gates replace GitHub environment gates.
Ticketing Into Your Stack (E7)
Drift (S1), incident, and change tickets don't have to go to GitHub Issues. The E7 TicketPlatform adapter (apps/ticket-platform/, @snowops/ticket-platform) supports:
- GitHub Issues (REST)
- Jira (REST v2)
- Linear (GraphQL)
- Azure DevOps Boards (WIQL + JSON-Patch)
Select your platform via the selectPlatform(name, env, overrides) factory or the snowops-ticket CLI (defaults to dry-run). All adapters share one idempotent marker-based upsert — a hidden HTML-comment marker in the ticket body dedupes, so re-runs update the same ticket instead of spamming new ones.
Module Versioning & Upgrades (F11)
Every SnowOps module reference must be pinned to a semver tag, never a floating branch:
source = "git::https://github.com/snowops/snowops-automation//modules/azure/key-vault?ref=key-vault/v1.0.0"
modules/registry.jsonis the source of truth for published modules and versions.- The
apps/module-registrytool audits your consumer tree and flagsunpinned,ref-mismatch, orunknown-versionreferences in CI. - Upgrades are deliberate: bump the
ref, review the plan, apply. No silent updates.
Run the pin audit as part of your pipeline so an unpinned module reference fails the build.
What This Delivers for Compliance
| Control theme | Framework reference | Asset |
|---|---|---|
| Change management on existing infra | SOC 2 CC8.1 · ISO 27001 A.8.32 | OPA gate (Path C), S1 drift, E7 change tickets |
| Exception handling during migration | SOC 2 CC3.4 | D5 waivers with expiry |
| Configuration management | SOC 2 CC7.1 · ISO 27001 A.8.9 | F12 import → module-managed + drift-detected |
| Controlled software updates | SOC 2 CC8.1 | F11 pinned versions + pin audit |
Offboarding / Clean Exit (W4)
Brownfield clients own their state and resources ([CO]/[CA]), so exiting a SnowOps engagement never strands infrastructure. The clean-exit checklist:
- Module sources keep resolving — they're public git tags pinned in your configs; they don't depend on SnowOps' tenancy or accounts.
- Decide what to keep. Most clients keep the modules and operate them in-house. To fully remove a module-managed resource,
terraform destroythat stack (decide backup/DR retention deliberately first). - Revoke access. Remove the SnowOps operator's PIM eligibility, guest accounts, and any federated credentials tied to SnowOps.
- Retain evidence. Snapshots (
compliance/snapshots/), drift history, and restore-drill reports (compliance/restore-drills/) are yours — keep them for your audit record. - Rotate shared secrets (if any existed) and confirm no SnowOps-owned automation retains write access.
- Data return/destruction. Per your MSA/DPA, confirm SnowOps holds no copy of your configuration or evidence beyond the agreed retention.
A formal offboarding should produce a signed checklist. Align it with the DPA/MSA terms in your contract.
Verification at Handover
- B6 reports
READYin your tenant. - OPA gate (Path C) blocks a non-compliant test change in your CI (GitHub or ADO).
- An F12 import of one existing resource produces a zero-change plan.
- S1 drift detection runs against your existing state backend and opens a ticket on a manual change.
-
apps/module-registrypin audit fails on a deliberately unpinned reference. - (ADO clients) A C5 pipeline runs plan-on-PR and gated apply-on-merge.
Related Guides
- Quick-Win Quality Gates — the zero-cloud starting point.
- Brownfield In-Place Strategy — deep mechanics and worked examples.
- M2a Greenfield Baseline · M2b Compliance Monitoring — for the capabilities you're adopting.