docker_sdk/
container.rs

1//! Create and manage containers.
2//!
3//! API Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Container>
4
5use std::{collections::HashMap, hash::Hash, io, iter::Peekable, path::Path, time::Duration};
6
7use futures_util::{
8    io::{AsyncRead, AsyncWrite},
9    stream::Stream,
10    TryStreamExt,
11};
12use hyper::Body;
13use mime::Mime;
14use serde::{Deserialize, Serialize};
15use serde_json::{json, Map, Value};
16use url::form_urlencoded;
17
18use crate::{
19    docker::Docker,
20    errors::{Error, Result},
21    exec::{Exec, ExecContainerOptions},
22    image::ContainerConfig,
23    network::NetworkSettings,
24    transport::Payload,
25    tty::{self, Multiplexer as TtyMultiPlexer},
26};
27
28#[cfg(feature = "chrono")]
29use crate::datetime::datetime_from_unix_timestamp;
30#[cfg(feature = "chrono")]
31use chrono::{DateTime, Utc};
32
33/// Interface for accessing and manipulating a docker container
34///
35/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Container)
36pub struct Container<'docker> {
37    docker: &'docker Docker,
38    id: String,
39}
40
41impl<'docker> Container<'docker> {
42    /// Exports an interface exposing operations against a container instance
43    pub fn new<S>(
44        docker: &'docker Docker,
45        id: S,
46    ) -> Self
47    where
48        S: Into<String>,
49    {
50        Container {
51            docker,
52            id: id.into(),
53        }
54    }
55
56    /// a getter for the container id
57    pub fn id(&self) -> &str {
58        &self.id
59    }
60
61    /// Inspects the current docker container instance's details
62    ///
63    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerInspect)
64    pub async fn inspect(&self) -> Result<ContainerDetails> {
65        self.docker
66            .get_json::<ContainerDetails>(&format!("/containers/{}/json", self.id)[..])
67            .await
68    }
69
70    /// Returns a `top` view of information about the container process
71    ///
72    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerTop)
73    pub async fn top(
74        &self,
75        psargs: Option<&str>,
76    ) -> Result<Top> {
77        let mut path = vec![format!("/containers/{}/top", self.id)];
78        if let Some(ref args) = psargs {
79            let encoded = form_urlencoded::Serializer::new(String::new())
80                .append_pair("ps_args", args)
81                .finish();
82            path.push(encoded)
83        }
84        self.docker.get_json(&path.join("?")).await
85    }
86
87    /// Returns a stream of logs emitted but the container instance
88    ///
89    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerLogs)
90    pub fn logs(
91        &self,
92        opts: &LogsOptions,
93    ) -> impl Stream<Item = Result<tty::TtyChunk>> + Unpin + 'docker {
94        let mut path = vec![format!("/containers/{}/logs", self.id)];
95        if let Some(query) = opts.serialize() {
96            path.push(query)
97        }
98
99        let stream = Box::pin(self.docker.stream_get(path.join("?")));
100
101        Box::pin(tty::decode(stream))
102    }
103
104    /// Attaches a multiplexed TCP stream to the container that can be used to read Stdout, Stderr and write Stdin.
105    async fn attach_raw(&self) -> Result<impl AsyncRead + AsyncWrite + Send + 'docker> {
106        self.docker
107            .stream_post_upgrade(
108                format!(
109                    "/containers/{}/attach?stream=1&stdout=1&stderr=1&stdin=1",
110                    self.id
111                ),
112                None,
113            )
114            .await
115    }
116
117    /// Attaches a [Multiplexer](crate::tty::Multiplexer) to the container.
118    ///
119    /// The [Multiplexer](crate::tty::Multiplexer) implements Stream for returning Stdout and
120    /// Stderr chunks. It also implements `[AsyncWrite]` for writing to Stdin.
121    ///
122    /// The multiplexer can be split into its read and write halves with the
123    /// [split](crate::tty::Multiplexer::split) method
124    ///
125    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach)
126    pub async fn attach(&self) -> Result<TtyMultiPlexer<'docker>> {
127        let tcp_stream = self.attach_raw().await?;
128
129        Ok(TtyMultiPlexer::new(tcp_stream))
130    }
131
132    /// Returns a set of changes made to the container instance
133    ///
134    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerChanges)
135    pub async fn changes(&self) -> Result<Vec<Change>> {
136        self.docker
137            .get_json::<Vec<Change>>(&format!("/containers/{}/changes", self.id)[..])
138            .await
139    }
140
141    /// Exports the current docker container into a tarball
142    ///
143    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerExport)
144    pub fn export(&self) -> impl Stream<Item = Result<Vec<u8>>> + 'docker {
145        self.docker
146            .stream_get(format!("/containers/{}/export", self.id))
147            .map_ok(|c| c.to_vec())
148    }
149
150    /// Returns a stream of stats specific to this container instance
151    ///
152    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStats)
153    pub fn stats(&self) -> impl Stream<Item = Result<Stats>> + Unpin + 'docker {
154        let codec = futures_codec::LinesCodec {};
155
156        let reader = Box::pin(
157            self.docker
158                .stream_get(format!("/containers/{}/stats", self.id))
159                .map_err(|e| io::Error::new(io::ErrorKind::Other, e)),
160        )
161        .into_async_read();
162
163        Box::pin(
164            futures_codec::FramedRead::new(reader, codec)
165                .map_err(Error::IO)
166                .and_then(|s: String| async move {
167                    serde_json::from_str(&s).map_err(Error::SerdeJsonError)
168                }),
169        )
170    }
171
172    /// Start the container instance
173    ///
174    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStart)
175    pub async fn start(&self) -> Result<()> {
176        self.docker
177            .post(&format!("/containers/{}/start", self.id)[..], None)
178            .await?;
179        Ok(())
180    }
181
182    /// Stop the container instance
183    ///
184    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStop)
185    pub async fn stop(
186        &self,
187        wait: Option<Duration>,
188    ) -> Result<()> {
189        let mut path = vec![format!("/containers/{}/stop", self.id)];
190        if let Some(w) = wait {
191            let encoded = form_urlencoded::Serializer::new(String::new())
192                .append_pair("t", &w.as_secs().to_string())
193                .finish();
194
195            path.push(encoded)
196        }
197        self.docker.post(&path.join("?"), None).await?;
198        Ok(())
199    }
200
201    /// Restart the container instance
202    ///
203    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRestart)
204    pub async fn restart(
205        &self,
206        wait: Option<Duration>,
207    ) -> Result<()> {
208        let mut path = vec![format!("/containers/{}/restart", self.id)];
209        if let Some(w) = wait {
210            let encoded = form_urlencoded::Serializer::new(String::new())
211                .append_pair("t", &w.as_secs().to_string())
212                .finish();
213            path.push(encoded)
214        }
215        self.docker.post(&path.join("?"), None).await?;
216        Ok(())
217    }
218
219    /// Kill the container instance
220    ///
221    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerKill)
222    pub async fn kill(
223        &self,
224        signal: Option<&str>,
225    ) -> Result<()> {
226        let mut path = vec![format!("/containers/{}/kill", self.id)];
227        if let Some(sig) = signal {
228            let encoded = form_urlencoded::Serializer::new(String::new())
229                .append_pair("signal", &sig.to_owned())
230                .finish();
231            path.push(encoded)
232        }
233        self.docker.post(&path.join("?"), None).await?;
234        Ok(())
235    }
236
237    /// Rename the container instance
238    ///
239    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRename)
240    pub async fn rename(
241        &self,
242        name: &str,
243    ) -> Result<()> {
244        let query = form_urlencoded::Serializer::new(String::new())
245            .append_pair("name", name)
246            .finish();
247        self.docker
248            .post(
249                &format!("/containers/{}/rename?{}", self.id, query)[..],
250                None,
251            )
252            .await?;
253        Ok(())
254    }
255
256    /// Pause the container instance
257    ///
258    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerPause)
259    pub async fn pause(&self) -> Result<()> {
260        self.docker
261            .post(&format!("/containers/{}/pause", self.id)[..], None)
262            .await?;
263        Ok(())
264    }
265
266    /// Unpause the container instance
267    ///
268    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerUnpause)
269    pub async fn unpause(&self) -> Result<()> {
270        self.docker
271            .post(&format!("/containers/{}/unpause", self.id)[..], None)
272            .await?;
273        Ok(())
274    }
275
276    /// Wait until the container stops
277    ///
278    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerWait)
279    pub async fn wait(&self) -> Result<Exit> {
280        self.docker
281            .post_json(format!("/containers/{}/wait", self.id), Payload::None)
282            .await
283    }
284
285    /// Delete the container instance
286    ///
287    /// Use remove instead to use the force/v options.
288    ///
289    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerDelete)
290    pub async fn delete(&self) -> Result<()> {
291        self.docker
292            .delete(&format!("/containers/{}", self.id)[..])
293            .await?;
294        Ok(())
295    }
296
297    /// Delete the container instance (todo: force/v)
298    ///
299    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRemove)
300    pub async fn remove(
301        &self,
302        opts: RmContainerOptions,
303    ) -> Result<()> {
304        let mut path = vec![format!("/containers/{}", self.id)];
305        if let Some(query) = opts.serialize() {
306            path.push(query)
307        }
308        self.docker.delete(&path.join("?")).await?;
309        Ok(())
310    }
311
312    /// Execute a command in this container
313    ///
314    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Exec)
315    pub fn exec(
316        &self,
317        opts: &ExecContainerOptions,
318    ) -> impl Stream<Item = Result<tty::TtyChunk>> + Unpin + 'docker {
319        Exec::create_and_start(self.docker, &self.id, opts)
320    }
321
322    /// Copy a file/folder from the container.  The resulting stream is a tarball of the extracted
323    /// files.
324    ///
325    /// If `path` is not an absolute path, it is relative to the container’s root directory. The
326    /// resource specified by `path` must exist. To assert that the resource is expected to be a
327    /// directory, `path` should end in `/` or `/`. (assuming a path separator of `/`). If `path`
328    /// ends in `/.`  then this indicates that only the contents of the path directory should be
329    /// copied.  A symlink is always resolved to its target.
330    ///
331    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerArchive)
332    pub fn copy_from(
333        &self,
334        path: &Path,
335    ) -> impl Stream<Item = Result<Vec<u8>>> + 'docker {
336        let path_arg = form_urlencoded::Serializer::new(String::new())
337            .append_pair("path", &path.to_string_lossy())
338            .finish();
339
340        let endpoint = format!("/containers/{}/archive?{}", self.id, path_arg);
341        self.docker.stream_get(endpoint).map_ok(|c| c.to_vec())
342    }
343
344    /// Copy a byte slice as file into (see `bytes`) the container.
345    ///
346    /// The file will be copied at the given location (see `path`) and will be owned by root
347    /// with access mask 644.
348    ///
349    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive)
350    pub async fn copy_file_into<P: AsRef<Path>>(
351        &self,
352        path: P,
353        bytes: &[u8],
354    ) -> Result<()> {
355        let path = path.as_ref();
356
357        let mut ar = tar::Builder::new(Vec::new());
358        let mut header = tar::Header::new_gnu();
359        header.set_size(bytes.len() as u64);
360        header.set_mode(0o0644);
361        ar.append_data(
362            &mut header,
363            path.to_path_buf()
364                .iter()
365                .skip(1)
366                .collect::<std::path::PathBuf>(),
367            bytes,
368        )?;
369        let data = ar.into_inner()?;
370
371        self.copy_to(Path::new("/"), data.into()).await?;
372        Ok(())
373    }
374
375    /// Copy a tarball (see `body`) to the container.
376    ///
377    /// The tarball will be copied to the container and extracted at the given location (see `path`).
378    ///
379    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive)
380    pub async fn copy_to(
381        &self,
382        path: &Path,
383        body: Body,
384    ) -> Result<()> {
385        let path_arg = form_urlencoded::Serializer::new(String::new())
386            .append_pair("path", &path.to_string_lossy())
387            .finish();
388
389        let mime = "application/x-tar".parse::<Mime>().unwrap();
390
391        self.docker
392            .put(
393                &format!("/containers/{}/archive?{}", self.id, path_arg),
394                Some((body, mime)),
395            )
396            .await?;
397        Ok(())
398    }
399}
400
401/// Interface for docker containers
402///
403/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Containers)
404pub struct Containers<'docker> {
405    docker: &'docker Docker,
406}
407
408impl<'docker> Containers<'docker> {
409    /// Exports an interface for interacting with docker containers
410    pub fn new(docker: &'docker Docker) -> Self {
411        Containers { docker }
412    }
413
414    /// Lists the container instances on the docker host
415    ///
416    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerList)
417    pub async fn list(
418        &self,
419        opts: &ContainerListOptions,
420    ) -> Result<Vec<ContainerInfo>> {
421        let mut path = vec!["/containers/json".to_owned()];
422        if let Some(query) = opts.serialize() {
423            path.push(query)
424        }
425        self.docker
426            .get_json::<Vec<ContainerInfo>>(&path.join("?"))
427            .await
428    }
429
430    /// Returns a reference to a set of operations available to a specific container instance
431    pub fn get<S>(
432        &self,
433        name: S,
434    ) -> Container<'docker>
435    where
436        S: Into<String>,
437    {
438        Container::new(self.docker, name)
439    }
440
441    /// Returns a builder interface for creating a new container instance
442    pub async fn create(
443        &self,
444        opts: &ContainerOptions,
445    ) -> Result<ContainerCreateInfo> {
446        let body: Body = opts.serialize()?.into();
447        let mut path = vec!["/containers/create".to_owned()];
448
449        if let Some(ref name) = opts.name {
450            path.push(
451                form_urlencoded::Serializer::new(String::new())
452                    .append_pair("name", name)
453                    .finish(),
454            );
455        }
456
457        self.docker
458            .post_json(&path.join("?"), Some((body, mime::APPLICATION_JSON)))
459            .await
460    }
461}
462
463/// Options for filtering container list results
464#[derive(Default, Debug)]
465pub struct ContainerListOptions {
466    params: HashMap<&'static str, String>,
467}
468
469impl ContainerListOptions {
470    /// return a new instance of a builder for options
471    pub fn builder() -> ContainerListOptionsBuilder {
472        ContainerListOptionsBuilder::default()
473    }
474
475    /// serialize options as a string. returns None if no options are defined
476    pub fn serialize(&self) -> Option<String> {
477        if self.params.is_empty() {
478            None
479        } else {
480            Some(
481                form_urlencoded::Serializer::new(String::new())
482                    .extend_pairs(&self.params)
483                    .finish(),
484            )
485        }
486    }
487}
488
489/// Filter options for container listings
490pub enum ContainerFilter {
491    ExitCode(u64),
492    Status(String),
493    LabelName(String),
494    Label(String, String),
495    Name(String),
496}
497
498/// Builder interface for `ContainerListOptions`
499#[derive(Default)]
500pub struct ContainerListOptionsBuilder {
501    params: HashMap<&'static str, String>,
502}
503
504impl ContainerListOptionsBuilder {
505    pub fn filter(
506        &mut self,
507        filters: Vec<ContainerFilter>,
508    ) -> &mut Self {
509        let mut param: HashMap<&str, Vec<String>> = HashMap::new();
510        for f in filters {
511            let (key, value) = match f {
512                ContainerFilter::ExitCode(c) => ("exited", c.to_string()),
513                ContainerFilter::Status(s) => ("status", s),
514                ContainerFilter::LabelName(n) => ("label", n),
515                ContainerFilter::Label(n, v) => ("label", format!("{}={}", n, v)),
516                ContainerFilter::Name(n) => ("name", n.to_string()),
517            };
518
519            param.entry(key).or_insert_with(Vec::new).push(value);
520        }
521        // structure is a a json encoded object mapping string keys to a list
522        // of string values
523        self.params
524            .insert("filters", serde_json::to_string(&param).unwrap());
525        self
526    }
527
528    pub fn all(&mut self) -> &mut Self {
529        self.params.insert("all", "true".to_owned());
530        self
531    }
532
533    pub fn since(
534        &mut self,
535        since: &str,
536    ) -> &mut Self {
537        self.params.insert("since", since.to_owned());
538        self
539    }
540
541    pub fn before(
542        &mut self,
543        before: &str,
544    ) -> &mut Self {
545        self.params.insert("before", before.to_owned());
546        self
547    }
548
549    pub fn sized(&mut self) -> &mut Self {
550        self.params.insert("size", "true".to_owned());
551        self
552    }
553
554    pub fn build(&self) -> ContainerListOptions {
555        ContainerListOptions {
556            params: self.params.clone(),
557        }
558    }
559}
560
561/// Interface for building a new docker container from an existing image
562#[derive(Serialize, Debug)]
563pub struct ContainerOptions {
564    pub name: Option<String>,
565    params: HashMap<&'static str, Value>,
566}
567
568/// Function to insert a JSON value into a tree where the desired
569/// location of the value is given as a path of JSON keys.
570fn insert<'a, I, V>(
571    key_path: &mut Peekable<I>,
572    value: &V,
573    parent_node: &mut Value,
574) where
575    V: Serialize,
576    I: Iterator<Item = &'a str>,
577{
578    let local_key = key_path.next().unwrap();
579
580    if key_path.peek().is_some() {
581        let node = parent_node
582            .as_object_mut()
583            .unwrap()
584            .entry(local_key.to_string())
585            .or_insert(Value::Object(Map::new()));
586
587        insert(key_path, value, node);
588    } else {
589        parent_node
590            .as_object_mut()
591            .unwrap()
592            .insert(local_key.to_string(), serde_json::to_value(value).unwrap());
593    }
594}
595
596impl ContainerOptions {
597    /// return a new instance of a builder for options
598    pub fn builder(name: &str) -> ContainerOptionsBuilder {
599        ContainerOptionsBuilder::new(name)
600    }
601
602    /// serialize options as a string. returns None if no options are defined
603    pub fn serialize(&self) -> Result<String> {
604        serde_json::to_string(&self.to_json()).map_err(Error::from)
605    }
606
607    fn to_json(&self) -> Value {
608        let mut body_members = Map::new();
609        // The HostConfig element gets initialized to an empty object,
610        // for backward compatibility.
611        body_members.insert("HostConfig".to_string(), Value::Object(Map::new()));
612        let mut body = Value::Object(body_members);
613        self.parse_from(&self.params, &mut body);
614        body
615    }
616
617    pub fn parse_from<'a, K, V>(
618        &self,
619        params: &'a HashMap<K, V>,
620        body: &mut Value,
621    ) where
622        &'a HashMap<K, V>: IntoIterator,
623        K: ToString + Eq + Hash,
624        V: Serialize,
625    {
626        for (k, v) in params.iter() {
627            let key_string = k.to_string();
628            insert(&mut key_string.split('.').peekable(), v, body)
629        }
630    }
631}
632
633#[derive(Default)]
634pub struct ContainerOptionsBuilder {
635    name: Option<String>,
636    params: HashMap<&'static str, Value>,
637}
638
639impl ContainerOptionsBuilder {
640    pub(crate) fn new(image: &str) -> Self {
641        let mut params = HashMap::new();
642
643        params.insert("Image", Value::String(image.to_owned()));
644        ContainerOptionsBuilder { name: None, params }
645    }
646
647    pub fn name(
648        &mut self,
649        name: &str,
650    ) -> &mut Self {
651        self.name = Some(name.to_owned());
652        self
653    }
654
655    /// Specify the working dir (corresponds to the `-w` docker cli argument)
656    pub fn working_dir(
657        &mut self,
658        working_dir: &str,
659    ) -> &mut Self {
660        self.params.insert("WorkingDir", json!(working_dir));
661        self
662    }
663
664    /// Specify any bind mounts, taking the form of `/some/host/path:/some/container/path`
665    pub fn volumes(
666        &mut self,
667        volumes: Vec<&str>,
668    ) -> &mut Self {
669        self.params.insert("HostConfig.Binds", json!(volumes));
670        self
671    }
672
673    /// enable all exposed ports on the container to be mapped to random, available, ports on the host
674    pub fn publish_all_ports(&mut self) -> &mut Self {
675        self.params
676            .insert("HostConfig.PublishAllPorts", json!(true));
677        self
678    }
679
680    pub fn expose(
681        &mut self,
682        srcport: u32,
683        protocol: &str,
684        hostport: u32,
685    ) -> &mut Self {
686        let mut exposedport: HashMap<String, String> = HashMap::new();
687        exposedport.insert("HostPort".to_string(), hostport.to_string());
688
689        // The idea here is to go thought the 'old' port binds and to apply them to the local
690        // 'port_bindings' variable, add the bind we want and replace the 'old' value
691        let mut port_bindings: HashMap<String, Value> = HashMap::new();
692        for (key, val) in self
693            .params
694            .get("HostConfig.PortBindings")
695            .unwrap_or(&json!(null))
696            .as_object()
697            .unwrap_or(&Map::new())
698            .iter()
699        {
700            port_bindings.insert(key.to_string(), json!(val));
701        }
702        port_bindings.insert(
703            format!("{}/{}", srcport, protocol),
704            json!(vec![exposedport]),
705        );
706
707        self.params
708            .insert("HostConfig.PortBindings", json!(port_bindings));
709
710        // Replicate the port bindings over to the exposed ports config
711        let mut exposed_ports: HashMap<String, Value> = HashMap::new();
712        let empty_config: HashMap<String, Value> = HashMap::new();
713        for key in port_bindings.keys() {
714            exposed_ports.insert(key.to_string(), json!(empty_config));
715        }
716
717        self.params.insert("ExposedPorts", json!(exposed_ports));
718
719        self
720    }
721
722    /// Publish a port in the container without assigning a port on the host
723    pub fn publish(
724        &mut self,
725        srcport: u32,
726        protocol: &str,
727    ) -> &mut Self {
728        /* The idea here is to go thought the 'old' port binds
729         * and to apply them to the local 'exposedport_bindings' variable,
730         * add the bind we want and replace the 'old' value */
731        let mut exposed_port_bindings: HashMap<String, Value> = HashMap::new();
732        for (key, val) in self
733            .params
734            .get("ExposedPorts")
735            .unwrap_or(&json!(null))
736            .as_object()
737            .unwrap_or(&Map::new())
738            .iter()
739        {
740            exposed_port_bindings.insert(key.to_string(), json!(val));
741        }
742        exposed_port_bindings.insert(format!("{}/{}", srcport, protocol), json!({}));
743
744        // Replicate the port bindings over to the exposed ports config
745        let mut exposed_ports: HashMap<String, Value> = HashMap::new();
746        let empty_config: HashMap<String, Value> = HashMap::new();
747        for key in exposed_port_bindings.keys() {
748            exposed_ports.insert(key.to_string(), json!(empty_config));
749        }
750
751        self.params.insert("ExposedPorts", json!(exposed_ports));
752
753        self
754    }
755
756    pub fn links(
757        &mut self,
758        links: Vec<&str>,
759    ) -> &mut Self {
760        self.params.insert("HostConfig.Links", json!(links));
761        self
762    }
763
764    pub fn memory(
765        &mut self,
766        memory: u64,
767    ) -> &mut Self {
768        self.params.insert("HostConfig.Memory", json!(memory));
769        self
770    }
771
772    /// Total memory limit (memory + swap) in bytes. Set to -1 (default) to enable unlimited swap.
773    pub fn memory_swap(
774        &mut self,
775        memory_swap: i64,
776    ) -> &mut Self {
777        self.params
778            .insert("HostConfig.MemorySwap", json!(memory_swap));
779        self
780    }
781
782    /// CPU quota in units of 10<sup>-9</sup> CPUs. Set to 0 (default) for there to be no limit.
783    ///
784    /// For example, setting `nano_cpus` to `500_000_000` results in the container being allocated
785    /// 50% of a single CPU, while `2_000_000_000` results in the container being allocated 2 CPUs.
786    pub fn nano_cpus(
787        &mut self,
788        nano_cpus: u64,
789    ) -> &mut Self {
790        self.params.insert("HostConfig.NanoCpus", json!(nano_cpus));
791        self
792    }
793
794    /// CPU quota in units of CPUs. This is a wrapper around `nano_cpus` to do the unit conversion.
795    ///
796    /// See [`nano_cpus`](#method.nano_cpus).
797    pub fn cpus(
798        &mut self,
799        cpus: f64,
800    ) -> &mut Self {
801        self.nano_cpus((1_000_000_000.0 * cpus) as u64)
802    }
803
804    /// Sets an integer value representing the container's relative CPU weight versus other
805    /// containers.
806    pub fn cpu_shares(
807        &mut self,
808        cpu_shares: u32,
809    ) -> &mut Self {
810        self.params
811            .insert("HostConfig.CpuShares", json!(cpu_shares));
812        self
813    }
814
815    pub fn labels(
816        &mut self,
817        labels: &HashMap<&str, &str>,
818    ) -> &mut Self {
819        self.params.insert("Labels", json!(labels));
820        self
821    }
822
823    /// Whether to attach to `stdin`.
824    pub fn attach_stdin(
825        &mut self,
826        attach: bool,
827    ) -> &mut Self {
828        self.params.insert("AttachStdin", json!(attach));
829        self.params.insert("OpenStdin", json!(attach));
830        self
831    }
832
833    /// Whether to attach to `stdout`.
834    pub fn attach_stdout(
835        &mut self,
836        attach: bool,
837    ) -> &mut Self {
838        self.params.insert("AttachStdout", json!(attach));
839        self
840    }
841
842    /// Whether to attach to `stderr`.
843    pub fn attach_stderr(
844        &mut self,
845        attach: bool,
846    ) -> &mut Self {
847        self.params.insert("AttachStderr", json!(attach));
848        self
849    }
850
851    /// Whether standard streams should be attached to a TTY.
852    pub fn tty(
853        &mut self,
854        tty: bool,
855    ) -> &mut Self {
856        self.params.insert("Tty", json!(tty));
857        self
858    }
859
860    pub fn extra_hosts(
861        &mut self,
862        hosts: Vec<&str>,
863    ) -> &mut Self {
864        self.params.insert("HostConfig.ExtraHosts", json!(hosts));
865        self
866    }
867
868    pub fn volumes_from(
869        &mut self,
870        volumes: Vec<&str>,
871    ) -> &mut Self {
872        self.params.insert("HostConfig.VolumesFrom", json!(volumes));
873        self
874    }
875
876    pub fn network_mode(
877        &mut self,
878        network: &str,
879    ) -> &mut Self {
880        self.params.insert("HostConfig.NetworkMode", json!(network));
881        self
882    }
883
884    pub fn env<E, S>(
885        &mut self,
886        envs: E,
887    ) -> &mut Self
888    where
889        S: AsRef<str> + Serialize,
890        E: AsRef<[S]> + Serialize,
891    {
892        self.params.insert("Env", json!(envs));
893        self
894    }
895
896    pub fn cmd(
897        &mut self,
898        cmds: Vec<&str>,
899    ) -> &mut Self {
900        self.params.insert("Cmd", json!(cmds));
901        self
902    }
903
904    pub fn entrypoint(
905        &mut self,
906        entrypoint: &str,
907    ) -> &mut Self {
908        self.params.insert("Entrypoint", json!(entrypoint));
909        self
910    }
911
912    pub fn capabilities(
913        &mut self,
914        capabilities: Vec<&str>,
915    ) -> &mut Self {
916        self.params.insert("HostConfig.CapAdd", json!(capabilities));
917        self
918    }
919
920    pub fn devices(
921        &mut self,
922        devices: Vec<HashMap<String, String>>,
923    ) -> &mut Self {
924        self.params.insert("HostConfig.Devices", json!(devices));
925        self
926    }
927
928    pub fn log_driver(
929        &mut self,
930        log_driver: &str,
931    ) -> &mut Self {
932        self.params
933            .insert("HostConfig.LogConfig.Type", json!(log_driver));
934        self
935    }
936
937    pub fn restart_policy(
938        &mut self,
939        name: &str,
940        maximum_retry_count: u64,
941    ) -> &mut Self {
942        self.params
943            .insert("HostConfig.RestartPolicy.Name", json!(name));
944        if name == "on-failure" {
945            self.params.insert(
946                "HostConfig.RestartPolicy.MaximumRetryCount",
947                json!(maximum_retry_count),
948            );
949        }
950        self
951    }
952
953    pub fn auto_remove(
954        &mut self,
955        set: bool,
956    ) -> &mut Self {
957        self.params.insert("HostConfig.AutoRemove", json!(set));
958        self
959    }
960
961    /// Signal to stop a container as a string. Default is "SIGTERM".
962    pub fn stop_signal(
963        &mut self,
964        sig: &str,
965    ) -> &mut Self {
966        self.params.insert("StopSignal", json!(sig));
967        self
968    }
969
970    /// Signal to stop a container as an integer. Default is 15 (SIGTERM).
971    pub fn stop_signal_num(
972        &mut self,
973        sig: u64,
974    ) -> &mut Self {
975        self.params.insert("StopSignal", json!(sig));
976        self
977    }
978
979    /// Timeout to stop a container. Only seconds are counted. Default is 10s
980    pub fn stop_timeout(
981        &mut self,
982        timeout: Duration,
983    ) -> &mut Self {
984        self.params.insert("StopTimeout", json!(timeout.as_secs()));
985        self
986    }
987
988    pub fn userns_mode(
989        &mut self,
990        mode: &str,
991    ) -> &mut Self {
992        self.params.insert("HostConfig.UsernsMode", json!(mode));
993        self
994    }
995
996    pub fn privileged(
997        &mut self,
998        set: bool,
999    ) -> &mut Self {
1000        self.params.insert("HostConfig.Privileged", json!(set));
1001        self
1002    }
1003
1004    pub fn user(
1005        &mut self,
1006        user: &str,
1007    ) -> &mut Self {
1008        self.params.insert("User", json!(user));
1009        self
1010    }
1011
1012    pub fn build(&self) -> ContainerOptions {
1013        ContainerOptions {
1014            name: self.name.clone(),
1015            params: self.params.clone(),
1016        }
1017    }
1018}
1019
1020/// Options for controlling log request results
1021#[derive(Default, Debug)]
1022pub struct LogsOptions {
1023    params: HashMap<&'static str, String>,
1024}
1025
1026impl LogsOptions {
1027    /// return a new instance of a builder for options
1028    pub fn builder() -> LogsOptionsBuilder {
1029        LogsOptionsBuilder::default()
1030    }
1031
1032    /// serialize options as a string. returns None if no options are defined
1033    pub fn serialize(&self) -> Option<String> {
1034        if self.params.is_empty() {
1035            None
1036        } else {
1037            Some(
1038                form_urlencoded::Serializer::new(String::new())
1039                    .extend_pairs(&self.params)
1040                    .finish(),
1041            )
1042        }
1043    }
1044}
1045
1046/// Builder interface for `LogsOptions`
1047#[derive(Default)]
1048pub struct LogsOptionsBuilder {
1049    params: HashMap<&'static str, String>,
1050}
1051
1052impl LogsOptionsBuilder {
1053    pub fn follow(
1054        &mut self,
1055        f: bool,
1056    ) -> &mut Self {
1057        self.params.insert("follow", f.to_string());
1058        self
1059    }
1060
1061    pub fn stdout(
1062        &mut self,
1063        s: bool,
1064    ) -> &mut Self {
1065        self.params.insert("stdout", s.to_string());
1066        self
1067    }
1068
1069    pub fn stderr(
1070        &mut self,
1071        s: bool,
1072    ) -> &mut Self {
1073        self.params.insert("stderr", s.to_string());
1074        self
1075    }
1076
1077    pub fn timestamps(
1078        &mut self,
1079        t: bool,
1080    ) -> &mut Self {
1081        self.params.insert("timestamps", t.to_string());
1082        self
1083    }
1084
1085    /// how_many can either be "all" or a to_string() of the number
1086    pub fn tail(
1087        &mut self,
1088        how_many: &str,
1089    ) -> &mut Self {
1090        self.params.insert("tail", how_many.to_owned());
1091        self
1092    }
1093
1094    #[cfg(feature = "chrono")]
1095    pub fn since<Tz>(
1096        &mut self,
1097        timestamp: &chrono::DateTime<Tz>,
1098    ) -> &mut Self
1099    where
1100        Tz: chrono::TimeZone,
1101    {
1102        self.params
1103            .insert("since", timestamp.timestamp().to_string());
1104        self
1105    }
1106
1107    #[cfg(not(feature = "chrono"))]
1108    pub fn since(
1109        &mut self,
1110        timestamp: i64,
1111    ) -> &mut Self {
1112        self.params.insert("since", timestamp.to_string());
1113        self
1114    }
1115
1116    pub fn build(&self) -> LogsOptions {
1117        LogsOptions {
1118            params: self.params.clone(),
1119        }
1120    }
1121}
1122
1123/// Options for controlling log request results
1124#[derive(Default, Debug)]
1125pub struct RmContainerOptions {
1126    params: HashMap<&'static str, String>,
1127}
1128
1129impl RmContainerOptions {
1130    /// return a new instance of a builder for options
1131    pub fn builder() -> RmContainerOptionsBuilder {
1132        RmContainerOptionsBuilder::default()
1133    }
1134
1135    /// serialize options as a string. returns None if no options are defined
1136    pub fn serialize(&self) -> Option<String> {
1137        if self.params.is_empty() {
1138            None
1139        } else {
1140            Some(
1141                form_urlencoded::Serializer::new(String::new())
1142                    .extend_pairs(&self.params)
1143                    .finish(),
1144            )
1145        }
1146    }
1147}
1148
1149/// Builder interface for `LogsOptions`
1150#[derive(Default)]
1151pub struct RmContainerOptionsBuilder {
1152    params: HashMap<&'static str, String>,
1153}
1154
1155impl RmContainerOptionsBuilder {
1156    pub fn force(
1157        &mut self,
1158        f: bool,
1159    ) -> &mut Self {
1160        self.params.insert("force", f.to_string());
1161        self
1162    }
1163
1164    pub fn volumes(
1165        &mut self,
1166        s: bool,
1167    ) -> &mut Self {
1168        self.params.insert("v", s.to_string());
1169        self
1170    }
1171
1172    pub fn build(&self) -> RmContainerOptions {
1173        RmContainerOptions {
1174            params: self.params.clone(),
1175        }
1176    }
1177}
1178
1179#[derive(Clone, Debug, Serialize, Deserialize)]
1180#[serde(rename_all = "PascalCase")]
1181pub struct ContainerInfo {
1182    #[cfg(feature = "chrono")]
1183    #[serde(deserialize_with = "datetime_from_unix_timestamp")]
1184    pub created: DateTime<Utc>,
1185    #[cfg(not(feature = "chrono"))]
1186    pub created: u64,
1187    pub command: String,
1188    pub id: String,
1189    pub image: String,
1190    #[serde(rename = "ImageID")]
1191    pub image_id: String,
1192    pub labels: HashMap<String, String>,
1193    pub names: Vec<String>,
1194    pub ports: Vec<Port>,
1195    pub state: String,
1196    pub status: String,
1197    pub size_rw: Option<i64>,
1198    pub size_root_fs: Option<i64>,
1199}
1200
1201#[derive(Clone, Debug, Serialize, Deserialize)]
1202#[serde(rename_all = "PascalCase")]
1203pub struct ContainerDetails {
1204    pub id: String,
1205    #[cfg(feature = "chrono")]
1206    pub created: DateTime<Utc>,
1207    #[cfg(not(feature = "chrono"))]
1208    pub created: String,
1209    pub path: String,
1210    pub args: Vec<String>,
1211    pub state: State,
1212    pub image: String,
1213    pub resolv_conf_path: String,
1214    pub hostname_path: String,
1215    pub hosts_path: String,
1216    pub log_path: String,
1217    pub name: String,
1218    pub restart_count: i64,
1219    pub driver: String,
1220    pub platform: String,
1221    pub mount_label: String,
1222    pub process_label: String,
1223    pub app_armor_profile: String,
1224    #[serde(rename = "ExecIDs")]
1225    pub exec_ids: Option<Vec<String>>,
1226    pub host_config: HostConfig,
1227    pub graph_driver: GraphDriverData,
1228    pub mounts: Vec<Mount>,
1229    pub config: ContainerConfig,
1230    pub network_settings: NetworkSettings,
1231}
1232
1233#[derive(Clone, Debug, Serialize, Deserialize)]
1234#[serde(rename_all = "PascalCase")]
1235pub struct GraphDriverData {
1236    pub name: String,
1237    pub data: HashMap<String, String>,
1238}
1239
1240#[derive(Clone, Debug, Serialize, Deserialize)]
1241#[serde(rename_all = "PascalCase")]
1242pub struct Mount {
1243    pub source: String,
1244    pub destination: String,
1245    pub mode: String,
1246    #[serde(rename = "RW")]
1247    pub rw: bool,
1248}
1249
1250#[derive(Clone, Debug, Serialize, Deserialize)]
1251#[serde(rename_all = "PascalCase")]
1252pub struct State {
1253    pub error: String,
1254    pub exit_code: u64,
1255    #[cfg(feature = "chrono")]
1256    pub finished_at: DateTime<Utc>,
1257    #[cfg(not(feature = "chrono"))]
1258    pub finished_at: String,
1259    #[serde(rename = "OOMKilled")]
1260    pub oom_killed: bool,
1261    pub paused: bool,
1262    pub pid: u64,
1263    pub restarting: bool,
1264    pub running: bool,
1265    #[cfg(feature = "chrono")]
1266    pub started_at: DateTime<Utc>,
1267    #[cfg(not(feature = "chrono"))]
1268    pub started_at: String,
1269    pub status: String,
1270}
1271
1272#[derive(Clone, Debug, Serialize, Deserialize)]
1273#[serde(rename_all = "PascalCase")]
1274pub struct HostConfig {
1275    pub cpu_shares: Option<i64>,
1276    pub memory: Option<i64>,
1277    pub cgroup_parent: Option<String>,
1278    pub blkio_weight_device: Option<Vec<ThrottleDevice>>,
1279    pub blkio_device_read_bps: Option<Vec<ThrottleDevice>>,
1280    pub blkio_device_write_bps: Option<Vec<ThrottleDevice>>,
1281    #[serde(rename = "BlkioDeviceReadIOps")]
1282    pub blkio_device_read_iops: Option<Vec<ThrottleDevice>>,
1283    #[serde(rename = "BlkioDeviceWriteIOps")]
1284    pub blkio_device_write_iops: Option<Vec<ThrottleDevice>>,
1285    pub cpu_period: Option<i64>,
1286    pub cpu_quota: Option<i64>,
1287    pub cpu_realtime_period: Option<i64>,
1288    pub cpu_realtime_runtime: Option<i64>,
1289    pub cpuset_cpus: Option<String>,
1290    pub cpuset_mems: Option<String>,
1291    pub devices: Option<Vec<DeviceMapping>>,
1292    pub device_cgroup_rules: Option<String>,
1293    pub device_requests: Option<Vec<DeviceRequest>>,
1294    #[serde(rename = "KernelMemoryTCP")]
1295    pub kernel_memory_tcp: i64,
1296    pub memory_reservation: Option<i64>,
1297    pub memory_swap: Option<i64>,
1298    pub memory_swappiness: Option<i64>,
1299    #[serde(rename = "NanoCPUs")]
1300    pub nano_cpus: Option<i64>,
1301    pub oom_kill_disable: bool,
1302    pub init: Option<bool>,
1303    pub pids_limit: Option<i64>,
1304    pub ulimits: Option<Vec<Ulimit>>,
1305    pub cpu_count: i64,
1306    pub cpu_percent: i64,
1307    #[serde(rename = "IOMaximumIOps")]
1308    pub io_maximum_iops: u64,
1309    #[serde(rename = "IOMaximumBandwith")]
1310    pub io_maximum_bandwith: Option<u64>,
1311    pub binds: Option<Vec<String>>,
1312    #[serde(rename = "ContainerIDFile")]
1313    pub container_id_file: String,
1314    pub log_config: LogConfig,
1315    pub network_mode: String,
1316    pub port_bindings: Option<PortMap>,
1317    pub restart_policy: RestartPolicy,
1318    pub auto_remove: bool,
1319    pub volume_driver: String,
1320    pub volumes_from: Option<Vec<String>>,
1321    pub mounts: Option<Vec<Mount>>,
1322    pub cap_add: Option<Vec<String>>,
1323    pub cap_drop: Option<Vec<String>>,
1324    pub dns: Option<Vec<String>>,
1325    pub dns_options: Option<Vec<String>>,
1326    pub dns_search: Option<Vec<String>>,
1327    pub extra_hosts: Option<Vec<String>>,
1328    pub group_add: Option<Vec<String>>,
1329    pub ipc_mode: String,
1330    pub cgroup: String,
1331    pub links: Option<Vec<String>>,
1332    pub oom_score_adj: i64,
1333    pub pid_mode: Option<String>,
1334    pub privileged: bool,
1335    pub publish_all_ports: bool,
1336    pub readonly_rootfs: Option<bool>,
1337    pub security_opt: Option<Vec<String>>,
1338    pub storage_opt: Option<HashMap<String, String>>,
1339    pub tmpfs: Option<HashMap<String, String>>,
1340    #[serde(rename = "UTSMode")]
1341    pub uts_mode: String,
1342    pub userns_mode: String,
1343    pub shm_size: u64,
1344    pub sysctls: Option<HashMap<String, String>>,
1345    pub runtime: String,
1346    pub console_size: Option<Vec<u64>>,
1347    pub isolation: String,
1348    pub masked_paths: Option<Vec<String>>,
1349    pub readonly_paths: Option<Vec<String>>,
1350}
1351
1352#[derive(Clone, Debug, Serialize, Deserialize)]
1353#[serde(rename_all = "PascalCase")]
1354pub struct ThrottleDevice {
1355    pub path: String,
1356    pub rate: u64,
1357}
1358
1359#[derive(Clone, Debug, Serialize, Deserialize)]
1360#[serde(rename_all = "PascalCase")]
1361pub struct RestartPolicy {
1362    pub name: String,
1363    pub maximum_retry_count: u64,
1364}
1365
1366pub type PortMap = HashMap<String, Option<Vec<PortBinding>>>;
1367
1368#[derive(Clone, Debug, Serialize, Deserialize)]
1369#[serde(rename_all = "PascalCase")]
1370pub struct PortBinding {
1371    pub host_ip: String,
1372    pub host_port: String,
1373}
1374
1375#[derive(Clone, Debug, Serialize, Deserialize)]
1376pub struct LogConfig {
1377    #[serde(rename = "Type")]
1378    pub type_: String,
1379    #[serde(rename = "Config")]
1380    pub config: HashMap<String, String>,
1381}
1382
1383#[derive(Clone, Debug, Serialize, Deserialize)]
1384#[serde(rename_all = "PascalCase")]
1385pub struct Ulimit {
1386    pub name: String,
1387    pub soft: u64,
1388    pub hard: u64,
1389}
1390
1391#[derive(Clone, Debug, Serialize, Deserialize)]
1392#[serde(rename_all = "PascalCase")]
1393pub struct DeviceMapping {
1394    pub path_on_host: Option<String>,
1395    pub path_in_container: Option<String>,
1396    pub cgroup_permissions: Option<String>,
1397}
1398
1399#[derive(Clone, Debug, Serialize, Deserialize)]
1400#[serde(rename_all = "PascalCase")]
1401pub struct DeviceRequest {
1402    pub driver: String,
1403    pub count: u64,
1404    #[serde(rename = "DeviceIDs")]
1405    pub device_ids: Vec<String>,
1406    pub capabilities: Vec<String>,
1407    pub options: Option<serde_json::Value>,
1408}
1409
1410#[derive(Clone, Debug, Serialize, Deserialize)]
1411#[serde(rename_all = "PascalCase")]
1412pub struct Port {
1413    pub ip: Option<String>,
1414    pub private_port: u64,
1415    pub public_port: Option<u64>,
1416    #[serde(rename = "Type")]
1417    pub typ: String,
1418}
1419
1420#[derive(Clone, Debug, Serialize, Deserialize)]
1421pub struct Stats {
1422    pub read: String,
1423    pub networks: HashMap<String, NetworkStats>,
1424    pub memory_stats: MemoryStats,
1425    pub blkio_stats: BlkioStats,
1426    pub cpu_stats: CpuStats,
1427}
1428
1429#[derive(Clone, Debug, Serialize, Deserialize)]
1430pub struct NetworkStats {
1431    pub rx_dropped: u64,
1432    pub rx_bytes: u64,
1433    pub rx_errors: u64,
1434    pub tx_packets: u64,
1435    pub tx_dropped: u64,
1436    pub rx_packets: u64,
1437    pub tx_errors: u64,
1438    pub tx_bytes: u64,
1439}
1440
1441#[derive(Clone, Debug, Serialize, Deserialize)]
1442pub struct MemoryStats {
1443    pub max_usage: u64,
1444    pub usage: u64,
1445    pub failcnt: Option<u64>,
1446    pub limit: u64,
1447    pub stats: MemoryStat,
1448}
1449
1450#[derive(Clone, Debug, Serialize, Deserialize)]
1451pub struct MemoryStat {
1452    pub total_pgmajfault: u64,
1453    pub cache: u64,
1454    pub mapped_file: u64,
1455    pub total_inactive_file: u64,
1456    pub pgpgout: u64,
1457    pub rss: u64,
1458    pub total_mapped_file: u64,
1459    pub writeback: u64,
1460    pub unevictable: u64,
1461    pub pgpgin: u64,
1462    pub total_unevictable: u64,
1463    pub pgmajfault: u64,
1464    pub total_rss: u64,
1465    pub total_rss_huge: u64,
1466    pub total_writeback: u64,
1467    pub total_inactive_anon: u64,
1468    pub rss_huge: u64,
1469    pub hierarchical_memory_limit: u64,
1470    pub hierarchical_memsw_limit: u64,
1471    pub total_pgfault: u64,
1472    pub total_active_file: u64,
1473    pub active_anon: u64,
1474    pub total_active_anon: u64,
1475    pub total_pgpgout: u64,
1476    pub total_cache: u64,
1477    pub inactive_anon: u64,
1478    pub active_file: u64,
1479    pub pgfault: u64,
1480    pub inactive_file: u64,
1481    pub total_pgpgin: u64,
1482}
1483
1484#[derive(Clone, Debug, Serialize, Deserialize)]
1485pub struct CpuStats {
1486    pub cpu_usage: CpuUsage,
1487    pub system_cpu_usage: u64,
1488    pub throttling_data: ThrottlingData,
1489}
1490
1491#[derive(Clone, Debug, Serialize, Deserialize)]
1492pub struct CpuUsage {
1493    pub percpu_usage: Vec<u64>,
1494    pub usage_in_usermode: u64,
1495    pub total_usage: u64,
1496    pub usage_in_kernelmode: u64,
1497}
1498
1499#[derive(Clone, Debug, Serialize, Deserialize)]
1500pub struct ThrottlingData {
1501    pub periods: u64,
1502    pub throttled_periods: u64,
1503    pub throttled_time: u64,
1504}
1505
1506#[derive(Clone, Debug, Serialize, Deserialize)]
1507pub struct BlkioStats {
1508    pub io_service_bytes_recursive: Vec<BlkioStat>,
1509    pub io_serviced_recursive: Vec<BlkioStat>,
1510    pub io_queue_recursive: Vec<BlkioStat>,
1511    pub io_service_time_recursive: Vec<BlkioStat>,
1512    pub io_wait_time_recursive: Vec<BlkioStat>,
1513    pub io_merged_recursive: Vec<BlkioStat>,
1514    pub io_time_recursive: Vec<BlkioStat>,
1515    pub sectors_recursive: Vec<BlkioStat>,
1516}
1517
1518#[derive(Clone, Debug, Serialize, Deserialize)]
1519pub struct BlkioStat {
1520    pub major: u64,
1521    pub minor: u64,
1522    pub op: String,
1523    pub value: u64,
1524}
1525
1526#[derive(Clone, Debug, Serialize, Deserialize)]
1527#[serde(rename_all = "PascalCase")]
1528pub struct Change {
1529    pub kind: u64,
1530    pub path: String,
1531}
1532
1533#[derive(Clone, Debug, Serialize, Deserialize)]
1534#[serde(rename_all = "PascalCase")]
1535pub struct Top {
1536    pub titles: Vec<String>,
1537    pub processes: Vec<Vec<String>>,
1538}
1539
1540#[derive(Clone, Debug, Serialize, Deserialize)]
1541#[serde(rename_all = "PascalCase")]
1542pub struct ContainerCreateInfo {
1543    pub id: String,
1544    pub warnings: Option<Vec<String>>,
1545}
1546
1547#[derive(Clone, Debug, Serialize, Deserialize)]
1548#[serde(rename_all = "PascalCase")]
1549pub struct Exit {
1550    pub status_code: u64,
1551}
1552
1553#[cfg(test)]
1554mod tests {
1555    use super::*;
1556    use crate::container::ContainerFilter::{ExitCode, Label, LabelName, Status};
1557
1558    #[test]
1559    fn container_options_simple() {
1560        let builder = ContainerOptionsBuilder::new("test_image");
1561        let options = builder.build();
1562
1563        assert_eq!(
1564            r#"{"HostConfig":{},"Image":"test_image"}"#,
1565            options.serialize().unwrap()
1566        );
1567    }
1568
1569    #[test]
1570    fn container_options_env() {
1571        let options = ContainerOptionsBuilder::new("test_image")
1572            .env(vec!["foo", "bar"])
1573            .build();
1574
1575        assert_eq!(
1576            r#"{"Env":["foo","bar"],"HostConfig":{},"Image":"test_image"}"#,
1577            options.serialize().unwrap()
1578        );
1579    }
1580
1581    #[test]
1582    fn container_options_env_dynamic() {
1583        let env: Vec<String> = ["foo", "bar", "baz"]
1584            .iter()
1585            .map(|s| String::from(*s))
1586            .collect();
1587
1588        let options = ContainerOptionsBuilder::new("test_image").env(&env).build();
1589
1590        assert_eq!(
1591            r#"{"Env":["foo","bar","baz"],"HostConfig":{},"Image":"test_image"}"#,
1592            options.serialize().unwrap()
1593        );
1594    }
1595
1596    #[test]
1597    fn container_options_user() {
1598        let options = ContainerOptionsBuilder::new("test_image")
1599            .user("alice")
1600            .build();
1601
1602        assert_eq!(
1603            r#"{"HostConfig":{},"Image":"test_image","User":"alice"}"#,
1604            options.serialize().unwrap()
1605        );
1606    }
1607
1608    #[test]
1609    fn container_options_host_config() {
1610        let options = ContainerOptionsBuilder::new("test_image")
1611            .network_mode("host")
1612            .auto_remove(true)
1613            .privileged(true)
1614            .build();
1615
1616        assert_eq!(
1617            r#"{"HostConfig":{"AutoRemove":true,"NetworkMode":"host","Privileged":true},"Image":"test_image"}"#,
1618            options.serialize().unwrap()
1619        );
1620    }
1621
1622    #[test]
1623    fn container_options_expose() {
1624        let options = ContainerOptionsBuilder::new("test_image")
1625            .expose(80, "tcp", 8080)
1626            .build();
1627        assert_eq!(
1628            r#"{"ExposedPorts":{"80/tcp":{}},"HostConfig":{"PortBindings":{"80/tcp":[{"HostPort":"8080"}]}},"Image":"test_image"}"#,
1629            options.serialize().unwrap()
1630        );
1631        // try exposing two
1632        let options = ContainerOptionsBuilder::new("test_image")
1633            .expose(80, "tcp", 8080)
1634            .expose(81, "tcp", 8081)
1635            .build();
1636        assert_eq!(
1637            r#"{"ExposedPorts":{"80/tcp":{},"81/tcp":{}},"HostConfig":{"PortBindings":{"80/tcp":[{"HostPort":"8080"}],"81/tcp":[{"HostPort":"8081"}]}},"Image":"test_image"}"#,
1638            options.serialize().unwrap()
1639        );
1640    }
1641
1642    #[test]
1643    fn container_options_publish() {
1644        let options = ContainerOptionsBuilder::new("test_image")
1645            .publish(80, "tcp")
1646            .build();
1647        assert_eq!(
1648            r#"{"ExposedPorts":{"80/tcp":{}},"HostConfig":{},"Image":"test_image"}"#,
1649            options.serialize().unwrap()
1650        );
1651        // try exposing two
1652        let options = ContainerOptionsBuilder::new("test_image")
1653            .publish(80, "tcp")
1654            .publish(81, "tcp")
1655            .build();
1656        assert_eq!(
1657            r#"{"ExposedPorts":{"80/tcp":{},"81/tcp":{}},"HostConfig":{},"Image":"test_image"}"#,
1658            options.serialize().unwrap()
1659        );
1660    }
1661
1662    /// Test container option PublishAllPorts
1663    #[test]
1664    fn container_options_publish_all_ports() {
1665        let options = ContainerOptionsBuilder::new("test_image")
1666            .publish_all_ports()
1667            .build();
1668
1669        assert_eq!(
1670            r#"{"HostConfig":{"PublishAllPorts":true},"Image":"test_image"}"#,
1671            options.serialize().unwrap()
1672        );
1673    }
1674
1675    /// Test container options that are nested 3 levels deep.
1676    #[test]
1677    fn container_options_nested() {
1678        let options = ContainerOptionsBuilder::new("test_image")
1679            .log_driver("fluentd")
1680            .build();
1681
1682        assert_eq!(
1683            r#"{"HostConfig":{"LogConfig":{"Type":"fluentd"}},"Image":"test_image"}"#,
1684            options.serialize().unwrap()
1685        );
1686    }
1687
1688    /// Test the restart policy settings
1689    #[test]
1690    fn container_options_restart_policy() {
1691        let mut options = ContainerOptionsBuilder::new("test_image")
1692            .restart_policy("on-failure", 10)
1693            .build();
1694
1695        assert_eq!(
1696            r#"{"HostConfig":{"RestartPolicy":{"MaximumRetryCount":10,"Name":"on-failure"}},"Image":"test_image"}"#,
1697            options.serialize().unwrap()
1698        );
1699
1700        options = ContainerOptionsBuilder::new("test_image")
1701            .restart_policy("always", 0)
1702            .build();
1703
1704        assert_eq!(
1705            r#"{"HostConfig":{"RestartPolicy":{"Name":"always"}},"Image":"test_image"}"#,
1706            options.serialize().unwrap()
1707        );
1708    }
1709
1710    #[test]
1711    fn container_list_options_multiple_labels() {
1712        let options = ContainerListOptions::builder()
1713            .filter(vec![
1714                Label("label1".to_string(), "value".to_string()),
1715                LabelName("label2".to_string()),
1716            ])
1717            .build();
1718
1719        let form = form_urlencoded::Serializer::new(String::new())
1720            .append_pair("filters", r#"{"label":["label1=value","label2"]}"#)
1721            .finish();
1722
1723        assert_eq!(form, options.serialize().unwrap())
1724    }
1725
1726    #[test]
1727    fn container_list_options_exit_code() {
1728        let options = ContainerListOptions::builder()
1729            .filter(vec![ExitCode(0)])
1730            .build();
1731
1732        let form = form_urlencoded::Serializer::new(String::new())
1733            .append_pair("filters", r#"{"exited":["0"]}"#)
1734            .finish();
1735
1736        assert_eq!(form, options.serialize().unwrap())
1737    }
1738
1739    #[test]
1740    fn container_list_options_status() {
1741        let options = ContainerListOptions::builder()
1742            .filter(vec![Status("running".to_string())])
1743            .build();
1744
1745        let form = form_urlencoded::Serializer::new(String::new())
1746            .append_pair("filters", r#"{"status":["running"]}"#)
1747            .finish();
1748
1749        assert_eq!(form, options.serialize().unwrap())
1750    }
1751
1752    #[test]
1753    fn container_list_options_combined() {
1754        let options = ContainerListOptions::builder()
1755            .all()
1756            .filter(vec![
1757                Label("label1".to_string(), "value".to_string()),
1758                LabelName("label2".to_string()),
1759                ExitCode(0),
1760                Status("running".to_string()),
1761            ])
1762            .build();
1763
1764        let serialized = options.serialize().unwrap();
1765
1766        assert!(serialized.contains("all=true"));
1767        assert!(serialized.contains("filters="));
1768        assert!(serialized.contains("%22label%22%3A%5B%22label1%3Dvalue%22%2C%22label2%22%5D"));
1769        assert!(serialized.contains("%22status%22%3A%5B%22running%22%5D"));
1770        assert!(serialized.contains("%22exited%22%3A%5B%220%22%5D"));
1771    }
1772
1773    #[cfg(feature = "chrono")]
1774    #[test]
1775    fn logs_options() {
1776        let timestamp = chrono::NaiveDateTime::from_timestamp(2_147_483_647, 0);
1777        let since = chrono::DateTime::<chrono::Utc>::from_utc(timestamp, chrono::Utc);
1778
1779        let options = LogsOptionsBuilder::default()
1780            .follow(true)
1781            .stdout(true)
1782            .stderr(true)
1783            .timestamps(true)
1784            .tail("all")
1785            .since(&since)
1786            .build();
1787
1788        let serialized = options.serialize().unwrap();
1789
1790        assert!(serialized.contains("follow=true"));
1791        assert!(serialized.contains("stdout=true"));
1792        assert!(serialized.contains("stderr=true"));
1793        assert!(serialized.contains("timestamps=true"));
1794        assert!(serialized.contains("tail=all"));
1795        assert!(serialized.contains("since=2147483647"));
1796    }
1797
1798    #[cfg(not(feature = "chrono"))]
1799    #[test]
1800    fn logs_options() {
1801        let options = LogsOptionsBuilder::default()
1802            .follow(true)
1803            .stdout(true)
1804            .stderr(true)
1805            .timestamps(true)
1806            .tail("all")
1807            .since(2_147_483_647)
1808            .build();
1809
1810        let serialized = options.serialize().unwrap();
1811
1812        assert!(serialized.contains("follow=true"));
1813        assert!(serialized.contains("stdout=true"));
1814        assert!(serialized.contains("stderr=true"));
1815        assert!(serialized.contains("timestamps=true"));
1816        assert!(serialized.contains("tail=all"));
1817        assert!(serialized.contains("since=2147483647"));
1818    }
1819}