feat: Add Go module and workspace test fixtures

- Created expected JSON files for Go modules and workspaces.
- Added go.mod and go.sum files for example projects.
- Implemented private module structure with expected JSON output.
- Introduced vendored dependencies with corresponding expected JSON.
- Developed PostgresGraphJobStore for managing graph jobs.
- Established SQL migration scripts for graph jobs schema.
- Implemented GraphJobRepository for CRUD operations on graph jobs.
- Created IGraphJobRepository interface for repository abstraction.
- Added unit tests for GraphJobRepository to ensure functionality.
This commit is contained in:
StellaOps Bot
2025-12-06 20:04:03 +02:00
parent a6f1406509
commit 05597616d6
178 changed files with 12022 additions and 4545 deletions

View File

@@ -75,7 +75,7 @@ internal static class CommandFactory
root.Add(BuildSdkCommand(services, verboseOption, cancellationToken));
root.Add(BuildMirrorCommand(services, verboseOption, cancellationToken));
root.Add(BuildAirgapCommand(services, verboseOption, cancellationToken));
root.Add(SystemCommandBuilder.BuildSystemCommand());
root.Add(SystemCommandBuilder.BuildSystemCommand(services, verboseOption, cancellationToken));
var pluginLogger = loggerFactory.CreateLogger<CliCommandModuleLoader>();
var pluginLoader = new CliCommandModuleLoader(services, options, pluginLogger);

View File

