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 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 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#[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}