Files
git.stella-ops.org/src/__Tests/Tools/FixtureHarvester/Program.cs

235 lines
9.4 KiB
C#

// <copyright file="Program.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using System.CommandLine;
using StellaOps.Testing.FixtureHarvester.Commands;
namespace StellaOps.Testing.FixtureHarvester;
/// <summary>
/// Fixture Harvester CLI entry point
/// </summary>
internal static class Program
{
internal static async Task<int> 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<string>("--type")
{
Description = "Fixture type: sbom, feed, vex",
Required = true
};
var harvestIdOption = new Option<string>("--id")
{
Description = "Unique fixture identifier",
Required = true
};
var harvestSourceOption = new Option<string>("--source")
{
Description = "Source URL or path"
};
var harvestOutputOption = new Option<string>("--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<string>("--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<string>("--fixture")
{
Description = "Fixture ID to regenerate"
};
var regenAllOption = new Option<bool>("--all")
{
Description = "Regenerate all fixtures",
DefaultValueFactory = _ => false
};
var regenConfirmOption = new Option<bool>("--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<string>("--image")
{
Description = "Image reference (e.g., alpine:3.19, myregistry.io/app:v1)",
Required = true
};
var ociOutputOption = new Option<string>("--output")
{
Description = "Output directory",
DefaultValueFactory = _ => "src/__Tests/fixtures/oci"
};
var ociVerifyOption = new Option<bool>("--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<string>("--feed")
{
Description = "Feed type: osv, ghsa, nvd, epss, kev, oval",
Required = true
};
var feedUrlOption = new Option<string>("--url")
{
Description = "Concelier base URL",
DefaultValueFactory = _ => "http://localhost:5010"
};
var feedCountOption = new Option<int>("--count")
{
Description = "Number of advisories to capture",
DefaultValueFactory = _ => 30
};
var feedOutputOption = new Option<string>("--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<string>("source")
{
Description = "Source name (list, all, openvex-examples, csaf-redhat, alpine-secdb) or 'list' to see all"
};
var vexCustomUrlOption = new Option<string>("--url")
{
Description = "Custom VEX document URL"
};
var vexOutputOption = new Option<string>("--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<string>("image")
{
Description = "Image key (list, all, alpine-minimal, debian-slim, distroless-static) or custom image ref"
};
var sbomFormatOption = new Option<string>("--format")
{
Description = "SBOM format: cyclonedx, spdx",
DefaultValueFactory = _ => "cyclonedx"
};
var sbomScannerOption = new Option<string>("--scanner")
{
Description = "Scanner tool: syft, trivy",
DefaultValueFactory = _ => "syft"
};
var sbomOutputOption = new Option<string>("--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();
}
}