A declarative, reviewable definition of an entire Microsoft Purview environment for a single tenant — both the Azure resource and the governance catalog — deployed automatically by GitHub Actions. Everything is a pull request.
What is this?
Microsoft Purview does not ship a single "Purview-as-Code" product. This repository fills that gap: it expresses your Purview configuration as code so every change is diff-able, reviewable in a pull request, and applied by an idempotent, auditable pipeline — never by hand-clicking in a portal.
Human-readable YAML and Bicep describe the desired state. The repo, not the portal, is the source of truth.
Every apply script reads current state, diffs it, then upserts. Re-running changes nothing that already matches.
GitHub Actions authenticates to Azure with short-lived OIDC tokens — no long-lived secrets stored anywhere.
A pull request is the change-review process. Control-plane and data-plane changes never mix in one PR.
A Squad of workspace-scoped Copilot agents intakes ideas, drafts issues, implements, and gates on owner approval.
Every resource, cmdlet, and API call cites a current Microsoft Learn page — model recall alone is never enough.
The core idea
A complete Purview IaC repo must manage two independent planes, each with its own API surface and authentication model. A change almost always lives in exactly one of them.
The Microsoft.Purview/accounts resource, networking,
managed identity, and SKU — defined as Bicep.
infra/deploy-infra.ymlSensitivity labels, DLP, data lifecycle & records, insider risk, communication compliance, DSPM, plus Data Map collections, glossary, classifications, sources & scans.
data-plane/ + scripts/deploy-<solution>.yml (5 surfaces); the rest apply locally
Why the split? The account name is the DNS hostname of the data plane
(<name>.purview.azure.com) and is treated as a fixed
input. Collections, glossary, classifications, sources, and scans are invisible to
ARM/Bicep — they live behind the Atlas v2 and Purview REST APIs, which is why the repo
ships PowerShell reconcilers driven by YAML.
How it works
A single CI/CD flow validates every change, then routes merges to the right plane by path. OIDC federated credentials give GitHub Actions short-lived tokens for both Azure Resource Manager and the Purview data plane — with no stored secret.
You open a PR that changes one plane. Reviewers see the full diff of desired state.
validate.yml runs Bicep lint, yamllint, and PSScriptAnalyzer.
Path filters decide which deploy workflow fires next.
infra/** → ARM. data-plane/** or scripts/** → Purview REST APIs.
The deploy-infra workflow runs
az deployment group create against
infra/main.bicep, provisioning the
Microsoft.Purview/accounts resource and its dependencies.
Each per-solution deploy-<solution>.yml workflow authenticates via OIDC, then
runs the one Deploy-*.ps1 reconciler it owns, rendering that YAML manifest into
idempotent PUT/PATCH calls against the Purview data-plane APIs. Deletes are opt-in.
Five surfaces have such a workflow today; the rest have no automated apply path yet and
are applied by running their reconciler locally.
Use it yourself
This is a tenant-neutral template — every tenant-specific value is a documented placeholder
(contoso, contoso.onmicrosoft.com).
It will not deploy against a real tenant until you make it yours. The
tenant onboarding guide has the exact commands.
Click Use this template on GitHub for a clean-history spin-off that can never push back, or git clone for a local workspace.
Run the @operator-kickoff agent to pick a local workspace or spin-off repo and install the no-push-back guard.
Run the @operator-tenant agent — it interviews you and writes your values into infra/parameters/lab.yaml and the identity-boundary statements.
Inspect what the agent produced, then commit it on a branch.
Create the Microsoft Entra app + OIDC federated credential, set the GitHub Environment secrets, and set the OWNER_APPROVAL_LOGIN variable.
Run az bicep build and the Pester suite, then trigger deploy-infra and the per-solution deploy-<solution> workflows.
Never commit real identifiers or secrets. Real tenant IDs, subscription
IDs, object IDs, and credentials belong in GitHub Environment secrets and are referenced
by name. In source, always use the zero-GUID placeholder
00000000-0000-0000-0000-000000000000 and Microsoft's
fictitious names (contoso,
fabrikam, adatum).
Find your way around
Two planes, one folder each, plus the automation and docs that hold them together. The
plane-to-workflow path filters depend on this separation — never put data-plane YAML under
infra/, and never put Bicep under data-plane/.
. ├── .github/ │ ├── workflows/ # validate + deploy-infra + per-solution deploy-* │ ├── instructions/ # path-scoped Copilot rules (Bicep, PowerShell, YAML…) │ ├── prompts/ # reusable /-invoked task templates │ └── agents/ # workspace-scoped Squad agents (personas) ├── infra/ # CONTROL PLANE — Bicep: Purview account + dependencies │ ├── main.bicep │ ├── main.bicepparam │ ├── modules/ │ └── parameters/ ├── data-plane/ # DATA PLANE — desired-state governance content │ ├── information-protection/ # sensitivity labels + policies │ ├── classifications/ # sensitive information type catalog │ ├── dlp/ data-lifecycle/ records/ irm/ dspm/ │ ├── collections/ glossary/ data-sources/ scans/ │ └── purview-role-groups/ administrative-units/ ├── scripts/ # PowerShell 7+ reconcilers, smoke tests, provisioning │ ├── Connect-Purview.ps1 │ ├── Deploy-*.ps1 # idempotent reconciler per data-plane solution │ └── modules/ ├── docs/ # kickoff, onboarding, architecture, ADRs, runbooks ├── tests/ # Pester 5.x unit tests (no live tenant) ├── AGENTS.md └── README.md
Non-negotiables
Every apply script GETs current state, diffs, then PUT/PATCHes.
Human-reviewable manifests; a pull request equals a change review.
OIDC federated credentials from GitHub to Entra — no long-lived secrets.
Control-plane PRs never change catalog content, and vice versa.
Grounded in Microsoft Learn
Microsoft Learn is the authoritative source for every recommendation in this repo. The core pages behind the two-plane model: