docker_compose_types/
lib.rs

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