Files
git.stella-ops.org/src/Tools/GoldenPairs/GoldenPairsApp.cs
2026-02-01 21:37:40 +02:00

321 lines
13 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using StellaOps.Tools.GoldenPairs.Models;
using StellaOps.Tools.GoldenPairs.Serialization;
using StellaOps.Tools.GoldenPairs.Services;
using System.Collections.Immutable;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Text;
namespace StellaOps.Tools.GoldenPairs;
public static class GoldenPairsApp
{
public static int RunAsync(string[] args)
{
var repoRootOption = new Option<DirectoryInfo?>("--repo-root")
{
Description = "Repository root (defaults to nearest folder containing src/StellaOps.sln)."
};
var datasetRootOption = new Option<DirectoryInfo?>("--dataset-root")
{
Description = "Dataset root (defaults to datasets/golden-pairs under repo root)."
};
var root = new RootCommand("Golden pairs corpus tooling.");
root.Add(repoRootOption);
root.Add(datasetRootOption);
root.Add(BuildMirrorCommand(repoRootOption, datasetRootOption));
root.Add(BuildDiffCommand(repoRootOption, datasetRootOption));
root.Add(BuildValidateCommand(repoRootOption, datasetRootOption));
var parseResult = root.Parse(args);
return parseResult.Invoke();
}
private static Command BuildMirrorCommand(Option<DirectoryInfo?> repoRootOption, Option<DirectoryInfo?> datasetRootOption)
{
var cveArgument = new Argument<string>("cve") { Description = "CVE identifier to mirror." };
var command = new Command("mirror", "Fetch artifacts for a golden pair.");
command.Add(cveArgument);
command.SetAction(async (parseResult, cancellationToken) =>
{
var context = ResolveContext(parseResult, repoRootOption, datasetRootOption);
if (context is null)
{
return 2;
}
using var provider = context.Provider;
var loader = provider.GetRequiredService<GoldenPairLoader>();
var mirror = provider.GetRequiredService<IPackageMirrorService>();
var layout = provider.GetRequiredService<GoldenPairLayout>();
var cve = parseResult.GetValue(cveArgument) ?? string.Empty;
var loadResult = await loader.LoadAsync(cve, cancellationToken).ConfigureAwait(false);
if (!loadResult.IsValid || loadResult.Metadata is null)
{
return ReportLoadErrors(loadResult.Errors);
}
var metadata = loadResult.Metadata;
Directory.CreateDirectory(layout.GetOriginalDirectory(metadata.Cve));
Directory.CreateDirectory(layout.GetPatchedDirectory(metadata.Cve));
var originalResult = await mirror.FetchAsync(metadata.Original, layout.GetOriginalDirectory(metadata.Cve), cancellationToken)
.ConfigureAwait(false);
if (!originalResult.Success)
{
Console.Error.WriteLine($"[FAIL] Original mirror failed: {originalResult.ErrorMessage}");
return 1;
}
var originalPath = EnsureArtifactNamed(metadata, layout.GetOriginalDirectory(metadata.Cve), originalResult.LocalPath);
WriteShaFile(originalPath, originalResult.ActualSha256);
var patchedResult = await mirror.FetchAsync(metadata.Patched, layout.GetPatchedDirectory(metadata.Cve), cancellationToken)
.ConfigureAwait(false);
if (!patchedResult.Success)
{
Console.Error.WriteLine($"[FAIL] Patched mirror failed: {patchedResult.ErrorMessage}");
return 1;
}
var patchedPath = EnsureArtifactNamed(metadata, layout.GetPatchedDirectory(metadata.Cve), patchedResult.LocalPath);
WriteShaFile(patchedPath, patchedResult.ActualSha256);
Console.WriteLine($"[OK] Mirrored {metadata.Cve} into {layout.GetPairDirectory(metadata.Cve)}");
return 0;
});
return command;
}
private static Command BuildDiffCommand(Option<DirectoryInfo?> repoRootOption, Option<DirectoryInfo?> datasetRootOption)
{
var cveArgument = new Argument<string>("cve") { Description = "CVE identifier to diff." };
var command = new Command("diff", "Run diff analysis on a golden pair.");
command.Add(cveArgument);
var outputOption = new Option<string>("--output")
{
Description = "Output format: json or table.",
DefaultValueFactory = _ => "json"
};
command.Add(outputOption);
command.SetAction(async (parseResult, cancellationToken) =>
{
var context = ResolveContext(parseResult, repoRootOption, datasetRootOption);
if (context is null)
{
return 2;
}
using var provider = context.Provider;
var loader = provider.GetRequiredService<GoldenPairLoader>();
var diff = provider.GetRequiredService<IDiffPipelineService>();
var layout = provider.GetRequiredService<GoldenPairLayout>();
var cve = parseResult.GetValue(cveArgument) ?? string.Empty;
var loadResult = await loader.LoadAsync(cve, cancellationToken).ConfigureAwait(false);
if (!loadResult.IsValid || loadResult.Metadata is null)
{
return ReportLoadErrors(loadResult.Errors);
}
GoldenDiffReport report;
try
{
report = await diff.DiffAsync(loadResult.Metadata, cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[FAIL] Diff failed: {ex.Message}");
return 1;
}
var reportJson = GoldenPairsJsonSerializer.SerializeIndented(report);
var reportPath = layout.GetDiffReportPath(loadResult.Metadata.Cve);
Directory.CreateDirectory(Path.GetDirectoryName(reportPath)!);
await File.WriteAllTextAsync(reportPath, reportJson, cancellationToken).ConfigureAwait(false);
var output = parseResult.GetValue(outputOption) ?? "json";
if (string.Equals(output, "table", StringComparison.OrdinalIgnoreCase))
{
WriteTableReport(report);
}
else
{
Console.WriteLine(reportJson);
}
Console.WriteLine($"[OK] Diff report written to {reportPath}");
return report.MatchesExpected ? 0 : 1;
});
return command;
}
private static Command BuildValidateCommand(Option<DirectoryInfo?> repoRootOption, Option<DirectoryInfo?> datasetRootOption)
{
var command = new Command("validate", "Validate all golden pairs in the corpus.");
var failFastOption = new Option<bool>("--fail-fast")
{
Description = "Stop at first failure."
};
command.Add(failFastOption);
command.SetAction(async (parseResult, cancellationToken) =>
{
var context = ResolveContext(parseResult, repoRootOption, datasetRootOption);
if (context is null)
{
return 2;
}
using var provider = context.Provider;
var loader = provider.GetRequiredService<GoldenPairLoader>();
var diff = provider.GetRequiredService<IDiffPipelineService>();
var layout = provider.GetRequiredService<GoldenPairLayout>();
var failFast = parseResult.GetValue(failFastOption);
var pairDirectories = Directory.EnumerateDirectories(layout.DatasetRoot, "CVE-*", SearchOption.TopDirectoryOnly)
.OrderBy(path => path, StringComparer.Ordinal)
.ToArray();
var failures = 0;
foreach (var pairDir in pairDirectories)
{
var metadataPath = Path.Combine(pairDir, GoldenPairLayout.DefaultMetadataFileName);
var loadResult = await loader.LoadFromPathAsync(metadataPath, cancellationToken).ConfigureAwait(false);
if (!loadResult.IsValid || loadResult.Metadata is null)
{
failures++;
Console.Error.WriteLine($"[FAIL] {Path.GetFileName(pairDir)}: metadata invalid.");
if (failFast)
{
return 1;
}
continue;
}
try
{
var report = await diff.DiffAsync(loadResult.Metadata, cancellationToken: cancellationToken).ConfigureAwait(false);
var reportJson = GoldenPairsJsonSerializer.SerializeIndented(report);
await File.WriteAllTextAsync(layout.GetDiffReportPath(loadResult.Metadata.Cve), reportJson, cancellationToken)
.ConfigureAwait(false);
if (report.MatchesExpected)
{
Console.WriteLine($"[OK] {loadResult.Metadata.Cve} validated.");
}
else
{
failures++;
Console.Error.WriteLine($"[FAIL] {loadResult.Metadata.Cve} mismatch.");
if (failFast)
{
return 1;
}
}
}
catch (Exception ex)
{
failures++;
Console.Error.WriteLine($"[FAIL] {loadResult.Metadata.Cve}: {ex.Message}");
if (failFast)
{
return 1;
}
}
}
Console.WriteLine($"Summary: {pairDirectories.Length - failures}/{pairDirectories.Length} passed.");
return failures == 0 ? 0 : 1;
});
return command;
}
private static GoldenPairsContext? ResolveContext(
ParseResult parseResult,
Option<DirectoryInfo?> repoRootOption,
Option<DirectoryInfo?> datasetRootOption)
{
var repoRoot = parseResult.GetValue(repoRootOption)?.FullName;
var datasetRoot = parseResult.GetValue(datasetRootOption)?.FullName;
var resolvedRepoRoot = GoldenPairsPaths.TryResolveRepoRoot(repoRoot);
if (resolvedRepoRoot is null)
{
Console.Error.WriteLine("[FAIL] Unable to resolve repo root. Provide --repo-root explicitly.");
return null;
}
var resolvedDatasetRoot = GoldenPairsPaths.ResolveDatasetRoot(resolvedRepoRoot, datasetRoot);
var metadataSchemaPath = GoldenPairsPaths.ResolveMetadataSchemaPath(resolvedRepoRoot);
var indexSchemaPath = GoldenPairsPaths.ResolveIndexSchemaPath(resolvedRepoRoot);
var provider = GoldenPairsServiceFactory.Build(resolvedDatasetRoot, metadataSchemaPath, indexSchemaPath);
return new GoldenPairsContext(resolvedRepoRoot, resolvedDatasetRoot, provider);
}
private static int ReportLoadErrors(ImmutableArray<GoldenPairLoadError> errors)
{
foreach (var error in errors)
{
var location = string.IsNullOrWhiteSpace(error.InstanceLocation) ? string.Empty : $" ({error.InstanceLocation})";
Console.Error.WriteLine($"[FAIL] {error.Message}{location}");
}
return 1;
}
private static string EnsureArtifactNamed(GoldenPairMetadata metadata, string destinationDirectory, string currentPath)
{
var expectedPath = Path.Combine(destinationDirectory, metadata.Artifact.Name);
if (string.Equals(currentPath, expectedPath, StringComparison.Ordinal))
{
return currentPath;
}
if (File.Exists(expectedPath))
{
File.Delete(expectedPath);
}
File.Move(currentPath, expectedPath);
return expectedPath;
}
private static void WriteShaFile(string filePath, string sha256)
{
var shaPath = $"{filePath}.sha256";
File.WriteAllText(shaPath, $"{sha256}\n", Encoding.ASCII);
}
private static void WriteTableReport(GoldenDiffReport report)
{
Console.WriteLine($"CVE: {report.Cve}");
Console.WriteLine($"Verdict: {report.Verdict.ToString().ToLowerInvariant()} ({report.Confidence:F2})");
Console.WriteLine($"Matches expected: {report.MatchesExpected}");
Console.WriteLine("Sections:");
foreach (var section in report.Sections)
{
Console.WriteLine($" {section.Name} - {section.Status.ToString().ToLowerInvariant()}");
}
}
private sealed record GoldenPairsContext(
string RepoRoot,
string DatasetRoot,
ServiceProvider Provider);
}