|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Aspire.Hosting.Azure.AppService;
using Aspire.Hosting.Lifecycle;
using Azure.Core;
using Azure.Provisioning;
using Azure.Provisioning.ApplicationInsights;
using Azure.Provisioning.AppService;
using Azure.Provisioning.ContainerRegistry;
using Azure.Provisioning.Expressions;
using Azure.Provisioning.OperationalInsights;
using Azure.Provisioning.Roles;
using Microsoft.Extensions.DependencyInjection;
namespace Aspire.Hosting;
/// <summary>
/// Extensions for adding Azure App Service Environment resources to a distributed application builder.
/// </summary>
public static partial class AzureAppServiceEnvironmentExtensions
{
internal static IDistributedApplicationBuilder AddAzureAppServiceInfrastructureCore(this IDistributedApplicationBuilder builder)
{
// ensure AzureProvisioning is added first so the AzureResourcePreparer lifecycle hook runs before AzureAppServiceInfrastructure
builder.AddAzureProvisioning();
builder.Services.Configure<AzureProvisioningOptions>(options => options.SupportsTargetedRoleAssignments = true);
builder.Services.TryAddEventingSubscriber<AzureAppServiceInfrastructure>();
return builder;
}
/// <summary>
/// Adds a azure app service environment resource to the distributed application builder.
/// </summary>
/// <param name="builder">The distributed application builder.</param>
/// <param name="name">The name of the resource.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
public static IResourceBuilder<AzureAppServiceEnvironmentResource> AddAzureAppServiceEnvironment(this IDistributedApplicationBuilder builder, string name)
{
builder.AddAzureAppServiceInfrastructureCore();
var resource = new AzureAppServiceEnvironmentResource(name, static infra =>
{
var prefix = infra.AspireResource.Name;
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>(string.Empty) };
infra.Add(userPrincipalId);
var tags = new ProvisioningParameter("tags", typeof(object))
{
Value = new BicepDictionary<string>()
};
infra.Add(tags);
var identity = new UserAssignedIdentity(Infrastructure.NormalizeBicepIdentifier($"{prefix}-mi"))
{
Tags = tags
};
infra.Add(identity);
ContainerRegistryService? containerRegistry = null;
if (resource.TryGetLastAnnotation<ContainerRegistryReferenceAnnotation>(out var registryReferenceAnnotation) && registryReferenceAnnotation.Registry is AzureProvisioningResource registry)
{
containerRegistry = (ContainerRegistryService)registry.AddAsExistingResource(infra);
}
else
{
containerRegistry = new ContainerRegistryService(Infrastructure.NormalizeBicepIdentifier($"{prefix}_acr"))
{
Sku = new() { Name = ContainerRegistrySkuName.Basic },
Tags = tags
};
}
infra.Add(containerRegistry);
var pullRa = containerRegistry.CreateRoleAssignment(ContainerRegistryBuiltInRole.AcrPull, identity);
// There's a bug in the CDK, see https://github.com/Azure/azure-sdk-for-net/issues/47265
pullRa.Name = BicepFunction.CreateGuid(containerRegistry.Id, identity.Id, pullRa.RoleDefinitionId);
infra.Add(pullRa);
var plan = new AppServicePlan(Infrastructure.NormalizeBicepIdentifier($"{prefix}-asplan"))
{
Sku = new AppServiceSkuDescription
{
Name = "P0V3",
Tier = "Premium"
},
Kind = "Linux",
IsReserved = 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);
infra.Add(new ProvisioningOutput("name", typeof(string))
{
Value = plan.Name
});
infra.Add(new ProvisioningOutput("planId", typeof(string))
{
Value = plan.Id
});
infra.Add(new ProvisioningOutput("webSiteSuffix", typeof(string))
{
Value = AzureAppServiceEnvironmentResource.GetWebSiteSuffixBicep()
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_REGISTRY_NAME", typeof(string))
{
Value = containerRegistry.Name
});
// AZD looks for this output to find the container registry endpoint
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_REGISTRY_ENDPOINT", typeof(string))
{
Value = containerRegistry.LoginServer
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID", typeof(string))
{
Value = identity.Id
});
infra.Add(new ProvisioningOutput("AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID", typeof(string))
{
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 (resource.EnableApplicationInsights)
{
ApplicationInsightsComponent? applicationInsights = null;
if (resource.ApplicationInsightsResource is not null)
{
applicationInsights = (ApplicationInsightsComponent)resource.ApplicationInsightsResource.AddAsExistingResource(infra);
}
else
{
// Create Log Analytics workspace
var logAnalyticsWorkspace = new OperationalInsightsWorkspace(prefix + "_law")
{
Sku = new OperationalInsightsWorkspaceSku()
{
Name = OperationalInsightsWorkspaceSkuName.PerGB2018
}
};
infra.Add(logAnalyticsWorkspace);
// Create Application Insights resource linked to the Log Analytics workspace
applicationInsights = new ApplicationInsightsComponent(prefix + "_ai")
{
ApplicationType = ApplicationInsightsApplicationType.Web,
Kind = "web",
WorkspaceResourceId = logAnalyticsWorkspace.Id,
IngestionMode = ComponentIngestionMode.LogAnalytics
};
if (resource.ApplicationInsightsLocation is not null)
{
var applicationInsightsLocation = new AzureLocation(resource.ApplicationInsightsLocation);
applicationInsights.Location = applicationInsightsLocation;
}
else if (resource.ApplicationInsightsLocationParameter is not null)
{
var applicationInsightsLocationParameter = resource.ApplicationInsightsLocationParameter.AsProvisioningParameter(infra);
applicationInsights.Location = applicationInsightsLocationParameter;
}
}
infra.Add(applicationInsights);
infra.Add(new ProvisioningOutput("AZURE_APPLICATION_INSIGHTS_INSTRUMENTATIONKEY", typeof(string))
{
Value = applicationInsights.InstrumentationKey
});
infra.Add(new ProvisioningOutput("AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING", typeof(string))
{
Value = applicationInsights.ConnectionString
});
}
});
if (!builder.ExecutionContext.IsPublishMode)
{
return builder.CreateResourceBuilder(resource);
}
return builder.AddResource(resource);
}
/// <summary>
/// Configures whether the Aspire dashboard should be included in the Azure App Service environment.
/// </summary>
/// <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>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;
return builder;
}
/// <summary>
/// Configures whether Azure Application Insights should be enabled for the Azure App Service.
/// </summary>
/// <param name="builder">The AzureAppServiceEnvironmentResource to configure.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAzureApplicationInsights(this IResourceBuilder<AzureAppServiceEnvironmentResource> builder)
{
ArgumentNullException.ThrowIfNull(builder);
builder.Resource.EnableApplicationInsights = true;
return builder;
}
/// <summary>
/// Configures whether Azure Application Insights should be enabled for the Azure App Service.
/// </summary>
/// <param name="builder">The AzureAppServiceEnvironmentResource to configure.</param>
/// <param name="applicationInsightsLocation">The location for Application Insights.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAzureApplicationInsights(this IResourceBuilder<AzureAppServiceEnvironmentResource> builder, string applicationInsightsLocation)
{
builder.WithAzureApplicationInsights();
builder.Resource.ApplicationInsightsLocation = applicationInsightsLocation;
return builder;
}
/// <summary>
/// Configures whether Azure Application Insights should be enabled for the Azure App Service.
/// </summary>
/// <param name="builder">The AzureAppServiceEnvironmentResource to configure.</param>
/// <param name="applicationInsightsLocation">The location parameter for Application Insights.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAzureApplicationInsights(this IResourceBuilder<AzureAppServiceEnvironmentResource> builder, IResourceBuilder<ParameterResource> applicationInsightsLocation)
{
builder.WithAzureApplicationInsights();
builder.Resource.ApplicationInsightsLocationParameter = applicationInsightsLocation.Resource;
return builder;
}
/// <summary>
/// Configures whether Azure Application Insights should be enabled for the Azure App Service.
/// </summary>
/// <param name="builder">The AzureAppServiceEnvironmentResource builder to configure.</param>
/// <param name="applicationInsightsBuilder">The Application Insights resource builder.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAzureApplicationInsights(this IResourceBuilder<AzureAppServiceEnvironmentResource> builder, IResourceBuilder<AzureApplicationInsightsResource> applicationInsightsBuilder)
{
builder.WithAzureApplicationInsights();
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;
}
}
|