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}