File: Ats\ExecutionConfigurationExports.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
#pragma warning disable ASPIRECERTIFICATES001
 
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Security.Cryptography.X509Certificates;
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
 
namespace Aspire.Hosting.Ats;
 
/// <summary>
/// ATS exports for execution-configuration helpers that need DTO or callback shims.
/// </summary>
internal static class ExecutionConfigurationExports
{
    /// <summary>
    /// Creates an execution configuration builder for the specified resource.
    /// </summary>
    /// <param name="resource">The resource to build the execution configuration for.</param>
    /// <returns>The execution configuration builder.</returns>
    [AspireExport]
    public static IExecutionConfigurationBuilder CreateExecutionConfiguration(this IResource resource)
    {
        ArgumentNullException.ThrowIfNull(resource);
 
        return ExecutionConfigurationBuilder.Create(resource);
    }
 
    /// <summary>
    /// Builds the execution configuration for the specified builder.
    /// </summary>
    /// <param name="builder">The execution configuration builder.</param>
    /// <param name="executionContext">The execution context used while building the configuration.</param>
    /// <param name="resourceLogger">The logger used while resolving values.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <returns>The resolved execution configuration.</returns>
    [AspireExport("buildExecutionConfiguration", MethodName = "build")]
    public static Task<IExecutionConfigurationResult> Build(
        this IExecutionConfigurationBuilder builder,
        DistributedApplicationExecutionContext executionContext,
        ILogger? resourceLogger = null,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(executionContext);
 
        return builder.BuildAsync(executionContext, resourceLogger ?? NullLogger.Instance, cancellationToken);
    }
 
    /// <summary>
    /// Adds an HTTPS certificate configuration gatherer using certificate metadata instead of a raw X509 certificate.
    /// </summary>
    /// <param name="builder">The execution configuration builder.</param>
    /// <param name="configContextFactory">The factory that creates the HTTPS certificate configuration context.</param>
    /// <returns>The execution configuration builder.</returns>
    [AspireExport("withHttpsCertificateConfigExport", MethodName = "withHttpsCertificateConfig")]
    public static IExecutionConfigurationBuilder WithHttpsCertificateConfig(
        this IExecutionConfigurationBuilder builder,
        Func<HttpsCertificateInfo, HttpsCertificateExecutionConfigurationContext> configContextFactory)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(configContextFactory);
 
        return ExecutionConfigurationBuilderExtensions.WithHttpsCertificateConfig(
            builder,
            certificate => configContextFactory(HttpsCertificateInfo.FromCertificate(certificate)));
    }
 
    /// <summary>
    /// Gets certificate trust execution-configuration data when present.
    /// </summary>
    /// <param name="configuration">The execution configuration result.</param>
    /// <returns>The certificate trust data. When no additional data is present, an empty DTO is returned.</returns>
    [AspireExport]
    public static CertificateTrustExecutionConfigurationExportData GetCertificateTrustData(this IExecutionConfigurationResult configuration)
    {
        ArgumentNullException.ThrowIfNull(configuration);
 
        if (!configuration.TryGetAdditionalData<CertificateTrustExecutionConfigurationData>(out var additionalData))
        {
            return new CertificateTrustExecutionConfigurationExportData
            {
                Scope = CertificateTrustScope.None,
                CertificateSubjects = [],
                CustomBundlePaths = []
            };
        }
 
        return new CertificateTrustExecutionConfigurationExportData
        {
            Scope = additionalData.Scope,
            CertificateSubjects = [.. additionalData.Certificates.Cast<X509Certificate2>().Select(static certificate => certificate.Subject)],
            CustomBundlePaths = [.. additionalData.CustomBundlesFactories.Keys]
        };
    }
 
    /// <summary>
    /// Gets HTTPS certificate execution-configuration data when present.
    /// </summary>
    /// <param name="configuration">The execution configuration result.</param>
    /// <returns>The HTTPS certificate data. When no additional data is present, an empty DTO is returned.</returns>
    [AspireExport]
    public static HttpsCertificateExecutionConfigurationExportData GetHttpsCertificateData(this IExecutionConfigurationResult configuration)
    {
        ArgumentNullException.ThrowIfNull(configuration);
 
        if (!configuration.TryGetAdditionalData<HttpsCertificateExecutionConfigurationData>(out var additionalData))
        {
            return new HttpsCertificateExecutionConfigurationExportData
            {
                Subject = string.Empty,
                KeyPathExpression = string.Empty,
                PfxPathExpression = string.Empty,
                IsKeyPathReferenced = false,
                IsPfxPathReferenced = false,
                IsCertificateWithKeyPathReferenced = false,
            };
        }
 
        return new HttpsCertificateExecutionConfigurationExportData
        {
            Subject = additionalData.Certificate.Subject,
            Thumbprint = additionalData.Certificate.Thumbprint,
            KeyPathExpression = additionalData.KeyPathReference.ValueExpression,
            PfxPathExpression = additionalData.PfxPathReference.ValueExpression,
            IsKeyPathReferenced = additionalData.IsKeyPathReferenced,
            IsPfxPathReferenced = additionalData.IsPfxPathReferenced,
            IsCertificateWithKeyPathReferenced = additionalData.IsCertificateWithKeyPathReferenced,
            Password = additionalData.Password
        };
    }
 
}
 
