use std::net::Ipv4Addr; use std::path::PathBuf; use std::str::FromStr; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; use crate::get_talos_config_path; use crate::node::{Node, OptionalNode}; use crate::patch::Patches; #[optional_struct] #[derive(Debug, Deserialize, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Base { #[serde(default)] pub(crate) kernel_args: Vec, #[serde(default)] pub(crate) patches: Patches, } #[optional_struct] #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Version { kubernetes: semver::Version, talos: semver::Version, } #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub enum ClusterEnv { Production, Staging, } #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)] #[serde(untagged)] pub enum NodeEntry { Name(String), #[serde(skip_deserializing)] Node(Box), } #[optional_struct] #[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Cluster { #[serde(skip_deserializing)] name: String, #[optional_rename(OptionalVersion)] #[optional_wrap] version: Version, nodes: Vec, cluster_env: ClusterEnv, control_plane_ip: Ipv4Addr, secrets_file: PathBuf, #[serde(default, skip_serializing)] #[optional_skip_wrap] pub(crate) default: OptionalNode, #[optional_rename(OptionalBase)] #[optional_wrap] #[serde(skip_serializing)] pub(crate) base: Base, // pub secrets_file: PathBuf, } impl Cluster { pub fn get(cluster_name: &str, env: &Environment) -> Self { let path = get_talos_config_path() .join("clusters") .join("default.yaml"); let default: OptionalCluster = if path.exists() { let content = std::fs::read_to_string(path).unwrap(); serde_yaml::from_str(&content).unwrap() } else { Default::default() }; let mut path = get_talos_config_path().join("clusters").join(cluster_name); path.add_extension("yaml"); let content = std::fs::read_to_string(path).unwrap(); let mut cluster: OptionalCluster = serde_yaml::from_str(&content).unwrap(); cluster.name = Some(cluster_name.to_string()); // For some reason apply on the cluster does not properly apply to the default settings... // So we manually apply it here first cluster.default = default.default.clone().apply(cluster.default); let mut cluster: Self = default.apply(cluster).try_into().unwrap(); cluster.nodes = cluster .nodes .clone() .into_iter() .map(|node_entry| { if let NodeEntry::Name(name) = node_entry { NodeEntry::Node(Box::new(Node::get(&name, env, &cluster))) } else { node_entry } }) .collect(); cluster.secrets_file = get_talos_config_path() .join("secrets") .join(cluster.secrets_file) .absolute() .unwrap(); cluster } } pub fn get_clusters(env: &Environment) -> Vec { WalkDir::new(get_talos_config_path().join("clusters")) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.metadata().unwrap().is_file()) .filter_map(|e| { let mut path = PathBuf::from_str(&e.file_name().to_string_lossy()).unwrap(); path.set_extension(""); let name = path.to_string_lossy().to_string(); if name == "default" { return None; } Some(Cluster::get(&name, env)) }) .collect() }