docker_sdk/
service.rs

1//! Manage and inspect services within a swarm.
2//!
3//! API Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Service>
4
5use std::{collections::HashMap, iter};
6
7use futures_util::stream::Stream;
8use hyper::Body;
9use serde::{Deserialize, Serialize};
10use serde_json::{json, Value};
11use url::form_urlencoded;
12
13use crate::{
14    container::LogsOptions,
15    docker::Docker,
16    errors::{Error, Result},
17    image::RegistryAuth,
18    tty,
19};
20
21#[cfg(feature = "chrono")]
22use chrono::{DateTime, Utc};
23
24/// Interface for docker services
25///
26/// API Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Service>
27pub struct Services<'docker> {
28    docker: &'docker Docker,
29}
30
31impl<'docker> Services<'docker> {
32    /// Exports an interface for interacting with docker services
33    pub fn new(docker: &'docker Docker) -> Self {
34        Services { docker }
35    }
36
37    /// Lists the docker services on the current docker host
38    ///
39    /// API Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ServiceList>
40    pub async fn list(
41        &self,
42        opts: &ServiceListOptions,
43    ) -> Result<Vec<ServiceInfo>> {
44        let mut path = vec!["/services".to_owned()];
45        if let Some(query) = opts.serialize() {
46            path.push(query);
47        }
48        self.docker
49            .get_json::<Vec<ServiceInfo>>(&path.join("?"))
50            .await
51    }
52
53    /// Returns a reference to a set of operations available for a named service
54    pub fn get(
55        &self,
56        name: &str,
57    ) -> Service<'docker> {
58        Service::new(self.docker, name)
59    }
60}
61
62/// Interface for accessing and manipulating a named docker volume
63///
64/// API Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Service>
65pub struct Service<'docker> {
66    docker: &'docker Docker,
67    name: String,
68}
69
70impl<'docker> Service<'docker> {
71    /// Exports an interface for operations that may be performed against a named service
72    pub fn new<S>(
73        docker: &'docker Docker,
74        name: S,
75    ) -> Self
76    where
77        S: Into<String>,
78    {
79        Service {
80            docker,
81            name: name.into(),
82        }
83    }
84
85    /// Creates a new service from ServiceOptions
86    ///
87    /// API Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ServiceCreate>
88    pub async fn create(
89        &self,
90        opts: &ServiceOptions,
91    ) -> Result<ServiceCreateInfo> {
92        let body: Body = opts.serialize()?.into();
93        let path = vec!["/service/create".to_owned()];
94
95        let headers = opts
96            .auth_header()
97            .map(|a| iter::once(("X-Registry-Auth", a)));
98
99        self.docker
100            .post_json_headers(
101                &path.join("?"),
102                Some((body, mime::APPLICATION_JSON)),
103                headers,
104            )
105            .await
106    }
107
108    /// Inspects a named service's details
109    ///
110    /// API Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ServiceInspect>
111    pub async fn inspect(&self) -> Result<ServiceDetails> {
112        self.docker
113            .get_json(&format!("/services/{}", self.name)[..])
114            .await
115    }
116
117    /// Deletes a service
118    ///
119    /// API Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ServiceDelete>
120    pub async fn delete(&self) -> Result<()> {
121        self.docker
122            .delete_json(&format!("/services/{}", self.name)[..])
123            .await
124    }
125
126    /// Returns a stream of logs from a service
127    ///
128    /// API Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ServiceLogs>
129    pub fn logs(
130        &self,
131        opts: &LogsOptions,
132    ) -> impl Stream<Item = Result<tty::TtyChunk>> + Unpin + 'docker {
133        let mut path = vec![format!("/services/{}/logs", self.name)];
134        if let Some(query) = opts.serialize() {
135            path.push(query)
136        }
137
138        let stream = Box::pin(self.docker.stream_get(path.join("?")));
139
140        Box::pin(tty::decode(stream))
141    }
142}
143
144/// Options for filtering services list results
145#[derive(Default, Debug)]
146pub struct ServiceListOptions {
147    params: HashMap<&'static str, String>,
148}
149
150impl ServiceListOptions {
151    /// return a new instance of a builder for options
152    pub fn builder() -> ServiceListOptionsBuilder {
153        ServiceListOptionsBuilder::default()
154    }
155
156    /// serialize options as a string. returns None if no options are defined
157    pub fn serialize(&self) -> Option<String> {
158        if self.params.is_empty() {
159            None
160        } else {
161            Some(
162                form_urlencoded::Serializer::new(String::new())
163                    .extend_pairs(&self.params)
164                    .finish(),
165            )
166        }
167    }
168}
169
170/// Filter options for services listings
171pub enum ServiceFilter {
172    Id(String),
173    Label(String),
174    ReplicatedMode,
175    GlobalMode,
176    Name(String),
177}
178
179/// Builder interface for `ServicesListOptions`
180#[derive(Default)]
181pub struct ServiceListOptionsBuilder {
182    params: HashMap<&'static str, String>,
183}
184
185impl ServiceListOptionsBuilder {
186    pub fn filter(
187        &mut self,
188        filters: Vec<ServiceFilter>,
189    ) -> &mut Self {
190        let mut param = HashMap::new();
191        for f in filters {
192            match f {
193                ServiceFilter::Id(i) => param.insert("id", vec![i]),
194                ServiceFilter::Label(l) => param.insert("label", vec![l]),
195                ServiceFilter::ReplicatedMode => {
196                    param.insert("mode", vec!["replicated".to_string()])
197                }
198                ServiceFilter::GlobalMode => param.insert("mode", vec!["global".to_string()]),
199                ServiceFilter::Name(n) => param.insert("name", vec![n.to_string()]),
200            };
201        }
202        // structure is a a json encoded object mapping string keys to a list
203        // of string values
204        self.params
205            .insert("filters", serde_json::to_string(&param).unwrap());
206        self
207    }
208
209    pub fn enable_status(&mut self) -> &mut Self {
210        self.params.insert("status", "true".to_owned());
211        self
212    }
213
214    pub fn build(&self) -> ServiceListOptions {
215        ServiceListOptions {
216            params: self.params.clone(),
217        }
218    }
219}
220
221#[derive(Default, Debug)]
222pub struct ServiceOptions {
223    auth: Option<RegistryAuth>,
224    params: HashMap<&'static str, Value>,
225}
226
227impl ServiceOptions {
228    /// return a new instance of a builder for options
229    pub fn builder() -> ServiceOptionsBuilder {
230        ServiceOptionsBuilder::default()
231    }
232
233    /// serialize options as a string. returns None if no options are defined
234    pub fn serialize(&self) -> Result<String> {
235        serde_json::to_string(&self.params).map_err(Error::from)
236    }
237
238    pub(crate) fn auth_header(&self) -> Option<String> {
239        self.auth.clone().map(|a| a.serialize())
240    }
241}
242
243#[derive(Default)]
244pub struct ServiceOptionsBuilder {
245    auth: Option<RegistryAuth>,
246    params: HashMap<&'static str, Result<Value>>,
247}
248
249impl ServiceOptionsBuilder {
250    pub fn name<S>(
251        &mut self,
252        name: S,
253    ) -> &mut Self
254    where
255        S: AsRef<str>,
256    {
257        self.params.insert("Name", Ok(json!(name.as_ref())));
258        self
259    }
260
261    pub fn labels<I>(
262        &mut self,
263        labels: I,
264    ) -> &mut Self
265    where
266        I: IntoIterator<Item = (String, String)>,
267    {
268        self.params.insert(
269            "Labels",
270            Ok(json!(labels
271                .into_iter()
272                .collect::<HashMap<String, String>>())),
273        );
274        self
275    }
276
277    pub fn task_template(
278        &mut self,
279        spec: &TaskSpec,
280    ) -> &mut Self {
281        self.params.insert("TaskTemplate", to_json_value(spec));
282        self
283    }
284
285    pub fn mode(
286        &mut self,
287        mode: &Mode,
288    ) -> &mut Self {
289        self.params.insert("Mode", to_json_value(mode));
290        self
291    }
292
293    pub fn update_config(
294        &mut self,
295        conf: &UpdateConfig,
296    ) -> &mut Self {
297        self.params.insert("UpdateConfig", to_json_value(conf));
298        self
299    }
300
301    pub fn rollback_config(
302        &mut self,
303        conf: &RollbackConfig,
304    ) -> &mut Self {
305        self.params.insert("RollbackConfig", to_json_value(conf));
306        self
307    }
308
309    pub fn networks<I>(
310        &mut self,
311        networks: I,
312    ) -> &mut Self
313    where
314        I: IntoIterator<Item = NetworkAttachmentConfig>,
315    {
316        self.params.insert(
317            "Networks",
318            to_json_value(
319                networks
320                    .into_iter()
321                    .collect::<Vec<NetworkAttachmentConfig>>(),
322            ),
323        );
324        self
325    }
326
327    pub fn endpoint_spec(
328        &mut self,
329        spec: &EndpointSpec,
330    ) -> &mut Self {
331        self.params.insert("EndpointSpec", to_json_value(spec));
332        self
333    }
334
335    pub fn auth(
336        &mut self,
337        auth: RegistryAuth,
338    ) -> &mut Self {
339        self.auth = Some(auth);
340        self
341    }
342
343    pub fn build(&mut self) -> Result<ServiceOptions> {
344        let params = std::mem::take(&mut self.params);
345        let mut new_params = HashMap::new();
346        for (k, v) in params.into_iter() {
347            new_params.insert(k, v?);
348        }
349        Ok(ServiceOptions {
350            auth: self.auth.take(),
351            params: new_params,
352        })
353    }
354}
355
356fn to_json_value<T>(value: T) -> Result<Value>
357where
358    T: Serialize,
359{
360    Ok(serde_json::to_value(value)?)
361}
362
363pub type ServicesInfo = Vec<ServiceInfo>;
364
365#[derive(Clone, Debug, Serialize, Deserialize)]
366#[serde(rename_all = "PascalCase")]
367pub struct ServiceInfo {
368    #[serde(rename = "ID")]
369    pub id: String,
370    pub version: ObjectVersion,
371    #[cfg(feature = "chrono")]
372    pub created_at: DateTime<Utc>,
373    #[cfg(not(feature = "chrono"))]
374    pub created_at: String,
375    #[cfg(feature = "chrono")]
376    pub updated_at: DateTime<Utc>,
377    #[cfg(not(feature = "chrono"))]
378    pub updated_at: String,
379    pub endpoint: Endpoint,
380    pub update_status: Option<UpdateStatus>,
381    pub service_status: Option<ServiceStatus>,
382    pub job_status: Option<JobStatus>,
383}
384
385#[derive(Clone, Debug, Serialize, Deserialize)]
386#[serde(rename_all = "PascalCase")]
387pub struct ObjectVersion {
388    pub index: u64,
389}
390
391#[derive(Clone, Debug, Serialize, Deserialize)]
392#[serde(rename_all = "PascalCase")]
393pub struct Endpoint {
394    pub spec: EndpointSpec,
395    pub ports: Option<Vec<EndpointPortConfig>>,
396    #[serde(rename = "VirtualIPs")]
397    pub virtual_ips: Option<serde_json::Value>,
398}
399
400#[derive(Clone, Debug, Serialize, Deserialize)]
401#[serde(rename_all = "PascalCase")]
402pub struct EndpointSpec {
403    pub mode: Option<String>,
404    pub ports: Option<Vec<EndpointPortConfig>>,
405}
406
407#[derive(Clone, Debug, Serialize, Deserialize)]
408#[serde(rename_all = "PascalCase")]
409pub struct EndpointPortConfig {
410    pub name: Option<String>,
411    pub protocol: String,
412    pub publish_mode: String,
413    pub published_port: Option<u64>,
414    pub target_port: u64,
415}
416
417#[derive(Clone, Debug, Serialize, Deserialize)]
418#[serde(rename_all = "PascalCase")]
419pub struct UpdateStatus {
420    pub state: String,
421    #[cfg(feature = "chrono")]
422    pub started_at: DateTime<Utc>,
423    #[cfg(not(feature = "chrono"))]
424    pub started_at: String,
425    #[cfg(feature = "chrono")]
426    pub completed_at: DateTime<Utc>,
427    #[cfg(not(feature = "chrono"))]
428    pub completed_at: String,
429    pub message: String,
430}
431
432#[derive(Clone, Debug, Serialize, Deserialize)]
433#[serde(rename_all = "PascalCase")]
434pub struct ServiceStatus {
435    pub running_tasks: u64,
436    pub desired_tasks: u64,
437    pub completed_tasks: u64,
438}
439
440#[derive(Clone, Debug, Serialize, Deserialize)]
441#[serde(rename_all = "PascalCase")]
442pub struct JobStatus {
443    pub job_iteration: ObjectVersion,
444    #[cfg(feature = "chrono")]
445    pub last_execution: DateTime<Utc>,
446    #[cfg(not(feature = "chrono"))]
447    pub last_execution: String,
448}
449
450#[derive(Clone, Debug, Serialize, Deserialize)]
451#[serde(rename_all = "PascalCase")]
452pub struct ServiceDetails {
453    #[serde(rename = "ID")]
454    pub id: String,
455    pub version: ObjectVersion,
456    #[cfg(feature = "chrono")]
457    pub created_at: DateTime<Utc>,
458    #[cfg(not(feature = "chrono"))]
459    pub created_at: String,
460    #[cfg(feature = "chrono")]
461    pub updated_at: DateTime<Utc>,
462    #[cfg(not(feature = "chrono"))]
463    pub updated_at: String,
464    pub spec: ServiceSpec,
465    pub endpoint: Endpoint,
466    pub update_status: Option<UpdateStatus>,
467    pub service_status: Option<ServiceStatus>,
468    pub job_status: Option<JobStatus>,
469}
470
471#[derive(Clone, Debug, Serialize, Deserialize)]
472#[serde(rename_all = "PascalCase")]
473pub struct ServiceSpec {
474    pub name: String,
475    pub labels: Option<serde_json::Value>,
476    pub task_template: TaskSpec,
477    pub mode: Mode,
478    pub update_config: Option<UpdateConfig>,
479    pub rollback_config: Option<RollbackConfig>,
480    pub networks: Option<Vec<NetworkAttachmentConfig>>,
481    pub endpoint_spec: EndpointSpec,
482}
483
484#[derive(Clone, Debug, Serialize, Deserialize)]
485#[serde(rename_all = "PascalCase")]
486// #TODO: Add missing fields...
487pub struct TaskSpec {}
488
489#[derive(Clone, Debug, Serialize, Deserialize)]
490#[serde(rename_all = "PascalCase")]
491pub struct Mode {
492    pub replicated: Option<Replicated>,
493    pub global: Option<serde_json::Value>,
494    pub replicated_job: Option<ReplicatedJob>,
495    pub global_job: Option<serde_json::Value>,
496}
497
498#[derive(Clone, Debug, Serialize, Deserialize)]
499#[serde(rename_all = "PascalCase")]
500pub struct Replicated {
501    pub replicas: u64,
502}
503
504#[derive(Clone, Debug, Serialize, Deserialize)]
505#[serde(rename_all = "PascalCase")]
506pub struct ReplicatedJob {
507    pub max_concurrent: u64,
508    pub total_completions: u64,
509}
510
511#[derive(Clone, Debug, Serialize, Deserialize)]
512#[serde(rename_all = "PascalCase")]
513pub struct UpdateConfig {
514    pub parallelism: u64,
515    pub delay: u64,
516    pub failure_action: String,
517    pub monitor: u64,
518    pub max_failure_ratio: usize,
519    pub order: String,
520}
521
522pub type RollbackConfig = UpdateConfig;
523
524#[derive(Clone, Debug, Serialize, Deserialize)]
525#[serde(rename_all = "PascalCase")]
526pub struct NetworkAttachmentConfig {
527    pub target: String,
528    pub aliases: Vec<String>,
529    pub driver_opts: Option<serde_json::Value>,
530}
531
532#[derive(Clone, Debug, Serialize, Deserialize)]
533pub struct ServiceCreateInfo {
534    #[serde(rename = "ID")]
535    pub id: String,
536    #[serde(rename = "Warning")]
537    pub warning: Option<String>,
538}