docker_pyo3/
image.rs

1use std::fs::OpenOptions;
2
3use crate::Pyo3Docker;
4use docker_api::models::{
5    ImageDeleteResponseItem, ImageHistory200Response, ImageInspect, ImagePrune200Response,
6    ImageSummary,
7};
8use docker_api::opts::{
9    ImageBuildOpts, ImageListOpts, ImagePushOpts, PullOpts, RegistryAuth, TagOpts,
10};
11
12use docker_api::{Image, Images};
13use futures_util::StreamExt;
14use pyo3::exceptions;
15use pyo3::prelude::*;
16use pyo3::types::PyDict;
17use pythonize::pythonize;
18use std::io::Write;
19
20#[pymodule]
21pub fn image(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
22    m.add_class::<Pyo3Images>()?;
23    m.add_class::<Pyo3Image>()?;
24    Ok(())
25}
26
27#[derive(Debug)]
28#[pyclass(name = "Images")]
29pub struct Pyo3Images(pub Images);
30
31#[derive(Debug)]
32#[pyclass(name = "Image")]
33pub struct Pyo3Image(pub Image);
34
35#[pymethods]
36impl Pyo3Images {
37    #[new]
38    pub fn new(docker: Pyo3Docker) -> Self {
39        Pyo3Images(Images::new(docker.0))
40    }
41
42    fn get(&self, name: &str) -> Pyo3Image {
43        Pyo3Image(self.0.get(name))
44    }
45
46    #[pyo3(signature = (all=None, digests=None, filter=None))]
47    fn list(
48        &self,
49        all: Option<bool>,
50        digests: Option<bool>,
51        filter: Option<&str>,
52    ) -> PyResult<Py<PyAny>> {
53        let mut opts = ImageListOpts::builder();
54        bo_setter!(all, opts);
55        bo_setter!(digests, opts);
56
57        let rv = __images_list(&self.0, &opts.build());
58
59        match rv {
60            Ok(rv) => Ok(pythonize_this!(rv)),
61            Err(rv) => Err(exceptions::PySystemError::new_err(format!("{rv:?}"))),
62        }
63    }
64
65    fn prune(&self) -> PyResult<Py<PyAny>> {
66        match __images_prune(&self.0) {
67            Ok(info) => Ok(pythonize_this!(info)),
68            Err(e) => Err(exceptions::PySystemError::new_err(format!("{e:?}"))),
69        }
70    }
71
72    #[pyo3(signature = (path, *, dockerfile=None, tag=None, extra_hosts=None, remote=None, quiet=None, nocahe=None, pull=None, rm=None, forcerm=None, memory=None, memswap=None, cpu_shares=None, cpu_set_cpus=None, cpu_period=None, cpu_quota=None, shm_size=None, squash=None, network_mode=None, platform=None, target=None, outputs=None, labels=None))]
73    fn build(
74        &self,
75        path: &str,
76        dockerfile: Option<&str>,
77        tag: Option<&str>,
78        extra_hosts: Option<&str>,
79        remote: Option<&str>,
80        quiet: Option<bool>,
81        nocahe: Option<bool>,
82        pull: Option<&str>,
83        rm: Option<bool>,
84        forcerm: Option<bool>,
85        memory: Option<usize>,
86        memswap: Option<usize>,
87        cpu_shares: Option<usize>,
88        cpu_set_cpus: Option<&str>,
89        cpu_period: Option<usize>,
90        cpu_quota: Option<usize>,
91        shm_size: Option<usize>,
92        squash: Option<bool>,
93        network_mode: Option<&str>,
94        platform: Option<&str>,
95        target: Option<&str>,
96        outputs: Option<&str>,
97        labels: Option<&Bound<'_, PyDict>>,
98    ) -> PyResult<Py<PyAny>> {
99        let mut bo = ImageBuildOpts::builder(path);
100
101        bo_setter!(dockerfile, bo);
102        bo_setter!(tag, bo);
103        bo_setter!(extra_hosts, bo);
104        bo_setter!(remote, bo);
105        bo_setter!(quiet, bo);
106        bo_setter!(nocahe, bo);
107        bo_setter!(pull, bo);
108        bo_setter!(rm, bo);
109        bo_setter!(forcerm, bo);
110        bo_setter!(memory, bo);
111        bo_setter!(memswap, bo);
112        bo_setter!(cpu_shares, bo);
113        bo_setter!(cpu_set_cpus, bo);
114        bo_setter!(cpu_period, bo);
115        bo_setter!(cpu_quota, bo);
116        bo_setter!(shm_size, bo);
117        bo_setter!(squash, bo);
118        bo_setter!(network_mode, bo);
119        bo_setter!(platform, bo);
120        bo_setter!(target, bo);
121        bo_setter!(outputs, bo);
122
123        let rv = __images_build(&self.0, &bo.build());
124
125        match rv {
126            Ok(rv) => Ok(pythonize_this!(rv)),
127            Err(rv) => Err(py_sys_exception!(rv)),
128        }
129    }
130
131    // fn search(&self) -> PyResult<()> {
132    //     Err(exceptions::PyNotImplementedError::new_err(
133    //         "This method is not available yet.",
134    //     ))
135    // }
136
137    #[pyo3(signature = (image=None, src=None, repo=None, tag=None, auth_password=None, auth_token=None))]
138    fn pull(
139        &self,
140        image: Option<&str>,
141        src: Option<&str>,
142        repo: Option<&str>,
143        tag: Option<&str>,
144        auth_password: Option<&Bound<'_, PyDict>>,
145        auth_token: Option<&Bound<'_, PyDict>>,
146    ) -> PyResult<Py<PyAny>> {
147        let mut pull_opts = PullOpts::builder();
148
149        if auth_password.is_some() && auth_token.is_some() {
150            let msg = "Got both auth_password and auth_token for image.push(). Only one of these options is allowed";
151            return Err(py_sys_exception!(msg));
152        }
153
154        let auth = if auth_password.is_some() && auth_token.is_none() {
155            let auth_dict = auth_password.unwrap();
156            let username = auth_dict.get_item("username").unwrap_or(None);
157            let password = auth_dict.get_item("password").unwrap_or(None);
158            let email = auth_dict.get_item("email").unwrap_or(None);
159            let server_address = auth_dict.get_item("server_address").unwrap_or(None);
160
161            let username = username.map(|v| v.extract::<String>().unwrap());
162            let password = password.map(|v| v.extract::<String>().unwrap());
163            let email = email.map(|v| v.extract::<String>().unwrap());
164            let server_address = server_address.map(|v| v.extract::<String>().unwrap());
165
166            let mut ra = RegistryAuth::builder();
167
168            bo_setter!(username, ra);
169            bo_setter!(password, ra);
170            bo_setter!(email, ra);
171            bo_setter!(server_address, ra);
172
173            Some(ra.build())
174        } else if auth_token.is_some() && auth_password.is_none() {
175            let token = RegistryAuth::token(
176                auth_token
177                    .unwrap()
178                    .get_item("identity_token")
179                    .unwrap_or(None)
180                    .expect("identity_token is required")
181                    .extract::<String>()
182                    .unwrap(),
183            );
184            Some(token)
185        } else {
186            Some(RegistryAuth::builder().build())
187        };
188
189        bo_setter!(src, pull_opts);
190        bo_setter!(repo, pull_opts);
191        bo_setter!(tag, pull_opts);
192        bo_setter!(image, pull_opts);
193        bo_setter!(auth, pull_opts);
194
195        let rv = __images_pull(&self.0, &pull_opts.build());
196
197        match rv {
198            Ok(rv) => Ok(pythonize_this!(rv)),
199            Err(rv) => Err(exceptions::PySystemError::new_err(format!("{rv}"))),
200        }
201    }
202
203    // fn export(&self) -> PyResult<()> {
204    //     Err(exceptions::PyNotImplementedError::new_err(
205    //         "This method is not available yet.",
206    //     ))
207    // }
208
209    // fn import(&self) -> PyResult<()> {
210    //     Err(exceptions::PyNotImplementedError::new_err(
211    //         "This method is not available yet.",
212    //     ))
213    // }
214
215    fn push(&self) -> PyResult<()> {
216        Err(exceptions::PyNotImplementedError::new_err(
217            "This method is not available yet.",
218        ))
219    }
220
221    fn clear_cache(&self) -> PyResult<()> {
222        Err(exceptions::PyNotImplementedError::new_err(
223            "This method is not available yet.",
224        ))
225    }
226}
227
228#[tokio::main]
229async fn __images_list(
230    images: &Images,
231    opts: &ImageListOpts,
232) -> Result<Vec<ImageSummary>, docker_api::Error> {
233    images.list(opts).await
234}
235
236#[tokio::main]
237async fn __images_prune(images: &Images) -> Result<ImagePrune200Response, docker_api::Error> {
238    images.prune(&Default::default()).await
239}
240
241#[tokio::main]
242async fn __images_build(
243    images: &Images,
244    opts: &ImageBuildOpts,
245) -> Result<Vec<String>, docker_api::Error> {
246    use futures_util::StreamExt;
247    let mut stream = images.build(opts);
248    let mut ok_stream_vec = Vec::new();
249    let mut err_message = None;
250    while let Some(build_result) = stream.next().await {
251        match build_result {
252            Ok(output) => ok_stream_vec.push(format!("{output:?}")),
253            Err(e) => err_message = Some(e),
254        }
255    }
256
257    match err_message {
258        Some(err_message) => Err(err_message),
259        _ => Ok(ok_stream_vec),
260    }
261}
262
263#[tokio::main]
264async fn __images_pull(
265    images: &Images,
266    pull_opts: &PullOpts,
267) -> Result<Vec<String>, docker_api::Error> {
268    let mut stream = images.pull(pull_opts);
269    let mut ok_stream_vec = Vec::new();
270    let mut err_message = None;
271    while let Some(pull_result) = stream.next().await {
272        match pull_result {
273            Ok(output) => ok_stream_vec.push(format!("{output:?}")),
274            Err(e) => err_message = Some(e),
275        }
276    }
277
278    match err_message {
279        Some(err_message) => Err(err_message),
280        _ => Ok(ok_stream_vec),
281    }
282}
283
284#[pymethods]
285impl Pyo3Image {
286    #[new]
287    fn new(docker: Pyo3Docker, name: &str) -> Pyo3Image {
288        Pyo3Image(Image::new(docker.0, name))
289    }
290
291    fn __repr__(&self) -> String {
292        let inspect = __image_inspect(&self.0).unwrap();
293        format!(
294            "Image(id: {:?}, name: {})",
295            inspect.id.unwrap(),
296            self.0.name()
297        )
298    }
299
300    fn __string__(&self) -> String {
301        self.__repr__()
302    }
303
304    fn name(&self) -> Py<PyAny> {
305        let rv = self.0.name();
306        pythonize_this!(rv)
307    }
308
309    fn inspect(&self) -> PyResult<Py<PyAny>> {
310        let rv = __image_inspect(&self.0);
311        match rv {
312            Ok(rv) => Ok(pythonize_this!(rv)),
313            Err(rv) => Err(py_sys_exception!(rv)),
314        }
315    }
316
317    fn remove(&self) -> PyResult<()> {
318        Err(exceptions::PyNotImplementedError::new_err(
319            "This method is not available yet.",
320        ))
321    }
322
323    fn delete(&self) -> PyResult<String> {
324        let rv = __image_delete(&self.0);
325        match rv {
326            Ok(rv) => {
327                let mut r_value = "".to_owned();
328                for r in rv {
329                    let r_str = format!("{r:?}");
330                    r_value.push_str(&r_str);
331                }
332                Ok(r_value)
333            }
334            Err(rv) => Err(py_sys_exception!(rv)),
335        }
336    }
337
338    fn history(&self) -> PyResult<String> {
339        let rv = __image_history(&self.0);
340
341        match rv {
342            Ok(rv) => {
343                let mut r_value = "".to_owned();
344                for r in rv {
345                    let r_str = format!("{r:?}");
346                    r_value.push_str(&r_str);
347                }
348                Ok(r_value)
349            }
350            Err(rv) => Err(py_sys_exception!(rv)),
351        }
352    }
353
354    fn export(&self, path: Option<&str>) -> PyResult<String> {
355        let path = if path.is_none() {
356            format!("{:?}", &self.0)
357        } else {
358            path.unwrap().to_string()
359        };
360
361        let rv = __image_export(&self.0, path);
362
363        if rv.is_some() {
364            match rv.unwrap() {
365                Ok(n) => Ok(n),
366                Err(e) => Err(py_sys_exception!(e)),
367            }
368        } else {
369            Err(exceptions::PySystemError::new_err("Unknown error occurred in export. (Seriously I don't know how you get here, open a ticket and tell me what happens)"))
370        }
371    }
372
373    #[pyo3(signature = (repo=None, tag=None))]
374    fn tag(&self, repo: Option<&str>, tag: Option<&str>) -> PyResult<()> {
375        let mut opts = TagOpts::builder();
376
377        bo_setter!(repo, opts);
378        bo_setter!(tag, opts);
379
380        let rv = __image_tag(&self.0, &opts.build());
381
382        match rv {
383            Ok(_rv) => Ok(()),
384            Err(rv) => Err(py_sys_exception!(rv)),
385        }
386    }
387
388    fn push(
389        &self,
390        auth_password: Option<&Bound<'_, PyDict>>,
391        auth_token: Option<&Bound<'_, PyDict>>,
392        tag: Option<&str>,
393    ) -> PyResult<()> {
394        if auth_password.is_some() && auth_token.is_some() {
395            let msg = "Got both auth_password and auth_token for image.push(). Only one of these options is allowed";
396            return Err(py_sys_exception!(msg));
397        }
398
399        let auth = if auth_password.is_some() && auth_token.is_none() {
400            let auth_dict = auth_password.unwrap();
401            let username = auth_dict.get_item("username").unwrap_or(None);
402            let password = auth_dict.get_item("password").unwrap_or(None);
403            let email = auth_dict.get_item("email").unwrap_or(None);
404            let server_address = auth_dict.get_item("server_address").unwrap_or(None);
405
406            let username = username.map(|v| v.extract::<String>().unwrap());
407            let password = password.map(|v| v.extract::<String>().unwrap());
408            let email = email.map(|v| v.extract::<String>().unwrap());
409            let server_address = server_address.map(|v| v.extract::<String>().unwrap());
410
411            let mut ra = RegistryAuth::builder();
412
413            bo_setter!(username, ra);
414            bo_setter!(password, ra);
415            bo_setter!(email, ra);
416            bo_setter!(server_address, ra);
417
418            Some(ra.build())
419        } else if auth_token.is_some() && auth_password.is_none() {
420            let token = RegistryAuth::token(
421                auth_token
422                    .unwrap()
423                    .get_item("identity_token")
424                    .unwrap_or(None)
425                    .expect("identity_token is required")
426                    .extract::<String>()
427                    .unwrap(),
428            );
429            Some(token)
430        } else {
431            Some(RegistryAuth::builder().build())
432        };
433
434        let mut opts = ImagePushOpts::builder();
435        bo_setter!(tag, opts);
436        bo_setter!(auth, opts);
437
438        let rv = __image_push(&self.0, &opts.build());
439        match rv {
440            Ok(_rv) => Ok(()),
441            Err(rv) => Err(py_sys_exception!(rv)),
442        }
443    }
444
445    fn distribution_inspect(&self) -> PyResult<()> {
446        Err(exceptions::PyNotImplementedError::new_err(
447            "This method is not available yet.",
448        ))
449    }
450}
451
452#[tokio::main]
453async fn __image_inspect(image: &Image) -> Result<ImageInspect, docker_api::Error> {
454    image.inspect().await
455}
456
457#[tokio::main]
458async fn __image_delete(image: &Image) -> Result<Vec<ImageDeleteResponseItem>, docker_api::Error> {
459    image.delete().await
460}
461
462#[tokio::main]
463async fn __image_history(image: &Image) -> Result<ImageHistory200Response, docker_api::Error> {
464    image.history().await
465}
466
467#[tokio::main]
468async fn __image_export(image: &Image, path: String) -> Option<Result<String, docker_api::Error>> {
469    let mut export_file = OpenOptions::new()
470        .write(true)
471        .create(true)
472        .open(path)
473        .unwrap();
474
475    let rv = image.export().next().await;
476
477    match rv {
478        None => None,
479        Some(_rv) => match _rv {
480            Ok(bytes) => {
481                let w_rv = export_file.write(&bytes).unwrap();
482                Some(Ok(format!("{w_rv:?}")))
483            }
484            Err(_rv) => Some(Err(_rv)),
485        },
486    }
487}
488
489#[tokio::main]
490async fn __image_tag(image: &Image, opts: &TagOpts) -> Result<(), docker_api::Error> {
491    image.tag(opts).await
492}
493
494#[tokio::main]
495async fn __image_push(image: &Image, opts: &ImagePushOpts) -> Result<(), docker_api::Error> {
496    image.push(opts).await
497}