use indexmap::IndexMap;
use schemars::JsonSchema;
use serde::Deserialize;
use crate::model::{External, Target, Virtiofs, Winrm};
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Overrides {
#[serde(default)]
pub docker: Option<DockerOverrides>,
#[serde(default)]
pub kubernetes: Option<KubernetesOverrides>,
#[serde(default)]
pub vagrant: Option<VagrantOverrides>,
#[serde(default)]
pub proxmox: Option<ProxmoxOverrides>,
#[serde(default)]
pub esxi: Option<EsxiOverrides>,
#[serde(default)]
pub aws: Option<CloudOverrides>,
#[serde(default)]
pub azure: Option<CloudOverrides>,
#[serde(default)]
pub gcp: Option<CloudOverrides>,
#[serde(default)]
pub digitalocean: Option<CloudOverrides>,
#[serde(default)]
pub linode: Option<CloudOverrides>,
#[serde(default)]
pub oci: Option<CloudOverrides>,
#[serde(default)]
pub external: Option<ExternalOverrides>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DockerOverrides {
#[serde(default)]
pub networks: IndexMap<String, DockerNetwork>,
#[serde(default)]
pub volumes: IndexMap<String, ExternalVolume>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DockerNetwork {
#[serde(default)]
pub cidr: Option<String>,
#[serde(default)]
pub external: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExternalVolume {
pub external: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExternalNetwork {
pub external: String,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct KubernetesOverrides {
#[serde(default)]
pub volumes: IndexMap<String, ExternalVolume>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct VagrantOverrides {
#[serde(default)]
pub machines: IndexMap<String, VagrantMachine>,
#[serde(default)]
pub controller: Option<VagrantBox>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct VagrantMachine {
#[serde(default, rename = "box")]
pub box_name: Option<String>,
#[serde(default)]
pub box_version: Option<String>,
#[serde(default)]
pub box_url: Option<String>,
#[serde(default)]
pub box_server_url: Option<String>,
#[serde(default)]
pub boxes: Option<ProviderBoxes>,
#[serde(default)]
pub winrm: Option<Winrm>,
#[serde(default)]
pub winssh: bool,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ProviderBoxes {
#[serde(default)]
pub qemu: Option<String>,
#[serde(default)]
pub hyperv: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct VagrantBox {
#[serde(default, rename = "box")]
pub box_name: Option<String>,
#[serde(default)]
pub box_version: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ProxmoxOverrides {
#[serde(default)]
pub machines: IndexMap<String, ProxmoxMachine>,
#[serde(default)]
pub volumes: IndexMap<String, ProxmoxVolume>,
#[serde(default)]
pub networks: IndexMap<String, ExternalNetwork>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ProxmoxMachine {
#[serde(default)]
pub image: Option<String>,
#[serde(default)]
pub lxc: bool,
}
#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ProxmoxVolume {
pub virtiofs: Virtiofs,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EsxiOverrides {
#[serde(default)]
pub networks: IndexMap<String, ExternalNetwork>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CloudOverrides {
#[serde(default)]
pub machines: IndexMap<String, CloudMachine>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CloudMachine {
pub image: String,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExternalOverrides {
#[serde(default)]
pub machines: IndexMap<String, ExternalMachine>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExternalMachine {
pub ssh: External,
}
impl Overrides {
#[must_use]
pub fn clouds(&self) -> [(&'static str, Option<&CloudOverrides>); 6] {
[
("aws", self.aws.as_ref()),
("azure", self.azure.as_ref()),
("gcp", self.gcp.as_ref()),
("digitalocean", self.digitalocean.as_ref()),
("linode", self.linode.as_ref()),
("oci", self.oci.as_ref()),
]
}
#[must_use]
pub fn targets_of(key: &str) -> &'static [Target] {
match key {
"docker" => &[Target::Docker, Target::Hosted, Target::DockerVm, Target::CloudDocker],
"kubernetes" => &[Target::Kubernetes],
"vagrant" => &[Target::Vagrant, Target::Hybrid],
"proxmox" => &[Target::Proxmox],
"esxi" => &[Target::Esxi],
"external" => &[Target::External],
"aws" | "azure" | "gcp" | "digitalocean" | "linode" | "oci" => &[Target::CloudVm],
_ => &[],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_override_key_applies_to_its_targets() {
assert_eq!(
Overrides::targets_of("docker"),
[Target::Docker, Target::Hosted, Target::DockerVm, Target::CloudDocker]
);
assert_eq!(Overrides::targets_of("kubernetes"), [Target::Kubernetes]);
assert_eq!(Overrides::targets_of("vagrant"), [Target::Vagrant, Target::Hybrid]);
assert_eq!(Overrides::targets_of("proxmox"), [Target::Proxmox]);
assert_eq!(Overrides::targets_of("esxi"), [Target::Esxi]);
assert_eq!(Overrides::targets_of("external"), [Target::External]);
for cloud in ["aws", "azure", "gcp", "digitalocean", "linode", "oci"] {
assert_eq!(Overrides::targets_of(cloud), [Target::CloudVm], "{cloud}");
}
assert_eq!(Overrides::targets_of("podman"), []);
}
#[test]
fn each_target_has_its_own_fields() {
let o: Overrides = serde_yaml_ng::from_str(
"{ docker: { networks: { office: { cidr: 10.70.0.0/24 } } }, vagrant: { machines: { portal: { box: example/debian, box_version: '1' } } }, proxmox: { machines: { status: { lxc: true } }, volumes: { uploads: { virtiofs: {} } } }, aws: { machines: { portal: { image: ami-1 } } } }",
)
.unwrap();
assert_eq!(o.vagrant.as_ref().unwrap().machines["portal"].box_name.as_deref(), Some("example/debian"));
assert!(o.proxmox.as_ref().unwrap().machines["status"].lxc);
assert_eq!(o.clouds()[0].1.unwrap().machines["portal"].image, "ami-1");
}
#[test]
fn a_field_another_target_has_is_refused() {
let e = serde_yaml_ng::from_str::<Overrides>("{ docker: { machines: { web: { box: x } } } }").unwrap_err();
assert!(e.to_string().contains("unknown field `machines`"));
assert!(serde_yaml_ng::from_str::<Overrides>("{ podman: {} }").is_err());
}
}