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 PublishPort,
9};
10use docker_api::{Container, Containers};
11use futures_util::stream::StreamExt;
12use futures_util::TryStreamExt;
13use pyo3::exceptions;
14use pyo3::prelude::*;
15use pyo3::types::{PyDateTime, PyDelta, PyDict, PyList};
16use pythonize::pythonize;
17use std::{collections::HashMap, fs::File, io::Read};
18use tar::Archive;
19
20use crate::Pyo3Docker;
21
22#[pymodule]
23pub fn container(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
24 m.add_class::<Pyo3Containers>()?;
25 m.add_class::<Pyo3Container>()?;
26 Ok(())
27}
28
29#[derive(Debug)]
30#[pyclass(name = "Containers")]
31pub struct Pyo3Containers(pub Containers);
32
33#[derive(Debug)]
34#[pyclass(name = "Container")]
35pub struct Pyo3Container(pub Container);
36
37#[pymethods]
38impl Pyo3Containers {
39 #[new]
40 pub fn new(docker: Pyo3Docker) -> Self {
41 Pyo3Containers(Containers::new(docker.0))
42 }
43
44 fn get(&self, id: &str) -> Pyo3Container {
45 Pyo3Container(self.0.get(id))
46 }
47
48 #[pyo3(signature = (all=None, since=None, before=None, sized=None))]
49 fn list(
50 &self,
51 all: Option<bool>,
52 since: Option<String>,
53 before: Option<String>,
54 sized: Option<bool>,
55 ) -> Py<PyAny> {
56 let mut builder = ContainerListOpts::builder();
57
58 bo_setter!(all, builder);
59 bo_setter!(since, builder);
60 bo_setter!(before, builder);
61 bo_setter!(sized, builder);
62
63 let cs = __containers_list(&self.0, &builder.build());
64 pythonize_this!(cs)
65 }
66
67 fn prune(&self) -> PyResult<Py<PyAny>> {
68 let rv = __containers_prune(&self.0, &Default::default());
69
70 match rv {
71 Ok(rv) => Ok(pythonize_this!(rv)),
72 Err(rv) => Err(py_sys_exception!(rv)),
73 }
74 }
75 #[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))]
76 fn create(
77 &self,
78 image: &str,
79 attach_stderr: Option<bool>,
80 attach_stdin: Option<bool>,
81 attach_stdout: Option<bool>,
82 auto_remove: Option<bool>,
83 capabilities: Option<&Bound<'_, PyList>>,
84 command: Option<&Bound<'_, PyList>>,
85 cpu_shares: Option<u32>,
86 cpus: Option<f64>,
87 devices: Option<&Bound<'_, PyList>>,
88 entrypoint: Option<&Bound<'_, PyList>>,
89 env: Option<&Bound<'_, PyList>>,
90 expose: Option<&Bound<'_, PyList>>,
91 extra_hosts: Option<&Bound<'_, PyList>>,
92 labels: Option<&Bound<'_, PyDict>>,
93 links: Option<&Bound<'_, PyList>>,
94 log_driver: Option<&str>,
95 memory: Option<u64>,
96 memory_swap: Option<i64>,
97 name: Option<&str>,
98 nano_cpus: Option<u64>,
99 network_mode: Option<&str>,
100 privileged: Option<bool>,
101 publish: Option<&Bound<'_, PyList>>,
102 publish_all_ports: Option<bool>,
103 restart_policy: Option<&Bound<'_, PyDict>>, security_options: Option<&Bound<'_, PyList>>,
105 stop_signal: Option<&str>,
106 stop_signal_num: Option<u64>,
107 stop_timeout: Option<&Bound<'_, PyDelta>>,
108 tty: Option<bool>,
109 user: Option<&str>,
110 userns_mode: Option<&str>,
111 volumes: Option<&Bound<'_, PyList>>,
112 volumes_from: Option<&Bound<'_, PyList>>,
113 working_dir: Option<&str>,
114 ) -> PyResult<Pyo3Container> {
115 let mut create_opts = ContainerCreateOpts::builder().image(image);
116
117 let links: Option<Vec<String>> = if links.is_some() {
118 links.unwrap().extract().unwrap()
119 } else {
120 None
121 };
122 let links: Option<Vec<&str>> = links
123 .as_ref()
124 .map(|v| v.iter().map(|s| s.as_str()).collect());
125
126 let capabilities_strings: Option<Vec<String>> = if capabilities.is_some() {
127 capabilities.unwrap().extract().unwrap()
128 } else {
129 None
130 };
131 let capabilities: Option<Vec<&str>> = capabilities_strings
132 .as_ref()
133 .map(|v| v.iter().map(|s| s.as_str()).collect());
134
135 let command_strings: Option<Vec<String>> = if command.is_some() {
136 command.unwrap().extract().unwrap()
137 } else {
138 None
139 };
140 let command: Option<Vec<&str>> = command_strings
141 .as_ref()
142 .map(|v| v.iter().map(|s| s.as_str()).collect());
143
144 let entrypoint_strings: Option<Vec<String>> = if entrypoint.is_some() {
145 entrypoint.unwrap().extract().unwrap()
146 } else {
147 None
148 };
149 let entrypoint: Option<Vec<&str>> = entrypoint_strings
150 .as_ref()
151 .map(|v| v.iter().map(|s| s.as_str()).collect());
152
153 let env_strings: Option<Vec<String>> = if env.is_some() {
154 env.unwrap().extract().unwrap()
155 } else {
156 None
157 };
158 let env: Option<Vec<&str>> = env_strings
159 .as_ref()
160 .map(|v| v.iter().map(|s| s.as_str()).collect());
161
162 let extra_hosts_strings: Option<Vec<String>> = if extra_hosts.is_some() {
163 extra_hosts.unwrap().extract().unwrap()
164 } else {
165 None
166 };
167 let extra_hosts: Option<Vec<&str>> = extra_hosts_strings
168 .as_ref()
169 .map(|v| v.iter().map(|s| s.as_str()).collect());
170
171 let security_options_strings: Option<Vec<String>> = if security_options.is_some() {
172 security_options.unwrap().extract().unwrap()
173 } else {
174 None
175 };
176 let security_options: Option<Vec<&str>> = security_options_strings
177 .as_ref()
178 .map(|v| v.iter().map(|s| s.as_str()).collect());
179
180 let volumes_strings: Option<Vec<String>> = if volumes.is_some() {
181 volumes.unwrap().extract().unwrap()
182 } else {
183 None
184 };
185 let volumes: Option<Vec<&str>> = volumes_strings
186 .as_ref()
187 .map(|v| v.iter().map(|s| s.as_str()).collect());
188
189 let volumes_from_strings: Option<Vec<String>> = if volumes_from.is_some() {
190 volumes_from.unwrap().extract().unwrap()
191 } else {
192 None
193 };
194 let volumes_from: Option<Vec<&str>> = volumes_from_strings
195 .as_ref()
196 .map(|v| v.iter().map(|s| s.as_str()).collect());
197
198 let devices_vec: Option<Vec<HashMap<String, String>>> = if devices.is_some() {
199 let list = devices.unwrap();
200 let mut result = Vec::new();
201 for item in list.iter() {
202 let dict: HashMap<String, String> = item.extract().unwrap();
203 result.push(dict);
204 }
205 Some(result)
206 } else {
207 None
208 };
209 let devices = devices_vec;
210
211 let labels_map: Option<HashMap<String, String>> = if labels.is_some() {
212 Some(labels.unwrap().extract().unwrap())
213 } else {
214 None
215 };
216 let labels: Option<HashMap<&str, &str>> = labels_map
217 .as_ref()
218 .map(|m| m.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect());
219
220 let stop_timeout_duration: Option<std::time::Duration> =
221 stop_timeout.map(|st| st.extract::<chrono::Duration>().unwrap().to_std().unwrap());
222 let stop_timeout = stop_timeout_duration;
223
224 bo_setter!(attach_stderr, create_opts);
225 bo_setter!(attach_stdin, create_opts);
226 bo_setter!(attach_stdout, create_opts);
227 bo_setter!(auto_remove, create_opts);
228 bo_setter!(cpu_shares, create_opts);
229 bo_setter!(cpus, create_opts);
230 bo_setter!(log_driver, create_opts);
231 bo_setter!(memory, create_opts);
232 bo_setter!(memory_swap, create_opts);
233 bo_setter!(name, create_opts);
234 bo_setter!(nano_cpus, create_opts);
235 bo_setter!(network_mode, create_opts);
236 bo_setter!(privileged, create_opts);
237 bo_setter!(stop_signal, create_opts);
238 bo_setter!(stop_signal_num, create_opts);
239 bo_setter!(tty, create_opts);
240 bo_setter!(user, create_opts);
241 bo_setter!(userns_mode, create_opts);
242 bo_setter!(working_dir, create_opts);
243
244 bo_setter!(devices, create_opts);
245 bo_setter!(links, create_opts);
246 bo_setter!(capabilities, create_opts);
247 bo_setter!(command, create_opts);
248 bo_setter!(entrypoint, create_opts);
249 bo_setter!(env, create_opts);
250 bo_setter!(extra_hosts, create_opts);
251 bo_setter!(security_options, create_opts);
252 bo_setter!(volumes, create_opts);
253 bo_setter!(volumes_from, create_opts);
254
255 bo_setter!(labels, create_opts);
256 bo_setter!(stop_timeout, create_opts);
257
258 if let Some(expose_list) = expose {
260 for item in expose_list.iter() {
261 let port_dict: &Bound<'_, PyDict> = item.downcast()?;
262 let srcport: u32 = port_dict
263 .get_item("srcport")?
264 .expect("srcport required")
265 .extract()?;
266 let hostport: u32 = port_dict
267 .get_item("hostport")?
268 .expect("hostport required")
269 .extract()?;
270 let protocol: String = match port_dict.get_item("protocol")? {
271 Some(p) => p.extract()?,
272 None => "tcp".to_string(),
273 };
274
275 let publish_port = match protocol.as_str() {
276 "tcp" => PublishPort::tcp(srcport),
277 "udp" => PublishPort::udp(srcport),
278 "sctp" => PublishPort::sctp(srcport),
279 _ => {
280 return Err(exceptions::PyValueError::new_err(format!(
281 "unknown protocol: {}",
282 protocol
283 )))
284 }
285 };
286
287 create_opts = create_opts.expose(publish_port, hostport);
288 }
289 }
290
291 if let Some(publish_list) = publish {
293 for item in publish_list.iter() {
294 let port_dict: &Bound<'_, PyDict> = item.downcast()?;
295 let port: u32 = port_dict
296 .get_item("port")?
297 .expect("port required")
298 .extract()?;
299 let protocol: String = match port_dict.get_item("protocol")? {
300 Some(p) => p.extract()?,
301 None => "tcp".to_string(),
302 };
303
304 let publish_port = match protocol.as_str() {
305 "tcp" => PublishPort::tcp(port),
306 "udp" => PublishPort::udp(port),
307 "sctp" => PublishPort::sctp(port),
308 _ => {
309 return Err(exceptions::PyValueError::new_err(format!(
310 "unknown protocol: {}",
311 protocol
312 )))
313 }
314 };
315
316 create_opts = create_opts.publish(publish_port);
317 }
318 }
319
320 if publish_all_ports.is_some() && publish_all_ports.unwrap() {
321 create_opts = create_opts.publish_all_ports();
322 }
323
324 if restart_policy.is_some() {
325 let policy_dict = restart_policy.unwrap();
326 let name = policy_dict
327 .get_item("name")
328 .unwrap_or(None)
329 .expect("restart_policy requires 'name' key")
330 .extract::<String>()
331 .unwrap();
332 let max_retry = policy_dict
333 .get_item("maximum_retry_count")
334 .unwrap_or(None)
335 .map(|v| v.extract::<u64>().unwrap())
336 .unwrap_or(0);
337
338 create_opts = create_opts.restart_policy(&name, max_retry);
339 }
340
341 let rv = __containers_create(&self.0, &create_opts.build());
345 match rv {
346 Ok(rv) => Ok(Pyo3Container(rv)),
347 Err(rv) => Err(py_sys_exception!(rv)),
348 }
349 }
350}
351
352#[tokio::main]
353async fn __containers_list(
354 containers: &Containers,
355 opts: &ContainerListOpts,
356) -> Vec<ContainerSummary> {
357 let x = containers.list(opts).await;
358 x.unwrap()
359}
360
361#[tokio::main]
362async fn __containers_prune(
363 containers: &Containers,
364 opts: &ContainerPruneOpts,
365) -> Result<ContainerPrune200Response, docker_api::Error> {
366 containers.prune(opts).await
367}
368
369#[tokio::main]
370async fn __containers_create(
371 containers: &Containers,
372 opts: &ContainerCreateOpts,
373) -> Result<Container, docker_api::Error> {
374 containers.create(opts).await
375}
376
377#[pymethods]
378impl Pyo3Container {
379 #[new]
380 fn new(docker: Pyo3Docker, id: String) -> Self {
381 Pyo3Container(Container::new(docker.0, id))
382 }
383
384 fn id(&self) -> String {
385 self.0.id().to_string()
386 }
387
388 fn inspect(&self) -> PyResult<Py<PyAny>> {
389 let ci = __container_inspect(&self.0);
390 Ok(pythonize_this!(ci))
391 }
392 #[pyo3(signature = (stdout=None, stderr=None, timestamps=None, n_lines=None, all=None, since=None))]
393 fn logs(
394 &self,
395 stdout: Option<bool>,
396 stderr: Option<bool>,
397 timestamps: Option<bool>,
398 n_lines: Option<usize>,
399 all: Option<bool>,
400 since: Option<&Bound<'_, PyDateTime>>,
401 ) -> String {
402 let mut log_opts = LogsOpts::builder();
403
404 bo_setter!(stdout, log_opts);
405 bo_setter!(stderr, log_opts);
406 bo_setter!(timestamps, log_opts);
407 bo_setter!(n_lines, log_opts);
408
409 if all.is_some() && all.unwrap() {
410 log_opts = log_opts.all();
412 }
413
414 if since.is_some() {
415 let rs_since: DateTime<Utc> = since.unwrap().extract().unwrap();
416 log_opts = log_opts.since(&rs_since);
417 }
418
419 __container_logs(&self.0, &log_opts.build())
420 }
421
422 fn remove(&self) -> PyResult<()> {
423 Err(exceptions::PyNotImplementedError::new_err(
424 "This method is not available yet.",
425 ))
426 }
427
428 fn delete(&self) -> PyResult<()> {
429 let rv = __container_delete(&self.0);
430 if rv.is_ok() {
431 Ok(())
432 } else {
433 Err(exceptions::PySystemError::new_err(
434 "Failed to delete container.",
435 ))
436 }
437 }
438
439 fn start(&self) -> PyResult<()> {
454 let rv = __container_start(&self.0);
455
456 match rv {
457 Ok(_rv) => Ok(()),
458 Err(_rv) => Err(exceptions::PySystemError::new_err(
459 "Failed to start container",
460 )),
461 }
462 }
463
464 fn stop(&self, wait: Option<&Bound<'_, PyDelta>>) -> PyResult<()> {
465 let wait: Option<std::time::Duration> = wait.map(|wait| {
466 wait.extract::<chrono::Duration>()
467 .unwrap()
468 .to_std()
469 .unwrap()
470 });
471
472 let rv = __container_stop(&self.0, wait);
473 match rv {
474 Ok(_rv) => Ok(()),
475 Err(_rv) => Err(exceptions::PySystemError::new_err(
476 "Failed to start container",
477 )),
478 }
479 }
480
481 fn restart(&self, wait: Option<&Bound<'_, PyDelta>>) -> PyResult<()> {
482 let wait: Option<std::time::Duration> = wait.map(|wait| {
483 wait.extract::<chrono::Duration>()
484 .unwrap()
485 .to_std()
486 .unwrap()
487 });
488
489 let rv = __container_restart(&self.0, wait);
490 match rv {
491 Ok(_rv) => Ok(()),
492 Err(_rv) => Err(exceptions::PySystemError::new_err(
493 "Failed to stop container",
494 )),
495 }
496 }
497
498 fn kill(&self, signal: Option<&str>) -> PyResult<()> {
499 let rv = __container_kill(&self.0, signal);
500 match rv {
501 Ok(_rv) => Ok(()),
502 Err(_rv) => Err(exceptions::PySystemError::new_err(
503 "Failed to kill container",
504 )),
505 }
506 }
507
508 fn rename(&self, name: &str) -> PyResult<()> {
509 let rv = __container_rename(&self.0, name);
510 match rv {
511 Ok(_rv) => Ok(()),
512 Err(_rv) => Err(exceptions::PySystemError::new_err(
513 "Failed to rename container",
514 )),
515 }
516 }
517
518 fn pause(&self) -> PyResult<()> {
519 let rv = __container_pause(&self.0);
520 match rv {
521 Ok(_rv) => Ok(()),
522 Err(_rv) => Err(exceptions::PySystemError::new_err(
523 "Failed to pause container",
524 )),
525 }
526 }
527
528 fn unpause(&self) -> PyResult<()> {
529 let rv = __container_unpause(&self.0);
530 match rv {
531 Ok(_rv) => Ok(()),
532 Err(_rv) => Err(exceptions::PySystemError::new_err(
533 "Failed to unpause container",
534 )),
535 }
536 }
537
538 fn wait(&self) -> Py<PyAny> {
539 let rv = __container_wait(&self.0).unwrap();
540 pythonize_this!(rv)
541 }
542
543 fn exec(
544 &self,
545 command: &Bound<'_, PyList>,
546 env: Option<&Bound<'_, PyList>>,
547 attach_stdout: Option<bool>,
548 attach_stderr: Option<bool>,
549 detach_keys: Option<&str>,
550 tty: Option<bool>,
551 privileged: Option<bool>,
552 user: Option<&str>,
553 working_dir: Option<&str>,
554 ) -> PyResult<()> {
555 let command_strings: Vec<String> = command.extract().unwrap();
556 let command: Vec<&str> = command_strings.iter().map(|s| s.as_str()).collect();
557 let mut exec_opts = ExecCreateOpts::builder().command(command);
558
559 if env.is_some() {
560 let env_strings: Vec<String> = env.unwrap().extract().unwrap();
561 let env: Vec<&str> = env_strings.iter().map(|s| s.as_str()).collect();
562 exec_opts = exec_opts.env(env);
563 }
564
565 bo_setter!(attach_stdout, exec_opts);
566 bo_setter!(attach_stderr, exec_opts);
567 bo_setter!(tty, exec_opts);
568 bo_setter!(detach_keys, exec_opts);
569 bo_setter!(privileged, exec_opts);
570 bo_setter!(user, exec_opts);
571 bo_setter!(working_dir, exec_opts);
572
573 let rv = __container_exec(&self.0, exec_opts.build());
574 let rv = rv.unwrap();
575 match rv {
576 Ok(_rv) => Ok(()),
577 Err(rv) => Err(exceptions::PySystemError::new_err(format!(
578 "Failed to exec container {rv}"
579 ))),
580 }
581 }
582
583 fn copy_from(&self, src: &str, dst: &str) -> PyResult<()> {
584 let rv = __container_copy_from(&self.0, src);
585
586 match rv {
587 Ok(rv) => {
588 let mut archive = Archive::new(&rv[..]);
589 let r = archive.unpack(dst);
590 match r {
591 Ok(_r) => Ok(()),
592 Err(r) => Err(exceptions::PySystemError::new_err(format!("{r}"))),
593 }
594 }
595 Err(rv) => Err(exceptions::PySystemError::new_err(format!("{rv}"))),
596 }
597 }
598
599 fn copy_file_into(&self, src: &str, dst: &str) -> PyResult<()> {
600 let mut file = File::open(src).unwrap();
601 let mut bytes = Vec::new();
602 file.read_to_end(&mut bytes)
603 .expect("Cannot read file on the localhost.");
604
605 let rv = __container_copy_file_into(&self.0, dst, &bytes);
606
607 match rv {
608 Ok(_rv) => Ok(()),
609 Err(rv) => Err(exceptions::PySystemError::new_err(format!("{rv}"))),
610 }
611 }
612
613 fn stat_file(&self, path: &str) -> Py<PyAny> {
614 let rv = __container_stat_file(&self.0, path).unwrap();
615 pythonize_this!(rv)
616 }
617
618 fn commit(&self) -> PyResult<()> {
619 Err(exceptions::PyNotImplementedError::new_err(
620 "This method is not available yet.",
621 ))
622 }
623
624 fn __repr__(&self) -> String {
625 let inspect = __container_inspect(&self.0);
626 format!(
627 "Container(id: {}, name: {}, status: {})",
628 inspect.id.unwrap(),
629 inspect.name.unwrap(),
630 inspect.state.unwrap().status.unwrap()
631 )
632 }
633
634 fn __string__(&self) -> String {
635 self.__repr__()
636 }
637}
638
639#[tokio::main]
640async fn __container_inspect(container: &Container) -> ContainerInspect200Response {
641 let c = container.inspect().await;
642 c.unwrap()
643}
644
645#[tokio::main]
646async fn __container_logs(container: &Container, log_opts: &LogsOpts) -> String {
647 let log_stream = container.logs(log_opts);
648
649 let log = log_stream
650 .map(|chunk| match chunk {
651 Ok(chunk) => chunk.to_vec(),
652 Err(e) => {
653 eprintln!("Error: {e}");
654 vec![]
655 }
656 })
657 .collect::<Vec<_>>()
658 .await
659 .into_iter()
660 .flatten()
661 .collect::<Vec<_>>();
662
663 format!("{}", String::from_utf8_lossy(&log))
664}
665
666#[tokio::main]
667async fn __container_delete(container: &Container) -> Result<String, docker_api::Error> {
668 container.delete().await
669}
670
671#[tokio::main]
672async fn __container_start(container: &Container) -> Result<(), docker_api::Error> {
673 container.start().await
674}
675
676#[tokio::main]
677async fn __container_stop(
678 container: &Container,
679 wait: Option<std::time::Duration>,
680) -> Result<(), docker_api::Error> {
681 container.stop(wait).await
682}
683
684#[tokio::main]
685async fn __container_restart(
686 container: &Container,
687 wait: Option<std::time::Duration>,
688) -> Result<(), docker_api::Error> {
689 container.restart(wait).await
690}
691
692#[tokio::main]
693async fn __container_kill(
694 container: &Container,
695 signal: Option<&str>,
696) -> Result<(), docker_api::Error> {
697 container.kill(signal).await
698}
699
700#[tokio::main]
701async fn __container_rename(container: &Container, name: &str) -> Result<(), docker_api::Error> {
702 container.rename(name).await
703}
704
705#[tokio::main]
706async fn __container_pause(container: &Container) -> Result<(), docker_api::Error> {
707 container.pause().await
708}
709
710#[tokio::main]
711async fn __container_unpause(container: &Container) -> Result<(), docker_api::Error> {
712 container.unpause().await
713}
714
715#[tokio::main]
716async fn __container_wait(
717 container: &Container,
718) -> Result<ContainerWaitResponse, docker_api::Error> {
719 container.wait().await
720}
721
722#[tokio::main]
723async fn __container_exec(
724 container: &Container,
725 exec_opts: ExecCreateOpts,
726) -> Option<Result<TtyChunk, docker_api::conn::Error>> {
727 container.exec(&exec_opts).next().await
728}
729
730#[tokio::main]
731async fn __container_copy_from(
732 container: &Container,
733 path: &str,
734) -> Result<Vec<u8>, docker_api::Error> {
735 container.copy_from(path).try_concat().await
736}
737
738#[tokio::main]
739async fn __container_copy_file_into(
740 container: &Container,
741 dst: &str,
742 bytes: &Vec<u8>,
743) -> Result<(), docker_api::Error> {
744 container.copy_file_into(dst, bytes).await
745}
746
747#[tokio::main]
748async fn __container_stat_file(
749 container: &Container,
750 src: &str,
751) -> Result<String, docker_api::Error> {
752 container.stat_file(src).await
753}