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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user