| File: Publishing\ContainerRuntimeResolver.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 ASPIRECONTAINERRUNTIME001 using Aspire.Hosting.Dcp; using Aspire.Shared; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Aspire.Hosting.Publishing; /// <summary> /// Resolves the container runtime asynchronously using explicit configuration or auto-detection. /// Caches the result after first resolution. /// </summary> internal sealed class ContainerRuntimeResolver : IContainerRuntimeResolver { private readonly IServiceProvider _serviceProvider; private readonly IOptions<DcpOptions> _dcpOptions; private readonly ILogger _logger; private readonly object _lock = new(); private Task<IContainerRuntime>? _cachedTask; public ContainerRuntimeResolver( IServiceProvider serviceProvider, IOptions<DcpOptions> dcpOptions, ILoggerFactory loggerFactory) { _serviceProvider = serviceProvider; _dcpOptions = dcpOptions; _logger = loggerFactory.CreateLogger("Aspire.Hosting.ContainerRuntime"); } public Task<IContainerRuntime> ResolveAsync(CancellationToken cancellationToken = default) { // Caching behavior: // - Completed successfully: return cached result. Caller's token is irrelevant. // - In-progress: return the in-flight task (started with a previous caller's token). // If this caller is cancelled, their await throws but the detection continues. // - Faulted (e.g. bad ASPIRE_CONTAINER_RUNTIME value): return faulted task. // Config won't change mid-process, so retry won't help. // - Cancelled: discard and retry with the new caller's token, since a different // caller may have a valid token. // - Null: first call, start detection with this caller's token. var task = _cachedTask; if (task is not null && !task.IsCanceled) { return task; } lock (_lock) { task = _cachedTask; if (task is not null && !task.IsCanceled) { return task; } _cachedTask = ResolveInternalAsync(cancellationToken); return _cachedTask; } } private async Task<IContainerRuntime> ResolveInternalAsync(CancellationToken cancellationToken) { var configuredRuntime = _dcpOptions.Value.ContainerRuntime; if (configuredRuntime is not null) { _logger.LogInformation("Container runtime '{RuntimeKey}' configured via ASPIRE_CONTAINER_RUNTIME.", configuredRuntime); return _serviceProvider.GetRequiredKeyedService<IContainerRuntime>(configuredRuntime); } // Auto-detect: probe available runtimes asynchronously. // See https://github.com/microsoft/dcp/blob/main/internal/containers/runtimes/runtime.go var detected = await ContainerRuntimeDetector.FindAvailableRuntimeAsync(logger: _logger, cancellationToken: cancellationToken).ConfigureAwait(false); var runtimeKey = detected?.Executable ?? KnownContainerRuntimes.Docker; if (detected is { IsHealthy: true }) { _logger.LogInformation("Container runtime auto-detected: {RuntimeName} ({Executable}).", detected.Name, detected.Executable); } else if (detected is { IsInstalled: true }) { _logger.LogWarning("Container runtime '{RuntimeName}' is installed but not running. {Error}", detected.Name, detected.Error); } else { _logger.LogWarning("No container runtime detected, defaulting to '{DefaultRuntime}'. Install Docker or Podman to use container features.", KnownContainerRuntimes.Docker); } return _serviceProvider.GetRequiredKeyedService<IContainerRuntime>(runtimeKey); } }