1#[macro_use]
2mod macros;
3pub mod compose;
4pub mod config;
5pub mod container;
6pub mod exec;
7pub mod image;
8pub mod network;
9pub mod node;
10pub mod plugin;
11pub mod secret;
12pub mod service;
13pub mod task;
14pub mod volume;
15
16use pyo3::prelude::*;
17use pyo3::types::PyDict;
18use pyo3::wrap_pymodule;
19
20use docker_api::models::{EventMessage, PingInfo, SystemInfo, SystemVersion};
21use docker_api::opts::EventsOpts;
22use docker_api::Docker;
23
24use futures_util::stream::StreamExt;
25
26use pythonize::pythonize;
27
28use config::Pyo3Configs;
29use container::Pyo3Containers;
30use image::Pyo3Images;
31use network::Pyo3Networks;
32use node::Pyo3Nodes;
33use plugin::Pyo3Plugins;
34use secret::Pyo3Secrets;
35use service::Pyo3Services;
36use task::Pyo3Tasks;
37use volume::Pyo3Volumes;
38
39#[cfg(unix)]
40static SYSTEM_DEFAULT_URI: &str = "unix:///var/run/docker.sock";
41
42#[cfg(not(unix))]
43static SYSTEM_DEFAULT_URI: &str = "tcp://localhost:2375";
44
45#[pyclass(name = "Docker")]
52#[derive(Clone, Debug)]
53pub struct Pyo3Docker(pub Docker);
54
55#[pymethods]
56impl Pyo3Docker {
57 #[new]
58 #[pyo3(signature = ( uri = SYSTEM_DEFAULT_URI))]
59 fn py_new(uri: &str) -> Self {
68 Pyo3Docker(Docker::new(uri).unwrap())
69 }
70
71 fn version(&self) -> Py<PyAny> {
76 let sv = __version(self.clone());
77 pythonize_this!(sv)
78 }
79
80 fn info(&self) -> Py<PyAny> {
85 let si = __info(self.clone());
86 pythonize_this!(si)
87 }
88
89 fn ping(&self) -> Py<PyAny> {
94 let pi = __ping(self.clone());
95 pythonize_this!(pi)
96 }
97
98 fn data_usage(&self) -> PyResult<Py<PyAny>> {
105 let du = __data_usage_via_cli();
106 match du {
107 Ok(du) => Ok(pythonize_this!(du)),
108 Err(e) => Err(pyo3::exceptions::PySystemError::new_err(e)),
109 }
110 }
111
112 #[pyo3(signature = (limit=None))]
123 fn events(&self, limit: Option<usize>) -> Py<PyAny> {
124 let limit = limit.unwrap_or(100);
125 let events = __events(self.clone(), limit);
126 pythonize_this!(events)
127 }
128
129 fn containers(&'_ self) -> Pyo3Containers {
134 Pyo3Containers::new(self.clone())
135 }
136
137 fn images(&'_ self) -> Pyo3Images {
142 Pyo3Images::new(self.clone())
143 }
144
145 fn networks(&'_ self) -> Pyo3Networks {
150 Pyo3Networks::new(self.clone())
151 }
152
153 fn volumes(&'_ self) -> Pyo3Volumes {
158 Pyo3Volumes::new(self.clone())
159 }
160
161 fn nodes(&'_ self) -> Pyo3Nodes {
168 Pyo3Nodes::new(self.clone())
169 }
170
171 fn services(&'_ self) -> Pyo3Services {
178 Pyo3Services::new(self.clone())
179 }
180
181 fn tasks(&'_ self) -> Pyo3Tasks {
189 Pyo3Tasks::new(self.clone())
190 }
191
192 fn secrets(&'_ self) -> Pyo3Secrets {
200 Pyo3Secrets::new(self.clone())
201 }
202
203 fn configs(&'_ self) -> Pyo3Configs {
211 Pyo3Configs::new(self.clone())
212 }
213
214 fn plugins(&'_ self) -> Pyo3Plugins {
222 Pyo3Plugins::new(self.clone())
223 }
224}
225
226#[tokio::main]
227async fn __version(docker: Pyo3Docker) -> SystemVersion {
228 let version = docker.0.version().await;
229 version.unwrap()
230}
231
232#[tokio::main]
233async fn __info(docker: Pyo3Docker) -> SystemInfo {
234 let info = docker.0.info().await;
235 info.unwrap()
236}
237
238#[tokio::main]
239async fn __ping(docker: Pyo3Docker) -> PingInfo {
240 let ping = docker.0.ping().await;
241 ping.unwrap()
242}
243
244#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
246#[serde(rename_all = "PascalCase")]
247struct DataUsageItem {
248 #[serde(rename = "Type")]
249 pub type_: String,
250 pub total_count: String,
251 pub active: String,
252 pub size: String,
253 pub reclaimable: String,
254}
255
256#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
258#[serde(rename_all = "PascalCase")]
259struct DataUsageCompat {
260 pub images: Option<DataUsageItem>,
261 pub containers: Option<DataUsageItem>,
262 pub volumes: Option<DataUsageItem>,
263 pub build_cache: Option<DataUsageItem>,
264}
265
266fn __data_usage_via_cli() -> Result<DataUsageCompat, String> {
268 use std::process::Command;
269
270 let output = Command::new("docker")
271 .args(["system", "df", "--format", "json"])
272 .output()
273 .map_err(|e| format!("Failed to execute docker: {}", e))?;
274
275 if !output.status.success() {
276 return Err(format!(
277 "docker system df failed: {}",
278 String::from_utf8_lossy(&output.stderr)
279 ));
280 }
281
282 let stdout = String::from_utf8_lossy(&output.stdout);
283
284 let mut result = DataUsageCompat {
286 images: None,
287 containers: None,
288 volumes: None,
289 build_cache: None,
290 };
291
292 for line in stdout.lines() {
293 if line.trim().is_empty() {
294 continue;
295 }
296 let item: DataUsageItem =
297 serde_json::from_str(line).map_err(|e| format!("Failed to parse JSON: {}", e))?;
298
299 match item.type_.as_str() {
300 "Images" => result.images = Some(item),
301 "Containers" => result.containers = Some(item),
302 "Local Volumes" => result.volumes = Some(item),
303 "Build Cache" => result.build_cache = Some(item),
304 _ => {}
305 }
306 }
307
308 Ok(result)
309}
310
311#[tokio::main]
312async fn __events(docker: Pyo3Docker, limit: usize) -> Vec<EventMessage> {
313 let opts = EventsOpts::builder().build();
314 let mut events_stream = docker.0.events(&opts);
315 let mut events = Vec::new();
316
317 let timeout_duration = std::time::Duration::from_secs(1);
319
320 loop {
321 let event_result = tokio::time::timeout(timeout_duration, events_stream.next()).await;
322 match event_result {
323 Ok(Some(Ok(event))) => {
324 events.push(event);
325 if events.len() >= limit {
326 break;
327 }
328 }
329 Ok(Some(Err(_))) => break,
330 Ok(None) => break,
331 Err(_) => break, }
333 }
334
335 events
336}
337
338#[pymodule]
340pub fn docker_pyo3(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
341 m.add_class::<Pyo3Docker>()?;
342
343 m.add_wrapped(wrap_pymodule!(compose::compose))?;
344 m.add_wrapped(wrap_pymodule!(config::config))?;
345 m.add_wrapped(wrap_pymodule!(image::image))?;
346 m.add_wrapped(wrap_pymodule!(container::container))?;
347 m.add_wrapped(wrap_pymodule!(exec::exec))?;
348 m.add_wrapped(wrap_pymodule!(network::network))?;
349 m.add_wrapped(wrap_pymodule!(node::node))?;
350 m.add_wrapped(wrap_pymodule!(plugin::plugin))?;
351 m.add_wrapped(wrap_pymodule!(secret::secret))?;
352 m.add_wrapped(wrap_pymodule!(service::service))?;
353 m.add_wrapped(wrap_pymodule!(task::task))?;
354 m.add_wrapped(wrap_pymodule!(volume::volume))?;
355
356 let sys = PyModule::import(_py, "sys")?;
357 let sys_modules: Bound<'_, PyDict> = sys.getattr("modules")?.cast_into()?;
358 sys_modules.set_item("docker_pyo3.compose", m.getattr("compose")?)?;
359 sys_modules.set_item("docker_pyo3.config", m.getattr("config")?)?;
360 sys_modules.set_item("docker_pyo3.image", m.getattr("image")?)?;
361 sys_modules.set_item("docker_pyo3.container", m.getattr("container")?)?;
362 sys_modules.set_item("docker_pyo3.exec", m.getattr("exec")?)?;
363 sys_modules.set_item("docker_pyo3.network", m.getattr("network")?)?;
364 sys_modules.set_item("docker_pyo3.node", m.getattr("node")?)?;
365 sys_modules.set_item("docker_pyo3.plugin", m.getattr("plugin")?)?;
366 sys_modules.set_item("docker_pyo3.secret", m.getattr("secret")?)?;
367 sys_modules.set_item("docker_pyo3.service", m.getattr("service")?)?;
368 sys_modules.set_item("docker_pyo3.task", m.getattr("task")?)?;
369 sys_modules.set_item("docker_pyo3.volume", m.getattr("volume")?)?;
370
371 Ok(())
372}