Plan: CLI Commands for Customer Stage Removal & Installations View#
Context#
Two pieces of infrastructure exist but aren't wired into the Orchestrator CLI:
-
ICustomerStageRemovalService— defined and implemented insrc/BenefitManager.Orchestrator/Services/CustomerStageCleanupService.csbut not registered in DI and not consumed by any command. Allows removing or deactivating a customer stage by itsGuididentifier. -
Agent
GET /api/installations— the Agent exposes this endpoint (inManagementEndpoints.cs), returningList<InstallationSummary>with per-component health (filesystem, Windows Service, IIS). However,IAgentClienthas no method to call it, so the Orchestrator CLI cannot query installation state.
Goal: Add two new CLI commands: remove-stage to expose customer stage removal, and status to query and display installation state from agent server(s).
Step 1: Register ICustomerStageRemovalService in DI#
File: src/BenefitManager.Orchestrator/Program.cs
Add alongside the existing service registrations:
- [ ] Service registered in
Program.cs
Step 2: New remove-stage CLI command#
New file: src/BenefitManager.Orchestrator/Commands/StageRemoveCommand.cs
Settings#
public sealed class StageRemoveSettings : CommandSettings
{
[CommandArgument(0, "<ENVIRONMENT>")]
[Description("The target environment (e.g. Acceptatie or Productie)")]
public string Environment { get; init; } = null!;
[CommandArgument(1, "<STAGE_IDENTIFIER>")]
[Description("The Guid identifier of the customer stage to remove")]
public Guid StageIdentifier { get; init; }
[CommandOption("--force")]
[Description("Deactivate instead of delete (sets Active=0, Online=0)")]
public bool Force { get; init; }
}
Command#
public sealed class StageRemoveCommand(ICustomerStageRemovalService stageRemovalService) : AsyncCommand<StageRemoveSettings>
- Inject
ICustomerStageRemovalServiceonly — this is a SQL-only operation, no agent interaction needed, so no--serveroption. - Call
stageRemovalService.RemoveAsync(env, stageIdentifier, force, ct). - Catch
InvalidOperationException(thrown when FK constraint fails without--force) and display the error message suggesting--force. - Return 0 on success, 1 on failure.
Registration in Program.cs#
cli.AddCommand<StageRemoveCommand>("remove-stage")
.WithDescription("Removes or deactivates a customer stage from the CommandCenter database.")
.WithExample(["remove-stage", "Productie", "a1b2c3d4-..."]);
- [ ]
StageRemoveSettingsclass created - [ ]
StageRemoveCommandclass created with error handling - [ ] Command registered in
Program.cs
Step 3: Add GetInstallationsAsync to IAgentClient#
Files:
- src/BenefitManager.Orchestrator/Services/IAgentClient.cs
- src/BenefitManager.Orchestrator/Services/AgentClient.cs
Interface addition#
Task<List<InstallationSummary>> GetInstallationsAsync(CancellationToken cancellationToken = default);
Implementation#
public async Task<List<InstallationSummary>> GetInstallationsAsync(CancellationToken cancellationToken = default)
{
var response = await httpClient.GetAsync("/api/installations", cancellationToken);
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync(cancellationToken);
return JsonSerializer.Deserialize<List<InstallationSummary>>(body, JsonOptions)
?? throw new InvalidOperationException("Empty response from /api/installations.");
}
The InstallationSummary record is already defined in BenefitManager.Contracts.Models.ManagementModels:
public record InstallationSummary(
string Environment, string Version, string Path,
List<ComponentStatus> Components, DateTimeOffset LastChecked);
- [ ]
GetInstallationsAsyncadded toIAgentClient - [ ]
GetInstallationsAsyncimplemented inAgentClient
Step 4: New status CLI command#
New file: src/BenefitManager.Orchestrator/Commands/StatusCommand.cs
Settings#
public sealed class StatusSettings : CommandSettings
{
[CommandOption("--server")]
[Description("Target specific server(s). Defaults to all configured agents.")]
public string[]? Servers { get; init; }
}
No required arguments — queries all installations across targeted servers.
Command#
public sealed class StatusCommand(IAgentClientFactory agentClientFactory) : AsyncCommand<StatusSettings>
- Resolve targets via the standard
--serverpattern. - For each server: call
agentClient.GetInstallationsAsync(ct). - Render results using a
Spectre.Consoletable:
Server: vmbenefitsbmp1
┌─────────────┬─────────┬─────────────────────────────┬──────────────────────────┐
│ Environment │ Version │ Path │ Components │
├─────────────┼─────────┼─────────────────────────────┼──────────────────────────┤
│ Productie │ 25.5.29 │ F:\BenefitManager\Prod\25.5 │ FS:Running Svc:Running … │
│ Acceptatie │ 25.6.1 │ F:\BenefitManager\Acc\25.6 │ FS:Running Svc:Stopped … │
└─────────────┴─────────┴─────────────────────────────┴──────────────────────────┘
- Use color coding:
Running= green,Stopped= yellow,Missing/Error= red,Unknown= grey. - On per-server error (unreachable agent): print error line, continue to next server.
- Return 0 if all servers responded, 1 if any failed.
Registration in Program.cs#
cli.AddCommand<StatusCommand>("status")
.WithDescription("Queries installed versions and component health from agent server(s).")
.WithExample(["status"]);
- [ ]
StatusSettingsclass created - [ ]
StatusCommandclass created with table rendering and error handling - [ ] Command registered in
Program.cs
Step 5: Tests#
File: tests/BenefitManager.Orchestrator.Tests/StatusCommandTests.cs (new)
Test GetInstallationsAsync by mocking IAgentClientFactory + IAgentClient:
- Single-server returns installations → verify client method called.
- Server unreachable (throws) → verify command handles gracefully.
File: tests/BenefitManager.Orchestrator.Tests/StageRemoveCommandTests.cs (new)
Test StageRemoveCommand by mocking ICustomerStageRemovalService:
- Successful removal → verify RemoveAsync called with correct args, return 0.
- FK constraint error (without --force) → verify return 1.
- Force mode → verify RemoveAsync called with force: true.
- [ ]
StatusCommandTests.cscreated with single/error test cases - [ ]
StageRemoveCommandTests.cscreated with success/error/force test cases
Files Summary#
| Action | File |
|---|---|
| Modify | src/BenefitManager.Orchestrator/Program.cs |
| Modify | src/BenefitManager.Orchestrator/Services/IAgentClient.cs |
| Modify | src/BenefitManager.Orchestrator/Services/AgentClient.cs |
| New | src/BenefitManager.Orchestrator/Commands/StageRemoveCommand.cs |
| New | src/BenefitManager.Orchestrator/Commands/StatusCommand.cs |
| New | tests/BenefitManager.Orchestrator.Tests/StatusCommandTests.cs |
| New | tests/BenefitManager.Orchestrator.Tests/StageRemoveCommandTests.cs |
Unchanged: CustomerStageCleanupService.cs, ManagementEndpoints.cs, ManagementModels.cs, all existing commands.
Verification#
dotnet build— 0 errors, 0 warningsdotnet test— all existing + new tests passorchestrator remove-stage Productie <guid>— removes the stage from SQLorchestrator remove-stage Productie <guid> --force— deactivates instead of deletingorchestrator status— shows installations table from all configured agentsorchestrator status --server vmbenefitsbmp1— shows installations from specific server onlyorchestrator --help— new commands appear in help output