Files

264 lines
9.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ZA.CoreService.ESBCertificateManager.Data;
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Services;
public sealed class DeploymentOrchestrator
{
private readonly CertificateDeployer _deployer = new();
private readonly RestartExecutor _restartExecutor;
private readonly TlsCertificateProbe _tlsProbe;
private readonly PreflightValidator _preflightValidator = new();
private readonly SqlRunLogger _sqlRunLogger;
private readonly AppSettings _settings;
public DeploymentOrchestrator(AppSettings settings)
{
_settings = settings;
_tlsProbe = new TlsCertificateProbe(settings.TlsTimeoutSeconds, settings.TlsRetryCount);
_restartExecutor = new RestartExecutor(settings.SonicConnections);
_sqlRunLogger = new SqlRunLogger(settings.ConnectionString);
}
public PreflightValidationResult ValidatePreflight(
string? certificatePath,
CertificateInfo? certificateInfo,
IReadOnlyList<DeploymentTarget> selectedTargets)
=> _preflightValidator.Validate(certificatePath, certificateInfo, selectedTargets);
public PreflightValidationResult ValidateRestartOnly(IReadOnlyList<DeploymentTarget> selectedTargets)
=> _preflightValidator.ValidateRestartOnly(selectedTargets);
/// <summary>
/// Nur ESB-/Container-Neustart ohne Zertifikatskopieren und ohne TLS-Probe.
/// </summary>
public async Task<DeploymentRunResult> RestartOnlyAsync(
IReadOnlyList<DeploymentTarget> selectedTargets,
IProgress<TargetProgressUpdate>? progress,
CancellationToken cancellationToken = default)
{
DateTimeOffset startedAt = DateTimeOffset.Now;
List<TargetStepResult> results = [];
using RunLogger logger = new(_settings.LogDirectory);
logger.Write($"Neustart-only gestartet für {selectedTargets.Count} Ziel(e).");
foreach (DeploymentTarget target in selectedTargets)
{
cancellationToken.ThrowIfCancellationRequested();
results.Add(await RestartTargetOnlyAsync(target, logger, progress, cancellationToken));
}
DateTimeOffset finishedAt = DateTimeOffset.Now;
DeploymentRunResult runResult = new()
{
TargetResults = results,
StartedAt = startedAt,
FinishedAt = finishedAt,
CertificateFilePath = string.Empty,
CertificateFingerprint = string.Empty
};
logger.Write(
$"Neustart-only beendet. Erfolg={runResult.OverallSuccess}; Dauer={(finishedAt - startedAt).TotalSeconds:F1}s; Log={logger.LogFilePath}");
try
{
await _sqlRunLogger.PersistRunResultAsync(runResult, cancellationToken);
}
catch (Exception ex)
{
logger.Write($"SQL-Protokollierung fehlgeschlagen (nicht kritisch): {ex.Message}");
}
return runResult;
}
private async Task<TargetStepResult> RestartTargetOnlyAsync(
DeploymentTarget target,
RunLogger logger,
IProgress<TargetProgressUpdate>? progress,
CancellationToken cancellationToken)
{
List<string> steps = [];
DateTimeOffset targetStart = DateTimeOffset.Now;
progress?.Report(new TargetProgressUpdate(target.Id, "Neustart…", false));
(bool restartOk, string restartStatus, string? restartDetail) =
await _restartExecutor.ExecuteAsync(target, cancellationToken);
RecordStep(steps, logger, target.Name, "Restart", restartStatus, restartDetail);
TargetStepResult targetResult = new()
{
TargetId = target.Id,
TargetName = target.Name,
Success = restartOk,
StatusText = restartStatus,
Detail = restartDetail,
Steps = steps,
StartedAt = targetStart,
FinishedAt = DateTimeOffset.Now,
CopySucceeded = true,
RestartSucceeded = restartOk,
TlsSucceeded = true,
ObservedFingerprint = null
};
progress?.Report(new TargetProgressUpdate(target.Id, restartStatus, restartOk));
return targetResult;
}
public async Task<DeploymentRunResult> RunAsync(
string certificatePath,
CertificateInfo certificateInfo,
IReadOnlyList<DeploymentTarget> selectedTargets,
IProgress<TargetProgressUpdate>? progress,
CancellationToken cancellationToken = default)
{
DateTimeOffset startedAt = DateTimeOffset.Now;
List<TargetStepResult> results = [];
using RunLogger logger = new(_settings.LogDirectory);
logger.Write($"Deployment gestartet für {selectedTargets.Count} Ziel(e). Zertifikat={Path.GetFileName(certificatePath)}");
foreach (DeploymentTarget target in selectedTargets)
{
cancellationToken.ThrowIfCancellationRequested();
results.Add(await RunTargetAsync(target, certificatePath, certificateInfo, logger, progress, cancellationToken));
}
DateTimeOffset finishedAt = DateTimeOffset.Now;
DeploymentRunResult runResult = new()
{
TargetResults = results,
StartedAt = startedAt,
FinishedAt = finishedAt,
CertificateFilePath = certificatePath,
CertificateFingerprint = certificateInfo.FingerprintSha256
};
logger.Write(
$"Deployment beendet. Erfolg={runResult.OverallSuccess}; Dauer={(finishedAt - startedAt).TotalSeconds:F1}s; Log={logger.LogFilePath}");
// Ergebnis in SQL-Datenbank protokollieren (wenn ConnectionString konfiguriert)
try
{
await _sqlRunLogger.PersistRunResultAsync(runResult, cancellationToken);
}
catch (Exception ex)
{
logger.Write($"SQL-Protokollierung fehlgeschlagen (nicht kritisch): {ex.Message}");
}
return runResult;
}
private async Task<TargetStepResult> RunTargetAsync(
DeploymentTarget target,
string certificatePath,
CertificateInfo certificateInfo,
RunLogger logger,
IProgress<TargetProgressUpdate>? progress,
CancellationToken cancellationToken)
{
List<string> steps = [];
DateTimeOffset targetStart = DateTimeOffset.Now;
progress?.Report(new TargetProgressUpdate(target.Id, "Läuft…", false));
(bool copyOk, string copyStatus, string? copyDetail, _) =
await _deployer.DeployAsync(certificatePath, target, cancellationToken);
RecordStep(steps, logger, target.Name, "Deploy", copyStatus, copyDetail);
if (!copyOk)
{
return Fail(target, copyStatus, copyDetail, steps, targetStart, progress);
}
progress?.Report(new TargetProgressUpdate(target.Id, copyStatus, false));
(bool restartOk, string restartStatus, string? restartDetail) =
await _restartExecutor.ExecuteAsync(target, cancellationToken);
RecordStep(steps, logger, target.Name, "Restart", restartStatus, restartDetail);
if (!restartOk)
{
return Fail(target, restartStatus, restartDetail, steps, targetStart, progress,
copySucceeded: copyOk);
}
progress?.Report(new TargetProgressUpdate(target.Id, restartStatus, false));
(bool tlsOk, string tlsStatus, string? tlsDetail, string? observedFingerprint) = await _tlsProbe.ProbeAsync(
target,
certificateInfo.FingerprintSha256,
cancellationToken);
RecordStep(steps, logger, target.Name, "TLS", tlsStatus, tlsDetail);
bool success = tlsOk;
string finalStatus = success
? (string.IsNullOrWhiteSpace(target.TlsHost) ? "Erfolg" : tlsStatus)
: tlsStatus;
TargetStepResult targetResult = new()
{
TargetId = target.Id,
TargetName = target.Name,
Success = success,
StatusText = finalStatus,
Detail = tlsDetail,
Steps = steps,
StartedAt = targetStart,
FinishedAt = DateTimeOffset.Now,
CopySucceeded = copyOk,
RestartSucceeded = restartOk,
TlsSucceeded = tlsOk,
ObservedFingerprint = observedFingerprint
};
progress?.Report(new TargetProgressUpdate(target.Id, finalStatus, success));
return targetResult;
}
private static void RecordStep(
List<string> steps,
RunLogger logger,
string targetName,
string phase,
string status,
string? detail)
{
steps.Add($"{status}: {detail}");
logger.Write($"[{targetName}] {phase}: {status} | {detail}");
}
private static TargetStepResult Fail(
DeploymentTarget target,
string status,
string? detail,
List<string> steps,
DateTimeOffset startedAt,
IProgress<TargetProgressUpdate>? progress,
bool copySucceeded = false,
bool restartSucceeded = false)
{
progress?.Report(new TargetProgressUpdate(target.Id, status, false));
return new TargetStepResult
{
TargetId = target.Id,
TargetName = target.Name,
Success = false,
StatusText = status,
Detail = detail,
Steps = steps,
StartedAt = startedAt,
FinishedAt = DateTimeOffset.Now,
CopySucceeded = copySucceeded,
RestartSucceeded = restartSucceeded,
TlsSucceeded = false
};
}
}
public readonly record struct TargetProgressUpdate(int TargetId, string StatusText, bool? SuccessHint);