fix: resolve CLI serialization failures and add README

Two Akka.NET deserialization bugs prevented CLI commands from reaching ManagementActor: IReadOnlyList<string> in AuthenticatedUser serialized as a compiler-generated internal type unknown to the server, and ManagementSuccess.Data carried server-side assembly types the CLI couldn't resolve on receipt. Fixed by using string[] for roles and pre-serializing response data to JSON in ManagementActor before sending. Adds full CLI reference documentation covering all 10 command groups.
This commit is contained in:
Joseph Doherty
2026-03-17 18:17:47 -04:00
parent 40f74e4a42
commit eea50014de
4 changed files with 566 additions and 16 deletions

558
src/ScadaLink.CLI/README.md Normal file
View File

@@ -0,0 +1,558 @@
# ScadaLink CLI
Command-line tool for managing the ScadaLink SCADA system. Connects to a Central node via Akka.NET ClusterClient and routes commands through the ManagementActor.
## Installation
```sh
dotnet build src/ScadaLink.CLI
```
The output binary is `scadalink` (or `scadalink.exe` on Windows).
## Connection
Every command requires a connection to a running Central node. Contact points can be supplied three ways, evaluated in this priority order:
1. `--contact-points` flag on the command line
2. `SCADALINK_CONTACT_POINTS` environment variable
3. `contactPoints` array in `~/.scadalink/config.json`
```sh
scadalink --contact-points akka.tcp://scadalink@central-host:8081 <command>
```
For a two-node HA cluster, supply both nodes comma-separated:
```sh
scadalink --contact-points akka.tcp://scadalink@node1:8081,akka.tcp://scadalink@node2:8082 <command>
```
## Global Options
These options are accepted by the root command and inherited by all subcommands.
| Option | Description |
|--------|-------------|
| `--contact-points <value>` | Comma-separated Akka cluster contact point URIs |
| `--username <value>` | LDAP username (reserved for future auth integration) |
| `--password <value>` | LDAP password (reserved for future auth integration) |
| `--format <json\|table>` | Output format (default: `json`) |
## Configuration File
`~/.scadalink/config.json` is loaded at startup. All fields are optional.
```json
{
"contactPoints": ["akka.tcp://scadalink@central-host:8081"],
"ldap": {
"server": "ldap.company.com",
"port": 636,
"useTls": true
},
"defaultFormat": "json"
}
```
## Environment Variables
| Variable | Description |
|----------|-------------|
| `SCADALINK_CONTACT_POINTS` | Comma-separated contact point URIs (overrides config file) |
| `SCADALINK_LDAP_SERVER` | LDAP server hostname (overrides config file) |
| `SCADALINK_FORMAT` | Default output format (overrides config file) |
## Output
All commands write JSON to stdout on success. Errors are written as JSON to stderr:
```json
{ "error": "Human-readable message", "code": "ERROR_CODE" }
```
Exit codes:
| Code | Meaning |
|------|---------|
| `0` | Success |
| `1` | Command error |
| `2` | Authorization failure |
---
## Command Reference
### `template` — Manage templates
#### `template list`
List all templates with their full attribute, alarm, script, and composition definitions.
```sh
scadalink --contact-points <uri> template list
```
#### `template get`
Get a single template by ID.
```sh
scadalink --contact-points <uri> template get --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Template ID |
#### `template create`
Create a new template, optionally inheriting from a parent.
```sh
scadalink --contact-points <uri> template create --name <string> [--description <string>] [--parent-id <int>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--name` | yes | Template name |
| `--description` | no | Template description |
| `--parent-id` | no | Parent template ID for inheritance |
#### `template delete`
Delete a template by ID.
```sh
scadalink --contact-points <uri> template delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Template ID |
---
### `instance` — Manage instances
#### `instance list`
List instances, with optional filters.
```sh
scadalink --contact-points <uri> instance list [--site-id <int>] [--template-id <int>] [--search <string>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--site-id` | no | Filter by site ID |
| `--template-id` | no | Filter by template ID |
| `--search` | no | Search term matched against instance name |
#### `instance create`
Create a new instance of a template at a site.
```sh
scadalink --contact-points <uri> instance create --name <string> --template-id <int> --site-id <int> [--area-id <int>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--name` | yes | Unique instance name |
| `--template-id` | yes | Template to instantiate |
| `--site-id` | yes | Site where the instance will run |
| `--area-id` | no | Area within the site |
#### `instance deploy`
Deploy an instance to its site. Acquires the per-instance operation lock.
```sh
scadalink --contact-points <uri> instance deploy --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Instance ID |
#### `instance enable`
Enable a previously disabled instance.
```sh
scadalink --contact-points <uri> instance enable --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Instance ID |
#### `instance disable`
Disable a running instance without deleting it.
```sh
scadalink --contact-points <uri> instance disable --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Instance ID |
#### `instance delete`
Delete an instance. The instance must be disabled first.
```sh
scadalink --contact-points <uri> instance delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Instance ID |
---
### `site` — Manage sites
#### `site list`
List all registered sites.
```sh
scadalink --contact-points <uri> site list
```
#### `site create`
Register a new site.
```sh
scadalink --contact-points <uri> site create --name <string> --identifier <string> [--description <string>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--name` | yes | Human-readable site name |
| `--identifier` | yes | Unique machine identifier used for cluster routing (e.g. `site-a`) |
| `--description` | no | Site description |
#### `site delete`
Delete a site. Fails if any instances are assigned to it.
```sh
scadalink --contact-points <uri> site delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Site ID |
#### `site deploy-artifacts`
Push compiled artifacts to one or all sites.
```sh
scadalink --contact-points <uri> site deploy-artifacts [--site-id <int>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--site-id` | no | Target site ID; omit to deploy to all sites |
---
### `deploy` — Deployment operations
#### `deploy instance`
Deploy a single instance (same as `instance deploy`).
```sh
scadalink --contact-points <uri> deploy instance --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Instance ID |
#### `deploy artifacts`
Deploy compiled artifacts to one or all sites (same as `site deploy-artifacts`).
```sh
scadalink --contact-points <uri> deploy artifacts [--site-id <int>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--site-id` | no | Target site ID; omit for all sites |
#### `deploy status`
Query deployment records, with optional filters and pagination.
```sh
scadalink --contact-points <uri> deploy status [--instance-id <int>] [--status <string>] [--page <int>] [--page-size <int>]
```
| Option | Required | Default | Description |
|--------|----------|---------|-------------|
| `--instance-id` | no | — | Filter by instance ID |
| `--status` | no | — | Filter by deployment status string |
| `--page` | no | `1` | Page number |
| `--page-size` | no | `50` | Results per page |
---
### `data-connection` — Manage data connections
#### `data-connection list`
List all configured data connections.
```sh
scadalink --contact-points <uri> data-connection list
```
#### `data-connection create`
Create a new data connection definition.
```sh
scadalink --contact-points <uri> data-connection create --name <string> --protocol <string> [--configuration <json>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--name` | yes | Connection name |
| `--protocol` | yes | Protocol identifier (e.g. `OpcUa`) |
| `--configuration` | no | Protocol-specific configuration as a JSON string |
#### `data-connection delete`
Delete a data connection.
```sh
scadalink --contact-points <uri> data-connection delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Data connection ID |
#### `data-connection assign`
Assign a data connection to a site.
```sh
scadalink --contact-points <uri> data-connection assign --connection-id <int> --site-id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--connection-id` | yes | Data connection ID |
| `--site-id` | yes | Site ID |
---
### `external-system` — Manage external HTTP systems
#### `external-system list`
List all external system definitions.
```sh
scadalink --contact-points <uri> external-system list
```
#### `external-system create`
Register an external HTTP system that scripts can call.
```sh
scadalink --contact-points <uri> external-system create --name <string> --endpoint-url <url> --auth-type <string> [--auth-config <json>]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--name` | yes | Display name |
| `--endpoint-url` | yes | Base URL of the external system |
| `--auth-type` | yes | Authentication type: `ApiKey` or `BasicAuth` |
| `--auth-config` | no | Auth credentials as a JSON string |
#### `external-system delete`
Delete an external system definition.
```sh
scadalink --contact-points <uri> external-system delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | External system ID |
---
### `notification` — Manage notification lists
#### `notification list`
List all notification lists.
```sh
scadalink --contact-points <uri> notification list
```
#### `notification create`
Create a notification list with one or more recipients.
```sh
scadalink --contact-points <uri> notification create --name <string> --emails <email1,email2,...>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--name` | yes | Notification list name |
| `--emails` | yes | Comma-separated list of recipient email addresses |
#### `notification delete`
Delete a notification list.
```sh
scadalink --contact-points <uri> notification delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Notification list ID |
---
### `security` — Security settings
#### `security api-key list`
List all inbound API keys.
```sh
scadalink --contact-points <uri> security api-key list
```
#### `security api-key create`
Create a new inbound API key. The generated key value is returned in the response and not stored in plaintext — save it immediately.
```sh
scadalink --contact-points <uri> security api-key create --name <string>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--name` | yes | Descriptive label for the key |
#### `security api-key delete`
Revoke and delete an API key.
```sh
scadalink --contact-points <uri> security api-key delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | API key ID |
#### `security role-mapping list`
List all LDAP group → role mappings.
```sh
scadalink --contact-points <uri> security role-mapping list
```
#### `security role-mapping create`
Map an LDAP group to a ScadaLink role.
```sh
scadalink --contact-points <uri> security role-mapping create --ldap-group <string> --role <string>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--ldap-group` | yes | LDAP group distinguished name or CN |
| `--role` | yes | ScadaLink role: `Admin`, `Design`, or `Deployment` |
#### `security role-mapping delete`
Remove an LDAP role mapping.
```sh
scadalink --contact-points <uri> security role-mapping delete --id <int>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--id` | yes | Mapping ID |
---
### `health` — Health monitoring
#### `health summary`
Return the current health state for all known sites as a JSON object keyed by site identifier.
```sh
scadalink --contact-points <uri> health summary
```
#### `health site`
Return the health state for a single site.
```sh
scadalink --contact-points <uri> health site --identifier <string>
```
| Option | Required | Description |
|--------|----------|-------------|
| `--identifier` | yes | Site identifier (e.g. `site-a`) |
---
### `audit-log` — Audit log queries
#### `audit-log query`
Query the central audit log with optional filters and pagination.
```sh
scadalink --contact-points <uri> audit-log query [options]
```
| Option | Required | Default | Description |
|--------|----------|---------|-------------|
| `--user` | no | — | Filter by username |
| `--entity-type` | no | — | Filter by entity type (e.g. `Template`, `Instance`) |
| `--action` | no | — | Filter by action (e.g. `Create`, `Delete`) |
| `--from` | no | — | Start timestamp in ISO 8601 format |
| `--to` | no | — | End timestamp in ISO 8601 format |
| `--page` | no | `1` | Page number |
| `--page-size` | no | `50` | Results per page |
---
## Architecture Notes
The CLI connects to the Central cluster using Akka.NET's `ClusterClient`. It does not join the cluster — it contacts the `ClusterClientReceptionist` on one of the configured Central nodes and sends commands to the `ManagementActor` at path `/user/management`.
The connection is established per-command invocation and torn down cleanly via `CoordinatedShutdown` when the command completes.
Role enforcement is applied by the ManagementActor on the server side. The current CLI placeholder user carries `Admin`, `Design`, and `Deployment` roles; production use will integrate LDAP authentication via `--username` / `--password`.