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}