feat: add HTTP Management API, migrate CLI from Akka ClusterClient to HTTP

Replace the CLI's Akka.NET ClusterClient transport with a simple HTTP client
targeting a new POST /management endpoint on the Central Host. The endpoint
handles Basic Auth, LDAP authentication, role resolution, and ManagementActor
dispatch in a single round-trip — eliminating the CLI's Akka, LDAP, and
Security dependencies.

Also fixes DCL ReSubscribeAll losing subscriptions on repeated reconnect by
deriving the tag list from _subscriptionsByInstance instead of _subscriptionIds.
This commit is contained in:
Joseph Doherty
2026-03-20 23:55:31 -04:00
parent 7740a3bcf9
commit 1a540f4f0a
38 changed files with 863 additions and 758 deletions

View File

@@ -2,47 +2,43 @@
## Purpose
The CLI is a standalone command-line tool for scripting and automating administrative operations against the ScadaLink central cluster. It connects to the ManagementActor via Akka.NET ClusterClient — it does not join the cluster as a full member and does not use HTTP/REST. The CLI provides the same administrative capabilities as the Central UI, enabling automation, batch operations, and integration with CI/CD pipelines.
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 Windows machine with network access to the central cluster.
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.
- Authenticate the user via LDAP credentials and include identity in every message sent to the ManagementActor.
- Connect to the central cluster via Akka.NET ClusterClient using configured contact points.
- Send management messages to the ManagementActor and display structured responses.
- 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**: Akka.NET `ClusterClient` connecting to `ClusterClientReceptionist` on the central cluster. The CLI does not join the cluster — it is a lightweight external client.
- **Serialization**: Message contracts from Commons (`Messages/Management/`), same as ManagementActor expects.
- **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 authenticates the user against LDAP/AD before any operation:
The CLI sends user credentials to the Management API via HTTP Basic Auth:
1. The user provides credentials via `--username` / `--password` options, or is prompted interactively if omitted.
2. The CLI performs a direct LDAP bind against the configured LDAP server (same mechanism as the Central UI login).
3. On successful bind, the CLI queries group memberships to determine roles and permitted sites.
4. Every message sent to the ManagementActor includes the `AuthenticatedUser` envelope with the user's identity, roles, and site permissions.
5. Credentials are not stored or cached between invocations. Each CLI invocation requires fresh authentication.
LDAP connection settings are read from the CLI configuration (see Configuration section).
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 uses Akka.NET ClusterClient to connect to the central cluster:
The CLI connects to the Central Host via HTTP:
- **Contact points**: One or more seed node addresses for the ClusterClientReceptionist. The CLI sends an initial contact to these addresses; the receptionist responds with the current set of cluster nodes hosting the ManagementActor.
- **No cluster membership**: The CLI does not join the Akka.NET cluster. It is an external process that communicates via the ClusterClient protocol.
- **Failover**: If the active central node fails over, ClusterClient transparently reconnects to the new active node via the receptionist. In-flight commands may time out and need to be retried.
- **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
@@ -205,28 +201,17 @@ scadalink api-method delete --id <id>
Configuration is resolved in the following priority order (highest wins):
1. **Command-line options**: `--contact-points`, `--username`, `--password`, `--format`.
1. **Command-line options**: `--url`, `--username`, `--password`, `--format`.
2. **Environment variables**:
- `SCADALINK_CONTACT_POINTS` — Comma-separated list of central cluster contact point addresses (e.g., `akka.tcp://ScadaLink@central1:8081,akka.tcp://ScadaLink@central2:8081`).
- `SCADALINK_LDAP_SERVER` — LDAP server address.
- `SCADALINK_LDAP_PORT` — LDAP port (default: 636 for LDAPS).
- `SCADALINK_MANAGEMENT_URL` — Management API URL (e.g., `http://central-host:5000`).
- `SCADALINK_FORMAT` — Default output format (`json` or `table`).
3. **Configuration file**: `~/.scadalink/config.json` — Persistent defaults for contact points, LDAP settings, and output format.
3. **Configuration file**: `~/.scadalink/config.json` — Persistent defaults for management URL and output format.
### Configuration File Format
```json
{
"contactPoints": [
"akka.tcp://ScadaLink@central1:8081",
"akka.tcp://ScadaLink@central2:8081"
],
"ldap": {
"server": "ad.example.com",
"port": 636,
"useTls": true
},
"defaultFormat": "json"
"managementUrl": "http://central-host:5000"
}
```
@@ -240,28 +225,22 @@ Configuration is resolved in the following priority order (highest wins):
| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | General error (command failed) |
| 2 | Authentication failure (LDAP bind failed) |
| 3 | Authorization failure (insufficient role) |
| 4 | Connection failure (cannot reach central cluster) |
| 5 | Validation failure (e.g., template validation errors) |
| 1 | General error (command failed, connection failure, or authentication failure) |
| 2 | Authorization failure (insufficient role) |
## Error Handling
- **Connection failure**: If the CLI cannot establish a ClusterClient connection within a timeout (default 10 seconds), it exits with code 4 and a descriptive error message.
- **Command timeout**: If the ManagementActor does not respond within 30 seconds (configurable), the command fails with a timeout error.
- **Authentication failure**: If the LDAP bind fails, the CLI exits with code 2 before sending any commands.
- **Authorization failure**: If the ManagementActor returns an Unauthorized response, the CLI exits with code 3.
- **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/`), shared types.
- **Commons**: Message contracts (`Messages/Management/`) for command type definitions and registry.
- **System.CommandLine**: Command-line argument parsing.
- **Akka.NET (Akka.Cluster.Tools)**: ClusterClient for communication with the central cluster.
- **LDAP client library**: For direct LDAP bind authentication (same approach as Security & Auth component).
## Interactions
- **Management Service (ManagementActor)**: The CLI's sole runtime dependency. All operations are sent as messages to the ManagementActor via ClusterClient.
- **Security & Auth**: The CLI performs LDAP authentication independently (same LDAP server, same bind mechanism) and passes the authenticated identity to the ManagementActor. The ManagementActor enforces authorization.
- **LDAP/Active Directory**: Direct bind for user authentication before any operation.
- **Management Service (via HTTP)**: The CLI's sole runtime dependency. All operations 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`. Handles LDAP authentication, role resolution, and ManagementActor dispatch.