diff --git a/src/cli.rs b/src/cli.rs index a86081e5..f5e086d5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -66,22 +66,19 @@ struct PromptPart<'a> { } fn print_deployment( - parts: &[( - &data::DeployData, - data::DeployDefs, - )], + parts: &[&data::DeployData], ) -> Result<(), toml::ser::Error> { let mut part_map: HashMap> = HashMap::new(); - for (data, defs) in parts { + for data in parts { part_map .entry(data.node_name.to_string()) .or_insert_with(HashMap::new) .insert( data.profile_name.to_string(), PromptPart { - user: &defs.profile_user, - ssh_user: &defs.ssh_user, + user: &data.profile_user, + ssh_user: &data.ssh_user, path: &data.profile.profile_settings.path, hostname: &data.node.node_settings.hostname, ssh_opts: &data.merged_settings.ssh_opts, @@ -108,10 +105,7 @@ pub enum PromptDeploymentError { } fn prompt_deployment( - parts: &[( - &data::DeployData, - data::DeployDefs, - )], + parts: &[&data::DeployData], ) -> Result<(), PromptDeploymentError> { print_deployment(parts)?; @@ -198,14 +192,10 @@ async fn run_deploy( .collect::>>, data::ResolveTargetError>>()?; let deploy_datas: Vec<&data::DeployData<'_>> = deploy_datas_.iter().flatten().collect(); - let mut parts: Vec<( - &data::DeployData, - data::DeployDefs, - )> = Vec::new(); + let mut parts: Vec<&data::DeployData> = Vec::new(); for deploy_data in deploy_datas { - let deploy_defs = deploy_data.defs()?; - parts.push((deploy_data, deploy_defs)); + parts.push(deploy_data); } if cmd_flags.interactive { @@ -214,27 +204,24 @@ async fn run_deploy( print_deployment(&parts[..])?; } - for (deploy_data, deploy_defs) in &parts { - deploy::push::push_profile(deploy::push::PushProfileData { - supports_flakes: &supports_flakes, - check_sigs: &cmd_flags.checksigs, - repo: &deploy_data.repo, - deploy_data: &deploy_data, - deploy_defs: &deploy_defs, - keep_result: &cmd_flags.keep_result, - result_path: cmd_flags.result_path.as_deref(), - extra_build_args: &cmd_flags.extra_build_args, - }) + for deploy_data in &parts { + deploy::push::push_profile( + supports_flakes, + deploy::push::ShowDerivationCommand::from_data(&deploy_data), + deploy::push::BuildCommand::from_data(&deploy_data), + deploy::push::SignCommand::from_data(&deploy_data), + deploy::push::CopyCommand::from_data(&deploy_data), + ) .await?; } - let mut succeeded: Vec<(&data::DeployData, &data::DeployDefs)> = vec![]; + let mut succeeded: Vec<&data::DeployData> = vec![]; // Run all deployments // In case of an error rollback any previoulsy made deployment. // Rollbacks adhere to the global seeting to auto_rollback and secondary // the profile's configuration - for (deploy_data, deploy_defs) in &parts { + for deploy_data in &parts { if let Err(e) = deploy::deploy::deploy_profile( &deploy_data.node_name, &deploy_data.profile_name, @@ -253,7 +240,7 @@ async fn run_deploy( // revoking all previous deploys // (adheres to profile configuration if not set explicitely by // the command line) - for (deploy_data, _) in &succeeded { + for deploy_data in &succeeded { if deploy_data.merged_settings.auto_rollback.unwrap_or(true) { deploy::deploy::revoke( &deploy_data.node_name, @@ -266,7 +253,7 @@ async fn run_deploy( } break; } - succeeded.push((deploy_data, deploy_defs)) + succeeded.push(deploy_data) } Ok(()) diff --git a/src/data.rs b/src/data.rs index 88e8c9df..2c7980eb 100644 --- a/src/data.rs +++ b/src/data.rs @@ -260,16 +260,18 @@ pub struct DeployData<'a> { pub node_name: String, pub profile_name: String, - pub hostname: Option<&'a str>, - pub flags: &'a Flags, pub node: &'a settings::Node, pub profile: &'a settings::Profile, pub merged_settings: settings::GenericSettings, + pub hostname: &'a str, + pub ssh_user: String, + pub ssh_uri: String, pub temp_path: String, pub profile_path: String, + pub profile_user: String, pub sudo: Option, } @@ -318,14 +320,6 @@ pub struct Flags { pub rollback_succeeded: bool, } -#[derive(Debug)] -pub struct DeployDefs { - pub ssh_user: String, - pub profile_user: String, - pub profile_path: String, - pub sudo: Option, -} - impl<'a> DeployData<'a> { fn new( @@ -351,14 +345,10 @@ impl<'a> DeployData<'a> { Some(ref x) => x.to_owned(), None => "/tmp".to_string(), }; - let profile_user = match merged_settings.user { - Some(ref x) => x.to_owned(), - None => match merged_settings.ssh_user { - Some(ref x) => x.to_owned(), - None => { - return Err(DeployDataError::NoProfileUser(profile_name, node_name)) - } - }, + let profile_user = if let Some(ref x) = merged_settings.user { x.to_owned() } else { + if let Some(ref x) = merged_settings.ssh_user { x.to_owned() } else { + return Err(DeployDataError::NoProfileUser(profile_name, node_name)) + } }; let profile_path = match profile.profile_settings.profile_path { None => format!("/nix/var/nix/profiles/{}", match &profile_user[..] { @@ -375,6 +365,11 @@ impl<'a> DeployData<'a> { Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)), _ => None, }; + let hostname = match hostname { + Some(x) => x, + None => &node.node_settings.hostname, + }; + let ssh_uri = format!("ssh://{}@{}", &ssh_user, &hostname); Ok(DeployData { repo, @@ -384,114 +379,13 @@ impl<'a> DeployData<'a> { profile, hostname, ssh_user, + ssh_uri, temp_path, profile_path, + profile_user, sudo, flags, merged_settings, }) } - - pub fn defs(&'a self) -> Result { - let ssh_user = match self.merged_settings.ssh_user { - Some(ref u) => u.clone(), - None => whoami::username(), - }; - let profile_user = self.get_profile_user()?; - let profile_path = self.get_profile_path()?; - let sudo = self.sudo()?; - - Ok(DeployDefs { - ssh_user, - profile_user, - profile_path, - sudo, - }) - } - - pub fn sudo(&'a self) -> Result, DeployDataError> { - let ssh_user = match self.merged_settings.ssh_user { - Some(ref u) => u.clone(), - None => whoami::username(), - }; - Ok( - match self.merged_settings.user { - Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)), - _ => None, - } - ) - } - - pub fn temp_path(&'a self) -> Result { - Ok( - match self.merged_settings.temp_path { - Some(ref x) => x.to_owned(), - None => "/tmp".to_string(), - } - ) - } - - pub fn ssh_uri(&'a self) -> Result { - - let hostname = match self.hostname { - Some(x) => x, - None => &self.node.node_settings.hostname, - }; - let curr_user = &whoami::username(); - let ssh_user = match self.merged_settings.ssh_user { - Some(ref u) => u, - None => curr_user, - }; - Ok(format!("ssh://{}@{}", ssh_user, hostname)) - } - - // can be dropped once ssh fully supports ipv6 uris - pub fn ssh_non_uri(&'a self) -> Result { - - let hostname = match self.hostname { - Some(x) => x, - None => &self.node.node_settings.hostname, - }; - let curr_user = &whoami::username(); - let ssh_user = match self.merged_settings.ssh_user { - Some(ref u) => u, - None => curr_user, - }; - Ok(format!("{}@{}", ssh_user, hostname)) - } - - pub fn ssh_opts(&'a self) -> Result, DeployDataError> { - Ok(self.merged_settings.ssh_opts.iter()) - } - - pub fn get_profile_path(&'a self) -> Result { - let profile_user = self.get_profile_user()?; - let profile_path = match self.profile.profile_settings.profile_path { - None => match &profile_user[..] { - "root" => format!("/nix/var/nix/profiles/{}", self.profile_name), - _ => format!( - "/nix/var/nix/profiles/per-user/{}/{}", - profile_user, self.profile_name - ), - }, - Some(ref x) => x.clone(), - }; - Ok(profile_path) - } - - pub fn get_profile_user(&'a self) -> Result { - let profile_user = match self.merged_settings.user { - Some(ref x) => x.clone(), - None => match self.merged_settings.ssh_user { - Some(ref x) => x.clone(), - None => { - return Err(DeployDataError::NoProfileUser( - self.profile_name.to_owned(), - self.node_name.to_owned(), - )) - } - }, - }; - Ok(profile_user) - } } diff --git a/src/deploy.rs b/src/deploy.rs index f356627b..94cb1219 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -17,11 +17,7 @@ pub struct SshCommand<'a> { impl<'a> SshCommand<'a> { pub fn from_data(d: &'a data::DeployData) -> Result { - let hostname = match d.hostname { - Some(x) => x, - None => &d.node.node_settings.hostname, - }; - let hoststring = format!("{}@{}", &d.ssh_user, hostname); + let hoststring = format!("{}@{}", &d.ssh_user, d.hostname); let opts = d.merged_settings.ssh_opts.as_ref(); Ok(SshCommand {hoststring, opts}) } diff --git a/src/push.rs b/src/push.rs index 197a6bb3..7b85eb46 100644 --- a/src/push.rs +++ b/src/push.rs @@ -48,31 +48,158 @@ pub enum PushProfileError { InvalidDeployDataError(#[from] data::DeployDataError), } -pub struct PushProfileData<'a> { - pub supports_flakes: &'a bool, - pub check_sigs: &'a bool, - pub repo: &'a str, - pub deploy_data: &'a data::DeployData<'a>, - pub deploy_defs: &'a data::DeployDefs, - pub keep_result: &'a bool, - pub result_path: Option<&'a str>, - pub extra_build_args: &'a [String], +pub struct ShowDerivationCommand<'a> { + closure: &'a str, } -pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileError> { - debug!( - "Finding the deriver of store path for {}", - &data.deploy_data.profile.profile_settings.path - ); +impl<'a> ShowDerivationCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + ShowDerivationCommand { + closure: d.profile.profile_settings.path.as_str(), + } + } + + fn build(self) -> Command { + // `nix-store --query --deriver` doesn't work on invalid paths, so we parse output of show-derivation :( + let mut cmd = Command::new("nix"); + + cmd + .arg("show-derivation") + .arg(&self.closure); + //cmd.what_is_this; + cmd + } +} + +pub struct SignCommand<'a> { + closure: &'a str, +} + +impl<'a> SignCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + SignCommand { + closure: d.profile.profile_settings.path.as_str(), + } + } + + fn build(self, local_key: String) -> Command { + let mut cmd = Command::new("nix"); + + cmd + .arg("sign-paths") + .arg("-r") + .arg("-k") + .arg(local_key) + .arg(&self.closure); + //cmd.what_is_this; + cmd + } +} + +pub struct CopyCommand<'a> { + closure: &'a str, + fast_connection: bool, + check_sigs: &'a bool, + ssh_uri: &'a str, + ssh_opts: String, +} + +impl<'a> CopyCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + CopyCommand { + closure: d.profile.profile_settings.path.as_str(), + fast_connection: d.merged_settings.fast_connection.unwrap_or(false), + check_sigs: &d.flags.checksigs, + ssh_uri: d.ssh_uri.as_str(), + ssh_opts: d.merged_settings.ssh_opts.iter().fold("".to_string(), |s, o| format!("{} {}", s, o)), + } + } + + fn build(self) -> Command { + let mut cmd = Command::new("nix"); + + cmd.arg("copy"); + + if self.fast_connection { + cmd.arg("--substitute-on-destination"); + } + + if !self.check_sigs { + cmd.arg("--no-check-sigs"); + } + cmd + .arg("--to") + .arg(self.ssh_uri) + .arg(self.closure) + .env("NIX_SSHOPTS", self.ssh_opts); + //cmd.what_is_this; + cmd + } +} + +pub struct BuildCommand<'a> { + node_name: &'a str, + profile_name: &'a str, + keep_result: &'a bool, + result_path: &'a str, + extra_build_args: &'a Vec, +} + +impl<'a> BuildCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + BuildCommand { + node_name: d.node_name.as_str(), + profile_name: d.profile_name.as_str(), + keep_result: &d.flags.keep_result, + result_path: &d.flags.result_path.as_deref().unwrap_or("./.deploy-gc"), + extra_build_args: &d.flags.extra_build_args, + } + } + + fn build(self, derivation_name: &str, supports_flakes: bool) -> Command { + let mut cmd = if supports_flakes { + Command::new("nix") + } else { + Command::new("nix-build") + }; - // `nix-store --query --deriver` doesn't work on invalid paths, so we parse output of show-derivation :( - let mut show_derivation_command = Command::new("nix"); + if supports_flakes { + cmd.arg("build").arg(derivation_name) + } else { + cmd.arg(derivation_name) + }; - show_derivation_command - .arg("show-derivation") - .arg(&data.deploy_data.profile.profile_settings.path); + match (self.keep_result, supports_flakes) { + (true, _) => { + cmd.arg("--out-link").arg(format!( + "{}/{}/{}", + self.result_path, self.node_name, self.profile_name + )) + } + (false, false) => cmd.arg("--no-out-link"), + (false, true) => cmd.arg("--no-link"), + }; + cmd.args(self.extra_build_args.iter()); + // cmd.what_is_this; + cmd + } +} - let show_derivation_output = show_derivation_command +pub async fn push_profile( + supports_flakes: bool, + show_derivation: ShowDerivationCommand<'_>, + build: BuildCommand<'_>, + sign: SignCommand<'_>, + copy: CopyCommand<'_>, +) -> Result<(), PushProfileError> { + let node_name = build.node_name; + let profile_name = build.profile_name; + let closure = show_derivation.closure; + + debug!("Finding the deriver of store path for {}", closure); + let mut show_derivation_cmd = show_derivation.build(); + + let show_derivation_output = show_derivation_cmd .output() .await .map_err(PushProfileError::ShowDerivationError)?; @@ -93,41 +220,11 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr .next() .ok_or(PushProfileError::ShowDerivationEmpty)?; - info!( - "Building profile `{}` for node `{}`", - data.deploy_data.profile_name, data.deploy_data.node_name - ); - - let mut build_command = if *data.supports_flakes { - Command::new("nix") - } else { - Command::new("nix-build") - }; - - if *data.supports_flakes { - build_command.arg("build").arg(derivation_name) - } else { - build_command.arg(derivation_name) - }; + info!("Building profile `{}` for node `{}`", profile_name, node_name); - match (data.keep_result, data.supports_flakes) { - (true, _) => { - let result_path = data.result_path.unwrap_or("./.deploy-gc"); + let mut build_cmd = build.build(*derivation_name, supports_flakes); - build_command.arg("--out-link").arg(format!( - "{}/{}/{}", - result_path, data.deploy_data.node_name, data.deploy_data.profile_name - )) - } - (false, false) => build_command.arg("--no-out-link"), - (false, true) => build_command.arg("--no-link"), - }; - - for extra_arg in data.extra_build_args { - build_command.arg(extra_arg); - } - - let build_exit_status = build_command + let build_exit_status = build_cmd // Logging should be in stderr, this just stops the store path from printing for no reason .stdout(Stdio::null()) .status() @@ -139,42 +236,19 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr a => return Err(PushProfileError::BuildExitError(a)), }; - if !Path::new( - format!( - "{}/deploy-rs-activate", - data.deploy_data.profile.profile_settings.path - ) - .as_str(), - ) - .exists() - { + if !Path::new(format!("{}/deploy-rs-activate", closure).as_str()).exists() { return Err(PushProfileError::DeployRsActivateDoesntExist); } - if !Path::new( - format!( - "{}/activate-rs", - data.deploy_data.profile.profile_settings.path - ) - .as_str(), - ) - .exists() - { + if !Path::new(format!("{}/activate-rs", closure).as_str()).exists() { return Err(PushProfileError::ActivateRsDoesntExist); } if let Ok(local_key) = std::env::var("LOCAL_KEY") { - info!( - "Signing key present! Signing profile `{}` for node `{}`", - data.deploy_data.profile_name, data.deploy_data.node_name - ); + info!("Signing key present! Signing profile `{}` for node `{}`", profile_name, node_name); - let sign_exit_status = Command::new("nix") - .arg("sign-paths") - .arg("-r") - .arg("-k") - .arg(local_key) - .arg(&data.deploy_data.profile.profile_settings.path) + let mut sign_cmd = sign.build(local_key); + let sign_exit_status = sign_cmd .status() .await .map_err(PushProfileError::SignError)?; @@ -185,30 +259,11 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr }; } - info!( - "Copying profile `{}` to node `{}`", - data.deploy_data.profile_name, data.deploy_data.node_name - ); - - let mut copy_command = Command::new("nix"); - copy_command.arg("copy"); + info!("Copying profile `{}` to node `{}`", profile_name, node_name); - if data.deploy_data.merged_settings.fast_connection != Some(true) { - copy_command.arg("--substitute-on-destination"); - } - - if !data.check_sigs { - copy_command.arg("--no-check-sigs"); - } + let mut copy_cmd = copy.build(); - let copy_exit_status = copy_command - .arg("--to") - .arg(data.deploy_data.ssh_uri()?) - .arg(&data.deploy_data.profile.profile_settings.path) - .env( - "NIX_SSHOPTS", - data.deploy_data.ssh_opts()?.fold("".to_string(), |s, o| format!("{} {}", s, o)) - ) + let copy_exit_status = copy_cmd .status() .await .map_err(PushProfileError::CopyError)?;