Skip to main content

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