rust_docker/api/
containers.rs

1#![allow(non_snake_case)]
2use std::collections::HashMap;
3
4use api::api_utils;
5use api::DockerApiClient;
6use utils::Response;
7
8use serde_json;
9
10#[derive(Serialize, Deserialize, Debug)]
11pub struct Container {
12    pub Id: String,
13    pub Names: Vec<String>,
14    pub Image: String,
15    pub ImageID: String,
16    pub Command: String,
17    pub State: String,
18    pub Status: String,
19    pub Ports: Vec<Port>,
20    pub Labels: Option<HashMap<String, String>>,
21
22    #[serde(default)]
23    pub SizeRw: Option<u64>,
24
25    #[serde(default)]
26    pub SizeRootFs: u64,
27    pub HostConfig: HostConfig,
28    pub Mounts: Vec<Mounts>,
29}
30
31#[derive(Serialize, Deserialize, Debug)]
32pub struct Port {
33    pub PrivatePort: u32,
34    pub PublicPort: u32,
35    pub Type: String,
36}
37
38#[derive(Serialize, Deserialize, Debug)]
39pub struct HostConfig {
40    pub NetworkMode: String,
41}
42
43#[derive(Serialize, Deserialize, Debug)]
44pub struct Mounts {
45    pub Name: String,
46    pub Source: String,
47    pub Destination: String,
48    pub Driver: String,
49    pub Mode: String,
50    pub RW: bool,
51    pub Propagation: String,
52}
53
54/// Structure for implementing Container Config
55/// Derives Default fot being able to get started even with minimal
56/// config.
57#[derive(Serialize, Deserialize, Debug, Default)]
58pub struct ContainerConfig {
59    pub Image: String,
60    pub Cmd: Vec<String>,
61
62    pub Hostname: String,
63    pub Domainname: String,
64    pub User: String,
65    pub AttachStdin: bool,
66    pub AttachStdout: bool,
67    pub AttachStderr: bool,
68    pub Tty: bool,
69    pub OpenStdin: bool,
70    pub StdinOnce: bool,
71    pub Env: Vec<String>,
72    pub Entrypoint: Option<String>,
73    pub Labels: Option<HashMap<String, String>>,
74    pub WorkingDir: String,
75}
76
77#[derive(Serialize, Deserialize, Debug)]
78pub struct CreateContainerResponse {
79    pub Id: String,
80}
81
82#[derive(Serialize, Deserialize, Debug, Default)]
83pub struct ContainerState {
84    pub Status: String,
85    pub Running: bool,
86    pub Paused: bool,
87    pub Restarting: bool,
88    pub OOMKilled: bool,
89    pub Dead: bool,
90    pub Pid: u64,
91    pub ExitCode: u64,
92    pub Error: String,
93    pub StartedAt: String,
94    pub FinishedAt: String,
95}
96
97/// * To use HostConfig use serde_json
98#[derive(Serialize, Deserialize, Debug, Default)]
99pub struct ContainerDetails {
100    pub Id: String,
101    pub Created: String,
102    pub Path: String,
103    pub Platform: Option<String>,
104    pub Args: Vec<String>,
105    pub State: ContainerState,
106    pub Image: String,
107    pub ResolvConfPath: String,
108    pub Name: String,
109    pub HostnamePath: String,
110    pub HostsPath: String,
111    pub LogPath: String,
112    pub RestartCount: u64,
113    pub Driver: String,
114    pub MountLabel: String,
115    pub ProcessLabel: String,
116    pub AppArmorProfile: String,
117    pub ExecIDs: Option<String>,
118    pub HostConfig: serde_json::Value,
119    pub Config: ContainerConfig,
120}
121
122#[derive(Serialize, Deserialize, Debug, Default)]
123pub struct ContainerFsChange {
124    Path: String,
125    Kind: u8,
126}
127
128pub trait Containers: DockerApiClient {
129    /// Just a helper function for the Containers DockerApiClient.
130    /// It formats the API request using the given parameters, and using
131    /// this request the docker daemon and sends back the response of the request
132    /// if the request was successful else an err.
133    fn get_response_from_api(
134        &self,
135        api_endpoint: &str,
136        method: &str,
137        body: &str,
138    ) -> Result<Response, String> {
139        let req = match api_utils::get_formatted_api_request(
140            api_endpoint,
141            method,
142            body,
143        ) {
144            Some(req) => req,
145            None => return Err("Error while preparing request".to_string()),
146        };
147
148        match self.request(&req) {
149            Some(resp) => match Response::parse_http_response(resp) {
150                Ok(response) => Ok(response),
151                Err(err) => {
152                    Err(format!("Response body was not valid : {}", err))
153                }
154            },
155            None => Err("Got no response from docker host.".to_string()),
156        }
157    }
158
159    /// Get Containers from the API endpoint with the method and query_param.
160    /// Helper function for Container trait.
161    fn get_containers(
162        &self,
163        api_endpoint: &str,
164        method: &str,
165        query_param: &str,
166    ) -> Result<Vec<Container>, String> {
167        let json_resp =
168            match self.get_response_from_api(api_endpoint, method, query_param)
169            {
170                Ok(resp) => {
171                    if resp.status_code == 200 {
172                        resp.body
173                    } else {
174                        return Err(format!(
175                            "Invalid Response : {} :: {}",
176                            resp.status_code, resp.body
177                        ));
178                    }
179                }
180                Err(err) => return Err(err),
181            };
182
183        let containers: Vec<Container> = match serde_json::from_str(&json_resp)
184        {
185            Ok(info) => info,
186            Err(err) => {
187                return Err(format!(
188                    "Error while deserializing JSON response : {}",
189                    err
190                ))
191            }
192        };
193
194        return Ok(containers);
195    }
196
197    /// List all the running containers
198    /// Return an instance of Vector of container
199    ///
200    /// # Example
201    ///
202    /// ```rust
203    /// extern crate docker_rs;
204    ///
205    /// use docker_rs::api::containers::Containers;
206    /// use docker_rs::client::DockerClient;
207    ///
208    /// let client = match DockerClient::new("unix:///var/run/docker.sock") {
209    ///     Ok(a) => a,
210    ///     Err(err) => {
211    ///         println!("{}", err);
212    ///         std::process::exit(1);
213    ///     }
214    /// };
215    ///
216    /// match client.list_running_containers(None) {
217    ///     Ok(containers) => println!("{:?}", containers),
218    ///     Err(err) => println!("An error occured : {}", err),
219    /// }
220    /// ```
221    fn list_running_containers(
222        &self,
223        limit: Option<u32>,
224    ) -> Result<Vec<Container>, String> {
225        let api_endpoint = "/containers/json";
226        let method = "GET";
227
228        let query_params = match limit {
229            Some(limit) => format!("?size=true&limit={}", limit),
230            None => "?size=true".to_string(),
231        };
232
233        self.get_containers(api_endpoint, method, &query_params)
234    }
235
236    /// List all containers whether running or stopped.
237    fn list_all_containers(
238        &self,
239        limit: Option<u32>,
240    ) -> Result<Vec<Container>, String> {
241        let api_endpoint = "/containers/json";
242        let method = "GET";
243
244        let query_params = match limit {
245            Some(limit) => format!("?all=true&size=true&limit={}", limit),
246            None => "?all=true&size=true".to_string(),
247        };
248
249        self.get_containers(api_endpoint, method, &query_params)
250    }
251
252    /// List container with the filter provided, the filter can be looked from
253    /// Docker engine official API documentation.
254    /// https://docs.docker.com/engine/api/v1.37/#operation/ContainerList
255    fn get_container_details_with_filter(
256        &self,
257        filter: &str,
258        limit: Option<u32>,
259    ) -> Result<Vec<Container>, String> {
260        let api_endpoint = "/containers/json";
261        let method = "GET";
262
263        let query_params = match limit {
264            Some(limit) => format!(
265                "?all=true&size=true&limit={}&filter={}",
266                limit, filter
267            ),
268            None => format!("?all=true&size=true&filter={}", filter),
269        };
270
271        self.get_containers(api_endpoint, method, &query_params)
272    }
273
274    /// Create a container from the ContainerConfig structure with the provided
275    /// `name`. The response for the request is the CreateContaierResponse struct
276    /// which contains the ID for the container which we created.
277    fn create_container(
278        &self,
279        name: &str,
280        config: ContainerConfig,
281    ) -> Result<CreateContainerResponse, String> {
282        let api_endpoint = format!("/containers/create?name={}", name);
283        let method = "POST";
284        let body = match serde_json::to_string(&config) {
285            Ok(body) => body,
286            Err(err) => {
287                return Err(format!(
288                    "Error while serialize Cotainer config : {}",
289                    err
290                ))
291            }
292        };
293
294        match self.get_response_from_api(&api_endpoint, method, &body) {
295            Ok(resp) => {
296                if resp.status_code != 201 {
297                    return Err(format!(
298                        "Invalid Request : {} :: {}",
299                        resp.status_code, resp.body
300                    ));
301                }
302                match serde_json::from_str(&resp.body) {
303                    Ok(info) => return Ok(info),
304                    Err(err) => {
305                        return Err(format!(
306                            "Error while deserializing JSON response : {}",
307                            err
308                        ))
309                    }
310                };
311            }
312            Err(err) => Err(err),
313        }
314    }
315
316    /// Creates/Spawn docker container from the configuration provided. It only
317    ///
318    /// * Rust does not provide named arguments, so we are doing it this way
319    /// Currently rust structures does not have default values, so all the
320    /// values for the structure needs to be specified.
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// extern crate docker_rs;
326    ///
327    /// use docker_rs::api::containers::Containers;
328    /// use docker_rs::client::DockerClient;
329    ///
330    /// let client = match DockerClient::new("unix:///var/run/docker.sock") {
331    ///     Ok(a) => a,
332    ///     Err(err) => {
333    ///         println!("{}", err);
334    ///         std::process::exit(1);
335    ///     }
336    /// };
337    ///
338    /// let mut cmd: Vec<String> = Vec::new();
339    /// cmd.push("ls".to_string());
340    ///
341    /// match client.create_container_minimal("my_container", "debian:jessie", cmd) {
342    ///     Ok(containers) => println!("{:?}", containers),
343    ///     Err(err) => println!("An error occured : {}", err),
344    /// }
345    /// ```
346    fn create_container_minimal(
347        &self,
348        name: &str,
349        image: &str,
350        cmd: Vec<String>,
351    ) -> Result<CreateContainerResponse, String> {
352        let config = ContainerConfig {
353            Image: image.to_string(),
354            Cmd: cmd,
355            ..Default::default()
356        };
357
358        self.create_container(name, config)
359    }
360
361    /// Inspects the container with the provided ID
362    /// Returns Low level information about the container.
363    ///
364    /// # Example
365    ///
366    /// ```rust
367    /// extern crate docker_rs;
368    ///
369    /// use docker_rs::api::containers::Containers;
370    /// use docker_rs::client::DockerClient;
371    ///
372    /// let client = match DockerClient::new("unix:///var/run/docker.sock") {
373    ///     Ok(a) => a,
374    ///     Err(err) => {
375    ///         println!("{}", err);
376    ///         std::process::exit(1);
377    ///     }
378    /// };
379    ///
380    /// // ID of the container passed as an argument.
381    /// match client.inspect_container("f808ca...") {
382    ///     Ok(info) => println!("{:?}", info),
383    ///     Err(err) => println!("An error occured : {}", err),
384    /// }
385    /// ```
386    fn inspect_container(&self, id: &str) -> Result<ContainerDetails, String> {
387        let api_endpoint = format!("/containers/{id}/json", id = id);
388        let method = "GET";
389
390        match self.get_response_from_api(&api_endpoint, method, "") {
391            Ok(resp) => {
392                if resp.status_code != 200 {
393                    return Err(format!(
394                        "Invalid Request : {} :: {}",
395                        resp.status_code, resp.body
396                    ));
397                }
398                match serde_json::from_str(&resp.body) {
399                    Ok(info) => return Ok(info),
400                    Err(err) => {
401                        return Err(format!(
402                            "Error while deserializing JSON response : {}",
403                            err
404                        ))
405                    }
406                };
407            }
408            Err(err) => Err(err),
409        }
410    }
411
412    /// Gives the changes done to somewhere in the filesystem in the docker container as a list of
413    /// files with the kind of changes.
414    fn get_container_filesystem_changes(
415        &self,
416        id: &str,
417    ) -> Result<Vec<ContainerFsChange>, String> {
418        let api_endpoint = format!("/containers/{id}/changes", id = id);
419        let method = "GET";
420
421        match self.get_response_from_api(&api_endpoint, method, "") {
422            Ok(resp) => {
423                // If the response is null, then there is no changes in the file
424                // system so just return and empty vector. Serializing this will
425                // result in error.
426                if resp.status_code != 200 {
427                    return Err(format!(
428                        "Invalid Request : {} :: {}",
429                        resp.status_code, resp.body
430                    ));
431                }
432                if resp.body == "null" {
433                    return Ok(Vec::new());
434                }
435
436                match serde_json::from_str(&resp.body) {
437                    Ok(info) => return Ok(info),
438                    Err(err) => {
439                        return Err(format!(
440                            "Error while deserializing JSON response : {}",
441                            err
442                        ))
443                    }
444                };
445            }
446            Err(err) => Err(err),
447        }
448    }
449
450    /// Function to manipulate container status
451    /// It is a parent function for all the commands which result in a status change
452    /// of the container.
453    ///
454    /// This includes the following:
455    /// * `start_container`
456    /// * `stop_container`
457    /// * `pause_container`
458    /// * `unpause_container`
459    /// * `restart_container`
460    /// * `kill_container`
461    /// * `rename_container`
462    ///
463    /// You can call any of these function or directly manipulate_container_status
464    ///
465    /// # Example
466    ///
467    /// ```rust
468    /// extern crate docker_rs;
469    ///
470    /// use docker_rs::api::containers::Containers;
471    /// use docker_rs::client::DockerClient;
472    ///
473    /// let client = match DockerClient::new("unix:///var/run/docker.sock") {
474    ///     Ok(a) => a,
475    ///     Err(err) => {
476    ///         println!("{}", err);
477    ///         std::process::exit(1);
478    ///     }
479    /// };
480    ///
481    /// // ID of the container passed as an argument.
482    /// match client.manipulate_container_status("start", "f808ca...", "") {
483    ///     Ok(info) => println!("{:?}", info),
484    ///     Err(err) => println!("An error occured : {}", err),
485    /// }
486    ///
487    /// // Or alternatively you can also directly use
488    /// match client.start_container("f808ca...") {
489    ///     Ok(info) => println!("{}", info),
490    ///     Err(err) => println!("An error occured : {}", err),
491    /// }
492    ///
493    /// // Similarly other function can also be used
494    /// ```
495    fn manipulate_container_status(
496        &self,
497        action: &str,
498        id: &str,
499        params: &str,
500    ) -> Result<String, String> {
501        let api_endpoint = format!(
502            "/containers/{id}/{action}",
503            id = id,
504            action = action
505        );
506        let method = "GET";
507
508        match self.get_response_from_api(&api_endpoint, method, params) {
509            Ok(resp) => {
510                if resp.status_code == 204 {
511                    Ok(format!("Container {} successful", action))
512                } else if resp.status_code == 304 {
513                    Err(format!("Container already {}ed", action))
514                } else {
515                    Err(format!(
516                        "Error while requesting Docker API : {} :: {}",
517                        resp.status_code, resp.body
518                    ))
519                }
520            }
521            Err(err) => return Err(err),
522        }
523    }
524
525    fn start_container(&self, id: &str) -> Result<String, String> {
526        self.manipulate_container_status("start", id, "")
527    }
528
529    fn stop_container(
530        &self,
531        id: &str,
532        delay: Option<&str>,
533    ) -> Result<String, String> {
534        let param = match delay {
535            Some(d) => format!("t={}", d),
536            None => String::new(),
537        };
538        self.manipulate_container_status("stop", id, &param)
539    }
540
541    fn pause_container(&self, id: &str) -> Result<String, String> {
542        self.manipulate_container_status("pause", id, "")
543    }
544
545    fn unpause_container(&self, id: &str) -> Result<String, String> {
546        self.manipulate_container_status("unpause", id, "")
547    }
548
549    fn restart_container(
550        &self,
551        id: &str,
552        delay: Option<&str>,
553    ) -> Result<String, String> {
554        let param = match delay {
555            Some(d) => format!("t={}", d),
556            None => String::new(),
557        };
558        self.manipulate_container_status("restart", id, &param)
559    }
560
561    fn kill_container(
562        &self,
563        id: &str,
564        signal: Option<&str>,
565    ) -> Result<String, String> {
566        let param = match signal {
567            Some(sig) => format!("signal={}", sig),
568            None => String::new(),
569        };
570        self.manipulate_container_status("kill", id, &param)
571    }
572
573    fn rename_container(&self, id: &str, name: &str) -> Result<String, String> {
574        let name_param = &format!("name={}", name);
575        self.manipulate_container_status("rename", id, name_param)
576    }
577}