using Microsoft.Data.SqlClient; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Data; /// /// Schreibt Deployment-Lauf-Ergebnisse in die SQL-Datenbank. /// Tabellen: dbo.DeploymentRuns (GUID-PK) + dbo.DeploymentTargetResults. /// Ist kein ConnectionString konfiguriert, werden alle Operationen still übersprungen. /// public sealed class SqlRunLogger { private readonly string? _connectionString; public SqlRunLogger(string? connectionString) { _connectionString = string.IsNullOrWhiteSpace(connectionString) ? null : connectionString; } public bool IsEnabled => _connectionString is not null; /// /// Legt einen neuen Lauf-Datensatz an. /// public async Task BeginRunAsync( Guid runId, DateTimeOffset startedAt, string certificateFilePath, string certificateFingerprint, CancellationToken cancellationToken = default) { if (_connectionString is null) return; const string sql = """ INSERT INTO dbo.DeploymentRuns (Id, StartedAtUtc, SourceFile, SourceFingerprint, StartedBy, MachineName, OverallStatus) VALUES (@Id, @StartedAtUtc, @SourceFile, @SourceFingerprint, @StartedBy, @MachineName, N'Running'); """; await using SqlConnection conn = new(_connectionString); await conn.OpenAsync(cancellationToken); await using SqlCommand cmd = new(sql, conn); cmd.Parameters.AddWithValue("@Id", runId); cmd.Parameters.AddWithValue("@StartedAtUtc", startedAt.UtcDateTime); cmd.Parameters.AddWithValue("@SourceFile", Path.GetFileName(certificateFilePath)); cmd.Parameters.AddWithValue("@SourceFingerprint", certificateFingerprint); cmd.Parameters.AddWithValue("@StartedBy", Environment.UserName); cmd.Parameters.AddWithValue("@MachineName", Environment.MachineName); await cmd.ExecuteNonQueryAsync(cancellationToken); } /// /// Aktualisiert den Lauf-Datensatz mit Endzeitpunkt und Gesamtstatus. /// public async Task CompleteRunAsync( Guid runId, DateTimeOffset finishedAt, bool overallSuccess, int successCount, int totalCount, CancellationToken cancellationToken = default) { if (_connectionString is null) return; string status = overallSuccess ? "Success" : (successCount > 0 ? "PartialFailure" : "Failure"); const string sql = """ UPDATE dbo.DeploymentRuns SET FinishedAtUtc = @FinishedAtUtc, OverallStatus = @OverallStatus WHERE Id = @Id; """; await using SqlConnection conn = new(_connectionString); await conn.OpenAsync(cancellationToken); await using SqlCommand cmd = new(sql, conn); cmd.Parameters.AddWithValue("@Id", runId); cmd.Parameters.AddWithValue("@FinishedAtUtc", finishedAt.UtcDateTime); cmd.Parameters.AddWithValue("@OverallStatus", status); await cmd.ExecuteNonQueryAsync(cancellationToken); } /// /// Schreibt das Ergebnis eines einzelnen Deployment-Ziels. /// public async Task WriteTargetResultAsync( Guid runId, TargetStepResult step, CancellationToken cancellationToken = default) { if (_connectionString is null) return; string status = step.Success ? "Success" : "Failure"; const string sql = """ INSERT INTO dbo.DeploymentTargetResults (DeploymentRunId, TargetId, TargetName, StartedAtUtc, FinishedAtUtc, CopySucceeded, RestartSucceeded, TlsSucceeded, ObservedFingerprint, Status, ErrorMessage) VALUES (@RunId, @TargetId, @TargetName, @StartedAtUtc, @FinishedAtUtc, @CopySucceeded, @RestartSucceeded, @TlsSucceeded, @ObservedFingerprint, @Status, @ErrorMessage); """; await using SqlConnection conn = new(_connectionString); await conn.OpenAsync(cancellationToken); await using SqlCommand cmd = new(sql, conn); cmd.Parameters.AddWithValue("@RunId", runId); cmd.Parameters.AddWithValue("@TargetId", step.TargetId); cmd.Parameters.AddWithValue("@TargetName", step.TargetName); cmd.Parameters.AddWithValue("@StartedAtUtc", step.StartedAt.UtcDateTime); cmd.Parameters.AddWithValue("@FinishedAtUtc", step.FinishedAt.UtcDateTime); cmd.Parameters.AddWithValue("@CopySucceeded", step.CopySucceeded); cmd.Parameters.AddWithValue("@RestartSucceeded", step.RestartSucceeded); cmd.Parameters.AddWithValue("@TlsSucceeded", step.TlsSucceeded); cmd.Parameters.AddWithValue("@ObservedFingerprint", (object?)step.ObservedFingerprint ?? DBNull.Value); cmd.Parameters.AddWithValue("@Status", status); cmd.Parameters.AddWithValue("@ErrorMessage", step.Success ? DBNull.Value : (object?)(step.Detail ?? step.StatusText)); await cmd.ExecuteNonQueryAsync(cancellationToken); } /// /// Persistiert ein vollständiges in einem einzigen Aufruf. /// public async Task PersistRunResultAsync( DeploymentRunResult runResult, CancellationToken cancellationToken = default) { if (_connectionString is null) return; await BeginRunAsync( runResult.RunId, runResult.StartedAt, runResult.CertificateFilePath, runResult.CertificateFingerprint, cancellationToken); foreach (TargetStepResult step in runResult.TargetResults) { await WriteTargetResultAsync(runResult.RunId, step, cancellationToken); } int successCount = runResult.TargetResults.Count(r => r.Success); await CompleteRunAsync( runResult.RunId, runResult.FinishedAt, runResult.OverallSuccess, successCount, runResult.TargetResults.Count, cancellationToken); } }