docker_api/opts/
image.rs

1use std::{
2    collections::HashMap,
3    path::{Path, PathBuf},
4    string::ToString,
5};
6
7use containers_api::opts::{Filter, FilterItem};
8use containers_api::url::encoded_pairs;
9use containers_api::{
10    impl_filter_func, impl_map_field, impl_opts_builder, impl_str_field, impl_url_bool_field,
11    impl_url_field, impl_url_str_field,
12};
13use serde::Serialize;
14
15#[derive(Clone, Serialize, Debug)]
16#[serde(untagged)]
17pub enum RegistryAuth {
18    Password {
19        username: String,
20        password: String,
21
22        #[serde(skip_serializing_if = "Option::is_none")]
23        email: Option<String>,
24
25        #[serde(rename = "serveraddress")]
26        #[serde(skip_serializing_if = "Option::is_none")]
27        server_address: Option<String>,
28    },
29    Token {
30        #[serde(rename = "identitytoken")]
31        identity_token: String,
32    },
33}
34
35impl RegistryAuth {
36    /// return a new instance with token authentication
37    pub fn token<S>(token: S) -> RegistryAuth
38    where
39        S: Into<String>,
40    {
41        RegistryAuth::Token {
42            identity_token: token.into(),
43        }
44    }
45
46    /// return a new instance of a builder for authentication
47    pub fn builder() -> RegistryAuthBuilder {
48        RegistryAuthBuilder::default()
49    }
50
51    /// serialize authentication as JSON in base64
52    pub fn serialize(&self) -> String {
53        serde_json::to_string(self)
54            .map(|c| base64::encode_config(c, base64::URL_SAFE))
55            .unwrap_or_default()
56    }
57}
58
59#[derive(Default)]
60pub struct RegistryAuthBuilder {
61    username: Option<String>,
62    password: Option<String>,
63    email: Option<String>,
64    server_address: Option<String>,
65}
66
67impl RegistryAuthBuilder {
68    /// The username used for authentication.
69    pub fn username<U>(mut self, username: U) -> Self
70    where
71        U: Into<String>,
72    {
73        self.username = Some(username.into());
74        self
75    }
76
77    /// The password used for authentication.
78    pub fn password<P>(mut self, password: P) -> Self
79    where
80        P: Into<String>,
81    {
82        self.password = Some(password.into());
83        self
84    }
85
86    /// The email addres used for authentication.
87    pub fn email<E>(mut self, email: E) -> Self
88    where
89        E: Into<String>,
90    {
91        self.email = Some(email.into());
92        self
93    }
94
95    /// The server address of registry, should be a domain/IP without a protocol.
96    /// Example: `10.92.0.1`, `docker.corp.local`
97    pub fn server_address<A>(mut self, server_address: A) -> Self
98    where
99        A: Into<String>,
100    {
101        self.server_address = Some(server_address.into());
102        self
103    }
104
105    /// Create the final authentication object.
106    pub fn build(&self) -> RegistryAuth {
107        RegistryAuth::Password {
108            username: self.username.clone().unwrap_or_default(),
109            password: self.password.clone().unwrap_or_default(),
110            email: self.email.clone(),
111            server_address: self.server_address.clone(),
112        }
113    }
114}
115
116impl_opts_builder!(url => Tag);
117
118impl TagOptsBuilder {
119    impl_url_str_field!(repo => "repo");
120
121    impl_url_str_field!(tag => "tag");
122}
123
124#[derive(Default, Debug)]
125pub struct PullOpts {
126    auth: Option<RegistryAuth>,
127    params: HashMap<&'static str, serde_json::Value>,
128}
129
130impl PullOpts {
131    /// return a new instance of a builder for Opts
132    pub fn builder() -> PullOptsBuilder {
133        PullOptsBuilder::default()
134    }
135
136    /// serialize Opts as a string. returns None if no Opts are defined
137    pub fn serialize(&self) -> Option<String> {
138        if self.params.is_empty() {
139            None
140        } else {
141            Some(encoded_pairs(
142                self.params
143                    .iter()
144                    .map(|(k, v)| (k, v.as_str().unwrap_or_default())),
145            ))
146        }
147    }
148
149    pub(crate) fn auth_header(&self) -> Option<String> {
150        self.auth.clone().map(|a| a.serialize())
151    }
152}
153
154pub struct PullOptsBuilder {
155    auth: Option<RegistryAuth>,
156    params: HashMap<&'static str, serde_json::Value>,
157}
158
159impl Default for PullOptsBuilder {
160    fn default() -> Self {
161        let mut params = HashMap::new();
162        params.insert("tag", serde_json::Value::String("latest".into()));
163
164        PullOptsBuilder { auth: None, params }
165    }
166}
167
168impl PullOptsBuilder {
169    impl_str_field!(
170    /// Name of the image to pull. The name may include a tag or digest.
171    /// This parameter may only be used when pulling an image.
172    /// If an untagged value is provided and no `tag` is provided, _all_
173    /// tags will be pulled
174    /// The pull is cancelled if the HTTP connection is closed.
175    image => "fromImage");
176
177    impl_str_field!(src => "fromSrc");
178
179    impl_str_field!(
180    /// Repository name given to an image when it is imported. The repo may include a tag.
181    /// This parameter may only be used when importing an image.
182    /// 
183    /// By default a `latest` tag is added when calling
184    /// [PullOptsBuilder::default](PullOptsBuilder::default).
185    repo => "repo");
186
187    impl_str_field!(
188    /// Tag or digest. If empty when pulling an image,
189    /// this causes all tags for the given image to be pulled.
190    tag => "tag");
191
192    pub fn auth(mut self, auth: RegistryAuth) -> Self {
193        self.auth = Some(auth);
194        self
195    }
196
197    pub fn build(self) -> PullOpts {
198        PullOpts {
199            auth: self.auth,
200            params: self.params,
201        }
202    }
203}
204
205#[derive(Default, Debug, Clone)]
206pub struct ImageBuildOpts {
207    pub path: PathBuf,
208    params: HashMap<&'static str, String>,
209}
210
211impl ImageBuildOpts {
212    /// return a new instance of a builder for Opts
213    /// path is expected to be a file path to a directory containing a Dockerfile
214    /// describing how to build a Docker image
215    pub fn builder<P>(path: P) -> ImageBuildOptsBuilder
216    where
217        P: AsRef<Path>,
218    {
219        ImageBuildOptsBuilder::new(path)
220    }
221
222    /// serialize Opts as a string. returns None if no Opts are defined
223    pub fn serialize(&self) -> Option<String> {
224        if self.params.is_empty() {
225            None
226        } else {
227            Some(encoded_pairs(&self.params))
228        }
229    }
230}
231
232#[derive(Default)]
233pub struct ImageBuildOptsBuilder {
234    path: PathBuf,
235    params: HashMap<&'static str, String>,
236}
237
238impl ImageBuildOptsBuilder {
239    /// path is expected to be a file path to a directory containing a Dockerfile
240    /// describing how to build a Docker image
241    pub(crate) fn new<P>(path: P) -> Self
242    where
243        P: AsRef<Path>,
244    {
245        ImageBuildOptsBuilder {
246            path: path.as_ref().to_path_buf(),
247            ..Default::default()
248        }
249    }
250
251    impl_url_str_field!(
252        /// Set the name of the docker file. defaults to `DockerFile`.
253        dockerfile => "dockerfile"
254    );
255
256    impl_url_str_field!(
257        /// Tag this image with a name after building it.
258        tag => "t"
259    );
260
261    impl_url_str_field!(
262        /// Extra hosts to add to /etc/hosts.
263        extra_hosts => "extrahosts"
264    );
265
266    impl_url_str_field!(remote => "remote");
267
268    impl_url_bool_field!(
269        /// Suppress verbose build output.
270        quiet => "q"
271    );
272
273    impl_url_bool_field!(
274        /// Don't use the image cache when building image.
275        nocahe => "nocache"
276    );
277
278    impl_url_str_field!(
279        /// Attempt to pull the image even if an older image exists locally.
280        pull => "pull"
281    );
282
283    impl_url_bool_field!(rm => "rm");
284
285    impl_url_bool_field!(forcerm => "forcerm");
286
287    impl_url_field!(
288        /// Set memory limit for build.
289        memory: usize => "memory"
290    );
291
292    impl_url_field!(
293        /// Total memory (memory + swap). Set as -1 to disable swap.
294        memswap: usize => "memswap"
295    );
296
297    impl_url_field!(
298        /// CPU shares (relative weight).
299        cpu_shares: usize => "cpushares"
300    );
301
302    impl_url_str_field!(
303        /// CPUs in which to allow execution (eg. `0-3`, `0,1`)
304        cpu_set_cpus => "cpusetcpus"
305    );
306
307    impl_url_field!(
308        /// The length of a CPU period in microseconds.
309        cpu_period: usize => "cpuperiod"
310    );
311
312    impl_url_field!(
313        /// Microseconds of CPU time that the container can get in a CPU period.
314        cpu_quota: usize => "cpuquota"
315    );
316
317    // TODO: buildargs
318
319    impl_url_field!(
320        /// Size of /dev/shm in bytes. The size must be greater than 0. If omitted the system uses 64MB.
321        shm_size: usize => "shmsize"
322    );
323
324    impl_url_bool_field!(
325        /// Squash the resulting images layers into a single layer. (Experimental release only.)
326        squash => "squash"
327    );
328
329    // TODO: use an enum?
330    impl_url_str_field!(
331        /// bridge`, `host`, `none`, `container:<name|id>`, or a custom network name.
332        network_mode => "networkmode"
333    );
334
335    impl_url_str_field!(
336        /// Platform in the format os[/arch[/variant]].
337        platform => "platform"
338    );
339
340    impl_url_str_field!(
341        /// Target build stage.
342        target => "target"
343    );
344
345    impl_url_str_field!(
346        /// BuildKit output configuration.
347        outputs => "outputs"
348    );
349
350    impl_map_field!(url
351        /// Add labels to this image.
352        labels => "labels"
353    );
354
355    pub fn build(&self) -> ImageBuildOpts {
356        ImageBuildOpts {
357            path: self.path.clone(),
358            params: self.params.clone(),
359        }
360    }
361}
362
363/// All forms that the image identifier can take.
364pub enum ImageName {
365    /// `<image>[:<tag>]`
366    Tag { image: String, tag: Option<String> },
367    /// `<image-id>`
368    Id(String),
369    /// `<image@digest>`
370    Digest { image: String, digest: String },
371}
372
373impl ToString for ImageName {
374    fn to_string(&self) -> String {
375        match &self {
376            ImageName::Tag { image, tag } => match tag {
377                Some(tag) => format!("{image}:{tag}"),
378                None => image.to_owned(),
379            },
380            ImageName::Id(id) => id.to_owned(),
381            ImageName::Digest { image, digest } => format!("{image}@{digest}"),
382        }
383    }
384}
385
386impl ImageName {
387    /// Create a [`Tag`](ImageName::Tag) variant of image name.
388    pub fn tag<I, T>(image: I, tag: Option<T>) -> Self
389    where
390        I: Into<String>,
391        T: Into<String>,
392    {
393        Self::Tag {
394            image: image.into(),
395            tag: tag.map(|t| t.into()),
396        }
397    }
398
399    /// Create a [`Id`](ImageName::Id) variant of image name.
400    pub fn id<I>(id: I) -> Self
401    where
402        I: Into<String>,
403    {
404        Self::Id(id.into())
405    }
406
407    /// Create a [`Digest`](ImageName::Digest) variant of image name.
408    pub fn digest<I, D>(image: I, digest: D) -> Self
409    where
410        I: Into<String>,
411        D: Into<String>,
412    {
413        Self::Digest {
414            image: image.into(),
415            digest: digest.into(),
416        }
417    }
418}
419
420/// Filter type used to filter listed images.
421pub enum ImageFilter {
422    Before(ImageName),
423    Dangling,
424    /// Label in the form of `label=key`.
425    LabelKey(String),
426    /// Label in the form of `label=key=val`.
427    Label(String, String),
428    Since(ImageName),
429    Reference(String, Option<String>),
430}
431
432impl Filter for ImageFilter {
433    fn query_item(&self) -> FilterItem {
434        use ImageFilter::*;
435        match &self {
436            Before(name) => FilterItem::new("before", name.to_string()),
437            Dangling => FilterItem::new("dangling", true.to_string()),
438            LabelKey(n) => FilterItem::new("label", n.to_owned()),
439            Label(n, v) => FilterItem::new("label", format!("{n}={v}")),
440            Since(name) => FilterItem::new("since", name.to_string()),
441            Reference(image, tag) => FilterItem::new(
442                "reference",
443                format!(
444                    "{}{}",
445                    image,
446                    tag.as_ref()
447                        .map_or("".to_string(), |tag| format!(":{}", tag))
448                ),
449            ),
450        }
451    }
452}
453
454impl_opts_builder!(url => ImageList);
455
456impl ImageListOptsBuilder {
457    impl_url_bool_field!(
458        /// Show all images. Only images from a final layer (no children) are shown by default.
459        all => "all"
460    );
461    impl_url_bool_field!(
462        /// Show digest information as a RepoDigests field on each image.
463        digests => "digests"
464    );
465    impl_url_bool_field!(
466        /// Compute and show shared size as a SharedSize field on each image.
467        shared_size => "shared-size"
468    );
469    impl_filter_func!(
470        /// Filter the listed images by one of the variants of the enum.
471        ImageFilter
472    );
473}
474
475impl_opts_builder!(url => ImageRemove);
476
477impl ImageRemoveOptsBuilder {
478    impl_url_bool_field!(
479        /// Remove the image even if it is being used by stopped containers or has other tags.
480        force => "force"
481    );
482    impl_url_bool_field!(
483        /// Do not delete untagged parent images.
484        noprune => "noprune"
485    );
486}
487
488impl_opts_builder!(url => ImagePrune);
489
490pub enum ImagesPruneFilter {
491    /// When set to `true`, prune only unused and untagged images.
492    /// When set to `false`, all unused images are pruned.
493    Dangling(bool),
494    #[cfg(feature = "chrono")]
495    #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
496    /// Prune images created before this timestamp. Same as `Until` but takes a datetime object.
497    UntilDate(chrono::DateTime<chrono::Utc>),
498    /// Prune images created before this timestamp. The <timestamp> can be Unix timestamps,
499    /// date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m)
500    /// computed relative to the daemon machine’s time.
501    Until(String),
502    /// Label in the form of `label=key`.
503    LabelKey(String),
504    /// Label in the form of `label=key=val`.
505    Label(String, String),
506}
507
508impl Filter for ImagesPruneFilter {
509    fn query_item(&self) -> FilterItem {
510        use ImagesPruneFilter::*;
511        match &self {
512            Dangling(dangling) => FilterItem::new("dangling", dangling.to_string()),
513            Until(until) => FilterItem::new("until", until.to_owned()),
514            #[cfg(feature = "chrono")]
515            UntilDate(until) => FilterItem::new("until", until.timestamp().to_string()),
516            LabelKey(label) => FilterItem::new("label", label.to_owned()),
517            Label(key, val) => FilterItem::new("label", format!("{key}={val}")),
518        }
519    }
520}
521
522impl ImagePruneOptsBuilder {
523    impl_filter_func!(ImagesPruneFilter);
524}
525
526impl_opts_builder!(url => ClearCache);
527
528pub enum CacheFilter {
529    /// Duration relative to daemon's time, during which build cache was not used,
530    /// in Go's duration format (e.g., '24h').
531    Until(String),
532    Id(String),
533    // ID of the parent.
534    Parent(String),
535    Type(String),
536    Description(String),
537    InUse,
538    Shared,
539    Private,
540}
541
542impl Filter for CacheFilter {
543    fn query_item(&self) -> FilterItem {
544        use CacheFilter::*;
545        match &self {
546            Until(until) => FilterItem::new("until", until.to_owned()),
547            Id(id) => FilterItem::new("id", id.to_owned()),
548            Parent(parent) => FilterItem::new("parent", parent.to_owned()),
549            Type(type_) => FilterItem::new("type_", type_.to_owned()),
550            Description(description) => FilterItem::new("description", description.to_owned()),
551            InUse => FilterItem::new("inuse", "".to_owned()),
552            Shared => FilterItem::new("shared", "".to_owned()),
553            Private => FilterItem::new("private", "".to_owned()),
554        }
555    }
556}
557
558impl ClearCacheOptsBuilder {
559    impl_url_field!(
560        /// Amount of disk space in bytes to keep for cache.
561        keep_storage: i64 => "keep-storage"
562    );
563    impl_url_bool_field!(
564        /// Remove all types of build cache
565        all => "all"
566    );
567    impl_filter_func!(
568        /// Filter the builder cache with variants of the enum.
569        CacheFilter
570    );
571}
572
573pub struct ImagePushOpts {
574    auth: Option<RegistryAuth>,
575    params: HashMap<&'static str, String>,
576}
577
578impl ImagePushOpts {
579    pub fn builder() -> ImagePushOptsBuilder {
580        ImagePushOptsBuilder::default()
581    }
582
583    pub fn serialize(&self) -> Option<String> {
584        if self.params.is_empty() {
585            None
586        } else {
587            Some(encoded_pairs(self.params.iter()))
588        }
589    }
590
591    pub(crate) fn auth_header(&self) -> Option<String> {
592        self.auth.clone().map(|a| a.serialize())
593    }
594}
595
596pub struct ImagePushOptsBuilder {
597    auth: Option<RegistryAuth>,
598    params: HashMap<&'static str, String>,
599}
600
601impl Default for ImagePushOptsBuilder {
602    fn default() -> Self {
603        Self {
604            auth: None,
605            params: [("tag", "latest".into())].into(),
606        }
607    }
608}
609
610impl ImagePushOptsBuilder {
611    impl_url_str_field!(
612        /// The tag to associate with the image on the registry.
613        tag => "tag"
614    );
615
616    pub fn auth(mut self, auth: RegistryAuth) -> Self {
617        self.auth = Some(auth);
618        self
619    }
620
621    pub fn build(self) -> ImagePushOpts {
622        ImagePushOpts {
623            auth: self.auth,
624            params: self.params,
625        }
626    }
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632
633    /// Test registry auth with token
634    #[test]
635    fn registry_auth_token() {
636        let opts = RegistryAuth::token("abc");
637        assert_eq!(
638            base64::encode(r#"{"identitytoken":"abc"}"#),
639            opts.serialize()
640        );
641    }
642
643    /// Test registry auth with username and password
644    #[test]
645    fn registry_auth_password_simple() {
646        let opts = RegistryAuth::builder()
647            .username("user_abc")
648            .password("password_abc")
649            .build();
650        assert_eq!(
651            base64::encode(r#"{"username":"user_abc","password":"password_abc"}"#),
652            opts.serialize()
653        );
654    }
655
656    /// Test registry auth with all fields
657    #[test]
658    fn registry_auth_password_all() {
659        let opts = RegistryAuth::builder()
660            .username("user_abc")
661            .password("password_abc")
662            .email("email_abc")
663            .server_address("https://example.org")
664            .build();
665        assert_eq!(
666            base64::encode(
667                r#"{"username":"user_abc","password":"password_abc","email":"email_abc","serveraddress":"https://example.org"}"#
668            ),
669            opts.serialize()
670        );
671    }
672
673    #[test]
674    fn test_image_filter_reference() {
675        let opts = ImageListOpts::builder()
676            .filter(vec![ImageFilter::Reference("image".to_string(), None)])
677            .build();
678        let serialized = opts.serialize();
679        assert!(serialized.is_some());
680        assert_eq!(
681            "filters=%7B%22reference%22%3A%5B%22image%22%5D%7D".to_string(),
682            serialized.unwrap()
683        );
684
685        let opts = ImageListOpts::builder()
686            .filter(vec![ImageFilter::Reference(
687                "image".to_string(),
688                Some("tag".to_string()),
689            )])
690            .build();
691        let serialized = opts.serialize();
692        assert!(serialized.is_some());
693        assert_eq!(
694            "filters=%7B%22reference%22%3A%5B%22image%3Atag%22%5D%7D".to_string(),
695            serialized.unwrap()
696        );
697    }
698}