Skip to main content

torrust_tracker_deployer_lib/application/steps/software/
docker.rs

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