1use crate::deserialize::*;
2#[cfg(feature = "json_schema")]
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::{collections::HashMap, path::PathBuf};
6use struct_patch::Patch;
7use url::Url;
8
9#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
11#[serde(rename_all = "camelCase")]
12#[patch(
13 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
14 attribute(serde(default))
16)]
17#[cfg_attr(
18 feature = "json_schema",
19 patch(
20 attribute(derive(JsonSchema)),
21 attribute(schemars(title = "Dofigen", rename = "Dofigen"))
22 )
23)]
24pub struct Dofigen {
25 #[patch(name = "VecPatch<String>")]
28 #[serde(skip_serializing_if = "Vec::is_empty")]
29 pub context: Vec<String>,
30
31 #[patch(name = "VecPatch<String>")]
34 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ignores"))))]
35 #[serde(skip_serializing_if = "Vec::is_empty")]
36 pub ignore: Vec<String>,
37
38 #[patch(name = "HashMapDeepPatch<String, StagePatch>")]
40 #[serde(skip_serializing_if = "HashMap::is_empty")]
41 pub builders: HashMap<String, Stage>,
42
43 #[patch(name = "StagePatch", attribute(serde(flatten)))]
45 #[serde(flatten)]
46 pub stage: Stage,
47
48 #[patch(name = "VecPatch<String>")]
51 #[serde(skip_serializing_if = "Vec::is_empty")]
52 pub entrypoint: Vec<String>,
53
54 #[patch(name = "VecPatch<String>")]
57 #[serde(skip_serializing_if = "Vec::is_empty")]
58 pub cmd: Vec<String>,
59
60 #[patch(name = "VecPatch<String>")]
63 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "volumes"))))]
64 #[serde(skip_serializing_if = "Vec::is_empty")]
65 pub volume: Vec<String>,
66
67 #[cfg_attr(
70 feature = "permissive",
71 patch(name = "VecDeepPatch<Port, ParsableStruct<PortPatch>>")
72 )]
73 #[cfg_attr(
74 not(feature = "permissive"),
75 patch(name = "VecDeepPatch<Port, PortPatch>")
76 )]
77 #[cfg_attr(
78 not(feature = "strict"),
79 patch(attribute(serde(alias = "port", alias = "ports")))
80 )]
81 #[serde(skip_serializing_if = "Vec::is_empty")]
82 pub expose: Vec<Port>,
83
84 #[patch(name = "Option<HealthcheckPatch>")]
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub healthcheck: Option<Healthcheck>,
89}
90
91#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
93#[patch(
94 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
95 attribute(serde(default)),
97)]
98#[cfg_attr(
99 feature = "json_schema",
100 patch(
101 attribute(derive(JsonSchema)),
102 attribute(schemars(title = "Stage", rename = "Stage"))
103 )
104)]
105pub struct Stage {
106 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
109 #[patch(name = "FromContextPatch", attribute(serde(flatten, default)))]
110 pub from: FromContext,
111
112 #[cfg_attr(not(feature = "strict"), patch(name = "NestedMap<String>"))]
115 #[cfg_attr(feature = "strict", patch(name = "HashMapPatch<String, String>"))]
116 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "labels"))))]
117 #[serde(skip_serializing_if = "HashMap::is_empty")]
118 pub label: HashMap<String, String>,
119
120 #[cfg_attr(
123 feature = "permissive",
124 patch(name = "Option<ParsableStruct<UserPatch>>")
125 )]
126 #[cfg_attr(not(feature = "permissive"), patch(name = "Option<UserPatch>"))]
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub user: Option<User>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
133 pub workdir: Option<String>,
134
135 #[patch(name = "HashMapPatch<String, String>")]
138 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "args"))))]
139 #[serde(skip_serializing_if = "HashMap::is_empty")]
140 pub arg: HashMap<String, String>,
141
142 #[patch(name = "HashMapPatch<String, String>")]
145 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "envs"))))]
146 #[serde(skip_serializing_if = "HashMap::is_empty")]
147 pub env: HashMap<String, String>,
148
149 #[cfg_attr(
152 not(feature = "strict"),
153 patch(attribute(serde(
154 alias = "add",
155 alias = "adds",
156 alias = "artifact",
157 alias = "artifacts"
158 )))
159 )]
160 #[cfg_attr(
161 feature = "permissive",
162 patch(name = "VecDeepPatch<CopyResource, ParsableStruct<CopyResourcePatch>>")
163 )]
164 #[cfg_attr(
165 not(feature = "permissive"),
166 patch(name = "VecDeepPatch<CopyResource, CopyResourcePatch>")
167 )]
168 #[serde(skip_serializing_if = "Vec::is_empty")]
169 pub copy: Vec<CopyResource>,
170
171 #[patch(name = "Option<RunPatch>")]
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub root: Option<Run>,
175
176 #[patch(name = "RunPatch", attribute(serde(flatten)))]
179 #[serde(flatten)]
180 pub run: Run,
181}
182
183#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
185#[patch(
186 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
187 attribute(serde(default)),
189)]
190#[cfg_attr(
191 feature = "json_schema",
192 patch(
193 attribute(derive(JsonSchema)),
194 attribute(schemars(title = "Run", rename = "Run"))
195 )
196)]
197pub struct Run {
198 #[patch(name = "VecPatch<String>")]
200 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "script"))))]
201 #[serde(skip_serializing_if = "Vec::is_empty")]
202 pub run: Vec<String>,
203
204 #[cfg_attr(
207 feature = "permissive",
208 patch(name = "VecDeepPatch<Cache, ParsableStruct<CachePatch>>")
209 )]
210 #[cfg_attr(
211 not(feature = "permissive"),
212 patch(name = "VecDeepPatch<Cache, CachePatch>")
213 )]
214 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "caches"))))]
215 #[serde(skip_serializing_if = "Vec::is_empty")]
216 pub cache: Vec<Cache>,
217
218 #[cfg_attr(
222 feature = "permissive",
223 patch(name = "VecDeepPatch<Bind, ParsableStruct<BindPatch>>")
224 )]
225 #[cfg_attr(
226 not(feature = "permissive"),
227 patch(name = "VecDeepPatch<Bind, BindPatch>")
228 )]
229 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "binds"))))]
230 #[serde(skip_serializing_if = "Vec::is_empty")]
231 pub bind: Vec<Bind>,
232}
233
234#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
237#[patch(
238 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
239 attribute(serde(default))
240)]
241#[cfg_attr(
242 feature = "json_schema",
243 patch(
244 attribute(derive(JsonSchema)),
245 attribute(schemars(title = "Cache", rename = "Cache"))
246 )
247)]
248pub struct Cache {
249 #[serde(skip_serializing_if = "Option::is_none")]
252 pub id: Option<String>,
253
254 #[cfg_attr(
256 not(feature = "strict"),
257 patch(attribute(serde(alias = "dst", alias = "destination")))
258 )]
259 pub target: String,
260
261 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ro"))))]
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub readonly: Option<bool>,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub sharing: Option<CacheSharing>,
269
270 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
272 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
273 pub from: FromContext,
274
275 #[serde(skip_serializing_if = "Option::is_none")]
277 pub source: Option<String>,
278
279 #[cfg_attr(
281 feature = "permissive",
282 patch(attribute(serde(
283 deserialize_with = "deserialize_from_optional_string_or_number",
284 default
285 )))
286 )]
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub chmod: Option<String>,
289
290 #[patch(name = "Option<UserPatch>")]
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub chown: Option<User>,
294}
295
296#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
299#[patch(
300 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
301 attribute(serde(default))
302)]
303#[cfg_attr(
304 feature = "json_schema",
305 patch(
306 attribute(derive(JsonSchema)),
307 attribute(schemars(title = "Bind", rename = "Bind"))
308 )
309)]
310pub struct Bind {
311 pub target: String,
313
314 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
316 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
317 pub from: FromContext,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub source: Option<String>,
322
323 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "rw"))))]
325 #[serde(skip_serializing_if = "Option::is_none")]
326 pub readwrite: Option<bool>,
327}
328
329#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
332#[patch(
333 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
334 attribute(serde(deny_unknown_fields, default))
335)]
336#[cfg_attr(
337 feature = "json_schema",
338 patch(
339 attribute(derive(JsonSchema)),
340 attribute(schemars(title = "Healthcheck", rename = "Healthcheck"))
341 )
342)]
343pub struct Healthcheck {
344 pub cmd: String,
346
347 #[serde(skip_serializing_if = "Option::is_none")]
349 pub interval: Option<String>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub timeout: Option<String>,
354
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub start: Option<String>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub retries: Option<u16>,
362}
363
364#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch, Hash, Eq, PartialOrd)]
366#[patch(
367 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
368 attribute(serde(deny_unknown_fields, default))
369)]
370#[cfg_attr(
371 feature = "json_schema",
372 patch(
373 attribute(derive(JsonSchema)),
374 attribute(schemars(title = "ImageName", rename = "ImageName"))
375 )
376)]
377pub struct ImageName {
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub host: Option<String>,
381
382 #[serde(skip_serializing_if = "Option::is_none")]
384 pub port: Option<u16>,
385
386 pub path: String,
388
389 #[serde(flatten, skip_serializing_if = "Option::is_none")]
391 #[patch(attribute(serde(flatten)))]
392 pub version: Option<ImageVersion>,
393}
394
395#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
398#[patch(
399 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
400 attribute(serde(deny_unknown_fields, default))
401)]
402#[cfg_attr(
403 feature = "json_schema",
404 patch(
405 attribute(derive(JsonSchema)),
406 attribute(schemars(title = "Copy", rename = "Copy"))
407 )
408)]
409pub struct Copy {
410 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
413 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
414 pub from: FromContext,
415
416 #[patch(name = "VecPatch<String>")]
418 #[cfg_attr(
419 not(feature = "strict"),
420 patch(attribute(serde(alias = "path", alias = "source")))
421 )]
422 #[serde(skip_serializing_if = "Vec::is_empty")]
423 pub paths: Vec<String>,
424
425 #[serde(flatten)]
427 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
428 pub options: CopyOptions,
429
430 #[patch(name = "VecPatch<String>")]
432 #[serde(skip_serializing_if = "Vec::is_empty")]
433 pub exclude: Vec<String>,
434
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub parents: Option<bool>,
438}
439
440#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
443#[patch(
444 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
445 attribute(serde(deny_unknown_fields, default))
446)]
447#[cfg_attr(
448 feature = "json_schema",
449 patch(
450 attribute(derive(JsonSchema)),
451 attribute(schemars(title = "CopyContent", rename = "CopyContent"))
452 )
453)]
454pub struct CopyContent {
455 pub content: String,
457
458 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "subst"))))]
460 #[serde(skip_serializing_if = "Option::is_none")]
461 pub substitute: Option<bool>,
462
463 #[serde(flatten)]
465 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
466 pub options: CopyOptions,
467}
468
469#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
472#[patch(
473 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
474 attribute(serde(deny_unknown_fields, default, rename_all = "camelCase"))
475)]
476#[cfg_attr(
477 feature = "json_schema",
478 patch(
479 attribute(derive(JsonSchema)),
480 attribute(schemars(title = "AddGitRepo", rename = "AddGitRepo"))
481 )
482)]
483pub struct AddGitRepo {
484 pub repo: String,
486
487 #[serde(flatten)]
489 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
490 pub options: CopyOptions,
491
492 #[patch(name = "VecPatch<String>")]
494 #[serde(skip_serializing_if = "Vec::is_empty")]
495 pub exclude: Vec<String>,
496
497 #[serde(skip_serializing_if = "Option::is_none")]
500 pub keep_git_dir: Option<bool>,
501}
502
503#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
505#[patch(
506 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
507 attribute(serde(deny_unknown_fields, default))
508)]
509#[cfg_attr(
510 feature = "json_schema",
511 patch(
512 attribute(derive(JsonSchema)),
513 attribute(schemars(title = "Add", rename = "Add"))
514 )
515)]
516pub struct Add {
517 #[patch(name = "VecPatch<Resource>")]
519 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "file"))))]
520 #[serde(skip_serializing_if = "Vec::is_empty")]
521 pub files: Vec<Resource>,
522
523 #[serde(flatten)]
525 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
526 pub options: CopyOptions,
527
528 #[serde(skip_serializing_if = "Option::is_none")]
531 pub checksum: Option<String>,
532}
533
534#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
536#[patch(
537 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
538 attribute(serde(deny_unknown_fields, default))
539)]
540#[cfg_attr(
541 feature = "json_schema",
542 patch(
543 attribute(derive(JsonSchema)),
544 attribute(schemars(title = "CopyOptions", rename = "CopyOptions"))
545 )
546)]
547pub struct CopyOptions {
548 #[cfg_attr(
550 not(feature = "strict"),
551 patch(attribute(serde(alias = "destination")))
552 )]
553 #[serde(skip_serializing_if = "Option::is_none")]
554 pub target: Option<String>,
555
556 #[patch(name = "Option<UserPatch>")]
559 #[serde(skip_serializing_if = "Option::is_none")]
560 pub chown: Option<User>,
561
562 #[cfg_attr(
565 feature = "permissive",
566 patch(attribute(serde(
567 deserialize_with = "deserialize_from_optional_string_or_number",
568 default
569 )))
570 )]
571 #[serde(skip_serializing_if = "Option::is_none")]
572 pub chmod: Option<String>,
573
574 #[serde(skip_serializing_if = "Option::is_none")]
577 pub link: Option<bool>,
578}
579
580#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
582#[patch(
583 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
584 attribute(serde(deny_unknown_fields, default))
585)]
586#[cfg_attr(
587 feature = "json_schema",
588 patch(
589 attribute(derive(JsonSchema)),
590 attribute(schemars(title = "User", rename = "User"))
591 )
592)]
593pub struct User {
594 pub user: String,
597
598 #[serde(skip_serializing_if = "Option::is_none")]
601 pub group: Option<String>,
602}
603
604#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
606#[patch(
607 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
608 attribute(serde(deny_unknown_fields, default))
609)]
610#[cfg_attr(
611 feature = "json_schema",
612 patch(
613 attribute(derive(JsonSchema)),
614 attribute(schemars(title = "Port", rename = "Port"))
615 )
616)]
617pub struct Port {
618 pub port: u16,
620
621 #[serde(skip_serializing_if = "Option::is_none")]
623 pub protocol: Option<PortProtocol>,
624}
625
626#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
630#[serde(rename_all = "camelCase")]
631#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
632pub enum ImageVersion {
633 Tag(String),
634 Digest(String),
635}
636
637#[derive(Serialize, Debug, Clone, PartialEq)]
639#[serde(rename_all = "camelCase")]
640pub enum FromContext {
641 FromImage(ImageName),
642 FromBuilder(String),
643 FromContext(Option<String>),
644}
645
646#[derive(Serialize, Debug, Clone, PartialEq)]
647#[serde(untagged)]
648pub enum CopyResource {
649 Copy(Copy),
650 Content(CopyContent),
651 AddGitRepo(AddGitRepo),
652 Add(Add),
653}
654
655#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
657#[serde(rename_all = "camelCase")]
658#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
659pub enum CacheSharing {
660 Shared,
661 Private,
662 Locked,
663}
664
665#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
667#[serde(rename_all = "camelCase")]
668#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
669pub enum PortProtocol {
670 Tcp,
671 Udp,
672}
673
674#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
676#[serde(untagged)]
677#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
678pub enum Resource {
679 Url(Url),
680 File(PathBuf),
681}
682
683#[derive(Debug, Clone, PartialEq, Deserialize)]
686#[serde(rename_all = "camelCase")]
687#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
688pub enum FromContextPatch {
689 #[cfg(not(feature = "permissive"))]
690 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
691 FromImage(ImageNamePatch),
692
693 #[cfg(feature = "permissive")]
694 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
695 FromImage(ParsableStruct<ImageNamePatch>),
696
697 #[cfg_attr(not(feature = "strict"), serde(alias = "builder"))]
698 FromBuilder(String),
699
700 #[cfg_attr(not(feature = "strict"), serde(alias = "from"))]
701 FromContext(Option<String>),
702}
703
704#[derive(Debug, Clone, PartialEq, Deserialize)]
705#[serde(untagged)]
706#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
707pub enum CopyResourcePatch {
708 Copy(CopyPatch),
709 Content(CopyContentPatch),
710 AddGitRepo(AddGitRepoPatch),
711 Add(AddPatch),
712 Unknown(UnknownPatch),
713}
714
715#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
716#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
717pub struct UnknownPatch {
718 #[serde(flatten)]
719 pub options: Option<CopyOptionsPatch>,
720 pub exclude: Option<VecPatch<String>>,
721}
722
723#[cfg(test)]
726mod test {
727 use super::*;
728 use pretty_assertions_sorted::assert_eq_sorted;
729
730 mod deserialize {
731 use super::*;
732
733 mod dofigen {
734 use super::*;
735
736 #[test]
737 fn empty() {
738 let data = r#""#;
739
740 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
741 let dofigen: Dofigen = dofigen.into();
742
743 assert_eq_sorted!(dofigen, Dofigen::default());
744 }
745
746 #[test]
747 fn from() {
748 let data = r#"
749 fromImage:
750 path: ubuntu
751 "#;
752
753 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
754 let dofigen: Dofigen = dofigen.into();
755
756 assert_eq_sorted!(
757 dofigen,
758 Dofigen {
759 stage: Stage {
760 from: FromContext::FromImage(ImageName {
761 path: "ubuntu".into(),
762 ..Default::default()
763 }),
764 ..Default::default()
765 },
766 ..Default::default()
767 }
768 );
769 }
770
771 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
772 #[test]
773 fn duplicate_from() {
774 let data = r#"
775 from:
776 path: ubuntu
777 from:
778 path: alpine
779 "#;
780
781 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
782
783 println!("{:?}", dofigen);
784
785 assert!(dofigen.is_err());
786 }
787
788 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
789 #[test]
790 fn duplicate_from_and_alias() {
791 let data = r#"
792 from:
793 path: ubuntu
794 image:
795 path: alpine
796 "#;
797
798 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
799
800 println!("{:?}", dofigen);
801
802 assert!(dofigen.is_err());
803 }
804 }
805
806 mod stage {
807 use super::*;
808
809 #[test]
810 fn empty() {
811 let data = r#""#;
812
813 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
814 let stage: Stage = stage.into();
815
816 assert_eq_sorted!(stage, Stage::default());
817 }
818
819 #[test]
820 fn from() {
821 let data = r#"
822 fromImage:
823 path: ubuntu
824 "#;
825
826 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
827 let stage: Stage = stage.into();
828
829 assert_eq_sorted!(
830 stage,
831 Stage {
832 from: FromContext::FromImage(ImageName {
833 path: "ubuntu".into(),
834 ..Default::default()
835 })
836 .into(),
837 ..Default::default()
838 }
839 );
840 }
841
842 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
843 #[test]
844 fn duplicate_from() {
845 let data = r#"
846 fromImage:
847 path: ubuntu
848 fromImage:
849 path: alpine
850 "#;
851
852 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
853
854 assert!(stage.is_err());
855 }
856
857 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
858 #[test]
859 fn duplicate_from_and_alias() {
860 let data = r#"
861 fromImage:
862 path: ubuntu
863 image:
864 path: alpine
865 "#;
866
867 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
868
869 assert!(stage.is_err());
870 }
871
872 #[test]
873 fn label() {
874 #[cfg(not(feature = "strict"))]
875 let data = r#"
876 label:
877 io.dofigen:
878 test: test
879 "#;
880 #[cfg(feature = "strict")]
881 let data = r#"
882 label:
883 io.dofigen.test: test
884 "#;
885
886 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
887 let stage: Stage = stage.into();
888
889 assert_eq_sorted!(
890 stage,
891 Stage {
892 label: HashMap::from([("io.dofigen.test".into(), "test".into())]),
893 ..Default::default()
894 }
895 );
896 }
897 }
898
899 mod user {
900 use super::*;
901
902 #[test]
903 fn name_and_group() {
904 let json_data = r#"{
905 "user": "test",
906 "group": "test"
907}"#;
908
909 let user: UserPatch = serde_yaml::from_str(json_data).unwrap();
910 let user: User = user.into();
911
912 assert_eq_sorted!(
913 user,
914 User {
915 user: "test".into(),
916 group: Some("test".into())
917 }
918 );
919 }
920 }
921
922 mod copy_resource {
923
924 use super::*;
925
926 #[test]
927 fn copy() {
928 let json_data = r#"{
929 "paths": ["file1.txt", "file2.txt"],
930 "target": "destination/",
931 "chown": {
932 "user": "root",
933 "group": "root"
934 },
935 "chmod": "755",
936 "link": true,
937 "fromImage": {"path": "my-image"}
938}"#;
939
940 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
941 let copy_resource: CopyResource = copy_resource.into();
942
943 assert_eq_sorted!(
944 copy_resource,
945 CopyResource::Copy(Copy {
946 paths: vec!["file1.txt".into(), "file2.txt".into()].into(),
947 options: CopyOptions {
948 target: Some("destination/".into()),
949 chown: Some(User {
950 user: "root".into(),
951 group: Some("root".into())
952 }),
953 chmod: Some("755".into()),
954 link: Some(true),
955 },
956 from: FromContext::FromImage(ImageName {
957 path: "my-image".into(),
958 ..Default::default()
959 }),
960 exclude: vec![].into(),
961 parents: None,
962 })
963 );
964 }
965
966 #[test]
967 fn copy_simple() {
968 let json_data = r#"{
969 "paths": ["file1.txt"]
970}"#;
971
972 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
973
974 assert_eq_sorted!(
975 copy_resource,
976 CopyResourcePatch::Copy(CopyPatch {
977 paths: Some(vec!["file1.txt".into()].into_patch()),
978 options: Some(CopyOptionsPatch::default()),
979 ..Default::default()
980 })
981 );
982
983 let copy_resource: CopyResource = copy_resource.into();
984
985 assert_eq_sorted!(
986 copy_resource,
987 CopyResource::Copy(Copy {
988 paths: vec!["file1.txt".into()].into(),
989 options: CopyOptions::default(),
990 ..Default::default()
991 })
992 );
993 }
994
995 #[cfg(feature = "permissive")]
996 #[test]
997 fn copy_chmod_int() {
998 let json_data = r#"{
999 "paths": ["file1.txt"],
1000 "chmod": 755
1001}"#;
1002
1003 let copy_resource: CopyPatch = serde_yaml::from_str(json_data).unwrap();
1004
1005 assert_eq_sorted!(
1006 copy_resource,
1007 CopyPatch {
1008 paths: Some(vec!["file1.txt".into()].into_patch()),
1009 options: Some(CopyOptionsPatch {
1010 chmod: Some(Some("755".into())),
1011 ..Default::default()
1012 }),
1013 ..Default::default()
1014 }
1015 );
1016 }
1017
1018 #[cfg(feature = "permissive")]
1019 #[test]
1020 fn deserialize_copy_from_str() {
1021 let json_data = "file1.txt destination/";
1022
1023 let copy_resource: ParsableStruct<CopyResourcePatch> =
1024 serde_yaml::from_str(json_data).unwrap();
1025 let copy_resource: CopyResource = copy_resource.into();
1026
1027 assert_eq_sorted!(
1028 copy_resource,
1029 CopyResource::Copy(Copy {
1030 paths: vec!["file1.txt".into()].into(),
1031 options: CopyOptions {
1032 target: Some("destination/".into()),
1033 ..Default::default()
1034 },
1035 ..Default::default()
1036 })
1037 );
1038 }
1039
1040 #[test]
1041 fn copy_content() {
1042 let json_data = r#"{
1043 "content": "echo coucou",
1044 "substitute": false,
1045 "target": "test.sh",
1046 "chown": {
1047 "user": "1001",
1048 "group": "1001"
1049 },
1050 "chmod": "555",
1051 "link": true
1052}"#;
1053
1054 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1055 let copy_resource: CopyResource = copy_resource.into();
1056
1057 assert_eq_sorted!(
1058 copy_resource,
1059 CopyResource::Content(CopyContent {
1060 content: "echo coucou".into(),
1061 substitute: Some(false),
1062 options: CopyOptions {
1063 target: Some("test.sh".into()),
1064 chown: Some(User {
1065 user: "1001".into(),
1066 group: Some("1001".into())
1067 }),
1068 chmod: Some("555".into()),
1069 link: Some(true),
1070 }
1071 })
1072 );
1073 }
1074
1075 #[test]
1076 fn add_git_repo() {
1077 let json_data = r#"{
1078 "repo": "https://github.com/example/repo.git",
1079 "target": "destination/",
1080 "chown": {
1081 "user": "root",
1082 "group": "root"
1083 },
1084 "chmod": "755",
1085 "link": true,
1086 "keepGitDir": true
1087 }"#;
1088
1089 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1090 let copy_resource: CopyResource = copy_resource.into();
1091
1092 assert_eq_sorted!(
1093 copy_resource,
1094 CopyResource::AddGitRepo(AddGitRepo {
1095 repo: "https://github.com/example/repo.git".into(),
1096 options: CopyOptions {
1097 target: Some("destination/".into()),
1098 chown: Some(User {
1099 user: "root".into(),
1100 group: Some("root".into())
1101 }),
1102 chmod: Some("755".into()),
1103 link: Some(true),
1104 },
1105 keep_git_dir: Some(true),
1106 exclude: vec![].into(),
1107 })
1108 );
1109 }
1110
1111 #[test]
1112 fn add() {
1113 let json_data = r#"{
1114 "files": ["file1.txt", "file2.txt"],
1115 "target": "destination/",
1116 "checksum": "sha256:abcdef123456",
1117 "chown": {
1118 "user": "root",
1119 "group": "root"
1120 },
1121 "chmod": "755",
1122 "link": true
1123 }"#;
1124
1125 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1126 let copy_resource: CopyResource = copy_resource.into();
1127
1128 assert_eq_sorted!(
1129 copy_resource,
1130 CopyResource::Add(Add {
1131 files: vec![
1132 Resource::File("file1.txt".into()),
1133 Resource::File("file2.txt".into())
1134 ]
1135 .into(),
1136 options: CopyOptions {
1137 target: Some("destination/".into()),
1138 chown: Some(User {
1139 user: "root".into(),
1140 group: Some("root".into())
1141 }),
1142 chmod: Some("755".into()),
1143 link: Some(true),
1144 },
1145 checksum: Some("sha256:abcdef123456".into()),
1146 })
1147 );
1148 }
1149 }
1150
1151 mod builder {
1152 use super::*;
1153
1154 #[test]
1155 fn with_bind() {
1156 let json_data = r#"
1157fromImage:
1158 path: clux/muslrust:stable
1159workdir: /app
1160bind:
1161 - target: /app
1162run:
1163 - cargo build --release
1164 - mv target/x86_64-unknown-linux-musl/release/dofigen /app/
1165"#;
1166
1167 let builder: Stage = serde_yaml::from_str::<StagePatch>(json_data)
1168 .unwrap()
1169 .into();
1170
1171 assert_eq_sorted!(
1172 builder,
1173 Stage {
1174 from: FromContext::FromImage(ImageName {
1175 path: "clux/muslrust:stable".into(),
1176 ..Default::default()
1177 }),
1178 workdir: Some("/app".into()),
1179 run: Run {
1180 bind: vec![Bind {
1181 target: "/app".into(),
1182 ..Default::default()
1183 }],
1184 run: vec![
1185 "cargo build --release".into(),
1186 "mv target/x86_64-unknown-linux-musl/release/dofigen /app/".into()
1187 ],
1188 ..Default::default()
1189 },
1190 ..Default::default()
1191 }
1192 );
1193 }
1194 }
1195 }
1196}