Complete management API and CLI command parity
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Text.Json;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Builds the <c>secured-write</c> command group. Currently exposes the read-only
|
||||
/// <c>list</c> sub-command, which relays a <see cref="ListSecuredWritesCommand"/> to the
|
||||
/// central <c>ManagementActor</c> (gated any-of <c>Operator</c> / <c>Verifier</c> /
|
||||
/// <c>Administrator</c>). The list is offset-paged via <c>--skip</c> / <c>--take</c>
|
||||
/// (arch-review P3); omitting them preserves the historical unpaged default (first 200 rows).
|
||||
/// Builds the two-person secured-write workflow. Submission requires an Operator;
|
||||
/// approve/reject require a distinct Verifier. Roles, site scope, expiry, and
|
||||
/// separation of duties are enforced by the management server.
|
||||
/// </summary>
|
||||
public static class SecuredWriteCommands
|
||||
{
|
||||
@@ -24,7 +23,7 @@ public static class SecuredWriteCommands
|
||||
private static readonly Option<int> TakeOption =
|
||||
new("--take") { Description = "Maximum rows to return (clamped to 1..500 server-side)", DefaultValueFactory = _ => 200 };
|
||||
|
||||
/// <summary>Builds the <c>secured-write</c> command group with its <c>list</c> sub-command.</summary>
|
||||
/// <summary>Builds the <c>secured-write</c> command group with list, submit, approve, and reject subcommands.</summary>
|
||||
/// <param name="urlOption">Global <c>--url</c> option for the management API endpoint.</param>
|
||||
/// <param name="formatOption">Global <c>--format</c> option for output format.</param>
|
||||
/// <param name="usernameOption">Global <c>--username</c> option for authentication.</param>
|
||||
@@ -32,8 +31,11 @@ public static class SecuredWriteCommands
|
||||
/// <returns>The configured <c>secured-write</c> command.</returns>
|
||||
public static Command Build(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var command = new Command("secured-write") { Description = "Query two-person (MxGateway) secured writes" };
|
||||
var command = new Command("secured-write") { Description = "Manage two-person (MxGateway) secured writes" };
|
||||
command.Add(BuildList(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildSubmit(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildDecision("approve", urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildDecision("reject", urlOption, formatOption, usernameOption, passwordOption));
|
||||
return command;
|
||||
}
|
||||
|
||||
@@ -53,6 +55,84 @@ public static class SecuredWriteCommands
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildSubmit(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var site = new Option<string>("--site") { Description = "Target site identifier", Required = true };
|
||||
var connection = new Option<string>("--connection") { Description = "MxGateway connection name", Required = true };
|
||||
var tag = new Option<string>("--tag-path") { Description = "Fully-qualified tag path", Required = true };
|
||||
var value = new Option<string>("--value-json") { Description = "JSON-serialized value (quote JSON strings)", Required = true };
|
||||
var type = new Option<string>("--value-type") { Description = "Target data type name, e.g. Boolean or Double", Required = true };
|
||||
var comment = new Option<string?>("--comment") { Description = "Operator comment" };
|
||||
var command = new Command("submit") { Description = "Submit a pending secured write as an Operator for a distinct Verifier" };
|
||||
command.Add(site); command.Add(connection); command.Add(tag); command.Add(value); command.Add(type); command.Add(comment);
|
||||
command.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var json = result.GetValue(value)!;
|
||||
try
|
||||
{
|
||||
using var parsed = JsonDocument.Parse(json);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
OutputFormatter.WriteError($"Invalid value JSON: {ex.Message}", "INVALID_JSON");
|
||||
return 1;
|
||||
}
|
||||
return await CommandHelpers.ExecuteCommandAsync(result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new SubmitSecuredWriteCommand(result.GetValue(site)!, result.GetValue(connection)!, result.GetValue(tag)!, json,
|
||||
result.GetValue(type)!, result.GetValue(comment)));
|
||||
});
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildDecision(string verb, Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var id = new Option<long>("--id") { Description = "Pending secured-write ID", Required = true };
|
||||
var comment = new Option<string?>("--comment") { Description = "Verifier comment" };
|
||||
var command = new Command(verb)
|
||||
{
|
||||
Description = verb == "approve"
|
||||
? "Approve and execute a pending write as a distinct Verifier"
|
||||
: "Reject a pending write as a distinct Verifier"
|
||||
};
|
||||
command.Add(id); command.Add(comment);
|
||||
command.SetAction((ParseResult result) =>
|
||||
{
|
||||
object request = verb == "approve"
|
||||
? new ApproveSecuredWriteCommand(result.GetValue(id), result.GetValue(comment))
|
||||
: new RejectSecuredWriteCommand(result.GetValue(id), result.GetValue(comment));
|
||||
return CommandHelpers.ExecuteCommandAsync(result, urlOption, formatOption, usernameOption, passwordOption, request,
|
||||
onSuccess: verb == "approve"
|
||||
? json => HandleApprovalResponse(json, CommandHelpers.ResolveFormat(result, formatOption, CliConfig.Load()))
|
||||
: null);
|
||||
});
|
||||
return command;
|
||||
}
|
||||
|
||||
private static int HandleApprovalResponse(string json, string format)
|
||||
{
|
||||
CommandHelpers.HandleResponse(new ManagementResponse(200, json, null, null), format);
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(json);
|
||||
var root = document.RootElement;
|
||||
if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty("status", out var status)
|
||||
&& status.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
if (status.GetString() == "Executed") return 0;
|
||||
if (status.GetString() == "Failed")
|
||||
{
|
||||
var detail = root.TryGetProperty("executionError", out var error) && error.ValueKind == JsonValueKind.String
|
||||
? error.GetString() : null;
|
||||
OutputFormatter.WriteError(detail ?? "The server reported that the secured write failed.", "SECURED_WRITE_FAILED");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (JsonException) { }
|
||||
OutputFormatter.WriteError("The server returned an invalid secured-write approval outcome.", "INVALID_RESPONSE");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a <see cref="ListSecuredWritesCommand"/> from a parsed <c>secured-write list</c>
|
||||
/// invocation. Exposed internally so command-construction tests can assert field mapping
|
||||
|
||||
Reference in New Issue
Block a user