Synchronize CLI documentation with management command parity

This commit is contained in:
Joseph Doherty
2026-09-10 01:20:12 -04:00
parent 0cc9db9e94
commit 2b9a7c3ca0
4 changed files with 376 additions and 169 deletions
+50 -55
View File
@@ -1,12 +1,12 @@
# CLI
The CLI is a standalone command-line tool that provides scripting and automation access to all ScadaBridge administrative operations. It connects to the central cluster's HTTP Management API and dispatches commands to the `ManagementActor` via `POST /management`. Authentication and role resolution happen server-side; the CLI sends LDAP credentials as HTTP Basic Auth on every request.
The CLI is a standalone command-line tool that provides scripting and automation access to all HTTP-eligible ManagementActor operations. It connects to the central cluster's HTTP Management API and dispatches commands to the `ManagementActor` via `POST /management`. Authentication and role resolution happen server-side; the CLI sends LDAP credentials as HTTP Basic Auth on every request.
## Overview
The CLI component lives in `src/ZB.MOM.WW.ScadaBridge.CLI/` and builds to a self-contained `scadabridge` executable (or `scadabridge.exe` on Windows). It is not part of the Host binary — it deploys on any machine with HTTP access to a central node.
The CLI component lives in `src/ZB.MOM.WW.ScadaBridge.CLI/` and builds to a .NET 10 `scadabridge` executable (or `scadabridge.exe` on Windows); self-contained distribution requires an explicit publish configuration. It is not part of the Host binary — it deploys on any machine with HTTP access to a central node.
The tool is built on `System.CommandLine` and exposes a hierarchical command tree organized by management domain (`template`, `instance`, `site`, `data-connection`, and so on). Every command follows the same pattern: resolve the management URL and credentials, construct a command object whose type maps to a `ManagementCommandRegistry` entry, serialize it into the `{ command, payload }` JSON envelope, and `POST` it to `<managementUrl>/management`. The server response — JSON success body or `{ error, code }` error envelope — is printed to stdout or stderr respectively.
The tool is built on `System.CommandLine` and exposes a hierarchical command tree organized by management domain (`template`, `instance`, `site`, `data-connection`, and so on). Management commands follow the same pattern: resolve the management URL and credentials, construct a command object whose type maps to a `ManagementCommandRegistry` entry, serialize it into the `{ command, payload }` JSON envelope, and `POST` it to `<managementUrl>/management`. The server response — JSON success body or `{ error, code }` error envelope — is printed to stdout or stderr respectively.
The CLI is the preferred automation interface. When setting up system state (sites, templates, data connections, deployments, security) without the Central UI, use `scadabridge` commands rather than direct database manipulation.
@@ -20,7 +20,11 @@ Direct-to-node access (`http://localhost:9001` for `central-a`, `http://localhos
### Command dispatch
Most commands serialize to a named management command and reach the `ManagementActor` through the `POST /management` endpoint. The `audit query` and `audit export` commands are the exception — they call plain REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`) introduced by Audit Log (#23) and therefore use `SendGetAsync`/`SendGetStreamAsync` on `ManagementHttpClient` rather than `SendCommandAsync`. The streaming `audit export` path uses `HttpCompletionOption.ResponseHeadersRead` to avoid buffering large payloads in memory.
Management commands use `POST /management`. Audit query, export, and tree use
`GET /api/audit/query`, `/api/audit/export`, and `/api/audit/tree`; source-node
backfill uses `POST /api/audit/backfill-source-node`. `audit verify-chain` is a
local placeholder that performs no verification. `debug stream` uses a persistent
SignalR connection. Audit export streams the response without buffering it all.
### Authentication and roles
@@ -30,46 +34,37 @@ Role enforcement is applied by the `ManagementActor`. Operations require the app
| Role | Covers |
|------|--------|
| `Admin` | Security settings, site management, SMTP config, audit-config queries |
| `Design` | Templates, shared scripts, external systems, DB connections, API methods, notification lists |
| `Deployment` | Instance lifecycle (deploy, enable, disable, delete), data connection bindings, bundle import/export |
| `OperationalAudit` | Reading the Audit Log (`audit query`) |
| `AuditExport` | Exporting the Audit Log (`audit export`) |
| `Administrator` | Security settings, site mutations, SMTP/SMS provider changes, bundle preview/import, certificate management, audit-config queries |
| `Designer` | Template/schema/script authoring, external systems, DB connections, API methods, notification lists, connection design, bundle export |
| `Deployer` | Instance lifecycle and overrides, bindings, deployment, runtime debugging, parked-call decisions |
| `Operator` | Submit secured writes |
| `Verifier` | Approve/reject secured writes from a different principal |
| Operational audit roles | Audit query/tree and Flow Debugger replay |
| Audit export roles | Export the audit log |
These are operation-level gates, not blanket restrictions on every read in a
group. Many management reads require authentication only; site scope and
operation-specific checks still apply. Secured-write listing accepts Operator,
Verifier, or Administrator. See the [server authorization matrix](../../src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs).
A request lacking the required role exits with code `2` (authorization failure). A bad-credential response (HTTP 401) exits with code `1`.
### Output formats
Every command accepts `--format json` (default) or `--format table`. For JSON output, successful server responses are written to stdout verbatim — the server controls the JSON shape and no re-serialization is applied. Locally-constructed output (errors, `debug stream` events) is serialized by `OutputFormatter` with indentation and camelCase. Table output renders a padded plain-text table derived from the response JSON — arrays become rows, single objects become a two-column `Property / Value` table. Errors are always written as `{ "error": "...", "code": "..." }` to stderr, regardless of format.
Management commands accept `--format json` (default) or `--format table`. Audit export accepts its own export formats. For JSON output, successful server responses are written to stdout verbatim — the server controls the JSON shape and no re-serialization is applied. Locally-constructed output (errors, `debug stream` events) is serialized by `OutputFormatter` with indentation and camelCase. Table output renders a padded plain-text table derived from the response JSON — arrays become rows, single objects become a two-column `Property / Value` table. Errors are always written as `{ "error": "...", "code": "..." }` to stderr, regardless of format.
## Architecture
`Program.cs` invokes `CliApplication.BuildRoot()`, which builds the `RootCommand` tree, attaches the four global options (`--url`, `--username`, `--password`, `--format`), and registers each command group:
[`Program.cs`](../../src/ZB.MOM.WW.ScadaBridge.CLI/Program.cs) invokes
[`CliApplication.BuildRoot()`](../../src/ZB.MOM.WW.ScadaBridge.CLI/CliApplication.cs),
which registers the global options and all 20 command groups, including the
connection browse/certificate extensions. The executable and parity tests use
this same tree.
```csharp
rootCommand.Add(TemplateCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(InstanceCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SiteCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(DeployCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(DataConnectionCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(ExternalSystemCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(NotificationCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SecurityCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(AuditLogCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(AuditCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(HealthCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(DebugCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SharedSchemaCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SharedScriptCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(DbConnectionCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(ApiMethodCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(BundleCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(CachedCallCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SecuredWriteCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(FlowDebugCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
```
Every command's action delegates to `CommandHelpers.ExecuteCommandAsync`, which owns the URL/credential resolution, `ManagementHttpClient` lifetime, and exit-code mapping. This consolidation means the authorization-failure exit code (`2`) is enforced uniformly — including for the `bundle` group, which uses `ExecuteCommandAsync` with a longer 5-minute timeout and a per-command `onSuccess` handler rather than a separate HTTP path.
Management actions use `CommandHelpers.ExecuteCommandAsync` for URL/credential
resolution, HTTP dispatch, formatting, and exit codes. Deployment aliases defer
the overall deadline to the server; bundles and Flow Debugger have explicit
policies. Audit REST and SignalR actions use their respective transports.
`CliConfig.Load()` is called at the start of every invocation. It merges `~/.scadabridge/config.json`, environment variables, and any defaults. A malformed or unreadable config file emits a warning to stderr and falls through to the environment variable / command-line precedence chain without crashing.
@@ -100,7 +95,7 @@ export SCADABRIDGE_USERNAME=multi-role
export SCADABRIDGE_PASSWORD=password
scadabridge template create --name "PumpStation" --description "Standard pump station"
scadabridge template attribute add --template-id 3 --name Speed --data-type Float --default-value 0
scadabridge template attribute add --template-id 3 --name Speed --data-type Float --value 0
scadabridge instance create --name "PS-01" --template-id 3 --site-id 1
scadabridge instance deploy --id 7
```
@@ -109,26 +104,26 @@ scadabridge instance deploy --id 7
| Group | Subcommands | Role required |
|-------|-------------|---------------|
| `template` | `list`, `get`, `create`, `update`, `delete`, `validate`, `resolved-members`, `move`; `attribute add/update/delete`; `alarm add/update/delete`; `script add/update/delete`; `composition add/delete`; `native-alarm-source add/list/update/remove` | `Design` |
| `instance` | `list`, `get`, `create`, `set-bindings`, `set-overrides`, `alarm-override set/delete/list`, `native-alarm-source set/clear/list/import`, `attribute-override delete`, `set-area`, `diff`, `deploy`, `enable`, `disable`, `delete` | `Deployment` |
| `site` | `list`, `get`, `create`, `delete`, `deploy-artifacts`; `area list/create/update/delete` | `Admin` |
| `deploy` | `instance`, `artifacts`, `status` | `Deployment` |
| `data-connection` | `list`, `get`, `create`, `update`, `delete`, `move`; `browse`, `search`, `verify-endpoint`, `certs` | `Design` / `Deployment` |
| `external-system` | `list`, `get`, `create`, `update`, `delete` | `Design` |
| `notification` | `list`, `get`, `create`, `update`, `delete`, `rename`; `recipient add/delete`; `smtp list/create/update`; `sms list/create/update` | `Design` / `Admin` |
| `template` | `list`, `get`, `create`, `update`, `delete`, `validate`, `resolved-members`, `move`; `attribute list/add/update/delete`; `alarm add/update/delete`; `script add/update/delete`; `composition add/delete`; `native-alarm-source add/list/update/remove`; `folder list/create/rename/move/reorder/delete`; `resync-members` | Designer mutations; reads generally authenticated |
| `instance` | `list`, `get`, `create`, `set-bindings`, `bind-module`, `set-overrides`, `import-overrides`, `alarm-override set/delete/list`, `native-alarm-source set/clear/list/import`, `attribute-override delete`, `set-area`, `diff`, `deploy`, `enable`, `disable`, `delete` | `Deployer` |
| `site` | `list`, `get`, `create`, `update`, `delete`, `deploy-artifacts`; `area list/create/update/move/delete` | Administrator site mutations; Designer/Deployer area mutations; Deployer artifact deployment |
| `deploy` | `instance`, `site`, `artifacts`, `status` | `Deployer` |
| `data-connection` | `list`, `get`, `create`, `update`, `delete`, `move`; `browse`, `search`, `verify-endpoint`, `certs` | `Designer` / `Deployer` |
| `external-system` | `list`, `get`, `create`, `update`, `delete`; `method list/get/create/update/delete` | `Designer` |
| `notification` | `list`, `get`, `create`, `update`, `delete`, `rename`; `recipient add/delete`; `smtp list/create/update`; `sms list/create/update` | `Designer` / `Administrator` |
| `security` | `api-key list/create/update/delete/set-methods`; `role-mapping list/create/update/delete`; `scope-rule list/add/delete` | `Admin` |
| `shared-schema` | `list`, `get`, `create`, `update`, `delete` | Read: authenticated; mutations: Designer |
| `secured-write` | `list`, `submit`, `approve`, `reject` | Operator / Verifier / Administrator, enforced per operation |
| `cached-call` | `list`, `retry`, `discard` | Server-enforced operational roles and site scope |
| `cached-call` | `retry`, `discard` (list parked messages with `health parked-messages`) | Server-enforced operational roles and site scope |
| `flow-debug` | `run-method`, `run-script`, `run-value-change`, `replay` | Deployer; replay also accepts OperationalAudit roles |
| `shared-script` | `list`, `get`, `create`, `update`, `delete` | `Design` |
| `db-connection` | `list`, `get`, `create`, `update`, `delete` | `Design` |
| `api-method` | `list`, `get`, `create`, `update`, `delete` | `Design` |
| `bundle` | `export`, `preview`, `import` | `Deployment` |
| `audit` | `query`, `export`, `verify-chain` | `OperationalAudit` / `AuditExport` |
| `shared-script` | `list`, `get`, `create`, `update`, `delete` | `Designer` |
| `db-connection` | `list`, `get`, `create`, `update`, `delete` | `Designer` |
| `api-method` | `list`, `get`, `create`, `update`, `delete` | `Designer` |
| `bundle` | `export`, `preview`, `import` | Designer export; Administrator preview/import |
| `audit` | `query`, `export`, `tree`, `backfill-source-node`, `verify-chain` | Audit roles; Administrator backfill; verify-chain is a local placeholder |
| `audit-config` | `query` (config-change audit trail; was `audit-log` pre-M8) | `Admin` |
| `health` | `summary`, `site`, `event-log`, `parked-messages` | `Deployment` |
| `debug` | `snapshot`, `stream` | `Deployment` |
| `health` | `summary`, `site`, `event-log`, `parked-messages` | Authenticated reads with server-side scope checks |
| `debug` | `snapshot`, `stream` | `Deployer` |
### Selected examples
@@ -198,10 +193,10 @@ The management URL is resolved in this order: `--url` flag → `SCADABRIDGE_MANA
## Dependencies & Interactions
- [Management Service (#18)](./ManagementService.md) — the server-side counterpart. Every CLI command (except `audit query`/`audit export`) translates to a named management command dispatched through `POST /management` to the `ManagementActor`. Role enforcement and LDAP authentication are applied there. The `ManagementCommandRegistry` in Commons maps command types to their names; both sides must stay in sync.
- [Management Service (#18)](./ManagementService.md) — the server-side counterpart. Every CLI command (except the audit REST/placeholder and SignalR stream operations) translates to a named management command dispatched through `POST /management` to the `ManagementActor`. Role enforcement and LDAP authentication are applied there. The `ManagementCommandRegistry` in Commons maps command types to their names; both sides must stay in sync.
- [Traefik Proxy (#20)](./TraefikProxy.md) — the recommended connection target. Pointing `--url` at the Traefik address ensures requests reach the active central node without per-command failover logic in the CLI. The `debug stream` command's WebSocket connection (SignalR `/hubs/debug-stream`) also traverses Traefik, which proxies the WebSocket upgrade natively.
- [Audit Log (#23)](./AuditLog.md) — the `audit` command group targets the `GET /api/audit/query` and `GET /api/audit/export` REST endpoints exposed by the Audit Log component, bypassing the management command envelope. The `audit-config` group (formerly `audit-log`) targets the configuration-change audit trail (`IAuditService`) via the standard management envelope.
- [Security & Auth (#10)](./Security.md) — the server resolves LDAP credentials and maps group memberships to ScadaBridge roles (`Admin`, `Design`, `Deployment`, `OperationalAudit`, `AuditExport`). The CLI does not interact with LDAP directly.
- [Audit Log (#23)](./AuditLog.md) — the `audit` command group targets the query, export, tree, and backfill REST endpoints exposed by the Audit Log component, bypassing the management command envelope; `verify-chain` is a local placeholder. The `audit-config` group (formerly `audit-log`) targets the configuration-change audit trail (`IAuditService`) via the standard management envelope.
- [Security & Auth (#10)](./Security.md) — the server resolves LDAP credentials and maps group memberships to ScadaBridge roles (including `Administrator`, `Designer`, `Deployer`, `Operator`, `Verifier`, and audit roles). The CLI does not interact with LDAP directly.
- [Commons (#16)](./Commons.md) — owns the management command record types and the `ManagementCommandRegistry` that maps each type to its wire name. The CLI project references Commons for these contracts.
- [Transport (#24)](./Transport.md) — the `bundle` command group drives the Transport feature: `bundle export` requests a base64-encoded bundle from the server and streams it to a local `.scadabundle` file; `bundle preview` uploads a file and returns the diff manifest; `bundle import` uploads a file and applies it with a configurable conflict policy.
- Design spec: [Component-CLI.md](../requirements/Component-CLI.md).
@@ -226,7 +221,7 @@ The central node or Traefik is not reachable at the configured URL. Verify the c
### Authorization failure (exit 2)
The server returned HTTP 403 or an error code of `FORBIDDEN`/`UNAUTHORIZED`. The authenticated user's LDAP groups do not map to a role with permission for the requested operation. Use `security role-mapping list` (requires `Admin`) to inspect role mappings. The `multi-role` test user (`password`) holds `Admin`, `Design`, and `Deployment` roles.
The server returned HTTP 403 or an error code of `FORBIDDEN`/`UNAUTHORIZED`. The authenticated user's LDAP groups do not map to a role with permission for the requested operation. Use `security role-mapping list` (requires `Administrator`) to inspect role mappings. The `multi-role` test user (`password`) holds `Administrator`, `Designer`, and `Deployer` roles.
### Malformed config file warning