diff options
-rw-r--r-- | examples/volumecreate.rs | 32 | ||||
-rw-r--r-- | examples/volumedelete.rs | 23 | ||||
-rw-r--r-- | examples/volumes.rs | 21 | ||||
-rw-r--r-- | src/builder.rs | 69 | ||||
-rw-r--r-- | src/lib.rs | 90 | ||||
-rw-r--r-- | src/rep.rs | 25 | ||||
-rw-r--r-- | src/tty.rs | 4 |
7 files changed, 259 insertions, 5 deletions
diff --git a/examples/volumecreate.rs b/examples/volumecreate.rs new file mode 100644 index 0000000..d955abe --- /dev/null +++ b/examples/volumecreate.rs @@ -0,0 +1,32 @@ +extern crate shiplift; +extern crate tokio; + +use shiplift::builder::VolumeCreateOptions; +use shiplift::Docker; +use std::collections::HashMap; +use std::env; +use tokio::prelude::Future; + +fn main() { + let docker = Docker::new(); + let volumes = docker.volumes(); + + let volume_name = env::args() + .nth(1) + .expect("You need to specify an volume name"); + + let mut labels = HashMap::new(); + labels.insert("com.github.softprops", "shiplift"); + + let fut = volumes + .create( + &VolumeCreateOptions::builder() + .name(volume_name.as_ref()) + .labels(&labels) + .build(), + ) + .map(|info| println!("{:?}", info)) + .map_err(|e| eprintln!("Error: {}", e)); + + tokio::run(fut); +} diff --git a/examples/volumedelete.rs b/examples/volumedelete.rs new file mode 100644 index 0000000..8689589 --- /dev/null +++ b/examples/volumedelete.rs @@ -0,0 +1,23 @@ +extern crate shiplift; +extern crate tokio; + +use shiplift::Docker; +use std::env; +use tokio::prelude::Future; + +fn main() { + let docker = Docker::new(); + let volumes = docker.volumes(); + + let volume_name = env::args() + .nth(1) + .expect("You need to specify an volume name"); + + let fut = volumes + .get(&volume_name) + .delete() + .map(|info| println!("{:?}", info)) + .map_err(|e| eprintln!("Error: {}", e)); + + tokio::run(fut); +} diff --git a/examples/volumes.rs b/examples/volumes.rs new file mode 100644 index 0000000..59c7b65 --- /dev/null +++ b/examples/volumes.rs @@ -0,0 +1,21 @@ +extern crate shiplift; +extern crate tokio; + +use shiplift::Docker; +use tokio::prelude::Future; + +fn main() { + let docker = Docker::new(); + let volumes = docker.volumes(); + + let fut = volumes + .list() + .map(|volumes| { + for v in volumes { + println!("volume -> {:#?}", v) + } + }) + .map_err(|e| eprintln!("Error: {}", e));; + + tokio::run(fut); +} diff --git a/src/builder.rs b/src/builder.rs index ceabc28..7f6f26d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1238,6 +1238,75 @@ impl ContainerConnectionOptions { } } +/// Interface for creating volumes +#[derive(Serialize)] +pub struct VolumeCreateOptions { + params: HashMap<&'static str, Value>, +} + +impl VolumeCreateOptions { + /// serialize options as a string. returns None if no options are defined + pub fn serialize(&self) -> Result<String> { + serde_json::to_string(&self.params).map_err(Error::from) + } + + pub fn parse_from<'a, K, V>( + &self, + params: &'a HashMap<K, V>, + body: &mut BTreeMap<String, Value>, + ) where + &'a HashMap<K, V>: IntoIterator, + K: ToString + Eq + Hash, + V: Serialize, + { + for (k, v) in params.iter() { + let key = k.to_string(); + let value = serde_json::to_value(v).unwrap(); + + body.insert(key, value); + } + } + + /// return a new instance of a builder for options + pub fn builder() -> VolumeCreateOptionsBuilder { + VolumeCreateOptionsBuilder::new() + } +} + +#[derive(Default)] +pub struct VolumeCreateOptionsBuilder { + params: HashMap<&'static str, Value>, +} + +impl VolumeCreateOptionsBuilder { + pub(crate) fn new() -> Self { + let params = HashMap::new(); + VolumeCreateOptionsBuilder { params } + } + + pub fn name( + &mut self, + name: &str, + ) -> &mut Self { + self.params.insert("Name", json!(name)); + self + } + + pub fn labels( + &mut self, + labels: &HashMap<&str, &str>, + ) -> &mut Self { + self.params.insert("Labels", json!(labels)); + self + } + + pub fn build(&self) -> VolumeCreateOptions { + VolumeCreateOptions { + params: self.params.clone(), + } + } +} + #[cfg(test)] mod tests { use super::ContainerOptionsBuilder; @@ -56,6 +56,7 @@ pub use builder::{ BuildOptions, ContainerConnectionOptions, ContainerFilter, ContainerListOptions, ContainerOptions, EventsOptions, ExecContainerOptions, ImageFilter, ImageListOptions, LogsOptions, NetworkCreateOptions, NetworkListOptions, PullOptions, RmContainerOptions, + VolumeCreateOptions, }; pub use errors::Error; use futures::{future::Either, Future, IntoFuture, Stream}; @@ -68,12 +69,12 @@ use hyperlocal::UnixConnector; use mime::Mime; use openssl::ssl::{SslConnector, SslFiletype, SslMethod}; use read::StreamReader; -use rep::Image as ImageRep; use rep::{ Change, Container as ContainerRep, ContainerCreateInfo, ContainerDetails, Event, Exit, History, - ImageDetails, Info, SearchResult, Stats, Status, Top, Version, + Image as ImageRep, ImageDetails, Info, NetworkCreateInfo, NetworkDetails as NetworkInfo, + SearchResult, Stats, Status, Top, Version, Volume as VolumeRep, VolumeCreateInfo, + Volumes as VolumesRep, }; -use rep::{NetworkCreateInfo, NetworkDetails as NetworkInfo}; use serde_json::Value; use std::borrow::Cow; use std::env; @@ -686,6 +687,85 @@ impl<'a, 'b> Network<'a, 'b> { } } +/// Interface for docker volumes +pub struct Volumes<'a> { + docker: &'a Docker, +} + +impl<'a> Volumes<'a> { + /// Exports an interface for interacting with docker volumes + pub fn new(docker: &'a Docker) -> Volumes<'a> { + Volumes { docker } + } + + pub fn create( + &self, + opts: &VolumeCreateOptions, + ) -> impl Future<Item = VolumeCreateInfo, Error = Error> { + let data = match opts.serialize() { + Ok(data) => data, + Err(e) => return Either::A(futures::future::err(e)), + }; + + let bytes = data.into_bytes(); + let path = vec!["/volumes/create".to_owned()]; + + Either::B( + self.docker + .post_json(&path.join("?"), Some((bytes, mime::APPLICATION_JSON))), + ) + } + + /// Lists the docker volumes on the current docker host + pub fn list(&self) -> impl Future<Item = Vec<VolumeRep>, Error = Error> { + let path = vec!["/volumes".to_owned()]; + + self.docker + .get_json::<VolumesRep>(&path.join("?")) + .map(|volumes: VolumesRep| match volumes.volumes { + Some(volumes) => volumes.clone(), + None => vec![], + }) + } + + /// Returns a reference to a set of operations available for a named volume + pub fn get<'b>( + &self, + name: &'b str, + ) -> Volume<'a, 'b> { + Volume::new(self.docker, name) + } +} + +/// Interface for accessing and manipulating a named docker volume +pub struct Volume<'a, 'b> { + docker: &'a Docker, + name: Cow<'b, str>, +} + +impl<'a, 'b> Volume<'a, 'b> { + /// Exports an interface for operations that may be performed against a named volume + pub fn new<S>( + docker: &'a Docker, + name: S, + ) -> Volume<'a, 'b> + where + S: Into<Cow<'b, str>>, + { + Volume { + docker, + name: name.into(), + } + } + + /// Deletes a volume + pub fn delete(&self) -> impl Future<Item = (), Error = Error> { + self.docker + .delete(&format!("/volumes/{}", self.name)[..]) + .map(|_| ()) + } +} + // https://docs.docker.com/reference/api/docker_remote_api_v1.17/ impl Docker { /// constructs a new Docker instance for a docker host listening at a url specified by an env var `DOCKER_HOST`, @@ -795,6 +875,10 @@ impl Docker { Networks::new(self) } + pub fn volumes(&self) -> Volumes { + Volumes::new(self) + } + /// Returns version information associated with the docker daemon pub fn version(&self) -> impl Future<Item = Version, Error = Error> { self.get_json("/version") @@ -456,3 +456,28 @@ pub enum Status { Untagged(String), Deleted(String), } + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct VolumeCreateInfo { + pub name: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct Volumes { + pub volumes: Option<Vec<Volume>>, + pub warnings: Option<Vec<String>>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct Volume { + pub created_at: String, + pub driver: String, + pub labels: Option<HashMap<String, String>>, + pub name: String, + pub mountpoint: String, + pub options: Option<HashMap<String, String>>, + pub scope: String, +} @@ -93,7 +93,7 @@ impl Decoder for TtyDecoder { 0 => { return Err(Error::InvalidResponse( "Unsupported stream of type stdin".to_string(), - )) + )); } 1 => StreamType::StdOut, 2 => StreamType::StdErr, @@ -101,7 +101,7 @@ impl Decoder for TtyDecoder { return Err(Error::InvalidResponse(format!( "Unsupported stream of type {}", n - ))) + ))); } }; |