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