torrust_tracker_deployer_lib/application/steps/application/
start_services.rs1use std::sync::Arc;
35
36use thiserror::Error;
37use tracing::{info, instrument};
38
39use crate::adapters::ansible::AnsibleClient;
40use crate::shared::command::CommandError;
41use crate::shared::{ErrorKind, Traceable};
42
43pub struct StartServicesStep {
48 ansible_client: Arc<AnsibleClient>,
49}
50
51impl StartServicesStep {
52 #[must_use]
58 pub fn new(ansible_client: Arc<AnsibleClient>) -> Self {
59 Self { ansible_client }
60 }
61
62 #[instrument(
74 name = "start_services",
75 skip_all,
76 fields(step_type = "application", operation = "start_services")
77 )]
78 pub fn execute(&self) -> Result<(), StartServicesStepError> {
79 info!(
80 step = "start_services",
81 status = "starting",
82 "Starting Docker Compose services on remote host"
83 );
84
85 self.ansible_client
86 .run_playbook("run-compose-services", &[])
87 .map_err(|source| StartServicesStepError::AnsiblePlaybookFailed {
88 message: source.to_string(),
89 source,
90 })?;
91
92 info!(
93 step = "start_services",
94 status = "success",
95 "Docker Compose services started successfully"
96 );
97
98 Ok(())
99 }
100}
101
102#[derive(Debug, Error)]
104pub enum StartServicesStepError {
105 #[error("Ansible playbook 'run-compose-services' failed: {message}")]
107 AnsiblePlaybookFailed {
108 message: String,
109 #[source]
110 source: CommandError,
111 },
112}
113
114impl StartServicesStepError {
115 #[must_use]
117 pub fn help(&self) -> &'static str {
118 match self {
119 Self::AnsiblePlaybookFailed { .. } => {
120 "Failed to start Docker Compose services. Please check:\n\
121 1. Docker daemon is running on the remote host\n\
122 2. Docker Compose files were deployed via 'release' command\n\
123 3. Container images can be pulled (network connectivity)\n\
124 4. No port conflicts with existing services\n\
125 5. Sufficient disk space and memory on the remote host\n\
126 6. SSH connectivity to the remote host is working"
127 }
128 }
129 }
130}
131
132impl Traceable for StartServicesStepError {
133 fn trace_format(&self) -> String {
134 match self {
135 Self::AnsiblePlaybookFailed { message, .. } => {
136 format!("StartServicesStep::AnsiblePlaybookFailed - {message}")
137 }
138 }
139 }
140
141 fn trace_source(&self) -> Option<&dyn Traceable> {
142 match self {
143 Self::AnsiblePlaybookFailed { source, .. } => Some(source),
144 }
145 }
146
147 fn error_kind(&self) -> ErrorKind {
148 ErrorKind::InfrastructureOperation
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use std::path::PathBuf;
155 use std::sync::Arc;
156
157 use super::*;
158 use crate::adapters::ansible::AnsibleClient;
159
160 #[test]
161 fn it_should_create_start_services_step() {
162 let ansible_client = Arc::new(AnsibleClient::new(PathBuf::from("test_inventory.yml")));
163
164 let step = StartServicesStep::new(ansible_client);
165
166 assert_eq!(
168 std::mem::size_of_val(&step),
169 std::mem::size_of::<Arc<AnsibleClient>>()
170 );
171 }
172
173 #[test]
174 fn errors_should_provide_help() {
175 let cmd_error = CommandError::ExecutionFailed {
176 command: "test".to_string(),
177 exit_code: "1".to_string(),
178 stdout: String::new(),
179 stderr: "test error".to_string(),
180 };
181 let error = StartServicesStepError::AnsiblePlaybookFailed {
182 message: "test".to_string(),
183 source: cmd_error,
184 };
185
186 let help = error.help();
187 assert!(help.contains("Docker daemon"));
188 assert!(help.contains("release"));
189 assert!(help.contains("port conflicts"));
190 }
191
192 #[test]
193 fn errors_should_implement_traceable() {
194 let cmd_error = CommandError::ExecutionFailed {
195 command: "test".to_string(),
196 exit_code: "1".to_string(),
197 stdout: String::new(),
198 stderr: "test error".to_string(),
199 };
200 let error = StartServicesStepError::AnsiblePlaybookFailed {
201 message: "test error".to_string(),
202 source: cmd_error,
203 };
204
205 assert!(error.trace_format().contains("AnsiblePlaybookFailed"));
206 assert!(error.trace_source().is_some());
207 assert!(matches!(
208 error.error_kind(),
209 ErrorKind::InfrastructureOperation
210 ));
211 }
212}