├── .gitignore ├── Cargo.toml ├── README.md ├── makefile └── src ├── common ├── container_config.rs ├── containers.rs ├── images.rs ├── mod.rs ├── port_mapping.rs ├── sys_info.rs └── version.rs ├── docker.rs ├── http ├── mod.rs └── response.rs ├── lib.rs └── methods ├── container.rs ├── image.rs ├── info.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /.DS_Store 2 | docker 3 | libdocker* 4 | Cargo.lock 5 | target/* 6 | *.glue -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "docker-rust" 4 | version = "0.0.2" 5 | authors = ["Abhinav Ajaonkar "] 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Docker-Rust (WIP) 2 | 3 | A client library for the Docker Remote API written in Rust. 4 | Currently targetting API v1.12+ 5 | 6 | A lot of functionality is currently on hold until Rust's HTTP and JSON libs are up to snuff. 7 | 8 | ### Run Tests 9 | 10 | ```bash 11 | make test 12 | ``` 13 | 14 | ## Use 15 | 16 | Declare cargo dependency in your `Cargo.toml` 17 | 18 | ``` 19 | [dependencies.docker] 20 | 21 | git = "https://github.com/abh1nav/docker-rust.git" 22 | ``` 23 | 24 | ## Features 25 | 26 | ### Get a list of running containers 27 | 28 | ```rust 29 | extern crate docker; 30 | 31 | use docker::Docker; 32 | 33 | // Currently only works if docker is run using a unix socket (the default) 34 | let client: Docker = Docker { 35 | socket_path: "/var/run/docker.sock" 36 | }; 37 | 38 | // common::containers::Containers is a Vec 39 | let containers: Containers = client.get_containers(); 40 | 41 | println!("Running container count: {}", containers.len()); 42 | ``` 43 | 44 | ### Restart, stop and remove a container 45 | 46 | ```rust 47 | let container_id = "5fc6a1226f01".to_string(); 48 | 49 | // Restart container 50 | client.restart_container(container_id.as_slice()); 51 | // OR wait 3 seconds for the container to stop before forcing restart 52 | client.restart_container_with_timeout(container_id.as_slice(), 3); 53 | 54 | // Stop the container 55 | client.stop_container(container_id.as_slice()); 56 | // OR wait 3 seconds for the container to stop before killing 57 | client.stop_container_with_timeout(container_id.as_slice(), 3); 58 | 59 | // Remove the container and its volumes 60 | client.remove_container(container_id.as_slice()); 61 | // OR remove with the force flag 62 | client.remove_container_with_force(container_id.as_slice()); 63 | ``` 64 | 65 | ### Get a list of Images 66 | 67 | ```rust 68 | // common::images::Images is a Vec 69 | let images: Images = client.get_images(); 70 | 71 | println!("Image count: {}", images.len()); 72 | ``` 73 | 74 | ### Utility endpoints 75 | 76 | ```rust 77 | 78 | // Get system info -> common::sys_info::SysInfo 79 | let sys_info = client.get_sys_info(); 80 | println!("Number of containers: {}\nNumber of Images: {}", sys_info.Containers, sys_info.Images); 81 | 82 | // Get docker version -> common::version::Version 83 | let version = client.get_version(); 84 | println!("Docker version: {}", version.Version); 85 | 86 | ``` 87 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: clean compile 2 | 3 | clean: 4 | rm -Rf target 5 | 6 | compile: 7 | cargo build 8 | 9 | test: 10 | cargo test 11 | -------------------------------------------------------------------------------- /src/common/container_config.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | #[cfg(test)] 4 | use serialize::{json, Decodable}; 5 | #[cfg(test)] 6 | use serialize::json::{DecoderError}; 7 | 8 | // 9 | // Representation of a containers "config" as returned by the container 10 | // inspect call - this is currently broken because we can't use 11 | // serialize::json::Decodable to decode something where the keys are 12 | // dynamic. 13 | // 14 | // Specifically, the ExposedPorts key in the config object is represented as: 15 | // 16 | // "ExposedPorts": { 17 | // "9000/tcp": {} 18 | // } 19 | // 20 | // and there's no way to parse this currently using stdlib's JSON decoder. 21 | // 22 | 23 | #[deriving(Decodable)] 24 | pub struct Config { 25 | AttachStderr: bool, 26 | AttachStdin: bool, 27 | AttachStdout: bool, 28 | Cmd: Vec, 29 | CpuShares: u8, 30 | Cpuset: String, 31 | Domainname: String, 32 | Entrypoint: Vec, 33 | Env: Vec, 34 | // ExposedPorts: ??? 35 | Hostname: String, 36 | Image: String, 37 | Memory: u64, 38 | MemorySwap: u64, 39 | NetworkDisabled: bool, 40 | OnBuild: String, 41 | OpenStdin: bool, 42 | PortSpecs: bool, 43 | StdinOnce: bool, 44 | Tty: bool, 45 | User: String, 46 | Volumes: String, 47 | WorkingDir: String 48 | } 49 | 50 | // 51 | // Test(s) 52 | // 53 | 54 | #[test] 55 | fn test_config_deserialization() { 56 | let json_string = "\"Config\":{\"AttachStderr\":false,\"AttachStdin\":false,\"AttachStdout\":false,\"Cmd\":[\"-e\",\"/docker.sock\"],\"CpuShares\":0,\"Cpuset\":\"\",\"Domainname\":\"\",\"Entrypoint\":[\"./dockerui\"],\"Env\":[\"HOME=/\",\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin\",\"GOPATH=/go\",\"GOROOT=/usr/local/go\"],\"ExposedPorts\":{\"9000/tcp\":{}},\"Hostname\":\"5fc6a1226f01\",\"Image\":\"crosbymichael/dockerui\",\"Memory\":0,\"MemorySwap\":0,\"NetworkDisabled\":false,\"OnBuild\":null,\"OpenStdin\":false,\"PortSpecs\":null,\"StdinOnce\":false,\"Tty\":false,\"User\":\"\",\"Volumes\":null,\"WorkingDir\":\"/app\"}"; 57 | let json_object = json::from_str(json_string); 58 | let mut decoder = json::Decoder::new(json_object.unwrap()); 59 | let c: Config = Decodable::decode(&mut decoder).unwrap(); 60 | } 61 | -------------------------------------------------------------------------------- /src/common/containers.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | #[cfg(test)] 4 | use serialize::{json, Decodable}; 5 | 6 | use super::port_mapping::PortMapping; 7 | 8 | /// 9 | /// Container representation returned by the /containers/json call 10 | /// 11 | 12 | #[deriving(Decodable)] 13 | pub struct Container { 14 | pub Command: String, 15 | pub Created: u64, 16 | pub Id: String, 17 | pub Image: String, 18 | pub Names: Vec, 19 | pub Ports: Vec, 20 | pub SizeRootFs: u64, 21 | pub SizeRw: u64, 22 | pub Status: String 23 | } 24 | 25 | /// 26 | /// Containers is a vector of Container objects - need this for 27 | /// auto-deserialization 28 | /// 29 | 30 | #[deriving(Decodable)] 31 | pub type Containers = Vec; 32 | 33 | /// 34 | /// Tests 35 | /// 36 | 37 | #[test] 38 | fn test_container_deserialization() { 39 | let json_string = "{\"Command\":\"/bin/bash\",\"Created\":1402812645,\"Id\":\"8397b5a5a497b701d3514ca18ba11dc24b32378a7328ef28510c0f49ef30cddf\",\"Image\":\"ubuntu:14.04\",\"Names\":[\"/silly_kirch\"],\"Ports\":[],\"Status\": \"Up 26 seconds\",\"Ports\":[{\"IP\": \"0.0.0.0\", \"PrivatePort\": 9000, \"PublicPort\": 9001, \"Type\": \"tcp\"}],\"SizeRootFs\":100,\"SizeRw\":12288}"; 40 | let json_object = json::from_str(json_string); 41 | let mut decoder = json::Decoder::new(json_object.unwrap()); 42 | 43 | let c: Container = Decodable::decode(&mut decoder).unwrap(); 44 | assert!(c.Command == "/bin/bash".to_string()); 45 | assert!(c.Created == 1402812645u64); 46 | assert!(c.Id == "8397b5a5a497b701d3514ca18ba11dc24b32378a7328ef28510c0f49ef30cddf".to_string()); 47 | assert!(c.Image == "ubuntu:14.04".to_string()); 48 | assert!(c.Names.len() == 1); 49 | assert!(*c.Names.get(0) == "/silly_kirch".to_string()); 50 | assert!(c.SizeRootFs == 100u64); 51 | assert!(c.SizeRw == 12288u64); 52 | assert!(c.Status == "Up 26 seconds".to_string()); 53 | assert!(c.Ports.len() == 1); 54 | 55 | let port_mapping = &c.Ports[0]; 56 | assert!(port_mapping.IP == "0.0.0.0".to_string()); 57 | assert!(port_mapping.PrivatePort == 9000); 58 | assert!(port_mapping.PublicPort == 9001); 59 | assert!(port_mapping.Type == "tcp".to_string()); 60 | } 61 | 62 | #[test] 63 | fn test_containers_deserialization() { 64 | let json_string = "[{\"Command\":\"/bin/bash\",\"Created\":1402812645,\"Id\":\"8397b5a5a497b701d3514ca18ba11dc24b32378a7328ef28510c0f49ef30cddf\",\"Image\":\"ubuntu:14.04\",\"Names\":[\"/silly_kirch\"],\"Ports\":[],\"Status\": \"Up 26 seconds\",\"Ports\":[{\"IP\": \"0.0.0.0\", \"PrivatePort\": 9000, \"PublicPort\": 9001, \"Type\": \"tcp\"}],\"SizeRootFs\":100,\"SizeRw\":12288}]"; 65 | let json_object = json::from_str(json_string); 66 | let mut decoder = json::Decoder::new(json_object.unwrap()); 67 | let containers: Containers = Decodable::decode(&mut decoder).unwrap(); 68 | assert!(containers.len() == 1); 69 | 70 | let c = &containers[0]; 71 | assert!(c.Command == "/bin/bash".to_string()); 72 | assert!(c.Created == 1402812645u64); 73 | assert!(c.Id == "8397b5a5a497b701d3514ca18ba11dc24b32378a7328ef28510c0f49ef30cddf".to_string()); 74 | assert!(c.Image == "ubuntu:14.04".to_string()); 75 | assert!(c.Names.len() == 1); 76 | assert!(*c.Names.get(0) == "/silly_kirch".to_string()); 77 | assert!(c.SizeRootFs == 100u64); 78 | assert!(c.SizeRw == 12288u64); 79 | assert!(c.Status == "Up 26 seconds".to_string()); 80 | assert!(c.Ports.len() == 1); 81 | 82 | let port_mapping = &c.Ports[0]; 83 | assert!(port_mapping.IP == "0.0.0.0".to_string()); 84 | assert!(port_mapping.PrivatePort == 9000); 85 | assert!(port_mapping.PublicPort == 9001); 86 | assert!(port_mapping.Type == "tcp".to_string()); 87 | } 88 | -------------------------------------------------------------------------------- /src/common/images.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | #[cfg(test)] 4 | use serialize::{json, Decodable}; 5 | 6 | /// 7 | /// Image representation returned by the /images/json call 8 | /// 9 | 10 | #[deriving(Decodable)] 11 | pub struct Image { 12 | pub RepoTags: Vec, 13 | pub Created: u64, 14 | pub Id: String, 15 | pub Size: u64, 16 | pub VirtualSize: u64 17 | } 18 | 19 | /// 20 | /// Images is a vector of Image objects - need this for 21 | /// auto-deserialization 22 | /// 23 | 24 | #[deriving(Decodable)] 25 | pub type Images = Vec; 26 | 27 | /// 28 | /// Tests 29 | /// 30 | 31 | #[test] 32 | fn test_container_deserialization() { 33 | let json_string = "{\"Created\":1385109237,\"Id\":\"e02d1bfed8743d7a0176de2e4c03842359f10ba10947cdc71e147bb538a2910c\",\"ParentId\":\"4995dc7f431090c5d32f7a55af3ab1b9b04f96af120d138f436fa991d5e15026\",\"RepoTags\":[\"dustin/couchbase:latest\"],\"Size\":0,\"VirtualSize\":749634786}"; 34 | let json_object = json::from_str(json_string); 35 | let mut decoder = json::Decoder::new(json_object.unwrap()); 36 | 37 | let i: Image = Decodable::decode(&mut decoder).unwrap(); 38 | assert!(i.Created == 1385109237u64); 39 | assert!(i.Id == "e02d1bfed8743d7a0176de2e4c03842359f10ba10947cdc71e147bb538a2910c".to_string()); 40 | assert!(i.RepoTags.len() == 1); 41 | assert!(*i.RepoTags.get(0) == "dustin/couchbase:latest".to_string()); 42 | assert!(i.Size == 0u64); 43 | assert!(i.VirtualSize == 749634786u64); 44 | } 45 | 46 | #[test] 47 | fn test_containers_deserialization() { 48 | let json_string = "[{\"Created\":1385109237,\"Id\":\"e02d1bfed8743d7a0176de2e4c03842359f10ba10947cdc71e147bb538a2910c\",\"ParentId\":\"4995dc7f431090c5d32f7a55af3ab1b9b04f96af120d138f436fa991d5e15026\",\"RepoTags\":[\"dustin/couchbase:latest\"],\"Size\":0,\"VirtualSize\":749634786}]"; 49 | let json_object = json::from_str(json_string); 50 | let mut decoder = json::Decoder::new(json_object.unwrap()); 51 | 52 | let images: Images = Decodable::decode(&mut decoder).unwrap(); 53 | assert!(images.len() == 1); 54 | let i: Image = &images[0]; 55 | assert!(i.Created == 1385109237u64); 56 | assert!(i.Id == "e02d1bfed8743d7a0176de2e4c03842359f10ba10947cdc71e147bb538a2910c".to_string()); 57 | assert!(i.RepoTags.len() == 1); 58 | assert!(*i.RepoTags.get(0) == "dustin/couchbase:latest".to_string()); 59 | assert!(i.Size == 0u64); 60 | assert!(i.VirtualSize == 749634786u64); 61 | } -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod port_mapping; 2 | // pub mod container_config; 3 | pub mod containers; 4 | pub mod images; 5 | pub mod sys_info; 6 | pub mod version; -------------------------------------------------------------------------------- /src/common/port_mapping.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | #[cfg(test)] 4 | use serialize::{json, Decodable}; 5 | 6 | #[deriving(Decodable)] 7 | pub struct PortMapping { 8 | pub IP: String, 9 | pub PrivatePort: uint, 10 | pub PublicPort: uint, 11 | pub Type: String 12 | } 13 | 14 | #[test] 15 | fn test_port_mapping_deserialization() { 16 | let json_string = "{\"IP\": \"0.0.0.0\", \"PrivatePort\": 9000, \"PublicPort\": 9001, \"Type\": \"tcp\"}"; 17 | let json_object = json::from_str(json_string); 18 | let mut decoder = json::Decoder::new(json_object.unwrap()); 19 | let port_mapping: PortMapping = Decodable::decode(&mut decoder).unwrap(); 20 | assert!(port_mapping.IP == "0.0.0.0".to_string()); 21 | assert!(port_mapping.PrivatePort == 9000); 22 | assert!(port_mapping.PublicPort == 9001); 23 | assert!(port_mapping.Type == "tcp".to_string()); 24 | } 25 | -------------------------------------------------------------------------------- /src/common/sys_info.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | #[cfg(test)] 4 | use serialize::{json, Decodable}; 5 | 6 | #[deriving(Decodable)] 7 | pub struct SysInfo { 8 | pub Containers: uint, 9 | pub Debug: uint, 10 | pub Driver: String, 11 | pub DriverStatus: Vec>, 12 | pub ExecutionDriver: String, 13 | pub IPv4Forwarding: uint, 14 | pub Images: uint, 15 | pub IndexServerAddress: String, 16 | pub InitPath: String, 17 | pub InitSha1: String, 18 | pub KernelVersion: String, 19 | pub MemoryLimit: uint, 20 | pub NEventsListener: uint, 21 | pub NFd: uint, 22 | pub NGoroutines: uint, 23 | pub SwapLimit: uint 24 | } 25 | 26 | #[test] 27 | fn test_sys_info_deserialization() { 28 | let json_string = "{\"Containers\":1,\"Debug\":0,\"Driver\":\"devicemapper\",\"DriverStatus\":[[\"Pool Name\",\"docker-8:1-1065722-pool\"],[\"Data file\",\"/var/lib/docker/devicemapper/devicemapper/data\"],[\"Metadata file\",\"/var/lib/docker/devicemapper/devicemapper/metadata\"],[\"Data Space Used\",\"3170.3 Mb\"],[\"Data Space Total\",\"102400.0 Mb\"],[\"Metadata Space Used\",\"4.4 Mb\"],[\"Metadata Space Total\",\"2048.0 Mb\"]],\"ExecutionDriver\":\"native-0.2\",\"IPv4Forwarding\":1,\"Images\":91,\"IndexServerAddress\":\"https://index.docker.io/v1/\",\"InitPath\":\"/usr/bin/docker\",\"InitSha1\":\"\",\"KernelVersion\":\"3.9.0\",\"MemoryLimit\":0,\"NEventsListener\":0,\"NFd\":16,\"NGoroutines\":14,\"SwapLimit\":0}"; 29 | let json_object = json::from_str(json_string); 30 | let mut decoder = json::Decoder::new(json_object.unwrap()); 31 | let s: SysInfo = Decodable::decode(&mut decoder).unwrap(); 32 | 33 | assert!(s.Containers == 1); 34 | assert!(s.Images == 91); 35 | assert!(s.Debug == 0); 36 | assert!(s.DriverStatus.len() == 7); 37 | assert!(s.InitPath == "/usr/bin/docker".to_string()); 38 | } 39 | -------------------------------------------------------------------------------- /src/common/version.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | #[cfg(test)] 4 | use serialize::{json, Decodable}; 5 | 6 | #[deriving(Decodable)] 7 | pub struct Version { 8 | pub Version: String, 9 | pub GitCommit: String, 10 | pub GoVersion: String 11 | } 12 | 13 | #[test] 14 | fn test_version_deserialization() { 15 | let json_string = "{\"Version\":\"0.2.2\",\"GitCommit\":\"5a2a5cc+CHANGES\",\"GoVersion\":\"go1.0.3\"}"; 16 | let json_object = json::from_str(json_string); 17 | let mut decoder = json::Decoder::new(json_object.unwrap()); 18 | let version: Version = Decodable::decode(&mut decoder).unwrap(); 19 | 20 | assert!(version.Version == "0.2.2".to_string()); 21 | assert!(version.GitCommit == "5a2a5cc+CHANGES".to_string()); 22 | assert!(version.GoVersion == "go1.0.3".to_string()); 23 | } 24 | -------------------------------------------------------------------------------- /src/docker.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use std::io::Command; 3 | #[cfg(test)] 4 | use std::io::timer; 5 | #[cfg(test)] 6 | use std::time::Duration; 7 | 8 | use super::common::containers::Containers as Containers; 9 | use super::common::images::Images as Images; 10 | use super::common::version::Version as Version; 11 | use super::common::sys_info::SysInfo as SysInfo; 12 | 13 | use super::methods::container as container; 14 | use super::methods::image as image; 15 | use super::methods::info as info; 16 | 17 | pub struct Docker { 18 | pub socket_path: String 19 | } 20 | 21 | impl Docker { 22 | 23 | /// 24 | /// GET /containers/json 25 | /// 26 | 27 | pub fn get_containers(&self) -> Containers { 28 | container::get_containers(self.socket_path.as_slice()) 29 | } 30 | 31 | /// 32 | /// POST /containers/(id)/stop 33 | /// 34 | 35 | pub fn stop_container(&self, id: &str) { 36 | container::stop_container_impl(self.socket_path.as_slice(), id, None); 37 | } 38 | 39 | pub fn stop_container_with_timeout(&self, id: &str, wait_time: uint) { 40 | container::stop_container_impl(self.socket_path.as_slice(), id, Some(wait_time)); 41 | } 42 | 43 | /// 44 | /// POST /containers/(id)/restart 45 | /// 46 | 47 | pub fn restart_container(&self, id: &str) { 48 | container::restart_container_impl(self.socket_path.as_slice(), id, None); 49 | } 50 | 51 | pub fn restart_container_with_timeout(&self, id: &str, wait_time: uint) { 52 | container::restart_container_impl(self.socket_path.as_slice(), id, Some(wait_time)); 53 | } 54 | 55 | /// 56 | /// DELETE /containers/(id)/ 57 | /// 58 | 59 | pub fn remove_container(&self, id: &str) { 60 | container::remove_container_impl(self.socket_path.as_slice(), id, false); 61 | } 62 | 63 | pub fn remove_container_with_force(&self, id:&str) { 64 | container::remove_container_impl(self.socket_path.as_slice(), id, true); 65 | } 66 | 67 | /// 68 | /// GET /images/json 69 | /// 70 | 71 | pub fn get_images(&self) -> Images { 72 | image::get_images(self.socket_path.as_slice()) 73 | } 74 | 75 | /// 76 | /// GET /info 77 | /// 78 | 79 | pub fn get_sys_info(&self) -> SysInfo { 80 | info::get_sys_info(self.socket_path.as_slice()) 81 | } 82 | 83 | /// 84 | /// GET /version 85 | /// 86 | 87 | pub fn get_version(&self) -> Version { 88 | info::get_version(self.socket_path.as_slice()) 89 | } 90 | 91 | } 92 | 93 | /// 94 | /// Test(s) 95 | /// 96 | 97 | #[cfg(test)] 98 | fn make_client() -> Docker { 99 | Docker { socket_path: "/var/run/docker.sock".to_string() } 100 | } 101 | 102 | #[cfg(test)] 103 | fn start_busybox_container() -> Option { 104 | match Command::new("docker").arg("run").arg("-t").arg("-d") 105 | .arg("busybox:latest").output() { 106 | Ok(process_output) => { 107 | let output = String::from_utf8(process_output.output).unwrap(); 108 | timer::sleep(Duration::milliseconds(1000)); 109 | let clean_output = output.as_slice().replace("\r\n", ""); 110 | let container_id = clean_output.as_slice().trim(); 111 | Some(String::from_str(container_id)) 112 | } 113 | Err(_) => None 114 | } 115 | } 116 | 117 | #[allow(type_limits)] 118 | #[test] 119 | fn test_get_containers() { 120 | let client = make_client(); 121 | let containers = client.get_containers(); 122 | let count: uint = containers.len(); 123 | assert!(count >= 0); 124 | } 125 | 126 | #[test] 127 | fn test_stop_and_remove_container() { 128 | // Start a test container 129 | let container_id = match start_busybox_container() { 130 | Some(id) => id, 131 | None => fail!("Failed to start test container") 132 | }; 133 | 134 | // Stop test container 135 | let client = Docker { socket_path: "/var/run/docker.sock".to_string() }; 136 | client.stop_container(container_id.as_slice()); 137 | 138 | // Remove test container 139 | client.remove_container(container_id.as_slice()); 140 | } 141 | 142 | #[test] 143 | fn test_restart_container() { 144 | let container_id = match start_busybox_container() { 145 | Some(id) => id, 146 | None => fail!("Failed to start test container") 147 | }; 148 | 149 | let client = make_client(); 150 | client.restart_container(container_id.as_slice()); 151 | timer::sleep(Duration::milliseconds(3000)); 152 | 153 | client.stop_container(container_id.as_slice()); 154 | client.remove_container(container_id.as_slice()); 155 | } 156 | 157 | #[allow(type_limits)] 158 | #[test] 159 | fn test_get_images() { 160 | let client = make_client(); 161 | let images = client.get_images(); 162 | let count: uint = images.len(); 163 | assert!(count >= 0); 164 | } 165 | 166 | #[test] 167 | fn test_get_sys_info() { 168 | let client = make_client(); 169 | let sys_info: SysInfo = client.get_sys_info(); 170 | assert!(sys_info.Debug == 0); 171 | assert!(sys_info.DriverStatus.len() > 0); 172 | } 173 | 174 | #[test] 175 | fn test_get_version() { 176 | let client = make_client(); 177 | let version = client.get_version(); 178 | assert!(version.Version != "".to_string()); 179 | assert!(version.GitCommit != "".to_string()); 180 | assert!(version.GoVersion != "".to_string()); 181 | } 182 | -------------------------------------------------------------------------------- /src/http/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::net::unix::UnixStream; 2 | use std::string::String; 3 | 4 | pub mod response; 5 | 6 | /// 7 | /// A macro to concatenate non-literal Strings. 8 | /// 9 | macro_rules! cat( 10 | ($inp:ident, $($sp:ident),+) => ({ 11 | $($inp.push_str($sp);)+ 12 | }); 13 | ) 14 | 15 | pub enum RequestType { 16 | GET, 17 | PUT, 18 | POST, 19 | DELETE 20 | } 21 | 22 | fn make_request_str(request_type: RequestType, path: &str) -> String { 23 | let mut result: String = String::new(); 24 | let suffix = " HTTP/1.0\r\n\r\n"; 25 | 26 | result.push_str(match request_type { 27 | GET => "GET ", 28 | PUT => "PUT ", 29 | POST => "POST ", 30 | DELETE => "DELETE " 31 | }); 32 | 33 | cat!(result, path, suffix); 34 | result.to_string() 35 | } 36 | 37 | pub fn make_request(socket_path: &str, request_type: RequestType, path: &str) -> response::Response { 38 | let http_request = make_request_str(request_type, path); 39 | let socket = Path::new(socket_path); 40 | 41 | let mut stream = match UnixStream::connect(&socket) { 42 | Err(_) => fail!("server is not running"), 43 | Ok(stream) => stream, 44 | }; 45 | 46 | // Send request 47 | match stream.write_str(http_request.as_slice()) { 48 | Err(_) => fail!("couldn't send request"), 49 | Ok(_) => {} 50 | }; 51 | 52 | // Read response 53 | let resp: String = match stream.read_to_string() { 54 | Err(_) => fail!("response derped"), 55 | Ok(resp) => resp 56 | }; 57 | 58 | response::parse(resp.as_slice()) 59 | } 60 | 61 | #[test] 62 | fn test_make_request_str() { 63 | let request_type = GET; 64 | let result = make_request_str(request_type, "/hello/world"); 65 | let expected = "GET /hello/world HTTP/1.0\r\n\r\n".to_string(); 66 | assert!(result == expected); 67 | } 68 | -------------------------------------------------------------------------------- /src/http/response.rs: -------------------------------------------------------------------------------- 1 | use std::str::StrSplits; 2 | 3 | pub struct Response { 4 | pub status_code: uint, 5 | pub body: String 6 | } 7 | 8 | pub fn parse(response_string: &str) -> Response { 9 | let status = response_string.split_str("\r\n").next().unwrap().words(); 10 | let status_code = from_str::(status.skip(1).next().unwrap()).unwrap(); 11 | 12 | let spl: StrSplits = response_string.split_str("\r\n\r\n"); 13 | let body = spl.skip(1).next().unwrap(); 14 | Response { 15 | status_code: status_code, 16 | body: String::from_str(body) 17 | } 18 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![comment = "Rust Docker Client"] 2 | 3 | #![license = "MIT/ASL2"] 4 | 5 | #![crate_type = "lib"] 6 | 7 | #![feature (globs, macro_rules)] 8 | 9 | extern crate collections; 10 | extern crate debug; 11 | extern crate serialize; 12 | 13 | pub use docker::Docker; 14 | 15 | mod http; 16 | mod methods; 17 | 18 | pub mod common; 19 | pub mod docker; 20 | 21 | -------------------------------------------------------------------------------- /src/methods/container.rs: -------------------------------------------------------------------------------- 1 | use serialize::{json, Decodable}; 2 | use serialize::json::DecoderError; 3 | 4 | use super::super::common::containers::Containers as Containers; 5 | use super::super::http as http; 6 | 7 | 8 | /// 9 | /// GET /containers/json 10 | /// 11 | 12 | fn parse_get_containers(json_string: &str) -> Result { 13 | let json_object = json::from_str(json_string); 14 | let mut decoder = json::Decoder::new(json_object.unwrap()); 15 | Decodable::decode(&mut decoder) 16 | } 17 | 18 | pub fn get_containers(socket_path: &str) -> Containers { 19 | let method = http::GET; 20 | let path = "/containers/json?all=1&size=1"; 21 | 22 | let response = http::make_request(socket_path.as_slice(), method, path); 23 | if response.status_code >= 200 && response.status_code < 300 { 24 | let result = parse_get_containers(response.body.as_slice()); 25 | match result { 26 | Err(_) => fail!("JSON response could not be decoded"), 27 | Ok(containers) => containers 28 | } 29 | } 30 | else { 31 | fail!("HTTP response code was {}", response.status_code); 32 | } 33 | } 34 | 35 | /// 36 | /// POST /containers/(id)/stop 37 | /// 38 | 39 | pub fn stop_container_impl(socket_path: &str, id: &str, wait_time: Option) { 40 | let method = http::POST; 41 | let mut path = String::new(); 42 | path.push_str("/containers/"); 43 | path.push_str(id); 44 | path.push_str("/stop"); 45 | 46 | match wait_time { 47 | Some(timeout_value) => { 48 | // If a wait time was specified, include it in the query string 49 | path.push_str("?t="); 50 | path.push_str(timeout_value.to_string().as_slice()); 51 | } 52 | None => { 53 | // Don't do anything 54 | } 55 | }; 56 | 57 | let response = http::make_request(socket_path.as_slice(), method, path.as_slice()); 58 | if response.status_code < 200 || response.status_code >= 400 { 59 | fail!("HTTP response code was {}\n{}", response.status_code, response.body); 60 | } 61 | } 62 | 63 | /// 64 | /// POST /containers/(id)/restart 65 | /// 66 | 67 | pub fn restart_container_impl(socket_path: &str, id: &str, wait_time: Option) { 68 | let method = http::POST; 69 | let mut path = String::new(); 70 | path.push_str("/containers/"); 71 | path.push_str(id); 72 | path.push_str("/restart"); 73 | 74 | match wait_time { 75 | Some(timeout_value) => { 76 | // If a wait time was specified, include it in the query string 77 | path.push_str("?t="); 78 | path.push_str(timeout_value.to_string().as_slice()); 79 | } 80 | None => { 81 | // Don't do anything 82 | } 83 | }; 84 | 85 | let response = http::make_request(socket_path.as_slice(), method, path.as_slice()); 86 | if response.status_code < 200 || response.status_code >= 300 { 87 | fail!("HTTP response code was {}", response.status_code); 88 | } 89 | } 90 | 91 | /// 92 | /// DELETE /containers/(id)/ 93 | /// 94 | 95 | pub fn remove_container_impl(socket_path: &str, id: &str, force: bool) { 96 | let method = http::DELETE; 97 | let mut path = String::new(); 98 | path.push_str("/containers/"); 99 | path.push_str(id); 100 | path.push_str("?v=1"); 101 | 102 | if force { 103 | path.push_str("&f=1"); 104 | } 105 | 106 | let response = http::make_request(socket_path.as_slice(), method, path.as_slice()); 107 | if response.status_code < 200 || response.status_code >= 300 { 108 | fail!("HTTP response code was {}", response.status_code); 109 | } 110 | } -------------------------------------------------------------------------------- /src/methods/image.rs: -------------------------------------------------------------------------------- 1 | use serialize::{json, Decodable}; 2 | use serialize::json::DecoderError; 3 | 4 | use super::super::common::images::Images as Images; 5 | use super::super::http as http; 6 | 7 | /// 8 | /// GET /images/json 9 | /// 10 | 11 | fn parse_get_images(json_string: &str) -> Result { 12 | let json_object = json::from_str(json_string); 13 | let mut decoder = json::Decoder::new(json_object.unwrap()); 14 | Decodable::decode(&mut decoder) 15 | } 16 | 17 | pub fn get_images(socket_path: &str) -> Images { 18 | let method = http::GET; 19 | let path = "/images/json?all=0"; 20 | 21 | let response = http::make_request(socket_path.as_slice(), method, path); 22 | if response.status_code >= 200 && response.status_code < 300 { 23 | let result = parse_get_images(response.body.as_slice()); 24 | match result { 25 | Err(_) => fail!("JSON response could not be decoded"), 26 | Ok(containers) => containers 27 | } 28 | } 29 | else { 30 | fail!("HTTP response code was {}", response.status_code); 31 | } 32 | } -------------------------------------------------------------------------------- /src/methods/info.rs: -------------------------------------------------------------------------------- 1 | use serialize::{json, Decodable}; 2 | use serialize::json::DecoderError; 3 | 4 | use super::super::common::version::Version as Version; 5 | use super::super::common::sys_info::SysInfo as SysInfo; 6 | 7 | use super::super::http as http; 8 | 9 | /// 10 | /// GET /info 11 | /// 12 | 13 | fn parse_get_sys_info(json_string: &str) -> Result { 14 | let json_object = json::from_str(json_string); 15 | let mut decoder = json::Decoder::new(json_object.unwrap()); 16 | Decodable::decode(&mut decoder) 17 | } 18 | 19 | pub fn get_sys_info(socket_path: &str) -> SysInfo { 20 | let method = http::GET; 21 | let path = "/info"; 22 | let response = http::make_request(socket_path.as_slice(), method, path); 23 | if response.status_code == 200 { 24 | let result = parse_get_sys_info(response.body.as_slice()); 25 | match result { 26 | Err(_) => fail!("JSON response could not be decoded"), 27 | Ok(sys_info) => sys_info 28 | } 29 | } 30 | else { 31 | fail!("HTTP response code was {}", response.status_code); 32 | } 33 | } 34 | 35 | /// 36 | /// GET /version 37 | /// 38 | 39 | fn parse_get_version(json_string: &str) -> Result { 40 | let json_object = json::from_str(json_string); 41 | let mut decoder = json::Decoder::new(json_object.unwrap()); 42 | Decodable::decode(&mut decoder) 43 | } 44 | 45 | pub fn get_version(socket_path: &str) -> Version { 46 | let method = http::GET; 47 | let path = "/version"; 48 | let response = http::make_request(socket_path.as_slice(), method, path); 49 | if response.status_code == 200 { 50 | let result = parse_get_version(response.body.as_slice()); 51 | match result { 52 | Err(_) => fail!("JSON response could not be decoded"), 53 | Ok(version) => version 54 | } 55 | } 56 | else { 57 | fail!("HTTP response code was {}", response.status_code); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/methods/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod container; 2 | pub mod image; 3 | pub mod info; --------------------------------------------------------------------------------