diff options
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | examples/imagebuild.rs | 14 | ||||
-rw-r--r-- | src/builder.rs | 77 | ||||
-rw-r--r-- | src/lib.rs | 88 | ||||
-rw-r--r-- | src/transport.rs | 29 |
6 files changed, 169 insertions, 44 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c8154e5..182b65f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.2.1 (unreleased) + +* removed `Body` type with a preference for `Into<hyper::client::Body>` + # 0.2.0 * many breaking changes required to make interfaces consistent, idomatic, and future friendly @@ -17,6 +17,7 @@ jed = "0.1" mime = "0.1" openssl = "0.7" rustc-serialize = "0.3" +tar = "0.3" url = "0.5" [dev-dependencies] diff --git a/examples/imagebuild.rs b/examples/imagebuild.rs new file mode 100644 index 0000000..ed08949 --- /dev/null +++ b/examples/imagebuild.rs @@ -0,0 +1,14 @@ +extern crate shiplift; + +use shiplift::{BuildOptions, Docker}; +use std::env; + +fn main() { + let docker = Docker::new(); + if let Some(path) = env::args().nth(1) { + let image = docker.images() + .build(&BuildOptions::builder(path).build()) + .unwrap(); + println!("{:?}", image); + } +} diff --git a/src/builder.rs b/src/builder.rs index 77265fc..a436a1e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -4,6 +4,83 @@ use self::super::Result; use std::collections::{BTreeMap, HashMap}; use rustc_serialize::json::{self, Json, ToJson}; use url::form_urlencoded; +use self::super::hyper::client::Body; + +#[derive(Default)] +pub struct BuildOptions { + pub path: String, + params: HashMap<&'static str, String>, +} + +impl BuildOptions { + pub fn builder<S>(path: S) -> BuildOptionsBuilder where S: Into<String> { + BuildOptionsBuilder::new(path) + } + pub fn serialize(&self) -> Option<String> { + if self.params.is_empty() { + None + } else { + Some(form_urlencoded::serialize(&self.params)) + } + } +} + +#[derive(Default)] +pub struct BuildOptionsBuilder { + path: String, + params: HashMap<&'static str, String>, +} + +impl BuildOptionsBuilder { + pub fn new<S>(path: S) -> BuildOptionsBuilder where S: Into<String>{ + BuildOptionsBuilder { + path: path.into(), + ..Default::default() + } + } + + pub fn dockerfile<P>(&mut self, path: P) -> &mut BuildOptionsBuilder where P: Into<String> { + self.params.insert("dockerfile", path.into()); + self + } + + pub fn tag<T>(&mut self, t: T) -> &mut BuildOptionsBuilder where T: Into<String> { + self.params.insert("t", t.into()); + self + } + + pub fn remote<R>(&mut self, r: R) -> &mut BuildOptionsBuilder where R: Into<String> { + self.params.insert("remote", r.into()); + self + } + + pub fn nocache<R>(&mut self, nc: bool) -> &mut BuildOptionsBuilder { + self.params.insert("nocache", nc.to_string()); + self + } + + pub fn rm(&mut self, r: bool) -> &mut BuildOptionsBuilder { + self.params.insert("rm", r.to_string()); + self + } + + pub fn forcerm(&mut self, fr: bool) -> &mut BuildOptionsBuilder { + self.params.insert("forcerm", fr.to_string()); + self + } + + // todo: memory + // todo: memswap + // todo: cpushares + // todo: cpusetcpus + // todo: cpuperiod + // todo: cpuquota + // todo: buildargs + + pub fn build(&self) -> BuildOptions { + BuildOptions { path: self.path.clone(), params: self.params.clone() } + } +} /// Options for filtering container list results #[derive(Default)] @@ -21,14 +21,18 @@ extern crate jed; extern crate openssl; extern crate rustc_serialize; extern crate url; +extern crate tar; pub mod builder; pub mod rep; pub mod transport; pub mod errors; +use std::fs::{self, DirEntry, File}; +use tar::Archive; + pub use errors::Error; -pub use builder::{ContainerOptions, ContainerListOptions, ContainerFilter, EventsOptions, ImageFilter, +pub use builder::{BuildOptions, ContainerOptions, ContainerListOptions, ContainerFilter, EventsOptions, ImageFilter, ImageListOptions, LogsOptions}; use hyper::{Client, Url}; use hyper::net::{HttpsConnector, Openssl}; @@ -41,12 +45,13 @@ use rep::{Change, ContainerCreateInfo, ContainerDetails, Container as ContainerR Info, SearchResult, Stats, Status, Top, Version}; use rustc_serialize::json::{self, Json}; use std::env::{self, VarError}; -use std::io::Read; +use std::io::{self, Read}; use std::iter::IntoIterator; use std::path::Path; use std::sync::Arc; use std::time::Duration; -use transport::{Body, Transport}; +use transport::{Transport}; +use hyper::client::Body; use url::{form_urlencoded, Host, RelativeSchemeData, SchemeData}; /// Represents the result of all docker operations @@ -128,6 +133,41 @@ impl<'a> Images<'a> { Images { docker: docker } } + pub fn build(&self, opts: &BuildOptions) -> Result<String> { + let mut path = vec!["/build".to_owned()]; + if let Some(query) = opts.serialize() { + path.push(query) + } + + + let file = File::create("build.tar").unwrap(); + let mut a = Archive::new(file); + + fn visit_dirs(dir: &Path, cb: &Fn(&DirEntry)) -> io::Result<()> { + if try!(fs::metadata(dir)).is_dir() { + for entry in try!(fs::read_dir(dir)) { + let entry = try!(entry); + if try!(fs::metadata(entry.path())).is_dir() { + try!(visit_dirs(&entry.path(), cb)); + } else { + cb(&entry); + } + } + } + Ok(()) + } + + { + let append = |e: &DirEntry| { + a.append_path(e.path()); + }; + visit_dirs(Path::new(&opts.path[..]), &append); + a.finish(); + } + + self.docker.post(&path.join("?"), Some(Body::ChunkedBody(&mut a.into_inner()))) + } + /// Lists the docker images on the current docker host pub fn list(&self, opts: &ImageListOptions) -> Result<Vec<ImageRep>> { let mut path = vec!["/images/json".to_owned()]; @@ -236,8 +276,8 @@ impl<'a, 'b> Container<'a, 'b> { } /// Start the container instance - pub fn start(&self) -> Result<()> { - self.docker.post(&format!("/containers/{}/start", self.id)[..], None).map(|_| ()) + pub fn start(&'a self) -> Result<()> { + self.docker.post(&format!("/containers/{}/start", self.id)[..], None as Option<&'a str>).map(|_| ()) } /// Stop the container instance @@ -247,7 +287,7 @@ impl<'a, 'b> Container<'a, 'b> { let encoded = form_urlencoded::serialize(vec![("t", w.as_secs().to_string())]); path.push(encoded) } - self.docker.post(&path.join("?"), None).map(|_| ()) + self.docker.post(&path.join("?"), None as Option<&'a str>).map(|_| ()) } /// Restart the container instance @@ -257,7 +297,7 @@ impl<'a, 'b> Container<'a, 'b> { let encoded = form_urlencoded::serialize(vec![("t", w.as_secs().to_string())]); path.push(encoded) } - self.docker.post(&path.join("?"), None).map(|_| ()) + self.docker.post(&path.join("?"), None as Option<&'a str>).map(|_| ()) } /// Kill the container instance @@ -267,7 +307,7 @@ impl<'a, 'b> Container<'a, 'b> { let encoded = form_urlencoded::serialize(vec![("signal", sig.to_owned())]); path.push(encoded) } - self.docker.post(&path.join("?"), None).map(|_| ()) + self.docker.post(&path.join("?"), None as Option<&'a str>).map(|_| ()) } /// Rename the container instance @@ -275,23 +315,26 @@ impl<'a, 'b> Container<'a, 'b> { let query = form_urlencoded::serialize(vec![("name", name)]); self.docker .post(&format!("/containers/{}/rename?{}", self.id, query)[..], - None) + None as Option<&'a str>) .map(|_| ()) } /// Pause the container instance pub fn pause(&self) -> Result<()> { - self.docker.post(&format!("/containers/{}/pause", self.id)[..], None).map(|_| ()) + let empty: Option<&'a str> = None; + self.docker.post(&format!("/containers/{}/pause", self.id)[..], empty).map(|_| ()) } /// Unpause the container instance pub fn unpause(&self) -> Result<()> { - self.docker.post(&format!("/containers/{}/unpause", self.id)[..], None).map(|_| ()) + let empty: Option<&'a str> = None; + self.docker.post(&format!("/containers/{}/unpause", self.id)[..], empty).map(|_| ()) } /// Wait until the container stops pub fn wait(&self) -> Result<Exit> { - let raw = try!(self.docker.post(&format!("/containers/{}/wait", self.id)[..], None)); + let empty: Option<&'a str> = None; + let raw = try!(self.docker.post(&format!("/containers/{}/wait", self.id)[..], empty)); Ok(try!(json::decode::<Exit>(&raw))) } @@ -334,8 +377,7 @@ impl<'a> Containers<'a> { let data = try!(opts.serialize()); let mut bytes = data.as_bytes(); let raw = try!(self.docker.post("/containers/create", - Some(Body::new(&mut Box::new(&mut bytes), - bytes.len() as u64)))); + Some(&mut bytes))); Ok(try!(json::decode::<ContainerCreateInfo>(&raw))) } } @@ -452,23 +494,23 @@ impl Docker { Ok(Box::new(it)) } - fn get(&self, endpoint: &str) -> Result<String> { - self.transport.request(Method::Get, endpoint, None) + fn get<'a>(&self, endpoint: &str) -> Result<String> { + self.transport.request(Method::Get, endpoint, None as Option<&'a str>) } - fn post(&self, endpoint: &str, body: Option<Body>) -> Result<String> { + fn post<'a, B>(&'a self, endpoint: &str, body: Option<B>) -> Result<String> where B: Into<Body<'a>> { self.transport.request(Method::Post, endpoint, body) } - fn delete(&self, endpoint: &str) -> Result<String> { - self.transport.request(Method::Delete, endpoint, None) + fn delete<'a>(&self, endpoint: &str) -> Result<String> { + self.transport.request(Method::Delete, endpoint, None as Option<&'a str>) } - fn stream_post(&self, endpoint: &str) -> Result<Box<Read>> { - self.transport.stream(Method::Post, endpoint, None) + fn stream_post<'a>(&self, endpoint: &str) -> Result<Box<Read>> { + self.transport.stream(Method::Post, endpoint, None as Option<&'a str>) } - fn stream_get(&self, endpoint: &str) -> Result<Box<Read>> { - self.transport.stream(Method::Get, endpoint, None) + fn stream_get<'a>(&self, endpoint: &str) -> Result<Box<Read>> { + self.transport.stream(Method::Get, endpoint, None as Option<&'a str>) } } diff --git a/src/transport.rs b/src/transport.rs index dcc70d2..c035fa5 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -5,6 +5,7 @@ extern crate mime; use hyper::Client; use hyper::client; +use hyper::client::Body; use self::super::{Error, Result}; use self::hyper::buffer::BufReader; use self::hyper::header::ContentType; @@ -40,7 +41,7 @@ impl fmt::Debug for Transport { } impl Transport { - pub fn request(&self, method: Method, endpoint: &str, body: Option<Body>) -> Result<String> { + pub fn request<'a, B>(&'a self, method: Method, endpoint: &str, body: Option<B>) -> Result<String> where B: Into<Body<'a>> { let mut res = match self.stream(method, endpoint, body) { Ok(r) => r, Err(e) => panic!("failed request {:?}", e), @@ -51,7 +52,9 @@ impl Transport { Ok(body) } - pub fn stream(&self, method: Method, endpoint: &str, body: Option<Body>) -> Result<Box<Read>> { + pub fn stream<'c, B>( + &'c self, method: Method, endpoint: &str, body: Option<B> + ) -> Result<Box<Read>> where B: Into<Body<'c>> { let req = match *self { Transport::Tcp { ref client, ref host } => { client.request(method, &format!("{}{}", host, endpoint)[..]) @@ -62,9 +65,9 @@ impl Transport { }; let embodied = match body { - Some(Body { read: r, size: l }) => { - let reader: &mut Read = *r.deref_mut(); - req.header(ContentType::json()).body(client::Body::SizedBody(reader, l)) + Some(b) => {//Body { read: r, size: l }) => { + //let reader: &mut Read = *r.deref_mut(); + req.header(ContentType::json()).body(b)//client::Body::SizedBody(reader, l)) } _ => req, }; @@ -109,19 +112,3 @@ impl Transport { } } } - -#[doc(hidden)] -pub struct Body<'a> { - read: &'a mut Box<&'a mut Read>, - size: u64, -} - -impl<'a> Body<'a> { - /// Create a new body instance - pub fn new(read: &'a mut Box<&'a mut Read>, size: u64) -> Body<'a> { - Body { - read: read, - size: size, - } - } -} |