File: Utils\EnvironmentChecker\DcpConnectionHealthCheck.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using Aspire.Cli.Bundles;
using Aspire.Cli.Layout;
using Aspire.Cli.Resources;
using Aspire.Shared;
using Microsoft.Extensions.Logging;
 
namespace Aspire.Cli.Utils.EnvironmentChecker;
 
/// <summary>
/// Checks whether the bundled Developer Control Plane can be reached with its generated kubeconfig.
/// </summary>
internal sealed class DcpConnectionHealthCheck(
    ILayoutDiscovery layoutDiscovery,
    IBundleService bundleService,
    IDcpConnectionChecker connectionTester,
    CliExecutionContext executionContext,
    ILogger<DcpConnectionHealthCheck> logger) : IEnvironmentCheck
{
    internal const string BundleCheckName = "dcp-bundle";
    internal const string ConnectionCheckName = "dcp-connection";
    internal const string EphemeralCertificateCheckName = "dcp-ephemeral-certificate";
    internal const string DeveloperCertificateCheckName = "dcp-developer-certificate";
 
    public int Order => 45; // DCP process checks are more expensive than local prerequisite probes.
 
    public async Task<IReadOnlyList<EnvironmentCheckResult>> CheckAsync(CancellationToken cancellationToken = default)
    {
        BundleLayoutLease? layoutLease = null;
        try
        {
            // An existing layout wins, which keeps an ASPIRE_DCP_PATH override and an already
            // extracted bundle working exactly as before.
            var dcpDirectory = layoutDiscovery.GetComponentPath(LayoutComponent.Dcp, executionContext.WorkingDirectory.FullName);
            if (string.IsNullOrWhiteSpace(dcpDirectory))
            {
                // Layout discovery only finds a bundle that is already on disk. It used to be extracted
                // by the time doctor ran because bundled NuGet search, which the background CLI update
                // check runs on startup, extracted it before launching aspire-managed. NuGet now runs
                // in-process and no longer does, so on a fresh install extract it here. The lease is
                // held for the whole check because the connection probes below launch DCP from it.
                layoutLease = await bundleService.EnsureExtractedAndAcquireLayoutAsync("cli", "doctor", cancellationToken).ConfigureAwait(false);
                dcpDirectory = layoutLease?.Layout.GetDcpPath();
            }
 
            if (string.IsNullOrWhiteSpace(dcpDirectory))
            {
                logger.LogDebug("Skipping DCP connection health checks because no Aspire bundle layout was discovered.");
                return [new EnvironmentCheckResult
                {
                    Category = EnvironmentCheckCategories.Aspire,
                    Name = BundleCheckName,
                    Status = EnvironmentCheckStatus.Warning,
                    Message = DoctorCommandStrings.DcpBundleNotFoundMessage,
                    Details = DoctorCommandStrings.DcpBundleNotFoundDetails
                }];
            }
 
            var dcpExecutablePath = BundleDiscovery.GetDcpExecutablePath(dcpDirectory);
            if (!File.Exists(dcpExecutablePath))
            {
                return [new EnvironmentCheckResult
                {
                    Category = EnvironmentCheckCategories.Aspire,
                    Name = BundleCheckName,
                    Status = EnvironmentCheckStatus.Fail,
                    Message = DoctorCommandStrings.DcpExecutableNotFoundMessage,
                    Details = string.Format(CultureInfo.CurrentCulture, DoctorCommandStrings.DcpExecutableNotFoundDetailsFormat, dcpExecutablePath)
                }];
            }
 
            var ephemeralCertificateTask = connectionTester.TestConnectionAsync(
                dcpDirectory,
                useDeveloperCertificate: false,
                cancellationToken);
 
            var developerCertificateTask = connectionTester.TestConnectionAsync(
                dcpDirectory,
                useDeveloperCertificate: true,
                cancellationToken);
 
            var results = await Task.WhenAll(ephemeralCertificateTask, developerCertificateTask).ConfigureAwait(false);
            if (results.All(result => result.Status == EnvironmentCheckStatus.Pass))
            {
                return [new EnvironmentCheckResult
                {
                    Category = EnvironmentCheckCategories.Aspire,
                    Name = ConnectionCheckName,
                    Status = EnvironmentCheckStatus.Pass,
                    Message = DoctorCommandStrings.DcpConnectionSucceededMessage
                }];
            }
 
            return results;
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            throw;
        }
        catch (Exception ex)
        {
            logger.LogDebug(ex, "Error checking DCP connection health.");
            return [new EnvironmentCheckResult
            {
                Category = EnvironmentCheckCategories.Aspire,
                Name = ConnectionCheckName,
                Status = EnvironmentCheckStatus.Fail,
                Message = DoctorCommandStrings.DcpConnectionCheckFailedMessage,
                Details = ex.Message
            }];
        }
        finally
        {
            layoutLease?.Dispose();
        }
    }
 
}