/// <summary>
/// ATS-friendly certificate metadata supplied to HTTPS certificate configuration callbacks.
/// </summary>
[AspireDto]
internal sealed class HttpsCertificateInfo
{
    /// <summary>
    /// The certificate subject.
    /// </summary>
    public required string Subject { get; init; }
 
    /// <summary>
    /// The certificate issuer.
    /// </summary>
    public required string Issuer { get; init; }
 
    /// <summary>
    /// The certificate thumbprint.
    /// </summary>
    public string? Thumbprint { get; init; }
 
    internal static HttpsCertificateInfo FromCertificate(X509Certificate2 certificate)
    {
        return new HttpsCertificateInfo
        {
            Subject = certificate.Subject,
            Issuer = certificate.Issuer,
            Thumbprint = certificate.Thumbprint
        };
    }
}
 
/// <summary>
/// ATS-friendly certificate trust data returned from an execution-configuration result.
/// </summary>
[AspireDto]
internal sealed class CertificateTrustExecutionConfigurationExportData
{
    /// <summary>
    /// The certificate trust scope.
    /// </summary>
    public required CertificateTrustScope Scope { get; init; }
 
    /// <summary>
    /// The certificate subjects included in the trust configuration.
    /// </summary>
    public required string[] CertificateSubjects { get; init; }
 
    /// <summary>
    /// The relative custom bundle paths.
    /// </summary>
    public required string[] CustomBundlePaths { get; init; }
}
 
/// <summary>
/// ATS-friendly HTTPS certificate data returned from an execution-configuration result.
/// </summary>
[AspireDto]
internal sealed class HttpsCertificateExecutionConfigurationExportData
{
    /// <summary>
    /// The certificate subject.
    /// </summary>
    public required string Subject { get; init; }
 
    /// <summary>
    /// The certificate thumbprint.
    /// </summary>
    public string? Thumbprint { get; init; }
 
    /// <summary>
    /// The expression for the key path reference.
    /// </summary>
    public required string KeyPathExpression { get; init; }
 
    /// <summary>
    /// The expression for the PFX path reference.
    /// </summary>
    public required string PfxPathExpression { get; init; }
 
    /// <summary>
    /// Indicates whether the key path was referenced.
    /// </summary>
    public required bool IsKeyPathReferenced { get; init; }
 
    /// <summary>
    /// Indicates whether the key path was referenced.
    /// </summary>
    public required bool IsCertificateWithKeyPathReferenced { get; init; }
 
    /// <summary>
    /// Indicates whether the PFX path was referenced.
    /// </summary>
    public required bool IsPfxPathReferenced { get; init; }
 
    /// <summary>
    /// The certificate password, if any.
    /// </summary>
    public string? Password { get; init; }
}