@@ -1,6 +1,10 @@
using System;
using System.CommandLine;
using System.Threading.Tasks;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Cli.Services;
using StellaOps.Infrastructure.Postgres.Migrations;
namespace StellaOps.Cli.Commands;
@@ -23,60 +27,118 @@ internal static class SystemCommandBuilder
};
}
internal static Command BuildSystemCommand()
internal static Command BuildSystemCommand(
IServiceProvider services,
Option<bool> verboseOption,
CancellationToken cancellationToken)
{
var moduleOption = new Option<string?>("--module", description: "Module name (Authority, Scheduler, Concelier, Policy, Notify, Excititor, all)");
var categoryOption = new Option<string?>("--category", description: "Migration category (startup, release, seed, data)");
var moduleOption = new Option<string?>(
"--module",
description: "Module name (Authority, Scheduler, Concelier, Policy, Notify, Excititor, all)");
var categoryOption = new Option<string?>(
"--category",
description: "Migration category (startup, release, seed, data)");
var dryRunOption = new Option<bool>("--dry-run", description: "List migrations without executing");
var connectionOption = new Option<string?>(
"--connection",
description: "PostgreSQL connection string override (otherwise uses STELLAOPS_POSTGRES_* env vars)");
var timeoutOption = new Option<int?>(
"--timeout",
description: "Command timeout in seconds for each migration (default 300).");
var forceOption = new Option<bool>(
"--force",
description: "Allow execution of release migrations without --dry-run.");
var run = new Command("migrations-run", "Run migrations for the selected module(s).");
run.AddOption(moduleOption);
run.AddOption(categoryOption);
run.AddOption(dryRunOption);
run.AddOption(connectionOption);
run.AddOption(timeoutOption);
run.AddOption(forceOption);
run.SetAction(async parseResult =>
{
var modules = MigrationModuleRegistry.GetModules(parseResult.GetValue(moduleOption)).ToList();
if (!modules.Any())
{
throw new CommandLineException("No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames));
throw new CommandLineException(
"No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames));
}
var category = ParseCategory(parseResult.GetValue(categoryOption));
if (category == MigrationCategory.Release && parseResult.GetValue(dryRunOption) == false)
var dryRun = parseResult.GetValue(dryRunOption);
var force = parseResult.GetValue(forceOption);
if (category == MigrationCategory.Release && !dryRun && !force)
{
throw new CommandLineException("Release migrations require explicit approval; use --dry-run to preview or run approved release migrations manually.");
throw new CommandLineException(
"Release migrations require explicit approval; use --dry-run to preview or --force to execute.");
}
var connection = parseResult.GetValue(connectionOption);
var timeoutSeconds = parseResult.GetValue(timeoutOption);
var verbose = parseResult.GetValue(verboseOption);
var migrationService = services.GetRequiredService<MigrationCommandService>();
foreach (var module in modules)
{
var result = await migrationService
.RunAsync(module, connection, category, dryRun, timeoutSeconds, cancellationToken)
.ConfigureAwait(false);
WriteRunResult(module, result, verbose);
}
// TODO: wire MigrationRunnerAdapter to execute migrations per module/category.
await Task.CompletedTask;
});
var status = new Command("migrations-status", "Show migration status for the selected module(s).");
status.AddOption(moduleOption);
status.AddOption(categoryOption);
status.AddOption(connectionOption);
status.SetAction(async parseResult =>
{
var modules = MigrationModuleRegistry.GetModules(parseResult.GetValue(moduleOption)).ToList();
if (!modules.Any())
{
throw new CommandLineException("No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames));
throw new CommandLineException(
"No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames));
}
var connection = parseResult.GetValue(connectionOption);
var verbose = parseResult.GetValue(verboseOption);
var migrationService = services.GetRequiredService<MigrationCommandService>();
foreach (var module in modules)
{
var statusResult = await migrationService
.GetStatusAsync(module, connection, cancellationToken)
.ConfigureAwait(false);
WriteStatusResult(module, statusResult, verbose);
}
ParseCategory(parseResult.GetValue(categoryOption));
// TODO: wire MigrationRunnerAdapter to fetch status.
await Task.CompletedTask;
});
var verify = new Command("migrations-verify", "Verify migration checksums for the selected module(s).");
verify.AddOption(moduleOption);
verify.AddOption(categoryOption);
verify.AddOption(connectionOption);
verify.SetAction(async parseResult =>
{
var modules = MigrationModuleRegistry.GetModules(parseResult.GetValue(moduleOption)).ToList();
if (!modules.Any())
{
throw new CommandLineException("No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames));
throw new CommandLineException(
"No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames));
}
var connection = parseResult.GetValue(connectionOption);
var migrationService = services.GetRequiredService<MigrationCommandService>();
foreach (var module in modules)
{
var errors = await migrationService
.VerifyAsync(module, connection, cancellationToken)
.ConfigureAwait(false);
WriteVerifyResult(module, errors);
}
ParseCategory(parseResult.GetValue(categoryOption));
// TODO: wire MigrationRunnerAdapter to verify checksums.
await Task.CompletedTask;
});
var system = new Command("system", "System operations (migrations).");
@@ -85,4 +147,84 @@ internal static class SystemCommandBuilder
system.Add(verify);
return system;
}
private static void WriteRunResult(MigrationModuleInfo module, MigrationResult result, bool verbose)
{
var prefix = $"[{module.Name}]";
if (!result.Success)
{
Console.Error.WriteLine($"{prefix} FAILED: {result.ErrorMessage}");
foreach (var error in result.ChecksumErrors)
{
Console.Error.WriteLine($"{prefix} checksum: {error}");
}
if (Environment.ExitCode == 0)
{
Environment.ExitCode = 1;
}
return;
}
Console.WriteLine(
$"{prefix} applied={result.AppliedCount} skipped={result.SkippedCount} filtered={result.FilteredCount} duration_ms={result.DurationMs}");
if (verbose && result.AppliedMigrations.Count > 0)
{
foreach (var migration in result.AppliedMigrations.OrderBy(m => m.Name))
{
var mode = migration.WasDryRun ? "DRY-RUN" : "APPLIED";
Console.WriteLine($"{prefix} {mode}: {migration.Name} ({migration.Category}) {migration.DurationMs}ms");
}
}
}
private static void WriteStatusResult(MigrationModuleInfo module, MigrationStatus status, bool verbose)
{
var prefix = $"[{module.Name}]";
Console.WriteLine(
$"{prefix} applied={status.AppliedCount} pending_startup={status.PendingStartupCount} pending_release={status.PendingReleaseCount} checksum_errors={status.ChecksumErrors.Count}");
if (verbose)
{
foreach (var pending in status.PendingMigrations.OrderBy(p => p.Name))
{
Console.WriteLine($"{prefix} pending {pending.Category}: {pending.Name}");
}
foreach (var error in status.ChecksumErrors)
{
Console.WriteLine($"{prefix} checksum: {error}");
}
}
if (status.HasBlockingIssues && Environment.ExitCode == 0)
{
Environment.ExitCode = 1;
}
}
private static void WriteVerifyResult(MigrationModuleInfo module, IReadOnlyList<string> errors)
{
var prefix = $"[{module.Name}]";
if (errors.Count == 0)
{
Console.WriteLine($"{prefix} checksum verification passed.");
return;
}
Console.Error.WriteLine($"{prefix} checksum verification failed ({errors.Count}).");
foreach (var error in errors)
{
Console.Error.WriteLine($"{prefix} {error}");
}
if (Environment.ExitCode == 0)
{
Environment.ExitCode = 1;
}
}
}