aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/main.rs140
-rw-r--r--src/utils/deploy.rs37
-rw-r--r--src/utils/mod.rs166
-rw-r--r--src/utils/push.rs46
4 files changed, 260 insertions, 129 deletions
diff --git a/src/main.rs b/src/main.rs
index ba87e46..0d80e42 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -31,6 +31,25 @@ struct Opts {
checksigs: bool,
/// Extra arguments to be passed to nix build
extra_build_args: Vec<String>,
+
+ /// Override the SSH user with the given value
+ #[clap(long)]
+ ssh_user: Option<String>,
+ /// Override the profile user with the given value
+ #[clap(long)]
+ profile_user: Option<String>,
+ /// Override the SSH options used
+ #[clap(long)]
+ ssh_opts: Option<String>,
+ /// Override if the connecting to the target node should be considered fast
+ #[clap(long)]
+ fast_connection: Option<bool>,
+ /// Override if a rollback should be attempted if activation fails
+ #[clap(long)]
+ auto_rollback: Option<bool>,
+ /// Override hostname used for the node
+ #[clap(long)]
+ hostname: Option<String>,
}
#[inline]
@@ -41,6 +60,7 @@ async fn push_all_profiles(
repo: &str,
top_settings: &utils::data::GenericSettings,
check_sigs: bool,
+ cmd_overrides: &utils::CmdOverrides,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Pushing all profiles for `{}`", node_name);
@@ -63,19 +83,23 @@ async fn push_all_profiles(
merged_settings.merge(node.generic_settings.clone());
merged_settings.merge(profile.generic_settings.clone());
- let deploy_data =
- utils::make_deploy_data(profile_name, node_name, &merged_settings).await?;
-
- utils::push::push_profile(
- profile,
- profile_name,
+ let deploy_data = utils::make_deploy_data(
+ top_settings,
node,
node_name,
+ profile,
+ profile_name,
+ cmd_overrides,
+ )?;
+
+ let deploy_defs = deploy_data.defs();
+
+ utils::push::push_profile(
supports_flakes,
check_sigs,
repo,
- &merged_settings,
&deploy_data,
+ &deploy_defs,
)
.await?;
}
@@ -88,6 +112,7 @@ async fn deploy_all_profiles(
node: &utils::data::Node,
node_name: &str,
top_settings: &utils::data::GenericSettings,
+ cmd_overrides: &utils::CmdOverrides,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Deploying all profiles for `{}`", node_name);
@@ -110,19 +135,18 @@ async fn deploy_all_profiles(
merged_settings.merge(node.generic_settings.clone());
merged_settings.merge(profile.generic_settings.clone());
- let deploy_data =
- utils::make_deploy_data(profile_name, node_name, &merged_settings).await?;
-
- utils::deploy::deploy_profile(
- profile,
- profile_name,
+ let deploy_data = utils::make_deploy_data(
+ top_settings,
node,
node_name,
- &merged_settings,
- &deploy_data,
- merged_settings.auto_rollback,
- )
- .await?;
+ profile,
+ profile_name,
+ cmd_overrides,
+ )?;
+
+ let deploy_defs = deploy_data.defs();
+
+ utils::deploy::deploy_profile(&deploy_data, &deploy_defs).await?;
}
Ok(())
@@ -197,7 +221,8 @@ async fn run_deploy(
deploy_flake: utils::DeployFlake<'_>,
data: utils::data::Data,
supports_flakes: bool,
- opts: &Opts,
+ check_sigs: bool,
+ cmd_overrides: utils::CmdOverrides,
) -> Result<(), Box<dyn std::error::Error>> {
match (deploy_flake.node, deploy_flake.profile) {
(Some(node_name), Some(profile_name)) => {
@@ -210,36 +235,27 @@ async fn run_deploy(
None => good_panic!("No profile was found named `{}`", profile_name),
};
- let mut merged_settings = data.generic_settings.clone();
- merged_settings.merge(node.generic_settings.clone());
- merged_settings.merge(profile.generic_settings.clone());
+ let deploy_data = utils::make_deploy_data(
+ &data.generic_settings,
+ node,
+ node_name,
+ profile,
+ profile_name,
+ &cmd_overrides,
+ )?;
- let deploy_data =
- utils::make_deploy_data(profile_name, node_name, &merged_settings).await?;
+ let deploy_defs = deploy_data.defs();
utils::push::push_profile(
- profile,
- profile_name,
- node,
- node_name,
supports_flakes,
- opts.checksigs,
+ check_sigs,
deploy_flake.repo,
- &merged_settings,
&deploy_data,
+ &deploy_defs,
)
.await?;
- utils::deploy::deploy_profile(
- profile,
- profile_name,
- node,
- node_name,
- &merged_settings,
- &deploy_data,
- merged_settings.auto_rollback,
- )
- .await?;
+ utils::deploy::deploy_profile(&deploy_data, &deploy_defs).await?;
}
(Some(node_name), None) => {
let node = match data.nodes.get(node_name) {
@@ -253,11 +269,12 @@ async fn run_deploy(
supports_flakes,
deploy_flake.repo,
&data.generic_settings,
- opts.checksigs,
+ check_sigs,
+ &cmd_overrides,
)
.await?;
- deploy_all_profiles(node, node_name, &data.generic_settings).await?;
+ deploy_all_profiles(node, node_name, &data.generic_settings, &cmd_overrides).await?;
}
(None, None) => {
info!("Deploying all profiles on all nodes");
@@ -269,13 +286,15 @@ async fn run_deploy(
supports_flakes,
deploy_flake.repo,
&data.generic_settings,
- opts.checksigs,
+ check_sigs,
+ &cmd_overrides,
)
.await?;
}
for (node_name, node) in &data.nodes {
- deploy_all_profiles(node, node_name, &data.generic_settings).await?;
+ deploy_all_profiles(node, node_name, &data.generic_settings, &cmd_overrides)
+ .await?;
}
}
(None, Some(_)) => {
@@ -285,6 +304,7 @@ async fn run_deploy(
Ok(())
}
+
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
if std::env::var("DEPLOY_LOG").is_err() {
@@ -297,12 +317,42 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let deploy_flake = utils::parse_flake(opts.flake.as_str());
+ let cmd_overrides = utils::CmdOverrides {
+ ssh_user: opts.ssh_user,
+ profile_user: opts.profile_user,
+ ssh_opts: opts.ssh_opts,
+ fast_connection: opts.fast_connection,
+ auto_rollback: opts.auto_rollback,
+ hostname: opts.hostname,
+ };
+
+ match (cmd_overrides.purity(), deploy_flake.node, deploy_flake.profile) {
+ (utils::OverridePurity::ErrorProfile, _, None) => good_panic!(
+ "You have specified an override not suitible for deploying to multiple profiles, please specify your target profile explicitly"
+ ),
+ (utils::OverridePurity::Error, None, _) => good_panic!(
+ "You have specified an override not suitible for deploying to multiple nodes, please specify your target node explicitly"
+ ),
+
+ (utils::OverridePurity::Warn, None, _) => warn!(
+ "Certain overrides you have provided might be dangerous when used on multiple nodes or profiles, be cautious"
+ ),
+ _ => (),
+ };
+
let supports_flakes = test_flake_support().await?;
let data =
get_deployment_data(supports_flakes, deploy_flake.repo, &opts.extra_build_args).await?;
- run_deploy(deploy_flake, data, supports_flakes, &opts).await?;
+ run_deploy(
+ deploy_flake,
+ data,
+ supports_flakes,
+ opts.checksigs,
+ cmd_overrides,
+ )
+ .await?;
Ok(())
}
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()?