docker_sdk/
image.rs

1//! Create and manage images.
2//!
3//! API Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Image>
4
5use std::{collections::HashMap, io::Read, iter};
6
7use futures_util::{stream::Stream, TryFutureExt, TryStreamExt};
8use hyper::Body;
9use serde::{Deserialize, Serialize};
10use url::form_urlencoded;
11
12use crate::{docker::Docker, errors::Result, tarball, transport::tar};
13
14#[cfg(feature = "chrono")]
15use crate::datetime::datetime_from_unix_timestamp;
16#[cfg(feature = "chrono")]
17use chrono::{DateTime, Utc};
18
19/// Interface for accessing and manipulating a named docker image
20///
21/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Image)
22pub struct Image<'docker> {
23    docker: &'docker Docker,
24    name: String,
25}
26
27impl<'docker> Image<'docker> {
28    /// Exports an interface for operations that may be performed against a named image
29    pub fn new<S>(
30        docker: &'docker Docker,
31        name: S,
32    ) -> Self
33    where
34        S: Into<String>,
35    {
36        Image {
37            docker,
38            name: name.into(),
39        }
40    }
41
42    /// Inspects a named image's details
43    ///
44    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageInspect)
45    pub async fn inspect(&self) -> Result<ImageDetails> {
46        self.docker
47            .get_json(&format!("/images/{}/json", self.name)[..])
48            .await
49    }
50
51    /// Lists the history of the images set of changes
52    ///
53    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageHistory)
54    pub async fn history(&self) -> Result<Vec<History>> {
55        self.docker
56            .get_json(&format!("/images/{}/history", self.name)[..])
57            .await
58    }
59
60    /// Deletes an image
61    ///
62    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImagePrune)
63    pub async fn delete(&self) -> Result<Vec<Status>> {
64        self.docker
65            .delete_json::<Vec<Status>>(&format!("/images/{}", self.name)[..])
66            .await
67    }
68
69    /// Export this image to a tarball
70    ///
71    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageGet)
72    pub fn export(&self) -> impl Stream<Item = Result<Vec<u8>>> + Unpin + 'docker {
73        Box::pin(
74            self.docker
75                .stream_get(format!("/images/{}/get", self.name))
76                .map_ok(|c| c.to_vec()),
77        )
78    }
79
80    /// Adds a tag to an image
81    ///
82    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageTag)
83    pub async fn tag(
84        &self,
85        opts: &TagOptions,
86    ) -> Result<()> {
87        let mut path = vec![format!("/images/{}/tag", self.name)];
88        if let Some(query) = opts.serialize() {
89            path.push(query)
90        }
91        let _ = self.docker.post(&path.join("?"), None).await?;
92        Ok(())
93    }
94}
95
96/// Interface for docker images
97pub struct Images<'docker> {
98    docker: &'docker Docker,
99}
100
101impl<'docker> Images<'docker> {
102    /// Exports an interface for interacting with docker images
103    pub fn new(docker: &'docker Docker) -> Self {
104        Images { docker }
105    }
106
107    /// Builds a new image build by reading a Dockerfile in a target directory
108    ///
109    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageBuild)
110    pub fn build(
111        &self,
112        opts: &BuildOptions,
113    ) -> impl Stream<Item = Result<ImageBuildChunk>> + Unpin + 'docker {
114        let mut endpoint = vec!["/build".to_owned()];
115        if let Some(query) = opts.serialize() {
116            endpoint.push(query)
117        }
118
119        // To not tie the lifetime of `opts` to the 'stream, we do the tarring work outside of the
120        // stream. But for backwards compatability, we have to return the error inside of the
121        // stream.
122        let mut bytes = Vec::default();
123        let tar_result = tarball::dir(&mut bytes, opts.path.as_str());
124
125        // We must take ownership of the Docker reference. If we don't then the lifetime of 'stream
126        // is incorrectly tied to `self`.
127        let docker = self.docker;
128        Box::pin(
129            async move {
130                // Bubble up error inside the stream for backwards compatability
131                tar_result?;
132
133                let value_stream = docker.stream_post_into(
134                    endpoint.join("?"),
135                    Some((Body::from(bytes), tar())),
136                    None::<iter::Empty<_>>,
137                );
138
139                Ok(value_stream)
140            }
141            .try_flatten_stream(),
142        )
143    }
144
145    /// Lists the docker images on the current docker host
146    ///
147    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageList)
148    pub async fn list(
149        &self,
150        opts: &ImageListOptions,
151    ) -> Result<Vec<ImageInfo>> {
152        let mut path = vec!["/images/json".to_owned()];
153        if let Some(query) = opts.serialize() {
154            path.push(query);
155        }
156        self.docker
157            .get_json::<Vec<ImageInfo>>(&path.join("?"))
158            .await
159    }
160
161    /// Returns a reference to a set of operations available for a named image
162    pub fn get<S>(
163        &self,
164        name: S,
165    ) -> Image<'docker>
166    where
167        S: Into<String>,
168    {
169        Image::new(self.docker, name)
170    }
171
172    /// Search for docker images by term
173    ///
174    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageSearch)
175    pub async fn search(
176        &self,
177        term: &str,
178    ) -> Result<Vec<SearchResult>> {
179        let query = form_urlencoded::Serializer::new(String::new())
180            .append_pair("term", term)
181            .finish();
182        self.docker
183            .get_json::<Vec<SearchResult>>(&format!("/images/search?{}", query)[..])
184            .await
185    }
186
187    /// Pull and create a new docker images from an existing image
188    ///
189    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImagePull)
190    pub fn pull(
191        &self,
192        opts: &PullOptions,
193    ) -> impl Stream<Item = Result<ImageBuildChunk>> + Unpin + 'docker {
194        let mut path = vec!["/images/{name}/push".to_owned()];
195        if let Some(query) = opts.serialize() {
196            path.push(query);
197        }
198        let headers = opts
199            .auth_header()
200            .map(|a| iter::once(("X-Registry-Auth", a)));
201
202        Box::pin(self.docker.stream_post_into(path.join("?"), None, headers))
203    }
204
205      /// Pull and create a new docker images from an existing image
206    ///
207    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Image/operation/ImagePush)
208    pub fn push(
209        &self,
210        opts: &PushOptions,
211        name: String,
212    ) -> impl Stream<Item = Result<ImageBuildChunk>> + Unpin + 'docker {
213        let uri = &format!("/images/{}/push", name);
214        let mut path = vec![uri.to_owned()];
215        if let Some(query) = opts.serialize() {
216            path.push(query);
217        }
218        let headers = opts
219            .auth_header()
220            .map(|a| iter::once(("X-Registry-Auth", a)));
221
222        Box::pin(self.docker.stream_post_into(path.join("?"), None, headers))
223    }
224
225    /// exports a collection of named images,
226    /// either by name, name:tag, or image id, into a tarball
227    ///
228    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageGetAll)
229    pub fn export(
230        &self,
231        names: Vec<&str>,
232    ) -> impl Stream<Item = Result<Vec<u8>>> + 'docker {
233        let params = names.iter().map(|n| ("names", *n));
234        let query = form_urlencoded::Serializer::new(String::new())
235            .extend_pairs(params)
236            .finish();
237        self.docker
238            .stream_get(format!("/images/get?{}", query))
239            .map_ok(|c| c.to_vec())
240    }
241
242    /// imports an image or set of images from a given tarball source
243    /// source can be uncompressed on compressed via gzip, bzip2 or xz
244    ///
245    /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ImageLoad)
246    pub fn import<R>(
247        self,
248        mut tarball: R,
249    ) -> impl Stream<Item = Result<ImageBuildChunk>> + Unpin + 'docker
250    where
251        R: Read + Send + 'docker,
252    {
253        Box::pin(
254            async move {
255                let mut bytes = Vec::default();
256
257                tarball.read_to_end(&mut bytes)?;
258
259                let value_stream = self.docker.stream_post_into(
260                    "/images/load",
261                    Some((Body::from(bytes), tar())),
262                    None::<iter::Empty<_>>,
263                );
264                Ok(value_stream)
265            }
266            .try_flatten_stream(),
267        )
268    }
269}
270
271#[derive(Clone, Serialize, Debug)]
272#[serde(untagged)]
273pub enum RegistryAuth {
274    Password {
275        username: String,
276        password: String,
277
278        #[serde(skip_serializing_if = "Option::is_none")]
279        email: Option<String>,
280
281        #[serde(rename = "serveraddress")]
282        #[serde(skip_serializing_if = "Option::is_none")]
283        server_address: Option<String>,
284    },
285    Token {
286        #[serde(rename = "identitytoken")]
287        identity_token: String,
288    },
289}
290
291impl RegistryAuth {
292    /// return a new instance with token authentication
293    pub fn token<S>(token: S) -> RegistryAuth
294    where
295        S: Into<String>,
296    {
297        RegistryAuth::Token {
298            identity_token: token.into(),
299        }
300    }
301
302    /// return a new instance of a builder for authentication
303    pub fn builder() -> RegistryAuthBuilder {
304        RegistryAuthBuilder::default()
305    }
306
307    /// serialize authentication as JSON in base64
308    pub fn serialize(&self) -> String {
309        serde_json::to_string(self)
310            .map(|c| base64::encode_config(&c, base64::URL_SAFE))
311            .unwrap()
312    }
313}
314
315#[derive(Default)]
316pub struct RegistryAuthBuilder {
317    username: Option<String>,
318    password: Option<String>,
319    email: Option<String>,
320    server_address: Option<String>,
321}
322
323impl RegistryAuthBuilder {
324    pub fn username<I>(
325        &mut self,
326        username: I,
327    ) -> &mut Self
328    where
329        I: Into<String>,
330    {
331        self.username = Some(username.into());
332        self
333    }
334
335    pub fn password<I>(
336        &mut self,
337        password: I,
338    ) -> &mut Self
339    where
340        I: Into<String>,
341    {
342        self.password = Some(password.into());
343        self
344    }
345
346    pub fn email<I>(
347        &mut self,
348        email: I,
349    ) -> &mut Self
350    where
351        I: Into<String>,
352    {
353        self.email = Some(email.into());
354        self
355    }
356
357    pub fn server_address<I>(
358        &mut self,
359        server_address: I,
360    ) -> &mut Self
361    where
362        I: Into<String>,
363    {
364        self.server_address = Some(server_address.into());
365        self
366    }
367
368    pub fn build(&self) -> RegistryAuth {
369        RegistryAuth::Password {
370            username: self.username.clone().unwrap_or_else(String::new),
371            password: self.password.clone().unwrap_or_else(String::new),
372            email: self.email.clone(),
373            server_address: self.server_address.clone(),
374        }
375    }
376}
377
378#[derive(Default, Debug)]
379pub struct TagOptions {
380    pub params: HashMap<&'static str, String>,
381}
382
383impl TagOptions {
384    /// return a new instance of a builder for options
385    pub fn builder() -> TagOptionsBuilder {
386        TagOptionsBuilder::default()
387    }
388
389    /// serialize options as a string. returns None if no options are defined
390    pub fn serialize(&self) -> Option<String> {
391        if self.params.is_empty() {
392            None
393        } else {
394            Some(
395                form_urlencoded::Serializer::new(String::new())
396                    .extend_pairs(&self.params)
397                    .finish(),
398            )
399        }
400    }
401}
402
403#[derive(Default)]
404pub struct TagOptionsBuilder {
405    params: HashMap<&'static str, String>,
406}
407
408impl TagOptionsBuilder {
409    pub fn repo<R>(
410        &mut self,
411        r: R,
412    ) -> &mut Self
413    where
414        R: Into<String>,
415    {
416        self.params.insert("repo", r.into());
417        self
418    }
419
420    pub fn tag<T>(
421        &mut self,
422        t: T,
423    ) -> &mut Self
424    where
425        T: Into<String>,
426    {
427        self.params.insert("tag", t.into());
428        self
429    }
430
431    pub fn build(&self) -> TagOptions {
432        TagOptions {
433            params: self.params.clone(),
434        }
435    }
436}
437
438#[derive(Default, Debug)]
439pub struct PullOptions {
440    auth: Option<RegistryAuth>,
441    params: HashMap<&'static str, String>,
442}
443
444impl PullOptions {
445    /// return a new instance of a builder for options
446    pub fn builder() -> PullOptionsBuilder {
447        PullOptionsBuilder::default()
448    }
449
450    /// serialize options as a string. returns None if no options are defined
451    pub fn serialize(&self) -> Option<String> {
452        if self.params.is_empty() {
453            None
454        } else {
455            Some(
456                form_urlencoded::Serializer::new(String::new())
457                    .extend_pairs(&self.params)
458                    .finish(),
459            )
460        }
461    }
462
463    pub(crate) fn auth_header(&self) -> Option<String> {
464        self.auth.clone().map(|a| a.serialize())
465    }
466}
467
468pub struct PullOptionsBuilder {
469    auth: Option<RegistryAuth>,
470    params: HashMap<&'static str, String>,
471}
472
473impl Default for PullOptionsBuilder {
474    fn default() -> Self {
475        let mut params = HashMap::new();
476        params.insert("tag", "latest".to_string());
477
478        PullOptionsBuilder { auth: None, params }
479    }
480}
481
482impl PullOptionsBuilder {
483    ///  Name of the image to pull. The name may include a tag or digest.
484    /// This parameter may only be used when pulling an image.
485    /// If an untagged value is provided and no `tag` is provided, _all_
486    /// tags will be pulled
487    /// The pull is cancelled if the HTTP connection is closed.
488    pub fn image<I>(
489        &mut self,
490        img: I,
491    ) -> &mut Self
492    where
493        I: Into<String>,
494    {
495        self.params.insert("fromImage", img.into());
496        self
497    }
498
499    pub fn src<S>(
500        &mut self,
501        s: S,
502    ) -> &mut Self
503    where
504        S: Into<String>,
505    {
506        self.params.insert("fromSrc", s.into());
507        self
508    }
509
510    /// Repository name given to an image when it is imported. The repo may include a tag.
511    /// This parameter may only be used when importing an image.
512    ///
513    /// By default a `latest` tag is added when calling
514    /// [PullOptionsBuilder::default](PullOptionsBuilder::default].
515    pub fn repo<R>(
516        &mut self,
517        r: R,
518    ) -> &mut Self
519    where
520        R: Into<String>,
521    {
522        self.params.insert("repo", r.into());
523        self
524    }
525
526    /// Tag or digest. If empty when pulling an image,
527    /// this causes all tags for the given image to be pulled.
528    pub fn tag<T>(
529        &mut self,
530        t: T,
531    ) -> &mut Self
532    where
533        T: Into<String>,
534    {
535        self.params.insert("tag", t.into());
536        self
537    }
538
539    pub fn auth(
540        &mut self,
541        auth: RegistryAuth,
542    ) -> &mut Self {
543        self.auth = Some(auth);
544        self
545    }
546
547    pub fn build(&mut self) -> PullOptions {
548        PullOptions {
549            auth: self.auth.take(),
550            params: self.params.clone(),
551        }
552    }
553}
554
555
556
557#[derive(Default, Debug)]
558pub struct PushOptions {
559    auth: Option<RegistryAuth>,
560    params: HashMap<&'static str, String>,
561}
562
563impl PushOptions {
564    pub fn builder() -> PushOptionsBuilder {
565        PushOptionsBuilder::default()
566    }
567
568    pub fn serialize(&self) -> Option<String> {
569        if self.params.is_empty() {
570            None
571        } else {
572            Some(
573                form_urlencoded::Serializer::new(String::new())
574                    .extend_pairs(&self.params)
575                    .finish(),
576            )
577        }
578    }
579
580    pub(crate) fn auth_header(&self) -> Option<String> {
581        self.auth.clone().map(|a| a.serialize())
582    }
583}
584
585pub struct PushOptionsBuilder {
586    auth: Option<RegistryAuth>,
587    params: HashMap<&'static str, String>,
588}
589
590impl Default for PushOptionsBuilder {
591    fn default() -> Self {
592        let params = HashMap::new();
593        PushOptionsBuilder { auth: None, params }
594    }
595}
596
597impl PushOptionsBuilder {
598
599    pub fn tag<T>(
600        &mut self,
601        t: T,
602    ) -> &mut Self
603    where
604        T: Into<String>,
605    {
606        self.params.insert("tag", t.into());
607        self
608    }
609
610    pub fn auth(
611        &mut self,
612        auth: RegistryAuth,
613    ) -> &mut Self {
614        self.auth = Some(auth);
615        self
616    }
617
618    pub fn build(&mut self) -> PushOptions {
619        PushOptions {
620            auth: self.auth.take(),
621            params: self.params.clone(),
622        }
623    }
624}
625
626#[derive(Default, Debug)]
627pub struct BuildOptions {
628    pub path: String,
629    params: HashMap<&'static str, String>,
630}
631
632impl BuildOptions {
633    /// return a new instance of a builder for options
634    /// path is expected to be a file path to a directory containing a Dockerfile
635    /// describing how to build a Docker image
636    pub fn builder<S>(path: S) -> BuildOptionsBuilder
637    where
638        S: Into<String>,
639    {
640        BuildOptionsBuilder::new(path)
641    }
642
643    /// serialize options as a string. returns None if no options are defined
644    pub fn serialize(&self) -> Option<String> {
645        if self.params.is_empty() {
646            None
647        } else {
648            Some(
649                form_urlencoded::Serializer::new(String::new())
650                    .extend_pairs(&self.params)
651                    .finish(),
652            )
653        }
654    }
655}
656
657#[derive(Default)]
658pub struct BuildOptionsBuilder {
659    path: String,
660    params: HashMap<&'static str, String>,
661}
662
663impl BuildOptionsBuilder {
664    /// path is expected to be a file path to a directory containing a Dockerfile
665    /// describing how to build a Docker image
666    pub(crate) fn new<S>(path: S) -> Self
667    where
668        S: Into<String>,
669    {
670        BuildOptionsBuilder {
671            path: path.into(),
672            ..Default::default()
673        }
674    }
675
676    /// set the name of the docker file. defaults to "DockerFile"
677    pub fn dockerfile<P>(
678        &mut self,
679        path: P,
680    ) -> &mut Self
681    where
682        P: Into<String>,
683    {
684        self.params.insert("dockerfile", path.into());
685        self
686    }
687
688    /// tag this image with a name after building it
689    pub fn tag<T>(
690        &mut self,
691        t: T,
692    ) -> &mut Self
693    where
694        T: Into<String>,
695    {
696        self.params.insert("t", t.into());
697        self
698    }
699
700    pub fn remote<R>(
701        &mut self,
702        r: R,
703    ) -> &mut Self
704    where
705        R: Into<String>,
706    {
707        self.params.insert("remote", r.into());
708        self
709    }
710
711    /// don't use the image cache when building image
712    pub fn nocache(
713        &mut self,
714        nc: bool,
715    ) -> &mut Self {
716        self.params.insert("nocache", nc.to_string());
717        self
718    }
719
720    pub fn rm(
721        &mut self,
722        r: bool,
723    ) -> &mut Self {
724        self.params.insert("rm", r.to_string());
725        self
726    }
727
728    pub fn forcerm(
729        &mut self,
730        fr: bool,
731    ) -> &mut Self {
732        self.params.insert("forcerm", fr.to_string());
733        self
734    }
735
736    /// `bridge`, `host`, `none`, `container:<name|id>`, or a custom network name.
737    pub fn network_mode<T>(
738        &mut self,
739        t: T,
740    ) -> &mut Self
741    where
742        T: Into<String>,
743    {
744        self.params.insert("networkmode", t.into());
745        self
746    }
747
748    pub fn memory(
749        &mut self,
750        memory: u64,
751    ) -> &mut Self {
752        self.params.insert("memory", memory.to_string());
753        self
754    }
755
756    pub fn cpu_shares(
757        &mut self,
758        cpu_shares: u32,
759    ) -> &mut Self {
760        self.params.insert("cpushares", cpu_shares.to_string());
761        self
762    }
763
764    // todo: memswap
765    // todo: cpusetcpus
766    // todo: cpuperiod
767    // todo: cpuquota
768    // todo: buildargs
769
770    pub fn platform<T>(
771        &mut self,
772        t: T,
773    ) -> &mut Self
774    where
775        T: Into<String>,
776    {
777        self.params.insert("platform", t.into());
778        self
779    }
780
781
782    pub fn build(&self) -> BuildOptions {
783        BuildOptions {
784            path: self.path.clone(),
785            params: self.params.clone(),
786        }
787    }
788}
789
790/// Filter options for image listings
791pub enum ImageFilter {
792    Dangling,
793    LabelName(String),
794    Label(String, String),
795}
796
797/// Options for filtering image list results
798#[derive(Default, Debug)]
799pub struct ImageListOptions {
800    params: HashMap<&'static str, String>,
801}
802
803impl ImageListOptions {
804    pub fn builder() -> ImageListOptionsBuilder {
805        ImageListOptionsBuilder::default()
806    }
807    pub fn serialize(&self) -> Option<String> {
808        if self.params.is_empty() {
809            None
810        } else {
811            Some(
812                form_urlencoded::Serializer::new(String::new())
813                    .extend_pairs(&self.params)
814                    .finish(),
815            )
816        }
817    }
818}
819
820/// Builder interface for `ImageListOptions`
821#[derive(Default)]
822pub struct ImageListOptionsBuilder {
823    params: HashMap<&'static str, String>,
824}
825
826impl ImageListOptionsBuilder {
827    pub fn digests(
828        &mut self,
829        d: bool,
830    ) -> &mut Self {
831        self.params.insert("digests", d.to_string());
832        self
833    }
834
835    pub fn all(&mut self) -> &mut Self {
836        self.params.insert("all", "true".to_owned());
837        self
838    }
839
840    pub fn filter_name(
841        &mut self,
842        name: &str,
843    ) -> &mut Self {
844        self.params.insert("filter", name.to_owned());
845        self
846    }
847
848    pub fn filter(
849        &mut self,
850        filters: Vec<ImageFilter>,
851    ) -> &mut Self {
852        let mut param = HashMap::new();
853        for f in filters {
854            match f {
855                ImageFilter::Dangling => param.insert("dangling", vec![true.to_string()]),
856                ImageFilter::LabelName(n) => param.insert("label", vec![n]),
857                ImageFilter::Label(n, v) => param.insert("label", vec![format!("{}={}", n, v)]),
858            };
859        }
860        // structure is a a json encoded object mapping string keys to a list
861        // of string values
862        self.params
863            .insert("filters", serde_json::to_string(&param).unwrap());
864        self
865    }
866
867    pub fn build(&self) -> ImageListOptions {
868        ImageListOptions {
869            params: self.params.clone(),
870        }
871    }
872}
873
874#[derive(Clone, Debug, Serialize, Deserialize)]
875pub struct SearchResult {
876    pub description: String,
877    pub is_official: bool,
878    pub is_automated: bool,
879    pub name: String,
880    pub star_count: u64,
881}
882
883#[derive(Clone, Debug, Serialize, Deserialize)]
884#[serde(rename_all = "PascalCase")]
885pub struct ImageInfo {
886    #[cfg(feature = "chrono")]
887    #[serde(deserialize_with = "datetime_from_unix_timestamp")]
888    pub created: DateTime<Utc>,
889    #[cfg(not(feature = "chrono"))]
890    pub created: u64,
891    pub id: String,
892    pub parent_id: String,
893    pub labels: Option<HashMap<String, String>>,
894    pub repo_tags: Option<Vec<String>>,
895    pub repo_digests: Option<Vec<String>>,
896    pub virtual_size: u64,
897}
898
899#[derive(Clone, Debug, Serialize, Deserialize)]
900#[serde(rename_all = "PascalCase")]
901pub struct ImageDetails {
902    pub architecture: String,
903    pub author: String,
904    pub comment: String,
905    pub config: ContainerConfig,
906    #[cfg(feature = "chrono")]
907    pub created: DateTime<Utc>,
908    #[cfg(not(feature = "chrono"))]
909    pub created: String,
910    pub docker_version: String,
911    pub id: String,
912    pub os: String,
913    pub parent: String,
914    pub repo_tags: Option<Vec<String>>,
915    pub repo_digests: Option<Vec<String>>,
916    pub size: u64,
917    pub virtual_size: u64,
918}
919
920#[derive(Clone, Debug, Serialize, Deserialize)]
921#[serde(rename_all = "PascalCase")]
922pub struct ContainerConfig {
923    pub attach_stderr: bool,
924    pub attach_stdin: bool,
925    pub attach_stdout: bool,
926    pub cmd: Option<Vec<String>>,
927    pub domainname: String,
928    pub entrypoint: Option<Vec<String>>,
929    pub env: Option<Vec<String>>,
930    pub exposed_ports: Option<HashMap<String, HashMap<String, String>>>,
931    pub hostname: String,
932    pub image: String,
933    pub labels: Option<HashMap<String, String>>,
934    // pub MacAddress: String,
935    pub on_build: Option<Vec<String>>,
936    // pub NetworkDisabled: bool,
937    pub open_stdin: bool,
938    pub stdin_once: bool,
939    pub tty: bool,
940    pub user: String,
941    pub working_dir: String,
942}
943
944impl ContainerConfig {
945    pub fn env(&self) -> HashMap<String, String> {
946        let mut map = HashMap::new();
947        if let Some(ref vars) = self.env {
948            for e in vars {
949                let pair: Vec<&str> = e.split('=').collect();
950                map.insert(pair[0].to_owned(), pair[1].to_owned());
951            }
952        }
953        map
954    }
955}
956
957#[derive(Clone, Debug, Serialize, Deserialize)]
958#[serde(rename_all = "PascalCase")]
959pub struct History {
960    pub id: String,
961    #[cfg(feature = "chrono")]
962    #[serde(deserialize_with = "datetime_from_unix_timestamp")]
963    pub created: DateTime<Utc>,
964    #[cfg(not(feature = "chrono"))]
965    pub created: u64,
966    pub created_by: String,
967}
968
969#[derive(Clone, Debug, Serialize, Deserialize)]
970pub enum Status {
971    Untagged(String),
972    Deleted(String),
973}
974
975#[derive(Serialize, Deserialize, Debug)]
976#[serde(untagged)]
977/// Represents a response chunk from Docker api when building, pulling or importing an image.
978pub enum ImageBuildChunk {
979    Update {
980        stream: String,
981    },
982    Error {
983        error: String,
984        #[serde(rename = "errorDetail")]
985        error_detail: ErrorDetail,
986    },
987    Digest {
988        aux: Aux,
989    },
990    PullStatus {
991        status: String,
992        id: Option<String>,
993        progress: Option<String>,
994        #[serde(rename = "progressDetail")]
995        progress_detail: Option<ProgressDetail>,
996    },
997    PushedResponse {
998        status: String,
999        digest: String,
1000        size: usize,
1001    },
1002    PushedStatus {
1003        status: String,
1004        id: Option<String>,
1005    },
1006    Null {
1007        
1008    }
1009    
1010}
1011
1012
1013
1014#[derive(Serialize, Deserialize, Debug)]
1015pub struct Aux {
1016    #[serde(rename = "ID")]
1017    id: String,
1018}
1019
1020#[derive(Serialize, Deserialize, Debug)]
1021pub struct ErrorDetail {
1022    message: String,
1023}
1024
1025#[derive(Serialize, Deserialize, Debug)]
1026pub struct ProgressDetail {
1027    current: Option<u64>,
1028    total: Option<u64>,
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034
1035    /// Test registry auth with token
1036    #[test]
1037    fn registry_auth_token() {
1038        let options = RegistryAuth::token("abc");
1039        assert_eq!(
1040            base64::encode(r#"{"identitytoken":"abc"}"#),
1041            options.serialize()
1042        );
1043    }
1044
1045    /// Test registry auth with username and password
1046    #[test]
1047    fn registry_auth_password_simple() {
1048        let options = RegistryAuth::builder()
1049            .username("user_abc")
1050            .password("password_abc")
1051            .build();
1052        assert_eq!(
1053            base64::encode(r#"{"username":"user_abc","password":"password_abc"}"#),
1054            options.serialize()
1055        );
1056    }
1057
1058    /// Test registry auth with all fields
1059    #[test]
1060    fn registry_auth_password_all() {
1061        let options = RegistryAuth::builder()
1062            .username("user_abc")
1063            .password("password_abc")
1064            .email("email_abc")
1065            .server_address("https://example.org")
1066            .build();
1067        assert_eq!(
1068            base64::encode(
1069                r#"{"username":"user_abc","password":"password_abc","email":"email_abc","serveraddress":"https://example.org"}"#
1070            ),
1071            options.serialize()
1072        );
1073    }
1074}