Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,30 @@
using System.Linq;
using MongoDB.Driver;
using StellaOps.Scanner.Storage.Catalog;
namespace StellaOps.Scanner.Storage.Migrations;
public sealed class EnsureLifecycleRuleTtlMigration : IMongoMigration
{
public string Id => "20251018-lifecycle-ttl";
public string Description => "Ensure lifecycle_rules expiresAt TTL index exists.";
public async Task ApplyAsync(IMongoDatabase database, CancellationToken cancellationToken)
{
var collection = database.GetCollection<LifecycleRuleDocument>(ScannerStorageDefaults.Collections.LifecycleRules);
var indexes = await collection.Indexes.ListAsync(cancellationToken).ConfigureAwait(false);
var existing = await indexes.ToListAsync(cancellationToken).ConfigureAwait(false);
if (existing.Any(x => string.Equals(x["name"].AsString, "lifecycle_expiresAt", StringComparison.Ordinal)))
{
return;
}
var model = new CreateIndexModel<LifecycleRuleDocument>(
Builders<LifecycleRuleDocument>.IndexKeys.Ascending(x => x.ExpiresAtUtc),
new CreateIndexOptions { Name = "lifecycle_expiresAt", ExpireAfter = TimeSpan.Zero });
await collection.Indexes.CreateOneAsync(model, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -0,0 +1,12 @@
using MongoDB.Driver;
namespace StellaOps.Scanner.Storage.Migrations;
public interface IMongoMigration
{
string Id { get; }
string Description { get; }
Task ApplyAsync(IMongoDatabase database, CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,19 @@
using MongoDB.Bson.Serialization.Attributes;
namespace StellaOps.Scanner.Storage.Migrations;
[BsonIgnoreExtraElements]
internal sealed class MongoMigrationDocument
{
[BsonId]
public string Id { get; set; } = string.Empty;
[BsonElement("description")]
[BsonIgnoreIfNull]
public string? Description { get; set; }
= null;
[BsonElement("appliedAt")]
public DateTime AppliedAtUtc { get; set; }
= DateTime.UtcNow;
}

View File

@@ -0,0 +1,94 @@
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
namespace StellaOps.Scanner.Storage.Migrations;
public sealed class MongoMigrationRunner
{
private readonly IMongoDatabase _database;
private readonly IReadOnlyList<IMongoMigration> _migrations;
private readonly ILogger<MongoMigrationRunner> _logger;
private readonly TimeProvider _timeProvider;
public MongoMigrationRunner(
IMongoDatabase database,
IEnumerable<IMongoMigration> migrations,
ILogger<MongoMigrationRunner> logger,
TimeProvider? timeProvider = null)
{
_database = database ?? throw new ArgumentNullException(nameof(database));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
_migrations = (migrations ?? throw new ArgumentNullException(nameof(migrations)))
.OrderBy(m => m.Id, StringComparer.Ordinal)
.ToArray();
}
public async Task RunAsync(CancellationToken cancellationToken)
{
if (_migrations.Count == 0)
{
return;
}
await EnsureCollectionExistsAsync(_database, cancellationToken).ConfigureAwait(false);
var collection = _database.GetCollection<MongoMigrationDocument>(ScannerStorageDefaults.Collections.Migrations);
var applied = await LoadAppliedMigrationIdsAsync(collection, cancellationToken).ConfigureAwait(false);
foreach (var migration in _migrations)
{
if (applied.Contains(migration.Id, StringComparer.Ordinal))
{
continue;
}
_logger.LogInformation("Applying scanner Mongo migration {MigrationId}: {Description}", migration.Id, migration.Description);
try
{
await migration.ApplyAsync(_database, cancellationToken).ConfigureAwait(false);
var document = new MongoMigrationDocument
{
Id = migration.Id,
Description = string.IsNullOrWhiteSpace(migration.Description) ? null : migration.Description,
AppliedAtUtc = _timeProvider.GetUtcNow().UtcDateTime,
};
await collection.InsertOneAsync(document, cancellationToken: cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Scanner Mongo migration {MigrationId} applied", migration.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Scanner Mongo migration {MigrationId} failed", migration.Id);
throw;
}
}
}
private static async Task EnsureCollectionExistsAsync(IMongoDatabase database, CancellationToken cancellationToken)
{
using var cursor = await database.ListCollectionNamesAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
var names = await cursor.ToListAsync(cancellationToken).ConfigureAwait(false);
if (!names.Contains(ScannerStorageDefaults.Collections.Migrations, StringComparer.Ordinal))
{
await database.CreateCollectionAsync(ScannerStorageDefaults.Collections.Migrations, cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
private static async Task<HashSet<string>> LoadAppliedMigrationIdsAsync(
IMongoCollection<MongoMigrationDocument> collection,
CancellationToken cancellationToken)
{
using var cursor = await collection.FindAsync(FilterDefinition<MongoMigrationDocument>.Empty, cancellationToken: cancellationToken).ConfigureAwait(false);
var documents = await cursor.ToListAsync(cancellationToken).ConfigureAwait(false);
var ids = new HashSet<string>(StringComparer.Ordinal);
foreach (var doc in documents)
{
if (!string.IsNullOrWhiteSpace(doc.Id))
{
ids.Add(doc.Id);
}
}
return ids;
}
}