diff --git a/src/cluster.rs b/src/cluster.rs index ea52484..bce3f70 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -1,5 +1,6 @@ use std::net::Ipv4Addr; use std::path::PathBuf; +use std::process::Command; use std::str::FromStr; use minijinja::Environment; @@ -9,9 +10,9 @@ use serde::{Deserialize, Serialize}; use walkdir::WalkDir; use crate::environment::PathEnvironment; -use crate::get_talos_path; use crate::node::{Node, OptionalNodeDeserialize}; use crate::patch::Patches; +use crate::{get_configs_path, get_talos_path}; #[optional_struct] #[derive(Debug, Deserialize, JsonSchema, Clone)] @@ -31,6 +32,16 @@ pub struct 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 { @@ -60,12 +71,12 @@ struct ClusterDeserialize { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct Cluster { - name: String, - version: Version, + pub(crate) name: String, + pub(crate) version: Version, nodes: Vec, cluster_env: ClusterEnv, - control_plane_ip: Ipv4Addr, - secrets_file: PathBuf, + pub(crate) control_plane_ip: Ipv4Addr, + pub(crate) secrets_file: PathBuf, } impl Cluster { @@ -142,6 +153,57 @@ impl Cluster { 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 { diff --git a/src/main.rs b/src/main.rs index 06449e7..b69af7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ mod cli; +use std::process::Command; + use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; -use crete::{get_repo_path, set_repo_path}; +use crete::{get_configs_path, get_repo_path, set_repo_path}; use minijinja::context; use thiserror::Error; @@ -16,6 +18,16 @@ enum Error { NoClustersFound, } +fn run_command(mut command: Command) { + let output = command.output().unwrap(); + if !output.stdout.is_empty() { + println!("{}", String::from_utf8_lossy(&output.stdout).trim_end()); + } + if !output.stderr.is_empty() { + println!("{}", String::from_utf8_lossy(&output.stderr).trim_end()); + } +} + fn generate(opts: &GlobalOpts) -> Result<(), Error> { set_repo_path(&opts.repo); @@ -46,6 +58,24 @@ fn generate(opts: &GlobalOpts) -> Result<(), Error> { std::fs::write(path.join(template_name), content).unwrap(); } + // Remove existing config files and create output directory + let path = get_configs_path(); + if path.exists() { + std::fs::remove_dir_all(&path).unwrap(); + } + std::fs::create_dir(&path).unwrap(); + + // Generate config files + for cluster in clusters { + for node in cluster.nodes() { + run_command(node.talosctl_gen_config_command(&cluster)); + } + + run_command(cluster.talosctl_gen_config_command()); + run_command(cluster.talosctl_add_endpoint_command()); + run_command(cluster.talosctl_merge_command()); + } + Ok(()) } diff --git a/src/node.rs b/src/node.rs index 8c28a62..5f599fb 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,4 +1,5 @@ use std::net::Ipv4Addr; +use std::process::Command; use minijinja::Environment; use optional_struct::{Applicable, optional_struct}; @@ -6,10 +7,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::cluster::{Base, Cluster}; -use crate::get_talos_path; 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")] @@ -167,6 +168,50 @@ impl 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 {