sln build fix (again), tests fixes, audit work and doctors work
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
using Npgsql;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Builders;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Database.Checks;
|
||||
|
||||
/// <summary>
|
||||
/// Checks for failed or incomplete database migrations.
|
||||
/// </summary>
|
||||
public sealed class FailedMigrationsCheck : DatabaseCheckBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string CheckId => "check.db.migrations.failed";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Failed Migrations";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Description => "Checks for failed or incomplete database migrations";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IReadOnlyList<string> Tags => ["database", "migrations", "schema"];
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<DoctorCheckResult> ExecuteCheckAsync(
|
||||
DoctorPluginContext context,
|
||||
string connectionString,
|
||||
CheckResultBuilder result,
|
||||
CancellationToken ct)
|
||||
{
|
||||
await using var connection = await CreateConnectionAsync(connectionString, ct);
|
||||
|
||||
// Check for Stella Ops migration tracking table
|
||||
var hasTrackingTable = await CheckTableExistsAsync(connection, "stella_migration_history", ct);
|
||||
|
||||
if (!hasTrackingTable)
|
||||
{
|
||||
return result
|
||||
.Info("No migration tracking table found - using EF Core migrations only")
|
||||
.WithEvidence("Migration status", e => e
|
||||
.Add("TrackingTableExists", "false"))
|
||||
.Build();
|
||||
}
|
||||
|
||||
// Check for failed migrations in tracking table
|
||||
await using var cmd = new NpgsqlCommand(@"
|
||||
SELECT migration_id, status, error_message, applied_at
|
||||
FROM stella_migration_history
|
||||
WHERE status = 'failed' OR status = 'incomplete'
|
||||
ORDER BY applied_at DESC
|
||||
LIMIT 5",
|
||||
connection);
|
||||
|
||||
var failedMigrations = new List<(string Id, string Status, string? Error)>();
|
||||
|
||||
await using var reader = await cmd.ExecuteReaderAsync(ct);
|
||||
while (await reader.ReadAsync(ct))
|
||||
{
|
||||
failedMigrations.Add((
|
||||
reader.GetString(0),
|
||||
reader.GetString(1),
|
||||
reader.IsDBNull(2) ? null : reader.GetString(2)
|
||||
));
|
||||
}
|
||||
|
||||
if (failedMigrations.Count > 0)
|
||||
{
|
||||
return result
|
||||
.Fail($"{failedMigrations.Count} failed/incomplete migration(s) found")
|
||||
.WithEvidence("Failed migrations", e =>
|
||||
{
|
||||
e.Add("FailedCount", failedMigrations.Count.ToString());
|
||||
for (int i = 0; i < failedMigrations.Count; i++)
|
||||
{
|
||||
var m = failedMigrations[i];
|
||||
e.Add($"Migration_{i + 1}", $"{m.Id} ({m.Status})");
|
||||
if (m.Error != null)
|
||||
{
|
||||
e.Add($"Error_{i + 1}", m.Error);
|
||||
}
|
||||
}
|
||||
})
|
||||
.WithCauses(
|
||||
"Migration script has errors",
|
||||
"Database permission issues",
|
||||
"Concurrent migration attempts")
|
||||
.WithRemediation(r => r
|
||||
.AddManualStep(1, "Review migration logs", "Check application logs for migration error details")
|
||||
.AddManualStep(2, "Fix migration issues", "Resolve the underlying issue and retry migration")
|
||||
.AddShellStep(3, "Retry migrations", "dotnet ef database update"))
|
||||
.WithVerification("stella doctor --check check.db.migrations.failed")
|
||||
.Build();
|
||||
}
|
||||
|
||||
return result
|
||||
.Pass("No failed migrations found")
|
||||
.WithEvidence("Migration status", e => e
|
||||
.Add("FailedMigrations", "0")
|
||||
.Add("TrackingTableExists", "true"))
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static async Task<bool> CheckTableExistsAsync(NpgsqlConnection connection, string tableName, CancellationToken ct)
|
||||
{
|
||||
await using var cmd = new NpgsqlCommand(
|
||||
$"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '{tableName}')",
|
||||
connection);
|
||||
return Convert.ToBoolean(await cmd.ExecuteScalarAsync(ct));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user