feat(core): add Docker container provider

This commit is contained in:
dualfroz committed 2026-08-24 10:02:19 +02:00
1 parent 8fda3d8211
commit b4d46783b3
4 files changed
+145

No files matched your search

@@ -0,0 +1,46 @@
using Vantage.Core.Models;
namespace Vantage.Core.Docker;
public static class DockerContainerParser
{
public const string Format = "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.State}}\t{{.Status}}";
public static IReadOnlyList<DockerContainer> Parse(string psOutput)
{
var containers = new List<DockerContainer>();
foreach (var rawLine in psOutput.Replace("\r\n", "\n").Split('\n'))
{
var line = rawLine.TrimEnd();
if (line.Length == 0)
continue;
var fields = line.Split('\t');
if (fields.Length < 5)
continue;
containers.Add(new DockerContainer
{
Id = fields[0].Trim(),
Name = fields[1].Trim(),
Image = fields[2].Trim(),
State = MapState(fields[3].Trim()),
StatusText = fields[4].Trim()
});
}
return containers;
}
private static ContainerState MapState(string state) => state.ToLowerInvariant() switch
{
"running" => ContainerState.Running,
"exited" => ContainerState.Exited,
"paused" => ContainerState.Paused,
"restarting" => ContainerState.Restarting,
"created" => ContainerState.Created,
"dead" => ContainerState.Dead,
_ => ContainerState.Other
};
}
@@ -0,0 +1,17 @@
using Vantage.Core.Models;
namespace Vantage.Core.Docker;
public enum DockerAction
{
Start,
Stop,
Restart
}
public interface IDockerProvider
{
Task<IReadOnlyList<DockerContainer>> ListContainersAsync(HostConfig host, CancellationToken cancellationToken);
Task ExecuteAsync(HostConfig host, DockerAction action, string containerId, CancellationToken cancellationToken);
}
@@ -0,0 +1,47 @@
using System.Collections.Concurrent;
using Vantage.Core.Models;
namespace Vantage.Core.Docker;
public sealed class MockDockerProvider : IDockerProvider
{
private readonly ConcurrentDictionary<string, List<DockerContainer>> byHost = new();
public Task<IReadOnlyList<DockerContainer>> ListContainersAsync(HostConfig host, CancellationToken cancellationToken)
{
var containers = byHost.GetOrAdd(host.Id, _ => Seed());
lock (containers)
return Task.FromResult<IReadOnlyList<DockerContainer>>(containers.ToArray());
}
public Task ExecuteAsync(HostConfig host, DockerAction action, string containerId, CancellationToken cancellationToken)
{
var containers = byHost.GetOrAdd(host.Id, _ => Seed());
lock (containers)
{
var index = containers.FindIndex(c => c.Id == containerId);
if (index < 0)
return Task.CompletedTask;
containers[index] = Transition(containers[index], action);
}
return Task.CompletedTask;
}
private static DockerContainer Transition(DockerContainer container, DockerAction action) => action switch
{
DockerAction.Stop => container with { State = ContainerState.Exited, StatusText = "Exited (0) just now" },
DockerAction.Start => container with { State = ContainerState.Running, StatusText = "Up 1 second" },
DockerAction.Restart => container with { State = ContainerState.Running, StatusText = "Up 1 second (restarted)" },
_ => container
};
private static List<DockerContainer> Seed() => new()
{
new DockerContainer { Id = "c1a2b3c4d5e6", Name = "caddy", Image = "caddy:2", State = ContainerState.Running, StatusText = "Up 6 days" },
new DockerContainer { Id = "f7e8d9c0b1a2", Name = "postgres", Image = "postgres:16", State = ContainerState.Running, StatusText = "Up 6 days (healthy)" },
new DockerContainer { Id = "1234abcd5678", Name = "grafana", Image = "grafana/grafana:11", State = ContainerState.Running, StatusText = "Up 2 days" },
new DockerContainer { Id = "9f8e7d6c5b4a", Name = "backup-runner", Image = "alpine:3.20", State = ContainerState.Exited, StatusText = "Exited (0) 3 hours ago" }
};
}
@@ -0,0 +1,35 @@
using Vantage.Core.Models;
using Vantage.Core.Ssh;
namespace Vantage.Core.Docker;
public sealed class SshDockerProvider : IDockerProvider
{
private readonly ISshCommandRunner runner;
public SshDockerProvider(ISshCommandRunner runner)
{
this.runner = runner;
}
public async Task<IReadOnlyList<DockerContainer>> ListContainersAsync(HostConfig host, CancellationToken cancellationToken)
{
var command = $"docker ps -a --no-trunc --format {ShellArgs.Quote(DockerContainerParser.Format)}";
var output = await runner.RunAsync(host, command, cancellationToken).ConfigureAwait(false);
return DockerContainerParser.Parse(output);
}
public async Task ExecuteAsync(HostConfig host, DockerAction action, string containerId, CancellationToken cancellationToken)
{
var verb = action switch
{
DockerAction.Start => "start",
DockerAction.Stop => "stop",
DockerAction.Restart => "restart",
_ => throw new ArgumentOutOfRangeException(nameof(action))
};
var command = $"docker {verb} {ShellArgs.Quote(containerId)}";
await runner.RunAsync(host, command, cancellationToken).ConfigureAwait(false);
}
}