feat(core): add Docker container provider
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user