refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
||||
|
||||
public static class InstanceCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the instance command and its subcommands.
|
||||
/// </summary>
|
||||
/// <param name="urlOption">The URL option.</param>
|
||||
/// <param name="formatOption">The format option.</param>
|
||||
/// <param name="usernameOption">The username option.</param>
|
||||
/// <param name="passwordOption">The password option.</param>
|
||||
/// <returns>The instance command with all subcommands.</returns>
|
||||
public static Command Build(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var command = new Command("instance") { Description = "Manage instances" };
|
||||
|
||||
command.Add(BuildList(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildGet(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildCreate(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildSetBindings(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildSetOverrides(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildAlarmOverride(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildSetArea(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildDiff(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildDeploy(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildEnable(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildDisable(urlOption, formatOption, usernameOption, passwordOption));
|
||||
command.Add(BuildDelete(urlOption, formatOption, usernameOption, passwordOption));
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Command BuildGet(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var cmd = new Command("get") { Description = "Get an instance by ID" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption, new GetInstanceCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildSetBindings(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var bindingsOption = new Option<string>("--bindings") { Description = "JSON array of [attributeName, dataConnectionId] pairs", Required = true };
|
||||
|
||||
var cmd = new Command("set-bindings") { Description = "Set data connection bindings for an instance" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(bindingsOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var bindingsJson = result.GetValue(bindingsOption)!;
|
||||
if (!TryParseBindings(bindingsJson, out var bindings, out var error))
|
||||
{
|
||||
OutputFormatter.WriteError(error!, "INVALID_ARGUMENT");
|
||||
return 1;
|
||||
}
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new SetConnectionBindingsCommand(id, bindings!));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the <c>--bindings</c> argument — a JSON array of
|
||||
/// <c>[attributeName, dataConnectionId]</c> pairs — into a typed list.
|
||||
/// Returns <c>false</c> with a descriptive <paramref name="error"/> instead of
|
||||
/// throwing when the JSON is malformed, a pair has the wrong arity, or an element
|
||||
/// has the wrong type.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON string to parse.</param>
|
||||
/// <param name="bindings">The parsed bindings list, or null if parsing fails.</param>
|
||||
/// <param name="error">The error message if parsing fails, or null on success.</param>
|
||||
/// <returns>True if parsing succeeded; false otherwise.</returns>
|
||||
internal static bool TryParseBindings(
|
||||
string json,
|
||||
out List<ConnectionBinding>? bindings,
|
||||
out string? error)
|
||||
{
|
||||
bindings = null;
|
||||
error = null;
|
||||
try
|
||||
{
|
||||
var pairs = System.Text.Json.JsonSerializer
|
||||
.Deserialize<List<List<System.Text.Json.JsonElement>>>(json);
|
||||
if (pairs == null)
|
||||
{
|
||||
error = "Bindings JSON must be a non-null array of [attributeName, dataConnectionId] pairs.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = new List<ConnectionBinding>(pairs.Count);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
if (pair.Count != 2)
|
||||
{
|
||||
error = "Each binding must be a [attributeName, dataConnectionId] pair of exactly two elements.";
|
||||
return false;
|
||||
}
|
||||
if (pair[0].ValueKind != System.Text.Json.JsonValueKind.String)
|
||||
{
|
||||
error = "The first element of each binding (attributeName) must be a string.";
|
||||
return false;
|
||||
}
|
||||
if (pair[1].ValueKind != System.Text.Json.JsonValueKind.Number
|
||||
|| !pair[1].TryGetInt32(out var connectionId))
|
||||
{
|
||||
error = "The second element of each binding (dataConnectionId) must be an integer.";
|
||||
return false;
|
||||
}
|
||||
result.Add(new ConnectionBinding(pair[0].GetString()!, connectionId));
|
||||
}
|
||||
|
||||
bindings = result;
|
||||
return true;
|
||||
}
|
||||
catch (System.Text.Json.JsonException ex)
|
||||
{
|
||||
error = $"Invalid bindings JSON: {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the <c>--overrides</c> argument — a JSON object of
|
||||
/// <c>attributeName -> value</c> pairs — into a typed dictionary. Returns
|
||||
/// <c>false</c> with a descriptive <paramref name="error"/> instead of throwing
|
||||
/// when the JSON is malformed or null.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON string to parse.</param>
|
||||
/// <param name="overrides">The parsed overrides dictionary, or null if parsing fails.</param>
|
||||
/// <param name="error">The error message if parsing fails, or null on success.</param>
|
||||
/// <returns>True if parsing succeeded; false otherwise.</returns>
|
||||
internal static bool TryParseOverrides(
|
||||
string json,
|
||||
out Dictionary<string, string?>? overrides,
|
||||
out string? error)
|
||||
{
|
||||
overrides = null;
|
||||
error = null;
|
||||
try
|
||||
{
|
||||
var parsed = System.Text.Json.JsonSerializer
|
||||
.Deserialize<Dictionary<string, string?>>(json);
|
||||
if (parsed == null)
|
||||
{
|
||||
error = "Overrides JSON must be a non-null object of attribute name -> value pairs.";
|
||||
return false;
|
||||
}
|
||||
overrides = parsed;
|
||||
return true;
|
||||
}
|
||||
catch (System.Text.Json.JsonException ex)
|
||||
{
|
||||
error = $"Invalid overrides JSON: {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Command BuildList(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var siteIdOption = new Option<int?>("--site-id") { Description = "Filter by site ID" };
|
||||
var templateIdOption = new Option<int?>("--template-id") { Description = "Filter by template ID" };
|
||||
var searchOption = new Option<string?>("--search") { Description = "Search term" };
|
||||
|
||||
var cmd = new Command("list") { Description = "List instances" };
|
||||
cmd.Add(siteIdOption);
|
||||
cmd.Add(templateIdOption);
|
||||
cmd.Add(searchOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var siteId = result.GetValue(siteIdOption);
|
||||
var templateId = result.GetValue(templateIdOption);
|
||||
var search = result.GetValue(searchOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new ListInstancesCommand(siteId, templateId, search));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildCreate(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var nameOption = new Option<string>("--name") { Description = "Unique instance name", Required = true };
|
||||
var templateIdOption = new Option<int>("--template-id") { Description = "Template ID", Required = true };
|
||||
var siteIdOption = new Option<int>("--site-id") { Description = "Site ID", Required = true };
|
||||
var areaIdOption = new Option<int?>("--area-id") { Description = "Area ID" };
|
||||
|
||||
var cmd = new Command("create") { Description = "Create a new instance" };
|
||||
cmd.Add(nameOption);
|
||||
cmd.Add(templateIdOption);
|
||||
cmd.Add(siteIdOption);
|
||||
cmd.Add(areaIdOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var name = result.GetValue(nameOption)!;
|
||||
var templateId = result.GetValue(templateIdOption);
|
||||
var siteId = result.GetValue(siteIdOption);
|
||||
var areaId = result.GetValue(areaIdOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new CreateInstanceCommand(name, templateId, siteId, areaId));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDeploy(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var cmd = new Command("deploy") { Description = "Deploy an instance" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption, new MgmtDeployInstanceCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildEnable(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var cmd = new Command("enable") { Description = "Enable an instance" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption, new MgmtEnableInstanceCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDisable(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var cmd = new Command("disable") { Description = "Disable an instance" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption, new MgmtDisableInstanceCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDelete(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var cmd = new Command("delete") { Description = "Delete an instance" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption, new MgmtDeleteInstanceCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildSetOverrides(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var overridesOption = new Option<string>("--overrides") { Description = "JSON object of attribute name -> value pairs, e.g. {\"Speed\": \"100\", \"Mode\": null}", Required = true };
|
||||
|
||||
var cmd = new Command("set-overrides") { Description = "Set attribute overrides for an instance" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(overridesOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var overridesJson = result.GetValue(overridesOption)!;
|
||||
if (!TryParseOverrides(overridesJson, out var overrides, out var error))
|
||||
{
|
||||
OutputFormatter.WriteError(error!, "INVALID_ARGUMENT");
|
||||
return 1;
|
||||
}
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new SetInstanceOverridesCommand(id, overrides!));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildAlarmOverride(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var group = new Command("alarm-override") { Description = "Manage per-instance alarm overrides" };
|
||||
|
||||
// set
|
||||
var setIdOption = new Option<int>("--instance-id") { Description = "Instance ID", Required = true };
|
||||
var setAlarmOption = new Option<string>("--alarm") { Description = "Alarm canonical name (e.g., 'TempLevels' or 'Pump.TempSensor.Heat')", Required = true };
|
||||
var setConfigOption = new Option<string?>("--trigger-config") { Description = "JSON override for TriggerConfiguration (HiLo: partial merge; others: whole-replace)" };
|
||||
var setPriorityOption = new Option<int?>("--priority") { Description = "Priority override (0-1000)" };
|
||||
var setCmd = new Command("set") { Description = "Set (upsert) an alarm override on an instance" };
|
||||
setCmd.Add(setIdOption); setCmd.Add(setAlarmOption); setCmd.Add(setConfigOption); setCmd.Add(setPriorityOption);
|
||||
setCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new SetInstanceAlarmOverrideCommand(
|
||||
result.GetValue(setIdOption),
|
||||
result.GetValue(setAlarmOption)!,
|
||||
result.GetValue(setConfigOption),
|
||||
result.GetValue(setPriorityOption)));
|
||||
});
|
||||
group.Add(setCmd);
|
||||
|
||||
// delete
|
||||
var delIdOption = new Option<int>("--instance-id") { Description = "Instance ID", Required = true };
|
||||
var delAlarmOption = new Option<string>("--alarm") { Description = "Alarm canonical name", Required = true };
|
||||
var delCmd = new Command("delete") { Description = "Remove an alarm override on an instance" };
|
||||
delCmd.Add(delIdOption); delCmd.Add(delAlarmOption);
|
||||
delCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new DeleteInstanceAlarmOverrideCommand(
|
||||
result.GetValue(delIdOption),
|
||||
result.GetValue(delAlarmOption)!));
|
||||
});
|
||||
group.Add(delCmd);
|
||||
|
||||
// list
|
||||
var listIdOption = new Option<int>("--instance-id") { Description = "Instance ID", Required = true };
|
||||
var listCmd = new Command("list") { Description = "List all alarm overrides for an instance" };
|
||||
listCmd.Add(listIdOption);
|
||||
listCmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new ListInstanceAlarmOverridesCommand(result.GetValue(listIdOption)));
|
||||
});
|
||||
group.Add(listCmd);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static Command BuildSetArea(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
var areaIdOption = new Option<int?>("--area-id") { Description = "Area ID (omit to clear area assignment)" };
|
||||
|
||||
var cmd = new Command("set-area") { Description = "Reassign an instance to a different area" };
|
||||
cmd.Add(idOption);
|
||||
cmd.Add(areaIdOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
var areaId = result.GetValue(areaIdOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new SetInstanceAreaCommand(id, areaId));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
private static Command BuildDiff(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
|
||||
{
|
||||
var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
|
||||
|
||||
var cmd = new Command("diff") { Description = "Show deployment diff (deployed vs current template)" };
|
||||
cmd.Add(idOption);
|
||||
cmd.SetAction(async (ParseResult result) =>
|
||||
{
|
||||
var id = result.GetValue(idOption);
|
||||
return await CommandHelpers.ExecuteCommandAsync(
|
||||
result, urlOption, formatOption, usernameOption, passwordOption,
|
||||
new GetDeploymentDiffCommand(id));
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user