forked from DGNum/colmena
Migrate to Clap 4.0
It was kind of a bumpy experience. Gradual migration to the Derive API is coming soon.
This commit is contained in:
parent
bf95e2dce8
commit
872949504b
16 changed files with 209 additions and 192 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -86,33 +86,31 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.22"
|
||||
version = "4.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
|
||||
checksum = "4ed45cc2c62a3eff523e718d8576ba762c83a3146151093283ac62ae11933a73"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "3.2.5"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8"
|
||||
checksum = "11cba7abac9b56dfe2f035098cdb3a43946f276e6db83b72c4e692343f9aab9a"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
@ -355,12 +353,6 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
|
@ -420,16 +412,6 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.1"
|
||||
|
@ -849,12 +831,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
|
|
|
@ -10,8 +10,8 @@ edition = "2021"
|
|||
async-stream = "0.3.2"
|
||||
async-trait = "0.1.42"
|
||||
atty = "0.2"
|
||||
clap = "3.0.0"
|
||||
clap_complete = "3.0.0"
|
||||
clap = "4.0.11"
|
||||
clap_complete = "4.0.2"
|
||||
clicolors-control = "1"
|
||||
console = "0.15.0"
|
||||
const_format = "0.2.22"
|
||||
|
|
60
src/cli.rs
60
src/cli.rs
|
@ -2,7 +2,10 @@
|
|||
|
||||
use std::env;
|
||||
|
||||
use clap::{value_parser, Arg, ArgMatches, ColorChoice, Command as ClapCommand};
|
||||
use clap::{
|
||||
builder::PossibleValue, value_parser, Arg, ArgMatches, ColorChoice, Command as ClapCommand,
|
||||
ValueEnum,
|
||||
};
|
||||
use clap_complete::Shell;
|
||||
use const_format::concatcp;
|
||||
use env_logger::fmt::WriteStyle;
|
||||
|
@ -93,7 +96,34 @@ macro_rules! handle_command {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn build_cli(include_internal: bool) -> ClapCommand<'static> {
|
||||
/// When to display color.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ColorWhen {
|
||||
/// Detect automatically.
|
||||
Auto,
|
||||
|
||||
/// Always display colors.
|
||||
Always,
|
||||
|
||||
/// Never display colors.
|
||||
Never,
|
||||
}
|
||||
|
||||
impl ValueEnum for ColorWhen {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[Self::Auto, Self::Always, Self::Never]
|
||||
}
|
||||
|
||||
fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
|
||||
match self {
|
||||
Self::Auto => Some(PossibleValue::new("auto")),
|
||||
Self::Always => Some(PossibleValue::new("always")),
|
||||
Self::Never => Some(PossibleValue::new("never")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_cli(include_internal: bool) -> ClapCommand {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let mut app = ClapCommand::new("Colmena")
|
||||
.bin_name("colmena")
|
||||
|
@ -123,13 +153,13 @@ pub fn build_cli(include_internal: bool) -> ClapCommand<'static> {
|
|||
.help("Show debug information for Nix commands")
|
||||
.long_help("Passes --show-trace to Nix commands")
|
||||
.global(true)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("impure")
|
||||
.long("impure")
|
||||
.help("Allow impure expressions")
|
||||
.long_help("Passes --impure to Nix commands")
|
||||
.global(true)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("color")
|
||||
.long("color")
|
||||
.help("When to colorize the output")
|
||||
|
@ -139,7 +169,7 @@ It's also possible to specify the preference using environment variables. See <h
|
|||
"#)
|
||||
.display_order(HELP_ORDER_LOW)
|
||||
.value_name("WHEN")
|
||||
.possible_values(&["auto", "always", "never"])
|
||||
.value_parser(value_parser!(ColorWhen))
|
||||
.default_value("auto")
|
||||
.global(true));
|
||||
|
||||
|
@ -153,7 +183,7 @@ It's also possible to specify the preference using environment variables. See <h
|
|||
.index(1)
|
||||
.value_parser(value_parser!(Shell))
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
.num_args(1),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -187,7 +217,7 @@ pub async fn run() {
|
|||
let mut app = build_cli(true);
|
||||
let matches = app.clone().get_matches();
|
||||
|
||||
set_color_pref(matches.value_of("color").unwrap());
|
||||
set_color_pref(matches.get_one("color").unwrap());
|
||||
init_logging();
|
||||
|
||||
handle_command!(apply, matches);
|
||||
|
@ -221,9 +251,9 @@ fn gen_completions(args: &ArgMatches) {
|
|||
clap_complete::generate(shell, &mut app, "colmena", &mut std::io::stdout());
|
||||
}
|
||||
|
||||
fn set_color_pref(cli: &str) {
|
||||
if cli != "auto" {
|
||||
clicolors_control::set_colors_enabled(cli == "always");
|
||||
fn set_color_pref(when: &ColorWhen) {
|
||||
if when != &ColorWhen::Auto {
|
||||
clicolors_control::set_colors_enabled(when == &ColorWhen::Always);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,3 +277,13 @@ fn init_logging() {
|
|||
.write_style(style)
|
||||
.init();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cli_debug_assert() {
|
||||
build_cli(true).debug_assert()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Arg, ArgMatches, Command as ClapCommand};
|
||||
use clap::{
|
||||
builder::{ArgPredicate, PossibleValuesParser, ValueParser},
|
||||
value_parser, Arg, ArgMatches, Command as ClapCommand,
|
||||
};
|
||||
|
||||
use crate::error::ColmenaError;
|
||||
use crate::nix::deployment::{
|
||||
Deployment, EvaluationNodeLimit, Evaluator, Goal, Options, ParallelismLimit,
|
||||
Deployment, EvaluationNodeLimit, EvaluatorType, Goal, Options, ParallelismLimit,
|
||||
};
|
||||
use crate::nix::NodeFilter;
|
||||
use crate::progress::SimpleProgressOutput;
|
||||
|
@ -24,17 +27,18 @@ The evaluation process is RAM-intensive. The default behavior is to limit the ma
|
|||
Set to 0 to disable the limit.
|
||||
"#)
|
||||
.default_value("auto")
|
||||
.takes_value(true)
|
||||
.validator(|s| {
|
||||
.num_args(1)
|
||||
.value_parser(ValueParser::new(|s: &str| -> Result<EvaluationNodeLimit, String> {
|
||||
if s == "auto" {
|
||||
return Ok(());
|
||||
return Ok(EvaluationNodeLimit::Heuristic);
|
||||
}
|
||||
|
||||
match s.parse::<usize>() {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(0) => Ok(EvaluationNodeLimit::None),
|
||||
Ok(n) => Ok(EvaluationNodeLimit::Manual(n)),
|
||||
Err(_) => Err(String::from("The value must be a valid number")),
|
||||
}
|
||||
}))
|
||||
})))
|
||||
.arg(Arg::new("parallel")
|
||||
.short('p')
|
||||
.long("parallel")
|
||||
|
@ -45,13 +49,8 @@ Set to 0 to disable the limit.
|
|||
Set to 0 to disable parallemism limit.
|
||||
"#)
|
||||
.default_value("10")
|
||||
.takes_value(true)
|
||||
.validator(|s| {
|
||||
match s.parse::<usize>() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(String::from("The value must be a valid number")),
|
||||
}
|
||||
}))
|
||||
.num_args(1)
|
||||
.value_parser(value_parser!(usize)))
|
||||
.arg(Arg::new("keep-result")
|
||||
.long("keep-result")
|
||||
.help("Create GC roots for built profiles")
|
||||
|
@ -60,13 +59,13 @@ Set to 0 to disable parallemism limit.
|
|||
The built system profiles will be added as GC roots so that they will not be removed by the garbage collector.
|
||||
The links will be created under .gcroots in the directory the Hive configuration is located.
|
||||
"#)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("Be verbose")
|
||||
.long_help("Deactivates the progress spinner and prints every line of output.")
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("no-keys")
|
||||
.long("no-keys")
|
||||
.help("Do not upload keys")
|
||||
|
@ -75,23 +74,23 @@ The links will be created under .gcroots in the directory the Hive configuration
|
|||
By default, Colmena will upload keys set in `deployment.keys` before deploying the new profile on a node.
|
||||
To upload keys without building or deploying the rest of the configuration, use `colmena upload-keys`.
|
||||
"#)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("reboot")
|
||||
.long("reboot")
|
||||
.help("Reboot nodes after activation")
|
||||
.long_help("Reboots nodes after activation and waits for them to come back up.")
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("no-substitute")
|
||||
.long("no-substitute")
|
||||
.alias("no-substitutes")
|
||||
.help("Do not use substitutes")
|
||||
.long_help("Disables the use of substituters when copying closures to the remote host.")
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("no-gzip")
|
||||
.long("no-gzip")
|
||||
.help("Do not use gzip")
|
||||
.long_help("Disables the use of gzip when copying closures to the remote host.")
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("build-on-target")
|
||||
.long("build-on-target")
|
||||
.help("Build the system profiles on the target nodes")
|
||||
|
@ -101,17 +100,17 @@ If enabled, the system profiles will be built on the target nodes themselves, no
|
|||
This overrides per-node perferences set in `deployment.buildOnTarget`.
|
||||
To temporarily disable remote build on all nodes, use `--no-build-on-target`.
|
||||
"#)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("no-build-on-target")
|
||||
.long("no-build-on-target")
|
||||
.hide(true)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("force-replace-unknown-profiles")
|
||||
.long("force-replace-unknown-profiles")
|
||||
.help("Ignore all targeted nodes deployment.replaceUnknownProfiles setting")
|
||||
.long_help(r#"If `deployment.replaceUnknownProfiles` is set for a target, using this switch
|
||||
will treat deployment.replaceUnknownProfiles as though it was set true and perform unknown profile replacement."#)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("evaluator")
|
||||
.long("evaluator")
|
||||
.help("The evaluator to use (experimental)")
|
||||
|
@ -119,10 +118,10 @@ will treat deployment.replaceUnknownProfiles as though it was set true and perfo
|
|||
|
||||
This is an experimental feature."#)
|
||||
.default_value("chunked")
|
||||
.possible_values(Evaluator::possible_values()))
|
||||
.value_parser(value_parser!(EvaluatorType)))
|
||||
}
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
let command = ClapCommand::new("apply")
|
||||
.about("Apply configurations on remote machines")
|
||||
.arg(
|
||||
|
@ -141,10 +140,10 @@ Same as the targets for switch-to-configuration, with the following extra pseudo
|
|||
"#,
|
||||
)
|
||||
.default_value("switch")
|
||||
.default_value_if("reboot", None, Some("boot"))
|
||||
.default_value_if("reboot", ArgPredicate::IsPresent, Some("boot"))
|
||||
.default_value("switch")
|
||||
.index(1)
|
||||
.possible_values(&[
|
||||
.value_parser(PossibleValuesParser::new([
|
||||
"build",
|
||||
"push",
|
||||
"switch",
|
||||
|
@ -152,7 +151,7 @@ Same as the targets for switch-to-configuration, with the following extra pseudo
|
|||
"test",
|
||||
"dry-activate",
|
||||
"keys",
|
||||
]),
|
||||
])),
|
||||
);
|
||||
let command = register_deploy_args(command);
|
||||
|
||||
|
@ -164,10 +163,15 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
|
||||
let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from);
|
||||
|
||||
let goal_arg = local_args.value_of("goal").unwrap();
|
||||
// FIXME: Just get_one::<Goal>
|
||||
let goal_arg = local_args.get_one::<String>("goal").unwrap();
|
||||
let goal = Goal::from_str(goal_arg).unwrap();
|
||||
|
||||
let filter = local_args.value_of("on").map(NodeFilter::new).transpose()?;
|
||||
// FIXME: Just get_one::<NodeFilter>
|
||||
let filter = local_args
|
||||
.get_one::<String>("on")
|
||||
.map(NodeFilter::new)
|
||||
.transpose()?;
|
||||
|
||||
if filter.is_none() && goal != Goal::Build {
|
||||
// User did not specify node, we should check meta and see rules
|
||||
|
@ -184,7 +188,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
.await?;
|
||||
let n_targets = targets.len();
|
||||
|
||||
let verbose = local_args.is_present("verbose") || goal == Goal::DryActivate;
|
||||
let verbose = local_args.get_flag("verbose") || goal == Goal::DryActivate;
|
||||
let mut output = SimpleProgressOutput::new(verbose);
|
||||
let progress = output.get_sender();
|
||||
|
||||
|
@ -193,22 +197,27 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
// FIXME: Configure limits
|
||||
let options = {
|
||||
let mut options = Options::default();
|
||||
options.set_substituters_push(!local_args.is_present("no-substitute"));
|
||||
options.set_gzip(!local_args.is_present("no-gzip"));
|
||||
options.set_upload_keys(!local_args.is_present("no-keys"));
|
||||
options.set_reboot(local_args.is_present("reboot"));
|
||||
options.set_substituters_push(!local_args.get_flag("no-substitute"));
|
||||
options.set_gzip(!local_args.get_flag("no-gzip"));
|
||||
options.set_upload_keys(!local_args.get_flag("no-keys"));
|
||||
options.set_reboot(local_args.get_flag("reboot"));
|
||||
options.set_force_replace_unknown_profiles(
|
||||
local_args.is_present("force-replace-unknown-profiles"),
|
||||
local_args.get_flag("force-replace-unknown-profiles"),
|
||||
);
|
||||
options.set_evaluator(
|
||||
local_args
|
||||
.get_one::<EvaluatorType>("evaluator")
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
);
|
||||
options.set_evaluator(local_args.value_of_t("evaluator").unwrap());
|
||||
|
||||
if local_args.is_present("keep-result") {
|
||||
if local_args.get_flag("keep-result") {
|
||||
options.set_create_gc_roots(true);
|
||||
}
|
||||
|
||||
if local_args.is_present("no-build-on-target") {
|
||||
if local_args.get_flag("no-build-on-target") {
|
||||
options.set_force_build_on_target(false);
|
||||
} else if local_args.is_present("build-on-target") {
|
||||
} else if local_args.get_flag("build-on-target") {
|
||||
options.set_force_build_on_target(true);
|
||||
}
|
||||
|
||||
|
@ -217,7 +226,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
|
||||
deployment.set_options(options);
|
||||
|
||||
if local_args.is_present("no-keys") && goal == Goal::UploadKeys {
|
||||
if local_args.get_flag("no-keys") && goal == Goal::UploadKeys {
|
||||
log::error!("--no-keys cannot be used when the goal is to upload keys");
|
||||
quit::with_code(1);
|
||||
}
|
||||
|
@ -225,11 +234,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
let parallelism_limit = {
|
||||
let mut limit = ParallelismLimit::default();
|
||||
limit.set_apply_limit({
|
||||
let limit = local_args
|
||||
.value_of("parallel")
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
let limit = local_args.get_one::<usize>("parallel").unwrap().to_owned();
|
||||
if limit == 0 {
|
||||
n_targets
|
||||
} else {
|
||||
|
@ -239,17 +244,10 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
limit
|
||||
};
|
||||
|
||||
let evaluation_node_limit = match local_args.value_of("eval-node-limit").unwrap() {
|
||||
"auto" => EvaluationNodeLimit::Heuristic,
|
||||
number => {
|
||||
let number = number.parse::<usize>().unwrap();
|
||||
if number == 0 {
|
||||
EvaluationNodeLimit::None
|
||||
} else {
|
||||
EvaluationNodeLimit::Manual(number)
|
||||
}
|
||||
}
|
||||
};
|
||||
let evaluation_node_limit = local_args
|
||||
.get_one::<EvaluationNodeLimit>("eval-node-limit")
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
deployment.set_parallelism_limit(parallelism_limit);
|
||||
deployment.set_evaluation_node_limit(evaluation_node_limit);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use clap::{Arg, ArgMatches, Command as ClapCommand};
|
||||
use clap::{builder::PossibleValuesParser, Arg, ArgMatches, Command as ClapCommand};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::error::ColmenaError;
|
||||
|
@ -10,7 +10,7 @@ use crate::nix::{host::Local as LocalHost, NodeName};
|
|||
use crate::progress::SimpleProgressOutput;
|
||||
use crate::util;
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
ClapCommand::new("apply-local")
|
||||
.about("Apply configurations on the local machine")
|
||||
.arg(Arg::new("goal")
|
||||
|
@ -18,16 +18,24 @@ pub fn subcommand() -> ClapCommand<'static> {
|
|||
.long_help("Same as the targets for switch-to-configuration.\n\"push\" is noop in apply-local.")
|
||||
.default_value("switch")
|
||||
.index(1)
|
||||
.possible_values(&["push", "switch", "boot", "test", "dry-activate", "keys"]))
|
||||
.value_parser(PossibleValuesParser::new([
|
||||
"push",
|
||||
"switch",
|
||||
"boot",
|
||||
"test",
|
||||
"dry-activate",
|
||||
"keys",
|
||||
])))
|
||||
.arg(Arg::new("sudo")
|
||||
.long("sudo")
|
||||
.help("Attempt to escalate privileges if not run as root"))
|
||||
.help("Attempt to escalate privileges if not run as root")
|
||||
.num_args(0))
|
||||
.arg(Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("Be verbose")
|
||||
.long_help("Deactivates the progress spinner and prints every line of output.")
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("no-keys")
|
||||
.long("no-keys")
|
||||
.help("Do not deploy keys")
|
||||
|
@ -35,11 +43,11 @@ pub fn subcommand() -> ClapCommand<'static> {
|
|||
|
||||
By default, Colmena will deploy keys set in `deployment.keys` before activating the profile on this host.
|
||||
"#)
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
.arg(Arg::new("node")
|
||||
.long("node")
|
||||
.help("Override the node name to use")
|
||||
.takes_value(true))
|
||||
.num_args(1))
|
||||
|
||||
// Removed
|
||||
.arg(Arg::new("sudo-command")
|
||||
|
@ -47,11 +55,11 @@ By default, Colmena will deploy keys set in `deployment.keys` before activating
|
|||
.value_name("COMMAND")
|
||||
.help("Removed: Configure deployment.privilegeEscalationCommand in node configuration")
|
||||
.hide(true)
|
||||
.takes_value(true))
|
||||
.num_args(1))
|
||||
}
|
||||
|
||||
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
|
||||
if local_args.occurrences_of("sudo-command") > 0 {
|
||||
if local_args.contains_id("sudo-command") {
|
||||
log::error!("--sudo-command has been removed. Please configure it in deployment.privilegeEscalationCommand in the node configuration.");
|
||||
quit::with_code(1);
|
||||
}
|
||||
|
@ -68,8 +76,8 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
quit::with_code(5);
|
||||
}
|
||||
|
||||
let escalate_privileges = local_args.is_present("sudo");
|
||||
let verbose = local_args.is_present("verbose") || escalate_privileges; // cannot use spinners with interactive sudo
|
||||
let escalate_privileges = local_args.get_flag("sudo");
|
||||
let verbose = local_args.get_flag("verbose") || escalate_privileges; // cannot use spinners with interactive sudo
|
||||
|
||||
{
|
||||
let euid: u32 = unsafe { libc::geteuid() };
|
||||
|
@ -81,8 +89,8 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
|
||||
let hive = util::hive_from_args(local_args).await.unwrap();
|
||||
let hostname = {
|
||||
let s = if local_args.is_present("node") {
|
||||
local_args.value_of("node").unwrap().to_owned()
|
||||
let s = if local_args.contains_id("node") {
|
||||
local_args.get_one::<String>("node").unwrap().to_owned()
|
||||
} else {
|
||||
hostname::get()
|
||||
.expect("Could not get hostname")
|
||||
|
@ -92,7 +100,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
|
||||
NodeName::new(s)?
|
||||
};
|
||||
let goal = Goal::from_str(local_args.value_of("goal").unwrap()).unwrap();
|
||||
let goal = Goal::from_str(local_args.get_one::<String>("goal").unwrap()).unwrap();
|
||||
|
||||
let target = {
|
||||
if let Some(info) = hive.deployment_info_single(&hostname).await.unwrap() {
|
||||
|
@ -131,7 +139,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
|
||||
let options = {
|
||||
let mut options = Options::default();
|
||||
options.set_upload_keys(!local_args.is_present("no-keys"));
|
||||
options.set_upload_keys(!local_args.get_flag("no-keys"));
|
||||
options
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use clap::{Arg, Command as ClapCommand};
|
||||
use clap::{builder::PossibleValuesParser, Arg, Command as ClapCommand};
|
||||
|
||||
use crate::util;
|
||||
|
||||
use super::apply;
|
||||
pub use super::apply::run;
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
let command = ClapCommand::new("build")
|
||||
.about("Build configurations but not push to remote machines")
|
||||
.long_about(
|
||||
|
@ -17,8 +17,8 @@ This subcommand behaves as if you invoked `apply` with the `build` goal."#,
|
|||
Arg::new("goal")
|
||||
.hide(true)
|
||||
.default_value("build")
|
||||
.possible_values(&["build"])
|
||||
.takes_value(true),
|
||||
.value_parser(PossibleValuesParser::new(["build"]))
|
||||
.num_args(1),
|
||||
);
|
||||
|
||||
let command = apply::register_deploy_args(command);
|
||||
|
|
|
@ -5,15 +5,15 @@ use clap::{Arg, ArgMatches, Command as ClapCommand};
|
|||
use crate::error::ColmenaError;
|
||||
use crate::util;
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
subcommand_gen("eval")
|
||||
}
|
||||
|
||||
pub fn deprecated_alias() -> ClapCommand<'static> {
|
||||
pub fn deprecated_alias() -> ClapCommand {
|
||||
subcommand_gen("introspect").hide(true)
|
||||
}
|
||||
|
||||
fn subcommand_gen(name: &str) -> ClapCommand<'static> {
|
||||
fn subcommand_gen(name: &'static str) -> ClapCommand {
|
||||
ClapCommand::new(name)
|
||||
.about("Evaluate an expression using the complete configuration")
|
||||
.long_about(r#"Evaluate an expression using the complete configuration
|
||||
|
@ -28,16 +28,16 @@ For example, to retrieve the configuration of one node, you may write something
|
|||
.index(1)
|
||||
.value_name("FILE")
|
||||
.help("The .nix file containing the expression")
|
||||
.takes_value(true))
|
||||
.num_args(1))
|
||||
.arg(Arg::new("expression")
|
||||
.short('E')
|
||||
.value_name("EXPRESSION")
|
||||
.help("The Nix expression")
|
||||
.takes_value(true))
|
||||
.num_args(1))
|
||||
.arg(Arg::new("instantiate")
|
||||
.long("instantiate")
|
||||
.help("Actually instantiate the expression")
|
||||
.takes_value(false))
|
||||
.num_args(0))
|
||||
}
|
||||
|
||||
pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
|
||||
|
@ -49,15 +49,21 @@ pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<()
|
|||
|
||||
let hive = util::hive_from_args(local_args).await?;
|
||||
|
||||
if !(local_args.is_present("expression") ^ local_args.is_present("expression_file")) {
|
||||
if !(local_args.contains_id("expression") ^ local_args.contains_id("expression_file")) {
|
||||
log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both.");
|
||||
quit::with_code(1);
|
||||
}
|
||||
|
||||
let expression = if local_args.is_present("expression") {
|
||||
local_args.value_of("expression").unwrap().to_string()
|
||||
let expression = if local_args.contains_id("expression") {
|
||||
local_args
|
||||
.get_one::<String>("expression")
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
} else {
|
||||
let path: PathBuf = local_args.value_of("expression_file").unwrap().into();
|
||||
let path = local_args
|
||||
.get_one::<PathBuf>("expression_file")
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
format!(
|
||||
"import {}",
|
||||
path.canonicalize()
|
||||
|
@ -67,7 +73,7 @@ pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<()
|
|||
)
|
||||
};
|
||||
|
||||
let instantiate = local_args.is_present("instantiate");
|
||||
let instantiate = local_args.get_flag("instantiate");
|
||||
let result = hive.introspect(expression, instantiate).await?;
|
||||
|
||||
if instantiate {
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::env;
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{Arg, ArgMatches, Command as ClapCommand};
|
||||
use clap::{value_parser, Arg, ArgMatches, Command as ClapCommand};
|
||||
use futures::future::join_all;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
|
@ -12,10 +12,9 @@ use crate::nix::NodeFilter;
|
|||
use crate::progress::SimpleProgressOutput;
|
||||
use crate::util;
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
let command = ClapCommand::new("exec")
|
||||
.about("Run a command on remote machines")
|
||||
.trailing_var_arg(true)
|
||||
.arg(
|
||||
Arg::new("parallel")
|
||||
.short('p')
|
||||
|
@ -29,11 +28,8 @@ In `colmena exec`, the parallelism limit is disabled (0) by default.
|
|||
"#,
|
||||
)
|
||||
.default_value("0")
|
||||
.takes_value(true)
|
||||
.validator(|s| match s.parse::<usize>() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(String::from("The value must be a valid number")),
|
||||
}),
|
||||
.num_args(1)
|
||||
.value_parser(value_parser!(usize)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("verbose")
|
||||
|
@ -41,15 +37,15 @@ In `colmena exec`, the parallelism limit is disabled (0) by default.
|
|||
.long("verbose")
|
||||
.help("Be verbose")
|
||||
.long_help("Deactivates the progress spinner and prints every line of output.")
|
||||
.takes_value(false),
|
||||
.num_args(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("command")
|
||||
.value_name("COMMAND")
|
||||
.last(true)
|
||||
.trailing_var_arg(true)
|
||||
.help("Command")
|
||||
.required(true)
|
||||
.multiple_occurrences(true)
|
||||
.num_args(1..)
|
||||
.long_help(
|
||||
r#"Command to run
|
||||
|
||||
|
@ -67,16 +63,16 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
let hive = util::hive_from_args(local_args).await?;
|
||||
let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from);
|
||||
|
||||
let filter = local_args.value_of("on").map(NodeFilter::new).transpose()?;
|
||||
// FIXME: Just get_one::<NodeFilter>
|
||||
let filter = local_args
|
||||
.get_one::<String>("on")
|
||||
.map(NodeFilter::new)
|
||||
.transpose()?;
|
||||
|
||||
let mut targets = hive.select_nodes(filter, ssh_config, true).await?;
|
||||
|
||||
let parallel_sp = Arc::new({
|
||||
let limit = local_args
|
||||
.value_of("parallel")
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
let limit = local_args.get_one::<usize>("parallel").unwrap().to_owned();
|
||||
|
||||
if limit > 0 {
|
||||
Some(Semaphore::new(limit))
|
||||
|
@ -87,13 +83,13 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
|
|||
|
||||
let command: Arc<Vec<String>> = Arc::new(
|
||||
local_args
|
||||
.values_of("command")
|
||||
.get_many::<String>("command")
|
||||
.unwrap()
|
||||
.map(|s| s.to_string())
|
||||
.cloned()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let mut output = SimpleProgressOutput::new(local_args.is_present("verbose"));
|
||||
let mut output = SimpleProgressOutput::new(local_args.get_flag("verbose"));
|
||||
|
||||
let (mut monitor, meta) = JobMonitor::new(output.get_sender());
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::error::ColmenaError;
|
|||
use crate::nix::evaluator::nix_eval_jobs::get_pinned_nix_eval_jobs;
|
||||
use crate::nix::NixCheck;
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
ClapCommand::new("nix-info").about("Show information about the current Nix installation")
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::error::ColmenaError;
|
|||
use crate::nix::info::NixCheck;
|
||||
use crate::util;
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
ClapCommand::new("repl")
|
||||
.about("Start an interactive REPL with the complete configuration")
|
||||
.long_about(
|
||||
|
|
|
@ -14,7 +14,7 @@ macro_rules! node {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
ClapCommand::new("test-progress")
|
||||
.about("Run progress spinner tests")
|
||||
.hide(true)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use clap::{Arg, Command as ClapCommand};
|
||||
use clap::{builder::PossibleValuesParser, Arg, Command as ClapCommand};
|
||||
|
||||
use crate::util;
|
||||
|
||||
use super::apply;
|
||||
pub use super::apply::run;
|
||||
|
||||
pub fn subcommand() -> ClapCommand<'static> {
|
||||
pub fn subcommand() -> ClapCommand {
|
||||
let command = ClapCommand::new("upload-keys")
|
||||
.about("Upload keys to remote hosts")
|
||||
.long_about(
|
||||
|
@ -17,8 +17,8 @@ This subcommand behaves as if you invoked `apply` with the pseudo `keys` goal."#
|
|||
Arg::new("goal")
|
||||
.hide(true)
|
||||
.default_value("keys")
|
||||
.possible_values(&["keys"])
|
||||
.takes_value(true),
|
||||
.value_parser(PossibleValuesParser::new(["keys"]))
|
||||
.num_args(1),
|
||||
);
|
||||
|
||||
let command = apply::register_deploy_args(command);
|
||||
|
|
|
@ -8,7 +8,7 @@ pub mod limits;
|
|||
pub use limits::{EvaluationNodeLimit, ParallelismLimit};
|
||||
|
||||
pub mod options;
|
||||
pub use options::{Evaluator, Options};
|
||||
pub use options::{EvaluatorType, Options};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
@ -162,10 +162,10 @@ impl Deployment {
|
|||
let deployment = DeploymentHandle::new(self);
|
||||
let meta_future = meta.run(|meta| async move {
|
||||
match deployment.options.evaluator {
|
||||
Evaluator::Chunked => {
|
||||
EvaluatorType::Chunked => {
|
||||
deployment.execute_chunked(meta.clone(), targets).await?;
|
||||
}
|
||||
Evaluator::Streaming => {
|
||||
EvaluatorType::Streaming => {
|
||||
log::warn!("Streaming evaluation is an experimental feature");
|
||||
deployment.execute_streaming(meta.clone(), targets).await?;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//! Deployment options.
|
||||
|
||||
use std::str::FromStr;
|
||||
use clap::{builder::PossibleValue, ValueEnum};
|
||||
|
||||
use crate::error::{ColmenaError, ColmenaResult};
|
||||
use crate::nix::CopyOptions;
|
||||
|
||||
/// Options for a deployment.
|
||||
|
@ -33,12 +32,12 @@ pub struct Options {
|
|||
pub(super) force_replace_unknown_profiles: bool,
|
||||
|
||||
/// Which evaluator to use (experimental).
|
||||
pub(super) evaluator: Evaluator,
|
||||
pub(super) evaluator: EvaluatorType,
|
||||
}
|
||||
|
||||
/// Which evaluator to use.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Evaluator {
|
||||
pub enum EvaluatorType {
|
||||
Chunked,
|
||||
Streaming,
|
||||
}
|
||||
|
@ -72,7 +71,7 @@ impl Options {
|
|||
self.force_replace_unknown_profiles = enable;
|
||||
}
|
||||
|
||||
pub fn set_evaluator(&mut self, evaluator: Evaluator) {
|
||||
pub fn set_evaluator(&mut self, evaluator: EvaluatorType) {
|
||||
self.evaluator = evaluator;
|
||||
}
|
||||
|
||||
|
@ -95,27 +94,20 @@ impl Default for Options {
|
|||
create_gc_roots: false,
|
||||
force_build_on_target: None,
|
||||
force_replace_unknown_profiles: false,
|
||||
evaluator: Evaluator::Chunked,
|
||||
evaluator: EvaluatorType::Chunked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Evaluator {
|
||||
type Err = ColmenaError;
|
||||
impl ValueEnum for EvaluatorType {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[Self::Chunked, Self::Streaming]
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> ColmenaResult<Self> {
|
||||
match s {
|
||||
"chunked" => Ok(Self::Chunked),
|
||||
"streaming" => Ok(Self::Streaming),
|
||||
_ => Err(ColmenaError::Unknown {
|
||||
message: "Valid values are 'chunked' and 'streaming'".to_string(),
|
||||
}),
|
||||
fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
|
||||
match self {
|
||||
Self::Chunked => Some(PossibleValue::new("chunked")),
|
||||
Self::Streaming => Some(PossibleValue::new("streaming")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluator {
|
||||
pub fn possible_values() -> &'static [&'static str] {
|
||||
&["chunked", "streaming"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use std::env;
|
||||
use std::future::Future;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use clap::{parser::ValueSource as ClapValueSource, ArgMatches};
|
||||
|
||||
use crate::error::ColmenaError;
|
||||
|
||||
|
@ -48,7 +48,7 @@ fn troubleshoot(
|
|||
// in their Colmena checkout, and encounter NoFlakesSupport
|
||||
// because Colmena always prefers flake.nix when it exists.
|
||||
|
||||
if global_args.occurrences_of("config") == 0 {
|
||||
if let Some(ClapValueSource::DefaultValue) = global_args.value_source("config") {
|
||||
let cwd = env::current_dir()?;
|
||||
if cwd.join("flake.nix").is_file() && cwd.join("hive.nix").is_file() {
|
||||
eprintln!(
|
||||
|
|
21
src/util.rs
21
src/util.rs
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||
use std::process::Stdio;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use clap::{Arg, ArgMatches, Command as ClapCommand};
|
||||
use clap::{parser::ValueSource as ClapValueSource, Arg, ArgMatches, Command as ClapCommand};
|
||||
use futures::future::join3;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader};
|
||||
|
@ -193,8 +193,8 @@ impl CommandExt for CommandExecution {
|
|||
}
|
||||
|
||||
pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
|
||||
let path = match args.occurrences_of("config") {
|
||||
0 => {
|
||||
let path = match args.value_source("config").unwrap() {
|
||||
ClapValueSource::DefaultValue => {
|
||||
// traverse upwards until we find hive.nix
|
||||
let mut cur = std::env::current_dir()?;
|
||||
let mut file_path = None;
|
||||
|
@ -231,9 +231,9 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
|
|||
|
||||
file_path.unwrap()
|
||||
}
|
||||
_ => {
|
||||
ClapValueSource::CommandLine => {
|
||||
let path = args
|
||||
.value_of("config")
|
||||
.get_one::<String>("config")
|
||||
.expect("The config arg should exist")
|
||||
.to_owned();
|
||||
let fpath = PathBuf::from(&path);
|
||||
|
@ -246,11 +246,11 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
|
|||
let hive_path = HivePath::Flake(flake);
|
||||
let mut hive = Hive::new(hive_path).await?;
|
||||
|
||||
if args.is_present("show-trace") {
|
||||
if args.get_flag("show-trace") {
|
||||
hive.set_show_trace(true);
|
||||
}
|
||||
|
||||
if args.is_present("impure") {
|
||||
if args.get_flag("impure") {
|
||||
hive.set_impure(true);
|
||||
}
|
||||
|
||||
|
@ -259,6 +259,7 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
|
|||
|
||||
fpath
|
||||
}
|
||||
x => panic!("Unexpected value source for config: {:?}", x),
|
||||
};
|
||||
|
||||
let hive_path = HivePath::from_path(path).await?;
|
||||
|
@ -273,11 +274,11 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
|
|||
|
||||
let mut hive = Hive::new(hive_path).await?;
|
||||
|
||||
if args.is_present("show-trace") {
|
||||
if args.get_flag("show-trace") {
|
||||
hive.set_show_trace(true);
|
||||
}
|
||||
|
||||
if args.is_present("impure") {
|
||||
if args.get_flag("impure") {
|
||||
hive.set_impure(true);
|
||||
}
|
||||
|
||||
|
@ -298,7 +299,7 @@ The list is comma-separated and globs are supported. To match tags, prepend the
|
|||
- edge-*
|
||||
- edge-*,core-*
|
||||
- @a-tag,@tags-can-have-*"#)
|
||||
.takes_value(true))
|
||||
.num_args(1))
|
||||
}
|
||||
|
||||
pub async fn capture_stream<R>(
|
||||
|
|
Loading…
Reference in a new issue