| File: DistributedApplicationPipelineExtensions.cs | Web Access |
| Project: src\playground\pipelines\Pipelines.Library\Pipelines.Library.csproj (Pipelines.Library) |
#pragma warning disable ASPIREAZURE001 #pragma warning disable ASPIREPIPELINES001 using System.Diagnostics; using System.IO.Compression; using System.Net.Http.Headers; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure; using Aspire.Hosting.Pipelines; using Azure.Core; using Azure.Identity; using Azure.Provisioning; namespace Pipelines.Library; public static class DistributedApplicationPipelineExtensions { public static IDistributedApplicationPipeline AddAppServiceZipDeploy(this IDistributedApplicationPipeline pipeline) { pipeline.AddStep("app-service-zip-deploy", async context => { var appServiceEnvironments = context.Model.Resources.OfType<AzureAppServiceEnvironmentResource>(); if (!appServiceEnvironments.Any()) { return; } foreach (var appServiceEnvironment in appServiceEnvironments) { foreach (var resource in context.Model.GetComputeResources()) { var annotation = resource.GetDeploymentTargetAnnotation(); if (annotation != null && annotation.ComputeEnvironment == appServiceEnvironment && annotation.DeploymentTarget is AzureAppServiceWebSiteResource websiteResource) { if (resource is not ProjectResource projectResource) { continue; } await DeployProjectToAppServiceAsync( context, projectResource, websiteResource, appServiceEnvironment, context.CancellationToken).ConfigureAwait(false); } } } }, dependsOn: WellKnownPipelineTags.DeployCompute); return pipeline; } private static async Task DeployProjectToAppServiceAsync( PipelineStepContext context, ProjectResource projectResource, AzureAppServiceWebSiteResource websiteResource, AzureAppServiceEnvironmentResource appServiceEnvironment, CancellationToken cancellationToken) { var projectMetadata = projectResource.GetProjectMetadata(); var projectPath = projectMetadata.ProjectPath; var publishTask = await context.ReportingStep.CreateTaskAsync($"Publishing {projectResource.Name}", cancellationToken).ConfigureAwait(false); await using (publishTask.ConfigureAwait(false)) { var publishDir = Path.Combine(Path.GetTempPath(), $"aspire-publish-{Guid.NewGuid()}"); Directory.CreateDirectory(publishDir); try { var publishProcess = Process.Start(new ProcessStartInfo { FileName = "dotnet", Arguments = $"publish \"{projectPath}\" -c Release -o \"{publishDir}\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }); if (publishProcess == null) { await publishTask.CompleteAsync( "Failed to start dotnet publish", CompletionState.CompletedWithError, cancellationToken).ConfigureAwait(false); return; } await publishProcess.WaitForExitAsync(cancellationToken).ConfigureAwait(false); if (publishProcess.ExitCode != 0) { var error = await publishProcess.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false); await publishTask.CompleteAsync( $"Publish failed: {error}", CompletionState.CompletedWithError, cancellationToken).ConfigureAwait(false); return; } await publishTask.CompleteAsync( "Publish completed", CompletionState.Completed, cancellationToken).ConfigureAwait(false); var zipTask = await context.ReportingStep.CreateTaskAsync($"Creating deployment package", cancellationToken).ConfigureAwait(false); await using (zipTask.ConfigureAwait(false)) { var zipPath = Path.Combine(Path.GetTempPath(), $"aspire-deploy-{Guid.NewGuid()}.zip"); ZipFile.CreateFromDirectory(publishDir, zipPath); await zipTask.CompleteAsync( "Deployment package created", CompletionState.Completed, cancellationToken).ConfigureAwait(false); var uploadTask = await context.ReportingStep.CreateTaskAsync($"Uploading to {projectResource.Name}", cancellationToken).ConfigureAwait(false); await using (uploadTask.ConfigureAwait(false)) { try { var siteName = websiteResource.Outputs[$"{Infrastructure.NormalizeBicepIdentifier(websiteResource.Name)}_name"]?.ToString(); if (string.IsNullOrEmpty(siteName)) { siteName = appServiceEnvironment.Outputs["name"]?.ToString(); } if (string.IsNullOrEmpty(siteName)) { await uploadTask.CompleteAsync( "Could not determine website name", CompletionState.CompletedWithError, cancellationToken).ConfigureAwait(false); return; } var credential = new AzureCliCredential(); var tokenRequestContext = new TokenRequestContext(["https://management.azure.com/.default"]); var accessToken = await credential.GetTokenAsync(tokenRequestContext, cancellationToken).ConfigureAwait(false); var kuduUrl = $"https://{siteName}.scm.azurewebsites.net/api/zipdeploy"; using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Token); httpClient.Timeout = TimeSpan.FromMinutes(30); await using var zipStream = File.OpenRead(zipPath); using var content = new StreamContent(zipStream); content.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); var response = await httpClient.PostAsync(kuduUrl, content, cancellationToken).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); await uploadTask.CompleteAsync( $"Upload failed: {response.StatusCode} - {errorContent}", CompletionState.CompletedWithError, cancellationToken).ConfigureAwait(false); return; } await uploadTask.CompleteAsync( "Upload completed successfully", CompletionState.Completed, cancellationToken).ConfigureAwait(false); } finally { if (File.Exists(zipPath)) { File.Delete(zipPath); } } } } } finally { if (Directory.Exists(publishDir)) { Directory.Delete(publishDir, recursive: true); } } } } }