using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; /// /// Orchestriert nur noch Container-Neustart (kein Deploy/TLS/SQL). /// 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 sonicConnections) { _settings = settings; _restartExecutor = new RestartExecutor(sonicConnections); _localCertificateDeployer = new LocalCertificateDeployer(); } public PreflightValidationResult ValidateRestartOnly(IReadOnlyList selectedTargets) => _preflightValidator.ValidateRestartOnly(selectedTargets); public async Task RestartOnlyAsync( IReadOnlyList selectedTargets, IProgress? progress, CancellationToken cancellationToken = default) { DateTimeOffset startedAt = DateTimeOffset.Now; List 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 DeployAsync( string certificateFilePath, string certificateFingerprint, IReadOnlyList selectedTargets, IProgress? 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 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 RestartTargetOnlyAsync( DeploymentTarget target, RunLogger logger, IProgress? progress, CancellationToken cancellationToken) { List 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 DeployTargetAsync( string certificateFilePath, DeploymentTarget target, RunLogger logger, IProgress? progress, CancellationToken cancellationToken) { List 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)); return new TargetStepResult { TargetId = target.Id, TargetName = target.Name, Success = true, StatusText = "Kopieren erfolgreich", Detail = deployedFilePath, Steps = steps, StartedAt = targetStart, FinishedAt = DateTimeOffset.Now, CopySucceeded = true, RestartSucceeded = true, 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);