docs(components): reference docs batch 4/4 — ManagementService, CLI, Transport, CentralUI, TraefikProxy, TreeView
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
# 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.
|
||||
|
||||
## 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 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 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.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Connection to the active node
|
||||
|
||||
The CLI connects through Traefik, the reverse proxy fronting the central cluster. Traefik routes each request to the active central node based on the `/health/active` probe, so the CLI does not need to know which node (`central-a` or `central-b`) is currently primary. Pointing `--url` (or `managementUrl`) at the Traefik address (default `http://localhost:9000` in Docker) provides automatic HA failover without any CLI-side configuration change.
|
||||
|
||||
Direct-to-node access (`http://localhost:9001` for `central-a`, `http://localhost:9002` for `central-b`) bypasses Traefik and is useful for diagnostics but not for production automation.
|
||||
|
||||
### 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.
|
||||
|
||||
### Authentication and roles
|
||||
|
||||
The CLI encodes `--username` and `--password` as an HTTP Basic Auth header on every request. The server performs the LDAP bind, group lookup, and role resolution. The CLI never contacts LDAP directly and never caches credentials between invocations.
|
||||
|
||||
Role enforcement is applied by the `ManagementActor`. Operations require the appropriate role:
|
||||
|
||||
| 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`) |
|
||||
|
||||
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`. JSON output goes to stdout and is formatted with indented, camelCase JSON. 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` builds the `RootCommand` tree, attaches the four global options (`--url`, `--username`, `--password`, `--format`), and registers each command group:
|
||||
|
||||
```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(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));
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
`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.
|
||||
|
||||
## Usage
|
||||
|
||||
### Running the CLI
|
||||
|
||||
```bash
|
||||
# Build
|
||||
dotnet build src/ZB.MOM.WW.ScadaBridge.CLI
|
||||
|
||||
# Minimal invocation (URL and credentials required)
|
||||
scadabridge --url http://localhost:9000 --username multi-role --password password template list
|
||||
|
||||
# Using Traefik (HA; routes to the active node automatically)
|
||||
scadabridge --url http://localhost:9000 --username multi-role --password password site list
|
||||
|
||||
# Table output
|
||||
scadabridge --url http://localhost:9000 --username multi-role --password password \
|
||||
--format table instance list --site-id 1
|
||||
```
|
||||
|
||||
Credentials are safer supplied via environment variables than on the command line, where they appear in process listings and shell history:
|
||||
|
||||
```bash
|
||||
export SCADABRIDGE_MANAGEMENT_URL=http://localhost:9000
|
||||
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 instance create --name "PS-01" --template-id 3 --site-id 1
|
||||
scadabridge instance deploy --id 7
|
||||
```
|
||||
|
||||
### Command groups
|
||||
|
||||
| Group | Subcommands | Role required |
|
||||
|-------|-------------|---------------|
|
||||
| `template` | `list`, `get`, `create`, `update`, `delete`, `validate`; `attribute add/update/delete`; `alarm add/update/delete`; `script add/update/delete`; `composition add/delete`; `native-alarm-source add/list/remove` | `Design` |
|
||||
| `instance` | `list`, `get`, `create`, `deploy`, `enable`, `disable`, `delete`, `set-bindings`; `native-alarm-source set/clear` | `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` | `Design` / `Deployment` |
|
||||
| `external-system` | `list`, `get`, `create`, `update`, `delete` | `Design` |
|
||||
| `notification` | `list`, `get`, `create`, `update`, `delete`; `smtp list/update` | `Design` / `Admin` |
|
||||
| `security` | `api-key list/create/update/delete`; `role-mapping list/create/update/delete`; `scope-rule list/add/delete` | `Admin` |
|
||||
| `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` |
|
||||
| `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` |
|
||||
|
||||
### Selected examples
|
||||
|
||||
```bash
|
||||
# Query the operational Audit Log for failed API outbound events in the last 24 hours
|
||||
scadabridge audit query --since 24h --channel ApiOutbound --errors-only --format table
|
||||
|
||||
# Export a full audit window to CSV
|
||||
scadabridge audit export --since 2026-05-01T00:00:00Z --until 2026-06-01T00:00:00Z \
|
||||
--format csv --output /tmp/audit-may-2026.csv
|
||||
|
||||
# Export a Transport bundle for selected templates (with transitive dependencies)
|
||||
scadabridge bundle export --output /tmp/pump-station.scadabundle \
|
||||
--templates "PumpStation,BaseModule" --include-dependencies
|
||||
|
||||
# Preview a bundle diff before importing
|
||||
scadabridge bundle preview --input /tmp/pump-station.scadabundle
|
||||
|
||||
# Import with overwrite conflict policy
|
||||
scadabridge bundle import --input /tmp/pump-station.scadabundle --on-conflict overwrite
|
||||
|
||||
# Stream live attribute and alarm changes for a running instance
|
||||
scadabridge debug stream --id 7
|
||||
|
||||
# Query deployment records for a specific instance
|
||||
scadabridge deploy status --instance-id 7 --page-size 20
|
||||
```
|
||||
|
||||
### Exit codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `0` | Success |
|
||||
| `1` | Command error (connection failure, validation error, server error) |
|
||||
| `2` | Authorization failure (insufficient role; HTTP 403 or `FORBIDDEN`/`UNAUTHORIZED` error code) |
|
||||
|
||||
## Configuration
|
||||
|
||||
`~/.scadabridge/config.json` is loaded on every invocation. A malformed or unreadable file emits a warning to stderr; it does not abort the invocation.
|
||||
|
||||
```json
|
||||
{
|
||||
"managementUrl": "http://localhost:9000",
|
||||
"defaultFormat": "json"
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `managementUrl` | — | Base URL for the Management API. Overridden by `SCADABRIDGE_MANAGEMENT_URL` env var, then by `--url`. |
|
||||
| `defaultFormat` | `json` | Default output format when `--format` is not supplied. Overridden by `SCADABRIDGE_FORMAT` env var, then by `--format`. |
|
||||
|
||||
Credentials are intentionally never stored in the config file — they are sourced from environment variables or supplied per-invocation on the command line. Storing them in the file would persist them to disk in plaintext.
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Variable | Overrides | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `SCADABRIDGE_MANAGEMENT_URL` | `managementUrl` in config file | Management API base URL |
|
||||
| `SCADABRIDGE_FORMAT` | `defaultFormat` in config file | Default output format |
|
||||
| `SCADABRIDGE_USERNAME` | — | LDAP username; overridden by `--username` |
|
||||
| `SCADABRIDGE_PASSWORD` | — | LDAP password; overridden by `--password`. Preferred over `--password` to avoid leaking credentials into process listings and shell history. |
|
||||
|
||||
### URL precedence
|
||||
|
||||
The management URL is resolved in this order: `--url` flag → `SCADABRIDGE_MANAGEMENT_URL` env var → `managementUrl` in config file. If none is set, the command exits with code `1` and a `NO_URL` error.
|
||||
|
||||
## 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.
|
||||
- [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.
|
||||
- [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).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No management URL
|
||||
|
||||
```
|
||||
{"error":"No management URL specified. Use --url, set SCADABRIDGE_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.","code":"NO_URL"}
|
||||
```
|
||||
|
||||
The URL is not set via `--url`, `SCADABRIDGE_MANAGEMENT_URL`, or the config file. Set one of these before running any command.
|
||||
|
||||
### Connection failed
|
||||
|
||||
```
|
||||
{"error":"Connection failed: Connection refused (localhost:9000)","code":"CONNECTION_FAILED"}
|
||||
```
|
||||
|
||||
The central node or Traefik is not reachable at the configured URL. Verify the cluster is running and the URL matches the Traefik port (default `9000` in Docker) or the node's direct port (`9001`/`9002`).
|
||||
|
||||
### 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.
|
||||
|
||||
### Malformed config file warning
|
||||
|
||||
```
|
||||
warning: ignoring malformed or unreadable /home/user/.scadabridge/config.json: ...
|
||||
```
|
||||
|
||||
`CliConfig.Load()` caught a `JsonException`, `IOException`, or `UnauthorizedAccessException` reading the config file. The invocation continues using environment variables and command-line options. Fix or recreate the config file.
|
||||
|
||||
### `audit-log` deprecation warning
|
||||
|
||||
```
|
||||
Warning: 'audit-log' is deprecated and will be removed in a future release. Use 'audit-config' instead.
|
||||
```
|
||||
|
||||
The `audit-log` command group was renamed to `audit-config` in M8 of Audit Log (#23). The old name still works but emits this warning to stderr. Migrate any scripts from `scadabridge audit-log ...` to `scadabridge audit-config ...`.
|
||||
|
||||
### Bundle timeout
|
||||
|
||||
`bundle export` and `bundle import` use a 5-minute per-command timeout (compared to the 30-second default). If a bundle operation times out, the server-side export or import may still be running. Re-try with a smaller selection or check the central node logs.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [CLI design specification](../requirements/Component-CLI.md)
|
||||
- [Management Service](./ManagementService.md)
|
||||
- [Traefik Proxy](./TraefikProxy.md)
|
||||
- [Audit Log](./AuditLog.md)
|
||||
- [Security](./Security.md)
|
||||
- [Commons](./Commons.md)
|
||||
Reference in New Issue
Block a user