-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathhandler.rs
More file actions
222 lines (194 loc) · 7.54 KB
/
Copy pathhandler.rs
File metadata and controls
222 lines (194 loc) · 7.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Show command handler implementation
//!
//! **Purpose**: Display environment information and status
//!
//! This handler retrieves and displays information about an environment
//! from storage. It is a read-only operation that does not modify any state
//! or make any network calls.
//!
//! ## Display Strategy
//!
//! The show command displays state-aware information:
//!
//! 1. **Basic Info (all states)**: Environment name, state, provider
//! 2. **Infrastructure (Provisioned+)**: IP, SSH port, SSH user, SSH key path
//! 3. **Next Step**: Guidance based on current state
//!
//! ## Design Rationale
//!
//! This command accepts an `EnvironmentName` in its `execute` method to align with other
//! command handlers (`ProvisionCommandHandler`, `ConfigureCommandHandler`). This design:
//!
//! - Loads environment from repository (consistent pattern across all handlers)
//! - Allows showing environments regardless of compile-time state (runtime extraction)
//! - Read-only operation - no state modifications
use std::sync::Arc;
use tracing::instrument;
use super::errors::ShowCommandHandlerError;
use super::info::{EnvironmentInfo, GrafanaInfo, InfrastructureInfo, PrometheusInfo, ServiceInfo};
use crate::domain::environment::repository::EnvironmentRepository;
use crate::domain::environment::state::AnyEnvironmentState;
use crate::domain::EnvironmentName;
/// Default SSH port when not specified
const DEFAULT_SSH_PORT: u16 = 22;
/// `ShowCommandHandler` extracts and formats environment information for display
///
/// **Purpose**: Read-only information extraction from environment state
///
/// This handler loads an environment from storage and extracts information
/// relevant to the environment's current state. It never modifies state
/// or makes network calls.
///
/// ## Information Extraction
///
/// - **All states**: Name, state name, provider
/// - **Provisioned+**: Infrastructure details (IP, SSH credentials)
/// - **All states**: Next step guidance
pub struct ShowCommandHandler {
repository: Arc<dyn EnvironmentRepository>,
}
impl ShowCommandHandler {
/// Create a new `ShowCommandHandler`
#[must_use]
pub fn new(repository: Arc<dyn EnvironmentRepository>) -> Self {
Self { repository }
}
/// Execute the show command workflow
///
/// Loads the environment and extracts state-aware information for display.
///
/// # Arguments
///
/// * `env_name` - The name of the environment to show
///
/// # Returns
///
/// * `Ok(EnvironmentInfo)` - Information about the environment
/// * `Err(ShowCommandHandlerError)` - If the environment cannot be loaded
///
/// # Errors
///
/// Returns an error if:
/// * Environment not found
/// * Environment state file is corrupted or unreadable
#[instrument(
name = "show_command",
skip_all,
fields(
command_type = "show",
environment = %env_name
)
)]
pub fn execute(
&self,
env_name: &EnvironmentName,
) -> Result<EnvironmentInfo, ShowCommandHandlerError> {
let any_env = self.load_environment(env_name)?;
Ok(Self::extract_info(&any_env))
}
/// Load environment from repository
fn load_environment(
&self,
env_name: &EnvironmentName,
) -> Result<AnyEnvironmentState, ShowCommandHandlerError> {
if !self.repository.exists(env_name)? {
return Err(ShowCommandHandlerError::EnvironmentNotFound {
name: env_name.to_string(),
});
}
self.repository.load(env_name)?.ok_or_else(|| {
ShowCommandHandlerError::EnvironmentNotFound {
name: env_name.to_string(),
}
})
}
/// Extract information from environment based on its state
fn extract_info(any_env: &AnyEnvironmentState) -> EnvironmentInfo {
let name = any_env.name().to_string();
let state = any_env.state_display_name().to_string();
let provider = any_env.provider_display_name().to_string();
let created_at = any_env.created_at();
let state_name = any_env.state_name().to_string();
let mut info = EnvironmentInfo::new(name, state, provider, created_at, state_name);
// Add infrastructure info if instance IP is available
if let Some(instance_ip) = any_env.instance_ip() {
let ssh_creds = any_env.ssh_credentials();
let ssh_port = any_env.ssh_port();
let infra = InfrastructureInfo::new(
instance_ip,
if ssh_port == 0 {
DEFAULT_SSH_PORT
} else {
ssh_port
},
ssh_creds.ssh_username.to_string(),
ssh_creds.ssh_priv_key_path.to_string_lossy().to_string(),
);
info = info.with_infrastructure(infra);
// Add service info for Released/Running states
if Self::should_show_services(any_env.state_name()) {
// Always compute from tracker config to show proper service information
// including TLS domains, localhost hints, and HTTPS status
let tracker_config = any_env.tracker_config();
let grafana_config = any_env.grafana_config();
let services =
ServiceInfo::from_tracker_config(tracker_config, instance_ip, grafana_config);
info = info.with_services(services);
// Add Prometheus info if configured
if any_env.prometheus_config().is_some() {
info = info.with_prometheus(PrometheusInfo::default_internal());
}
// Add Grafana info if configured
if let Some(grafana) = any_env.grafana_config() {
info = info.with_grafana(GrafanaInfo::from_config(grafana, instance_ip));
}
}
}
info
}
/// Determine if services should be shown based on state
///
/// Services are shown for states where the tracker configuration has been
/// deployed and services may be running (Released, Running, or related failed states).
fn should_show_services(state_name: &str) -> bool {
matches!(
state_name,
"released" | "running" | "release_failed" | "run_failed"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
mod should_show_services {
use super::*;
#[test]
fn it_should_show_services_for_released_state() {
assert!(ShowCommandHandler::should_show_services("released"));
}
#[test]
fn it_should_show_services_for_running_state() {
assert!(ShowCommandHandler::should_show_services("running"));
}
#[test]
fn it_should_show_services_for_release_failed_state() {
assert!(ShowCommandHandler::should_show_services("release_failed"));
}
#[test]
fn it_should_show_services_for_run_failed_state() {
assert!(ShowCommandHandler::should_show_services("run_failed"));
}
#[test]
fn it_should_not_show_services_for_created_state() {
assert!(!ShowCommandHandler::should_show_services("created"));
}
#[test]
fn it_should_not_show_services_for_provisioned_state() {
assert!(!ShowCommandHandler::should_show_services("provisioned"));
}
#[test]
fn it_should_not_show_services_for_configured_state() {
assert!(!ShowCommandHandler::should_show_services("configured"));
}
}
}