colmena/src/command/apply.rs
2021-02-09 22:33:45 -08:00

224 lines
7.7 KiB
Rust

use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use clap::{Arg, App, SubCommand, ArgMatches};
use crate::nix::deployment::{
Deployment,
Goal,
Target,
DeploymentOptions,
EvaluationNodeLimit,
ParallelismLimit,
};
use crate::nix::host::local as localhost;
use crate::util;
pub fn register_deploy_args<'a, 'b>(command: App<'a, 'b>) -> App<'a, 'b> {
command
.arg(Arg::with_name("eval-node-limit")
.long("eval-node-limit")
.value_name("LIMIT")
.help("Evaluation node limit")
.long_help(r#"Limits the maximum number of hosts to be evaluated at once.
The evaluation process is RAM-intensive. The default behavior is to limit the maximum number of host evaluated at the same time based on naive heuristics.
Set to 0 to disable the limit.
"#)
.default_value("auto")
.takes_value(true)
.validator(|s| {
if s == "auto" {
return Ok(());
}
match s.parse::<usize>() {
Ok(_) => Ok(()),
Err(_) => Err(String::from("The value must be a valid number")),
}
}))
.arg(Arg::with_name("parallel")
.short("p")
.long("parallel")
.value_name("LIMIT")
.help("Deploy parallelism limit")
.long_help(r#"Limits the maximum number of hosts to be deployed in parallel.
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")),
}
}))
.arg(Arg::with_name("verbose")
.short("v")
.long("verbose")
.help("Be verbose")
.long_help("Deactivates the progress spinner and prints every line of output.")
.takes_value(false))
.arg(Arg::with_name("no-keys")
.long("no-keys")
.help("Do not upload keys")
.long_help(r#"Do not upload secret keys set in `deployment.keys`.
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))
.arg(Arg::with_name("no-substitutes")
.long("no-substitutes")
.help("Do not use substitutes")
.long_help("Disables the use of substituters when copying closures to the remote host.")
.takes_value(false))
.arg(Arg::with_name("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))
}
pub fn subcommand() -> App<'static, 'static> {
let command = SubCommand::with_name("apply")
.about("Apply configurations on remote machines")
.arg(Arg::with_name("goal")
.help("Deployment goal")
.long_help("Same as the targets for switch-to-configuration.\n\"push\" means only copying the closures to remote nodes.")
.default_value("switch")
.index(1)
.possible_values(&["build", "push", "switch", "boot", "test", "dry-activate", "keys"]))
;
let command = register_deploy_args(command);
util::register_selector_args(command)
}
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
let hive = util::hive_from_args(local_args).unwrap();
log::info!("Enumerating nodes...");
let all_nodes = hive.deployment_info().await.unwrap();
let selected_nodes = match local_args.value_of("on") {
Some(filter) => {
util::filter_nodes(&all_nodes, filter)
}
None => all_nodes.keys().cloned().collect(),
};
if selected_nodes.len() == 0 {
log::warn!("No hosts matched. Exiting...");
quit::with_code(2);
}
let ssh_config = env::var("SSH_CONFIG_FILE")
.ok().map(PathBuf::from);
// FIXME: This is ugly :/ Make an enum wrapper for this fake "keys" goal
let goal_arg = local_args.value_of("goal").unwrap();
let goal = if goal_arg == "keys" {
Goal::Build
} else {
Goal::from_str(goal_arg).unwrap()
};
let build_only = goal == Goal::Build && goal_arg != "keys";
let mut targets = HashMap::new();
for node in &selected_nodes {
let config = all_nodes.get(node).unwrap();
let host = config.to_ssh_host();
match host {
Some(mut host) => {
if let Some(ssh_config) = ssh_config.as_ref() {
host.set_ssh_config(ssh_config.clone());
}
targets.insert(
node.clone(),
Target::new(host.upcast(), config.clone()),
);
}
None => {
if build_only {
targets.insert(
node.clone(),
Target::new(localhost(), config.clone()),
);
}
}
}
}
if targets.len() == all_nodes.len() {
log::info!("Selected all {} nodes.", targets.len());
} else if targets.len() == selected_nodes.len() {
log::info!("Selected {} out of {} hosts.", targets.len(), all_nodes.len());
} else {
log::info!("Selected {} out of {} hosts ({} skipped)", targets.len(), all_nodes.len(), selected_nodes.len() - targets.len());
}
if targets.len() == 0 {
log::warn!("No selected nodes are accessible over SSH. Exiting...");
quit::with_code(2);
}
let mut deployment = Deployment::new(hive, targets, goal);
let mut options = DeploymentOptions::default();
options.set_substituters_push(!local_args.is_present("no-substitutes"));
options.set_gzip(!local_args.is_present("no-gzip"));
options.set_progress_bar(!local_args.is_present("verbose"));
options.set_upload_keys(!local_args.is_present("no-keys"));
deployment.set_options(options);
if local_args.is_present("no-keys") && goal_arg == "keys" {
log::error!("--no-keys cannot be used when the goal is to upload keys");
quit::with_code(1);
}
let mut parallelism_limit = ParallelismLimit::default();
parallelism_limit.set_apply_limit({
let limit = local_args.value_of("parallel").unwrap().parse::<usize>().unwrap();
if limit == 0 {
selected_nodes.len() // HACK
} else {
local_args.value_of("parallel").unwrap().parse::<usize>().unwrap()
}
});
parallelism_limit.set_build_limit({
let limit = local_args.value_of("parallel").unwrap().parse::<usize>().unwrap();
if limit == 0 {
panic!("The build parallelism limit must not be 0");
}
limit
});
deployment.set_parallelism_limit(parallelism_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)
}
}
};
deployment.set_evaluation_node_limit(evaluation_node_limit);
let deployment = Arc::new(deployment);
if goal_arg == "keys" {
deployment.upload_keys().await;
} else {
deployment.execute().await;
}
}