Bring-your-own services — reuse existing Azure resources or provision new¶
CSA Loom is reuse-first: every backing Azure service can either be reused from a resource that already exists in any subscription/resource group, or provisioned new by the deploy. You control this per-service via parameters, environment variables, or (at runtime) the live-wiring scripts — so the same template fits any enterprise scenario without edits.
The model¶
For each service there are three states:
| State | How | Result |
|---|---|---|
| Reuse existing | set EXISTING_<SVC> (name) [+ _RG / _SUB] | the module is skipped; the Console wires to the existing resource |
| Provision new | leave EXISTING_<SVC> empty + the *Enabled flag true | a new resource is deployed and wired |
| Honest gate | leave both empty / flag false | the navigator renders a Fluent MessageBar naming the missing config — never a fake |
Reuse is resolved at three layers, all idempotent:
- Deploy time (fresh env) —
params/<boundary>.bicepparamreadsreadEnvironmentVariable('EXISTING_*', ''); when set,main.bicepskips the matching admin-plane module and wires the Console env to the existing resource. - Post-deploy RBAC —
scripts/csa-loom/grant-navigator-rbac.shgrants the Console UAMI the per-resource roles on whatever resource was resolved (provisioned or reused), discovering names when not passed. - Live env (already-running) —
scripts/csa-loom/patch-navigator-env.shhonors the sameEXISTING_*overrides, else discovers in the Loom RGs, and patches the liveloom-consolecontainer app (the live env can't re-runmain.bicep). This is what reconciles non-deterministic values (Databricks workspace URL, cross-region ADX cluster URI) that bicep can't synthesize.
Discover what already exists¶
# Read-only inventory across every subscription the signed-in principal sees,
# with ready-to-source EXISTING_* exports for each reusable resource.
bash scripts/csa-loom/discover-services.sh
Generate a BYO bicepparam (the wizard)¶
scripts/csa-loom/byo-wizard.sh is a bicepparam generator: it scans every subscription you can see, prompts reuse / new / honest-gate per service, and writes a ready params/<name>.generated.bicepparam (drop-in for az deployment sub create -p) plus a matching temp/<name>.byo-exports.sh with the canonical EXISTING_* exports for the post-deploy scripts.
# Interactive (default boundary commercial-full):
bash scripts/csa-loom/byo-wizard.sh --boundary commercial-full
# Non-interactive (CI / 1-button) — drive each choice via env:
BYO_NONINTERACTIVE=1 \
BYO_PURVIEW='reuse:dmlz-dev-purview-eastus:rg-dmlz-dev-governance-eastus:<sub>' \
BYO_APIM='reuse:dml-ai-east-aigateway:rg-dlz-aiml-stack-dev:<sub>' \
BYO_AISEARCH=new BYO_ADX=gate \
bash scripts/csa-loom/byo-wizard.sh --boundary commercial-full --non-interactive
# → az deployment sub create -f platform/fiab/bicep/main.bicep \
# -p platform/fiab/bicep/params/commercial-full.generated.bicepparam -l <region>
# → source temp/commercial-full.generated.byo-exports.sh && bash scripts/csa-loom/grant-navigator-rbac.sh
Each BYO_<KEY> = reuse:<name>[:<rg>[:<sub>]] | new | gate. Gov boundaries (gcc, gcc-high, il5) force fabricEnabled=false (no Microsoft Fabric).
Per-service reference¶
Every reuse pair captures name + RG + subscription (…_SUB) so cross-sub reuse (e.g. a shared-governance-sub Purview) is a first-class deploy-time input. The …_SUB value flows into the LOOM_<SVC>_SUB Console env var, which the matching navigator client reads (apim-client → LOOM_APIM_SUB, synapse-dev-client/synapse-pool-arm → LOOM_SYNAPSE_SUB, cosmos-account-client → LOOM_COSMOS_ACCOUNT_SUB, foundry-client → LOOM_FOUNDRY_SUB, foundry-cs-client → LOOM_AOAI_SUB, adf-client → LOOM_ADF_SUB), falling back to LOOM_SUBSCRIPTION_ID when empty. The matching management-plane RBAC role is granted on the reused resource in its subscription by grant-navigator-rbac.sh. Purview is the exception: its catalog data-plane is reached by account host ({account}.purview.azure.com) + a portal-granted role, so it is subscription-agnostic — EXISTING_PURVIEW_RG/ _SUB are captured for discovery only and there is no LOOM_PURVIEW_SUB/_RG runtime env wire.
| Service | Reuse env var(s) | *Enabled flag (new) | UAMI role granted on reuse |
|---|---|---|---|
| AI Search | EXISTING_AI_SEARCH_SERVICE (+_RG +_SUB) | aiSearchEnabled | Search Service + Index Data Contributor (+ enables AAD auth) |
| API Management | EXISTING_APIM (+_RG +_SUB) | apimEnabled | API Management Service Contributor |
| ADX / Kusto | EXISTING_KUSTO_CLUSTER (+_RG +_SUB) | adxEnabled | AllDatabasesAdmin principal assignment |
| AI Foundry / AOAI | EXISTING_AOAI (+_RG +_SUB) | aiFoundryEnabled | Cognitive Services Contributor |
| Purview | EXISTING_PURVIEW (+_RG +_SUB, discovery only) → sets existingPurviewAccount (overrides loomPurviewAccount) | purviewEnabled | Data Curator + Data Product Owner (Purview portal, data-plane) |
| Cosmos (navigator) | EXISTING_COSMOS_ACCOUNT (+_RG +_SUB) | DLZ-provisioned | DocumentDB Account Contributor + Built-in Data Contributor |
| Event Hubs | EXISTING_EVENTHUB_NAMESPACE (+_RG +_SUB) | DLZ-provisioned | Event Hubs Data Owner + Contributor |
| Synapse | EXISTING_SYNAPSE (+_RG +_SUB) | DLZ-provisioned | Synapse Admin (bootstrap) |
| Databricks | EXISTING_DATABRICKS (+_RG +_SUB) + EXISTING_DATABRICKS_HOSTNAME | DLZ-provisioned | SCIM workspace SP (bootstrap) |
| Data Factory | EXISTING_ADF (+_RG +_SUB) → LOOM_ADF_NAME/_RG/_SUB | DLZ-provisioned | Data Factory Contributor |
| Azure SQL | bound per-item in the editor (any server) | — | per-server Entra admin |
| Microsoft Fabric | fabricEnabled (default false — Azure-native, no Fabric dependency) | — | n/a (opt-in only) |
The four admin-plane services (AI Search, APIM, ADX, Foundry) gate their provisioning module on
empty(existing<Svc>), so naming an existing one never deploys a duplicate. DLZ services always provision a per-domain instance; to reuse instead, set theEXISTING_*override (the bicep wires the Console env to the reused resource; the RBAC + env-patch scripts grant/wire it).…_SUBvalues are pure string pass-throughs (NOT Bicepexistingcross-sub references); post-deploy RBAC targets the resolved subscription.
Examples¶
Reuse an existing AI Search service in another resource group, provision everything else new:
export EXISTING_AI_SEARCH_SERVICE=my-shared-search
export EXISTING_AI_SEARCH_RG=rg-shared-ai
export EXISTING_AI_SEARCH_SUB=<sub-id> # only when cross-subscription
az deployment sub create -f platform/fiab/bicep/main.bicep \
-p platform/fiab/bicep/params/commercial-full.bicepparam
# then, post-deploy:
EXISTING_AI_SEARCH_SERVICE=my-shared-search EXISTING_AI_SEARCH_RG=rg-shared-ai \
bash scripts/csa-loom/grant-navigator-rbac.sh
Reuse the tenant Purview (only one Enterprise Purview is allowed per tenant, so this is almost always a reuse — often cross-subscription):
Point the live console at an existing APIM without redeploying bicep: