Skip to content

Commit 53c74d8

Browse files
authored
Verify multi-compute environment deployment for Azure (dotnet#11100)
1 parent b57f00b commit 53c74d8

File tree

4 files changed

+156
-9
lines changed

4 files changed

+156
-9
lines changed

playground/deployers/Deployers.AppHost/AppHost.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
#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.
2+
13
var builder = DistributedApplication.CreateBuilder(args);
24

3-
builder.AddAzureContainerAppEnvironment("env");
5+
var aca = builder.AddAzureContainerAppEnvironment("aca-env");
6+
var aas = builder.AddAzureAppServiceEnvironment("aas-env");
47

58
var storage = builder.AddAzureStorage("storage");
69

@@ -9,14 +12,17 @@
912
storage.AddBlobContainer("mycontainer2", blobContainerName: "test-container-2");
1013
storage.AddQueue("myqueue", queueName: "my-queue");
1114

12-
builder.AddRedis("cache");
15+
builder.AddRedis("cache")
16+
.WithComputeEnvironment(aca);
1317

1418
builder.AddProject<Projects.Deployers_ApiService>("api-service")
15-
.WithExternalHttpEndpoints();
19+
.WithExternalHttpEndpoints()
20+
.WithComputeEnvironment(aas);
1621

1722
builder.AddDockerfile("python-app", "../Deployers.Dockerfile")
1823
.WithHttpEndpoint(targetPort: 80)
19-
.WithExternalHttpEndpoints();
24+
.WithExternalHttpEndpoints()
25+
.WithComputeEnvironment(aca);
2026

2127
#if !SKIP_DASHBOARD_REFERENCE
2228
// This project is only added in playground projects to support development/debugging

playground/deployers/Deployers.AppHost/Deployers.AppHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
<ItemGroup>
1717
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.AppContainers" />
18+
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.AppService" />
1819
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure" />
1920
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.Storage" />
2021
<AspireProjectOrPackageReference Include="Aspire.Hosting.Redis" />

src/Aspire.Hosting.Azure/AzureDeployingContext.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private async Task<bool> TryProvisionAzureInfrastructure(AzureEnvironmentResourc
116116

117117
private async Task<bool> TryDeployContainerImages(DistributedApplicationModel model, CancellationToken cancellationToken)
118118
{
119-
var computeResources = model.GetComputeResources();
119+
var computeResources = model.GetComputeResources().Where(r => r.RequiresImageBuildAndPush());
120120

121121
if (!computeResources.Any())
122122
{
@@ -188,7 +188,7 @@ private async Task DeployComputeResource(IPublishingStep parentStep, IResource c
188188
{
189189
try
190190
{
191-
if (computeResource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var deploymentTarget))
191+
if (computeResource.GetDeploymentTargetAnnotation() is { } deploymentTarget)
192192
{
193193
if (deploymentTarget.DeploymentTarget is AzureBicepResource bicepResource)
194194
{
@@ -223,7 +223,7 @@ private async Task DeployComputeResource(IPublishingStep parentStep, IResource c
223223

224224
private static bool TryGetContainerRegistry(IResource computeResource, [NotNullWhen(true)] out IContainerRegistry? containerRegistry)
225225
{
226-
if (computeResource.TryGetLastAnnotation<DeploymentTargetAnnotation>(out var deploymentTarget) &&
226+
if (computeResource.GetDeploymentTargetAnnotation() is { } deploymentTarget &&
227227
deploymentTarget.ContainerRegistry is { } registry)
228228
{
229229
containerRegistry = registry;

tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ public async Task DeployAsync_WithContainer_Works()
188188
Assert.Equal("test.westus.azurecontainerapps.io", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"]);
189189
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"]);
190190

191-
// Assert - Verify ACR login command was called
192-
Assert.Contains(mockProcessRunner.ExecutedCommands,
191+
// Assert - Verify ACR login command was not called since no image was pushed
192+
Assert.DoesNotContain(mockProcessRunner.ExecutedCommands,
193193
cmd => cmd.ExecutablePath.Contains("az") &&
194194
cmd.Arguments == "acr login --name testregistry");
195195

@@ -205,6 +205,57 @@ public async Task DeployAsync_WithContainer_Works()
205205
cmd.Arguments.StartsWith("push testregistry.azurecr.io/"));
206206
}
207207

208+
[Fact]
209+
public async Task DeployAsync_WithDockerfile_Works()
210+
{
211+
// Arrange
212+
var mockProcessRunner = new MockProcessRunner();
213+
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, publisher: "default", isDeploy: true);
214+
var deploymentOutputs = new Dictionary<string, object>
215+
{
216+
["env_AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
217+
["env_AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
218+
["env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
219+
["env_AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
220+
["env_AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" }
221+
};
222+
var armClientProvider = new TestArmClientProvider(deploymentOutputs);
223+
ConfigureTestServices(builder, armClientProvider: armClientProvider, processRunner: mockProcessRunner);
224+
225+
var containerAppEnv = builder.AddAzureContainerAppEnvironment("env");
226+
var azureEnv = builder.AddAzureEnvironment();
227+
builder.AddDockerfile("api", "api.Dockerfile");
228+
229+
// Act
230+
using var app = builder.Build();
231+
await app.StartAsync();
232+
await app.WaitForShutdownAsync();
233+
234+
// Assert that container environment outputs are propagated to outputs because they are
235+
// hoisted up for the container resource
236+
Assert.Equal("testregistry", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_NAME"]);
237+
Assert.Equal("testregistry.azurecr.io", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_ENDPOINT"]);
238+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"]);
239+
Assert.Equal("test.westus.azurecontainerapps.io", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"]);
240+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"]);
241+
242+
// Assert - Verify ACR login command was called since Dockerfile image needs to be pushed
243+
Assert.Contains(mockProcessRunner.ExecutedCommands,
244+
cmd => cmd.ExecutablePath.Contains("az") &&
245+
cmd.Arguments == "acr login --name testregistry");
246+
247+
// Assert - Verify Docker tag and push called for Dockerfile
248+
Assert.Contains(mockProcessRunner.ExecutedCommands,
249+
cmd => cmd.ExecutablePath == "docker" &&
250+
cmd.Arguments != null &&
251+
cmd.Arguments.StartsWith("tag api testregistry.azurecr.io/"));
252+
253+
Assert.Contains(mockProcessRunner.ExecutedCommands,
254+
cmd => cmd.ExecutablePath == "docker" &&
255+
cmd.Arguments != null &&
256+
cmd.Arguments.StartsWith("push testregistry.azurecr.io/"));
257+
}
258+
208259
[Fact]
209260
public async Task DeployAsync_WithProjectResource_Works()
210261
{
@@ -256,6 +307,95 @@ public async Task DeployAsync_WithProjectResource_Works()
256307
cmd.Arguments.StartsWith("push testregistry.azurecr.io/"));
257308
}
258309

310+
[Fact]
311+
public async Task DeployAsync_WithMultipleComputeEnvironments_Works()
312+
{
313+
// Arrange
314+
var mockProcessRunner = new MockProcessRunner();
315+
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, publisher: "default", isDeploy: true);
316+
var deploymentOutputs = new Dictionary<string, object>
317+
{
318+
["aca_env_AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "acaregistry" },
319+
["aca_env_AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "acaregistry.azurecr.io" },
320+
["aca_env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aca-identity" },
321+
["aca_env_AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "aca.westus.azurecontainerapps.io" },
322+
["aca_env_AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/acaenv" },
323+
["aas_env_AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "aasregistry" },
324+
["aas_env_AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "aasregistry.azurecr.io" },
325+
["aas_env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aas-identity" },
326+
["aas_env_planId"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Web/serverfarms/aasplan" },
327+
["aas_env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID"] = new { type = "String", value = "aas-client-id" }
328+
};
329+
var armClientProvider = new TestArmClientProvider(deploymentOutputs);
330+
ConfigureTestServices(builder, armClientProvider: armClientProvider, processRunner: mockProcessRunner);
331+
332+
var acaEnv = builder.AddAzureContainerAppEnvironment("aca-env");
333+
var aasEnv = builder.AddAzureAppServiceEnvironment("aas-env");
334+
var azureEnv = builder.AddAzureEnvironment();
335+
336+
var storage = builder.AddAzureStorage("storage");
337+
storage.AddBlobContainer("mycontainer1", blobContainerName: "test-container-1");
338+
storage.AddBlobContainer("mycontainer2", blobContainerName: "test-container-2");
339+
storage.AddQueue("myqueue", queueName: "my-queue");
340+
341+
builder.AddRedis("cache").WithComputeEnvironment(acaEnv);
342+
builder.AddProject<Project>("api-service", launchProfileName: null).WithComputeEnvironment(aasEnv);
343+
builder.AddDockerfile("python-app", "python-app.Dockerfile").WithComputeEnvironment(acaEnv);
344+
345+
// Act
346+
using var app = builder.Build();
347+
await app.StartAsync();
348+
await app.WaitForShutdownAsync();
349+
350+
// Assert ACA environment outputs are properly set
351+
Assert.Equal("acaregistry", acaEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_NAME"]);
352+
Assert.Equal("acaregistry.azurecr.io", acaEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_ENDPOINT"]);
353+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aca-identity", acaEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"]);
354+
Assert.Equal("aca.westus.azurecontainerapps.io", acaEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"]);
355+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/acaenv", acaEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"]);
356+
357+
// Assert AAS environment outputs are properly set
358+
Assert.Equal("aasregistry", aasEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_NAME"]);
359+
Assert.Equal("aasregistry.azurecr.io", aasEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_ENDPOINT"]);
360+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aas-identity", aasEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"]);
361+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Web/serverfarms/aasplan", aasEnv.Resource.Outputs["planId"]);
362+
Assert.Equal("aas-client-id", aasEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID"]);
363+
364+
// Assert ACR login commands were called for both registries
365+
Assert.Contains(mockProcessRunner.ExecutedCommands,
366+
cmd => cmd.ExecutablePath.Contains("az") &&
367+
cmd.Arguments == "acr login --name acaregistry");
368+
Assert.Contains(mockProcessRunner.ExecutedCommands,
369+
cmd => cmd.ExecutablePath.Contains("az") &&
370+
cmd.Arguments == "acr login --name aasregistry");
371+
372+
// Assert Docker operations for project resource deployed to AAS environment
373+
Assert.Contains(mockProcessRunner.ExecutedCommands,
374+
cmd => cmd.ExecutablePath == "docker" &&
375+
cmd.Arguments != null &&
376+
cmd.Arguments.StartsWith("tag api-service aasregistry.azurecr.io/"));
377+
Assert.Contains(mockProcessRunner.ExecutedCommands,
378+
cmd => cmd.ExecutablePath == "docker" &&
379+
cmd.Arguments != null &&
380+
cmd.Arguments.StartsWith("push aasregistry.azurecr.io/"));
381+
382+
// Assert Docker operations NOT performed for existing container image deployed to ACA environment
383+
Assert.DoesNotContain(mockProcessRunner.ExecutedCommands,
384+
cmd => cmd.ExecutablePath == "docker" &&
385+
cmd.Arguments != null &&
386+
cmd.Arguments.StartsWith("tag cache acaregistry.azurecr.io/"));
387+
388+
// Assert Docker operations for project resource deployed to ACA environment
389+
Assert.Contains(mockProcessRunner.ExecutedCommands,
390+
cmd => cmd.ExecutablePath == "docker" &&
391+
cmd.Arguments != null &&
392+
cmd.Arguments.StartsWith("tag python-app acaregistry.azurecr.io/"));
393+
Assert.Contains(mockProcessRunner.ExecutedCommands,
394+
cmd => cmd.ExecutablePath == "docker" &&
395+
cmd.Arguments != null &&
396+
cmd.Arguments.StartsWith("push acaregistry.azurecr.io/"));
397+
}
398+
259399
private static void ConfigureTestServices(IDistributedApplicationTestingBuilder builder,
260400
IInteractionService? interactionService = null,
261401
IBicepProvisioner? bicepProvisioner = null,

0 commit comments

Comments
 (0)