├── .gitignore ├── CHANGELOG.md ├── src ├── filesystem.rs ├── version.rs ├── lib.rs ├── image.rs ├── system.rs ├── unix.rs ├── process.rs ├── http.rs ├── container.rs ├── tcp.rs ├── stats.rs ├── docker.rs └── test.rs ├── .travis.yml ├── .travis └── update_docs.sh ├── Makefile ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Cargo.lock 3 | target 4 | doc 5 | main.rs -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### v0.0.41 2 | 3 | - TCP, Unix socket connections are more efficient. 4 | 5 | ### v0.0.40 6 | 7 | - Create an image API 8 | -------------------------------------------------------------------------------- /src/filesystem.rs: -------------------------------------------------------------------------------- 1 | #[derive(RustcEncodable, RustcDecodable)] 2 | #[allow(non_snake_case)] 3 | pub struct FilesystemChange { 4 | pub Path: String, 5 | pub Kind: u8 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - 1.4.0 5 | - nightly 6 | 7 | script: 8 | - cargo build 9 | - cargo test 10 | - cargo doc --no-deps 11 | 12 | after_success: 13 | - test $TRAVIS_PULL_REQUEST == "false" && test $TRAVIS_BRANCH == "master" && ./.travis/update_docs.sh -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | #[derive(RustcEncodable, RustcDecodable)] 2 | #[allow(non_snake_case)] 3 | pub struct Version { 4 | pub Version: String, 5 | pub ApiVersion: String, 6 | pub GitCommit: String, 7 | pub GoVersion: String, 8 | pub Os: String, 9 | pub Arch: String, 10 | pub KernelVersion: String, 11 | pub BuildTime: Option, 12 | pub Experimental:Option 13 | } 14 | -------------------------------------------------------------------------------- /.travis/update_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit -o nounset 4 | 5 | git clone --branch gh-pages "https://$GH_TOKEN@github.com/${TRAVIS_REPO_SLUG}.git" deploy_docs 6 | cd deploy_docs 7 | 8 | git config user.name "Graham Lee" 9 | git config user.email "ghmlee@ghmlee.com" 10 | 11 | rm -rf doc 12 | mv ../target/doc . 13 | 14 | git add -A . 15 | git commit -m "It is bulit at ${TRAVIS_COMMIT}." 16 | git push -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) 2 | PROJECT := $(dir $(MAKEFILE)) 3 | CARGO := $(PROJECT)/Cargo.toml 4 | 5 | export CARGO 6 | export OPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include 7 | export OPENSSL_ROOT_DIR=/usr/local/opt/openssl 8 | 9 | default: build 10 | 11 | build: 12 | @cargo build --manifest-path $(CARGO) 13 | 14 | run: 15 | cargo run 16 | 17 | clean: 18 | cargo clean 19 | 20 | publish: 21 | cargo publish 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "docker" 3 | description = "Docker Remote API in Rust" 4 | version = "0.0.41" 5 | authors = ["Graham Lee "] 6 | license = "Apache-2.0" 7 | homepage = "https://github.com/ghmlee/rust-docker" 8 | repository = "https://github.com/ghmlee/rust-docker.git" 9 | documentation = "https://ghmlee.github.io/rust-docker/doc/docker" 10 | readme = "README.md" 11 | keywords = ["docker"] 12 | 13 | [dependencies] 14 | openssl = "0.6.7" 15 | unix_socket = "0.4.6" 16 | rustc-serialize = "0.3.16" -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Docker 2 | #![doc(html_root_url="https://ghmlee.github.io/rust-docker/doc")] 3 | 4 | // import external libraries 5 | extern crate openssl; 6 | extern crate unix_socket; 7 | extern crate rustc_serialize; 8 | 9 | // declare modules 10 | mod tcp; 11 | mod unix; 12 | mod http; 13 | mod test; 14 | mod docker; 15 | pub mod container; 16 | pub mod stats; 17 | pub mod system; 18 | pub mod image; 19 | pub mod process; 20 | pub mod filesystem; 21 | pub mod version; 22 | 23 | // publicly re-export 24 | pub use docker::Docker; 25 | -------------------------------------------------------------------------------- /src/image.rs: -------------------------------------------------------------------------------- 1 | #[derive(RustcEncodable, RustcDecodable)] 2 | #[allow(non_snake_case)] 3 | pub struct Image { 4 | pub Created: u64, 5 | pub Id: String, 6 | pub ParentId: String, 7 | pub RepoTags: Vec, 8 | pub Size: u64, 9 | pub VirtualSize: u64 10 | } 11 | 12 | impl Clone for Image { 13 | fn clone(&self) -> Self { 14 | let image = Image { 15 | Created: self.Created, 16 | Id: self.Id.clone(), 17 | ParentId: self.ParentId.clone(), 18 | RepoTags: self.RepoTags.clone(), 19 | Size: self.Size, 20 | VirtualSize: self.VirtualSize 21 | }; 22 | return image; 23 | } 24 | } 25 | 26 | #[derive(RustcEncodable, RustcDecodable)] 27 | pub struct ImageStatus { 28 | pub status: Option, 29 | pub error: Option 30 | } 31 | 32 | impl Clone for ImageStatus { 33 | fn clone(&self) -> Self { 34 | let image_status = ImageStatus { 35 | status: self.status.clone(), 36 | error: self.status.clone() 37 | }; 38 | return image_status; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/system.rs: -------------------------------------------------------------------------------- 1 | #[derive(RustcEncodable, RustcDecodable)] 2 | #[allow(non_snake_case)] 3 | pub struct SystemInfo { 4 | pub Containers: u64, 5 | pub Images: u64, 6 | pub Driver: String, 7 | pub DriverStatus: Vec<(String, String)>, 8 | pub ExecutionDriver: String, 9 | pub KernelVersion: String, 10 | pub NCPU: u64, 11 | pub MemTotal: u64, 12 | pub Name: String, 13 | pub ID: String, 14 | pub Debug: u64, // bool 15 | pub NFd: u64, 16 | pub NGoroutines: u64, 17 | pub NEventsListener: u64, 18 | pub InitPath: String, 19 | pub InitSha1: String, 20 | pub IndexServerAddress: String, 21 | pub MemoryLimit: u64, // bool 22 | pub SwapLimit: u64, // bool 23 | pub IPv4Forwarding: u64, // bool 24 | pub Labels: Option>, 25 | pub DockerRootDir: String, 26 | pub OperatingSystem: String, 27 | } 28 | 29 | impl Clone for SystemInfo { 30 | fn clone(&self) -> Self { 31 | let system_info = SystemInfo { 32 | Containers: self.Containers, 33 | Images: self.Images, 34 | Driver: self.Driver.clone(), 35 | DriverStatus: self.DriverStatus.clone(), 36 | ExecutionDriver: self.ExecutionDriver.clone(), 37 | KernelVersion: self.KernelVersion.clone(), 38 | NCPU: self.NCPU, 39 | MemTotal: self.MemTotal, 40 | Name: self.Name.clone(), 41 | ID: self.ID.clone(), 42 | Debug: self.Debug, 43 | NFd: self.NFd, 44 | NGoroutines: self.NGoroutines, 45 | NEventsListener: self.NEventsListener, 46 | InitPath: self.InitPath.clone(), 47 | InitSha1: self.InitSha1.clone(), 48 | IndexServerAddress: self.IndexServerAddress.clone(), 49 | MemoryLimit: self.MemoryLimit, 50 | SwapLimit: self.SwapLimit, 51 | IPv4Forwarding: self.IPv4Forwarding, 52 | Labels: self.Labels.clone(), 53 | DockerRootDir: self.DockerRootDir.clone(), 54 | OperatingSystem: self.OperatingSystem.clone() 55 | }; 56 | return system_info; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/unix.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::io::{Read, Write}; 3 | use std::error::Error; 4 | 5 | use unix_socket; 6 | 7 | pub struct UnixStream { 8 | stream: unix_socket::UnixStream 9 | } 10 | 11 | impl UnixStream { 12 | pub fn connect(addr: &str) -> std::io::Result { 13 | let stream = try!(unix_socket::UnixStream::connect(addr)); 14 | 15 | let unix_stream = UnixStream { 16 | stream: stream 17 | }; 18 | 19 | return Ok(unix_stream); 20 | } 21 | 22 | pub fn read(&mut self, buf: &[u8]) -> std::io::Result> { 23 | let mut stream = self.stream.try_clone().unwrap(); 24 | 25 | match stream.write_all(buf) { 26 | Ok(_) => {} 27 | Err(e) => { 28 | let err = std::io::Error::new(std::io::ErrorKind::ConnectionAborted, 29 | e.description()); 30 | return Err(err); 31 | } 32 | }; 33 | 34 | const BUFFER_SIZE: usize = 1024; 35 | let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; 36 | let mut raw: Vec = Vec::new(); 37 | loop { 38 | let len = match stream.read(&mut buffer) { 39 | Ok(len) => len, 40 | Err(e) => { 41 | let err = std::io::Error::new(std::io::ErrorKind::ConnectionAborted, 42 | e.description()); 43 | return Err(err); 44 | } 45 | }; 46 | 47 | for i in 0..len { raw.push(buffer[i]); } 48 | 49 | if len > 4 && buffer[len - 5] == 48 && buffer[len - 4] == 13 && buffer[len - 3] == 10 && buffer[len - 2] == 13 && buffer[len - 1] == 10 { break; } 50 | if len > 1 && buffer[len - 2] == 13 && buffer[len - 1] == 10 { continue; } 51 | if len < BUFFER_SIZE { break; } 52 | } 53 | return Ok(raw); 54 | } 55 | } 56 | 57 | impl Clone for UnixStream { 58 | fn clone(&self) -> Self { 59 | let stream = UnixStream { 60 | stream: self.stream.try_clone().unwrap() 61 | }; 62 | 63 | return stream; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Error; 2 | use std::fmt::{Display, Formatter}; 3 | 4 | pub struct Process { 5 | pub user: String, 6 | pub pid: String, 7 | pub cpu: Option, 8 | pub memory: Option, 9 | pub vsz: Option, 10 | pub rss: Option, 11 | pub tty: Option, 12 | pub stat: Option, 13 | pub start: Option, 14 | pub time: Option, 15 | pub command: String, 16 | } 17 | 18 | #[derive(RustcEncodable, RustcDecodable)] 19 | #[allow(non_snake_case)] 20 | pub struct Top { 21 | pub Titles: Vec, 22 | pub Processes: Vec> 23 | } 24 | 25 | impl Display for Process { 26 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 27 | let mut s = String::new(); 28 | 29 | s.push_str(&*self.user.clone()); 30 | 31 | s.push_str(","); 32 | s.push_str(&*self.pid.clone()); 33 | 34 | match self.cpu.clone() { 35 | Some(v) => { 36 | s.push_str(","); 37 | s.push_str(&*v); 38 | }, 39 | None => {} 40 | } 41 | 42 | match self.memory.clone() { 43 | Some(v) => { 44 | s.push_str(","); 45 | s.push_str(&*v); 46 | }, 47 | None => {} 48 | } 49 | 50 | match self.vsz.clone() { 51 | Some(v) => { 52 | s.push_str(","); 53 | s.push_str(&*v); 54 | }, 55 | None => {} 56 | } 57 | 58 | match self.rss.clone() { 59 | Some(v) => { 60 | s.push_str(","); 61 | s.push_str(&*v); 62 | }, 63 | None => {} 64 | } 65 | 66 | match self.tty.clone() { 67 | Some(v) => { 68 | s.push_str(","); 69 | s.push_str(&*v); 70 | }, 71 | None => {} 72 | } 73 | 74 | match self.stat.clone() { 75 | Some(v) => { 76 | s.push_str(","); 77 | s.push_str(&*v); 78 | }, 79 | None => {} 80 | } 81 | 82 | match self.start.clone() { 83 | Some(v) => { 84 | s.push_str(","); 85 | s.push_str(&*v); 86 | }, 87 | None => {} 88 | } 89 | 90 | match self.time.clone() { 91 | Some(v) => { 92 | s.push_str(","); 93 | s.push_str(&*v); 94 | }, 95 | None => {} 96 | } 97 | 98 | s.push_str(","); 99 | s.push_str(&*self.command.clone()); 100 | 101 | write!(f, "{}", s) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::io::{Result, Error, ErrorKind}; 3 | use std::collections::HashMap; 4 | 5 | pub struct Response { 6 | pub http_version: String, 7 | pub status_code: u16, 8 | pub headers: HashMap, 9 | pub body: Vec 10 | } 11 | 12 | pub fn get_response(raw: &Vec) -> std::io::Result { 13 | let mut headers_raw: Vec = Vec::new(); 14 | let mut body_raw: Vec = Vec::new(); 15 | let mut body_index: usize = 0; 16 | 17 | // headers 18 | for i in 0..raw.len() { 19 | if i + 3 >= raw.len() { break; } 20 | 21 | if raw[i] == 13 && raw[i+1] == 10 && raw[i+2] == 13 && raw[i+3] == 10 { // CRLFCRLF 22 | body_index = i + 4; 23 | if body_index >= raw.len() { body_index = raw.len() - 1; } 24 | break; 25 | } 26 | 27 | headers_raw.push(raw[i]); 28 | } 29 | 30 | // body 31 | for i in body_index..raw.len() { 32 | body_raw.push(raw[i]); 33 | } 34 | 35 | let headers = match String::from_utf8(headers_raw) { 36 | Ok(headers) => headers, 37 | Err(_) => { 38 | let err = Error::new(ErrorKind::InvalidInput, 39 | "Docker returns an invalid http response."); 40 | return Err(err); 41 | } 42 | }; 43 | 44 | let mut http_version = String::new(); 45 | let mut status_code: u16 = 0; 46 | let mut headers_map: HashMap = HashMap::new(); 47 | let mut index: usize = 0; 48 | 49 | let lines: Vec<&str> = headers.split("\r\n").collect(); 50 | for line in lines.iter() { 51 | index += 1; 52 | 53 | if index == 1 { 54 | let items: Vec<&str> = line.split(" ").collect(); 55 | if items.len() < 2 { continue; } 56 | http_version = items[0].to_string(); 57 | status_code = match std::str::FromStr::from_str(items[1]) { 58 | Ok(i) => i, 59 | Err(_) => 0 60 | }; 61 | continue; 62 | } 63 | 64 | let items: Vec<&str> = line.split(": ").collect(); 65 | if items.len() != 2 { continue; } 66 | let key = items[0].to_string(); 67 | let value = items[1].to_string(); 68 | headers_map.insert(key, value); 69 | } 70 | 71 | let response = Response{ 72 | http_version: http_version, 73 | status_code: status_code, 74 | headers: headers_map, 75 | body: body_raw 76 | }; 77 | 78 | return Ok(response); 79 | } 80 | 81 | impl Response { 82 | pub fn get_encoded_body(&self) -> Result { 83 | let encoded_body = match String::from_utf8(self.body.clone()) { 84 | Ok(headers) => headers, 85 | Err(_) => { 86 | let err = Error::new(ErrorKind::InvalidInput, 87 | "Docker returns an invalid http response."); 88 | return Err(err); 89 | } 90 | }; 91 | 92 | let chunked_body: Vec<&str> = encoded_body.split("\r\n").collect(); 93 | if chunked_body.len() == 1 { // not chunked 94 | return Ok(encoded_body.clone()); 95 | } else { // chunked 96 | let mut chunks = String::new(); 97 | let mut index: i64 = 0; 98 | for chunk in chunked_body.iter() { 99 | if chunk.len() == 0 { continue; } 100 | index = index + 1; 101 | if index % 2 != 0 { continue; } 102 | chunks.push_str(chunk); 103 | } 104 | return Ok(chunks); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/container.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::error::Error; 3 | use std::collections::HashMap; 4 | 5 | #[derive(RustcEncodable, RustcDecodable)] 6 | #[allow(non_snake_case)] 7 | //Labels, HostConfig 8 | pub struct Container { 9 | pub Id: String, 10 | pub Image: String, 11 | pub Status: String, 12 | pub Command: String, 13 | pub Created: u64, 14 | pub Names: Vec, 15 | pub Ports: Vec, 16 | pub SizeRw: Option, // I guess it is optional on Mac. 17 | pub SizeRootFs: u64, 18 | pub Labels: Option>, 19 | pub HostConfig: HostConfig 20 | } 21 | 22 | #[derive(RustcEncodable, RustcDecodable)] 23 | #[allow(non_snake_case)] 24 | pub struct Port { 25 | pub IP: Option, 26 | pub PrivatePort: u64, 27 | pub PublicPort: Option, 28 | pub Type: String 29 | } 30 | 31 | #[derive(RustcEncodable, RustcDecodable)] 32 | #[allow(non_snake_case)] 33 | pub struct HostConfig { 34 | pub NetworkMode: String 35 | } 36 | 37 | #[derive(RustcEncodable, RustcDecodable)] 38 | #[allow(non_snake_case)] 39 | pub struct ContainerInfo { 40 | pub AppArmorProfile: String, 41 | pub Args: Vec, 42 | // Config 43 | pub Created: String, 44 | pub Driver: String, 45 | pub ExecDriver: String, 46 | // ExecIDs 47 | // HostConfig 48 | pub HostnamePath: String, 49 | pub HostsPath: String, 50 | pub LogPath: String, 51 | pub Id: String, 52 | pub Image: String, 53 | pub MountLabel: String, 54 | pub Name: String, 55 | // NetworkSettings 56 | pub Path: String, 57 | pub ProcessLabel: String, 58 | pub ResolvConfPath: String, 59 | pub RestartCount: u64, 60 | // State 61 | pub Volumes: HashMap, 62 | pub VolumesRW: HashMap 63 | } 64 | 65 | impl Clone for Container { 66 | fn clone(&self) -> Self { 67 | let container = Container { 68 | Id: self.Id.clone(), 69 | Image: self.Image.clone(), 70 | Status: self.Status.clone(), 71 | Command: self.Command.clone(), 72 | Created: self.Created.clone(), 73 | Names: self.Names.clone(), 74 | Ports: self.Ports.clone(), 75 | SizeRw: self.SizeRw, 76 | SizeRootFs: self.SizeRootFs, 77 | Labels: self.Labels.clone(), 78 | HostConfig: self.HostConfig.clone() 79 | }; 80 | 81 | return container; 82 | } 83 | } 84 | 85 | impl std::fmt::Display for Container { 86 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { 87 | write!(f, "{}", self.Id) 88 | } 89 | } 90 | 91 | impl std::clone::Clone for Port { 92 | fn clone(&self) -> Self { 93 | let port = Port { 94 | IP: self.IP.clone(), 95 | PrivatePort: self.PrivatePort.clone(), 96 | PublicPort: self.PublicPort.clone(), 97 | Type: self.Type.clone() 98 | }; 99 | return port; 100 | } 101 | } 102 | 103 | impl Clone for HostConfig { 104 | fn clone(&self) -> Self { 105 | let host_config = HostConfig { 106 | NetworkMode: self.NetworkMode.clone() 107 | }; 108 | return host_config; 109 | } 110 | } 111 | 112 | impl Clone for ContainerInfo { 113 | fn clone(&self) -> Self { 114 | let container_info = ContainerInfo { 115 | AppArmorProfile: self.AppArmorProfile.clone(), 116 | Args: self.Args.clone(), 117 | // Config 118 | Created: self.Created.clone(), 119 | Driver: self.Driver.clone(), 120 | ExecDriver: self.ExecDriver.clone(), 121 | // ExecIDs 122 | // HostConfig 123 | HostnamePath: self.HostnamePath.clone(), 124 | HostsPath: self.HostsPath.clone(), 125 | LogPath: self.LogPath.clone(), 126 | Id: self.Id.clone(), 127 | Image: self.Image.clone(), 128 | MountLabel: self.MountLabel.clone(), 129 | Name: self.Name.clone(), 130 | // NetworkSettings 131 | Path: self.Path.clone(), 132 | ProcessLabel: self.ProcessLabel.clone(), 133 | ResolvConfPath: self.ResolvConfPath.clone(), 134 | RestartCount: self.RestartCount, 135 | // State 136 | Volumes: self.Volumes.clone(), 137 | VolumesRW: self.VolumesRW.clone() 138 | }; 139 | return container_info; 140 | } 141 | } 142 | 143 | impl std::fmt::Display for ContainerInfo { 144 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { 145 | write!(f, "{}", self.Id) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/tcp.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::io::{Read, Write}; 3 | use std::path::Path; 4 | use std::error::Error; 5 | 6 | use openssl; 7 | 8 | pub struct TcpStream { 9 | tls: bool, 10 | stream: std::net::TcpStream, 11 | ssl_stream: Option> 12 | } 13 | 14 | impl TcpStream { 15 | pub fn connect(addr: &str) -> std::io::Result { 16 | let stream = try!(std::net::TcpStream::connect(addr)); 17 | 18 | let tcp_stream = TcpStream { 19 | tls: false, 20 | stream: stream, 21 | ssl_stream: None, 22 | }; 23 | 24 | return Ok(tcp_stream); 25 | } 26 | 27 | pub fn set_ssl_context(&mut self, key: &Path, cert: &Path, ca: &Path) -> std::io::Result<()> { 28 | self.tls = true; 29 | 30 | let mut context = match openssl::ssl::SslContext::new(openssl::ssl::SslMethod::Tlsv1) { 31 | Ok(context) => context, 32 | Err(e) => { 33 | let err = std::io::Error::new(std::io::ErrorKind::NotConnected, 34 | e.description()); 35 | return Err(err); 36 | } 37 | }; 38 | 39 | match context.set_private_key_file(key, openssl::x509::X509FileType::PEM) { 40 | Ok(_) => {} 41 | Err(e) => { 42 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 43 | e.description()); 44 | return Err(err); 45 | } 46 | } 47 | 48 | match context.set_certificate_file(cert, openssl::x509::X509FileType::PEM) { 49 | Ok(_) => {} 50 | Err(e) => { 51 | let err = std::io::Error::new(std::io::ErrorKind::NotConnected, 52 | e.description()); 53 | return Err(err); 54 | } 55 | } 56 | 57 | match context.set_CA_file(ca) { 58 | Ok(_) => {} 59 | Err(e) => { 60 | let err = std::io::Error::new(std::io::ErrorKind::NotConnected, 61 | e.description()); 62 | return Err(err); 63 | } 64 | } 65 | 66 | let stream = self.stream.try_clone().unwrap(); 67 | let ssl_stream = match openssl::ssl::SslStream::new(&context, stream) { 68 | Ok(ssl_stream) => ssl_stream, 69 | Err(e) => { 70 | let err = std::io::Error::new(std::io::ErrorKind::NotConnected, 71 | e.description()); 72 | return Err(err); 73 | } 74 | }; 75 | 76 | self.ssl_stream = Some(ssl_stream); 77 | 78 | return Ok(()); 79 | } 80 | 81 | pub fn read(&mut self, buf: &[u8]) -> std::io::Result> { 82 | let raw = match self.tls { 83 | false => { 84 | let mut stream = self.stream.try_clone().unwrap(); 85 | let _ = stream.write(buf); 86 | let raw = try!(TcpStream::read_from_stream(&mut stream)); 87 | raw 88 | } 89 | true => { 90 | let mut ssl_stream = self.ssl_stream.as_mut().unwrap().try_clone().unwrap(); 91 | let _ = ssl_stream.write(buf); 92 | let raw = try!(TcpStream::read_from_stream(&mut ssl_stream)); 93 | raw 94 | } 95 | }; 96 | return Ok(raw); 97 | } 98 | 99 | fn read_from_stream(stream: &mut S) -> std::io::Result> { 100 | const BUFFER_SIZE: usize = 1024; 101 | let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; 102 | let mut raw: Vec = Vec::new(); 103 | let mut is_shaked = false; 104 | loop { 105 | let len = match stream.read(&mut buffer) { 106 | Ok(size) => size, 107 | Err(e) => { 108 | let err = std::io::Error::new(std::io::ErrorKind::NotConnected, 109 | e.description()); 110 | return Err(err); 111 | } 112 | }; 113 | 114 | if len == 4 && 115 | buffer[0] == 13 && 116 | buffer[1] == 10 && 117 | buffer[2] == 13 && 118 | buffer[3] == 10 { break; } 119 | 120 | for i in 0..len { raw.push(buffer[i]); } 121 | 122 | if len > 1 && buffer[len - 2] == 13 && buffer[len - 1] == 10 { is_shaked = false; continue; } 123 | if is_shaked == false && len <= BUFFER_SIZE { is_shaked = true; continue; } 124 | if len < BUFFER_SIZE { break; } 125 | } 126 | 127 | return Ok(raw); 128 | } 129 | } 130 | 131 | impl Clone for TcpStream { 132 | fn clone(&self) -> Self { 133 | let ssl_stream = match self.ssl_stream { 134 | Some(ref ssl_stream) => { 135 | Some(ssl_stream.try_clone().unwrap()) 136 | } 137 | None => None 138 | }; 139 | 140 | let stream = TcpStream { 141 | tls: self.tls.clone(), 142 | stream: self.stream.try_clone().unwrap(), 143 | ssl_stream: ssl_stream 144 | }; 145 | 146 | return stream; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker 2 | 3 | [![Build Status](https://travis-ci.org/ghmlee/rust-docker.svg)](https://travis-ci.org/ghmlee/rust-docker) 4 | 5 | This is a Docker Remote API binding in Rust. Documentation is available [here](https://ghmlee.github.io/rust-docker/doc/docker). 6 | 7 | ## Quick start 8 | 9 | ``` 10 | [dependencies] 11 | docker = "0.0.41" 12 | ``` 13 | 14 | ```rust 15 | extern crate docker; 16 | 17 | use docker::Docker; 18 | 19 | fn main() { 20 | let docker = match Docker::connect("unix:///var/run/docker.sock") { 21 | Ok(docker) => docker, 22 | Err(e) => { panic!("{}", e); } 23 | }; 24 | } 25 | ``` 26 | 27 | ## Debug 28 | * OpenSSL (>= v1.0.0) 29 | * Rust (>= v1.4.0) 30 | * Docker (>= v1.5.0) 31 | 32 | ### OpenSSL 33 | 34 | #### Mac OS X 35 | ```bash 36 | brew install openssl 37 | brew link --force openssl 38 | 39 | export OPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include 40 | export OPENSSL_ROOT_DIR=/usr/local/opt/openssl 41 | ``` 42 | 43 | ## Examples 44 | 45 | ### Containers 46 | 47 | ```rust 48 | extern crate docker; 49 | 50 | use docker::Docker; 51 | 52 | fn main() { 53 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 54 | Ok(docker) => docker, 55 | Err(e) => { panic!("{}", e); } 56 | }; 57 | 58 | let containers = match docker.get_containers(false) { 59 | Ok(containers) => containers, 60 | Err(e) => { panic!("{}", e); } 61 | }; 62 | } 63 | ``` 64 | 65 | ### Stats 66 | 67 | ```rust 68 | extern crate docker; 69 | 70 | use docker::Docker; 71 | 72 | fn main() { 73 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 74 | Ok(docker) => docker, 75 | Err(e) => { panic!("{}", e); } 76 | }; 77 | 78 | let containers = match docker.get_containers(false) { 79 | Ok(containers) => containers, 80 | Err(e) => { panic!("{}", e); } 81 | }; 82 | 83 | let stats = match docker.get_stats(&containers[0]) { 84 | Ok(stats) => stats, 85 | Err(e) => { panic!("{}", e); } 86 | }; 87 | } 88 | ``` 89 | 90 | ### Images 91 | 92 | ```rust 93 | extern crate docker; 94 | 95 | use docker::Docker; 96 | 97 | fn main() { 98 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 99 | Ok(docker) => docker, 100 | Err(e) => { panic!("{}", e); } 101 | }; 102 | 103 | let images = match docker.get_images(false) { 104 | Ok(images) => images, 105 | Err(e) => { panic!({}, e); } 106 | }; 107 | } 108 | 109 | ``` 110 | 111 | ### Info 112 | 113 | ```rust 114 | extern crate docker; 115 | 116 | use docker::Docker; 117 | 118 | fn main() { 119 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 120 | Ok(docker) => docker, 121 | Err(e) => { panic!("{}", e); } 122 | }; 123 | 124 | let info = match docker.get_system_info() { 125 | Ok(info) => info, 126 | Err(e) => { panic!("{}", e); } 127 | }; 128 | } 129 | ``` 130 | 131 | ### Processes 132 | 133 | ```rust 134 | extern crate docker; 135 | 136 | use docker::Docker; 137 | 138 | fn main() { 139 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 140 | Ok(docker) => docker, 141 | Err(e) => { panic!("{}", e); } 142 | }; 143 | 144 | let containers = match docker.get_containers(false) { 145 | Ok(containers) => containers, 146 | Err(e) => { panic!("{}", e); } 147 | }; 148 | 149 | let processes = match docker.get_processes(&containers[0]) { 150 | Ok(processes) => processes, 151 | Err(e) => { panic!("{}", e); } 152 | }; 153 | } 154 | ``` 155 | 156 | ### Filesystem changes 157 | 158 | ```rust 159 | extern crate docker; 160 | 161 | use docker::Docker; 162 | 163 | fn main() { 164 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 165 | Ok(docker) => docker, 166 | Err(e) => { panic!("{}", e); } 167 | }; 168 | 169 | let containers = match docker.get_containers(false) { 170 | Ok(containers) => containers, 171 | Err(e) => { panic!("{}", e); } 172 | }; 173 | 174 | let changes = match docker.get_filesystem_changes(&containers[0]) { 175 | Ok(changes) => changes, 176 | Err(e) => { panic!("{}", e); } 177 | }; 178 | } 179 | ``` 180 | 181 | ### Export a container 182 | 183 | ```rust 184 | extern crate docker; 185 | 186 | use docker::Docker; 187 | 188 | fn main() { 189 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 190 | Ok(docker) => docker, 191 | Err(e) => { panic!("{}", e); } 192 | }; 193 | 194 | let containers = match docker.get_containers(false) { 195 | Ok(containers) => containers, 196 | Err(e) => { panic!("{}", e); } 197 | }; 198 | 199 | let bytes = match docker.export_container(&containers[0]) { 200 | Ok(bytes) => bytes, 201 | Err(e) => { panic!("{}", e); } 202 | }; 203 | } 204 | ``` 205 | 206 | ### Create an image 207 | 208 | ```rust 209 | extern crate docker; 210 | 211 | use docker::Docker; 212 | 213 | fn main() { 214 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 215 | Ok(docker) => docker, 216 | Err(e) => { panic!("{}", e); } 217 | }; 218 | 219 | let image = "debian".to_string(); 220 | let tag = "latest".to_string(); 221 | 222 | let statuses = match docker.create_image(image, tag) { 223 | Ok(statuses) => statuses, 224 | Err(e) => { panic!("{}", e); } 225 | }; 226 | 227 | match statuses.last() { 228 | Some(last) => { 229 | println!("{}", last.clone().status.unwrap()); 230 | } 231 | None => { println!("none"); } 232 | } 233 | } 234 | ``` 235 | 236 | ### Ping the docker server 237 | 238 | ```rust 239 | extern crate docker; 240 | 241 | use docker::Docker; 242 | 243 | fn main() { 244 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 245 | Ok(docker) => docker, 246 | Err(e) => { panic!("{}", e); } 247 | }; 248 | 249 | let ping = match docker.ping() { 250 | Ok(ping) => ping, 251 | Err(e) => { panic!("{}", e); } 252 | }; 253 | } 254 | ``` 255 | 256 | ### Show the docker version information 257 | 258 | ```rust 259 | extern crate docker; 260 | 261 | use docker::Docker; 262 | 263 | fn main() { 264 | let mut docker = match Docker::connect("unix:///var/run/docker.sock") { 265 | Ok(docker) => docker, 266 | Err(e) => { panic!("{}", e); } 267 | }; 268 | 269 | let version = match docker.get_version() { 270 | Ok(version) => version, 271 | Err(e) => {panic!("{}",e)} 272 | }; 273 | } 274 | ``` 275 | 276 | ## Docker Toolbox 277 | 278 | By default, `Docker Toolbox` runs `docker` with TLS enabled. It auto-generates certificates. The `docker-machine` will copy them to `~/.docker/machine/certs` on the host machine once the VM has started. 279 | 280 | ### Example 281 | 282 | ```rust 283 | extern crate docker; 284 | 285 | use docker::Docker; 286 | use std::path::Path; 287 | 288 | fn main() { 289 | let key = Path::new("/Users//.docker/machine/certs/key.pem"); 290 | let cert = Path::new("/Users//.docker/machine/certs/cert.pem"); 291 | let ca = Path::new("/Users//.docker/machine/certs/ca.pem"); 292 | 293 | let mut docker = match Docker::connect("tcp://192.168.99.100:2376") { 294 | Ok(docker) => docker, 295 | Err(e) => { panic!("{}", e); } 296 | }; 297 | docker.set_tls(&key, &cert, &ca).unwrap(); 298 | } 299 | ``` 300 | 301 | ## Contributing 302 | 303 | 1. Fork it 304 | 2. Create your a new remote upstream repository (`git remote add upstream git@github.com:ghmlee/rust-docker.git`) 305 | 3. Commit your changes (`git commit -m 'Add some feature'`) 306 | 4. Push to the branch (`git push origin your-branch`) 307 | 5. Create new Pull Request -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | #[derive(RustcEncodable, RustcDecodable)] 2 | pub struct Stats { 3 | pub read: String, 4 | pub network: Network, 5 | pub memory_stats: MemoryStats, 6 | pub cpu_stats: CpuStats, 7 | pub blkio_stats: BlkioStats 8 | } 9 | 10 | #[derive(RustcEncodable, RustcDecodable)] 11 | pub struct Network { 12 | pub rx_dropped: u64, 13 | pub rx_bytes: u64, 14 | pub rx_errors: u64, 15 | pub tx_packets: u64, 16 | pub tx_dropped: u64, 17 | pub rx_packets: u64, 18 | pub tx_errors: u64, 19 | pub tx_bytes: u64 20 | } 21 | 22 | #[derive(RustcEncodable, RustcDecodable)] 23 | pub struct MemoryStats { 24 | pub max_usage: u64, 25 | pub usage: u64, 26 | pub failcnt: u64, 27 | pub limit: u64, 28 | pub stats: MemoryStat 29 | } 30 | 31 | #[derive(RustcEncodable, RustcDecodable)] 32 | pub struct MemoryStat { 33 | pub total_pgmajfault: u64, 34 | pub cache: u64, 35 | pub mapped_file: u64, 36 | pub total_inactive_file: u64, 37 | pub pgpgout: u64, 38 | pub rss: u64, 39 | pub total_mapped_file: u64, 40 | pub writeback: u64, 41 | pub unevictable: u64, 42 | pub pgpgin: u64, 43 | pub total_unevictable: u64, 44 | pub pgmajfault: u64, 45 | pub total_rss: u64, 46 | pub total_rss_huge: u64, 47 | pub total_writeback: u64, 48 | pub total_inactive_anon: u64, 49 | pub rss_huge: u64, 50 | pub hierarchical_memory_limit: u64, 51 | pub hierarchical_memsw_limit: u64, 52 | pub total_pgfault: u64, 53 | pub total_active_file: u64, 54 | pub active_anon: u64, 55 | pub total_active_anon: u64, 56 | pub total_pgpgout: u64, 57 | pub total_cache: u64, 58 | pub inactive_anon: u64, 59 | pub active_file: u64, 60 | pub pgfault: u64, 61 | pub inactive_file: u64, 62 | pub total_pgpgin: u64, 63 | pub swap: u64, 64 | pub total_swap: u64 65 | } 66 | 67 | #[derive(RustcEncodable, RustcDecodable)] 68 | pub struct CpuStats { 69 | pub cpu_usage: CpuUsage, 70 | pub system_cpu_usage: u64, 71 | pub throttling_data: ThrottlingData 72 | } 73 | 74 | #[derive(RustcEncodable, RustcDecodable)] 75 | pub struct CpuUsage { 76 | pub percpu_usage: Vec, 77 | pub usage_in_usermode: u64, 78 | pub total_usage: u64, 79 | pub usage_in_kernelmode: u64 80 | } 81 | 82 | #[derive(RustcEncodable, RustcDecodable)] 83 | pub struct ThrottlingData { 84 | pub periods: u64, 85 | pub throttled_periods: u64, 86 | pub throttled_time: u64 87 | } 88 | 89 | #[derive(RustcEncodable, RustcDecodable)] 90 | pub struct BlkioStats { 91 | pub io_service_bytes_recursive: Vec, 92 | pub io_serviced_recursive: Vec, 93 | pub io_queue_recursive: Vec, 94 | pub io_service_time_recursive: Vec, 95 | pub io_wait_time_recursive: Vec, 96 | pub io_merged_recursive: Vec, 97 | pub io_time_recursive: Vec, 98 | pub sectors_recursive: Vec 99 | } 100 | 101 | #[derive(RustcEncodable, RustcDecodable)] 102 | pub struct BlkioStat { 103 | pub major: u64, 104 | pub minor: u64, 105 | pub op: String, 106 | pub value: u64 107 | } 108 | 109 | impl Clone for Stats { 110 | fn clone(&self) -> Stats { 111 | let stats = Stats { 112 | read: self.read.clone(), 113 | network: self.network.clone(), 114 | memory_stats: self.memory_stats.clone(), 115 | cpu_stats: self.cpu_stats.clone(), 116 | blkio_stats: self.blkio_stats.clone() 117 | }; 118 | return stats; 119 | } 120 | } 121 | 122 | impl Clone for Network { 123 | fn clone(&self) -> Self { 124 | let network = Network { 125 | rx_dropped: self.rx_dropped, 126 | rx_bytes: self.rx_bytes, 127 | rx_errors: self.rx_errors, 128 | tx_packets: self.tx_packets, 129 | tx_dropped: self.tx_dropped, 130 | rx_packets: self.rx_packets, 131 | tx_errors: self.tx_errors, 132 | tx_bytes: self.tx_bytes 133 | }; 134 | return network; 135 | } 136 | } 137 | 138 | impl Clone for MemoryStats { 139 | fn clone(&self) -> Self { 140 | let memory_stats = MemoryStats { 141 | max_usage: self.max_usage, 142 | usage: self.usage, 143 | failcnt: self.failcnt, 144 | limit: self.limit, 145 | stats: self.stats.clone() 146 | }; 147 | return memory_stats; 148 | } 149 | } 150 | 151 | impl Clone for MemoryStat { 152 | fn clone(&self) -> Self { 153 | let memory_stat = MemoryStat { 154 | total_pgmajfault: self.total_pgmajfault, 155 | cache: self.cache, 156 | mapped_file: self.mapped_file, 157 | total_inactive_file: self.total_inactive_file, 158 | pgpgout: self.pgpgout, 159 | rss: self.rss, 160 | total_mapped_file: self.total_mapped_file, 161 | writeback: self.writeback, 162 | unevictable: self.unevictable, 163 | pgpgin: self.pgpgin, 164 | total_unevictable: self.total_unevictable, 165 | pgmajfault: self.pgmajfault, 166 | total_rss: self.total_rss, 167 | total_rss_huge: self.total_rss_huge, 168 | total_writeback: self.total_writeback, 169 | total_inactive_anon: self.total_inactive_anon, 170 | rss_huge: self.rss_huge, 171 | hierarchical_memory_limit: self.hierarchical_memory_limit, 172 | hierarchical_memsw_limit: self.hierarchical_memsw_limit, 173 | total_pgfault: self.total_pgfault, 174 | total_active_file: self.total_active_file, 175 | active_anon: self.active_anon, 176 | total_active_anon: self.total_active_anon, 177 | total_pgpgout: self.total_pgpgout, 178 | total_cache: self.total_cache, 179 | inactive_anon: self.inactive_anon, 180 | active_file: self.active_file, 181 | pgfault: self.pgfault, 182 | inactive_file: self.inactive_file, 183 | total_pgpgin: self.total_pgpgin, 184 | swap: self.swap, 185 | total_swap: self.total_swap 186 | }; 187 | return memory_stat; 188 | } 189 | } 190 | 191 | impl Clone for CpuStats { 192 | fn clone(&self) -> Self { 193 | let cpu_stats = CpuStats { 194 | cpu_usage: self.cpu_usage.clone(), 195 | system_cpu_usage: self.system_cpu_usage, 196 | throttling_data: self.throttling_data.clone() 197 | }; 198 | return cpu_stats; 199 | } 200 | } 201 | 202 | impl Clone for CpuUsage { 203 | fn clone(&self) -> Self { 204 | let cpu_usage = CpuUsage { 205 | percpu_usage: self.percpu_usage.clone(), 206 | usage_in_usermode: self.usage_in_usermode, 207 | total_usage: self.total_usage, 208 | usage_in_kernelmode: self.usage_in_kernelmode 209 | }; 210 | return cpu_usage; 211 | } 212 | } 213 | 214 | impl Clone for ThrottlingData { 215 | fn clone(&self) -> Self { 216 | let throttling_data = ThrottlingData { 217 | periods: self.periods, 218 | throttled_periods: self.throttled_periods, 219 | throttled_time: self.throttled_time 220 | }; 221 | return throttling_data; 222 | } 223 | } 224 | 225 | impl Clone for BlkioStats { 226 | fn clone(&self) -> Self { 227 | let blkio_stats = BlkioStats { 228 | io_service_bytes_recursive: self.io_service_bytes_recursive.clone(), 229 | io_serviced_recursive: self.io_serviced_recursive.clone(), 230 | io_queue_recursive: self.io_queue_recursive.clone(), 231 | io_service_time_recursive: self.io_service_time_recursive.clone(), 232 | io_wait_time_recursive: self.io_wait_time_recursive.clone(), 233 | io_merged_recursive: self.io_merged_recursive.clone(), 234 | io_time_recursive: self.io_time_recursive.clone(), 235 | sectors_recursive: self.sectors_recursive.clone() 236 | }; 237 | return blkio_stats; 238 | } 239 | } 240 | 241 | impl Clone for BlkioStat { 242 | fn clone(&self) -> Self { 243 | let blkio_stat = BlkioStat { 244 | major: self.major, 245 | minor: self.minor, 246 | op: self.op.clone(), 247 | value: self.value 248 | }; 249 | return blkio_stat; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/docker.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::error::Error; 3 | use std::path::Path; 4 | 5 | use tcp::TcpStream; 6 | use unix::UnixStream; 7 | use http::{self, Response}; 8 | 9 | use container::{Container, ContainerInfo}; 10 | use process::{Process, Top}; 11 | use stats::Stats; 12 | use system::SystemInfo; 13 | use image::{Image, ImageStatus}; 14 | use filesystem::FilesystemChange; 15 | use version::Version; 16 | 17 | use rustc_serialize::json; 18 | 19 | pub struct Docker { 20 | protocol: Protocol, 21 | unix_stream: Option, 22 | tcp_stream: Option, 23 | } 24 | 25 | enum Protocol { 26 | UNIX, 27 | TCP 28 | } 29 | 30 | impl Docker { 31 | pub fn connect(addr: &str) -> std::io::Result { 32 | let components: Vec<&str> = addr.split("://").collect(); 33 | if components.len() != 2 { 34 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 35 | "The address is invalid."); 36 | return Err(err); 37 | } 38 | 39 | let protocol = components[0]; 40 | let path = components[1].to_string(); 41 | 42 | let protocol = match protocol { 43 | "unix" => Protocol::UNIX, 44 | "tcp" => Protocol::TCP, 45 | _ => { 46 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 47 | "The protocol is not supported."); 48 | return Err(err); 49 | } 50 | }; 51 | 52 | let unix_stream = match protocol { 53 | Protocol::UNIX => { 54 | let stream = try!(UnixStream::connect(&*path)); 55 | Some(stream) 56 | } 57 | _ => None 58 | }; 59 | 60 | let tcp_stream = match protocol { 61 | Protocol::TCP => { 62 | let stream = try!(TcpStream::connect(&*path)); 63 | Some(stream) 64 | } 65 | _ => None 66 | }; 67 | 68 | let docker = Docker { 69 | protocol: protocol, 70 | unix_stream: unix_stream, 71 | tcp_stream: tcp_stream 72 | }; 73 | return Ok(docker); 74 | } 75 | 76 | pub fn set_tls(&mut self, key: &Path, cert: &Path, ca: &Path) -> std::io::Result<()> { 77 | match self.tcp_stream { 78 | Some(_) => { 79 | let mut tcp_stream = self.tcp_stream.as_mut().unwrap(); 80 | try!(tcp_stream.set_ssl_context(key, cert, ca)); 81 | } 82 | None => {} 83 | } 84 | 85 | return Ok(()); 86 | } 87 | 88 | // 89 | // Containers 90 | // 91 | 92 | pub fn get_containers(&mut self, all: bool) -> std::io::Result> { 93 | let a = match all { 94 | true => "1", 95 | false => "0" 96 | }; 97 | 98 | let request = format!("GET /containers/json?all={}&size=1 HTTP/1.1\r\n\r\n", a); 99 | let raw = try!(self.read(request.as_bytes())); 100 | let response = try!(self.get_response(&raw)); 101 | try!(self.get_status_code(&response)); 102 | let body = try!(response.get_encoded_body()); 103 | 104 | let containers: Vec = match json::decode(&body) { 105 | Ok(containers) => containers, 106 | Err(e) => { 107 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 108 | e.description()); 109 | return Err(err); 110 | } 111 | }; 112 | 113 | return Ok(containers); 114 | } 115 | 116 | pub fn get_processes(&mut self, container: &Container) -> std::io::Result> { 117 | let request = format!("GET /containers/{}/top HTTP/1.1\r\n\r\n", container.Id); 118 | let raw = try!(self.read(request.as_bytes())); 119 | let response = try!(self.get_response(&raw)); 120 | try!(self.get_status_code(&response)); 121 | let body = try!(response.get_encoded_body()); 122 | 123 | let top: Top = match json::decode(&body) { 124 | Ok(top) => top, 125 | Err(e) => { 126 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 127 | e.description()); 128 | return Err(err); 129 | } 130 | }; 131 | 132 | let mut processes: Vec = Vec::new(); 133 | let mut process_iter = top.Processes.iter(); 134 | loop { 135 | let process = match process_iter.next() { 136 | Some(process) => process, 137 | None => { break; } 138 | }; 139 | 140 | let mut p = Process{ 141 | user: String::new(), 142 | pid: String::new(), 143 | cpu: None, 144 | memory: None, 145 | vsz: None, 146 | rss: None, 147 | tty: None, 148 | stat: None, 149 | start: None, 150 | time: None, 151 | command: String::new() 152 | }; 153 | 154 | let mut value_iter = process.iter(); 155 | let mut i: usize = 0; 156 | loop { 157 | let value = match value_iter.next() { 158 | Some(value) => value, 159 | None => { break; } 160 | }; 161 | let key = &top.Titles[i]; 162 | match key.as_ref() { 163 | "USER" => { p.user = value.clone() }, 164 | "PID" => { p.pid = value.clone() }, 165 | "%CPU" => { p.cpu = Some(value.clone()) }, 166 | "%MEM" => { p.memory = Some(value.clone()) }, 167 | "VSZ" => { p.vsz = Some(value.clone()) }, 168 | "RSS" => { p.rss = Some(value.clone()) }, 169 | "TTY" => { p.tty = Some(value.clone()) }, 170 | "STAT" => { p.stat = Some(value.clone()) }, 171 | "START" => { p.start = Some(value.clone()) }, 172 | "TIME" => { p.time = Some(value.clone()) }, 173 | "COMMAND" => { p.command = value.clone() }, 174 | _ => {} 175 | } 176 | 177 | i = i + 1; 178 | }; 179 | 180 | processes.push(p); 181 | } 182 | 183 | return Ok(processes); 184 | } 185 | 186 | pub fn get_stats(&mut self, container: &Container) -> std::io::Result { 187 | if container.Status.contains("Up") == false { 188 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 189 | "The container is already stopped."); 190 | return Err(err); 191 | } 192 | 193 | let request = format!("GET /containers/{}/stats HTTP/1.1\r\n\r\n", container.Id); 194 | let raw = try!(self.read(request.as_bytes())); 195 | let response = try!(self.get_response(&raw)); 196 | try!(self.get_status_code(&response)); 197 | let body = try!(response.get_encoded_body()); 198 | 199 | let stats: Stats = match json::decode(&body) { 200 | Ok(stats) => stats, 201 | Err(e) => { 202 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 203 | e.description()); 204 | return Err(err); 205 | } 206 | }; 207 | return Ok(stats); 208 | } 209 | 210 | // 211 | // Image 212 | // 213 | 214 | pub fn create_image(&mut self, image: String, tag: String) -> std::io::Result> { 215 | let request = format!("POST /images/create?fromImage={}&tag={} HTTP/1.1\r\n\r\n", image, tag); 216 | let raw = try!(self.read(request.as_bytes())); 217 | let response = try!(self.get_response(&raw)); 218 | let body = format!("[{}]", try!(response.get_encoded_body())); 219 | let fixed = body.replace("}{", "},{"); 220 | 221 | let statuses: Vec = match json::decode(&fixed) { 222 | Ok(statuses) => statuses, 223 | Err(e) => { 224 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 225 | e.description()); 226 | return Err(err); 227 | } 228 | }; 229 | return Ok(statuses); 230 | } 231 | 232 | pub fn get_images(&mut self, all: bool) -> std::io::Result> { 233 | let a = match all { 234 | true => "1", 235 | false => "0" 236 | }; 237 | let request = format!("GET /images/json?all={} HTTP/1.1\r\n\r\n", a); 238 | let raw = try!(self.read(request.as_bytes())); 239 | let response = try!(self.get_response(&raw)); 240 | try!(self.get_status_code(&response)); 241 | let body = try!(response.get_encoded_body()); 242 | 243 | let images: Vec = match json::decode(&body) { 244 | Ok(images) => images, 245 | Err(e) => { 246 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 247 | e.description()); 248 | return Err(err); 249 | } 250 | }; 251 | return Ok(images); 252 | } 253 | 254 | pub fn get_system_info(&mut self) -> std::io::Result { 255 | let request = "GET /info HTTP/1.1\r\n\r\n"; 256 | let raw = try!(self.read(request.as_bytes())); 257 | let response = try!(self.get_response(&raw)); 258 | try!(self.get_status_code(&response)); 259 | let body = try!(response.get_encoded_body()); 260 | 261 | let info: SystemInfo = match json::decode(&body) { 262 | Ok(info) => info, 263 | Err(e) => { 264 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 265 | e.description()); 266 | return Err(err); 267 | } 268 | }; 269 | return Ok(info); 270 | } 271 | 272 | pub fn get_container_info(&mut self, container: &Container) -> std::io::Result { 273 | let request = format!("GET /containers/{}/json HTTP/1.1\r\n\r\n", container.Id); 274 | let raw = try!(self.read(request.as_bytes())); 275 | let response = try!(self.get_response(&raw)); 276 | try!(self.get_status_code(&response)); 277 | let body = try!(response.get_encoded_body()); 278 | 279 | let container_info: ContainerInfo = match json::decode(&body) { 280 | Ok(body) => body, 281 | Err(e) => { 282 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 283 | e.description()); 284 | return Err(err); 285 | } 286 | }; 287 | return Ok(container_info); 288 | } 289 | 290 | pub fn get_filesystem_changes(&mut self, container: &Container) -> std::io::Result> { 291 | let request = format!("GET /containers/{}/changes HTTP/1.1\r\n\r\n", container.Id); 292 | let raw = try!(self.read(request.as_bytes())); 293 | let response = try!(self.get_response(&raw)); 294 | try!(self.get_status_code(&response)); 295 | let body = try!(response.get_encoded_body()); 296 | 297 | let filesystem_changes: Vec = match json::decode(&body) { 298 | Ok(body) => body, 299 | Err(e) => { 300 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, 301 | e.description()); 302 | return Err(err); 303 | } 304 | }; 305 | return Ok(filesystem_changes); 306 | } 307 | 308 | pub fn export_container(&mut self, container: &Container) -> std::io::Result> { 309 | let request = format!("GET /containers/{}/export HTTP/1.1\r\n\r\n", container.Id); 310 | let raw = try!(self.read(request.as_bytes())); 311 | let response = try!(self.get_response(&raw)); 312 | try!(self.get_status_code(&response)); 313 | 314 | return Ok(response.body); 315 | } 316 | 317 | pub fn ping(&mut self) -> std::io::Result { 318 | let request = format!("GET /_ping HTTP/1.1\r\n\r\n"); 319 | let raw = try!(self.read(request.as_bytes())); 320 | let response = try!(self.get_response(&raw)); 321 | try!(self.get_status_code(&response)); 322 | let encoded_body = try!(response.get_encoded_body()); 323 | 324 | return Ok(encoded_body); 325 | } 326 | 327 | pub fn get_version(&mut self) -> std::io::Result { 328 | let request = format!("GET /version HTTP/1.1\r\n\r\n"); 329 | let raw = try!(self.read(request.as_bytes())); 330 | let response = try!(self.get_response(&raw)); 331 | try!(self.get_status_code(&response)); 332 | let encoded_body = try!(response.get_encoded_body()); 333 | 334 | let version: Version = match json::decode(&encoded_body){ 335 | Ok(body) => body, 336 | Err(e) => { 337 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, e.description()); 338 | return Err(err); 339 | } 340 | }; 341 | return Ok(version); 342 | } 343 | 344 | fn read(&mut self, buf: &[u8]) -> std::io::Result> { 345 | return match self.protocol { 346 | Protocol::UNIX => { 347 | let mut stream = self.unix_stream.as_mut().unwrap(); 348 | stream.read(buf) 349 | } 350 | Protocol::TCP => { 351 | let mut stream = self.tcp_stream.as_mut().unwrap(); 352 | stream.read(buf) 353 | } 354 | }; 355 | } 356 | 357 | fn get_response(&self, raw: &Vec) -> std::io::Result { 358 | http::get_response(raw) 359 | } 360 | 361 | fn get_status_code(&self, response: &Response) -> std::io::Result<()> { 362 | let status_code = response.status_code; 363 | match status_code / 100 { 364 | 2 => { Ok(()) } 365 | _ => { 366 | let desc = format!("Docker returns an error with {} status code.", status_code); 367 | let err = std::io::Error::new(std::io::ErrorKind::InvalidInput, desc); 368 | return Err(err); 369 | } 370 | } 371 | } 372 | } 373 | 374 | 375 | 376 | impl Clone for Docker { 377 | fn clone(&self)-> Self { 378 | let protocol = match self.protocol { 379 | Protocol::UNIX => Protocol::UNIX, 380 | Protocol::TCP => Protocol::TCP 381 | }; 382 | 383 | let unix_stream = match self.unix_stream { 384 | Some(ref unix_stream) => { 385 | Some(unix_stream.clone()) 386 | } 387 | None => None 388 | }; 389 | 390 | let tcp_stream = match self.tcp_stream { 391 | Some(ref tcp_stream) => { 392 | Some(tcp_stream.clone()) 393 | } 394 | None => None 395 | }; 396 | 397 | let docker = Docker { 398 | protocol: protocol, 399 | unix_stream: unix_stream, 400 | tcp_stream: tcp_stream 401 | }; 402 | return docker; 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use rustc_serialize::json; 3 | #[cfg(test)] 4 | use container::{Container, ContainerInfo}; 5 | #[cfg(test)] 6 | use process::{Top}; 7 | #[cfg(test)] 8 | use stats::Stats; 9 | #[cfg(test)] 10 | use system::SystemInfo; 11 | #[cfg(test)] 12 | use image::Image; 13 | #[cfg(test)] 14 | use filesystem::FilesystemChange; 15 | #[cfg(test)] 16 | use version::Version; 17 | 18 | #[test] 19 | #[cfg(test)] 20 | fn get_containers() { 21 | let response = get_containers_response(); 22 | let _: Vec = match json::decode(&response) { 23 | Ok(body) => body, 24 | Err(_) => { assert!(false); return; } 25 | }; 26 | } 27 | 28 | #[test] 29 | #[cfg(test)] 30 | fn get_stats() { 31 | let response = get_stats_response(); 32 | let _: Stats = match json::decode(&response) { 33 | Ok(body) => body, 34 | Err(_) => { assert!(false); return; } 35 | }; 36 | } 37 | 38 | #[test] 39 | #[cfg(test)] 40 | fn get_system_info() { 41 | let response = get_system_info_response(); 42 | let _: SystemInfo = match json::decode(&response) { 43 | Ok(body) => body, 44 | Err(_) => { assert!(false); return; } 45 | }; 46 | } 47 | 48 | #[test] 49 | #[cfg(test)] 50 | fn get_images() { 51 | let response = get_images_response(); 52 | let _: Vec = match json::decode(&response) { 53 | Ok(body) => body, 54 | Err(_) => { assert!(false); return; } 55 | }; 56 | } 57 | 58 | #[test] 59 | #[cfg(test)] 60 | fn get_container_info() { 61 | let response = get_container_info_response(); 62 | let _: ContainerInfo = match json::decode(&response) { 63 | Ok(body) => body, 64 | Err(_) => { assert!(false); return; } 65 | }; 66 | } 67 | 68 | #[test] 69 | #[cfg(test)] 70 | fn get_processes() { 71 | let response = get_processes_response(); 72 | let _: Top = match json::decode(&response) { 73 | Ok(body) => body, 74 | Err(_) => { assert!(false); return; } 75 | }; 76 | } 77 | 78 | #[test] 79 | #[cfg(test)] 80 | fn get_filesystem_changes() { 81 | let response = get_filesystem_changes_response(); 82 | let _: Vec = match json::decode(&response) { 83 | Ok(body) => body, 84 | Err(_) => { assert!(false); return; } 85 | }; 86 | } 87 | 88 | #[test] 89 | #[cfg(test)] 90 | fn get_version(){ 91 | let response = get_version_response(); 92 | let _: Version = match json::decode(&response) { 93 | Ok(body) => body, 94 | Err(_) => { assert!(false); return; } 95 | }; 96 | } 97 | 98 | #[cfg(test)] 99 | fn get_containers_response() -> String { 100 | return "[{\"Id\":\"ed3221f4adc05b9ecfbf56b1aa76d4e6e70d5b73b3876c322fc10d017c64ca86\",\"Names\":[\"/rust\"],\"Image\":\"ghmlee/rust:latest\",\"Command\":\"bash\",\"Created\":1439434052,\"Ports\":[{\"IP\":\"0.0.0.0\",\"PrivatePort\":8888,\"PublicPort\":8888,\"Type\":\"tcp\"}],\"SizeRootFs\":253602755,\"Labels\":{},\"Status\":\"Exited (137) 12 hours ago\",\"HostConfig\":{\"NetworkMode\":\"default\"},\"SizeRw\":10832473}]".to_string(); 101 | } 102 | 103 | #[cfg(test)] 104 | fn get_containers_response_long() -> String { 105 | return "[{\"Id\":\"2931241fd8d910316faaff849d906b0decfcd1ec123fff5153bcbae9f73a112e\",\"Names\":[\"/abdcdev_abdcctl_1\"],\"Image\":\"abcdefgh/abcdef-abcd-abdcctl:abcdefgh-v0.1.0\",\"ImageID\":\"96e385a9d743afd5b704a794f92560cce88aee8ab464018b1a84caf7b5d9d22b\",\"Command\":\"/w/w npm start\",\"Created\":1449220146,\"Ports\":[{\"PrivatePort\":3000,\"Type\":\"tcp\"},{\"IP\":\"127.0.0.1\",\"PrivatePort\":3100,\"PublicPort\":3100,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"dbb2feac5036db2f484344e8732207404b4d0ae22540cf43f4344fc17db71d90\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"abdcctl\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 2 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"8344036afa76f538144939b1b5c21535c0c297965c9edb1c50ded6c9de0d4e51\",\"Names\":[\"/abdcdev_abdcopsabcd_1\"],\"Image\":\"abcdefgh/abcdef-abcd-ops:abcdefgh-v0.1.0\",\"ImageID\":\"bfd4a651b7bb5bcdebe77fc68e2723194d40ad2beb9c3e33e4649f1b411fce9b\",\"Command\":\"/w/w npm start\",\"Created\":1449220138,\"Ports\":[{\"PrivatePort\":3000,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"210b124c721f2d3da1d0ca81217b2044e4185852a3759ff140d26067eca1a258\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"abdcopsabcd\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 2 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"eb4c97482c961833c5bc0ce7e727bf68c1d6e853f777da4ef32ad1da0cd00551\",\"Names\":[\"/abdcdev_abcdefadmin_1\"],\"Image\":\"abdcdev_abcdefadmin\",\"ImageID\":\"c2d23cb88f87426007f8179af74a6964a06b69d5911c4dab0e3e5b9acaabd6af\",\"Command\":\"/w/w npm start\",\"Created\":1449220133,\"Ports\":[{\"PrivatePort\":3000,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"408798c1a8a0ba71883180c19806262f411654c530e53f7d1c5f77f769e64e2e\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"abcdefadmin\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 2 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"b28afbd208625505fd68ab3814c4d14f7a1bd70db1a68d5ef8a2f0af046eed55\",\"Names\":[\"/abdcdev_abcdefctl_1\"],\"Image\":\"abcdefgh/abcdef-abcdefctl:abcdefgh-v0.1.3\",\"ImageID\":\"8dd0d7a8f2b161b423e3c66410ffb01c46cff76782e41e7b7633cced0ae696ef\",\"Command\":\"/usr/bin/abcdefctl supervise\",\"Created\":1449220030,\"Ports\":[],\"Labels\":{\"com.docker.compose.config-hash\":\"454b184fb1d39a1716f888389d4932c6a583c28f895cbc77c1b8d2b291910219\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"abcdefctl\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\"},\"Status\":\"Up 2 hours\",\"HostConfig\":{\"NetworkMode\":\"host\"}},{\"Id\":\"552b7f8975fe4306749eb9e058366e7001c34523b776c5200c366295a8e12f31\",\"Names\":[\"/weaveproxy\"],\"Image\":\"weaveworks/weaveexec:1.3.1\",\"ImageID\":\"619d88f027004f82d23c1bd2a93636cde7e9dd8d0306b63801c6c5504828c8fa\",\"Command\":\"/home/weave/weaveproxy --no-default-ipalloc --no-rewrite-hosts --without-dns -H /var/run/weave/weave.sock -H 0.0.0.0:12345 --tlsverify --tlscacert /home/weave/tls/ca.pem --tlscert /home/weave/tls/cert.pem --tlskey /home/weave/tls/key.pem\",\"Created\":1449150621,\"Ports\":[],\"Labels\":{\"works.weave.role\":\"system\"},\"Status\":\"Up 22 hours\",\"HostConfig\":{\"NetworkMode\":\"host\"}},{\"Id\":\"8dec1642c907035b1afdd765a3960134edb72bab7ec21221f1104e6c592b9d47\",\"Names\":[\"/weave\"],\"Image\":\"weaveworks/weave:1.3.1\",\"ImageID\":\"4482abc1ac8c5c36e464a8ff222b72f77c46c73295507b1d6681c59af1e0794e\",\"Command\":\"/home/weave/weaver --port 6783 --name 4e:d0:a7:51:27:54 --nickname dev --datapath weave --iface veth-weave --ipalloc-range 169.254.0.0/16 --dns-effective-listen-address 172.17.0.1 --dns-listen-address 172.17.0.1:53 --http-addr 127.0.0.1:6784 --docker-api unix:///var/run/docker.sock\",\"Created\":1449150612,\"Ports\":[],\"Labels\":{\"works.weave.role\":\"system\"},\"Status\":\"Up 22 hours\",\"HostConfig\":{\"NetworkMode\":\"host\"}},{\"Id\":\"a281d40eb5a5c549dd627297f3546a762d07e3c06585f101d426111c5ca87125\",\"Names\":[\"/abdcdev_abcdefabcd_1\"],\"Image\":\"abcdefgh/abcdef-abcd\",\"ImageID\":\"45d74b4cf11517e380fbc52196ba12dc6c0f83fe97ab1cb9ada4c92d0cadad89\",\"Command\":\"/w/w npm start\",\"Created\":1449146597,\"Ports\":[{\"IP\":\"127.0.0.1\",\"PrivatePort\":3000,\"PublicPort\":3200,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"0bfc03b9fc73d9ff97d329a55d2121225d97feb7efdb50bd32e3a00ee8fa16b6\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"abcdefabcd\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"698bdfb90a2bf91fd5a5717a362110617e07348c8d841a3fcaf52c6001f6b925\",\"Names\":[\"/abdcdev_sslrproxy_1\"],\"Image\":\"abdcdev_sslrproxy\",\"ImageID\":\"383fb91a69460529844cff4a8af20c56054461f148d5b168d66e60cb32e4de1a\",\"Command\":\"/w/w nginx -c /etc/nginx/nginx.conf\",\"Created\":1449146521,\"Ports\":[{\"IP\":\"0.0.0.0\",\"PrivatePort\":443,\"PublicPort\":443,\"Type\":\"tcp\"},{\"PrivatePort\":80,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"e00af6e792687216c18e4972ff8211940fde66ac94535ff50a1a7bd1880ae61d\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"sslrproxy\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"2bc7e3061892492c82054fa3be6c6e5e9f8ca9f933b217385bcfc3d55ac9bd1d\",\"Names\":[\"/abdcdev_adminabcdefauth_1\"],\"Image\":\"abcdefgh/abcdef-auth\",\"ImageID\":\"de0c2e6c590f0c50870520728b1a1b1a943ebee0f6f38eac10b26117bdc01cd1\",\"Command\":\"/w/w npm start\",\"Created\":1449146518,\"Ports\":[{\"PrivatePort\":3001,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"9990f233869e71289783868219004d1006bcb61062eeb96ec06b6591293c57b6\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"adminabcdefauth\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"41b2d1ba3a3135756562c777cf02822a4f81cbd24daf535920083f73b1e7496d\",\"Names\":[\"/abdcdev_intabcdefauth_1\"],\"Image\":\"abcdefgh/abcdef-auth\",\"ImageID\":\"de0c2e6c590f0c50870520728b1a1b1a943ebee0f6f38eac10b26117bdc01cd1\",\"Command\":\"/w/w npm start\",\"Created\":1449146515,\"Ports\":[{\"PrivatePort\":3001,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"3bfd1573ed9b17830b71a7d1ca5badc28623af9366a503146c2038a17e4e3795\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"intabcdefauth\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"603fcc4e5c90d8430d87cf4a5d17ed286eec71d7f118495c55bc8c2b9f503552\",\"Names\":[\"/abdcdev_ldap_1\"],\"Image\":\"abdcdev_ldap\",\"ImageID\":\"b53cb44bbfb3cdbcc0e2d6361e840d4e54bbe86af6bcfc458632a38448f5e06e\",\"Command\":\"/w/w /bin/sh -c /usr/local/bin/start_ldap.sh\",\"Created\":1449146451,\"Ports\":[{\"IP\":\"127.0.0.1\",\"PrivatePort\":389,\"PublicPort\":389,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"1737e5571ef796e1b0bd5280e719b159d893510bc93cc41b841f625954cea7e0\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"ldap\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"0\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"997b74083f92e52396d0743f8c69d5665dc525194d7a3e14d67cdd9ea09c4375\",\"Names\":[\"/abdcdev_rsyslog_1\"],\"Image\":\"abcdefgh/rsyslog:elasticsearch\",\"ImageID\":\"465c2dc38907b52a4f6ddc6ba02793cd0be1fc87d59e9d68cdc37811706c9149\",\"Command\":\"/w/w /usr/local/bin/start.sh -n\",\"Created\":1449146448,\"Ports\":[{\"IP\":\"127.0.0.1\",\"PrivatePort\":514,\"PublicPort\":1514,\"Type\":\"tcp\"},{\"PrivatePort\":514,\"Type\":\"udp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"80fcc75ec55280a51b7091c25dc549d274a02789a0461b5239507b6b735b4285\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"rsyslog\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"1\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"411e267eeae94f73de1b63623314f93d5f1df971420ff8076b9f85407389ef58\",\"Names\":[\"/abdcdev_kibana_1\"],\"Image\":\"abcdefgh/kibana\",\"ImageID\":\"3e305ce8fe94240b42c4ac215c1e266d265e093cdbdee7154df2d8be0bd22445\",\"Command\":\"/w/w /bin/sh -c 'counter=0 while [ ! \\\\\"$(curl elasticsearch:9200 > /dev/null)\\\\\" -a $counter -lt 30 ]; do sleep 1; ((counter++)); echo $counter; done ./bin/kibana'\",\"Created\":1449146425,\"Ports\":[{\"IP\":\"127.0.0.1\",\"PrivatePort\":5601,\"PublicPort\":5601,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"7b4ba8414c065d3aa206cecd8bbc114b0de6da1acabb55eec1819609fed60cbb\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"kibana\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"1\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"6af6d7cfeae2745fb0aca72ca29a87196f835464244717680322d84af4df3605\",\"Names\":[\"/abdcdev_ssllogstash_1\"],\"Image\":\"abdcdev_ssllogstash\",\"ImageID\":\"8a6111f837f79cdeb81139de5621b666d68d91fe82df37456b7d1118e90608d7\",\"Command\":\"/w/w /opt/logstash/bin/logstash -f /etc/logstash/conf.d/\",\"Created\":1449146297,\"Ports\":[{\"IP\":\"0.0.0.0\",\"PrivatePort\":50000,\"PublicPort\":514,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"bbd9a7ddc4471ab02c1b716e597b9b1d07c8a278b3b3daef4e420f54f1c60e82\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"ssllogstash\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"b444aa95b3f2ac55d188e3b8bcde5d262dddb9a3bd860f5ec7f502f283e61e19\",\"Names\":[\"/abdcdev_extsaltmaster_1\"],\"Image\":\"abdcdev_extsaltmaster\",\"ImageID\":\"818e61f5ab40dcbc322350fcc35ceb38c0142b3706aca74758397ac375e75ab3\",\"Command\":\"/w/w salt-master -l info\",\"Created\":1449146294,\"Ports\":[{\"IP\":\"0.0.0.0\",\"PrivatePort\":44506,\"PublicPort\":44506,\"Type\":\"tcp\"},{\"IP\":\"0.0.0.0\",\"PrivatePort\":44505,\"PublicPort\":44505,\"Type\":\"tcp\"},{\"PrivatePort\":4506,\"Type\":\"tcp\"},{\"PrivatePort\":4505,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"c164e56fef0af4c9323ec77e45f715b279e245b267b85848f48a5f6d0fb58cc8\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"extsaltmaster\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"ef0bf8c1ef2b1b13cdb16f3d8bd4dc65542519e88c7a91584eedc6ca0dded456\",\"Names\":[\"/ef0bf8c1ef_abdcdev_netopsabcd_1\"],\"Image\":\"abcdefgh/abcdef-abcd-netops:abcdefgh-v0.2.0\",\"ImageID\":\"697f78b7255da1a5f422dc863023d50011d1c3256771e56f240adf3c63e4ea32\",\"Command\":\"/w/w npm start\",\"Created\":1449146288,\"Ports\":[{\"PrivatePort\":3000,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"39a290b43d5c490055274d13a1939434ac44d1f898eadbb32759f262a6e2bb8d\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"netopsabcd\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 2 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"82240f370bc8770a589ffb71c9af0b1ba551c46bb81fbc6edb008405e0c84e03\",\"Names\":[\"/abdcdev_netopsjobs_1\"],\"Image\":\"abdcdev_netopsjobs\",\"ImageID\":\"1f84baea8e9a805346f755cd8cb095ad3ba13cfd00dd3a24fb55e049d46ebde5\",\"Command\":\"/w/w npm start\",\"Created\":1449146120,\"Ports\":[{\"PrivatePort\":3000,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"8c426b26c9e6de3b8af33fb5159b8b442440cfa96775bacc65394c828a0b2250\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"netopsjobs\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"ad8f4a8344710568f4955f08b649ac1ee1d033a23e5395701b4f0a81fc50d05b\",\"Names\":[\"/abdcdev_rabbitmq_1\"],\"Image\":\"rabbitmq:3-management\",\"ImageID\":\"9e6ba0accabe2633011ce5d4b5f9da4531c4df5358033b63c182f83161255d89\",\"Command\":\"/w/w /docker-entrypoint.sh rabbitmq-server\",\"Created\":1449146117,\"Ports\":[{\"IP\":\"127.0.0.1\",\"PrivatePort\":5672,\"PublicPort\":5672,\"Type\":\"tcp\"},{\"PrivatePort\":5671,\"Type\":\"tcp\"},{\"PrivatePort\":4369,\"Type\":\"tcp\"},{\"PrivatePort\":25672,\"Type\":\"tcp\"},{\"IP\":\"127.0.0.1\",\"PrivatePort\":15672,\"PublicPort\":15672,\"Type\":\"tcp\"},{\"PrivatePort\":15671,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"ccc4a223ccc863d037e375dec1325e81d3b2cfaeb2bd565eb2466eb5aaddd0c4\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"rabbitmq\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"78237fdec313691858e76c19507a967b14acbbd3e308907706ede436c9ff795f\",\"Names\":[\"/abdcdev_iperf3_1\"],\"Image\":\"abcdefgh/iperf3:latest\",\"ImageID\":\"78c515c71c616bf59ac082e7453a9d787e00c3f1c409e0f0cf5bd86292086f2f\",\"Command\":\"/bin/sh -c 'iperf3 -s'\",\"Created\":1449146093,\"Ports\":[{\"IP\":\"0.0.0.0\",\"PrivatePort\":5201,\"PublicPort\":5201,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"437e525f28fdc0fba960139dfa70e85ce73cfb9caaf5d5680ee72da906710c9d\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"iperf3\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"2\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}},{\"Id\":\"de4ae2223b7732780c44e6395550487ece0abb4776f38c1fe5c317706bd54f32\",\"Names\":[\"/abdcdev_elasticsearch_1\"],\"Image\":\"abcdefgh/elasticsearch\",\"ImageID\":\"750277ad2cea8c5bc712abaa3962bc8eed78944cec19bda9663c65e7f0bd5d6a\",\"Command\":\"/w/w /elasticsearch/bin/elasticsearch\",\"Created\":1449146051,\"Ports\":[{\"IP\":\"127.0.0.1\",\"PrivatePort\":9300,\"PublicPort\":9300,\"Type\":\"tcp\"},{\"IP\":\"127.0.0.1\",\"PrivatePort\":9200,\"PublicPort\":9200,\"Type\":\"tcp\"}],\"Labels\":{\"com.docker.compose.config-hash\":\"60b131b015ee9d7fed7774b654838b6ad37904de9192d8be082ef101e13d9e0d\",\"com.docker.compose.container-number\":\"1\",\"com.docker.compose.oneoff\":\"False\",\"com.docker.compose.project\":\"abdcdev\",\"com.docker.compose.service\":\"elasticsearch\",\"com.docker.compose.version\":\"1.5.1\",\"za.co.abcdefgh.abcdef.projectname\":\"abcdefDev\",\"za.co.abcdefgh.abcdef.startorder\":\"0\"},\"Status\":\"Up 4 hours\",\"HostConfig\":{\"NetworkMode\":\"default\"}}]".to_string(); 106 | } 107 | 108 | #[cfg(test)] 109 | fn get_stats_response() -> String { 110 | return "{\"read\":\"2015-04-09T07:02:08.480022082Z\",\"network\":{\"rx_bytes\":5820720,\"rx_packets\":2742,\"rx_errors\":0,\"rx_dropped\":1,\"tx_bytes\":158527,\"tx_packets\":2124,\"tx_errors\":0,\"tx_dropped\":0},\"cpu_stats\":{\"cpu_usage\":{\"total_usage\":19194125000,\"percpu_usage\":[14110113138,3245604417,845722573,992684872],\"usage_in_kernelmode\":1110000000,\"usage_in_usermode\":18160000000},\"system_cpu_usage\":1014488290000000,\"throttling_data\":{\"periods\":0,\"throttled_periods\":0,\"throttled_time\":0}},\"memory_stats\":{\"usage\":208437248,\"max_usage\":318791680,\"stats\":{\"active_anon\":27213824,\"active_file\":129069056,\"cache\":178946048,\"hierarchical_memory_limit\":18446744073709551615,\"hierarchical_memsw_limit\":18446744073709551615,\"inactive_anon\":0,\"inactive_file\":49876992,\"mapped_file\":10809344,\"pgfault\":99588,\"pgmajfault\":819,\"pgpgin\":130731,\"pgpgout\":153466,\"rss\":29331456,\"rss_huge\":6291456,\"swap\":0,\"total_active_anon\":27213824,\"total_active_file\":129069056,\"total_cache\":178946048,\"total_inactive_anon\":0,\"total_inactive_file\":49876992,\"total_mapped_file\":10809344,\"total_pgfault\":99588,\"total_pgmajfault\":819,\"total_pgpgin\":130731,\"total_pgpgout\":153466,\"total_rss\":29331456,\"total_rss_huge\":6291456,\"total_swap\":0,\"total_unevictable\":0,\"total_writeback\":0,\"unevictable\":0,\"writeback\":0},\"failcnt\":0,\"limit\":16854257664},\"blkio_stats\":{\"io_service_bytes_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"Read\",\"value\":150687744},{\"major\":8,\"minor\":0,\"op\":\"Write\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Sync\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Async\",\"value\":150687744},{\"major\":8,\"minor\":0,\"op\":\"Total\",\"value\":150687744}],\"io_serviced_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"Read\",\"value\":484},{\"major\":8,\"minor\":0,\"op\":\"Write\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Sync\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Async\",\"value\":484},{\"major\":8,\"minor\":0,\"op\":\"Total\",\"value\":484}],\"io_queue_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"Read\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Write\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Sync\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Async\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Total\",\"value\":0}],\"io_service_time_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"Read\",\"value\":2060941295},{\"major\":8,\"minor\":0,\"op\":\"Write\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Sync\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Async\",\"value\":2060941295},{\"major\":8,\"minor\":0,\"op\":\"Total\",\"value\":2060941295}],\"io_wait_time_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"Read\",\"value\":5476872825},{\"major\":8,\"minor\":0,\"op\":\"Write\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Sync\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Async\",\"value\":5476872825},{\"major\":8,\"minor\":0,\"op\":\"Total\",\"value\":5476872825}],\"io_merged_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"Read\",\"value\":79},{\"major\":8,\"minor\":0,\"op\":\"Write\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Sync\",\"value\":0},{\"major\":8,\"minor\":0,\"op\":\"Async\",\"value\":79},{\"major\":8,\"minor\":0,\"op\":\"Total\",\"value\":79}],\"io_time_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"\",\"value\":1814}],\"sectors_recursive\":[{\"major\":8,\"minor\":0,\"op\":\"\",\"value\":294312}]}}".to_string(); 111 | } 112 | 113 | #[cfg(test)] 114 | fn get_system_info_response() -> String { 115 | return "{\"Containers\":6,\"Debug\":0,\"DockerRootDir\":\"/var/lib/docker\",\"Driver\":\"btrfs\",\"DriverStatus\":[[\"Build Version\",\"Btrfs v3.17.1\"],[\"Library Version\",\"101\"]],\"ExecutionDriver\":\"native-0.2\",\"ID\":\"WG63:3NIU:TSI2:FV7J:IL2O:YPXA:JR3F:XEKT:JZVR:JA6T:QMYE:B4SB\",\"IPv4Forwarding\":1,\"Images\":190,\"IndexServerAddress\":\"https://index.docker.io/v1/\",\"InitPath\":\"/usr/libexec/docker/dockerinit\",\"InitSha1\":\"30c93967bdc3634b6036e1a76fd547bbe171b264\",\"KernelVersion\":\"3.18.6\",\"Labels\":null,\"MemTotal\":16854257664,\"MemoryLimit\":1,\"NCPU\":4,\"NEventsListener\":0,\"NFd\":68,\"NGoroutines\":95,\"Name\":\"core\",\"OperatingSystem\":\"CoreOS 607.0.0\",\"RegistryConfig\":{\"IndexConfigs\":{\"docker.io\":{\"Mirrors\":null,\"Name\":\"docker.io\",\"Official\":true,\"Secure\":true}},\"InsecureRegistryCIDRs\":[\"127.0.0.0/8\"]},\"SwapLimit\":1}".to_string(); 116 | } 117 | 118 | #[cfg(test)] 119 | fn get_images_response() -> String { 120 | return "[{\"Created\":1428533761,\"Id\":\"533da4fa223bfbca0f56f65724bb7a4aae7a1acd6afa2309f370463eaf9c34a4\",\"ParentId\":\"84ac0b87e42afe881d36f03dea817f46893f9443f9fc10b64ec279737384df12\",\"RepoTags\":[\"ghmlee/rust:nightly\"],\"Size\":0,\"VirtualSize\":806688288},{\"Created\":1371157430,\"Id\":\"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158\",\"ParentId\":\"\",\"RepoTags\":[],\"Size\":0,\"VirtualSize\":0}]".to_string(); 121 | } 122 | 123 | #[cfg(test)] 124 | fn get_container_info_response() -> String { 125 | return "{\"AppArmorProfile\":\"\",\"Args\":[],\"Config\":{\"AttachStderr\":false,\"AttachStdin\":false,\"AttachStdout\":false,\"Cmd\":[\"/run.sh\"],\"CpuShares\":0,\"Cpuset\":\"\",\"Domainname\":\"\",\"Entrypoint\":null,\"Env\":[\"HOME=/\",\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"INFLUXDB_VERSION=0.8.8\",\"PRE_CREATE_DB=**None**\",\"SSL_SUPPORT=**False**\",\"SSL_CERT=**None**\"],\"ExposedPorts\":{\"8083/tcp\":{},\"8084/tcp\":{},\"8086/tcp\":{},\"8090/tcp\":{},\"8099/tcp\":{}},\"Hostname\":\"a9de92dfbf97\",\"Image\":\"cosmosio/influxdb\",\"Labels\":{},\"MacAddress\":\"\",\"Memory\":0,\"MemorySwap\":0,\"NetworkDisabled\":false,\"OnBuild\":null,\"OpenStdin\":false,\"PortSpecs\":null,\"StdinOnce\":false,\"Tty\":false,\"User\":\"\",\"Volumes\":{\"/data\":{}},\"WorkingDir\":\"\"},\"Created\":\"2015-04-26T23:15:20.6051724Z\",\"Driver\":\"aufs\",\"ExecDriver\":\"native-0.2\",\"ExecIDs\":null,\"HostConfig\":{\"Binds\":null,\"CapAdd\":null,\"CapDrop\":null,\"CgroupParent\":\"\",\"ContainerIDFile\":\"\",\"CpuShares\":0,\"CpusetCpus\":\"\",\"Devices\":[],\"Dns\":null,\"DnsSearch\":null,\"ExtraHosts\":null,\"IpcMode\":\"\",\"Links\":null,\"LogConfig\":{\"Config\":null,\"Type\":\"json-file\"},\"LxcConf\":[],\"Memory\":0,\"MemorySwap\":0,\"NetworkMode\":\"bridge\",\"PidMode\":\"\",\"PortBindings\":{\"8083/tcp\":[{\"HostIp\":\"\",\"HostPort\":\"8083\"}],\"8086/tcp\":[{\"HostIp\":\"\",\"HostPort\":\"8086\"}]},\"Privileged\":false,\"PublishAllPorts\":false,\"ReadonlyRootfs\":false,\"RestartPolicy\":{\"MaximumRetryCount\":0,\"Name\":\"no\"},\"SecurityOpt\":null,\"Ulimits\":null,\"VolumesFrom\":null},\"HostnamePath\":\"/mnt/sda1/var/lib/docker/containers/a9de92dfbf9739aa945efbeafb8112d9dd8b986a724185afe1cdca3ab2ff4a3c/hostname\",\"HostsPath\":\"/mnt/sda1/var/lib/docker/containers/a9de92dfbf9739aa945efbeafb8112d9dd8b986a724185afe1cdca3ab2ff4a3c/hosts\",\"Id\":\"a9de92dfbf9739aa945efbeafb8112d9dd8b986a724185afe1cdca3ab2ff4a3c\",\"Image\":\"bd6edeff2eb78594a5a48d498efc7ef04cafb126c37e5ae7533c9f243985742a\",\"LogPath\":\"/mnt/sda1/var/lib/docker/containers/a9de92dfbf9739aa945efbeafb8112d9dd8b986a724185afe1cdca3ab2ff4a3c/a9de92dfbf9739aa945efbeafb8112d9dd8b986a724185afe1cdca3ab2ff4a3c-json.log\",\"MountLabel\":\"\",\"Name\":\"/influxdb\",\"NetworkSettings\":{\"Bridge\":\"docker0\",\"Gateway\":\"172.17.42.1\",\"GlobalIPv6Address\":\"\",\"GlobalIPv6PrefixLen\":0,\"IPAddress\":\"172.17.0.2\",\"IPPrefixLen\":16,\"IPv6Gateway\":\"\",\"LinkLocalIPv6Address\":\"fe80::42:acff:fe11:2\",\"LinkLocalIPv6PrefixLen\":64,\"MacAddress\":\"02:42:ac:11:00:02\",\"PortMapping\":null,\"Ports\":{\"8083/tcp\":[{\"HostIp\":\"0.0.0.0\",\"HostPort\":\"8083\"}],\"8084/tcp\":null,\"8086/tcp\":[{\"HostIp\":\"0.0.0.0\",\"HostPort\":\"8086\"}],\"8090/tcp\":null,\"8099/tcp\":null}},\"Path\":\"/run.sh\",\"ProcessLabel\":\"\",\"ResolvConfPath\":\"/mnt/sda1/var/lib/docker/containers/a9de92dfbf9739aa945efbeafb8112d9dd8b986a724185afe1cdca3ab2ff4a3c/resolv.conf\",\"RestartCount\":0,\"State\":{\"Dead\":false,\"Error\":\"\",\"ExitCode\":0,\"FinishedAt\":\"0001-01-01T00:00:00Z\",\"OOMKilled\":false,\"Paused\":false,\"Pid\":891,\"Restarting\":false,\"Running\":true,\"StartedAt\":\"2015-04-26T23:15:21.034009864Z\"},\"Volumes\":{\"/data\":\"/mnt/sda1/var/lib/docker/vfs/dir/e5519f5fe7434608f575d84a532cc7ee0a16b792d78679fe34b7d04269469694\"},\"VolumesRW\":{\"/data\":true}}".to_string(); 126 | } 127 | 128 | #[cfg(test)] 129 | fn get_processes_response() -> String { 130 | return "{\"Processes\":[[\"4586\",\"999\",\"rust\"]],\"Titles\":[\"PID\",\"USER\",\"COMMAND\"]}".to_string(); 131 | } 132 | 133 | #[cfg(test)] 134 | fn get_filesystem_changes_response() -> String { 135 | return "[{\"Path\":\"/tmp\",\"Kind\":0}]".to_string(); 136 | } 137 | 138 | #[cfg(test)] 139 | fn get_version_response() -> String { 140 | return "{\"Version\":\"1.8.1\",\"ApiVersion\":\"1.20\",\"GitCommit\":\"d12ea79\",\"GoVersion\":\"go1.4.2\",\"Os\":\"linux\",\"Arch\":\"amd64\",\"KernelVersion\":\"4.0.9-boot2docker\",\"BuildTime\":\"Thu Aug 13 02:49:29 UTC 2015\"}".to_string(); 141 | } 142 | --------------------------------------------------------------------------------