use std::net::Ipv4Addr; use optional_struct::{Applicable, optional_struct}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ base_dir, cluster::Cluster, patch::{OptionalPatches, PatchEnv, Patches}, schematic::Schematic, secret::Secret, }; #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "camelCase")] enum NodeType { Worker, #[serde(rename(serialize = "controlplane"))] ControlPlane, } #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)] #[serde(rename_all = "camelCase")] enum NodeArch { Amd64, } #[optional_struct] #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] struct Tailscale { auth_key: Secret, advertise_routes: bool, #[serde(default)] server: Option, } #[optional_struct] #[derive(Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] struct Network { interface: String, ip: Ipv4Addr, netmask: Ipv4Addr, gateway: Ipv4Addr, dns: [Ipv4Addr; 2], #[optional_rename(OptionalTailscale)] #[optional_wrap] tailscale: Tailscale, } #[optional_struct] #[derive(Debug, Serialize, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase", deny_unknown_fields)] struct Install { auto: bool, disk: String, serial: Option, } #[optional_struct] #[derive(Debug, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Node { #[serde(skip_deserializing)] hostname: String, arch: NodeArch, schematic: Schematic, r#type: NodeType, #[optional_rename(OptionalNetwork)] #[optional_wrap] network: Network, ntp: String, #[optional_rename(OptionalInstall)] #[optional_wrap] install: Install, kernel_args: Vec, #[optional_rename(OptionalPatches)] #[optional_wrap] pub(crate) patches: Patches, sops: Secret, } impl Node { pub fn get(node_name: &str, env: &PatchEnv, cluster: &Cluster) -> Self { let mut path = base_dir().join("nodes").join(node_name); let named = OptionalNode { hostname: Some( path.file_name() .expect("Path should be valid") .to_string_lossy() .to_string(), ), ..OptionalNode::default() }; path.add_extension("yaml"); let content = std::fs::read_to_string(path).unwrap(); let node: OptionalNode = serde_yaml::from_str(&content).unwrap(); // We want all vectors to be empty vectors by default // Sadly we have to this manually // TODO: Find a better way of doing this let default = OptionalNode { patches: Some(OptionalPatches { all: Some(vec![]), control_plane: Some(vec![]), }), kernel_args: vec![].into(), ..Default::default() }; // Combine all the optional node parts into complete struct let mut node: Node = default // Apply cluster default settings .apply(cluster.default.clone()) // Apply hostname based on filename .apply(named) // Override node specific settings .apply(node) .try_into() .unwrap(); // Prepend the cluster base values let mut kernel_args = cluster.base.kernel_args.clone(); kernel_args.extend(node.kernel_args); node.kernel_args = kernel_args; let patches = cluster.base.patches.clone().extend(node.patches); node.patches = patches; // Render patches node.patches = node.patches.clone().render(env, cluster, &node); node } }