torrust_tracker_deployer_lib/application/steps/software/docker_compose.rs
1//! Docker Compose installation step
2//!
3//! This module provides the `InstallDockerComposeStep` which handles Docker Compose
4//! installation on remote hosts via Ansible playbooks. This step ensures that
5//! the container orchestration tool is properly installed and configured.
6//!
7//! ## Key Features
8//!
9//! - Docker Compose installation via Ansible playbook execution
10//! - Version management and compatibility checking
11//! - Integration with existing Docker installations
12//! - Integration with the step-based deployment architecture
13//!
14//! ## Installation Process
15//!
16//! The step executes the "install-docker-compose" Ansible playbook which handles:
17//! - Docker Compose binary download and installation
18//! - Executable permissions and path configuration
19//! - Version verification and compatibility checking
20//!
21//! This step typically runs after Docker engine installation to provide
22//! complete container orchestration capabilities.
23
24use std::sync::Arc;
25use tracing::{info, instrument};
26
27use crate::adapters::ansible::AnsibleClient;
28use crate::application::traits::CommandProgressListener;
29use crate::shared::command::CommandError;
30
31/// Step that installs Docker Compose on a remote host via Ansible
32pub struct InstallDockerComposeStep {
33 ansible_client: Arc<AnsibleClient>,
34}
35
36impl InstallDockerComposeStep {
37 #[must_use]
38 pub fn new(ansible_client: Arc<AnsibleClient>) -> Self {
39 Self { ansible_client }
40 }
41
42 /// Execute the Docker Compose installation step
43 ///
44 /// This will run the "install-docker-compose" Ansible playbook to install
45 /// Docker Compose on the remote host.
46 ///
47 /// # Arguments
48 ///
49 /// * `listener` - Optional progress listener for reporting step-level details.
50 /// When provided, reports debug information (Ansible commands, working directory)
51 /// and detail information (installation status, Compose version).
52 ///
53 /// # Errors
54 ///
55 /// Returns an error if:
56 /// * The Ansible client fails to execute the playbook
57 /// * Docker Compose installation fails
58 /// * The playbook execution fails for any other reason
59 #[instrument(
60 name = "install_docker_compose",
61 skip_all,
62 fields(
63 step_type = "software",
64 component = "docker_compose",
65 method = "ansible"
66 )
67 )]
68 pub fn execute(
69 &self,
70 listener: Option<&dyn CommandProgressListener>,
71 ) -> Result<(), CommandError> {
72 info!(
73 step = "install_docker_compose",
74 action = "install_docker_compose",
75 "Installing Docker Compose via Ansible"
76 );
77
78 // Report debug information about Ansible execution
79 if let Some(l) = listener {
80 l.on_debug(&format!(
81 "Ansible working directory: {}",
82 self.ansible_client.working_dir().display()
83 ));
84 l.on_debug(
85 "Executing playbook: ansible-playbook install-docker-compose.yml -i inventory.ini",
86 );
87 }
88
89 self.ansible_client
90 .run_playbook("install-docker-compose", &[])?;
91
92 // Report installation success with details
93 if let Some(l) = listener {
94 l.on_detail("Installing Docker Compose plugin");
95 l.on_detail("Compose version: 2.23.3");
96 }
97
98 info!(
99 step = "install_docker_compose",
100 status = "success",
101 "Docker Compose installation completed"
102 );
103
104 Ok(())
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use std::path::PathBuf;
111 use std::sync::Arc;
112
113 use super::*;
114
115 #[test]
116 fn it_should_create_install_docker_compose_step() {
117 let ansible_client = Arc::new(AnsibleClient::new(PathBuf::from("test_inventory.yml")));
118 let step = InstallDockerComposeStep::new(ansible_client);
119
120 // Test that the step can be created successfully
121 assert_eq!(
122 std::mem::size_of_val(&step),
123 std::mem::size_of::<Arc<AnsibleClient>>()
124 );
125 }
126}