using System.Diagnostics; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; public sealed class RestartExecutor { private readonly IReadOnlyList _sonicConnections; public RestartExecutor(IReadOnlyList sonicConnections) { _sonicConnections = sonicConnections; } public Task<(bool Success, string Status, string? Detail)> ExecuteAsync( DeploymentTarget target, CancellationToken cancellationToken = default) => target.RestartType switch { RestartType.None => Task.FromResult<(bool, string, string?)>((true, "Neustart übersprungen", "RestartType=None")), RestartType.Command => ExecuteCommandAsync(target, cancellationToken), RestartType.SonicContainer => ExecuteSonicRestartAsync(target, importXapi: false, cancellationToken), RestartType.SonicContainerWithXapi => ExecuteSonicRestartAsync(target, importXapi: true, cancellationToken), _ => Task.FromResult<(bool, string, string?)>((false, "Unbekannter RestartType", $"RestartType={target.RestartType}")) }; private async Task<(bool Success, string Status, string? Detail)> ExecuteSonicRestartAsync( DeploymentTarget target, bool importXapi, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(target.ContainerName)) { return (false, "Sonic-Neustart fehlgeschlagen", $"Ziel '{target.Name}': ContainerName fehlt."); } SonicConnection? connection = _sonicConnections .FirstOrDefault(c => string.Equals(c.Name, target.SonicConnectionName, StringComparison.OrdinalIgnoreCase)); if (connection is null) { return (false, "Sonic-Verbindung nicht gefunden", $"Ziel '{target.Name}': SonicConnection '{target.SonicConnectionName}' ist nicht in AppSettings konfiguriert."); } using SonicManagementClient client = new(connection); if (importXapi) { if (string.IsNullOrWhiteSpace(target.XapiSourcePath)) { return (false, "XApi-Import fehlgeschlagen", $"Ziel '{target.Name}': XapiSourcePath fehlt bei RestartType=SonicContainerWithXapi."); } return await client.ImportXapiAndRestartAsync(target.ContainerName, target.XapiSourcePath, cancellationToken); } return await client.RestartContainerAsync(target.ContainerName, cancellationToken); } private async Task<(bool Success, string Status, string? Detail)> ExecuteCommandAsync( DeploymentTarget target, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(target.RestartCommand)) { return (true, "Neustart übersprungen", "RestartType=None"); } int timeoutSeconds = Math.Clamp(target.RestartTimeoutSeconds, 1, 600); ProcessStartInfo startInfo = new() { FileName = target.RestartCommand, Arguments = target.RestartArguments ?? string.Empty, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; try { using Process process = new() { StartInfo = startInfo }; if (!process.Start()) { return (false, "Neustart fehlgeschlagen", "Prozess konnte nicht gestartet werden."); } Task stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken); Task stderrTask = process.StandardError.ReadToEndAsync(cancellationToken); using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutCts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds)); try { await process.WaitForExitAsync(timeoutCts.Token); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { try { process.Kill(entireProcessTree: true); } catch { /* ignore */ } return (false, "Neustart Timeout", $"Timeout nach {timeoutSeconds}s."); } string detail = BuildDetail(process.ExitCode, await stdoutTask, await stderrTask); return process.ExitCode == 0 ? (true, "Neustart ok", detail) : (false, "Neustart fehlgeschlagen", detail); } catch (OperationCanceledException) { throw; } catch (Exception ex) { return (false, "Neustart fehlgeschlagen", ex.Message); } } private static string BuildDetail(int exitCode, string stdout, string stderr) { string detail = $"ExitCode={exitCode}"; stdout = Truncate(stdout).Trim(); if (!string.IsNullOrWhiteSpace(stdout)) { detail += "; out=" + stdout; } stderr = Truncate(stderr).Trim(); if (!string.IsNullOrWhiteSpace(stderr)) { detail += "; err=" + stderr; } return detail; } private static string Truncate(string value, int max = 400) { if (string.IsNullOrEmpty(value) || value.Length <= max) { return value; } return value[..max] + "…"; } }