use serde_json as json;
use serde::Deserialize;
use crate::container::{Config, Killer, Remover, CreatedContainer};
use hyper::{Client, Request};
use hyper::rt::{Stream, Future};
use hyperlocal::{UnixConnector, Uri};
use tokio_core::reactor::Core;
use tokio::prelude::stream::Concat2;
use tokio::prelude::Async;
use crate::container::info::ContainerInfo;
#[derive(Debug)]
pub struct DockerClient {
path: String,
client: Client<UnixConnector, hyper::Body>,
}
#[derive(Deserialize, Debug)]
pub struct ErrorMessage {
pub message: String
}
#[derive(Debug)]
pub enum DockerError {
BadParameters(ErrorMessage),
ServerError(ErrorMessage),
NotFound(ErrorMessage),
NotRunning(ErrorMessage),
AlreadyStarted(ErrorMessage),
ContainerExists(ErrorMessage),
UnknownStatus
}
#[derive(Deserialize, Debug)]
pub struct FSChanges {
#[serde(rename(deserialize = "Path"))]
path: String,
#[serde(rename(deserialize = "Kind"))]
kind: i32,
}
struct DockerFuture {
status: hyper::StatusCode,
body: Concat2<hyper::Body>,
}
#[derive(Clone)]
struct DockerResponse {
status: u16,
body: String,
}
impl Future for DockerFuture {
type Item = DockerResponse;
type Error = hyper::Error;
fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> {
self.body.poll().map(|s| {
match s {
Async::NotReady => Ok(Async::<Self::Item>::NotReady),
Async::Ready(s) => {
Ok(Async::Ready(DockerResponse{
status: self.status.as_u16(),
body: std::str::from_utf8(&s).unwrap_or("").to_string(),
}))
},
}
}).unwrap()
}
}
impl DockerClient {
pub fn connect<T>(path: T) -> DockerClient
where T: Into<String>
{
DockerClient {
path: path.into(),
client: Client::builder()
.keep_alive(false)
.build::<_, hyper::Body>(UnixConnector::new()),
}
}
fn execute(&self, request: hyper::Request<hyper::Body>) -> DockerResponse {
let mut core = Core::new().unwrap();
let client = self.client.clone();
let future = client.request(request)
.and_then(|res|
DockerFuture {
status: res.status(),
body: res.into_body().concat2()
}
)
.map_err(hyper::Error::from)
.map_err(Box::<hyper::Error>::from);
core.run(future).unwrap()
}
}
impl DockerClient {
pub fn create_container(&self, config: Config) -> Result<CreatedContainer, DockerError> {
let uri: hyper::Uri = Uri::new(self.path.as_str(), config.get_path().as_str()).into();
let request = Request::post(uri)
.header("Content-Type", "application/json")
.body(hyper::Body::from(json::to_string(&config).unwrap()))
.unwrap();
let response = self.execute(request);
match response.status {
201 => Ok(json::from_str(response.body.as_str()).unwrap()),
400 => Err(DockerError::BadParameters(json::from_str(response.body.as_str()).unwrap())),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
409 => Err(DockerError::ContainerExists(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn get_fs_changes<T>(&self, id: T) -> Result<Vec<FSChanges>, DockerError>
where T: Into<String> {
let id = id.into();
let path = format!("/containers/{}/changes", id);
let url: hyper::Uri = Uri::new(self.path.as_str(), path.as_str()).into();
let request = Request::get(url)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
200 => {
let obj: Option<Vec<FSChanges>> = json::from_str(response.body.as_str()).unwrap();
Ok(obj.unwrap_or(Vec::new()))
},
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn start_container<T>(&self, id: T, _detach_keys: T) -> Result<(), DockerError>
where T: Into<String> {
let id = id.into();
let path = format!("/containers/{}/start", id);
let uri: hyper::Uri = Uri::new(self.path.as_str(), path.as_str()).into();
let request = Request::post(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
204 => Ok(()),
304 => Ok(()),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn stop_container<T>(&self, id: T, _wait: Option<i32>) -> Result<(), DockerError>
where T: Into<String>
{
let path = format!("/containers/{}/stop", id.into());
let uri: hyper::Uri = Uri::new(self.path.as_str(), path.as_str()).into();
let request = Request::post(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
204 => Ok(()),
304 => Ok(()),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn pause_container<T>(&self, id: T) -> Result<(), DockerError>
where T: Into<String>
{
let path = format!("/containers/{}/pause", id.into());
let uri: hyper::Uri = Uri::new(self.path.as_str(), path.as_str()).into();
let request = Request::post(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
204 => Ok(()),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn unpause_container<T>(&self, id: T) -> Result<(), DockerError>
where T: Into<String> {
let path = format!("/containers/{}/unpause", id.into());
let uri: hyper::Uri = Uri::new(self.path.as_str(), path.as_str()).into();
let request = Request::post(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
204 => Ok(()),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn rename_container<T>(&self, id: T, new_name: T) -> Result<(), DockerError>
where T: Into<String> {
let path = format!("/containers/{}/rename?name={}", id.into(), new_name.into());
let uri: hyper::Uri = Uri::new(self.path.as_str(), path.as_str()).into();
let request = Request::post(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
204 => Ok(()),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
409 => Err(DockerError::ContainerExists(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn kill_container(&self, killer: Killer) -> Result<(), DockerError> {
let uri: hyper::Uri = Uri::new(self.path.as_str(), killer.get_path().as_str()).into();
let request = Request::post(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
204 => Ok(()),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
409 => Err(DockerError::NotRunning(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn remove_container(&self, remover: Remover) -> Result<(), DockerError> {
let uri: hyper::Uri = Uri::new(self.path.as_str(), remover.get_path().as_str()).into();
let request = Request::post(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
204 => Ok(()),
400 => Err(DockerError::BadParameters(json::from_str(response.body.as_str()).unwrap())),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
409 => Err(DockerError::NotRunning(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
pub fn inspect_container<T>(&self, id: T, size: bool) -> Result<ContainerInfo, DockerError>
where T: Into<String>
{
let path = format!("/containers/{}/json?size={}", id.into(), size.to_string());
let uri: hyper::Uri = Uri::new(self.path.as_str(), path.as_str()).into();
let request = Request::get(uri)
.body(hyper::Body::empty())
.unwrap();
let response = self.execute(request);
match response.status {
200 => Ok(json::from_str(response.body.as_str()).unwrap()),
404 => Err(DockerError::NotFound(json::from_str(response.body.as_str()).unwrap())),
500 => Err(DockerError::ServerError(json::from_str(response.body.as_str()).unwrap())),
_ => Err(DockerError::UnknownStatus),
}
}
}