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:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -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;
}
}