Repository navigation
Add Deploy-to-Azure Bicep template - #1477
Josh Duffney (duffney) wants to merge 9 commits into
Conversation
Provisions AKS (OIDC issuer, workload identity, native Key Vault Secrets Provider add-on, Cilium network policy), a VNet with private endpoints for Key Vault/Cosmos DB/Redis/Storage, Cosmos DB (MongoDB API only), Azure Cache for Redis (Entra ID auth preferred), a blob+queue storage account, and an ACR with AcrPull granted to the AKS kubelet identity. Explicitly excludes azd, FluxCD/GitOps, ASO, ESO, KEDA-via-Flux, CorpNet IP allowlisting, GitHub App/Entra bootstrap scripts, and Windows node pools. Does not touch the existing infra/ (azd dev CosmosDB) or root azure.yaml. Includes: - main.bicep orchestrator + 7 modules (networking, aks, keyvault, cosmosdb, redis, storage, acr), using Azure Verified Modules where available - main.parameters.json with sensible defaults - Compiled azuredeploy.json for the Deploy-to-Azure button, plus a CI workflow (.github/workflows/deploy-azure-drift.yml) that fails if it drifts from main.bicep - createUiDefinition.json with a Basics/Advanced portal UI flow - metadata.json for the template gallery - deploy/azure/README.md documenting prerequisites, the CLI deploy path, and what this does NOT automate (the Helm app install, BYO secrets) - Deploy-to-Azure button + link added to the root README All Bicep files compile cleanly via 'az bicep build' with zero errors and zero warnings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
Upgrade avm/res/container-service/managed-cluster from 0.9.0 to 0.14.0. The pinned 0.9.0/0.13.0 module versions generated a managedClusters resource at API version 2024-09-02-preview, which Azure has since retired (not present in the supported api-versions list), causing deployments to fail with: NoRegisteredProviderFound: No registered resource provider found for location 'westus3' and API version '2024-09-02-preview' for type 'managedClusters'. 0.14.0 generates the resource at 2025-10-01 (current, non-preview). Updated param usage to match this version's schema: enablePrivateCluster moved under apiServerAccessProfile, and enableWorkloadIdentity moved under securityProfile.workloadIdentity.enabled. All module outputs (resourceId, name, oidcIssuerUrl, kubeletIdentityObjectId, systemAssignedMIPrincipalId) are unchanged between versions. Verified: deploy/azure/modules/aks.bicep and deploy/azure/main.bicep both compile with zero errors/warnings via 'az bicep build'; confirmed 2024-09-02-preview no longer appears anywhere in the compiled azuredeploy.json. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
…t kind The AVM database-account module (br/public:avm/res/document-db/database-account:0.10.0) derives the account's 'kind' property from whether mongodbDatabases is non-empty, independent of capabilitiesToAdd. Passing an empty mongodbDatabases array left the account at kind=GlobalDocumentDB (SQL API) even with EnableMongo set, which then rejected the MongoDB-groupId private endpoint with: BadRequest: Call to Microsoft.DocumentDB/databaseAccounts failed. Error message: GroupId MongoDB is not supported Fixed by provisioning a default database (name configurable via the new 'databaseName' param, default 'scope') so the module correctly resolves kind=MongoDB. The child mongodb-database module already skips setting throughput when EnableServerless is present, so this has no cost impact under the default serverless configuration. Verified: deploy/azure/modules/cosmosdb.bicep and the full deploy/azure/main.bicep both compile with zero errors/warnings via 'az bicep build'; confirmed the compiled azuredeploy.json's kind expression now resolves to 'MongoDB' when mongodbDatabases is populated. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
Wassim Chegham (manekinekko)
left a comment
There was a problem hiding this comment.
Thanks for the PR, Josh! The module separation, private-endpoint wiring, and secure connection-string outputs are a good foundation. I've left three initial comments on resource naming, the portal wizard, and the post-deployment instructions.
|
|
||
| var vnetName = 'vnet-${environmentName}-${resourceToken}' | ||
| var aksName = 'aks-${environmentName}-${resourceToken}' | ||
| var keyVaultName = take('kv-${environmentName}-${resourceToken}', 24) |
There was a problem hiding this comment.
[P2] Preserve a valid name and the uniqueness suffix when truncating.
A 20-character environmentName is accepted by the template, but this expression produces a Key Vault name ending in a hyphen. For example, abcdefghijklmnopqrst becomes kv-abcdefghijklmnopqrst-, which Azure rejects. With a 19-character name, only one character of the uniqueness suffix survives.
Could we truncate the environment portion before appending the suffix, rather than truncating the completed resource name? The Storage account naming expression has the same suffix-truncation problem and should preserve its uniqueness suffix too.
| } | ||
| ] | ||
| }, | ||
| "outputs": { |
There was a problem hiding this comment.
[P2] Put the output mappings inside parameters and connect this wizard to the buttons.
outputs is currently at the document root, but the CreateUiDefinition schema requires parameters.outputs to bind the controls to the ARM template parameters.
Also, both Deploy to Azure buttons reference only azuredeploy.json; neither references this UI definition, so users won't see the custom wizard or its validation. Could we move these mappings into parameters and wire the UI definition into both deployment links?
|
|
||
| - **Installing Scope itself.** This template provisions infrastructure only. The | ||
| Scope application (API, workers, Judge, Portal, Token Manager) is installed | ||
| separately via Helm charts against the AKS cluster this template creates. See the |
There was a problem hiding this comment.
[P2] Point to an available application-install procedure.
This directs users to install Scope via Helm charts and sends them to the documentation site for instructions, but I couldn't find a Helm chart or Helm installation instructions. Is this part of a separate PR?
…rite to race cache creation The primaryKey output called listKeys() against a resourceId() built purely from the redisName parameter, with no symbolic reference to the redis AVM module. Bicep only infers an implicit dependsOn from symbolic-name references, so this output had no dependency edge on the cache's actual provisioning - ARM could evaluate listKeys() before the long-running Redis create finished, which explains reports of the redis-primary-key secret being missing from Key Vault after otherwise-successful deployments (the cache itself provisions fine; only the secret write silently failed). Fixed by declaring an existing resource reference for the cache (a compile-time-known ID, valid as a listKeys() target) with an explicit dependsOn: [redis] to force correct ordering. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
…e connectivity The private DNS zone for the Cosmos DB MongoDB API private endpoint was named 'privatelink.mongo.cosmos.azure.net' - wrong TLD, likely a copy/paste mix-up with the Key Vault zone (privatelink.vaultcore.azure.net). The canonical zone name, per the public DNS CNAME chain (*.mongo.cosmos.azure.com -> *.privatelink.mongo.cosmos.azure.com), is 'privatelink.mongo.cosmos.azure.com' (.com). With the wrong zone name, pods resolved the Cosmos Mongo hostname to its public IP (no matching records in the misnamed zone), and Cosmos's firewall rejected the connection since publicNetworkAccess is Disabled - MongoServerSelectionError connecting to the public IP on port 10255. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
…ad identity The shared workload identity only had Key Vault Secrets User (read-only), which is correct for the Helm chart's Secrets Store CSI mounts but blocks the Token Manager service (apps/token-manager), which writes BYO credentials (GitHub/Anthropic API keys, etc.) into the vault at runtime via SecretClient.setSecret() using this same shared identity. Read-only access 403s that write path, leaving the portal's built-in credential registration UI (POST/DELETE /api/v1/keys) non-functional out of the box on a freshly provisioned environment. Key Vault Secrets Officer is a superset of Secrets User (adds set/delete alongside get/list on secrets, still scoped to secrets only - no key/cert or permission-management access), so this replaces rather than stacks alongside the previous role assignment. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
… Data Owner Storage Blob Data Contributor's data actions do not include Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags/write, so the app's practice of setting Blob Index Tags (requestId/runId/iteration) via the x-ms-tags header on per-iteration workspace snapshot uploads 403s with AuthorizationPermissionMismatch under Contributor alone. 100% reproducible, confirmed via raw PUT with/without x-ms-tags using a storage-scoped AAD token exchanged from the workload identity's federated token. Storage Blob Data Owner includes the tags/write data action alongside everything Contributor already grants, so this keeps tagged snapshot uploads working without the app needing to drop tags. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
…ed Redis
Classic Azure Cache for Redis (Microsoft.Cache/redis) is being retired for
new deployments, causing DeploymentFailed errors on the redis sub-deployment
("Azure Cache for Redis is retiring, create Azure Managed Redis instance
instead").
Rewrote redis.bicep to use the AVM module avm/res/cache/redis-enterprise:0.5.0
(Microsoft.Cache/redisEnterprise), targeting the "Balanced" Azure Managed
Redis SKU tier:
- Replaced the Basic/Standard/Premium skuName + C/P skuFamily + integer
skuCapacity parameter trio with a single skuName enum of AMR SKU strings
(Balanced_B0/B1/B3/B5/B10; capacity is now encoded in the SKU name itself).
Default is Balanced_B1.
- Private endpoint service/groupId changed from redisCache to redisEnterprise;
private DNS zone changed from privatelink.redis.cache.windows.net to
privatelink.redisenterprise.cache.azure.net.
- Entra ID (Azure AD) data-plane auth is now granted via an access policy
assignment on the database submodule (accessPolicyAssignments), replacing
the classic module's redisConfiguration 'aad-enabled' flag. The shared
workload identity's principal ID is threaded through from main.bicep (same
pattern already used for storage.bicep).
- Access-key auth (accessKeysAuthentication: 'Enabled') is kept as a
compatibility fallback; the primary access key is still surfaced as a
@secure() output and written to the redis-primary-key Key Vault secret, now
sourced directly from the AVM module's own output instead of a manual
listKeys() call — the AMR database submodule's output is correctly ordered,
so the existing-resource + explicit dependsOn workaround from the prior
dependsOn fix is no longer needed.
- Updated main.bicep (params, redisName max length 63->60 per
Microsoft.Cache/redisEnterprise naming rules, module wiring, outputs),
main.parameters.json, createUiDefinition.json (single SKU dropdown, capacity
slider removed), metadata.json, and README.md to match.
Verified with `az bicep build` on the module and full orchestrator (clean,
zero errors/warnings), `az bicep lint`, and a rebuild-and-diff drift check
against the committed azuredeploy.json (no drift).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
Live deployment hit:
Unable to process template language expressions for resource
'.../redis-apa-0'... 'The language expression property name doesnt
exist, available properties are userObjectId.' (Code: InvalidTemplate)
Root cause: the AVM redis-enterprise module's nested access-policy-assignment
deployment reads parameters('accessPolicyAssignments')[i].name directly
(not via tryGet), even though name is documented as optional/nullable on
accessPolicyAssignmentType. ARM strips omitted optional properties from the
JSON object entirely (there is no null placeholder), so direct property
access on a missing key throws InvalidTemplate. Our accessPolicyAssignments
array item only set userObjectId, omitting name as the (incorrect) docs
imply is fine.
Fix: explicitly set name: 'workload-identity-access' on the access policy
assignment entry in redis.bicep's database block, working around the
module's property-access bug.
Verified with az bicep build (module + full orchestrator, clean), az bicep
lint (clean), and a rebuild-and-diff drift check against the committed
azuredeploy.json (no drift) - confirmed the compiled ARM template now
includes the explicit name property on the access policy assignment.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2250d38f-729e-4dd7-b6e1-41b525e44a72
Summary
Adds a "Deploy to Azure" portal button that provisions the Azure infrastructure needed to run Scope: AKS, networking, Key Vault, Cosmos DB, Redis, Storage, and a container registry. This gives anyone a one-click way to stand up a ready-to-use environment, as a sanitized, minimal, resource-group-scoped Bicep template under a new
deploy/azure/folder.The existing
infra/folder (azd dev-CosmosDB setup) and rootazure.yamlare untouched; this is a separate, independent deployment path intended for new environments, not local/dev workflows.Approach
deploy/azure/main.biceporchestrates 7 modules underdeploy/azure/modules/(networking,aks,keyvault,cosmosdb,redis,storage,acr), built against a shared parameter/output contract and using Azure Verified Modules (AVM) where available:main.bicepis compiled todeploy/azure/azuredeploy.json(the file the Deploy-to-Azure button URL points at), alongsidemain.parameters.json,createUiDefinition.json(Basics + Advanced portal UI flow), andmetadata.jsonfor the template gallery. A new CI workflow (.github/workflows/deploy-azure-drift.yml) rebuildsmain.bicepand fails ifazuredeploy.jsonhas drifted, plus lints all module files.deploy/azure/README.mddocuments prerequisites, the CLI deploy path, and explicitly what this does not automate: the Helm-based app install itself, and BYO secrets the user must populate in Key Vault before/after deploying. The rootREADME.mdlinks to it with the Deploy-to-Azure button.Notable assumptions (documented in
deploy/azure/README.md)az aks get-credentials+kubectl/helmwork without a jumpbox; only data-plane services (Key Vault, Cosmos, Redis, Storage) are private.Testing
az bicep buildonmain.bicepand each of the 7 modules individually: all compile with zero errors and zero warnings.azuredeploy.jsonmatches a fresh rebuild ofmain.bicepexactly (no drift), matching the new CI check's logic.createUiDefinition.jsonandmain.parameters.jsonas syntactically valid JSON.az deployment group validate/what-if against a real Azure subscription (no Azure credentials available in this environment).Documentation and compatibility
deploy/azure/README.mdadded; rootREADME.mdupdated with a "Deploy to Azure" section and button.infra//azd setup.Checklist
up()/down()and keep it CosmosDB-compatible. (N/A)NOTICE/NOTICE-REVIEW.txtwithpnpm noticeas needed. (N/A - no dependency changes)