From e14acaf2bdc14bbdc30f3d558b62f64fe33ff5f9 Mon Sep 17 00:00:00 2001
From: notgne2
Date: Thu, 1 Oct 2020 18:21:40 -0700
Subject: Rework system for deploy properties, add CLI override flags

---
 src/utils/deploy.rs |  37 +++++-------
 src/utils/mod.rs    | 166 +++++++++++++++++++++++++++++++++++++++-------------
 src/utils/push.rs   |  46 +++++++--------
 3 files changed, 165 insertions(+), 84 deletions(-)

(limited to 'src/utils')

diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs
index 1abae64..d46f2db 100644
--- a/src/utils/deploy.rs
+++ b/src/utils/deploy.rs
@@ -2,8 +2,6 @@
 //
 // SPDX-License-Identifier: MPL-2.0
 
-use super::data;
-
 use tokio::process::Command;
 
 fn build_activate_command(
@@ -68,38 +66,35 @@ fn test_activation_command_builder() {
 }
 
 pub async fn deploy_profile(
-    profile: &data::Profile,
-    profile_name: &str,
-    node: &data::Node,
-    node_name: &str,
-    merged_settings: &data::GenericSettings,
     deploy_data: &super::DeployData<'_>,
-    auto_rollback: bool,
+    deploy_defs: &super::DeployDefs<'_>,
 ) -> Result<(), Box<dyn std::error::Error>> {
     info!(
         "Activating profile `{}` for node `{}`",
-        profile_name, node_name
+        deploy_data.profile_name, deploy_data.node_name
     );
 
-    let activate_path_str = super::deploy_path_to_activate_path_str(&deploy_data.current_exe)?;
+    let activate_path_str = super::deploy_path_to_activate_path_str(&deploy_defs.current_exe)?;
 
     let self_activate_command = build_activate_command(
         activate_path_str,
-        &deploy_data.sudo,
-        &deploy_data.profile_path,
-        &profile.profile_settings.path,
-        &profile.profile_settings.activate,
-        &profile.profile_settings.bootstrap,
-        auto_rollback,
+        &deploy_defs.sudo,
+        &deploy_defs.profile_path,
+        &deploy_data.profile.profile_settings.path,
+        &deploy_data.profile.profile_settings.activate,
+        &deploy_data.profile.profile_settings.bootstrap,
+        deploy_data.merged_settings.auto_rollback,
     )?;
 
+    let hostname = match deploy_data.cmd_overrides.hostname {
+        Some(ref x) => x,
+        None => &deploy_data.node.node_settings.hostname,
+    };
+
     let mut c = Command::new("ssh");
-    let mut ssh_command = c.arg(format!(
-        "ssh://{}@{}",
-        deploy_data.ssh_user, node.node_settings.hostname
-    ));
+    let mut ssh_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname));
 
