Skip to content

Commit

Permalink
Allow customizing Azure ContainerApp resources with ProvisioningBuild…
Browse files Browse the repository at this point in the history
…Options.

Fix dotnet#6496
  • Loading branch information
eerhardt committed Oct 25, 2024
1 parent 5e94eca commit b1760c1
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@
using Azure.Provisioning.KeyVault;
using Azure.Provisioning.Resources;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Aspire.Hosting.Azure;

/// <summary>
/// Represents the infrastructure for Azure Container Apps within the Aspire Hosting environment.
/// Implements the <see cref="IDistributedApplicationLifecycleHook"/> interface to provide lifecycle hooks for distributed applications.
/// </summary>
internal sealed class AzureContainerAppsInfrastructure(ILogger<AzureContainerAppsInfrastructure> logger, DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook
internal sealed class AzureContainerAppsInfrastructure(
ILogger<AzureContainerAppsInfrastructure> logger,
IOptions<AzureProvisioningOptions> provisioningOptions,
DistributedApplicationExecutionContext executionContext) : IDistributedApplicationLifecycleHook
{
public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -48,7 +52,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
continue;
}

var containerApp = await containerAppEnvironmentContext.CreateContainerAppAsync(r, executionContext, cancellationToken).ConfigureAwait(false);
var containerApp = await containerAppEnvironmentContext.CreateContainerAppAsync(r, provisioningOptions.Value, executionContext, cancellationToken).ConfigureAwait(false);

r.Annotations.Add(new DeploymentTargetAnnotation(containerApp));
}
Expand All @@ -74,11 +78,12 @@ IManifestExpressionProvider clientId

private readonly Dictionary<IResource, ContainerAppContext> _containerApps = [];

public async Task<AzureBicepResource> CreateContainerAppAsync(IResource resource, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken)
public async Task<AzureBicepResource> CreateContainerAppAsync(IResource resource, AzureProvisioningOptions provisioningOptions, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken)
{
var context = await ProcessResourceAsync(resource, executionContext, cancellationToken).ConfigureAwait(false);

var provisioningResource = new AzureProvisioningResource(resource.Name, context.BuildContainerApp);
provisioningResource.ProvisioningBuildOptions = provisioningOptions.ProvisioningBuildOptions;

provisioningResource.Annotations.Add(new ManifestPublishingCallbackAnnotation(provisioningResource.WriteToManifest));

Expand Down
90 changes: 90 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using System.Runtime.CompilerServices;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using Azure.Provisioning;
using Azure.Provisioning.AppContainers;
using Azure.Provisioning.Primitives;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -1165,6 +1167,94 @@ param outputs_azure_container_apps_environment_id string
Assert.Equal(expectedBicep, bicep);
}

[Fact]
public async Task CanCustomizeWithProvisioningBuildOptions()
{
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);

builder.Services.Configure<AzureProvisioningOptions>(options => options.ProvisioningBuildOptions.InfrastructureResolvers.Insert(0, new MyResourceNamePropertyResolver()));
builder.AddAzureContainerAppsInfrastructure();

builder.AddContainer("api1", "myimage");

using var app = builder.Build();

await ExecuteBeforeStartHooksAsync(app, default);

var model = app.Services.GetRequiredService<DistributedApplicationModel>();

var container = Assert.Single(model.GetContainerResources());

container.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target);

var resource = target?.DeploymentTarget as AzureProvisioningResource;

Assert.NotNull(resource);

var (_, bicep) = await ManifestUtils.GetManifestWithBicep(resource);

var expectedBicep =
"""
@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location
param outputs_azure_container_registry_managed_identity_id string
param outputs_managed_identity_client_id string
param outputs_azure_container_apps_environment_id string
resource api1 'Microsoft.App/containerApps@2024-03-01' = {
name: 'api1-my'
location: location
properties: {
configuration: {
activeRevisionsMode: 'Single'
}
environmentId: outputs_azure_container_apps_environment_id
template: {
containers: [
{
image: 'myimage:latest'
name: 'api1'
env: [
{
name: 'AZURE_CLIENT_ID'
value: outputs_managed_identity_client_id
}
]
}
]
scale: {
minReplicas: 1
}
}
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${outputs_azure_container_registry_managed_identity_id}': { }
}
}
}
""";
output.WriteLine(bicep);
Assert.Equal(expectedBicep, bicep);
}

private sealed class MyResourceNamePropertyResolver : DynamicResourceNamePropertyResolver
{
public override void ResolveProperties(ProvisionableConstruct construct, ProvisioningBuildOptions options)
{
if (construct is ContainerApp app)
{
app.Name = app.Name.Value + "-my";
}

base.ResolveProperties(construct, options);
}
}

[Fact]
public async Task ExternalEndpointBecomesIngress()
{
Expand Down

0 comments on commit b1760c1

Please sign in to comment.