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 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "builder"))))]
55 #[serde(skip_serializing_if = "HashMap::is_empty")]
56 pub builders: HashMap<String, Stage>,
58
59 #[patch(name = "StagePatch", attribute(serde(flatten)))]
61 #[serde(flatten)]
62 pub stage: Stage,
63
64 #[patch(name = "VecPatch<String>")]
67 #[serde(skip_serializing_if = "Vec::is_empty")]
68 pub entrypoint: Vec<String>,
69
70 #[patch(name = "VecPatch<String>")]
73 #[serde(skip_serializing_if = "Vec::is_empty")]
74 pub cmd: Vec<String>,
75
76 #[patch(name = "VecPatch<String>")]
79 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "volumes"))))]
80 #[serde(skip_serializing_if = "Vec::is_empty")]
81 pub volume: Vec<String>,
82
83 #[cfg_attr(
86 feature = "permissive",
87 patch(name = "VecDeepPatch<Port, ParsableStruct<PortPatch>>")
88 )]
89 #[cfg_attr(
90 not(feature = "permissive"),
91 patch(name = "VecDeepPatch<Port, PortPatch>")
92 )]
93 #[cfg_attr(
94 not(feature = "strict"),
95 patch(attribute(serde(alias = "port", alias = "ports")))
96 )]
97 #[serde(skip_serializing_if = "Vec::is_empty")]
98 pub expose: Vec<Port>,
99
100 #[patch(name = "Option<HealthcheckPatch>")]
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub healthcheck: Option<Healthcheck>,
105}
106
107#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
109#[patch(
110 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
111 attribute(serde(default)),
113)]
114#[cfg_attr(
115 feature = "json_schema",
116 patch(
117 attribute(derive(JsonSchema)),
118 attribute(schemars(title = "Stage", rename = "Stage"))
119 )
120)]
121pub struct Stage {
122 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
125 #[patch(name = "FromContextPatch", attribute(serde(flatten, default)))]
126 pub from: FromContext,
127
128 #[cfg_attr(not(feature = "strict"), patch(name = "NestedMap<String>"))]
131 #[cfg_attr(feature = "strict", patch(name = "HashMapPatch<String, String>"))]
132 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "labels"))))]
133 #[serde(skip_serializing_if = "HashMap::is_empty")]
134 pub label: HashMap<String, String>,
135
136 #[cfg_attr(
139 feature = "permissive",
140 patch(name = "Option<ParsableStruct<UserPatch>>")
141 )]
142 #[cfg_attr(not(feature = "permissive"), patch(name = "Option<UserPatch>"))]
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub user: Option<User>,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
149 pub workdir: Option<String>,
150
151 #[patch(name = "HashMapPatch<String, String>")]
154 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "args"))))]
155 #[serde(skip_serializing_if = "HashMap::is_empty")]
156 pub arg: HashMap<String, String>,
157
158 #[patch(name = "HashMapPatch<String, String>")]
161 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "envs"))))]
162 #[serde(skip_serializing_if = "HashMap::is_empty")]
163 pub env: HashMap<String, String>,
164
165 #[cfg_attr(
168 not(feature = "strict"),
169 patch(attribute(serde(
170 alias = "add",
171 alias = "adds",
172 alias = "artifact",
173 alias = "artifacts"
174 )))
175 )]
176 #[cfg_attr(
177 feature = "permissive",
178 patch(name = "VecDeepPatch<CopyResource, ParsableStruct<CopyResourcePatch>>")
179 )]
180 #[cfg_attr(
181 not(feature = "permissive"),
182 patch(name = "VecDeepPatch<CopyResource, CopyResourcePatch>")
183 )]
184 #[serde(skip_serializing_if = "Vec::is_empty")]
185 pub copy: Vec<CopyResource>,
186
187 #[patch(name = "Option<RunPatch>")]
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub root: Option<Run>,
191
192 #[patch(name = "RunPatch", attribute(serde(flatten)))]
195 #[serde(flatten)]
196 pub run: Run,
197}
198
199#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
201#[patch(
202 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
203 attribute(serde(default)),
205)]
206#[cfg_attr(
207 feature = "json_schema",
208 patch(
209 attribute(derive(JsonSchema)),
210 attribute(schemars(title = "Run", rename = "Run"))
211 )
212)]
213pub struct Run {
214 #[patch(name = "VecPatch<String>")]
216 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "script"))))]
217 #[serde(skip_serializing_if = "Vec::is_empty")]
218 pub run: Vec<String>,
219
220 #[patch(name = "VecPatch<String>")]
223 #[serde(skip_serializing_if = "Vec::is_empty")]
224 pub shell: Vec<String>,
225
226 #[cfg_attr(
229 feature = "permissive",
230 patch(name = "VecDeepPatch<Cache, ParsableStruct<CachePatch>>")
231 )]
232 #[cfg_attr(
233 not(feature = "permissive"),
234 patch(name = "VecDeepPatch<Cache, CachePatch>")
235 )]
236 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "caches"))))]
237 #[serde(skip_serializing_if = "Vec::is_empty")]
238 pub cache: Vec<Cache>,
239
240 #[cfg_attr(
244 feature = "permissive",
245 patch(name = "VecDeepPatch<Bind, ParsableStruct<BindPatch>>")
246 )]
247 #[cfg_attr(
248 not(feature = "permissive"),
249 patch(name = "VecDeepPatch<Bind, BindPatch>")
250 )]
251 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "binds"))))]
252 #[serde(skip_serializing_if = "Vec::is_empty")]
253 pub bind: Vec<Bind>,
254
255 #[cfg_attr(
257 feature = "permissive",
258 patch(name = "VecDeepPatch<TmpFs, ParsableStruct<TmpFsPatch>>")
259 )]
260 #[cfg_attr(
261 not(feature = "permissive"),
262 patch(name = "VecDeepPatch<TmpFs, TmpFsPatch>")
263 )]
264 #[serde(skip_serializing_if = "Vec::is_empty")]
265 pub tmpfs: Vec<TmpFs>,
266
267 #[patch(name = "VecDeepPatch<Secret, SecretPatch>")]
271 #[serde(skip_serializing_if = "Vec::is_empty")]
272 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "secrets"))))]
273 pub secret: Vec<Secret>,
274
275 #[patch(name = "VecDeepPatch<Ssh, SshPatch>")]
278 #[serde(skip_serializing_if = "Vec::is_empty")]
279 pub ssh: Vec<Ssh>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
284 pub network: Option<Network>,
285
286 #[serde(skip_serializing_if = "Option::is_none")]
289 pub security: Option<Security>,
290}
291
292#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
295#[patch(
296 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
297 attribute(serde(default))
298)]
299#[cfg_attr(
300 feature = "json_schema",
301 patch(
302 attribute(derive(JsonSchema)),
303 attribute(schemars(title = "Cache", rename = "Cache"))
304 )
305)]
306pub struct Cache {
307 #[serde(skip_serializing_if = "Option::is_none")]
310 pub id: Option<String>,
311
312 #[cfg_attr(
314 not(feature = "strict"),
315 patch(attribute(serde(alias = "dst", alias = "destination")))
316 )]
317 pub target: String,
318
319 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ro"))))]
321 #[serde(skip_serializing_if = "Option::is_none")]
322 pub readonly: Option<bool>,
323
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub sharing: Option<CacheSharing>,
327
328 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
330 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
331 pub from: FromContext,
332
333 #[serde(skip_serializing_if = "Option::is_none")]
335 pub source: Option<String>,
336
337 #[cfg_attr(
339 feature = "permissive",
340 patch(attribute(serde(
341 deserialize_with = "deserialize_from_optional_string_or_number",
342 default
343 )))
344 )]
345 #[serde(skip_serializing_if = "Option::is_none")]
346 #[cfg_attr(
347 feature = "json_schema",
348 patch(attribute(schemars(schema_with = "optional_string_or_number_schema")))
349 )]
350 pub chmod: Option<String>,
351
352 #[patch(name = "Option<UserPatch>")]
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub chown: Option<User>,
356}
357
358#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
361#[patch(
362 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
363 attribute(serde(default))
364)]
365#[cfg_attr(
366 feature = "json_schema",
367 patch(
368 attribute(derive(JsonSchema)),
369 attribute(schemars(title = "Bind", rename = "Bind"))
370 )
371)]
372pub struct Bind {
373 pub target: String,
375
376 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
378 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
379 pub from: FromContext,
380
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub source: Option<String>,
384
385 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "rw"))))]
387 #[serde(skip_serializing_if = "Option::is_none")]
388 pub readwrite: Option<bool>,
389}
390
391#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
394#[patch(
395 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
396 attribute(serde(default))
397)]
398#[cfg_attr(
399 feature = "json_schema",
400 patch(
401 attribute(derive(JsonSchema)),
402 attribute(schemars(title = "TmpFs", rename = "TmpFs"))
403 )
404)]
405pub struct TmpFs {
406 pub target: String,
408
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub size: Option<String>,
412}
413
414#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
418#[patch(
419 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
420 attribute(serde(default))
421)]
422#[cfg_attr(
423 feature = "json_schema",
424 patch(
425 attribute(derive(JsonSchema)),
426 attribute(schemars(title = "Secret", rename = "Secret"))
427 )
428)]
429pub struct Secret {
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub id: Option<String>,
433
434 #[serde(skip_serializing_if = "Option::is_none")]
436 pub target: Option<String>,
437
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub env: Option<String>,
441
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub required: Option<bool>,
445
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub mode: Option<String>,
449
450 #[serde(skip_serializing_if = "Option::is_none")]
452 pub uid: Option<u16>,
453
454 #[serde(skip_serializing_if = "Option::is_none")]
456 pub gid: Option<u16>,
457}
458
459#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
462#[patch(
463 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
464 attribute(serde(default))
465)]
466#[cfg_attr(
467 feature = "json_schema",
468 patch(
469 attribute(derive(JsonSchema)),
470 attribute(schemars(title = "Ssh", rename = "Ssh"))
471 )
472)]
473pub struct Ssh {
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub id: Option<String>,
477
478 #[serde(skip_serializing_if = "Option::is_none")]
480 pub target: Option<String>,
481
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub required: Option<bool>,
485
486 #[serde(skip_serializing_if = "Option::is_none")]
488 pub mode: Option<String>,
489
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub uid: Option<u16>,
493
494 #[serde(skip_serializing_if = "Option::is_none")]
496 pub gid: Option<u16>,
497}
498
499#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
502#[patch(
503 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
504 attribute(serde(deny_unknown_fields, default))
505)]
506#[cfg_attr(
507 feature = "json_schema",
508 patch(
509 attribute(derive(JsonSchema)),
510 attribute(schemars(title = "Healthcheck", rename = "Healthcheck"))
511 )
512)]
513pub struct Healthcheck {
514 pub cmd: String,
516
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub interval: Option<String>,
520
521 #[serde(skip_serializing_if = "Option::is_none")]
523 pub timeout: Option<String>,
524
525 #[serde(skip_serializing_if = "Option::is_none")]
527 pub start: Option<String>,
528
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub retries: Option<u16>,
532}
533
534#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch, Hash, Eq, PartialOrd)]
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 = "ImageName", rename = "ImageName"))
545 )
546)]
547pub struct ImageName {
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub host: Option<String>,
551
552 #[serde(skip_serializing_if = "Option::is_none")]
554 pub port: Option<u16>,
555
556 pub path: String,
558
559 #[serde(flatten, skip_serializing_if = "Option::is_none")]
561 #[patch(attribute(serde(flatten)))]
562 pub version: Option<ImageVersion>,
563
564 #[serde(skip_serializing_if = "Option::is_none")]
569 pub platform: Option<String>,
570}
571
572#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
575#[patch(
576 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
577 attribute(serde(deny_unknown_fields, default))
578)]
579#[cfg_attr(
580 feature = "json_schema",
581 patch(
582 attribute(derive(JsonSchema)),
583 attribute(schemars(title = "Copy", rename = "Copy"))
584 )
585)]
586pub struct Copy {
587 #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
590 #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
591 pub from: FromContext,
592
593 #[patch(name = "VecPatch<String>")]
595 #[cfg_attr(
596 not(feature = "strict"),
597 patch(attribute(serde(alias = "path", alias = "source")))
598 )]
599 #[serde(skip_serializing_if = "Vec::is_empty")]
600 pub paths: Vec<String>,
601
602 #[serde(flatten)]
604 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
605 pub options: CopyOptions,
606
607 #[patch(name = "VecPatch<String>")]
609 #[serde(skip_serializing_if = "Vec::is_empty")]
610 pub exclude: Vec<String>,
611
612 #[serde(skip_serializing_if = "Option::is_none")]
614 pub parents: Option<bool>,
615}
616
617#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
620#[patch(
621 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
622 attribute(serde(deny_unknown_fields, default))
623)]
624#[cfg_attr(
625 feature = "json_schema",
626 patch(
627 attribute(derive(JsonSchema)),
628 attribute(schemars(title = "CopyContent", rename = "CopyContent"))
629 )
630)]
631pub struct CopyContent {
632 pub content: String,
634
635 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "subst"))))]
637 #[serde(skip_serializing_if = "Option::is_none")]
638 pub substitute: Option<bool>,
639
640 #[serde(flatten)]
642 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
643 pub options: CopyOptions,
644}
645
646#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
649#[patch(
650 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
651 attribute(serde(deny_unknown_fields, default, rename_all = "camelCase"))
652)]
653#[cfg_attr(
654 feature = "json_schema",
655 patch(
656 attribute(derive(JsonSchema)),
657 attribute(schemars(title = "AddGitRepo", rename = "AddGitRepo"))
658 )
659)]
660pub struct AddGitRepo {
661 pub repo: String,
663
664 #[serde(flatten)]
666 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
667 pub options: CopyOptions,
668
669 #[patch(name = "VecPatch<String>")]
671 #[serde(skip_serializing_if = "Vec::is_empty")]
672 pub exclude: Vec<String>,
673
674 #[serde(skip_serializing_if = "Option::is_none")]
677 pub keep_git_dir: Option<bool>,
678
679 #[serde(skip_serializing_if = "Option::is_none")]
682 pub checksum: Option<String>,
683}
684
685#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
687#[patch(
688 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
689 attribute(serde(deny_unknown_fields, default))
690)]
691#[cfg_attr(
692 feature = "json_schema",
693 patch(
694 attribute(derive(JsonSchema)),
695 attribute(schemars(title = "Add", rename = "Add"))
696 )
697)]
698pub struct Add {
699 #[patch(name = "VecPatch<Resource>")]
701 #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "file"))))]
702 #[serde(skip_serializing_if = "Vec::is_empty")]
703 pub files: Vec<Resource>,
704
705 #[serde(flatten)]
707 #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
708 pub options: CopyOptions,
709
710 #[serde(skip_serializing_if = "Option::is_none")]
713 pub checksum: Option<String>,
714
715 #[serde(skip_serializing_if = "Option::is_none")]
719 pub unpack: Option<bool>,
720}
721
722#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
724#[patch(
725 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
726 attribute(serde(deny_unknown_fields, default))
727)]
728#[cfg_attr(
729 feature = "json_schema",
730 patch(
731 attribute(derive(JsonSchema)),
732 attribute(schemars(title = "CopyOptions", rename = "CopyOptions"))
733 )
734)]
735pub struct CopyOptions {
736 #[cfg_attr(
738 not(feature = "strict"),
739 patch(attribute(serde(alias = "destination")))
740 )]
741 #[serde(skip_serializing_if = "Option::is_none")]
742 pub target: Option<String>,
743
744 #[patch(name = "Option<UserPatch>")]
747 #[serde(skip_serializing_if = "Option::is_none")]
748 pub chown: Option<User>,
749
750 #[cfg_attr(
753 feature = "permissive",
754 patch(attribute(serde(
755 deserialize_with = "deserialize_from_optional_string_or_number",
756 default
757 )))
758 )]
759 #[serde(skip_serializing_if = "Option::is_none")]
760 #[cfg_attr(
761 feature = "json_schema",
762 patch(attribute(schemars(schema_with = "optional_string_or_number_schema")))
763 )]
764 pub chmod: Option<String>,
765
766 #[serde(skip_serializing_if = "Option::is_none")]
769 pub link: Option<bool>,
770}
771
772#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
774#[patch(
775 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
776 attribute(serde(deny_unknown_fields, default))
777)]
778#[cfg_attr(
779 feature = "json_schema",
780 patch(
781 attribute(derive(JsonSchema)),
782 attribute(schemars(title = "User", rename = "User"))
783 )
784)]
785pub struct User {
786 pub user: String,
789
790 #[serde(skip_serializing_if = "Option::is_none")]
793 pub group: Option<String>,
794}
795
796#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
798#[patch(
799 attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
800 attribute(serde(deny_unknown_fields, default))
801)]
802#[cfg_attr(
803 feature = "json_schema",
804 patch(
805 attribute(derive(JsonSchema)),
806 attribute(schemars(title = "Port", rename = "Port"))
807 )
808)]
809pub struct Port {
810 pub port: u16,
812
813 #[serde(skip_serializing_if = "Option::is_none")]
815 pub protocol: Option<PortProtocol>,
816}
817
818#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
822#[serde(rename_all = "camelCase")]
823#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
824pub enum ImageVersion {
825 Tag(String),
826 Digest(String),
827}
828
829#[derive(Serialize, Debug, Clone, PartialEq)]
831#[serde(rename_all = "camelCase")]
832pub enum FromContext {
833 FromImage(ImageName),
834 FromBuilder(String),
835 FromContext(Option<String>),
836}
837
838#[derive(Serialize, Debug, Clone, PartialEq)]
839#[serde(untagged)]
840pub enum CopyResource {
841 Copy(Copy),
842 Content(CopyContent),
843 AddGitRepo(AddGitRepo),
844 Add(Add),
845}
846
847#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
849#[serde(rename_all = "camelCase")]
850#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
851pub enum CacheSharing {
852 Shared,
853 Private,
854 Locked,
855}
856
857#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
859#[serde(rename_all = "camelCase")]
860#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
861pub enum PortProtocol {
862 Tcp,
863 Udp,
864}
865
866#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
868#[serde(untagged)]
869#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
870pub enum Resource {
871 Url(Url),
872 File(PathBuf),
873}
874
875#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
877#[serde(rename_all = "camelCase")]
878#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
879pub enum Network {
880 Default,
882 None,
884 Host,
886}
887
888#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
890#[serde(rename_all = "camelCase")]
891#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
892pub enum Security {
893 Sandbox,
894 Insecure,
895}
896
897#[derive(Debug, Clone, PartialEq, Deserialize)]
900#[serde(rename_all = "camelCase")]
901#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
902pub enum FromContextPatch {
903 #[cfg(not(feature = "permissive"))]
904 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
905 FromImage(ImageNamePatch),
906
907 #[cfg(feature = "permissive")]
908 #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
909 FromImage(ParsableStruct<ImageNamePatch>),
910
911 #[cfg_attr(not(feature = "strict"), serde(alias = "builder"))]
912 FromBuilder(String),
913
914 #[cfg_attr(not(feature = "strict"), serde(alias = "from"))]
915 FromContext(Option<String>),
916}
917
918#[derive(Debug, Clone, PartialEq, Deserialize)]
919#[serde(untagged)]
920#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
921pub enum CopyResourcePatch {
922 Copy(CopyPatch),
923 Content(CopyContentPatch),
924 AddGitRepo(AddGitRepoPatch),
925 Add(AddPatch),
926 Unknown(UnknownPatch),
927}
928
929#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
930#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
931pub struct UnknownPatch {
932 #[serde(flatten)]
933 pub options: Option<CopyOptionsPatch>,
934 pub exclude: Option<VecPatch<String>>,
935}
936
937#[cfg(test)]
940mod test {
941 use super::*;
942 use pretty_assertions_sorted::assert_eq_sorted;
943
944 mod deserialize {
945 use super::*;
946
947 mod dofigen {
948 use super::*;
949
950 #[test]
951 fn empty() {
952 let data = r#""#;
953
954 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
955 let dofigen: Dofigen = dofigen.into();
956
957 assert_eq_sorted!(dofigen, Dofigen::default());
958 }
959
960 #[test]
961 fn from() {
962 let data = r#"
963 fromImage:
964 path: ubuntu
965 "#;
966
967 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
968 let dofigen: Dofigen = dofigen.into();
969
970 assert_eq_sorted!(
971 dofigen,
972 Dofigen {
973 stage: Stage {
974 from: FromContext::FromImage(ImageName {
975 path: "ubuntu".into(),
976 ..Default::default()
977 }),
978 ..Default::default()
979 },
980 ..Default::default()
981 }
982 );
983 }
984
985 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
986 #[test]
987 fn duplicate_from() {
988 let data = r#"
989 from:
990 path: ubuntu
991 from:
992 path: alpine
993 "#;
994
995 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
996
997 println!("{:?}", dofigen);
998
999 assert!(dofigen.is_err());
1000 }
1001
1002 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
1003 #[test]
1004 fn duplicate_from_and_alias() {
1005 let data = r#"
1006 from:
1007 path: ubuntu
1008 image:
1009 path: alpine
1010 "#;
1011
1012 let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
1013
1014 println!("{:?}", dofigen);
1015
1016 assert!(dofigen.is_err());
1017 }
1018
1019 #[test]
1020 fn global_arg() {
1021 let data = r#"
1022 globalArg:
1023 IMAGE: ubuntu
1024 fromContext: ${IMAGE}
1025 "#;
1026
1027 let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
1028 let dofigen: Dofigen = dofigen.into();
1029
1030 assert_eq_sorted!(
1031 dofigen,
1032 Dofigen {
1033 global_arg: HashMap::from([("IMAGE".into(), "ubuntu".into())]),
1034 stage: Stage {
1035 from: FromContext::FromContext(Some("${IMAGE}".into())).into(),
1036 ..Default::default()
1037 },
1038 ..Default::default()
1039 }
1040 );
1041 }
1042 }
1043
1044 mod stage {
1045 use super::*;
1046
1047 #[test]
1048 fn empty() {
1049 let data = r#""#;
1050
1051 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
1052 let stage: Stage = stage.into();
1053
1054 assert_eq_sorted!(stage, Stage::default());
1055 }
1056
1057 #[test]
1058 fn from() {
1059 let data = r#"
1060 fromImage:
1061 path: ubuntu
1062 "#;
1063
1064 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
1065 let stage: Stage = stage.into();
1066
1067 assert_eq_sorted!(
1068 stage,
1069 Stage {
1070 from: FromContext::FromImage(ImageName {
1071 path: "ubuntu".into(),
1072 ..Default::default()
1073 })
1074 .into(),
1075 ..Default::default()
1076 }
1077 );
1078 }
1079
1080 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
1081 #[test]
1082 fn duplicate_from() {
1083 let data = r#"
1084 fromImage:
1085 path: ubuntu
1086 fromImage:
1087 path: alpine
1088 "#;
1089
1090 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
1091
1092 assert!(stage.is_err());
1093 }
1094
1095 #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
1096 #[test]
1097 fn duplicate_from_and_alias() {
1098 let data = r#"
1099 fromImage:
1100 path: ubuntu
1101 image:
1102 path: alpine
1103 "#;
1104
1105 let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
1106
1107 assert!(stage.is_err());
1108 }
1109
1110 #[test]
1111 fn label() {
1112 #[cfg(not(feature = "strict"))]
1113 let data = r#"
1114 label:
1115 io.dofigen:
1116 test: test
1117 "#;
1118 #[cfg(feature = "strict")]
1119 let data = r#"
1120 label:
1121 io.dofigen.test: test
1122 "#;
1123
1124 let stage: StagePatch = serde_yaml::from_str(data).unwrap();
1125 let stage: Stage = stage.into();
1126
1127 assert_eq_sorted!(
1128 stage,
1129 Stage {
1130 label: HashMap::from([("io.dofigen.test".into(), "test".into())]),
1131 ..Default::default()
1132 }
1133 );
1134 }
1135 }
1136
1137 mod user {
1138 use super::*;
1139
1140 #[test]
1141 fn name_and_group() {
1142 let json_data = r#"{
1143 "user": "test",
1144 "group": "test"
1145}"#;
1146
1147 let user: UserPatch = serde_yaml::from_str(json_data).unwrap();
1148 let user: User = user.into();
1149
1150 assert_eq_sorted!(
1151 user,
1152 User {
1153 user: "test".into(),
1154 group: Some("test".into())
1155 }
1156 );
1157 }
1158 }
1159
1160 mod copy_resource {
1161
1162 use super::*;
1163
1164 #[test]
1165 fn copy() {
1166 let json_data = r#"{
1167 "paths": ["file1.txt", "file2.txt"],
1168 "target": "destination/",
1169 "chown": {
1170 "user": "root",
1171 "group": "root"
1172 },
1173 "chmod": "755",
1174 "link": true,
1175 "fromImage": {"path": "my-image"}
1176}"#;
1177
1178 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1179 let copy_resource: CopyResource = copy_resource.into();
1180
1181 assert_eq_sorted!(
1182 copy_resource,
1183 CopyResource::Copy(Copy {
1184 paths: vec!["file1.txt".into(), "file2.txt".into()].into(),
1185 options: CopyOptions {
1186 target: Some("destination/".into()),
1187 chown: Some(User {
1188 user: "root".into(),
1189 group: Some("root".into())
1190 }),
1191 chmod: Some("755".into()),
1192 link: Some(true),
1193 },
1194 from: FromContext::FromImage(ImageName {
1195 path: "my-image".into(),
1196 ..Default::default()
1197 }),
1198 exclude: vec![].into(),
1199 parents: None,
1200 })
1201 );
1202 }
1203
1204 #[test]
1205 fn copy_simple() {
1206 let json_data = r#"{
1207 "paths": ["file1.txt"]
1208}"#;
1209
1210 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1211
1212 assert_eq_sorted!(
1213 copy_resource,
1214 CopyResourcePatch::Copy(CopyPatch {
1215 paths: Some(vec!["file1.txt".into()].into_patch()),
1216 options: Some(CopyOptionsPatch::default()),
1217 ..Default::default()
1218 })
1219 );
1220
1221 let copy_resource: CopyResource = copy_resource.into();
1222
1223 assert_eq_sorted!(
1224 copy_resource,
1225 CopyResource::Copy(Copy {
1226 paths: vec!["file1.txt".into()].into(),
1227 options: CopyOptions::default(),
1228 ..Default::default()
1229 })
1230 );
1231 }
1232
1233 #[cfg(feature = "permissive")]
1234 #[test]
1235 fn copy_chmod_int() {
1236 let json_data = r#"{
1237 "paths": ["file1.txt"],
1238 "chmod": 755
1239}"#;
1240
1241 let copy_resource: CopyPatch = serde_yaml::from_str(json_data).unwrap();
1242
1243 assert_eq_sorted!(
1244 copy_resource,
1245 CopyPatch {
1246 paths: Some(vec!["file1.txt".into()].into_patch()),
1247 options: Some(CopyOptionsPatch {
1248 chmod: Some(Some("755".into())),
1249 ..Default::default()
1250 }),
1251 ..Default::default()
1252 }
1253 );
1254 }
1255
1256 #[cfg(feature = "permissive")]
1257 #[test]
1258 fn deserialize_copy_from_str() {
1259 let json_data = "file1.txt destination/";
1260
1261 let copy_resource: ParsableStruct<CopyResourcePatch> =
1262 serde_yaml::from_str(json_data).unwrap();
1263 let copy_resource: CopyResource = copy_resource.into();
1264
1265 assert_eq_sorted!(
1266 copy_resource,
1267 CopyResource::Copy(Copy {
1268 paths: vec!["file1.txt".into()].into(),
1269 options: CopyOptions {
1270 target: Some("destination/".into()),
1271 ..Default::default()
1272 },
1273 ..Default::default()
1274 })
1275 );
1276 }
1277
1278 #[test]
1279 fn copy_content() {
1280 let json_data = r#"{
1281 "content": "echo coucou",
1282 "substitute": false,
1283 "target": "test.sh",
1284 "chown": {
1285 "user": "1001",
1286 "group": "1001"
1287 },
1288 "chmod": "555",
1289 "link": true
1290}"#;
1291
1292 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1293 let copy_resource: CopyResource = copy_resource.into();
1294
1295 assert_eq_sorted!(
1296 copy_resource,
1297 CopyResource::Content(CopyContent {
1298 content: "echo coucou".into(),
1299 substitute: Some(false),
1300 options: CopyOptions {
1301 target: Some("test.sh".into()),
1302 chown: Some(User {
1303 user: "1001".into(),
1304 group: Some("1001".into())
1305 }),
1306 chmod: Some("555".into()),
1307 link: Some(true),
1308 }
1309 })
1310 );
1311 }
1312
1313 #[test]
1314 fn add_git_repo() {
1315 let json_data = r#"{
1316 "repo": "https://github.com/example/repo.git",
1317 "target": "destination/",
1318 "chown": {
1319 "user": "root",
1320 "group": "root"
1321 },
1322 "chmod": "755",
1323 "link": true,
1324 "keepGitDir": true,
1325 "checksum": "sha256:abcdef123456"
1326 }"#;
1327
1328 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1329 let copy_resource: CopyResource = copy_resource.into();
1330
1331 assert_eq_sorted!(
1332 copy_resource,
1333 CopyResource::AddGitRepo(AddGitRepo {
1334 repo: "https://github.com/example/repo.git".into(),
1335 options: CopyOptions {
1336 target: Some("destination/".into()),
1337 chown: Some(User {
1338 user: "root".into(),
1339 group: Some("root".into())
1340 }),
1341 chmod: Some("755".into()),
1342 link: Some(true),
1343 },
1344 keep_git_dir: Some(true),
1345 exclude: vec![].into(),
1346 checksum: Some("sha256:abcdef123456".into()),
1347 })
1348 );
1349 }
1350
1351 #[test]
1352 fn add() {
1353 let json_data = r#"{
1354 "files": ["file1.txt", "file2.txt"],
1355 "target": "destination/",
1356 "checksum": "sha256:abcdef123456",
1357 "chown": {
1358 "user": "root",
1359 "group": "root"
1360 },
1361 "chmod": "755",
1362 "link": true,
1363 "unpack": false
1364 }"#;
1365
1366 let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1367 let copy_resource: CopyResource = copy_resource.into();
1368
1369 assert_eq_sorted!(
1370 copy_resource,
1371 CopyResource::Add(Add {
1372 files: vec![
1373 Resource::File("file1.txt".into()),
1374 Resource::File("file2.txt".into())
1375 ]
1376 .into(),
1377 options: CopyOptions {
1378 target: Some("destination/".into()),
1379 chown: Some(User {
1380 user: "root".into(),
1381 group: Some("root".into())
1382 }),
1383 chmod: Some("755".into()),
1384 link: Some(true),
1385 },
1386 checksum: Some("sha256:abcdef123456".into()),
1387 unpack: Some(false),
1388 })
1389 );
1390 }
1391 }
1392
1393 mod builder {
1394 use super::*;
1395
1396 #[test]
1397 fn with_bind() {
1398 let json_data = r#"
1399fromImage:
1400 path: clux/muslrust:stable
1401workdir: /app
1402bind:
1403 - target: /app
1404run:
1405 - cargo build --release
1406 - mv target/x86_64-unknown-linux-musl/release/dofigen /app/
1407"#;
1408
1409 let builder: Stage = serde_yaml::from_str::<StagePatch>(json_data)
1410 .unwrap()
1411 .into();
1412
1413 assert_eq_sorted!(
1414 builder,
1415 Stage {
1416 from: FromContext::FromImage(ImageName {
1417 path: "clux/muslrust:stable".into(),
1418 ..Default::default()
1419 }),
1420 workdir: Some("/app".into()),
1421 run: Run {
1422 bind: vec![Bind {
1423 target: "/app".into(),
1424 ..Default::default()
1425 }],
1426 run: vec![
1427 "cargo build --release".into(),
1428 "mv target/x86_64-unknown-linux-musl/release/dofigen /app/".into()
1429 ],
1430 ..Default::default()
1431 },
1432 ..Default::default()
1433 }
1434 );
1435 }
1436 }
1437 }
1438}