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