feat(cli): scadalink audit query subcommand (#23 M8)
This commit is contained in:
@@ -24,22 +24,164 @@ public static class AuditCommands
|
||||
|
||||
private static Command BuildQuery(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var sinceOption = new Option<string?>("--since") { Description = "Start time: relative (1h, 24h, 7d) or ISO-8601" };
|
||||
var untilOption = new Option<string?>("--until") { Description = "End time: relative (1h, 24h, 7d) or ISO-8601" };
|
||||
var channelOption = new Option<string?>("--channel") { Description = "Filter by channel (OutboundApi, OutboundDb, Notification, InboundApi)" };
|
||||
var kindOption = new Option<string?>("--kind") { Description = "Filter by event kind" };
|
||||
var statusOption = new Option<string?>("--status") { Description = "Filter by status (single value)" };
|
||||
var siteOption = new Option<string?>("--site") { Description = "Filter by source site ID" };
|
||||
var instanceOption = new Option<string?>("--instance") { Description = "Filter by instance" };
|
||||
var targetOption = new Option<string?>("--target") { Description = "Filter by target (external system, DB connection, notification list)" };
|
||||
var actorOption = new Option<string?>("--actor") { Description = "Filter by actor" };
|
||||
var correlationIdOption = new Option<string?>("--correlation-id") { Description = "Filter by correlation ID" };
|
||||
var errorsOnlyOption = new Option<bool>("--errors-only") { Description = "Show only failed events (status=Failed; overrides --status)" };
|
||||
var pageSizeOption = new Option<int>("--page-size") { Description = "Events per page (1-1000)" };
|
||||
pageSizeOption.DefaultValueFactory = _ => 100;
|
||||
var allOption = new Option<bool>("--all") { Description = "Fetch every page, following the keyset cursor" };
|
||||
|
||||
var cmd = new Command("query") { Description = "Query audit log events" };
|
||||
cmd.SetAction((ParseResult result) => 0);
|
||||
cmd.Add(sinceOption);
|
||||
cmd.Add(untilOption);
|
||||
cmd.Add(channelOption);
|
||||
cmd.Add(kindOption);
|
||||
cmd.Add(statusOption);
|
||||
cmd.Add(siteOption);
|
||||
cmd.Add(instanceOption);
|
||||
cmd.Add(targetOption);
|
||||
cmd.Add(actorOption);
|
||||
cmd.Add(correlationIdOption);
|
||||
cmd.Add(errorsOnlyOption);
|
||||
cmd.Add(pageSizeOption);
|
||||
cmd.Add(allOption);
|
||||
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var connection = AuditCommandHelpers.ResolveConnection(result, urlOption, usernameOption, passwordOption);
|
||||
if (connection.Error != null)
|
||||
{
|
||||
OutputFormatter.WriteError(connection.Error, connection.ErrorCode!);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var format = AuditCommandHelpers.ResolveFormat(result, formatOption);
|
||||
var formatter = AuditFormatterFactory.Create(format, Console.Error);
|
||||
|
||||
var args = new AuditQueryArgs
|
||||
{
|
||||
Since = result.GetValue(sinceOption),
|
||||
Until = result.GetValue(untilOption),
|
||||
Channel = result.GetValue(channelOption),
|
||||
Kind = result.GetValue(kindOption),
|
||||
Status = result.GetValue(statusOption),
|
||||
Site = result.GetValue(siteOption),
|
||||
Instance = result.GetValue(instanceOption),
|
||||
Target = result.GetValue(targetOption),
|
||||
Actor = result.GetValue(actorOption),
|
||||
CorrelationId = result.GetValue(correlationIdOption),
|
||||
ErrorsOnly = result.GetValue(errorsOnlyOption),
|
||||
PageSize = result.GetValue(pageSizeOption),
|
||||
};
|
||||
var fetchAll = result.GetValue(allOption);
|
||||
|
||||
try
|
||||
{
|
||||
using var client = new ManagementHttpClient(connection.Url!, connection.Username!, connection.Password!);
|
||||
return await AuditQueryHelpers.RunQueryAsync(
|
||||
client, args, fetchAll, formatter, Console.Out, DateTimeOffset.UtcNow);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
OutputFormatter.WriteError(ex.Message, "INVALID_ARGUMENT");
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildExport(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var sinceOption = new Option<string>("--since") { Description = "Start time: relative (1h, 24h, 7d) or ISO-8601", Required = true };
|
||||
var untilOption = new Option<string>("--until") { Description = "End time: relative (1h, 24h, 7d) or ISO-8601", Required = true };
|
||||
var formatExportOption = new Option<string>("--format") { Description = "Export format", Required = true };
|
||||
formatExportOption.AcceptOnlyFromAmong("csv", "jsonl", "parquet");
|
||||
var outputOption = new Option<string>("--output") { Description = "Destination file path", Required = true };
|
||||
var channelOption = new Option<string?>("--channel") { Description = "Filter by channel" };
|
||||
var kindOption = new Option<string?>("--kind") { Description = "Filter by event kind" };
|
||||
var statusOption = new Option<string?>("--status") { Description = "Filter by status" };
|
||||
var siteOption = new Option<string?>("--site") { Description = "Filter by source site ID" };
|
||||
var targetOption = new Option<string?>("--target") { Description = "Filter by target" };
|
||||
var actorOption = new Option<string?>("--actor") { Description = "Filter by actor" };
|
||||
|
||||
var cmd = new Command("export") { Description = "Export audit log events to a file" };
|
||||
cmd.SetAction((ParseResult result) => 0);
|
||||
cmd.Add(sinceOption);
|
||||
cmd.Add(untilOption);
|
||||
cmd.Add(formatExportOption);
|
||||
cmd.Add(outputOption);
|
||||
cmd.Add(channelOption);
|
||||
cmd.Add(kindOption);
|
||||
cmd.Add(statusOption);
|
||||
cmd.Add(siteOption);
|
||||
cmd.Add(targetOption);
|
||||
cmd.Add(actorOption);
|
||||
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var connection = AuditCommandHelpers.ResolveConnection(result, urlOption, usernameOption, passwordOption);
|
||||
if (connection.Error != null)
|
||||
{
|
||||
OutputFormatter.WriteError(connection.Error, connection.ErrorCode!);
|
||||
return 1;
|
||||
}
|
||||
|
||||
var args = new AuditExportArgs
|
||||
{
|
||||
Since = result.GetValue(sinceOption)!,
|
||||
Until = result.GetValue(untilOption)!,
|
||||
Format = result.GetValue(formatExportOption)!,
|
||||
Output = result.GetValue(outputOption)!,
|
||||
Channel = result.GetValue(channelOption),
|
||||
Kind = result.GetValue(kindOption),
|
||||
Status = result.GetValue(statusOption),
|
||||
Site = result.GetValue(siteOption),
|
||||
Target = result.GetValue(targetOption),
|
||||
Actor = result.GetValue(actorOption),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var client = new ManagementHttpClient(connection.Url!, connection.Username!, connection.Password!);
|
||||
return await AuditExportHelpers.RunExportAsync(client, args, Console.Out, DateTimeOffset.UtcNow);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
OutputFormatter.WriteError(ex.Message, "INVALID_ARGUMENT");
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildVerifyChain(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var monthOption = new Option<string>("--month") { Description = "Month to verify (YYYY-MM)", Required = true };
|
||||
|
||||
var cmd = new Command("verify-chain") { Description = "Verify the audit log hash chain for a month" };
|
||||
cmd.SetAction((ParseResult result) => 0);
|
||||
cmd.Add(monthOption);
|
||||
cmd.SetAction((ParseResult result) =>
|
||||
{
|
||||
var month = result.GetValue(monthOption)!;
|
||||
if (!AuditVerifyChainHelpers.IsValidMonth(month))
|
||||
{
|
||||
OutputFormatter.WriteError(
|
||||
$"Invalid month '{month}'. Expected YYYY-MM (e.g. 2026-05).", "INVALID_ARGUMENT");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
"Hash-chain tamper-evidence is not enabled in this release. "
|
||||
+ "See Component-AuditLog.md (Security & Tamper-Evidence) for the v1.x roadmap.");
|
||||
return 0;
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user