Files
123123/ZA.CoreService.ESBCertificateManager/Services/DeploymentOrchestrator.cs

346 lines
12 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.Models;
namespace ZA.CoreService.ESBCertificateManager.Services;
/// <summary>
/// Orchestriert nur noch Container-Neustart (kein Deploy/TLS/SQL).
/// </summary>
public sealed class DeploymentOrchestrator
{
private readonly RestartExecutor _restartExecutor;
private readonly LocalCertificateDeployer _localCertificateDeployer;
private readonly PreflightValidator _preflightValidator = new();
private readonly AppSettings _settings;
public DeploymentOrchestrator(
AppSettings settings,
IReadOnlyList<SonicConnection> sonicConnections)
{
_settings = settings;
_restartExecutor =
new RestartExecutor(sonicConnections);
_localCertificateDeployer =
new LocalCertificateDeployer();
}
public PreflightValidationResult ValidateRestartOnly(IReadOnlyList<DeploymentTarget> selectedTargets)
=> _preflightValidator.ValidateRestartOnly(selectedTargets);
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 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 beendet. Erfolg={runResult.OverallSuccess}; Dauer={(finishedAt - startedAt).TotalSeconds:F1}s; Log={logger.LogFilePath}");
return runResult;
}
public async Task<DeploymentRunResult> DeployAsync(
string certificateFilePath,
string certificateFingerprint,
IReadOnlyList<DeploymentTarget> selectedTargets,
IProgress<TargetProgressUpdate>? progress,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(certificateFilePath))
{
throw new ArgumentException(
"Es wurde kein Zertifikatsdateipfad angegeben.",
nameof(certificateFilePath));
}
if (!File.Exists(certificateFilePath))
{
throw new FileNotFoundException(
"Die ausgewählte Zertifikatsdatei wurde nicht gefunden.",
certificateFilePath);
}
if (selectedTargets.Count == 0)
{
throw new ArgumentException(
"Es wurde kein Bereitstellungsziel ausgewählt.",
nameof(selectedTargets));
}
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(certificateFilePath)}");
foreach (DeploymentTarget target in selectedTargets)
{
cancellationToken.ThrowIfCancellationRequested();
TargetStepResult result = await DeployTargetAsync(
certificateFilePath,
target,
logger,
progress,
cancellationToken);
results.Add(result);
}
DateTimeOffset finishedAt = DateTimeOffset.Now;
DeploymentRunResult runResult = new()
{
TargetResults = results,
StartedAt = startedAt,
FinishedAt = finishedAt,
CertificateFilePath = certificateFilePath,
CertificateFingerprint = certificateFingerprint
};
logger.Write(
$"Deployment beendet. Erfolg={runResult.OverallSuccess}; " +
$"Dauer={(finishedAt - startedAt).TotalSeconds:F1}s; " +
$"Log={logger.LogFilePath}");
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);
steps.Add($"{restartStatus}: {restartDetail}");
logger.Write($"[{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;
}
private async Task<TargetStepResult> DeployTargetAsync(
string certificateFilePath,
DeploymentTarget target,
RunLogger logger,
IProgress<TargetProgressUpdate>? progress,
CancellationToken cancellationToken)
{
List<string> steps = [];
DateTimeOffset targetStart = DateTimeOffset.Now;
try
{
progress?.Report(new TargetProgressUpdate(
target.Id,
$"Zertifikat wird nach '{target.TargetDirectory}' kopiert ...",
false));
string deployedFilePath =
await _localCertificateDeployer.DeployAsync(
certificateFilePath,
target.TargetDirectory,
target.CertificateFileName,
target.BackupEnabled,
target.BackupDirectoryName,
cancellationToken);
string copyStatus =
$"Zertifikat kopiert: {Path.GetFileName(deployedFilePath)}";
steps.Add(copyStatus);
logger.Write(
$"[{target.Name}] {copyStatus} | Ziel={deployedFilePath}");
progress?.Report(new TargetProgressUpdate(
target.Id,
copyStatus,
true));
// Nach dem Dateitausch Container neu starten, damit Sonic
// das neue Zertifikat lädt. Ohne Neustart bleibt oft das Alte aktiv.
bool restartNeeded =
target.RestartType is RestartType.SonicContainer
or RestartType.SonicContainerWithXapi
|| !string.IsNullOrWhiteSpace(target.ContainerName);
if (!restartNeeded)
{
return new TargetStepResult
{
TargetId = target.Id,
TargetName = target.Name,
Success = true,
StatusText = "Kopieren erfolgreich (kein Neustart)",
Detail = deployedFilePath,
Steps = steps,
StartedAt = targetStart,
FinishedAt = DateTimeOffset.Now,
CopySucceeded = true,
RestartSucceeded = true,
TlsSucceeded = true,
ObservedFingerprint = null
};
}
progress?.Report(new TargetProgressUpdate(
target.Id,
"Zertifikat kopiert Container-Neustart …",
true));
(bool restartOk, string restartStatus, string? restartDetail) =
await _restartExecutor.ExecuteAsync(
target,
cancellationToken);
string restartStep =
$"{restartStatus}: {restartDetail}";
steps.Add(restartStep);
logger.Write(
$"[{target.Name}] Restart nach Deploy: {restartStep}");
progress?.Report(new TargetProgressUpdate(
target.Id,
restartOk
? "Austausch + Neustart OK"
: restartStatus,
restartOk));
return new TargetStepResult
{
TargetId = target.Id,
TargetName = target.Name,
Success = restartOk,
StatusText = restartOk
? "Austausch + Neustart OK"
: $"Kopiert, Neustart fehlgeschlagen: {restartStatus}",
Detail =
$"Datei={deployedFilePath}"
+ Environment.NewLine
+ (restartDetail ?? string.Empty),
Steps = steps,
StartedAt = targetStart,
FinishedAt = DateTimeOffset.Now,
CopySucceeded = true,
RestartSucceeded = restartOk,
TlsSucceeded = true,
ObservedFingerprint = null
};
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
string errorStatus = $"Kopieren fehlgeschlagen: {ex.Message}";
steps.Add(errorStatus);
logger.Write($"[{target.Name}] {errorStatus}");
progress?.Report(new TargetProgressUpdate(
target.Id,
errorStatus,
false));
return new TargetStepResult
{
TargetId = target.Id,
TargetName = target.Name,
Success = false,
StatusText = "Kopieren fehlgeschlagen",
Detail = ex.Message,
Steps = steps,
StartedAt = targetStart,
FinishedAt = DateTimeOffset.Now,
CopySucceeded = false,
RestartSucceeded = false,
TlsSucceeded = false,
ObservedFingerprint = null
};
}
}
private static string? CreateCertificateBackup(string currentCertificatePath)
{
if (string.IsNullOrWhiteSpace(currentCertificatePath) ||
!File.Exists(currentCertificatePath))
{
return null;
}
string certificateDirectory =
Path.GetDirectoryName(currentCertificatePath)
?? throw new InvalidOperationException(
$"Kein Zielordner für Zertifikat gefunden: {currentCertificatePath}");
string backupDirectory = Path.Combine(certificateDirectory, "Backup");
Directory.CreateDirectory(backupDirectory);
string fileNameWithoutExtension =
Path.GetFileNameWithoutExtension(currentCertificatePath);
string extension = Path.GetExtension(currentCertificatePath);
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string backupFileName =
$"{fileNameWithoutExtension}_{timestamp}{extension}";
string backupPath = Path.Combine(backupDirectory, backupFileName);
File.Copy(currentCertificatePath, backupPath, overwrite: false);
return backupPath;
}
}
public readonly record struct TargetProgressUpdate(int TargetId, string StatusText, bool? SuccessHint);