71 lines
2.3 KiB
C#
71 lines
2.3 KiB
C#
|
|
using Docker.DotNet;
|
||
|
|
using Docker.DotNet.Models;
|
||
|
|
using Microsoft.Extensions.Logging;
|
||
|
|
using Microsoft.Extensions.Options;
|
||
|
|
|
||
|
|
namespace AnsonBot.Core;
|
||
|
|
|
||
|
|
public interface IRconService
|
||
|
|
{
|
||
|
|
Task<string> SendAsync(string command, CancellationToken ct = default);
|
||
|
|
}
|
||
|
|
|
||
|
|
public class DockerRconService : IRconService, IDisposable
|
||
|
|
{
|
||
|
|
private readonly DockerOptions _options;
|
||
|
|
private readonly ILogger<DockerRconService> _logger;
|
||
|
|
private readonly DockerClient _client;
|
||
|
|
|
||
|
|
public DockerRconService(
|
||
|
|
IOptions<DockerOptions> options,
|
||
|
|
ILogger<DockerRconService> logger)
|
||
|
|
{
|
||
|
|
_options = options.Value;
|
||
|
|
_logger = logger;
|
||
|
|
|
||
|
|
var config = string.IsNullOrWhiteSpace(_options.DaemonUri)
|
||
|
|
? new DockerClientConfiguration()
|
||
|
|
: new DockerClientConfiguration(new Uri(_options.DaemonUri));
|
||
|
|
|
||
|
|
_client = config.CreateClient();
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<string> SendAsync(string command, CancellationToken ct = default)
|
||
|
|
{
|
||
|
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||
|
|
cts.CancelAfter(TimeSpan.FromSeconds(_options.TimeoutSeconds));
|
||
|
|
|
||
|
|
_logger.LogInformation("Exec in {Container}: {Cli} {Command}",
|
||
|
|
_options.ContainerName, _options.RconCliPath, command);
|
||
|
|
|
||
|
|
var exec = await _client.Exec.ExecCreateContainerAsync(
|
||
|
|
_options.ContainerName,
|
||
|
|
new ContainerExecCreateParameters
|
||
|
|
{
|
||
|
|
// Passed as argv, so no shell quoting concerns.
|
||
|
|
Cmd = new List<string> { _options.RconCliPath, command },
|
||
|
|
AttachStdout = true,
|
||
|
|
AttachStderr = true
|
||
|
|
},
|
||
|
|
cts.Token);
|
||
|
|
|
||
|
|
using var stream = await _client.Exec.StartAndAttachContainerExecAsync(
|
||
|
|
exec.ID, tty: false, cts.Token);
|
||
|
|
|
||
|
|
var (stdout, stderr) = await stream.ReadOutputToEndAsync(cts.Token);
|
||
|
|
|
||
|
|
var inspect = await _client.Exec.InspectContainerExecAsync(exec.ID, cts.Token);
|
||
|
|
|
||
|
|
if (inspect.ExitCode != 0)
|
||
|
|
{
|
||
|
|
_logger.LogError("rcon exec failed (exit {Code}): {Error}", inspect.ExitCode, stderr);
|
||
|
|
throw new InvalidOperationException(
|
||
|
|
$"'{_options.RconCliPath} {command}' exited with code {inspect.ExitCode}: {stderr}");
|
||
|
|
}
|
||
|
|
|
||
|
|
return stdout;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Dispose() => _client.Dispose();
|
||
|
|
}
|