File: Pipelines\DefaultPipelineOutputSnapshotter.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.
 
using Aspire.Hosting.Dcp.Process;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Options;
 
#pragma warning disable ASPIREPIPELINES005 // Internal consumer of the experimental output diffing API.
 
namespace Aspire.Hosting.Pipelines;
 
/// <summary>
/// Captures pipeline output with the configured output selection and Git policy.
/// </summary>
internal sealed class DefaultPipelineOutputSnapshotter : IPipelineOutputSnapshotter<PipelineOutputSnapshot>
{
    private readonly PipelineOutputSnapshotter _snapshotter;
    private readonly IProcessRunner _processRunner;
    private readonly PipelineOutputDiffMode _mode;
    private readonly Matcher _matcher = new(PipelineOutputRoot.PathComparison, preserveFilterOrder: true);
 
    /// <summary>
    /// Creates a snapshotter with the configured output selection and Git policy.
    /// </summary>
    public DefaultPipelineOutputSnapshotter(PipelineOutputSnapshotter snapshotter, IProcessRunner processRunner, IOptions<PipelineOutputDiffOptions> options)
    {
        _snapshotter = snapshotter;
        _processRunner = processRunner;
        var policy = options.Value;
        _mode = policy.Mode;
        if (policy.Rules.Count == 0)
        {
            _matcher.AddInclude("**/*");
        }
        foreach (var rule in policy.Rules)
        {
            if (string.Equals(rule.Action, "Include", StringComparison.OrdinalIgnoreCase))
            {
                _matcher.AddInclude(rule.Pattern);
            }
            else
            {
                _matcher.AddExclude(rule.Pattern);
            }
        }
    }
 
    /// <inheritdoc />
    public async Task<PipelineOutputSnapshot> CaptureAsync(string outputDirectory, CancellationToken ct)
    {
        var directory = PipelineOutputRoot.NormalizeDirectoryPath(outputDirectory);
        if (!PipelineOutputSnapshotter.ValidateOutputDirectory(directory, ct))
        {
            // Missing roots are empty, not an invitation to search parents.
            return new(directory, new Dictionary<string, PipelineOutputFileSnapshot>(PipelineOutputRoot.PathComparer));
        }
        var git = _mode is PipelineOutputDiffMode.Auto or PipelineOutputDiffMode.Git
            ? await GitPipelineOutputReader.TryCreateAsync(directory, _processRunner, ct).ConfigureAwait(false)
            : null;
        if (git is null && _mode is PipelineOutputDiffMode.Git)
        {
            throw new DistributedApplicationException($"Git is required to capture pipeline output, but the Git executable or a usable worktree is unavailable: {directory}");
        }
        return new(directory, await _snapshotter.CaptureAsync(directory, _matcher, git, ct).ConfigureAwait(false));
    }
}
 
#pragma warning restore ASPIREPIPELINES005