Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,11 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> AddAzureAppSe
},
Kind = "Linux",
IsReserved = true,
// Enable per-site scaling so each app service can scale independently
IsPerSiteScaling = true
// Enable perSiteScaling or automatic scaling so each app service can scale independently
IsPerSiteScaling = !resource.EnableAutomaticScaling,
IsElasticScaleEnabled = resource.EnableAutomaticScaling,
// Capping the automatic scaling limit to 10 as per best practices
MaximumElasticWorkerCount = 10
};

infra.Add(plan);
Expand Down Expand Up @@ -220,9 +223,9 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> AddAzureAppSe
/// <summary>
/// Configures whether the Aspire dashboard should be included in the Azure App Service environment.
/// </summary>
/// <param name="builder">The AzureAppServiceEnvironmentResource to configure.</param>
/// <param name="builder">The <see cref="IResourceBuilder{AzureAppServiceEnvironmentResource}"/> to configure.</param>
/// <param name="enable">Whether to include the Aspire dashboard. Default is true.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining additional configuration."/></returns>
public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithDashboard(this IResourceBuilder<AzureAppServiceEnvironmentResource> builder, bool enable = true)
{
builder.Resource.EnableDashboard = enable;
Expand Down Expand Up @@ -271,4 +274,15 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAzureAppl
builder.Resource.ApplicationInsightsResource = applicationInsightsBuilder.Resource;
return builder;
}

/// <summary>
/// Configures whether automatic scaling should be enabled for the app services in Azure App Service environment.
/// </summary>
/// <param name="builder">The <see cref="IResourceBuilder{AzureAppServiceEnvironmentResource}"/> to configure.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for chaining additional configuration.</returns>
public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAutomaticScaling(this IResourceBuilder<AzureAppServiceEnvironmentResource> builder)
{
builder.Resource.EnableAutomaticScaling = true;
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public class AzureAppServiceEnvironmentResource(string name, Action<AzureResourc
/// </summary>
internal AzureApplicationInsightsResource? ApplicationInsightsResource { get; set; }

/// <summary>
/// Enables or disables automatic scaling for the App Service Plan.
/// </summary>
internal bool EnableAutomaticScaling { get; set; }

/// <summary>
/// Gets the name of the App Service Plan.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ public static WebSite AddDashboard(AzureResourceInfrastructure infra,
UseManagedIdentityCreds = true,
IsHttp20Enabled = true,
Http20ProxyFlag = 1,
// Setting NumberOfWorkers to 1 to ensure dashboard runs on 1 instance
// Setting instance count to 1 to ensure dashboard runs on 1 instance
NumberOfWorkers = 1,
FunctionAppScaleLimit = 1,
ElasticWebAppScaleLimit = 1,
// IsAlwaysOn set to true ensures the app is always running
IsAlwaysOn = true,
AppSettings = []
Expand Down
21 changes: 21 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,27 @@ await Verify(manifest.ToString(), "json")
.AppendContentAsFile(bicep, "bicep");
}

[Fact]
public async Task AddAppServiceToEnvironmentWithAutomaticScaling()
{
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);

builder.AddAzureAppServiceEnvironment("env").WithAutomaticScaling();

using var app = builder.Build();

await ExecuteBeforeStartHooksAsync(app, default);

var model = app.Services.GetRequiredService<DistributedApplicationModel>();

var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());

var (manifest, bicep) = await GetManifestWithBicep(environment);

await Verify(manifest.ToString(), "json")
.AppendContentAsFile(bicep, "bicep");
}

[Fact]
public async Task AddAppServiceWithArgs()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = {
name: take('envasplan-${uniqueString(resourceGroup().id)}', 60)
location: location
properties: {
elasticScaleEnabled: false
perSiteScaling: true
reserved: true
maximumElasticWorkerCount: 10
}
kind: 'Linux'
sku: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
@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: {
elasticScaleEnabled: true
perSiteScaling: false
reserved: true
maximumElasticWorkerCount: 10
}
kind: 'Linux'
sku: {
name: 'P0V3'
tier: 'Premium'
}
}

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
functionAppScaleLimit: 1
elasticWebAppScaleLimit: 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

output webSiteSuffix string = uniqueString(resourceGroup().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

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'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
"userPrincipalId": ""
}
}
Loading