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