-    for ssh_opt in &merged_settings.ssh_opts {
+    for ssh_opt in &deploy_data.merged_settings.ssh_opts {
         ssh_command = ssh_command.arg(ssh_opt);
     }
 
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 30201c3..bfdbc5e 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -5,6 +5,8 @@
 use std::borrow::Cow;
 use std::path::PathBuf;
 
+use merge::Merge;
+
 #[macro_export]
 macro_rules! good_panic {
     ($($tts:tt)*) => {{
@@ -17,6 +19,40 @@ pub mod data;
 pub mod deploy;
 pub mod push;
 
+pub struct CmdOverrides {
+    pub ssh_user: Option<String>,
+    pub profile_user: Option<String>,
+    pub ssh_opts: Option<String>,
+    pub fast_connection: Option<bool>,
+    pub auto_rollback: Option<bool>,
+    pub hostname: Option<String>,
+}
+
+pub enum OverridePurity {
+    ErrorProfile,
+    Error,
+    Warn,
+    Pure,
+}
+
+impl CmdOverrides {
+    pub fn purity(&self) -> OverridePurity {
+        if self.profile_user.is_some() {
+            return OverridePurity::ErrorProfile;
+        }
+
+        if self.hostname.is_some() || self.ssh_user.is_some() {
+            return OverridePurity::Error;
+        }
+
+        if self.ssh_opts.is_some() || self.fast_connection.is_some() {
+            return OverridePurity::Warn;
+        }
+
+        OverridePurity::Pure
+    }
+}
+
 #[derive(PartialEq, Debug)]
 pub struct DeployFlake<'a> {
     pub repo: &'a str,
@@ -80,60 +116,110 @@ fn test_parse_flake() {
 }
 
 pub struct DeployData<'a> {
-    pub sudo: Option<String>,
+    pub node_name: &'a str,
+    pub node: &'a data::Node,
+    pub profile_name: &'a str,
+    pub profile: &'a data::Profile,
+
+    pub cmd_overrides: &'a CmdOverrides,
+
+    pub merged_settings: data::GenericSettings,
+}
+
+pub struct DeployDefs<'a> {
     pub ssh_user: Cow<'a, str>,
     pub profile_user: Cow<'a, str>,
     pub profile_path: String,
     pub current_exe: PathBuf,
+    pub sudo: Option<String>,
 }
 
-pub async fn make_deploy_data<'a>(
-    profile_name: &str,
-    node_name: &str,
-    merged_settings: &'a data::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
+impl<'a> DeployData<'a> {
+    pub fn defs(&'a self) -> DeployDefs<'a> {
+        let ssh_user: Cow<str> = match self.merged_settings.ssh_user {
+            Some(ref u) => u.into(),
+            None => whoami::username().into(),
+        };
+
+        let profile_user: Cow<str> = match self.merged_settings.user {
+            Some(ref x) => x.into(),
+            None => match self.merged_settings.ssh_user {
+                Some(ref x) => x.into(),
+                None => good_panic!(
+                    "Neither user nor sshUser set for profile `{}` of node `{}`",
+                    self.profile_name,
+                    self.node_name
+                ),
+            },
+        };
+
+        let profile_path = match &profile_user[..] {
+            "root" => format!("/nix/var/nix/profiles/{}", self.profile_name),
+            _ => format!(
+                "/nix/var/nix/profiles/per-user/{}/{}",
+                profile_user, self.profile_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 self.merged_settings.user {
+            Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)),
+            _ => None,
+        };
 
-    let sudo: Option<String> = match merged_settings.user {
-        Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)),
-        _ => 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");
+        }
+
+        DeployDefs {
+            ssh_user,
+            profile_user,
+            profile_path,
+            current_exe,
+            sudo,
+        }
+    }
+}
 
-    let current_exe = std::env::current_exe().expect("Expected to find current executable path");
+pub fn make_deploy_data<'a, 's>(
+    top_settings: &'s data::GenericSettings,
+    node: &'a data::Node,
+    node_name: &'a str,
+    profile: &'a data::Profile,
+    profile_name: &'a str,
+    cmd_overrides: &'a CmdOverrides,
+) -> Result<DeployData<'a>, Box<dyn std::error::Error>> {
+    let mut merged_settings = top_settings.clone();
+    merged_settings.merge(node.generic_settings.clone());
+    merged_settings.merge(profile.generic_settings.clone());
 
-    if !current_exe.starts_with("/nix/store/") {
-        good_panic!("The deploy binary must be in the Nix store");
+    if cmd_overrides.ssh_user.is_some() {
+        merged_settings.ssh_user = cmd_overrides.ssh_user.clone();
+    }
+    if cmd_overrides.profile_user.is_some() {
+        merged_settings.user = cmd_overrides.profile_user.clone();
+    }
+    if let Some(ref ssh_opts) = cmd_overrides.ssh_opts {
+        merged_settings.ssh_opts = ssh_opts.split(' ').map(|x| x.to_owned()).collect();
+    }
+    if let Some(fast_connection) = cmd_overrides.fast_connection {
+        merged_settings.fast_connection = fast_connection;
+    }
+    if let Some(auto_rollback) = cmd_overrides.auto_rollback {
+        merged_settings.auto_rollback = auto_rollback;
     }
 
     Ok(DeployData {
-        sudo,
-        ssh_user,
-        profile_user,
-        profile_path,
-        current_exe,
+        profile,
+        profile_name,
+        node,
+        node_name,
+
+        cmd_overrides,
+
+        merged_settings,
     })
 }
 
diff --git a/src/utils/push.rs b/src/utils/push.rs
index 38a576f..9a6748e 100644
--- a/src/utils/push.rs
+++ b/src/utils/push.rs
@@ -2,30 +2,24 @@
 //
 // SPDX-License-Identifier: MPL-2.0
 
-use super::data;
-
 use std::process::Stdio;
 use tokio::process::Command;
 
 pub async fn push_profile(
-    profile: &data::Profile,
-    profile_name: &str,
-    node: &data::Node,
-    node_name: &str,
     supports_flakes: bool,
     check_sigs: bool,
     repo: &str,
-    merged_settings: &data::GenericSettings,
     deploy_data: &super::DeployData<'_>,
+    deploy_defs: &super::DeployDefs<'_>,
 ) -> Result<(), Box<dyn std::error::Error>> {
     info!(
         "Pushing profile `{}` for node `{}`",
-        profile_name, node_name
+        deploy_data.profile_name, deploy_data.node_name
     );
 
     debug!(
         "Building profile `{} for node `{}`",
-        profile_name, node_name
+        deploy_data.profile_name, deploy_data.node_name
     );
 
     if supports_flakes {
@@ -34,7 +28,7 @@ pub async fn push_profile(
             .arg("--no-link")
             .arg(format!(
                 "{}#deploy.nodes.{}.profiles.{}.path",
-                repo, node_name, profile_name
+                repo, deploy_data.node_name, deploy_data.profile_name
             ))
             .stdout(Stdio::null())
             .stderr(Stdio::null())
@@ -46,7 +40,7 @@ pub async fn push_profile(
             .arg("-A")
             .arg(format!(
                 "deploy.nodes.{}.profiles.{}.path",
-                node_name, profile_name
+                deploy_data.node_name, deploy_data.profile_name
             ))
             .stdout(Stdio::null())
             .stderr(Stdio::null())
@@ -57,7 +51,7 @@ pub async fn push_profile(
     if let Ok(local_key) = std::env::var("LOCAL_KEY") {
         info!(
             "Signing key present! Signing profile `{}` for node `{}`",
-            profile_name, node_name
+            deploy_data.profile_name, deploy_data.node_name
         );
 
         Command::new("nix")
@@ -65,9 +59,9 @@ pub async fn push_profile(
             .arg("-r")
             .arg("-k")
             .arg(local_key)
-            .arg(&profile.profile_settings.path)
+            .arg(&deploy_data.profile.profile_settings.path)
             .arg(&super::deploy_path_to_activate_path_str(
-                &deploy_data.current_exe,
+                &deploy_defs.current_exe,
             )?)
             .stdout(Stdio::null())
             .stderr(Stdio::null())
@@ -75,12 +69,15 @@ pub async fn push_profile(
             .await?;
     }
 
-    debug!("Copying profile `{} for node `{}`", profile_name, node_name);
+    debug!(
+        "Copying profile `{} for node `{}`",
+        deploy_data.profile_name, deploy_data.node_name
+    );
 
     let mut copy_command_ = Command::new("nix");
     let mut copy_command = copy_command_.arg("copy");
 
-    if merged_settings.fast_connection {
+    if deploy_data.merged_settings.fast_connection {
         copy_command = copy_command.arg("--substitute-on-destination");
     }
 
@@ -88,7 +85,8 @@ pub async fn push_profile(
         copy_command = copy_command.arg("--no-check-sigs");
     }
 
-    let ssh_opts_str = merged_settings
+    let ssh_opts_str = deploy_data
+        .merged_settings
         .ssh_opts
         // This should provide some extra safety, but it also breaks for some reason, oh well
         // .iter()
@@ -96,15 +94,17 @@ pub async fn push_profile(
         // .collect::<Vec<String>>()
         .join(" ");
 
+    let hostname = match deploy_data.cmd_overrides.hostname {
+        Some(ref x) => x,
+        None => &deploy_data.node.node_settings.hostname,
+    };
+
     copy_command
         .arg("--to")
-        .arg(format!(
-            "ssh://{}@{}",
-            deploy_data.ssh_user, node.node_settings.hostname
-        ))
-        .arg(&profile.profile_settings.path)
+        .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname))
+        .arg(&deploy_data.profile.profile_settings.path)
         .arg(&super::deploy_path_to_activate_path_str(
-            &deploy_data.current_exe,
+            &deploy_defs.current_exe,
         )?)
         .env("NIX_SSHOPTS", ssh_opts_str)
         .spawn()?
-- 
cgit v1.2.3