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