From b5256c524f4f03ad4ecbbd0180b68534039a52aa Mon Sep 17 00:00:00 2001 From: mbrown379 Date: Tue, 28 Jan 2025 22:37:41 -0500 Subject: [PATCH 1/2] Support multiple domains via ConfigureCustomDomain --- .../ContainerAppExtensions.cs | 16 +-- .../AzureContainerAppsTests.cs | 131 ++++++++++++++++++ 2 files changed, 138 insertions(+), 9 deletions(-) diff --git a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs index bce9c2ba7c..af7e8165cd 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs @@ -90,14 +90,12 @@ public static void ConfigureCustomDomain(this ContainerApp app, IResourceBuilder new NullLiteralExpression() ); - app.Configuration.Ingress.CustomDomains = new BicepList() - { - new ContainerAppCustomDomain() - { - BindingType = bindingTypeConditional, - Name = customDomainParameter, - CertificateId = certificateOrEmpty - } - }; + app.Configuration.Ingress.CustomDomains.Add( + new ContainerAppCustomDomain + { + BindingType = bindingTypeConditional, + Name = customDomainParameter, + CertificateId = certificateOrEmpty + }); } } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs index 409806a531..d2fafec008 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs @@ -1175,6 +1175,137 @@ param customDomain string Assert.Equal(expectedBicep, bicep); } + [Fact] + public async Task ConfigureMultipleCustomDomainsMutatesIngress() + { + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + var customDomain1 = builder.AddParameter("customDomain1"); + var certificateName1 = builder.AddParameter("certificateName1"); + + var customDomain2 = builder.AddParameter("customDomain2"); + var certificateName2 = builder.AddParameter("certificateName2"); + + builder.AddAzureContainerAppsInfrastructure(); + builder.AddContainer("api", "myimage") + .WithHttpEndpoint(targetPort: 1111) + .PublishAsAzureContainerApp((module, c) => + { + c.ConfigureCustomDomain(customDomain1, certificateName1); + c.ConfigureCustomDomain(customDomain2, certificateName2); + }); + + using var app = builder.Build(); + + await ExecuteBeforeStartHooksAsync(app, default); + + var model = app.Services.GetRequiredService(); + + var container = Assert.Single(model.GetContainerResources()); + + container.TryGetLastAnnotation(out var target); + + var resource = target?.DeploymentTarget as AzureBicepResource; + + Assert.NotNull(resource); + + var (manifest, bicep) = await ManifestUtils.GetManifestWithBicep(resource); + + var m = manifest.ToString(); + + var expectedManifest = + """ + { + "type": "azure.bicep.v0", + "path": "api.module.bicep", + "params": { + "outputs_azure_container_registry_managed_identity_id": "{.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", + "outputs_managed_identity_client_id": "{.outputs.MANAGED_IDENTITY_CLIENT_ID}", + "outputs_azure_container_apps_environment_id": "{.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_ID}", + "certificateName1": "{certificateName1.value}", + "customDomain1": "{customDomain1.value}", + "certificateName2": "{certificateName2.value}", + "customDomain2": "{customDomain2.value}" + } + } + """; + + Assert.Equal(expectedManifest, m); + + 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 + + param certificateName1 string + + param customDomain1 string + + param certificateName2 string + + param customDomain2 string + + resource api 'Microsoft.App/containerApps@2024-03-01' = { + name: 'api' + location: location + properties: { + configuration: { + activeRevisionsMode: 'Single' + ingress: { + external: false + targetPort: 1111 + transport: 'http' + customDomains: [ + { + name: customDomain1 + bindingType: (certificateName1 != '') ? 'SniEnabled' : 'Disabled' + certificateId: (certificateName1 != '') ? '${outputs_azure_container_apps_environment_id}/managedCertificates/${certificateName1}' : null + } + { + name: customDomain2 + bindingType: (certificateName2 != '') ? 'SniEnabled' : 'Disabled' + certificateId: (certificateName2 != '') ? '${outputs_azure_container_apps_environment_id}/managedCertificates/${certificateName2}' : null + } + ] + } + } + environmentId: outputs_azure_container_apps_environment_id + template: { + containers: [ + { + image: 'myimage:latest' + name: 'api' + 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); + } + [Fact] public async Task VolumesAndBindMountsAreTranslation() { From 004d06a9844ece0d136addd6f440f432b73cf6f2 Mon Sep 17 00:00:00 2001 From: mbrown379 Date: Wed, 29 Jan 2025 09:19:02 -0500 Subject: [PATCH 2/2] Rename to AddCustomDomain --- .../AzureContainerApps.AppHost/Program.cs | 2 +- .../ContainerAppExtensions.cs | 20 +++++++++---------- .../PublicAPI.Unshipped.txt | 2 +- .../AzureContainerAppsTests.cs | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs b/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs index 23de037dc3..7561cfad50 100644 --- a/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs +++ b/playground/AzureContainerApps/AzureContainerApps.AppHost/Program.cs @@ -39,7 +39,7 @@ .WithEnvironment("VALUE", param) .PublishAsAzureContainerApp((module, app) => { - app.ConfigureCustomDomain(customDomain, certificateName); + app.AddCustomDomain(customDomain, certificateName); // Scale to 0 app.Template.Scale.MinReplicas = 0; diff --git a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs index af7e8165cd..0532ffab29 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppContainers/ContainerAppExtensions.cs @@ -16,22 +16,22 @@ namespace Aspire.Hosting; public static class ContainerAppExtensions { /// - /// Configures the custom domain for the container app. + /// Adds a custom domain to the container app. /// - /// The container app resource to configure for custom domain usage. + /// The container app resource to add a custom domain. /// A resource builder for a parameter resource capturing the name of the custom domain. /// A resource builder for a parameter resource capturing the name of the certficate configured in the Azure Portal. /// Throws if the container app resource is not parented to a . /// - /// The extension method + /// The extension method /// simplifies the process of assigning a custom domain to a container app resource when it is deployed. It has no impact on local development. - /// The method is used + /// The method is used /// in conjunction with the /// callback. Assigning a custom domain to a container app resource is a multi-step process and requires multiple deployments. - /// The method takes + /// The method takes /// two arguments which are parameter resource builders. The first is a parameter that represents the custom domain and the second is a parameter that /// represents the name of the managed certificate provisioned via the Azure Portal - /// When deploying with custom domains configured for the first time leave the parameter empty (when prompted + /// When deploying with custom domains added for the first time leave the parameter empty (when prompted /// by the Azure Developer CLI). Once the applicatio is deployed acucessfully access to the Azure Portal to bind the custom domain to a managed SSL /// certificate. Once the certificate is successfully provisioned, subsequent deployments of the application can use this certificate name when the /// is prompted. @@ -40,7 +40,7 @@ public static class ContainerAppExtensions /// /// /// This example shows declaring two parameters to capture the custom domain and certificate name and - /// passing them to the + /// passing them to the /// method via the /// extension method. /// @@ -50,16 +50,16 @@ public static class ContainerAppExtensions /// builder.AddProject<Projects.InventoryService>("inventory") /// .PublishAsAzureContainerApp((module, app) => /// { - /// app.ConfigureCustomDomain(customDomain, certificateName); + /// app.AddCustomDomain(customDomain, certificateName); /// }); /// /// [Experimental("ASPIREACADOMAINS001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")] - public static void ConfigureCustomDomain(this ContainerApp app, IResourceBuilder customDomain, IResourceBuilder certificateName) + public static void AddCustomDomain(this ContainerApp app, IResourceBuilder customDomain, IResourceBuilder certificateName) { if (app.ParentInfrastructure is not AzureResourceInfrastructure module) { - throw new ArgumentException("Cannot configure custom domain when resource is not parented by ResourceModuleConstruct.", nameof(app)); + throw new ArgumentException("Cannot add custom domain when resource is not parented by ResourceModuleConstruct.", nameof(app)); } var containerAppManagedEnvironmentIdParameter = module.GetProvisionableResources().OfType().Single( diff --git a/src/Aspire.Hosting.Azure.AppContainers/PublicAPI.Unshipped.txt b/src/Aspire.Hosting.Azure.AppContainers/PublicAPI.Unshipped.txt index 2c6ef51fae..744fc58ae6 100644 --- a/src/Aspire.Hosting.Azure.AppContainers/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting.Azure.AppContainers/PublicAPI.Unshipped.txt @@ -11,4 +11,4 @@ static Aspire.Hosting.AzureContainerAppContainerExtensions.PublishAsAzureContain static Aspire.Hosting.AzureContainerAppExecutableExtensions.PublishAsAzureContainerApp(this Aspire.Hosting.ApplicationModel.IResourceBuilder! executable, System.Action! configure) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.AzureContainerAppExtensions.AddAzureContainerAppsInfrastructure(this Aspire.Hosting.IDistributedApplicationBuilder! builder) -> Aspire.Hosting.IDistributedApplicationBuilder! static Aspire.Hosting.AzureContainerAppProjectExtensions.PublishAsAzureContainerApp(this Aspire.Hosting.ApplicationModel.IResourceBuilder! project, System.Action! configure) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! -static Aspire.Hosting.ContainerAppExtensions.ConfigureCustomDomain(this Azure.Provisioning.AppContainers.ContainerApp! app, Aspire.Hosting.ApplicationModel.IResourceBuilder! customDomain, Aspire.Hosting.ApplicationModel.IResourceBuilder! certificateName) -> void +static Aspire.Hosting.ContainerAppExtensions.AddCustomDomain(this Azure.Provisioning.AppContainers.ContainerApp! app, Aspire.Hosting.ApplicationModel.IResourceBuilder! customDomain, Aspire.Hosting.ApplicationModel.IResourceBuilder! certificateName) -> void diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs index d2fafec008..3640aeebb2 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureContainerAppsTests.cs @@ -1060,7 +1060,7 @@ param outputs_azure_container_apps_environment_id string } [Fact] - public async Task ConfigureCustomDomainsMutatesIngress() + public async Task AddCustomDomainMutatesIngress() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); @@ -1072,7 +1072,7 @@ public async Task ConfigureCustomDomainsMutatesIngress() .WithHttpEndpoint(targetPort: 1111) .PublishAsAzureContainerApp((module, c) => { - c.ConfigureCustomDomain(customDomain, certificateName); + c.AddCustomDomain(customDomain, certificateName); }); using var app = builder.Build(); @@ -1176,7 +1176,7 @@ param customDomain string } [Fact] - public async Task ConfigureMultipleCustomDomainsMutatesIngress() + public async Task AddMultipleCustomDomainsMutatesIngress() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); @@ -1191,8 +1191,8 @@ public async Task ConfigureMultipleCustomDomainsMutatesIngress() .WithHttpEndpoint(targetPort: 1111) .PublishAsAzureContainerApp((module, c) => { - c.ConfigureCustomDomain(customDomain1, certificateName1); - c.ConfigureCustomDomain(customDomain2, certificateName2); + c.AddCustomDomain(customDomain1, certificateName1); + c.AddCustomDomain(customDomain2, certificateName2); }); using var app = builder.Build();