This feature adds a new console command to display environment information with state-aware details. Users can quickly view essential information about their deployment environments without manually inspecting JSON files.
Currently, users must manually inspect environment JSON files in data/{environment-name}/environment.json to check environment state and details. This is not user-friendly and doesn't provide a clear view of the environment's current status.
Users need a convenient way to:
- Check the current state of an environment
- View basic environment information (name, state, creation time)
- See state-specific details (IP address for provisioned environments, service URLs for running environments)
- Quickly understand environment status without reading JSON files
- Goal 1: Provide a console command to display environment information
- Goal 2: Show state-aware details (different information based on environment state)
- Goal 3: Use human-friendly output formatting consistent with existing commands
- Goal 4: Handle errors gracefully (environment not found, invalid state, etc.)
- Future support for JSON output format
- Verbosity levels for more or less detail
- Provider-specific information display
What this feature explicitly does NOT aim to do:
- Real-time monitoring (CPU, memory, network usage)
- Historical state transition tracking
- Log viewing or tailing
- Multiple environment listing (that's the separate
listcommand) - Infrastructure verification and smoke tests (use the
testcommand instead) - Remote service health checks or connectivity validation
Implement a new read-only command that:
- Loads environment data from the persisted JSON file
- Extracts relevant information based on current state
- Formats and displays information using UserOutput system
- Provides clear error messages for common failure scenarios
The command follows the existing command architecture pattern:
- Application Layer:
ShowCommand(orStatusCommand) with command handler - Presentation Layer: Console subcommand for CLI interface
- Read-Only: No state modifications, only information display
Decision: Use show
Rationale:
- The command displays stored environment information without verification
- Aligns with common CLI patterns (
kubectl get,docker inspect,git show) - Reserves
statusfor future infrastructure/service status checking - Clearer intent: "show me information about this environment"
Future Command Separation:
show(this feature) - Display stored environment data (fast, read-only, no network calls)test(already implemented) - Validate infrastructure components (cloud-init, Docker, Docker Compose)status(future) - Check runtime status of deployed services (health checks, connectivity validation)
┌─────────────────────────────────────────────────────┐
│ User │
└───────────────────────┬─────────────────────────────┘
│
│ torrust-tracker-deployer show <env>
↓
┌─────────────────────────────────────────────────────┐
│ Presentation Layer │
│ - Parse CLI arguments │
│ - Create ExecutionContext │
│ - Dispatch to ShowCommand │
└───────────────────────┬─────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────┐
│ Application Layer │
│ - ShowCommandHandler │
│ - Load environment from persistence │
│ - Extract state-specific information │
│ - Format output via UserOutput │
└───────────────────────┬─────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────┐
│ Domain Layer │
│ - Environment<S> entity with state │
│ - Read-only access to environment data │
└─────────────────────────────────────────────────────┘
↑
│
┌─────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ - EnvironmentLoader (read JSON from filesystem) │
│ - FileSystemRepository │
└─────────────────────────────────────────────────────┘
Environment: my-environment
State: Created
Provider: LXD
The environment configuration is ready. Run 'provision' to create infrastructure.
Note: Creation timestamp will be added in Phase 7 of the implementation.
Environment: my-environment
State: Provisioned
Provider: LXD
Infrastructure:
Instance IP: 10.140.190.14
SSH Port: 22
SSH User: ubuntu
SSH Key: /home/user/.ssh/torrust_deployer_key
Connection:
ssh -i /home/user/.ssh/torrust_deployer_key ubuntu@10.140.190.14
Next step: Run 'configure' to set up the system.
Environment: my-environment
State: Configured
Provider: LXD
Infrastructure:
Instance IP: 10.140.190.14
SSH Port: 22
SSH User: ubuntu
SSH Key: /home/user/.ssh/torrust_deployer_key
System:
Docker: Installed
Docker Compose: Installed
Firewall: Configured
Connection:
ssh -i /home/user/.ssh/torrust_deployer_key ubuntu@10.140.190.14
Next step: Run 'release' to deploy application configuration.
Environment: my-environment
State: Running
Provider: Hetzner Cloud
Infrastructure:
Instance IP: 157.10.23.45
SSH Port: 22
SSH User: root
Tracker Services:
UDP Trackers:
- udp://157.10.23.45:6868/announce
- udp://157.10.23.45:6969/announce
HTTP Tracker:
- http://157.10.23.45:7070/announce
- Health: http://157.10.23.45:7070/health_check
API Endpoint:
- http://157.10.23.45:1212/api
- Health: http://157.10.23.45:1212/api/health_check
Status: ✓ All services running
-
Command Name:
show- Displays stored environment information only
- Consistent with industry CLI patterns for information display
- Reserves
statusfor future service health/runtime status checks - Clear separation of concerns:
show(data),test(infrastructure),status(services)
-
Read-Only Operation
- No state modifications
- Fast and safe to run repeatedly
- No side effects on infrastructure
-
Human-Friendly Output First
- Initial implementation uses pretty-printed output with formatting
- JSON output can be added later with
--format jsonflag - Consistent with existing command output patterns
-
State-Aware Information
- Different details shown based on environment state
- Provides relevant, actionable information for each state
- Clear next-step guidance
-
No Remote Verification
- Display stored state information only
- No network calls to remote infrastructure
- Fast and reliable operation
- Infrastructure verification is handled by the separate
testcommand
- Pros: Common for checking service state, familiar to users
- Cons: Implies dynamic health checking, may cause confusion with service status
- Decision: Not chosen -
showis clearer for information display
- Pros: More accurate real-time status
- Cons: Slower, can fail on network issues, adds complexity, duplicates the
testcommand functionality - Decision: Not chosen - infrastructure verification is the responsibility of the
testcommand
- Pros: Maximum flexibility immediately
- Cons: More implementation work, delays delivery
- Decision: Not chosen - implement human-friendly output first, add JSON later
No major architectural changes required. This follows the existing command pattern:
- New application layer command handler
- New presentation layer console subcommand
- Reuses existing environment loading infrastructure
Purpose: Application layer command handler that loads environment and formats output
Location: src/application/commands/show/mod.rs
Interface:
use crate::domain::environment::{Environment, EnvironmentName};
use crate::infrastructure::persistence::EnvironmentLoader;
use crate::presentation::views::UserOutput;
use std::sync::Arc;
pub struct ShowCommandHandler {
environment_loader: Arc<dyn EnvironmentLoader>,
}
impl ShowCommandHandler {
pub fn new(environment_loader: Arc<dyn EnvironmentLoader>) -> Self {
Self { environment_loader }
}
/// Execute the show command for the given environment name
pub async fn execute(
&self,
environment_name: &EnvironmentName,
user_output: &Arc<UserOutput>,
) -> Result<(), ShowCommandError> {
// 1. Load environment (any state)
let environment = self.environment_loader.load(environment_name)?;
// 2. Extract information based on state
let info = self.extract_info(&environment);
// 3. Format and display via UserOutput
self.display_info(&info, user_output);
Ok(())
}
fn extract_info(&self, environment: &Environment) -> EnvironmentInfo {
// Extract state-specific information
todo!()
}
fn display_info(&self, info: &EnvironmentInfo, user_output: &Arc<UserOutput>) {
// Format and display information
todo!()
}
}Dependencies: EnvironmentLoader, UserOutput
Purpose: Presentation layer CLI interface for the show command
Location: src/presentation/input/cli/subcommands/show.rs
Interface:
use clap::Args;
#[derive(Args)]
pub struct ShowCommand {
/// Name of the environment to show
#[arg(value_name = "ENVIRONMENT")]
pub environment: String,
// Future options
// #[arg(long, value_name = "FORMAT")]
// pub format: Option<OutputFormat>,
//
// #[arg(long)]
// pub check_remote: bool,
}Integration Point: Add to main CLI enum in src/presentation/input/cli/commands.rs
Purpose: Data transfer object for environment information
Location: src/application/commands/show/info.rs
Interface:
use crate::domain::environment::EnvironmentState;
use std::net::IpAddr;
pub struct EnvironmentInfo {
pub name: String,
pub state: EnvironmentState,
pub provider: String,
pub created_at: Option<String>,
pub infrastructure: Option<InfrastructureInfo>,
pub services: Option<ServicesInfo>,
pub next_step: String,
}
pub struct InfrastructureInfo {
pub instance_ip: IpAddr,
pub ssh_port: u16,
pub ssh_user: String,
pub ssh_key_path: String,
}
pub struct ServicesInfo {
pub udp_trackers: Vec<url::Url>,
pub http_tracker: Option<url::Url>,
pub http_tracker_health: Option<url::Url>,
pub api_endpoint: Option<url::Url>,
pub api_health: Option<url::Url>,
}No new persistent data structures required. The command reads existing environment JSON:
{
"environment": {
"name": "my-environment",
"state": "Provisioned",
"provider_config": { ... },
"runtime_outputs": {
"instance_ip": "10.140.190.14"
}
}
}New public API in application layer:
// src/application/commands/mod.rs
pub mod show;
pub use show::ShowCommandHandler;New console subcommand:
// src/presentation/input/cli/commands.rs
#[derive(Subcommand)]
pub enum Commands {
// ... existing commands
Show(ShowCommand),
}No new configuration required. Command uses existing environment data directory structure.
| File Path | Purpose | Effort |
|---|---|---|
src/application/commands/show/mod.rs |
ShowCommandHandler | Medium |
src/application/commands/show/handler.rs |
Command execution logic | Medium |
src/application/commands/show/info.rs |
EnvironmentInfo DTOs | Low |
src/application/commands/show/formatter.rs |
Output formatting logic | Medium |
src/application/commands/show/error.rs |
Command-specific errors | Low |
src/presentation/input/cli/subcommands/show.rs |
CLI subcommand definition | Low |
tests/e2e/commands/show_command.rs |
E2E tests for show command | Medium |
docs/user-guide/commands/show.md |
User documentation | Low |
| File Path | Changes Required | Effort |
|---|---|---|
src/application/commands/mod.rs |
Export show command module | Low |
src/presentation/input/cli/commands.rs |
Add Show variant to Commands enum | Low |
src/presentation/dispatch/mod.rs |
Add show command dispatch | Low |
docs/console-commands.md |
Add show command documentation | Low |
docs/user-guide/commands.md |
Add command reference | Low |
docs/roadmap.md |
Mark task 5.1 as complete | Low |
None. This is a new command with no impact on existing functionality.
- Positive: Fast read-only operation, no network calls (initially)
- Neutral: Performance comparable to other read-only operations
- Impact: Minimal - just file read and output formatting
- Read-only operation - no security risks
- Displays IP addresses and SSH information - user should have access to this
- No credential exposure - SSH keys not displayed
- Environment data already stored locally with file permissions
Top-Down Approach: Implement from presentation layer down to domain layer for iterative, testable development.
Why Top-Down:
- Command is runnable from the very beginning
- E2E tests can be written early and validate incrementally
- Features appear progressively in output as implementation progresses
- Faster feedback loop - see results immediately
- Natural workflow matches user interaction
Development Flow:
- Presentation Layer - CLI subcommand skeleton (shows "Not implemented")
- Application Layer - Command handler skeleton with error handling (shows basic info)
- Add state-specific details - Progressively add Provisioned, Configured, Running state info using existing environment data (with error handling for missing data)
- Refine formatting - Improve output appearance and user experience
- Testing and documentation - Comprehensive testing and user guides
- Extend internal state - Add new fields to domain model (creation timestamp, service URLs)
- Enhanced display - Show newly available information
Each step produces a working, testable command with incrementally more features. Error handling is integrated at each phase rather than being deferred. Start with existing data, then extend what we store to display more information.
- Create CLI subcommand definition in
src/presentation/input/cli/subcommands/show.rs - Add Show variant to Commands enum in
src/presentation/input/cli/commands.rs - Add basic command dispatch in
src/presentation/dispatch/mod.rs - Add placeholder handler that returns "Not implemented" message
- Verify command appears in
--helpoutput - Create initial E2E test in
tests/e2e/commands/show_command.rs(create environment, run show command) - Manual CLI testing - command is runnable
Estimated Duration: 1-2 hours
Result: Command is runnable from the CLI, shows "Not implemented" placeholder message. E2E test validates basic command execution.
Testing: E2E test will evolve throughout implementation as we add more features. Initially validates command doesn't crash and produces output.
- Create
ShowCommandHandlerstructure insrc/application/commands/show/mod.rs - Create
ShowCommandErrorenum insrc/application/commands/show/error.rs - Implement environment loading logic with error handling
- Handle environment not found errors with clear messages
- Create
EnvironmentInfoDTO insrc/application/commands/show/info.rs - Add basic state extraction logic (name, state, provider)
- Display basic information via UserOutput
- Add comprehensive unit tests for command handler (including error scenarios)
- Update E2E test to validate basic info display (name, state, provider)
Estimated Duration: 2-3 hours
Result: Command displays environment name, state, and provider for all environments.
Testing: Unit tests cover handler logic, error scenarios, and DTO creation. E2E test validates end-to-end basic info display.
- Implement state-specific info extraction using existing environment data
- Add infrastructure details (IP, SSH user, SSH port) for Provisioned state from runtime_outputs
- Handle missing runtime_outputs gracefully with clear error messages
- Extract service configuration for Running state from tracker config
- Handle all environment states (Created, Configured, Released, etc.)
- Handle invalid or corrupted environment state data
- Add comprehensive unit tests for info extraction (including edge cases and missing data)
- Update E2E test to validate state-specific details
Estimated Duration: 3-4 hours
Note: This phase uses only data already stored in environment JSON - no new fields added yet.
Testing: Unit tests cover all state transitions, missing data scenarios, and edge cases. E2E test validates state-specific information extraction.
- Implement output formatter with UserOutput
- Add state-aware formatting
- Include next-step guidance
- Add visual improvements (colors, spacing)
- Add unit tests for output formatting logic
- Update E2E test to validate output formatting and next-step guidance
- Manual testing of output on terminal
Estimated Duration: 2-3 hours
Testing: Unit tests cover formatting logic for all states and visual elements. E2E test validates complete formatted output.
- Analyze and decide on E2E testing strategy for different environment states:
- Strategy 1: Call show command in existing E2E workflow tests (
e2e_complete_workflow_tests,e2e_deployment_workflow_tests,e2e_infrastructure_lifecycle_tests) after each state transition - Strategy 2: Mock internal state in dedicated E2E test before running show command
- Strategy 3: Test different states only via unit tests for message building logic (decoupled from internal state)
- Strategy 1: Call show command in existing E2E workflow tests (
- Implement chosen testing strategy
- Add additional E2E tests for error scenarios (non-existent environment, corrupted data)
- Verify test coverage meets requirements (unit + E2E)
- Write user documentation in
docs/user-guide/commands/show.md - Update
docs/console-commands.mdwith show command documentation - Update
docs/user-guide/commands.mdwith command reference - Update
docs/roadmap.mdto mark task 5.1 as complete
Estimated Duration: 2-3 hours
Testing Decision Factors:
- Strategy 1: Most realistic, tests actual state transitions, but requires running through full workflows (slower, more complex)
- Strategy 2: More isolated, faster, but requires mocking infrastructure (may not catch integration issues)
- Strategy 3: Fastest, best for unit testing message logic, but doesn't validate end-to-end state reading
Recommended Approach: Combination of strategies - Strategy 3 for message formatting unit tests, Strategy 1 for realistic E2E validation in existing workflow tests.
- Add
created_atfield to Environment domain model - Update environment JSON serialization to include timestamp
- Update
createcommand to populate creation timestamp - Update
showcommand to display creation timestamp - Add unit tests for timestamp persistence and display
- Update E2E test to validate timestamp display
Estimated Duration: 1-2 hours
Note: This extends the internal state with new information. Since this is early development with no production users, no backward compatibility or migration logic is needed.
Testing: Unit tests cover timestamp creation, serialization, and display. E2E test validates timestamp appears in output.
- Add
service_endpointsfield to RuntimeOutputs domain model - Define ServiceEndpoints struct with URL fields and health check endpoints
- Update
runcommand to populate service URLs after successful startup - Update
showcommand to read URLs from RuntimeOutputs (with fallback to construction) - Add SSH key path to infrastructure info
- Add comprehensive unit tests for service endpoints persistence and display
- Update E2E test to validate service URLs display
Estimated Duration: 2-3 hours
Note: This enhancement makes service URLs a first-class part of the deployment state, available to all commands without rebuilding.
Testing: Unit tests cover ServiceEndpoints creation, serialization, URL construction, and fallback logic. E2E test validates service URLs appear in output for Running state.
Testing: Unit tests cover ServiceEndpoints creation, serialization, URL construction, and fallback logic. E2E test validates service URLs appear in output for Running state.
Total Estimated Duration: 14-22 hours (2-3 days) for complete implementation including enhancements
Notes:
- Error handling is integrated throughout all phases rather than being a separate phase, ensuring robust error management at each development step.
- Unit tests are written incrementally at each phase for code added or modified in that phase.
- E2E test is created in Phase 1 and evolves throughout implementation, providing continuous validation of the feature.
- Testing strategy for different environment states is analyzed and decided in Phase 5.
- Command displays environment name and state for all state types
- Command shows IP address, SSH port, SSH user, and SSH key path for Provisioned environments
- Command includes ready-to-use SSH connection command for Provisioned environments
- Command shows service URLs for Running environments
- Command provides next-step guidance based on state
- Command handles non-existent environments with clear error message
- Command handles invalid or corrupted environment data gracefully
- Output is human-friendly and easy to read
- Command execution is fast (< 100ms for typical environment)
- Code follows project conventions and style guidelines
- All linters pass (clippy, rustfmt, shellcheck, etc.)
- No compiler warnings
- Uses UserOutput for all output (no println! or eprintln!)
- Follows DDD layer placement (Application and Presentation layers)
- Error types use explicit enums (not anyhow)
- Performance is acceptable (< 100ms)
- Unit tests cover ShowCommandHandler logic
- Unit tests cover info extraction for all states
- Unit tests cover output formatting
- Unit tests cover error scenarios
- E2E test validates command with Created environment
- E2E test validates command with Provisioned environment
- E2E test validates command with Running environment
- E2E test validates error handling for non-existent environment
- Edge cases tested (missing data, invalid state, etc.)
-
docs/features/environment-status-command/- Feature documentation complete -
docs/console-commands.md- Show command section added -
docs/user-guide/commands.md- Show command reference added -
docs/user-guide/commands/show.md- Detailed command guide created -
docs/roadmap.md- Task 5.1 marked as complete - Inline code documentation (doc comments)
- Command help text in CLI (--help)
- No code duplication (DRY principle)
- Clear separation of concerns (formatting vs logic)
- Meaningful variable and function names
- Proper error context and messages
- Follows module organization conventions
Add support for machine-readable JSON output:
torrust-tracker-deployer show my-environment --format jsonOutput:
{
"name": "my-environment",
"state": "Running",
"provider": "hetzner",
"infrastructure": {
"ip_address": "157.10.23.45",
"ssh_port": 22,
"ssh_user": "root"
},
"services": {
"udp_trackers": ["udp://157.10.23.45:6868", "udp://157.10.23.45:6969"],
"http_tracker": "http://157.10.23.45:7070",
"api_endpoint": "http://157.10.23.45:1212/api"
},
"created_at": "2025-12-16T10:30:00Z",
"updated_at": "2025-12-16T14:22:30Z"
}Follow output handling guidelines in docs/contributing/output-handling.md:
// ✓ Correct
user_output.lock().borrow_mut().info("Environment: my-environment");
user_output.lock().borrow_mut().success("State: Running");
// ✗ Wrong - NEVER use these
println!("Environment: my-environment");
eprintln!("Error loading environment");Follow error handling principles in docs/contributing/error-handling.md:
// Define explicit error types
#[derive(Debug, thiserror::Error)]
pub enum ShowCommandError {
#[error("Environment '{0}' not found")]
EnvironmentNotFound(EnvironmentName),
#[error("Failed to load environment: {0}")]
LoadError(#[from] EnvironmentLoadError),
#[error("Invalid environment state: {0}")]
InvalidState(String),
}Follow module organization conventions in docs/contributing/module-organization.md:
- Public items first
- High-level abstractions before implementation details
- Imports at the top of file
- Prefer imports over full paths
Follow unit testing conventions in docs/contributing/testing/unit-testing/naming-conventions.md:
- Use behavior-driven naming:
it_should_display_ip_when_environment_is_provisioned() - Never use
test_prefix - Follow What-When-Then structure
Implementation Note: Currently, service URLs are constructed on-demand from environment configuration. For better performance and consistency, consider updating the RuntimeOutputs domain model (in src/domain/environment/runtime_outputs.rs) to store service URLs after the run command successfully starts services.
Benefits:
- URLs available to all commands without rebuilding
- Ensures consistency across commands
- Single source of truth for deployed service endpoints
- Aligns with the RuntimeOutputs purpose (data generated during deployment)
Proposed RuntimeOutputs Extension:
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceEndpoints {
pub udp_trackers: Vec<url::Url>,
pub http_tracker: Option<url::Url>,
pub http_tracker_health: Option<url::Url>,
pub api_endpoint: Option<url::Url>,
pub api_health: Option<url::Url>,
}
pub struct RuntimeOutputs {
pub instance_ip: Option<IpAddr>,
pub provision_method: Option<ProvisionMethod>,
// Add after run command succeeds:
pub service_endpoints: Option<ServiceEndpoints>,
}Implementation Strategy:
- Update
RuntimeOutputswithservice_endpointsfield - Modify
runcommand to populate URLs after successful service startup - Update
showcommand to read fromRuntimeOutputswhen available - Fall back to constructing URLs if
service_endpointsisNone(for backward compatibility)
This enhancement can be implemented as part of this feature or as a separate preliminary task.