stackify_docker_api/opts/
image.rs

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