use std::{ fmt::{Display, Formatter}, net::Ipv4Addr, str::FromStr, }; use minijinja::{Environment, context}; use optional_struct::optional_struct; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; use crate::{base_dir, cluster::Cluster, node::Node}; pub struct PatchEnv<'e>(Environment<'e>); impl<'e> Default for PatchEnv<'e> { fn default() -> Self { let mut env = Environment::new(); env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict); let dir = base_dir().join("patches"); for entry in WalkDir::new(&dir) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.metadata().unwrap().is_file()) { let source = std::fs::read_to_string(entry.path()).unwrap(); let name = entry .path() .strip_prefix(&dir) .unwrap() .with_extension("") .to_str() .unwrap() .to_owned(); env.add_filter("to_prefix", |netmask: String| { let netmask = Ipv4Addr::from_str(&netmask).map_err(|err| { minijinja::Error::new(minijinja::ErrorKind::InvalidOperation, err.to_string()) })?; let mask = netmask.to_bits(); let prefix = mask.leading_ones(); if mask.checked_shl(prefix).unwrap_or(0) == 0 { Ok(prefix as u8) } else { Err(minijinja::Error::new( minijinja::ErrorKind::InvalidOperation, "invalid IP prefix length", )) } }); env.add_template_owned(name, source).unwrap(); } Self(env) } } #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)] #[serde(untagged)] pub(crate) enum Patch { Name(String), #[serde(skip)] Resolved(serde_yaml::Value), } #[optional_struct] #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq, Default)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Patches { pub(crate) all: Vec, pub(crate) control_plane: Vec, } fn render(patches: Vec, env: &PatchEnv, cluster: &Cluster, node: &Node) -> Vec { patches .into_iter() .map(|patch| { if let Patch::Name(name) = patch { Patch::Resolved( serde_yaml::from_str( &env.0 .get_template(&name) .unwrap() .render(context!(node, cluster)) .unwrap(), ) .unwrap(), ) } else { patch } }) .collect() } impl Patches { pub(crate) fn extend(mut self, other: Self) -> Self { self.all.extend(other.all); self.control_plane.extend(other.control_plane); Self { all: self.all, control_plane: self.control_plane, } } pub(crate) fn render(self, env: &PatchEnv, cluster: &Cluster, node: &Node) -> Self { Self { all: render(self.all.clone(), env, cluster, node), control_plane: render(self.control_plane.clone(), env, cluster, node), } } }