torrust_tracker_deployer_lib/presentation/cli/controllers/register/
handler.rs1use std::cell::RefCell;
7use std::net::IpAddr;
8use std::sync::Arc;
9
10use parking_lot::ReentrantMutex;
11
12use crate::application::command_handlers::RegisterCommandHandler;
13use crate::domain::environment::name::EnvironmentName;
14use crate::domain::environment::repository::EnvironmentRepository;
15use crate::domain::environment::state::Provisioned;
16use crate::domain::environment::Environment;
17use crate::presentation::cli::input::cli::OutputFormat;
18use crate::presentation::cli::views::commands::register::{
19 JsonView, RegisterDetailsData, TextView,
20};
21use crate::presentation::cli::views::progress::ProgressReporter;
22use crate::presentation::cli::views::Render;
23use crate::presentation::cli::views::UserOutput;
24use crate::shared::clock::Clock;
25
26use super::errors::RegisterSubcommandError;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30enum RegisterStep {
31 ValidateInput,
32 CreateCommandHandler,
33 RegisterInstance,
34}
35
36impl RegisterStep {
37 const ALL: &'static [Self] = &[
39 Self::ValidateInput,
40 Self::CreateCommandHandler,
41 Self::RegisterInstance,
42 ];
43
44 const fn count() -> usize {
46 Self::ALL.len()
47 }
48
49 fn description(self) -> &'static str {
51 match self {
52 Self::ValidateInput => "Validating input",
53 Self::CreateCommandHandler => "Creating command handler",
54 Self::RegisterInstance => "Registering instance",
55 }
56 }
57}
58
59pub struct RegisterCommandController {
77 repository: Arc<dyn EnvironmentRepository + Send + Sync>,
78 clock: Arc<dyn Clock>,
79 progress: ProgressReporter,
80}
81
82impl RegisterCommandController {
83 #[allow(clippy::needless_pass_by_value)] pub fn new(
86 repository: Arc<dyn EnvironmentRepository + Send + Sync>,
87 clock: Arc<dyn Clock>,
88 user_output: Arc<ReentrantMutex<RefCell<UserOutput>>>,
89 ) -> Self {
90 let progress = ProgressReporter::new(user_output, RegisterStep::count());
91
92 Self {
93 repository,
94 clock,
95 progress,
96 }
97 }
98
99 #[allow(clippy::result_large_err)]
123 pub async fn execute(
124 &mut self,
125 environment_name: &str,
126 instance_ip_str: &str,
127 ssh_port: Option<u16>,
128 output_format: OutputFormat,
129 ) -> Result<Environment<Provisioned>, RegisterSubcommandError> {
130 let (env_name, instance_ip) = self.validate_input(environment_name, instance_ip_str)?;
131
132 let handler = self.create_command_handler()?;
133
134 let provisioned = self
135 .register_instance(&handler, &env_name, instance_ip, ssh_port)
136 .await?;
137
138 self.complete_workflow(&provisioned, output_format)?;
139
140 Ok(provisioned)
141 }
142
143 #[allow(clippy::result_large_err)]
145 fn validate_input(
146 &mut self,
147 name: &str,
148 ip_str: &str,
149 ) -> Result<(EnvironmentName, IpAddr), RegisterSubcommandError> {
150 self.progress
151 .start_step(RegisterStep::ValidateInput.description())?;
152
153 let env_name = EnvironmentName::new(name.to_string()).map_err(|source| {
154 RegisterSubcommandError::InvalidEnvironmentName {
155 name: name.to_string(),
156 source,
157 }
158 })?;
159
160 let instance_ip: IpAddr = ip_str.parse().map_err(|e: std::net::AddrParseError| {
161 RegisterSubcommandError::InvalidIpAddress {
162 value: ip_str.to_string(),
163 reason: e.to_string(),
164 }
165 })?;
166
167 self.progress.complete_step(None)?;
168
169 Ok((env_name, instance_ip))
170 }
171
172 #[allow(clippy::result_large_err)]
174 fn create_command_handler(
175 &mut self,
176 ) -> Result<RegisterCommandHandler, RegisterSubcommandError> {
177 self.progress
178 .start_step(RegisterStep::CreateCommandHandler.description())?;
179
180 let handler = RegisterCommandHandler::new(
181 self.clock.clone(),
182 Arc::clone(&self.repository) as Arc<dyn EnvironmentRepository>,
183 );
184
185 self.progress.complete_step(None)?;
186
187 Ok(handler)
188 }
189
190 #[allow(clippy::result_large_err)]
192 async fn register_instance(
193 &mut self,
194 handler: &RegisterCommandHandler,
195 env_name: &EnvironmentName,
196 instance_ip: IpAddr,
197 ssh_port: Option<u16>,
198 ) -> Result<Environment<Provisioned>, RegisterSubcommandError> {
199 self.progress
200 .start_step(RegisterStep::RegisterInstance.description())?;
201
202 let provisioned = handler
203 .execute(env_name, instance_ip, ssh_port)
204 .await
205 .map_err(|source| RegisterSubcommandError::RegisterOperationFailed {
206 name: env_name.to_string(),
207 source: Box::new(source),
208 })?;
209
210 self.progress.complete_step(None)?;
211
212 Ok(provisioned)
213 }
214
215 #[allow(clippy::result_large_err)]
219 fn complete_workflow(
220 &mut self,
221 provisioned: &Environment<Provisioned>,
222 output_format: OutputFormat,
223 ) -> Result<(), RegisterSubcommandError> {
224 let data = RegisterDetailsData::from_environment(provisioned);
225 match output_format {
226 OutputFormat::Text => {
227 self.progress.complete(&TextView::render(&data)?)?;
228 }
229 OutputFormat::Json => {
230 self.progress.result(&JsonView::render(&data)?)?;
231 }
232 }
233 Ok(())
234 }
235}