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 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 pub fn builder() -> RegistryAuthBuilder {
49 RegistryAuthBuilder::default()
50 }
51
52 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 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 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 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 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 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 pub fn builder() -> PullOptsBuilder {
134 PullOptsBuilder::default()
135 }
136
137 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 image => "fromImage");
177
178 impl_str_field!(src => "fromSrc");
179
180 impl_str_field!(
181 repo => "repo");
187
188 impl_str_field!(
189 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 pub fn builder<P>(path: P) -> ImageBuildOptsBuilder
217 where
218 P: AsRef<Path>,
219 {
220 ImageBuildOptsBuilder::new(path)
221 }
222
223 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 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 dockerfile => "dockerfile"
255 );
256
257 impl_url_str_field!(
258 tag => "t"
260 );
261
262 impl_url_str_field!(
263 extra_hosts => "extrahosts"
265 );
266
267 impl_url_str_field!(remote => "remote");
268
269 impl_url_bool_field!(
270 quiet => "q"
272 );
273
274 impl_url_bool_field!(
275 nocahe => "nocache"
277 );
278
279 impl_url_str_field!(
280 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 memory: usize => "memory"
291 );
292
293 impl_url_field!(
294 memswap: usize => "memswap"
296 );
297
298 impl_url_field!(
299 cpu_shares: usize => "cpushares"
301 );
302
303 impl_url_str_field!(
304 cpu_set_cpus => "cpusetcpus"
306 );
307
308 impl_url_field!(
309 cpu_period: usize => "cpuperiod"
311 );
312
313 impl_url_field!(
314 cpu_quota: usize => "cpuquota"
316 );
317
318 impl_map_field!(url
319 build_args => "buildargs"
321 );
322
323 impl_url_field!(
324 shm_size: usize => "shmsize"
326 );
327
328 impl_url_bool_field!(
329 squash => "squash"
331 );
332
333 impl_url_str_field!(
335 network_mode => "networkmode"
337 );
338
339 impl_url_str_field!(
340 platform => "platform"
342 );
343
344 impl_url_str_field!(
345 target => "target"
347 );
348
349 impl_url_str_field!(
350 outputs => "outputs"
352 );
353
354 impl_map_field!(url
355 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
367pub enum ImageName {
369 Tag { image: String, tag: Option<String> },
371 Id(String),
373 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 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 pub fn id<I>(id: I) -> Self
405 where
406 I: Into<String>,
407 {
408 Self::Id(id.into())
409 }
410
411 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
424pub enum ImageFilter {
426 Before(ImageName),
427 Dangling,
428 LabelKey(String),
430 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 all => "all"
464 );
465 impl_url_bool_field!(
466 digests => "digests"
468 );
469 impl_url_bool_field!(
470 shared_size => "shared-size"
472 );
473 impl_filter_func!(
474 ImageFilter
476 );
477}
478
479impl_opts_builder!(url => ImageRemove);
480
481impl ImageRemoveOptsBuilder {
482 impl_url_bool_field!(
483 force => "force"
485 );
486 impl_url_bool_field!(
487 noprune => "noprune"
489 );
490}
491
492impl_opts_builder!(url => ImagePrune);
493
494pub enum ImagesPruneFilter {
495 Dangling(bool),
498 #[cfg(feature = "chrono")]
499 #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
500 UntilDate(chrono::DateTime<chrono::Utc>),
502 Until(String),
506 LabelKey(String),
508 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 Until(String),
536 Id(String),
537 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 keep_storage: i64 => "keep-storage"
566 );
567 impl_url_bool_field!(
568 all => "all"
570 );
571 impl_filter_func!(
572 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 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]
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]
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]
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}