// // Copyright (c) Stella Operations. Licensed under BUSL-1.1. // using System.CommandLine; using StellaOps.Testing.FixtureHarvester.Commands; namespace StellaOps.Testing.FixtureHarvester; /// /// Fixture Harvester CLI entry point /// internal static class Program { internal static async Task Main(string[] args) { var rootCommand = new RootCommand("Stella Ops Fixture Harvester - Acquire, curate, and pin test fixtures"); // Harvest command var harvestCommand = new Command("harvest", "Harvest and store a fixture with metadata"); var harvestTypeOption = new Option("--type") { Description = "Fixture type: sbom, feed, vex", Required = true }; var harvestIdOption = new Option("--id") { Description = "Unique fixture identifier", Required = true }; var harvestSourceOption = new Option("--source") { Description = "Source URL or path" }; var harvestOutputOption = new Option("--output") { Description = "Output directory", DefaultValueFactory = _ => "src/__Tests/fixtures" }; harvestCommand.Add(harvestTypeOption); harvestCommand.Add(harvestIdOption); harvestCommand.Add(harvestSourceOption); harvestCommand.Add(harvestOutputOption); harvestCommand.SetAction((parseResult, _) => { var type = parseResult.GetValue(harvestTypeOption) ?? string.Empty; var id = parseResult.GetValue(harvestIdOption) ?? string.Empty; var source = parseResult.GetValue(harvestSourceOption); var output = parseResult.GetValue(harvestOutputOption) ?? "src/__Tests/fixtures"; return HarvestCommand.ExecuteAsync(type, id, source, output); }); // Validate command var validateCommand = new Command("validate", "Validate fixtures against manifest"); var validatePathOption = new Option("--path") { Description = "Fixtures directory path", DefaultValueFactory = _ => "src/__Tests/fixtures" }; validateCommand.Add(validatePathOption); validateCommand.SetAction((parseResult, _) => { var path = parseResult.GetValue(validatePathOption) ?? "src/__Tests/fixtures"; return ValidateCommand.ExecuteAsync(path); }); // Regen command var regenCommand = new Command("regen", "Regenerate expected outputs (manual, use with caution)"); var regenFixtureOption = new Option("--fixture") { Description = "Fixture ID to regenerate" }; var regenAllOption = new Option("--all") { Description = "Regenerate all fixtures", DefaultValueFactory = _ => false }; var regenConfirmOption = new Option("--confirm") { Description = "Confirm regeneration", DefaultValueFactory = _ => false }; regenCommand.Add(regenFixtureOption); regenCommand.Add(regenAllOption); regenCommand.Add(regenConfirmOption); regenCommand.SetAction((parseResult, _) => { var fixture = parseResult.GetValue(regenFixtureOption); var all = parseResult.GetValue(regenAllOption); var confirm = parseResult.GetValue(regenConfirmOption); return RegenCommand.ExecuteAsync(fixture, all, confirm); }); // OCI Pin command (FH-004) var ociPinCommand = new Command("oci-pin", "Pin OCI image digests for deterministic testing"); var ociImageOption = new Option("--image") { Description = "Image reference (e.g., alpine:3.19, myregistry.io/app:v1)", Required = true }; var ociOutputOption = new Option("--output") { Description = "Output directory", DefaultValueFactory = _ => "src/__Tests/fixtures/oci" }; var ociVerifyOption = new Option("--verify") { Description = "Verify digest by re-fetching manifest", DefaultValueFactory = _ => true }; ociPinCommand.Add(ociImageOption); ociPinCommand.Add(ociOutputOption); ociPinCommand.Add(ociVerifyOption); ociPinCommand.SetAction((parseResult, _) => { var image = parseResult.GetValue(ociImageOption) ?? string.Empty; var output = parseResult.GetValue(ociOutputOption) ?? "src/__Tests/fixtures/oci"; var verify = parseResult.GetValue(ociVerifyOption); return OciPinCommand.ExecuteAsync(image, output, verify); }); // Feed Snapshot command (FH-005) var feedSnapshotCommand = new Command("feed-snapshot", "Capture vulnerability feed snapshots"); var feedTypeOption = new Option("--feed") { Description = "Feed type: osv, ghsa, nvd, epss, kev, oval", Required = true }; var feedUrlOption = new Option("--url") { Description = "Concelier base URL", DefaultValueFactory = _ => "http://localhost:5010" }; var feedCountOption = new Option("--count") { Description = "Number of advisories to capture", DefaultValueFactory = _ => 30 }; var feedOutputOption = new Option("--output") { Description = "Output directory", DefaultValueFactory = _ => "src/__Tests/fixtures/feeds" }; feedSnapshotCommand.Add(feedTypeOption); feedSnapshotCommand.Add(feedUrlOption); feedSnapshotCommand.Add(feedCountOption); feedSnapshotCommand.Add(feedOutputOption); feedSnapshotCommand.SetAction((parseResult, _) => { var feed = parseResult.GetValue(feedTypeOption) ?? string.Empty; var url = parseResult.GetValue(feedUrlOption) ?? "http://localhost:5010"; var count = parseResult.GetValue(feedCountOption); var output = parseResult.GetValue(feedOutputOption) ?? "src/__Tests/fixtures/feeds"; return FeedSnapshotCommand.ExecuteAsync(feed, url, count, output); }); // VEX Source command (FH-006) var vexSourceCommand = new Command("vex", "Acquire OpenVEX and CSAF samples"); var vexSourceArg = new Argument("source") { Description = "Source name (list, all, openvex-examples, csaf-redhat, alpine-secdb) or 'list' to see all" }; var vexCustomUrlOption = new Option("--url") { Description = "Custom VEX document URL" }; var vexOutputOption = new Option("--output") { Description = "Output directory", DefaultValueFactory = _ => "src/__Tests/fixtures/vex" }; vexSourceCommand.Add(vexSourceArg); vexSourceCommand.Add(vexCustomUrlOption); vexSourceCommand.Add(vexOutputOption); vexSourceCommand.SetAction((parseResult, _) => { var source = parseResult.GetValue(vexSourceArg) ?? string.Empty; var url = parseResult.GetValue(vexCustomUrlOption); var output = parseResult.GetValue(vexOutputOption) ?? "src/__Tests/fixtures/vex"; return VexSourceCommand.ExecuteAsync(source, url, output); }); // SBOM Golden command (FH-007) var sbomGoldenCommand = new Command("sbom-golden", "Generate SBOM golden fixtures from container images"); var sbomImageArg = new Argument("image") { Description = "Image key (list, all, alpine-minimal, debian-slim, distroless-static) or custom image ref" }; var sbomFormatOption = new Option("--format") { Description = "SBOM format: cyclonedx, spdx", DefaultValueFactory = _ => "cyclonedx" }; var sbomScannerOption = new Option("--scanner") { Description = "Scanner tool: syft, trivy", DefaultValueFactory = _ => "syft" }; var sbomOutputOption = new Option("--output") { Description = "Output directory", DefaultValueFactory = _ => "src/__Tests/fixtures/sbom" }; sbomGoldenCommand.Add(sbomImageArg); sbomGoldenCommand.Add(sbomFormatOption); sbomGoldenCommand.Add(sbomScannerOption); sbomGoldenCommand.Add(sbomOutputOption); sbomGoldenCommand.SetAction((parseResult, _) => { var image = parseResult.GetValue(sbomImageArg) ?? string.Empty; var format = parseResult.GetValue(sbomFormatOption) ?? "cyclonedx"; var scanner = parseResult.GetValue(sbomScannerOption) ?? "syft"; var output = parseResult.GetValue(sbomOutputOption) ?? "src/__Tests/fixtures/sbom"; return SbomGoldenCommand.ExecuteAsync(image, format, scanner, output); }); rootCommand.Add(harvestCommand); rootCommand.Add(validateCommand); rootCommand.Add(regenCommand); rootCommand.Add(ociPinCommand); rootCommand.Add(feedSnapshotCommand); rootCommand.Add(vexSourceCommand); rootCommand.Add(sbomGoldenCommand); return await rootCommand.Parse(args).InvokeAsync(); } }