diff options
author | notgne2 | 2020-09-28 14:30:54 -0700 |
---|---|---|
committer | notgne2 | 2020-09-28 14:30:54 -0700 |
commit | 294a40a4ac3bb098f5a6d77f1323c1e5eca260d6 (patch) | |
tree | 2097ced7bd9d8e637dec2398560535e7dd7a3711 | |
parent | 622ae7c1b46b188171121486ef93d582a4180495 (diff) |
some basic modularization
-rw-r--r-- | src/main.rs | 320 |
1 files changed, 20 insertions, 300 deletions
diff --git a/src/main.rs b/src/main.rs index cd57692..7e96941 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,11 @@ use clap::Clap; -use merge::Merge; -use std::{collections::HashMap, path::PathBuf}; - -use std::borrow::Cow; use std::process::Stdio; use tokio::process::Command; -use std::path::Path; +use merge::Merge; -use std::process; +use std::path::Path; extern crate pretty_env_logger; #[macro_use] @@ -18,12 +14,10 @@ extern crate log; #[macro_use] extern crate serde_derive; -macro_rules! good_panic { - ($($tts:tt)*) => {{ - error!($($tts)*); - process::exit(1); - }} -} +#[macro_use] +mod utils; + +// use utils::*; /// Simple Rust rewrite of a simple Nix Flake deployment tool #[derive(Clap, Debug)] @@ -73,289 +67,13 @@ enum SubCommand { Activate(ActivateOpts), } -#[derive(Deserialize, Debug, Clone, Merge)] -pub struct GenericSettings { - #[serde(rename(deserialize = "sshUser"))] - pub ssh_user: Option<String>, - pub user: Option<String>, - #[serde( - skip_serializing_if = "Vec::is_empty", - default, - rename(deserialize = "sshOpts") - )] - #[merge(strategy = merge::vec::append)] - pub ssh_opts: Vec<String>, - #[serde(rename(deserialize = "fastConnection"))] - pub fast_connection: Option<bool>, - #[serde(rename(deserialize = "autoRollback"))] - pub auto_rollback: Option<String>, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct NodeSettings { - pub hostname: String, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct ProfileSettings { - pub path: String, - pub activate: Option<String>, - pub bootstrap: Option<String>, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Profile { - #[serde(flatten)] - pub profile_settings: ProfileSettings, - #[serde(flatten)] - pub generic_settings: GenericSettings, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Node { - #[serde(flatten)] - pub generic_settings: GenericSettings, - #[serde(flatten)] - pub node_settings: NodeSettings, - - pub profiles: HashMap<String, Profile>, - #[serde( - skip_serializing_if = "Vec::is_empty", - default, - rename(deserialize = "profilesOrder") - )] - pub profiles_order: Vec<String>, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Data { - #[serde(flatten)] - pub generic_settings: GenericSettings, - pub nodes: HashMap<String, Node>, -} - -struct DeployData<'a> { - pub sudo: Option<String>, - pub ssh_user: Cow<'a, str>, - pub profile_user: Cow<'a, str>, - pub profile_path: String, - pub current_exe: PathBuf, -} - -async fn make_deploy_data<'a>( - profile_name: &str, - node_name: &str, - merged_settings: &'a GenericSettings, -) -> Result<DeployData<'a>, Box<dyn std::error::Error>> { - let ssh_user: Cow<str> = match &merged_settings.ssh_user { - Some(u) => u.into(), - None => whoami::username().into(), - }; - - let profile_user: Cow<str> = match &merged_settings.user { - Some(x) => x.into(), - None => match &merged_settings.ssh_user { - Some(x) => x.into(), - None => good_panic!( - "Neither user nor sshUser set for profile `{}` of node `{}`", - profile_name, - node_name - ), - }, - }; - - let profile_path = match &profile_user[..] { - "root" => format!("/nix/var/nix/profiles/{}", profile_name), - _ => format!( - "/nix/var/nix/profiles/per-user/{}/{}", - profile_user, profile_name - ), - }; - - let sudo: Option<String> = match merged_settings.user { - Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user).into()), - _ => None, - }; - - let current_exe = std::env::current_exe().expect("Expected to find current executable path"); - - if !current_exe.starts_with("/nix/store/") { - good_panic!("The deploy binary must be in the Nix store"); - } - - Ok(DeployData { - sudo, - ssh_user, - profile_user, - profile_path, - current_exe, - }) -} - -async fn push_profile( - profile: &Profile, - profile_name: &str, - node: &Node, - node_name: &str, - supports_flakes: bool, - check_sigs: bool, - repo: &str, - merged_settings: &GenericSettings, - deploy_data: &DeployData<'_>, -) -> Result<(), Box<dyn std::error::Error>> { - info!( - "Pushing profile `{}` for node `{}`", - profile_name, node_name - ); - - debug!( - "Building profile `{} for node `{}`", - profile_name, node_name - ); - - if supports_flakes { - Command::new("nix") - .arg("build") - .arg("--no-link") - .arg(format!( - "{}#deploy.nodes.{}.profiles.{}.path", - repo, node_name, profile_name - )) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - } else { - Command::new("nix-build") - .arg(&repo) - .arg("-A") - .arg(format!( - "deploy.nodes.{}.profiles.{}.path", - node_name, profile_name - )) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - } - - if let Ok(local_key) = std::env::var("LOCAL_KEY") { - info!( - "Signing key present! Signing profile `{}` for node `{}`", - profile_name, node_name - ); - - Command::new("nix") - .arg("sign-paths") - .arg("-r") - .arg("-k") - .arg(local_key) - .arg(&profile.profile_settings.path) - .arg(&deploy_data.current_exe) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - } - - debug!("Copying profile `{} for node `{}`", profile_name, node_name); - - let mut copy_command_ = Command::new("nix"); - let mut copy_command = copy_command_.arg("copy"); - - if let Some(true) = merged_settings.fast_connection { - copy_command = copy_command.arg("--substitute-on-destination"); - } - - if !check_sigs { - copy_command = copy_command.arg("--no-check-sigs"); - } - - let ssh_opts_str = merged_settings - .ssh_opts - // This should provide some extra safety, but it also breaks for some reason, oh well - // .iter() - // .map(|x| format!("'{}'", x)) - // .collect::<Vec<String>>() - .join(" "); - - copy_command - .arg("--to") - .arg(format!( - "ssh://{}@{}", - deploy_data.ssh_user, node.node_settings.hostname - )) - .arg(&profile.profile_settings.path) - .arg(&deploy_data.current_exe) - .env("NIX_SSHOPTS", ssh_opts_str) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - - Ok(()) -} - -async fn deploy_profile( - profile: &Profile, - profile_name: &str, - node: &Node, - node_name: &str, - merged_settings: &GenericSettings, - deploy_data: &DeployData<'_>, -) -> Result<(), Box<dyn std::error::Error>> { - info!( - "Activating profile `{}` for node `{}`", - profile_name, node_name - ); - - let mut self_activate_command = format!( - "{} activate '{}' '{}'", - deploy_data.current_exe.as_path().to_str().unwrap(), - deploy_data.profile_path, - profile.profile_settings.path, - ); - - if let Some(sudo_cmd) = &deploy_data.sudo { - self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); - } - - if let Some(ref bootstrap_cmd) = profile.profile_settings.bootstrap { - self_activate_command = format!( - "{} --bootstrap-cmd '{}'", - self_activate_command, bootstrap_cmd - ); - } - - if let Some(ref activate_cmd) = profile.profile_settings.activate { - self_activate_command = format!( - "{} --activate-cmd '{}'", - self_activate_command, activate_cmd - ); - } - - let mut c = Command::new("ssh"); - let mut ssh_command = c.arg(format!( - "ssh://{}@{}", - deploy_data.ssh_user, node.node_settings.hostname - )); - - for ssh_opt in &merged_settings.ssh_opts { - ssh_command = ssh_command.arg(ssh_opt); - } - - ssh_command.arg(self_activate_command).spawn()?.await?; - - Ok(()) -} - #[inline] async fn push_all_profiles( - node: &Node, + node: &utils::data::Node, node_name: &str, supports_flakes: bool, repo: &str, - top_settings: &GenericSettings, + top_settings: &utils::data::GenericSettings, check_sigs: bool, ) -> Result<(), Box<dyn std::error::Error>> { info!("Pushing all profiles for `{}`", node_name); @@ -379,9 +97,10 @@ async fn push_all_profiles( merged_settings.merge(node.generic_settings.clone()); merged_settings.merge(profile.generic_settings.clone()); - let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; + let deploy_data = + utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; - push_profile( + utils::push::push_profile( profile, profile_name, node, @@ -400,9 +119,9 @@ async fn push_all_profiles( #[inline] async fn deploy_all_profiles( - node: &Node, + node: &utils::data::Node, node_name: &str, - top_settings: &GenericSettings, + top_settings: &utils::data::GenericSettings, ) -> Result<(), Box<dyn std::error::Error>> { info!("Deploying all profiles for `{}`", node_name); @@ -425,9 +144,10 @@ async fn deploy_all_profiles( merged_settings.merge(node.generic_settings.clone()); merged_settings.merge(profile.generic_settings.clone()); - let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; + let deploy_data = + utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; - deploy_profile( + utils::deploy::deploy_profile( profile, profile_name, node, @@ -513,7 +233,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { } }; - let data: Data = serde_json::from_str(&data_json)?; + let data: utils::data::Data = serde_json::from_str(&data_json)?; match (maybe_node, maybe_profile) { (Some(node_name), Some(profile_name)) => { @@ -531,9 +251,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { merged_settings.merge(profile.generic_settings.clone()); let deploy_data = - make_deploy_data(profile_name, node_name, &merged_settings).await?; + utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; - push_profile( + utils::push::push_profile( profile, profile_name, node, @@ -546,7 +266,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { ) .await?; - deploy_profile( + utils::deploy::deploy_profile( profile, profile_name, node, |