1use crate::deserialize::*;
2#[cfg(feature = "json_schema")]
3use crate::json_schema::optional_string_or_number_schema;
4#[cfg(feature = "json_schema")]
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::{collections::HashMap, path::PathBuf};
8use struct_patch::Patch;
9use url::Url;
10
11#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
13#[serde(rename_all = "camelCase")]
14#[patch(
15 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
16 attribute(serde(default, rename_all = "camelCase"))
18)]
19#[cfg_attr(
20 feature = "json_schema",
21 patch(
22 attribute(derive(JsonSchema)),
23 attribute(schemars(
24 title = "Dofigen",
25 rename = "Dofigen",
26 extend("$id" = "https://json.schemastore.org/dofigen.json"),
27 description = "Dofigen is a Dockerfile generator using a simplified description in YAML or JSON format"
28 ))
29 )
30)]
31pub struct Dofigen {
32 #[patch(name = "VecPatch<String>")]
35 #[serde(skip_serializing_if = "Vec::is_empty")]
36 pub context: Vec<String>,
37
38 #[patch(name = "VecPatch<String>")]
41 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ignores"))))]
42 #[serde(skip_serializing_if = "Vec::is_empty")]
43 pub ignore: Vec<String>,
44
45 #[patch(name = "HashMapPatch<String, String>")]
48 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "globalArgs"))))]
49 #[serde(skip_serializing_if = "HashMap::is_empty")]
50 pub global_arg: HashMap<String, String>,
51
52 #[patch(name = "HashMapDeepPatch<String, StagePatch>")]
54 #[serde(skip_serializing_if = "HashMap::is_empty")]
55 pub builders: HashMap<String, Stage>,
56
57 #[patch(name = "StagePatch", attribute(serde(flatten)))]
59 #[serde(flatten)]
60 pub stage: Stage,
61
62 #[patch(name = "VecPatch<String>")]
65 #[serde(skip_serializing_if = "Vec::is_empty")]
66 pub entrypoint: Vec<String>,
67
68 #[patch(name = "VecPatch<String>")]
71 #[serde(skip_serializing_if = "Vec::is_empty")]
72 pub cmd: Vec<String>,
73
74 #[patch(name = "VecPatch<String>")]
77 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "volumes"))))]
78 #[serde(skip_serializing_if = "Vec::is_empty")]
79 pub volume: Vec<String>,
80
81 #[cfg_attr(
84 feature = "permissive",
85 patch(name = "VecDeepPatch<Port, ParsableStruct<PortPatch>>")
86 )]
87 #[cfg_attr(
88 not(feature = "permissive"),
89 patch(name = "VecDeepPatch<Port, PortPatch>")
90 )]
91 #[cfg_attr(
92 not(feature = "strict"),
93 patch(attribute(serde(alias = "port", alias = "ports")))
94 )]
95 #[serde(skip_serializing_if = "Vec::is_empty")]
96 pub expose: Vec<Port>,
97
98 #[patch(name = "Option<HealthcheckPatch>")]
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub healthcheck: Option<Healthcheck>,
103}
104
105#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
107#[patch(
108 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
109 attribute(serde(default)),
111)]
112#[cfg_attr(
113 feature = "json_schema",
114 patch(
115 attribute(derive(JsonSchema)),
116 attribute(schemars(title = "Stage", rename = "Stage"))
117 )
118)]
119pub struct Stage {
120 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
123 #[patch(name = "FromContextPatch", attribute(serde(flatten, default)))]
124 pub from: FromContext,
125
126 #[cfg_attr(not(feature = "strict"), patch(name = "NestedMap<String>"))]
129 #[cfg_attr(feature = "strict", patch(name = "HashMapPatch<String, String>"))]
130 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "labels"))))]
131 #[serde(skip_serializing_if = "HashMap::is_empty")]
132 pub label: HashMap<String, String>,
133
134 #[cfg_attr(
137 feature = "permissive",
138 patch(name = "Option<ParsableStruct<UserPatch>>")
139 )]
140 #[cfg_attr(not(feature = "permissive"), patch(name = "Option<UserPatch>"))]
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub user: Option<User>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
147 pub workdir: Option<String>,
148
149 #[patch(name = "HashMapPatch<String, String>")]
152 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "args"))))]
153 #[serde(skip_serializing_if = "HashMap::is_empty")]
154 pub arg: HashMap<String, String>,
155
156 #[patch(name = "HashMapPatch<String, String>")]
159 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "envs"))))]
160 #[serde(skip_serializing_if = "HashMap::is_empty")]
161 pub env: HashMap<String, String>,
162
163 #[cfg_attr(
166 not(feature = "strict"),
167 patch(attribute(serde(
168 alias = "add",
169 alias = "adds",
170 alias = "artifact",
171 alias = "artifacts"
172 )))
173 )]
174 #[cfg_attr(
175 feature = "permissive",
176 patch(name = "VecDeepPatch<CopyResource, ParsableStruct<CopyResourcePatch>>")
177 )]
178 #[cfg_attr(
179 not(feature = "permissive"),
180 patch(name = "VecDeepPatch<CopyResource, CopyResourcePatch>")
181 )]
182 #[serde(skip_serializing_if = "Vec::is_empty")]
183 pub copy: Vec<CopyResource>,
184
185 #[patch(name = "Option<RunPatch>")]
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub root: Option<Run>,
189
190 #[patch(name = "RunPatch", attribute(serde(flatten)))]
193 #[serde(flatten)]
194 pub run: Run,
195}
196
197#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
199#[patch(
200 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
201 attribute(serde(default)),
203)]
204#[cfg_attr(
205 feature = "json_schema",
206 patch(
207 attribute(derive(JsonSchema)),
208 attribute(schemars(title = "Run", rename = "Run"))
209 )
210)]
211pub struct Run {
212 #[patch(name = "VecPatch<String>")]
214 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "script"))))]
215 #[serde(skip_serializing_if = "Vec::is_empty")]
216 pub run: Vec<String>,
217
218 #[patch(name = "VecPatch<String>")]
221 #[serde(skip_serializing_if = "Vec::is_empty")]
222 pub shell: Vec<String>,
223
224 #[cfg_attr(
227 feature = "permissive",
228 patch(name = "VecDeepPatch<Cache, ParsableStruct<CachePatch>>")
229 )]
230 #[cfg_attr(
231 not(feature = "permissive"),
232 patch(name = "VecDeepPatch<Cache, CachePatch>")
233 )]
234 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "caches"))))]
235 #[serde(skip_serializing_if = "Vec::is_empty")]
236 pub cache: Vec<Cache>,
237
238 #[cfg_attr(
242 feature = "permissive",
243 patch(name = "VecDeepPatch<Bind, ParsableStruct<BindPatch>>")
244 )]
245 #[cfg_attr(
246 not(feature = "permissive"),
247 patch(name = "VecDeepPatch<Bind, BindPatch>")
248 )]
249 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "binds"))))]
250 #[serde(skip_serializing_if = "Vec::is_empty")]
251 pub bind: Vec<Bind>,
252
253 #[cfg_attr(
255 feature = "permissive",
256 patch(name = "VecDeepPatch<TmpFs, ParsableStruct<TmpFsPatch>>")
257 )]
258 #[cfg_attr(
259 not(feature = "permissive"),
260 patch(name = "VecDeepPatch<TmpFs, TmpFsPatch>")
261 )]
262 #[serde(skip_serializing_if = "Vec::is_empty")]
263 pub tmpfs: Vec<TmpFs>,
264
265 #[patch(name = "VecDeepPatch<Secret, SecretPatch>")]
269 #[serde(skip_serializing_if = "Vec::is_empty")]
270 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "secrets"))))]
271 pub secret: Vec<Secret>,
272
273 #[patch(name = "VecDeepPatch<Ssh, SshPatch>")]
276 #[serde(skip_serializing_if = "Vec::is_empty")]
277 pub ssh: Vec<Ssh>,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
282 pub network: Option<Network>,
283
284 #[serde(skip_serializing_if = "Option::is_none")]
287 pub security: Option<Security>,
288}
289
290#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
293#[patch(
294 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
295 attribute(serde(default))
296)]
297#[cfg_attr(
298 feature = "json_schema",
299 patch(
300 attribute(derive(JsonSchema)),
301 attribute(schemars(title = "Cache", rename = "Cache"))
302 )
303)]
304pub struct Cache {
305 #[serde(skip_serializing_if = "Option::is_none")]
308 pub id: Option<String>,
309
310 #[cfg_attr(
312 not(feature = "strict"),
313 patch(attribute(serde(alias = "dst", alias = "destination")))
314 )]
315 pub target: String,
316
317 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ro"))))]
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub readonly: Option<bool>,
321
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub sharing: Option<CacheSharing>,
325
326 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
328 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
329 pub from: FromContext,
330
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub source: Option<String>,
334
335 #[cfg_attr(
337 feature = "permissive",
338 patch(attribute(serde(
339 deserialize_with = "deserialize_from_optional_string_or_number",
340 default
341 )))
342 )]
343 #[serde(skip_serializing_if = "Option::is_none")]
344 #[cfg_attr(
345 feature = "json_schema",
346 patch(attribute(schemars(schema_with = "optional_string_or_number_schema")))
347 )]
348 pub chmod: Option<String>,
349
350 #[patch(name = "Option<UserPatch>")]
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub chown: Option<User>,
354}
355
356#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
359#[patch(
360 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
361 attribute(serde(default))
362)]
363#[cfg_attr(
364 feature = "json_schema",
365 patch(
366 attribute(derive(JsonSchema)),
367 attribute(schemars(title = "Bind", rename = "Bind"))
368 )
369)]
370pub struct Bind {
371 pub target: String,
373
374 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
376 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
377 pub from: FromContext,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub source: Option<String>,
382
383 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "rw"))))]
385 #[serde(skip_serializing_if = "Option::is_none")]
386 pub readwrite: Option<bool>,
387}
388
389#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
392#[patch(
393 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
394 attribute(serde(default))
395)]
396#[cfg_attr(
397 feature = "json_schema",
398 patch(
399 attribute(derive(JsonSchema)),
400 attribute(schemars(title = "TmpFs", rename = "TmpFs"))
401 )
402)]
403pub struct TmpFs {
404 pub target: String,
406
407 #[serde(skip_serializing_if = "Option::is_none")]
409 pub size: Option<String>,
410}
411
412#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
416#[patch(
417 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
418 attribute(serde(default))
419)]
420#[cfg_attr(
421 feature = "json_schema",
422 patch(
423 attribute(derive(JsonSchema)),
424 attribute(schemars(title = "Secret", rename = "Secret"))
425 )
426)]
427pub struct Secret {
428 #[serde(skip_serializing_if = "Option::is_none")]
430 pub id: Option<String>,
431
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub target: Option<String>,
435
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub env: Option<String>,
439
440 #[serde(skip_serializing_if = "Option::is_none")]
442 pub required: Option<bool>,
443
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub mode: Option<String>,
447
448 #[serde(skip_serializing_if = "Option::is_none")]
450 pub uid: Option<u16>,
451
452 #[serde(skip_serializing_if = "Option::is_none")]
454 pub gid: Option<u16>,
455}
456
457#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
460#[patch(
461 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
462 attribute(serde(default))
463)]
464#[cfg_attr(
465 feature = "json_schema",
466 patch(
467 attribute(derive(JsonSchema)),
468 attribute(schemars(title = "Ssh", rename = "Ssh"))
469 )
470)]
471pub struct Ssh {
472 #[serde(skip_serializing_if = "Option::is_none")]
474 pub id: Option<String>,
475
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub target: Option<String>,
479
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub required: Option<bool>,
483
484 #[serde(skip_serializing_if = "Option::is_none")]
486 pub mode: Option<String>,
487
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub uid: Option<u16>,
491
492 #[serde(skip_serializing_if = "Option::is_none")]
494 pub gid: Option<u16>,
495}
496
497#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
500#[patch(
501 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
502 attribute(serde(deny_unknown_fields, default))
503)]
504#[cfg_attr(
505 feature = "json_schema",
506 patch(
507 attribute(derive(JsonSchema)),
508 attribute(schemars(title = "Healthcheck", rename = "Healthcheck"))
509 )
510)]
511pub struct Healthcheck {
512 pub cmd: String,
514
515 #[serde(skip_serializing_if = "Option::is_none")]
517 pub interval: Option<String>,
518
519 #[serde(skip_serializing_if = "Option::is_none")]
521 pub timeout: Option<String>,
522
523 #[serde(skip_serializing_if = "Option::is_none")]
525 pub start: Option<String>,
526
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub retries: Option<u16>,
530}
531
532#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch, Hash, Eq, PartialOrd)]
534#[patch(
535 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
536 attribute(serde(deny_unknown_fields, default))
537)]
538#[cfg_attr(
539 feature = "json_schema",
540 patch(
541 attribute(derive(JsonSchema)),
542 attribute(schemars(title = "ImageName", rename = "ImageName"))
543 )
544)]
545pub struct ImageName {
546 #[serde(skip_serializing_if = "Option::is_none")]
548 pub host: Option<String>,
549
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub port: Option<u16>,
553
554 pub path: String,
556
557 #[serde(flatten, skip_serializing_if = "Option::is_none")]
559 #[patch(attribute(serde(flatten)))]
560 pub version: Option<ImageVersion>,
561
562 #[serde(skip_serializing_if = "Option::is_none")]
567 pub platform: Option<String>,
568}
569
570#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
573#[patch(
574 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
575 attribute(serde(deny_unknown_fields, default))
576)]
577#[cfg_attr(
578 feature = "json_schema",
579 patch(
580 attribute(derive(JsonSchema)),
581 attribute(schemars(title = "Copy", rename = "Copy"))
582 )
583)]
584pub struct Copy {
585 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
588 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
589 pub from: FromContext,
590
591 #[patch(name = "VecPatch<String>")]
593 #[cfg_attr(
594 not(feature = "strict"),
595 patch(attribute(serde(alias = "path", alias = "source")))
596 )]
597 #[serde(skip_serializing_if = "Vec::is_empty")]
598 pub paths: Vec<String>,
599
600 #[serde(flatten)]
602 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
603 pub options: CopyOptions,
604
605 #[patch(name = "VecPatch<String>")]
607 #[serde(skip_serializing_if = "Vec::is_empty")]
608 pub exclude: Vec<String>,
609
610 #[serde(skip_serializing_if = "Option::is_none")]
612 pub parents: Option<bool>,
613}
614
615#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
618#[patch(
619 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
620 attribute(serde(deny_unknown_fields, default))
621)]
622#[cfg_attr(
623 feature = "json_schema",
624 patch(
625 attribute(derive(JsonSchema)),
626 attribute(schemars(title = "CopyContent", rename = "CopyContent"))
627 )
628)]
629pub struct CopyContent {
630 pub content: String,
632
633 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "subst"))))]
635 #[serde(skip_serializing_if = "Option::is_none")]
636 pub substitute: Option<bool>,
637
638 #[serde(flatten)]
640 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
641 pub options: CopyOptions,
642}
643
644#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
647#[patch(
648 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
649 attribute(serde(deny_unknown_fields, default, rename_all = "camelCase"))
650)]
651#[cfg_attr(
652 feature = "json_schema",
653 patch(
654 attribute(derive(JsonSchema)),
655 attribute(schemars(title = "AddGitRepo", rename = "AddGitRepo"))
656 )
657)]
658pub struct AddGitRepo {
659 pub repo: String,
661
662 #[serde(flatten)]
664 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
665 pub options: CopyOptions,
666
667 #[patch(name = "VecPatch<String>")]
669 #[serde(skip_serializing_if = "Vec::is_empty")]
670 pub exclude: Vec<String>,
671
672 #[serde(skip_serializing_if = "Option::is_none")]
675 pub keep_git_dir: Option<bool>,
676
677 #[serde(skip_serializing_if = "Option::is_none")]
680 pub checksum: Option<String>,
681}
682
683#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
685#[patch(
686 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
687 attribute(serde(deny_unknown_fields, default))
688)]
689#[cfg_attr(
690 feature = "json_schema",
691 patch(
692 attribute(derive(JsonSchema)),
693 attribute(schemars(title = "Add", rename = "Add"))
694 )
695)]
696pub struct Add {
697 #[patch(name = "VecPatch<Resource>")]
699 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "file"))))]
700 #[serde(skip_serializing_if = "Vec::is_empty")]
701 pub files: Vec<Resource>,
702
703 #[serde(flatten)]
705 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
706 pub options: CopyOptions,
707
708 #[serde(skip_serializing_if = "Option::is_none")]
711 pub checksum: Option<String>,
712
713 #[serde(skip_serializing_if = "Option::is_none")]
717 pub unpack: Option<bool>,
718}
719
720#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
722#[patch(
723 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
724 attribute(serde(deny_unknown_fields, default))
725)]
726#[cfg_attr(
727 feature = "json_schema",
728 patch(
729 attribute(derive(JsonSchema)),
730 attribute(schemars(title = "CopyOptions", rename = "CopyOptions"))
731 )
732)]
733pub struct CopyOptions {
734 #[cfg_attr(
736 not(feature = "strict"),
737 patch(attribute(serde(alias = "destination")))
738 )]
739 #[serde(skip_serializing_if = "Option::is_none")]
740 pub target: Option<String>,
741
742 #[patch(name = "Option<UserPatch>")]
745 #[serde(skip_serializing_if = "Option::is_none")]
746 pub chown: Option<User>,
747
748 #[cfg_attr(
751 feature = "permissive",
752 patch(attribute(serde(
753 deserialize_with = "deserialize_from_optional_string_or_number",
754 default
755 )))
756 )]
757 #[serde(skip_serializing_if = "Option::is_none")]
758 #[cfg_attr(
759 feature = "json_schema",
760 patch(attribute(schemars(schema_with = "optional_string_or_number_schema")))
761 )]
762 pub chmod: Option<String>,
763
764 #[serde(skip_serializing_if = "Option::is_none")]
767 pub link: Option<bool>,
768}
769
770#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
772#[patch(
773 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
774 attribute(serde(deny_unknown_fields, default))
775)]
776#[cfg_attr(
777 feature = "json_schema",
778 patch(
779 attribute(derive(JsonSchema)),
780 attribute(schemars(title = "User", rename = "User"))
781 )
782)]
783pub struct User {
784 pub user: String,
787
788 #[serde(skip_serializing_if = "Option::is_none")]
791 pub group: Option<String>,
792}
793
794#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
796#[patch(
797 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
798 attribute(serde(deny_unknown_fields, default))
799)]
800#[cfg_attr(
801 feature = "json_schema",
802 patch(
803 attribute(derive(JsonSchema)),
804 attribute(schemars(title = "Port", rename = "Port"))
805 )
806)]
807pub struct Port {
808 pub port: u16,
810
811 #[serde(skip_serializing_if = "Option::is_none")]
813 pub protocol: Option<PortProtocol>,
814}
815
816#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
820#[serde(rename_all = "camelCase")]
821#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
822pub enum ImageVersion {
823 Tag(String),
824 Digest(String),
825}
826
827#[derive(Serialize, Debug, Clone, PartialEq)]
829#[serde(rename_all = "camelCase")]
830pub enum FromContext {
831 FromImage(ImageName),
832 FromBuilder(String),
833 FromContext(Option<String>),
834}
835
836#[derive(Serialize, Debug, Clone, PartialEq)]
837#[serde(untagged)]
838pub enum CopyResource {
839 Copy(Copy),
840 Content(CopyContent),
841 AddGitRepo(AddGitRepo),
842 Add(Add),
843}
844
845#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
847#[serde(rename_all = "camelCase")]
848#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
849pub enum CacheSharing {
850 Shared,
851 Private,
852 Locked,
853}
854
855#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
857#[serde(rename_all = "camelCase")]
858#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
859pub enum PortProtocol {
860 Tcp,
861 Udp,
862}
863
864#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
866#[serde(untagged)]
867#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
868pub enum Resource {
869 Url(Url),
870 File(PathBuf),
871}
872
873#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
875#[serde(rename_all = "camelCase")]
876#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
877pub enum Network {
878 Default,
880 None,
882 Host,
884}
885
886#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
888#[serde(rename_all = "camelCase")]
889#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
890pub enum Security {
891 Sandbox,
892 Insecure,
893}
894
895#[derive(Debug, Clone, PartialEq, Deserialize)]
898#[serde(rename_all = "camelCase")]
899#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
900pub enum FromContextPatch {
901 #[cfg(not(feature = "permissive"))]
902 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
903 FromImage(ImageNamePatch),
904
905 #[cfg(feature = "permissive")]
906 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
907 FromImage(ParsableStruct<ImageNamePatch>),
908
909 #[cfg_attr(not(feature = "strict"), serde(alias = "builder"))]
910 FromBuilder(String),
911
912 #[cfg_attr(not(feature = "strict"), serde(alias = "from"))]
913 FromContext(Option<String>),
914}
915
916#[derive(Debug, Clone, PartialEq, Deserialize)]
917#[serde(untagged)]
918#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
919pub enum CopyResourcePatch {
920 Copy(CopyPatch),
921 Content(CopyContentPatch),
922 AddGitRepo(AddGitRepoPatch),
923 Add(AddPatch),
924 Unknown(UnknownPatch),
925}
926
927#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
928#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
929pub struct UnknownPatch {
930 #[serde(flatten)]
931 pub options: Option<CopyOptionsPatch>,
932 pub exclude: Option<VecPatch<String>>,
933}
934
935#[cfg(test)]
938mod test {
939 use super::*;
940 use pretty_assertions_sorted::assert_eq_sorted;
941
942 mod deserialize {
943 use super::*;
944
945 mod dofigen {
946 use super::*;
947
948 #[test]
949 fn empty() {
950 let data = r#""#;
951
952 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
953 let dofigen: Dofigen = dofigen.into();
954
955 assert_eq_sorted!(dofigen, Dofigen::default());
956 }
957
958 #[test]
959 fn from() {
960 let data = r#"
961 fromImage:
962 path: ubuntu
963 "#;
964
965 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
966 let dofigen: Dofigen = dofigen.into();
967
968 assert_eq_sorted!(
969 dofigen,
970 Dofigen {
971 stage: Stage {
972 from: FromContext::FromImage(ImageName {
973 path: "ubuntu".into(),
974 ..Default::default()
975 }),
976 ..Default::default()
977 },
978 ..Default::default()
979 }
980 );
981 }
982
983 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
984 #[test]
985 fn duplicate_from() {
986 let data = r#"
987 from:
988 path: ubuntu
989 from:
990 path: alpine
991 "#;
992
993 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
994
995 println!("{:?}", dofigen);
996
997 assert!(dofigen.is_err());
998 }
999
1000 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
1001 #[test]
1002 fn duplicate_from_and_alias() {
1003 let data = r#"
1004 from:
1005 path: ubuntu
1006 image:
1007 path: alpine
1008 "#;
1009
1010 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
1011
1012 println!("{:?}", dofigen);
1013
1014 assert!(dofigen.is_err());
1015 }
1016
1017 #[test]
1018 fn global_arg() {
1019 let data = r#"
1020 globalArg:
1021 IMAGE: ubuntu
1022 fromContext: ${IMAGE}
1023 "#;
1024
1025 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
1026 let dofigen: Dofigen = dofigen.into();
1027
1028 assert_eq_sorted!(
1029 dofigen,
1030 Dofigen {
1031 global_arg: HashMap::from([("IMAGE".into(), "ubuntu".into())]),
1032 stage: Stage {
1033 from: FromContext::FromContext(Some("${IMAGE}".into())).into(),
1034 ..Default::default()
1035 },
1036 ..Default::default()
1037 }
1038 );
1039 }
1040 }
1041
1042 mod stage {
1043 use super::*;
1044
1045 #[test]
1046 fn empty() {
1047 let data = r#""#;
1048
1049 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
1050 let stage: Stage = stage.into();
1051
1052 assert_eq_sorted!(stage, Stage::default());
1053 }
1054
1055 #[test]
1056 fn from() {
1057 let data = r#"
1058 fromImage:
1059 path: ubuntu
1060 "#;
1061
1062 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
1063 let stage: Stage = stage.into();
1064
1065 assert_eq_sorted!(
1066 stage,
1067 Stage {
1068 from: FromContext::FromImage(ImageName {
1069 path: "ubuntu".into(),
1070 ..Default::default()
1071 })
1072 .into(),
1073 ..Default::default()
1074 }
1075 );
1076 }
1077
1078 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
1079 #[test]
1080 fn duplicate_from() {
1081 let data = r#"
1082 fromImage:
1083 path: ubuntu
1084 fromImage:
1085 path: alpine
1086 "#;
1087
1088 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
1089
1090 assert!(stage.is_err());
1091 }
1092
1093 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
1094 #[test]
1095 fn duplicate_from_and_alias() {
1096 let data = r#"
1097 fromImage:
1098 path: ubuntu
1099 image:
1100 path: alpine
1101 "#;
1102
1103 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
1104
1105 assert!(stage.is_err());
1106 }
1107
1108 #[test]
1109 fn label() {
1110 #[cfg(not(feature = "strict"))]
1111 let data = r#"
1112 label:
1113 io.dofigen:
1114 test: test
1115 "#;
1116 #[cfg(feature = "strict")]
1117 let data = r#"
1118 label:
1119 io.dofigen.test: test
1120 "#;
1121
1122 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
1123 let stage: Stage = stage.into();
1124
1125 assert_eq_sorted!(
1126 stage,
1127 Stage {
1128 label: HashMap::from([("io.dofigen.test".into(), "test".into())]),
1129 ..Default::default()
1130 }
1131 );
1132 }
1133 }
1134
1135 mod user {
1136 use super::*;
1137
1138 #[test]
1139 fn name_and_group() {
1140 let json_data = r#"{
1141 "user": "test",
1142 "group": "test"
1143}"#;
1144
1145 let user: UserPatch = serde_yaml::from_str(json_data).unwrap();
1146 let user: User = user.into();
1147
1148 assert_eq_sorted!(
1149 user,
1150 User {
1151 user: "test".into(),
1152 group: Some("test".into())
1153 }
1154 );
1155 }
1156 }
1157
1158 mod copy_resource {
1159
1160 use super::*;
1161
1162 #[test]
1163 fn copy() {
1164 let json_data = r#"{
1165 "paths": ["file1.txt", "file2.txt"],
1166 "target": "destination/",
1167 "chown": {
1168 "user": "root",
1169 "group": "root"
1170 },
1171 "chmod": "755",
1172 "link": true,
1173 "fromImage": {"path": "my-image"}
1174}"#;
1175
1176 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1177 let copy_resource: CopyResource = copy_resource.into();
1178
1179 assert_eq_sorted!(
1180 copy_resource,
1181 CopyResource::Copy(Copy {
1182 paths: vec!["file1.txt".into(), "file2.txt".into()].into(),
1183 options: CopyOptions {
1184 target: Some("destination/".into()),
1185 chown: Some(User {
1186 user: "root".into(),
1187 group: Some("root".into())
1188 }),
1189 chmod: Some("755".into()),
1190 link: Some(true),
1191 },
1192 from: FromContext::FromImage(ImageName {
1193 path: "my-image".into(),
1194 ..Default::default()
1195 }),
1196 exclude: vec![].into(),
1197 parents: None,
1198 })
1199 );
1200 }
1201
1202 #[test]
1203 fn copy_simple() {
1204 let json_data = r#"{
1205 "paths": ["file1.txt"]
1206}"#;
1207
1208 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1209
1210 assert_eq_sorted!(
1211 copy_resource,
1212 CopyResourcePatch::Copy(CopyPatch {
1213 paths: Some(vec!["file1.txt".into()].into_patch()),
1214 options: Some(CopyOptionsPatch::default()),
1215 ..Default::default()
1216 })
1217 );
1218
1219 let copy_resource: CopyResource = copy_resource.into();
1220
1221 assert_eq_sorted!(
1222 copy_resource,
1223 CopyResource::Copy(Copy {
1224 paths: vec!["file1.txt".into()].into(),
1225 options: CopyOptions::default(),
1226 ..Default::default()
1227 })
1228 );
1229 }
1230
1231 #[cfg(feature = "permissive")]
1232 #[test]
1233 fn copy_chmod_int() {
1234 let json_data = r#"{
1235 "paths": ["file1.txt"],
1236 "chmod": 755
1237}"#;
1238
1239 let copy_resource: CopyPatch = serde_yaml::from_str(json_data).unwrap();
1240
1241 assert_eq_sorted!(
1242 copy_resource,
1243 CopyPatch {
1244 paths: Some(vec!["file1.txt".into()].into_patch()),
1245 options: Some(CopyOptionsPatch {
1246 chmod: Some(Some("755".into())),
1247 ..Default::default()
1248 }),
1249 ..Default::default()
1250 }
1251 );
1252 }
1253
1254 #[cfg(feature = "permissive")]
1255 #[test]
1256 fn deserialize_copy_from_str() {
1257 let json_data = "file1.txt destination/";
1258
1259 let copy_resource: ParsableStruct<CopyResourcePatch> =
1260 serde_yaml::from_str(json_data).unwrap();
1261 let copy_resource: CopyResource = copy_resource.into();
1262
1263 assert_eq_sorted!(
1264 copy_resource,
1265 CopyResource::Copy(Copy {
1266 paths: vec!["file1.txt".into()].into(),
1267 options: CopyOptions {
1268 target: Some("destination/".into()),
1269 ..Default::default()
1270 },
1271 ..Default::default()
1272 })
1273 );
1274 }
1275
1276 #[test]
1277 fn copy_content() {
1278 let json_data = r#"{
1279 "content": "echo coucou",
1280 "substitute": false,
1281 "target": "test.sh",
1282 "chown": {
1283 "user": "1001",
1284 "group": "1001"
1285 },
1286 "chmod": "555",
1287 "link": true
1288}"#;
1289
1290 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1291 let copy_resource: CopyResource = copy_resource.into();
1292
1293 assert_eq_sorted!(
1294 copy_resource,
1295 CopyResource::Content(CopyContent {
1296 content: "echo coucou".into(),
1297 substitute: Some(false),
1298 options: CopyOptions {
1299 target: Some("test.sh".into()),
1300 chown: Some(User {
1301 user: "1001".into(),
1302 group: Some("1001".into())
1303 }),
1304 chmod: Some("555".into()),
1305 link: Some(true),
1306 }
1307 })
1308 );
1309 }
1310
1311 #[test]
1312 fn add_git_repo() {
1313 let json_data = r#"{
1314 "repo": "https://github.com/example/repo.git",
1315 "target": "destination/",
1316 "chown": {
1317 "user": "root",
1318 "group": "root"
1319 },
1320 "chmod": "755",
1321 "link": true,
1322 "keepGitDir": true,
1323 "checksum": "sha256:abcdef123456"
1324 }"#;
1325
1326 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1327 let copy_resource: CopyResource = copy_resource.into();
1328
1329 assert_eq_sorted!(
1330 copy_resource,
1331 CopyResource::AddGitRepo(AddGitRepo {
1332 repo: "https://github.com/example/repo.git".into(),
1333 options: CopyOptions {
1334 target: Some("destination/".into()),
1335 chown: Some(User {
1336 user: "root".into(),
1337 group: Some("root".into())
1338 }),
1339 chmod: Some("755".into()),
1340 link: Some(true),
1341 },
1342 keep_git_dir: Some(true),
1343 exclude: vec![].into(),
1344 checksum: Some("sha256:abcdef123456".into()),
1345 })
1346 );
1347 }
1348
1349 #[test]
1350 fn add() {
1351 let json_data = r#"{
1352 "files": ["file1.txt", "file2.txt"],
1353 "target": "destination/",
1354 "checksum": "sha256:abcdef123456",
1355 "chown": {
1356 "user": "root",
1357 "group": "root"
1358 },
1359 "chmod": "755",
1360 "link": true,
1361 "unpack": false
1362 }"#;
1363
1364 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1365 let copy_resource: CopyResource = copy_resource.into();
1366
1367 assert_eq_sorted!(
1368 copy_resource,
1369 CopyResource::Add(Add {
1370 files: vec![
1371 Resource::File("file1.txt".into()),
1372 Resource::File("file2.txt".into())
1373 ]
1374 .into(),
1375 options: CopyOptions {
1376 target: Some("destination/".into()),
1377 chown: Some(User {
1378 user: "root".into(),
1379 group: Some("root".into())
1380 }),
1381 chmod: Some("755".into()),
1382 link: Some(true),
1383 },
1384 checksum: Some("sha256:abcdef123456".into()),
1385 unpack: Some(false),
1386 })
1387 );
1388 }
1389 }
1390
1391 mod builder {
1392 use super::*;
1393
1394 #[test]
1395 fn with_bind() {
1396 let json_data = r#"
1397fromImage:
1398 path: clux/muslrust:stable
1399workdir: /app
1400bind:
1401 - target: /app
1402run:
1403 - cargo build --release
1404 - mv target/x86_64-unknown-linux-musl/release/dofigen /app/
1405"#;
1406
1407 let builder: Stage = serde_yaml::from_str::<StagePatch>(json_data)
1408 .unwrap()
1409 .into();
1410
1411 assert_eq_sorted!(
1412 builder,
1413 Stage {
1414 from: FromContext::FromImage(ImageName {
1415 path: "clux/muslrust:stable".into(),
1416 ..Default::default()
1417 }),
1418 workdir: Some("/app".into()),
1419 run: Run {
1420 bind: vec![Bind {
1421 target: "/app".into(),
1422 ..Default::default()
1423 }],
1424 run: vec![
1425 "cargo build --release".into(),
1426 "mv target/x86_64-unknown-linux-musl/release/dofigen /app/".into()
1427 ],
1428 ..Default::default()
1429 },
1430 ..Default::default()
1431 }
1432 );
1433 }
1434 }
1435 }
1436}