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