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