282 lines
14 KiB
Markdown
282 lines
14 KiB
Markdown
# Component: CLI
|
|
|
|
## Purpose
|
|
|
|
The CLI is a standalone command-line tool for scripting and automating administrative operations against the ScadaLink central cluster. It connects to the Central Host's HTTP Management API (`POST /management`), which dispatches commands to the ManagementActor. Authentication and role resolution are handled server-side — the CLI sends credentials via HTTP Basic Auth. The CLI provides the same administrative capabilities as the Central UI, enabling automation, batch operations, and integration with CI/CD pipelines.
|
|
|
|
## Location
|
|
|
|
Standalone executable, not part of the Host binary. Deployed on any machine with HTTP access to a central node.
|
|
|
|
`src/ScadaLink.CLI/`
|
|
|
|
## Responsibilities
|
|
|
|
- Parse command-line arguments and dispatch to the appropriate management operation.
|
|
- Send HTTP requests to the Central Host's Management API endpoint with Basic Auth credentials.
|
|
- Display structured responses from the Management API.
|
|
- Support both JSON and human-readable table output formats.
|
|
|
|
## Technology
|
|
|
|
- **Argument parsing**: `System.CommandLine` library for command/subcommand/option parsing with built-in help generation.
|
|
- **Transport**: HTTP client connecting to the Central Host's `POST /management` endpoint. Authentication is via HTTP Basic Auth — the server performs LDAP bind and role resolution.
|
|
- **Serialization**: Commands serialized as JSON with a type discriminator (`command` field). Message contracts from Commons define the command types.
|
|
|
|
## Authentication
|
|
|
|
The CLI sends user credentials to the Management API via HTTP Basic Auth:
|
|
|
|
1. The user provides credentials via `--username` / `--password` options.
|
|
2. On each request, the CLI encodes credentials as a Basic Auth header and sends them with the command.
|
|
3. The server performs LDAP authentication, group lookup, and role resolution — the CLI does not communicate with LDAP directly.
|
|
4. Credentials are not stored or cached between invocations. Each CLI invocation requires fresh credentials.
|
|
|
|
## Connection
|
|
|
|
The CLI connects to the Central Host via HTTP:
|
|
|
|
- **Management URL**: The URL of a central node's web server (e.g., `http://localhost:9001`). The management API is served at `POST /management` on the same host as the Central UI.
|
|
- **Failover**: For HA, use a load balancer URL in front of both central nodes. The management API is stateless (Basic Auth per request), so any central node can handle any request without sticky sessions.
|
|
- **No Akka.NET dependency**: The CLI is a pure HTTP client with no Akka.NET runtime.
|
|
|
|
## Command Structure
|
|
|
|
The CLI uses a hierarchical subcommand structure mirroring the Management Service message groups:
|
|
|
|
```
|
|
scadalink <group> <action> [options]
|
|
```
|
|
|
|
All entities are identified by their integer **ID** (via `--id`, `--template-id`,
|
|
`--site-id`, etc.), not by name. Create/update commands take individual flags — there
|
|
is no `--file` option. The authoritative, always-current reference is the in-repo
|
|
`src/ScadaLink.CLI/README.md`; the command lists below mirror the implemented command
|
|
tree at the time of writing.
|
|
|
|
### Template Commands
|
|
```
|
|
scadalink template list
|
|
scadalink template get --id <id>
|
|
scadalink template create --name <name> [--description <desc>] [--parent-id <id>]
|
|
scadalink template update --id <id> [--name <name>] [--description <desc>] [--parent-id <id>]
|
|
scadalink template validate --id <id>
|
|
scadalink template delete --id <id>
|
|
scadalink template attribute add --template-id <id> --name <name> --data-type <type> [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
|
|
scadalink template attribute update --id <id> [--name <name>] [--data-type <type>] [--value <value>] [--description <desc>] [--data-source <ref>] [--locked <bool>]
|
|
scadalink template attribute delete --id <id>
|
|
scadalink template alarm add --template-id <id> --name <name> --trigger-type <type> --priority <n> [--description <desc>] [--trigger-config <json>] [--locked <bool>]
|
|
scadalink template alarm update --id <id> [--name <name>] [--trigger-type <type>] [--priority <n>] [--description <desc>] [--trigger-config <json>] [--locked <bool>]
|
|
scadalink template alarm delete --id <id>
|
|
scadalink template script add --template-id <id> --name <name> --code <code> --trigger-type <type> [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
|
|
scadalink template script update --id <id> [--name <name>] [--code <code>] [--trigger-type <type>] [--trigger-config <json>] [--locked <bool>] [--parameters <json>] [--return-def <json>]
|
|
scadalink template script delete --id <id>
|
|
scadalink template composition add --template-id <id> --instance-name <name> --composed-template-id <id>
|
|
scadalink template composition delete --template-id <id> --instance-name <name>
|
|
```
|
|
|
|
### Instance Commands
|
|
```
|
|
scadalink instance list [--site-id <id>] [--template-id <id>] [--search <term>]
|
|
scadalink instance get --id <id>
|
|
scadalink instance create --name <name> --template-id <id> --site-id <id> [--area-id <id>]
|
|
scadalink instance set-bindings --id <id> --bindings <json>
|
|
scadalink instance set-overrides --id <id> --overrides <json>
|
|
scadalink instance alarm-override set --instance-id <id> --alarm <name> [--trigger-config <json>] [--priority <n>]
|
|
scadalink instance alarm-override delete --instance-id <id> --alarm <name>
|
|
scadalink instance alarm-override list --instance-id <id>
|
|
scadalink instance set-area --id <id> [--area-id <id>]
|
|
scadalink instance diff --id <id>
|
|
scadalink instance deploy --id <id>
|
|
scadalink instance enable --id <id>
|
|
scadalink instance disable --id <id>
|
|
scadalink instance delete --id <id>
|
|
```
|
|
|
|
`--bindings` is a JSON array of `[attributeName, dataConnectionId]` pairs, e.g.
|
|
`[["Speed", 5], ["Mode", 7]]`. `--overrides` is a JSON object of attribute name to
|
|
value, e.g. `{"Speed": "100", "Mode": null}`.
|
|
|
|
### Site Commands
|
|
```
|
|
scadalink site list
|
|
scadalink site get --id <id>
|
|
scadalink site create --identifier <id> --name <name> [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
|
|
scadalink site update --id <id> [--name <name>] [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
|
|
scadalink site delete --id <id>
|
|
scadalink site area list --site-id <id>
|
|
scadalink site area create --site-id <id> --name <name> [--parent-id <id>]
|
|
scadalink site area update --id <id> --name <name>
|
|
scadalink site area delete --id <id>
|
|
scadalink site deploy-artifacts [--site-id <id>]
|
|
```
|
|
|
|
### Deployment Commands
|
|
```
|
|
scadalink deploy instance --id <id>
|
|
scadalink deploy artifacts [--site-id <id>]
|
|
scadalink deploy status [--instance-id <id>] [--status <status>] [--page <n>] [--page-size <n>]
|
|
```
|
|
|
|
### Data Connection Commands
|
|
```
|
|
scadalink data-connection list [--site-id <id>]
|
|
scadalink data-connection get --id <id>
|
|
scadalink data-connection create --site-id <id> --name <name> --protocol <protocol> [--backup-config <json>] [--failover-retry-count <n>]
|
|
scadalink data-connection update --id <id> [--name <name>] [--protocol <protocol>] [--backup-config <json>] [--failover-retry-count <n>]
|
|
scadalink data-connection delete --id <id>
|
|
```
|
|
|
|
### External System Commands
|
|
```
|
|
scadalink external-system list
|
|
scadalink external-system get --id <id>
|
|
scadalink external-system create --name <name> --endpoint-url <url> --auth-type <type> [--auth-config <json>]
|
|
scadalink external-system update --id <id> [--name <name>] [--endpoint-url <url>] [--auth-type <type>] [--auth-config <json>]
|
|
scadalink external-system delete --id <id>
|
|
scadalink external-system method list --external-system-id <id>
|
|
scadalink external-system method get --id <id>
|
|
scadalink external-system method create --external-system-id <id> --name <name> --http-method <verb> --path <path> [--params <json>] [--return <json>]
|
|
scadalink external-system method update --id <id> [--name <name>] [--http-method <verb>] [--path <path>] [--params <json>] [--return <json>]
|
|
scadalink external-system method delete --id <id>
|
|
```
|
|
|
|
### Notification Commands
|
|
```
|
|
scadalink notification list
|
|
scadalink notification get --id <id>
|
|
scadalink notification create --name <name> --emails <comma-separated>
|
|
scadalink notification update --id <id> [--name <name>] [--emails <comma-separated>]
|
|
scadalink notification delete --id <id>
|
|
scadalink notification smtp list
|
|
scadalink notification smtp update --id <id> --server <host> --port <n> --auth-mode <mode> --from-address <email>
|
|
```
|
|
|
|
### Security Commands
|
|
```
|
|
scadalink security api-key list
|
|
scadalink security api-key create --name <name>
|
|
scadalink security api-key update --id <id> --enabled <bool>
|
|
scadalink security api-key delete --id <id>
|
|
scadalink security role-mapping list
|
|
scadalink security role-mapping create --ldap-group <group> --role <role>
|
|
scadalink security role-mapping update --id <id> [--ldap-group <group>] [--role <role>]
|
|
scadalink security role-mapping delete --id <id>
|
|
scadalink security scope-rule list [--mapping-id <id>]
|
|
scadalink security scope-rule add --mapping-id <id> --site-id <id>
|
|
scadalink security scope-rule delete --id <id>
|
|
```
|
|
|
|
### Audit Log Commands
|
|
```
|
|
scadalink audit-log query [--user <username>] [--entity-type <type>] [--action <action>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>]
|
|
```
|
|
|
|
### Health Commands
|
|
```
|
|
scadalink health summary
|
|
scadalink health site --identifier <site-identifier>
|
|
scadalink health event-log --site <site-identifier> [--event-type <type>] [--severity <level>] [--keyword <term>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>] [--instance-name <name>]
|
|
scadalink health parked-messages --site <site-identifier> [--page <n>] [--page-size <n>]
|
|
```
|
|
|
|
### Debug Commands
|
|
```
|
|
scadalink debug snapshot --id <id>
|
|
scadalink debug stream --id <id>
|
|
```
|
|
|
|
The `debug snapshot` command retrieves a point-in-time snapshot via the HTTP Management API.
|
|
|
|
The `debug stream` command streams live attribute values and alarm state changes in real-time using a SignalR WebSocket connection. The CLI connects to the `/hubs/debug-stream` SignalR hub on the central server, authenticates with Basic Auth, and subscribes to the specified instance. Events are printed as they arrive — JSON format (default) outputs one NDJSON object per event; table format shows streaming rows. Press Ctrl+C to disconnect.
|
|
|
|
Key behaviors:
|
|
- **Automatic reconnection**: Uses SignalR's `.WithAutomaticReconnect()` to re-establish the connection on loss.
|
|
- **Re-subscription**: Automatically re-subscribes to the instance after reconnection.
|
|
- **Traefik compatible**: Works through the Traefik reverse proxy — WebSocket upgrade is proxied natively.
|
|
- **Required role**: `Deployment`.
|
|
|
|
Unlike `debug snapshot` (which uses the HTTP Management API), `debug stream` uses `Microsoft.AspNetCore.SignalR.Client` as a dependency for its WebSocket transport.
|
|
|
|
### Shared Script Commands
|
|
```
|
|
scadalink shared-script list
|
|
scadalink shared-script get --id <id>
|
|
scadalink shared-script create --name <name> --code <code> [--parameters <json>] [--return-def <json>]
|
|
scadalink shared-script update --id <id> [--name <name>] [--code <code>] [--parameters <json>] [--return-def <json>]
|
|
scadalink shared-script delete --id <id>
|
|
```
|
|
|
|
### Database Connection Commands
|
|
```
|
|
scadalink db-connection list
|
|
scadalink db-connection get --id <id>
|
|
scadalink db-connection create --name <name> --connection-string <string>
|
|
scadalink db-connection update --id <id> [--name <name>] [--connection-string <string>]
|
|
scadalink db-connection delete --id <id>
|
|
```
|
|
|
|
### Inbound API Method Commands
|
|
```
|
|
scadalink api-method list
|
|
scadalink api-method get --id <id>
|
|
scadalink api-method create --name <name> --script <code> [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
|
|
scadalink api-method update --id <id> [--script <code>] [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
|
|
scadalink api-method delete --id <id>
|
|
```
|
|
|
|
The `--format json|table` option is recursive and accepted on every command above.
|
|
|
|
## Configuration
|
|
|
|
Configuration is resolved in the following priority order (highest wins):
|
|
|
|
1. **Command-line options**: `--url`, `--username`, `--password`, `--format`.
|
|
2. **Environment variables**:
|
|
- `SCADALINK_MANAGEMENT_URL` — Management API URL (e.g., `http://central-host:5000`).
|
|
- `SCADALINK_FORMAT` — Default output format (`json` or `table`).
|
|
- `SCADALINK_USERNAME` / `SCADALINK_PASSWORD` — LDAP credentials. Preferred over
|
|
`--password` on the command line, which is visible in process listings and shell
|
|
history. Credentials are never read from the config file.
|
|
3. **Configuration file**: `~/.scadalink/config.json` — Persistent defaults for management URL and output format only (never credentials).
|
|
|
|
### Configuration File Format
|
|
|
|
```json
|
|
{
|
|
"managementUrl": "http://central-host:5000"
|
|
}
|
|
```
|
|
|
|
## Output Formats
|
|
|
|
- **JSON** (default): Machine-readable JSON output to stdout. Suitable for piping to `jq` or processing in scripts. Errors are written to stderr as JSON objects with `error` and `code` fields.
|
|
- **Table** (`--format table` or `--table`): Human-readable tabular output with aligned columns. Suitable for interactive use.
|
|
|
|
## Exit Codes
|
|
|
|
| Code | Meaning |
|
|
|------|---------|
|
|
| 0 | Success |
|
|
| 1 | General error (command failed, connection failure, or authentication failure) |
|
|
| 2 | Authorization failure (insufficient role) |
|
|
|
|
## Error Handling
|
|
|
|
- **Connection failure**: If the CLI cannot connect to the management URL (e.g., DNS failure, connection refused), it exits with code 1 and a descriptive error message.
|
|
- **Command timeout**: If the server does not respond within 30 seconds, the command fails with a timeout error (HTTP 504).
|
|
- **Authentication failure**: If the server returns HTTP 401 (LDAP bind failed), the CLI exits with code 1.
|
|
- **Authorization failure**: If the server returns HTTP 403, the CLI exits with code 2.
|
|
|
|
## Dependencies
|
|
|
|
- **Commons**: Message contracts (`Messages/Management/`) for command type definitions and registry.
|
|
- **System.CommandLine**: Command-line argument parsing.
|
|
- **Microsoft.AspNetCore.SignalR.Client**: SignalR client for the `debug stream` command's WebSocket connection.
|
|
|
|
## Interactions
|
|
|
|
- **Management Service (via HTTP)**: The primary runtime dependency. All operations except `debug stream` are sent as HTTP POST requests to the Management API endpoint on a central node, which dispatches to the ManagementActor.
|
|
- **Central Host**: Serves the Management API at `POST /management` and the debug stream SignalR hub at `/hubs/debug-stream`. Handles LDAP authentication, role resolution, and ManagementActor dispatch.
|
|
- **Debug Stream Hub (via SignalR WebSocket)**: The `debug stream` command connects to the `/hubs/debug-stream` hub on the central server for real-time event streaming. This is the only CLI command that uses a persistent connection rather than request/response.
|