sln build fix (again), tests fixes, audit work and doctors work

This commit is contained in:
master
2026-01-12 22:15:51 +02:00
parent 9873f80830
commit 9330c64349
812 changed files with 48051 additions and 3891 deletions

View File

@@ -0,0 +1,136 @@
using System.Globalization;
using Docker.DotNet;
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Docker.Checks;
/// <summary>
/// Validates Docker API version compatibility.
/// </summary>
public sealed class DockerApiVersionCheck : IDoctorCheck
{
private static readonly Version MinimumApiVersion = new(1, 41);
private static readonly Version RecommendedApiVersion = new(1, 43);
/// <inheritdoc />
public string CheckId => "check.docker.apiversion";
/// <inheritdoc />
public string Name => "Docker API Version";
/// <inheritdoc />
public string Description => "Validates Docker API version meets minimum requirements";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["docker", "api", "compatibility"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(3);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context) => true;
/// <inheritdoc />
public async Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.docker", DoctorCategory.Infrastructure.ToString());
var dockerHost = context.Configuration.GetValue<string>("Docker:Host")
?? GetDefaultDockerHost();
try
{
using var dockerClient = CreateDockerClient(dockerHost);
var version = await dockerClient.System.GetVersionAsync(ct);
if (!Version.TryParse(version.APIVersion, out var apiVersion))
{
return result
.Warn($"Cannot parse API version: {version.APIVersion}")
.WithEvidence("Docker API", e =>
{
e.Add("ReportedVersion", version.APIVersion);
e.Add("DockerVersion", version.Version);
})
.Build();
}
var issues = new List<string>();
if (apiVersion < MinimumApiVersion)
{
issues.Add($"API version {apiVersion} is below minimum required {MinimumApiVersion}");
}
else if (apiVersion < RecommendedApiVersion)
{
issues.Add($"API version {apiVersion} is below recommended {RecommendedApiVersion}");
}
if (issues.Count > 0)
{
return result
.Warn($"{issues.Count} API version issue(s)")
.WithEvidence("Docker API", e =>
{
e.Add("ApiVersion", apiVersion.ToString());
e.Add("MinimumRequired", MinimumApiVersion.ToString());
e.Add("Recommended", RecommendedApiVersion.ToString());
e.Add("DockerVersion", version.Version);
e.Add("Os", version.Os);
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Update Docker", "Install the latest Docker version for your OS")
.AddManualStep(2, "Verify version", "Run: docker version"))
.WithVerification("stella doctor --check check.docker.apiversion")
.Build();
}
return result
.Pass($"Docker API version {apiVersion} meets requirements")
.WithEvidence("Docker API", e =>
{
e.Add("ApiVersion", apiVersion.ToString());
e.Add("MinimumRequired", MinimumApiVersion.ToString());
e.Add("Recommended", RecommendedApiVersion.ToString());
e.Add("DockerVersion", version.Version);
e.Add("BuildTime", version.BuildTime ?? "(not available)");
e.Add("GitCommit", version.GitCommit ?? "(not available)");
})
.Build();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
return result
.Skip($"Cannot check API version: {ex.Message}")
.WithEvidence("Docker API", e =>
{
e.Add("Host", dockerHost);
e.Add("Error", ex.GetType().Name);
})
.Build();
}
}
private static string GetDefaultDockerHost()
{
if (OperatingSystem.IsWindows())
{
return "npipe://./pipe/docker_engine";
}
return "unix:///var/run/docker.sock";
}
private static DockerClient CreateDockerClient(string host)
{
var config = new DockerClientConfiguration(new Uri(host));
return config.CreateClient();
}
}

View File

