File: src\Shared\ContainerRuntimeDetector.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 Microsoft.Extensions.Logging;
 
namespace Aspire.Shared;
 
/// <summary>
/// Discovers container runtimes using Aspire probes and DCP's runtime priority order.
/// </summary>
internal static class ContainerRuntimeDetector
{
    // Availability takes precedence, followed by the same runtime order used by DCP.
    // https://github.com/microsoft/dcp/blob/main/internal/containers/runtimes/runtime.go
    private static readonly ContainerRuntimeProbe[] s_knownRuntimes =
    [
        new(KnownContainerRuntimes.Docker, "docker", "Docker", 0),
        new(KnownContainerRuntimes.Podman, "podman", "Podman", 1),
        new WslcContainerRuntimeProbe()
    ];
 
    /// <summary>
    /// Gets the probes supported on the host operating system, in discovery order.
    /// </summary>
    internal static IEnumerable<ContainerRuntimeProbe> GetKnownRuntimes(bool isWindows)
    {
        return s_knownRuntimes.Where(runtime => runtime.IsSupportedHost(isWindows));
    }
 
    /// <summary>
    /// Finds the best available runtime, or checks only an explicitly configured runtime.
    /// </summary>
    public static async Task<ContainerRuntimeInfo?> FindAvailableRuntimeAsync(
        string? configuredRuntime = null,
        ILogger? logger = null,
        CancellationToken cancellationToken = default,
        IReadOnlyCollection<string>? supportedRuntimes = null)
    {
        cancellationToken.ThrowIfCancellationRequested();
        if (!string.IsNullOrEmpty(configuredRuntime))
        {
            logger?.LogDebug("Checking explicitly configured runtime: {Runtime}", configuredRuntime);
            var runtime = GetProbe(configuredRuntime);
            return await runtime.CheckAsync(logger, cancellationToken).ConfigureAwait(false);
        }
 
        var runtimes = GetKnownRuntimes(OperatingSystem.IsWindows())
            .Where(runtime => supportedRuntimes is null || supportedRuntimes.Contains(runtime.Key, StringComparers.ContainerRuntime))
            .ToArray();
        logger?.LogDebug("Auto-detecting container runtime, probing {Count} known runtimes...", runtimes.Length);
        var results = await Task.WhenAll(runtimes.Select(runtime => runtime.CheckAsync(logger, cancellationToken))).ConfigureAwait(false);
 
        return FindBestRuntime(results);
    }
 
    /// <summary>
    /// Checks a runtime using its registered probe.
    /// </summary>
    public static Task<ContainerRuntimeInfo> CheckRuntimeAsync(
        string runtimeKey,
        ILogger? logger = null,
        CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        var runtime = GetProbe(runtimeKey);
        return runtime.CheckAsync(logger, cancellationToken);
    }
 
    /// <summary>
    /// Selects by availability, then runtime priority, independently of result order.
    /// </summary>
    public static ContainerRuntimeInfo? FindBestRuntime(IEnumerable<ContainerRuntimeInfo> results)
    {
        ContainerRuntimeInfo? best = null;
        foreach (var candidate in results)
        {
            if (best is null)
            {
                best = candidate;
                continue;
            }
 
            if (!best.IsInstalled && candidate.IsInstalled)
            {
                best = candidate;
            }
            else if (!best.IsRunning && candidate.IsRunning)
            {
                best = candidate;
            }
            else if (candidate.Priority < best.Priority
                && candidate.IsInstalled == best.IsInstalled
                && candidate.IsRunning == best.IsRunning)
            {
                best = candidate;
            }
        }
 
        return best;
    }
 
    internal static ContainerRuntimeProbe GetProbe(string runtimeKey)
    {
        return s_knownRuntimes.FirstOrDefault(runtime =>
            string.Equals(runtime.Key, runtimeKey, StringComparisons.ContainerRuntime))
            ?? throw new ArgumentException(
                $"Unsupported container runtime '{runtimeKey}'. Supported runtimes: {string.Join(", ", s_knownRuntimes.Select(runtime => runtime.Key))}.",
                nameof(runtimeKey));
    }
}