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