Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@ public static IResourceBuilder<AzureContainerAppEnvironmentResource> AddAzureCon
{
var appEnvResource = (AzureContainerAppEnvironmentResource)infra.AspireResource;

// Check if this is an existing resource
if (appEnvResource.TryGetLastAnnotation<ExistingAzureResourceAnnotation>(out var _))
{
// For existing resources, just add the reference and outputs
var existingEnvironment = (ContainerAppManagedEnvironment)appEnvResource.AddAsExistingResource(infra);

// Add the output that container apps need to reference the environment
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_APPS_ENVIRONMENT_ID", typeof(string))
{
Value = existingEnvironment.Id
});

// Required for azd to output the dashboard URL
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN", typeof(string))
{
Value = existingEnvironment.DefaultDomain
});

return; // Skip creating all child resources
}

// This tells azd to avoid creating infrastructure
var userPrincipalId = new ProvisioningParameter(AzureBicepResource.KnownParameters.UserPrincipalId, typeof(string)) { Value = new BicepValue<string>(string.Empty) };
infra.Add(userPrincipalId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken

foreach (var r in @event.Model.GetComputeResources())
{
// Check if this resource should use this specific environment
if (r.TryGetLastAnnotation<ComputeEnvironmentAnnotation>(out var computeEnvAnnotation))
{
// Resource is bound to a specific environment
if (computeEnvAnnotation.ComputeEnvironment != environment)
{
// Skip this resource - it belongs to a different environment
continue;
}
}
else if (caes.Length > 1)
{
// Resource has no explicit environment binding but there are multiple environments.
// Skip it for now - it will be handled by GetDeploymentTargetAnnotation later
// which will throw a clear error if the resource is actually used.
continue;
}

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

// Capture information about the container registry used by the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@

namespace Aspire.Hosting.ApplicationModel;

internal sealed class ComputeEnvironmentAnnotation(IComputeEnvironmentResource computeEnvironment) : IResourceAnnotation
/// <summary>
/// Annotation that specifies which compute environment a resource should be deployed to.
/// </summary>
/// <param name="computeEnvironment">The compute environment that the resource should be deployed to.</param>
public sealed class ComputeEnvironmentAnnotation(IComputeEnvironmentResource computeEnvironment) : IResourceAnnotation
{
/// <summary>
/// Gets the compute environment that the resource should be deployed to.
/// </summary>
public IComputeEnvironmentResource ComputeEnvironment { get; } = computeEnvironment;
}
88 changes: 88 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2051,4 +2051,92 @@ public async Task GetHostAddressExpression()
Assert.Equal(env.Resource, output.Resource);
Assert.Equal("AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN", output.Name);
}

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

var environmentName = builder.AddParameter("environmentName");
var sharedResourceGroupName = builder.AddParameter("sharedResourceGroupName");

var existingEnv = builder.AddAzureContainerAppEnvironment("env")
.AsExisting(environmentName, sharedResourceGroupName);

builder.AddContainer("api", "myimage")
.WithComputeEnvironment(existingEnv)
.PublishAsAzureContainerApp((_, _) => { });

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 (manifest, bicep) = await GetManifestWithBicep(resource);

await Verify(manifest.ToString(), "json")
.AppendContentAsFile(bicep, "bicep");
}

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

var env1Name = builder.AddParameter("env1Name");
var env2Name = builder.AddParameter("env2Name");
var sharedResourceGroupName = builder.AddParameter("sharedResourceGroupName");

var env1 = builder.AddAzureContainerAppEnvironment("env1")
.AsExisting(env1Name, sharedResourceGroupName);

var env2 = builder.AddAzureContainerAppEnvironment("env2")
.AsExisting(env2Name, sharedResourceGroupName);

builder.AddContainer("api1", "myimage1")
.WithComputeEnvironment(env1)
.PublishAsAzureContainerApp((_, _) => { });

builder.AddContainer("api2", "myimage2")
.WithComputeEnvironment(env2)
.PublishAsAzureContainerApp((_, _) => { });

using var app = builder.Build();

await ExecuteBeforeStartHooksAsync(app, default);

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

var containers = model.GetContainerResources().ToArray();
Assert.Equal(2, containers.Length);

var api1 = containers.Single(c => c.Name == "api1");
api1.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target1);
var resource1 = target1?.DeploymentTarget as AzureProvisioningResource;
Assert.NotNull(resource1);
Assert.Equal(env1.Resource, target1?.ComputeEnvironment);

var api2 = containers.Single(c => c.Name == "api2");
api2.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var target2);
var resource2 = target2?.DeploymentTarget as AzureProvisioningResource;
Assert.NotNull(resource2);
Assert.Equal(env2.Resource, target2?.ComputeEnvironment);

var (manifest1, bicep1) = await GetManifestWithBicep(resource1);
var (manifest2, bicep2) = await GetManifestWithBicep(resource2);

await Verify(manifest1.ToString(), "json")
.AppendContentAsFile(bicep1, "bicep")
.AppendContentAsFile(manifest2.ToString(), "json")
.AppendContentAsFile(bicep2, "bicep");
}
}
Loading