-
Notifications
You must be signed in to change notification settings - Fork 717
Added initial support for app service as a compute environment #9090
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
f601838
75754d0
d377bda
de11dd4
2517e25
8584b0c
c12e359
6990e1b
70ea39b
bca9076
94ef997
d1f17a4
655962f
964202c
b2cceb5
770945d
2ed723d
3282b01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>$(DefaultTargetFramework)</TargetFramework> | ||
| <IsPackable>true</IsPackable> | ||
| <PackageTags>aspire integration hosting azure</PackageTags> | ||
| <Description>Azure app service resource types for .NET Aspire.</Description> | ||
| <PackageIconFullPath>$(SharedDir)Azure_256x.png</PackageIconFullPath> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does app service have its own icon? |
||
| <SuppressFinalPackageVersion>true</SuppressFinalPackageVersion> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Compile Include="$(SharedDir)BicepFunction2.cs" Link="Provisioning\Utils\BicepFunction2.cs" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.Provisioning.AppService" /> | ||
| <PackageReference Include="Azure.Provisioning.ContainerRegistry" /> | ||
| <ProjectReference Include="..\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" /> | ||
| <ProjectReference Include="..\Aspire.Hosting.Azure.ContainerRegistry\Aspire.Hosting.Azure.ContainerRegistry.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // 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 Azure.Provisioning.AppService; | ||
|
|
||
| namespace Aspire.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Provides extension methods for publishing compute resources as Azure App Service websites. | ||
| /// </summary> | ||
| public static class AzureAppServiceComputeResourceExtensions | ||
| { | ||
| /// <summary> | ||
| /// Publishes the specified compute resource as an Azure App Service. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of the compute resource.</typeparam> | ||
| /// <param name="builder">The compute resource builder.</param> | ||
| /// <param name="configure">The configuration action for the App Service WebSite resource.</param> | ||
| /// <returns>The updated compute resource builder.</returns> | ||
| /// <remarks> | ||
| /// This method checks if the application is in publish mode. If it is, it adds the necessary infrastructure | ||
davidfowl marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// for Azure App Service and applies the provided configuration action to the App Service WebSite resource. | ||
| /// <example> | ||
| /// <code> | ||
| /// builder.AddNpmApp("name", "image").PublishAsAzureAppServiceWebsite((infrastructure, app) => | ||
|
||
| /// { | ||
| /// // Configure the App Service WebSite resource here | ||
| /// }); | ||
| /// </code> | ||
| /// </example> | ||
| /// </remarks> | ||
| public static IResourceBuilder<T> PublishAsAzureAppServiceWebsite<T>(this IResourceBuilder<T> builder, Action<AzureResourceInfrastructure, WebSite> configure) | ||
| #pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
| where T : IComputeResource | ||
| #pragma warning restore ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
| { | ||
| ArgumentNullException.ThrowIfNull(builder); | ||
| ArgumentNullException.ThrowIfNull(configure); | ||
|
|
||
| if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) | ||
| { | ||
| return builder; | ||
| } | ||
|
|
||
| return builder.WithAnnotation(new AzureAppServiceWebsiteCustomizationAnnotation(configure)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // 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.AppService; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Aspire.Hosting.Azure; | ||
|
|
||
| internal sealed class AzureAppServiceEnvironmentContext( | ||
| ILogger logger, | ||
| DistributedApplicationExecutionContext executionContext, | ||
| AzureAppServiceEnvironmentResource environment) | ||
| { | ||
| public ILogger Logger => logger; | ||
|
|
||
| public DistributedApplicationExecutionContext ExecutionContext => executionContext; | ||
|
|
||
| public AzureAppServiceEnvironmentResource Environment => environment; | ||
|
|
||
| private readonly Dictionary<IResource, AzureAppServiceWebsiteContext> _appServices = []; | ||
|
|
||
| public AzureAppServiceWebsiteContext GetAppServiceContext(IResource resource) | ||
| { | ||
| if (!_appServices.TryGetValue(resource, out var context)) | ||
| { | ||
| throw new InvalidOperationException($"App Service context not found for resource {resource.Name}."); | ||
| } | ||
|
|
||
| return context; | ||
| } | ||
|
|
||
| public async Task<AzureBicepResource> CreateAppServiceAsync(IResource resource, AzureProvisioningOptions provisioningOptions, CancellationToken cancellationToken) | ||
| { | ||
| if (!_appServices.TryGetValue(resource, out var context)) | ||
| { | ||
| _appServices[resource] = context = new AzureAppServiceWebsiteContext(resource, this); | ||
| await context.ProcessAsync(cancellationToken).ConfigureAwait(false); | ||
| } | ||
|
|
||
| var provisioningResource = new AzureProvisioningResource(resource.Name, context.BuildWebSite) | ||
| { | ||
| ProvisioningBuildOptions = provisioningOptions.ProvisioningBuildOptions | ||
| }; | ||
|
|
||
| return provisioningResource; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // 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.Provisioning; | ||
| using Azure.Provisioning.AppService; | ||
| using Azure.Provisioning.ContainerRegistry; | ||
| using Azure.Provisioning.Expressions; | ||
| 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 | ||
| { | ||
| /// <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> AddAppServiceEnvironment(this IDistributedApplicationBuilder builder, string name) | ||
davidfowl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| builder.AddAzureProvisioning(); | ||
| builder.Services.Configure<AzureProvisioningOptions>(options => options.SupportsTargetedRoleAssignments = true); | ||
|
|
||
| if (builder.ExecutionContext.IsPublishMode) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this check necessary? The infrastructure class checks in its hook already. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its the same in aca |
||
| { | ||
| builder.Services.TryAddLifecycleHook<AzureAppServiceInfrastructure>(); | ||
| } | ||
|
|
||
| var resource = new AzureAppServiceEnvironmentResource(name, static infra => | ||
| { | ||
| var prefix = infra.AspireResource.Name; | ||
| var resource = infra.AspireResource; | ||
|
|
||
| var identity = new UserAssignedIdentity(Infrastructure.NormalizeBicepIdentifier($"{prefix}-mi")) | ||
| { | ||
| }; | ||
|
|
||
| infra.Add(identity); | ||
|
|
||
| // This tells azd to avoid creating infrastructure | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really? That's the contract? If there is a parameter named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep @vhvb1989. I think this is because we also support yaml mode where the app uses none of these types and azd still works. |
||
| var userPrincipalId = new ProvisioningParameter(AzureBicepResource.KnownParameters.UserPrincipalId, typeof(string)); | ||
| infra.Add(userPrincipalId); | ||
|
|
||
| var tags = new ProvisioningParameter("tags", typeof(object)) | ||
| { | ||
| Value = new BicepDictionary<string>() | ||
| }; | ||
|
|
||
| infra.Add(tags); | ||
|
|
||
| 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 }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should consolidate this across ACA and here, so there is only 1 place to change the defaults of an ACR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This, endpoint grouping, and env and arg processing. |
||
| 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")) | ||
davidfowl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| Sku = new AppServiceSkuDescription | ||
| { | ||
| Name = "B1", | ||
| Tier = "Basic" | ||
| }, | ||
| Kind = "Linux", | ||
| IsReserved = true | ||
| }; | ||
|
|
||
| infra.Add(plan); | ||
|
|
||
| infra.Add(new ProvisioningOutput("id", typeof(string)) | ||
davidfowl marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| Value = plan.Id | ||
| }); | ||
|
|
||
| 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 (!builder.ExecutionContext.IsPublishMode) | ||
| { | ||
| return builder.CreateResourceBuilder(resource); | ||
| } | ||
|
|
||
| return builder.AddResource(resource); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // 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; | ||
|
|
||
| namespace Aspire.Hosting.Azure.AppService; | ||
|
|
||
| /// <summary> | ||
| /// Represents an Azure App Service Environment resource. | ||
| /// </summary> | ||
| /// <param name="name">The name of the Azure App Service Environment.</param> | ||
| /// <param name="configureInfrastructure">The callback to configure the Azure infrastructure for this resource.</param> | ||
| public class AzureAppServiceEnvironmentResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure) : | ||
| AzureProvisioningResource(name, configureInfrastructure), | ||
| #pragma warning disable ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
| IComputeEnvironmentResource, | ||
| IAzureContainerRegistry | ||
| #pragma warning restore ASPIRECOMPUTE001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
| { | ||
| // We don't want these to be public if we end up with an app service | ||
| // per compute resource. | ||
| internal BicepOutputReference IdOutputReference => new("id", this); | ||
| internal BicepOutputReference ContainerRegistryUrl => new("AZURE_CONTAINER_REGISTRY_ENDPOINT", this); | ||
| internal BicepOutputReference ContainerRegistryName => 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); | ||
|
|
||
| ReferenceExpression IAzureContainerRegistry.ManagedIdentityId => | ||
| ReferenceExpression.Create($"{ContainerRegistryManagedIdentityId}"); | ||
|
|
||
| ReferenceExpression IContainerRegistry.Name => | ||
| ReferenceExpression.Create($"{ContainerRegistryName}"); | ||
|
|
||
| ReferenceExpression IContainerRegistry.Endpoint => | ||
| ReferenceExpression.Create($"{ContainerRegistryUrl}"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want any package tags specific for AppService?