diff --git a/src/cli.rs b/src/cli.rs index 8b69fea..38844d1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -56,13 +56,19 @@ macro_rules! register_command { macro_rules! handle_command { ($module:ident, $matches:ident) => { if let Some(sub_matches) = $matches.subcommand_matches(stringify!($module)) { - command::$module::run(&$matches, &sub_matches).await; + crate::troubleshooter::run_wrapped( + &$matches, &sub_matches, + command::$module::run, + ).await; return; } }; ($name:expr, $module:ident, $matches:ident) => { if let Some(sub_matches) = $matches.subcommand_matches($name) { - command::$module::run(&$matches, &sub_matches).await; + crate::troubleshooter::run_wrapped( + &$matches, &sub_matches, + command::$module::run, + ).await; return; } }; diff --git a/src/command/apply.rs b/src/command/apply.rs index d04dbf3..f77c871 100644 --- a/src/command/apply.rs +++ b/src/command/apply.rs @@ -13,6 +13,7 @@ use crate::nix::deployment::{ EvaluationNodeLimit, ParallelismLimit, }; +use crate::nix::NixError; use crate::nix::host::local as localhost; use crate::util; @@ -114,13 +115,13 @@ pub fn subcommand() -> App<'static, 'static> { util::register_selector_args(command) } -pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { - let hive = util::hive_from_args(local_args).await.unwrap(); +pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) -> Result<(), NixError> { + let hive = util::hive_from_args(local_args).await?; log::info!("Enumerating nodes..."); - let all_nodes = hive.deployment_info().await.unwrap(); + let all_nodes = hive.deployment_info().await?; - let nix_options = hive.nix_options().await.unwrap(); + let nix_options = hive.nix_options().await?; let selected_nodes = match local_args.value_of("on") { Some(filter) => { @@ -241,4 +242,6 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { if !success { quit::with_code(10); } + + Ok(()) } diff --git a/src/command/apply_local.rs b/src/command/apply_local.rs index 3c28570..e6ec0c3 100644 --- a/src/command/apply_local.rs +++ b/src/command/apply_local.rs @@ -12,7 +12,7 @@ use crate::nix::deployment::{ Target, DeploymentOptions, }; -use crate::nix::host; +use crate::nix::{NixError, host}; use crate::util; pub fn subcommand() -> App<'static, 'static> { @@ -57,7 +57,7 @@ By default, Colmena will deploy keys set in `deployment.keys` before activating .takes_value(false)) } -pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { +pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) -> Result<(), NixError> { // Sanity check: Are we running NixOS? if let Ok(os_release) = fs::read_to_string("/etc/os-release").await { if !os_release.contains("ID=nixos\n") { @@ -131,6 +131,8 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { if !success { quit::with_code(10); } + + Ok(()) } async fn escalate(sudo: &str) -> ! { diff --git a/src/command/eval.rs b/src/command/eval.rs index 4e2c4f3..0eb05d7 100644 --- a/src/command/eval.rs +++ b/src/command/eval.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use clap::{Arg, App, AppSettings, SubCommand, ArgMatches}; use crate::util; +use crate::nix::NixError; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("eval") @@ -37,12 +38,12 @@ pub fn deprecated_alias() -> App<'static, 'static> { .setting(AppSettings::Hidden) } -pub async fn run(global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { +pub async fn run(global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) -> Result<(), NixError> { if let Some("introspect") = global_args.subcommand_name() { log::warn!("`colmena introspect` has been renamed to `colmena eval`. Please update your scripts."); } - let hive = util::hive_from_args(local_args).await.unwrap(); + let hive = util::hive_from_args(local_args).await?; if !(local_args.is_present("expression") ^ local_args.is_present("expression_file")) { log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both."); @@ -57,11 +58,13 @@ pub async fn run(global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { }; let instantiate = local_args.is_present("instantiate"); - let result = hive.introspect(expression, instantiate).await.unwrap(); + let result = hive.introspect(expression, instantiate).await?; if instantiate { print!("{}", result); } else { println!("{}", result); } + + Ok(()) } diff --git a/src/command/exec.rs b/src/command/exec.rs index eeda5c0..3c692f5 100644 --- a/src/command/exec.rs +++ b/src/command/exec.rs @@ -54,11 +54,11 @@ It's recommended to use -- to separate Colmena options from the command to run. util::register_selector_args(command) } -pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { - let hive = util::hive_from_args(local_args).await.unwrap(); +pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) -> Result<(), NixError> { + let hive = util::hive_from_args(local_args).await?; log::info!("Enumerating nodes..."); - let all_nodes = hive.deployment_info().await.unwrap(); + let all_nodes = hive.deployment_info().await?; let selected_nodes = match local_args.value_of("on") { Some(filter) => { @@ -167,4 +167,6 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { join_all(futures).await; }).await; + + Ok(()) } diff --git a/src/command/nix_info.rs b/src/command/nix_info.rs index 62f8716..4e7c98b 100644 --- a/src/command/nix_info.rs +++ b/src/command/nix_info.rs @@ -1,14 +1,16 @@ use clap::{App, SubCommand, ArgMatches}; -use crate::nix::NixCheck; +use crate::nix::{NixCheck, NixError}; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("nix-info") .about("Show information about the current Nix installation") } -pub async fn run(_global_args: &ArgMatches<'_>, _local_args: &ArgMatches<'_>) { +pub async fn run(_global_args: &ArgMatches<'_>, _local_args: &ArgMatches<'_>) -> Result<(), NixError> { let check = NixCheck::detect().await; check.print_version_info(); check.print_flakes_info(false); + + Ok(()) } diff --git a/src/command/test_progress.rs b/src/command/test_progress.rs index 2a6751a..43c9f14 100644 --- a/src/command/test_progress.rs +++ b/src/command/test_progress.rs @@ -3,6 +3,7 @@ use std::time::Duration; use clap::{App, AppSettings, SubCommand, ArgMatches}; use tokio::time; +use crate::nix::NixError; use crate::progress::{Progress, OutputStyle}; pub fn subcommand() -> App<'static, 'static> { @@ -11,7 +12,7 @@ pub fn subcommand() -> App<'static, 'static> { .setting(AppSettings::Hidden) } -pub async fn run(_global_args: &ArgMatches<'_>, _local_args: &ArgMatches<'_>) { +pub async fn run(_global_args: &ArgMatches<'_>, _local_args: &ArgMatches<'_>) -> Result<(), NixError> { let progress = Progress::with_style(OutputStyle::Condensed); let mut task = progress.create_task_progress(String::from("test")); @@ -21,4 +22,6 @@ pub async fn run(_global_args: &ArgMatches<'_>, _local_args: &ArgMatches<'_>) { } task.success("Completed"); + + Ok(()) } diff --git a/src/main.rs b/src/main.rs index b69620a..f3585ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod nix; mod cli; mod command; mod progress; +mod troubleshooter; mod util; #[tokio::main] diff --git a/src/troubleshooter.rs b/src/troubleshooter.rs new file mode 100644 index 0000000..b78e732 --- /dev/null +++ b/src/troubleshooter.rs @@ -0,0 +1,54 @@ +//! Automatic troubleshooter. +//! +//! Tries to provide some useful hints when things go wrong. + +use std::env; +use std::future::Future; + +use clap::ArgMatches; + +use crate::nix::NixError; + +/// Runs a closure and tries to troubleshoot if it returns an error. +pub async fn run_wrapped<'a, F, U, T>(global_args: &'a ArgMatches<'a>, local_args: &'a ArgMatches<'a>, f: U) -> T + where U: FnOnce(&'a ArgMatches<'a>, &'a ArgMatches<'a>) -> F, + F: Future>, +{ + match f(global_args, local_args).await { + Ok(r) => r, + Err(error) => { + log::error!("-----"); + log::error!("Operation failed with error: {}", error); + + if let Err(own_error) = troubleshoot(global_args, local_args, &error) { + log::error!("Error occurred while trying to troubleshoot another error: {}", own_error); + } + + // Ensure we exit with a code + quit::with_code(1); + }, + } +} + +fn troubleshoot(global_args: &ArgMatches<'_>, _local_args: &ArgMatches<'_>, error: &NixError) -> Result<(), NixError> { + match error { + NixError::NoFlakesSupport => { + // People following the tutorial might put hive.nix directly + // in their Colmena checkout, and encounter NoFlakesSupport + // because Colmena always prefers flake.nix when it exists. + + if global_args.occurrences_of("config") == 0 { + let cwd = env::current_dir()?; + if cwd.join("flake.nix").is_file() && cwd.join("hive.nix").is_file() { + eprintln!("Hint: You have both flake.nix and hive.nix in the current directory, and"); + eprintln!(" Colmena will always prefer flake.nix if it exists."); + eprintln!(); + eprintln!(" Try passing `-f hive.nix` explicitly if this is what you want."); + } + }; + } + _ => {}, + } + + Ok(()) +}