Skip to main content

mecha10_deploy/services/docker/
mod.rs

1#![allow(dead_code)]
2
3//! Docker service for container and Docker Compose operations
4//!
5//! This service provides a centralized interface for all Docker operations,
6//! including installation checks, daemon status, image building, and
7//! container lifecycle management.
8
9mod compose;
10mod containers;
11mod installation;
12mod types;
13
14pub use types::{ContainerInfo, DockerInfo};
15
16use anyhow::Result;
17use std::path::Path;
18use std::time::Duration;
19
20pub struct DockerService {
21    compose_file: Option<String>,
22}
23
24impl DockerService {
25    /// Create a new Docker service
26    pub fn new() -> Self {
27        Self { compose_file: None }
28    }
29
30    /// Create a Docker service with a custom compose file path
31    ///
32    /// # Arguments
33    ///
34    /// * `compose_file` - Path to docker-compose.yml
35    pub fn with_compose_file(compose_file: impl Into<String>) -> Self {
36        Self {
37            compose_file: Some(compose_file.into()),
38        }
39    }
40
41    /// Check if Docker is installed and accessible
42    ///
43    /// # Errors
44    ///
45    /// Returns an error if Docker is not found or not executable.
46    pub fn check_installation(&self) -> Result<DockerInfo> {
47        installation::check_installation()
48    }
49
50    /// Check if Docker daemon is running
51    ///
52    /// # Errors
53    ///
54    /// Returns an error if the daemon is not running or unreachable.
55    pub fn check_daemon(&self) -> Result<()> {
56        installation::check_daemon()
57    }
58
59    /// Build Docker images using Docker Compose
60    ///
61    /// # Arguments
62    ///
63    /// * `service` - Optional service name to build (builds all if None)
64    pub fn compose_build(&self, service: Option<&str>) -> Result<()> {
65        compose::compose_build(self.compose_file.as_deref(), service)
66    }
67
68    /// Start services using Docker Compose
69    ///
70    /// # Arguments
71    ///
72    /// * `detach` - Run in detached mode
73    pub async fn compose_up(&self, detach: bool) -> Result<()> {
74        compose::compose_up(self.compose_file.as_deref(), detach).await
75    }
76
77    /// Start a specific service using Docker Compose
78    ///
79    /// # Arguments
80    ///
81    /// * `service` - Service name to start
82    /// * `detach` - Run in detached mode
83    pub async fn compose_up_service(&self, service: &str, detach: bool) -> Result<()> {
84        compose::compose_up_service(self.compose_file.as_deref(), service, detach).await
85    }
86
87    /// Run a one-off command in a service container
88    ///
89    /// Uses `docker compose run` to start a service with custom command arguments.
90    ///
91    /// # Arguments
92    ///
93    /// * `service` - Service name to run
94    /// * `detach` - Run in detached mode
95    /// * `service_ports` - Publish service ports (useful for detached mode)
96    /// * `command_args` - Additional arguments to pass to the container's entrypoint
97    pub async fn compose_run_service(
98        &self,
99        service: &str,
100        detach: bool,
101        service_ports: bool,
102        command_args: &[String],
103    ) -> Result<()> {
104        compose::compose_run_service(
105            self.compose_file.as_deref(),
106            service,
107            detach,
108            service_ports,
109            command_args,
110        )
111        .await
112    }
113
114    /// Stop services using Docker Compose
115    ///
116    /// # Arguments
117    ///
118    /// * `service` - Optional service name to stop (stops all if None)
119    pub async fn compose_stop(&self, service: Option<&str>) -> Result<()> {
120        compose::compose_stop(self.compose_file.as_deref(), service).await
121    }
122
123    /// Stop and remove services using Docker Compose
124    pub async fn compose_down(&self) -> Result<()> {
125        compose::compose_down(self.compose_file.as_deref()).await
126    }
127
128    /// Restart services using Docker Compose
129    ///
130    /// # Arguments
131    ///
132    /// * `service` - Optional service name to restart (restarts all if None)
133    pub async fn compose_restart(&self, service: Option<&str>) -> Result<()> {
134        compose::compose_restart(self.compose_file.as_deref(), service).await
135    }
136
137    /// Get logs from Docker Compose services
138    ///
139    /// # Arguments
140    ///
141    /// * `service` - Optional service name (all services if None)
142    /// * `follow` - Follow log output
143    /// * `tail` - Number of lines to show from the end
144    pub fn compose_logs(&self, service: Option<&str>, follow: bool, tail: Option<usize>) -> Result<()> {
145        compose::compose_logs(self.compose_file.as_deref(), service, follow, tail)
146    }
147
148    /// Check if a container is running
149    ///
150    /// # Arguments
151    ///
152    /// * `container_name` - Name or ID of the container
153    pub async fn is_container_running(&self, container_name: &str) -> Result<bool> {
154        containers::is_container_running(container_name).await
155    }
156
157    /// List running containers
158    pub async fn list_containers(&self) -> Result<Vec<ContainerInfo>> {
159        containers::list_containers().await
160    }
161
162    /// Check if docker-compose.yml exists
163    ///
164    /// # Arguments
165    ///
166    /// * `path` - Path to check (defaults to current directory)
167    pub fn compose_file_exists(&self, path: Option<&Path>) -> bool {
168        containers::compose_file_exists(self.compose_file.as_deref(), path)
169    }
170
171    /// Wait for a service to be healthy
172    ///
173    /// # Arguments
174    ///
175    /// * `container_name` - Name of the container
176    /// * `timeout` - Maximum time to wait
177    pub async fn wait_for_healthy(&self, container_name: &str, timeout: Duration) -> Result<()> {
178        containers::wait_for_healthy(container_name, timeout).await
179    }
180
181    /// Start project services for development mode
182    ///
183    /// This is a convenience method that:
184    /// 1. Checks if services are already running
185    /// 2. Starts only the services that aren't running
186    /// 3. Waits for them to be healthy
187    ///
188    /// # Arguments
189    ///
190    /// * `services` - List of service names to start
191    /// * `project_name` - Project name for container naming
192    ///
193    /// # Returns
194    ///
195    /// Returns Ok(true) if services were started, Ok(false) if already running
196    pub async fn start_project_services(&self, services: &[String], project_name: &str) -> Result<bool> {
197        if services.is_empty() {
198            return Ok(false);
199        }
200
201        let mut all_running = true;
202        for service in services {
203            let container_name = format!("{}-{}", project_name, service);
204            if !self.is_container_running(&container_name).await.unwrap_or(false) {
205                all_running = false;
206                break;
207            }
208        }
209
210        if all_running {
211            return Ok(false);
212        }
213
214        for service in services {
215            self.compose_up_service(service, true).await?;
216        }
217
218        tokio::time::sleep(Duration::from_secs(2)).await;
219
220        Ok(true)
221    }
222}
223
224impl Default for DockerService {
225    fn default() -> Self {
226        Self::new()
227    }
228}