Skip to content

Commit cfb79b0

Browse files
captainsafiadavidfowlCopilot
authored
Verify Functions deployment via aspire deploy (#11117)
* Verify Functions deployment via aspire deploy * Update playground/deployers/Deployers.AppHost/AppHost.cs Co-authored-by: Copilot <[email protected]> * Update tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: David Fowler <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 0016ddb commit cfb79b0

File tree

5 files changed

+141
-8
lines changed

5 files changed

+141
-8
lines changed

playground/AzureFunctionsEndToEnd/AzureFunctionsEndToEnd.ApiService/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ static string RandomString(int length)
4141

4242
app.MapGet("/publish/blob", async (BlobServiceClient client, CancellationToken cancellationToken, int length = 20) =>
4343
{
44-
var container = client.GetBlobContainerClient("blobs");
44+
var container = client.GetBlobContainerClient("myblobcontainer");
4545
await container.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
4646

4747
var entry = new { Id = Guid.NewGuid(), Text = RandomString(length) };

playground/AzureFunctionsEndToEnd/AzureFunctionsEndToEnd.Functions/MyAzureBlobTrigger.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ public class MyAzureBlobTrigger(BlobContainerClient containerClient, ILogger<MyA
88
{
99
[Function(nameof(MyAzureBlobTrigger))]
1010
[BlobOutput("test-files/{name}.txt", Connection = "blob")]
11-
public async Task<string> RunAsync([BlobTrigger("blobs/{name}", Connection = "blob")] string triggerString, FunctionContext context)
11+
public async Task<string> RunAsync([BlobTrigger("myblobcontainer/{name}", Connection = "blob")] string triggerString, FunctionContext context)
1212
{
1313
var blobName = (string)context.BindingContext.BindingData["name"]!;
1414
await containerClient.UploadBlobAsync(blobName, new BinaryData(triggerString));
1515

16-
logger.LogInformation("C# blob trigger function invoked for 'blobs/{source}' with {message}...", blobName, triggerString);
16+
logger.LogInformation("C# blob trigger function invoked for 'myblobcontainer/{source}' with {message}...", blobName, triggerString);
1717
return triggerString.ToUpper();
1818
}
1919
}

playground/deployers/Deployers.AppHost/AppHost.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
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.
1+
#pragma warning disable ASPIRECOMPUTE001
2+
#pragma warning disable ASPIRECOSMOSDB001
23

34
var builder = DistributedApplication.CreateBuilder(args);
45

@@ -7,14 +8,33 @@
78

89
var storage = builder.AddAzureStorage("storage");
910

10-
storage.AddBlobs("blobs");
11-
storage.AddBlobContainer("mycontainer1", blobContainerName: "test-container-1");
12-
storage.AddBlobContainer("mycontainer2", blobContainerName: "test-container-2");
13-
storage.AddQueue("myqueue", queueName: "my-queue");
11+
var queue = storage.AddQueues("queue");
12+
var blob = storage.AddBlobs("foobarbaz");
13+
var myBlobContainer = storage.AddBlobContainer("myblobcontainer");
14+
15+
var eventHub = builder.AddAzureEventHubs("eventhubs")
16+
.RunAsEmulator()
17+
.AddHub("myhub");
18+
var serviceBus = builder.AddAzureServiceBus("messaging")
19+
.RunAsEmulator();
20+
serviceBus.AddServiceBusQueue("myqueue");
21+
var cosmosDb = builder.AddAzureCosmosDB("cosmosdb")
22+
.RunAsPreviewEmulator();
23+
var database = cosmosDb.AddCosmosDatabase("mydatabase");
24+
database.AddContainer("mycontainer", "/id");
1425

1526
builder.AddRedis("cache")
1627
.WithComputeEnvironment(aca);
1728

29+
builder.AddProject<Projects.AzureFunctionsEndToEnd_ApiService>("functions-api-service")
30+
.WithExternalHttpEndpoints()
31+
.WithComputeEnvironment(aas)
32+
.WithReference(eventHub).WaitFor(eventHub)
33+
.WithReference(serviceBus).WaitFor(serviceBus)
34+
.WithReference(cosmosDb).WaitFor(cosmosDb)
35+
.WithReference(queue)
36+
.WithReference(blob);
37+
1838
builder.AddProject<Projects.Deployers_ApiService>("api-service")
1939
.WithExternalHttpEndpoints()
2040
.WithComputeEnvironment(aas);
@@ -24,6 +44,16 @@
2444
.WithExternalHttpEndpoints()
2545
.WithComputeEnvironment(aca);
2646

47+
builder.AddAzureFunctionsProject<Projects.AzureFunctionsEndToEnd_Functions>("func-app")
48+
.WithExternalHttpEndpoints()
49+
.WithComputeEnvironment(aca)
50+
.WithReference(eventHub).WaitFor(eventHub)
51+
.WithReference(serviceBus).WaitFor(serviceBus)
52+
.WithReference(cosmosDb).WaitFor(cosmosDb)
53+
.WithReference(myBlobContainer).WaitFor(myBlobContainer)
54+
.WithReference(blob)
55+
.WithReference(queue);
56+
2757
#if !SKIP_DASHBOARD_REFERENCE
2858
// This project is only added in playground projects to support development/debugging
2959
// of the dashboard. It is not required in end developer code. Comment out this code

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@
1616
<ItemGroup>
1717
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.AppContainers" />
1818
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.AppService" />
19+
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.Functions" />
1920
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure" />
2021
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.Storage" />
2122
<AspireProjectOrPackageReference Include="Aspire.Hosting.Redis" />
23+
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.EventHubs" />
24+
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.ServiceBus" />
25+
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.CosmosDB" />
2226
</ItemGroup>
2327

2428
<ItemGroup>
2529
<ProjectReference Include="..\Deployers.ApiService\Deployers.ApiService.csproj" />
30+
<ProjectReference Include="..\..\AzureFunctionsEndToEnd\AzureFunctionsEndToEnd.ApiService\AzureFunctionsEndToEnd.ApiService.csproj" />
31+
<ProjectReference Include="..\..\AzureFunctionsEndToEnd\AzureFunctionsEndToEnd.Functions\AzureFunctionsEndToEnd.Functions.csproj" />
2632
</ItemGroup>
2733

2834
</Project>

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Microsoft.Extensions.Configuration;
1818
using Microsoft.Extensions.Hosting;
1919
using Aspire.TestUtilities;
20+
using Aspire.Hosting.ApplicationModel;
2021

2122
namespace Aspire.Hosting.Azure.Tests;
2223

@@ -396,6 +397,85 @@ public async Task DeployAsync_WithMultipleComputeEnvironments_Works()
396397
cmd.Arguments.StartsWith("push acaregistry.azurecr.io/"));
397398
}
398399

400+
[Fact]
401+
public async Task DeployAsync_WithAzureFunctionsProject_Works()
402+
{
403+
// Arrange
404+
var mockProcessRunner = new MockProcessRunner();
405+
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, publisher: "default", isDeploy: true);
406+
var deploymentOutputs = new Dictionary<string, object>
407+
{
408+
// ACA Environment outputs (needed for containerAppEnv)
409+
["env_AZURE_CONTAINER_REGISTRY_NAME"] = new { type = "String", value = "testregistry" },
410+
["env_AZURE_CONTAINER_REGISTRY_ENDPOINT"] = new { type = "String", value = "testregistry.azurecr.io" },
411+
["env_AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
412+
["env_AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"] = new { type = "String", value = "test.westus.azurecontainerapps.io" },
413+
["env_AZURE_CONTAINER_APPS_ENVIRONMENT_ID"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv" },
414+
415+
// Storage outputs for funcstorage
416+
["funcstorage_blobEndpoint"] = new { type = "String", value = "https://testfuncstorage.blob.core.windows.net/" },
417+
["funcstorage_queueEndpoint"] = new { type = "String", value = "https://testfuncstorage.queue.core.windows.net/" },
418+
["funcstorage_tableEndpoint"] = new { type = "String", value = "https://testfuncstorage.table.core.windows.net/" },
419+
420+
// Storage outputs for hoststorage
421+
["hoststorage_blobEndpoint"] = new { type = "String", value = "https://testhoststorage.blob.core.windows.net/" },
422+
["hoststorage_queueEndpoint"] = new { type = "String", value = "https://testhoststorage.queue.core.windows.net/" },
423+
["hoststorage_tableEndpoint"] = new { type = "String", value = "https://testhoststorage.table.core.windows.net/" },
424+
425+
["funcapp_identity_id"] = new { type = "String", value = "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" },
426+
["funcapp_identity_clientId"] = new { type = "String", value = "test-client-id" }
427+
};
428+
var armClientProvider = new TestArmClientProvider(deploymentOutputs);
429+
ConfigureTestServices(builder, armClientProvider: armClientProvider, processRunner: mockProcessRunner);
430+
431+
var containerAppEnv = builder.AddAzureContainerAppEnvironment("env");
432+
var azureEnv = builder.AddAzureEnvironment();
433+
434+
// Add Azure Storage for the Functions project
435+
var storage = builder.AddAzureStorage("funcstorage");
436+
var hostStorage = builder.AddAzureStorage("hoststorage");
437+
var blobs = storage.AddBlobs("blobs");
438+
var funcApp = builder.AddAzureFunctionsProject<TestFunctionsProject>("funcapp")
439+
.WithReference(blobs)
440+
.WithHostStorage(hostStorage);
441+
442+
// Act
443+
using var app = builder.Build();
444+
await app.StartAsync();
445+
await app.WaitForShutdownAsync();
446+
447+
// Assert that container environment outputs are propagated
448+
Assert.Equal("testregistry", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_NAME"]);
449+
Assert.Equal("testregistry.azurecr.io", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_ENDPOINT"]);
450+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID"]);
451+
Assert.Equal("test.westus.azurecontainerapps.io", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"]);
452+
Assert.Equal("/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.App/managedEnvironments/testenv", containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"]);
453+
454+
// Assert that funcapp outputs are propagated
455+
var funcAppDeployment = Assert.IsType<AzureProvisioningResource>(funcApp.Resource.GetDeploymentTargetAnnotation()?.DeploymentTarget);
456+
Assert.NotNull(funcAppDeployment);
457+
Assert.Equal(await ((BicepOutputReference)funcAppDeployment.Parameters["env_outputs_azure_container_apps_environment_default_domain"]!).GetValueAsync(), containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN"]);
458+
Assert.Equal(await ((BicepOutputReference)funcAppDeployment.Parameters["env_outputs_azure_container_apps_environment_id"]!).GetValueAsync(), containerAppEnv.Resource.Outputs["AZURE_CONTAINER_APPS_ENVIRONMENT_ID"]);
459+
Assert.Equal("https://testfuncstorage.blob.core.windows.net/", await ((BicepOutputReference)funcAppDeployment.Parameters["funcstorage_outputs_blobendpoint"]!).GetValueAsync());
460+
Assert.Equal("https://testhoststorage.blob.core.windows.net/", await ((BicepOutputReference)funcAppDeployment.Parameters["hoststorage_outputs_blobendpoint"]!).GetValueAsync());
461+
462+
// Assert - Verify ACR login command was called since Functions image needs to be built and pushed
463+
Assert.Contains(mockProcessRunner.ExecutedCommands,
464+
cmd => cmd.ExecutablePath.Contains("az") &&
465+
cmd.Arguments == "acr login --name testregistry");
466+
467+
// Assert - Verify Docker tag and push called for Azure Functions project
468+
Assert.Contains(mockProcessRunner.ExecutedCommands,
469+
cmd => cmd.ExecutablePath == "docker" &&
470+
cmd.Arguments != null &&
471+
cmd.Arguments.StartsWith("tag funcapp testregistry.azurecr.io/"));
472+
473+
Assert.Contains(mockProcessRunner.ExecutedCommands,
474+
cmd => cmd.ExecutablePath == "docker" &&
475+
cmd.Arguments != null &&
476+
cmd.Arguments.StartsWith("push testregistry.azurecr.io/"));
477+
}
478+
399479
private static void ConfigureTestServices(IDistributedApplicationTestingBuilder builder,
400480
IInteractionService? interactionService = null,
401481
IBicepProvisioner? bicepProvisioner = null,
@@ -453,4 +533,21 @@ private sealed class Project : IProjectMetadata
453533
{
454534
public string ProjectPath => "project";
455535
}
536+
537+
private sealed class TestFunctionsProject : IProjectMetadata
538+
{
539+
public string ProjectPath => "functions-project";
540+
541+
public LaunchSettings LaunchSettings => new()
542+
{
543+
Profiles = new Dictionary<string, LaunchProfile>
544+
{
545+
["funcapp"] = new()
546+
{
547+
CommandLineArgs = "--port 7071",
548+
LaunchBrowser = false,
549+
}
550+
}
551+
};
552+
}
456553
}

0 commit comments

Comments
 (0)