@@ -0,0 +1,131 @@
using Docker.DotNet;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Docker.Checks;
/// <summary>
/// Validates Docker daemon availability and responsiveness.
/// </summary>
public sealed class DockerDaemonCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.docker.daemon";
/// <inheritdoc />
public string Name => "Docker Daemon";
/// <inheritdoc />
public string Description => "Validates Docker daemon is running and responsive";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Fail;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["docker", "daemon", "container"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context) => true;
/// <inheritdoc />
public async Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.docker", DoctorCategory.Infrastructure.ToString());
var dockerHost = context.Configuration.GetValue<string>("Docker:Host")
?? GetDefaultDockerHost();
var timeout = context.Configuration.GetValue<int?>("Docker:TimeoutSeconds") ?? 10;
try
{
using var dockerClient = CreateDockerClient(dockerHost);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
await dockerClient.System.PingAsync(cts.Token);
var version = await dockerClient.System.GetVersionAsync(cts.Token);
return result
.Pass("Docker daemon is running and responsive")
.WithEvidence("Docker daemon", e =>
{
e.Add("Host", dockerHost);
e.Add("Version", version.Version);
e.Add("ApiVersion", version.APIVersion);
e.Add("Os", version.Os);
e.Add("Arch", version.Arch);
e.Add("KernelVersion", version.KernelVersion ?? "(not available)");
})
.Build();
}
catch (DockerApiException ex)
{
return result
.Fail($"Docker API error: {ex.Message}")
.WithEvidence("Docker daemon", e =>
{
e.Add("Host", dockerHost);
e.Add("StatusCode", ex.StatusCode.ToString());
e.Add("ResponseBody", TruncateMessage(ex.ResponseBody ?? "(no body)"));
})
.WithCauses("Docker daemon returned an error response")
.WithRemediation(r => r
.AddManualStep(1, "Check daemon status", "Run: docker info")
.AddManualStep(2, "Restart daemon", "Run: sudo systemctl restart docker"))
.WithVerification("stella doctor --check check.docker.daemon")
.Build();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
return result
.Fail($"Cannot connect to Docker daemon: {ex.Message}")
.WithEvidence("Docker daemon", e =>
{
e.Add("Host", dockerHost);
e.Add("Error", ex.GetType().Name);
e.Add("Message", TruncateMessage(ex.Message));
})
.WithCauses("Docker daemon is not running or not accessible")
.WithRemediation(r => r
.AddManualStep(1, "Install Docker", "Follow Docker installation guide for your OS")
.AddManualStep(2, "Start daemon", "Run: sudo systemctl start docker")
.AddManualStep(3, "Verify installation", "Run: docker version"))
.WithVerification("stella doctor --check check.docker.daemon")
.Build();
}
}
private static string GetDefaultDockerHost()
{
if (OperatingSystem.IsWindows())
{
return "npipe://./pipe/docker_engine";
}
return "unix:///var/run/docker.sock";
}
private static DockerClient CreateDockerClient(string host)
{
var config = new DockerClientConfiguration(new Uri(host));
return config.CreateClient();
}
private static string TruncateMessage(string message, int maxLength = 200)
{
if (message.Length <= maxLength)
{
return message;
}
return message[..maxLength] + "...";
}
}

View File

@@ -0,0 +1,143 @@
using Docker.DotNet;
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Docker.Checks;
/// <summary>
/// Validates Docker network configuration.
/// </summary>
public sealed class DockerNetworkCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.docker.network";
/// <inheritdoc />
public string Name => "Docker Network";
/// <inheritdoc />
public string Description => "Validates Docker network configuration and connectivity";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["docker", "network", "connectivity"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context) => true;
/// <inheritdoc />
public async Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.docker", DoctorCategory.Infrastructure.ToString());
var dockerHost = context.Configuration.GetValue<string>("Docker:Host")
?? GetDefaultDockerHost();
var requiredNetworks = context.Configuration.GetSection("Docker:RequiredNetworks").Get<string[]>()
?? ["bridge"];
try
{
using var dockerClient = CreateDockerClient(dockerHost);
var networks = await dockerClient.Networks.ListNetworksAsync(cancellationToken: ct);
var issues = new List<string>();
var foundNetworks = new List<string>();
var missingNetworks = new List<string>();
foreach (var requiredNetwork in requiredNetworks)
{
var found = networks.Any(n =>
n.Name.Equals(requiredNetwork, StringComparison.OrdinalIgnoreCase));
if (found)
{
foundNetworks.Add(requiredNetwork);
}
else
{
missingNetworks.Add(requiredNetwork);
issues.Add($"Required network '{requiredNetwork}' not found");
}
}
var bridgeNetwork = networks.FirstOrDefault(n =>
n.Driver?.Equals("bridge", StringComparison.OrdinalIgnoreCase) == true);
if (bridgeNetwork == null)
{
issues.Add("No bridge network driver available");
}
var totalNetworks = networks.Count;
var networkDrivers = networks
.Select(n => n.Driver ?? "unknown")
.Distinct()
.ToList();
if (issues.Count > 0)
{
return result
.Warn($"{issues.Count} Docker network issue(s)")
.WithEvidence("Docker networks", e =>
{
e.Add("TotalNetworks", totalNetworks.ToString());
e.Add("AvailableDrivers", string.Join(", ", networkDrivers));
e.Add("FoundRequired", string.Join(", ", foundNetworks));
e.Add("MissingRequired", string.Join(", ", missingNetworks));
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "List networks", "Run: docker network ls")
.AddManualStep(2, "Create network", "Run: docker network create <network-name>"))
.WithVerification("stella doctor --check check.docker.network")
.Build();
}
return result
.Pass($"Docker networks configured ({totalNetworks} available)")
.WithEvidence("Docker networks", e =>
{
e.Add("TotalNetworks", totalNetworks.ToString());
e.Add("AvailableDrivers", string.Join(", ", networkDrivers));
e.Add("RequiredNetworks", string.Join(", ", requiredNetworks));
e.Add("BridgeNetwork", bridgeNetwork?.Name ?? "(none)");
})
.Build();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
return result
.Skip($"Cannot check Docker networks: {ex.Message}")
.WithEvidence("Docker networks", e =>
{
e.Add("Host", dockerHost);
e.Add("Error", ex.GetType().Name);
})
.Build();
}
}
private static string GetDefaultDockerHost()
{
if (OperatingSystem.IsWindows())
{
return "npipe://./pipe/docker_engine";
}
return "unix:///var/run/docker.sock";
}
private static DockerClient CreateDockerClient(string host)
{
var config = new DockerClientConfiguration(new Uri(host));
return config.CreateClient();
}
}

