This commit is contained in:
2026-03-02 05:05:33 +01:00
parent 08c1d0c605
commit 6cc6fe6fc7
31 changed files with 1240 additions and 0 deletions

23
crete/src/bin/schemas.rs Normal file
View File

@@ -0,0 +1,23 @@
use std::{fs::File, io::Write};
use crete::{cluster::Cluster, node::OptionalNode};
use repo_path_lib::repo_dir;
use schemars::{JsonSchema, schema_for};
fn write<T>(name: &str)
where
T: JsonSchema,
{
let mut path = repo_dir().join("schemas").join(name);
path.add_extension("json");
let mut file = File::create(path).unwrap();
let schema = serde_json::to_string_pretty(&schema_for!(T)).unwrap();
file.write_all(schema.as_bytes()).unwrap();
}
// TODO: Create directory if it does not exist
fn main() {
write::<Cluster>("cluster");
write::<OptionalNode>("node");
}

64
crete/src/cluster.rs Normal file
View File

@@ -0,0 +1,64 @@
use std::net::Ipv4Addr;
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{
base_dir,
node::{Node, OptionalNode},
patch::Patches,
};
#[derive(Debug, Deserialize, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Base {
#[serde(default)]
pub(crate) kernel_args: Vec<String>,
#[serde(default)]
pub(crate) patches: Patches,
}
// TODO: Extra version validation? Make sure it exists?
#[derive(Debug, Deserialize, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Version {
// kubernetes: semver::Version,
// talos: semver::Version,
kubernetes: String,
talos: String,
}
#[derive(Debug, Deserialize, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Cluster {
#[serde(skip_deserializing)]
name: String,
version: Version,
nodes: Vec<String>,
production: bool,
control_plane_ip: Ipv4Addr,
#[serde(default)]
pub(crate) default: OptionalNode,
pub(crate) base: Base,
// pub secrets_file: PathBuf,
}
impl Cluster {
pub fn get(cluster_name: &str) -> Self {
let mut path = base_dir().join("clusters").join(cluster_name);
path.add_extension("yaml");
let content = std::fs::read_to_string(path).unwrap();
let mut cluster: Self = serde_yaml::from_str(&content).unwrap();
cluster.name = cluster_name.to_string();
cluster
}
pub fn nodes(&self) -> Vec<Node> {
self.nodes
.iter()
.map(|node_name| Node::get(self.clone(), node_name))
.collect()
}
}

11
crete/src/lib.rs Normal file
View File

@@ -0,0 +1,11 @@
pub mod cluster;
pub mod node;
pub mod patch;
use std::path::PathBuf;
use repo_path_lib::repo_dir;
fn base_dir() -> PathBuf {
repo_dir().join("talos")
}

7
crete/src/main.rs Normal file
View File

@@ -0,0 +1,7 @@
use crete::cluster::Cluster;
fn main() {
let cluster = Cluster::get("testing");
println!("{:#?}", cluster.nodes());
}

119
crete/src/node.rs Normal file
View File

@@ -0,0 +1,119 @@
use std::net::Ipv4Addr;
use optional_struct::{Applicable, optional_struct};
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{
base_dir,
cluster::Cluster,
patch::{OptionalPatches, Patches},
};
#[derive(Debug, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum NodeType {
Worker,
ControlPlane,
}
#[derive(Debug, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum NodeArch {
Amd64,
}
#[optional_struct]
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct Network {
interface: String,
ip: Ipv4Addr,
netmask: Ipv4Addr,
gateway: Ipv4Addr,
dns: [Ipv4Addr; 2],
advertise_routes: bool,
}
#[optional_struct]
#[derive(Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct Install {
auto: bool,
disk: String,
serial: Option<String>,
}
#[optional_struct]
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Node {
#[serde(skip_deserializing)]
hostname: String,
arch: NodeArch,
r#type: NodeType,
#[optional_rename(OptionalNetwork)]
network: Network,
// TODO: Type that verifies url?
ntp: String,
#[optional_rename(OptionalInstall)]
#[serde(default)]
install: Install,
kernel_args: Vec<String>,
#[optional_rename(OptionalPatches)]
#[serde(default)]
patches: Patches,
// TODO: Per machine patches, append to global list of patches
// Any patches are specified under default they will get overridden
}
impl Node {
pub fn get(mut cluster: Cluster, node_name: &str) -> 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: OptionalPatches {
all: vec![].into(),
control_plane: vec![].into(),
},
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)
// Apply hostname based on filename
.apply(named)
// Override node specific settings
.apply(node)
.try_into()
.unwrap();
// Prepend the cluster base values
cluster.base.kernel_args.extend(node.kernel_args);
node.kernel_args = cluster.base.kernel_args;
cluster.base.patches.extend(node.patches);
node.patches = cluster.base.patches;
node
}
}

69
crete/src/patch.rs Normal file
View File

@@ -0,0 +1,69 @@
use optional_struct::optional_struct;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize};
use crate::base_dir;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Patch(String);
impl JsonSchema for Patch {
fn schema_name() -> std::borrow::Cow<'static, str> {
String::schema_name()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
String::json_schema(generator)
}
fn inline_schema() -> bool {
String::inline_schema()
}
fn schema_id() -> std::borrow::Cow<'static, str> {
String::schema_name()
}
}
impl Patch {
pub fn get(patch_name: impl AsRef<str>) -> Self {
let mut path = base_dir().join("patches").join(patch_name.as_ref());
path.add_extension("yaml");
let content = std::fs::read_to_string(path).unwrap();
Self(content)
}
pub fn resolve(&self) -> serde_yaml::Result<ResolvedPatch> {
Ok(ResolvedPatch(serde_yaml::from_str(&self.0)?))
}
}
impl<'de> Deserialize<'de> for Patch {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let name: &str = Deserialize::deserialize(deserializer)?;
Ok(Self::get(name))
}
}
#[derive(Debug, Serialize)]
pub struct ResolvedPatch(serde_yaml::Value);
#[optional_struct]
#[derive(Debug, 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>,
}
impl Patches {
pub(crate) fn extend(&mut self, other: Patches) {
self.all.extend(other.all);
self.control_plane.extend(other.control_plane);
}
}