1use chrono::{DateTime, Utc};
2use docker_api::conn::TtyChunk;
3use docker_api::models::{
4 ContainerInspect200Response, ContainerPrune200Response, ContainerSummary, ContainerWaitResponse,
5};
6use docker_api::opts::{
7 ContainerCreateOpts, ContainerListOpts, ContainerPruneOpts, ExecCreateOpts, LogsOpts,
8};
9use docker_api::{Container, Containers};
10use futures_util::stream::StreamExt;
11use futures_util::TryStreamExt;
12use pyo3::exceptions;
13use pyo3::prelude::*;
14use pyo3::types::{PyDateTime, PyDelta, PyDict, PyList};
15use pythonize::pythonize;
16use std::{fs::File, io::Read};
17use tar::Archive;
18
19use crate::Pyo3Docker;
20
21#[pymodule]
22pub fn container(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
23 m.add_class::<Pyo3Containers>()?;
24 m.add_class::<Pyo3Container>()?;
25 Ok(())
26}
27
28#[derive(Debug)]
29#[pyclass(name = "Containers")]
30pub struct Pyo3Containers(pub Containers);
31
32#[derive(Debug)]
33#[pyclass(name = "Container")]
34pub struct Pyo3Container(pub Container);
35
36#[pymethods]
37impl Pyo3Containers {
38 #[new]
39 pub fn new(docker: Pyo3Docker) -> Self {
40 Pyo3Containers(Containers::new(docker.0))
41 }
42
43 fn get(&self, id: &str) -> Pyo3Container {
44 Pyo3Container(self.0.get(id))
45 }
46
47 #[pyo3(signature = (all=None, since=None, before=None, sized=None))]
48 fn list(
49 &self,
50 all: Option<bool>,
51 since: Option<String>,
52 before: Option<String>,
53 sized: Option<bool>,
54 ) -> Py<PyAny> {
55 let mut builder = ContainerListOpts::builder();
56
57 bo_setter!(all, builder);
58 bo_setter!(since, builder);
59 bo_setter!(before, builder);
60 bo_setter!(sized, builder);
61
62 let cs = __containers_list(&self.0, &builder.build());
63 pythonize_this!(cs)
64 }
65
66 fn prune(&self) -> PyResult<Py<PyAny>> {
67 let rv = __containers_prune(&self.0, &Default::default());
68
69 match rv {
70 Ok(rv) => Ok(pythonize_this!(rv)),
71 Err(rv) => Err(py_sys_exception!(rv)),
72 }
73 }
74 #[pyo3(signature = (image, *, attach_stderr=None, attach_stdin=None, attach_stdout=None, auto_remove=None, capabilities=None, command=None, cpu_shares=None, cpus=None, devices=None, entrypoint=None, env=None, expose=None, extra_hosts=None, labels=None, links=None, log_driver=None, memory=None, memory_swap=None, name=None, nano_cpus=None, network_mode=None, privileged=None, publish=None, publish_all_ports=None, restart_policy=None, security_options=None, stop_signal=None, stop_signal_num=None, stop_timeout=None, tty=None, user=None, userns_mode=None, volumes=None, volumes_from=None, working_dir=None))]
75 fn create(
76 &self,
77 image: &str,
78 attach_stderr: Option<bool>,
79 attach_stdin: Option<bool>,
80 attach_stdout: Option<bool>,
81 auto_remove: Option<bool>,
82 capabilities: Option<&Bound<'_, PyList>>,
83 command: Option<&Bound<'_, PyList>>,
84 cpu_shares: Option<u32>,
85 cpus: Option<f64>,
86 devices: Option<&Bound<'_, PyList>>,
87 entrypoint: Option<&Bound<'_, PyList>>,
88 env: Option<&Bound<'_, PyList>>,
89 expose: Option<&Bound<'_, PyList>>,
90 extra_hosts: Option<&Bound<'_, PyList>>,
91 labels: Option<&Bound<'_, PyDict>>,
92 links: Option<&Bound<'_, PyList>>,
93 log_driver: Option<&str>,
94 memory: Option<u64>,
95 memory_swap: Option<i64>,
96 name: Option<&str>,
97 nano_cpus: Option<u64>,
98 network_mode: Option<&str>,
99 privileged: Option<bool>,
100 publish: Option<&Bound<'_, PyList>>,
101 publish_all_ports: Option<bool>,
102 restart_policy: Option<&Bound<'_, PyDict>>, security_options: Option<&Bound<'_, PyList>>,
104 stop_signal: Option<&str>,
105 stop_signal_num: Option<u64>,
106 stop_timeout: Option<&Bound<'_, PyDelta>>,
107 tty: Option<bool>,
108 user: Option<&str>,
109 userns_mode: Option<&str>,
110 volumes: Option<&Bound<'_, PyList>>,
111 volumes_from: Option<&Bound<'_, PyList>>,
112 working_dir: Option<&str>,
113 ) -> PyResult<Pyo3Container> {
114 let mut create_opts = ContainerCreateOpts::builder().image(image);
115
116 let links: Option<Vec<String>> = if links.is_some() {
117 links.unwrap().extract().unwrap()
118 } else {
119 None
120 };
121 let links: Option<Vec<&str>> = links
122 .as_ref()
123 .map(|v| v.iter().map(|s| s.as_str()).collect());
124
125 bo_setter!(attach_stderr, create_opts);
126 bo_setter!(attach_stdin, create_opts);
127 bo_setter!(attach_stdout, create_opts);
128 bo_setter!(auto_remove, create_opts);
129 bo_setter!(cpu_shares, create_opts);
130 bo_setter!(cpus, create_opts);
131 bo_setter!(log_driver, create_opts);
132 bo_setter!(memory, create_opts);
133 bo_setter!(memory_swap, create_opts);
134 bo_setter!(name, create_opts);
135 bo_setter!(nano_cpus, create_opts);
136 bo_setter!(network_mode, create_opts);
137 bo_setter!(privileged, create_opts);
138 bo_setter!(stop_signal, create_opts);
139 bo_setter!(stop_signal_num, create_opts);
140 bo_setter!(tty, create_opts);
141 bo_setter!(user, create_opts);
142 bo_setter!(userns_mode, create_opts);
143 bo_setter!(working_dir, create_opts);
144
145 bo_setter!(links, create_opts);
150
151 let rv = __containers_create(&self.0, &create_opts.build());
166 match rv {
167 Ok(rv) => Ok(Pyo3Container(rv)),
168 Err(rv) => Err(py_sys_exception!(rv)),
169 }
170 }
171}
172
173#[tokio::main]
174async fn __containers_list(
175 containers: &Containers,
176 opts: &ContainerListOpts,
177) -> Vec<ContainerSummary> {
178 let x = containers.list(opts).await;
179 x.unwrap()
180}
181
182#[tokio::main]
183async fn __containers_prune(
184 containers: &Containers,
185 opts: &ContainerPruneOpts,
186) -> Result<ContainerPrune200Response, docker_api::Error> {
187 containers.prune(opts).await
188}
189
190#[tokio::main]
191async fn __containers_create(
192 containers: &Containers,
193 opts: &ContainerCreateOpts,
194) -> Result<Container, docker_api::Error> {
195 containers.create(opts).await
196}
197
198#[pymethods]
199impl Pyo3Container {
200 #[new]
201 fn new(docker: Pyo3Docker, id: String) -> Self {
202 Pyo3Container(Container::new(docker.0, id))
203 }
204
205 fn id(&self) -> String {
206 self.0.id().to_string()
207 }
208
209 fn inspect(&self) -> PyResult<Py<PyAny>> {
210 let ci = __container_inspect(&self.0);
211 Ok(pythonize_this!(ci))
212 }
213 #[pyo3(signature = (stdout=None, stderr=None, timestamps=None, n_lines=None, all=None, since=None))]
214 fn logs(
215 &self,
216 stdout: Option<bool>,
217 stderr: Option<bool>,
218 timestamps: Option<bool>,
219 n_lines: Option<usize>,
220 all: Option<bool>,
221 since: Option<&Bound<'_, PyDateTime>>,
222 ) -> String {
223 let mut log_opts = LogsOpts::builder();
224
225 bo_setter!(stdout, log_opts);
226 bo_setter!(stderr, log_opts);
227 bo_setter!(timestamps, log_opts);
228 bo_setter!(n_lines, log_opts);
229
230 if all.is_some() && all.unwrap() {
231 log_opts = log_opts.all();
233 }
234
235 if since.is_some() {
236 let rs_since: DateTime<Utc> = since.unwrap().extract().unwrap();
237 log_opts = log_opts.since(&rs_since);
238 }
239
240 __container_logs(&self.0, &log_opts.build())
241 }
242
243 fn remove(&self) -> PyResult<()> {
244 Err(exceptions::PyNotImplementedError::new_err(
245 "This method is not available yet.",
246 ))
247 }
248
249 fn delete(&self) -> PyResult<()> {
250 let rv = __container_delete(&self.0);
251 if rv.is_ok() {
252 Ok(())
253 } else {
254 Err(exceptions::PySystemError::new_err(
255 "Failed to delete container.",
256 ))
257 }
258 }
259
260 fn start(&self) -> PyResult<()> {
275 let rv = __container_start(&self.0);
276
277 match rv {
278 Ok(_rv) => Ok(()),
279 Err(_rv) => Err(exceptions::PySystemError::new_err(
280 "Failed to start container",
281 )),
282 }
283 }
284
285 fn stop(&self, wait: Option<&Bound<'_, PyDelta>>) -> PyResult<()> {
286 let wait: Option<std::time::Duration> = wait.map(|wait| {
287 wait.extract::<chrono::Duration>()
288 .unwrap()
289 .to_std()
290 .unwrap()
291 });
292
293 let rv = __container_stop(&self.0, wait);
294 match rv {
295 Ok(_rv) => Ok(()),
296 Err(_rv) => Err(exceptions::PySystemError::new_err(
297 "Failed to start container",
298 )),
299 }
300 }
301
302 fn restart(&self, wait: Option<&Bound<'_, PyDelta>>) -> PyResult<()> {
303 let wait: Option<std::time::Duration> = wait.map(|wait| {
304 wait.extract::<chrono::Duration>()
305 .unwrap()
306 .to_std()
307 .unwrap()
308 });
309
310 let rv = __container_restart(&self.0, wait);
311 match rv {
312 Ok(_rv) => Ok(()),
313 Err(_rv) => Err(exceptions::PySystemError::new_err(
314 "Failed to stop container",
315 )),
316 }
317 }
318
319 fn kill(&self, signal: Option<&str>) -> PyResult<()> {
320 let rv = __container_kill(&self.0, signal);
321 match rv {
322 Ok(_rv) => Ok(()),
323 Err(_rv) => Err(exceptions::PySystemError::new_err(
324 "Failed to kill container",
325 )),
326 }
327 }
328
329 fn rename(&self, name: &str) -> PyResult<()> {
330 let rv = __container_rename(&self.0, name);
331 match rv {
332 Ok(_rv) => Ok(()),
333 Err(_rv) => Err(exceptions::PySystemError::new_err(
334 "Failed to rename container",
335 )),
336 }
337 }
338
339 fn pause(&self) -> PyResult<()> {
340 let rv = __container_pause(&self.0);
341 match rv {
342 Ok(_rv) => Ok(()),
343 Err(_rv) => Err(exceptions::PySystemError::new_err(
344 "Failed to pause container",
345 )),
346 }
347 }
348
349 fn unpause(&self) -> PyResult<()> {
350 let rv = __container_unpause(&self.0);
351 match rv {
352 Ok(_rv) => Ok(()),
353 Err(_rv) => Err(exceptions::PySystemError::new_err(
354 "Failed to unpause container",
355 )),
356 }
357 }
358
359 fn wait(&self) -> Py<PyAny> {
360 let rv = __container_wait(&self.0).unwrap();
361 pythonize_this!(rv)
362 }
363
364 fn exec(
365 &self,
366 command: &Bound<'_, PyList>,
367 env: Option<&Bound<'_, PyList>>,
368 attach_stdout: Option<bool>,
369 attach_stderr: Option<bool>,
370 privileged: Option<bool>,
373 user: Option<&str>,
374 working_dir: Option<&str>,
375 ) -> PyResult<()> {
376 let command_strings: Vec<String> = command.extract().unwrap();
377 let command: Vec<&str> = command_strings.iter().map(|s| s.as_str()).collect();
378 let mut exec_opts = ExecCreateOpts::builder().command(command);
379
380 if env.is_some() {
381 let env_strings: Vec<String> = env.unwrap().extract().unwrap();
382 let env: Vec<&str> = env_strings.iter().map(|s| s.as_str()).collect();
383 exec_opts = exec_opts.env(env);
384 }
385
386 bo_setter!(attach_stdout, exec_opts);
387 bo_setter!(attach_stderr, exec_opts);
388 bo_setter!(privileged, exec_opts);
391 bo_setter!(user, exec_opts);
392 bo_setter!(working_dir, exec_opts);
393
394 let rv = __container_exec(&self.0, exec_opts.build());
395 let rv = rv.unwrap();
396 match rv {
397 Ok(_rv) => Ok(()),
398 Err(rv) => Err(exceptions::PySystemError::new_err(format!(
399 "Failed to exec container {rv}"
400 ))),
401 }
402 }
403
404 fn copy_from(&self, src: &str, dst: &str) -> PyResult<()> {
405 let rv = __container_copy_from(&self.0, src);
406
407 match rv {
408 Ok(rv) => {
409 let mut archive = Archive::new(&rv[..]);
410 let r = archive.unpack(dst);
411 match r {
412 Ok(_r) => Ok(()),
413 Err(r) => Err(exceptions::PySystemError::new_err(format!("{r}"))),
414 }
415 }
416 Err(rv) => Err(exceptions::PySystemError::new_err(format!("{rv}"))),
417 }
418 }
419
420 fn copy_file_into(&self, src: &str, dst: &str) -> PyResult<()> {
421 let mut file = File::open(src).unwrap();
422 let mut bytes = Vec::new();
423 file.read_to_end(&mut bytes)
424 .expect("Cannot read file on the localhost.");
425
426 let rv = __container_copy_file_into(&self.0, dst, &bytes);
427
428 match rv {
429 Ok(_rv) => Ok(()),
430 Err(rv) => Err(exceptions::PySystemError::new_err(format!("{rv}"))),
431 }
432 }
433
434 fn stat_file(&self, path: &str) -> Py<PyAny> {
435 let rv = __container_stat_file(&self.0, path).unwrap();
436 pythonize_this!(rv)
437 }
438
439 fn commit(&self) -> PyResult<()> {
440 Err(exceptions::PyNotImplementedError::new_err(
441 "This method is not available yet.",
442 ))
443 }
444
445 fn __repr__(&self) -> String {
446 let inspect = __container_inspect(&self.0);
447 format!(
448 "Container(id: {}, name: {}, status: {})",
449 inspect.id.unwrap(),
450 inspect.name.unwrap(),
451 inspect.state.unwrap().status.unwrap()
452 )
453 }
454
455 fn __string__(&self) -> String {
456 self.__repr__()
457 }
458}
459
460#[tokio::main]
461async fn __container_inspect(container: &Container) -> ContainerInspect200Response {
462 let c = container.inspect().await;
463 c.unwrap()
464}
465
466#[tokio::main]
467async fn __container_logs(container: &Container, log_opts: &LogsOpts) -> String {
468 let log_stream = container.logs(log_opts);
469
470 let log = log_stream
471 .map(|chunk| match chunk {
472 Ok(chunk) => chunk.to_vec(),
473 Err(e) => {
474 eprintln!("Error: {e}");
475 vec![]
476 }
477 })
478 .collect::<Vec<_>>()
479 .await
480 .into_iter()
481 .flatten()
482 .collect::<Vec<_>>();
483
484 format!("{}", String::from_utf8_lossy(&log))
485}
486
487#[tokio::main]
488async fn __container_delete(container: &Container) -> Result<String, docker_api::Error> {
489 container.delete().await
490}
491
492#[tokio::main]
493async fn __container_start(container: &Container) -> Result<(), docker_api::Error> {
494 container.start().await
495}
496
497#[tokio::main]
498async fn __container_stop(
499 container: &Container,
500 wait: Option<std::time::Duration>,
501) -> Result<(), docker_api::Error> {
502 container.stop(wait).await
503}
504
505#[tokio::main]
506async fn __container_restart(
507 container: &Container,
508 wait: Option<std::time::Duration>,
509) -> Result<(), docker_api::Error> {
510 container.restart(wait).await
511}
512
513#[tokio::main]
514async fn __container_kill(
515 container: &Container,
516 signal: Option<&str>,
517) -> Result<(), docker_api::Error> {
518 container.kill(signal).await
519}
520
521#[tokio::main]
522async fn __container_rename(container: &Container, name: &str) -> Result<(), docker_api::Error> {
523 container.rename(name).await
524}
525
526#[tokio::main]
527async fn __container_pause(container: &Container) -> Result<(), docker_api::Error> {
528 container.pause().await
529}
530
531#[tokio::main]
532async fn __container_unpause(container: &Container) -> Result<(), docker_api::Error> {
533 container.unpause().await
534}
535
536#[tokio::main]
537async fn __container_wait(
538 container: &Container,
539) -> Result<ContainerWaitResponse, docker_api::Error> {
540 container.wait().await
541}
542
543#[tokio::main]
544async fn __container_exec(
545 container: &Container,
546 exec_opts: ExecCreateOpts,
547) -> Option<Result<TtyChunk, docker_api::conn::Error>> {
548 container.exec(&exec_opts).next().await
549}
550
551#[tokio::main]
552async fn __container_copy_from(
553 container: &Container,
554 path: &str,
555) -> Result<Vec<u8>, docker_api::Error> {
556 container.copy_from(path).try_concat().await
557}
558
559#[tokio::main]
560async fn __container_copy_file_into(
561 container: &Container,
562 dst: &str,
563 bytes: &Vec<u8>,
564) -> Result<(), docker_api::Error> {
565 container.copy_file_into(dst, bytes).await
566}
567
568#[tokio::main]
569async fn __container_stat_file(
570 container: &Container,
571 src: &str,
572) -> Result<String, docker_api::Error> {
573 container.stat_file(src).await
574}