docker_pyo3/
container.rs

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>, // name,maximum_retry_count,
101        _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        // this will suck
141
142        // bo_setter!(devices, create_opts);
143
144        bo_setter!(links, create_opts);
145
146        // bo_setter!(publish_all_ports, create_opts);
147        // bo_setter!(restart_policy, create_opts);
148        // bo_setter!(security_options, create_opts);
149        // bo_setter!(stop_timeout, create_opts);
150        // bo_setter!(volumes, create_opts);
151        // bo_setter!(volumes_from, create_opts);
152        // bo_setter!(capabilities, create_opts);
153        // bo_setter!(command, create_opts);
154        // bo_setter!(entrypoint, create_opts);
155        // bo_setter!(env, create_opts);
156        // bo_setter!(expose, create_opts);
157        // bo_setter!(extra_hosts, create_opts);
158        // bo_setter!(labels, create_opts);
159
160        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            // all needs to be called w/o a value
226            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 top(&self) -> PyResult<()> {
255    //     Err(exceptions::PyNotImplementedError::new_err(
256    //         "This method is not available yet.",
257    //     ))
258    // }
259
260    // fn export(&self, docker_path: &str, local_path: &str) -> PyResult<()> {
261    //     let bytes = self.0.export();
262    //     let mut archive = Archive::new(&bytes[..]);
263    //     archive.unpack(local_path);
264
265    //     Ok(())
266    // }
267
268    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        // detach_keys: Option<&str>,
365        // tty: Option<bool>,
366        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!(tty, exec_opts);
381        // bo_setter!(detach_keys,exec_opts);
382        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}