Skip to main content

docker_compose_config/
lib.rs

1use core::fmt;
2use indexmap::{IndexMap, IndexSet};
3use merge_it::*;
4#[cfg(feature = "schemars")]
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::cmp::Ordering;
9use std::collections::{BTreeMap, BTreeSet};
10
11type StringBTreeMap = BTreeMap<String, String>;
12
13mod service;
14pub use service::*;
15
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
17#[cfg_attr(feature = "schemars", derive(JsonSchema))]
18#[serde(untagged)]
19pub enum StringOrNum {
20	String(String),
21	Num(i64),
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
25#[cfg_attr(feature = "schemars", derive(JsonSchema))]
26#[serde(untagged)]
27pub enum SingleValue {
28	String(String),
29	Bool(bool),
30	Int(i64),
31	Float(f64),
32}
33
34impl fmt::Display for SingleValue {
35	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36		match self {
37			Self::String(s) => f.write_str(s),
38			Self::Bool(b) => write!(f, "{b}"),
39			Self::Int(i) => write!(f, "{i}"),
40			Self::Float(fl) => write!(f, "{fl}"),
41		}
42	}
43}
44
45#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
46#[cfg_attr(feature = "schemars", derive(JsonSchema))]
47#[serde(untagged)]
48pub enum ListOrMap {
49	List(BTreeSet<String>),
50	Map(BTreeMap<String, String>),
51}
52
53impl ListOrMap {
54	pub fn contains(&self, key: &str) -> bool {
55		match self {
56			Self::List(list) => list.contains(key),
57			Self::Map(map) => map.contains_key(key),
58		}
59	}
60
61	pub fn get(&self, key: &str) -> Option<&String> {
62		match self {
63			Self::List(list) => list.get(key),
64			Self::Map(map) => map.get(key),
65		}
66	}
67
68	pub fn is_empty(&self) -> bool {
69		match self {
70			Self::List(btree_set) => btree_set.is_empty(),
71			Self::Map(btree_map) => btree_map.is_empty(),
72		}
73	}
74}
75
76impl Default for ListOrMap {
77	fn default() -> Self {
78		Self::List(Default::default())
79	}
80}
81
82impl Merge for ListOrMap {
83	fn merge(&mut self, other: Self) {
84		match self {
85			Self::List(left_list) => match other {
86				Self::List(other_list) => {
87					left_list.extend(other_list);
88				}
89				Self::Map(other_map) => {
90					if left_list.is_empty() || !other_map.is_empty() {
91						*self = Self::Map(other_map);
92					}
93				}
94			},
95			Self::Map(left_map) => match other {
96				Self::List(other_list) => {
97					if left_map.is_empty() || !other_list.is_empty() {
98						*self = Self::List(other_list);
99					}
100				}
101				Self::Map(other_map) => {
102					left_map.extend(other_map);
103				}
104			},
105		}
106	}
107}
108
109#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
110#[cfg_attr(feature = "schemars", derive(JsonSchema))]
111#[serde(untagged)]
112pub enum StringOrList {
113	String(String),
114	List(Vec<String>),
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
118#[cfg_attr(feature = "schemars", derive(JsonSchema))]
119#[serde(untagged)]
120pub enum StringOrSortedList {
121	String(String),
122	List(BTreeSet<String>),
123}
124
125impl StringOrSortedList {
126	pub fn is_empty(&self) -> bool {
127		match self {
128			Self::String(str) => str.is_empty(),
129			Self::List(list) => list.is_empty(),
130		}
131	}
132}
133
134impl Default for StringOrSortedList {
135	fn default() -> Self {
136		Self::List(Default::default())
137	}
138}
139
140impl Merge for StringOrSortedList {
141	fn merge(&mut self, right: Self) {
142		match self {
143			Self::String(left_string) => {
144				match right {
145					Self::List(mut right_list) => {
146						let left_string = std::mem::take(left_string);
147
148						right_list.insert(left_string);
149
150						*self = Self::List(right_list);
151					}
152					Self::String(right_string) => {
153						if left_string.is_empty() || !right_string.is_empty() {
154							*left_string = right_string;
155						}
156					}
157				};
158			}
159			Self::List(left_list) => match right {
160				Self::String(right_string) => {
161					left_list.insert(right_string);
162				}
163				Self::List(right_list) => {
164					left_list.extend(right_list);
165				}
166			},
167		}
168	}
169}
170
171/// Configuration settings for a Docker Compose file.
172#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default, Merge)]
173#[cfg_attr(feature = "schemars", derive(JsonSchema))]
174#[serde(default)]
175pub struct ComposeFile {
176	/// The top-level name property is defined by the Compose Specification as the project name to be used if you don't set one explicitly.
177	///
178	/// See more: https://docs.docker.com/reference/compose-file/version-and-name/#name-top-level-element
179	#[serde(skip_serializing_if = "Option::is_none")]
180	pub name: Option<String>,
181
182	/// Requires: Docker Compose 2.20.0 and later
183	///
184	/// The include top-level section is used to define the dependency on another Compose application, or sub-domain. Each path listed in the include section is loaded as an individual Compose application model, with its own project directory, in order to resolve relative paths.
185	///
186	/// See more: https://docs.docker.com/reference/compose-file/include/
187	#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
188	pub include: BTreeSet<Include>,
189
190	/// Defines the services for the Compose application.
191	///
192	/// See more: https://docs.docker.com/reference/compose-file/services/
193	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
194	#[cfg(feature = "presets")]
195	pub services: BTreeMap<String, ServicePresetRef>,
196	#[cfg(not(feature = "presets"))]
197	pub services: BTreeMap<String, Service>,
198
199	/// Defines or references configuration data that is granted to services in your Compose application.
200	///
201	/// See more: https://docs.docker.com/reference/compose-file/configs/
202	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
203	pub configs: BTreeMap<String, TopLevelConfig>,
204
205	/// The top-level models section declares AI models that are used by your Compose application. These models are typically pulled as OCI artifacts, run by a model runner, and exposed as an API that your service containers can consume.
206	///
207	/// See more: https://docs.docker.com/reference/compose-file/models/
208	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
209	pub models: BTreeMap<String, TopLevelModel>,
210
211	/// The named networks for the Compose application.
212	///
213	/// See more: https://docs.docker.com/reference/compose-file/networks/
214	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
215	pub networks: BTreeMap<String, TopLevelNetwork>,
216
217	/// The named secrets for the Compose application.
218	///
219	/// See more: https://docs.docker.com/reference/compose-file/secrets/
220	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
221	pub secrets: BTreeMap<String, TopLevelSecret>,
222
223	/// The named volumes for the Compose application.
224	///
225	/// See more: https://docs.docker.com/reference/compose-file/volumes/
226	#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
227	pub volumes: BTreeMap<String, TopLevelVolume>,
228
229	#[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
230	pub extensions: BTreeMap<String, Value>,
231}
232
233impl ComposeFile {
234	pub fn new() -> Self {
235		Default::default()
236	}
237}
238
239/// Compose application or sub-projects to be included.
240///
241/// See more: https://docs.docker.com/reference/compose-file/include/#long-syntax
242#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default, Eq)]
243#[cfg_attr(feature = "schemars", derive(JsonSchema))]
244#[serde(deny_unknown_fields)]
245#[serde(default)]
246pub struct IncludeSettings {
247	/// Defines the location of the Compose file(s) to be parsed and included into the local Compose model.
248	#[serde(skip_serializing_if = "Option::is_none")]
249	pub path: Option<StringOrSortedList>,
250
251	/// Defines a base path to resolve relative paths set in the Compose file. It defaults to the directory of the included Compose file.
252	#[serde(skip_serializing_if = "Option::is_none")]
253	pub project_directory: Option<String>,
254
255	/// Defines an environment file(s) to use to define default values when interpolating variables in the Compose file being parsed. It defaults to .env file in the project_directory for the Compose file being parsed.
256	///
257	/// See more: https://docs.docker.com/reference/compose-file/include/#env_file
258	#[serde(skip_serializing_if = "Option::is_none")]
259	pub env_file: Option<StringOrList>,
260}
261
262impl PartialOrd for IncludeSettings {
263	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
264		Some(self.cmp(other))
265	}
266}
267
268impl Ord for IncludeSettings {
269	fn cmp(&self, other: &Self) -> Ordering {
270		self.path.cmp(&other.path)
271	}
272}
273
274#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq)]
275#[cfg_attr(feature = "schemars", derive(JsonSchema))]
276#[serde(untagged)]
277pub enum Include {
278	Short(String),
279	Long(IncludeSettings),
280}
281
282impl Default for Include {
283	fn default() -> Self {
284		Self::Short(Default::default())
285	}
286}
287
288#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
289#[cfg_attr(feature = "schemars", derive(JsonSchema))]
290#[serde(untagged)]
291pub enum Ulimit {
292	Single(StringOrNum),
293	SoftHard {
294		soft: StringOrNum,
295		hard: StringOrNum,
296	},
297}
298
299/// Network configuration for the Compose application.
300///
301/// See more: https://docs.docker.com/reference/compose-file/networks/
302#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
303#[cfg_attr(feature = "schemars", derive(JsonSchema))]
304#[serde(deny_unknown_fields)]
305pub struct TopLevelNetwork {
306	/// If set to true, it specifies that this network’s lifecycle is maintained outside of that of the application. Compose doesn't attempt to create these networks, and returns an error if one doesn't exist.
307	///
308	/// See more: https://docs.docker.com/reference/compose-file/networks/#external
309	#[serde(skip_serializing_if = "Option::is_none")]
310	pub external: Option<bool>,
311	/// Custom name for this network.
312	///
313	/// See more: https://docs.docker.com/reference/compose-file/networks/#name
314
315	#[serde(skip_serializing_if = "Option::is_none")]
316	#[serde(default)]
317	pub name: Option<String>,
318
319	/// By default, Compose provides external connectivity to networks. internal, when set to true, lets you create an externally isolated network.
320	#[serde(skip_serializing_if = "Option::is_none")]
321	#[serde(default)]
322	pub internal: Option<bool>,
323
324	/// Specifies which driver should be used for this network. Compose returns an error if the driver is not available on the platform.
325	///
326	/// For more information on drivers and available options, see [Network drivers](https://docs.docker.com/engine/network/drivers/).
327	#[serde(skip_serializing_if = "Option::is_none")]
328	#[serde(default)]
329	pub driver: Option<String>,
330
331	/// If `attachable` is set to `true`, then standalone containers should be able to attach to this network, in addition to services. If a standalone container attaches to the network, it can communicate with services and other standalone containers that are also attached to the network.
332	#[serde(skip_serializing_if = "Option::is_none")]
333	#[serde(default)]
334	pub attachable: Option<bool>,
335
336	/// Can be used to disable IPv4 address assignment.
337	#[serde(skip_serializing_if = "Option::is_none")]
338	#[serde(default)]
339	enable_ipv4: Option<bool>,
340
341	/// Enables IPv6 address assignment.
342	#[serde(skip_serializing_if = "Option::is_none")]
343	#[serde(default)]
344	pub enable_ipv6: Option<bool>,
345
346	/// Specifies a custom IPAM configuration.
347	///
348	/// See more: https://docs.docker.com/reference/compose-file/networks/#ipam
349	#[serde(skip_serializing_if = "Option::is_none")]
350	#[serde(default)]
351	pub ipam: Option<Ipam>,
352
353	/// A list of options as key-value pairs to pass to the driver. These options are driver-dependent.
354	///
355	/// Consult the [network drivers documentation](https://docs.docker.com/engine/network/) for more information.
356	#[serde(skip_serializing_if = "Option::is_none")]
357	#[serde(default)]
358	pub driver_opts: Option<BTreeMap<String, Option<SingleValue>>>,
359
360	#[serde(skip_serializing_if = "Option::is_none")]
361	#[serde(default)]
362	pub labels: Option<ListOrMap>,
363}
364
365/// Specifies a custom IPAM configuration.
366///
367/// See more: https://docs.docker.com/reference/compose-file/networks/#ipam
368#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
369#[cfg_attr(feature = "schemars", derive(JsonSchema))]
370#[serde(deny_unknown_fields)]
371#[serde(default)]
372pub struct Ipam {
373	/// Custom IPAM driver, instead of the default.
374	#[serde(skip_serializing_if = "Option::is_none")]
375	pub driver: Option<String>,
376
377	/// A list with zero or more configuration elements.
378	#[serde(skip_serializing_if = "Option::is_none")]
379	pub config: Option<BTreeSet<IpamConfig>>,
380
381	/// Driver-specific options as a key-value mapping.
382	#[serde(skip_serializing_if = "Option::is_none")]
383	pub options: Option<StringBTreeMap>,
384}
385
386/// IPAM specific configurations.
387///
388/// See more: https://docs.docker.com/reference/compose-file/networks/#ipam
389#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
390#[cfg_attr(feature = "schemars", derive(JsonSchema))]
391#[serde(deny_unknown_fields)]
392#[serde(default)]
393pub struct IpamConfig {
394	/// Subnet in CIDR format that represents a network segment
395	#[serde(skip_serializing_if = "Option::is_none")]
396	pub subnet: Option<String>,
397
398	/// Range of IPs from which to allocate container IPs.
399	#[serde(skip_serializing_if = "Option::is_none")]
400	pub ip_range: Option<String>,
401
402	/// IPv4 or IPv6 gateway for the master subnet
403	#[serde(skip_serializing_if = "Option::is_none")]
404	pub gateway: Option<String>,
405
406	/// Auxiliary IPv4 or IPv6 addresses used by Network driver.
407	#[serde(skip_serializing_if = "Option::is_none")]
408	pub aux_addresses: Option<StringBTreeMap>,
409}
410
411impl PartialOrd for IpamConfig {
412	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
413		Some(self.cmp(other))
414	}
415}
416
417impl Ord for IpamConfig {
418	fn cmp(&self, other: &Self) -> Ordering {
419		self.subnet.cmp(&other.subnet)
420	}
421}
422
423/// Specifies a service discovery method for external clients connecting to a service. See more: https://docs.docker.com/reference/compose-file/deploy/#endpoint_mode
424#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
425#[cfg_attr(feature = "schemars", derive(JsonSchema))]
426#[serde(rename_all = "lowercase")]
427pub enum EndpointMode {
428	/// Assigns the service a virtual IP (VIP) that acts as the front end for clients to reach the service on a network. Platform routes requests between the client and nodes running the service, without client knowledge of how many nodes are participating in the service or their IP addresses or ports.
429	Vip,
430
431	/// Platform sets up DNS entries for the service such that a DNS query for the service name returns a list of IP addresses (DNS round-robin), and the client connects directly to one of these.
432	Dnsrr,
433}
434
435/// Defines the replication model used to run a service or job. See more: https://docs.docker.com/reference/compose-file/deploy/#mode
436#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
437#[cfg_attr(feature = "schemars", derive(JsonSchema))]
438#[serde(rename_all = "kebab-case")]
439pub enum DeployMode {
440	/// Ensures exactly one task continuously runs per physical node until stopped.
441	Global,
442
443	/// Continuously runs a specified number of tasks across nodes until stopped (default).
444	Replicated,
445
446	/// Executes a defined number of tasks until a completion state (exits with code 0)'.
447	/// Total tasks are determined by replicas.
448	/// Concurrency can be limited using the max-concurrent option (CLI only).
449	ReplicatedJob,
450
451	/// Executes one task per physical node with a completion state (exits with code 0).
452	/// Automatically runs on new nodes as they are added.
453	GlobalJob,
454}
455
456/// Compose Deploy Specification https://docs.docker.com/reference/compose-file/deploy
457#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
458#[cfg_attr(feature = "schemars", derive(JsonSchema))]
459#[serde(deny_unknown_fields)]
460#[serde(default)]
461pub struct Deploy {
462	/// Specifies a service discovery method for external clients connecting to a service. See more: https://docs.docker.com/reference/compose-file/deploy/#endpoint_mode
463	#[serde(skip_serializing_if = "Option::is_none")]
464	pub endpoint_mode: Option<EndpointMode>,
465
466	/// Defines the replication model used to run a service or job. See more: https://docs.docker.com/reference/compose-file/deploy/#mode
467	#[serde(skip_serializing_if = "Option::is_none")]
468	pub mode: Option<DeployMode>,
469
470	/// If the service is replicated (which is the default), replicas specifies the number of containers that should be running at any given time. See more: https://docs.docker.com/reference/compose-file/deploy/#replicas
471	#[serde(skip_serializing_if = "Option::is_none")]
472	pub replicas: Option<i64>,
473
474	/// Specifies metadata for the service. These labels are only set on the service and not on any containers for the service. This assumes the platform has some native concept of "service" that can match the Compose application model. See more: https://docs.docker.com/reference/compose-file/deploy/#labels
475	#[serde(skip_serializing_if = "Option::is_none")]
476	pub labels: Option<ListOrMap>,
477
478	/// Configures how the service should be rolled back in case of a failing update. See more: https://docs.docker.com/reference/compose-file/deploy/#rollback_config
479	#[serde(skip_serializing_if = "Option::is_none")]
480	pub rollback_config: Option<RollbackConfig>,
481
482	/// Configures how the service should be updated. Useful for configuring rolling updates. See more: https://docs.docker.com/reference/compose-file/deploy/#update_config
483	#[serde(skip_serializing_if = "Option::is_none")]
484	pub update_config: Option<UpdateConfig>,
485
486	/// Configures physical resource constraints for container to run on platform. See more: https://docs.docker.com/reference/compose-file/deploy/#resources
487	#[serde(skip_serializing_if = "Option::is_none")]
488	pub resources: Option<Resources>,
489
490	/// Configures if and how to restart containers when they exit. If restart_policy is not set, Compose considers the restart field set by the service configuration. See more: https://docs.docker.com/reference/compose-file/deploy/#restart_policy
491	#[serde(skip_serializing_if = "Option::is_none")]
492	pub restart_policy: Option<RestartPolicy>,
493
494	/// Specifies constraints and preferences for the platform to select a physical node to run service containers. See more: https://docs.docker.com/reference/compose-file/deploy/#placement
495	#[serde(skip_serializing_if = "Option::is_none")]
496	pub placement: Option<Placement>,
497}
498
499/// Resource constraints and reservations for the service.
500#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
501#[cfg_attr(feature = "schemars", derive(JsonSchema))]
502#[serde(deny_unknown_fields)]
503#[serde(default)]
504pub struct Limits {
505	/// Limit for how much of the available CPU resources, as number of cores, a container can use.
506	#[serde(skip_serializing_if = "Option::is_none")]
507	pub cpus: Option<StringOrNum>,
508
509	/// Limit on the amount of memory a container can allocate (e.g., '1g', '1024m').
510	#[serde(skip_serializing_if = "Option::is_none")]
511	pub memory: Option<String>,
512
513	/// Maximum number of PIDs available to the container.
514	#[serde(skip_serializing_if = "Option::is_none")]
515	pub pids: Option<StringOrNum>,
516}
517
518/// Resource reservations for the service containers.
519#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
520#[cfg_attr(feature = "schemars", derive(JsonSchema))]
521#[serde(default)]
522#[serde(deny_unknown_fields)]
523pub struct Reservations {
524	/// Reservation for how much of the available CPU resources, as number of cores, a container can use.
525	#[serde(skip_serializing_if = "Option::is_none")]
526	pub cpus: Option<StringOrNum>,
527
528	/// Reservation on the amount of memory a container can allocate (e.g., '1g', '1024m').
529	#[serde(skip_serializing_if = "Option::is_none")]
530	pub memory: Option<String>,
531
532	/// User-defined resources to reserve.
533	#[serde(skip_serializing_if = "Option::is_none")]
534	pub generic_resources: Option<BTreeSet<GenericResource>>,
535
536	/// Device reservations for the container.
537	#[serde(skip_serializing_if = "Option::is_none")]
538	pub devices: Option<BTreeSet<Device>>,
539}
540
541/// User-defined resources for services, allowing services to reserve specialized hardware resources.
542#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default, PartialOrd, Ord)]
543#[cfg_attr(feature = "schemars", derive(JsonSchema))]
544#[serde(default)]
545#[serde(deny_unknown_fields)]
546pub struct GenericResource {
547	/// Specification for discrete (countable) resources.
548	#[serde(skip_serializing_if = "Option::is_none")]
549	pub discrete_resource_spec: Option<DiscreteResourceSpec>,
550}
551
552/// Specification for discrete (countable) resources.
553#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
554#[cfg_attr(feature = "schemars", derive(JsonSchema))]
555#[serde(default)]
556#[serde(deny_unknown_fields)]
557pub struct DiscreteResourceSpec {
558	/// Type of resource (e.g., 'GPU', 'FPGA', 'SSD').
559	#[serde(skip_serializing_if = "Option::is_none")]
560	pub kind: Option<String>,
561
562	/// Number of resources of this kind to reserve.
563	#[serde(skip_serializing_if = "Option::is_none")]
564	pub value: Option<StringOrNum>,
565}
566
567impl PartialOrd for DiscreteResourceSpec {
568	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
569		Some(self.cmp(other))
570	}
571}
572
573impl Ord for DiscreteResourceSpec {
574	fn cmp(&self, other: &Self) -> Ordering {
575		self.kind.cmp(&other.kind)
576	}
577}
578
579/// Device reservations for containers, allowing services to access specific hardware devices.
580#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
581#[cfg_attr(feature = "schemars", derive(JsonSchema))]
582#[serde(deny_unknown_fields)]
583pub struct Device {
584	/// Device driver to use (e.g., 'nvidia').
585	#[serde(skip_serializing_if = "Option::is_none")]
586	#[serde(default)]
587	pub driver: Option<String>,
588
589	/// Number of devices of this type to reserve.
590	#[serde(skip_serializing_if = "Option::is_none")]
591	#[serde(default)]
592	pub count: Option<StringOrNum>,
593
594	/// List of specific device IDs to reserve.
595	#[serde(skip_serializing_if = "Option::is_none")]
596	#[serde(default)]
597	pub device_ids: Option<BTreeSet<String>>,
598
599	/// List of capabilities the device needs to have (e.g., 'gpu', 'compute', 'utility').
600	#[serde(skip_serializing_if = "BTreeSet::is_empty")]
601	pub capabilities: BTreeSet<String>,
602
603	/// Driver-specific options for the device.
604	#[serde(skip_serializing_if = "Option::is_none")]
605	#[serde(default)]
606	pub options: Option<ListOrMap>,
607}
608
609impl PartialOrd for Device {
610	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
611		Some(self.cmp(other))
612	}
613}
614
615impl Ord for Device {
616	fn cmp(&self, other: &Self) -> Ordering {
617		self.driver.cmp(&other.driver)
618	}
619}
620
621/// Specifies constraints and preferences for the platform to select a physical node to run service containers. See more: https://docs.docker.com/reference/compose-file/deploy/#placement
622#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
623#[cfg_attr(feature = "schemars", derive(JsonSchema))]
624#[serde(default)]
625#[serde(deny_unknown_fields)]
626pub struct Placement {
627	/// Defines a required property the platform's node must fulfill to run the service container.
628	#[serde(skip_serializing_if = "Option::is_none")]
629	pub constraints: Option<BTreeSet<String>>,
630
631	/// Defines a strategy (currently spread is the only supported strategy) to spread tasks evenly over the values of the datacenter node label.
632	#[serde(skip_serializing_if = "Option::is_none")]
633	pub preferences: Option<BTreeSet<Preferences>>,
634}
635
636/// Defines a strategy (currently spread is the only supported strategy) to spread tasks evenly over the values of the datacenter node label.
637#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
638#[cfg_attr(feature = "schemars", derive(JsonSchema))]
639#[serde(deny_unknown_fields)]
640pub struct Preferences {
641	pub spread: String,
642}
643
644/// Configures physical resource constraints for container to run on platform.
645///
646/// See more: https://docs.docker.com/reference/compose-file/deploy/#resources
647#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
648#[cfg_attr(feature = "schemars", derive(JsonSchema))]
649#[serde(deny_unknown_fields)]
650#[serde(default)]
651pub struct Resources {
652	/// The platform must prevent the container from allocating more resources.
653	#[serde(skip_serializing_if = "Option::is_none")]
654	pub limits: Option<Limits>,
655	/// The platform must guarantee the container can allocate at least the configured amount.
656	#[serde(skip_serializing_if = "Option::is_none")]
657	pub reservations: Option<Reservations>,
658}
659
660/// The condition that should trigger a restart.
661#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
662#[cfg_attr(feature = "schemars", derive(JsonSchema))]
663#[serde(rename_all = "kebab-case")]
664pub enum RestartPolicyCondition {
665	/// Containers are not automatically restarted regardless of the exit status.
666	None,
667
668	/// The container is restarted if it exits due to an error, which manifests as a non-zero exit code.
669	OnFailure,
670
671	/// (default) Containers are restarted regardless of the exit status.
672	Any,
673}
674
675/// Configures if and how to restart containers when they exit. If restart_policy is not set, Compose considers the restart field set by the service configuration.
676///
677/// See more: https://docs.docker.com/reference/compose-file/deploy/#restart_policy
678#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
679#[cfg_attr(feature = "schemars", derive(JsonSchema))]
680#[serde(default)]
681#[serde(deny_unknown_fields)]
682pub struct RestartPolicy {
683	/// The condition that should trigger a restart.
684	#[serde(skip_serializing_if = "Option::is_none")]
685	pub condition: Option<RestartPolicyCondition>,
686
687	/// How long to wait between restart attempts, specified as a duration. The default is 0, meaning restart attempts can occur immediately.
688	#[serde(skip_serializing_if = "Option::is_none")]
689	pub delay: Option<String>,
690
691	/// The maximum number of failed restart attempts allowed before giving up. (Default: unlimited retries.) A failed attempt only counts toward max_attempts if the container does not successfully restart within the time defined by window. For example, if max_attempts is set to 2 and the container fails to restart within the window on the first try, Compose continues retrying until two such failed attempts occur, even if that means trying more than twice.
692	#[serde(skip_serializing_if = "Option::is_none")]
693	pub max_attempts: Option<i64>,
694
695	/// The amount of time to wait after a restart to determine whether it was successful, specified as a duration (default: the result is evaluated immediately after the restart).
696	#[serde(skip_serializing_if = "Option::is_none")]
697	pub window: Option<String>,
698}
699
700/// What to do if an update fails.
701#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
702#[cfg_attr(feature = "schemars", derive(JsonSchema))]
703#[serde(rename_all = "kebab-case")]
704pub enum UpdateFailureAction {
705	Continue,
706	Rollback,
707	Pause,
708}
709
710/// What to do if an update fails.
711#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
712#[cfg_attr(feature = "schemars", derive(JsonSchema))]
713#[serde(rename_all = "kebab-case")]
714pub enum RollbackFailureAction {
715	Continue,
716	Pause,
717}
718
719/// What to do if an update fails.
720#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
721#[cfg_attr(feature = "schemars", derive(JsonSchema))]
722#[serde(rename_all = "kebab-case")]
723pub enum OperationsOrder {
724	StartFirst,
725	StopFirst,
726}
727
728/// Configures how the service should be rolled back in case of a failing update.
729///
730/// See more: https://docs.docker.com/reference/compose-file/deploy/#rollback_config
731#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
732#[cfg_attr(feature = "schemars", derive(JsonSchema))]
733#[serde(default)]
734#[serde(deny_unknown_fields)]
735pub struct RollbackConfig {
736	/// The number of containers to rollback at a time.
737	#[serde(skip_serializing_if = "Option::is_none")]
738	pub parallelism: Option<i64>,
739	/// The time to wait between each container group's rollback
740	#[serde(skip_serializing_if = "Option::is_none")]
741	pub delay: Option<String>,
742	/// What to do if a rollback fails. One of continue or pause (default pause)
743	#[serde(skip_serializing_if = "Option::is_none")]
744	pub failure_action: Option<RollbackFailureAction>,
745	/// Duration after each task update to monitor for failure (ns|us|ms|s|m|h) (default 0s).
746	#[serde(skip_serializing_if = "Option::is_none")]
747	pub monitor: Option<String>,
748	/// Failure rate to tolerate during a rollback.
749	#[serde(skip_serializing_if = "Option::is_none")]
750	pub max_failure_ratio: Option<f64>,
751
752	/// Order of operations during rollbacks. One of stop-first (old task is stopped before starting new one), or start-first (new task is started first, and the running tasks briefly overlap) (default stop-first).
753	#[serde(skip_serializing_if = "Option::is_none")]
754	pub order: Option<OperationsOrder>,
755}
756
757/// Configures how the service should be updated. Useful for configuring rolling updates.
758///
759/// See more: https://docs.docker.com/reference/compose-file/deploy/#update_config
760#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
761#[cfg_attr(feature = "schemars", derive(JsonSchema))]
762#[serde(deny_unknown_fields)]
763#[serde(default)]
764pub struct UpdateConfig {
765	/// The number of containers to update at a time.
766	#[serde(skip_serializing_if = "Option::is_none")]
767	pub parallelism: Option<i64>,
768	/// The time to wait between updating a group of containers.
769	#[serde(skip_serializing_if = "Option::is_none")]
770	pub delay: Option<String>,
771	/// What to do if an update fails. One of continue, rollback, or pause (default: pause).
772	#[serde(skip_serializing_if = "Option::is_none")]
773	pub failure_action: Option<UpdateFailureAction>,
774	/// Duration after each task update to monitor for failure (ns|us|ms|s|m|h) (default 0s).
775	#[serde(skip_serializing_if = "Option::is_none")]
776	pub monitor: Option<String>,
777	/// Failure rate to tolerate during an update.
778	#[serde(skip_serializing_if = "Option::is_none")]
779	pub max_failure_ratio: Option<f64>,
780
781	/// Order of operations during updates. One of stop-first (old task is stopped before starting new one), or start-first (new task is started first, and the running tasks briefly overlap) (default stop-first).
782	#[serde(skip_serializing_if = "Option::is_none")]
783	pub order: Option<OperationsOrder>,
784}
785
786/// Secret configuration for the Compose application.
787///
788/// See more: https://docs.docker.com/reference/compose-file/secrets/
789#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
790#[cfg_attr(feature = "schemars", derive(JsonSchema))]
791#[serde(deny_unknown_fields, rename_all = "snake_case")]
792pub enum TopLevelSecret {
793	/// Path to a file containing the secret value.
794	File(String),
795	/// Path to a file containing the secret value.
796	Environment(String),
797	#[serde(untagged)]
798	External {
799		/// Specifies that this secret already exists and was created outside of Compose.
800		external: bool,
801		/// Specifies the name of the external secret.
802		name: String,
803	},
804}
805
806/// Configuration for service configs or secrets, defining how they are mounted in the container.
807#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
808#[cfg_attr(feature = "schemars", derive(JsonSchema))]
809#[serde(untagged)]
810pub enum ServiceConfigOrSecret {
811	/// Name of the config or secret to grant access to.
812	String(String),
813	/// Detailed configuration for a config or secret.
814	Advanced(ServiceConfigOrSecretSettings),
815}
816
817impl PartialOrd for ServiceConfigOrSecretSettings {
818	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
819		Some(self.cmp(other))
820	}
821}
822
823impl Ord for ServiceConfigOrSecretSettings {
824	fn cmp(&self, other: &Self) -> Ordering {
825		self.source.cmp(&other.source)
826	}
827}
828
829/// Configuration for service configs or secrets, defining how they are mounted in the container.
830#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
831#[cfg_attr(feature = "schemars", derive(JsonSchema))]
832#[serde(deny_unknown_fields)]
833pub struct ServiceConfigOrSecretSettings {
834	/// Name of the config or secret as defined in the top-level configs or secrets section.
835	pub source: String,
836
837	/// Path in the container where the config or secret will be mounted. Defaults to /<source> for configs and /run/secrets/<source> for secrets.
838	#[serde(skip_serializing_if = "Option::is_none")]
839	#[serde(default)]
840	pub target: Option<String>,
841
842	/// UID of the file in the container. Default is 0 (root).
843	#[serde(skip_serializing_if = "Option::is_none")]
844	#[serde(default)]
845	pub uid: Option<String>,
846
847	/// GID of the file in the container. Default is 0 (root).
848	#[serde(skip_serializing_if = "Option::is_none")]
849	#[serde(default)]
850	pub gid: Option<String>,
851
852	/// File permission mode inside the container, in octal. Default is 0444 for configs and 0400 for secrets.
853	#[serde(skip_serializing_if = "Option::is_none")]
854	#[serde(default)]
855	pub mode: Option<StringOrNum>,
856}
857
858/// Defines or references configuration data that is granted to services in your Compose application.
859///
860/// See more: https://docs.docker.com/reference/compose-file/configs/
861#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default, Eq)]
862#[cfg_attr(feature = "schemars", derive(JsonSchema))]
863#[serde(default)]
864#[serde(deny_unknown_fields)]
865pub struct TopLevelConfig {
866	/// The name of the config object in the container engine to look up. This field can be used to reference configs that contain special characters. The name is used as is and will not be scoped with the project name.
867	#[serde(skip_serializing_if = "Option::is_none")]
868	pub name: Option<String>,
869
870	/// If set to true, external specifies that this config has already been created. Compose does not attempt to create it, and if it does not exist, an error occurs.
871	#[serde(skip_serializing_if = "Option::is_none")]
872	pub external: Option<bool>,
873
874	/// The content is created with the inlined value. Introduced in Docker Compose version 2.23.1.
875	#[serde(skip_serializing_if = "Option::is_none")]
876	pub content: Option<String>,
877
878	/// The config content is created with the value of an environment variable.
879	#[serde(skip_serializing_if = "Option::is_none")]
880	pub environment: Option<String>,
881
882	/// The config is created with the contents of the file at the specified path.
883	#[serde(skip_serializing_if = "Option::is_none")]
884	pub file: Option<String>,
885}
886
887/// Adds hostname mappings to the container network interface configuration (/etc/hosts for Linux).
888///
889/// See more: https://docs.docker.com/reference/compose-file/services/#extra_hosts
890#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
891#[cfg_attr(feature = "schemars", derive(JsonSchema))]
892#[serde(untagged)]
893pub enum ExtraHosts {
894	/// List of host:IP mappings in the format 'hostname:IP'.
895	List(BTreeSet<String>),
896
897	/// List mapping hostnames to IP addresses.
898	Map(BTreeMap<String, StringOrSortedList>),
899}
900
901impl Merge for ExtraHosts {
902	fn merge(&mut self, other: Self) {
903		if let Self::List(left_list) = self
904			&& let Self::List(right_list) = other
905		{
906			left_list.extend(right_list);
907		} else if let Self::Map(left_map) = self
908			&& let Self::Map(right_map) = other
909		{
910			left_map.extend(right_map);
911		} else {
912			*self = other;
913		}
914	}
915}
916
917/// Language Model for the Compose application.
918///
919/// See more: https://docs.docker.com/reference/compose-file/models/
920#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
921#[cfg_attr(feature = "schemars", derive(JsonSchema))]
922#[serde(deny_unknown_fields)]
923pub struct TopLevelModel {
924	/// Language Model to run.
925	pub model: String,
926
927	/// Custom name for this model.
928	#[serde(skip_serializing_if = "Option::is_none")]
929	#[serde(default)]
930	pub name: Option<String>,
931
932	/// The context window size for the model.
933	#[serde(skip_serializing_if = "Option::is_none")]
934	#[serde(default)]
935	pub context_size: Option<u64>,
936
937	/// Raw runtime flags to pass to the inference engine.
938	#[serde(skip_serializing_if = "Option::is_none")]
939	#[serde(default)]
940	pub runtime_flags: Option<Vec<String>>,
941}
942
943impl PartialOrd for TopLevelModel {
944	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
945		Some(self.cmp(other))
946	}
947}
948
949impl Ord for TopLevelModel {
950	fn cmp(&self, other: &Self) -> Ordering {
951		self.name
952			.cmp(&other.name)
953			.then_with(|| self.model.cmp(&other.model))
954	}
955}
956
957/// Volume configuration for the Compose application.
958///
959/// See more: https://docs.docker.com/reference/compose-file/volumes/
960#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
961#[cfg_attr(feature = "schemars", derive(JsonSchema))]
962#[serde(deny_unknown_fields)]
963pub struct TopLevelVolume {
964	/// If set to true, it specifies that this volume already exists on the platform and its lifecycle is managed outside of that of the application.
965	///
966	/// See more: https://docs.docker.com/reference/compose-file/volumes/#external
967	#[serde(default, skip_serializing_if = "Option::is_none")]
968	pub external: Option<bool>,
969
970	/// Sets a custom name for a volume.
971	///
972	/// See more: https://docs.docker.com/reference/compose-file/volumes/#name
973	#[serde(skip_serializing_if = "Option::is_none")]
974	#[serde(default)]
975	pub name: Option<String>,
976
977	/// Specifies which volume driver should be used. If the driver is not available, Compose returns an error and doesn't deploy the application.
978	#[serde(skip_serializing_if = "Option::is_none")]
979	#[serde(default)]
980	pub driver: Option<String>,
981
982	/// Specifies a list of options as key-value pairs to pass to the driver for this volume. The options are driver-dependent.
983	#[serde(skip_serializing_if = "BTreeMap::is_empty")]
984	#[serde(default)]
985	pub driver_opts: BTreeMap<String, SingleValue>,
986
987	/// Labels are used to add metadata to volumes. You can use either an array or a dictionary.
988	///
989	/// It's recommended that you use reverse-DNS notation to prevent your labels from conflicting with those used by other software.
990	///
991	/// See more: https://docs.docker.com/reference/compose-file/volumes/#labels
992	#[serde(skip_serializing_if = "Option::is_none")]
993	#[serde(default)]
994	pub labels: Option<ListOrMap>,
995}