From b520fd1abde391badbbf9c1e2842051166b77a6d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 22 Nov 2025 14:55:17 +0000
Subject: [PATCH 1/6] Initial plan
From f3eb370d330809ab1533f218d0b10a5fa0dbb315 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 22 Nov 2025 15:06:59 +0000
Subject: [PATCH 2/6] Add subscription and tenant scope support for Azure
resources - Core implementation
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../AzureBicepResource.cs | 24 ++
.../AzureBicepResourceScope.cs | 54 +++-
.../AzureProvisioningResource.cs | 88 +++++-
.../AzureResourcePreparer.cs | 50 +++-
.../ExistingAzureResourceAnnotation.cs | 39 +++
.../ExistingAzureResourceExtensions.cs | 280 ++++++++++++++++++
.../Provisioning/BicepUtilities.cs | 42 ++-
7 files changed, 558 insertions(+), 19 deletions(-)
diff --git a/src/Aspire.Hosting.Azure/AzureBicepResource.cs b/src/Aspire.Hosting.Azure/AzureBicepResource.cs
index 58b60bf1f17..d6c92269fe9 100644
--- a/src/Aspire.Hosting.Azure/AzureBicepResource.cs
+++ b/src/Aspire.Hosting.Azure/AzureBicepResource.cs
@@ -277,6 +277,30 @@ public virtual void WriteToManifest(ManifestPublishingContext context)
null => ""
};
context.Writer.WriteString("resourceGroup", resourceGroup);
+
+ // Only write subscription if it has a value to maintain backward compatibility
+ if (Scope.Subscription is not null)
+ {
+ var subscription = Scope.Subscription switch
+ {
+ IManifestExpressionProvider output => output.ValueExpression,
+ object obj => obj.ToString(),
+ null => ""
+ };
+ context.Writer.WriteString("subscription", subscription);
+ }
+
+ // Only write tenant if it has a value to maintain backward compatibility
+ if (Scope.Tenant is not null)
+ {
+ var tenant = Scope.Tenant switch
+ {
+ IManifestExpressionProvider output => output.ValueExpression,
+ object obj => obj.ToString(),
+ null => ""
+ };
+ context.Writer.WriteString("tenant", tenant);
+ }
context.Writer.WriteEndObject();
}
}
diff --git a/src/Aspire.Hosting.Azure/AzureBicepResourceScope.cs b/src/Aspire.Hosting.Azure/AzureBicepResourceScope.cs
index 382cf302704..22002184d6f 100644
--- a/src/Aspire.Hosting.Azure/AzureBicepResourceScope.cs
+++ b/src/Aspire.Hosting.Azure/AzureBicepResourceScope.cs
@@ -6,26 +6,72 @@ namespace Aspire.Hosting.Azure;
///
/// Represents the scope associated with the resource.
///
-/// The name of the existing resource group.
-public sealed class AzureBicepResourceScope(object resourceGroup)
+public sealed class AzureBicepResourceScope
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class with a resource group.
+ ///
+ /// The name of the existing resource group.
+ public AzureBicepResourceScope(object resourceGroup)
+ {
+ ArgumentNullException.ThrowIfNull(resourceGroup);
+ ResourceGroup = resourceGroup;
+ }
+
+ ///
+ /// Initializes a new instance of the class with both resource group and subscription.
///
/// The name of the existing resource group.
/// The subscription identifier associated with the resource group.
public AzureBicepResourceScope(object resourceGroup, object subscription) : this(resourceGroup)
{
+ ArgumentNullException.ThrowIfNull(subscription);
+ Subscription = subscription;
+ }
+
+ ///
+ /// Initializes a new instance of the class for subscription-level resources.
+ ///
+ /// The subscription identifier for subscription-level resources.
+ /// Must be true to indicate this is a subscription-only scope.
+ public AzureBicepResourceScope(object subscription, bool isSubscriptionScope)
+ {
+ ArgumentNullException.ThrowIfNull(subscription);
+ if (!isSubscriptionScope)
+ {
+ throw new ArgumentException("isSubscriptionScope parameter must be true when creating subscription-only scope.", nameof(isSubscriptionScope));
+ }
Subscription = subscription;
}
+ ///
+ /// Initializes a new instance of the class for tenant-level resources.
+ ///
+ /// The tenant identifier for tenant-level resources.
+ /// Must be true to indicate this is a tenant-only scope.
+ /// Must be true to differentiate from other constructors.
+ public AzureBicepResourceScope(object tenant, bool isTenantScope, bool isTenantScopeMarker)
+ {
+ ArgumentNullException.ThrowIfNull(tenant);
+ if (!isTenantScope)
+ {
+ throw new ArgumentException("isTenantScope parameter must be true when creating tenant-only scope.", nameof(isTenantScope));
+ }
+ Tenant = tenant;
+ }
+
///
/// Represents the resource group to encode in the scope.
///
- public object ResourceGroup { get; } = resourceGroup;
+ public object? ResourceGroup { get; }
///
/// Represents the subscription to encode in the scope.
///
public object? Subscription { get; }
+
+ ///
+ /// Represents the tenant to encode in the scope.
+ ///
+ public object? Tenant { get; }
}
diff --git a/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs b/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs
index 28f45ffe2c9..aee3a648499 100644
--- a/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs
+++ b/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs
@@ -132,9 +132,30 @@ public static T CreateExistingOrNewProvisionableResource(AzureResourceInfrast
? nameParameter.AsProvisioningParameter(infrastructure)
: new BicepValue((string)existingAnnotation.Name);
provisionedResource = createExisting(infrastructure.AspireResource.GetBicepIdentifier(), existingResourceName);
- if (existingAnnotation.ResourceGroup is not null)
+
+ // Set scope if either resource group, subscription, or tenant is specified
+ if (existingAnnotation.ResourceGroup is not null || existingAnnotation.Subscription is not null || existingAnnotation.Tenant is not null)
{
- infrastructure.AspireResource.Scope = new(existingAnnotation.ResourceGroup);
+ if (existingAnnotation.Tenant is not null && existingAnnotation.Subscription is null && existingAnnotation.ResourceGroup is null)
+ {
+ // Tenant only
+ infrastructure.AspireResource.Scope = new(existingAnnotation.Tenant, isTenantScope: true, isTenantScopeMarker: true);
+ }
+ else if (existingAnnotation.ResourceGroup is not null && existingAnnotation.Subscription is not null)
+ {
+ // Both resource group and subscription
+ infrastructure.AspireResource.Scope = new(existingAnnotation.ResourceGroup, existingAnnotation.Subscription);
+ }
+ else if (existingAnnotation.ResourceGroup is not null)
+ {
+ // Resource group only
+ infrastructure.AspireResource.Scope = new(existingAnnotation.ResourceGroup);
+ }
+ else if (existingAnnotation.Subscription is not null)
+ {
+ // Subscription only
+ infrastructure.AspireResource.Scope = new(existingAnnotation.Subscription, isSubscriptionScope: true);
+ }
}
}
else
@@ -200,11 +221,66 @@ static bool ResourceGroupEquals(object existingResourceGroup, object? infraResou
if (existingAnnotation.ResourceGroup is not null &&
!ResourceGroupEquals(existingAnnotation.ResourceGroup, infra.AspireResource.Scope?.ResourceGroup))
{
- BicepValue scope = existingAnnotation.ResourceGroup switch
+ BicepValue scope;
+
+ // Handle subscription-scoped existing resource
+ if (existingAnnotation.Subscription is not null)
{
- string rgName => new FunctionCallExpression(new IdentifierExpression("resourceGroup"), new StringLiteralExpression(rgName)),
- ParameterResource p => new FunctionCallExpression(new IdentifierExpression("resourceGroup"), p.AsProvisioningParameter(infra).Value.Compile()),
- _ => throw new NotSupportedException($"Resource group type '{existingAnnotation.ResourceGroup.GetType()}' is not supported.")
+ scope = existingAnnotation.Subscription switch
+ {
+ string subId when existingAnnotation.ResourceGroup is string rgName =>
+ new FunctionCallExpression(new IdentifierExpression("resourceGroup"), new StringLiteralExpression(subId), new StringLiteralExpression(rgName)),
+ string subId when existingAnnotation.ResourceGroup is ParameterResource rgParam =>
+ new FunctionCallExpression(new IdentifierExpression("resourceGroup"), new StringLiteralExpression(subId), rgParam.AsProvisioningParameter(infra).Value.Compile()),
+ ParameterResource subParam when existingAnnotation.ResourceGroup is string rgName =>
+ new FunctionCallExpression(new IdentifierExpression("resourceGroup"), subParam.AsProvisioningParameter(infra).Value.Compile(), new StringLiteralExpression(rgName)),
+ ParameterResource subParam when existingAnnotation.ResourceGroup is ParameterResource rgParam =>
+ new FunctionCallExpression(new IdentifierExpression("resourceGroup"), subParam.AsProvisioningParameter(infra).Value.Compile(), rgParam.AsProvisioningParameter(infra).Value.Compile()),
+ _ => throw new NotSupportedException($"Subscription type '{existingAnnotation.Subscription.GetType()}' is not supported.")
+ };
+ }
+ else
+ {
+ scope = existingAnnotation.ResourceGroup switch
+ {
+ string rgName => new FunctionCallExpression(new IdentifierExpression("resourceGroup"), new StringLiteralExpression(rgName)),
+ ParameterResource p => new FunctionCallExpression(new IdentifierExpression("resourceGroup"), p.AsProvisioningParameter(infra).Value.Compile()),
+ _ => throw new NotSupportedException($"Resource group type '{existingAnnotation.ResourceGroup.GetType()}' is not supported.")
+ };
+ }
+
+ // HACK: This is a dance we do to set extra properties using Azure.Provisioning
+ // will be resolved if we ever get https://github.com/Azure/azure-sdk-for-net/issues/47980
+ var expression = scope.Compile();
+ var value = new BicepValue(expression);
+ ((IBicepValue)value).Self = new BicepValueReference(provisionableResource, "Scope", ["scope"]);
+ provisionableResource.ProvisionableProperties["scope"] = value;
+ }
+ // Handle subscription-only scope (no resource group override)
+ else if (existingAnnotation.Subscription is not null)
+ {
+ BicepValue scope = existingAnnotation.Subscription switch
+ {
+ string subId => new FunctionCallExpression(new IdentifierExpression("subscription"), new StringLiteralExpression(subId)),
+ ParameterResource subParam => new FunctionCallExpression(new IdentifierExpression("subscription"), subParam.AsProvisioningParameter(infra).Value.Compile()),
+ _ => throw new NotSupportedException($"Subscription type '{existingAnnotation.Subscription.GetType()}' is not supported.")
+ };
+
+ // HACK: This is a dance we do to set extra properties using Azure.Provisioning
+ // will be resolved if we ever get https://github.com/Azure/azure-sdk-for-net/issues/47980
+ var expression = scope.Compile();
+ var value = new BicepValue(expression);
+ ((IBicepValue)value).Self = new BicepValueReference(provisionableResource, "Scope", ["scope"]);
+ provisionableResource.ProvisionableProperties["scope"] = value;
+ }
+ // Handle tenant-only scope (no resource group or subscription override)
+ else if (existingAnnotation.Tenant is not null)
+ {
+ BicepValue scope = existingAnnotation.Tenant switch
+ {
+ string tenantId => new FunctionCallExpression(new IdentifierExpression("tenant"), new StringLiteralExpression(tenantId)),
+ ParameterResource tenantParam => new FunctionCallExpression(new IdentifierExpression("tenant"), tenantParam.AsProvisioningParameter(infra).Value.Compile()),
+ _ => throw new NotSupportedException($"Tenant type '{existingAnnotation.Tenant.GetType()}' is not supported.")
};
// HACK: This is a dance we do to set extra properties using Azure.Provisioning
diff --git a/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs b/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs
index 20f2af837de..7dd6716bd9e 100644
--- a/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs
+++ b/src/Aspire.Hosting.Azure/AzureResourcePreparer.cs
@@ -298,11 +298,30 @@ private List CreateRoleAssignmentsResources(
ProvisioningBuildOptions = options.Value.ProvisioningBuildOptions,
};
- // existing resource role assignments need to be scoped to the resource's resource group
+ // existing resource role assignments need to be scoped to the resource's resource group or subscription or tenant
if (targetResource.TryGetLastAnnotation(out var existingAnnotation) &&
- existingAnnotation.ResourceGroup is not null)
+ (existingAnnotation.ResourceGroup is not null || existingAnnotation.Subscription is not null || existingAnnotation.Tenant is not null))
{
- roleAssignmentResource.Scope = new(existingAnnotation.ResourceGroup);
+ if (existingAnnotation.Tenant is not null && existingAnnotation.Subscription is null && existingAnnotation.ResourceGroup is null)
+ {
+ // Tenant only
+ roleAssignmentResource.Scope = new(existingAnnotation.Tenant, isTenantScope: true, isTenantScopeMarker: true);
+ }
+ else if (existingAnnotation.ResourceGroup is not null && existingAnnotation.Subscription is not null)
+ {
+ // Both resource group and subscription
+ roleAssignmentResource.Scope = new(existingAnnotation.ResourceGroup, existingAnnotation.Subscription);
+ }
+ else if (existingAnnotation.ResourceGroup is not null)
+ {
+ // Resource group only
+ roleAssignmentResource.Scope = new(existingAnnotation.ResourceGroup);
+ }
+ else if (existingAnnotation.Subscription is not null)
+ {
+ // Subscription only
+ roleAssignmentResource.Scope = new(existingAnnotation.Subscription, isSubscriptionScope: true);
+ }
}
roleAssignmentResources.Add(roleAssignmentResource);
@@ -506,11 +525,30 @@ private AzureProvisioningResource CreateGlobalRoleAssignmentsResource(
ProvisioningBuildOptions = options.Value.ProvisioningBuildOptions,
};
- // existing resource role assignments need to be scoped to the resource's resource group
+ // existing resource role assignments need to be scoped to the resource's resource group or subscription or tenant
if (targetResource.TryGetLastAnnotation(out var existingAnnotation) &&
- existingAnnotation.ResourceGroup is not null)
+ (existingAnnotation.ResourceGroup is not null || existingAnnotation.Subscription is not null || existingAnnotation.Tenant is not null))
{
- roleAssignmentResource.Scope = new(existingAnnotation.ResourceGroup);
+ if (existingAnnotation.Tenant is not null && existingAnnotation.Subscription is null && existingAnnotation.ResourceGroup is null)
+ {
+ // Tenant only
+ roleAssignmentResource.Scope = new(existingAnnotation.Tenant, isTenantScope: true, isTenantScopeMarker: true);
+ }
+ else if (existingAnnotation.ResourceGroup is not null && existingAnnotation.Subscription is not null)
+ {
+ // Both resource group and subscription
+ roleAssignmentResource.Scope = new(existingAnnotation.ResourceGroup, existingAnnotation.Subscription);
+ }
+ else if (existingAnnotation.ResourceGroup is not null)
+ {
+ // Resource group only
+ roleAssignmentResource.Scope = new(existingAnnotation.ResourceGroup);
+ }
+ else if (existingAnnotation.Subscription is not null)
+ {
+ // Subscription only
+ roleAssignmentResource.Scope = new(existingAnnotation.Subscription, isSubscriptionScope: true);
+ }
}
return roleAssignmentResource;
diff --git a/src/Aspire.Hosting.Azure/ExistingAzureResourceAnnotation.cs b/src/Aspire.Hosting.Azure/ExistingAzureResourceAnnotation.cs
index 3406090c6f6..42002f4b133 100644
--- a/src/Aspire.Hosting.Azure/ExistingAzureResourceAnnotation.cs
+++ b/src/Aspire.Hosting.Azure/ExistingAzureResourceAnnotation.cs
@@ -11,6 +11,29 @@ namespace Aspire.Hosting.Azure;
///
public sealed class ExistingAzureResourceAnnotation(object name, object? resourceGroup = null) : IResourceAnnotation
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ public ExistingAzureResourceAnnotation(object name, object? resourceGroup, object subscription) : this(name, resourceGroup)
+ {
+ Subscription = subscription;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The tenant identifier associated with the subscription.
+ public ExistingAzureResourceAnnotation(object name, object? resourceGroup, object subscription, object tenant) : this(name, resourceGroup, subscription)
+ {
+ Tenant = tenant;
+ }
+
///
/// Gets the name of the existing resource.
///
@@ -26,4 +49,20 @@ public sealed class ExistingAzureResourceAnnotation(object name, object? resourc
/// Supports a or a via runtime validation.
///
public object? ResourceGroup { get; } = resourceGroup;
+
+ ///
+ /// Gets the subscription identifier associated with the resource group.
+ ///
+ ///
+ /// Supports a or a via runtime validation.
+ ///
+ public object? Subscription { get; }
+
+ ///
+ /// Gets the tenant identifier associated with the subscription.
+ ///
+ ///
+ /// Supports a or a via runtime validation.
+ ///
+ public object? Tenant { get; }
}
diff --git a/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs b/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs
index 000dd14af03..c7d765b80a8 100644
--- a/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs
+++ b/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs
@@ -125,4 +125,284 @@ public static IResourceBuilder AsExisting(this IResourceBuilder builder
return builder;
}
+
+ // ===== Subscription support methods =====
+
+ ///
+ /// Marks the resource as an existing resource when the application is running.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource when the application is running.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource when the application is deployed.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource when the application is deployed.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource));
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExisting(this IResourceBuilder builder, string name, string? resourceGroup)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup));
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription));
+
+ return builder;
+ }
+
+ // ===== Tenant support methods =====
+
+ ///
+ /// Marks the resource as an existing resource when the application is running.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The tenant identifier associated with the subscription.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter, IResourceBuilder tenantParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource, tenantParameter.Resource));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource when the application is running.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The tenant identifier associated with the subscription.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription, string tenant)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription, tenant));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource when the application is deployed.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The tenant identifier associated with the subscription.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter, IResourceBuilder tenantParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource, tenantParameter.Resource));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource when the application is deployed.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The tenant identifier associated with the subscription.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription, string tenant)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription, tenant));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The tenant identifier associated with the subscription.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter, IResourceBuilder tenantParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource, tenantParameter.Resource));
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The subscription identifier associated with the resource group.
+ /// The tenant identifier associated with the subscription.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription, string tenant)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription, tenant));
+
+ return builder;
+ }
}
diff --git a/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs b/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs
index 38c43fea9ed..509b9b7d05c 100644
--- a/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs
+++ b/src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs
@@ -67,15 +67,39 @@ public static async Task SetScopeAsync(JsonObject scope, AzureBicepResource reso
{
// Resolve the scope from the AzureBicepResource if it has already been set
// via the ConfigureInfrastructure callback. If not, fallback to the ExistingAzureResourceAnnotation.
- var targetScope = GetExistingResourceGroup(resource);
+ var targetResourceGroup = GetExistingResourceGroup(resource);
+ var targetSubscription = GetExistingSubscription(resource);
+ var targetTenant = GetExistingTenant(resource);
- scope["resourceGroup"] = targetScope switch
+ scope["resourceGroup"] = targetResourceGroup switch
{
string s => s,
IValueProvider v => await v.GetValueAsync(cancellationToken).ConfigureAwait(false),
null => null,
- _ => throw new NotSupportedException($"The scope value type {targetScope.GetType()} is not supported.")
+ _ => throw new NotSupportedException($"The scope value type {targetResourceGroup.GetType()} is not supported.")
};
+
+ // Only set subscription if it has a value to maintain backward compatibility
+ if (targetSubscription is not null)
+ {
+ scope["subscription"] = targetSubscription switch
+ {
+ string s => s,
+ IValueProvider v => await v.GetValueAsync(cancellationToken).ConfigureAwait(false),
+ _ => throw new NotSupportedException($"The scope subscription type {targetSubscription.GetType()} is not supported.")
+ };
+ }
+
+ // Only set tenant if it has a value to maintain backward compatibility
+ if (targetTenant is not null)
+ {
+ scope["tenant"] = targetTenant switch
+ {
+ string s => s,
+ IValueProvider v => await v.GetValueAsync(cancellationToken).ConfigureAwait(false),
+ _ => throw new NotSupportedException($"The scope tenant type {targetTenant.GetType()} is not supported.")
+ };
+ }
}
///
@@ -147,4 +171,16 @@ public static string GetChecksum(AzureBicepResource resource, JsonObject paramet
(resource.TryGetLastAnnotation(out var existingResource) ?
existingResource.ResourceGroup :
null);
+
+ internal static object? GetExistingSubscription(AzureBicepResource resource) =>
+ resource.Scope?.Subscription ??
+ (resource.TryGetLastAnnotation(out var existingResource) ?
+ existingResource.Subscription :
+ null);
+
+ internal static object? GetExistingTenant(AzureBicepResource resource) =>
+ resource.Scope?.Tenant ??
+ (resource.TryGetLastAnnotation(out var existingResource) ?
+ existingResource.Tenant :
+ null);
}
From 30d24db94f8fdfd482eb53fecb6518b9d4ce14ef Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 22 Nov 2025 15:14:49 +0000
Subject: [PATCH 3/6] Add comprehensive tests for subscription and tenant scope
support
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../ExistingAzureResourceExtensionsTests.cs | 176 ++++++++++++++++++
.../ExistingAzureResourceTests.cs | 78 ++++++++
...ubscriptionInPublishMode#00.verified.bicep | 17 ++
...SubscriptionInPublishMode#00.verified.json | 12 ++
...ubscriptionInPublishMode#01.verified.bicep | 25 +++
...SubscriptionInPublishMode#01.verified.json | 14 ++
...riptionOnlyInPublishMode#00.verified.bicep | 17 ++
...criptionOnlyInPublishMode#00.verified.json | 12 ++
...riptionOnlyInPublishMode#01.verified.bicep | 25 +++
...criptionOnlyInPublishMode#01.verified.json | 14 ++
...TenantScopeInPublishMode#00.verified.bicep | 17 ++
...hTenantScopeInPublishMode#00.verified.json | 12 ++
...TenantScopeInPublishMode#01.verified.bicep | 25 +++
...hTenantScopeInPublishMode#01.verified.json | 14 ++
14 files changed, 458 insertions(+)
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.bicep
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.json
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.bicep
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.json
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.bicep
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.json
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.bicep
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.json
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.bicep
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.json
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.bicep
create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.json
diff --git a/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceExtensionsTests.cs
index a6c45703dfa..2bebc9601fb 100644
--- a/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceExtensionsTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceExtensionsTests.cs
@@ -136,5 +136,181 @@ public void AsExistingInBothModesWorks(bool isPublishMode)
Assert.Equal("name", existingNameParameter.Name);
var existingResourceGroupParameter = Assert.IsType(existingAzureResourceAnnotation.ResourceGroup);
Assert.Equal("resourceGroup", existingResourceGroupParameter.Name);
+ Assert.Null(existingAzureResourceAnnotation.Subscription);
+ Assert.Null(existingAzureResourceAnnotation.Tenant);
+ }
+
+ // ====== Subscription Support Tests ======
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void AsExistingWithSubscriptionInBothModesWorks(bool isPublishMode)
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(isPublishMode ? DistributedApplicationOperation.Publish : DistributedApplicationOperation.Run);
+
+ var nameParameter = builder.AddParameter("name", "existingName");
+ var resourceGroupParameter = builder.AddParameter("resourceGroup", "existingResourceGroup");
+ var subscriptionParameter = builder.AddParameter("subscription", "12345678-1234-1234-1234-123456789012");
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .AsExisting(nameParameter, resourceGroupParameter, subscriptionParameter);
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ var existingNameParameter = Assert.IsType(existingAzureResourceAnnotation.Name);
+ Assert.Equal("name", existingNameParameter.Name);
+ var existingResourceGroupParameter = Assert.IsType(existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("resourceGroup", existingResourceGroupParameter.Name);
+ var existingSubscriptionParameter = Assert.IsType(existingAzureResourceAnnotation.Subscription);
+ Assert.Equal("subscription", existingSubscriptionParameter.Name);
+ Assert.Null(existingAzureResourceAnnotation.Tenant);
+ }
+
+ [Fact]
+ public void CanCallAsExistingWithStringAndSubscriptionArguments()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .AsExisting("existingName", "existingResourceGroup", "12345678-1234-1234-1234-123456789012");
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ Assert.Equal("existingName", existingAzureResourceAnnotation.Name);
+ Assert.Equal("existingResourceGroup", existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("12345678-1234-1234-1234-123456789012", existingAzureResourceAnnotation.Subscription);
+ Assert.Null(existingAzureResourceAnnotation.Tenant);
+ }
+
+ [Fact]
+ public void RunAsExistingWithSubscriptionInRunModeWorks()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
+
+ var nameParameter = builder.AddParameter("name", "existingName");
+ var resourceGroupParameter = builder.AddParameter("resourceGroup", "existingResourceGroup");
+ var subscriptionParameter = builder.AddParameter("subscription", "12345678-1234-1234-1234-123456789012");
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .RunAsExisting(nameParameter, resourceGroupParameter, subscriptionParameter);
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ var existingNameParameter = Assert.IsType(existingAzureResourceAnnotation.Name);
+ Assert.Equal("name", existingNameParameter.Name);
+ var existingResourceGroupParameter = Assert.IsType(existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("resourceGroup", existingResourceGroupParameter.Name);
+ var existingSubscriptionParameter = Assert.IsType(existingAzureResourceAnnotation.Subscription);
+ Assert.Equal("subscription", existingSubscriptionParameter.Name);
+ }
+
+ [Fact]
+ public void PublishAsExistingWithSubscriptionInPublishModeWorks()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var nameParameter = builder.AddParameter("name", "existingName");
+ var resourceGroupParameter = builder.AddParameter("resourceGroup", "existingResourceGroup");
+ var subscriptionParameter = builder.AddParameter("subscription", "12345678-1234-1234-1234-123456789012");
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .PublishAsExisting(nameParameter, resourceGroupParameter, subscriptionParameter);
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ var existingNameParameter = Assert.IsType(existingAzureResourceAnnotation.Name);
+ Assert.Equal("name", existingNameParameter.Name);
+ var existingResourceGroupParameter = Assert.IsType(existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("resourceGroup", existingResourceGroupParameter.Name);
+ var existingSubscriptionParameter = Assert.IsType(existingAzureResourceAnnotation.Subscription);
+ Assert.Equal("subscription", existingSubscriptionParameter.Name);
+ }
+
+ // ====== Tenant Support Tests ======
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void AsExistingWithTenantInBothModesWorks(bool isPublishMode)
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(isPublishMode ? DistributedApplicationOperation.Publish : DistributedApplicationOperation.Run);
+
+ var nameParameter = builder.AddParameter("name", "existingName");
+ var resourceGroupParameter = builder.AddParameter("resourceGroup", "existingResourceGroup");
+ var subscriptionParameter = builder.AddParameter("subscription", "12345678-1234-1234-1234-123456789012");
+ var tenantParameter = builder.AddParameter("tenant", "87654321-4321-4321-4321-210987654321");
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .AsExisting(nameParameter, resourceGroupParameter, subscriptionParameter, tenantParameter);
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ var existingNameParameter = Assert.IsType(existingAzureResourceAnnotation.Name);
+ Assert.Equal("name", existingNameParameter.Name);
+ var existingResourceGroupParameter = Assert.IsType(existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("resourceGroup", existingResourceGroupParameter.Name);
+ var existingSubscriptionParameter = Assert.IsType(existingAzureResourceAnnotation.Subscription);
+ Assert.Equal("subscription", existingSubscriptionParameter.Name);
+ var existingTenantParameter = Assert.IsType(existingAzureResourceAnnotation.Tenant);
+ Assert.Equal("tenant", existingTenantParameter.Name);
+ }
+
+ [Fact]
+ public void CanCallAsExistingWithStringAndTenantArguments()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .AsExisting("existingName", "existingResourceGroup", "12345678-1234-1234-1234-123456789012", "87654321-4321-4321-4321-210987654321");
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ Assert.Equal("existingName", existingAzureResourceAnnotation.Name);
+ Assert.Equal("existingResourceGroup", existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("12345678-1234-1234-1234-123456789012", existingAzureResourceAnnotation.Subscription);
+ Assert.Equal("87654321-4321-4321-4321-210987654321", existingAzureResourceAnnotation.Tenant);
+ }
+
+ [Fact]
+ public void RunAsExistingWithTenantInRunModeWorks()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run);
+
+ var nameParameter = builder.AddParameter("name", "existingName");
+ var resourceGroupParameter = builder.AddParameter("resourceGroup", "existingResourceGroup");
+ var subscriptionParameter = builder.AddParameter("subscription", "12345678-1234-1234-1234-123456789012");
+ var tenantParameter = builder.AddParameter("tenant", "87654321-4321-4321-4321-210987654321");
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .RunAsExisting(nameParameter, resourceGroupParameter, subscriptionParameter, tenantParameter);
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ var existingNameParameter = Assert.IsType(existingAzureResourceAnnotation.Name);
+ Assert.Equal("name", existingNameParameter.Name);
+ var existingResourceGroupParameter = Assert.IsType(existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("resourceGroup", existingResourceGroupParameter.Name);
+ var existingSubscriptionParameter = Assert.IsType(existingAzureResourceAnnotation.Subscription);
+ Assert.Equal("subscription", existingSubscriptionParameter.Name);
+ var existingTenantParameter = Assert.IsType(existingAzureResourceAnnotation.Tenant);
+ Assert.Equal("tenant", existingTenantParameter.Name);
+ }
+
+ [Fact]
+ public void PublishAsExistingWithTenantInPublishModeWorks()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var nameParameter = builder.AddParameter("name", "existingName");
+ var resourceGroupParameter = builder.AddParameter("resourceGroup", "existingResourceGroup");
+ var subscriptionParameter = builder.AddParameter("subscription", "12345678-1234-1234-1234-123456789012");
+ var tenantParameter = builder.AddParameter("tenant", "87654321-4321-4321-4321-210987654321");
+
+ var serviceBus = builder.AddAzureServiceBus("sb")
+ .PublishAsExisting(nameParameter, resourceGroupParameter, subscriptionParameter, tenantParameter);
+
+ Assert.True(serviceBus.Resource.TryGetLastAnnotation(out var existingAzureResourceAnnotation));
+ var existingNameParameter = Assert.IsType(existingAzureResourceAnnotation.Name);
+ Assert.Equal("name", existingNameParameter.Name);
+ var existingResourceGroupParameter = Assert.IsType(existingAzureResourceAnnotation.ResourceGroup);
+ Assert.Equal("resourceGroup", existingResourceGroupParameter.Name);
+ var existingSubscriptionParameter = Assert.IsType(existingAzureResourceAnnotation.Subscription);
+ Assert.Equal("subscription", existingSubscriptionParameter.Name);
+ var existingTenantParameter = Assert.IsType(existingAzureResourceAnnotation.Tenant);
+ Assert.Equal("tenant", existingTenantParameter.Name);
}
}
diff --git a/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceTests.cs b/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceTests.cs
index 4eb53f464c1..42eb9c64737 100644
--- a/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/ExistingAzureResourceTests.cs
@@ -502,4 +502,82 @@ await Verify(manifest.ToString(), "json")
.AppendContentAsFile(bicep, "bicep");
}
+
+ [Fact]
+ public async Task SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var existingResourceName = builder.AddParameter("existingResourceName");
+ var existingResourceGroupName = builder.AddParameter("existingResourceGroupName");
+ var existingSubscriptionId = builder.AddParameter("existingSubscriptionId");
+ var serviceBus = builder.AddAzureServiceBus("messaging")
+ .PublishAsExisting(existingResourceName, existingResourceGroupName, existingSubscriptionId);
+ serviceBus.AddServiceBusQueue("queue");
+
+ using var app = builder.Build();
+ var model = app.Services.GetRequiredService();
+ var (manifest, bicep) = await GetManifestWithBicep(model, serviceBus.Resource);
+
+ // ensure the role assignments resource has the correct manifest and bicep, specifically the correct scope property
+ var messagingRoles = Assert.Single(model.Resources.OfType(), r => r.Name == "messaging-roles");
+ var (rolesManifest, rolesBicep) = await GetManifestWithBicep(messagingRoles, skipPreparer: true);
+
+ await Verify(manifest.ToString(), "json")
+ .AppendContentAsFile(bicep, "bicep")
+ .AppendContentAsFile(rolesManifest.ToString(), "json")
+ .AppendContentAsFile(rolesBicep, "bicep");
+ }
+
+ [Fact]
+ public async Task SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var existingResourceName = builder.AddParameter("existingResourceName");
+ var existingSubscriptionId = builder.AddParameter("existingSubscriptionId");
+ var serviceBus = builder.AddAzureServiceBus("messaging")
+ .PublishAsExisting(existingResourceName, null, existingSubscriptionId);
+ serviceBus.AddServiceBusQueue("queue");
+
+ using var app = builder.Build();
+ var model = app.Services.GetRequiredService();
+ var (manifest, bicep) = await GetManifestWithBicep(model, serviceBus.Resource);
+
+ // ensure the role assignments resource has the correct manifest and bicep, specifically the correct scope property
+ var messagingRoles = Assert.Single(model.Resources.OfType(), r => r.Name == "messaging-roles");
+ var (rolesManifest, rolesBicep) = await GetManifestWithBicep(messagingRoles, skipPreparer: true);
+
+ await Verify(manifest.ToString(), "json")
+ .AppendContentAsFile(bicep, "bicep")
+ .AppendContentAsFile(rolesManifest.ToString(), "json")
+ .AppendContentAsFile(rolesBicep, "bicep");
+ }
+
+ [Fact]
+ public async Task SupportsExistingServiceBusWithTenantScopeInPublishMode()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var existingResourceName = builder.AddParameter("existingResourceName");
+ var existingResourceGroupName = builder.AddParameter("existingResourceGroupName");
+ var existingSubscriptionId = builder.AddParameter("existingSubscriptionId");
+ var existingTenantId = builder.AddParameter("existingTenantId");
+ var serviceBus = builder.AddAzureServiceBus("messaging")
+ .PublishAsExisting(existingResourceName, existingResourceGroupName, existingSubscriptionId, existingTenantId);
+ serviceBus.AddServiceBusQueue("queue");
+
+ using var app = builder.Build();
+ var model = app.Services.GetRequiredService();
+ var (manifest, bicep) = await GetManifestWithBicep(model, serviceBus.Resource);
+
+ // ensure the role assignments resource has the correct manifest and bicep, specifically the correct scope property
+ var messagingRoles = Assert.Single(model.Resources.OfType(), r => r.Name == "messaging-roles");
+ var (rolesManifest, rolesBicep) = await GetManifestWithBicep(messagingRoles, skipPreparer: true);
+
+ await Verify(manifest.ToString(), "json")
+ .AppendContentAsFile(bicep, "bicep")
+ .AppendContentAsFile(rolesManifest.ToString(), "json")
+ .AppendContentAsFile(rolesBicep, "bicep");
+ }
}
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.bicep
new file mode 100644
index 00000000000..155a63b7ed3
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.bicep
@@ -0,0 +1,17 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param existingResourceName string
+
+resource messaging 'Microsoft.ServiceBus/namespaces@2024-01-01' existing = {
+ name: existingResourceName
+}
+
+resource queue 'Microsoft.ServiceBus/namespaces/queues@2024-01-01' = {
+ name: 'queue'
+ parent: messaging
+}
+
+output serviceBusEndpoint string = messaging.properties.serviceBusEndpoint
+
+output name string = existingResourceName
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.json
new file mode 100644
index 00000000000..c3d3352f47a
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#00.verified.json
@@ -0,0 +1,12 @@
+{
+ "type": "azure.bicep.v1",
+ "connectionString": "{messaging.outputs.serviceBusEndpoint}",
+ "path": "messaging.module.bicep",
+ "params": {
+ "existingResourceName": "{existingResourceName.value}"
+ },
+ "scope": {
+ "resourceGroup": "{existingResourceGroupName.value}",
+ "subscription": "{existingSubscriptionId.value}"
+ }
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.bicep
new file mode 100644
index 00000000000..97181e35e40
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.bicep
@@ -0,0 +1,25 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param existingResourceName string
+
+param existingSubscriptionId string
+
+param principalType string
+
+param principalId string
+
+resource messaging 'Microsoft.ServiceBus/namespaces@2024-01-01' existing = {
+ name: existingResourceName
+ scope: subscription(existingSubscriptionId)
+}
+
+resource messaging_AzureServiceBusDataOwner 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(messaging.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419'))
+ properties: {
+ principalId: principalId
+ roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419')
+ principalType: principalType
+ }
+ scope: messaging
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.json
new file mode 100644
index 00000000000..de95061d302
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithResourceGroupAndSubscriptionInPublishMode#01.verified.json
@@ -0,0 +1,14 @@
+{
+ "type": "azure.bicep.v1",
+ "path": "messaging-roles.module.bicep",
+ "params": {
+ "existingResourceName": "{existingResourceName.value}",
+ "existingSubscriptionId": "{existingSubscriptionId.value}",
+ "principalType": "",
+ "principalId": ""
+ },
+ "scope": {
+ "resourceGroup": "{existingResourceGroupName.value}",
+ "subscription": "{existingSubscriptionId.value}"
+ }
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.bicep
new file mode 100644
index 00000000000..155a63b7ed3
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.bicep
@@ -0,0 +1,17 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param existingResourceName string
+
+resource messaging 'Microsoft.ServiceBus/namespaces@2024-01-01' existing = {
+ name: existingResourceName
+}
+
+resource queue 'Microsoft.ServiceBus/namespaces/queues@2024-01-01' = {
+ name: 'queue'
+ parent: messaging
+}
+
+output serviceBusEndpoint string = messaging.properties.serviceBusEndpoint
+
+output name string = existingResourceName
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.json
new file mode 100644
index 00000000000..81813612db5
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#00.verified.json
@@ -0,0 +1,12 @@
+{
+ "type": "azure.bicep.v1",
+ "connectionString": "{messaging.outputs.serviceBusEndpoint}",
+ "path": "messaging.module.bicep",
+ "params": {
+ "existingResourceName": "{existingResourceName.value}"
+ },
+ "scope": {
+ "resourceGroup": "",
+ "subscription": "{existingSubscriptionId.value}"
+ }
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.bicep
new file mode 100644
index 00000000000..97181e35e40
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.bicep
@@ -0,0 +1,25 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param existingResourceName string
+
+param existingSubscriptionId string
+
+param principalType string
+
+param principalId string
+
+resource messaging 'Microsoft.ServiceBus/namespaces@2024-01-01' existing = {
+ name: existingResourceName
+ scope: subscription(existingSubscriptionId)
+}
+
+resource messaging_AzureServiceBusDataOwner 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(messaging.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419'))
+ properties: {
+ principalId: principalId
+ roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419')
+ principalType: principalType
+ }
+ scope: messaging
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.json
new file mode 100644
index 00000000000..c721cf28521
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithSubscriptionOnlyInPublishMode#01.verified.json
@@ -0,0 +1,14 @@
+{
+ "type": "azure.bicep.v1",
+ "path": "messaging-roles.module.bicep",
+ "params": {
+ "existingResourceName": "{existingResourceName.value}",
+ "existingSubscriptionId": "{existingSubscriptionId.value}",
+ "principalType": "",
+ "principalId": ""
+ },
+ "scope": {
+ "resourceGroup": "",
+ "subscription": "{existingSubscriptionId.value}"
+ }
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.bicep
new file mode 100644
index 00000000000..155a63b7ed3
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.bicep
@@ -0,0 +1,17 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param existingResourceName string
+
+resource messaging 'Microsoft.ServiceBus/namespaces@2024-01-01' existing = {
+ name: existingResourceName
+}
+
+resource queue 'Microsoft.ServiceBus/namespaces/queues@2024-01-01' = {
+ name: 'queue'
+ parent: messaging
+}
+
+output serviceBusEndpoint string = messaging.properties.serviceBusEndpoint
+
+output name string = existingResourceName
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.json
new file mode 100644
index 00000000000..c3d3352f47a
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#00.verified.json
@@ -0,0 +1,12 @@
+{
+ "type": "azure.bicep.v1",
+ "connectionString": "{messaging.outputs.serviceBusEndpoint}",
+ "path": "messaging.module.bicep",
+ "params": {
+ "existingResourceName": "{existingResourceName.value}"
+ },
+ "scope": {
+ "resourceGroup": "{existingResourceGroupName.value}",
+ "subscription": "{existingSubscriptionId.value}"
+ }
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.bicep
new file mode 100644
index 00000000000..97181e35e40
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.bicep
@@ -0,0 +1,25 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param existingResourceName string
+
+param existingSubscriptionId string
+
+param principalType string
+
+param principalId string
+
+resource messaging 'Microsoft.ServiceBus/namespaces@2024-01-01' existing = {
+ name: existingResourceName
+ scope: subscription(existingSubscriptionId)
+}
+
+resource messaging_AzureServiceBusDataOwner 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(messaging.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419'))
+ properties: {
+ principalId: principalId
+ roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '090c5cfd-751d-490a-894a-3ce6f1109419')
+ principalType: principalType
+ }
+ scope: messaging
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.json
new file mode 100644
index 00000000000..de95061d302
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/ExistingAzureResourceTests.SupportsExistingServiceBusWithTenantScopeInPublishMode#01.verified.json
@@ -0,0 +1,14 @@
+{
+ "type": "azure.bicep.v1",
+ "path": "messaging-roles.module.bicep",
+ "params": {
+ "existingResourceName": "{existingResourceName.value}",
+ "existingSubscriptionId": "{existingSubscriptionId.value}",
+ "principalType": "",
+ "principalId": ""
+ },
+ "scope": {
+ "resourceGroup": "{existingResourceGroupName.value}",
+ "subscription": "{existingSubscriptionId.value}"
+ }
+}
\ No newline at end of file
From b2d216de8a5670283f06fd817c564194f38c9c07 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 22 Nov 2025 15:25:09 +0000
Subject: [PATCH 4/6] Refactor scope property setting logic to reduce
duplication
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../AzureProvisioningResource.cs | 31 ++++++++-----------
1 file changed, 13 insertions(+), 18 deletions(-)
diff --git a/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs b/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs
index aee3a648499..37c7bda955e 100644
--- a/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs
+++ b/src/Aspire.Hosting.Azure/AzureProvisioningResource.cs
@@ -217,6 +217,16 @@ static bool ResourceGroupEquals(object existingResourceGroup, object? infraResou
return false;
}
+ static void SetScopeProperty(ProvisionableResource provisionableResource, BicepValue scope)
+ {
+ // HACK: This is a dance we do to set extra properties using Azure.Provisioning
+ // will be resolved if we ever get https://github.com/Azure/azure-sdk-for-net/issues/47980
+ var expression = scope.Compile();
+ var value = new BicepValue(expression);
+ ((IBicepValue)value).Self = new BicepValueReference(provisionableResource, "Scope", ["scope"]);
+ provisionableResource.ProvisionableProperties["scope"] = value;
+ }
+
// Apply resource group scope if the target infrastructure's resource group is different from the existing annotation's resource group
if (existingAnnotation.ResourceGroup is not null &&
!ResourceGroupEquals(existingAnnotation.ResourceGroup, infra.AspireResource.Scope?.ResourceGroup))
@@ -249,12 +259,7 @@ static bool ResourceGroupEquals(object existingResourceGroup, object? infraResou
};
}
- // HACK: This is a dance we do to set extra properties using Azure.Provisioning
- // will be resolved if we ever get https://github.com/Azure/azure-sdk-for-net/issues/47980
- var expression = scope.Compile();
- var value = new BicepValue(expression);
- ((IBicepValue)value).Self = new BicepValueReference(provisionableResource, "Scope", ["scope"]);
- provisionableResource.ProvisionableProperties["scope"] = value;
+ SetScopeProperty(provisionableResource, scope);
}
// Handle subscription-only scope (no resource group override)
else if (existingAnnotation.Subscription is not null)
@@ -266,12 +271,7 @@ static bool ResourceGroupEquals(object existingResourceGroup, object? infraResou
_ => throw new NotSupportedException($"Subscription type '{existingAnnotation.Subscription.GetType()}' is not supported.")
};
- // HACK: This is a dance we do to set extra properties using Azure.Provisioning
- // will be resolved if we ever get https://github.com/Azure/azure-sdk-for-net/issues/47980
- var expression = scope.Compile();
- var value = new BicepValue(expression);
- ((IBicepValue)value).Self = new BicepValueReference(provisionableResource, "Scope", ["scope"]);
- provisionableResource.ProvisionableProperties["scope"] = value;
+ SetScopeProperty(provisionableResource, scope);
}
// Handle tenant-only scope (no resource group or subscription override)
else if (existingAnnotation.Tenant is not null)
@@ -283,12 +283,7 @@ static bool ResourceGroupEquals(object existingResourceGroup, object? infraResou
_ => throw new NotSupportedException($"Tenant type '{existingAnnotation.Tenant.GetType()}' is not supported.")
};
- // HACK: This is a dance we do to set extra properties using Azure.Provisioning
- // will be resolved if we ever get https://github.com/Azure/azure-sdk-for-net/issues/47980
- var expression = scope.Compile();
- var value = new BicepValue(expression);
- ((IBicepValue)value).Self = new BicepValueReference(provisionableResource, "Scope", ["scope"]);
- provisionableResource.ProvisionableProperties["scope"] = value;
+ SetScopeProperty(provisionableResource, scope);
}
return true;
From c2d4b8113cc3abee6d07f86e004664f6d48ba52e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 25 Nov 2025 05:54:54 +0000
Subject: [PATCH 5/6] Rename AsExisting overloads to AsExistingInSubscription,
AsExistingInResourceGroup, AsExistingInTenant
Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
---
.../ExistingAzureResourceExtensions.cs | 256 +++++++++++++-----
.../ExistingAzureResourceExtensionsTests.cs | 109 ++++++--
.../ExistingAzureResourceTests.cs | 6 +-
3 files changed, 285 insertions(+), 86 deletions(-)
diff --git a/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs b/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs
index c7d765b80a8..32daeab1bf9 100644
--- a/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs
+++ b/src/Aspire.Hosting.Azure/ExistingAzureResourceExtensions.cs
@@ -126,89 +126,185 @@ public static IResourceBuilder AsExisting(this IResourceBuilder builder
return builder;
}
- // ===== Subscription support methods =====
+ ///
+ /// Marks the resource as an existing resource in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group, or to use the current resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExisting(this IResourceBuilder builder, string name, string? resourceGroup)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup));
+
+ return builder;
+ }
+
+ // ===== Subscription-scoped existing resource methods =====
///
- /// Marks the resource as an existing resource when the application is running.
+ /// Marks the resource as an existing resource in a specific subscription when the application is running.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
+ /// The subscription identifier containing the resource.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ public static IResourceBuilder RunAsExistingInSubscription(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder subscriptionParameter)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
- builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource));
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroup: null, subscriptionParameter.Resource));
}
return builder;
}
///
- /// Marks the resource as an existing resource when the application is running.
+ /// Marks the resource as an existing resource in a specific subscription when the application is running.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
+ /// The subscription identifier containing the resource.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription)
+ public static IResourceBuilder RunAsExistingInSubscription(this IResourceBuilder builder, string name, string subscription)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
- builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription));
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup: null, subscription));
}
return builder;
}
///
- /// Marks the resource as an existing resource when the application is deployed.
+ /// Marks the resource as an existing resource in a specific subscription when the application is deployed.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
+ /// The subscription identifier containing the resource.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ public static IResourceBuilder PublishAsExistingInSubscription(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder subscriptionParameter)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
- builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource));
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroup: null, subscriptionParameter.Resource));
}
return builder;
}
///
- /// Marks the resource as an existing resource when the application is deployed.
+ /// Marks the resource as an existing resource in a specific subscription when the application is deployed.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
+ /// The subscription identifier containing the resource.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription)
+ public static IResourceBuilder PublishAsExistingInSubscription(this IResourceBuilder builder, string name, string subscription)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup: null, subscription));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in a specific subscription in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The subscription identifier containing the resource.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExistingInSubscription(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder subscriptionParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroup: null, subscriptionParameter.Resource));
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in a specific subscription in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The subscription identifier containing the resource.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExistingInSubscription(this IResourceBuilder builder, string name, string subscription)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup: null, subscription));
+
+ return builder;
+ }
+
+ // ===== Resource group-scoped existing resource methods (with subscription) =====
+
+ ///
+ /// Marks the resource as an existing resource in a specific resource group and subscription when the application is running.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder RunAsExistingInResourceGroup(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter.Resource, subscriptionParameter.Resource));
+ }
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in a specific resource group and subscription when the application is running.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder RunAsExistingInResourceGroup(this IResourceBuilder builder, string name, string resourceGroup, string subscription)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription));
}
@@ -217,52 +313,78 @@ public static IResourceBuilder PublishAsExisting(this IResourceBuilder
}
///
- /// Marks the resource as an existing resource in both run and publish modes.
+ /// Marks the resource as an existing resource in a specific resource group and subscription when the application is deployed.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource group.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder AsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ public static IResourceBuilder PublishAsExistingInResourceGroup(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder resourceGroupParameter, IResourceBuilder subscriptionParameter)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
- builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource));
+ if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter.Resource, subscriptionParameter.Resource));
+ }
return builder;
}
///
- /// Marks the resource as an existing resource in both run and publish modes.
+ /// Marks the resource as an existing resource in a specific resource group and subscription when the application is deployed.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource group.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder AsExisting(this IResourceBuilder builder, string name, string? resourceGroup)
+ public static IResourceBuilder PublishAsExistingInResourceGroup(this IResourceBuilder builder, string name, string resourceGroup, string subscription)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
- builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup));
+ if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
+ {
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(name, resourceGroup, subscription));
+ }
return builder;
}
///
- /// Marks the resource as an existing resource in both run and publish modes.
+ /// Marks the resource as an existing resource in a specific resource group and subscription in both run and publish modes.
+ ///
+ /// The type of the resource.
+ /// The resource builder.
+ /// The name of the existing resource.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource group.
+ /// The resource builder with the existing resource annotation added.
+ public static IResourceBuilder AsExistingInResourceGroup(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder resourceGroupParameter, IResourceBuilder subscriptionParameter)
+ where T : IAzureResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter.Resource, subscriptionParameter.Resource));
+
+ return builder;
+ }
+
+ ///
+ /// Marks the resource as an existing resource in a specific resource group and subscription in both run and publish modes.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource group.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder AsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription)
+ public static IResourceBuilder AsExistingInResourceGroup(this IResourceBuilder builder, string name, string resourceGroup, string subscription)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
@@ -272,42 +394,42 @@ public static IResourceBuilder AsExisting(this IResourceBuilder builder
return builder;
}
- // ===== Tenant support methods =====
+ // ===== Tenant-scoped existing resource methods =====
///
- /// Marks the resource as an existing resource when the application is running.
+ /// Marks the resource as an existing resource in a specific tenant when the application is running.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
- /// The tenant identifier associated with the subscription.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource.
+ /// The tenant identifier containing the subscription.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter, IResourceBuilder tenantParameter)
+ public static IResourceBuilder RunAsExistingInTenant(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder resourceGroupParameter, IResourceBuilder subscriptionParameter, IResourceBuilder tenantParameter)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
- builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource, tenantParameter.Resource));
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter.Resource, subscriptionParameter.Resource, tenantParameter.Resource));
}
return builder;
}
///
- /// Marks the resource as an existing resource when the application is running.
+ /// Marks the resource as an existing resource in a specific tenant when the application is running.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
- /// The tenant identifier associated with the subscription.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource.
+ /// The tenant identifier containing the subscription.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription, string tenant)
+ public static IResourceBuilder RunAsExistingInTenant(this IResourceBuilder builder, string name, string resourceGroup, string subscription, string tenant)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
@@ -321,39 +443,39 @@ public static IResourceBuilder RunAsExisting(this IResourceBuilder buil
}
///
- /// Marks the resource as an existing resource when the application is deployed.
+ /// Marks the resource as an existing resource in a specific tenant when the application is deployed.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
- /// The tenant identifier associated with the subscription.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource.
+ /// The tenant identifier containing the subscription.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder? resourceGroupParameter, IResourceBuilder subscriptionParameter, IResourceBuilder tenantParameter)
+ public static IResourceBuilder PublishAsExistingInTenant(this IResourceBuilder builder, IResourceBuilder nameParameter, IResourceBuilder resourceGroupParameter, IResourceBuilder subscriptionParameter, IResourceBuilder tenantParameter)
where T : IAzureResource
{
ArgumentNullException.ThrowIfNull(builder);
if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
- builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter?.Resource, subscriptionParameter.Resource, tenantParameter.Resource));
+ builder.WithAnnotation(new ExistingAzureResourceAnnotation(nameParameter.Resource, resourceGroupParameter.Resource, subscriptionParameter.Resource, tenantParameter.Resource));
}
return builder;
}
///
- /// Marks the resource as an existing resource when the application is deployed.
+ /// Marks the resource as an existing resource in a specific tenant when the application is deployed.
///
/// The type of the resource.
/// The resource builder.
/// The name of the existing resource.
- /// The name of the existing resource group, or to use the current resource group.
- /// The subscription identifier associated with the resource group.
- /// The tenant identifier associated with the subscription.
+ /// The name of the existing resource group.
+ /// The subscription identifier containing the resource.
+ /// The tenant identifier containing the subscription.
/// The resource builder with the existing resource annotation added.
- public static IResourceBuilder PublishAsExisting(this IResourceBuilder builder, string name, string? resourceGroup, string subscription, string tenant)
+ public static IResourceBuilder PublishAsExistingInTenant(this IResourceBuilder