mod cli; mod error; mod file; mod http; use std::net::SocketAddr; use std::pin::pin; use axum::Router; use axum::routing::get; use clap::{CommandFactory, Parser}; use clap_complete::{Shell, generate as generate_complete}; use crete::cluster::Cluster; use crete::environment::PathEnvironment; use crete::talosconfig::{get_current_context, set_context}; use crete::{get_configs_path, get_repo_path, set_repo_path, tftp}; use futures::StreamExt; use futures::future::join_all; use minijinja::context; use crate::cli::{Cli, Commands, GlobalOpts}; use crate::error::Error; use crate::file::get_file; use crate::http::handler; async fn generate(opts: &GlobalOpts) -> Result<(), Error> { let clusters = Cluster::get_all().await; if clusters.is_empty() { return Err(Error::NoClustersFound); } let path = opts.repo.join("rendered"); // We don't really care if this fails, as that likely mean that the directory did not exist tokio::fs::remove_dir_all(&path).await.ok(); tokio::fs::create_dir_all(&path).await.unwrap(); // Render templates let template_env = PathEnvironment::new(&get_repo_path().join("templates")); let mut stream = pin!(template_env.files()); while let Some(template_name) = stream.next().await { let template = template_env.get_template(&template_name).unwrap(); let content = template .render(context! { clusters => clusters.values().collect::>(), root => opts.repo }) .unwrap(); tokio::fs::write(path.join(template_name), content) .await .unwrap(); } let context = get_current_context().await; // Remove existing config files and create output directory let path = get_configs_path(); tokio::fs::remove_dir_all(&path).await.ok(); tokio::fs::create_dir_all(&path).await.unwrap(); // Generate config files join_all(clusters.values().map(|cluster| async move { join_all(cluster.nodes().map(|node| { let cluster = cluster.clone(); async move { node.config_write(&cluster).await; } })) .await; // Run these commands in parallel with the the node commands // NOTE: These do need to happen in the correct order cluster.talosconfig_write().await; })) .await; // Merge all the cluster talosconfig files into a single file // Since this all modifies the same output file this has to happen sequentially for (_, cluster) in clusters { cluster.talosconfig_merge().await; } if let Some(context) = context { set_context(context).await; } Ok(()) } #[tokio::main] async fn main() -> Result<(), Error> { let cli = Cli::parse(); set_repo_path(&cli.global_opts.repo); match cli.command { Commands::Generate => generate(&cli.global_opts).await?, Commands::ShellCompletions => generate_complete( Shell::from_env().unwrap_or(Shell::Bash), &mut Cli::command(), "crete", &mut std::io::stdout(), ), Commands::Serve { port, ip } => { tokio::spawn(async move { let app = Router::new().fallback_service(get(handler)); let addr: SocketAddr = (ip, port).into(); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); println!("Http server started on {addr}"); axum::serve(listener, app).await.unwrap(); }); tftp::serve(ip, move |filename, _| { let filename = filename.to_owned(); async move { get_file(filename).await.map(|(file, _)| file) } }) .await?; } }; Ok(()) }