| File: LocalDaemons\ContainerRuntime.cs | Web Access |
| Project: src\sdk\src\Containers\Microsoft.NET.Build.Containers\Microsoft.NET.Build.Containers.csproj (Microsoft.NET.Build.Containers) |
// 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; using Microsoft.NET.Build.Containers.Resources; namespace Microsoft.NET.Build.Containers; /// <summary> /// Selects a local container runtime and delegates local image-store operations to it. /// </summary> internal sealed class ContainerRuntime : ILocalRegistry { public const string DockerCommand = "docker"; public const string PodmanCommand = "podman"; public const string WslcCommand = "wslc"; public const string MacOSContainerCommand = "container"; private const string FallbackCommands = $"{DockerCommand}/{PodmanCommand}"; private static readonly TimeSpan s_defaultProbeTimeout = TimeSpan.FromSeconds(5); private readonly ILogger _logger; private readonly ContainerRuntimeOperations _operations; private readonly Func<bool> _isPodmanAlias; private readonly bool _isWindows; private readonly bool _isMacOS; private readonly TimeSpan _probeTimeout; private IContainerRuntime? _runtime; public ContainerRuntime(string? command, ILoggerFactory loggerFactory) : this( command, loggerFactory, ContainerRuntimeOperations.TryRunCommandAsync, DockerContainerRuntime.IsPodmanAlias, RuntimeInformation.IsOSPlatform(OSPlatform.Windows), RuntimeInformation.IsOSPlatform(OSPlatform.OSX), s_defaultProbeTimeout) { } internal ContainerRuntime(ILoggerFactory loggerFactory, bool probePlatformNativeCli) : this( command: null, loggerFactory, ContainerRuntimeOperations.TryRunCommandAsync, DockerContainerRuntime.IsPodmanAlias, probePlatformNativeCli && RuntimeInformation.IsOSPlatform(OSPlatform.Windows), probePlatformNativeCli && RuntimeInformation.IsOSPlatform(OSPlatform.OSX), s_defaultProbeTimeout) { } internal ContainerRuntime( string? command, ILoggerFactory loggerFactory, Func<string, string, CancellationToken, Task<bool>> tryRunCommand, Func<bool> isPodmanAlias, bool isWindows, bool isMacOS, TimeSpan? probeTimeout = null) { _logger = loggerFactory.CreateLogger<ContainerRuntime>(); _operations = new ContainerRuntimeOperations(_logger, tryRunCommand); _isPodmanAlias = isPodmanAlias; _isWindows = isWindows; _isMacOS = isMacOS; _probeTimeout = probeTimeout ?? s_defaultProbeTimeout; _runtime = command is null ? null : CreateRuntime(command); } public ContainerRuntime(ILoggerFactory loggerFactory) : this(null, loggerFactory) { } /// <inheritdoc /> public async Task LoadAsync( BuiltImage image, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) { IContainerRuntime runtime = await GetRuntimeAsync(cancellationToken).ConfigureAwait(false) ?? throw CreateRuntimeNotFoundException(); await runtime.LoadAsync(image, sourceReference, destinationReference, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> public async Task LoadAsync( MultiArchImage multiArchImage, SourceImageReference sourceReference, DestinationImageReference destinationReference, CancellationToken cancellationToken) { IContainerRuntime runtime = await GetRuntimeAsync(cancellationToken).ConfigureAwait(false) ?? throw CreateRuntimeNotFoundException(); await runtime.LoadAsync(multiArchImage, sourceReference, destinationReference, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> public async Task<bool> IsAvailableAsync(CancellationToken cancellationToken) { IContainerRuntime? runtime = await GetRuntimeAsync(cancellationToken); if (runtime is null) { _logger.LogError($"Cannot find {GetCommandsForCurrentPlatform()} executable."); return false; } try { return await runtime.IsAvailableAsync(cancellationToken).ConfigureAwait(false); } catch (Exception ex) { _logger.LogInformation(Strings.LocalDocker_FailedToGetConfig, ex.Message); _logger.LogTrace("Full information: {0}", ex); return false; } } /// <inheritdoc /> public bool IsAvailable() => IsAvailableAsync(default).GetAwaiter().GetResult(); internal ContainerRuntimeKind GetTelemetryValue() => GetRuntimeAsync(default).GetAwaiter().GetResult()?.GetTelemetryValue() ?? ContainerRuntimeKind.Unknown; internal string GetManifestMediaType(string defaultManifestMediaType, KnownImageFormats? imageFormat) => GetRuntimeAsync(default).GetAwaiter().GetResult()?.GetManifestMediaType(defaultManifestMediaType, imageFormat) ?? ContainerRuntimeBase.GetDefaultManifestMediaType(defaultManifestMediaType, imageFormat); public override string ToString() => string.Format(Strings.DockerCli_PushInfo, _runtime?.GetTelemetryValue().ToString()); private IContainerRuntime CreateRuntime(string command) => command switch { DockerCommand => new DockerContainerRuntime(_operations, _logger), PodmanCommand => new PodmanContainerRuntime(_operations, _logger), WslcCommand => new WslcContainerRuntime(_operations, _logger), MacOSContainerCommand => new MacOSContainerRuntime(_operations, _logger), _ => throw new ArgumentException($"{command} is an unknown command.") }; private async ValueTask<IContainerRuntime?> GetRuntimeAsync(CancellationToken cancellationToken) { if (_runtime is not null) { return _runtime; } IContainerRuntime? platformNativeRuntime = _isWindows ? CreateRuntime(WslcCommand) : _isMacOS ? CreateRuntime(MacOSContainerCommand) : null; if (platformNativeRuntime is not null && await ProbeAsync(platformNativeRuntime, cancellationToken).ConfigureAwait(false)) { return _runtime = platformNativeRuntime; } IContainerRuntime podmanRuntime = CreateRuntime(PodmanCommand); IContainerRuntime dockerRuntime = CreateRuntime(DockerCommand); Task<bool> podmanAvailable = ProbeAsync(podmanRuntime, cancellationToken); Task<bool> dockerAvailable = ProbeAsync(dockerRuntime, cancellationToken); await Task.WhenAll(podmanAvailable, dockerAvailable).ConfigureAwait(false); if (dockerAvailable.Result && podmanAvailable.Result && _isPodmanAlias()) { _runtime = podmanRuntime; } else if (dockerAvailable.Result) { _runtime = dockerRuntime; } else if (podmanAvailable.Result) { _runtime = podmanRuntime; } return _runtime; } private async Task<bool> ProbeAsync(IContainerRuntime runtime, CancellationToken cancellationToken) { using CancellationTokenSource timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeout.CancelAfter(_probeTimeout); try { return await runtime.ProbeAsync(timeout.Token).ConfigureAwait(false); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { return false; } } private string GetCommandsForCurrentPlatform() => _isWindows ? $"{WslcCommand}/{FallbackCommands}" : _isMacOS ? $"{MacOSContainerCommand}/{FallbackCommands}" : FallbackCommands; private NotImplementedException CreateRuntimeNotFoundException() => new(Resource.FormatString(Strings.ContainerRuntimeProcessCreationFailed, GetCommandsForCurrentPlatform())); }