docker_compose_types/
lib.rs

1use derive_builder::*;
2#[cfg(feature = "indexmap")]
3use indexmap::IndexMap;
4use serde::{Deserialize, Deserializer, Serialize};
5#[cfg(feature = "norway")]
6use serde_norway as serde_yaml;
7#[cfg(feature = "yml")]
8use serde_yml as serde_yaml;
9
10#[cfg(not(feature = "indexmap"))]
11use std::collections::HashMap;
12use std::convert::TryFrom;
13use std::fmt;
14use std::str::FromStr;
15
16use serde_yaml::Value;
17
18#[allow(clippy::large_enum_variant)]
19#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
20#[serde(untagged)]
21pub enum ComposeFile {
22    V2Plus(Compose),
23    #[cfg(feature = "indexmap")]
24    V1(IndexMap<String, Service>),
25    #[cfg(not(feature = "indexmap"))]
26    V1(HashMap<String, Service>),
27    Single(SingleService),
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
31pub struct SingleService {
32    pub service: Service,
33}
34
35#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
36pub struct Compose {
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub version: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub name: Option<String>,
41    #[serde(
42        default,
43        rename = "include",
44        alias = "includes",
45        skip_serializing_if = "Option::is_none"
46    )]
47    pub includes: Option<Includes>,
48    #[serde(default, skip_serializing_if = "Services::is_empty")]
49    pub services: Services,
50    #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")]
51    pub volumes: TopLevelVolumes,
52    #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")]
53    pub networks: ComposeNetworks,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub service: Option<Service>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub secrets: Option<ComposeSecrets>,
58    #[cfg(feature = "indexmap")]
59    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
60    pub extensions: IndexMap<Extension, Value>,
61    #[cfg(not(feature = "indexmap"))]
62    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
63    pub extensions: HashMap<Extension, Value>,
64}
65
66impl Compose {
67    pub fn new() -> Self {
68        Default::default()
69    }
70}
71
72#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
73#[serde(untagged)]
74pub enum StringOrList {
75    Simple(String),
76    List(Vec<String>),
77}
78
79#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
80#[builder(setter(into), default)]
81pub struct Include {
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub project_directory: Option<String>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub path: Option<StringOrList>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub env_file: Option<EnvFile>,
88}
89
90#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
91#[serde(untagged)]
92pub enum Includes {
93    Short(Vec<String>),
94    Long(Vec<Include>),
95}
96
97impl Includes {
98    pub fn is_empty(&self) -> bool {
99        match self {
100            Includes::Short(xs) => xs.is_empty(),
101            Includes::Long(xs) => xs.is_empty(),
102        }
103    }
104}
105
106impl Default for Includes {
107    fn default() -> Self {
108        Self::Short(vec![])
109    }
110}
111
112#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
113#[builder(setter(into), default)]
114pub struct Service {
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub hostname: Option<String>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub domainname: Option<String>,
119    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
120    pub privileged: bool,
121    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
122    pub read_only: bool,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub healthcheck: Option<Healthcheck>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub deploy: Option<Deploy>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub image: Option<String>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub container_name: Option<String>,
131    #[serde(skip_serializing_if = "Option::is_none", rename = "build")]
132    pub build_: Option<BuildStep>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub pid: Option<String>,
135    #[serde(default, skip_serializing_if = "Ports::is_empty")]
136    pub ports: Ports,
137    #[serde(default, skip_serializing_if = "Environment::is_empty")]
138    pub environment: Environment,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub network_mode: Option<String>,
141    #[serde(default, skip_serializing_if = "Vec::is_empty")]
142    pub devices: Vec<String>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub restart: Option<String>,
145    #[serde(default, skip_serializing_if = "Labels::is_empty")]
146    pub labels: Labels,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub tmpfs: Option<Tmpfs>,
149    #[serde(default, skip_serializing_if = "Ulimits::is_empty")]
150    pub ulimits: Ulimits,
151    #[serde(default, skip_serializing_if = "Vec::is_empty")]
152    pub volumes: Vec<Volumes>,
153    #[serde(default, skip_serializing_if = "Networks::is_empty")]
154    pub networks: Networks,
155    #[serde(default, skip_serializing_if = "Vec::is_empty")]
156    pub cap_add: Vec<String>,
157    #[serde(default, skip_serializing_if = "Vec::is_empty")]
158    pub cap_drop: Vec<String>,
159    #[serde(default, skip_serializing_if = "DependsOnOptions::is_empty")]
160    pub depends_on: DependsOnOptions,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub command: Option<Command>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub entrypoint: Option<Entrypoint>,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub env_file: Option<EnvFile>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub stop_grace_period: Option<String>,
169    #[serde(default, skip_serializing_if = "Vec::is_empty")]
170    pub profiles: Vec<String>,
171    #[serde(default, skip_serializing_if = "Vec::is_empty")]
172    pub links: Vec<String>,
173    #[serde(default, skip_serializing_if = "Vec::is_empty")]
174    pub dns: Vec<String>,
175    #[serde(default, skip_serializing_if = "Vec::is_empty")]
176    pub dns_opt: Vec<String>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub ipc: Option<String>,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub net: Option<String>,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub stop_signal: Option<String>,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub user: Option<String>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub userns_mode: Option<String>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub working_dir: Option<String>,
189    #[serde(default, skip_serializing_if = "Vec::is_empty")]
190    pub expose: Vec<String>,
191    #[serde(default, skip_serializing_if = "Vec::is_empty")]
192    pub volumes_from: Vec<String>,
193    #[cfg(feature = "indexmap")]
194    #[serde(
195        default,
196        deserialize_with = "de_extends_indexmap",
197        skip_serializing_if = "IndexMap::is_empty"
198    )]
199    pub extends: IndexMap<String, String>,
200    #[cfg(not(feature = "indexmap"))]
201    #[serde(
202        default,
203        deserialize_with = "de_extends_hashmap",
204        skip_serializing_if = "HashMap::is_empty"
205    )]
206    pub extends: HashMap<String, String>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub logging: Option<LoggingParameters>,
209    #[serde(default, skip_serializing_if = "is_zero")]
210    pub scale: i64,
211    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
212    pub init: bool,
213    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
214    pub stdin_open: bool,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub shm_size: Option<String>,
217    #[cfg(feature = "indexmap")]
218    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
219    pub extensions: IndexMap<Extension, Value>,
220    #[cfg(not(feature = "indexmap"))]
221    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
222    pub extensions: HashMap<Extension, Value>,
223    #[serde(default, skip_serializing_if = "Vec::is_empty")]
224    pub extra_hosts: Vec<String>,
225    #[serde(default, skip_serializing_if = "Vec::is_empty")]
226    pub group_add: Vec<Group>,
227    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
228    pub tty: bool,
229    #[serde(default, skip_serializing_if = "SysCtls::is_empty")]
230    pub sysctls: SysCtls,
231    #[serde(default, skip_serializing_if = "Vec::is_empty")]
232    pub security_opt: Vec<String>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub secrets: Option<Secrets>,
235    #[serde(default, skip_serializing_if = "Option::is_none")]
236    pub pull_policy: Option<PullPolicy>,
237    #[serde(default, skip_serializing_if = "Option::is_none")]
238    pub cgroup_parent: Option<String>,
239    #[serde(default, skip_serializing_if = "Option::is_none")]
240    pub mem_limit: Option<String>,
241    #[serde(default, skip_serializing_if = "Option::is_none")]
242    pub mem_reservation: Option<String>,
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub mem_swappiness: Option<u16>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub runtime: Option<String>,
247}
248
249#[cfg(feature = "indexmap")]
250fn de_extends_indexmap<'de, D>(deserializer: D) -> Result<IndexMap<String, String>, D::Error>
251where
252    D: Deserializer<'de>,
253{
254    let value = Value::deserialize(deserializer)?;
255    if let Some(value_str) = value.as_str() {
256        let mut map = IndexMap::new();
257        map.insert("service".to_string(), value_str.to_string());
258        return Ok(map);
259    }
260
261    if let Some(value_map) = value.as_mapping() {
262        let mut map = IndexMap::new();
263        for (k, v) in value_map {
264            if !k.is_string() || !v.is_string() {
265                return Err(serde::de::Error::custom(
266                    "extends must must have string type for both Keys and Values".to_string(),
267                ));
268            }
269            //Should be safe due to previous check
270            map.insert(
271                k.as_str().unwrap().to_string(),
272                v.as_str().unwrap().to_string(),
273            );
274        }
275        return Ok(map);
276    }
277
278    Err(serde::de::Error::custom(
279        "extends must either be a map or a string".to_string(),
280    ))
281}
282
283#[cfg(not(feature = "indexmap"))]
284fn de_extends_hashmap<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
285where
286    D: Deserializer<'de>,
287{
288    let value = Value::deserialize(deserializer)?;
289    if let Some(value_str) = value.as_str() {
290        let mut map = HashMap::new();
291        map.insert("service".to_string(), value_str.to_string());
292        return Ok(map);
293    }
294
295    if let Some(value_map) = value.as_mapping() {
296        let mut map = HashMap::new();
297        for (k, v) in value_map {
298            if !k.is_string() || !v.is_string() {
299                return Err(serde::de::Error::custom(
300                    "extends must must have string type for both Keys and Values".to_string(),
301                ));
302            }
303            //Should be safe due to previous check
304            map.insert(
305                k.as_str().unwrap().to_string(),
306                v.as_str().unwrap().to_string(),
307            );
308        }
309        return Ok(map);
310    }
311
312    Err(serde::de::Error::custom(
313        "extends must either be a map or a string".to_string(),
314    ))
315}
316
317impl Service {
318    pub fn image(&self) -> &str {
319        self.image.as_deref().unwrap_or_default()
320    }
321
322    pub fn network_mode(&self) -> &str {
323        self.network_mode.as_deref().unwrap_or_default()
324    }
325}
326
327type EnvFile = StringOrList;
328
329#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
330#[serde(untagged)]
331pub enum DependsOnOptions {
332    Simple(Vec<String>),
333    #[cfg(feature = "indexmap")]
334    Conditional(IndexMap<String, DependsCondition>),
335    #[cfg(not(feature = "indexmap"))]
336    Conditional(HashMap<String, DependsCondition>),
337}
338
339impl Default for DependsOnOptions {
340    fn default() -> Self {
341        Self::Simple(Vec::new())
342    }
343}
344
345impl DependsOnOptions {
346    pub fn is_empty(&self) -> bool {
347        match self {
348            Self::Simple(v) => v.is_empty(),
349            Self::Conditional(m) => m.is_empty(),
350        }
351    }
352}
353/// https://docs.docker.com/reference/compose-file/services/#depends_on
354#[derive(Default, Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
355pub struct DependsCondition {
356    pub condition: String,
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub restart: Option<bool>,
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub required: Option<bool>,
361}
362
363impl DependsCondition {
364    pub fn new(condition: impl Into<String>) -> Self {
365        Self {
366            condition: condition.into(),
367            ..Default::default()
368        }
369    }
370    pub fn service_started() -> Self {
371        Self::new("service_started")
372    }
373
374    pub fn service_healthy() -> Self {
375        Self::new("service_healthy")
376    }
377
378    pub fn service_completed_successfully() -> Self {
379        Self::new("service_completed_successfully")
380    }
381
382    pub fn with_restart(self, flag: bool) -> Self {
383        Self {
384            restart: Some(flag),
385            ..self
386        }
387    }
388    pub fn with_required(self, flag: bool) -> Self {
389        Self {
390            required: Some(flag),
391            ..self
392        }
393    }
394}
395
396#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
397pub struct LoggingParameters {
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub driver: Option<String>,
400    #[cfg(feature = "indexmap")]
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub options: Option<IndexMap<String, SingleValue>>,
403    #[cfg(not(feature = "indexmap"))]
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub options: Option<HashMap<String, SingleValue>>,
406}
407
408#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
409#[serde(untagged)]
410pub enum Ports {
411    Short(Vec<String>),
412    Long(Vec<Port>),
413}
414
415impl Default for Ports {
416    fn default() -> Self {
417        Self::Short(Vec::default())
418    }
419}
420
421impl Ports {
422    pub fn is_empty(&self) -> bool {
423        match self {
424            Self::Short(v) => v.is_empty(),
425            Self::Long(v) => v.is_empty(),
426        }
427    }
428}
429
430#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
431pub struct Port {
432    pub target: u16,
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub host_ip: Option<String>,
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub published: Option<PublishedPort>,
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub protocol: Option<String>,
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub mode: Option<String>,
441}
442
443#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
444#[serde(untagged)]
445pub enum PublishedPort {
446    Single(u16),
447    Range(String),
448}
449
450#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
451#[serde(untagged)]
452pub enum Environment {
453    List(Vec<String>),
454    #[cfg(feature = "indexmap")]
455    KvPair(IndexMap<String, Option<SingleValue>>),
456    #[cfg(not(feature = "indexmap"))]
457    KvPair(HashMap<String, Option<SingleValue>>),
458}
459
460impl Default for Environment {
461    fn default() -> Self {
462        Self::List(Vec::new())
463    }
464}
465
466impl Environment {
467    pub fn is_empty(&self) -> bool {
468        match self {
469            Self::List(v) => v.is_empty(),
470            Self::KvPair(m) => m.is_empty(),
471        }
472    }
473}
474
475#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
476#[serde(try_from = "String")]
477pub struct Extension(String);
478
479impl FromStr for Extension {
480    type Err = ExtensionParseError;
481
482    fn from_str(s: &str) -> Result<Self, Self::Err> {
483        let owned = s.to_owned();
484        Extension::try_from(owned)
485    }
486}
487
488impl TryFrom<String> for Extension {
489    type Error = ExtensionParseError;
490
491    fn try_from(s: String) -> Result<Self, Self::Error> {
492        if s.starts_with("x-") {
493            Ok(Self(s))
494        } else {
495            Err(ExtensionParseError(s))
496        }
497    }
498}
499
500/// The result of a failed TryFrom<String> conversion for [`Extension`]
501///
502/// Contains the string that was being converted
503#[derive(Clone, Debug, Eq, PartialEq, Hash)]
504pub struct ExtensionParseError(pub String);
505
506impl fmt::Display for ExtensionParseError {
507    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
508        write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
509    }
510}
511
512impl std::error::Error for ExtensionParseError {}
513
514#[cfg(feature = "indexmap")]
515#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
516pub struct Services(pub IndexMap<String, Option<Service>>);
517#[cfg(not(feature = "indexmap"))]
518#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
519pub struct Services(pub HashMap<String, Option<Service>>);
520
521impl Services {
522    pub fn is_empty(&self) -> bool {
523        self.0.is_empty()
524    }
525}
526
527#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
528#[serde(untagged)]
529pub enum Labels {
530    List(Vec<String>),
531    #[cfg(feature = "indexmap")]
532    Map(IndexMap<String, String>),
533    #[cfg(not(feature = "indexmap"))]
534    Map(HashMap<String, String>),
535}
536
537impl Default for Labels {
538    fn default() -> Self {
539        Self::List(Vec::new())
540    }
541}
542
543impl Labels {
544    pub fn is_empty(&self) -> bool {
545        match self {
546            Self::List(v) => v.is_empty(),
547            Self::Map(m) => m.is_empty(),
548        }
549    }
550}
551
552#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
553#[serde(untagged)]
554pub enum Tmpfs {
555    Simple(String),
556    List(Vec<String>),
557}
558
559#[cfg(feature = "indexmap")]
560#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
561pub struct Ulimits(pub IndexMap<String, Ulimit>);
562#[cfg(not(feature = "indexmap"))]
563#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
564pub struct Ulimits(pub HashMap<String, Ulimit>);
565
566impl Ulimits {
567    pub fn is_empty(&self) -> bool {
568        self.0.is_empty()
569    }
570}
571
572#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
573#[serde(untagged)]
574pub enum Ulimit {
575    Single(i64),
576    SoftHard { soft: i64, hard: i64 },
577}
578
579#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
580#[serde(untagged)]
581pub enum Networks {
582    Simple(Vec<String>),
583    Advanced(AdvancedNetworks),
584}
585
586impl Default for Networks {
587    fn default() -> Self {
588        Self::Simple(Vec::new())
589    }
590}
591
592impl Networks {
593    pub fn is_empty(&self) -> bool {
594        match self {
595            Self::Simple(n) => n.is_empty(),
596            Self::Advanced(n) => n.0.is_empty(),
597        }
598    }
599}
600
601#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
602#[serde(untagged)]
603pub enum BuildStep {
604    Simple(String),
605    Advanced(AdvancedBuildStep),
606}
607
608#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
609#[serde(deny_unknown_fields)]
610#[builder(setter(into), default)]
611pub struct AdvancedBuildStep {
612    pub context: String,
613    #[serde(default, skip_serializing_if = "Option::is_none")]
614    pub dockerfile: Option<String>,
615    #[serde(default, skip_serializing_if = "Option::is_none")]
616    pub dockerfile_inline: Option<String>,
617    #[serde(default, skip_serializing_if = "Option::is_none")]
618    pub args: Option<BuildArgs>,
619    #[serde(default, skip_serializing_if = "Option::is_none")]
620    pub shm_size: Option<u64>,
621    #[serde(default, skip_serializing_if = "Option::is_none")]
622    pub target: Option<String>,
623    #[serde(default, skip_serializing_if = "Option::is_none")]
624    pub network: Option<String>,
625    #[serde(default, skip_serializing_if = "Vec::is_empty")]
626    pub cache_from: Vec<String>,
627    #[serde(default, skip_serializing_if = "Labels::is_empty")]
628    pub labels: Labels,
629}
630
631#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
632#[serde(untagged)]
633pub enum BuildArgs {
634    Simple(String),
635    List(Vec<String>),
636    #[cfg(feature = "indexmap")]
637    KvPair(IndexMap<String, String>),
638    #[cfg(not(feature = "indexmap"))]
639    KvPair(HashMap<String, String>),
640}
641
642#[cfg(feature = "indexmap")]
643#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
644pub struct AdvancedNetworks(pub IndexMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
645#[cfg(not(feature = "indexmap"))]
646#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
647pub struct AdvancedNetworks(pub HashMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
648
649#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
650#[serde(deny_unknown_fields)]
651pub struct AdvancedNetworkSettings {
652    #[serde(skip_serializing_if = "Option::is_none")]
653    pub ipv4_address: Option<String>,
654    #[serde(skip_serializing_if = "Option::is_none")]
655    pub ipv6_address: Option<String>,
656    #[serde(default, skip_serializing_if = "Vec::is_empty")]
657    pub aliases: Vec<String>,
658}
659
660#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
661#[serde(untagged)]
662pub enum SysCtls {
663    List(Vec<String>),
664    #[cfg(feature = "indexmap")]
665    Map(IndexMap<String, Option<SingleValue>>),
666    #[cfg(not(feature = "indexmap"))]
667    Map(HashMap<String, Option<SingleValue>>),
668}
669
670impl Default for SysCtls {
671    fn default() -> Self {
672        Self::List(Vec::new())
673    }
674}
675
676impl SysCtls {
677    pub fn is_empty(&self) -> bool {
678        match self {
679            Self::List(v) => v.is_empty(),
680            Self::Map(m) => m.is_empty(),
681        }
682    }
683}
684
685#[cfg(feature = "indexmap")]
686#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
687pub struct TopLevelVolumes(pub IndexMap<String, MapOrEmpty<ComposeVolume>>);
688#[cfg(not(feature = "indexmap"))]
689#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
690pub struct TopLevelVolumes(pub HashMap<String, MapOrEmpty<ComposeVolume>>);
691
692impl TopLevelVolumes {
693    pub fn is_empty(&self) -> bool {
694        self.0.is_empty()
695    }
696}
697
698#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
699pub struct ComposeVolume {
700    #[serde(skip_serializing_if = "Option::is_none")]
701    pub driver: Option<String>,
702    #[cfg(feature = "indexmap")]
703    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
704    pub driver_opts: IndexMap<String, Option<SingleValue>>,
705    #[cfg(not(feature = "indexmap"))]
706    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
707    pub driver_opts: HashMap<String, Option<SingleValue>>,
708    #[serde(skip_serializing_if = "Option::is_none")]
709    pub external: Option<ExternalVolume>,
710    #[serde(default, skip_serializing_if = "Labels::is_empty")]
711    pub labels: Labels,
712    #[serde(skip_serializing_if = "Option::is_none")]
713    pub name: Option<String>,
714}
715
716#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
717#[serde(untagged)]
718pub enum ExternalVolume {
719    Bool(bool),
720    Name { name: String },
721}
722
723#[cfg(feature = "indexmap")]
724#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
725pub struct ComposeNetworks(pub IndexMap<String, MapOrEmpty<NetworkSettings>>);
726
727#[cfg(not(feature = "indexmap"))]
728#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
729pub struct ComposeNetworks(pub HashMap<String, MapOrEmpty<NetworkSettings>>);
730
731impl ComposeNetworks {
732    pub fn is_empty(&self) -> bool {
733        self.0.is_empty()
734    }
735}
736
737#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
738#[serde(untagged)]
739pub enum ComposeNetwork {
740    Detailed(ComposeNetworkSettingDetails),
741    Bool(bool),
742}
743
744#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
745#[serde(deny_unknown_fields)]
746pub struct ComposeNetworkSettingDetails {
747    pub name: String,
748}
749
750#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
751#[serde(deny_unknown_fields)]
752pub struct ExternalNetworkSettingBool(bool);
753
754#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
755#[serde(deny_unknown_fields)]
756pub struct NetworkSettings {
757    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
758    pub attachable: bool,
759    #[serde(skip_serializing_if = "Option::is_none")]
760    pub driver: Option<String>,
761    #[cfg(feature = "indexmap")]
762    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
763    pub driver_opts: IndexMap<String, Option<SingleValue>>,
764    #[cfg(not(feature = "indexmap"))]
765    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
766    pub driver_opts: HashMap<String, Option<SingleValue>>,
767    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
768    pub enable_ipv6: bool,
769    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
770    pub internal: bool,
771    #[serde(skip_serializing_if = "Option::is_none")]
772    pub external: Option<ComposeNetwork>,
773    #[serde(skip_serializing_if = "Option::is_none")]
774    pub ipam: Option<Ipam>,
775    #[serde(default, skip_serializing_if = "Labels::is_empty")]
776    pub labels: Labels,
777    #[serde(skip_serializing_if = "Option::is_none")]
778    pub name: Option<String>,
779}
780
781#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
782#[serde(deny_unknown_fields)]
783pub struct Ipam {
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub driver: Option<String>,
786    #[serde(default, skip_serializing_if = "Vec::is_empty")]
787    pub config: Vec<IpamConfig>,
788}
789
790#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
791#[serde(deny_unknown_fields)]
792pub struct IpamConfig {
793    pub subnet: String,
794    #[serde(skip_serializing_if = "Option::is_none")]
795    pub gateway: Option<String>,
796}
797
798#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
799#[serde(deny_unknown_fields)]
800pub struct Deploy {
801    #[serde(skip_serializing_if = "Option::is_none")]
802    pub mode: Option<String>,
803    #[serde(skip_serializing_if = "Option::is_none")]
804    pub replicas: Option<i64>,
805    #[serde(default, skip_serializing_if = "Vec::is_empty")]
806    pub labels: Vec<String>,
807    #[serde(skip_serializing_if = "Option::is_none")]
808    pub update_config: Option<UpdateConfig>,
809    #[serde(skip_serializing_if = "Option::is_none")]
810    pub resources: Option<Resources>,
811    #[serde(skip_serializing_if = "Option::is_none")]
812    pub restart_policy: Option<RestartPolicy>,
813    #[serde(skip_serializing_if = "Option::is_none")]
814    pub placement: Option<Placement>,
815}
816
817fn is_zero(val: &i64) -> bool {
818    *val == 0
819}
820
821#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
822#[serde(deny_unknown_fields)]
823pub struct Healthcheck {
824    #[serde(skip_serializing_if = "Option::is_none")]
825    pub test: Option<HealthcheckTest>,
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub interval: Option<String>,
828    #[serde(skip_serializing_if = "Option::is_none")]
829    pub timeout: Option<String>,
830    #[serde(default, skip_serializing_if = "is_zero")]
831    pub retries: i64,
832    #[serde(skip_serializing_if = "Option::is_none")]
833    pub start_period: Option<String>,
834    #[serde(skip_serializing_if = "Option::is_none")]
835    pub start_interval: Option<String>,
836    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
837    pub disable: bool,
838}
839
840#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
841#[serde(untagged)]
842pub enum HealthcheckTest {
843    Single(String),
844    Multiple(Vec<String>),
845}
846
847#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
848#[serde(deny_unknown_fields)]
849pub struct Limits {
850    #[serde(skip_serializing_if = "Option::is_none")]
851    pub cpus: Option<String>,
852    #[serde(skip_serializing_if = "Option::is_none")]
853    pub memory: Option<String>,
854    #[serde(skip_serializing_if = "Option::is_none")]
855    pub devices: Option<Vec<Device>>,
856}
857
858#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
859#[serde(deny_unknown_fields)]
860pub struct Device {
861    #[serde(skip_serializing_if = "Option::is_none")]
862    pub driver: Option<String>,
863    #[serde(skip_serializing_if = "Option::is_none")]
864    pub count: Option<u32>,
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub device_ids: Option<Vec<String>>,
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub capabilities: Option<Vec<String>>,
869    #[serde(skip_serializing_if = "Option::is_none")]
870    #[cfg(feature = "indexmap")]
871    pub options: Option<IndexMap<String, Value>>,
872    #[cfg(not(feature = "indexmap"))]
873    pub options: Option<HashMap<String, Value>>,
874}
875
876#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
877#[serde(deny_unknown_fields)]
878pub struct Placement {
879    #[serde(skip_serializing_if = "Vec::is_empty", default)]
880    pub constraints: Vec<String>,
881    #[serde(skip_serializing_if = "Vec::is_empty", default)]
882    pub preferences: Vec<Preferences>,
883}
884
885#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
886#[serde(deny_unknown_fields)]
887pub struct Preferences {
888    pub spread: String,
889}
890
891#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
892#[serde(deny_unknown_fields)]
893pub struct Resources {
894    #[serde(skip_serializing_if = "Option::is_none")]
895    pub limits: Option<Limits>,
896    #[serde(skip_serializing_if = "Option::is_none")]
897    pub reservations: Option<Limits>,
898}
899
900#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
901#[serde(deny_unknown_fields)]
902pub struct RestartPolicy {
903    #[serde(skip_serializing_if = "Option::is_none")]
904    pub condition: Option<String>,
905    #[serde(skip_serializing_if = "Option::is_none")]
906    pub delay: Option<String>,
907    #[serde(skip_serializing_if = "Option::is_none")]
908    pub max_attempts: Option<i64>,
909    #[serde(skip_serializing_if = "Option::is_none")]
910    pub window: Option<String>,
911}
912
913#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
914#[serde(deny_unknown_fields)]
915pub struct UpdateConfig {
916    #[serde(skip_serializing_if = "Option::is_none")]
917    pub parallelism: Option<i64>,
918    #[serde(skip_serializing_if = "Option::is_none")]
919    pub delay: Option<String>,
920    #[serde(skip_serializing_if = "Option::is_none")]
921    pub failure_action: Option<String>,
922    #[serde(skip_serializing_if = "Option::is_none")]
923    pub monitor: Option<String>,
924    #[serde(skip_serializing_if = "Option::is_none")]
925    pub max_failure_ratio: Option<f64>,
926}
927
928#[cfg(feature = "indexmap")]
929#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
930pub struct ComposeSecrets(
931    #[serde(with = "serde_yaml::with::singleton_map_recursive")]
932    pub  IndexMap<String, Option<ComposeSecret>>,
933);
934
935#[cfg(not(feature = "indexmap"))]
936#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
937pub struct ComposeSecrets(
938    #[serde(with = "serde_yaml::with::singleton_map_recursive")]
939    pub  HashMap<String, Option<ComposeSecret>>,
940);
941
942#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
943#[serde(deny_unknown_fields, rename_all = "snake_case")]
944pub enum ComposeSecret {
945    File(String),
946    Environment(String),
947    #[serde(untagged)]
948    External {
949        external: bool,
950        name: String,
951    },
952}
953
954#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
955#[serde(untagged)]
956pub enum Secrets {
957    Simple(Vec<String>),
958    Advanced(Vec<AdvancedSecrets>),
959}
960
961impl Default for Secrets {
962    fn default() -> Self {
963        Self::Simple(Vec::new())
964    }
965}
966
967impl Secrets {
968    pub fn is_empty(&self) -> bool {
969        match self {
970            Self::Simple(v) => v.is_empty(),
971            Self::Advanced(v) => v.is_empty(),
972        }
973    }
974}
975
976#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
977#[serde(deny_unknown_fields)]
978pub struct AdvancedSecrets {
979    pub source: String,
980    #[serde(skip_serializing_if = "Option::is_none")]
981    pub target: Option<String>,
982    #[serde(default, skip_serializing_if = "Option::is_none")]
983    pub uid: Option<String>,
984    #[serde(default, skip_serializing_if = "Option::is_none")]
985    pub gid: Option<String>,
986    #[serde(default, skip_serializing_if = "Option::is_none")]
987    pub mode: Option<String>,
988}
989
990#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
991#[serde(rename_all = "lowercase")]
992pub enum PullPolicy {
993    Always,
994    Never,
995    #[serde(alias = "if_not_present")]
996    Missing,
997    Build,
998}
999
1000#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
1001#[serde(untagged)]
1002pub enum Volumes {
1003    Simple(String),
1004    Advanced(AdvancedVolumes),
1005}
1006
1007#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
1008#[serde(deny_unknown_fields)]
1009pub struct AdvancedVolumes {
1010    #[serde(skip_serializing_if = "Option::is_none")]
1011    pub source: Option<String>,
1012    pub target: String,
1013    #[serde(rename = "type")]
1014    pub _type: String,
1015    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1016    pub read_only: bool,
1017    #[serde(default, skip_serializing_if = "Option::is_none")]
1018    pub bind: Option<Bind>,
1019    #[serde(default, skip_serializing_if = "Option::is_none")]
1020    pub volume: Option<Volume>,
1021    #[serde(default, skip_serializing_if = "Option::is_none")]
1022    pub tmpfs: Option<TmpfsSettings>,
1023}
1024
1025#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
1026#[serde(deny_unknown_fields)]
1027pub struct Bind {
1028    pub propagation: Option<String>,
1029    pub create_host_path: Option<bool>,
1030    pub selinux: Option<String>,
1031}
1032
1033#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
1034#[serde(deny_unknown_fields)]
1035pub struct Volume {
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub nocopy: Option<bool>,
1038    #[serde(skip_serializing_if = "Option::is_none")]
1039    pub subpath: Option<String>,
1040}
1041
1042#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
1043#[serde(deny_unknown_fields)]
1044pub struct TmpfsSettings {
1045    pub size: u64,
1046}
1047
1048#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
1049#[serde(untagged)]
1050pub enum Command {
1051    Simple(String),
1052    Args(Vec<String>),
1053}
1054
1055#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
1056#[serde(untagged)]
1057pub enum Entrypoint {
1058    Simple(String),
1059    List(Vec<String>),
1060}
1061
1062#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
1063#[serde(untagged)]
1064pub enum SingleValue {
1065    String(String),
1066    Bool(bool),
1067    Unsigned(u64),
1068    Signed(i64),
1069    Float(f64),
1070}
1071
1072impl fmt::Display for SingleValue {
1073    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1074        match self {
1075            Self::String(s) => f.write_str(s),
1076            Self::Bool(b) => write!(f, "{b}"),
1077            Self::Unsigned(u) => write!(f, "{u}"),
1078            Self::Signed(i) => write!(f, "{i}"),
1079            Self::Float(fl) => write!(f, "{fl}"),
1080        }
1081    }
1082}
1083
1084#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
1085#[serde(untagged)]
1086pub enum Group {
1087    Named(String),
1088    Gid(u32),
1089}
1090
1091#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)]
1092#[serde(untagged)]
1093pub enum MapOrEmpty<T> {
1094    Map(T),
1095    Empty,
1096}
1097
1098impl<T> Default for MapOrEmpty<T> {
1099    fn default() -> Self {
1100        Self::Empty
1101    }
1102}
1103
1104impl<T> From<MapOrEmpty<T>> for Option<T> {
1105    fn from(value: MapOrEmpty<T>) -> Self {
1106        match value {
1107            MapOrEmpty::Map(t) => Some(t),
1108            MapOrEmpty::Empty => None,
1109        }
1110    }
1111}
1112
1113impl<T> Serialize for MapOrEmpty<T>
1114where
1115    T: Serialize,
1116{
1117    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1118    where
1119        S: serde::Serializer,
1120    {
1121        match self {
1122            Self::Map(t) => t.serialize(serializer),
1123            Self::Empty => {
1124                use serde::ser::SerializeMap;
1125                serializer.serialize_map(None)?.end()
1126            }
1127        }
1128    }
1129}