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);
}
}