-
Notifications
You must be signed in to change notification settings - Fork 736
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
Conversation
There was a problem hiding this 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()
| if (context.ExecutionContext.IsRunMode) | ||
| { | ||
| template.Dispose(); | ||
| } |
Copilot
AI
Aug 6, 2025
There was a problem hiding this comment.
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.
| if (context.ExecutionContext.IsRunMode) | |
| { | |
| template.Dispose(); | |
| } | |
| template.Dispose(); |
| // TODO: Prompt here. | ||
| await deployingStep.FailAsync("Deployment contains unresolvable parameters.", cancellationToken).ConfigureAwait(false); |
Copilot
AI
Aug 6, 2025
There was a problem hiding this comment.
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.
| // 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."); |
| if (string.IsNullOrEmpty(_options.ResourceGroup)) | ||
| { | ||
| // Generate an resource group name since none was provided | ||
| // Create a unique resource group name and save it in user secrets |
There was a problem hiding this comment.
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.
| } | ||
|
|
||
| var principal = await userPrincipalProvider.GetUserPrincipalAsync(cancellationToken).ConfigureAwait(false); | ||
| var outputPath = _publishingOptions.OutputPath is { } outputPathValue ? Path.GetFullPath(outputPathValue) : null; |
There was a problem hiding this comment.
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 }, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| if (deployment.Data.Properties.ProvisioningState == ResourcesProvisioningState.Succeeded) | ||
| { | ||
| template.Dispose(); | ||
| if (context.ExecutionContext.IsRunMode) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
| /// </summary> | ||
| [Experimental("ASPIREAZURE001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")] | ||
| public sealed class AzureEnvironmentResource : Resource | ||
| public sealed class AzureEnvironmentResource : AzureBicepResource |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
Yep.
Should this be an AzureProvisoningResource instead?
AzureProvisioningResource encodes some concepts that I don't think make sense for main.bicep (role assignments, existing resources). Also, main.bicep isn't a provisionable resource itself but more so a set of provisioned resources so I feel like it makes more sense to just model it as a Bicep resource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make it one? The underlying logic in AzureProvisioningContext creates an infrastructure object. I don’t think it need to be in this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it make it one?
We need to be able to set outputs from the deployment onto the AzureEnvironmentResource so that we can propogate them to future steps (e.g. resolving the registry info for the ACR instance that we automatically provision) and we need a shape that matches the API of the AzureBicepProvisioner.
If AzureEnvironmentResource doesn't implement AzureBicepResource, how do you initiate the provisioning of main.bicep without modifying BicepProvisioner?
|
So I jumped straight into doing aspire deploy ... I didn't run it first - so the parameters were not set. |
|
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this happen?
There was a problem hiding this comment.
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.
|
@mitchdenny b8beec3 includes fixes for the two issues you ran into. The exception you got during 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 The other issue was due to the fact that setting the |
b8beec3 to
9738c8e
Compare
|
So once I cleared out the old deployment the deployment completed successfully, but the container app wasn't deployed (the environment was though). Just not there yet? |
|
.. container images didn't get pushed either. |
mitchdenny
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall I think this looks like a good incremental step. What are the issues with merging this now in terms of shipping 9.5?
|
Biggest problem is have (and it's small) is this |
|
Because of the scope of this change we want to get this in earlier than later so we can flush out any issues. |
Yep, it's just based on the Azure environment resource name which is in turn based on the AppHost hash. I'll push a change here to make it more unique to the deployment.
Yep, this change only deploys the "main.bicep" to the target environment. It doesn't handle pushing container images or deploying the container app instances themselves. The prototype branch shows what the north star looks like here although I dunno if the "push image to registry" action will end up being inlined.
Yeah, #10448 tracks the overall flow here and followup issues.
Ah, I missed this comment earlier. I'll have to wrap my head around what the API shape for this would look like but I do agree that there is probably some relationship between existing resources in another subscription and this change. I think this is where we probably need to start modeling |
Check in with what AZD does here. There is a balance to be had. We don't want deployments of the same apphost to two different RGs to conflict with their generated deployment ID, but there is a limit to the number of deployments within a subscription and then yuo have to go an purge old deployments - so you don't necessarily want to recreate it each time. It might make sense to replicate what AZD does here. |
Yep, c2b96cd uses Unix timestamp to differentiate the same way azd does. azd uses the environment name (aka resource group name) as the prefix whereas we're currently using the resource name (which is hashed with the AppHost SHA). The timestamp suffix feels like a good enough differentiator for now although we might end up incoroporating the environment name into the deployment name too. |
|
@captainsafia ship it! |




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 currentBicepProvisionerimplementation assumes that only one resource is deployed and that status is set through theResourceNotificationService.