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 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 pub fn builder() -> RegistryAuthBuilder {
48 RegistryAuthBuilder::default()
49 }
50
51 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 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 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 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 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 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 pub fn builder() -> PullOptsBuilder {
133 PullOptsBuilder::default()
134 }
135
136 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 image => "fromImage");
176
177 impl_str_field!(src => "fromSrc");
178
179 impl_str_field!(
180 repo => "repo");
186
187 impl_str_field!(
188 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 pub fn builder<P>(path: P) -> ImageBuildOptsBuilder
216 where
217 P: AsRef<Path>,
218 {
219 ImageBuildOptsBuilder::new(path)
220 }
221
222 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 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 dockerfile => "dockerfile"
254 );
255
256 impl_url_str_field!(
257 tag => "t"
259 );
260
261 impl_url_str_field!(
262 extra_hosts => "extrahosts"
264 );
265
266 impl_url_str_field!(remote => "remote");
267
268 impl_url_bool_field!(
269 quiet => "q"
271 );
272
273 impl_url_bool_field!(
274 nocahe => "nocache"
276 );
277
278 impl_url_str_field!(
279 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 memory: usize => "memory"
290 );
291
292 impl_url_field!(
293 memswap: usize => "memswap"
295 );
296
297 impl_url_field!(
298 cpu_shares: usize => "cpushares"
300 );
301
302 impl_url_str_field!(
303 cpu_set_cpus => "cpusetcpus"
305 );
306
307 impl_url_field!(
308 cpu_period: usize => "cpuperiod"
310 );
311
312 impl_url_field!(
313 cpu_quota: usize => "cpuquota"
315 );
316
317 impl_url_field!(
320 shm_size: usize => "shmsize"
322 );
323
324 impl_url_bool_field!(
325 squash => "squash"
327 );
328
329 impl_url_str_field!(
331 network_mode => "networkmode"
333 );
334
335 impl_url_str_field!(
336 platform => "platform"
338 );
339
340 impl_url_str_field!(
341 target => "target"
343 );
344
345 impl_url_str_field!(
346 outputs => "outputs"
348 );
349
350 impl_map_field!(url
351 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
363pub enum ImageName {
365 Tag { image: String, tag: Option<String> },
367 Id(String),
369 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 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 pub fn id<I>(id: I) -> Self
401 where
402 I: Into<String>,
403 {
404 Self::Id(id.into())
405 }
406
407 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
420pub enum ImageFilter {
422 Before(ImageName),
423 Dangling,
424 LabelKey(String),
426 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 all => "all"
460 );
461 impl_url_bool_field!(
462 digests => "digests"
464 );
465 impl_url_bool_field!(
466 shared_size => "shared-size"
468 );
469 impl_filter_func!(
470 ImageFilter
472 );
473}
474
475impl_opts_builder!(url => ImageRemove);
476
477impl ImageRemoveOptsBuilder {
478 impl_url_bool_field!(
479 force => "force"
481 );
482 impl_url_bool_field!(
483 noprune => "noprune"
485 );
486}
487
488impl_opts_builder!(url => ImagePrune);
489
490pub enum ImagesPruneFilter {
491 Dangling(bool),
494 #[cfg(feature = "chrono")]
495 #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
496 UntilDate(chrono::DateTime<chrono::Utc>),
498 Until(String),
502 LabelKey(String),
504 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 Until(String),
532 Id(String),
533 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 keep_storage: i64 => "keep-storage"
562 );
563 impl_url_bool_field!(
564 all => "all"
566 );
567 impl_filter_func!(
568 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 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]
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]
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]
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}