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 #[patch(name = "VecPatch<String>")]
207 #[serde(skip_serializing_if = "Vec::is_empty")]
208 pub shell: Vec<String>,
209
210 #[cfg_attr(
213 feature = "permissive",
214 patch(name = "VecDeepPatch<Cache, ParsableStruct<CachePatch>>")
215 )]
216 #[cfg_attr(
217 not(feature = "permissive"),
218 patch(name = "VecDeepPatch<Cache, CachePatch>")
219 )]
220 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "caches"))))]
221 #[serde(skip_serializing_if = "Vec::is_empty")]
222 pub cache: Vec<Cache>,
223
224 #[cfg_attr(
228 feature = "permissive",
229 patch(name = "VecDeepPatch<Bind, ParsableStruct<BindPatch>>")
230 )]
231 #[cfg_attr(
232 not(feature = "permissive"),
233 patch(name = "VecDeepPatch<Bind, BindPatch>")
234 )]
235 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "binds"))))]
236 #[serde(skip_serializing_if = "Vec::is_empty")]
237 pub bind: Vec<Bind>,
238}
239
240#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
243#[patch(
244 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
245 attribute(serde(default))
246)]
247#[cfg_attr(
248 feature = "json_schema",
249 patch(
250 attribute(derive(JsonSchema)),
251 attribute(schemars(title = "Cache", rename = "Cache"))
252 )
253)]
254pub struct Cache {
255 #[serde(skip_serializing_if = "Option::is_none")]
258 pub id: Option<String>,
259
260 #[cfg_attr(
262 not(feature = "strict"),
263 patch(attribute(serde(alias = "dst", alias = "destination")))
264 )]
265 pub target: String,
266
267 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ro"))))]
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub readonly: Option<bool>,
271
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub sharing: Option<CacheSharing>,
275
276 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
278 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
279 pub from: FromContext,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub source: Option<String>,
284
285 #[cfg_attr(
287 feature = "permissive",
288 patch(attribute(serde(
289 deserialize_with = "deserialize_from_optional_string_or_number",
290 default
291 )))
292 )]
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub chmod: Option<String>,
295
296 #[patch(name = "Option<UserPatch>")]
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub chown: Option<User>,
300}
301
302#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
305#[patch(
306 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
307 attribute(serde(default))
308)]
309#[cfg_attr(
310 feature = "json_schema",
311 patch(
312 attribute(derive(JsonSchema)),
313 attribute(schemars(title = "Bind", rename = "Bind"))
314 )
315)]
316pub struct Bind {
317 pub target: String,
319
320 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
322 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
323 pub from: FromContext,
324
325 #[serde(skip_serializing_if = "Option::is_none")]
327 pub source: Option<String>,
328
329 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "rw"))))]
331 #[serde(skip_serializing_if = "Option::is_none")]
332 pub readwrite: Option<bool>,
333}
334
335#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
338#[patch(
339 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
340 attribute(serde(deny_unknown_fields, default))
341)]
342#[cfg_attr(
343 feature = "json_schema",
344 patch(
345 attribute(derive(JsonSchema)),
346 attribute(schemars(title = "Healthcheck", rename = "Healthcheck"))
347 )
348)]
349pub struct Healthcheck {
350 pub cmd: String,
352
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub interval: Option<String>,
356
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub timeout: Option<String>,
360
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub start: Option<String>,
364
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub retries: Option<u16>,
368}
369
370#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch, Hash, Eq, PartialOrd)]
372#[patch(
373 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
374 attribute(serde(deny_unknown_fields, default))
375)]
376#[cfg_attr(
377 feature = "json_schema",
378 patch(
379 attribute(derive(JsonSchema)),
380 attribute(schemars(title = "ImageName", rename = "ImageName"))
381 )
382)]
383pub struct ImageName {
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub host: Option<String>,
387
388 #[serde(skip_serializing_if = "Option::is_none")]
390 pub port: Option<u16>,
391
392 pub path: String,
394
395 #[serde(flatten, skip_serializing_if = "Option::is_none")]
397 #[patch(attribute(serde(flatten)))]
398 pub version: Option<ImageVersion>,
399}
400
401#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
404#[patch(
405 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
406 attribute(serde(deny_unknown_fields, default))
407)]
408#[cfg_attr(
409 feature = "json_schema",
410 patch(
411 attribute(derive(JsonSchema)),
412 attribute(schemars(title = "Copy", rename = "Copy"))
413 )
414)]
415pub struct Copy {
416 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
419 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
420 pub from: FromContext,
421
422 #[patch(name = "VecPatch<String>")]
424 #[cfg_attr(
425 not(feature = "strict"),
426 patch(attribute(serde(alias = "path", alias = "source")))
427 )]
428 #[serde(skip_serializing_if = "Vec::is_empty")]
429 pub paths: Vec<String>,
430
431 #[serde(flatten)]
433 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
434 pub options: CopyOptions,
435
436 #[patch(name = "VecPatch<String>")]
438 #[serde(skip_serializing_if = "Vec::is_empty")]
439 pub exclude: Vec<String>,
440
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub parents: Option<bool>,
444}
445
446#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
449#[patch(
450 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
451 attribute(serde(deny_unknown_fields, default))
452)]
453#[cfg_attr(
454 feature = "json_schema",
455 patch(
456 attribute(derive(JsonSchema)),
457 attribute(schemars(title = "CopyContent", rename = "CopyContent"))
458 )
459)]
460pub struct CopyContent {
461 pub content: String,
463
464 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "subst"))))]
466 #[serde(skip_serializing_if = "Option::is_none")]
467 pub substitute: Option<bool>,
468
469 #[serde(flatten)]
471 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
472 pub options: CopyOptions,
473}
474
475#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
478#[patch(
479 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
480 attribute(serde(deny_unknown_fields, default, rename_all = "camelCase"))
481)]
482#[cfg_attr(
483 feature = "json_schema",
484 patch(
485 attribute(derive(JsonSchema)),
486 attribute(schemars(title = "AddGitRepo", rename = "AddGitRepo"))
487 )
488)]
489pub struct AddGitRepo {
490 pub repo: String,
492
493 #[serde(flatten)]
495 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
496 pub options: CopyOptions,
497
498 #[patch(name = "VecPatch<String>")]
500 #[serde(skip_serializing_if = "Vec::is_empty")]
501 pub exclude: Vec<String>,
502
503 #[serde(skip_serializing_if = "Option::is_none")]
506 pub keep_git_dir: Option<bool>,
507}
508
509#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
511#[patch(
512 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
513 attribute(serde(deny_unknown_fields, default))
514)]
515#[cfg_attr(
516 feature = "json_schema",
517 patch(
518 attribute(derive(JsonSchema)),
519 attribute(schemars(title = "Add", rename = "Add"))
520 )
521)]
522pub struct Add {
523 #[patch(name = "VecPatch<Resource>")]
525 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "file"))))]
526 #[serde(skip_serializing_if = "Vec::is_empty")]
527 pub files: Vec<Resource>,
528
529 #[serde(flatten)]
531 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
532 pub options: CopyOptions,
533
534 #[serde(skip_serializing_if = "Option::is_none")]
537 pub checksum: Option<String>,
538}
539
540#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
542#[patch(
543 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
544 attribute(serde(deny_unknown_fields, default))
545)]
546#[cfg_attr(
547 feature = "json_schema",
548 patch(
549 attribute(derive(JsonSchema)),
550 attribute(schemars(title = "CopyOptions", rename = "CopyOptions"))
551 )
552)]
553pub struct CopyOptions {
554 #[cfg_attr(
556 not(feature = "strict"),
557 patch(attribute(serde(alias = "destination")))
558 )]
559 #[serde(skip_serializing_if = "Option::is_none")]
560 pub target: Option<String>,
561
562 #[patch(name = "Option<UserPatch>")]
565 #[serde(skip_serializing_if = "Option::is_none")]
566 pub chown: Option<User>,
567
568 #[cfg_attr(
571 feature = "permissive",
572 patch(attribute(serde(
573 deserialize_with = "deserialize_from_optional_string_or_number",
574 default
575 )))
576 )]
577 #[serde(skip_serializing_if = "Option::is_none")]
578 pub chmod: Option<String>,
579
580 #[serde(skip_serializing_if = "Option::is_none")]
583 pub link: Option<bool>,
584}
585
586#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
588#[patch(
589 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
590 attribute(serde(deny_unknown_fields, default))
591)]
592#[cfg_attr(
593 feature = "json_schema",
594 patch(
595 attribute(derive(JsonSchema)),
596 attribute(schemars(title = "User", rename = "User"))
597 )
598)]
599pub struct User {
600 pub user: String,
603
604 #[serde(skip_serializing_if = "Option::is_none")]
607 pub group: Option<String>,
608}
609
610#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
612#[patch(
613 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
614 attribute(serde(deny_unknown_fields, default))
615)]
616#[cfg_attr(
617 feature = "json_schema",
618 patch(
619 attribute(derive(JsonSchema)),
620 attribute(schemars(title = "Port", rename = "Port"))
621 )
622)]
623pub struct Port {
624 pub port: u16,
626
627 #[serde(skip_serializing_if = "Option::is_none")]
629 pub protocol: Option<PortProtocol>,
630}
631
632#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
636#[serde(rename_all = "camelCase")]
637#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
638pub enum ImageVersion {
639 Tag(String),
640 Digest(String),
641}
642
643#[derive(Serialize, Debug, Clone, PartialEq)]
645#[serde(rename_all = "camelCase")]
646pub enum FromContext {
647 FromImage(ImageName),
648 FromBuilder(String),
649 FromContext(Option<String>),
650}
651
652#[derive(Serialize, Debug, Clone, PartialEq)]
653#[serde(untagged)]
654pub enum CopyResource {
655 Copy(Copy),
656 Content(CopyContent),
657 AddGitRepo(AddGitRepo),
658 Add(Add),
659}
660
661#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
663#[serde(rename_all = "camelCase")]
664#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
665pub enum CacheSharing {
666 Shared,
667 Private,
668 Locked,
669}
670
671#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
673#[serde(rename_all = "camelCase")]
674#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
675pub enum PortProtocol {
676 Tcp,
677 Udp,
678}
679
680#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
682#[serde(untagged)]
683#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
684pub enum Resource {
685 Url(Url),
686 File(PathBuf),
687}
688
689#[derive(Debug, Clone, PartialEq, Deserialize)]
692#[serde(rename_all = "camelCase")]
693#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
694pub enum FromContextPatch {
695 #[cfg(not(feature = "permissive"))]
696 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
697 FromImage(ImageNamePatch),
698
699 #[cfg(feature = "permissive")]
700 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
701 FromImage(ParsableStruct<ImageNamePatch>),
702
703 #[cfg_attr(not(feature = "strict"), serde(alias = "builder"))]
704 FromBuilder(String),
705
706 #[cfg_attr(not(feature = "strict"), serde(alias = "from"))]
707 FromContext(Option<String>),
708}
709
710#[derive(Debug, Clone, PartialEq, Deserialize)]
711#[serde(untagged)]
712#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
713pub enum CopyResourcePatch {
714 Copy(CopyPatch),
715 Content(CopyContentPatch),
716 AddGitRepo(AddGitRepoPatch),
717 Add(AddPatch),
718 Unknown(UnknownPatch),
719}
720
721#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
722#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
723pub struct UnknownPatch {
724 #[serde(flatten)]
725 pub options: Option<CopyOptionsPatch>,
726 pub exclude: Option<VecPatch<String>>,
727}
728
729#[cfg(test)]
732mod test {
733 use super::*;
734 use pretty_assertions_sorted::assert_eq_sorted;
735
736 mod deserialize {
737 use super::*;
738
739 mod dofigen {
740 use super::*;
741
742 #[test]
743 fn empty() {
744 let data = r#""#;
745
746 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
747 let dofigen: Dofigen = dofigen.into();
748
749 assert_eq_sorted!(dofigen, Dofigen::default());
750 }
751
752 #[test]
753 fn from() {
754 let data = r#"
755 fromImage:
756 path: ubuntu
757 "#;
758
759 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
760 let dofigen: Dofigen = dofigen.into();
761
762 assert_eq_sorted!(
763 dofigen,
764 Dofigen {
765 stage: Stage {
766 from: FromContext::FromImage(ImageName {
767 path: "ubuntu".into(),
768 ..Default::default()
769 }),
770 ..Default::default()
771 },
772 ..Default::default()
773 }
774 );
775 }
776
777 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
778 #[test]
779 fn duplicate_from() {
780 let data = r#"
781 from:
782 path: ubuntu
783 from:
784 path: alpine
785 "#;
786
787 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
788
789 println!("{:?}", dofigen);
790
791 assert!(dofigen.is_err());
792 }
793
794 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
795 #[test]
796 fn duplicate_from_and_alias() {
797 let data = r#"
798 from:
799 path: ubuntu
800 image:
801 path: alpine
802 "#;
803
804 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
805
806 println!("{:?}", dofigen);
807
808 assert!(dofigen.is_err());
809 }
810 }
811
812 mod stage {
813 use super::*;
814
815 #[test]
816 fn empty() {
817 let data = r#""#;
818
819 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
820 let stage: Stage = stage.into();
821
822 assert_eq_sorted!(stage, Stage::default());
823 }
824
825 #[test]
826 fn from() {
827 let data = r#"
828 fromImage:
829 path: ubuntu
830 "#;
831
832 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
833 let stage: Stage = stage.into();
834
835 assert_eq_sorted!(
836 stage,
837 Stage {
838 from: FromContext::FromImage(ImageName {
839 path: "ubuntu".into(),
840 ..Default::default()
841 })
842 .into(),
843 ..Default::default()
844 }
845 );
846 }
847
848 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
849 #[test]
850 fn duplicate_from() {
851 let data = r#"
852 fromImage:
853 path: ubuntu
854 fromImage:
855 path: alpine
856 "#;
857
858 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
859
860 assert!(stage.is_err());
861 }
862
863 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
864 #[test]
865 fn duplicate_from_and_alias() {
866 let data = r#"
867 fromImage:
868 path: ubuntu
869 image:
870 path: alpine
871 "#;
872
873 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
874
875 assert!(stage.is_err());
876 }
877
878 #[test]
879 fn label() {
880 #[cfg(not(feature = "strict"))]
881 let data = r#"
882 label:
883 io.dofigen:
884 test: test
885 "#;
886 #[cfg(feature = "strict")]
887 let data = r#"
888 label:
889 io.dofigen.test: test
890 "#;
891
892 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
893 let stage: Stage = stage.into();
894
895 assert_eq_sorted!(
896 stage,
897 Stage {
898 label: HashMap::from([("io.dofigen.test".into(), "test".into())]),
899 ..Default::default()
900 }
901 );
902 }
903 }
904
905 mod user {
906 use super::*;
907
908 #[test]
909 fn name_and_group() {
910 let json_data = r#"{
911 "user": "test",
912 "group": "test"
913}"#;
914
915 let user: UserPatch = serde_yaml::from_str(json_data).unwrap();
916 let user: User = user.into();
917
918 assert_eq_sorted!(
919 user,
920 User {
921 user: "test".into(),
922 group: Some("test".into())
923 }
924 );
925 }
926 }
927
928 mod copy_resource {
929
930 use super::*;
931
932 #[test]
933 fn copy() {
934 let json_data = r#"{
935 "paths": ["file1.txt", "file2.txt"],
936 "target": "destination/",
937 "chown": {
938 "user": "root",
939 "group": "root"
940 },
941 "chmod": "755",
942 "link": true,
943 "fromImage": {"path": "my-image"}
944}"#;
945
946 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
947 let copy_resource: CopyResource = copy_resource.into();
948
949 assert_eq_sorted!(
950 copy_resource,
951 CopyResource::Copy(Copy {
952 paths: vec!["file1.txt".into(), "file2.txt".into()].into(),
953 options: CopyOptions {
954 target: Some("destination/".into()),
955 chown: Some(User {
956 user: "root".into(),
957 group: Some("root".into())
958 }),
959 chmod: Some("755".into()),
960 link: Some(true),
961 },
962 from: FromContext::FromImage(ImageName {
963 path: "my-image".into(),
964 ..Default::default()
965 }),
966 exclude: vec![].into(),
967 parents: None,
968 })
969 );
970 }
971
972 #[test]
973 fn copy_simple() {
974 let json_data = r#"{
975 "paths": ["file1.txt"]
976}"#;
977
978 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
979
980 assert_eq_sorted!(
981 copy_resource,
982 CopyResourcePatch::Copy(CopyPatch {
983 paths: Some(vec!["file1.txt".into()].into_patch()),
984 options: Some(CopyOptionsPatch::default()),
985 ..Default::default()
986 })
987 );
988
989 let copy_resource: CopyResource = copy_resource.into();
990
991 assert_eq_sorted!(
992 copy_resource,
993 CopyResource::Copy(Copy {
994 paths: vec!["file1.txt".into()].into(),
995 options: CopyOptions::default(),
996 ..Default::default()
997 })
998 );
999 }
1000
1001 #[cfg(feature = "permissive")]
1002 #[test]
1003 fn copy_chmod_int() {
1004 let json_data = r#"{
1005 "paths": ["file1.txt"],
1006 "chmod": 755
1007}"#;
1008
1009 let copy_resource: CopyPatch = serde_yaml::from_str(json_data).unwrap();
1010
1011 assert_eq_sorted!(
1012 copy_resource,
1013 CopyPatch {
1014 paths: Some(vec!["file1.txt".into()].into_patch()),
1015 options: Some(CopyOptionsPatch {
1016 chmod: Some(Some("755".into())),
1017 ..Default::default()
1018 }),
1019 ..Default::default()
1020 }
1021 );
1022 }
1023
1024 #[cfg(feature = "permissive")]
1025 #[test]
1026 fn deserialize_copy_from_str() {
1027 let json_data = "file1.txt destination/";
1028
1029 let copy_resource: ParsableStruct<CopyResourcePatch> =
1030 serde_yaml::from_str(json_data).unwrap();
1031 let copy_resource: CopyResource = copy_resource.into();
1032
1033 assert_eq_sorted!(
1034 copy_resource,
1035 CopyResource::Copy(Copy {
1036 paths: vec!["file1.txt".into()].into(),
1037 options: CopyOptions {
1038 target: Some("destination/".into()),
1039 ..Default::default()
1040 },
1041 ..Default::default()
1042 })
1043 );
1044 }
1045
1046 #[test]
1047 fn copy_content() {
1048 let json_data = r#"{
1049 "content": "echo coucou",
1050 "substitute": false,
1051 "target": "test.sh",
1052 "chown": {
1053 "user": "1001",
1054 "group": "1001"
1055 },
1056 "chmod": "555",
1057 "link": true
1058}"#;
1059
1060 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1061 let copy_resource: CopyResource = copy_resource.into();
1062
1063 assert_eq_sorted!(
1064 copy_resource,
1065 CopyResource::Content(CopyContent {
1066 content: "echo coucou".into(),
1067 substitute: Some(false),
1068 options: CopyOptions {
1069 target: Some("test.sh".into()),
1070 chown: Some(User {
1071 user: "1001".into(),
1072 group: Some("1001".into())
1073 }),
1074 chmod: Some("555".into()),
1075 link: Some(true),
1076 }
1077 })
1078 );
1079 }
1080
1081 #[test]
1082 fn add_git_repo() {
1083 let json_data = r#"{
1084 "repo": "https://github.com/example/repo.git",
1085 "target": "destination/",
1086 "chown": {
1087 "user": "root",
1088 "group": "root"
1089 },
1090 "chmod": "755",
1091 "link": true,
1092 "keepGitDir": true
1093 }"#;
1094
1095 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1096 let copy_resource: CopyResource = copy_resource.into();
1097
1098 assert_eq_sorted!(
1099 copy_resource,
1100 CopyResource::AddGitRepo(AddGitRepo {
1101 repo: "https://github.com/example/repo.git".into(),
1102 options: CopyOptions {
1103 target: Some("destination/".into()),
1104 chown: Some(User {
1105 user: "root".into(),
1106 group: Some("root".into())
1107 }),
1108 chmod: Some("755".into()),
1109 link: Some(true),
1110 },
1111 keep_git_dir: Some(true),
1112 exclude: vec![].into(),
1113 })
1114 );
1115 }
1116
1117 #[test]
1118 fn add() {
1119 let json_data = r#"{
1120 "files": ["file1.txt", "file2.txt"],
1121 "target": "destination/",
1122 "checksum": "sha256:abcdef123456",
1123 "chown": {
1124 "user": "root",
1125 "group": "root"
1126 },
1127 "chmod": "755",
1128 "link": true
1129 }"#;
1130
1131 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1132 let copy_resource: CopyResource = copy_resource.into();
1133
1134 assert_eq_sorted!(
1135 copy_resource,
1136 CopyResource::Add(Add {
1137 files: vec![
1138 Resource::File("file1.txt".into()),
1139 Resource::File("file2.txt".into())
1140 ]
1141 .into(),
1142 options: CopyOptions {
1143 target: Some("destination/".into()),
1144 chown: Some(User {
1145 user: "root".into(),
1146 group: Some("root".into())
1147 }),
1148 chmod: Some("755".into()),
1149 link: Some(true),
1150 },
1151 checksum: Some("sha256:abcdef123456".into()),
1152 })
1153 );
1154 }
1155 }
1156
1157 mod builder {
1158 use super::*;
1159
1160 #[test]
1161 fn with_bind() {
1162 let json_data = r#"
1163fromImage:
1164 path: clux/muslrust:stable
1165workdir: /app
1166bind:
1167 - target: /app
1168run:
1169 - cargo build --release
1170 - mv target/x86_64-unknown-linux-musl/release/dofigen /app/
1171"#;
1172
1173 let builder: Stage = serde_yaml::from_str::<StagePatch>(json_data)
1174 .unwrap()
1175 .into();
1176
1177 assert_eq_sorted!(
1178 builder,
1179 Stage {
1180 from: FromContext::FromImage(ImageName {
1181 path: "clux/muslrust:stable".into(),
1182 ..Default::default()
1183 }),
1184 workdir: Some("/app".into()),
1185 run: Run {
1186 bind: vec![Bind {
1187 target: "/app".into(),
1188 ..Default::default()
1189 }],
1190 run: vec![
1191 "cargo build --release".into(),
1192 "mv target/x86_64-unknown-linux-musl/release/dofigen /app/".into()
1193 ],
1194 ..Default::default()
1195 },
1196 ..Default::default()
1197 }
1198 );
1199 }
1200 }
1201 }
1202}