dofigen_lib/
dofigen_struct.rs

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/** Represents the Dockerfile main stage */
10#[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(deny_unknown_fields)),
15    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    /// The context of the Docker build
26    /// This is used to generate a .dockerignore file
27    #[patch(name = "VecPatch<String>")]
28    #[serde(skip_serializing_if = "Vec::is_empty")]
29    pub context: Vec<String>,
30
31    /// The elements to ignore from the build context
32    /// This is used to generate a .dockerignore file
33    #[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    /// The builder stages of the Dockerfile
39    #[patch(name = "HashMapDeepPatch<String, StagePatch>")]
40    #[serde(skip_serializing_if = "HashMap::is_empty")]
41    pub builders: HashMap<String, Stage>,
42
43    /// The runtime stage of the Dockerfile
44    #[patch(name = "StagePatch", attribute(serde(flatten)))]
45    #[serde(flatten)]
46    pub stage: Stage,
47
48    /// The entrypoint of the Dockerfile
49    /// See https://docs.docker.com/reference/dockerfile/#entrypoint
50    #[patch(name = "VecPatch<String>")]
51    #[serde(skip_serializing_if = "Vec::is_empty")]
52    pub entrypoint: Vec<String>,
53
54    /// The default command of the Dockerfile
55    /// See https://docs.docker.com/reference/dockerfile/#cmd
56    #[patch(name = "VecPatch<String>")]
57    #[serde(skip_serializing_if = "Vec::is_empty")]
58    pub cmd: Vec<String>,
59
60    /// Create volume mounts
61    /// See https://docs.docker.com/reference/dockerfile/#volume
62    #[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    /// The ports exposed by the Dockerfile
68    /// See https://docs.docker.com/reference/dockerfile/#expose
69    #[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    /// The healthcheck of the Dockerfile
85    /// See https://docs.docker.com/reference/dockerfile/#healthcheck
86    #[patch(name = "Option<HealthcheckPatch>")]
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub healthcheck: Option<Healthcheck>,
89}
90
91/// Represents a Dockerfile stage
92#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
93#[patch(
94    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
95    // attribute(serde(deny_unknown_fields)),
96    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    /// The base of the stage
107    /// See https://docs.docker.com/reference/dockerfile/#from
108    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
109    #[patch(name = "FromContextPatch", attribute(serde(flatten, default)))]
110    pub from: FromContext,
111
112    /// Add metadata to an image
113    /// See https://docs.docker.com/reference/dockerfile/#label
114    #[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    /// The user and group of the stage
121    /// See https://docs.docker.com/reference/dockerfile/#user
122    #[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    /// The working directory of the stage
131    /// See https://docs.docker.com/reference/dockerfile/#workdir
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub workdir: Option<String>,
134
135    /// The build args that can be used in the stage
136    /// See https://docs.docker.com/reference/dockerfile/#arg
137    #[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    /// The environment variables of the stage
143    /// See https://docs.docker.com/reference/dockerfile/#env
144    #[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    /// The copy instructions of the stage
150    /// See https://docs.docker.com/reference/dockerfile/#copy and https://docs.docker.com/reference/dockerfile/#add
151    #[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    /// The run instructions of the stage as root user
172    #[patch(name = "Option<RunPatch>")]
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub root: Option<Run>,
175
176    /// The run instructions of the stage
177    /// See https://docs.docker.com/reference/dockerfile/#run
178    #[patch(name = "RunPatch", attribute(serde(flatten)))]
179    #[serde(flatten)]
180    pub run: Run,
181}
182
183/// Represents a run command
184#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
185#[patch(
186    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
187    // attribute(serde(deny_unknown_fields)),
188    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    /// The commands to run
199    #[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    /// The cache definitions during the run
205    /// See https://docs.docker.com/reference/dockerfile/#run---mounttypecache
206    #[cfg_attr(
207        feature = "permissive",
208        patch(name = "VecDeepPatch<Cache, ParsableStruct<CachePatch>>")
209    )]
210    #[cfg_attr(
211        not(feature = "permissive"),
212        patch(name = "VecDeepPatch<Cache, CachePatch>")
213    )]
214    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "caches"))))]
215    #[serde(skip_serializing_if = "Vec::is_empty")]
216    pub cache: Vec<Cache>,
217
218    /// The file system bindings during the run
219    /// This is used to mount a file or directory from the host into the container only during the run and it's faster than a copy
220    /// See https://docs.docker.com/reference/dockerfile/#run---mounttypebind
221    #[cfg_attr(
222        feature = "permissive",
223        patch(name = "VecDeepPatch<Bind, ParsableStruct<BindPatch>>")
224    )]
225    #[cfg_attr(
226        not(feature = "permissive"),
227        patch(name = "VecDeepPatch<Bind, BindPatch>")
228    )]
229    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "binds"))))]
230    #[serde(skip_serializing_if = "Vec::is_empty")]
231    pub bind: Vec<Bind>,
232}
233
234/// Represents a cache definition during a run
235/// See https://docs.docker.com/reference/dockerfile/#run---mounttypecache
236#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
237#[patch(
238    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
239    attribute(serde(default))
240)]
241#[cfg_attr(
242    feature = "json_schema",
243    patch(
244        attribute(derive(JsonSchema)),
245        attribute(schemars(title = "Cache", rename = "Cache"))
246    )
247)]
248pub struct Cache {
249    /// The id of the cache
250    /// This is used to share the cache between different stages
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub id: Option<String>,
253
254    /// The target path of the cache
255    #[cfg_attr(
256        not(feature = "strict"),
257        patch(attribute(serde(alias = "dst", alias = "destination")))
258    )]
259    pub target: String,
260
261    /// Defines if the cache is readonly
262    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ro"))))]
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub readonly: Option<bool>,
265
266    /// The sharing strategy of the cache
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub sharing: Option<CacheSharing>,
269
270    /// Build stage, context, or image name to use as a base of the cache mount. Defaults to empty directory.
271    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
272    #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
273    pub from: FromContext,
274
275    /// Subpath in the from to mount. Defaults to the root of the from
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub source: Option<String>,
278
279    /// The permissions of the cache
280    #[cfg_attr(
281        feature = "permissive",
282        patch(attribute(serde(
283            deserialize_with = "deserialize_from_optional_string_or_number",
284            default
285        )))
286    )]
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub chmod: Option<String>,
289
290    /// The user and group that own the cache
291    #[patch(name = "Option<UserPatch>")]
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub chown: Option<User>,
294}
295
296/// Represents file system binding during a run
297/// See https://docs.docker.com/reference/dockerfile/#run---mounttypebind
298#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
299#[patch(
300    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
301    attribute(serde(default))
302)]
303#[cfg_attr(
304    feature = "json_schema",
305    patch(
306        attribute(derive(JsonSchema)),
307        attribute(schemars(title = "Bind", rename = "Bind"))
308    )
309)]
310pub struct Bind {
311    /// The target path of the bind
312    pub target: String,
313
314    /// The base of the bind
315    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
316    #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
317    pub from: FromContext,
318
319    /// Source path in the from. Defaults to the root of the from
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub source: Option<String>,
322
323    /// Defines if the bind is read and write
324    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "rw"))))]
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub readwrite: Option<bool>,
327}
328
329/// Represents the Dockerfile healthcheck instruction
330/// See https://docs.docker.com/reference/dockerfile/#healthcheck
331#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
332#[patch(
333    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
334    attribute(serde(deny_unknown_fields, default))
335)]
336#[cfg_attr(
337    feature = "json_schema",
338    patch(
339        attribute(derive(JsonSchema)),
340        attribute(schemars(title = "Healthcheck", rename = "Healthcheck"))
341    )
342)]
343pub struct Healthcheck {
344    /// The test to run
345    pub cmd: String,
346
347    /// The interval between two tests
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub interval: Option<String>,
350
351    /// The timeout of the test
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub timeout: Option<String>,
354
355    /// The start period of the test
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub start: Option<String>,
358
359    /// The number of retries
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub retries: Option<u16>,
362}
363
364/// Represents a Docker image name
365#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch, Hash, Eq, PartialOrd)]
366#[patch(
367    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
368    attribute(serde(deny_unknown_fields, default))
369)]
370#[cfg_attr(
371    feature = "json_schema",
372    patch(
373        attribute(derive(JsonSchema)),
374        attribute(schemars(title = "ImageName", rename = "ImageName"))
375    )
376)]
377pub struct ImageName {
378    /// The host of the image registry
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub host: Option<String>,
381
382    /// The port of the image registry
383    #[serde(skip_serializing_if = "Option::is_none")]
384    pub port: Option<u16>,
385
386    /// The path of the image repository
387    pub path: String,
388
389    /// The version of the image
390    #[serde(flatten, skip_serializing_if = "Option::is_none")]
391    #[patch(attribute(serde(flatten)))]
392    pub version: Option<ImageVersion>,
393}
394
395/// Represents the COPY instruction in a Dockerfile.
396/// See https://docs.docker.com/reference/dockerfile/#copy
397#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
398#[patch(
399    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
400    attribute(serde(deny_unknown_fields, default))
401)]
402#[cfg_attr(
403    feature = "json_schema",
404    patch(
405        attribute(derive(JsonSchema)),
406        attribute(schemars(title = "Copy", rename = "Copy"))
407    )
408)]
409pub struct Copy {
410    /// The origin of the copy
411    /// See https://docs.docker.com/reference/dockerfile/#copy---from
412    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
413    #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
414    pub from: FromContext,
415
416    /// The paths to copy
417    #[patch(name = "VecPatch<String>")]
418    #[cfg_attr(
419        not(feature = "strict"),
420        patch(attribute(serde(alias = "path", alias = "source")))
421    )]
422    #[serde(skip_serializing_if = "Vec::is_empty")]
423    pub paths: Vec<String>,
424
425    /// The options of the copy
426    #[serde(flatten)]
427    #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
428    pub options: CopyOptions,
429
430    /// See https://docs.docker.com/reference/dockerfile/#copy---exclude
431    #[patch(name = "VecPatch<String>")]
432    #[serde(skip_serializing_if = "Vec::is_empty")]
433    pub exclude: Vec<String>,
434
435    /// See https://docs.docker.com/reference/dockerfile/#copy---parents
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub parents: Option<bool>,
438}
439
440/// Represents the COPY instruction in a Dockerfile from file content.
441/// See https://docs.docker.com/reference/dockerfile/#example-creating-inline-files
442#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
443#[patch(
444    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
445    attribute(serde(deny_unknown_fields, default))
446)]
447#[cfg_attr(
448    feature = "json_schema",
449    patch(
450        attribute(derive(JsonSchema)),
451        attribute(schemars(title = "CopyContent", rename = "CopyContent"))
452    )
453)]
454pub struct CopyContent {
455    /// Content of the file to copy
456    pub content: String,
457
458    /// If true, replace variables in the content at build time. Default is true.
459    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "subst"))))]
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub substitute: Option<bool>,
462
463    /// The options of the copy
464    #[serde(flatten)]
465    #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
466    pub options: CopyOptions,
467}
468
469/// Represents the ADD instruction in a Dockerfile specific for Git repo.
470/// See https://docs.docker.com/reference/dockerfile/#adding-private-git-repositories
471#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
472#[patch(
473    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
474    attribute(serde(deny_unknown_fields, default, rename_all = "camelCase"))
475)]
476#[cfg_attr(
477    feature = "json_schema",
478    patch(
479        attribute(derive(JsonSchema)),
480        attribute(schemars(title = "AddGitRepo", rename = "AddGitRepo"))
481    )
482)]
483pub struct AddGitRepo {
484    /// The URL of the Git repository
485    pub repo: String,
486
487    /// The options of the copy
488    #[serde(flatten)]
489    #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
490    pub options: CopyOptions,
491
492    /// See https://docs.docker.com/reference/dockerfile/#copy---exclude
493    #[patch(name = "VecPatch<String>")]
494    #[serde(skip_serializing_if = "Vec::is_empty")]
495    pub exclude: Vec<String>,
496
497    /// Keep the git directory
498    /// See https://docs.docker.com/reference/dockerfile/#add---keep-git-dir
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub keep_git_dir: Option<bool>,
501}
502
503/// Represents the ADD instruction in a Dockerfile file from URLs or uncompress an archive.
504#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
505#[patch(
506    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
507    attribute(serde(deny_unknown_fields, default))
508)]
509#[cfg_attr(
510    feature = "json_schema",
511    patch(
512        attribute(derive(JsonSchema)),
513        attribute(schemars(title = "Add", rename = "Add"))
514    )
515)]
516pub struct Add {
517    /// The files to add
518    #[patch(name = "VecPatch<Resource>")]
519    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "file"))))]
520    #[serde(skip_serializing_if = "Vec::is_empty")]
521    pub files: Vec<Resource>,
522
523    /// The options of the copy
524    #[serde(flatten)]
525    #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
526    pub options: CopyOptions,
527
528    /// The checksum of the files
529    /// See https://docs.docker.com/reference/dockerfile/#add---checksum
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub checksum: Option<String>,
532}
533
534/// Represents the options of a COPY/ADD instructions
535#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
536#[patch(
537    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
538    attribute(serde(deny_unknown_fields, default))
539)]
540#[cfg_attr(
541    feature = "json_schema",
542    patch(
543        attribute(derive(JsonSchema)),
544        attribute(schemars(title = "CopyOptions", rename = "CopyOptions"))
545    )
546)]
547pub struct CopyOptions {
548    /// The target path of the copied files
549    #[cfg_attr(
550        not(feature = "strict"),
551        patch(attribute(serde(alias = "destination")))
552    )]
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub target: Option<String>,
555
556    /// The user and group that own the copied files
557    /// See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod
558    #[patch(name = "Option<UserPatch>")]
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub chown: Option<User>,
561
562    /// The permissions of the copied files
563    /// See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod
564    #[cfg_attr(
565        feature = "permissive",
566        patch(attribute(serde(
567            deserialize_with = "deserialize_from_optional_string_or_number",
568            default
569        )))
570    )]
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub chmod: Option<String>,
573
574    /// Use of the link flag
575    /// See https://docs.docker.com/reference/dockerfile/#copy---link
576    #[serde(skip_serializing_if = "Option::is_none")]
577    pub link: Option<bool>,
578}
579
580/// Represents user and group definition
581#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
582#[patch(
583    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
584    attribute(serde(deny_unknown_fields, default))
585)]
586#[cfg_attr(
587    feature = "json_schema",
588    patch(
589        attribute(derive(JsonSchema)),
590        attribute(schemars(title = "User", rename = "User"))
591    )
592)]
593pub struct User {
594    /// The user name or ID
595    /// The ID is preferred
596    pub user: String,
597
598    /// The group name or ID
599    /// The ID is preferred
600    #[serde(skip_serializing_if = "Option::is_none")]
601    pub group: Option<String>,
602}
603
604/// Represents a port definition
605#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
606#[patch(
607    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
608    attribute(serde(deny_unknown_fields, default))
609)]
610#[cfg_attr(
611    feature = "json_schema",
612    patch(
613        attribute(derive(JsonSchema)),
614        attribute(schemars(title = "Port", rename = "Port"))
615    )
616)]
617pub struct Port {
618    /// The port number
619    pub port: u16,
620
621    /// The protocol of the port
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub protocol: Option<PortProtocol>,
624}
625
626///////////////// Enums //////////////////
627
628/// Represents a Docker image version
629#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
630#[serde(rename_all = "camelCase")]
631#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
632pub enum ImageVersion {
633    Tag(String),
634    Digest(String),
635}
636
637/// Represents a copy origin
638#[derive(Serialize, Debug, Clone, PartialEq)]
639#[serde(rename_all = "camelCase")]
640pub enum FromContext {
641    FromImage(ImageName),
642    FromBuilder(String),
643    FromContext(Option<String>),
644}
645
646#[derive(Serialize, Debug, Clone, PartialEq)]
647#[serde(untagged)]
648pub enum CopyResource {
649    Copy(Copy),
650    Content(CopyContent),
651    AddGitRepo(AddGitRepo),
652    Add(Add),
653}
654
655/// Represents a cache sharing strategy
656#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
657#[serde(rename_all = "camelCase")]
658#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
659pub enum CacheSharing {
660    Shared,
661    Private,
662    Locked,
663}
664
665/// Represents a port protocol
666#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
667#[serde(rename_all = "camelCase")]
668#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
669pub enum PortProtocol {
670    Tcp,
671    Udp,
672}
673
674/// Represents a resource
675#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
676#[serde(untagged)]
677#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
678pub enum Resource {
679    Url(Url),
680    File(PathBuf),
681}
682
683///////////////// Enum Patches //////////////////
684
685#[derive(Debug, Clone, PartialEq, Deserialize)]
686#[serde(rename_all = "camelCase")]
687#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
688pub enum FromContextPatch {
689    #[cfg(not(feature = "permissive"))]
690    #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
691    FromImage(ImageNamePatch),
692
693    #[cfg(feature = "permissive")]
694    #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
695    FromImage(ParsableStruct<ImageNamePatch>),
696
697    #[cfg_attr(not(feature = "strict"), serde(alias = "builder"))]
698    FromBuilder(String),
699
700    #[cfg_attr(not(feature = "strict"), serde(alias = "from"))]
701    FromContext(Option<String>),
702}
703
704#[derive(Debug, Clone, PartialEq, Deserialize)]
705#[serde(untagged)]
706#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
707pub enum CopyResourcePatch {
708    Copy(CopyPatch),
709    Content(CopyContentPatch),
710    AddGitRepo(AddGitRepoPatch),
711    Add(AddPatch),
712    Unknown(UnknownPatch),
713}
714
715#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
716#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
717pub struct UnknownPatch {
718    #[serde(flatten)]
719    pub options: Option<CopyOptionsPatch>,
720    pub exclude: Option<VecPatch<String>>,
721}
722
723///////////////// Tests //////////////////
724
725#[cfg(test)]
726mod test {
727    use super::*;
728    use pretty_assertions_sorted::assert_eq_sorted;
729
730    mod deserialize {
731        use super::*;
732
733        mod dofigen {
734            use super::*;
735
736            #[test]
737            fn empty() {
738                let data = r#""#;
739
740                let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
741                let dofigen: Dofigen = dofigen.into();
742
743                assert_eq_sorted!(dofigen, Dofigen::default());
744            }
745
746            #[test]
747            fn from() {
748                let data = r#"
749                fromImage:
750                  path: ubuntu
751                "#;
752
753                let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
754                let dofigen: Dofigen = dofigen.into();
755
756                assert_eq_sorted!(
757                    dofigen,
758                    Dofigen {
759                        stage: Stage {
760                            from: FromContext::FromImage(ImageName {
761                                path: "ubuntu".into(),
762                                ..Default::default()
763                            }),
764                            ..Default::default()
765                        },
766                        ..Default::default()
767                    }
768                );
769            }
770
771            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
772            #[test]
773            fn duplicate_from() {
774                let data = r#"
775                from:
776                    path: ubuntu
777                from:
778                    path: alpine
779                "#;
780
781                let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
782
783                println!("{:?}", dofigen);
784
785                assert!(dofigen.is_err());
786            }
787
788            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
789            #[test]
790            fn duplicate_from_and_alias() {
791                let data = r#"
792                from:
793                  path: ubuntu
794                image:
795                  path: alpine
796                "#;
797
798                let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
799
800                println!("{:?}", dofigen);
801
802                assert!(dofigen.is_err());
803            }
804        }
805
806        mod stage {
807            use super::*;
808
809            #[test]
810            fn empty() {
811                let data = r#""#;
812
813                let stage: StagePatch = serde_yaml::from_str(data).unwrap();
814                let stage: Stage = stage.into();
815
816                assert_eq_sorted!(stage, Stage::default());
817            }
818
819            #[test]
820            fn from() {
821                let data = r#"
822                fromImage:
823                  path: ubuntu
824                "#;
825
826                let stage: StagePatch = serde_yaml::from_str(data).unwrap();
827                let stage: Stage = stage.into();
828
829                assert_eq_sorted!(
830                    stage,
831                    Stage {
832                        from: FromContext::FromImage(ImageName {
833                            path: "ubuntu".into(),
834                            ..Default::default()
835                        })
836                        .into(),
837                        ..Default::default()
838                    }
839                );
840            }
841
842            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
843            #[test]
844            fn duplicate_from() {
845                let data = r#"
846                fromImage:
847                    path: ubuntu
848                fromImage:
849                    path: alpine
850                "#;
851
852                let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
853
854                assert!(stage.is_err());
855            }
856
857            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
858            #[test]
859            fn duplicate_from_and_alias() {
860                let data = r#"
861                fromImage:
862                  path: ubuntu
863                image:
864                  path: alpine
865                "#;
866
867                let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
868
869                assert!(stage.is_err());
870            }
871
872            #[test]
873            fn label() {
874                #[cfg(not(feature = "strict"))]
875                let data = r#"
876                label:
877                  io.dofigen:
878                    test: test
879                "#;
880                #[cfg(feature = "strict")]
881                let data = r#"
882                label:
883                  io.dofigen.test: test
884                "#;
885
886                let stage: StagePatch = serde_yaml::from_str(data).unwrap();
887                let stage: Stage = stage.into();
888
889                assert_eq_sorted!(
890                    stage,
891                    Stage {
892                        label: HashMap::from([("io.dofigen.test".into(), "test".into())]),
893                        ..Default::default()
894                    }
895                );
896            }
897        }
898
899        mod user {
900            use super::*;
901
902            #[test]
903            fn name_and_group() {
904                let json_data = r#"{
905    "user": "test",
906    "group": "test"
907}"#;
908
909                let user: UserPatch = serde_yaml::from_str(json_data).unwrap();
910                let user: User = user.into();
911
912                assert_eq_sorted!(
913                    user,
914                    User {
915                        user: "test".into(),
916                        group: Some("test".into())
917                    }
918                );
919            }
920        }
921
922        mod copy_resource {
923
924            use super::*;
925
926            #[test]
927            fn copy() {
928                let json_data = r#"{
929    "paths": ["file1.txt", "file2.txt"],
930    "target": "destination/",
931    "chown": {
932        "user": "root",
933        "group": "root"
934    },
935    "chmod": "755",
936    "link": true,
937    "fromImage": {"path": "my-image"}
938}"#;
939
940                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
941                let copy_resource: CopyResource = copy_resource.into();
942
943                assert_eq_sorted!(
944                    copy_resource,
945                    CopyResource::Copy(Copy {
946                        paths: vec!["file1.txt".into(), "file2.txt".into()].into(),
947                        options: CopyOptions {
948                            target: Some("destination/".into()),
949                            chown: Some(User {
950                                user: "root".into(),
951                                group: Some("root".into())
952                            }),
953                            chmod: Some("755".into()),
954                            link: Some(true),
955                        },
956                        from: FromContext::FromImage(ImageName {
957                            path: "my-image".into(),
958                            ..Default::default()
959                        }),
960                        exclude: vec![].into(),
961                        parents: None,
962                    })
963                );
964            }
965
966            #[test]
967            fn copy_simple() {
968                let json_data = r#"{
969    "paths": ["file1.txt"]
970}"#;
971
972                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
973
974                assert_eq_sorted!(
975                    copy_resource,
976                    CopyResourcePatch::Copy(CopyPatch {
977                        paths: Some(vec!["file1.txt".into()].into_patch()),
978                        options: Some(CopyOptionsPatch::default()),
979                        ..Default::default()
980                    })
981                );
982
983                let copy_resource: CopyResource = copy_resource.into();
984
985                assert_eq_sorted!(
986                    copy_resource,
987                    CopyResource::Copy(Copy {
988                        paths: vec!["file1.txt".into()].into(),
989                        options: CopyOptions::default(),
990                        ..Default::default()
991                    })
992                );
993            }
994
995            #[cfg(feature = "permissive")]
996            #[test]
997            fn copy_chmod_int() {
998                let json_data = r#"{
999    "paths": ["file1.txt"],
1000    "chmod": 755
1001}"#;
1002
1003                let copy_resource: CopyPatch = serde_yaml::from_str(json_data).unwrap();
1004
1005                assert_eq_sorted!(
1006                    copy_resource,
1007                    CopyPatch {
1008                        paths: Some(vec!["file1.txt".into()].into_patch()),
1009                        options: Some(CopyOptionsPatch {
1010                            chmod: Some(Some("755".into())),
1011                            ..Default::default()
1012                        }),
1013                        ..Default::default()
1014                    }
1015                );
1016            }
1017
1018            #[cfg(feature = "permissive")]
1019            #[test]
1020            fn deserialize_copy_from_str() {
1021                let json_data = "file1.txt destination/";
1022
1023                let copy_resource: ParsableStruct<CopyResourcePatch> =
1024                    serde_yaml::from_str(json_data).unwrap();
1025                let copy_resource: CopyResource = copy_resource.into();
1026
1027                assert_eq_sorted!(
1028                    copy_resource,
1029                    CopyResource::Copy(Copy {
1030                        paths: vec!["file1.txt".into()].into(),
1031                        options: CopyOptions {
1032                            target: Some("destination/".into()),
1033                            ..Default::default()
1034                        },
1035                        ..Default::default()
1036                    })
1037                );
1038            }
1039
1040            #[test]
1041            fn copy_content() {
1042                let json_data = r#"{
1043    "content": "echo coucou",
1044    "substitute": false,
1045    "target": "test.sh",
1046    "chown": {
1047        "user": "1001",
1048        "group": "1001"
1049    },
1050    "chmod": "555",
1051    "link": true
1052}"#;
1053
1054                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1055                let copy_resource: CopyResource = copy_resource.into();
1056
1057                assert_eq_sorted!(
1058                    copy_resource,
1059                    CopyResource::Content(CopyContent {
1060                        content: "echo coucou".into(),
1061                        substitute: Some(false),
1062                        options: CopyOptions {
1063                            target: Some("test.sh".into()),
1064                            chown: Some(User {
1065                                user: "1001".into(),
1066                                group: Some("1001".into())
1067                            }),
1068                            chmod: Some("555".into()),
1069                            link: Some(true),
1070                        }
1071                    })
1072                );
1073            }
1074
1075            #[test]
1076            fn add_git_repo() {
1077                let json_data = r#"{
1078            "repo": "https://github.com/example/repo.git",
1079            "target": "destination/",
1080            "chown": {
1081                "user": "root",
1082                "group": "root"
1083            },
1084            "chmod": "755",
1085            "link": true,
1086            "keepGitDir": true
1087        }"#;
1088
1089                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1090                let copy_resource: CopyResource = copy_resource.into();
1091
1092                assert_eq_sorted!(
1093                    copy_resource,
1094                    CopyResource::AddGitRepo(AddGitRepo {
1095                        repo: "https://github.com/example/repo.git".into(),
1096                        options: CopyOptions {
1097                            target: Some("destination/".into()),
1098                            chown: Some(User {
1099                                user: "root".into(),
1100                                group: Some("root".into())
1101                            }),
1102                            chmod: Some("755".into()),
1103                            link: Some(true),
1104                        },
1105                        keep_git_dir: Some(true),
1106                        exclude: vec![].into(),
1107                    })
1108                );
1109            }
1110
1111            #[test]
1112            fn add() {
1113                let json_data = r#"{
1114            "files": ["file1.txt", "file2.txt"],
1115            "target": "destination/",
1116            "checksum": "sha256:abcdef123456",
1117            "chown": {
1118                "user": "root",
1119                "group": "root"
1120            },
1121            "chmod": "755",
1122            "link": true
1123        }"#;
1124
1125                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
1126                let copy_resource: CopyResource = copy_resource.into();
1127
1128                assert_eq_sorted!(
1129                    copy_resource,
1130                    CopyResource::Add(Add {
1131                        files: vec![
1132                            Resource::File("file1.txt".into()),
1133                            Resource::File("file2.txt".into())
1134                        ]
1135                        .into(),
1136                        options: CopyOptions {
1137                            target: Some("destination/".into()),
1138                            chown: Some(User {
1139                                user: "root".into(),
1140                                group: Some("root".into())
1141                            }),
1142                            chmod: Some("755".into()),
1143                            link: Some(true),
1144                        },
1145                        checksum: Some("sha256:abcdef123456".into()),
1146                    })
1147                );
1148            }
1149        }
1150
1151        mod builder {
1152            use super::*;
1153
1154            #[test]
1155            fn with_bind() {
1156                let json_data = r#"
1157fromImage:
1158  path: clux/muslrust:stable
1159workdir: /app
1160bind:
1161  - target: /app
1162run:
1163  - cargo build --release
1164  - mv target/x86_64-unknown-linux-musl/release/dofigen /app/
1165"#;
1166
1167                let builder: Stage = serde_yaml::from_str::<StagePatch>(json_data)
1168                    .unwrap()
1169                    .into();
1170
1171                assert_eq_sorted!(
1172                    builder,
1173                    Stage {
1174                        from: FromContext::FromImage(ImageName {
1175                            path: "clux/muslrust:stable".into(),
1176                            ..Default::default()
1177                        }),
1178                        workdir: Some("/app".into()),
1179                        run: Run {
1180                            bind: vec![Bind {
1181                                target: "/app".into(),
1182                                ..Default::default()
1183                            }],
1184                            run: vec![
1185                                "cargo build --release".into(),
1186                                "mv target/x86_64-unknown-linux-musl/release/dofigen /app/".into()
1187                            ],
1188                            ..Default::default()
1189                        },
1190                        ..Default::default()
1191                    }
1192                );
1193            }
1194        }
1195    }
1196}