Add Azure resource scope support - #17988
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17988Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17988" |
There was a problem hiding this comment.
Pull request overview
Adds Azure resource scope support (resource group + subscription, subscription, and current-tenant) to Aspire’s Azure Bicep publishing/provisioning pipeline so existing resources and custom Bicep modules can be targeted outside the default deployment resource group.
Changes:
- Extends the manifest schema and manifest/Bicep generation to encode and emit module scopes for resource group, subscription, and tenant deployments.
- Updates Azure provisioning to resolve cross-subscription resource groups, select the correct ARM deployment collection (RG vs subscription vs tenant), and produce scope-correct portal URLs.
- Adds/updates unit tests and Verify snapshots covering scoped existing resources and scoped Bicep templates.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.json | New snapshot for role-assignment module manifest including RG+subscription scope. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.bicep | New snapshot for role-assignment module Bicep output. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.json | New snapshot for Service Bus module manifest including RG+subscription scope. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.bicep | New snapshot for Service Bus module Bicep output. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsBicepTemplateWithTenantScopeInPublishMode.verified.json | New snapshot for tenant-scoped module manifest. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsBicepTemplateWithTenantScopeInPublishMode.verified.bicep | New snapshot for tenant-scoped module Bicep output. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsBicepTemplateWithSubscriptionScopeInPublishMode.verified.json | New snapshot for subscription-scoped module manifest. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsBicepTemplateWithSubscriptionScopeInPublishMode.verified.bicep | New snapshot for subscription-scoped module Bicep output. |
| tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.AzurePublishingContext_WritesScopedModuleExpressions.verified.bicep | New snapshot validating generated scope: expressions for RG/subscription/tenant modules in the main Bicep. |
| tests/Aspire.Hosting.Azure.Tests/ProvisioningTestHelpers.cs | Enhances ARM test fakes to model subscription/tenant deployment collections and scoped RG lookup. |
| tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceTests.cs | Adds publish-mode coverage for scoped existing Service Bus and scoped custom Bicep templates. |
| tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceExtensionsTests.cs | Adds unit tests for new AsExistingIn* APIs across run/publish modes. |
| tests/Aspire.Hosting.Azure.Tests/BicepUtilitiesTests.cs | Adds tests for serializing/removing scoped values (RG+subscription, subscription-only, tenant, stale removal). |
| tests/Aspire.Hosting.Azure.Tests/AzureEnvironmentResourceTests.cs | Adds test verifying AzurePublishingContext emits correct module scope expressions. |
| tests/Aspire.Hosting.Azure.Tests/AzureBicepProvisionerTests.cs | Adds coverage ensuring provisioner selects correct ARM deployment collection per scope and sets location. |
| src/Schema/aspire-8.0.json | Extends manifest schema to allow scope.subscription and scope.tenant for azure.bicep.v1. |
| src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs | Implements scoped subscription resolution, scope-aware deployment collection selection, and scope-correct deployment URLs/logging. |
| src/Aspire.Hosting.Azure/Provisioning/Internal/IProvisioningServices.cs | Extends provisioning abstractions: subscription lookup by id and tenant deployment collection support. |
| src/Aspire.Hosting.Azure/Provisioning/Internal/DefaultArmClientProvider.cs | Implements new provisioning abstraction methods using Azure SDK resources. |
| src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs | Extends scope serialization to include subscription and tenant and clears stale scope keys. |
| src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs | Adds public & polyglot-facing APIs for existing resources in RG+subscription, subscription scope, and tenant scope. |
| src/Aspire.Hosting.Azure/ExistingAzureResourceAnnotation.cs | Extends annotation to carry subscription and current-tenant scoping information. |
| src/Aspire.Hosting.Azure/AzureResourcePreparer.cs | Applies full existing-resource scope (not just RG) to role-assignment resources. |
| src/Aspire.Hosting.Azure/AzurePublishingContext.cs | Emits correct Bicep module scope: expressions for RG/subscription/tenant. |
| src/Aspire.Hosting.Azure/AzureProvisioningResource.cs | Applies the expanded scope model into provisioning expressions and scope equality checks. |
| src/Aspire.Hosting.Azure/AzureBicepResourceScope.cs | Extends scope model to support subscription-only and current-tenant scopes and converts from existing-resource annotation. |
| src/Aspire.Hosting.Azure/AzureBicepResource.cs | Writes expanded scope object (RG/subscription/tenant) into azure.bicep.v1 manifest entries. |
Copilot's findings
- Files reviewed: 27/27 changed files
- Comments generated: 2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only set ARM deployment location for subscription and tenant scoped Bicep deployments. Resource group deployments reject the location property, which blocked existing-resource and ACA live deployment validation. Also clarify that tenant-scoped existing-resource APIs target the current tenant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Report no resource group for subscription and tenant scoped Azure deployments, and clarify the manifest schema only supports the current tenant scope. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
|
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I would like to see an end to end deployment test for this. For cross subscription we might be able to fake it by just using that overload but deploying to the same subscription. Before I click approve on this I want to see those tests and those deployment test results. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Added Deployment result from local live run against subscription |
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
|
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/deployment-test |
|
🚀 Deployment tests starting on PR #17988... This will deploy to real Azure infrastructure. Results will be posted here when complete. |
|
❌ Deployment E2E Tests failed — 37 passed, 5 failed, 0 cancelled View test results and recordings
|
PR Testing ReportPR Information
Artifact Version Verification
Changes Analyzed
Test Scenarios ExecutedScenario 1: C# all-scope publish sampleObjective: Verify C# AppHost APIs generate valid Bicep for default resource group, explicit resource group + subscription, subscription scope, tenant scope, and existing Service Bus in an explicit resource group + subscription. Steps: Created Evidence:
Observations: Bicep compiled successfully. Custom scoped Bicep templates need to declare Scenario 2: TypeScript existing resource scope sampleObjective: Verify generated TypeScript APIs compile and publish for existing Azure resources scoped to an explicit resource group + subscription. Steps: Created Evidence:
Observations: TypeScript generated existing-resource scope helpers are usable. Direct custom Scenario 3: Manual live Azure deploy sampleObjective: Verify a realistic Azure deployment using an existing Service Bus namespace in a separate resource group, scoped with resource group + subscription, without invoking the E2E test project. Steps: Created Evidence:
Observations: Deploy succeeded with 12/12 steps and queue verification returned Scenario 4: Focused automated testsObjective: Validate changed Azure hosting, TypeScript codegen, and schema behavior with targeted MTP filters. Results:
Evidence: Logs are under Summary
Overall ResultPR VERIFIED with one cleanup observation: scoped child resources deployed into a pre-existing resource group are not removed by |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR Testing ReportPR Information
Artifact Version Verification
Changes Analyzed
Test Scenarios ExecutedScenario 1: Shared ACR in another resource group with ACA deployed to app resource groupObjective: Validate the customer scenario from the associated issues: local/dev or production app deployment into one resource group while using a shared organization Azure Container Registry from another resource group in the same subscription. Steps:
AppHost shape: var sharedRegistry = builder.AddAzureContainerRegistry("sharedacr")
.PublishAsExistingInResourceGroup("<acr-name>", "<shared-resource-group>", "<subscription-id>");
builder.AddAzureContainerAppEnvironment("env")
.WithAzureContainerRegistry(sharedRegistry);Observed:
module sharedacr 'sharedacr/sharedacr.bicep' = {
name: 'sharedacr'
scope: resourceGroup('<subscription-id>', '<shared-resource-group>')
}
resource sharedacr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
name: '<acr-name>'
scope: resourceGroup('<subscription-id>', '<shared-resource-group>')
}
resource sharedacr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: sharedacr
}Impact: Cleanup:
Evidence:
Summary
Overall ResultISSUE FOUND |
Adds two focused unit tests to verify that existing Azure resources with cross-subscription and tenant scope annotations survive the DCP run-mode pipeline (ExecuteBeforeStartHooksAsync) without blowing up: - SupportsExistingServiceBusWithResourceGroupAndSubscriptionInRunMode - SupportsExistingServiceBusWithTenantScopeInRunMode Each test verifies that the scope annotation is preserved correctly and that no deployment target is added in run mode (the annotation is carried but provisioning is deferred to the BicepProvisioner at local F5 time, not the DCP startup pipeline). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mitch Denny (mitchdenny)
left a comment
There was a problem hiding this comment.
Code review complete. The architecture is sound — scope metadata flows cleanly from the AppHost annotation through Bicep generation and ARM deployment routing. Test coverage is solid across all four scope types at the unit level, with a passing live deployment E2E test for the primary cross-subscription/RG scenario.
I've added two additional run-mode unit tests to cover the DCP path for cross-subscription and tenant scope:
SupportsExistingServiceBusWithResourceGroupAndSubscriptionInRunModeSupportsExistingServiceBusWithTenantScopeInRunMode
These verify that the scope annotations survive ExecuteBeforeStartHooksAsync without errors and carry through correctly in run mode.
One known limitation documented by the author: cross-scope resource references within a single Bicep module (e.g. ACA + shared ACR in different RGs) fail with Bicep BCP139. This is a Bicep module architecture issue outside the scope of this PR and is tracked in the PR testing report.
|
❓ CLI E2E Tests unknown — 115 passed, 0 failed, 2 unknown (commit View all recordings
📹 Recordings uploaded automatically from CI run #27413259906 |
Description
Revives and updates the stale Azure scope support from #13121 so Aspire users can target Azure resources outside the default deployment resource group.
Users can now model existing Azure resources and custom Azure Bicep deployments at these scopes:
resourceGroup(resourceGroupName)resourceGroup(subscriptionId, resourceGroupName)subscription(subscriptionId)tenant()for the current tenantTenant scope intentionally targets only the current tenant. Bicep supports
tenant()but does not support selecting an arbitrary tenant withtenant(tenantId), so manifests write"tenant": "current"and the schema now enforces that value.Examples
C# AppHost existing-resource APIs
Resource-group-scoped services should use the resource-group APIs.
RunAsExisting*applies only in run mode,PublishAsExisting*applies only in publish/deploy mode, andAsExisting*applies in both modes.Subscription- and tenant-scoped resources use the matching existing-resource APIs:
C# AppHost custom Bicep deployment scopes
Custom Bicep deployments can set
AzureBicepResource.Scopedirectly.TypeScript AppHost setup
TypeScript AppHosts use empty package versions in
aspire.config.json, matching the other playground TypeScript samples:{ "appHost": { "path": "apphost.mts", "language": "typescript/nodejs" }, "packages": { "Aspire.Hosting.Azure": "", "Aspire.Hosting.Azure.ServiceBus": "" } }TypeScript AppHost existing-resource APIs
The same existing-resource scope APIs are exported to TypeScript AppHosts.
Generated manifest and Bicep behavior
Scoped Azure Bicep resources now emit
azure.bicep.v1manifest entries with scope metadata:{ "type": "azure.bicep.v1", "path": "subscriptionScoped.module.bicep", "scope": { "subscription": "00000000-0000-0000-0000-000000000000" } }{ "type": "azure.bicep.v1", "path": "tenantScoped.module.bicep", "scope": { "tenant": "current" } }Generated Bicep module scopes use the matching ARM scope function:
Implementation details
AzureBicepResourceScopesupport for resource-group, subscription, and current-tenant scopes.azure.resource.groupfor subscription- and tenant-scoped deployments.tenant()but nottenant(tenantId).Known end-to-end validation finding
Live PR-build validation of the associated shared-resource scenario found that #17988 is necessary but not sufficient for Azure Container Apps using a shared Azure Container Registry from another resource group.
The tested AppHost shape was:
#17988 correctly models and publishes the shared ACR scope as
resourceGroup(subscriptionId, sharedResourceGroup), and deployment can build, log in to, and push images to the shared ACR. However, liveaspire deployfails when the ACA environment module compiles because the generatedenv/env.bicepcontains a cross-scope existing ACR andAcrPullrole assignment inside a resource-group-scoped module, which Bicep rejects withBCP139.The companion ACR/ACA composition fix is #18118 ("Fix BCP139 cross-resource-group ACR AcrPull role in compute environments"). That change moves the
AcrPullrole assignment into a separately scoped module and keeps the ACA environment module resource-group-local. Both #17988 and #18118 are needed for the shared ACR in another resource group + ACA in this deployment resource group end-to-end scenario.Validation
dotnet test --project tests/Aspire.Hosting.Azure.Tests/Aspire.Hosting.Azure.Tests.csproj --no-launch-profile -- --filter-class "*.AzureBicepProvisionerTests" --filter-class "*.ExistingAzureResourceExtensionsTests" --filter-class "*.ExistingAzureResourceTests" --filter-class "*.BicepUtilitiesTests" --filter-class "*.AzureEnvironmentResourceTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Hosting.Azure.Tests/Aspire.Hosting.Azure.Tests.csproj --no-launch-profile -- --filter-class "*.BicepUtilitiesTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj --no-launch-profile -- --filter-class "*.SchemaTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests.csproj --no-launch-profile -- --filter-method "*.Scanner_AzureExistingResourceScopes_ExposeTypeScriptCapabilities" --filter-method "*.GenerateDistributedApplication_WithAzureExistingResourceScopes_EmitsTypeScriptMethods" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"./build.sh --build /p:SkipNativeBuild=trueTypeScript AppHost validation with empty package versions in
aspire.config.json:aspire restorenpx tsc --noEmit --project tsconfig.apphost.jsonaspire publish -o artifacts --non-interactiveaz bicep build --file artifacts/main.bicep --stdoutPR dogfood CLI validation:
aspireversion13.5.0-pr.17988.gda775ed1from the PR build.resourceGroup(subscriptionId, resourceGroupName),subscription(subscriptionId), andtenant()module scopes;az bicep build --file artifacts/main.bicep --stdoutsucceeded.Live Azure deploy validation in
westus2:location; Azure rejected ACA and existing-storage modules withInvalidDeployment. Fixed by only settingArmDeploymentContent.Locationfor subscription and tenant deployments.provision-subscope,provision-scopedstorage,provision-aca-acr,provision-aca, andprovision-hello-containerappsucceeded.helloreachedSucceeded/Runningand servedhttps://hello.proudwave-e68e3b55.westus2.azurecontainerapps.io.scoped-containerwas created under storage accountaspscope0607163331inrg-aspire-scope-live-existing-0607163331.rg-aspire-scope-live-sub-0607163331with the expected test tag.Microsoft.Resources/deployments/write), confirming the generated deployment path reaches the expected authorization boundary.API clarity pass:
RunAsExisting/PublishAsExisting/AsExistingnaming pattern.current-tenant-scoped, matching Bicep'stenant()behavior.Live PR-build validation of the shared ACR + ACA customer scenario:
aspireversion13.5.0-pr.17988.g6956065ffrom the PR build.PublishAsExistingInResourceGroup(...).WithAzureContainerRegistry(...).aspire publishemitted the expectedresourceGroup(subscriptionId, sharedResourceGroup)scope.aspire deploy --clear-cachefailed with BicepBCP139inenv/env.bicepbecause the ACA environment module still contained a cross-scope existing ACR andAcrPullrole assignment. Posted the detailed report in Add Azure resource scope support #17988 (comment). This requires Fix BCP139 cross-resource-group ACR AcrPull role in compute environments #18118.Replaces #13121.
Fixes #5901
Part of #7514. The shared ACR + ACA end-to-end scenario from #7514 also requires companion PR #18118.
Checklist
<remarks />and<code />elements on your triple slash comments?