#[macro_use]
extern crate log;
#[macro_use]
#[macro_use]
extern crate hyper;
extern crate flate2;
extern crate hyperlocal;
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;
mod tarball;
pub use errors::Error;
pub use builder::{BuildOptions, ContainerOptions, ContainerListOptions, ContainerFilter,
EventsOptions, ImageFilter, ImageListOptions, LogsOptions,
PullOptions, RmContainerOptions
};
use hyper::{Client, Url};
use hyper::header::ContentType;
use hyper::net::{HttpsConnector, Openssl};
use hyper::method::Method;
use hyperlocal::UnixSocketConnector;
use openssl::x509::X509FileType;
use openssl::ssl::{SslContext, SslMethod};
use rep::Image as ImageRep;
use rep::{PullOutput, PullInfo, BuildOutput, Change, ContainerCreateInfo, ContainerDetails,
Container as ContainerRep, Event, Exit, History, ImageDetails, Info, SearchResult,
Stats, Status, Top, Version};
use rustc_serialize::json::{self, Json};
use std::env::{self, VarError};
use std::io::Read;
use std::iter::IntoIterator;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use transport::{tar, Transport};
use hyper::client::Body;
use url::{form_urlencoded};
pub type Result<T> = std::result::Result<T, Error>;
pub struct Docker {
transport: Transport,
}
pub struct Image<'a, 'b> {
docker: &'a Docker,
name: &'b str,
}
impl<'a, 'b> Image<'a, 'b> {
pub fn new(docker: &'a Docker, name: &'b str) -> Image<'a, 'b> {
Image {
docker: docker,
name: name,
}
}
pub fn inspect(&self) -> Result<ImageDetails> {
let raw = try!(self.docker.get(&format!("/images/{}/json", self.name)[..]));
Ok(try!(json::decode::<ImageDetails>(&raw)))
}
pub fn history(&self) -> Result<Vec<History>> {
let raw = try!(self.docker.get(&format!("/images/{}/history", self.name)[..]));
Ok(try!(json::decode::<Vec<History>>(&raw)))
}
pub fn delete(&self) -> Result<Vec<Status>> {
let raw = try!(self.docker.delete(&format!("/images/{}", self.name)[..]));
Ok(match try!(Json::from_str(&raw)) {
Json::Array(ref xs) => {
xs.iter().map(|j| {
let obj = j.as_object().expect("expected json object");
obj.get("Untagged")
.map(|sha| {
Status::Untagged(sha.as_string()
.expect("expected Untagged to be a string")
.to_owned())
})
.or(obj.get("Deleted")
.map(|sha| {
Status::Deleted(sha.as_string()
.expect("expected Deleted to be a string")
.to_owned())
}))
.expect("expected Untagged or Deleted")
})
}
_ => unreachable!(),
}
.collect())
}
pub fn export(&self) -> Result<Box<Read>> {
self.docker.stream_get(&format!("/images/{}/get", self.name)[..])
}
}
pub struct Images<'a> {
docker: &'a Docker,
}
impl<'a> Images<'a> {
pub fn new(docker: &'a Docker) -> Images<'a> {
Images { docker: docker }
}
pub fn build(&self, opts: &BuildOptions) -> Result<Box<Iterator<Item = BuildOutput>>> {
let mut path = vec!["/build".to_owned()];
if let Some(query) = opts.serialize() {
path.push(query)
}
let mut bytes = vec![];
try!(tarball::dir(&mut bytes, &opts.path[..]));
let raw = try!(self.docker.stream_post(&path.join("?"),
Some((Body::BufBody(&bytes[..], bytes.len()),
tar()))));
let it = jed::Iter::new(raw).into_iter().map(|j| {
debug!("{:?}", j);
let obj = j.as_object().expect("expected json object");
obj.get("stream")
.map(|stream| {
BuildOutput::Stream(stream.as_string()
.expect("expected stream to be a string")
.to_owned())
})
.or(obj.get("error")
.map(|err| {
BuildOutput::Err(err.as_string()
.expect("expected error to be a string")
.to_owned())
}))
.expect("expected build output stream or error")
});
Ok(Box::new(it))
}
pub fn list(&self, opts: &ImageListOptions) -> Result<Vec<ImageRep>> {
let mut path = vec!["/images/json".to_owned()];
if let Some(query) = opts.serialize() {
path.push(query);
}
let raw = try!(self.docker.get(&path.join("?")));
Ok(try!(json::decode::<Vec<ImageRep>>(&raw)))
}
pub fn get(&'a self, name: &'a str) -> Image {
Image::new(self.docker, name)
}
pub fn search(&self, term: &str) -> Result<Vec<SearchResult>> {
let query = form_urlencoded::serialize(vec![("term", term)]);
let raw = try!(self.docker.get(&format!("/images/search?{}", query)[..]));
Ok(try!(json::decode::<Vec<SearchResult>>(&raw)))
}
pub fn pull(&self, opts: &PullOptions) -> Result<Box<Iterator<Item = PullOutput>>> {
let mut path = vec!["/images/create".to_owned()];
if let Some(query) = opts.serialize() {
path.push(query);
}
let raw = try!(self.docker
.stream_post(&path.join("?"), None as Option<(&'a str, ContentType)>));
let it = jed::Iter::new(raw).into_iter().map(|j| {
debug!("{:?}", j);
let s = json::encode(&j).unwrap();
json::decode::<PullInfo>(&s)
.map(|info| {
PullOutput::Status {
id: info.id,
status: info.status,
progress: info.progress,
progress_detail: info.progressDetail,
}
})
.ok()
.or(j.as_object()
.expect("expected json object")
.get("error")
.map(|err| {
PullOutput::Err(err.as_string()
.expect("expected error to be a string")
.to_owned())
}))
.expect("expected pull status or error")
});
Ok(Box::new(it))
}
pub fn export(&self, names: Vec<&str>) -> Result<Box<Read>> {
let params = names.iter()
.map(|n| ("names", *n))
.collect::<Vec<(&str, &str)>>();
let query = form_urlencoded::serialize(params);
self.docker.stream_get(&format!("/images/get?{}", query)[..])
}
}
pub struct Container<'a, 'b> {
docker: &'a Docker,
id: &'b str,
}
impl<'a, 'b> Container<'a, 'b> {
pub fn new(docker: &'a Docker, id: &'b str) -> Container<'a, 'b> {
Container {
docker: docker,
id: id,
}
}
pub fn id(&self) -> &str { &self.id }
pub fn inspect(&self) -> Result<ContainerDetails> {
let raw = try!(self.docker.get(&format!("/containers/{}/json", self.id)[..]));
Ok(try!(json::decode::<ContainerDetails>(&raw)))
}
pub fn top(&self, psargs: Option<&str>) -> Result<Top> {
let mut path = vec![format!("/containers/{}/top", self.id)];
if let Some(ref args) = psargs {
let encoded = form_urlencoded::serialize(vec![("ps_args", args)]);
path.push(encoded)
}
let raw = try!(self.docker.get(&path.join("?")));
Ok(try!(json::decode::<Top>(&raw)))
}
pub fn logs(&self, opts: &LogsOptions) -> Result<Box<Read>> {
let mut path = vec![format!("/containers/{}/logs", self.id)];
if let Some(query) = opts.serialize() {
path.push(query)
}
self.docker.stream_get(&path.join("?"))
}
pub fn changes(&self) -> Result<Vec<Change>> {
let raw = try!(self.docker.get(&format!("/containers/{}/changes", self.id)[..]));
Ok(try!(json::decode::<Vec<Change>>(&raw)))
}
pub fn export(&self) -> Result<Box<Read>> {
self.docker.stream_get(&format!("/containers/{}/export", self.id)[..])
}
pub fn stats(&self) -> Result<Box<Iterator<Item = Stats>>> {
let raw = try!(self.docker.stream_get(&format!("/containers/{}/stats", self.id)[..]));
let it = jed::Iter::new(raw).into_iter().map(|j| {
debug!("{:?}", j);
let s = json::encode(&j).unwrap();
json::decode::<Stats>(&s).unwrap()
});
Ok(Box::new(it))
}
pub fn start(&'a self) -> Result<()> {
self.docker
.post(&format!("/containers/{}/start", self.id)[..],
None as Option<(&'a str, ContentType)>)
.map(|_| ())
}
pub fn stop(&self, wait: Option<Duration>) -> Result<()> {
let mut path = vec![format!("/containers/{}/stop", self.id)];
if let Some(w) = wait {
let encoded = form_urlencoded::serialize(vec![("t", w.as_secs().to_string())]);
path.push(encoded)
}
self.docker.post(&path.join("?"), None as Option<(&'a str, ContentType)>).map(|_| ())
}
pub fn restart(&self, wait: Option<Duration>) -> Result<()> {
let mut path = vec![format!("/containers/{}/restart", self.id)];
if let Some(w) = wait {
let encoded = form_urlencoded::serialize(vec![("t", w.as_secs().to_string())]);
path.push(encoded)
}
self.docker.post(&path.join("?"), None as Option<(&'a str, ContentType)>).map(|_| ())
}
pub fn kill(&self, signal: Option<&str>) -> Result<()> {
let mut path = vec![format!("/containers/{}/kill", self.id)];
if let Some(sig) = signal {
let encoded = form_urlencoded::serialize(vec![("signal", sig.to_owned())]);
path.push(encoded)
}
self.docker.post(&path.join("?"), None as Option<(&'a str, ContentType)>).map(|_| ())
}
pub fn rename(&self, name: &str) -> Result<()> {
let query = form_urlencoded::serialize(vec![("name", name)]);
self.docker
.post(&format!("/containers/{}/rename?{}", self.id, query)[..],
None as Option<(&'a str, ContentType)>)
.map(|_| ())
}
pub fn pause(&self) -> Result<()> {
self.docker
.post(&format!("/containers/{}/pause", self.id)[..],
None as Option<(&'a str, ContentType)>)
.map(|_| ())
}
pub fn unpause(&self) -> Result<()> {
self.docker
.post(&format!("/containers/{}/unpause", self.id)[..],
None as Option<(&'a str, ContentType)>)
.map(|_| ())
}
pub fn wait(&self) -> Result<Exit> {
let raw = try!(self.docker.post(&format!("/containers/{}/wait", self.id)[..],
None as Option<(&'a str, ContentType)>));
Ok(try!(json::decode::<Exit>(&raw)))
}
pub fn delete(&self) -> Result<()> {
self.docker.delete(&format!("/containers/{}", self.id)[..]).map(|_| ())
}
pub fn remove(&self, opts: RmContainerOptions) -> Result<()> {
let mut path = vec![format!("/containers/{}", self.id)];
if let Some(query) = opts.serialize() {
path.push(query)
}
try!(self.docker.delete(&path.join("?")));
Ok(())
}
}
pub struct Containers<'a> {
docker: &'a Docker,
}
impl<'a> Containers<'a> {
pub fn new(docker: &'a Docker) -> Containers<'a> {
Containers { docker: docker }
}
pub fn list(&self, opts: &ContainerListOptions) -> Result<Vec<ContainerRep>> {
let mut path = vec!["/containers/json".to_owned()];
if let Some(query) = opts.serialize() {
path.push(query)
}
let raw = try!(self.docker.get(&path.join("?")));
Ok(try!(json::decode::<Vec<ContainerRep>>(&raw)))
}
pub fn get(&'a self, name: &'a str) -> Container {
Container::new(self.docker, name)
}
pub fn create(&'a self, opts: &ContainerOptions) -> Result<ContainerCreateInfo> {
let data = try!(opts.serialize());
let mut bytes = data.as_bytes();
let mut path = vec!["/containers/create".to_owned()];
if let Some(ref name) = opts.name {
path.push(form_urlencoded::serialize(vec![("name", name)]));
}
let raw = try!(self.docker.post(&path.join("?"),
Some((&mut bytes, ContentType::json()))));
Ok(try!(json::decode::<ContainerCreateInfo>(&raw)))
}
}
impl Docker {
pub fn new() -> Docker {
let fallback: std::result::Result<String, VarError> = Ok("unix:///var/run/docker.sock"
.to_owned());
let host = env::var("DOCKER_HOST")
.or(fallback)
.map(|h| {
Url::parse(&h)
.ok()
.expect("invalid url")
})
.ok()
.expect("expected host");
Docker::host(host)
}
pub fn host(host: Url) -> Docker {
match host.scheme() {
"unix" => {
Docker {
transport: Transport::Unix {
client: Client::with_connector(UnixSocketConnector),
path: host.path().to_owned(),
},
}
}
_ => {
let client = if let Some(ref certs) = env::var("DOCKER_CERT_PATH").ok() {
let mut ssl_ctx = SslContext::new(SslMethod::Sslv23).unwrap();
ssl_ctx.set_cipher_list("DEFAULT").unwrap();
let cert = &format!("{}/cert.pem", certs);
let key = &format!("{}/key.pem", certs);
let _ = ssl_ctx.set_certificate_file(&Path::new(cert), X509FileType::PEM);
let _ = ssl_ctx.set_private_key_file(&Path::new(key), X509FileType::PEM);
if let Some(_) = env::var("DOCKER_TLS_VERIFY").ok() {
let ca = &format!("{}/ca.pem", certs);
let _ = ssl_ctx.set_CA_file(&Path::new(ca));
};
Client::with_connector(HttpsConnector::new(Openssl {
context: Arc::new(ssl_ctx),
}))
} else {
Client::new()
};
Docker {
transport: Transport::Tcp {
client: client,
host: format!("https://{}:{}", host.host_str().unwrap().to_owned(), host.port_or_known_default().unwrap()),
},
}
}
}
}
pub fn images<'a>(&'a self) -> Images {
Images::new(self)
}
pub fn containers<'a>(&'a self) -> Containers {
Containers::new(self)
}
pub fn version(&self) -> Result<Version> {
let raw = try!(self.get("/version"));
Ok(try!(json::decode::<Version>(&raw)))
}
pub fn info(&self) -> Result<Info> {
let raw = try!(self.get("/info"));
Ok(try!(json::decode::<Info>(&raw)))
}
pub fn ping(&self) -> Result<String> {
self.get("/_ping")
}
pub fn events(&self, opts: &EventsOptions) -> Result<Box<Iterator<Item = Event>>> {
let mut path = vec!["/events".to_owned()];
if let Some(query) = opts.serialize() {
path.push(query);
}
let raw = try!(self.stream_get(&path.join("?")[..]));
let it = jed::Iter::new(raw).into_iter().map(|j| {
debug!("{:?}", j);
let s = json::encode(&j).unwrap();
json::decode::<Event>(&s).unwrap()
});
Ok(Box::new(it))
}
fn get<'a>(&self, endpoint: &str) -> Result<String> {
self.transport.request(Method::Get,
endpoint,
None as Option<(&'a str, ContentType)>)
}
fn post<'a, B>(&'a self, endpoint: &str, body: Option<(B, ContentType)>) -> Result<String>
where B: Into<Body<'a>>
{
self.transport.request(Method::Post, endpoint, body)
}
fn delete<'a>(&self, endpoint: &str) -> Result<String> {
self.transport.request(Method::Delete,
endpoint,
None as Option<(&'a str, ContentType)>)
}
fn stream_post<'a, B>(&'a self,
endpoint: &str,
body: Option<(B, ContentType)>)
-> Result<Box<Read>>
where B: Into<Body<'a>>
{
self.transport.stream(Method::Post, endpoint, body)
}
fn stream_get<'a>(&self, endpoint: &str) -> Result<Box<Read>> {
self.transport.stream(Method::Get,
endpoint,
None as Option<(&'a str, ContentType)>)
}
}