|
| 1 | +# Implement Environment Show Command |
| 2 | + |
| 3 | +**Issue**: #241 |
| 4 | +**Parent Epic**: N/A (Part of Roadmap Section 5: "Add extra console app commands") |
| 5 | +**Related**: |
| 6 | + |
| 7 | +- Roadmap #1 - Task 5.1 |
| 8 | +- Feature Specification: [docs/features/environment-status-command/](../features/environment-status-command/) |
| 9 | + |
| 10 | +## Overview |
| 11 | + |
| 12 | +Implement a new console command `show` that displays environment information with state-aware details. The command provides a read-only view of stored environment data without remote verification, making it fast and reliable for users to inspect their deployment state. |
| 13 | + |
| 14 | +This command displays different information based on the environment's current state (Created, Provisioned, Configured, Released, Running) and provides next-step guidance to help users understand what actions to take. |
| 15 | + |
| 16 | +## Goals |
| 17 | + |
| 18 | +- [ ] Provide console command to display environment information |
| 19 | +- [ ] Show state-aware details (different information per state) |
| 20 | +- [ ] Use human-friendly output formatting with UserOutput |
| 21 | +- [ ] Handle errors gracefully with actionable messages |
| 22 | +- [ ] Maintain fast execution (< 100ms for typical environments) |
| 23 | +- [ ] Ensure comprehensive test coverage (unit + E2E) |
| 24 | + |
| 25 | +## 🏗️ Architecture Requirements |
| 26 | + |
| 27 | +**DDD Layers**: Application + Presentation |
| 28 | + |
| 29 | +**Module Paths**: |
| 30 | + |
| 31 | +- `src/application/commands/show/` (command handler, DTOs, errors, formatter) |
| 32 | +- `src/presentation/input/cli/subcommands/show.rs` (CLI interface) |
| 33 | + |
| 34 | +**Patterns**: |
| 35 | + |
| 36 | +- Application Layer: Command Handler pattern |
| 37 | +- Presentation Layer: CLI Subcommand pattern |
| 38 | +- Data Transfer: DTO pattern (EnvironmentInfo, InfrastructureInfo, ServicesInfo) |
| 39 | + |
| 40 | +### Module Structure Requirements |
| 41 | + |
| 42 | +- [ ] Follow DDD layer separation (see [docs/codebase-architecture.md](../codebase-architecture.md)) |
| 43 | +- [ ] Application layer handles business logic (loading, extraction, formatting) |
| 44 | +- [ ] Presentation layer handles CLI input/output only |
| 45 | +- [ ] No direct infrastructure dependencies (use existing EnvironmentLoader) |
| 46 | +- [ ] Use appropriate module organization (see [docs/contributing/module-organization.md](../contributing/module-organization.md)) |
| 47 | +- [ ] Follow import conventions (imports first, prefer imports over full paths) |
| 48 | + |
| 49 | +### Architectural Constraints |
| 50 | + |
| 51 | +- [ ] **CRITICAL**: All output through UserOutput - **NEVER use `println!`, `eprintln!`, or direct stdio** (see [docs/contributing/output-handling.md](../contributing/output-handling.md)) |
| 52 | +- [ ] No business logic in presentation layer (only CLI argument parsing and dispatch) |
| 53 | +- [ ] Error handling with explicit enum types (not anyhow) - see [docs/contributing/error-handling.md](../contributing/error-handling.md) |
| 54 | +- [ ] Read-only operation - no state modifications |
| 55 | +- [ ] No remote network calls (displays stored data only) |
| 56 | +- [ ] Testing strategy: unit tests at each phase + E2E test evolution |
| 57 | + |
| 58 | +### Anti-Patterns to Avoid |
| 59 | + |
| 60 | +- ❌ **Direct `println!`/`eprintln!` usage** (must use UserOutput) |
| 61 | +- ❌ Business logic in CLI subcommand |
| 62 | +- ❌ Using anyhow instead of explicit error enums |
| 63 | +- ❌ Remote verification/health checks (out of scope - use `test` command) |
| 64 | +- ❌ Batching test completions (mark complete immediately after each phase) |
| 65 | + |
| 66 | +## Specifications |
| 67 | + |
| 68 | +### Command Interface |
| 69 | + |
| 70 | +**Command Name**: `show` (not `status` - reserves that for future service health checks) |
| 71 | + |
| 72 | +**Usage**: |
| 73 | + |
| 74 | +```bash |
| 75 | +torrust-tracker-deployer show <environment-name> |
| 76 | +``` |
| 77 | + |
| 78 | +**Example**: |
| 79 | + |
| 80 | +```bash |
| 81 | +torrust-tracker-deployer show my-environment |
| 82 | +``` |
| 83 | + |
| 84 | +### Output Format by State |
| 85 | + |
| 86 | +#### Created State |
| 87 | + |
| 88 | +```text |
| 89 | +Environment: my-environment |
| 90 | +State: Created |
| 91 | +Provider: LXD |
| 92 | +
|
| 93 | +The environment configuration is ready. Run 'provision' to create infrastructure. |
| 94 | +``` |
| 95 | + |
| 96 | +#### Provisioned State |
| 97 | + |
| 98 | +```text |
| 99 | +Environment: my-environment |
| 100 | +State: Provisioned |
| 101 | +Provider: LXD |
| 102 | +
|
| 103 | +Infrastructure: |
| 104 | + Instance IP: 10.140.190.14 |
| 105 | + SSH Port: 22 |
| 106 | + SSH User: ubuntu |
| 107 | + SSH Key: /home/user/.ssh/torrust_deployer_key |
| 108 | +
|
| 109 | +Connection: |
| 110 | + ssh -i /home/user/.ssh/torrust_deployer_key [email protected] |
| 111 | +
|
| 112 | +Next step: Run 'configure' to set up the system. |
| 113 | +``` |
| 114 | + |
| 115 | +#### Running State |
| 116 | + |
| 117 | +```text |
| 118 | +Environment: my-environment |
| 119 | +State: Running |
| 120 | +Provider: Hetzner Cloud |
| 121 | +
|
| 122 | +Infrastructure: |
| 123 | + Instance IP: 157.10.23.45 |
| 124 | + SSH Port: 22 |
| 125 | + SSH User: root |
| 126 | +
|
| 127 | +Tracker Services: |
| 128 | + UDP Trackers: |
| 129 | + - udp://157.10.23.45:6868/announce |
| 130 | + - udp://157.10.23.45:6969/announce |
| 131 | + HTTP Tracker: |
| 132 | + - http://157.10.23.45:7070/announce |
| 133 | + - Health: http://157.10.23.45:7070/health_check |
| 134 | + API Endpoint: |
| 135 | + - http://157.10.23.45:1212/api |
| 136 | + - Health: http://157.10.23.45:1212/api/health_check |
| 137 | +
|
| 138 | +Status: ✓ All services running |
| 139 | +``` |
| 140 | + |
| 141 | +### Data Transfer Objects |
| 142 | + |
| 143 | +**EnvironmentInfo**: |
| 144 | + |
| 145 | +```rust |
| 146 | +pub struct EnvironmentInfo { |
| 147 | + pub name: String, |
| 148 | + pub state: EnvironmentState, |
| 149 | + pub provider: String, |
| 150 | + pub created_at: Option<String>, |
| 151 | + pub infrastructure: Option<InfrastructureInfo>, |
| 152 | + pub services: Option<ServicesInfo>, |
| 153 | + pub next_step: String, |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +**InfrastructureInfo**: |
| 158 | + |
| 159 | +```rust |
| 160 | +pub struct InfrastructureInfo { |
| 161 | + pub instance_ip: IpAddr, |
| 162 | + pub ssh_port: u16, |
| 163 | + pub ssh_user: String, |
| 164 | + pub ssh_key_path: String, |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +**ServicesInfo**: |
| 169 | + |
| 170 | +```rust |
| 171 | +pub struct ServicesInfo { |
| 172 | + pub udp_trackers: Vec<url::Url>, |
| 173 | + pub http_tracker: Option<url::Url>, |
| 174 | + pub http_tracker_health: Option<url::Url>, |
| 175 | + pub api_endpoint: Option<url::Url>, |
| 176 | + pub api_health: Option<url::Url>, |
| 177 | +} |
| 178 | +``` |
| 179 | + |
| 180 | +### Error Handling |
| 181 | + |
| 182 | +**ShowCommandError** enum with explicit variants: |
| 183 | + |
| 184 | +```rust |
| 185 | +#[derive(Debug, thiserror::Error)] |
| 186 | +pub enum ShowCommandError { |
| 187 | + #[error("Environment '{0}' not found")] |
| 188 | + EnvironmentNotFound(EnvironmentName), |
| 189 | + |
| 190 | + #[error("Failed to load environment: {0}")] |
| 191 | + LoadError(#[from] EnvironmentLoadError), |
| 192 | + |
| 193 | + #[error("Invalid environment state: {0}")] |
| 194 | + InvalidState(String), |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +All errors must include actionable messages guiding users on how to resolve the issue. |
| 199 | + |
| 200 | +## Implementation Plan |
| 201 | + |
| 202 | +### Phase 1: Presentation Layer - CLI Skeleton (1-2 hours) |
| 203 | + |
| 204 | +- [ ] Create CLI subcommand in `src/presentation/input/cli/subcommands/show.rs` |
| 205 | +- [ ] Add `Show` variant to `Commands` enum in `src/presentation/input/cli/commands.rs` |
| 206 | +- [ ] Add command dispatch in `src/presentation/dispatch/mod.rs` |
| 207 | +- [ ] Add placeholder handler returning "Not implemented" message via UserOutput |
| 208 | +- [ ] Verify command appears in `--help` output |
| 209 | +- [ ] Create initial E2E test in `tests/e2e/commands/show_command.rs` (creates environment, runs show) |
| 210 | +- [ ] Manual CLI testing - command is runnable |
| 211 | + |
| 212 | +**Result**: Command runnable from CLI with placeholder message. E2E test validates basic execution. |
| 213 | + |
| 214 | +### Phase 2: Application Layer Foundation (2-3 hours) |
| 215 | + |
| 216 | +- [ ] Create `ShowCommandHandler` in `src/application/commands/show/mod.rs` |
| 217 | +- [ ] Create `ShowCommandError` enum in `src/application/commands/show/error.rs` |
| 218 | +- [ ] Create `EnvironmentInfo` DTO in `src/application/commands/show/info.rs` |
| 219 | +- [ ] Implement environment loading with error handling (environment not found) |
| 220 | +- [ ] Extract basic info (name, state, provider) from environment |
| 221 | +- [ ] Display basic information via UserOutput (no println!/eprintln!) |
| 222 | +- [ ] Comprehensive unit tests for handler logic and error scenarios |
| 223 | +- [ ] Update E2E test to validate basic info display |
| 224 | + |
| 225 | +**Result**: Command displays environment name, state, and provider. |
| 226 | + |
| 227 | +### Phase 3: State-Aware Information Extraction (3-4 hours) |
| 228 | + |
| 229 | +- [ ] Implement state-specific extraction using existing environment data |
| 230 | +- [ ] Add infrastructure details for Provisioned state (IP, SSH port, SSH user, SSH key path) |
| 231 | +- [ ] Handle missing runtime_outputs gracefully with clear errors |
| 232 | +- [ ] Extract service configuration for Running state from tracker config |
| 233 | +- [ ] Handle all environment states (Created, Configured, Released, Running, etc.) |
| 234 | +- [ ] Handle invalid/corrupted state data |
| 235 | +- [ ] Comprehensive unit tests for all states and edge cases |
| 236 | +- [ ] Update E2E test to validate state-specific details |
| 237 | + |
| 238 | +**Result**: Command shows state-aware information for all environment states. |
| 239 | + |
| 240 | +**Note**: Uses only data already in environment JSON - no new fields yet. |
| 241 | + |
| 242 | +### Phase 4: Output Formatting (2-3 hours) |
| 243 | + |
| 244 | +- [ ] Implement output formatter using UserOutput |
| 245 | +- [ ] Add state-aware formatting for each environment state |
| 246 | +- [ ] Include next-step guidance based on current state |
| 247 | +- [ ] Add visual improvements (colors, spacing, structured output) |
| 248 | +- [ ] Unit tests for formatting logic |
| 249 | +- [ ] Update E2E test to validate output formatting |
| 250 | +- [ ] Manual terminal testing |
| 251 | + |
| 252 | +**Result**: Human-friendly, well-formatted output with next-step guidance. |
| 253 | + |
| 254 | +### Phase 5: Testing Strategy Analysis and Documentation (2-3 hours) |
| 255 | + |
| 256 | +- [ ] Analyze E2E testing strategies for different states: |
| 257 | + - **Strategy 1**: Call show in existing workflow tests after each state transition |
| 258 | + - **Strategy 2**: Mock internal state in dedicated E2E test |
| 259 | + - **Strategy 3**: Test different states via unit tests only (message formatting) |
| 260 | + - **Decision**: Use combination (Strategy 3 for formatting + Strategy 1 for E2E) |
| 261 | +- [ ] Implement chosen strategy |
| 262 | +- [ ] Add E2E tests for error scenarios (missing environment, corrupted data) |
| 263 | +- [ ] Verify test coverage meets requirements |
| 264 | +- [ ] Write user documentation in `docs/user-guide/commands/show.md` |
| 265 | +- [ ] Update `docs/console-commands.md` with show command |
| 266 | +- [ ] Update `docs/user-guide/commands.md` with command reference |
| 267 | +- [ ] Update `docs/roadmap.md` to mark task 5.1 as complete |
| 268 | + |
| 269 | +**Result**: Complete test coverage and user documentation. |
| 270 | + |
| 271 | +### Phase 6: Add Creation Timestamp (1-2 hours) |
| 272 | + |
| 273 | +- [ ] Add `created_at` field to Environment domain model |
| 274 | +- [ ] Update environment JSON serialization to include timestamp |
| 275 | +- [ ] Update `create` command to populate creation timestamp |
| 276 | +- [ ] Update `show` command to display creation timestamp |
| 277 | +- [ ] Unit tests for timestamp persistence and display |
| 278 | +- [ ] Update E2E test to validate timestamp display |
| 279 | + |
| 280 | +**Result**: Show command displays when environment was created. |
| 281 | + |
| 282 | +**Note**: Extends domain model with new field. No backward compatibility needed (early development). |
| 283 | + |
| 284 | +### Phase 7: Add Service URLs to RuntimeOutputs (2-3 hours) |
| 285 | + |
| 286 | +- [ ] Add `service_endpoints` field to RuntimeOutputs domain model (`src/domain/environment/runtime_outputs.rs`) |
| 287 | +- [ ] Define `ServiceEndpoints` struct with `url::Url` fields (not String) |
| 288 | +- [ ] Update `run` command to populate service URLs after successful startup |
| 289 | +- [ ] Update `show` command to read from RuntimeOutputs (with fallback to construction) |
| 290 | +- [ ] Unit tests for ServiceEndpoints persistence and display |
| 291 | +- [ ] Update E2E test to validate service URLs display |
| 292 | + |
| 293 | +**Result**: Service URLs stored in RuntimeOutputs and displayed in show command. |
| 294 | + |
| 295 | +**Note**: Makes service URLs first-class deployment state, available to all commands. |
| 296 | + |
| 297 | +## Acceptance Criteria |
| 298 | + |
| 299 | +> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations. |
| 300 | +
|
| 301 | +**Quality Checks**: |
| 302 | + |
| 303 | +- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` |
| 304 | + - All linters pass (markdown, yaml, toml, cspell, clippy, rustfmt, shellcheck) |
| 305 | + - All unit tests pass |
| 306 | + - Documentation builds successfully |
| 307 | + - E2E tests pass |
| 308 | + |
| 309 | +**Functional Requirements**: |
| 310 | + |
| 311 | +- [ ] Command displays environment name and state for all state types |
| 312 | +- [ ] Command shows IP, SSH port, SSH user, SSH key path for Provisioned+ states |
| 313 | +- [ ] Command includes ready-to-use SSH connection command |
| 314 | +- [ ] Command shows service URLs for Running state |
| 315 | +- [ ] Command provides next-step guidance based on state |
| 316 | +- [ ] Command handles missing environment with clear error + suggestion to use `list` command |
| 317 | +- [ ] Command handles invalid/corrupted data gracefully |
| 318 | +- [ ] Output is human-friendly and easy to read |
| 319 | +- [ ] Command execution is fast (< 100ms typical) |
| 320 | + |
| 321 | +**Technical Requirements**: |
| 322 | + |
| 323 | +- [ ] **All output via UserOutput** (no println!/eprintln!) |
| 324 | +- [ ] Follows DDD layer placement (Application + Presentation) |
| 325 | +- [ ] Error types use explicit enums (not anyhow) |
| 326 | +- [ ] Follows module organization conventions (public first, imports at top) |
| 327 | +- [ ] Follows import conventions (prefer imports over full paths) |
| 328 | +- [ ] Performance acceptable (< 100ms) |
| 329 | + |
| 330 | +**Testing Requirements**: |
| 331 | + |
| 332 | +- [ ] Unit tests cover ShowCommandHandler logic |
| 333 | +- [ ] Unit tests cover info extraction for all states |
| 334 | +- [ ] Unit tests cover output formatting |
| 335 | +- [ ] Unit tests cover all error scenarios and edge cases |
| 336 | +- [ ] Unit tests use behavior-driven naming (`it_should_*_when_*`) |
| 337 | +- [ ] E2E test validates Created environment |
| 338 | +- [ ] E2E test validates Provisioned environment |
| 339 | +- [ ] E2E test validates Running environment |
| 340 | +- [ ] E2E test validates error handling for missing environment |
| 341 | +- [ ] Test coverage includes missing data and invalid states |
| 342 | + |
| 343 | +**Documentation Requirements**: |
| 344 | + |
| 345 | +- [ ] Feature docs complete in `docs/features/environment-status-command/` |
| 346 | +- [ ] Show command section added to `docs/console-commands.md` |
| 347 | +- [ ] Command reference added to `docs/user-guide/commands.md` |
| 348 | +- [ ] Detailed guide created in `docs/user-guide/commands/show.md` |
| 349 | +- [ ] Roadmap updated (task 5.1 marked complete) |
| 350 | +- [ ] Inline code documentation (doc comments for public APIs) |
| 351 | +- [ ] Command help text complete (shows in `--help`) |
| 352 | + |
| 353 | +**Code Quality**: |
| 354 | + |
| 355 | +- [ ] No code duplication (DRY principle) |
| 356 | +- [ ] Clear separation of concerns (formatting vs logic vs presentation) |
| 357 | +- [ ] Meaningful variable and function names |
| 358 | +- [ ] Proper error context with actionable messages |
| 359 | +- [ ] Follows behavior-driven test naming (`it_should_*_when_*`, never `test_*`) |
| 360 | + |
| 361 | +## Related Documentation |
| 362 | + |
| 363 | +- **Feature Specification**: [docs/features/environment-status-command/specification.md](../features/environment-status-command/specification.md) |
| 364 | +- **Questions & Answers**: [docs/features/environment-status-command/questions.md](../features/environment-status-command/questions.md) |
| 365 | +- **Feature Overview**: [docs/features/environment-status-command/README.md](../features/environment-status-command/README.md) |
| 366 | +- **Codebase Architecture**: [docs/codebase-architecture.md](../codebase-architecture.md) |
| 367 | +- **Output Handling**: [docs/contributing/output-handling.md](../contributing/output-handling.md) |
| 368 | +- **Error Handling**: [docs/contributing/error-handling.md](../contributing/error-handling.md) |
| 369 | +- **Module Organization**: [docs/contributing/module-organization.md](../contributing/module-organization.md) |
| 370 | +- **Unit Testing Conventions**: [docs/contributing/testing/unit-testing.md](../contributing/testing/unit-testing.md) |
| 371 | + |
| 372 | +## Notes |
| 373 | + |
| 374 | +### Key Design Decisions |
| 375 | + |
| 376 | +- **Command Name**: `show` (not `status`) - reserves `status` for future service health checks |
| 377 | +- **No Remote Verification**: Displays stored state only - infrastructure verification is in `test` command |
| 378 | +- **Clear Command Separation**: |
| 379 | + - `show` - Display stored data (fast, no network) |
| 380 | + - `test` - Verify infrastructure (cloud-init, Docker, Docker Compose) |
| 381 | + - `status` (future) - Check service health (connectivity, health endpoints) |
| 382 | + |
| 383 | +### Development Approach |
| 384 | + |
| 385 | +- **Top-Down Development**: Implement presentation layer first for immediate runnability |
| 386 | +- **E2E Test Evolution**: Create in Phase 1, update at each phase for continuous validation |
| 387 | +- **Unit Tests**: Write at every phase for code added/modified |
| 388 | +- **Incremental Progress**: Each phase produces working, testable command with more features |
| 389 | + |
| 390 | +### Future Enhancements |
| 391 | + |
| 392 | +- **JSON Output Format**: Can be added later with `--format json` flag |
| 393 | +- **Provider-Specific Details**: LXD container ID/profile, Hetzner server ID/datacenter/type |
| 394 | + |
| 395 | +### Estimated Duration |
| 396 | + |
| 397 | +**Total**: 14-22 hours (2-3 days) for complete implementation |
| 398 | + |
| 399 | +**Target Completion**: January 2026 |
0 commit comments