Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled
- Implemented CreateInstanceView for creating new instances. - Added HostsView for managing SSH hosts with CRUD operations. - Created InstancesView for displaying and managing instances. - Developed LogsView for viewing operation logs. - Introduced SecretsView for managing secrets associated with hosts. - Established SettingsView for configuring application settings. - Created MainWindow as the main application window with navigation. - Added app manifest and configuration files for logging and settings.
160 lines
5.3 KiB
C#
160 lines
5.3 KiB
C#
using System.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using OTSSignsOrchestrator.Core.Configuration;
|
|
using OTSSignsOrchestrator.Core.Models.DTOs;
|
|
using OTSSignsOrchestrator.Core.Models.Entities;
|
|
using OTSSignsOrchestrator.Core.Services;
|
|
|
|
namespace OTSSignsOrchestrator.Desktop.Services;
|
|
|
|
/// <summary>
|
|
/// Docker CLI service that executes docker commands on a remote host over SSH.
|
|
/// Requires an SshHost to be set before use via SetHost().
|
|
/// </summary>
|
|
public class SshDockerCliService : IDockerCliService
|
|
{
|
|
private readonly SshConnectionService _ssh;
|
|
private readonly DockerOptions _options;
|
|
private readonly ILogger<SshDockerCliService> _logger;
|
|
private SshHost? _currentHost;
|
|
|
|
public SshDockerCliService(
|
|
SshConnectionService ssh,
|
|
IOptions<DockerOptions> options,
|
|
ILogger<SshDockerCliService> logger)
|
|
{
|
|
_ssh = ssh;
|
|
_options = options.Value;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the SSH host to use for Docker commands.
|
|
/// </summary>
|
|
public void SetHost(SshHost host)
|
|
{
|
|
_currentHost = host;
|
|
}
|
|
|
|
public SshHost? CurrentHost => _currentHost;
|
|
|
|
public async Task<DeploymentResultDto> DeployStackAsync(string stackName, string composeYaml, bool resolveImage = false)
|
|
{
|
|
EnsureHost();
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
var args = "docker stack deploy --compose-file -";
|
|
if (resolveImage)
|
|
args += " --resolve-image changed";
|
|
args += $" {stackName}";
|
|
|
|
_logger.LogInformation("Deploying stack via SSH: {StackName} on {Host}", stackName, _currentHost!.Label);
|
|
|
|
var (exitCode, stdout, stderr) = await _ssh.RunCommandWithStdinAsync(_currentHost, args, composeYaml);
|
|
sw.Stop();
|
|
|
|
var result = new DeploymentResultDto
|
|
{
|
|
StackName = stackName,
|
|
Success = exitCode == 0,
|
|
ExitCode = exitCode,
|
|
Output = stdout,
|
|
ErrorMessage = stderr,
|
|
Message = exitCode == 0 ? "Success" : "Failed",
|
|
DurationMs = sw.ElapsedMilliseconds
|
|
};
|
|
|
|
if (result.Success)
|
|
_logger.LogInformation("Stack deployed via SSH: {StackName} | duration={DurationMs}ms", stackName, result.DurationMs);
|
|
else
|
|
_logger.LogError("Stack deploy failed via SSH: {StackName} | error={Error}", stackName, result.ErrorMessage);
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<DeploymentResultDto> RemoveStackAsync(string stackName)
|
|
{
|
|
EnsureHost();
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
_logger.LogInformation("Removing stack via SSH: {StackName} on {Host}", stackName, _currentHost!.Label);
|
|
|
|
var (exitCode, stdout, stderr) = await _ssh.RunCommandAsync(_currentHost, $"docker stack rm {stackName}");
|
|
sw.Stop();
|
|
|
|
var result = new DeploymentResultDto
|
|
{
|
|
StackName = stackName,
|
|
Success = exitCode == 0,
|
|
ExitCode = exitCode,
|
|
Output = stdout,
|
|
ErrorMessage = stderr,
|
|
Message = exitCode == 0 ? "Success" : "Failed",
|
|
DurationMs = sw.ElapsedMilliseconds
|
|
};
|
|
|
|
if (result.Success)
|
|
_logger.LogInformation("Stack removed via SSH: {StackName}", stackName);
|
|
else
|
|
_logger.LogError("Stack remove failed via SSH: {StackName} | error={Error}", stackName, result.ErrorMessage);
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<List<StackInfo>> ListStacksAsync()
|
|
{
|
|
EnsureHost();
|
|
|
|
var (exitCode, stdout, _) = await _ssh.RunCommandAsync(
|
|
_currentHost!, "docker stack ls --format '{{.Name}}\\t{{.Services}}'");
|
|
|
|
if (exitCode != 0 || string.IsNullOrWhiteSpace(stdout))
|
|
return new List<StackInfo>();
|
|
|
|
return stdout
|
|
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(line =>
|
|
{
|
|
var parts = line.Split('\t', 2);
|
|
return new StackInfo
|
|
{
|
|
Name = parts[0].Trim(),
|
|
ServiceCount = parts.Length > 1 && int.TryParse(parts[1].Trim(), out var c) ? c : 0
|
|
};
|
|
})
|
|
.ToList();
|
|
}
|
|
|
|
public async Task<List<ServiceInfo>> InspectStackServicesAsync(string stackName)
|
|
{
|
|
EnsureHost();
|
|
|
|
var (exitCode, stdout, _) = await _ssh.RunCommandAsync(
|
|
_currentHost!, $"docker stack services {stackName} --format '{{{{.Name}}}}\\t{{{{.Image}}}}\\t{{{{.Replicas}}}}'");
|
|
|
|
if (exitCode != 0 || string.IsNullOrWhiteSpace(stdout))
|
|
return new List<ServiceInfo>();
|
|
|
|
return stdout
|
|
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(line =>
|
|
{
|
|
var parts = line.Split('\t', 3);
|
|
return new ServiceInfo
|
|
{
|
|
Name = parts.Length > 0 ? parts[0].Trim() : "",
|
|
Image = parts.Length > 1 ? parts[1].Trim() : "",
|
|
Replicas = parts.Length > 2 ? parts[2].Trim() : ""
|
|
};
|
|
})
|
|
.ToList();
|
|
}
|
|
|
|
private void EnsureHost()
|
|
{
|
|
if (_currentHost == null)
|
|
throw new InvalidOperationException("No SSH host configured. Call SetHost() before using Docker commands.");
|
|
}
|
|
}
|