Skip to content

Hook up Azure Deployer to BicepProvisioner #10845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

captainsafia
Copy link
Member

@captainsafia captainsafia commented Aug 6, 2025

Contributes towards #10448.

The next step here is to revise the APIs for IBicepProvisioner so that it is possible to query the ARM deployment to get the status on each subresource that is deployed from main.bicep. The current BicepProvisioner implementation assumes that only one resource is deployed and that status is set through the ResourceNotificationService.

aspire-deploy-azure-2

@Copilot Copilot AI review requested due to automatic review settings August 6, 2025 00:33
@github-actions github-actions bot added the area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication label Aug 6, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR connects the Azure Deployer to the BicepProvisioner, enabling deployment-time provisioning of Azure resources. The changes focus on extending the existing provisioning infrastructure to handle both run-time and deploy-time scenarios.

Key changes:

  • Enhanced ProvisioningContext to include execution context and output path information
  • Modified BicepProvisioner to handle both subscription-scoped and resource group-scoped deployments
  • Connected AzureEnvironmentResource to use BicepProvisioner during deployment phase

Reviewed Changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Aspire.Hosting.Azure/Provisioning/ProvisioningContext.cs Added execution context and output path to provisioning context
src/Aspire.Hosting.Azure/Provisioning/Provisioners/IBicepProvisioner.cs New interface for Bicep provisioning operations
src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs Enhanced to support both run-time and deploy-time provisioning modes
src/Aspire.Hosting.Azure/AzureEnvironmentResource.cs Changed inheritance to AzureBicepResource and integrated deployment logic
src/Aspire.Hosting.Azure/AzureDeployingContext.cs Added comprehensive deployment orchestration with parameter mapping
src/Aspire.Hosting.Azure/AzureBicepResource.cs Fixed template file path handling for custom output directories
tests/Aspire.Hosting.Azure.Tests/* Updated test infrastructure to support new interfaces and parameters
playground/deployers/Deployers.AppHost/* New playground project demonstrating Azure deployment capabilities
Comments suppressed due to low confidence (1)

tests/Aspire.Hosting.Azure.Tests/ProvisioningTestHelpers.cs:187

  • The TestArmDeploymentCollection implementation is referenced but not defined in this file. This could cause test failures if the implementation is missing or incomplete.
    public IArmDeploymentCollection GetArmDeployments()

Comment on lines +208 to +211
if (context.ExecutionContext.IsRunMode)
{
template.Dispose();
}
Copy link
Preview

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The disposal of the template should happen regardless of execution mode. Moving the template.Dispose() call outside the if block would ensure proper resource cleanup in all scenarios.

Suggested change
if (context.ExecutionContext.IsRunMode)
{
template.Dispose();
}
template.Dispose();

Copilot uses AI. Check for mistakes.

Comment on lines +49 to +50
// TODO: Prompt here.
await deployingStep.FailAsync("Deployment contains unresolvable parameters.", cancellationToken).ConfigureAwait(false);
Copy link
Preview

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO comment indicates incomplete functionality for handling unresolvable parameters. This could leave the deployment in an inconsistent state without proper parameter resolution.

Suggested change
// TODO: Prompt here.
await deployingStep.FailAsync("Deployment contains unresolvable parameters.", cancellationToken).ConfigureAwait(false);
throw new InvalidOperationException(
$"Deployment contains unresolvable parameter: '{provisioningParameter.BicepIdentifier}'. " +
"Please ensure all required parameters are provided or handled before deployment.");

Copilot uses AI. Check for mistakes.

@@ -217,14 +220,14 @@ public async Task<ProvisioningContext> CreateProvisioningContextAsync(JsonObject
// Create a unique resource group name and save it in user secrets
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We never create a resource group via the ProvisioningContextProvider when in publish mode and always rely on it to be provisoned via the declaration in main.bicep.

@@ -259,6 +262,7 @@ public async Task<ProvisioningContext> CreateProvisioningContextAsync(JsonObject
}

var principal = await userPrincipalProvider.GetUserPrincipalAsync(cancellationToken).ConfigureAwait(false);
var outputPath = _publishingOptions.OutputPath is { } outputPathValue ? Path.GetFullPath(outputPathValue) : null;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We past the output path to the ProvisioningContext during deployment so that we can reuse the bicep templates that were generated by the publish step during deployment.

}),
cancellationToken).ConfigureAwait(false);
})
{ Location = context.Location },
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARM requires a location when doing a subscription-scoped deployment so we pass it to ArmDeploymentContent here. There's no consequence to setting the location for RG-based deployments so setting this up conditionally.

var deployments = resourceGroup.GetArmDeployments();
// Deploy-time provisioning should target the subscription scope while run-time
// provisioning should target the resource group scope.
var deployments = context.ExecutionContext.IsPublishMode
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the places where we need to differentiate between run-mode and publish-mode in the provisioner. Deployments during publish-mode are subscription scoped and need to be resolved differently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be based on the mode. This should be part of the Scope property on the AzureBicepResource. We'll need to do this #7514 and I suspect it'll look similar to this.

@@ -198,48 +205,54 @@ await notificationService.PublishUpdateAsync(resource, state =>

if (deployment.Data.Properties.ProvisioningState == ResourcesProvisioningState.Succeeded)
{
template.Dispose();
if (context.ExecutionContext.IsRunMode)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to keep the template files used to do the deployment around during deploy mode (for now: see #10642).

/// <summary>
/// Provides functionality for provisioning Azure Bicep resources.
/// </summary>
internal interface IBicepProvisioner
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface exists to support testability. We need an IBicepProvisioner implementation that no-ops in tests.

/// <param name="context">The provisioning context containing Azure subscription, resource group, and other deployment details.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task GetOrCreateResourceAsync(AzureBicepResource resource, ProvisioningContext context, CancellationToken cancellationToken);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method currently returns no value but I can see it evolving to return a type that we can use to poll the underlying ArmOperation for their state so we can get granular state updates on each resource deployed from main.bicep in the future.

@@ -17,7 +19,7 @@ namespace Aspire.Hosting.Azure;
/// Emits a <c>main.bicep</c> that aggregates all provisionable resources.
/// </summary>
[Experimental("ASPIREAZURE001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")]
public sealed class AzureEnvironmentResource : Resource
public sealed class AzureEnvironmentResource : AzureBicepResource
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AzureEnvironmentResource needs to implement AzureBicepResource so we can set parameters and store outputs from the ARM deployment, like the ACR instance associated with a container apps environment for future image pushes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this represents main.bicep now? Should this be an AzureProvisoningResource instead?

@captainsafia captainsafia added area-deployment azure Issues associated specifically with scenarios tied to using Azure and removed area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication labels Aug 6, 2025
@@ -0,0 +1,22 @@
var builder = DistributedApplication.CreateBuilder(args);

builder.AddAzureContainerAppEnvironment("env");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its kind of strange that we add AddAzureContainerAppEnvironment(...) when we aren't using anything other than Azure resources. I realize this is probably just the case for this playground but I wonder if we will see cases of people using apphosts to deploy things without any compute (containers/projects) because the resources they deploy have some built-in compute functionality that they are leveraging.

In which case - does that API name make sense?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see a world where you only only use Aspire deploy to deploy Azure resources and not any actual compute resources. For example, I saw a scenario recently on a static front-end deployed on an Azure Storage instance with Azure Front Door for routing.

We've set up the APIs for AddAcaEnv such that you don't really "discover" you need them until you try to publish or deploy a project with compute resources (or role assignments).

We've really overloaded the term "environment" in our APIs and I'm not sure how we dig ourselves out of it. Although it won't really be exposed in public API to the user until the add compute.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today we need a compute environment or we'll generate invalid bicep. If you provision an azure resource that depends on managed identity (which we do by default) but don't provide a compute environment, we'll generate invalid bicep (with a missing user assigned managed identity).

@mitchdenny
Copy link
Member

Do I need to pre-create the resource group at the moment?
image

@mitchdenny
Copy link
Member

So I jumped straight into doing aspire deploy ... I didn't run it first - so the parameters were not set.

@captainsafia
Copy link
Member Author

Do I need to pre-create the resource group at the moment?

image

Uh-oh! I think this is something I need to fix. I realize I didn't validate the "create resource group from main bicep" flow since I was using an existing resource group.

@mitchdenny
Copy link
Member

mitchdenny commented Aug 6, 2025

Failing to provision in the dashboard also:

image

@mitchdenny
Copy link
Member

So it looked to me like it was building the storage roles and storage templates simultaneously in the dashboard. That is probably OK - but I'm wondering if it tries to start deploying them simultaneously as well which would be an issue.

await provisioningContextProvider.CreateProvisioningContextAsync(userSecrets, cancellationToken).ConfigureAwait(false);
var provisioningContext = await provisioningContextProvider.CreateProvisioningContextAsync(userSecrets, cancellationToken).ConfigureAwait(false);

if (resource.PublishingContext is null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this happen?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently -- never. I left this as an explicit check for when we do #10620.

@captainsafia
Copy link
Member Author

@mitchdenny b8beec3 includes fixes for the two issues you ran into.

The exception you got during aspire deploy about resource groups was due to the fact that I was skipping automatic resource group creation via the ProvisioniningContextProvider during publish so that we can rely on the provisioning that happens in main.bicep.

Adding some more conditional logic based on modes to the context provider was annoying but I realized that resource group creation is idempotent since we use ArmDeploymentMode.Incremental so we can always create the resource group via the SDK on the apphost side and not need to change the bicep.

The other issue was due to the fact that setting the Location on the ARM demployment is only supported on subscription-scoped deployments, which we only do during aspire deploy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-deployment azure Issues associated specifically with scenarios tied to using Azure
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants