use derive_builder::*;
#[cfg(feature = "indexmap")]
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
#[cfg(not(feature = "indexmap"))]
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
use std::str::FromStr;
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum ComposeFile {
V2Plus(Compose),
#[cfg(feature = "indexmap")]
V1(IndexMap<String, Service>),
#[cfg(not(feature = "indexmap"))]
V1(HashMap<String, Service>),
Single(SingleService),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
pub struct SingleService {
pub service: Service,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
pub struct Compose {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub services: Option<Services>,
#[serde(skip_serializing_if = "Option::is_none")]
pub volumes: Option<TopLevelVolumes>,
#[serde(skip_serializing_if = "Option::is_none")]
pub networks: Option<ComposeNetworks>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<Service>,
#[cfg(feature = "indexmap")]
#[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
pub extensions: IndexMap<Extension, Value>,
#[cfg(not(feature = "indexmap"))]
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
pub extensions: HashMap<Extension, Value>,
}
impl Compose {
pub fn new() -> Self {
Default::default()
}
}
#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
#[builder(setter(into), default)]
pub struct Service {
#[serde(skip_serializing_if = "Option::is_none")]
pub hostname: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub privileged: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub healthcheck: Option<Healthcheck>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deploy: Option<Deploy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub container_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "build")]
pub build_: Option<BuildStep>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ports: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<Environment>,
#[serde(skip_serializing_if = "Option::is_none")]
pub network_mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub devices: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub restart: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<Labels>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ulimits: Option<Ulimits>,
#[serde(skip_serializing_if = "Option::is_none")]
pub volumes: Option<Volumes>,
#[serde(skip_serializing_if = "Option::is_none")]
pub networks: Option<Networks>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cap_add: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub depends_on: Option<DependsOnOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<Command>,
#[serde(skip_serializing_if = "Option::is_none")]
pub entrypoint: Option<Entrypoint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub env_file: Option<EnvFile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_grace_period: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profiles: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub links: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ipc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub net: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_signal: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub working_dir: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub expose: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub volumes_from: Vec<String>,
#[cfg(feature = "indexmap")]
#[serde(skip_serializing_if = "Option::is_none")]
pub extends: Option<IndexMap<String, String>>,
#[cfg(not(feature = "indexmap"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub extends: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logging: Option<LoggingParameters>,
#[serde(default, skip_serializing_if = "is_zero")]
pub scale: i64,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub init: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub stdin_open: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub shm_size: Option<String>,
#[cfg(feature = "indexmap")]
#[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
pub extensions: IndexMap<Extension, Value>,
#[cfg(not(feature = "indexmap"))]
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
pub extensions: HashMap<Extension, Value>,
}
impl Service {
pub fn image(&self) -> &str {
self.image.as_deref().unwrap_or_default()
}
pub fn network_mode(&self) -> &str {
self.network_mode.as_deref().unwrap_or_default()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(untagged)]
pub enum EnvFile {
Simple(String),
List(Vec<String>),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum DependsOnOptions {
Simple(Vec<String>),
#[cfg(feature = "indexmap")]
Conditional(IndexMap<String, DependsCondition>),
#[cfg(not(feature = "indexmap"))]
Conditional(HashMap<String, DependsCondition>),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct DependsCondition {
pub condition: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct LoggingParameters {
pub driver: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<LoggingParameterOptions>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct LoggingParameterOptions {
#[serde(rename = "max-size")]
pub max_size: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum Environment {
List(Vec<String>),
#[cfg(feature = "indexmap")]
KvPair(IndexMap<String, Option<EnvTypes>>),
#[cfg(not(feature = "indexmap"))]
KvPair(HashMap<String, Option<EnvTypes>>),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash)]
#[serde(untagged)]
pub enum EnvTypes {
String(String),
Number(serde_yaml::Number),
Bool(bool),
Null,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
#[serde(try_from = "String")]
pub struct Extension(String);
impl FromStr for Extension {
type Err = ExtensionParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let owned = s.to_owned();
Extension::try_from(owned)
}
}
impl TryFrom<String> for Extension {
type Error = ExtensionParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
if s.starts_with("x-") {
Ok(Self(s))
} else {
Err(ExtensionParseError(s))
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct ExtensionParseError(pub String);
impl fmt::Display for ExtensionParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
}
}
impl std::error::Error for ExtensionParseError {}
#[cfg(feature = "indexmap")]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Services(pub IndexMap<String, Option<Service>>);
#[cfg(not(feature = "indexmap"))]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Services(pub HashMap<String, Option<Service>>);
#[cfg(feature = "indexmap")]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Labels(pub IndexMap<String, String>);
#[cfg(not(feature = "indexmap"))]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Labels(pub HashMap<String, String>);
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct Ulimits {
pub nofile: Nofile,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct Nofile {
pub soft: i64,
pub hard: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum Networks {
Simple(Vec<String>),
Advanced(AdvancedNetworks),
}
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum BuildStep {
Simple(String),
Advanced(AdvancedBuildStep),
}
#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
#[serde(deny_unknown_fields)]
#[builder(setter(into), default)]
pub struct AdvancedBuildStep {
pub context: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dockerfile: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub args: Option<BuildArgs>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub shm_size: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub network: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_from: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub labels: Option<BuildLabels>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum BuildLabels {
List(Vec<String>),
#[cfg(feature = "indexmap")]
KvPair(IndexMap<String, Option<String>>),
#[cfg(not(feature = "indexmap"))]
KvPair(HashMap<String, Option<String>>),
}
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum BuildArgs {
Simple(String),
List(Vec<String>),
#[cfg(feature = "indexmap")]
KvPair(IndexMap<String, String>),
#[cfg(not(feature = "indexmap"))]
KvPair(HashMap<String, String>),
}
#[cfg(feature = "indexmap")]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct AdvancedNetworks(pub IndexMap<String, Option<AdvancedNetworkSettings>>);
#[cfg(not(feature = "indexmap"))]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct AdvancedNetworks(pub HashMap<String, Option<AdvancedNetworkSettings>>);
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct AdvancedNetworkSettings {
pub ipv4_address: String,
}
#[cfg(feature = "indexmap")]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct ComposeVolumes(pub IndexMap<String, Option<IndexMap<String, String>>>);
#[cfg(not(feature = "indexmap"))]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct ComposeVolumes(pub HashMap<String, Option<HashMap<String, String>>>);
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum TopLevelVolumes {
CV(ComposeVolumes),
Labelled(LabelledComposeVolumes),
}
#[cfg(feature = "indexmap")]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct LabelledComposeVolumes(pub IndexMap<String, VolumeLabels>);
#[cfg(not(feature = "indexmap"))]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct LabelledComposeVolumes(pub HashMap<String, VolumeLabels>);
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct VolumeLabels {
#[cfg(feature = "indexmap")]
pub labels: IndexMap<String, String>,
#[cfg(not(feature = "indexmap"))]
pub labels: HashMap<String, String>,
}
#[cfg(feature = "indexmap")]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct ComposeNetworks(pub IndexMap<String, NetworkSettingsOptions>);
#[cfg(not(feature = "indexmap"))]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct ComposeNetworks(pub HashMap<String, NetworkSettingsOptions>);
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum NetworkSettingsOptions {
Settings(NetworkSettings),
#[cfg(feature = "indexmap")]
Empty(IndexMap<(), ()>),
#[cfg(not(feature = "indexmap"))]
Empty(HashMap<(), ()>),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(untagged)]
pub enum ComposeNetwork {
Detailed(ComposeNetworkSettingDetails),
Bool(bool),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ComposeNetworkSettingDetails {
pub name: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct ExternalNetworkSettingBool(bool);
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
#[serde(deny_unknown_fields)]
pub struct NetworkSettings {
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub attachable: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub driver: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub internal: Option<ComposeNetwork>,
#[serde(skip_serializing_if = "Option::is_none")]
pub external: Option<ComposeNetwork>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ipam: Option<Ipam>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct Ipam {
pub config: Vec<IpamConfig>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct IpamConfig {
pub subnet: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[serde(deny_unknown_fields)]
pub struct Deploy {
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub replicas: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_config: Option<UpdateConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<Resources>,
#[serde(skip_serializing_if = "Option::is_none")]
pub restart_policy: Option<RestartPolicy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub placement: Option<Placement>,
}
fn is_zero(val: &i64) -> bool {
*val == 0
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct Healthcheck {
#[serde(skip_serializing_if = "Option::is_none")]
pub test: Option<HealthcheckTest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interval: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
#[serde(default, skip_serializing_if = "is_zero")]
pub retries: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_period: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub disable: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(untagged)]
pub enum HealthcheckTest {
Single(String),
Multiple(Vec<String>),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
#[serde(deny_unknown_fields)]
pub struct Limits {
#[serde(skip_serializing_if = "Option::is_none")]
pub cpus: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
#[serde(deny_unknown_fields)]
pub struct Placement {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub constraints: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub preferences: Vec<Preferences>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct Preferences {
pub spread: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
#[serde(deny_unknown_fields)]
pub struct Resources {
pub limits: Option<Limits>,
pub reservations: Option<Limits>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
#[serde(deny_unknown_fields)]
pub struct RestartPolicy {
#[serde(skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_attempts: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[serde(deny_unknown_fields)]
pub struct UpdateConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub parallelism: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_action: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub monitor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_failure_ratio: Option<f64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(untagged)]
pub enum Volumes {
Simple(Vec<String>),
Advanced(Vec<AdvancedVolumes>),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(deny_unknown_fields)]
pub struct AdvancedVolumes {
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
pub target: String,
#[serde(rename = "type")]
pub _type: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub read_only: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub volume: Option<Volume>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
#[serde(deny_unknown_fields)]
pub struct Volume {
pub nocopy: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(untagged)]
pub enum Command {
Simple(String),
Args(Vec<String>),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(untagged)]
pub enum Entrypoint {
Simple(String),
List(Vec<String>),
}