diff options
Diffstat (limited to '')
-rw-r--r-- | src/data.rs (renamed from src/utils/data.rs) | 0 | ||||
-rw-r--r-- | src/lib.rs (renamed from src/utils/mod.rs) | 146 | ||||
-rw-r--r-- | src/push.rs (renamed from src/utils/push.rs) | 95 | ||||
-rw-r--r-- | src/utils/deploy.rs | 179 |
4 files changed, 185 insertions, 235 deletions
diff --git a/src/utils/data.rs b/src/data.rs index f557e41..f557e41 100644 --- a/src/utils/data.rs +++ b/src/data.rs diff --git a/src/utils/mod.rs b/src/lib.rs index a891261..edc0507 100644 --- a/src/utils/mod.rs +++ b/src/lib.rs @@ -3,20 +3,132 @@ // // SPDX-License-Identifier: MPL-2.0 -use rnix::{types::*, NodeOrToken, SyntaxKind::*, SyntaxNode}; - -use std::path::PathBuf; +use rnix::{types::*, SyntaxKind::*}; use merge::Merge; use thiserror::Error; -#[macro_export] -macro_rules! good_panic { - ($($tts:tt)*) => {{ - error!($($tts)*); - std::process::exit(1); - }} +use flexi_logger::*; + +#[macro_use] +extern crate log; + +#[macro_use] +extern crate serde_derive; + +pub fn make_lock_path(temp_path: &str, closure: &str) -> String { + let lock_hash = + &closure["/nix/store/".len()..closure.find('-').unwrap_or_else(|| closure.len())]; + format!("{}/deploy-rs-canary-{}", temp_path, lock_hash) +} + +fn make_emoji(level: log::Level) -> &'static str { + match level { + log::Level::Error => "❌", + log::Level::Warn => "⚠️", + log::Level::Info => "ℹ️", + log::Level::Debug => "❓", + log::Level::Trace => "🖊️", + } +} + +pub fn logger_formatter_activate( + w: &mut dyn std::io::Write, + _now: &mut DeferredNow, + record: &Record, +) -> Result<(), std::io::Error> { + let level = record.level(); + + write!( + w, + "⭐ {} [activate] [{}] {}", + make_emoji(level), + style(level, level.to_string()), + record.args() + ) +} + +pub fn logger_formatter_wait( + w: &mut dyn std::io::Write, + _now: &mut DeferredNow, + record: &Record, +) -> Result<(), std::io::Error> { + let level = record.level(); + + write!( + w, + "👀 {} [wait] [{}] {}", + make_emoji(level), + style(level, level.to_string()), + record.args() + ) +} + +pub fn logger_formatter_deploy( + w: &mut dyn std::io::Write, + _now: &mut DeferredNow, + record: &Record, +) -> Result<(), std::io::Error> { + let level = record.level(); + + write!( + w, + "🚀 {} [deploy] [{}] {}", + make_emoji(level), + style(level, level.to_string()), + record.args() + ) +} + +pub enum LoggerType { + Deploy, + Activate, + Wait, +} + +pub fn init_logger( + debug_logs: bool, + log_dir: Option<&str>, + logger_type: LoggerType, +) -> Result<(), FlexiLoggerError> { + let logger_formatter = match logger_type { + LoggerType::Deploy => logger_formatter_deploy, + LoggerType::Activate => logger_formatter_activate, + LoggerType::Wait => logger_formatter_wait, + }; + + if let Some(log_dir) = log_dir { + let mut logger = Logger::with_env_or_str("debug") + .log_to_file() + .format_for_stderr(logger_formatter) + .set_palette("196;208;51;7;8".to_string()) + .directory(log_dir) + .duplicate_to_stderr(match debug_logs { + true => Duplicate::Debug, + false => Duplicate::Info, + }) + .print_message(); + + match logger_type { + LoggerType::Activate => logger = logger.discriminant("activate"), + LoggerType::Wait => logger = logger.discriminant("wait"), + LoggerType::Deploy => (), + } + + logger.start()?; + } else { + Logger::with_env_or_str(match debug_logs { + true => "debug", + false => "info", + }) + .log_target(LogTarget::StdErr) + .format(logger_formatter) + .set_palette("196;208;51;7;8".to_string()) + .start()?; + } + + Ok(()) } pub mod data; @@ -191,6 +303,9 @@ pub struct DeployData<'a> { pub cmd_overrides: &'a CmdOverrides, pub merged_settings: data::GenericSettings, + + pub debug_logs: bool, + pub log_dir: Option<&'a str>, } #[derive(Debug)] @@ -259,6 +374,8 @@ pub fn make_deploy_data<'a, 's>( profile: &'a data::Profile, profile_name: &'a str, cmd_overrides: &'a CmdOverrides, + debug_logs: bool, + log_dir: Option<&'a str>, ) -> DeployData<'a> { let mut merged_settings = profile.generic_settings.clone(); merged_settings.merge(node.generic_settings.clone()); @@ -292,13 +409,8 @@ pub fn make_deploy_data<'a, 's>( cmd_overrides, merged_settings, - } -} -#[derive(Error, Debug)] -pub enum DeployPathToActivatePathError { - #[error("Deploy path did not have a parent directory")] - PathTooShort, - #[error("Deploy path was not valid utf8")] - InvalidUtf8, + debug_logs, + log_dir, + } } diff --git a/src/utils/push.rs b/src/push.rs index 503e062..2f83019 100644 --- a/src/utils/push.rs +++ b/src/push.rs @@ -2,22 +2,22 @@ // // SPDX-License-Identifier: MPL-2.0 +use std::path::Path; use std::process::Stdio; use tokio::process::Command; -use std::path::Path; use thiserror::Error; #[derive(Error, Debug)] pub enum PushProfileError { - #[error("Failed to calculate activate bin path from deploy bin path: {0}")] - DeployPathToActivatePathError(#[from] super::DeployPathToActivatePathError), #[error("Failed to run Nix build command: {0}")] BuildError(std::io::Error), #[error("Nix build command resulted in a bad exit code: {0:?}")] BuildExitError(Option<i32>), - #[error("Activation script deploy-rs-activate does not exist in profile.\n\ - Did you forget to use deploy-rs#lib.<...>.activate.<...> on your profile path?")] + #[error( + "Activation script deploy-rs-activate does not exist in profile.\n\ + Did you forget to use deploy-rs#lib.<...>.activate.<...> on your profile path?" + )] DeployRsActivateDoesntExist, #[error("Activation script activate-rs does not exist in profile.\n\ Is there a mismatch in deploy-rs used in the flake you're deploying and deploy-rs command you're running?")] @@ -32,53 +32,55 @@ pub enum PushProfileError { CopyExitError(Option<i32>), } -pub async fn push_profile( - supports_flakes: bool, - check_sigs: bool, - repo: &str, - deploy_data: &super::DeployData<'_>, - deploy_defs: &super::DeployDefs, - keep_result: bool, - result_path: Option<&str>, - extra_build_args: &[String], -) -> Result<(), PushProfileError> { +pub struct PushProfileData<'a> { + pub supports_flakes: bool, + pub check_sigs: bool, + pub repo: &'a str, + pub deploy_data: &'a super::DeployData<'a>, + pub deploy_defs: &'a super::DeployDefs, + pub keep_result: bool, + pub result_path: Option<&'a str>, + pub extra_build_args: &'a [String], +} + +pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileError> { info!( "Building profile `{}` for node `{}`", - deploy_data.profile_name, deploy_data.node_name + data.deploy_data.profile_name, data.deploy_data.node_name ); - let mut build_c = if supports_flakes { + let mut build_c = if data.supports_flakes { Command::new("nix") } else { Command::new("nix-build") }; - let mut build_command = if supports_flakes { + let mut build_command = if data.supports_flakes { build_c.arg("build").arg(format!( "{}#deploy.nodes.\"{}\".profiles.\"{}\".path", - repo, deploy_data.node_name, deploy_data.profile_name + data.repo, data.deploy_data.node_name, data.deploy_data.profile_name )) } else { - build_c.arg(&repo).arg("-A").arg(format!( + build_c.arg(&data.repo).arg("-A").arg(format!( "deploy.nodes.\"{}\".profiles.\"{}\".path", - deploy_data.node_name, deploy_data.profile_name + data.deploy_data.node_name, data.deploy_data.profile_name )) }; - build_command = match (keep_result, supports_flakes) { + build_command = match (data.keep_result, data.supports_flakes) { (true, _) => { - let result_path = result_path.unwrap_or("./.deploy-gc"); + let result_path = data.result_path.unwrap_or("./.deploy-gc"); build_command.arg("--out-link").arg(format!( "{}/{}/{}", - result_path, deploy_data.node_name, deploy_data.profile_name + 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 extra_build_args { + for extra_arg in data.extra_build_args { build_command = build_command.arg(extra_arg); } @@ -94,20 +96,34 @@ pub async fn push_profile( a => return Err(PushProfileError::BuildExitError(a)), }; - if ! Path::new(format!("{}/deploy-rs-activate", deploy_data.profile.profile_settings.path).as_str()).exists() { + if !Path::new( + format!( + "{}/deploy-rs-activate", + data.deploy_data.profile.profile_settings.path + ) + .as_str(), + ) + .exists() + { return Err(PushProfileError::DeployRsActivateDoesntExist); } - if ! Path::new(format!("{}/activate-rs", deploy_data.profile.profile_settings.path).as_str()).exists() { + if !Path::new( + format!( + "{}/activate-rs", + data.deploy_data.profile.profile_settings.path + ) + .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 `{}`", - deploy_data.profile_name, deploy_data.node_name + data.deploy_data.profile_name, data.deploy_data.node_name ); let sign_exit_status = Command::new("nix") @@ -115,7 +131,7 @@ pub async fn push_profile( .arg("-r") .arg("-k") .arg(local_key) - .arg(&deploy_data.profile.profile_settings.path) + .arg(&data.deploy_data.profile.profile_settings.path) .status() .await .map_err(PushProfileError::SignError)?; @@ -128,21 +144,22 @@ pub async fn push_profile( debug!( "Copying profile `{}` to node `{}`", - deploy_data.profile_name, deploy_data.node_name + data.deploy_data.profile_name, data.deploy_data.node_name ); let mut copy_command_ = Command::new("nix"); let mut copy_command = copy_command_.arg("copy"); - if deploy_data.merged_settings.fast_connection != Some(true) { + if data.deploy_data.merged_settings.fast_connection != Some(true) { copy_command = copy_command.arg("--substitute-on-destination"); } - if !check_sigs { + if !data.check_sigs { copy_command = copy_command.arg("--no-check-sigs"); } - let ssh_opts_str = deploy_data + let ssh_opts_str = data + .deploy_data .merged_settings .ssh_opts // This should provide some extra safety, but it also breaks for some reason, oh well @@ -151,15 +168,15 @@ pub async fn push_profile( // .collect::<Vec<String>>() .join(" "); - let hostname = match deploy_data.cmd_overrides.hostname { + let hostname = match data.deploy_data.cmd_overrides.hostname { Some(ref x) => x, - None => &deploy_data.node.node_settings.hostname, + None => &data.deploy_data.node.node_settings.hostname, }; let copy_exit_status = copy_command .arg("--to") - .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)) - .arg(&deploy_data.profile.profile_settings.path) + .arg(format!("ssh://{}@{}", data.deploy_defs.ssh_user, hostname)) + .arg(&data.deploy_data.profile.profile_settings.path) .env("NIX_SSHOPTS", ssh_opts_str) .status() .await diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs deleted file mode 100644 index 14a44a0..0000000 --- a/src/utils/deploy.rs +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Serokell <https://serokell.io/> -// SPDX-FileCopyrightText: 2020 Andreas Fuchs <asf@boinkor.net> -// -// SPDX-License-Identifier: MPL-2.0 - -use std::borrow::Cow; -use tokio::process::Command; - -use thiserror::Error; - -fn build_activate_command( - sudo: &Option<String>, - profile_path: &str, - closure: &str, - auto_rollback: bool, - temp_path: &Cow<str>, - confirm_timeout: u16, - magic_rollback: bool, -) -> String { - let mut self_activate_command = format!( - "{}/activate-rs '{}' '{}' --temp-path {} --confirm-timeout {}", - closure, profile_path, closure, temp_path, confirm_timeout - ); - - if magic_rollback { - self_activate_command = format!("{} --magic-rollback", self_activate_command); - } - - if auto_rollback { - self_activate_command = format!("{} --auto-rollback", self_activate_command); - } - - if let Some(sudo_cmd) = &sudo { - self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); - } - - self_activate_command -} - -#[test] -fn test_activation_command_builder() { - let activate_path_str = "/blah/bin/activate".to_string(); - let sudo = Some("sudo -u test".to_string()); - let profile_path = "/blah/profiles/test"; - let closure = "/nix/store/blah/etc"; - let auto_rollback = true; - let temp_path = &"/tmp".into(); - let confirm_timeout = 30; - let magic_rollback = true; - - assert_eq!( - build_activate_command( - &sudo, - profile_path, - closure, - auto_rollback, - temp_path, - confirm_timeout, - magic_rollback - ), - "sudo -u test /nix/store/blah/etc/activate-rs '/blah/profiles/test' '/nix/store/blah/etc' --temp-path /tmp --confirm-timeout 30 --magic-rollback --auto-rollback" - .to_string(), - ); -} - -#[derive(Error, Debug)] -pub enum DeployProfileError { - #[error("Failed to calculate activate bin path from deploy bin path: {0}")] - DeployPathToActivatePathError(#[from] super::DeployPathToActivatePathError), - #[error("Failed to run activation command over SSH: {0}")] - SSHActivateError(std::io::Error), - #[error("Activation over SSH resulted in a bad exit code: {0:?}")] - SSHActivateExitError(Option<i32>), - #[error("Failed to run confirmation command over SSH (the server should roll back): {0}")] - SSHConfirmError(std::io::Error), - #[error( - "Confirming activation over SSH resulted in a bad exit code (the server should roll back): {0:?}" - )] - SSHConfirmExitError(Option<i32>), -} - -pub async fn deploy_profile( - deploy_data: &super::DeployData<'_>, - deploy_defs: &super::DeployDefs, -) -> Result<(), DeployProfileError> { - info!( - "Activating profile `{}` for node `{}`", - deploy_data.profile_name, deploy_data.node_name - ); - - let temp_path: Cow<str> = 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 self_activate_command = build_activate_command( - &deploy_defs.sudo, - &deploy_defs.profile_path, - &deploy_data.profile.profile_settings.path, - auto_rollback, - &temp_path, - confirm_timeout, - magic_rollback, - ); - - 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 mut c = Command::new("ssh"); - let mut ssh_command = c - .arg("-t") - .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); - - for ssh_opt in &deploy_data.merged_settings.ssh_opts { - ssh_command = ssh_command.arg(ssh_opt); - } - - let ssh_exit_status = ssh_command - .arg(self_activate_command) - .status() - .await - .map_err(DeployProfileError::SSHActivateError)?; - - match ssh_exit_status.code() { - Some(0) => (), - a => return Err(DeployProfileError::SSHActivateExitError(a)), - }; - - info!("Success activating!"); - - if magic_rollback { - info!("Attempting to confirm activation"); - - let mut c = Command::new("ssh"); - let mut ssh_confirm_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); - - for ssh_opt in &deploy_data.merged_settings.ssh_opts { - ssh_confirm_command = ssh_confirm_command.arg(ssh_opt); - } - - let lock_hash = &deploy_data.profile.profile_settings.path["/nix/store/".len()..]; - let lock_path = format!("{}/deploy-rs-canary-{}", temp_path, lock_hash); - - let mut confirm_command = format!("rm {}", lock_path); - if let Some(sudo_cmd) = &deploy_defs.sudo { - confirm_command = format!("{} {}", sudo_cmd, confirm_command); - } - - debug!( - "Attempting to run command to confirm deployment: {}", - confirm_command - ); - - let ssh_exit_status = ssh_confirm_command - .arg(confirm_command) - .status() - .await - .map_err(DeployProfileError::SSHConfirmError)?; - - match ssh_exit_status.code() { - Some(0) => (), - a => return Err(DeployProfileError::SSHConfirmExitError(a)), - }; - - info!("Deployment confirmed."); - } - - Ok(()) -} |