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}