View File

@@ -0,0 +1,161 @@
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
using StellaOps.Doctor.Plugins.Builders;
namespace StellaOps.Doctor.Plugins.Docker.Checks;
/// <summary>
/// Validates Docker socket accessibility and permissions.
/// </summary>
public sealed class DockerSocketCheck : IDoctorCheck
{
/// <inheritdoc />
public string CheckId => "check.docker.socket";
/// <inheritdoc />
public string Name => "Docker Socket";
/// <inheritdoc />
public string Description => "Validates Docker socket exists and is accessible";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Fail;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["docker", "socket", "permissions"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromMilliseconds(100);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context) => true;
/// <inheritdoc />
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.docker", DoctorCategory.Infrastructure.ToString());
var dockerHost = context.Configuration.GetValue<string>("Docker:Host")
?? GetDefaultDockerHost();
var issues = new List<string>();
if (OperatingSystem.IsWindows())
{
return Task.FromResult(CheckWindowsNamedPipe(result, dockerHost, issues));
}
return Task.FromResult(CheckUnixSocket(result, dockerHost, issues));
}
private static DoctorCheckResult CheckUnixSocket(
CheckResultBuilder result,
string dockerHost,
List<string> issues)
{
var socketPath = dockerHost.StartsWith("unix://", StringComparison.OrdinalIgnoreCase)
? dockerHost["unix://".Length..]
: "/var/run/docker.sock";
var socketExists = File.Exists(socketPath);
var socketReadable = false;
var socketWritable = false;
if (socketExists)
{
try
{
using var stream = new FileStream(socketPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
socketReadable = true;
}
catch
{
// Cannot read socket
}
try
{
using var stream = new FileStream(socketPath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
socketWritable = true;
}
catch
{
// Cannot write to socket
}
}
if (!socketExists)
{
issues.Add($"Docker socket not found at {socketPath}");
}
else if (!socketReadable || !socketWritable)
{
issues.Add($"Insufficient permissions on {socketPath}");
}
if (issues.Count > 0)
{
return result
.Fail($"{issues.Count} Docker socket issue(s)")
.WithEvidence("Docker socket", e =>
{
e.Add("Path", socketPath);
e.Add("Exists", socketExists.ToString());
e.Add("Readable", socketReadable.ToString());
e.Add("Writable", socketWritable.ToString());
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Check Docker installation", "Ensure Docker is installed and running")
.AddManualStep(2, "Add user to docker group", "Run: sudo usermod -aG docker $USER")
.AddManualStep(3, "Re-login", "Log out and back in for group changes to take effect"))
.WithVerification("stella doctor --check check.docker.socket")
.Build();
}
return result
.Pass("Docker socket is accessible")
.WithEvidence("Docker socket", e =>
{
e.Add("Path", socketPath);
e.Add("Exists", socketExists.ToString());
e.Add("Readable", socketReadable.ToString());
e.Add("Writable", socketWritable.ToString());
})
.Build();
}
private static DoctorCheckResult CheckWindowsNamedPipe(
CheckResultBuilder result,
string dockerHost,
List<string> issues)
{
var pipePath = dockerHost.StartsWith("npipe://", StringComparison.OrdinalIgnoreCase)
? dockerHost
: "npipe://./pipe/docker_engine";
// On Windows, we primarily check via Docker daemon connectivity
// Named pipe access is handled by the daemon check
return result
.Pass("Docker named pipe configured")
.WithEvidence("Docker socket", e =>
{
e.Add("Type", "Named Pipe");
e.Add("Path", pipePath);
e.Add("Platform", "Windows");
e.Add("Note", "Connectivity verified via daemon check");
})
.Build();
}
private static string GetDefaultDockerHost()
{
if (OperatingSystem.IsWindows())
{
return "npipe://./pipe/docker_engine";
}
return "unix:///var/run/docker.sock";
}
}

View File

@@ -0,0 +1,192 @@
using System.Globalization;
using Docker.DotNet;
using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Docker.Checks;
/// <summary>
/// Validates Docker storage and disk space usage.
/// </summary>
public sealed class DockerStorageCheck : IDoctorCheck
{
private const double DefaultMaxUsagePercent = 85.0;
/// <inheritdoc />
public string CheckId => "check.docker.storage";
/// <inheritdoc />
public string Name => "Docker Storage";
/// <inheritdoc />
public string Description => "Validates Docker storage driver and disk space usage";
/// <inheritdoc />
public DoctorSeverity DefaultSeverity => DoctorSeverity.Warn;
/// <inheritdoc />
public IReadOnlyList<string> Tags => ["docker", "storage", "disk"];
/// <inheritdoc />
public TimeSpan EstimatedDuration => TimeSpan.FromSeconds(5);
/// <inheritdoc />
public bool CanRun(DoctorPluginContext context) => true;
/// <inheritdoc />
public async Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.docker", DoctorCategory.Infrastructure.ToString());
var dockerHost = context.Configuration.GetValue<string>("Docker:Host")
?? GetDefaultDockerHost();
var minFreeSpaceGb = context.Configuration.GetValue<double?>("Docker:MinFreeSpaceGb") ?? 10.0;
var maxUsagePercent = context.Configuration.GetValue<double?>("Docker:MaxStorageUsagePercent")
?? DefaultMaxUsagePercent;
try
{
using var dockerClient = CreateDockerClient(dockerHost);
var systemInfo = await dockerClient.System.GetSystemInfoAsync(ct);
var issues = new List<string>();
var storageDriver = systemInfo.Driver ?? "unknown";
var dockerRoot = systemInfo.DockerRootDir ?? "/var/lib/docker";
// Check storage driver
var recommendedDrivers = new[] { "overlay2", "btrfs", "zfs" };
var isRecommendedDriver = recommendedDrivers.Any(d =>
storageDriver.Equals(d, StringComparison.OrdinalIgnoreCase));
if (!isRecommendedDriver)
{
issues.Add($"Storage driver '{storageDriver}' is not recommended (use overlay2, btrfs, or zfs)");
}
// Get disk info from Docker root directory
long? totalSpace = null;
long? freeSpace = null;
double? usagePercent = null;
try
{
if (Directory.Exists(dockerRoot))
{
var driveInfo = new DriveInfo(Path.GetPathRoot(dockerRoot) ?? dockerRoot);
totalSpace = driveInfo.TotalSize;
freeSpace = driveInfo.AvailableFreeSpace;
if (totalSpace > 0)
{
usagePercent = ((totalSpace.Value - freeSpace.Value) / (double)totalSpace.Value) * 100;
}
}
}
catch
{
// Disk info may not be available on all platforms
}
if (freeSpace.HasValue)
{
var minFreeSpaceBytes = (long)(minFreeSpaceGb * 1024 * 1024 * 1024);
if (freeSpace.Value < minFreeSpaceBytes)
{
var freeGb = freeSpace.Value / (1024.0 * 1024 * 1024);
issues.Add($"Low disk space: {freeGb:F1} GB free (minimum: {minFreeSpaceGb:F0} GB)");
}
}
if (usagePercent.HasValue && usagePercent.Value > maxUsagePercent)
{
issues.Add($"Disk usage {usagePercent.Value:F1}% exceeds threshold {maxUsagePercent:F0}%");
}
if (issues.Count > 0)
{
return result
.Warn($"{issues.Count} Docker storage issue(s)")
.WithEvidence("Docker storage", e =>
{
e.Add("StorageDriver", storageDriver);
e.Add("DockerRoot", dockerRoot);
e.Add("TotalSpace", FormatBytes(totalSpace));
e.Add("FreeSpace", FormatBytes(freeSpace));
e.Add("UsagePercent", usagePercent?.ToString("F1", CultureInfo.InvariantCulture) ?? "(unknown)");
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Prune unused data", "Run: docker system prune -a")
.AddManualStep(2, "Check disk usage", "Run: docker system df")
.AddManualStep(3, "Add storage", "Expand disk or add additional storage"))
.WithVerification("stella doctor --check check.docker.storage")
.Build();
}
return result
.Pass("Docker storage is healthy")
.WithEvidence("Docker storage", e =>
{
e.Add("StorageDriver", storageDriver);
e.Add("DockerRoot", dockerRoot);
e.Add("TotalSpace", FormatBytes(totalSpace));
e.Add("FreeSpace", FormatBytes(freeSpace));
e.Add("UsagePercent", usagePercent?.ToString("F1", CultureInfo.InvariantCulture) ?? "(unknown)");
e.Add("IsRecommendedDriver", isRecommendedDriver.ToString());
})
.Build();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
return result
.Skip($"Cannot check Docker storage: {ex.Message}")
.WithEvidence("Docker storage", e =>
{
e.Add("Host", dockerHost);
e.Add("Error", ex.GetType().Name);
})
.Build();
}
}
private static string GetDefaultDockerHost()
{
if (OperatingSystem.IsWindows())
{
return "npipe://./pipe/docker_engine";
}
return "unix:///var/run/docker.sock";
}
private static DockerClient CreateDockerClient(string host)
{
var config = new DockerClientConfiguration(new Uri(host));
return config.CreateClient();
}
private static string FormatBytes(long? bytes)
{
if (!bytes.HasValue)
{
return "(unknown)";
}
var b = bytes.Value;
string[] suffixes = ["B", "KB", "MB", "GB", "TB"];
var i = 0;
double size = b;
while (size >= 1024 && i < suffixes.Length - 1)
{
size /= 1024;
i++;
}
return $"{size:F1} {suffixes[i]}";
}
}

View File

@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Doctor.Plugins;
namespace StellaOps.Doctor.Plugins.Docker.DependencyInjection;
/// <summary>
/// Extension methods for registering the Docker diagnostics plugin.
/// </summary>
public static class DockerPluginExtensions
{
/// <summary>
/// Adds the Docker diagnostics plugin to the service collection.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddDoctorDockerPlugin(this IServiceCollection services)
{
services.AddSingleton<IDoctorPlugin, DockerPlugin>();
return services;
}
}

View File

@@ -0,0 +1,42 @@
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
using StellaOps.Doctor.Plugins.Docker.Checks;
namespace StellaOps.Doctor.Plugins.Docker;
/// <summary>
/// Plugin providing Docker container runtime diagnostic checks.
/// </summary>
public sealed class DockerPlugin : IDoctorPlugin
{
/// <inheritdoc />
public string PluginId => "stellaops.doctor.docker";
/// <inheritdoc />
public string DisplayName => "Docker Runtime";
/// <inheritdoc />
public DoctorCategory Category => DoctorCategory.Infrastructure;
/// <inheritdoc />
public Version Version => new(1, 0, 0);
/// <inheritdoc />
public Version MinEngineVersion => new(1, 0, 0);
/// <inheritdoc />
public bool IsAvailable(IServiceProvider services) => true;
/// <inheritdoc />
public IReadOnlyList<IDoctorCheck> GetChecks(DoctorPluginContext context) =>
[
new DockerDaemonCheck(),
new DockerSocketCheck(),
new DockerApiVersionCheck(),
new DockerNetworkCheck(),
new DockerStorageCheck()
];
/// <inheritdoc />
public Task InitializeAsync(DoctorPluginContext context, CancellationToken ct) => Task.CompletedTask;
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Doctor\StellaOps.Doctor.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Docker.DotNet" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
</Project>