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