diff --git a/src/Aspire.Hosting.Azure.AppContainers/Aspire.Hosting.Azure.AppContainers.csproj b/src/Aspire.Hosting.Azure.AppContainers/Aspire.Hosting.Azure.AppContainers.csproj
index 9a3b8a93018..2aeb93f852a 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/Aspire.Hosting.Azure.AppContainers.csproj
+++ b/src/Aspire.Hosting.Azure.AppContainers/Aspire.Hosting.Azure.AppContainers.csproj
@@ -20,6 +20,7 @@
+
diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs
index 03d7a9821f0..a170bf91bb9 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs
+++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppEnvironmentResource.cs
@@ -32,14 +32,6 @@ public AzureContainerAppEnvironmentResource(string name, Action();
- var loginToAcrStep = new PipelineStep
- {
- Name = $"login-to-acr-{name}",
- Description = $"Logs in to Azure Container Registry for {name}.",
- Action = context => AzureEnvironmentResourceHelpers.LoginToRegistryAsync(this, context),
- Tags = ["acr-login"]
- };
-
// Add print-dashboard-url step
var printDashboardUrlStep = new PipelineStep
{
@@ -51,7 +43,6 @@ public AzureContainerAppEnvironmentResource(string name, Action
{
- var acrLoginSteps = context.GetSteps(this, "acr-login");
-
// Wire up build step dependencies
// Build steps are created by ProjectResource and ContainerResource
foreach (var computeResource in context.Model.GetComputeResources())
@@ -113,9 +102,6 @@ public AzureContainerAppEnvironmentResource(string name, Action
internal BicepOutputReference ContainerAppDomain => new("AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN", this);
+ ///
+ /// Gets the name of the associated Azure Container Registry.
+ ///
+ internal BicepOutputReference ContainerRegistryName => new("AZURE_CONTAINER_REGISTRY_NAME", this);
+
///
/// Gets the URL endpoint of the associated Azure Container Registry.
///
@@ -179,17 +168,28 @@ await context.ReportingStep.CompleteAsync(
///
public BicepOutputReference NameOutputReference => new("AZURE_CONTAINER_APPS_ENVIRONMENT_NAME", this);
+ internal Dictionary VolumeNames { get; } = [];
+
///
- /// Gets the container registry name.
+ /// Gets the default container registry for this environment.
///
- private BicepOutputReference ContainerRegistryName => new("AZURE_CONTAINER_REGISTRY_NAME", this);
+ internal AzureContainerRegistryResource? DefaultContainerRegistry { get; set; }
- internal Dictionary VolumeNames { get; } = [];
+ ReferenceExpression IContainerRegistry.Name => GetContainerRegistry()?.Name ?? ReferenceExpression.Create($"{ContainerRegistryName}");
+
+ ReferenceExpression IContainerRegistry.Endpoint => GetContainerRegistry()?.Endpoint ?? ReferenceExpression.Create($"{ContainerRegistryUrl}");
- // Implement IAzureContainerRegistry interface
- ReferenceExpression IContainerRegistry.Name => ReferenceExpression.Create($"{ContainerRegistryName}");
+ private IContainerRegistry? GetContainerRegistry()
+ {
+ // Check for explicit container registry reference annotation
+ if (this.TryGetLastAnnotation(out var annotation))
+ {
+ return annotation.Registry;
+ }
- ReferenceExpression IContainerRegistry.Endpoint => ReferenceExpression.Create($"{ContainerRegistryUrl}");
+ // Fall back to default container registry
+ return DefaultContainerRegistry;
+ }
ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}");
diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs
index 5494975bac7..e4287e4716a 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs
+++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppExtensions.cs
@@ -89,19 +89,23 @@ public static IResourceBuilder AddAzureCon
infra.Add(identity);
- ContainerRegistryService? containerRegistry = null;
- if (appEnvResource.TryGetLastAnnotation(out var registryReferenceAnnotation) && registryReferenceAnnotation.Registry is AzureProvisioningResource registry)
+ AzureProvisioningResource? registry = null;
+ if (appEnvResource.TryGetLastAnnotation(out var registryReferenceAnnotation) &&
+ registryReferenceAnnotation.Registry is AzureProvisioningResource explicitRegistry)
{
- containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
+ registry = explicitRegistry;
}
- else
+ else if (appEnvResource.DefaultContainerRegistry is not null)
{
- containerRegistry = new ContainerRegistryService(Infrastructure.NormalizeBicepIdentifier($"{appEnvResource.Name}_acr"))
- {
- Sku = new() { Name = ContainerRegistrySkuName.Basic },
- Tags = tags
- };
+ registry = appEnvResource.DefaultContainerRegistry;
}
+
+ if (registry is null)
+ {
+ throw new InvalidOperationException($"No container registry associated with environment '{appEnvResource.Name}'. This should have been added automatically.");
+ }
+
+ var containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
infra.Add(containerRegistry);
var pullRa = containerRegistry.CreateRoleAssignment(ContainerRegistryBuiltInRole.AcrPull, identity);
@@ -323,14 +327,19 @@ public static IResourceBuilder AddAzureCon
});
});
- if (builder.ExecutionContext.IsRunMode)
- {
+ // Create the default container registry resource before creating the environment
+ var registryName = $"{name}-acr";
+ var defaultRegistry = CreateDefaultAzureContainerRegistry(builder, registryName, containerAppEnvResource);
+ containerAppEnvResource.DefaultContainerRegistry = defaultRegistry;
+
+ // Create the resource builder first, then attach the registry to avoid recreating builders
+ var appEnvBuilder = builder.ExecutionContext.IsRunMode
// HACK: We need to return a valid resource builder for the container app environment
// but in run mode, we don't want to add the resource to the builder.
- return builder.CreateResourceBuilder(containerAppEnvResource);
- }
+ ? builder.CreateResourceBuilder(containerAppEnvResource)
+ : builder.AddResource(containerAppEnvResource);
- return builder.AddResource(containerAppEnvResource);
+ return appEnvBuilder;
}
///
@@ -379,4 +388,57 @@ public static IResourceBuilder WithAzureLo
return builder;
}
+
+ private static AzureContainerRegistryResource CreateDefaultAzureContainerRegistry(IDistributedApplicationBuilder builder, string name, AzureContainerAppEnvironmentResource containerAppEnvironment)
+ {
+ var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
+ {
+ var registry = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infrastructure,
+ (identifier, resourceName) =>
+ {
+ var resource = ContainerRegistryService.FromExisting(identifier);
+ resource.Name = resourceName;
+ return resource;
+ },
+ (infra) =>
+ {
+ var newRegistry = new ContainerRegistryService(infra.AspireResource.GetBicepIdentifier())
+ {
+ Sku = new ContainerRegistrySku { Name = ContainerRegistrySkuName.Basic },
+ Tags = { { "aspire-resource-name", infra.AspireResource.Name } }
+ };
+
+ if (containerAppEnvironment.UseAzdNamingConvention)
+ {
+ var resourceToken = new ProvisioningVariable("resourceToken", typeof(string))
+ {
+ Value = BicepFunction.GetUniqueString(BicepFunction.GetResourceGroup().Id)
+ };
+ infrastructure.Add(resourceToken);
+
+ newRegistry.Name = new FunctionCallExpression(
+ new IdentifierExpression("replace"),
+ new InterpolatedStringExpression([
+ new StringLiteralExpression("acr-"),
+ new IdentifierExpression(resourceToken.BicepIdentifier)
+ ]),
+ new StringLiteralExpression("-"),
+ new StringLiteralExpression(""));
+ }
+
+ return newRegistry;
+ });
+
+ infrastructure.Add(registry);
+ infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = registry.Name });
+ infrastructure.Add(new ProvisioningOutput("loginServer", typeof(string)) { Value = registry.LoginServer });
+ };
+
+ var resource = new AzureContainerRegistryResource(name, configureInfrastructure);
+ if (builder.ExecutionContext.IsPublishMode)
+ {
+ builder.AddResource(resource);
+ }
+ return resource;
+ }
}
diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs
index 45646fa3a0f..40e5dc5f26d 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs
+++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppResource.cs
@@ -7,8 +7,6 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Pipelines;
-using Aspire.Hosting.Publishing;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Aspire.Hosting.Azure.AppContainers;
@@ -29,42 +27,18 @@ public AzureContainerAppResource(string name, Action
{
- // Get the registry from the target resource's deployment target annotation
+ // Get the deployment target annotation
var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
- if (deploymentTargetAnnotation?.ContainerRegistry is not IContainerRegistry registry)
+ if (deploymentTargetAnnotation is null)
{
- // No registry available, skip push
return [];
}
var steps = new List();
- if (targetResource.RequiresImageBuildAndPush())
- {
- // Create push step for this deployment target
- var pushStep = new PipelineStep
- {
- Name = $"push-{targetResource.Name}",
- Description = $"Pushes the container image for {targetResource.Name} to Azure Container Registry.",
- Action = async ctx =>
- {
- var containerImageBuilder = ctx.Services.GetRequiredService();
-
- await AzureEnvironmentResourceHelpers.PushImageToRegistryAsync(
- registry,
- targetResource,
- ctx,
- containerImageBuilder).ConfigureAwait(false);
- },
- Tags = [WellKnownPipelineTags.PushContainerImage]
- };
-
- steps.Add(pushStep);
- }
-
if (!targetResource.TryGetEndpoints(out var endpoints))
{
endpoints = [];
@@ -116,26 +90,10 @@ await AzureEnvironmentResourceHelpers.PushImageToRegistryAsync(
// Add pipeline configuration annotation to wire up dependencies
Annotations.Add(new PipelineConfigurationAnnotation((context) =>
{
- // Find the push step for this resource
- var pushSteps = context.GetSteps(this, WellKnownPipelineTags.PushContainerImage);
-
- // Make push step depend on build steps of the target resource
- var buildSteps = context.GetSteps(targetResource, WellKnownPipelineTags.BuildCompute);
-
- pushSteps.DependsOn(buildSteps);
-
- // Make push step depend on the registry being provisioned
- var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
- if (deploymentTargetAnnotation?.ContainerRegistry is IResource registryResource)
- {
- var registryProvisionSteps = context.GetSteps(registryResource, WellKnownPipelineTags.ProvisionInfrastructure);
-
- pushSteps.DependsOn(registryProvisionSteps);
- }
-
var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
- // Make provision steps depend on push steps
+ // The app deployment should depend on push steps from the target resource
+ var pushSteps = context.GetSteps(targetResource, WellKnownPipelineTags.PushContainerImage);
provisionSteps.DependsOn(pushSteps);
// Ensure summary step runs after provision
diff --git a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs
index fb59d97b28c..fe15eaedc35 100644
--- a/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs
+++ b/src/Aspire.Hosting.Azure.AppContainers/AzureContainerAppsInfrastructure.cs
@@ -30,6 +30,13 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
foreach (var environment in caes)
{
+ // Remove the default container registry from the model if an explicit registry is configured
+ if (environment.HasAnnotationOfType() &&
+ environment.DefaultContainerRegistry is not null)
+ {
+ @event.Model.Resources.Remove(environment.DefaultContainerRegistry);
+ }
+
var containerAppEnvironmentContext = new ContainerAppEnvironmentContext(
logger,
executionContext,
diff --git a/src/Aspire.Hosting.Azure.AppService/Aspire.Hosting.Azure.AppService.csproj b/src/Aspire.Hosting.Azure.AppService/Aspire.Hosting.Azure.AppService.csproj
index 7c51e304132..29c6d288b13 100644
--- a/src/Aspire.Hosting.Azure.AppService/Aspire.Hosting.Azure.AppService.csproj
+++ b/src/Aspire.Hosting.Azure.AppService/Aspire.Hosting.Azure.AppService.csproj
@@ -19,6 +19,7 @@
+
diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs
index 9ffa8abed9f..7f50acc0b4c 100644
--- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs
+++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs
@@ -44,6 +44,10 @@ public static IResourceBuilder AddAzureAppSe
{
builder.AddAzureAppServiceInfrastructureCore();
+ // Create the default container registry resource before creating the environment
+ var registryName = $"{name}-acr";
+ var defaultRegistry = CreateDefaultAzureContainerRegistry(builder, registryName);
+
var resource = new AzureAppServiceEnvironmentResource(name, static infra =>
{
var prefix = infra.AspireResource.Name;
@@ -67,20 +71,23 @@ public static IResourceBuilder AddAzureAppSe
infra.Add(identity);
- ContainerRegistryService? containerRegistry = null;
- if (resource.TryGetLastAnnotation(out var registryReferenceAnnotation) && registryReferenceAnnotation.Registry is AzureProvisioningResource registry)
+ AzureProvisioningResource? registry = null;
+ if (resource.TryGetLastAnnotation(out var registryReferenceAnnotation) &&
+ registryReferenceAnnotation.Registry is AzureProvisioningResource explicitRegistry)
{
- containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
+ registry = explicitRegistry;
}
- else
+ else if (resource.DefaultContainerRegistry is not null)
{
- containerRegistry = new ContainerRegistryService(Infrastructure.NormalizeBicepIdentifier($"{prefix}_acr"))
- {
- Sku = new() { Name = ContainerRegistrySkuName.Basic },
- Tags = tags
- };
+ registry = resource.DefaultContainerRegistry;
+ }
+
+ if (registry is null)
+ {
+ throw new InvalidOperationException($"No container registry associated with environment '{resource.Name}'. This should have been added automatically.");
}
+ var containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
infra.Add(containerRegistry);
var pullRa = containerRegistry.CreateRoleAssignment(ContainerRegistryBuiltInRole.AcrPull, identity);
@@ -205,14 +212,17 @@ public static IResourceBuilder AddAzureAppSe
Value = applicationInsights.ConnectionString
});
}
- });
-
- if (!builder.ExecutionContext.IsPublishMode)
+ })
{
- return builder.CreateResourceBuilder(resource);
- }
+ DefaultContainerRegistry = defaultRegistry
+ };
+
+ // Create the resource builder first, then attach the registry to avoid recreating builders
+ var appServiceEnvBuilder = builder.ExecutionContext.IsPublishMode
+ ? builder.AddResource(resource)
+ : builder.CreateResourceBuilder(resource);
- return builder.AddResource(resource);
+ return appServiceEnvBuilder;
}
///
@@ -277,4 +287,34 @@ public static IResourceBuilder WithAzureAppl
builder.Resource.ApplicationInsightsResource = applicationInsightsBuilder.Resource;
return builder;
}
+
+ private static AzureContainerRegistryResource CreateDefaultAzureContainerRegistry(IDistributedApplicationBuilder builder, string name)
+ {
+ var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
+ {
+ var registry = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infrastructure,
+ (identifier, resourceName) =>
+ {
+ var resource = ContainerRegistryService.FromExisting(identifier);
+ resource.Name = resourceName;
+ return resource;
+ },
+ (infra) => new ContainerRegistryService(infra.AspireResource.GetBicepIdentifier())
+ {
+ Sku = new ContainerRegistrySku { Name = ContainerRegistrySkuName.Basic },
+ Tags = { { "aspire-resource-name", infra.AspireResource.Name } }
+ });
+
+ infrastructure.Add(registry);
+ infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = registry.Name });
+ infrastructure.Add(new ProvisioningOutput("loginServer", typeof(string)) { Value = registry.LoginServer });
+ };
+
+ var resource = new AzureContainerRegistryResource(name, configureInfrastructure);
+ if (builder.ExecutionContext.IsPublishMode)
+ {
+ builder.AddResource(resource);
+ }
+ return resource;
+ }
}
diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs
index a5f4762c7d0..490be41fc3e 100644
--- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs
+++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs
@@ -35,14 +35,6 @@ public AzureAppServiceEnvironmentResource(string name, Action();
- var loginToAcrStep = new PipelineStep
- {
- Name = $"login-to-acr-{name}",
- Description = $"Logs in to Azure Container Registry for {name}.",
- Action = context => AzureEnvironmentResourceHelpers.LoginToRegistryAsync(this, context),
- Tags = ["acr-login"]
- };
-
// Add print-dashboard-url step
var printDashboardUrlStep = new PipelineStep
{
@@ -54,7 +46,6 @@ public AzureAppServiceEnvironmentResource(string name, Action
{
- var acrLoginSteps = context.GetSteps(this, "acr-login");
-
// Wire up build step dependencies
// Build steps are created by ProjectResource and ContainerResource
foreach (var computeResource in context.Model.GetComputeResources())
@@ -116,9 +105,6 @@ public AzureAppServiceEnvironmentResource(string name, Action GetWebSiteSuffixBicep() =>
BicepFunction.GetUniqueString(BicepFunction.GetResourceGroup().Id);
- ReferenceExpression IAzureContainerRegistry.ManagedIdentityId =>
- ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}");
+ ///
+ /// Gets the default container registry for this environment.
+ ///
+ internal AzureContainerRegistryResource? DefaultContainerRegistry { get; set; }
- ReferenceExpression IContainerRegistry.Name =>
- ReferenceExpression.Create($"{ContainerRegistryName}");
+ ReferenceExpression IContainerRegistry.Name => GetContainerRegistry()?.Name ?? ReferenceExpression.Create($"{ContainerRegistryName}");
+
+ ReferenceExpression IContainerRegistry.Endpoint => GetContainerRegistry()?.Endpoint ?? ReferenceExpression.Create($"{ContainerRegistryUrl}");
+
+ private IContainerRegistry? GetContainerRegistry()
+ {
+ // Check for explicit container registry reference annotation
+ if (this.TryGetLastAnnotation(out var annotation))
+ {
+ return annotation.Registry;
+ }
+
+ // Fall back to default container registry
+ return DefaultContainerRegistry;
+ }
- ReferenceExpression IContainerRegistry.Endpoint =>
- ReferenceExpression.Create($"{ContainerRegistryUrl}");
+ ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}");
ReferenceExpression IComputeEnvironmentResource.GetHostAddressExpression(EndpointReference endpointReference)
{
diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceInfrastructure.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceInfrastructure.cs
index 8681ac3a885..8f86f009518 100644
--- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceInfrastructure.cs
+++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceInfrastructure.cs
@@ -32,6 +32,13 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
foreach (var appServiceEnvironment in appServiceEnvironments)
{
+ // Remove the default container registry from the model if an explicit registry is configured
+ if (appServiceEnvironment.HasAnnotationOfType() &&
+ appServiceEnvironment.DefaultContainerRegistry is not null)
+ {
+ @event.Model.Resources.Remove(appServiceEnvironment.DefaultContainerRegistry);
+ }
+
var appServiceEnvironmentContext = new AzureAppServiceEnvironmentContext(
logger,
executionContext,
diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs
index d5fbf5bf2c2..56a2923a961 100644
--- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs
+++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebSiteResource.cs
@@ -7,8 +7,6 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Pipelines;
-using Aspire.Hosting.Publishing;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Aspire.Hosting.Azure;
@@ -29,42 +27,18 @@ public AzureAppServiceWebSiteResource(string name, Action
{
- // Get the registry from the target resource's deployment target annotation
+ // Get the deployment target annotation
var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
- if (deploymentTargetAnnotation?.ContainerRegistry is not IContainerRegistry registry)
+ if (deploymentTargetAnnotation is null)
{
- // No registry available, skip push
return [];
}
var steps = new List();
- if (targetResource.RequiresImageBuildAndPush())
- {
- // Create push step for this deployment target
- var pushStep = new PipelineStep
- {
- Name = $"push-{targetResource.Name}",
- Description = $"Pushes the container image for {targetResource.Name} to Azure Container Registry.",
- Action = async ctx =>
- {
- var containerImageBuilder = ctx.Services.GetRequiredService();
-
- await AzureEnvironmentResourceHelpers.PushImageToRegistryAsync(
- registry,
- targetResource,
- ctx,
- containerImageBuilder).ConfigureAwait(false);
- },
- Tags = [WellKnownPipelineTags.PushContainerImage]
- };
-
- steps.Add(pushStep);
- }
-
if (!targetResource.TryGetEndpoints(out var endpoints))
{
endpoints = [];
@@ -111,26 +85,10 @@ await AzureEnvironmentResourceHelpers.PushImageToRegistryAsync(
// Add pipeline configuration annotation to wire up dependencies
Annotations.Add(new PipelineConfigurationAnnotation((context) =>
{
- // Find the push step for this resource
- var pushSteps = context.GetSteps(this, WellKnownPipelineTags.PushContainerImage);
-
var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
- // Make push step depend on build steps of the target resource
- var buildSteps = context.GetSteps(targetResource, WellKnownPipelineTags.BuildCompute);
-
- pushSteps.DependsOn(buildSteps);
-
- // Make push step depend on the registry being provisioned
- var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
- if (deploymentTargetAnnotation?.ContainerRegistry is IResource registryResource)
- {
- var registryProvisionSteps = context.GetSteps(registryResource, WellKnownPipelineTags.ProvisionInfrastructure);
-
- pushSteps.DependsOn(registryProvisionSteps);
- }
-
- // The app deployment should depend on the push step
+ // The app deployment should depend on push steps from the target resource
+ var pushSteps = context.GetSteps(targetResource, WellKnownPipelineTags.PushContainerImage);
provisionSteps.DependsOn(pushSteps);
// Ensure summary step runs after provision
diff --git a/src/Aspire.Hosting.Azure.ContainerRegistry/Aspire.Hosting.Azure.ContainerRegistry.csproj b/src/Aspire.Hosting.Azure.ContainerRegistry/Aspire.Hosting.Azure.ContainerRegistry.csproj
index 0098a25dab1..85835e0091a 100644
--- a/src/Aspire.Hosting.Azure.ContainerRegistry/Aspire.Hosting.Azure.ContainerRegistry.csproj
+++ b/src/Aspire.Hosting.Azure.ContainerRegistry/Aspire.Hosting.Azure.ContainerRegistry.csproj
@@ -11,6 +11,10 @@
+
+
+
+
diff --git a/src/Aspire.Hosting.Azure/AzureEnvironmentResourceHelpers.cs b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryHelpers.cs
similarity index 56%
rename from src/Aspire.Hosting.Azure/AzureEnvironmentResourceHelpers.cs
rename to src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryHelpers.cs
index 346659723b6..77c1731da61 100644
--- a/src/Aspire.Hosting.Azure/AzureEnvironmentResourceHelpers.cs
+++ b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryHelpers.cs
@@ -9,15 +9,14 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure.Provisioning.Internal;
using Aspire.Hosting.Pipelines;
-using Aspire.Hosting.Publishing;
using Microsoft.Extensions.DependencyInjection;
namespace Aspire.Hosting.Azure;
///
-/// Helper methods for Azure environment resources to handle container image operations.
+/// Helper methods for Azure Container Registry operations.
///
-internal static class AzureEnvironmentResourceHelpers
+internal static class AzureContainerRegistryHelpers
{
public static async Task LoginToRegistryAsync(IContainerRegistry registry, PipelineStepContext context)
{
@@ -25,17 +24,13 @@ public static async Task LoginToRegistryAsync(IContainerRegistry registry, Pipel
var tokenCredentialProvider = context.Services.GetRequiredService();
// Find the AzureEnvironmentResource from the application model
- var azureEnvironment = context.Model.Resources.OfType().FirstOrDefault();
- if (azureEnvironment == null)
- {
+ var azureEnvironment = context.Model.Resources.OfType().FirstOrDefault() ??
throw new InvalidOperationException("AzureEnvironmentResource must be present in the application model.");
- }
-
var registryName = await registry.Name.GetValueAsync(context.CancellationToken).ConfigureAwait(false) ??
- throw new InvalidOperationException("Failed to retrieve container registry information.");
-
+ throw new InvalidOperationException("Failed to retrieve container registry information.");
+
var registryEndpoint = await registry.Endpoint.GetValueAsync(context.CancellationToken).ConfigureAwait(false) ??
- throw new InvalidOperationException("Failed to retrieve container registry endpoint.");
+ throw new InvalidOperationException("Failed to retrieve container registry endpoint.");
var loginTask = await context.ReportingStep.CreateTaskAsync($"Logging in to **{registryName}**", context.CancellationToken).ConfigureAwait(false);
await using (loginTask.ConfigureAwait(false))
@@ -63,32 +58,4 @@ await acrLoginService.LoginAsync(
}
}
}
-
- public static async Task PushImageToRegistryAsync(IContainerRegistry registry, IResource resource, PipelineStepContext context, IResourceContainerImageManager containerImageBuilder)
- {
- var registryName = await registry.Name.GetValueAsync(context.CancellationToken).ConfigureAwait(false) ??
- throw new InvalidOperationException("Failed to retrieve container registry information.");
-
- IValueProvider cir = new ContainerImageReference(resource);
- var targetTag = await cir.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
-
- var pushTask = await context.ReportingStep.CreateTaskAsync($"Pushing **{resource.Name}** to **{registryName}**", context.CancellationToken).ConfigureAwait(false);
- await using (pushTask.ConfigureAwait(false))
- {
- try
- {
- if (targetTag == null)
- {
- throw new InvalidOperationException($"Failed to get target tag for {resource.Name}");
- }
- await containerImageBuilder.PushImageAsync(resource, context.CancellationToken).ConfigureAwait(false);
- await pushTask.CompleteAsync($"Successfully pushed **{resource.Name}** to `{targetTag}`", CompletionState.Completed, context.CancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- await pushTask.CompleteAsync($"Failed to push **{resource.Name}**: {ex.Message}", CompletionState.CompletedWithError, context.CancellationToken).ConfigureAwait(false);
- throw;
- }
- }
- }
}
diff --git a/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryResource.cs b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryResource.cs
index c417cfaf0da..f0c91f471c5 100644
--- a/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryResource.cs
+++ b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryResource.cs
@@ -1,7 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable ASPIREPIPELINES001
+#pragma warning disable ASPIREPIPELINES003
+
using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Pipelines;
using Azure.Provisioning.ContainerRegistry;
using Azure.Provisioning.Primitives;
@@ -10,9 +14,38 @@ namespace Aspire.Hosting.Azure;
///
/// Represents an Azure Container Registry resource.
///
-public class AzureContainerRegistryResource(string name, Action configureInfrastructure)
- : AzureProvisioningResource(name, configureInfrastructure), IContainerRegistry
+public class AzureContainerRegistryResource : AzureProvisioningResource, IContainerRegistry
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the resource.
+ /// The callback to configure the Azure infrastructure for this resource.
+ public AzureContainerRegistryResource(string name, Action configureInfrastructure)
+ : base(name, configureInfrastructure)
+ {
+ Annotations.Add(new PipelineStepAnnotation((factoryContext) =>
+ {
+ var loginStep = new PipelineStep
+ {
+ Name = $"login-to-acr-{name}",
+ Action = context => AzureContainerRegistryHelpers.LoginToRegistryAsync(this, context),
+ Tags = ["acr-login"],
+ RequiredBySteps = [WellKnownPipelineSteps.PushPrereq],
+ Resource = this
+ };
+ return [loginStep];
+ }));
+
+ Annotations.Add(new PipelineConfigurationAnnotation((context) =>
+ {
+ var loginSteps = context.GetSteps(this, "acr-login");
+ var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
+
+ loginSteps.DependsOn(provisionSteps);
+ }));
+ }
+
///
/// The name of the Azure Container Registry.
///
@@ -34,15 +67,15 @@ public override ProvisionableResource AddAsExistingResource(AzureResourceInfrast
{
var bicepIdentifier = this.GetBicepIdentifier();
var resources = infra.GetProvisionableResources();
-
+
// Check if a ContainerRegistryService with the same identifier already exists
var existingStore = resources.OfType().SingleOrDefault(store => store.BicepIdentifier == bicepIdentifier);
-
+
if (existingStore is not null)
{
return existingStore;
}
-
+
// Create and add new resource if it doesn't exist
var store = ContainerRegistryService.FromExisting(bicepIdentifier);
diff --git a/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj b/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj
index c86c7db11f1..c4902bd73ff 100644
--- a/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj
+++ b/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj
@@ -41,8 +41,7 @@
-
-
+
diff --git a/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs b/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs
index 22cd794ce3c..c821a483669 100644
--- a/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs
+++ b/src/Aspire.Hosting/ApplicationModel/ProjectResource.cs
@@ -29,12 +29,14 @@ public class ProjectResource : Resource, IResourceWithEnvironment, IResourceWith
/// The name of the resource.
public ProjectResource(string name) : base(name)
{
- // Add pipeline step annotation to create a build step for this project
+ // Add pipeline step annotation to create build and push steps for this project
Annotations.Add(new PipelineStepAnnotation((factoryContext) =>
{
+ var steps = new List();
+
if (factoryContext.Resource.IsExcludedFromPublish())
{
- return [];
+ return steps;
}
var buildStep = new PipelineStep
@@ -44,10 +46,25 @@ public ProjectResource(string name) : base(name)
Action = BuildProjectImage,
Tags = [WellKnownPipelineTags.BuildCompute],
RequiredBySteps = [WellKnownPipelineSteps.Build],
- DependsOnSteps = [WellKnownPipelineSteps.BuildPrereq]
+ DependsOnSteps = [WellKnownPipelineSteps.BuildPrereq],
+ Resource = this
};
+ steps.Add(buildStep);
- return [buildStep];
+ if (this.RequiresImageBuildAndPush())
+ {
+ var pushStep = new PipelineStep
+ {
+ Name = $"push-{name}",
+ Action = ctx => PipelineStepHelpers.PushImageToRegistryAsync(this, ctx),
+ Tags = [WellKnownPipelineTags.PushContainerImage],
+ RequiredBySteps = [WellKnownPipelineSteps.Push],
+ Resource = this
+ };
+ steps.Add(pushStep);
+ }
+
+ return steps;
}));
// Add default container build options annotation
@@ -70,6 +87,13 @@ public ProjectResource(string name) : base(name)
buildSteps.DependsOn(context.GetSteps(containerFile.Source, WellKnownPipelineTags.BuildCompute));
}
}
+
+ // Wire up dependencies for push steps
+ var projectBuildSteps = context.GetSteps(this, WellKnownPipelineTags.BuildCompute);
+ var pushSteps = context.GetSteps(this, WellKnownPipelineTags.PushContainerImage);
+
+ pushSteps.DependsOn(projectBuildSteps);
+ pushSteps.DependsOn(WellKnownPipelineSteps.PushPrereq);
}));
}
// Keep track of the config host for each Kestrel endpoint annotation
diff --git a/src/Aspire.Hosting/ApplicationModel/RegistryTargetAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/RegistryTargetAnnotation.cs
new file mode 100644
index 00000000000..9f675cf6d0e
--- /dev/null
+++ b/src/Aspire.Hosting/ApplicationModel/RegistryTargetAnnotation.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// Annotation that indicates a resource should use a specific container registry as its default target.
+///
+///
+/// This annotation is automatically added to resources when a container registry is added to the application model.
+/// It provides a default registry for resources that don't have an explicit .
+///
+/// The container registry resource.
+internal sealed class RegistryTargetAnnotation(IContainerRegistry registry) : IResourceAnnotation
+{
+ ///
+ /// Gets the container registry resource.
+ ///
+ public IContainerRegistry Registry { get; } = registry;
+}
diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs b/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs
index 242acb3345d..e41c30b4f5a 100644
--- a/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs
+++ b/src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs
@@ -1132,8 +1132,12 @@ internal static async Task ProcessImagePushOptionsCal
/// The container registry associated with the resource.
/// Thrown when the resource does not have a container registry reference.
///
- /// This method first checks for a container registry in the .
- /// If not found, it falls back to the .
+ /// This method checks for a container registry in the following order:
+ ///
+ /// - The on the resource.
+ /// - The on the resource (set via WithContainerRegistry).
+ /// - The on the resource (automatically added when a registry is added to the app model).
+ ///
///
internal static IContainerRegistry GetContainerRegistry(this IResource resource)
{
@@ -1144,10 +1148,29 @@ internal static IContainerRegistry GetContainerRegistry(this IResource resource)
return deploymentTarget.ContainerRegistry;
}
- // Fall back to ContainerRegistryReferenceAnnotation
- var registryAnnotation = resource.Annotations.OfType().LastOrDefault()
- ?? throw new InvalidOperationException($"Resource '{resource.Name}' does not have a container registry reference.");
- return registryAnnotation.Registry;
+ // Try ContainerRegistryReferenceAnnotation (explicit WithContainerRegistry call)
+ var registryAnnotation = resource.Annotations.OfType().LastOrDefault();
+ if (registryAnnotation is not null)
+ {
+ return registryAnnotation.Registry;
+ }
+
+ // Fall back to RegistryTargetAnnotation (added automatically via BeforeStartEvent)
+ var registryTargetAnnotations = resource.Annotations.OfType().ToArray();
+ if (registryTargetAnnotations.Length == 1)
+ {
+ return registryTargetAnnotations[0].Registry;
+ }
+
+ if (registryTargetAnnotations.Length > 1)
+ {
+ var registryNames = string.Join(", ", registryTargetAnnotations.Select(a => a.Registry is IResource res ? res.Name : a.Registry.ToString()));
+ throw new InvalidOperationException(
+ $"Resource '{resource.Name}' has multiple container registries available - '{registryNames}'. " +
+ $"Please specify which registry to use with '.WithContainerRegistry(registryBuilder)'.");
+ }
+
+ throw new InvalidOperationException($"Resource '{resource.Name}' does not have a container registry reference.");
}
///
diff --git a/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs b/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs
index 63d76e1310d..70f95b21bff 100644
--- a/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs
+++ b/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs
@@ -4,6 +4,8 @@
using System.Diagnostics.CodeAnalysis;
using Aspire.Hosting.ApplicationModel;
+#pragma warning disable ASPIRECOMPUTE003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
namespace Aspire.Hosting;
///
@@ -50,9 +52,13 @@ public static IResourceBuilder AddContainerRegistry(
var resource = new ContainerRegistryResource(name, endpointExpression, repositoryExpression);
- return builder.ExecutionContext.IsRunMode
+ var resourceBuilder = builder.ExecutionContext.IsRunMode
? builder.CreateResourceBuilder(resource)
: builder.AddResource(resource);
+
+ SubscribeToAddRegistryTargetAnnotations(builder, resource);
+
+ return resourceBuilder;
}
///
@@ -98,9 +104,30 @@ public static IResourceBuilder AddContainerRegistry(
var resource = new ContainerRegistryResource(name, endpointExpression, repositoryExpression);
- return builder.ExecutionContext.IsRunMode
+ var resourceBuilder = builder.ExecutionContext.IsRunMode
? builder.CreateResourceBuilder(resource)
: builder.AddResource(resource);
+
+ SubscribeToAddRegistryTargetAnnotations(builder, resource);
+
+ return resourceBuilder;
+ }
+
+ ///
+ /// Subscribes to BeforeStartEvent to add RegistryTargetAnnotation to all resources in the model.
+ ///
+ private static void SubscribeToAddRegistryTargetAnnotations(IDistributedApplicationBuilder builder, ContainerRegistryResource registry)
+ {
+ builder.Eventing.Subscribe((beforeStartEvent, cancellationToken) =>
+ {
+ foreach (var resource in beforeStartEvent.Model.Resources)
+ {
+ // Add a RegistryTargetAnnotation to indicate this registry is available as a default target
+ resource.Annotations.Add(new RegistryTargetAnnotation(registry));
+ }
+
+ return Task.CompletedTask;
+ });
}
///
diff --git a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs
index 28e7b75c867..a42d815a369 100644
--- a/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs
+++ b/src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs
@@ -23,39 +23,65 @@ namespace Aspire.Hosting;
public static class ContainerResourceBuilderExtensions
{
///
- /// Ensures that a container resource has a PipelineStepAnnotation for building if it has a DockerfileBuildAnnotation.
+ /// Ensures that a container resource has PipelineStepAnnotations for building and pushing.
///
/// The type of container resource.
/// The resource builder.
- internal static IResourceBuilder EnsureBuildPipelineStepAnnotation(this IResourceBuilder builder) where T : ContainerResource
+ internal static IResourceBuilder EnsureBuildAndPushPipelineAnnotations(this IResourceBuilder builder) where T : ContainerResource
{
- // Use replace semantics to ensure we only have one PipelineStepAnnotation for building
- return builder.WithAnnotation(new PipelineStepAnnotation((factoryContext) =>
+ builder.WithAnnotation(new PipelineStepAnnotation((factoryContext) =>
{
- if (!builder.Resource.RequiresImageBuild() || builder.Resource.IsExcludedFromPublish())
+ var steps = new List();
+
+ if (builder.Resource.IsExcludedFromPublish())
{
- return [];
+ return steps;
}
- var buildStep = new PipelineStep
+ if (builder.Resource.RequiresImageBuild())
{
- Name = $"build-{builder.Resource.Name}",
- Description = $"Builds the container image for the {builder.Resource.Name} container.",
- Action = async ctx =>
+ var buildStep = new PipelineStep
{
- var containerImageBuilder = ctx.Services.GetRequiredService();
-
- await containerImageBuilder.BuildImageAsync(
- builder.Resource,
- ctx.CancellationToken).ConfigureAwait(false);
- },
- Tags = [WellKnownPipelineTags.BuildCompute],
- RequiredBySteps = [WellKnownPipelineSteps.Build],
- DependsOnSteps = [WellKnownPipelineSteps.BuildPrereq]
- };
+ Name = $"build-{builder.Resource.Name}",
+ Action = async ctx =>
+ {
+ var containerImageBuilder = ctx.Services.GetRequiredService();
+ await containerImageBuilder.BuildImageAsync(
+ builder.Resource,
+ ctx.CancellationToken).ConfigureAwait(false);
+ },
+ Tags = [WellKnownPipelineTags.BuildCompute],
+ RequiredBySteps = [WellKnownPipelineSteps.Build],
+ DependsOnSteps = [WellKnownPipelineSteps.BuildPrereq],
+ Resource = builder.Resource
+ };
+ steps.Add(buildStep);
+ }
+
+ if (builder.Resource.RequiresImageBuildAndPush())
+ {
+ var pushStep = new PipelineStep
+ {
+ Name = $"push-{builder.Resource.Name}",
+ Action = ctx => PipelineStepHelpers.PushImageToRegistryAsync(builder.Resource, ctx),
+ Tags = [WellKnownPipelineTags.PushContainerImage],
+ RequiredBySteps = [WellKnownPipelineSteps.Push],
+ Resource = builder.Resource
+ };
+ steps.Add(pushStep);
+ }
- return [buildStep];
+ return steps;
}), ResourceAnnotationMutationBehavior.Replace);
+
+ return builder.WithAnnotation(new PipelineConfigurationAnnotation(context =>
+ {
+ var buildSteps = context.GetSteps(builder.Resource, WellKnownPipelineTags.BuildCompute);
+ var pushSteps = context.GetSteps(builder.Resource, WellKnownPipelineTags.PushContainerImage);
+
+ pushSteps.DependsOn(buildSteps);
+ pushSteps.DependsOn(WellKnownPipelineSteps.PushPrereq);
+ }), ResourceAnnotationMutationBehavior.Append);
}
///
@@ -560,11 +586,13 @@ public static IResourceBuilder WithDockerfile(this IResourceBuilder bui
{
context.LocalImageName = dockerfileAnnotation.ImageName ?? context.Resource.Name;
context.LocalImageTag = dockerfileAnnotation.ImageTag ?? "latest";
+ context.TargetPlatform = ContainerTargetPlatform.LinuxAmd64;
}
else
{
context.LocalImageName = context.Resource.Name;
context.LocalImageTag = "latest";
+ context.TargetPlatform = ContainerTargetPlatform.LinuxAmd64;
}
context.TargetPlatform = ContainerTargetPlatform.LinuxAmd64;
});
@@ -577,7 +605,7 @@ public static IResourceBuilder WithDockerfile(this IResourceBuilder bui
annotation.ImageTag = imageTag;
return builder.WithAnnotation(annotation, ResourceAnnotationMutationBehavior.Replace)
.WithAnnotation(defaultContainerBuildOptions)
- .EnsureBuildPipelineStepAnnotation();
+ .EnsureBuildAndPushPipelineAnnotations();
}
return builder.WithAnnotation(annotation, ResourceAnnotationMutationBehavior.Replace)
@@ -585,7 +613,7 @@ public static IResourceBuilder WithDockerfile(this IResourceBuilder bui
.WithImageRegistry(registry: null)
.WithImage(imageName)
.WithImageTag(imageTag)
- .EnsureBuildPipelineStepAnnotation();
+ .EnsureBuildAndPushPipelineAnnotations();
}
///
@@ -700,11 +728,13 @@ public static IResourceBuilder WithDockerfileFactory(this IResourceBuilder
{
context.LocalImageName = dockerfileAnnotation.ImageName ?? context.Resource.Name;
context.LocalImageTag = dockerfileAnnotation.ImageTag ?? "latest";
+ context.TargetPlatform = ContainerTargetPlatform.LinuxAmd64;
}
else
{
context.LocalImageName = context.Resource.Name;
context.LocalImageTag = "latest";
+ context.TargetPlatform = ContainerTargetPlatform.LinuxAmd64;
}
context.TargetPlatform = ContainerTargetPlatform.LinuxAmd64;
});
@@ -717,7 +747,7 @@ public static IResourceBuilder WithDockerfileFactory(this IResourceBuilder
annotation.ImageTag = imageTag;
return builder.WithAnnotation(annotation, ResourceAnnotationMutationBehavior.Replace)
.WithAnnotation(defaultContainerBuildOptions, ResourceAnnotationMutationBehavior.Append)
- .EnsureBuildPipelineStepAnnotation();
+ .EnsureBuildAndPushPipelineAnnotations();
}
return builder.WithAnnotation(annotation, ResourceAnnotationMutationBehavior.Replace)
@@ -725,7 +755,7 @@ public static IResourceBuilder WithDockerfileFactory(this IResourceBuilder
.WithImageRegistry(registry: null)
.WithImage(imageName)
.WithImageTag(imageTag)
- .EnsureBuildPipelineStepAnnotation();
+ .EnsureBuildAndPushPipelineAnnotations();
}
///
diff --git a/src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs b/src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs
index e01b3951986..feb10891129 100644
--- a/src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs
+++ b/src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs
@@ -152,6 +152,69 @@ public DistributedApplicationPipeline()
Action = context => Task.CompletedTask
});
+ // Add a default "Push" meta-step that all push steps should be required by
+ // Push unconditionally depends on PushPrereq to ensure annotations are set up
+ var pushStep = new PipelineStep
+ {
+ Name = WellKnownPipelineSteps.Push,
+ Description = "Aggregation step for all push operations. All push steps should be required by this step.",
+ Action = _ => Task.CompletedTask
+ };
+ pushStep.DependsOn(WellKnownPipelineSteps.PushPrereq);
+ _steps.Add(pushStep);
+
+ _steps.Add(new PipelineStep
+ {
+ Name = WellKnownPipelineSteps.PushPrereq,
+ Description = "Prerequisite step that runs before any push operations.",
+ Action = context =>
+ {
+ foreach (var resource in context.Model.Resources)
+ {
+ if (!resource.RequiresImageBuildAndPush())
+ {
+ continue;
+ }
+
+ // Skip if resource already has a ContainerRegistryReferenceAnnotation (explicit WithContainerRegistry call)
+ if (resource.TryGetAnnotationsIncludingAncestorsOfType(out var annotations) &&
+ annotations.Any())
+ {
+ continue;
+ }
+
+ // Skip if resource has a deployment target with a ContainerRegistry set
+ var deploymentTargetAnnotation = resource.GetDeploymentTargetAnnotation();
+ if (deploymentTargetAnnotation?.ContainerRegistry is not null)
+ {
+ continue;
+ }
+
+ // Check for RegistryTargetAnnotations (automatically added via BeforeStartEvent)
+ var registryTargetAnnotations = resource.Annotations.OfType().ToArray();
+
+ // When multiple registries exist, require explicit WithContainerRegistry call
+ if (registryTargetAnnotations.Length > 1)
+ {
+ var registryNames = string.Join(", ", registryTargetAnnotations.Select(a => a.Registry is IResource res ? res.Name : a.Registry.ToString()));
+ throw new InvalidOperationException(
+ $"Resource '{resource.Name}' requires image push but has multiple container registries available - '{registryNames}'. " +
+ $"Please specify which registry to use with '.WithContainerRegistry(registryBuilder)'.");
+ }
+
+ // When no registry is available, throw an error
+ if (registryTargetAnnotations.Length == 0)
+ {
+ throw new InvalidOperationException(
+ $"Resource '{resource.Name}' requires image push but no container registry is available. " +
+ $"Please add a container registry using 'builder.AddContainerRegistry(...)' or specify one with '.WithContainerRegistry(registryBuilder)'.");
+ }
+ }
+
+ return Task.CompletedTask;
+ },
+ });
+
// Add a default "Publish" aggregation step that all publish steps should be required by
_steps.Add(new PipelineStep
{
diff --git a/src/Aspire.Hosting/Pipelines/PipelineStepHelpers.cs b/src/Aspire.Hosting/Pipelines/PipelineStepHelpers.cs
new file mode 100644
index 00000000000..96422165c37
--- /dev/null
+++ b/src/Aspire.Hosting/Pipelines/PipelineStepHelpers.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPIREPIPELINES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable ASPIREPIPELINES003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable ASPIRECONTAINERRUNTIME001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Publishing;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Aspire.Hosting.Pipelines;
+
+///
+/// Helper methods for common pipeline step operations.
+///
+internal static class PipelineStepHelpers
+{
+ ///
+ /// Pushes a container image to a registry with proper logging and task reporting.
+ ///
+ /// The resource whose image should be pushed.
+ /// The pipeline step context for logging and task reporting.
+ /// A task representing the async operation.
+ public static async Task PushImageToRegistryAsync(IResource resource, PipelineStepContext context)
+ {
+ var registry = resource.GetContainerRegistry();
+ var registryName = await registry.Name.GetValueAsync(context.CancellationToken).ConfigureAwait(false)
+ ?? throw new InvalidOperationException("Failed to retrieve container registry information.");
+
+ IValueProvider cir = new ContainerImageReference(resource);
+ var targetTag = await cir.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
+
+ var pushTask = await context.ReportingStep.CreateTaskAsync(
+ $"Pushing **{resource.Name}** to **{registryName}**",
+ context.CancellationToken).ConfigureAwait(false);
+
+ await using (pushTask.ConfigureAwait(false))
+ {
+ try
+ {
+ if (targetTag is null)
+ {
+ throw new InvalidOperationException($"Failed to get target tag for {resource.Name}");
+ }
+
+ var containerImageManager = context.Services.GetRequiredService();
+ await containerImageManager.PushImageAsync(resource, context.CancellationToken).ConfigureAwait(false);
+
+ await pushTask.CompleteAsync(
+ $"Successfully pushed **{resource.Name}** to `{targetTag}`",
+ CompletionState.Completed,
+ context.CancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ await pushTask.CompleteAsync(
+ $"Failed to push **{resource.Name}**: {ex.Message}",
+ CompletionState.CompletedWithError,
+ context.CancellationToken).ConfigureAwait(false);
+ throw;
+ }
+ }
+ }
+}
diff --git a/src/Aspire.Hosting/Pipelines/WellKnownPipelineSteps.cs b/src/Aspire.Hosting/Pipelines/WellKnownPipelineSteps.cs
index 7d15fb4d325..855271cefa1 100644
--- a/src/Aspire.Hosting/Pipelines/WellKnownPipelineSteps.cs
+++ b/src/Aspire.Hosting/Pipelines/WellKnownPipelineSteps.cs
@@ -48,6 +48,17 @@ public static class WellKnownPipelineSteps
///
public const string BuildPrereq = "build-prereq";
+ ///
+ /// The meta-step that coordinates all push operations.
+ /// All push steps should be required by this step.
+ ///
+ public const string Push = "push";
+
+ ///
+ /// The prerequisite step that runs before any push operations.
+ ///
+ public const string PushPrereq = "push-prereq";
+
///
/// The diagnostic step that dumps dependency graph information for troubleshooting.
///
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
index 53a05e3a9c5..a7c3f8cfa3b 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
@@ -5,10 +5,12 @@
#pragma warning disable ASPIRECOMPUTE002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable ASPIREAZURE002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
#pragma warning disable ASPIREDOCKERFILEBUILDER001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable ASPIREPIPELINES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
using System.Text.Json.Nodes;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure.AppContainers;
+using Aspire.Hosting.Pipelines;
using Aspire.Hosting.Utils;
using Azure.Provisioning;
using Azure.Provisioning.AppContainers;
@@ -2051,4 +2053,115 @@ public async Task GetHostAddressExpression()
Assert.Equal(env.Resource, output.Resource);
Assert.Equal("AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN", output.Name);
}
+
+ [Fact]
+ public async Task ContainerAppProvisionDependsOnTargetPushStep()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddAzureContainerAppEnvironment("env");
+ builder.AddProject("api", launchProfileName: null)
+ .WithHttpEndpoint();
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var model = app.Services.GetRequiredService();
+ var projectResource = Assert.Single(model.GetProjectResources());
+
+ projectResource.TryGetLastAnnotation(out var target);
+ var containerAppResource = target?.DeploymentTarget as AzureContainerAppResource;
+ Assert.NotNull(containerAppResource);
+
+ var configAnnotations = containerAppResource.Annotations.OfType().ToList();
+ Assert.NotEmpty(configAnnotations);
+ }
+
+ [Fact]
+ public async Task EnvironmentCreatesDefaultAcrWhenNoExplicitRegistry()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddAzureContainerAppEnvironment("env");
+
+ builder.AddProject("api", launchProfileName: null)
+ .WithHttpEndpoint();
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var model = app.Services.GetRequiredService();
+
+ var acrResources = model.Resources.OfType().ToList();
+ Assert.Single(acrResources);
+
+ var defaultAcr = acrResources[0];
+ Assert.Contains("acr", defaultAcr.Name);
+ }
+
+ [Fact]
+ public async Task DefaultAcrNotAddedToModelWhenExplicitRegistryExists()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var customRegistry = builder.AddAzureContainerRegistry("customregistry");
+ builder.AddAzureContainerAppEnvironment("env")
+ .WithAzureContainerRegistry(customRegistry);
+
+ builder.AddProject("api", launchProfileName: null)
+ .WithHttpEndpoint();
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var model = app.Services.GetRequiredService();
+
+ var acrResources = model.Resources.OfType().ToList();
+ Assert.Single(acrResources);
+ Assert.Equal("customregistry", acrResources[0].Name);
+ }
+
+ [Fact]
+ public async Task EnvironmentDelegatesToAssociatedRegistry()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var customRegistry = builder.AddAzureContainerRegistry("customregistry");
+ var env = builder.AddAzureContainerAppEnvironment("env")
+ .WithAzureContainerRegistry(customRegistry);
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var containerRegistryInterface = env.Resource as IContainerRegistry;
+ Assert.NotNull(containerRegistryInterface);
+ Assert.NotNull(containerRegistryInterface.Endpoint);
+ Assert.NotNull(containerRegistryInterface.Name);
+ }
+
+ [Fact]
+ public async Task DefaultContainerRegistryUsesAzdNamingWhenEnvironmentDoes()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddAzureContainerAppEnvironment("env")
+ .WithAzdResourceNaming();
+
+ builder.AddProject("api", launchProfileName: null)
+ .WithHttpEndpoint();
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var model = app.Services.GetRequiredService();
+
+ var acrResources = model.Resources.OfType().ToList();
+ Assert.Single(acrResources);
+
+ var defaultAcr = acrResources[0];
+ var (manifest, bicep) = await GetManifestWithBicep(defaultAcr);
+
+ await Verify(manifest.ToString(), "json")
+ .AppendContentAsFile(bicep, "bicep");
+ }
}
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureContainerRegistryTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureContainerRegistryTests.cs
index a6c5e0be56a..b505388c6b8 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureContainerRegistryTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureContainerRegistryTests.cs
@@ -1,8 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable ASPIREPIPELINES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure.AppContainers;
+using Aspire.Hosting.Pipelines;
using Aspire.Hosting.Utils;
using Azure.Provisioning.ContainerRegistry;
using Microsoft.Extensions.DependencyInjection;
@@ -127,6 +130,105 @@ await Verify(manifest.ToString(), "json")
.AppendContentAsFile(bicep, "bicep");
}
+ [Fact]
+ public async Task AzureContainerRegistryHasLoginStep()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var acr = builder.AddAzureContainerRegistry("acr");
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var pipelineStepAnnotations = acr.Resource.Annotations.OfType().ToList();
+ Assert.True(pipelineStepAnnotations.Count >= 2);
+
+ var factoryContext = new PipelineStepFactoryContext
+ {
+ PipelineContext = null!,
+ Resource = acr.Resource
+ };
+
+ var allSteps = new List();
+ foreach (var annotation in pipelineStepAnnotations)
+ {
+ allSteps.AddRange(await annotation.CreateStepsAsync(factoryContext));
+ }
+
+ var loginStep = allSteps.FirstOrDefault(s => s.Name == "login-to-acr-acr");
+ Assert.NotNull(loginStep);
+ Assert.Contains("acr-login", loginStep.Tags);
+ }
+
+ [Fact]
+ public async Task LoginStepRequiredByPushPrereq()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var acr = builder.AddAzureContainerRegistry("acr");
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var pipelineStepAnnotations = acr.Resource.Annotations.OfType().ToList();
+
+ var factoryContext = new PipelineStepFactoryContext
+ {
+ PipelineContext = null!,
+ Resource = acr.Resource
+ };
+
+ var allSteps = new List();
+ foreach (var annotation in pipelineStepAnnotations)
+ {
+ allSteps.AddRange(await annotation.CreateStepsAsync(factoryContext));
+ }
+
+ var loginStep = allSteps.FirstOrDefault(s => s.Name == "login-to-acr-acr");
+ Assert.NotNull(loginStep);
+ Assert.Contains(WellKnownPipelineSteps.PushPrereq, loginStep.RequiredBySteps);
+ }
+
+ [Fact]
+ public async Task AzureContainerRegistryHasProvisionStep()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var acr = builder.AddAzureContainerRegistry("acr");
+
+ using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ var pipelineStepAnnotations = acr.Resource.Annotations.OfType().ToList();
+
+ var factoryContext = new PipelineStepFactoryContext
+ {
+ PipelineContext = null!,
+ Resource = acr.Resource
+ };
+
+ var allSteps = new List();
+ foreach (var annotation in pipelineStepAnnotations)
+ {
+ allSteps.AddRange(await annotation.CreateStepsAsync(factoryContext));
+ }
+
+ var provisionStep = allSteps.FirstOrDefault(s => s.Name == "provision-acr");
+ Assert.NotNull(provisionStep);
+ Assert.Contains(WellKnownPipelineTags.ProvisionInfrastructure, provisionStep.Tags);
+ }
+
+ [Fact]
+ public void LoginStepDependsOnProvisionStep()
+ {
+ var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var acr = builder.AddAzureContainerRegistry("acr");
+
+ var configAnnotations = acr.Resource.Annotations.OfType().ToList();
+ Assert.True(configAnnotations.Count >= 2);
+ }
+
private sealed class Project : IProjectMetadata
{
public string ProjectPath => "project";
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs
index 330df7206e3..cb81da9e83d 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs
@@ -128,13 +128,25 @@ public async Task DeployAsync_WithBuildOnlyContainers()
// Arrange
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
var mockActivityReporter = new TestPublishingActivityReporter(testOutputHelper);
- var armClientProvider = new TestArmClientProvider(new Dictionary
+ var armClientProvider = new TestArmClientProvider(deploymentName =>
{
- ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
- ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
- ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ return deploymentName switch
+ {
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
+ string name when name.StartsWith("env") => new Dictionary
+ {
+ ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
+ ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
+ ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ },
+ _ => []
+ };
});
ConfigureTestServices(builder, armClientProvider: armClientProvider, activityReporter: mockActivityReporter);
@@ -177,13 +189,25 @@ public async Task DeployAsync_WithAzureStorageResourcesWorks()
var mockActivityReporter = new TestPublishingActivityReporter(testOutputHelper);
var fakeContainerRuntime = new FakeContainerRuntime();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
- var armClientProvider = new TestArmClientProvider(new Dictionary
+ var armClientProvider = new TestArmClientProvider(deploymentName =>
{
- ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
- ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
- ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ return deploymentName switch
+ {
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
+ string name when name.StartsWith("env") => new Dictionary
+ {
+ ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
+ ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
+ ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ },
+ _ => []
+ };
});
ConfigureTestServices(builder, armClientProvider: armClientProvider, activityReporter: mockActivityReporter, containerRuntime: fakeContainerRuntime);
@@ -224,13 +248,25 @@ public async Task DeployAsync_WithContainer_Works()
var mockProcessRunner = new MockProcessRunner();
var fakeContainerRuntime = new FakeContainerRuntime();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
- var armClientProvider = new TestArmClientProvider(new Dictionary
+ var armClientProvider = new TestArmClientProvider(deploymentName =>
{
- ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
- ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
- ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ return deploymentName switch
+ {
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
+ string name when name.StartsWith("env") => new Dictionary
+ {
+ ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
+ ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
+ ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ },
+ _ => []
+ };
});
ConfigureTestServices(builder, armClientProvider: armClientProvider, activityReporter: mockActivityReporter, processRunner: mockProcessRunner, containerRuntime: fakeContainerRuntime);
@@ -272,13 +308,25 @@ public async Task DeployAsync_WithDockerfile_Works()
var mockProcessRunner = new MockProcessRunner();
var fakeContainerRuntime = new FakeContainerRuntime();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
- var armClientProvider = new TestArmClientProvider(new Dictionary
+ var armClientProvider = new TestArmClientProvider(deploymentName =>
{
- ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
- ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
- ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ return deploymentName switch
+ {
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
+ string name when name.StartsWith("env") => new Dictionary
+ {
+ ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
+ ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
+ ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ },
+ _ => []
+ };
});
ConfigureTestServices(builder, armClientProvider: armClientProvider, activityReporter: mockActivityReporter, processRunner: mockProcessRunner, containerRuntime: fakeContainerRuntime);
@@ -326,13 +374,25 @@ public async Task DeployAsync_WithProjectResource_Works()
var fakeContainerRuntime = new FakeContainerRuntime();
var mockActivityReporter = new TestPublishingActivityReporter(testOutputHelper);
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
- var armClientProvider = new TestArmClientProvider(new Dictionary
+ var armClientProvider = new TestArmClientProvider(deploymentName =>
{
- ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
- ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
- ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ return deploymentName switch
+ {
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
+ string name when name.StartsWith("env") => new Dictionary
+ {
+ ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
+ ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
+ ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ },
+ _ => []
+ };
});
ConfigureTestServices(builder, armClientProvider: armClientProvider, processRunner: mockProcessRunner, activityReporter: mockActivityReporter, containerRuntime: fakeContainerRuntime);
@@ -386,6 +446,11 @@ public async Task DeployAsync_WithMultipleComputeEnvironments_Works(string step)
{
return deploymentName switch
{
+ string name when name.StartsWith("aca-env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "acaregistry" },
+ ["loginServer"] = new { type = "String", value = "acaregistry.azurecr.io" }
+ },
string name when name.StartsWith("aca-env") => new Dictionary
{
["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "acaregistry" },
@@ -395,6 +460,11 @@ public async Task DeployAsync_WithMultipleComputeEnvironments_Works(string step)
["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "aca.westus.azurecontainerapps.io" },
["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/acaenv" }
},
+ string name when name.StartsWith("aas-env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "aasregistry" },
+ ["loginServer"] = new { type = "String", value = "aasregistry.azurecr.io" }
+ },
string name when name.StartsWith("aas-env") => new Dictionary
{
["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "aasregistry" },
@@ -605,13 +675,25 @@ public async Task DeployAsync_WithSingleRedisCache_CallsDeployingComputeResource
var fakeContainerRuntime = new FakeContainerRuntime();
var mockActivityReporter = new TestPublishingActivityReporter(testOutputHelper);
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
- var armClientProvider = new TestArmClientProvider(new Dictionary
+ var armClientProvider = new TestArmClientProvider(deploymentName =>
{
- ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
- ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
- ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ return deploymentName switch
+ {
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
+ string name when name.StartsWith("env") => new Dictionary
+ {
+ ["AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
+ ["AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
+ ["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ },
+ _ => []
+ };
});
ConfigureTestServices(builder, armClientProvider: armClientProvider, processRunner: mockProcessRunner, activityReporter: mockActivityReporter);
@@ -657,10 +739,22 @@ public async Task DeployAsync_WithOnlyAzureResources_PrintsDashboardUrl()
var fakeContainerRuntime = new FakeContainerRuntime();
var mockActivityReporter = new TestPublishingActivityReporter(testOutputHelper);
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
- var armClientProvider = new TestArmClientProvider(new Dictionary
+ var armClientProvider = new TestArmClientProvider(deploymentName =>
{
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
- ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ return deploymentName switch
+ {
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
+ string name when name.StartsWith("env") => new Dictionary
+ {
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
+ ["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
+ },
+ _ => []
+ };
});
ConfigureTestServices(builder, armClientProvider: armClientProvider, processRunner: mockProcessRunner, activityReporter: mockActivityReporter);
@@ -815,6 +909,11 @@ public async Task DeployAsync_WithAzureFunctionsProject_Works()
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, step: WellKnownPipelineSteps.Deploy);
var deploymentOutputsProvider = (string deploymentName) => deploymentName switch
{
+ string name when name.StartsWith("env-acr") => new Dictionary
+ {
+ ["name"] = new { type = "String", value = "testregistry" },
+ ["loginServer"] = new { type = "String", value = "testregistry.azurecr.io" }
+ },
string name when name.StartsWith("env") => new Dictionary
{
["name"] = new { type = "String", value = "env" },
diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureUserAssignedIdentityTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureUserAssignedIdentityTests.cs
index 97c1806fa9a..802259687ff 100644
--- a/tests/Aspire.Hosting.Azure.Tests/AzureUserAssignedIdentityTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/AzureUserAssignedIdentityTests.cs
@@ -69,21 +69,22 @@ public async Task AddAzureUserAssignedIdentity_WithRoleAssignments_Works()
var model = app.Services.GetRequiredService();
await ExecuteBeforeStartHooksAsync(app, default);
- Assert.Collection(model.Resources.OrderBy(r => r.Name),
+ Assert.Collection(model.Resources,
r => Assert.IsType(r),
+ r => Assert.IsType(r),
r => Assert.IsType(r),
+ r => Assert.IsType(r),
r => Assert.IsType(r),
r =>
{
Assert.IsType(r);
Assert.Equal("myidentity-roles-myregistry", r.Name);
- },
- r => Assert.IsType(r));
+ });
var identityResource = Assert.Single(model.Resources.OfType());
var (_, identityBicep) = await GetManifestWithBicep(identityResource, skipPreparer: true);
- var registryResource = Assert.Single(model.Resources.OfType());
+ var registryResource = Assert.Single(model.Resources.OfType(), r => r.Name == "myregistry");
var (_, registryBicep) = await GetManifestWithBicep(registryResource, skipPreparer: true);
var identityRoleAssignments = Assert.Single(model.Resources.OfType(), r => r.Name == "myidentity-roles-myregistry");
@@ -153,6 +154,7 @@ public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_Works()
// Validate that only the resources we expect to see are in the model
Assert.Collection(model.Resources,
r => Assert.IsType(r),
+ r => Assert.IsType(r),
r => Assert.IsType(r),
r => Assert.IsType(r),
r => Assert.IsType(r),
@@ -208,6 +210,7 @@ public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_AzureAppServ
// Validate that only the resources we expect to see are in the model
Assert.Collection(model.Resources,
r => Assert.IsType(r),
+ r => Assert.IsType(r),
r => Assert.IsType(r),
r => Assert.IsType(r),
r => Assert.IsType(r),
@@ -284,6 +287,7 @@ public async Task WithAzureUserAssignedIdentity_WithRoleAssignments_MultipleProj
// Validate that only the resources we expect to see are in the model
Assert.Collection(model.Resources,
r => Assert.IsType(r),
+ r => Assert.IsType(r),
r => Assert.IsType(r),
r => Assert.IsType(r),
r => Assert.IsType(r),
diff --git a/tests/Aspire.Hosting.Azure.Tests/ContainerRegistryTests.cs b/tests/Aspire.Hosting.Azure.Tests/ContainerRegistryTests.cs
index ab75fb4d602..9235020dd26 100644
--- a/tests/Aspire.Hosting.Azure.Tests/ContainerRegistryTests.cs
+++ b/tests/Aspire.Hosting.Azure.Tests/ContainerRegistryTests.cs
@@ -106,8 +106,6 @@ public async Task ContainerRegistryInfoIsAccessibleFromPublisher()
// Verify the container registry properties on the environment
Assert.NotNull(publisher.EnvironmentRegistry.Name);
Assert.NotNull(publisher.EnvironmentRegistry.Endpoint);
- var azureRegistry = Assert.IsType(publisher.EnvironmentRegistry, exactMatch: false);
- Assert.NotNull(azureRegistry.ManagedIdentityId);
// Verify container registry info was found in child compute resources
Assert.True(publisher.ComputeResourceRegistryFound);
@@ -116,17 +114,18 @@ public async Task ContainerRegistryInfoIsAccessibleFromPublisher()
// Verify the container registry properties on the compute resource
Assert.NotNull(publisher.ComputeResourceRegistry.Name);
Assert.NotNull(publisher.ComputeResourceRegistry.Endpoint);
- azureRegistry = Assert.IsType(publisher.ComputeResourceRegistry, exactMatch: false);
- Assert.NotNull(azureRegistry.ManagedIdentityId);
- // Verify both registries are the same instance (or at least have the same values)
+ // Verify both registries have the same values
Assert.Equal(publisher.EnvironmentRegistry.Name.ToString(),
publisher.ComputeResourceRegistry.Name.ToString());
Assert.Equal(publisher.EnvironmentRegistry.Endpoint.ToString(),
publisher.ComputeResourceRegistry.Endpoint.ToString());
- azureRegistry = Assert.IsType(publisher.EnvironmentRegistry, exactMatch: false);
- Assert.Equal(publisher.AzureContainerRegistry?.ManagedIdentityId.ToString(),
- azureRegistry.ManagedIdentityId.ToString());
+
+ // If the registry is an Azure container registry with managed identity, verify it
+ if (publisher.EnvironmentRegistry is IAzureContainerRegistry azureRegistry)
+ {
+ Assert.NotNull(azureRegistry.ManagedIdentityId);
+ }
}
///
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.bicep
index 4fc6860b1c3..a9a464c42ef 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.bicep
@@ -1,23 +1,20 @@
-@description('The location for the resource(s) to be deployed.')
+@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -50,7 +47,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.json
index 0bc8a391d67..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.json
@@ -1,7 +1,8 @@
-{
+{
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
-}
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.bicep
index 04a7112eac6..cae57cbe6d9 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.bicep
@@ -5,19 +5,16 @@ param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -134,7 +131,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.json
index d3afcb328bb..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithAutomaticScaling.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.bicep
index 6ef1ab716fc..fefad8e0fb2 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.bicep
@@ -1,23 +1,20 @@
-@description('The location for the resource(s) to be deployed.')
+@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -151,7 +148,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
@@ -167,4 +164,4 @@ output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env'
output AZURE_APPLICATION_INSIGHTS_INSTRUMENTATIONKEY string = env_ai.properties.InstrumentationKey
-output AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING string = env_ai.properties.ConnectionString
+output AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING string = env_ai.properties.ConnectionString
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.json
index 0bc8a391d67..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsDefaultLocation.verified.json
@@ -1,7 +1,8 @@
-{
+{
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
-}
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.bicep
index f44fa16e1d0..db0e5c88d55 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.bicep
@@ -1,23 +1,20 @@
-@description('The location for the resource(s) to be deployed.')
+@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -151,7 +148,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.json
index 0bc8a391d67..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocation.verified.json
@@ -1,7 +1,8 @@
-{
+{
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
-}
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.bicep
index 090d3108606..d4e7519f40c 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.bicep
@@ -1,10 +1,12 @@
-@description('The location for the resource(s) to be deployed.')
+@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
param appInsightsLocation string
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
@@ -13,13 +15,8 @@ resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' =
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -153,7 +150,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.json
index f1763823cd6..43767c55cff 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsLocationParam.verified.json
@@ -1,8 +1,9 @@
-{
+{
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"appInsightsLocation": "{appInsightsLocation.value}",
"userPrincipalId": ""
}
-}
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.bicep
index bc7c5590c6d..742706e45e3 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.bicep
@@ -1,10 +1,12 @@
-@description('The location for the resource(s) to be deployed.')
+@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
param existingappinsights_outputs_name string
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
@@ -13,13 +15,8 @@ resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' =
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -136,7 +133,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.json
index f18a373f1f8..75f212c0623 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithExistingApplicationInsights.verified.json
@@ -1,8 +1,9 @@
-{
+{
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"existingappinsights_outputs_name": "{existingAppInsights.outputs.name}",
"userPrincipalId": ""
}
-}
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep
index 4a03ba0008c..d0db9433307 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep
@@ -1,23 +1,20 @@
-@description('The location for the resource(s) to be deployed.')
+@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -130,7 +127,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.json
index d3afcb328bb..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep
index 4a03ba0008c..d0db9433307 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep
@@ -1,23 +1,20 @@
-@description('The location for the resource(s) to be deployed.')
+@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -130,7 +127,7 @@ output planId string = env_asplan.id
output webSiteSuffix string = uniqueString(resourceGroup().id)
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.json
index d3afcb328bb..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json
index 325c9cbf4fb..b09b1238c1a 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json
@@ -1,17 +1,27 @@
{
"$schema": "https://json.schemastore.org/aspire-8.0.json",
"resources": {
+ "env1-acr": {
+ "type": "azure.bicep.v0",
+ "path": "env1-acr.module.bicep"
+ },
"env1": {
"type": "azure.bicep.v0",
"path": "env1.module.bicep",
"params": {
+ "env1_acr_outputs_name": "{env1-acr.outputs.name}",
"userPrincipalId": ""
}
},
+ "env2-acr": {
+ "type": "azure.bicep.v0",
+ "path": "env2-acr.module.bicep"
+ },
"env2": {
"type": "azure.bicep.v0",
"path": "env2.module.bicep",
"params": {
+ "env2_acr_outputs_name": "{env2-acr.outputs.name}",
"userPrincipalId": ""
}
},
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.bicep
index f0f23e5c9d1..657b91abbfb 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.bicep
@@ -5,6 +5,8 @@ param userPrincipalId string = ''
param tags object = { }
+param app_host_acr_outputs_name string
+
param log_env_shared_name string
param log_env_shared_rg string
@@ -15,13 +17,8 @@ resource app_host_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-3
tags: tags
}
-resource app_host_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('apphostacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource app_host_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: app_host_acr_outputs_name
}
resource app_host_acr_app_host_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -72,7 +69,7 @@ output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = log_env_shared.name
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = log_env_shared.id
-output AZURE_CONTAINER_REGISTRY_NAME string = app_host_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = app_host_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = app_host_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.json
index 26e00384caa..0d767c250b8 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppEnvironmentExtensionsTests.WithAzureLogAnalyticsWorkspace_RespectsExistingWorkspaceInDifferentResourceGroup.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "app-host.module.bicep",
"params": {
+ "app_host_acr_outputs_name": "{app-host-acr.outputs.name}",
"log_env_shared_name": "{log-env-shared-name.value}",
"log_env_shared_rg": "{log-env-shared-rg.value}",
"userPrincipalId": ""
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.bicep
index c7d3019bf86..cf2311305b3 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.bicep
@@ -5,19 +5,16 @@ param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -117,7 +114,7 @@ output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = env_law.name
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = env_law.id
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.json
index d3afcb328bb..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=False.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.bicep
index fdd209194a6..02c6f6f5ae5 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.bicep
@@ -5,6 +5,8 @@ param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
var resourceToken = uniqueString(resourceGroup().id)
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
@@ -13,13 +15,8 @@ resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' =
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
name: replace('acr-${resourceToken}', '-', '')
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.json
index d3afcb328bb..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.AddContainerAppEnvironmentAddsEnvironmentResource_useAzdNaming=True.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.bicep
index 59284bc6187..71f4a563423 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.bicep
@@ -5,6 +5,8 @@ param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
param customworkspace_outputs_name string
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
@@ -13,13 +15,8 @@ resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' =
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -69,7 +66,7 @@ output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = customworkspace_outputs_name
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = customworkspace.id
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.json
index ebfcb10a37a..0a71a394f85 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithCustomWorkspace#00.verified.json
@@ -2,7 +2,8 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"customworkspace_outputs_name": "{customworkspace.outputs.name}",
"userPrincipalId": ""
}
-}
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.bicep
index 7f38a22e186..9445bd45184 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.bicep
@@ -5,19 +5,16 @@ param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -66,7 +63,7 @@ output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = env_law.name
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = env_law.id
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.json
index d3afcb328bb..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardDisabled.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.bicep
index b105cc690c4..1faa4b8334a 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.bicep
@@ -5,19 +5,16 @@ param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -74,7 +71,7 @@ output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = env_law.name
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = env_law.id
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.json
index d3afcb328bb..aff9b2f4d4f 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.ContainerAppEnvironmentWithDashboardEnabled.verified.json
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
+ "env_acr_outputs_name": "{env-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.DefaultContainerRegistryUsesAzdNamingWhenEnvironmentDoes.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.DefaultContainerRegistryUsesAzdNamingWhenEnvironmentDoes.verified.bicep
new file mode 100644
index 00000000000..3f89e9df542
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.DefaultContainerRegistryUsesAzdNamingWhenEnvironmentDoes.verified.bicep
@@ -0,0 +1,19 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+var resourceToken = uniqueString(resourceGroup().id)
+
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
+ name: replace('acr-${resourceToken}', '-', '')
+ location: location
+ sku: {
+ name: 'Basic'
+ }
+ tags: {
+ 'aspire-resource-name': 'env-acr'
+ }
+}
+
+output name string = replace('acr-${resourceToken}', '-', '')
+
+output loginServer string = env_acr.properties.loginServer
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.DefaultContainerRegistryUsesAzdNamingWhenEnvironmentDoes.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.DefaultContainerRegistryUsesAzdNamingWhenEnvironmentDoes.verified.json
new file mode 100644
index 00000000000..9d5b7aac4e6
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.DefaultContainerRegistryUsesAzdNamingWhenEnvironmentDoes.verified.json
@@ -0,0 +1,4 @@
+{
+ "type": "azure.bicep.v0",
+ "path": "env-acr.module.bicep"
+}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleAzureContainerAppEnvironmentsSupported.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleAzureContainerAppEnvironmentsSupported.verified.json
index 5a40e7831db..5b1ff4d2335 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleAzureContainerAppEnvironmentsSupported.verified.json
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleAzureContainerAppEnvironmentsSupported.verified.json
@@ -1,17 +1,27 @@
{
"$schema": "https://json.schemastore.org/aspire-8.0.json",
"resources": {
+ "env1-acr": {
+ "type": "azure.bicep.v0",
+ "path": "env1-acr.module.bicep"
+ },
"env1": {
"type": "azure.bicep.v0",
"path": "env1.module.bicep",
"params": {
+ "env1_acr_outputs_name": "{env1-acr.outputs.name}",
"userPrincipalId": ""
}
},
+ "env2-acr": {
+ "type": "azure.bicep.v0",
+ "path": "env2-acr.module.bicep"
+ },
"env2": {
"type": "azure.bicep.v0",
"path": "env2.module.bicep",
"params": {
+ "env2_acr_outputs_name": "{env2-acr.outputs.name}",
"userPrincipalId": ""
}
},
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#02.verified.txt b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#02.verified.txt
index e7370e9f1f5..5f4e5709703 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#02.verified.txt
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#02.verified.txt
@@ -2,6 +2,7 @@
"type": "azure.bicep.v0",
"path": "my-ace.module.bicep",
"params": {
+ "my_ace_acr_outputs_name": "{my-ace-acr.outputs.name}",
"userPrincipalId": ""
}
}
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#03.verified.txt b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#03.verified.txt
index fb5c0833a90..5689d8c8692 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#03.verified.txt
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureContainerAppsTests.MultipleVolumesHaveUniqueNamesInBicep#03.verified.txt
@@ -5,19 +5,16 @@ param userPrincipalId string = ''
param tags object = { }
+param my_ace_acr_outputs_name string
+
resource my_ace_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('my_ace_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource my_ace_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('myaceacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource my_ace_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: my_ace_acr_outputs_name
}
resource my_ace_acr_my_ace_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -165,7 +162,7 @@ output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = my_ace_law.name
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = my_ace_law.id
-output AZURE_CONTAINER_REGISTRY_NAME string = my_ace_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = my_ace_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = my_ace_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithAzureResourceDependencies_DoesNotHang_step=diagnostics.verified.txt b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithAzureResourceDependencies_DoesNotHang_step=diagnostics.verified.txt
index c3bfd6d3fa2..785150d6cbd 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithAzureResourceDependencies_DoesNotHang_step=diagnostics.verified.txt
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithAzureResourceDependencies_DoesNotHang_step=diagnostics.verified.txt
@@ -5,7 +5,7 @@ PIPELINE DEPENDENCY GRAPH DIAGNOSTICS
This diagnostic output shows the complete pipeline dependency graph structure.
Use this to understand step relationships and troubleshoot execution issues.
-Total steps defined: 24
+Total steps defined: 27
Analysis for full pipeline execution (showing all steps and their relationships)
@@ -22,22 +22,25 @@ Steps with no dependencies run first, followed by steps that depend on them.
6. validate-azure-login
7. create-provisioning-context
8. provision-api-identity
- 9. provision-env
- 10. provision-kv
- 11. login-to-acr-env
- 12. push-api
- 13. provision-api-website
- 14. print-api-summary
- 15. provision-api-roles-kv
- 16. provision-azure-bicep-resources
- 17. print-dashboard-url-env
- 18. deploy
- 19. deploy-api
- 20. diagnostics
- 21. publish-prereq
- 22. publish-azure634f9
- 23. publish
- 24. publish-manifest
+ 9. provision-env-acr
+ 10. provision-env
+ 11. provision-kv
+ 12. login-to-acr-env-acr
+ 13. push-prereq
+ 14. push-api
+ 15. provision-api-website
+ 16. print-api-summary
+ 17. provision-api-roles-kv
+ 18. provision-azure-bicep-resources
+ 19. print-dashboard-url-env
+ 20. deploy
+ 21. deploy-api
+ 22. diagnostics
+ 23. publish-prereq
+ 24. publish-azure634f9
+ 25. publish
+ 26. publish-manifest
+ 27. push
DETAILED STEP ANALYSIS
======================
@@ -81,10 +84,9 @@ Step: diagnostics
Description: Dumps dependency graph information for troubleshooting pipeline execution.
Dependencies: none
-Step: login-to-acr-env
- Description: Logs in to Azure Container Registry for env.
- Dependencies: ✓ provision-env
- Resource: env (AzureAppServiceEnvironmentResource)
+Step: login-to-acr-env-acr
+ Dependencies: ✓ provision-env-acr
+ Resource: env-acr (AzureContainerRegistryResource)
Tags: acr-login
Step: print-api-summary
@@ -123,16 +125,22 @@ Step: provision-api-website
Step: provision-azure-bicep-resources
Description: Aggregation step for all Azure infrastructure provisioning operations.
- Dependencies: ✓ create-provisioning-context, ✓ deploy-prereq, ✓ provision-api-identity, ✓ provision-api-roles-kv, ✓ provision-api-website, ✓ provision-env, ✓ provision-kv
+ Dependencies: ✓ create-provisioning-context, ✓ deploy-prereq, ✓ provision-api-identity, ✓ provision-api-roles-kv, ✓ provision-api-website, ✓ provision-env, ✓ provision-env-acr, ✓ provision-kv
Resource: azure634f9 (AzureEnvironmentResource)
Tags: provision-infra
Step: provision-env
Description: Provisions the Azure Bicep resource env using Azure infrastructure.
- Dependencies: ✓ create-provisioning-context
+ Dependencies: ✓ create-provisioning-context, ✓ provision-env-acr
Resource: env (AzureAppServiceEnvironmentResource)
Tags: provision-infra
+Step: provision-env-acr
+ Description: Provisions the Azure Bicep resource env-acr using Azure infrastructure.
+ Dependencies: ✓ create-provisioning-context
+ Resource: env-acr (AzureContainerRegistryResource)
+ Tags: provision-infra
+
Step: provision-kv
Description: Provisions the Azure Bicep resource kv using Azure infrastructure.
Dependencies: ✓ create-provisioning-context
@@ -156,12 +164,19 @@ Step: publish-prereq
Description: Prerequisite step that runs before any publish operations.
Dependencies: ✓ process-parameters
+Step: push
+ Description: Aggregation step for all push operations. All push steps should be required by this step.
+ Dependencies: ✓ push-api, ✓ push-prereq
+
Step: push-api
- Description: Pushes the container image for api to Azure Container Registry.
- Dependencies: ✓ build-api, ✓ login-to-acr-env, ✓ provision-env
- Resource: api-website (AzureAppServiceWebSiteResource)
+ Dependencies: ✓ build-api, ✓ push-prereq
+ Resource: api (ProjectResource)
Tags: push-container-image
+Step: push-prereq
+ Description: Prerequisite step that runs before any push operations.
+ Dependencies: ✓ login-to-acr-env-acr
+
Step: validate-azure-login
Description: Validates Azure CLI authentication before deployment.
Dependencies: ✓ deploy-prereq
@@ -213,34 +228,36 @@ If targeting 'create-provisioning-context':
If targeting 'deploy':
Direct dependencies: build-api, create-provisioning-context, print-api-summary, print-dashboard-url-env, provision-azure-bicep-resources, validate-azure-login
- Total steps: 17
+ Total steps: 19
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-env | provision-kv (parallel)
- [5] login-to-acr-env | provision-api-roles-kv (parallel)
- [6] push-api
- [7] provision-api-website
- [8] print-api-summary | provision-azure-bicep-resources (parallel)
- [9] print-dashboard-url-env
- [10] deploy
+ [4] provision-api-identity | provision-env-acr | provision-kv (parallel)
+ [5] login-to-acr-env-acr | provision-api-roles-kv | provision-env (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] print-api-summary | provision-azure-bicep-resources (parallel)
+ [10] print-dashboard-url-env
+ [11] deploy
If targeting 'deploy-api':
Direct dependencies: print-api-summary
- Total steps: 14
+ Total steps: 16
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-env | provision-kv (parallel)
- [5] login-to-acr-env
- [6] push-api
- [7] provision-api-website
- [8] print-api-summary
- [9] deploy-api
+ [4] provision-api-identity | provision-env-acr | provision-kv (parallel)
+ [5] login-to-acr-env-acr | provision-env (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] print-api-summary
+ [10] deploy-api
If targeting 'deploy-prereq':
Direct dependencies: process-parameters
@@ -255,45 +272,47 @@ If targeting 'diagnostics':
Execution order:
[0] diagnostics
-If targeting 'login-to-acr-env':
- Direct dependencies: provision-env
+If targeting 'login-to-acr-env-acr':
+ Direct dependencies: provision-env-acr
Total steps: 6
Execution order:
[0] process-parameters
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-env
- [5] login-to-acr-env
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
If targeting 'print-api-summary':
Direct dependencies: provision-api-website
- Total steps: 13
+ Total steps: 15
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-env | provision-kv (parallel)
- [5] login-to-acr-env
- [6] push-api
- [7] provision-api-website
- [8] print-api-summary
+ [4] provision-api-identity | provision-env-acr | provision-kv (parallel)
+ [5] login-to-acr-env-acr | provision-env (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] print-api-summary
If targeting 'print-dashboard-url-env':
Direct dependencies: provision-azure-bicep-resources, provision-env
- Total steps: 15
+ Total steps: 17
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-env | provision-kv (parallel)
- [5] login-to-acr-env | provision-api-roles-kv (parallel)
- [6] push-api
- [7] provision-api-website
- [8] provision-azure-bicep-resources
- [9] print-dashboard-url-env
+ [4] provision-api-identity | provision-env-acr | provision-kv (parallel)
+ [5] login-to-acr-env-acr | provision-api-roles-kv | provision-env (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] provision-azure-bicep-resources
+ [10] print-dashboard-url-env
If targeting 'process-parameters':
Direct dependencies: none
@@ -324,32 +343,45 @@ If targeting 'provision-api-roles-kv':
If targeting 'provision-api-website':
Direct dependencies: create-provisioning-context, provision-api-identity, provision-env, provision-kv, push-api
- Total steps: 12
+ Total steps: 14
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-env | provision-kv (parallel)
- [5] login-to-acr-env
- [6] push-api
- [7] provision-api-website
+ [4] provision-api-identity | provision-env-acr | provision-kv (parallel)
+ [5] login-to-acr-env-acr | provision-env (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
If targeting 'provision-azure-bicep-resources':
- Direct dependencies: create-provisioning-context, deploy-prereq, provision-api-identity, provision-api-roles-kv, provision-api-website, provision-env, provision-kv
- Total steps: 14
+ Direct dependencies: create-provisioning-context, deploy-prereq, provision-api-identity, provision-api-roles-kv, provision-api-website, provision-env, provision-env-acr, provision-kv
+ Total steps: 16
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-env | provision-kv (parallel)
- [5] login-to-acr-env | provision-api-roles-kv (parallel)
- [6] push-api
- [7] provision-api-website
- [8] provision-azure-bicep-resources
+ [4] provision-api-identity | provision-env-acr | provision-kv (parallel)
+ [5] login-to-acr-env-acr | provision-api-roles-kv | provision-env (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] provision-azure-bicep-resources
If targeting 'provision-env':
+ Direct dependencies: create-provisioning-context, provision-env-acr
+ Total steps: 6
+ Execution order:
+ [0] process-parameters
+ [1] deploy-prereq
+ [2] validate-azure-login
+ [3] create-provisioning-context
+ [4] provision-env-acr
+ [5] provision-env
+
+If targeting 'provision-env-acr':
Direct dependencies: create-provisioning-context
Total steps: 5
Execution order:
@@ -357,7 +389,7 @@ If targeting 'provision-env':
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-env
+ [4] provision-env-acr
If targeting 'provision-kv':
Direct dependencies: create-provisioning-context
@@ -399,17 +431,44 @@ If targeting 'publish-prereq':
[0] process-parameters
[1] publish-prereq
+If targeting 'push':
+ Direct dependencies: push-api, push-prereq
+ Total steps: 11
+ Execution order:
+ [0] process-parameters
+ [1] build-prereq | deploy-prereq (parallel)
+ [2] build-api | validate-azure-login (parallel)
+ [3] create-provisioning-context
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
+ [6] push-prereq
+ [7] push-api
+ [8] push
+
If targeting 'push-api':
- Direct dependencies: build-api, login-to-acr-env, provision-env
- Total steps: 9
+ Direct dependencies: build-api, push-prereq
+ Total steps: 10
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-env
- [5] login-to-acr-env
- [6] push-api
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
+ [6] push-prereq
+ [7] push-api
+
+If targeting 'push-prereq':
+ Direct dependencies: login-to-acr-env-acr
+ Total steps: 7
+ Execution order:
+ [0] process-parameters
+ [1] deploy-prereq
+ [2] validate-azure-login
+ [3] create-provisioning-context
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
+ [6] push-prereq
If targeting 'validate-azure-login':
Direct dependencies: deploy-prereq
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithMultipleComputeEnvironments_Works_step=diagnostics.verified.txt b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithMultipleComputeEnvironments_Works_step=diagnostics.verified.txt
index d06ef1dabe6..b8879a28206 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithMultipleComputeEnvironments_Works_step=diagnostics.verified.txt
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithMultipleComputeEnvironments_Works_step=diagnostics.verified.txt
@@ -5,7 +5,7 @@ PIPELINE DEPENDENCY GRAPH DIAGNOSTICS
This diagnostic output shows the complete pipeline dependency graph structure.
Use this to understand step relationships and troubleshoot execution issues.
-Total steps defined: 33
+Total steps defined: 37
Analysis for full pipeline execution (showing all steps and their relationships)
@@ -22,31 +22,35 @@ Steps with no dependencies run first, followed by steps that depend on them.
6. build
7. validate-azure-login
8. create-provisioning-context
- 9. provision-aas-env
- 10. login-to-acr-aas-env
- 11. push-api-service
- 12. provision-api-service-website
- 13. print-api-service-summary
- 14. provision-aca-env
- 15. provision-cache-containerapp
- 16. print-cache-summary
- 17. login-to-acr-aca-env
- 18. push-python-app
- 19. provision-python-app-containerapp
- 20. provision-storage
- 21. provision-azure-bicep-resources
- 22. print-dashboard-url-aas-env
- 23. print-dashboard-url-aca-env
- 24. print-python-app-summary
- 25. deploy
- 26. deploy-api-service
- 27. deploy-cache
- 28. deploy-python-app
- 29. diagnostics
- 30. publish-prereq
- 31. publish-azure634f9
- 32. publish
- 33. publish-manifest
+ 9. provision-aas-env-acr
+ 10. provision-aas-env
+ 11. login-to-acr-aas-env-acr
+ 12. provision-aca-env-acr
+ 13. login-to-acr-aca-env-acr
+ 14. push-prereq
+ 15. push-api-service
+ 16. provision-api-service-website
+ 17. print-api-service-summary
+ 18. provision-aca-env
+ 19. provision-cache-containerapp
+ 20. print-cache-summary
+ 21. push-python-app
+ 22. provision-python-app-containerapp
+ 23. provision-storage
+ 24. provision-azure-bicep-resources
+ 25. print-dashboard-url-aas-env
+ 26. print-dashboard-url-aca-env
+ 27. print-python-app-summary
+ 28. deploy
+ 29. deploy-api-service
+ 30. deploy-cache
+ 31. deploy-python-app
+ 32. diagnostics
+ 33. publish-prereq
+ 34. publish-azure634f9
+ 35. publish
+ 36. publish-manifest
+ 37. push
DETAILED STEP ANALYSIS
======================
@@ -68,7 +72,6 @@ Step: build-prereq
Dependencies: ✓ process-parameters
Step: build-python-app
- Description: Builds the container image for the python-app container.
Dependencies: ✓ build-prereq, ✓ deploy-prereq, ✓ deploy-prereq
Resource: python-app (ContainerResource)
Tags: build-compute
@@ -108,16 +111,14 @@ Step: diagnostics
Description: Dumps dependency graph information for troubleshooting pipeline execution.
Dependencies: none
-Step: login-to-acr-aas-env
- Description: Logs in to Azure Container Registry for aas-env.
- Dependencies: ✓ provision-aas-env
- Resource: aas-env (AzureAppServiceEnvironmentResource)
+Step: login-to-acr-aas-env-acr
+ Dependencies: ✓ provision-aas-env-acr
+ Resource: aas-env-acr (AzureContainerRegistryResource)
Tags: acr-login
-Step: login-to-acr-aca-env
- Description: Logs in to Azure Container Registry for aca-env.
- Dependencies: ✓ provision-aca-env
- Resource: aca-env (AzureContainerAppEnvironmentResource)
+Step: login-to-acr-aca-env-acr
+ Dependencies: ✓ provision-aca-env-acr
+ Resource: aca-env-acr (AzureContainerRegistryResource)
Tags: acr-login
Step: print-api-service-summary
@@ -156,16 +157,28 @@ Step: process-parameters
Step: provision-aas-env
Description: Provisions the Azure Bicep resource aas-env using Azure infrastructure.
- Dependencies: ✓ create-provisioning-context
+ Dependencies: ✓ create-provisioning-context, ✓ provision-aas-env-acr
Resource: aas-env (AzureAppServiceEnvironmentResource)
Tags: provision-infra
+Step: provision-aas-env-acr
+ Description: Provisions the Azure Bicep resource aas-env-acr using Azure infrastructure.
+ Dependencies: ✓ create-provisioning-context
+ Resource: aas-env-acr (AzureContainerRegistryResource)
+ Tags: provision-infra
+
Step: provision-aca-env
Description: Provisions the Azure Bicep resource aca-env using Azure infrastructure.
- Dependencies: ✓ create-provisioning-context
+ Dependencies: ✓ create-provisioning-context, ✓ provision-aca-env-acr
Resource: aca-env (AzureContainerAppEnvironmentResource)
Tags: provision-infra
+Step: provision-aca-env-acr
+ Description: Provisions the Azure Bicep resource aca-env-acr using Azure infrastructure.
+ Dependencies: ✓ create-provisioning-context
+ Resource: aca-env-acr (AzureContainerRegistryResource)
+ Tags: provision-infra
+
Step: provision-api-service-website
Description: Provisions the Azure Bicep resource api-service-website using Azure infrastructure.
Dependencies: ✓ create-provisioning-context, ✓ provision-aas-env, ✓ push-api-service
@@ -174,7 +187,7 @@ Step: provision-api-service-website
Step: provision-azure-bicep-resources
Description: Aggregation step for all Azure infrastructure provisioning operations.
- Dependencies: ✓ create-provisioning-context, ✓ deploy-prereq, ✓ provision-aas-env, ✓ provision-aca-env, ✓ provision-api-service-website, ✓ provision-cache-containerapp, ✓ provision-python-app-containerapp, ✓ provision-storage
+ Dependencies: ✓ create-provisioning-context, ✓ deploy-prereq, ✓ provision-aas-env, ✓ provision-aas-env-acr, ✓ provision-aca-env, ✓ provision-aca-env-acr, ✓ provision-api-service-website, ✓ provision-cache-containerapp, ✓ provision-python-app-containerapp, ✓ provision-storage
Resource: azure634f9 (AzureEnvironmentResource)
Tags: provision-infra
@@ -213,16 +226,22 @@ Step: publish-prereq
Description: Prerequisite step that runs before any publish operations.
Dependencies: ✓ process-parameters
+Step: push
+ Description: Aggregation step for all push operations. All push steps should be required by this step.
+ Dependencies: ✓ push-api-service, ✓ push-prereq, ✓ push-python-app
+
Step: push-api-service
- Description: Pushes the container image for api-service to Azure Container Registry.
- Dependencies: ✓ build-api-service, ✓ login-to-acr-aas-env, ✓ provision-aas-env
- Resource: api-service-website (AzureAppServiceWebSiteResource)
+ Dependencies: ✓ build-api-service, ✓ push-prereq
+ Resource: api-service (ProjectResource)
Tags: push-container-image
+Step: push-prereq
+ Description: Prerequisite step that runs before any push operations.
+ Dependencies: ✓ login-to-acr-aas-env-acr, ✓ login-to-acr-aca-env-acr
+
Step: push-python-app
- Description: Pushes the container image for python-app to Azure Container Registry.
- Dependencies: ✓ build-python-app, ✓ login-to-acr-aca-env, ✓ provision-aca-env
- Resource: python-app-containerapp (AzureContainerAppResource)
+ Dependencies: ✓ build-python-app, ✓ push-prereq
+ Resource: python-app (ContainerResource)
Tags: push-container-image
Step: validate-azure-login
@@ -284,47 +303,50 @@ If targeting 'create-provisioning-context':
If targeting 'deploy':
Direct dependencies: build-api-service, build-python-app, create-provisioning-context, print-api-service-summary, print-cache-summary, print-dashboard-url-aas-env, print-dashboard-url-aca-env, print-python-app-summary, provision-azure-bicep-resources, validate-azure-login
- Total steps: 24
+ Total steps: 27
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env | provision-aca-env | provision-storage (parallel)
- [5] login-to-acr-aas-env | login-to-acr-aca-env | provision-cache-containerapp (parallel)
- [6] print-cache-summary | push-api-service | push-python-app (parallel)
- [7] provision-api-service-website | provision-python-app-containerapp (parallel)
- [8] print-api-service-summary | print-python-app-summary | provision-azure-bicep-resources (parallel)
- [9] print-dashboard-url-aas-env | print-dashboard-url-aca-env (parallel)
- [10] deploy
+ [4] provision-aas-env-acr | provision-aca-env-acr | provision-storage (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aas-env | provision-aca-env (parallel)
+ [6] provision-cache-containerapp | push-prereq (parallel)
+ [7] print-cache-summary | push-api-service | push-python-app (parallel)
+ [8] provision-api-service-website | provision-python-app-containerapp (parallel)
+ [9] print-api-service-summary | print-python-app-summary | provision-azure-bicep-resources (parallel)
+ [10] print-dashboard-url-aas-env | print-dashboard-url-aca-env (parallel)
+ [11] deploy
If targeting 'deploy-api-service':
Direct dependencies: print-api-service-summary
- Total steps: 12
+ Total steps: 16
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env
- [5] login-to-acr-aas-env
- [6] push-api-service
- [7] provision-api-service-website
- [8] print-api-service-summary
- [9] deploy-api-service
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aas-env (parallel)
+ [6] push-prereq
+ [7] push-api-service
+ [8] provision-api-service-website
+ [9] print-api-service-summary
+ [10] deploy-api-service
If targeting 'deploy-cache':
Direct dependencies: print-cache-summary
- Total steps: 8
+ Total steps: 9
Execution order:
[0] process-parameters
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-aca-env
- [5] provision-cache-containerapp
- [6] print-cache-summary
- [7] deploy-cache
+ [4] provision-aca-env-acr
+ [5] provision-aca-env
+ [6] provision-cache-containerapp
+ [7] print-cache-summary
+ [8] deploy-cache
If targeting 'deploy-prereq':
Direct dependencies: process-parameters
@@ -335,18 +357,19 @@ If targeting 'deploy-prereq':
If targeting 'deploy-python-app':
Direct dependencies: print-python-app-summary
- Total steps: 12
+ Total steps: 16
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aca-env
- [5] login-to-acr-aca-env
- [6] push-python-app
- [7] provision-python-app-containerapp
- [8] print-python-app-summary
- [9] deploy-python-app
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aca-env (parallel)
+ [6] push-prereq
+ [7] push-python-app
+ [8] provision-python-app-containerapp
+ [9] print-python-app-summary
+ [10] deploy-python-app
If targeting 'diagnostics':
Direct dependencies: none
@@ -354,97 +377,102 @@ If targeting 'diagnostics':
Execution order:
[0] diagnostics
-If targeting 'login-to-acr-aas-env':
- Direct dependencies: provision-aas-env
+If targeting 'login-to-acr-aas-env-acr':
+ Direct dependencies: provision-aas-env-acr
Total steps: 6
Execution order:
[0] process-parameters
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-aas-env
- [5] login-to-acr-aas-env
+ [4] provision-aas-env-acr
+ [5] login-to-acr-aas-env-acr
-If targeting 'login-to-acr-aca-env':
- Direct dependencies: provision-aca-env
+If targeting 'login-to-acr-aca-env-acr':
+ Direct dependencies: provision-aca-env-acr
Total steps: 6
Execution order:
[0] process-parameters
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-aca-env
- [5] login-to-acr-aca-env
+ [4] provision-aca-env-acr
+ [5] login-to-acr-aca-env-acr
If targeting 'print-api-service-summary':
Direct dependencies: provision-api-service-website
- Total steps: 11
+ Total steps: 15
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env
- [5] login-to-acr-aas-env
- [6] push-api-service
- [7] provision-api-service-website
- [8] print-api-service-summary
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aas-env (parallel)
+ [6] push-prereq
+ [7] push-api-service
+ [8] provision-api-service-website
+ [9] print-api-service-summary
If targeting 'print-cache-summary':
Direct dependencies: provision-cache-containerapp
- Total steps: 7
+ Total steps: 8
Execution order:
[0] process-parameters
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-aca-env
- [5] provision-cache-containerapp
- [6] print-cache-summary
+ [4] provision-aca-env-acr
+ [5] provision-aca-env
+ [6] provision-cache-containerapp
+ [7] print-cache-summary
If targeting 'print-dashboard-url-aas-env':
Direct dependencies: provision-aas-env, provision-azure-bicep-resources
- Total steps: 19
+ Total steps: 22
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env | provision-aca-env | provision-storage (parallel)
- [5] login-to-acr-aas-env | login-to-acr-aca-env | provision-cache-containerapp (parallel)
- [6] push-api-service | push-python-app (parallel)
- [7] provision-api-service-website | provision-python-app-containerapp (parallel)
- [8] provision-azure-bicep-resources
- [9] print-dashboard-url-aas-env
+ [4] provision-aas-env-acr | provision-aca-env-acr | provision-storage (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aas-env | provision-aca-env (parallel)
+ [6] provision-cache-containerapp | push-prereq (parallel)
+ [7] push-api-service | push-python-app (parallel)
+ [8] provision-api-service-website | provision-python-app-containerapp (parallel)
+ [9] provision-azure-bicep-resources
+ [10] print-dashboard-url-aas-env
If targeting 'print-dashboard-url-aca-env':
Direct dependencies: provision-aca-env, provision-azure-bicep-resources
- Total steps: 19
+ Total steps: 22
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env | provision-aca-env | provision-storage (parallel)
- [5] login-to-acr-aas-env | login-to-acr-aca-env | provision-cache-containerapp (parallel)
- [6] push-api-service | push-python-app (parallel)
- [7] provision-api-service-website | provision-python-app-containerapp (parallel)
- [8] provision-azure-bicep-resources
- [9] print-dashboard-url-aca-env
+ [4] provision-aas-env-acr | provision-aca-env-acr | provision-storage (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aas-env | provision-aca-env (parallel)
+ [6] provision-cache-containerapp | push-prereq (parallel)
+ [7] push-api-service | push-python-app (parallel)
+ [8] provision-api-service-website | provision-python-app-containerapp (parallel)
+ [9] provision-azure-bicep-resources
+ [10] print-dashboard-url-aca-env
If targeting 'print-python-app-summary':
Direct dependencies: provision-python-app-containerapp
- Total steps: 11
+ Total steps: 15
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aca-env
- [5] login-to-acr-aca-env
- [6] push-python-app
- [7] provision-python-app-containerapp
- [8] print-python-app-summary
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aca-env (parallel)
+ [6] push-prereq
+ [7] push-python-app
+ [8] provision-python-app-containerapp
+ [9] print-python-app-summary
If targeting 'process-parameters':
Direct dependencies: none
@@ -453,6 +481,17 @@ If targeting 'process-parameters':
[0] process-parameters
If targeting 'provision-aas-env':
+ Direct dependencies: create-provisioning-context, provision-aas-env-acr
+ Total steps: 6
+ Execution order:
+ [0] process-parameters
+ [1] deploy-prereq
+ [2] validate-azure-login
+ [3] create-provisioning-context
+ [4] provision-aas-env-acr
+ [5] provision-aas-env
+
+If targeting 'provision-aas-env-acr':
Direct dependencies: create-provisioning-context
Total steps: 5
Execution order:
@@ -460,9 +499,20 @@ If targeting 'provision-aas-env':
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-aas-env
+ [4] provision-aas-env-acr
If targeting 'provision-aca-env':
+ Direct dependencies: create-provisioning-context, provision-aca-env-acr
+ Total steps: 6
+ Execution order:
+ [0] process-parameters
+ [1] deploy-prereq
+ [2] validate-azure-login
+ [3] create-provisioning-context
+ [4] provision-aca-env-acr
+ [5] provision-aca-env
+
+If targeting 'provision-aca-env-acr':
Direct dependencies: create-provisioning-context
Total steps: 5
Execution order:
@@ -470,58 +520,62 @@ If targeting 'provision-aca-env':
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-aca-env
+ [4] provision-aca-env-acr
If targeting 'provision-api-service-website':
Direct dependencies: create-provisioning-context, provision-aas-env, push-api-service
- Total steps: 10
+ Total steps: 14
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env
- [5] login-to-acr-aas-env
- [6] push-api-service
- [7] provision-api-service-website
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aas-env (parallel)
+ [6] push-prereq
+ [7] push-api-service
+ [8] provision-api-service-website
If targeting 'provision-azure-bicep-resources':
- Direct dependencies: create-provisioning-context, deploy-prereq, provision-aas-env, provision-aca-env, provision-api-service-website, provision-cache-containerapp, provision-python-app-containerapp, provision-storage
- Total steps: 18
+ Direct dependencies: create-provisioning-context, deploy-prereq, provision-aas-env, provision-aas-env-acr, provision-aca-env, provision-aca-env-acr, provision-api-service-website, provision-cache-containerapp, provision-python-app-containerapp, provision-storage
+ Total steps: 21
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env | provision-aca-env | provision-storage (parallel)
- [5] login-to-acr-aas-env | login-to-acr-aca-env | provision-cache-containerapp (parallel)
- [6] push-api-service | push-python-app (parallel)
- [7] provision-api-service-website | provision-python-app-containerapp (parallel)
- [8] provision-azure-bicep-resources
+ [4] provision-aas-env-acr | provision-aca-env-acr | provision-storage (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aas-env | provision-aca-env (parallel)
+ [6] provision-cache-containerapp | push-prereq (parallel)
+ [7] push-api-service | push-python-app (parallel)
+ [8] provision-api-service-website | provision-python-app-containerapp (parallel)
+ [9] provision-azure-bicep-resources
If targeting 'provision-cache-containerapp':
Direct dependencies: create-provisioning-context, provision-aca-env
- Total steps: 6
+ Total steps: 7
Execution order:
[0] process-parameters
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-aca-env
- [5] provision-cache-containerapp
+ [4] provision-aca-env-acr
+ [5] provision-aca-env
+ [6] provision-cache-containerapp
If targeting 'provision-python-app-containerapp':
Direct dependencies: create-provisioning-context, provision-aca-env, push-python-app
- Total steps: 10
+ Total steps: 14
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aca-env
- [5] login-to-acr-aca-env
- [6] push-python-app
- [7] provision-python-app-containerapp
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr | provision-aca-env (parallel)
+ [6] push-prereq
+ [7] push-python-app
+ [8] provision-python-app-containerapp
If targeting 'provision-storage':
Direct dependencies: create-provisioning-context
@@ -563,29 +617,57 @@ If targeting 'publish-prereq':
[0] process-parameters
[1] publish-prereq
+If targeting 'push':
+ Direct dependencies: push-api-service, push-prereq, push-python-app
+ Total steps: 15
+ Execution order:
+ [0] process-parameters
+ [1] build-prereq | deploy-prereq (parallel)
+ [2] build-api-service | build-python-app | validate-azure-login (parallel)
+ [3] create-provisioning-context
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr (parallel)
+ [6] push-prereq
+ [7] push-api-service | push-python-app (parallel)
+ [8] push
+
If targeting 'push-api-service':
- Direct dependencies: build-api-service, login-to-acr-aas-env, provision-aas-env
- Total steps: 9
+ Direct dependencies: build-api-service, push-prereq
+ Total steps: 12
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api-service | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aas-env
- [5] login-to-acr-aas-env
- [6] push-api-service
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr (parallel)
+ [6] push-prereq
+ [7] push-api-service
-If targeting 'push-python-app':
- Direct dependencies: build-python-app, login-to-acr-aca-env, provision-aca-env
+If targeting 'push-prereq':
+ Direct dependencies: login-to-acr-aas-env-acr, login-to-acr-aca-env-acr
Total steps: 9
+ Execution order:
+ [0] process-parameters
+ [1] deploy-prereq
+ [2] validate-azure-login
+ [3] create-provisioning-context
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr (parallel)
+ [6] push-prereq
+
+If targeting 'push-python-app':
+ Direct dependencies: build-python-app, push-prereq
+ Total steps: 12
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-python-app | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-aca-env
- [5] login-to-acr-aca-env
- [6] push-python-app
+ [4] provision-aas-env-acr | provision-aca-env-acr (parallel)
+ [5] login-to-acr-aas-env-acr | login-to-acr-aca-env-acr (parallel)
+ [6] push-prereq
+ [7] push-python-app
If targeting 'validate-azure-login':
Direct dependencies: deploy-prereq
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithRedisAccessKeyAuthentication_CreatesCorrectDependencies.verified.txt b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithRedisAccessKeyAuthentication_CreatesCorrectDependencies.verified.txt
index 107388b54dc..023df734203 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithRedisAccessKeyAuthentication_CreatesCorrectDependencies.verified.txt
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureDeployerTests.DeployAsync_WithRedisAccessKeyAuthentication_CreatesCorrectDependencies.verified.txt
@@ -5,7 +5,7 @@ PIPELINE DEPENDENCY GRAPH DIAGNOSTICS
This diagnostic output shows the complete pipeline dependency graph structure.
Use this to understand step relationships and troubleshoot execution issues.
-Total steps defined: 31
+Total steps defined: 34
Analysis for full pipeline execution (showing all steps and their relationships)
@@ -26,25 +26,28 @@ Steps with no dependencies run first, followed by steps that depend on them.
10. provision-cache
11. provision-cosmos-kv
12. provision-cosmos
- 13. provision-env
- 14. provision-pg-kv
- 15. provision-pg
- 16. login-to-acr-env
- 17. push-api
- 18. provision-api-website
- 19. print-api-summary
- 20. provision-api-roles-cache-kv
- 21. provision-api-roles-cosmos-kv
- 22. provision-api-roles-pg-kv
- 23. provision-azure-bicep-resources
- 24. print-dashboard-url-env
- 25. deploy
- 26. deploy-api
- 27. diagnostics
- 28. publish-prereq
- 29. publish-azure634f9
- 30. publish
- 31. publish-manifest
+ 13. provision-env-acr
+ 14. provision-env
+ 15. provision-pg-kv
+ 16. provision-pg
+ 17. login-to-acr-env-acr
+ 18. push-prereq
+ 19. push-api
+ 20. provision-api-website
+ 21. print-api-summary
+ 22. provision-api-roles-cache-kv
+ 23. provision-api-roles-cosmos-kv
+ 24. provision-api-roles-pg-kv
+ 25. provision-azure-bicep-resources
+ 26. print-dashboard-url-env
+ 27. deploy
+ 28. deploy-api
+ 29. diagnostics
+ 30. publish-prereq
+ 31. publish-azure634f9
+ 32. publish
+ 33. publish-manifest
+ 34. push
DETAILED STEP ANALYSIS
======================
@@ -88,10 +91,9 @@ Step: diagnostics
Description: Dumps dependency graph information for troubleshooting pipeline execution.
Dependencies: none
-Step: login-to-acr-env
- Description: Logs in to Azure Container Registry for env.
- Dependencies: ✓ provision-env
- Resource: env (AzureAppServiceEnvironmentResource)
+Step: login-to-acr-env-acr
+ Dependencies: ✓ provision-env-acr
+ Resource: env-acr (AzureContainerRegistryResource)
Tags: acr-login
Step: print-api-summary
@@ -142,7 +144,7 @@ Step: provision-api-website
Step: provision-azure-bicep-resources
Description: Aggregation step for all Azure infrastructure provisioning operations.
- Dependencies: ✓ create-provisioning-context, ✓ deploy-prereq, ✓ provision-api-identity, ✓ provision-api-roles-cache-kv, ✓ provision-api-roles-cosmos-kv, ✓ provision-api-roles-pg-kv, ✓ provision-api-website, ✓ provision-cache, ✓ provision-cache-kv, ✓ provision-cosmos, ✓ provision-cosmos-kv, ✓ provision-env, ✓ provision-pg, ✓ provision-pg-kv
+ Dependencies: ✓ create-provisioning-context, ✓ deploy-prereq, ✓ provision-api-identity, ✓ provision-api-roles-cache-kv, ✓ provision-api-roles-cosmos-kv, ✓ provision-api-roles-pg-kv, ✓ provision-api-website, ✓ provision-cache, ✓ provision-cache-kv, ✓ provision-cosmos, ✓ provision-cosmos-kv, ✓ provision-env, ✓ provision-env-acr, ✓ provision-pg, ✓ provision-pg-kv
Resource: azure634f9 (AzureEnvironmentResource)
Tags: provision-infra
@@ -172,10 +174,16 @@ Step: provision-cosmos-kv
Step: provision-env
Description: Provisions the Azure Bicep resource env using Azure infrastructure.
- Dependencies: ✓ create-provisioning-context
+ Dependencies: ✓ create-provisioning-context, ✓ provision-env-acr
Resource: env (AzureAppServiceEnvironmentResource)
Tags: provision-infra
+Step: provision-env-acr
+ Description: Provisions the Azure Bicep resource env-acr using Azure infrastructure.
+ Dependencies: ✓ create-provisioning-context
+ Resource: env-acr (AzureContainerRegistryResource)
+ Tags: provision-infra
+
Step: provision-pg
Description: Provisions the Azure Bicep resource pg using Azure infrastructure.
Dependencies: ✓ create-provisioning-context, ✓ provision-pg-kv
@@ -205,12 +213,19 @@ Step: publish-prereq
Description: Prerequisite step that runs before any publish operations.
Dependencies: ✓ process-parameters
+Step: push
+ Description: Aggregation step for all push operations. All push steps should be required by this step.
+ Dependencies: ✓ push-api, ✓ push-prereq
+
Step: push-api
- Description: Pushes the container image for api to Azure Container Registry.
- Dependencies: ✓ build-api, ✓ login-to-acr-env, ✓ provision-env
- Resource: api-website (AzureAppServiceWebSiteResource)
+ Dependencies: ✓ build-api, ✓ push-prereq
+ Resource: api (ProjectResource)
Tags: push-container-image
+Step: push-prereq
+ Description: Prerequisite step that runs before any push operations.
+ Dependencies: ✓ login-to-acr-env-acr
+
Step: validate-azure-login
Description: Validates Azure CLI authentication before deployment.
Dependencies: ✓ deploy-prereq
@@ -262,34 +277,36 @@ If targeting 'create-provisioning-context':
If targeting 'deploy':
Direct dependencies: build-api, create-provisioning-context, print-api-summary, print-dashboard-url-env, provision-azure-bicep-resources, validate-azure-login
- Total steps: 24
+ Total steps: 26
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env | provision-pg-kv (parallel)
- [5] login-to-acr-env | provision-api-roles-cache-kv | provision-api-roles-cosmos-kv | provision-api-roles-pg-kv | provision-cache | provision-cosmos | provision-pg (parallel)
- [6] push-api
- [7] provision-api-website
- [8] print-api-summary | provision-azure-bicep-resources (parallel)
- [9] print-dashboard-url-env
- [10] deploy
+ [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env-acr | provision-pg-kv (parallel)
+ [5] login-to-acr-env-acr | provision-api-roles-cache-kv | provision-api-roles-cosmos-kv | provision-api-roles-pg-kv | provision-cache | provision-cosmos | provision-env | provision-pg (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] print-api-summary | provision-azure-bicep-resources (parallel)
+ [10] print-dashboard-url-env
+ [11] deploy
If targeting 'deploy-api':
Direct dependencies: print-api-summary
- Total steps: 19
+ Total steps: 21
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env | provision-pg-kv (parallel)
- [5] login-to-acr-env | provision-cache | provision-cosmos | provision-pg (parallel)
- [6] push-api
- [7] provision-api-website
- [8] print-api-summary
- [9] deploy-api
+ [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env-acr | provision-pg-kv (parallel)
+ [5] login-to-acr-env-acr | provision-cache | provision-cosmos | provision-env | provision-pg (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] print-api-summary
+ [10] deploy-api
If targeting 'deploy-prereq':
Direct dependencies: process-parameters
@@ -304,45 +321,47 @@ If targeting 'diagnostics':
Execution order:
[0] diagnostics
-If targeting 'login-to-acr-env':
- Direct dependencies: provision-env
+If targeting 'login-to-acr-env-acr':
+ Direct dependencies: provision-env-acr
Total steps: 6
Execution order:
[0] process-parameters
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-env
- [5] login-to-acr-env
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
If targeting 'print-api-summary':
Direct dependencies: provision-api-website
- Total steps: 18
+ Total steps: 20
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env | provision-pg-kv (parallel)
- [5] login-to-acr-env | provision-cache | provision-cosmos | provision-pg (parallel)
- [6] push-api
- [7] provision-api-website
- [8] print-api-summary
+ [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env-acr | provision-pg-kv (parallel)
+ [5] login-to-acr-env-acr | provision-cache | provision-cosmos | provision-env | provision-pg (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] print-api-summary
If targeting 'print-dashboard-url-env':
Direct dependencies: provision-azure-bicep-resources, provision-env
- Total steps: 22
+ Total steps: 24
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env | provision-pg-kv (parallel)
- [5] login-to-acr-env | provision-api-roles-cache-kv | provision-api-roles-cosmos-kv | provision-api-roles-pg-kv | provision-cache | provision-cosmos | provision-pg (parallel)
- [6] push-api
- [7] provision-api-website
- [8] provision-azure-bicep-resources
- [9] print-dashboard-url-env
+ [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env-acr | provision-pg-kv (parallel)
+ [5] login-to-acr-env-acr | provision-api-roles-cache-kv | provision-api-roles-cosmos-kv | provision-api-roles-pg-kv | provision-cache | provision-cosmos | provision-env | provision-pg (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] provision-azure-bicep-resources
+ [10] print-dashboard-url-env
If targeting 'process-parameters':
Direct dependencies: none
@@ -395,30 +414,32 @@ If targeting 'provision-api-roles-pg-kv':
If targeting 'provision-api-website':
Direct dependencies: create-provisioning-context, provision-api-identity, provision-cache, provision-cache-kv, provision-cosmos, provision-cosmos-kv, provision-env, provision-pg, provision-pg-kv, push-api
- Total steps: 17
+ Total steps: 19
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env | provision-pg-kv (parallel)
- [5] login-to-acr-env | provision-cache | provision-cosmos | provision-pg (parallel)
- [6] push-api
- [7] provision-api-website
+ [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env-acr | provision-pg-kv (parallel)
+ [5] login-to-acr-env-acr | provision-cache | provision-cosmos | provision-env | provision-pg (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
If targeting 'provision-azure-bicep-resources':
- Direct dependencies: create-provisioning-context, deploy-prereq, provision-api-identity, provision-api-roles-cache-kv, provision-api-roles-cosmos-kv, provision-api-roles-pg-kv, provision-api-website, provision-cache, provision-cache-kv, provision-cosmos, provision-cosmos-kv, provision-env, provision-pg, provision-pg-kv
- Total steps: 21
+ Direct dependencies: create-provisioning-context, deploy-prereq, provision-api-identity, provision-api-roles-cache-kv, provision-api-roles-cosmos-kv, provision-api-roles-pg-kv, provision-api-website, provision-cache, provision-cache-kv, provision-cosmos, provision-cosmos-kv, provision-env, provision-env-acr, provision-pg, provision-pg-kv
+ Total steps: 23
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env | provision-pg-kv (parallel)
- [5] login-to-acr-env | provision-api-roles-cache-kv | provision-api-roles-cosmos-kv | provision-api-roles-pg-kv | provision-cache | provision-cosmos | provision-pg (parallel)
- [6] push-api
- [7] provision-api-website
- [8] provision-azure-bicep-resources
+ [4] provision-api-identity | provision-cache-kv | provision-cosmos-kv | provision-env-acr | provision-pg-kv (parallel)
+ [5] login-to-acr-env-acr | provision-api-roles-cache-kv | provision-api-roles-cosmos-kv | provision-api-roles-pg-kv | provision-cache | provision-cosmos | provision-env | provision-pg (parallel)
+ [6] push-prereq
+ [7] push-api
+ [8] provision-api-website
+ [9] provision-azure-bicep-resources
If targeting 'provision-cache':
Direct dependencies: create-provisioning-context, provision-cache-kv
@@ -463,6 +484,17 @@ If targeting 'provision-cosmos-kv':
[4] provision-cosmos-kv
If targeting 'provision-env':
+ Direct dependencies: create-provisioning-context, provision-env-acr
+ Total steps: 6
+ Execution order:
+ [0] process-parameters
+ [1] deploy-prereq
+ [2] validate-azure-login
+ [3] create-provisioning-context
+ [4] provision-env-acr
+ [5] provision-env
+
+If targeting 'provision-env-acr':
Direct dependencies: create-provisioning-context
Total steps: 5
Execution order:
@@ -470,7 +502,7 @@ If targeting 'provision-env':
[1] deploy-prereq
[2] validate-azure-login
[3] create-provisioning-context
- [4] provision-env
+ [4] provision-env-acr
If targeting 'provision-pg':
Direct dependencies: create-provisioning-context, provision-pg-kv
@@ -523,17 +555,44 @@ If targeting 'publish-prereq':
[0] process-parameters
[1] publish-prereq
+If targeting 'push':
+ Direct dependencies: push-api, push-prereq
+ Total steps: 11
+ Execution order:
+ [0] process-parameters
+ [1] build-prereq | deploy-prereq (parallel)
+ [2] build-api | validate-azure-login (parallel)
+ [3] create-provisioning-context
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
+ [6] push-prereq
+ [7] push-api
+ [8] push
+
If targeting 'push-api':
- Direct dependencies: build-api, login-to-acr-env, provision-env
- Total steps: 9
+ Direct dependencies: build-api, push-prereq
+ Total steps: 10
Execution order:
[0] process-parameters
[1] build-prereq | deploy-prereq (parallel)
[2] build-api | validate-azure-login (parallel)
[3] create-provisioning-context
- [4] provision-env
- [5] login-to-acr-env
- [6] push-api
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
+ [6] push-prereq
+ [7] push-api
+
+If targeting 'push-prereq':
+ Direct dependencies: login-to-acr-env-acr
+ Total steps: 7
+ Execution order:
+ [0] process-parameters
+ [1] deploy-prereq
+ [2] validate-azure-login
+ [3] create-provisioning-context
+ [4] provision-env-acr
+ [5] login-to-acr-env-acr
+ [6] push-prereq
If targeting 'validate-azure-login':
Direct dependencies: deploy-prereq
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.AzurePublishingContext_CapturesParametersAndOutputsCorrectly_WithSnapshot#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.AzurePublishingContext_CapturesParametersAndOutputsCorrectly_WithSnapshot#00.verified.bicep
index 84d95d31e32..b66d29a6f82 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.AzurePublishingContext_CapturesParametersAndOutputsCorrectly_WithSnapshot#00.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.AzurePublishingContext_CapturesParametersAndOutputsCorrectly_WithSnapshot#00.verified.bicep
@@ -15,11 +15,20 @@ resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
location: location
}
+module acaEnv_acr 'acaEnv-acr/acaEnv-acr.bicep' = {
+ name: 'acaEnv-acr'
+ scope: rg
+ params: {
+ location: location
+ }
+}
+
module acaEnv 'acaEnv/acaEnv.bicep' = {
name: 'acaEnv'
scope: rg
params: {
location: location
+ acaenv_acr_outputs_name: acaEnv_acr.outputs.name
userPrincipalId: principalId
}
}
@@ -78,9 +87,9 @@ module fe_roles_account 'fe-roles-account/fe-roles-account.bicep' = {
}
}
-output acaEnv_AZURE_CONTAINER_REGISTRY_NAME string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_NAME
+output acaEnv_acr_name string = acaEnv_acr.outputs.name
-output acaEnv_AZURE_CONTAINER_REGISTRY_ENDPOINT string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
+output acaEnv_acr_loginServer string = acaEnv_acr.outputs.loginServer
output acaEnv_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID
@@ -88,6 +97,8 @@ output acaEnv_AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = acaEnv.ou
output acaEnv_AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = acaEnv.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_ID
+output acaEnv_AZURE_CONTAINER_REGISTRY_ENDPOINT string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
+
output fe_identity_id string = fe_identity.outputs.id
output storage_blobEndpoint string = storage.outputs.blobEndpoint
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.PublishAsync_GeneratesMainBicep_WithSnapshots.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.PublishAsync_GeneratesMainBicep_WithSnapshots.verified.bicep
index 072a24148d6..4ca4110c778 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.PublishAsync_GeneratesMainBicep_WithSnapshots.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.PublishAsync_GeneratesMainBicep_WithSnapshots.verified.bicep
@@ -19,11 +19,20 @@ resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
location: location
}
+module acaEnv_acr 'acaEnv-acr/acaEnv-acr.bicep' = {
+ name: 'acaEnv-acr'
+ scope: rg
+ params: {
+ location: location
+ }
+}
+
module acaEnv 'acaEnv/acaEnv.bicep' = {
name: 'acaEnv'
scope: rg
params: {
location: location
+ acaenv_acr_outputs_name: acaEnv_acr.outputs.name
userPrincipalId: principalId
}
}
@@ -116,9 +125,9 @@ module fe_roles_storage 'fe-roles-storage/fe-roles-storage.bicep' = {
}
}
-output acaEnv_AZURE_CONTAINER_REGISTRY_NAME string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_NAME
+output acaEnv_acr_name string = acaEnv_acr.outputs.name
-output acaEnv_AZURE_CONTAINER_REGISTRY_ENDPOINT string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
+output acaEnv_acr_loginServer string = acaEnv_acr.outputs.loginServer
output acaEnv_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID
@@ -132,6 +141,8 @@ output account_connectionString string = account.outputs.connectionString
output myapp_identity_clientId string = myapp_identity.outputs.clientId
+output acaEnv_AZURE_CONTAINER_REGISTRY_ENDPOINT string = acaEnv.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
+
output fe_identity_id string = fe_identity.outputs.id
output storage_blobEndpoint string = storage.outputs.blobEndpoint
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#00.verified.bicep
index a19fed818bb..c2170bb2bef 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#00.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#00.verified.bicep
@@ -11,18 +11,27 @@ resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
location: location
}
+module env_acr 'env-acr/env-acr.bicep' = {
+ name: 'env-acr'
+ scope: rg
+ params: {
+ location: location
+ }
+}
+
module env 'env/env.bicep' = {
name: 'env'
scope: rg
params: {
location: location
+ env_acr_outputs_name: env_acr.outputs.name
userPrincipalId: principalId
}
}
-output env_AZURE_CONTAINER_REGISTRY_NAME string = env.outputs.AZURE_CONTAINER_REGISTRY_NAME
+output env_acr_name string = env_acr.outputs.name
-output env_AZURE_CONTAINER_REGISTRY_ENDPOINT string = env.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
+output env_acr_loginServer string = env_acr.outputs.loginServer
output env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#01.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#01.verified.bicep
index b105cc690c4..1faa4b8334a 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#01.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_GeneratesProperBicep#01.verified.bicep
@@ -5,19 +5,16 @@ param userPrincipalId string = ''
param tags object = { }
+param env_acr_outputs_name string
+
resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}
-resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
- name: take('envacr${uniqueString(resourceGroup().id)}', 50)
- location: location
- sku: {
- name: 'Basic'
- }
- tags: tags
+resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = {
+ name: env_acr_outputs_name
}
resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
@@ -74,7 +71,7 @@ output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = env_law.name
output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = env_law.id
-output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name
+output AZURE_CONTAINER_REGISTRY_NAME string = env_acr_outputs_name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer
diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_RespectsStronglyTypedProperties.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_RespectsStronglyTypedProperties.verified.bicep
index 11f1b437e64..57ddc9b1adb 100644
--- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_RespectsStronglyTypedProperties.verified.bicep
+++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureEnvironmentResourceTests.WhenUsedWithAzureContainerAppsEnvironment_RespectsStronglyTypedProperties.verified.bicep
@@ -11,18 +11,27 @@ resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
location: location
}
+module env_acr 'env-acr/env-acr.bicep' = {
+ name: 'env-acr'
+ scope: rg
+ params: {
+ location: location
+ }
+}
+
module env 'env/env.bicep' = {
name: 'env'
scope: rg
params: {
location: location
+ env_acr_outputs_name: env_acr.outputs.name
userPrincipalId: principalId
}
}
-output env_AZURE_CONTAINER_REGISTRY_NAME string = env.outputs.AZURE_CONTAINER_REGISTRY_NAME
+output env_acr_name string = env_acr.outputs.name
-output env_AZURE_CONTAINER_REGISTRY_ENDPOINT string = env.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT
+output env_acr_loginServer string = env_acr.outputs.loginServer
output env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID
diff --git a/tests/Aspire.Hosting.Containers.Tests/WithDockerfileTests.cs b/tests/Aspire.Hosting.Containers.Tests/WithDockerfileTests.cs
index 0ad78a44613..5090d746158 100644
--- a/tests/Aspire.Hosting.Containers.Tests/WithDockerfileTests.cs
+++ b/tests/Aspire.Hosting.Containers.Tests/WithDockerfileTests.cs
@@ -784,12 +784,17 @@ public async Task WithDockerfileFactorySyncFactoryCreatesAnnotationWithFactory()
Resource = container.Resource
};
var steps = (await stepsAnnotation.CreateStepsAsync(factoryContext)).ToList();
- var buildStep = Assert.Single(steps);
+ Assert.Equal(2, steps.Count);
+
+ var buildStep = steps.Single(s => s.Tags.Contains(WellKnownPipelineTags.BuildCompute));
Assert.Equal("build-mycontainer", buildStep.Name);
- Assert.Contains(WellKnownPipelineTags.BuildCompute, buildStep.Tags);
Assert.Contains(WellKnownPipelineSteps.Build, buildStep.RequiredBySteps);
Assert.Contains(WellKnownPipelineSteps.BuildPrereq, buildStep.DependsOnSteps);
+ var pushStep = steps.Single(s => s.Tags.Contains(WellKnownPipelineTags.PushContainerImage));
+ Assert.Equal("push-mycontainer", pushStep.Name);
+ Assert.Contains(WellKnownPipelineSteps.Push, pushStep.RequiredBySteps);
+
// Verify the factory produces the expected content
var context = new DockerfileFactoryContext
{
@@ -941,12 +946,16 @@ public async Task WithDockerfile_AutomaticallyGeneratesBuildStep_WithCorrectDepe
};
var steps = (await pipelineStepAnnotation.CreateStepsAsync(factoryContext)).ToList();
+ Assert.Equal(2, steps.Count);
- var buildStep = Assert.Single(steps);
+ var buildStep = steps.Single(s => s.Tags.Contains(WellKnownPipelineTags.BuildCompute));
Assert.Equal("build-test-container", buildStep.Name);
- Assert.Contains(WellKnownPipelineTags.BuildCompute, buildStep.Tags);
Assert.Contains(WellKnownPipelineSteps.Build, buildStep.RequiredBySteps);
Assert.Contains(WellKnownPipelineSteps.BuildPrereq, buildStep.DependsOnSteps);
+
+ var pushStep = steps.Single(s => s.Tags.Contains(WellKnownPipelineTags.PushContainerImage));
+ Assert.Equal("push-test-container", pushStep.Name);
+ Assert.Contains(WellKnownPipelineSteps.Push, pushStep.RequiredBySteps);
}
[Fact]
@@ -977,10 +986,15 @@ public async Task WithDockerfile_CalledMultipleTimes_OverwritesPreviousBuildStep
};
var steps = (await pipelineStepAnnotation1.CreateStepsAsync(factoryContext)).ToList();
- var buildStep = Assert.Single(steps);
+ Assert.Equal(2, steps.Count);
+
+ var buildStep = steps.Single(s => s.Tags.Contains(WellKnownPipelineTags.BuildCompute));
Assert.Equal("build-test-container", buildStep.Name);
- Assert.Contains(WellKnownPipelineTags.BuildCompute, buildStep.Tags);
Assert.Contains(WellKnownPipelineSteps.Build, buildStep.RequiredBySteps);
Assert.Contains(WellKnownPipelineSteps.BuildPrereq, buildStep.DependsOnSteps);
+
+ var pushStep = steps.Single(s => s.Tags.Contains(WellKnownPipelineTags.PushContainerImage));
+ Assert.Equal("push-test-container", pushStep.Name);
+ Assert.Contains(WellKnownPipelineSteps.Push, pushStep.RequiredBySteps);
}
}
diff --git a/tests/Aspire.Hosting.Tests/ContainerRegistryResourceTests.cs b/tests/Aspire.Hosting.Tests/ContainerRegistryResourceTests.cs
index 68e2690dec0..1c7f1ec7c9d 100644
--- a/tests/Aspire.Hosting.Tests/ContainerRegistryResourceTests.cs
+++ b/tests/Aspire.Hosting.Tests/ContainerRegistryResourceTests.cs
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable ASPIRECOMPUTE003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable ASPIREPIPELINES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+using Aspire.Hosting.Pipelines;
using Aspire.Hosting.Utils;
using Microsoft.Extensions.DependencyInjection;
@@ -329,4 +331,355 @@ public void WithContainerRegistryWithNullRegistryThrows()
Assert.Throws(() => container.WithContainerRegistry(registry));
}
+
+ [Fact]
+ public void ContainerWithDockerfileHasPushStepAndConfigurationAnnotations()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var container = builder.AddDockerfile("mycontainer", "../myapp");
+
+ var pipelineStepAnnotations = container.Resource.Annotations.OfType().ToList();
+ var pipelineConfigAnnotations = container.Resource.Annotations.OfType().ToList();
+
+ Assert.NotEmpty(pipelineStepAnnotations);
+ Assert.NotEmpty(pipelineConfigAnnotations);
+ }
+
+ [Fact]
+ public void ProjectResourceHasPushStepAndConfigurationAnnotations()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var project = builder.AddProject("api");
+
+ var pipelineStepAnnotations = project.Resource.Annotations.OfType().ToList();
+ var pipelineConfigAnnotations = project.Resource.Annotations.OfType().ToList();
+
+ Assert.NotEmpty(pipelineStepAnnotations);
+ Assert.NotEmpty(pipelineConfigAnnotations);
+ }
+
+ [Fact]
+ public async Task ProjectResourcePushStepHasPushContainerImageTag()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var project = builder.AddProject("api");
+
+ var pipelineStepAnnotation = Assert.Single(project.Resource.Annotations.OfType());
+
+ var factoryContext = new PipelineStepFactoryContext
+ {
+ PipelineContext = null!,
+ Resource = project.Resource
+ };
+
+ var steps = (await pipelineStepAnnotation.CreateStepsAsync(factoryContext)).ToList();
+
+ var pushStep = steps.FirstOrDefault(s => s.Name == "push-api");
+ Assert.NotNull(pushStep);
+ Assert.Contains(WellKnownPipelineTags.PushContainerImage, pushStep.Tags);
+ Assert.Contains(WellKnownPipelineSteps.Push, pushStep.RequiredBySteps);
+ }
+
+ [Fact]
+ public async Task ContainerWithDockerfilePushStepHasPushContainerImageTag()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var container = builder.AddDockerfile("mycontainer", "../myapp");
+
+ var pipelineStepAnnotation = Assert.Single(container.Resource.Annotations.OfType());
+
+ var factoryContext = new PipelineStepFactoryContext
+ {
+ PipelineContext = null!,
+ Resource = container.Resource
+ };
+
+ var steps = (await pipelineStepAnnotation.CreateStepsAsync(factoryContext)).ToList();
+
+ var pushStep = steps.FirstOrDefault(s => s.Name == "push-mycontainer");
+ Assert.NotNull(pushStep);
+ Assert.Contains(WellKnownPipelineTags.PushContainerImage, pushStep.Tags);
+ Assert.Contains(WellKnownPipelineSteps.Push, pushStep.RequiredBySteps);
+ }
+
+ [Fact]
+ public async Task ContainerWithoutDockerfileHasNoPushStep()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var container = builder.AddContainer("mycontainer", "myimage");
+
+ var pipelineStepAnnotations = container.Resource.Annotations.OfType().ToList();
+
+ foreach (var annotation in pipelineStepAnnotations)
+ {
+ var factoryContext = new PipelineStepFactoryContext
+ {
+ PipelineContext = null!,
+ Resource = container.Resource
+ };
+
+ var steps = (await annotation.CreateStepsAsync(factoryContext)).ToList();
+
+ Assert.DoesNotContain(steps, s => s.Tags.Contains(WellKnownPipelineTags.PushContainerImage));
+ }
+ }
+
+ [Fact]
+ public async Task ProjectResourcePushStepDependsOnBuildStep()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var project = builder.AddProject("api");
+
+ var pipelineStepAnnotation = Assert.Single(project.Resource.Annotations.OfType());
+
+ var factoryContext = new PipelineStepFactoryContext
+ {
+ PipelineContext = null!,
+ Resource = project.Resource
+ };
+
+ var steps = (await pipelineStepAnnotation.CreateStepsAsync(factoryContext)).ToList();
+
+ var buildStep = steps.FirstOrDefault(s => s.Name == "build-api");
+ var pushStep = steps.FirstOrDefault(s => s.Name == "push-api");
+
+ Assert.NotNull(buildStep);
+ Assert.NotNull(pushStep);
+
+ Assert.Contains(WellKnownPipelineTags.BuildCompute, buildStep.Tags);
+ }
+
+ [Fact]
+ public void WithContainerRegistryAddsAnnotationToProject()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry = builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var project = builder.AddProject("api")
+ .WithContainerRegistry(registry);
+
+ var annotation = project.Resource.Annotations.OfType().FirstOrDefault();
+ Assert.NotNull(annotation);
+ Assert.Same(registry.Resource, annotation.Registry);
+ }
+
+ [Fact]
+ public void MultipleRegistriesCanBeAddedWithExplicitSelection()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry1 = builder.AddContainerRegistry("docker-hub", "docker.io", "user1");
+ var registry2 = builder.AddContainerRegistry("ghcr", "ghcr.io", "user2");
+
+ var project = builder.AddProject("api")
+ .WithContainerRegistry(registry1);
+
+ var annotations = project.Resource.Annotations.OfType().ToList();
+ Assert.Single(annotations);
+ Assert.Same(registry1.Resource, annotations[0].Registry);
+ }
+
+ [Fact]
+ public void ContainerRegistryReferenceAnnotationHoldsCorrectRegistry()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry = builder.AddContainerRegistry("acr", "myregistry.azurecr.io");
+
+ var annotation = new ContainerRegistryReferenceAnnotation(registry.Resource);
+
+ Assert.Same(registry.Resource, annotation.Registry);
+ }
+
+ [Fact]
+ public async Task RegistryTargetAnnotationIsAddedToResourcesOnBeforeStartEvent()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry = builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var project = builder.AddProject("api");
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ // Before BeforeStartEvent, the project should not have RegistryTargetAnnotation
+ Assert.Empty(project.Resource.Annotations.OfType());
+
+ // Simulate BeforeStartEvent
+ var beforeStartEvent = new BeforeStartEvent(app.Services, appModel);
+ await builder.Eventing.PublishAsync(beforeStartEvent);
+
+ // After BeforeStartEvent, the project should have RegistryTargetAnnotation
+ var registryTargetAnnotation = Assert.Single(project.Resource.Annotations.OfType());
+ Assert.Same(registry.Resource, registryTargetAnnotation.Registry);
+ }
+
+ [Fact]
+ public async Task MultipleRegistriesAddMultipleRegistryTargetAnnotations()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry1 = builder.AddContainerRegistry("docker-hub", "docker.io", "user1");
+ var registry2 = builder.AddContainerRegistry("ghcr", "ghcr.io", "user2");
+ var project = builder.AddProject("api");
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ // Simulate BeforeStartEvent
+ var beforeStartEvent = new BeforeStartEvent(app.Services, appModel);
+ await builder.Eventing.PublishAsync(beforeStartEvent);
+
+ // Project should have two RegistryTargetAnnotations
+ var registryTargetAnnotations = project.Resource.Annotations.OfType().ToList();
+ Assert.Equal(2, registryTargetAnnotations.Count);
+
+ var registryResources = registryTargetAnnotations.Select(a => a.Registry).ToList();
+ Assert.Contains(registry1.Resource, registryResources);
+ Assert.Contains(registry2.Resource, registryResources);
+ }
+
+ [Fact]
+ public async Task GetContainerRegistryReturnsRegistryFromRegistryTargetAnnotation()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry = builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var project = builder.AddProject("api");
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ // Simulate BeforeStartEvent to add RegistryTargetAnnotation
+ var beforeStartEvent = new BeforeStartEvent(app.Services, appModel);
+ await builder.Eventing.PublishAsync(beforeStartEvent);
+
+ // GetContainerRegistry should return the registry from RegistryTargetAnnotation
+ var containerRegistry = project.Resource.GetContainerRegistry();
+ Assert.Same(registry.Resource, containerRegistry);
+ }
+
+ [Fact]
+ public async Task GetContainerRegistryPrefersContainerRegistryReferenceAnnotationOverRegistryTargetAnnotation()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry1 = builder.AddContainerRegistry("docker-hub", "docker.io", "user1");
+ var registry2 = builder.AddContainerRegistry("ghcr", "ghcr.io", "user2");
+ var project = builder.AddProject("api")
+ .WithContainerRegistry(registry2); // Explicit preference for registry2
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ // Simulate BeforeStartEvent to add RegistryTargetAnnotations
+ var beforeStartEvent = new BeforeStartEvent(app.Services, appModel);
+ await builder.Eventing.PublishAsync(beforeStartEvent);
+
+ // GetContainerRegistry should return registry2 (from ContainerRegistryReferenceAnnotation)
+ // even though both registries added RegistryTargetAnnotations
+ var containerRegistry = project.Resource.GetContainerRegistry();
+ Assert.Same(registry2.Resource, containerRegistry);
+ }
+
+ [Fact]
+ public async Task GetContainerRegistryThrowsWhenMultipleRegistryTargetAnnotationsAndNoExplicitSelection()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry1 = builder.AddContainerRegistry("docker-hub", "docker.io", "user1");
+ var registry2 = builder.AddContainerRegistry("ghcr", "ghcr.io", "user2");
+ var project = builder.AddProject("api");
+ // No explicit WithContainerRegistry call
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ // Simulate BeforeStartEvent to add RegistryTargetAnnotations
+ var beforeStartEvent = new BeforeStartEvent(app.Services, appModel);
+ await builder.Eventing.PublishAsync(beforeStartEvent);
+
+ // GetContainerRegistry should throw because there are multiple registries and no explicit selection
+ var exception = Assert.Throws(project.Resource.GetContainerRegistry);
+ Assert.Contains("multiple container registries", exception.Message);
+ Assert.Contains("WithContainerRegistry", exception.Message);
+ }
+
+ [Fact]
+ public void GetContainerRegistryThrowsWhenNoRegistryAvailable()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var project = builder.AddProject("api");
+ // No container registry added
+
+ // GetContainerRegistry should throw because there's no registry available
+ var exception = Assert.Throws(project.Resource.GetContainerRegistry);
+ Assert.Contains("does not have a container registry reference", exception.Message);
+ }
+
+ [Fact]
+ public async Task RegistryTargetAnnotationIsAddedToAllResourcesInModel()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var registry = builder.AddContainerRegistry("docker-hub", "docker.io", "myuser");
+ var project1 = builder.AddProject("api1");
+ var project2 = builder.AddProject("api2");
+ var container = builder.AddContainer("redis", "redis:latest");
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ // Simulate BeforeStartEvent
+ var beforeStartEvent = new BeforeStartEvent(app.Services, appModel);
+ await builder.Eventing.PublishAsync(beforeStartEvent);
+
+ // All resources should have RegistryTargetAnnotation
+ Assert.Single(project1.Resource.Annotations.OfType());
+ Assert.Single(project2.Resource.Annotations.OfType());
+ Assert.Single(container.Resource.Annotations.OfType());
+ }
+
+ [Fact]
+ public async Task WithContainerRegistryOverridesDefaultRegistryTargetAnnotation()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
+
+ var defaultRegistry = builder.AddContainerRegistry("docker-hub", "docker.io", "default");
+ var specificRegistry = builder.AddContainerRegistry("acr", "myregistry.azurecr.io", "specific");
+
+ var project = builder.AddProject("api")
+ .WithContainerRegistry(specificRegistry);
+
+ using var app = builder.Build();
+ var appModel = app.Services.GetRequiredService();
+
+ // Simulate BeforeStartEvent
+ var beforeStartEvent = new BeforeStartEvent(app.Services, appModel);
+ await builder.Eventing.PublishAsync(beforeStartEvent);
+
+ // The project has both RegistryTargetAnnotations (from BeforeStartEvent) and ContainerRegistryReferenceAnnotation
+ var registryTargetAnnotations = project.Resource.Annotations.OfType().ToList();
+ Assert.Equal(2, registryTargetAnnotations.Count);
+
+ var containerRegistryRefAnnotation = Assert.Single(project.Resource.Annotations.OfType());
+ Assert.Same(specificRegistry.Resource, containerRegistryRefAnnotation.Registry);
+
+ // GetContainerRegistry should return specificRegistry because ContainerRegistryReferenceAnnotation takes precedence
+ var containerRegistry = project.Resource.GetContainerRegistry();
+ Assert.Same(specificRegistry.Resource, containerRegistry);
+ }
}
diff --git a/tests/Aspire.Hosting.Tests/Pipelines/DistributedApplicationPipelineTests.cs b/tests/Aspire.Hosting.Tests/Pipelines/DistributedApplicationPipelineTests.cs
index edcc4c8e9d3..b5f704c9be1 100644
--- a/tests/Aspire.Hosting.Tests/Pipelines/DistributedApplicationPipelineTests.cs
+++ b/tests/Aspire.Hosting.Tests/Pipelines/DistributedApplicationPipelineTests.cs
@@ -1422,12 +1422,14 @@ public async Task ExecuteAsync_WithConfigurationCallback_ExecutesCallback()
await pipeline.ExecuteAsync(context).DefaultTimeout();
Assert.True(callbackExecuted);
- Assert.Equal(10, capturedSteps.Count); // Updated to account for all default steps including process-parameters
+ Assert.Equal(12, capturedSteps.Count); // Updated to account for all default steps including process-parameters, push, push-prereq
Assert.Contains(capturedSteps, s => s.Name == "deploy");
Assert.Contains(capturedSteps, s => s.Name == "process-parameters");
Assert.Contains(capturedSteps, s => s.Name == "deploy-prereq");
Assert.Contains(capturedSteps, s => s.Name == "build");
Assert.Contains(capturedSteps, s => s.Name == "build-prereq");
+ Assert.Contains(capturedSteps, s => s.Name == "push");
+ Assert.Contains(capturedSteps, s => s.Name == "push-prereq");
Assert.Contains(capturedSteps, s => s.Name == "publish");
Assert.Contains(capturedSteps, s => s.Name == "publish-prereq");
Assert.Contains(capturedSteps, s => s.Name == "diagnostics");
diff --git a/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs b/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs
index 56ec7bd1179..01eff1f94d8 100644
--- a/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs
+++ b/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs
@@ -753,23 +753,26 @@ public async Task ProjectResource_AutomaticallyGeneratesBuildStep_WithCorrectDep
var resource = Assert.Single(projectResources);
- // Verify the project has a PipelineStepAnnotation
+ // Verify the project has a single PipelineStepAnnotation that emits build and push steps
var pipelineStepAnnotation = Assert.Single(resource.Annotations.OfType());
- // Create a factory context for testing the annotation
var factoryContext = new PipelineStepFactoryContext
{
- PipelineContext = null!, // Not needed for this test
+ PipelineContext = null!,
Resource = resource
};
var steps = (await pipelineStepAnnotation.CreateStepsAsync(factoryContext)).ToList();
+ Assert.Equal(2, steps.Count);
- var buildStep = Assert.Single(steps);
- Assert.Equal("build-test-project", buildStep.Name);
+ var buildStep = steps.First(s => s.Name == "build-test-project");
Assert.Contains(WellKnownPipelineTags.BuildCompute, buildStep.Tags);
Assert.Contains(WellKnownPipelineSteps.Build, buildStep.RequiredBySteps);
Assert.Contains(WellKnownPipelineSteps.BuildPrereq, buildStep.DependsOnSteps);
+
+ var pushStep = steps.First(s => s.Name == "push-test-project");
+ Assert.Contains(WellKnownPipelineTags.PushContainerImage, pushStep.Tags);
+ Assert.Contains(WellKnownPipelineSteps.Push, pushStep.RequiredBySteps);
}
[Fact]
@@ -799,8 +802,7 @@ public void ProjectResourceWithContainerFilesDestinationAnnotationCreatesPipelin
Assert.Equal(sourceContainer.Resource, containerFilesAnnotation.Source);
Assert.Equal("./wwwroot", containerFilesAnnotation.DestinationPath);
- var pipelineStepAnnotations = resource.Annotations.OfType().ToList();
- Assert.Single(pipelineStepAnnotations);
+ Assert.Single(resource.Annotations.OfType());
}
[Fact]