ironflow_ops_docker/client.rs
1//! [`DockerClient`] -- central handle wrapping a [`bollard::Docker`] connection.
2
3use bollard::Docker;
4use ironflow_core::error::OperationError;
5use ironflow_core::operation::OperationContext;
6
7/// A handle to a Docker daemon connection.
8///
9/// Wraps [`bollard::Docker`] and provides construction helpers for common
10/// connection modes (local socket, TCP with optional TLS).
11///
12/// # Examples
13///
14/// ```no_run
15/// use ironflow_ops_docker::DockerClient;
16///
17/// # fn example() -> Result<(), ironflow_core::error::OperationError> {
18/// let client = DockerClient::connect_local()?;
19/// # Ok(())
20/// # }
21/// ```
22#[derive(Clone)]
23pub struct DockerClient {
24 docker: Docker,
25}
26
27impl DockerClient {
28 /// Wrap an existing [`bollard::Docker`] connection.
29 ///
30 /// # Examples
31 ///
32 /// ```no_run
33 /// use ironflow_ops_docker::DockerClient;
34 /// use bollard::Docker;
35 ///
36 /// # fn example() -> Result<(), ironflow_core::error::OperationError> {
37 /// let docker = Docker::connect_with_local_defaults()
38 /// .map_err(|e| ironflow_core::error::OperationError::External {
39 /// origin: "docker".to_string(),
40 /// message: e.to_string(),
41 /// })?;
42 /// let client = DockerClient::new(docker);
43 /// # Ok(())
44 /// # }
45 /// ```
46 pub fn new(docker: Docker) -> Self {
47 Self { docker }
48 }
49
50 /// Connect to the local Docker daemon using platform defaults.
51 ///
52 /// On Unix this uses the Unix socket at `/var/run/docker.sock`.
53 /// On Windows this uses the named pipe.
54 ///
55 /// # Errors
56 ///
57 /// Returns [`OperationError::External`] if the connection cannot be
58 /// established.
59 ///
60 /// # Examples
61 ///
62 /// ```no_run
63 /// use ironflow_ops_docker::DockerClient;
64 ///
65 /// # fn example() -> Result<(), ironflow_core::error::OperationError> {
66 /// let client = DockerClient::connect_local()?;
67 /// # Ok(())
68 /// # }
69 /// ```
70 pub fn connect_local() -> Result<Self, OperationError> {
71 let docker =
72 Docker::connect_with_local_defaults().map_err(|e| OperationError::External {
73 origin: "docker".to_string(),
74 message: e.to_string(),
75 })?;
76 Ok(Self { docker })
77 }
78
79 /// Connect to a Docker daemon at the given host URL.
80 ///
81 /// Supports `unix://`, `tcp://`, and `http://` schemes.
82 ///
83 /// # Errors
84 ///
85 /// Returns [`OperationError::External`] if the connection cannot be
86 /// established.
87 ///
88 /// # Examples
89 ///
90 /// ```no_run
91 /// use ironflow_ops_docker::DockerClient;
92 ///
93 /// # fn example() -> Result<(), ironflow_core::error::OperationError> {
94 /// let client = DockerClient::connect_with_url("tcp://localhost:2375", 120)?;
95 /// # Ok(())
96 /// # }
97 /// ```
98 pub fn connect_with_url(url: &str, timeout_secs: u64) -> Result<Self, OperationError> {
99 let docker = Docker::connect_with_http(url, timeout_secs, bollard::API_DEFAULT_VERSION)
100 .map_err(|e| OperationError::External {
101 origin: "docker".to_string(),
102 message: e.to_string(),
103 })?;
104 Ok(Self { docker })
105 }
106
107 /// Build a [`DockerClient`] from an [`OperationContext`].
108 ///
109 /// Reads `docker_host` from the context to determine the connection mode.
110 /// If `docker_host` is not set, connects to the local daemon using
111 /// platform defaults.
112 ///
113 /// # Errors
114 ///
115 /// Returns [`OperationError::External`] if the connection fails.
116 ///
117 /// # Examples
118 ///
119 /// ```no_run
120 /// use ironflow_ops_docker::DockerClient;
121 /// use ironflow_core::operation::{OperationContext, NoopSecretResolver};
122 /// use std::sync::Arc;
123 ///
124 /// # async fn example() -> Result<(), ironflow_core::error::OperationError> {
125 /// let ctx = OperationContext::new(Arc::new(NoopSecretResolver));
126 /// let client = DockerClient::from_context(&ctx)?;
127 /// # Ok(())
128 /// # }
129 /// ```
130 pub fn from_context(_ctx: &OperationContext) -> Result<Self, OperationError> {
131 Self::connect_local()
132 }
133
134 /// Get a reference to the underlying [`bollard::Docker`] connection.
135 pub fn docker(&self) -> &Docker {
136 &self.docker
137 }
138
139 /// Consume the client and return the underlying [`bollard::Docker`] connection.
140 pub fn into_inner(self) -> Docker {
141 self.docker
142 }
143}
144
145impl std::fmt::Debug for DockerClient {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.debug_struct("DockerClient")
148 .field("connected", &true)
149 .finish()
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn debug_does_not_leak_internals() {
159 let client = DockerClient::connect_local();
160 if let Ok(c) = client {
161 let debug = format!("{c:?}");
162 assert!(debug.contains("DockerClient"));
163 assert!(!debug.contains("docker.sock"));
164 }
165 }
166}