bollard_next/
service.rs

1//! Service API: manage and inspect docker services within a swarm
2
3use crate::docker::BodyType;
4pub use crate::models::*;
5
6use super::Docker;
7use crate::auth::{DockerCredentials, DockerCredentialsHeader};
8use crate::errors::Error;
9use bytes::Bytes;
10use http::header::CONTENT_TYPE;
11use http::request::Builder;
12use http::Method;
13use http_body_util::Full;
14use serde_derive::Serialize;
15
16use std::{collections::HashMap, hash::Hash};
17
18/// Parameters used in the [List Service API](super::Docker::list_services())
19///
20/// ## Examples
21///
22/// ```rust
23/// # use std::collections::HashMap;
24/// # use std::default::Default;
25/// use bollard_next::service::ListServicesOptions;
26///
27/// let mut filters = HashMap::new();
28/// filters.insert("mode", vec!["global"]);
29///
30/// ListServicesOptions{
31///     filters,
32///     ..Default::default()
33/// };
34/// ```
35///
36/// ```rust
37/// # use bollard_next::service::ListServicesOptions;
38/// # use std::default::Default;
39///
40/// let options: ListServicesOptions<&str> = Default::default();
41/// ```
42#[derive(Debug, Clone, Default, PartialEq, Serialize)]
43pub struct ListServicesOptions<T>
44where
45    T: Into<String> + Eq + Hash + serde::ser::Serialize,
46{
47    /// Filters to process on the service list, encoded as JSON. Available filters:
48    ///  - `id`=`<ID>` a services's ID
49    ///  - `label`=`key` or `label`=`"key=value"` of a service label
50    ///  - `mode`=`["replicated"|"global"] a service's scheduling mode
51    ///  - `name`=`<name>` a services's name
52    #[serde(serialize_with = "crate::docker::serialize_as_json")]
53    pub filters: HashMap<T, Vec<T>>,
54
55    /// Include service status, with count of running and desired tasks.
56    pub status: bool,
57}
58
59/// Parameters used in the [Inspect Service API](Docker::inspect_service())
60///
61/// ## Examples
62///
63/// ```rust
64/// use bollard_next::service::InspectServiceOptions;
65///
66/// InspectServiceOptions{
67///     insert_defaults: true,
68/// };
69/// ```
70#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize)]
71#[serde(rename_all = "camelCase")]
72pub struct InspectServiceOptions {
73    /// Fill empty fields with default values.
74    pub insert_defaults: bool,
75}
76
77/// Parameters used in the [Update Service API](Docker::update_service())
78///
79/// ## Examples
80///
81/// ```rust
82/// use bollard_next::service::UpdateServiceOptions;
83///
84/// UpdateServiceOptions{
85///     version: 1234,
86///     ..Default::default()
87/// };
88/// ```
89#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize)]
90#[serde(rename_all = "camelCase")]
91pub struct UpdateServiceOptions {
92    /// The version number of the service object being updated. This is required to avoid conflicting writes. This version number should be the value as currently set on the service before the update.
93    pub version: u64,
94    /// If the X-Registry-Auth header is not specified, this parameter indicates whether to use registry authorization credentials from the current or the previous spec.
95    #[serde(serialize_with = "serialize_registry_auth_from")]
96    pub registry_auth_from: bool,
97    /// Set to this parameter to true to cause a server-side rollback to the previous service spec. The supplied spec will be ignored in this case.
98    #[serde(serialize_with = "serialize_rollback")]
99    pub rollback: bool,
100}
101
102#[allow(clippy::trivially_copy_pass_by_ref)]
103pub(crate) fn serialize_registry_auth_from<S>(
104    registry_auth_from: &bool,
105    s: S,
106) -> Result<S::Ok, S::Error>
107where
108    S: serde::Serializer,
109{
110    s.serialize_str(if *registry_auth_from {
111        "previous-spec"
112    } else {
113        "spec"
114    })
115}
116
117#[allow(clippy::trivially_copy_pass_by_ref)]
118fn serialize_rollback<S>(rollback: &bool, s: S) -> Result<S::Ok, S::Error>
119where
120    S: serde::Serializer,
121{
122    s.serialize_str(if *rollback { "previous" } else { "" })
123}
124
125impl Docker {
126    /// ---
127    ///
128    /// # List Services
129    ///
130    /// Returns a list of services.
131    ///
132    /// # Arguments
133    ///
134    ///  - Optional [ListServicesOptions](ListServicesOptions) struct.
135    ///
136    /// # Returns
137    ///
138    ///  - Vector of [Services](Service), wrapped in a Future.
139    ///
140    /// # Examples
141    ///
142    /// ```rust
143    /// # use bollard_next::Docker;
144    /// # let docker = Docker::connect_with_http_defaults().unwrap();
145    /// use bollard_next::service::ListServicesOptions;
146    ///
147    /// use std::collections::HashMap;
148    /// use std::default::Default;
149    ///
150    /// let mut filters = HashMap::new();
151    /// filters.insert("mode", vec!["global"]);
152    ///
153    /// let options = Some(ListServicesOptions{
154    ///     filters,
155    ///     ..Default::default()
156    /// });
157    ///
158    /// docker.list_services(options);
159    /// ```
160    pub async fn list_services<T>(
161        &self,
162        options: Option<ListServicesOptions<T>>,
163    ) -> Result<Vec<Service>, Error>
164    where
165        T: Into<String> + Eq + Hash + serde::ser::Serialize,
166    {
167        let url = "/services";
168
169        let req = self.build_request(
170            url,
171            Builder::new().method(hyper::Method::GET),
172            options,
173            Ok(BodyType::Left(Full::new(Bytes::new()))),
174        );
175
176        self.process_into_value(req).await
177    }
178
179    /// ---
180    ///
181    /// # Create Service
182    ///
183    /// Dispatch a new service on the docker swarm
184    ///
185    /// # Arguments
186    ///
187    ///  - [ServiceSpec](ServiceSpec) struct.
188    ///  - Optional [Docker Credentials](DockerCredentials) struct.
189    ///
190    /// # Returns
191    ///
192    ///  - A [Service Create Response](ServiceCreateResponse) struct,
193    ///    wrapped in a Future.
194    ///
195    /// # Examples
196    ///
197    /// ```rust
198    /// # use bollard_next::Docker;
199    /// # use std::collections::HashMap;
200    /// # use std::default::Default;
201    /// # let docker = Docker::connect_with_http_defaults().unwrap();
202    /// use bollard_next::service::{
203    ///     ServiceSpec,
204    ///     ServiceSpecMode,
205    ///     ServiceSpecModeReplicated,
206    ///     TaskSpec,
207    ///     TaskSpecContainerSpec
208    /// };
209    ///
210    /// let service = ServiceSpec {
211    ///     name: Some(String::from("my-service")),
212    ///     mode: Some(ServiceSpecMode {
213    ///         replicated: Some(ServiceSpecModeReplicated {
214    ///             replicas: Some(2)
215    ///         }),
216    ///         ..Default::default()
217    ///     }),
218    ///     task_template: Some(TaskSpec {
219    ///         container_spec: Some(TaskSpecContainerSpec {
220    ///             image: Some(String::from("hello-world")),
221    ///             ..Default::default()
222    ///         }),
223    ///         ..Default::default()
224    ///     }),
225    ///     ..Default::default()
226    /// };
227    /// let credentials = None;
228    ///
229    /// docker.create_service(service, credentials);
230    /// ```
231    pub async fn create_service(
232        &self,
233        service_spec: ServiceSpec,
234        credentials: Option<DockerCredentials>,
235    ) -> Result<ServiceCreateResponse, Error> {
236        let url = "/services/create";
237
238        let req = self.build_request_with_registry_auth(
239            url,
240            Builder::new()
241                .method(Method::POST)
242                .header(CONTENT_TYPE, "application/json"),
243            None::<String>,
244            Docker::serialize_payload(Some(service_spec)),
245            DockerCredentialsHeader::Auth(credentials),
246        );
247
248        self.process_into_value(req).await
249    }
250
251    /// ---
252    ///
253    /// # Inspect Service
254    ///
255    /// Inspect a service.
256    ///
257    /// # Arguments
258    ///
259    ///  - Service name or id as a string slice.
260    ///  - Optional [Inspect Service Options](InspectServiceOptions) struct.
261    ///
262    /// # Returns
263    ///
264    ///  - [Service](Service), wrapped in a Future.
265    ///
266    /// # Examples
267    ///
268    /// ```rust
269    /// # use bollard_next::Docker;
270    /// # let docker = Docker::connect_with_http_defaults().unwrap();
271    /// use bollard_next::service::InspectServiceOptions;
272    ///
273    /// let options = Some(InspectServiceOptions{
274    ///     insert_defaults: true,
275    /// });
276    ///
277    /// docker.inspect_service("my-service", options);
278    /// ```
279    pub async fn inspect_service(
280        &self,
281        service_name: &str,
282        options: Option<InspectServiceOptions>,
283    ) -> Result<Service, Error> {
284        let url = format!("/services/{service_name}");
285
286        let req = self.build_request(
287            &url,
288            Builder::new().method(hyper::Method::GET),
289            options,
290            Ok(BodyType::Left(Full::new(Bytes::new()))),
291        );
292
293        self.process_into_value(req).await
294    }
295
296    /// ---
297    ///
298    /// # Delete Service
299    ///
300    /// Delete a service.
301    ///
302    /// # Arguments
303    ///
304    /// - Service name or id as a string slice.
305    ///
306    /// # Returns
307    ///
308    ///  - unit type `()`, wrapped in a Future.
309    ///
310    /// # Examples
311    ///
312    /// ```rust
313    /// # use bollard_next::Docker;
314    /// # let docker = Docker::connect_with_http_defaults().unwrap();
315    ///
316    /// docker.delete_service("my-service");
317    /// ```
318    pub async fn delete_service(&self, service_name: &str) -> Result<(), Error> {
319        let url = format!("/services/{service_name}");
320
321        let req = self.build_request(
322            &url,
323            Builder::new().method(hyper::Method::DELETE),
324            None::<String>,
325            Ok(BodyType::Left(Full::new(Bytes::new()))),
326        );
327
328        self.process_into_unit(req).await
329    }
330
331    /// ---
332    ///
333    /// # Update Service
334    ///
335    /// Update an existing service
336    ///
337    /// # Arguments
338    ///
339    ///  - Service name or id as a string slice.
340    ///  - [ServiceSpec](ServiceSpec) struct.
341    ///  - [UpdateServiceOptions](UpdateServiceOptions) struct.
342    ///  - Optional [Docker Credentials](DockerCredentials) struct.
343    ///
344    /// # Returns
345    ///
346    ///  - A [Service Update Response](ServiceUpdateResponse) struct,
347    ///    wrapped in a Future.
348    ///
349    /// # Examples
350    ///
351    /// ```rust
352    /// # use bollard_next::Docker;
353    /// # let docker = Docker::connect_with_http_defaults().unwrap();
354    /// use bollard_next::service::{
355    ///     InspectServiceOptions,
356    ///     ServiceSpec,
357    ///     ServiceSpecMode,
358    ///     ServiceSpecModeReplicated,
359    ///     TaskSpec,
360    ///     TaskSpecContainerSpec,
361    ///     UpdateServiceOptions,
362    /// };
363    ///
364    /// use std::collections::HashMap;
365    /// use std::default::Default;
366    ///
367    /// let result = async move {
368    ///     let service_name = "my-service";
369    ///     let current_version = docker.inspect_service(
370    ///         service_name,
371    ///         None::<InspectServiceOptions>
372    ///     ).await?.version.unwrap().index.unwrap();
373    ///     let service = ServiceSpec {
374    ///         mode: Some(ServiceSpecMode {
375    ///             replicated: Some(ServiceSpecModeReplicated {
376    ///                 replicas: Some(0)
377    ///             }),
378    ///             ..Default::default()
379    ///         }),
380    ///         ..Default::default()
381    ///     };
382    ///     let options = UpdateServiceOptions {
383    ///         version: current_version,
384    ///         ..Default::default()
385    ///     };
386    ///     let credentials = None;
387    ///
388    ///     docker.update_service("my-service", service, options, credentials).await
389    /// };
390    /// ```
391    pub async fn update_service(
392        &self,
393        service_name: &str,
394        service_spec: ServiceSpec,
395        options: UpdateServiceOptions,
396        credentials: Option<DockerCredentials>,
397    ) -> Result<ServiceUpdateResponse, Error> {
398        let url = format!("/services/{service_name}/update");
399
400        let req = self.build_request_with_registry_auth(
401            &url,
402            Builder::new()
403                .method(Method::POST)
404                .header(CONTENT_TYPE, "application/json"),
405            Some(options),
406            Docker::serialize_payload(Some(service_spec)),
407            DockerCredentialsHeader::Auth(credentials),
408        );
409
410        self.process_into_value(req).await
411    }
412}