File: Publishing\ManifestPublishingExtensions.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// 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
 
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Aspire.Hosting.Pipelines;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
 
namespace Aspire.Hosting.Publishing;
 
/// <summary>
/// Provides extension methods for adding manifest publishing to the pipeline.
/// </summary>
[Experimental("ASPIREPIPELINES001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
public static class ManifestPublishingExtensions
{
    /// <summary>
    /// Adds a step to the pipeline that publishes an Aspire manifest file.
    /// </summary>
    /// <param name="pipeline">The pipeline to add the manifest publishing step to.</param>
    /// <returns>The pipeline for chaining.</returns>
    public static IDistributedApplicationPipeline AddManifestPublishing(this IDistributedApplicationPipeline pipeline)
    {
        var step = new PipelineStep
        {
            Name = "publish-manifest",
            Action = async context =>
            {
                var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
                var logger = loggerFactory.CreateLogger("Aspire.Hosting.Publishing.ManifestPublisher");
                var pipelineOptions = context.Services.GetRequiredService<IOptions<PipelineOptions>>();
                var executionContext = context.Services.GetRequiredService<DistributedApplicationExecutionContext>();
 
                if (pipelineOptions.Value.OutputPath == null)
                {
                    throw new DistributedApplicationException(
                        "The '--output-path [path]' option was not specified even though manifest publishing was requested."
                        );
                }
 
                var outputPath = pipelineOptions.Value.OutputPath;
 
                if (!outputPath.EndsWith(".json"))
                {
                    // If the manifest path ends with .json we assume that the output path was specified
                    // as a filename. If not, we assume that the output path was specified as a directory
                    // and append aspire-manifest.json to the path. This is so that we retain backwards
                    // compatibility with AZD, but also support manifest publishing via the Aspire CLI
                    // where the output path is a directory (since not all publishers use a manifest).
                    outputPath = Path.Combine(outputPath, "aspire-manifest.json");
                }
 
                var parentDirectory = Directory.GetParent(outputPath);
                if (!Directory.Exists(parentDirectory!.FullName))
                {
                    // Create the directory if it does not exist
                    Directory.CreateDirectory(parentDirectory.FullName);
                }
 
                using var stream = new FileStream(outputPath, FileMode.Create);
                using var jsonWriter = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
 
                var manifestPath = outputPath;
                var publishingContext = new ManifestPublishingContext(executionContext, manifestPath, jsonWriter, context.CancellationToken);
 
                await publishingContext.WriteModel(context.Model, context.CancellationToken).ConfigureAwait(false);
 
                var fullyQualifiedPath = Path.GetFullPath(outputPath);
                logger.LogInformation("Published manifest to: {ManifestPath}", fullyQualifiedPath);
            }
        };
        pipeline.AddStep(step);
 
        return pipeline;
    }
}