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: &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>>, // name,maximum_retry_count,
103        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        // this will suck
146
147        // bo_setter!(devices, create_opts);
148
149        bo_setter!(links, create_opts);
150
151        // bo_setter!(publish_all_ports, create_opts);
152        // bo_setter!(restart_policy, create_opts);
153        // bo_setter!(security_options, create_opts);
154        // bo_setter!(stop_timeout, create_opts);
155        // bo_setter!(volumes, create_opts);
156        // bo_setter!(volumes_from, create_opts);
157        // bo_setter!(capabilities, create_opts);
158        // bo_setter!(command, create_opts);
159        // bo_setter!(entrypoint, create_opts);
160        // bo_setter!(env, create_opts);
161        // bo_setter!(expose, create_opts);
162        // bo_setter!(extra_hosts, create_opts);
163        // bo_setter!(labels, create_opts);
164
165        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            // all needs to be called w/o a value
232            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 top(&self) -> PyResult<()> {
261    //     Err(exceptions::PyNotImplementedError::new_err(
262    //         "This method is not available yet.",
263    //     ))
264    // }
265
266    // fn export(&self, docker_path: &str, local_path: &str) -> PyResult<()> {
267    //     let bytes = self.0.export();
268    //     let mut archive = Archive::new(&bytes[..]);
269    //     archive.unpack(local_path);
270
271    //     Ok(())
272    // }
273
274    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        // detach_keys: Option<&str>,
371        // tty: Option<bool>,
372        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!(tty, exec_opts);
389        // bo_setter!(detach_keys,exec_opts);
390        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}