diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs index 38d68f29324..5db5b1003bd 100644 --- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs +++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentExtensions.cs @@ -44,7 +44,7 @@ public static IResourceBuilder AddAzureAppSe var resource = new AzureAppServiceEnvironmentResource(name, static infra => { var prefix = infra.AspireResource.Name; - var resource = infra.AspireResource; + var resource = (AzureAppServiceEnvironmentResource)infra.AspireResource; // This tells azd to avoid creating infrastructure var userPrincipalId = new ProvisioningParameter(AzureBicepResource.KnownParameters.UserPrincipalId, typeof(string)) { Value = new BicepValue(string.Empty) }; @@ -96,7 +96,9 @@ public static IResourceBuilder AddAzureAppSe Tier = "Premium" }, Kind = "Linux", - IsReserved = true + IsReserved = true, + // Enable per-site scaling so each app service can scale independently + IsPerSiteScaling = true }; infra.Add(plan); @@ -131,6 +133,17 @@ public static IResourceBuilder AddAzureAppSe { Value = identity.ClientId }); + + if (resource.EnableDashboard) + { + // Add aspire dashboard website + var website = AzureAppServiceEnvironmentUtility.AddDashboard(infra, identity, plan.Id); + + infra.Add(new ProvisioningOutput("AZURE_APP_SERVICE_DASHBOARD_URI", typeof(string)) + { + Value = BicepFunction.Interpolate($"https://{AzureAppServiceEnvironmentUtility.GetDashboardHostName(prefix)}.azurewebsites.net") + }); + } }); if (!builder.ExecutionContext.IsPublishMode) @@ -140,4 +153,16 @@ public static IResourceBuilder AddAzureAppSe return builder.AddResource(resource); } + + /// + /// Configures whether the Aspire dashboard should be included in the Azure App Service environment. + /// + /// The AzureAppServiceEnvironmentResource to configure. + /// Whether to include the Aspire dashboard. Default is true. + /// + public static IResourceBuilder WithDashboard(this IResourceBuilder builder, bool enable = true) + { + builder.Resource.EnableDashboard = enable; + return builder; + } } diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs index 2b88713c98d..6c07a16a15b 100644 --- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs +++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentResource.cs @@ -26,12 +26,25 @@ public class AzureAppServiceEnvironmentResource(string name, Action new("AZURE_CONTAINER_REGISTRY_NAME", this); internal BicepOutputReference ContainerRegistryManagedIdentityId => new("AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID", this); internal BicepOutputReference ContainerRegistryClientId => new("AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID", this); + internal BicepOutputReference WebsiteContributorManagedIdentityId => new("AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID", this); + internal BicepOutputReference WebsiteContributorManagedIdentityPrincipalId => new("AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID", this); + + /// + /// Gets or sets a value indicating whether the Aspire dashboard should be included in the container app environment. + /// Default is true. + /// + internal bool EnableDashboard { get; set; } = true; /// /// Gets the name of the App Service Plan. /// public BicepOutputReference NameOutputReference => new("name", this); + /// + /// Gets the URI of the App Service Environment dashboard. + /// + public BicepOutputReference DashboardUriReference => new("AZURE_APP_SERVICE_DASHBOARD_URI", this); + ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}"); diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentUtility.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentUtility.cs new file mode 100644 index 00000000000..4e7eddfe673 --- /dev/null +++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceEnvironmentUtility.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Azure.Core; +using Azure.Provisioning; +using Azure.Provisioning.AppService; +using Azure.Provisioning.Authorization; +using Azure.Provisioning.Expressions; +using Azure.Provisioning.Resources; +using Azure.Provisioning.Roles; + +namespace Aspire.Hosting.Azure.AppService; + +internal static class AzureAppServiceEnvironmentUtility +{ + internal const string ResourceName = "aspiredashboard"; + + public static BicepValue GetDashboardHostName(string aspireResourceName) + { + return BicepFunction.Take( + BicepFunction.Interpolate($"{BicepFunction.ToLower(aspireResourceName)}-{BicepFunction.ToLower(ResourceName)}-{BicepFunction.GetUniqueString(BicepFunction.GetResourceGroup().Id)}"), 60); + } + + public static WebSite AddDashboard(AzureResourceInfrastructure infra, + UserAssignedIdentity otelIdentity, + BicepValue appServicePlanId) + { + // This ACR identity is used by the dashboard to authorize the telemetry data + // coming from the dotnet web apps. This identity is being assigned to every web app + // in the aspire project and can be safely reused for authorization in the dashboard. + var otelClientId = otelIdentity.ClientId; + var prefix = infra.AspireResource.Name; + var contributorIdentity = new UserAssignedIdentity(Infrastructure.NormalizeBicepIdentifier($"{prefix}-contributor-mi")); + + infra.Add(contributorIdentity); + + // Add Reader role assignment + var rgRaId = BicepFunction.GetSubscriptionResourceId( + "Microsoft.Authorization/roleDefinitions", + "acdd72a7-3385-48ef-bd42-f606fba81ae7"); + var rgRaName = BicepFunction.CreateGuid(BicepFunction.GetResourceGroup().Id, contributorIdentity.Id, rgRaId); + + var rgRa = new RoleAssignment(Infrastructure.NormalizeBicepIdentifier($"{prefix}_ra")) + { + Name = rgRaName, + PrincipalType = RoleManagementPrincipalType.ServicePrincipal, + PrincipalId = contributorIdentity.PrincipalId, + RoleDefinitionId = rgRaId + }; + + infra.Add(rgRa); + + var dashboard = new WebSite("dashboard") + { + // Use the host name as the name of the web app + Name = GetDashboardHostName(infra.AspireResource.Name), + AppServicePlanId = appServicePlanId, + // Aspire dashboards are created with a new kind aspiredashboard + Kind = "app,linux,aspiredashboard", + SiteConfig = new SiteConfigProperties() + { + LinuxFxVersion = "ASPIREDASHBOARD|1.0", + AcrUserManagedIdentityId = otelClientId, + UseManagedIdentityCreds = true, + IsHttp20Enabled = true, + Http20ProxyFlag = 1, + // Setting NumberOfWorkers to 1 to ensure dashboard runs on 1 instance + NumberOfWorkers = 1, + // IsAlwaysOn set to true ensures the app is always running + IsAlwaysOn = true, + AppSettings = [] + }, + Identity = new ManagedServiceIdentity() + { + ManagedServiceIdentityType = ManagedServiceIdentityType.UserAssigned, + UserAssignedIdentities = [] + } + }; + + var contributorMid = BicepFunction.Interpolate($"{contributorIdentity.Id}").Compile().ToString(); + dashboard.Identity.UserAssignedIdentities[contributorMid] = new UserAssignedIdentityDetails(); + + // Security is handled by app service platform + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "Dashboard__Frontend__AuthMode", Value = "Unsecured" }); + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "Dashboard__Otlp__AuthMode", Value = "Unsecured" }); + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "Dashboard__Otlp__SuppressUnsecuredTelemetryMessage", Value = "true" }); + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "Dashboard__ResourceServiceClient__AuthMode", Value = "Unsecured" }); + // Dashboard ports + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "WEBSITES_PORT", Value = "5000" }); + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "HTTP20_ONLY_PORT", Value = "4317" }); + // Enable SCM preloading to ensure dashboard is always available + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "WEBSITE_START_SCM_WITH_PRELOAD", Value = "true" }); + // Appsettings related to managed identity for auth + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "AZURE_CLIENT_ID", Value = contributorIdentity.ClientId }); + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "ALLOWED_MANAGED_IDENTITIES", Value = otelClientId }); + // Added appsetting to identify the resources in a specific aspire environment + dashboard.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "ASPIRE_ENVIRONMENT_NAME", Value = infra.AspireResource.Name }); + + infra.Add(dashboard); + + // Outputs needed by the app service environment + // This identity needs website contributor access on the websites for resource server to work + infra.Add(new ProvisioningOutput("AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID", typeof(string)) + { + Value = contributorIdentity.Id + }); + + infra.Add(new ProvisioningOutput("AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID", typeof(string)) + { + Value = contributorIdentity.PrincipalId + }); + + return dashboard; + } +} diff --git a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebsiteContext.cs b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebsiteContext.cs index f0f32e5722a..9d888edf364 100644 --- a/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebsiteContext.cs +++ b/src/Aspire.Hosting.Azure.AppService/AzureAppServiceWebsiteContext.cs @@ -7,6 +7,7 @@ using Aspire.Hosting.ApplicationModel; using Azure.Provisioning; using Azure.Provisioning.AppService; +using Azure.Provisioning.Authorization; using Azure.Provisioning.Expressions; using Azure.Provisioning.Resources; @@ -212,7 +213,7 @@ public void BuildWebSite(AzureResourceInfrastructure infra) var acrMidParameter = environmentContext.Environment.ContainerRegistryManagedIdentityId.AsProvisioningParameter(infra); var acrClientIdParameter = environmentContext.Environment.ContainerRegistryClientId.AsProvisioningParameter(infra); var containerImage = AllocateParameter(new ContainerImageReference(Resource)); - + var webSite = new WebSite("webapp") { // Use the host name as the name of the web app @@ -224,6 +225,12 @@ public void BuildWebSite(AzureResourceInfrastructure infra) LinuxFxVersion = "SITECONTAINERS", AcrUserManagedIdentityId = acrClientIdParameter, UseManagedIdentityCreds = true, + // Setting NumberOfWorkers to maximum allowed value for Premium SKU + // https://learn.microsoft.com/en-us/azure/app-service/manage-scale-up + // This is required due to use of feature PerSiteScaling for the App Service plan + // We want the web apps to scale normally as defined for the app service plan + // so setting the maximum number of workers to the maximum allowed for Premium V2 SKU. + NumberOfWorkers = 30, AppSettings = [] }, Identity = new ManagedServiceIdentity() @@ -306,6 +313,9 @@ static FunctionCallExpression Join(BicepExpression args, string delimeter) => }); } + // Added appsetting to identify the resource in a specific aspire environment + webSite.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "ASPIRE_ENVIRONMENT_NAME", Value = environmentContext.Environment.Name }); + // Probes #pragma warning disable ASPIREPROBES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. if (resource.TryGetAnnotationsOfType(out var probeAnnotations)) @@ -323,7 +333,17 @@ static FunctionCallExpression Join(BicepExpression args, string delimeter) => } #pragma warning restore ASPIREPROBES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + RoleAssignment? webSiteRa = null; + if (environmentContext.Environment.EnableDashboard) + { + webSiteRa = AddDashboardPermissionAndSettings(webSite, acrClientIdParameter); + } + infra.Add(webSite); + if (webSiteRa is not null) + { + infra.Add(webSiteRa); + } // Allow users to customize the web app here if (resource.TryGetAnnotationsOfType(out var customizeWebSiteAnnotations)) @@ -363,6 +383,36 @@ private ProvisioningParameter AllocateParameter(IManifestExpressionProvider para return parameter.AsProvisioningParameter(Infra, isSecure: secretType == SecretType.Normal); } + private RoleAssignment AddDashboardPermissionAndSettings(WebSite webSite, ProvisioningParameter acrClientIdParameter) + { + var dashboardUri = environmentContext.Environment.DashboardUriReference.AsProvisioningParameter(Infra); + var contributorId = environmentContext.Environment.WebsiteContributorManagedIdentityId.AsProvisioningParameter(Infra); + var contributorPrincipalId = environmentContext.Environment.WebsiteContributorManagedIdentityPrincipalId.AsProvisioningParameter(Infra); + + // Add the appsettings specific to sending telemetry data to dashboard + webSite.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "OTEL_SERVICE_NAME", Value = resource.Name }); + webSite.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "OTEL_EXPORTER_OTLP_PROTOCOL", Value = "grpc" }); + webSite.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "OTEL_EXPORTER_OTLP_ENDPOINT", Value = "http://localhost:6001" }); + webSite.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR", Value = "true" }); + webSite.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "OTEL_COLLECTOR_URL", Value = dashboardUri }); + webSite.SiteConfig.AppSettings.Add(new AppServiceNameValuePair { Name = "OTEL_CLIENT_ID", Value = acrClientIdParameter }); + + // Add Website Contributor role assignment to dashboard's managed identity for this webapp + var websiteRaId = BicepFunction.GetSubscriptionResourceId( + "Microsoft.Authorization/roleDefinitions", + "de139f84-1756-47ae-9be6-808fbbe84772"); + var websiteRaName = BicepFunction.CreateGuid(webSite.Id, contributorId, websiteRaId); + + return new RoleAssignment(Infrastructure.NormalizeBicepIdentifier($"{Infra.AspireResource.Name}_ra")) + { + Name = websiteRaName, + Scope = new IdentifierExpression(webSite.BicepIdentifier), + PrincipalType = RoleManagementPrincipalType.ServicePrincipal, + PrincipalId = contributorPrincipalId, + RoleDefinitionId = websiteRaId, + }; + } + enum SecretType { None, diff --git a/src/Aspire.Hosting.Azure/AzureDeployingContext.cs b/src/Aspire.Hosting.Azure/AzureDeployingContext.cs index 8a4b52c268c..f0322f674f2 100644 --- a/src/Aspire.Hosting.Azure/AzureDeployingContext.cs +++ b/src/Aspire.Hosting.Azure/AzureDeployingContext.cs @@ -482,6 +482,12 @@ private static string TryGetComputeResourceEndpoint(IResource computeResource, I { return $"https://aspire-dashboard.ext.{domainValue}"; } + // If the resource is a compute environment (app service), we can use its properties + // to get the dashboard URL. + if (environmentBicepResource.Outputs.TryGetValue($"AZURE_APP_SERVICE_DASHBOARD_URI", out var dashboardUri)) + { + return (string?)dashboardUri; + } } } diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs index 6f4c348c015..bdfbe193db4 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs @@ -397,6 +397,62 @@ public async Task ResourceWithProbes() await Verify(projectBicep, "bicep"); } + [Fact] + public async Task AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource() + { + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + builder.AddAzureAppServiceEnvironment("env").WithDashboard(false); + + using var app = builder.Build(); + + await ExecuteBeforeStartHooksAsync(app, default); + + var model = app.Services.GetRequiredService(); + + var environment = Assert.Single(model.Resources.OfType()); + + var (manifest, bicep) = await GetManifestWithBicep(environment); + + await Verify(manifest.ToString(), "json") + .AppendContentAsFile(bicep, "bicep"); + } + + [Fact] + public async Task AddAppServiceToEnvironmentWithoutDashboard() + { + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); + + builder.AddAzureAppServiceEnvironment("env").WithDashboard(false); + + // Add 2 projects with endpoints + var project1 = builder.AddProject("project1", launchProfileName: null) + .WithHttpEndpoint() + .WithExternalHttpEndpoints(); + + var project2 = builder.AddProject("project2", launchProfileName: null) + .WithHttpEndpoint() + .WithExternalHttpEndpoints() + .WithReference(project1); + + using var app = builder.Build(); + + await ExecuteBeforeStartHooksAsync(app, default); + + var model = app.Services.GetRequiredService(); + + project2.Resource.TryGetLastAnnotation(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"); + } + private static Task<(JsonNode ManifestNode, string BicepText)> GetManifestWithBicep(IResource resource) => AzureManifestUtils.GetManifestWithBicep(resource, skipPreparer: true); diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.bicep new file mode 100644 index 00000000000..86d0ea42ffd --- /dev/null +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.bicep @@ -0,0 +1,57 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +param userPrincipalId string = '' + +param tags object = { } + +resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { + name: take('env_mi-${uniqueString(resourceGroup().id)}', 128) + location: location + tags: tags +} + +resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = { + name: take('envacr${uniqueString(resourceGroup().id)}', 50) + location: location + sku: { + name: 'Basic' + } + tags: tags +} + +resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(env_acr.id, env_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')) + properties: { + principalId: env_mi.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + principalType: 'ServicePrincipal' + } + scope: env_acr +} + +resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = { + name: take('envasplan-${uniqueString(resourceGroup().id)}', 60) + location: location + properties: { + perSiteScaling: true + reserved: true + } + kind: 'Linux' + sku: { + name: 'P0V3' + tier: 'Premium' + } +} + +output name string = env_asplan.name + +output planId string = env_asplan.id + +output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name + +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer + +output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env_mi.id + +output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID string = env_mi.properties.clientId diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.json new file mode 100644 index 00000000000..0bc8a391d67 --- /dev/null +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceEnvironmentWithoutDashboardAddsEnvironmentResource.verified.json @@ -0,0 +1,7 @@ +{ + "type": "azure.bicep.v0", + "path": "env.module.bicep", + "params": { + "userPrincipalId": "" + } +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.bicep new file mode 100644 index 00000000000..074638a4ba6 --- /dev/null +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.bicep @@ -0,0 +1,75 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +param env_outputs_azure_container_registry_endpoint string + +param env_outputs_planid string + +param env_outputs_azure_container_registry_managed_identity_id string + +param env_outputs_azure_container_registry_managed_identity_client_id string + +param project2_containerimage string + +param project2_containerport string + +resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { + name: 'main' + properties: { + authType: 'UserAssigned' + image: project2_containerimage + isMain: true + userManagedIdentityClientId: env_outputs_azure_container_registry_managed_identity_client_id + } + parent: webapp +} + +resource webapp 'Microsoft.Web/sites@2024-11-01' = { + name: take('${toLower('project2')}-${uniqueString(resourceGroup().id)}', 60) + location: location + properties: { + serverFarmId: env_outputs_planid + siteConfig: { + numberOfWorkers: 30 + linuxFxVersion: 'SITECONTAINERS' + acrUseManagedIdentityCreds: true + acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id + appSettings: [ + { + name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES' + value: 'true' + } + { + name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES' + value: 'true' + } + { + name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY' + value: 'in_memory' + } + { + name: 'ASPNETCORE_FORWARDEDHEADERS_ENABLED' + value: 'true' + } + { + name: 'HTTP_PORTS' + value: project2_containerport + } + { + name: 'services__project1__http__0' + value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' + } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + ] + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${env_outputs_azure_container_registry_managed_identity_id}': { } + } + } +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.json new file mode 100644 index 00000000000..4e0562d0eff --- /dev/null +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.json @@ -0,0 +1,12 @@ +{ + "type": "azure.bicep.v0", + "path": "project2.module.bicep", + "params": { + "env_outputs_azure_container_registry_endpoint": "{env.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT}", + "env_outputs_planid": "{env.outputs.planId}", + "env_outputs_azure_container_registry_managed_identity_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", + "env_outputs_azure_container_registry_managed_identity_client_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", + "project2_containerimage": "{project2.containerImage}", + "project2_containerport": "{project2.containerPort}" + } +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.bicep index be4ba9aacbd..9cf323ac43e 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param env_outputs_azure_container_registry_endpoint string @@ -13,6 +13,12 @@ param api_containerimage string param api_containerport string +param env_outputs_azure_app_service_dashboard_uri string + +param env_outputs_azure_website_contributor_managed_identity_id string + +param env_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -30,6 +36,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { properties: { serverFarmId: env_outputs_planid siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id @@ -54,6 +61,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'HTTP_PORTS' value: api_containerport } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'api' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: env_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: env_outputs_azure_container_registry_managed_identity_client_id + } ] webSocketsEnabled: true } @@ -64,4 +99,14 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { '${env_outputs_azure_container_registry_managed_identity_id}': { } } } -} \ No newline at end of file +} + +resource api_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, env_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: env_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.json index 3cdded0b976..dbf9ec3989a 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.json +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsDeploymentTargetWithContainerAppToProjectResources.verified.json @@ -1,4 +1,4 @@ -{ +{ "type": "azure.bicep.v0", "path": "api.module.bicep", "params": { @@ -7,6 +7,9 @@ "env_outputs_azure_container_registry_managed_identity_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", "env_outputs_azure_container_registry_managed_identity_client_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", "api_containerimage": "{api.containerImage}", - "api_containerport": "{api.containerPort}" + "api_containerport": "{api.containerPort}", + "env_outputs_azure_app_service_dashboard_uri": "{env.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env_outputs_azure_website_contributor_managed_identity_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env_outputs_azure_website_contributor_managed_identity_principal_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep index ecca0504dd1..b102149cb31 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddContainerAppEnvironmentAddsEnvironmentResource.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param userPrincipalId string = '' @@ -34,6 +34,7 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = { name: take('envasplan-${uniqueString(resourceGroup().id)}', 60) location: location properties: { + perSiteScaling: true reserved: true } kind: 'Linux' @@ -43,6 +44,86 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = { } } +resource env_contributor_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { + name: take('env_contributor_mi-${uniqueString(resourceGroup().id)}', 128) + location: location +} + +resource env_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(resourceGroup().id, env_contributor_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')) + properties: { + principalId: env_contributor_mi.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + principalType: 'ServicePrincipal' + } +} + +resource dashboard 'Microsoft.Web/sites@2024-11-01' = { + name: take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60) + location: location + properties: { + serverFarmId: env_asplan.id + siteConfig: { + numberOfWorkers: 1 + linuxFxVersion: 'ASPIREDASHBOARD|1.0' + acrUseManagedIdentityCreds: true + acrUserManagedIdentityID: env_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: 'WEBSITES_PORT' + value: '5000' + } + { + name: 'HTTP20_ONLY_PORT' + value: '4317' + } + { + name: 'WEBSITE_START_SCM_WITH_PRELOAD' + value: 'true' + } + { + name: 'AZURE_CLIENT_ID' + value: env_contributor_mi.properties.clientId + } + { + name: 'ALLOWED_MANAGED_IDENTITIES' + value: env_mi.properties.clientId + } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + ] + alwaysOn: true + http20Enabled: true + http20ProxyFlag: 1 + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${env_contributor_mi.id}': { } + } + } + kind: 'app,linux,aspiredashboard' +} + output name string = env_asplan.name output planId string = env_asplan.id @@ -53,4 +134,10 @@ output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env_mi.id -output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID string = env_mi.properties.clientId \ No newline at end of file +output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID string = env_mi.properties.clientId + +output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID string = env_contributor_mi.id + +output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID string = env_contributor_mi.properties.principalId + +output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' \ No newline at end of file diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.bicep index d08f805592e..37dfe78b3d5 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param env_outputs_azure_container_registry_endpoint string @@ -11,6 +11,12 @@ param env_outputs_azure_container_registry_managed_identity_client_id string param api_containerimage string +param env_outputs_azure_app_service_dashboard_uri string + +param env_outputs_azure_website_contributor_managed_identity_id string + +param env_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -28,6 +34,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { properties: { serverFarmId: env_outputs_planid siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id @@ -36,6 +43,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'PORT' value: '85' } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'api' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: env_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: env_outputs_azure_container_registry_managed_identity_client_id + } ] } } @@ -45,4 +80,14 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { '${env_outputs_azure_container_registry_managed_identity_id}': { } } } -} \ No newline at end of file +} + +resource api_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, env_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: env_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.json index 4006e252c4f..069979ae62c 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.json +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddDockerfileWithAppServiceInfrastructureAddsDeploymentTargetWithAppServiceToContainerResources.verified.json @@ -1,4 +1,4 @@ -{ +{ "type": "azure.bicep.v0", "path": "api.module.bicep", "params": { @@ -6,6 +6,9 @@ "env_outputs_planid": "{env.outputs.planId}", "env_outputs_azure_container_registry_managed_identity_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", "env_outputs_azure_container_registry_managed_identity_client_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", - "api_containerimage": "{api.containerImage}" + "api_containerimage": "{api.containerImage}", + "env_outputs_azure_app_service_dashboard_uri": "{env.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env_outputs_azure_website_contributor_managed_identity_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env_outputs_azure_website_contributor_managed_identity_principal_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep index ecca0504dd1..f90c40fe14f 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceEnvironmentCanReferenceExistingAppServicePlan.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param userPrincipalId string = '' @@ -34,6 +34,7 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = { name: take('envasplan-${uniqueString(resourceGroup().id)}', 60) location: location properties: { + perSiteScaling: true reserved: true } kind: 'Linux' @@ -43,6 +44,86 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = { } } +resource env_contributor_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { + name: take('env_contributor_mi-${uniqueString(resourceGroup().id)}', 128) + location: location +} + +resource env_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(resourceGroup().id, env_contributor_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')) + properties: { + principalId: env_contributor_mi.properties.principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + principalType: 'ServicePrincipal' + } +} + +resource dashboard 'Microsoft.Web/sites@2024-11-01' = { + name: take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60) + location: location + properties: { + serverFarmId: env_asplan.id + siteConfig: { + numberOfWorkers: 1 + linuxFxVersion: 'ASPIREDASHBOARD|1.0' + acrUseManagedIdentityCreds: true + acrUserManagedIdentityID: env_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: 'WEBSITES_PORT' + value: '5000' + } + { + name: 'HTTP20_ONLY_PORT' + value: '4317' + } + { + name: 'WEBSITE_START_SCM_WITH_PRELOAD' + value: 'true' + } + { + name: 'AZURE_CLIENT_ID' + value: env_contributor_mi.properties.clientId + } + { + name: 'ALLOWED_MANAGED_IDENTITIES' + value: env_mi.properties.clientId + } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + ] + alwaysOn: true + http20Enabled: true + http20ProxyFlag: 1 + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${env_contributor_mi.id}': { } + } + } + kind: 'app,linux,aspiredashboard' +} + output name string = env_asplan.name output planId string = env_asplan.id @@ -53,4 +134,10 @@ output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env_mi.id -output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID string = env_mi.properties.clientId \ No newline at end of file +output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID string = env_mi.properties.clientId + +output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID string = env_contributor_mi.id + +output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID string = env_contributor_mi.properties.principalId + +output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.bicep index a4c2c942b80..a92750caf6a 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param env_outputs_azure_container_registry_endpoint string @@ -11,6 +11,12 @@ param env_outputs_azure_container_registry_managed_identity_client_id string param api_containerimage string +param env_outputs_azure_app_service_dashboard_uri string + +param env_outputs_azure_website_contributor_managed_identity_id string + +param env_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -28,6 +34,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { properties: { serverFarmId: env_outputs_planid siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id @@ -52,6 +59,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'PORT' value: '80' } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'api' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: env_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: env_outputs_azure_container_registry_managed_identity_client_id + } ] } } @@ -61,4 +96,14 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { '${env_outputs_azure_container_registry_managed_identity_id}': { } } } -} \ No newline at end of file +} + +resource api_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, env_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: env_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.json index 4006e252c4f..069979ae62c 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.json +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AzureAppServiceSupportBaitAndSwitchResources.verified.json @@ -1,4 +1,4 @@ -{ +{ "type": "azure.bicep.v0", "path": "api.module.bicep", "params": { @@ -6,6 +6,9 @@ "env_outputs_planid": "{env.outputs.planId}", "env_outputs_azure_container_registry_managed_identity_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", "env_outputs_azure_container_registry_managed_identity_client_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", - "api_containerimage": "{api.containerImage}" + "api_containerimage": "{api.containerImage}", + "env_outputs_azure_app_service_dashboard_uri": "{env.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env_outputs_azure_website_contributor_managed_identity_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env_outputs_azure_website_contributor_managed_identity_principal_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep index 055a4988c46..bd0059330d7 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param env_outputs_azure_container_registry_endpoint string @@ -13,6 +13,12 @@ param project2_containerimage string param project2_containerport string +param env_outputs_azure_app_service_dashboard_uri string + +param env_outputs_azure_website_contributor_managed_identity_id string + +param env_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -30,6 +36,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { properties: { serverFarmId: env_outputs_planid siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id @@ -58,6 +65,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'services__project1__http__0' value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'project2' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: env_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: env_outputs_azure_container_registry_managed_identity_client_id + } ] } } @@ -67,4 +102,14 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { '${env_outputs_azure_container_registry_managed_identity_id}': { } } } -} \ No newline at end of file +} + +resource project2_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, env_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: env_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.json index 133bb4d92b4..f850deb03d4 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.json +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.json @@ -1,4 +1,4 @@ -{ +{ "type": "azure.bicep.v0", "path": "project2.module.bicep", "params": { @@ -7,6 +7,9 @@ "env_outputs_azure_container_registry_managed_identity_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", "env_outputs_azure_container_registry_managed_identity_client_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", "project2_containerimage": "{project2.containerImage}", - "project2_containerport": "{project2.containerPort}" + "project2_containerport": "{project2.containerPort}", + "env_outputs_azure_app_service_dashboard_uri": "{env.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env_outputs_azure_website_contributor_managed_identity_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env_outputs_azure_website_contributor_managed_identity_principal_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.bicep index d02597b2721..0a1b2cb0e13 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param env_outputs_azure_container_registry_endpoint string @@ -21,6 +21,12 @@ param api_identity_outputs_id string param api_identity_outputs_clientid string +param env_outputs_azure_app_service_dashboard_uri string + +param env_outputs_azure_website_contributor_managed_identity_id string + +param env_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -58,6 +64,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { serverFarmId: env_outputs_planid keyVaultReferenceIdentity: api_identity_outputs_id siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id @@ -86,6 +93,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'AZURE_CLIENT_ID' value: api_identity_outputs_clientid } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'api' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: env_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: env_outputs_azure_container_registry_managed_identity_client_id + } ] } } @@ -97,3 +132,13 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { } } } + +resource api_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, env_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: env_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.json index d44c762d50b..bc30b2ead00 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.json +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.KeyvaultReferenceHandling.verified.json @@ -1,4 +1,4 @@ -{ +{ "type": "azure.bicep.v0", "path": "api.module.bicep", "params": { @@ -11,6 +11,9 @@ "kvName": "{kvName.value}", "sharedRg": "{sharedRg.value}", "api_identity_outputs_id": "{api-identity.outputs.id}", - "api_identity_outputs_clientid": "{api-identity.outputs.clientId}" + "api_identity_outputs_clientid": "{api-identity.outputs.clientId}", + "env_outputs_azure_app_service_dashboard_uri": "{env.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env_outputs_azure_website_contributor_managed_identity_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env_outputs_azure_website_contributor_managed_identity_principal_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json index ab5ddeb4b2f..576fa8a046d 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.MultipleAzureAppServiceEnvironmentsSupported.verified.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://json.schemastore.org/aspire-8.0.json", "resources": { "env1": { @@ -26,7 +26,10 @@ "env1_outputs_azure_container_registry_managed_identity_id": "{env1.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", "env1_outputs_azure_container_registry_managed_identity_client_id": "{env1.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", "servicea_containerimage": "{ServiceA.containerImage}", - "servicea_containerport": "{ServiceA.containerPort}" + "servicea_containerport": "{ServiceA.containerPort}", + "env1_outputs_azure_app_service_dashboard_uri": "{env1.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env1_outputs_azure_website_contributor_managed_identity_id": "{env1.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env1_outputs_azure_website_contributor_managed_identity_principal_id": "{env1.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } }, "env": { @@ -62,7 +65,10 @@ "env2_outputs_azure_container_registry_managed_identity_id": "{env2.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}", "env2_outputs_azure_container_registry_managed_identity_client_id": "{env2.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", "serviceb_containerimage": "{ServiceB.containerImage}", - "serviceb_containerport": "{ServiceB.containerPort}" + "serviceb_containerport": "{ServiceB.containerPort}", + "env2_outputs_azure_app_service_dashboard_uri": "{env2.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env2_outputs_azure_website_contributor_managed_identity_id": "{env2.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env2_outputs_azure_website_contributor_managed_identity_principal_id": "{env2.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } }, "env": { @@ -88,4 +94,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.ResourceWithProbes.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.ResourceWithProbes.verified.bicep index 81903b3ad5c..65ccb730c1a 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.ResourceWithProbes.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.ResourceWithProbes.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param env_outputs_azure_container_registry_endpoint string @@ -13,6 +13,12 @@ param project1_containerimage string param project1_containerport string +param env_outputs_azure_app_service_dashboard_uri string + +param env_outputs_azure_website_contributor_managed_identity_id string + +param env_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -30,6 +36,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { properties: { serverFarmId: env_outputs_planid siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id @@ -54,6 +61,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'HTTPS_PORTS' value: project1_containerport } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'project1' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: env_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: env_outputs_azure_container_registry_managed_identity_client_id + } ] healthCheckPath: '/health' } @@ -64,4 +99,14 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { '${env_outputs_azure_container_registry_managed_identity_id}': { } } } -} \ No newline at end of file +} + +resource project1_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, env_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: env_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.bicep index 49f1e990488..e00a9f36113 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param env_outputs_azure_container_registry_endpoint string @@ -15,6 +15,12 @@ param api_containerport string param customvalue string +param env_outputs_azure_app_service_dashboard_uri string + +param env_outputs_azure_website_contributor_managed_identity_id string + +param env_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -32,6 +38,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { properties: { serverFarmId: env_outputs_planid siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: env_outputs_azure_container_registry_managed_identity_client_id @@ -60,6 +67,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'CUSTOM_VALUE' value: customvalue } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'env' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'api' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: env_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: env_outputs_azure_container_registry_managed_identity_client_id + } ] } } @@ -69,4 +104,14 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { '${env_outputs_azure_container_registry_managed_identity_id}': { } } } +} + +resource api_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, env_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: env_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp } \ No newline at end of file diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.json b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.json index 21afcf7ade7..c22be722ee9 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.json +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.UnknownManifestExpressionProviderIsHandledWithAllocateParameter.verified.json @@ -1,4 +1,4 @@ -{ +{ "type": "azure.bicep.v0", "path": "api.module.bicep", "params": { @@ -8,6 +8,9 @@ "env_outputs_azure_container_registry_managed_identity_client_id": "{env.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID}", "api_containerimage": "{api.containerImage}", "api_containerport": "{api.containerPort}", - "customvalue": "{customValue}" + "customvalue": "{customValue}", + "env_outputs_azure_app_service_dashboard_uri": "{env.outputs.AZURE_APP_SERVICE_DASHBOARD_URI}", + "env_outputs_azure_website_contributor_managed_identity_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID}", + "env_outputs_azure_website_contributor_managed_identity_principal_id": "{env.outputs.AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID}" } -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_AzureAppService_Works#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_AzureAppService_Works#00.verified.bicep index f1e8fdd1b5a..f9d75ca447f 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_AzureAppService_Works#00.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_AzureAppService_Works#00.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param appservice_outputs_azure_container_registry_endpoint string @@ -15,6 +15,12 @@ param myidentity_outputs_id string param myidentity_outputs_clientid string +param appservice_outputs_azure_app_service_dashboard_uri string + +param appservice_outputs_azure_website_contributor_managed_identity_id string + +param appservice_outputs_azure_website_contributor_managed_identity_principal_id string + resource mainContainer 'Microsoft.Web/sites/sitecontainers@2024-11-01' = { name: 'main' properties: { @@ -33,6 +39,7 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { serverFarmId: appservice_outputs_planid keyVaultReferenceIdentity: myidentity_outputs_id siteConfig: { + numberOfWorkers: 30 linuxFxVersion: 'SITECONTAINERS' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: appservice_outputs_azure_container_registry_managed_identity_client_id @@ -53,6 +60,34 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'AZURE_CLIENT_ID' value: myidentity_outputs_clientid } + { + name: 'ASPIRE_ENVIRONMENT_NAME' + value: 'appservice' + } + { + name: 'OTEL_SERVICE_NAME' + value: 'myapp' + } + { + name: 'OTEL_EXPORTER_OTLP_PROTOCOL' + value: 'grpc' + } + { + name: 'OTEL_EXPORTER_OTLP_ENDPOINT' + value: 'http://localhost:6001' + } + { + name: 'WEBSITE_ENABLE_ASPIRE_OTEL_SIDECAR' + value: 'true' + } + { + name: 'OTEL_COLLECTOR_URL' + value: appservice_outputs_azure_app_service_dashboard_uri + } + { + name: 'OTEL_CLIENT_ID' + value: appservice_outputs_azure_container_registry_managed_identity_client_id + } ] } } @@ -63,4 +98,14 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { '${myidentity_outputs_id}': { } } } +} + +resource myapp_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(webapp.id, appservice_outputs_azure_website_contributor_managed_identity_id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772')) + properties: { + principalId: appservice_outputs_azure_website_contributor_managed_identity_principal_id + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'de139f84-1756-47ae-9be6-808fbbe84772') + principalType: 'ServicePrincipal' + } + scope: webapp } \ No newline at end of file diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_Works#00.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_Works#00.verified.bicep index 59c4ebc5d78..7add56a6345 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_Works#00.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureUserAssignedIdentityTests.WithAzureUserAssignedIdentity_WithRoleAssignments_Works#00.verified.bicep @@ -1,4 +1,4 @@ -@description('The location for the resource(s) to be deployed.') +@description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location param cae_outputs_azure_container_apps_environment_default_domain string @@ -71,4 +71,4 @@ resource myapp 'Microsoft.App/containerApps@2025-02-02-preview' = { '${cae_outputs_azure_container_registry_managed_identity_id}': { } } } -} \ No newline at end of file +}