Files
crete/src/node.rs
T

226 lines
6.3 KiB
Rust

use std::net::Ipv4Addr;
use std::process::Command;
use minijinja::Environment;
use optional_struct::{Applicable, optional_struct};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::cluster::{Base, Cluster};
use crate::patch::{OptionalPatches, OptionalPatchesString, Patches};
use crate::schematic::Schematic;
use crate::secret::Secret;
use crate::{get_configs_path, get_talos_path};
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum NodeType {
Worker,
#[serde(rename(serialize = "controlplane"))]
ControlPlane,
}
impl From<NodeType> for &str {
fn from(value: NodeType) -> Self {
match value {
NodeType::Worker => "worker",
NodeType::ControlPlane => "controlplane",
}
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum NodeArch {
Amd64,
}
#[optional_struct]
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct Tailscale {
auth_key: Secret,
advertise_routes: bool,
#[serde(default)]
server: Option<String>,
}
#[optional_struct]
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct Network {
interface: String,
ip: Ipv4Addr,
netmask: Ipv4Addr,
gateway: Ipv4Addr,
dns: [Ipv4Addr; 2],
#[optional_rename(OptionalTailscale)]
#[optional_wrap]
tailscale: Tailscale,
}
#[optional_struct]
#[derive(Debug, Serialize, Deserialize, JsonSchema, Default, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct Install {
auto: bool,
disk: String,
serial: Option<String>,
}
#[optional_struct]
#[derive(Debug, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub(crate) struct NodeDeserialize {
arch: NodeArch,
schematic: Schematic,
r#type: NodeType,
#[optional_rename(OptionalNetwork)]
#[optional_wrap]
network: Network,
ntp: String,
#[optional_rename(OptionalInstall)]
#[optional_wrap]
install: Install,
kernel_args: Vec<String>,
#[optional_rename(OptionalPatchesString)]
#[optional_wrap]
patches: Patches<String>,
sops: Secret,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Node {
hostname: String,
arch: NodeArch,
schematic: Schematic,
r#type: NodeType,
network: Network,
ntp: String,
install: Install,
kernel_args: Vec<String>,
patches: Patches<serde_yaml::Value>,
sops: Secret,
}
impl Node {
pub(crate) fn get(
node_name: &str,
env: &Environment,
cluster: &Cluster,
default: &OptionalNodeDeserialize,
base: &Base,
) -> Self {
let mut path = get_talos_path().join("nodes").join(node_name);
let hostname = path
.file_name()
.expect("Path should be valid")
.to_string_lossy()
.to_string();
path.add_extension("yaml");
let content = std::fs::read_to_string(path).unwrap();
let node: OptionalNodeDeserialize = 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 = OptionalNodeDeserialize {
patches: Some(OptionalPatches {
all: Some(vec![]),
control_plane: Some(vec![]),
}),
kernel_args: vec![].into(),
..Default::default()
}
.apply(default.clone());
// Combine all the optional node parts into complete struct
let node: NodeDeserialize = default
// Override node specific settings
.apply(node)
.try_into()
.unwrap();
// Prepend the cluster base values
let mut kernel_args = base.kernel_args.clone();
kernel_args.extend(node.kernel_args);
let patches = base.patches.clone().extend(node.patches);
let node = Node {
hostname,
arch: node.arch,
schematic: node.schematic,
r#type: node.r#type,
network: node.network,
ntp: node.ntp,
install: node.install,
kernel_args,
patches: Default::default(),
sops: node.sops,
};
// Render patches
let patches = patches.render(env, cluster, &node);
Node { patches, ..node }
}
pub fn talosctl_gen_config_command(&self, cluster: &Cluster) -> Command {
let mut path = get_configs_path().join(&cluster.name).join(&self.hostname);
path.add_extension("yaml");
let mut command = Command::new("talosctl");
command.args([
"gen",
"config",
&cluster.name,
&format!("https://{}:6443", cluster.control_plane_ip),
"--with-secrets",
cluster.secrets_file.to_str().expect("Path should be valid"),
"--talos-version",
&cluster.version.talos(),
"--kubernetes-version",
&cluster.version.kubernetes(),
"--output-types",
self.r#type.into(),
"--install-image",
&format!(
"factory.talos.dev/metal-installer/{}:{}",
self.schematic,
cluster.version.talos()
),
"--with-docs=false",
"--with-examples=false",
"-o",
path.to_str().expect("Path should be valid utf-8"),
]);
for patch in &self.patches.all {
command.args(["--config-patch", &serde_json::to_string(&patch).unwrap()]);
}
for patch in &self.patches.control_plane {
command.args([
"--config-patch-control-plane",
&serde_json::to_string(&patch).unwrap(),
]);
}
command
}
}
impl JsonSchema for Node {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Node".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
OptionalNodeDeserialize::json_schema(generator)
}
}