Files
crete/src/cluster.rs
T

218 lines
6.3 KiB
Rust

use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::process::Command;
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::environment::PathEnvironment;
use crate::node::{Node, OptionalNodeDeserialize};
use crate::patch::Patches;
use crate::{get_configs_path, get_talos_path};
#[optional_struct]
#[derive(Debug, Deserialize, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub(crate) struct Base {
#[serde(default)]
pub(crate) kernel_args: Vec<String>,
#[serde(default)]
pub(crate) patches: Patches<String>,
}
#[optional_struct]
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Version {
kubernetes: semver::Version,
talos: semver::Version,
}
impl Version {
pub(crate) fn kubernetes(&self) -> String {
format!("v{}", self.kubernetes)
}
pub(crate) fn talos(&self) -> String {
format!("v{}", self.talos)
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum ClusterEnv {
Production,
Staging,
}
#[optional_struct]
#[derive(Debug, Deserialize, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct ClusterDeserialize {
#[optional_rename(OptionalVersion)]
#[optional_wrap]
version: Version,
nodes: Vec<String>,
cluster_env: ClusterEnv,
control_plane_ip: Ipv4Addr,
secrets_file: PathBuf,
#[serde(default)]
#[optional_skip_wrap]
default: OptionalNodeDeserialize,
#[optional_rename(OptionalBase)]
#[optional_wrap]
base: Base,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Cluster {
pub(crate) name: String,
pub(crate) version: Version,
nodes: Vec<Node>,
cluster_env: ClusterEnv,
pub(crate) control_plane_ip: Ipv4Addr,
pub(crate) secrets_file: PathBuf,
}
impl Cluster {
pub(crate) fn get(cluster_name: &str, env: &Environment) -> Self {
let path = get_talos_path().join("clusters").join("default.yaml");
let default: OptionalClusterDeserialize = 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_path().join("clusters").join(cluster_name);
path.add_extension("yaml");
let content = std::fs::read_to_string(path).unwrap();
let mut cluster: OptionalClusterDeserialize = serde_yaml::from_str(&content).unwrap();
// 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 cluster: ClusterDeserialize = default.apply(cluster).try_into().unwrap();
let nodes = cluster.nodes;
let default = cluster.default;
let base = cluster.base;
let secrets_file = get_talos_path()
.join("secrets")
.join(cluster.secrets_file)
.absolute()
.unwrap();
let cluster = Self {
name: cluster_name.into(),
version: cluster.version,
nodes: vec![],
cluster_env: cluster.cluster_env,
control_plane_ip: cluster.control_plane_ip,
secrets_file,
};
let nodes = nodes
.iter()
.map(|name| Node::get(name, env, &cluster, &default, &base))
.collect();
Self { nodes, ..cluster }
}
pub fn get_all() -> Vec<Cluster> {
let env = PathEnvironment::new_patches();
WalkDir::new(get_talos_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()
}
pub fn nodes(&self) -> &[Node] {
&self.nodes
}
pub fn talosctl_gen_config_command(&self) -> Command {
let path = get_configs_path().join(&self.name).join("talosconfig");
let mut command = Command::new("talosctl");
command.args([
"gen",
"config",
&self.name,
&format!("https://{}:6443", self.control_plane_ip),
"--with-secrets",
self.secrets_file.to_str().expect("Path should be valid"),
"--output-types",
"talosconfig",
"-o",
path.to_str().expect("Path should be valid utf-8"),
]);
command
}
pub fn talosctl_add_endpoint_command(&self) -> Command {
let path = get_configs_path().join(&self.name).join("talosconfig");
let mut command = Command::new("talosctl");
command.args([
"config",
"--talosconfig",
path.to_str().expect("Path should be valid utf-8"),
"endpoint",
&self.control_plane_ip.to_string(),
]);
command
}
pub fn talosctl_merge_command(&self) -> Command {
let cluster_path = get_configs_path().join(&self.name).join("talosconfig");
let path = get_configs_path().join("talosconfig");
let mut command = Command::new("talosctl");
command.args([
"config",
"--talosconfig",
path.to_str().expect("Path should be valid utf-8"),
"merge",
cluster_path.to_str().expect("Path should be valid utf-8"),
]);
command
}
}
impl JsonSchema for Cluster {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Cluster".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
OptionalClusterDeserialize::json_schema(generator)
}
}