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 #[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 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}