diff --git a/src/cli.rs b/src/cli.rs index 72ecee9d..0e5cc2d8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -28,66 +28,16 @@ pub struct Opts { /// A list of flakes to deploy alternatively #[clap(long, group = "deploy")] targets: Option>, - /// Check signatures when using `nix copy` - #[clap(short, long)] - checksigs: bool, - /// Use the interactive prompt before deployment - #[clap(short, long)] - interactive: bool, - /// Extra arguments to be passed to nix build - extra_build_args: Vec, - - /// Print debug logs to output - #[clap(short, long)] - debug_logs: bool, - /// Directory to print logs to (including the background activation process) - #[clap(long)] - log_dir: Option, - - /// Keep the build outputs of each built profile - #[clap(short, long)] - keep_result: bool, - /// Location to keep outputs from built profiles in - #[clap(short, long)] - result_path: Option, - /// Skip the automatic pre-build checks - #[clap(short, long)] - skip_checks: bool, - - /// Override the SSH user with the given value - #[clap(long)] - ssh_user: Option, - /// Override the profile user with the given value - #[clap(long)] - profile_user: Option, - /// Override the SSH options used - #[clap(long)] - ssh_opts: Option, - /// Override if the connecting to the target node should be considered fast - #[clap(long)] - fast_connection: Option, - /// Override if a rollback should be attempted if activation fails - #[clap(long)] - auto_rollback: Option, /// Override hostname used for the node #[clap(long)] hostname: Option, - /// Make activation wait for confirmation, or roll back after a period of time - #[clap(long)] - magic_rollback: Option, - /// How long activation should wait for confirmation (if using magic-rollback) - #[clap(long)] - confirm_timeout: Option, - /// Where to store temporary files (only used by magic-rollback) - #[clap(long)] - temp_path: Option, - /// Show what will be activated on the machines - #[clap(long)] - dry_activate: bool, - /// Revoke all previously succeeded deploys when deploying multiple profiles - #[clap(long)] - rollback_succeeded: Option, + + #[clap(flatten)] + flags: data::Flags, + + #[clap(flatten)] + generic_settings: settings::GenericSettings, } /// Returns if the available Nix installation supports flakes @@ -240,27 +190,20 @@ type ToDeploy<'a> = Vec<( )>; async fn run_deploy( - deploy_targets: Vec, - data: Vec, + targets: Vec, + settings: Vec, supports_flakes: bool, - check_sigs: bool, - interactive: bool, - cmd_overrides: &data::CmdOverrides, - keep_result: bool, - result_path: Option<&str>, - extra_build_args: &[String], - debug_logs: bool, - dry_activate: bool, - log_dir: &Option, - rollback_succeeded: bool, + hostname: Option, + cmd_settings: settings::GenericSettings, + cmd_flags: data::Flags, ) -> Result<(), RunDeployError> { - let to_deploy: ToDeploy = deploy_targets + let to_deploy: ToDeploy = targets .iter() - .zip(&data) - .map(|(deploy_target, data)| { - let to_deploys: ToDeploy = match (&deploy_target.node, &deploy_target.profile) { + .zip(&settings) + .map(|(target, root)| { + let to_deploys: ToDeploy = match (&target.node, &target.profile) { (Some(node_name), Some(profile_name)) => { - let node = match data.nodes.get(node_name) { + let node = match root.nodes.get(node_name) { Some(x) => x, None => Err(RunDeployError::NodeNotFound(node_name.to_owned()))?, }; @@ -270,14 +213,14 @@ async fn run_deploy( }; vec![( - &deploy_target, - &data, + &target, + &root, (node_name.as_str(), node), (profile_name.as_str(), profile), )] } (Some(node_name), None) => { - let node = match data.nodes.get(node_name) { + let node = match root.nodes.get(node_name) { Some(x) => x, None => return Err(RunDeployError::NodeNotFound(node_name.to_owned())), }; @@ -306,13 +249,13 @@ async fn run_deploy( profiles_list .into_iter() - .map(|x| (deploy_target, data, (node_name.as_str(), node), x)) + .map(|x| (target, root, (node_name.as_str(), node), x)) .collect() } (None, None) => { let mut l = Vec::new(); - for (node_name, node) in &data.nodes { + for (node_name, node) in &root.nodes { let mut profiles_list: Vec<(&str, &settings::Profile)> = Vec::new(); for profile_name in [ @@ -337,7 +280,7 @@ async fn run_deploy( let ll: ToDeploy = profiles_list .into_iter() - .map(|x| (deploy_target, data, (node_name.as_str(), node), x)) + .map(|x| (target, root, (node_name.as_str(), node), x)) .collect(); l.extend(ll); @@ -360,39 +303,39 @@ async fn run_deploy( data::DeployDefs, )> = Vec::new(); - for (deploy_target, data, (node_name, node), (profile_name, profile)) in to_deploy { + for (target, root, (node_name, node), (profile_name, profile)) in to_deploy { let deploy_data = data::make_deploy_data( - &data.generic_settings, + &root.generic_settings, + &cmd_settings, + &cmd_flags, node, node_name, profile, profile_name, - &cmd_overrides, - debug_logs, - log_dir.as_deref(), + hostname.as_deref(), ); let deploy_defs = deploy_data.defs()?; - parts.push((deploy_target, deploy_data, deploy_defs)); + parts.push((target, deploy_data, deploy_defs)); } - if interactive { + if cmd_flags.interactive { prompt_deployment(&parts[..])?; } else { print_deployment(&parts[..])?; } - for (deploy_target, deploy_data, deploy_defs) in &parts { + for (target, deploy_data, deploy_defs) in &parts { deploy::push::push_profile(deploy::push::PushProfileData { - supports_flakes, - check_sigs, - repo: &deploy_target.repo, + supports_flakes: &supports_flakes, + check_sigs: &cmd_flags.checksigs, + repo: &target.repo, deploy_data: &deploy_data, deploy_defs: &deploy_defs, - keep_result, - result_path, - extra_build_args, + keep_result: &cmd_flags.keep_result, + result_path: cmd_flags.result_path.as_deref(), + extra_build_args: &cmd_flags.extra_build_args, }) .await?; } @@ -404,14 +347,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, dry_activate).await + if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, cmd_flags.dry_activate).await { error!("{}", e); - if dry_activate { + if cmd_flags.dry_activate { info!("dry run, not rolling back"); } info!("Revoking previous deploys"); - if rollback_succeeded && cmd_overrides.auto_rollback.unwrap_or(true) { + if cmd_flags.rollback_succeeded && cmd_settings.auto_rollback.unwrap_or(true) { // revoking all previous deploys // (adheres to profile configuration if not set explicitely by // the command line) @@ -456,8 +399,8 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> { }; deploy::init_logger( - opts.debug_logs, - opts.log_dir.as_deref(), + opts.flags.debug_logs, + opts.flags.log_dir.as_deref(), deploy::LoggerType::Deploy, )?; @@ -466,51 +409,30 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> { .targets .unwrap_or_else(|| vec![opts.clone().target.unwrap_or(".".to_string())]); - let deploy_targets: Vec = deploys - .into_iter() - .map(|f| f.parse::()) - .collect::, data::ParseTargetError>>()?; - - let cmd_overrides = data::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, - magic_rollback: opts.magic_rollback, - temp_path: opts.temp_path, - confirm_timeout: opts.confirm_timeout, - dry_activate: opts.dry_activate, - }; - let supports_flakes = test_flake_support().await.map_err(RunError::FlakeTest)?; if !supports_flakes { warn!("A Nix version without flakes support was detected, support for this is work in progress"); } - if !opts.skip_checks { - for deploy_target in deploy_targets.iter() { - flake::check_deployment(supports_flakes, &deploy_target.repo, &opts.extra_build_args).await?; + let targets: Vec = deploys + .into_iter() + .map(|f| f.parse::()) + .collect::, data::ParseTargetError>>()?; + + if !opts.flags.skip_checks { + for target in targets.iter() { + flake::check_deployment(supports_flakes, &target.repo, &opts.flags.extra_build_args).await?; } } - let result_path = opts.result_path.as_deref(); - let data = flake::get_deployment_data(supports_flakes, &deploy_targets, &opts.extra_build_args).await?; + let settings = flake::get_deployment_data(supports_flakes, &targets, &opts.flags.extra_build_args).await?; run_deploy( - deploy_targets, - data, + targets, + settings, supports_flakes, - opts.checksigs, - opts.interactive, - &cmd_overrides, - opts.keep_result, - result_path, - &opts.extra_build_args, - opts.debug_logs, - opts.dry_activate, - &opts.log_dir, - opts.rollback_succeeded.unwrap_or(true), + opts.hostname, + opts.generic_settings, + opts.flags, ) .await?; diff --git a/src/data.rs b/src/data.rs index 78154049..cb6af586 100644 --- a/src/data.rs +++ b/src/data.rs @@ -6,6 +6,7 @@ use rnix::{types::*, SyntaxKind::*}; use merge::Merge; use thiserror::Error; +use clap::Clap; use crate::settings; @@ -158,33 +159,53 @@ fn test_deploy_target_from_str() { ); } -#[derive(Debug)] -pub struct CmdOverrides { - pub ssh_user: Option, - pub profile_user: Option, - pub ssh_opts: Option, - pub fast_connection: Option, - pub auto_rollback: Option, - pub hostname: Option, - pub magic_rollback: Option, - pub temp_path: Option, - pub confirm_timeout: Option, - pub dry_activate: bool, -} - #[derive(Debug, Clone)] pub struct DeployData<'a> { pub node_name: &'a str, pub node: &'a settings::Node, pub profile_name: &'a str, pub profile: &'a settings::Profile, + pub hostname: Option<&'a str>, - pub cmd_overrides: &'a CmdOverrides, - + pub flags: &'a Flags, pub merged_settings: settings::GenericSettings, +} - pub debug_logs: bool, - pub log_dir: Option<&'a str>, +#[derive(Clap, Debug, Clone)] +pub struct Flags { + /// Check signatures when using `nix copy` + #[clap(short, long)] + pub checksigs: bool, + /// Use the interactive prompt before deployment + #[clap(short, long)] + pub interactive: bool, + /// Extra arguments to be passed to nix build + pub extra_build_args: Vec, + + /// Print debug logs to output + #[clap(short, long)] + pub debug_logs: bool, + /// Directory to print logs to (including the background activation process) + #[clap(long)] + pub log_dir: Option, + + /// Keep the build outputs of each built profile + #[clap(short, long)] + pub keep_result: bool, + /// Location to keep outputs from built profiles in + #[clap(short, long)] + pub result_path: Option, + + /// Skip the automatic pre-build checks + #[clap(short, long)] + pub skip_checks: bool, + /// Make activation wait for confirmation, or roll back after a period of time + /// Show what will be activated on the machines + #[clap(long)] + pub dry_activate: bool, + /// Revoke all previously succeeded deploys when deploying multiple profiles + #[clap(long)] + pub rollback_succeeded: bool, } #[derive(Debug)] @@ -257,47 +278,32 @@ impl<'a> DeployData<'a> { } } -pub fn make_deploy_data<'a, 's>( - top_settings: &'s settings::GenericSettings, +pub fn make_deploy_data<'a>( + top_settings: &'a settings::GenericSettings, + cmd_settings: &'a settings::GenericSettings, + flags: &'a Flags, node: &'a settings::Node, node_name: &'a str, profile: &'a settings::Profile, profile_name: &'a str, - cmd_overrides: &'a CmdOverrides, - debug_logs: bool, - log_dir: Option<&'a str>, + hostname: Option<&'a str>, ) -> DeployData<'a> { - let mut merged_settings = profile.generic_settings.clone(); + let mut merged_settings = cmd_settings.clone(); + merged_settings.merge(profile.generic_settings.clone()); merged_settings.merge(node.generic_settings.clone()); merged_settings.merge(top_settings.clone()); - 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 = Some(fast_connection); - } - if let Some(auto_rollback) = cmd_overrides.auto_rollback { - merged_settings.auto_rollback = Some(auto_rollback); - } - if let Some(magic_rollback) = cmd_overrides.magic_rollback { - merged_settings.magic_rollback = Some(magic_rollback); - } + // if let Some(ref ssh_opts) = cmd_overrides.ssh_opts { + // merged_settings.ssh_opts = ssh_opts.split(' ').map(|x| x.to_owned()).collect(); + // } DeployData { node_name, node, profile_name, profile, - cmd_overrides, + hostname, + flags, merged_settings, - debug_logs, - log_dir, } } diff --git a/src/deploy.rs b/src/deploy.rs index 432d2208..03f77b16 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -297,16 +297,16 @@ pub async fn deploy_profile( temp_path: &temp_path, confirm_timeout, magic_rollback, - debug_logs: deploy_data.debug_logs, - log_dir: deploy_data.log_dir, + debug_logs: deploy_data.flags.debug_logs, + log_dir: deploy_data.flags.log_dir.as_deref(), dry_activate, }); debug!("Constructed activation command: {}", self_activate_command); - let hostname = match deploy_data.cmd_overrides.hostname { - Some(ref x) => x, - None => &deploy_data.node.node_settings.hostname, + let hostname = match deploy_data.hostname { + Some(x) => x, + None => deploy_data.node.node_settings.hostname.as_str(), }; let ssh_addr = format!("{}@{}", deploy_defs.ssh_user, hostname); @@ -340,8 +340,8 @@ pub async fn deploy_profile( sudo: &deploy_defs.sudo, closure: &deploy_data.profile.profile_settings.path, temp_path: &temp_path, - debug_logs: deploy_data.debug_logs, - log_dir: deploy_data.log_dir, + debug_logs: deploy_data.flags.debug_logs, + log_dir: deploy_data.flags.log_dir.as_deref(), }); debug!("Constructed wait command: {}", self_wait_command); @@ -425,15 +425,15 @@ pub async fn revoke( sudo: &deploy_defs.sudo, closure: &deploy_data.profile.profile_settings.path, profile_path: &deploy_data.get_profile_path()?, - debug_logs: deploy_data.debug_logs, - log_dir: deploy_data.log_dir, + debug_logs: deploy_data.flags.debug_logs, + log_dir: deploy_data.flags.log_dir.as_deref(), }); debug!("Constructed revoke command: {}", self_revoke_command); - let hostname = match deploy_data.cmd_overrides.hostname { - Some(ref x) => x, - None => &deploy_data.node.node_settings.hostname, + let hostname = match deploy_data.hostname { + Some(x) => x, + None => deploy_data.node.node_settings.hostname.as_str(), }; let ssh_addr = format!("{}@{}", deploy_defs.ssh_user, hostname); diff --git a/src/push.rs b/src/push.rs index 5483c959..597f8b8e 100644 --- a/src/push.rs +++ b/src/push.rs @@ -46,12 +46,12 @@ pub enum PushProfileError { } pub struct PushProfileData<'a> { - pub supports_flakes: bool, - pub check_sigs: bool, + 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: bool, + pub keep_result: &'a bool, pub result_path: Option<&'a str>, pub extra_build_args: &'a [String], } @@ -95,13 +95,13 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr data.deploy_data.profile_name, data.deploy_data.node_name ); - let mut build_command = if data.supports_flakes { + let mut build_command = if *data.supports_flakes { Command::new("nix") } else { Command::new("nix-build") }; - if data.supports_flakes { + if *data.supports_flakes { build_command.arg("build").arg(derivation_name) } else { build_command.arg(derivation_name) @@ -208,9 +208,9 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr // .collect::>() .join(" "); - let hostname = match data.deploy_data.cmd_overrides.hostname { - Some(ref x) => x, - None => &data.deploy_data.node.node_settings.hostname, + let hostname = match data.deploy_data.hostname { + Some(x) => x, + None => data.deploy_data.node.node_settings.hostname.as_str(), }; let copy_exit_status = copy_command diff --git a/src/settings.rs b/src/settings.rs index 9ce50a0f..6ec6da24 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,30 +2,46 @@ // // SPDX-License-Identifier: MPL-2.0 +use clap::Clap; use merge::Merge; use serde::Deserialize; use std::collections::HashMap; -#[derive(Deserialize, Debug, Clone, Merge)] +#[derive(Clap, Deserialize, Debug, Clone, Merge)] pub struct GenericSettings { + /// Override the SSH user with the given value + #[clap(long)] #[serde(rename(deserialize = "sshUser"))] pub ssh_user: Option, + /// Override the profile user with the given value + #[clap(long = "profile-user")] pub user: Option, + /// Override the SSH options used + #[clap(long)] #[serde( skip_serializing_if = "Vec::is_empty", default, rename(deserialize = "sshOpts") )] #[merge(strategy = merge::vec::append)] - pub ssh_opts: Vec, + pub ssh_opts: Vec, // TODO: figure out type casting + /// Override if the connecting to the target node should be considered fast + #[clap(long)] #[serde(rename(deserialize = "fastConnection"))] pub fast_connection: Option, + /// Override if a rollback should be attempted if activation fails + #[clap(long)] #[serde(rename(deserialize = "autoRollback"))] pub auto_rollback: Option, + /// How long activation should wait for confirmation (if using magic-rollback) + #[clap(long)] #[serde(rename(deserialize = "confirmTimeout"))] pub confirm_timeout: Option, + /// Where to store temporary files (only used by magic-rollback) + #[clap(long)] #[serde(rename(deserialize = "tempPath"))] pub temp_path: Option, + #[clap(long)] #[serde(rename(deserialize = "magicRollback"))] pub magic_rollback: Option, }