Skip to main content

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}