#[macro_use]
extern crate log;
extern crate hyper;
extern crate hyperlocal;
extern crate jed;
extern crate openssl;
extern crate rustc_serialize;
extern crate url;
pub mod builder;
pub mod rep;
pub mod transport;
pub mod errors;
pub use errors::Error;
pub use builder::{ContainerOptions, ContainerListOptions, ContainerFilter, EventsOptions, ImageFilter,
ImageListOptions, LogsOptions};
use hyper::{Client, Url};
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::{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::{Body, Transport};
use url::{form_urlencoded, Host, RelativeSchemeData, SchemeData};
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 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 create(&self, from: &str) -> Result<Box<Read>> {
let query = form_urlencoded::serialize(vec![("fromImage", from)]);
self.docker.stream_post(&format!("/images/create?{}", query)[..])
}
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 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| {
let s = json::encode(&j).unwrap();
json::decode::<Stats>(&s).unwrap()
});
Ok(Box::new(it))
}
pub fn start(&self) -> Result<()> {
self.docker.post(&format!("/containers/{}/start", self.id)[..], None).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).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).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).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)
.map(|_| ())
}
pub fn pause(&self) -> Result<()> {
self.docker.post(&format!("/containers/{}/pause", self.id)[..], None).map(|_| ())
}
pub fn unpause(&self) -> Result<()> {
self.docker.post(&format!("/containers/{}/unpause", self.id)[..], None).map(|_| ())
}
pub fn wait(&self) -> Result<Exit> {
let raw = try!(self.docker.post(&format!("/containers/{}/wait", self.id)[..], None));
Ok(try!(json::decode::<Exit>(&raw)))
}
pub fn delete(&self) -> Result<()> {
self.docker.delete(&format!("/containers/{}", self.id)[..]).map(|_| ())
}
}
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 raw = try!(self.docker.post("/containers/create",
Some(Body::new(&mut Box::new(&mut bytes),
bytes.len() as u64))));
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 {
let domain = match host.scheme_data {
SchemeData::NonRelative(s) => s,
SchemeData::Relative(RelativeSchemeData { host, .. }) => {
match host {
Host::Domain(s) => s,
Host::Ipv6(a) => a.to_string(),
Host::Ipv4(a) => a.to_string(),
}
}
};
match &host.scheme[..] {
"unix" => {
Docker {
transport: Transport::Unix {
client: Client::with_connector(UnixSocketConnector),
path: domain,
},
}
}
_ => {
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:{}", domain.to_owned()),
},
}
}
}
}
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| {
let s = json::encode(&j).unwrap();
json::decode::<Event>(&s).unwrap()
});
Ok(Box::new(it))
}
fn get(&self, endpoint: &str) -> Result<String> {
self.transport.request(Method::Get, endpoint, None)
}
fn post(&self, endpoint: &str, body: Option<Body>) -> Result<String> {
self.transport.request(Method::Post, endpoint, body)
}
fn delete(&self, endpoint: &str) -> Result<String> {
self.transport.request(Method::Delete, endpoint, None)
}
fn stream_post(&self, endpoint: &str) -> Result<Box<Read>> {
self.transport.stream(Method::Post, endpoint, None)
}
fn stream_get(&self, endpoint: &str) -> Result<Box<Read>> {
self.transport.stream(Method::Get, endpoint, None)
}
}