From 138d0424fbb06b94aac0543b9cdc9d8437232acf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:57:50 +0000 Subject: [PATCH 1/4] Initial plan From 4ebe41818509be653b6014ed80dee2f1e68864a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:03:26 +0000 Subject: [PATCH 2/4] Normalize app service app insights bicep identifiers Agent-Logs-Url: https://github.com/microsoft/aspire/sessions/e848458a-5173-4241-b442-80d01de3d5c2 Co-authored-by: eerhardt <8291187+eerhardt@users.noreply.github.com> --- .../AzureAppServiceEnvironmentExtensions.cs | 4 ++-- .../AzureAppServiceTests.cs | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs index 89a40883cf6..05756dfe388 100644 --- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs @@ -206,7 +206,7 @@ public static IResourceBuilder AddAzureAppSe else { // Create Log Analytics workspace - var logAnalyticsWorkspace = new OperationalInsightsWorkspace(prefix + "_law") + var logAnalyticsWorkspace = new OperationalInsightsWorkspace(Infrastructure.NormalizeBicepIdentifier($"{prefix}-law")) { Sku = new OperationalInsightsWorkspaceSku() { @@ -217,7 +217,7 @@ public static IResourceBuilder AddAzureAppSe infra.Add(logAnalyticsWorkspace); // Create Application Insights resource linked to the Log Analytics workspace - applicationInsights = new ApplicationInsightsComponent(prefix + "_ai") + applicationInsights = new ApplicationInsightsComponent(Infrastructure.NormalizeBicepIdentifier($"{prefix}-ai")) { ApplicationType = ApplicationInsightsApplicationType.Web, Kind = "web", diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs index defebb44e39..91fd1becb96 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs @@ -846,6 +846,27 @@ await Verify(manifest.ToString(), "json") .AppendContentAsFile(bicep, "bicep"); } + [Fact] + public async Task AddAppServiceWithApplicationInsightsNormalizesBicepIdentifiers() + { + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + builder.AddAzureAppServiceEnvironment("env-1").WithAzureApplicationInsights(); + + using var app = builder.Build(); + + await ExecuteBeforeStartHooksAsync(app, default); + + var model = app.Services.GetRequiredService(); + + var environment = Assert.Single(model.Resources.OfType()); + + var (_, bicep) = await GetManifestWithBicep(environment); + + Assert.Contains("resource env_1_law ", bicep); + Assert.Contains("resource env_1_ai ", bicep); + } + [Fact] public async Task AddAppServiceWithApplicationInsightsLocationParam() { From ea186e2c6b7b68fa893e72dcd1b981aa02fd3bfa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 00:09:35 +0000 Subject: [PATCH 3/4] Preserve app insights bicep suffix separators Agent-Logs-Url: https://github.com/microsoft/aspire/sessions/e848458a-5173-4241-b442-80d01de3d5c2 Co-authored-by: eerhardt <8291187+eerhardt@users.noreply.github.com> --- .../AzureAppServiceEnvironmentExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs index 05756dfe388..64d18fd8cdd 100644 --- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs @@ -206,7 +206,7 @@ public static IResourceBuilder AddAzureAppSe else { // Create Log Analytics workspace - var logAnalyticsWorkspace = new OperationalInsightsWorkspace(Infrastructure.NormalizeBicepIdentifier($"{prefix}-law")) + var logAnalyticsWorkspace = new OperationalInsightsWorkspace(Infrastructure.NormalizeBicepIdentifier($"{prefix}_law")) { Sku = new OperationalInsightsWorkspaceSku() { @@ -217,7 +217,7 @@ public static IResourceBuilder AddAzureAppSe infra.Add(logAnalyticsWorkspace); // Create Application Insights resource linked to the Log Analytics workspace - applicationInsights = new ApplicationInsightsComponent(Infrastructure.NormalizeBicepIdentifier($"{prefix}-ai")) + applicationInsights = new ApplicationInsightsComponent(Infrastructure.NormalizeBicepIdentifier($"{prefix}_ai")) { ApplicationType = ApplicationInsightsApplicationType.Web, Kind = "web", From 6823410139dfe25385fef9ceb4b855f09b89efa7 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 29 Apr 2026 10:34:32 -0500 Subject: [PATCH 4/4] Address PR feedback --- .../AzureAppServiceEnvironmentExtensions.cs | 12 +- .../AzureAppServiceTests.cs | 3 +- ...sNormalizesBicepIdentifiers.verified.bicep | 171 ++++++++++++++++++ 3 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsNormalizesBicepIdentifiers.verified.bicep diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs index 64d18fd8cdd..86af48fbdca 100644 --- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs @@ -87,8 +87,8 @@ public static IResourceBuilder AddAzureAppSe var resource = new AzureAppServiceEnvironmentResource(name, static infra => { - var prefix = infra.AspireResource.Name; var resource = (AzureAppServiceEnvironmentResource)infra.AspireResource; + var prefix = Infrastructure.NormalizeBicepIdentifier(resource.Name); // This tells azd to avoid creating infrastructure var userPrincipalId = new ProvisioningParameter(AzureBicepResource.KnownParameters.UserPrincipalId, typeof(string)) { Value = new BicepValue(string.Empty) }; @@ -101,7 +101,7 @@ public static IResourceBuilder AddAzureAppSe infra.Add(tags); - var identity = new UserAssignedIdentity(Infrastructure.NormalizeBicepIdentifier($"{prefix}-mi")) + var identity = new UserAssignedIdentity($"{prefix}_mi") { Tags = tags }; @@ -133,7 +133,7 @@ public static IResourceBuilder AddAzureAppSe pullRa.Name = BicepFunction.CreateGuid(containerRegistry.Id, identity.Id, pullRa.RoleDefinitionId); infra.Add(pullRa); - var plan = new AppServicePlan(Infrastructure.NormalizeBicepIdentifier($"{prefix}-asplan")) + var plan = new AppServicePlan($"{prefix}_asplan") { Sku = new AppServiceSkuDescription { @@ -191,7 +191,7 @@ public static IResourceBuilder AddAzureAppSe infra.Add(new ProvisioningOutput("AZURE_APP_SERVICE_DASHBOARD_URI", typeof(string)) { - Value = BicepFunction.Interpolate($"https://{AzureAppServiceEnvironmentUtility.GetDashboardHostName(prefix)}.azurewebsites.net") + Value = BicepFunction.Interpolate($"https://{AzureAppServiceEnvironmentUtility.GetDashboardHostName(resource.Name)}.azurewebsites.net") }); } @@ -206,7 +206,7 @@ public static IResourceBuilder AddAzureAppSe else { // Create Log Analytics workspace - var logAnalyticsWorkspace = new OperationalInsightsWorkspace(Infrastructure.NormalizeBicepIdentifier($"{prefix}_law")) + var logAnalyticsWorkspace = new OperationalInsightsWorkspace($"{prefix}_law") { Sku = new OperationalInsightsWorkspaceSku() { @@ -217,7 +217,7 @@ public static IResourceBuilder AddAzureAppSe infra.Add(logAnalyticsWorkspace); // Create Application Insights resource linked to the Log Analytics workspace - applicationInsights = new ApplicationInsightsComponent(Infrastructure.NormalizeBicepIdentifier($"{prefix}_ai")) + applicationInsights = new ApplicationInsightsComponent($"{prefix}_ai") { ApplicationType = ApplicationInsightsApplicationType.Web, Kind = "web", diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs index 91fd1becb96..781cc678cc5 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs @@ -863,8 +863,7 @@ public async Task AddAppServiceWithApplicationInsightsNormalizesBicepIdentifiers var (_, bicep) = await GetManifestWithBicep(environment); - Assert.Contains("resource env_1_law ", bicep); - Assert.Contains("resource env_1_ai ", bicep); + await Verify(bicep, "bicep"); } [Fact] diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsNormalizesBicepIdentifiers.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsNormalizesBicepIdentifiers.verified.bicep new file mode 100644 index 00000000000..08a87488e0a --- /dev/null +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithApplicationInsightsNormalizesBicepIdentifiers.verified.bicep @@ -0,0 +1,171 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +param userPrincipalId string = '' + +param tags object = { } + +param env_1_acr_outputs_name string + +resource env_1_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { + name: take('env_1_mi-${uniqueString(resourceGroup().id)}', 128) + location: location + tags: tags +} + +resource env_1_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' existing = { + name: env_1_acr_outputs_name +} + +resource env_1_acr_env_1_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(env_1_acr.id, env_1_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')) + properties: { + principalId: env_1_mi.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + principalType: 'ServicePrincipal' + } + scope: env_1_acr +} + +resource env_1_asplan 'Microsoft.Web/serverfarms@2025-03-01' = { + name: take('env1asplan-${uniqueString(resourceGroup().id)}', 60) + location: location + properties: { + perSiteScaling: true + reserved: true + } + kind: 'Linux' + sku: { + name: 'P0V3' + tier: 'Premium' + } +} + +resource env_1_contributor_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { + name: take('env_1_contributor_mi-${uniqueString(resourceGroup().id)}', 128) + location: location +} + +resource env_1_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(resourceGroup().id, env_1_contributor_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')) + properties: { + principalId: env_1_contributor_mi.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + principalType: 'ServicePrincipal' + } +} + +resource dashboard 'Microsoft.Web/sites@2025-03-01' = { + name: take('${toLower('env-1')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60) + location: location + properties: { + serverFarmId: env_1_asplan.id + siteConfig: { + numberOfWorkers: 1 + linuxFxVersion: 'ASPIREDASHBOARD|1.0' + acrUseManagedIdentityCreds: true + acrUserManagedIdentityID: env_1_mi.properties.clientId + appSettings: [ + { + name: 'DASHBOARD__FRONTEND__AUTHMODE' + value: 'Unsecured' + } + { + name: 'DASHBOARD__OTLP__AUTHMODE' + value: 'Unsecured' + } + { + name: 'DASHBOARD__OTLP__SUPPRESSUNSECUREDTELEMETRYMESSAGE' + value: 'true' + } + { + name: 'DASHBOARD__RESOURCESERVICECLIENT__AUTHMODE' + value: 'Unsecured' + } + { + name: 'DASHBOARD__UI__DISABLEIMPORT' + value: 'true' + } + { + name: 'WEBSITES_PORT' + value: '5000' + } + { + name: 'HTTP20_ONLY_PORT' + value: '4317' + } + { + name: 'WEBSITE_START_SCM_WITH_PRELOAD' + value: 'true' + } + { + name: 'AZURE_CLIENT_ID' + value: env_1_contributor_mi.properties.clientId + } + { + name: 'ALLOWED_MANAGED_IDENTITIES' + value: env_1_mi.properties.clientId + } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env-1' + } + ] + alwaysOn: true + http20Enabled: true + http20ProxyFlag: 1 + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${env_1_contributor_mi.id}': { } + } + } + kind: 'app,linux,aspiredashboard' +} + +resource env_1_law 'Microsoft.OperationalInsights/workspaces@2025-02-01' = { + name: take('env1law-${uniqueString(resourceGroup().id)}', 63) + location: location + properties: { + sku: { + name: 'PerGB2018' + } + } +} + +resource env_1_ai 'Microsoft.Insights/components@2020-02-02' = { + name: take('env_1_ai-${uniqueString(resourceGroup().id)}', 260) + kind: 'web' + location: location + properties: { + Application_Type: 'web' + IngestionMode: 'LogAnalytics' + WorkspaceResourceId: env_1_law.id + } +} + +output name string = env_1_asplan.name + +output planId string = env_1_asplan.id + +output webSiteSuffix string = uniqueString(resourceGroup().id) + +output AZURE_CONTAINER_REGISTRY_NAME string = env_1_acr.name + +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_1_acr.properties.loginServer + +output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env_1_mi.id + +output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID string = env_1_mi.properties.clientId + +output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID string = env_1_contributor_mi.id + +output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID string = env_1_contributor_mi.properties.principalId + +output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env-1')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' + +output AZURE_APPLICATION_INSIGHTS_INSTRUMENTATIONKEY string = env_1_ai.properties.InstrumentationKey + +output AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING string = env_1_ai.properties.ConnectionString \ No newline at end of file