docker_pyo3/
service.rs

1use crate::Pyo3Docker;
2use chrono::{DateTime, Utc};
3use docker_api::opts::{LogsOpts, ServiceListOpts};
4use docker_api::{Service, Services};
5use futures_util::stream::StreamExt;
6use pyo3::exceptions;
7use pyo3::prelude::*;
8use pyo3::types::PyDateTime;
9use pythonize::pythonize;
10
11#[pymodule]
12pub fn service(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
13    m.add_class::<Pyo3Services>()?;
14    m.add_class::<Pyo3Service>()?;
15    Ok(())
16}
17
18/// Interface for managing Docker Swarm services collection.
19///
20/// Swarm mode must be enabled for these operations to work.
21#[derive(Debug)]
22#[pyclass(name = "Services")]
23pub struct Pyo3Services {
24    services: Services,
25}
26
27/// Represents an individual Docker Swarm service.
28///
29/// Swarm mode must be enabled for these operations to work.
30#[derive(Debug)]
31#[pyclass(name = "Service")]
32pub struct Pyo3Service {
33    service: Service,
34}
35
36#[pymethods]
37impl Pyo3Services {
38    #[new]
39    pub fn new(docker: Pyo3Docker) -> Self {
40        Pyo3Services {
41            services: Services::new(docker.0),
42        }
43    }
44
45    /// Get a specific service by ID or name.
46    ///
47    /// Args:
48    ///     id: Service ID or name
49    ///
50    /// Returns:
51    ///     Service: Service instance
52    pub fn get(&self, id: &str) -> Pyo3Service {
53        Pyo3Service {
54            service: self.services.get(id),
55        }
56    }
57
58    /// List all services in the swarm.
59    ///
60    /// Returns:
61    ///     list[dict]: List of service information dictionaries
62    ///
63    /// Raises:
64    ///     SystemError: If the operation fails (e.g., swarm not initialized)
65    pub fn list(&self) -> PyResult<Py<PyAny>> {
66        let rv = __services_list(&self.services, &Default::default());
67
68        match rv {
69            Ok(rv) => Ok(pythonize_this!(rv)),
70            Err(rv) => Err(py_sys_exception!(rv)),
71        }
72    }
73}
74
75#[tokio::main]
76async fn __services_list(
77    services: &Services,
78    opts: &ServiceListOpts,
79) -> Result<Vec<docker_api::models::Service>, docker_api::Error> {
80    services.list(opts).await
81}
82
83#[pymethods]
84impl Pyo3Service {
85    #[new]
86    pub fn new(docker: Pyo3Docker, id: &str) -> Self {
87        Pyo3Service {
88            service: Service::new(docker.0, id),
89        }
90    }
91
92    /// Get the service ID.
93    ///
94    /// Returns:
95    ///     str: Service ID
96    pub fn id(&self) -> String {
97        self.service.name().to_string()
98    }
99
100    /// Inspect the service to get detailed information.
101    ///
102    /// Returns:
103    ///     dict: Detailed service information including spec, endpoint, update status, etc.
104    ///
105    /// Raises:
106    ///     SystemError: If the operation fails
107    pub fn inspect(&self) -> PyResult<Py<PyAny>> {
108        let rv = __service_inspect(&self.service);
109
110        match rv {
111            Ok(rv) => Ok(pythonize_this!(rv)),
112            Err(rv) => Err(py_sys_exception!(rv)),
113        }
114    }
115
116    /// Delete the service from the swarm.
117    ///
118    /// Returns:
119    ///     None
120    ///
121    /// Raises:
122    ///     SystemError: If the service cannot be deleted
123    pub fn delete(&self) -> PyResult<()> {
124        let rv = __service_delete(&self.service);
125        match rv {
126            Ok(rv) => Ok(rv),
127            Err(rv) => Err(py_sys_exception!(rv)),
128        }
129    }
130
131    /// Get service logs.
132    ///
133    /// Args:
134    ///     stdout: Include stdout
135    ///     stderr: Include stderr
136    ///     timestamps: Include timestamps
137    ///     n_lines: Number of lines to return from the end of logs
138    ///     all: Return all logs
139    ///     since: Only return logs since this datetime
140    ///
141    /// Returns:
142    ///     str: Service logs
143    #[pyo3(signature = (stdout=None, stderr=None, timestamps=None, n_lines=None, all=None, since=None))]
144    pub fn logs(
145        &self,
146        stdout: Option<bool>,
147        stderr: Option<bool>,
148        timestamps: Option<bool>,
149        n_lines: Option<usize>,
150        all: Option<bool>,
151        since: Option<&Bound<'_, PyDateTime>>,
152    ) -> String {
153        let mut log_opts = LogsOpts::builder();
154
155        bo_setter!(stdout, log_opts);
156        bo_setter!(stderr, log_opts);
157        bo_setter!(timestamps, log_opts);
158        bo_setter!(n_lines, log_opts);
159
160        if all.is_some() && all.unwrap() {
161            log_opts = log_opts.all();
162        }
163
164        if since.is_some() {
165            let rs_since: DateTime<Utc> = since.unwrap().extract().unwrap();
166            log_opts = log_opts.since(&rs_since);
167        }
168
169        __service_logs(&self.service, &log_opts.build())
170    }
171}
172
173#[tokio::main]
174async fn __service_inspect(
175    service: &Service,
176) -> Result<docker_api::models::Service, docker_api::Error> {
177    service.inspect().await
178}
179
180#[tokio::main]
181async fn __service_delete(service: &Service) -> Result<(), docker_api::Error> {
182    service.delete().await
183}
184
185#[tokio::main]
186async fn __service_logs(service: &Service, log_opts: &LogsOpts) -> String {
187    let log_stream = service.logs(log_opts);
188
189    let log = log_stream
190        .map(|chunk| match chunk {
191            Ok(chunk) => chunk.to_vec(),
192            Err(e) => {
193                eprintln!("Error: {e}");
194                vec![]
195            }
196        })
197        .collect::<Vec<_>>()
198        .await
199        .into_iter()
200        .flatten()
201        .collect::<Vec<_>>();
202
203    format!("{}", String::from_utf8_lossy(&log))
204}