-
Notifications
You must be signed in to change notification settings - Fork 882
Expand file tree
/
Copy pathAzureAppServiceWebSiteResource.cs
More file actions
150 lines (126 loc) · 7.01 KB
/
AzureAppServiceWebSiteResource.cs
File metadata and controls
150 lines (126 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable ASPIREPIPELINES001
#pragma warning disable ASPIREAZURE001
#pragma warning disable ASPIREPIPELINES003
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Pipelines;
using Microsoft.Extensions.Logging;
namespace Aspire.Hosting.Azure;
/// <summary>
/// Represents an Azure App Service Web Site resource.
/// </summary>
public class AzureAppServiceWebSiteResource : AzureProvisioningResource
{
/// <summary>
/// Initializes a new instance of the <see cref="AzureAppServiceWebSiteResource"/> class.
/// </summary>
/// <param name="name">The name of the resource in the Aspire application model.</param>
/// <param name="configureInfrastructure">Callback to configure the Azure resources.</param>
/// <param name="targetResource">The target resource that this Azure Web Site is being created for.</param>
public AzureAppServiceWebSiteResource(string name, Action<AzureResourceInfrastructure> configureInfrastructure, IResource targetResource)
: base(name, configureInfrastructure)
{
TargetResource = targetResource;
// Add pipeline step annotation for deploy
Annotations.Add(new PipelineStepAnnotation((factoryContext) =>
{
// Get the deployment target annotation
var deploymentTargetAnnotation = targetResource.GetDeploymentTargetAnnotation();
if (deploymentTargetAnnotation is null)
{
return [];
}
var steps = new List<PipelineStep>();
var printResourceSummary = new PipelineStep
{
Name = $"print-{targetResource.Name}-summary",
Description = $"Prints the deployment summary and URL for {targetResource.Name}.",
Action = async ctx =>
{
var computerEnv = (AzureAppServiceEnvironmentResource)deploymentTargetAnnotation.ComputeEnvironment!;
string? deploymentSlot = null;
if (computerEnv.DeploymentSlot is not null || computerEnv.DeploymentSlotParameter is not null)
{
deploymentSlot = computerEnv.DeploymentSlotParameter is null ?
computerEnv.DeploymentSlot :
await computerEnv.DeploymentSlotParameter.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false);
}
var hostName = await GetAppServiceWebsiteNameAsync(ctx, deploymentSlot).ConfigureAwait(false);
var endpoint = $"https://{hostName}.azurewebsites.net";
ctx.ReportingStep.Log(LogLevel.Information, $"Successfully deployed **{targetResource.Name}** to [{endpoint}]({endpoint})", enableMarkdown: true);
ctx.Summary.Add(targetResource.Name, endpoint);
},
Tags = ["print-summary"],
RequiredBySteps = [WellKnownPipelineSteps.Deploy]
};
var deployStep = new PipelineStep
{
Name = $"deploy-{targetResource.Name}",
Description = $"Aggregation step for deploying {targetResource.Name} to Azure App Service.",
Action = _ => Task.CompletedTask,
Tags = [WellKnownPipelineTags.DeployCompute]
};
deployStep.DependsOn(printResourceSummary);
steps.Add(deployStep);
steps.Add(printResourceSummary);
return steps;
}));
// Add pipeline configuration annotation to wire up dependencies
Annotations.Add(new PipelineConfigurationAnnotation((context) =>
{
var provisionSteps = context.GetSteps(this, WellKnownPipelineTags.ProvisionInfrastructure);
// The app deployment should depend on push steps from the target resource
var pushSteps = context.GetSteps(targetResource, WellKnownPipelineTags.PushContainerImage);
provisionSteps.DependsOn(pushSteps);
// The app deployment should depend on role assignment and identity provisioning for the target resource
// This ensures role assignments and private endpoints are ready before the app is deployed
var roleAssignmentPrefix = $"{targetResource.Name}-roles-";
foreach (var resource in context.Model.Resources)
{
if (resource.Name.StartsWith(roleAssignmentPrefix, StringComparison.Ordinal))
{
var roleSteps = context.GetSteps(resource, WellKnownPipelineTags.ProvisionInfrastructure);
provisionSteps.DependsOn(roleSteps);
}
}
// Ensure summary step runs after provision
context.GetSteps(this, "print-summary").DependsOn(provisionSteps);
}));
}
/// <summary>
/// Gets the target resource that this Azure Web Site is being created for.
/// </summary>
public IResource TargetResource { get; }
/// <summary>
/// Gets the Azure App Service website name, optionally including the deployment slot suffix.
/// </summary>
/// <param name="context">The pipeline step context.</param>
/// <param name="deploymentSlot">The optional deployment slot name to append to the website name.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the website name.</returns>
private async Task<string> GetAppServiceWebsiteNameAsync(PipelineStepContext context, string? deploymentSlot = null)
{
var computerEnv = (AzureAppServiceEnvironmentResource)TargetResource.GetDeploymentTargetAnnotation()!.ComputeEnvironment!;
var websiteSuffix = await computerEnv.WebSiteSuffix.GetValueAsync(context.CancellationToken).ConfigureAwait(false);
var websiteName = $"{TargetResource.Name.ToLowerInvariant()}-{websiteSuffix}";
if (string.IsNullOrWhiteSpace(deploymentSlot))
{
return TruncateToMaxLength(websiteName, 60);
}
websiteName = TruncateToMaxLength(websiteName, MaxWebSiteNamePrefixLengthWithSlot);
websiteName += $"-{deploymentSlot}";
return TruncateToMaxLength(websiteName, MaxHostPrefixLengthWithSlot);
}
private static string TruncateToMaxLength(string value, int maxLength)
{
if (value.Length <= maxLength)
{
return value;
}
return value.Substring(0, maxLength);
}
// For Azure App Service, the maximum length for a host name is 63 characters. With slot, the host name is 59 characters, with 4 characters reserved for random slot suffix (very edge case).
// Source of truth: https://msazure.visualstudio.com/One/_git/AAPT-Antares-Websites?path=%2Fsrc%2FHosting%2FAdministrationService%2FMicrosoft.Web.Hosting.Administration.Api%2FCommonConstants.cs&_a=contents&version=GBdev
internal const int MaxHostPrefixLengthWithSlot = 59;
internal const int MaxWebSiteNamePrefixLengthWithSlot = 40;
}