264 lines
9.6 KiB
C#
264 lines
9.6 KiB
C#
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);
|