lmrc_docker/client/
mod.rs

1//! Docker client configuration and connection management.
2
3use crate::error::{DockerError, Result};
4use bollard::Docker;
5use std::path::PathBuf;
6use tracing::{debug, info};
7
8/// Configuration for connecting to Docker daemon.
9#[derive(Debug, Clone)]
10pub struct DockerClientConfig {
11    /// Connection URI (unix:///var/run/docker.sock, tcp://host:port, etc.)
12    pub uri: Option<String>,
13    /// Connection timeout in seconds
14    pub timeout: u64,
15    /// API version to use
16    pub api_version: Option<String>,
17}
18
19impl Default for DockerClientConfig {
20    fn default() -> Self {
21        Self {
22            uri: None,
23            timeout: 120,
24            api_version: None,
25        }
26    }
27}
28
29impl DockerClientConfig {
30    /// Create a new configuration with default values.
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Set the connection URI.
36    pub fn uri(mut self, uri: impl Into<String>) -> Self {
37        self.uri = Some(uri.into());
38        self
39    }
40
41    /// Set the connection timeout in seconds.
42    pub fn timeout(mut self, timeout: u64) -> Self {
43        self.timeout = timeout;
44        self
45    }
46
47    /// Set the Docker API version.
48    pub fn api_version(mut self, version: impl Into<String>) -> Self {
49        self.api_version = Some(version.into());
50        self
51    }
52}
53
54/// Main Docker client for managing containers, images, networks, and volumes.
55pub struct DockerClient {
56    /// Underlying Bollard Docker client
57    pub(crate) docker: Docker,
58    /// Client configuration
59    #[allow(dead_code)]
60    pub(crate) config: DockerClientConfig,
61}
62
63impl DockerClient {
64    /// Create a new Docker client with default configuration.
65    ///
66    /// This respects the `DOCKER_HOST` environment variable.
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if unable to connect to the Docker daemon.
71    ///
72    /// # Example
73    ///
74    /// ```no_run
75    /// use lmrc_docker::DockerClient;
76    ///
77    /// #[tokio::main]
78    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
79    ///     let client = DockerClient::new()?;
80    ///     Ok(())
81    /// }
82    /// ```
83    pub fn new() -> Result<Self> {
84        Self::with_config(DockerClientConfig::default())
85    }
86
87    /// Create a new Docker client with custom configuration.
88    ///
89    /// # Errors
90    ///
91    /// Returns an error if unable to connect to the Docker daemon.
92    ///
93    /// # Example
94    ///
95    /// ```no_run
96    /// use lmrc_docker::{DockerClient, DockerClientConfig};
97    ///
98    /// #[tokio::main]
99    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
100    ///     let config = DockerClientConfig::new()
101    ///         .timeout(60)
102    ///         .api_version("1.43");
103    ///     let client = DockerClient::with_config(config)?;
104    ///     Ok(())
105    /// }
106    /// ```
107    pub fn with_config(config: DockerClientConfig) -> Result<Self> {
108        debug!("Creating Docker client with config: {:?}", config);
109        debug!("DOCKER_HOST = {:?}", std::env::var("DOCKER_HOST").ok());
110
111        let docker =
112            Docker::connect_with_defaults().map_err(|e| DockerError::Connection(e.to_string()))?;
113
114        Ok(Self { docker, config })
115    }
116
117    /// Connect to Docker daemon using Unix socket.
118    ///
119    /// # Example
120    ///
121    /// ```no_run
122    /// use lmrc_docker::DockerClient;
123    ///
124    /// #[tokio::main]
125    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
126    ///     let client = DockerClient::connect_with_unix("/var/run/docker.sock")?;
127    ///     Ok(())
128    /// }
129    /// ```
130    pub fn connect_with_unix(path: impl Into<PathBuf>) -> Result<Self> {
131        let path = path.into();
132        let config = DockerClientConfig::new().uri(format!("unix://{}", path.display()));
133        Self::with_config(config)
134    }
135
136    /// Connect to Docker daemon using TCP.
137    ///
138    /// # Example
139    ///
140    /// ```no_run
141    /// use lmrc_docker::DockerClient;
142    ///
143    /// #[tokio::main]
144    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
145    ///     let client = DockerClient::connect_with_tcp("localhost:2375")?;
146    ///     Ok(())
147    /// }
148    /// ```
149    pub fn connect_with_tcp(addr: impl Into<String>) -> Result<Self> {
150        let config = DockerClientConfig::new().uri(format!("tcp://{}", addr.into()));
151        Self::with_config(config)
152    }
153
154    /// Check if Docker daemon is accessible and get version info.
155    ///
156    /// # Example
157    ///
158    /// ```no_run
159    /// use lmrc_docker::DockerClient;
160    ///
161    /// #[tokio::main]
162    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
163    ///     let client = DockerClient::new()?;
164    ///     let version = client.version().await?;
165    ///     println!("Docker version: {}", version);
166    ///     Ok(())
167    /// }
168    /// ```
169    pub async fn version(&self) -> Result<String> {
170        info!("Getting Docker version...");
171        let version =
172            self.docker.version().await.map_err(|e| {
173                DockerError::Connection(format!("Failed to get Docker version: {}", e))
174            })?;
175
176        let version_string = version.version.unwrap_or_else(|| "unknown".to_string());
177        info!("Docker version: {}", version_string);
178        Ok(version_string)
179    }
180
181    /// Ping the Docker daemon to check connectivity.
182    ///
183    /// # Example
184    ///
185    /// ```no_run
186    /// use lmrc_docker::DockerClient;
187    ///
188    /// #[tokio::main]
189    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
190    ///     let client = DockerClient::new()?;
191    ///     client.ping().await?;
192    ///     println!("Docker daemon is accessible");
193    ///     Ok(())
194    /// }
195    /// ```
196    pub async fn ping(&self) -> Result<()> {
197        self.docker
198            .ping()
199            .await
200            .map_err(|e| DockerError::Connection(format!("Failed to ping Docker daemon: {}", e)))?;
201        Ok(())
202    }
203
204    /// Get system information from Docker daemon.
205    pub async fn info(&self) -> Result<bollard::models::SystemInfo> {
206        self.docker
207            .info()
208            .await
209            .map_err(|e| DockerError::Connection(format!("Failed to get Docker info: {}", e)))
210    }
211
212    /// Access the underlying Bollard Docker client for advanced operations.
213    pub fn inner(&self) -> &Docker {
214        &self.docker
215    }
216
217    /// Access registry operations.
218    pub fn registry(&self) -> crate::registry::Registry<'_> {
219        crate::registry::Registry::new(self)
220    }
221}
222
223impl Default for DockerClient {
224    fn default() -> Self {
225        Self::new().expect("Failed to create default Docker client")
226    }
227}