//! Manage and inspect services within a swarm. //! //! API Reference: use std::{collections::HashMap, iter}; use futures_util::stream::Stream; use hyper::Body; use serde::Serialize; use serde_json::{json, Value}; use url::form_urlencoded; use crate::{ errors::{Error, Result}, image::RegistryAuth, rep::{ EndpointSpec, Mode, NetworkAttachmentConfig, RollbackConfig, Service as ServiceInfo, ServiceCreateInfo, ServiceDetails, TaskSpec, UpdateConfig, }, tty, Docker, LogsOptions, }; /// Interface for docker services pub struct Services<'docker> { docker: &'docker Docker, } impl<'docker> Services<'docker> { /// Exports an interface for interacting with docker services pub fn new(docker: &'docker Docker) -> Self { Services { docker } } /// Lists the docker services on the current docker host pub async fn list( &self, opts: &ServiceListOptions, ) -> Result> { let mut path = vec!["/services".to_owned()]; if let Some(query) = opts.serialize() { path.push(query); } self.docker .get_json::>(&path.join("?")) .await } /// Returns a reference to a set of operations available for a named service pub fn get( &self, name: &str, ) -> Service<'docker> { Service::new(self.docker, name) } } /// Interface for accessing and manipulating a named docker volume pub struct Service<'docker> { docker: &'docker Docker, name: String, } impl<'docker> Service<'docker> { /// Exports an interface for operations that may be performed against a named service pub fn new( docker: &'docker Docker, name: S, ) -> Self where S: Into, { Service { docker, name: name.into(), } } /// Creates a new service from ServiceOptions pub async fn create( &self, opts: &ServiceOptions, ) -> Result { let body: Body = opts.serialize()?.into(); let path = vec!["/service/create".to_owned()]; let headers = opts .auth_header() .map(|a| iter::once(("X-Registry-Auth", a))); self.docker .post_json_headers( &path.join("?"), Some((body, mime::APPLICATION_JSON)), headers, ) .await } /// Inspects a named service's details pub async fn inspect(&self) -> Result { self.docker .get_json(&format!("/services/{}", self.name)[..]) .await } /// Deletes a service pub async fn delete(&self) -> Result<()> { self.docker .delete_json(&format!("/services/{}", self.name)[..]) .await } /// Returns a stream of logs from a service pub fn logs( &self, opts: &LogsOptions, ) -> impl Stream> + Unpin + 'docker { let mut path = vec![format!("/services/{}/logs", self.name)]; if let Some(query) = opts.serialize() { path.push(query) } let stream = Box::pin(self.docker.stream_get(path.join("?"))); Box::pin(tty::decode(stream)) } } /// Options for filtering services list results #[derive(Default, Debug)] pub struct ServiceListOptions { params: HashMap<&'static str, String>, } impl ServiceListOptions { /// return a new instance of a builder for options pub fn builder() -> ServiceListOptionsBuilder { ServiceListOptionsBuilder::default() } /// serialize options as a string. returns None if no options are defined pub fn serialize(&self) -> Option { if self.params.is_empty() { None } else { Some( form_urlencoded::Serializer::new(String::new()) .extend_pairs(&self.params) .finish(), ) } } } /// Filter options for services listings pub enum ServiceFilter { Id(String), Label(String), ReplicatedMode, GlobalMode, Name(String), } /// Builder interface for `ServicesListOptions` #[derive(Default)] pub struct ServiceListOptionsBuilder { params: HashMap<&'static str, String>, } impl ServiceListOptionsBuilder { pub fn filter( &mut self, filters: Vec, ) -> &mut Self { let mut param = HashMap::new(); for f in filters { match f { ServiceFilter::Id(i) => param.insert("id", vec![i]), ServiceFilter::Label(l) => param.insert("label", vec![l]), ServiceFilter::ReplicatedMode => { param.insert("mode", vec!["replicated".to_string()]) } ServiceFilter::GlobalMode => param.insert("mode", vec!["global".to_string()]), ServiceFilter::Name(n) => param.insert("name", vec![n.to_string()]), }; } // structure is a a json encoded object mapping string keys to a list // of string values self.params .insert("filters", serde_json::to_string(¶m).unwrap()); self } pub fn enable_status(&mut self) -> &mut Self { self.params.insert("status", "true".to_owned()); self } pub fn build(&self) -> ServiceListOptions { ServiceListOptions { params: self.params.clone(), } } } #[derive(Default, Debug)] pub struct ServiceOptions { auth: Option, params: HashMap<&'static str, Value>, } impl ServiceOptions { /// return a new instance of a builder for options pub fn builder() -> ServiceOptionsBuilder { ServiceOptionsBuilder::default() } /// serialize options as a string. returns None if no options are defined pub fn serialize(&self) -> Result { serde_json::to_string(&self.params).map_err(Error::from) } pub(crate) fn auth_header(&self) -> Option { self.auth.clone().map(|a| a.serialize()) } } #[derive(Default)] pub struct ServiceOptionsBuilder { auth: Option, params: HashMap<&'static str, Result>, } impl ServiceOptionsBuilder { pub fn name( &mut self, name: S, ) -> &mut Self where S: AsRef, { self.params.insert("Name", Ok(json!(name.as_ref()))); self } pub fn labels( &mut self, labels: I, ) -> &mut Self where I: IntoIterator, { self.params.insert( "Labels", Ok(json!(labels .into_iter() .collect::>())), ); self } pub fn task_template( &mut self, spec: &TaskSpec, ) -> &mut Self { self.params.insert("TaskTemplate", to_json_value(spec)); self } pub fn mode( &mut self, mode: &Mode, ) -> &mut Self { self.params.insert("Mode", to_json_value(mode)); self } pub fn update_config( &mut self, conf: &UpdateConfig, ) -> &mut Self { self.params.insert("UpdateConfig", to_json_value(conf)); self } pub fn rollback_config( &mut self, conf: &RollbackConfig, ) -> &mut Self { self.params.insert("RollbackConfig", to_json_value(conf)); self } pub fn networks( &mut self, networks: I, ) -> &mut Self where I: IntoIterator, { self.params.insert( "Networks", to_json_value( networks .into_iter() .collect::>(), ), ); self } pub fn endpoint_spec( &mut self, spec: &EndpointSpec, ) -> &mut Self { self.params.insert("EndpointSpec", to_json_value(spec)); self } pub fn auth( &mut self, auth: RegistryAuth, ) -> &mut Self { self.auth = Some(auth); self } pub fn build(&mut self) -> Result { let params = std::mem::take(&mut self.params); let mut new_params = HashMap::new(); for (k, v) in params.into_iter() { new_params.insert(k, v?); } Ok(ServiceOptions { auth: self.auth.take(), params: new_params, }) } } fn to_json_value(value: T) -> Result where T: Serialize, { Ok(serde_json::to_value(value)?) }