From 29fba1efa512a93dc53cc4e95ec39ba298f6097a Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 26 Aug 2021 17:08:17 -0500 Subject: [PATCH] Refactor & simplify the target setting resolver --- src/cli.rs | 18 ++- src/data.rs | 86 +++++++++-- src/deploy.rs | 390 ++++++++++++++++++++++++++++---------------------- src/push.rs | 2 +- 4 files changed, 303 insertions(+), 193 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 51a0a1a7..b2fcb9f8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -235,7 +235,14 @@ async fn run_deploy( // Rollbacks adhere to the global seeting to auto_rollback and secondary // the profile's configuration for (deploy_data, deploy_defs) in &parts { - if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, cmd_flags.dry_activate).await + if let Err(e) = deploy::deploy::deploy_profile( + &deploy_data.node_name, + &deploy_data.profile_name, + deploy::deploy::SshCommand::from_data(&deploy_data)?, + deploy::deploy::ActivateCommand::from_data(&deploy_data), + deploy::deploy::WaitCommand::from_data(&deploy_data), + deploy::deploy::ConfirmCommand::from_data(&deploy_data), + ).await { error!("{}", e); if cmd_flags.dry_activate { @@ -246,9 +253,14 @@ async fn run_deploy( // revoking all previous deploys // (adheres to profile configuration if not set explicitely by // the command line) - for (deploy_data, deploy_defs) in &succeeded { + for (deploy_data, _) in &succeeded { if deploy_data.merged_settings.auto_rollback.unwrap_or(true) { - deploy::deploy::revoke(*deploy_data, *deploy_defs).await?; + deploy::deploy::revoke( + &deploy_data.node_name, + &deploy_data.profile_name, + deploy::deploy::SshCommand::from_data(&deploy_data)?, + deploy::deploy::RevokeCommand::from_data(&deploy_data), + ).await?; } } } diff --git a/src/data.rs b/src/data.rs index 66c9063c..3dddc702 100644 --- a/src/data.rs +++ b/src/data.rs @@ -34,6 +34,8 @@ pub enum ResolveTargetError { ProfileNotFound(String, String, String), #[error("Profile was provided without a node name for repo `{0}`")] ProfileWithoutNode(String), + #[error("Deployment data invalid: {0}")] + InvalidDeployDataError(#[from] DeployDataError), } impl<'a> Target { @@ -70,7 +72,7 @@ impl<'a> Target { node_, profile_, hostname, - ); + )?; vec![d] }) } else { @@ -257,12 +259,18 @@ pub struct DeployData<'a> { pub repo: String, pub node_name: String, pub profile_name: String, - pub node: &'a settings::Node, - pub profile: &'a settings::Profile, + 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 ssh_user: String, + pub temp_path: String, + pub profile_path: String, + pub sudo: Option, } #[derive(Error, Debug)] @@ -330,7 +338,7 @@ impl<'a> DeployData<'a> { node: &'a settings::Node, profile: &'a settings::Profile, hostname: Option<&'a str>, - ) -> DeployData<'a> { + ) -> Result, DeployDataError> { let mut merged_settings = cmd_settings.clone(); merged_settings.merge(profile.generic_settings.clone()); merged_settings.merge(node.generic_settings.clone()); @@ -339,17 +347,49 @@ impl<'a> DeployData<'a> { // if let Some(ref ssh_opts) = cmd_overrides.ssh_opts { // merged_settings.ssh_opts = ssh_opts.split(' ').map(|x| x.to_owned()).collect(); // } + let temp_path = match merged_settings.temp_path { + 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_path = match profile.profile_settings.profile_path { + None => format!("/nix/var/nix/profiles/{}", match &profile_user[..] { + "root" => profile_name.to_owned(), + _ => format!("per-user/{}/{}", profile_user, profile_name), + }), + Some(ref x) => x.to_owned(), + }; + let ssh_user = match merged_settings.ssh_user { + Some(ref u) => u.to_owned(), + None => whoami::username(), + }; + let sudo = match merged_settings.user { + Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)), + _ => None, + }; - DeployData { + Ok(DeployData { repo, node_name, profile_name, node, profile, hostname, + ssh_user, + temp_path, + profile_path, + sudo, flags, merged_settings, - } + }) } pub fn defs(&'a self) -> Result { @@ -357,15 +397,9 @@ impl<'a> DeployData<'a> { Some(ref u) => u.clone(), None => whoami::username(), }; - let profile_user = self.get_profile_user()?; - let profile_path = self.get_profile_path()?; - - let sudo: Option = match self.merged_settings.user { - Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)), - _ => None, - }; + let sudo = self.sudo()?; Ok(DeployDefs { ssh_user, @@ -375,6 +409,28 @@ impl<'a> DeployData<'a> { }) } + 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 { @@ -404,8 +460,8 @@ impl<'a> DeployData<'a> { Ok(format!("{}@{}", ssh_user, hostname)) } - pub fn ssh_opts(&'a self) -> impl Iterator { - self.merged_settings.ssh_opts.iter() + pub fn ssh_opts(&'a self) -> Result, DeployDataError> { + Ok(self.merged_settings.ssh_opts.iter()) } pub fn get_profile_path(&'a self) -> Result { diff --git a/src/deploy.rs b/src/deploy.rs index 3feb538c..23c57b21 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -5,18 +5,41 @@ // SPDX-License-Identifier: MPL-2.0 use log::{debug, info}; -use std::borrow::Cow; use thiserror::Error; use tokio::process::Command; use crate::data; -struct ActivateCommandData<'a> { - sudo: &'a Option, +pub struct SshCommand<'a> { + hoststring: String, + opts: &'a Vec, +} + +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 opts = d.merged_settings.ssh_opts.as_ref(); + Ok(SshCommand {hoststring, opts}) + } + + fn build(&self) -> Command { + let mut cmd = Command::new("ssh"); + cmd.arg(&self.hoststring); + cmd.args(self.opts.iter()); + cmd + } +} + +pub struct ActivateCommand<'a> { + sudo: Option<&'a str>, profile_path: &'a str, + temp_path: &'a str, closure: &'a str, auto_rollback: bool, - temp_path: &'a str, confirm_timeout: u16, magic_rollback: bool, debug_logs: bool, @@ -24,49 +47,66 @@ struct ActivateCommandData<'a> { dry_activate: bool, } -fn build_activate_command(data: &ActivateCommandData) -> String { - let mut self_activate_command = format!("{}/activate-rs", data.closure); - - if data.debug_logs { - self_activate_command = format!("{} --debug-logs", self_activate_command); +impl<'a> ActivateCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + ActivateCommand { + sudo: d.sudo.as_deref(), + profile_path: &d.profile_path, + temp_path: &d.temp_path, + closure: &d.profile.profile_settings.path, + auto_rollback: d.merged_settings.auto_rollback.unwrap_or(true), + confirm_timeout: d.merged_settings.confirm_timeout.unwrap_or(30), + magic_rollback: d.merged_settings.magic_rollback.unwrap_or(true), + debug_logs: d.flags.debug_logs, + log_dir: d.flags.log_dir.as_deref(), + dry_activate: d.flags.dry_activate, + } } - if let Some(log_dir) = data.log_dir { - self_activate_command = format!("{} --log-dir {}", self_activate_command, log_dir); - } + fn build(self) -> String { + let mut cmd = format!("{}/activate-rs", self.closure); - self_activate_command = format!( - "{} activate '{}' '{}' --temp-path '{}'", - self_activate_command, data.closure, data.profile_path, data.temp_path - ); + if self.debug_logs { + cmd = format!("{} --debug-logs", cmd); + } - self_activate_command = format!( - "{} --confirm-timeout {}", - self_activate_command, data.confirm_timeout - ); + if let Some(log_dir) = self.log_dir { + cmd = format!("{} --log-dir {}", cmd, log_dir); + } - if data.magic_rollback { - self_activate_command = format!("{} --magic-rollback", self_activate_command); - } + cmd = format!( + "{} activate '{}' '{}' --temp-path '{}'", + cmd, self.closure, self.profile_path, self.temp_path + ); - if data.auto_rollback { - self_activate_command = format!("{} --auto-rollback", self_activate_command); - } + cmd = format!( + "{} --confirm-timeout {}", + cmd, self.confirm_timeout + ); - if data.dry_activate { - self_activate_command = format!("{} --dry-activate", self_activate_command); - } + if self.magic_rollback { + cmd = format!("{} --magic-rollback", cmd); + } - if let Some(sudo_cmd) = &data.sudo { - self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); - } + if self.auto_rollback { + cmd = format!("{} --auto-rollback", cmd); + } + + if self.dry_activate { + cmd = format!("{} --dry-activate", cmd); + } + + if let Some(sudo_cmd) = &self.sudo { + cmd = format!("{} {}", sudo_cmd, cmd); + } - self_activate_command + cmd + } } #[test] fn test_activation_command_builder() { - let sudo = Some("sudo -u test".to_string()); + let sudo = Some("sudo -u test"); let profile_path = "/blah/profiles/test"; let closure = "/nix/store/blah/etc"; let auto_rollback = true; @@ -78,8 +118,8 @@ fn test_activation_command_builder() { let log_dir = Some("/tmp/something.txt"); assert_eq!( - build_activate_command(&ActivateCommandData { - sudo: &sudo, + ActivateCommand { + sudo, profile_path, closure, auto_rollback, @@ -89,113 +129,165 @@ fn test_activation_command_builder() { debug_logs, log_dir, dry_activate - }), + }.build(), "sudo -u test /nix/store/blah/etc/activate-rs --debug-logs --log-dir /tmp/something.txt activate '/nix/store/blah/etc' '/blah/profiles/test' --temp-path '/tmp' --confirm-timeout 30 --magic-rollback --auto-rollback" .to_string(), ); } -struct WaitCommandData<'a> { - sudo: &'a Option, +pub struct WaitCommand<'a> { + sudo: Option<&'a str>, closure: &'a str, temp_path: &'a str, debug_logs: bool, log_dir: Option<&'a str>, } -fn build_wait_command(data: &WaitCommandData) -> String { - let mut self_activate_command = format!("{}/activate-rs", data.closure); - - if data.debug_logs { - self_activate_command = format!("{} --debug-logs", self_activate_command); +impl<'a> WaitCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + WaitCommand { + sudo: d.sudo.as_deref(), + temp_path: &d.temp_path, + closure: &d.profile.profile_settings.path, + debug_logs: d.flags.debug_logs, + log_dir: d.flags.log_dir.as_deref(), + } } - if let Some(log_dir) = data.log_dir { - self_activate_command = format!("{} --log-dir {}", self_activate_command, log_dir); - } + fn build(self) -> String { + let mut cmd = format!("{}/activate-rs", self.closure); - self_activate_command = format!( - "{} wait '{}' --temp-path '{}'", - self_activate_command, data.closure, data.temp_path, - ); + if self.debug_logs { + cmd = format!("{} --debug-logs", cmd); + } - if let Some(sudo_cmd) = &data.sudo { - self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); - } + if let Some(log_dir) = self.log_dir { + cmd = format!("{} --log-dir {}", cmd, log_dir); + } + + cmd = format!( + "{} wait '{}' --temp-path '{}'", + cmd, self.closure, self.temp_path, + ); + + if let Some(sudo_cmd) = &self.sudo { + cmd = format!("{} {}", sudo_cmd, cmd); + } - self_activate_command + cmd + } } #[test] fn test_wait_command_builder() { - let sudo = Some("sudo -u test".to_string()); + let sudo = Some("sudo -u test"); let closure = "/nix/store/blah/etc"; let temp_path = "/tmp"; let debug_logs = true; let log_dir = Some("/tmp/something.txt"); assert_eq!( - build_wait_command(&WaitCommandData { - sudo: &sudo, + WaitCommand { + sudo, closure, temp_path, debug_logs, log_dir - }), + }.build(), "sudo -u test /nix/store/blah/etc/activate-rs --debug-logs --log-dir /tmp/something.txt wait '/nix/store/blah/etc' --temp-path '/tmp'" .to_string(), ); } -struct RevokeCommandData<'a> { - sudo: &'a Option, +pub struct RevokeCommand<'a> { + sudo: Option<&'a str>, closure: &'a str, profile_path: &'a str, debug_logs: bool, log_dir: Option<&'a str>, } -fn build_revoke_command(data: &RevokeCommandData) -> String { - let mut self_activate_command = format!("{}/activate-rs", data.closure); - - if data.debug_logs { - self_activate_command = format!("{} --debug-logs", self_activate_command); +impl<'a> RevokeCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + RevokeCommand { + sudo: d.sudo.as_deref(), + profile_path: &d.profile_path, + closure: &d.profile.profile_settings.path, + debug_logs: d.flags.debug_logs, + log_dir: d.flags.log_dir.as_deref(), + } } - if let Some(log_dir) = data.log_dir { - self_activate_command = format!("{} --log-dir {}", self_activate_command, log_dir); - } - self_activate_command = format!("{} revoke '{}'", self_activate_command, data.profile_path); + fn build(self) -> String { + let mut cmd = format!("{}/activate-rs", self.closure); - if let Some(sudo_cmd) = &data.sudo { - self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); - } + if self.debug_logs { + cmd = format!("{} --debug-logs", cmd); + } + + if let Some(log_dir) = self.log_dir { + cmd = format!("{} --log-dir {}", cmd, log_dir); + } + + cmd = format!("{} revoke '{}'", cmd, self.profile_path); + + if let Some(sudo_cmd) = &self.sudo { + cmd = format!("{} {}", sudo_cmd, cmd); + } - self_activate_command + cmd + } } #[test] fn test_revoke_command_builder() { - let sudo = Some("sudo -u test".to_string()); + let sudo = Some("sudo -u test"); let closure = "/nix/store/blah/etc"; let profile_path = "/nix/var/nix/per-user/user/profile"; let debug_logs = true; let log_dir = Some("/tmp/something.txt"); assert_eq!( - build_revoke_command(&RevokeCommandData { - sudo: &sudo, + RevokeCommand { + sudo, closure, profile_path, debug_logs, log_dir - }), + }.build(), "sudo -u test /nix/store/blah/etc/activate-rs --debug-logs --log-dir /tmp/something.txt revoke '/nix/var/nix/per-user/user/profile'" .to_string(), ); } +pub struct ConfirmCommand<'a> { + sudo: Option<&'a str>, + temp_path: &'a str, + closure: &'a str, +} + +impl<'a> ConfirmCommand<'a> { + pub fn from_data(d: &'a data::DeployData) -> Self { + ConfirmCommand { + sudo: d.sudo.as_deref(), + temp_path: &d.temp_path, + closure: &d.profile.profile_settings.path, + } + } + + + fn build(self) -> String { + let lock_path = super::make_lock_path(&self.temp_path, &self.closure); + + let mut cmd = format!("rm {}", lock_path); + if let Some(sudo_cmd) = &self.sudo { + cmd = format!("{} {}", sudo_cmd, cmd); + } + cmd + } +} + #[derive(Error, Debug)] pub enum ConfirmProfileError { #[error("Failed to run confirmation command over SSH (the server should roll back): {0}")] @@ -204,34 +296,24 @@ pub enum ConfirmProfileError { "Confirming activation over SSH resulted in a bad exit code (the server should roll back): {0:?}" )] SSHConfirmExit(Option), - - #[error("Deployment data invalid: {0}")] - DeployData(#[from] data::DeployDataError), } pub async fn confirm_profile( - deploy_data: &data::DeployData<'_>, - deploy_defs: &data::DeployDefs, - temp_path: Cow<'_, str>, + ssh: SshCommand<'_>, + confirm: ConfirmCommand<'_>, ) -> Result<(), ConfirmProfileError> { - let mut ssh_confirm_command = Command::new("ssh"); - ssh_confirm_command.arg(deploy_data.ssh_non_uri()?); - ssh_confirm_command.args(deploy_data.ssh_opts()); - let lock_path = super::make_lock_path(&temp_path, &deploy_data.profile.profile_settings.path); + let mut ssh_confirm_cmd = ssh.build(); - let mut confirm_command = format!("rm {}", lock_path); - if let Some(sudo_cmd) = &deploy_defs.sudo { - confirm_command = format!("{} {}", sudo_cmd, confirm_command); - } + let confirm_cmd = confirm.build(); debug!( "Attempting to run command to confirm deployment: {}", - confirm_command + confirm_cmd ); - let ssh_confirm_exit_status = ssh_confirm_command - .arg(confirm_command) + let ssh_confirm_exit_status = ssh_confirm_cmd + .arg(confirm_cmd) .status() .await .map_err(ConfirmProfileError::SSHConfirm)?; @@ -263,56 +345,31 @@ pub enum DeployProfileError { #[error("Error confirming deployment: {0}")] Confirm(#[from] ConfirmProfileError), - - #[error("Deployment data invalid: {0}")] - DeployData(#[from] data::DeployDataError), } pub async fn deploy_profile( - deploy_data: &data::DeployData<'_>, - deploy_defs: &data::DeployDefs, - dry_activate: bool, + node_name: &str, + profile_name: &str, + ssh: SshCommand<'_>, + activate: ActivateCommand<'_>, + wait: WaitCommand<'_>, + confirm: ConfirmCommand<'_>, ) -> Result<(), DeployProfileError> { - if !dry_activate { - info!( - "Activating profile `{}` for node `{}`", - deploy_data.profile_name, deploy_data.node_name - ); + if !activate.dry_activate { + info!("Activating profile `{}` for node `{}`", profile_name, node_name); } + let dry_activate = &activate.dry_activate.clone(); + let magic_rollback = &activate.magic_rollback.clone(); - let temp_path: Cow = match &deploy_data.merged_settings.temp_path { - Some(x) => x.into(), - None => "/tmp".into(), - }; - - let confirm_timeout = deploy_data.merged_settings.confirm_timeout.unwrap_or(30); - - let magic_rollback = deploy_data.merged_settings.magic_rollback.unwrap_or(true); - - let auto_rollback = deploy_data.merged_settings.auto_rollback.unwrap_or(true); + let activate_cmd = activate.build(); - let self_activate_command = build_activate_command(&ActivateCommandData { - sudo: &deploy_defs.sudo, - profile_path: &deploy_defs.profile_path, - closure: &deploy_data.profile.profile_settings.path, - auto_rollback, - temp_path: &temp_path, - confirm_timeout, - magic_rollback, - debug_logs: deploy_data.flags.debug_logs, - log_dir: deploy_data.flags.log_dir.as_deref(), - dry_activate, - }); + debug!("Constructed activation command: {}", activate_cmd); - debug!("Constructed activation command: {}", self_activate_command); + let mut ssh_activate_cmd = ssh.build(); - let mut ssh_activate_command = Command::new("ssh"); - ssh_activate_command.arg(deploy_data.ssh_non_uri()?); - ssh_activate_command.args(deploy_data.ssh_opts()); - - if !magic_rollback || dry_activate { - let ssh_activate_exit_status = ssh_activate_command - .arg(self_activate_command) + if !*magic_rollback || *dry_activate { + let ssh_activate_exit_status = ssh_activate_cmd + .arg(activate_cmd) .status() .await .map_err(DeployProfileError::SSHActivate)?; @@ -322,32 +379,25 @@ pub async fn deploy_profile( a => return Err(DeployProfileError::SSHActivateExit(a)), }; - if dry_activate { + if *dry_activate { info!("Completed dry-activate!"); } else { info!("Success activating, done!"); } } else { - let self_wait_command = build_wait_command(&WaitCommandData { - sudo: &deploy_defs.sudo, - closure: &deploy_data.profile.profile_settings.path, - temp_path: &temp_path, - debug_logs: deploy_data.flags.debug_logs, - log_dir: deploy_data.flags.log_dir.as_deref(), - }); + let wait_cmd = wait.build(); - debug!("Constructed wait command: {}", self_wait_command); + debug!("Constructed wait command: {}", wait_cmd); - let ssh_activate = ssh_activate_command - .arg(self_activate_command) + let ssh_activate = ssh_activate_cmd + .arg(activate_cmd) .spawn() .map_err(DeployProfileError::SSHSpawnActivate)?; info!("Creating activation waiter"); - let mut ssh_wait_command = Command::new("ssh"); - ssh_wait_command.arg(deploy_data.ssh_non_uri()?); - ssh_wait_command.args(deploy_data.ssh_opts()); + + let mut ssh_wait_cmd = ssh.build(); let (send_activate, recv_activate) = tokio::sync::oneshot::channel(); let (send_activated, recv_activated) = tokio::sync::oneshot::channel(); @@ -370,7 +420,7 @@ pub async fn deploy_profile( send_activated.send(()).unwrap(); }); tokio::select! { - x = ssh_wait_command.arg(self_wait_command).status() => { + x = ssh_wait_cmd.arg(wait_cmd).status() => { debug!("Wait command ended"); match x.map_err(DeployProfileError::SSHWait)?.code() { Some(0) => (), @@ -385,7 +435,7 @@ pub async fn deploy_profile( info!("Success activating, attempting to confirm activation"); - let c = confirm_profile(deploy_data, deploy_defs, temp_path).await; + let c = confirm_profile(ssh, confirm).await; recv_activated.await.unwrap(); c?; } @@ -402,34 +452,26 @@ pub enum RevokeProfileError { SSHRevoke(std::io::Error), #[error("Revoking over SSH resulted in a bad exit code: {0:?}")] SSHRevokeExit(Option), - - #[error("Deployment data invalid: {0}")] - DeployData(#[from] data::DeployDataError), } pub async fn revoke( - deploy_data: &data::DeployData<'_>, - deploy_defs: &data::DeployDefs, + node_name: &str, + profile_name: &str, + ssh: SshCommand<'_>, + revoke: RevokeCommand<'_>, ) -> Result<(), RevokeProfileError> { - let self_revoke_command = build_revoke_command(&RevokeCommandData { - sudo: &deploy_defs.sudo, - closure: &deploy_data.profile.profile_settings.path, - profile_path: &deploy_data.get_profile_path()?, - debug_logs: deploy_data.flags.debug_logs, - log_dir: deploy_data.flags.log_dir.as_deref(), - }); - - debug!("Constructed revoke command: {}", self_revoke_command); - - let mut ssh_activate_command = Command::new("ssh"); - ssh_activate_command.arg(deploy_data.ssh_non_uri()?); - ssh_activate_command.args(deploy_data.ssh_opts()); - - let ssh_revoke = ssh_activate_command - .arg(self_revoke_command) + info!("Revoking profile `{}` for node `{}`", profile_name, node_name); + + let revoke_cmd = revoke.build(); + debug!("Constructed revoke command: {}", revoke_cmd); + + let mut ssh_revoke_cmd = ssh.build(); + + let ssh_revoke_cmd = ssh_revoke_cmd + .arg(revoke_cmd) .spawn() .map_err(RevokeProfileError::SSHSpawnRevoke)?; - let result = ssh_revoke.wait_with_output().await; + let result = ssh_revoke_cmd.wait_with_output().await; match result { Err(x) => Err(RevokeProfileError::SSHRevoke(x)), diff --git a/src/push.rs b/src/push.rs index 6c0f0b92..76d11b92 100644 --- a/src/push.rs +++ b/src/push.rs @@ -207,7 +207,7 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr .arg(&data.deploy_data.profile.profile_settings.path) .env( "NIX_SSHOPTS", - data.deploy_data.ssh_opts().fold("".to_string(), |s, o| format!("{} {}", s, o)) + data.deploy_data.ssh_opts()?.fold("".to_string(), |s, o| format!("{} {}", s, o)) ) .status() .await