Files
git.stella-ops.org/src/Cli/StellaOps.Cli/Commands/ReachGraph/ReachGraphCommandGroup.cs

180 lines
5.5 KiB
C#

// Licensed to StellaOps under the AGPL-3.0-or-later license.
using System.CommandLine;
namespace StellaOps.Cli.Commands.ReachGraph;
/// <summary>
/// CLI command group for reachability graph operations.
/// stella reachgraph [slice|replay|verify]
/// </summary>
public static class ReachGraphCommandGroup
{
public static Command Build()
{
var command = new Command("reachgraph", "Reachability graph operations");
command.Add(BuildSliceCommand());
command.Add(BuildReplayCommand());
command.Add(BuildVerifyCommand());
return command;
}
private static Command BuildSliceCommand()
{
var digestOption = new Option<string>("--digest", "-d")
{
Description = "BLAKE3 digest of the graph",
Required = true
};
var cveOption = new Option<string?>("--cve")
{
Description = "CVE identifier to slice by"
};
var purlOption = new Option<string?>("--purl", "-p")
{
Description = "Package PURL pattern to slice by"
};
var entrypointOption = new Option<string?>("--entrypoint", "-e")
{
Description = "Entrypoint path or symbol pattern"
};
var fileOption = new Option<string?>("--file", "-f")
{
Description = "File path pattern (glob) to slice by"
};
var depthOption = new Option<int>("--depth")
{
Description = "Max traversal depth"
}.SetDefaultValue(3);
var outputOption = new Option<string>("--output", "-o")
{
Description = "Output format: json, table, or dot (GraphViz)"
}.SetDefaultValue("table");
var apiUrlOption = new Option<string>("--api-url")
{
Description = "ReachGraph Store API URL"
}.SetDefaultValue("http://localhost:5000");
var command = new Command("slice", "Query a slice of a reachability graph")
{
digestOption,
cveOption,
purlOption,
entrypointOption,
fileOption,
depthOption,
outputOption,
apiUrlOption
};
command.SetAction(async (parseResult, ct) =>
{
var digest = parseResult.GetValue(digestOption) ?? string.Empty;
var cve = parseResult.GetValue(cveOption);
var purl = parseResult.GetValue(purlOption);
var entrypoint = parseResult.GetValue(entrypointOption);
var file = parseResult.GetValue(fileOption);
var depth = parseResult.GetValue(depthOption);
var output = parseResult.GetValue(outputOption) ?? "table";
var apiUrl = parseResult.GetValue(apiUrlOption) ?? "http://localhost:5000";
await ReachGraphCommandHandlers.HandleSliceAsync(
digest, cve, purl, entrypoint, file, depth, output, apiUrl);
});
return command;
}
private static Command BuildReplayCommand()
{
var inputsOption = new Option<string>("--inputs", "-i")
{
Description = "Comma-separated input files (sbom.json,vex.json,callgraph.json)",
Required = true
};
var expectedOption = new Option<string>("--expected", "-e")
{
Description = "Expected BLAKE3 digest",
Required = true
};
var outputFileOption = new Option<string?>("--output-file", "-o")
{
Description = "Write computed graph to file"
};
var verboseOption = new Option<bool>("--verbose", "-v")
{
Description = "Show verbose output"
};
var apiUrlOption = new Option<string>("--api-url")
{
Description = "ReachGraph Store API URL"
}.SetDefaultValue("http://localhost:5000");
var command = new Command("replay", "Verify deterministic replay of a graph")
{
inputsOption,
expectedOption,
outputFileOption,
verboseOption,
apiUrlOption
};
command.SetAction(async (parseResult, ct) =>
{
var inputs = parseResult.GetValue(inputsOption) ?? string.Empty;
var expected = parseResult.GetValue(expectedOption) ?? string.Empty;
var outputFile = parseResult.GetValue(outputFileOption);
var verbose = parseResult.GetValue(verboseOption);
var apiUrl = parseResult.GetValue(apiUrlOption) ?? "http://localhost:5000";
await ReachGraphCommandHandlers.HandleReplayAsync(
inputs, expected, outputFile, verbose, apiUrl);
});
return command;
}
private static Command BuildVerifyCommand()
{
var digestOption = new Option<string>("--digest", "-d")
{
Description = "BLAKE3 digest of the graph to verify",
Required = true
};
var apiUrlOption = new Option<string>("--api-url")
{
Description = "ReachGraph Store API URL"
}.SetDefaultValue("http://localhost:5000");
var command = new Command("verify", "Verify signatures on a reachability graph")
{
digestOption,
apiUrlOption
};
command.SetAction(async (parseResult, ct) =>
{
var digest = parseResult.GetValue(digestOption) ?? string.Empty;
var apiUrl = parseResult.GetValue(apiUrlOption) ?? "http://localhost:5000";
await ReachGraphCommandHandlers.HandleVerifyAsync(digest, apiUrl);
});
return command;
}
}