Files
metal/crete/src/patch.rs
2026-03-05 03:51:03 +01:00

118 lines
3.4 KiB
Rust

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<Patch>,
pub(crate) control_plane: Vec<Patch>,
}
fn render(patches: Vec<Patch>, env: &PatchEnv, cluster: &Cluster, node: &Node) -> Vec<Patch> {
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),
}
}
}