2020-12-18 10:27:44 +01:00
|
|
|
use std::collections::HashMap;
|
2021-01-24 23:08:48 +01:00
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::process::Stdio;
|
2020-12-18 10:27:44 +01:00
|
|
|
|
2020-12-29 20:31:19 +01:00
|
|
|
use clap::{App, Arg, ArgMatches};
|
2021-01-24 23:08:48 +01:00
|
|
|
use futures::future::join3;
|
2020-12-16 05:21:26 +01:00
|
|
|
use glob::Pattern as GlobPattern;
|
2021-01-24 23:08:48 +01:00
|
|
|
use tokio::io::{AsyncRead, AsyncBufReadExt, BufReader};
|
|
|
|
use tokio::process::Command;
|
2020-12-16 05:21:26 +01:00
|
|
|
|
2021-11-21 08:34:52 +01:00
|
|
|
use super::nix::{Flake, NodeName, NodeConfig, Hive, HivePath, NixResult};
|
2021-02-10 19:38:03 +01:00
|
|
|
use super::progress::TaskProgress;
|
2020-12-18 10:27:44 +01:00
|
|
|
|
|
|
|
enum NodeFilter {
|
|
|
|
NameFilter(GlobPattern),
|
|
|
|
TagFilter(GlobPattern),
|
|
|
|
}
|
|
|
|
|
2021-01-24 23:08:48 +01:00
|
|
|
/// Non-interactive execution of an arbitrary Nix command.
|
|
|
|
pub struct CommandExecution {
|
|
|
|
command: Command,
|
2021-02-10 19:38:03 +01:00
|
|
|
progress_bar: TaskProgress,
|
2021-01-24 23:08:48 +01:00
|
|
|
stdout: Option<String>,
|
|
|
|
stderr: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CommandExecution {
|
2021-02-10 04:28:45 +01:00
|
|
|
pub fn new(command: Command) -> Self {
|
2021-01-24 23:08:48 +01:00
|
|
|
Self {
|
|
|
|
command,
|
2021-02-10 19:38:03 +01:00
|
|
|
progress_bar: TaskProgress::default(),
|
2021-01-24 23:08:48 +01:00
|
|
|
stdout: None,
|
|
|
|
stderr: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-10 19:38:03 +01:00
|
|
|
/// Provides a TaskProgress to use to display output.
|
|
|
|
pub fn set_progress_bar(&mut self, bar: TaskProgress) {
|
2021-02-10 04:28:45 +01:00
|
|
|
self.progress_bar = bar;
|
2021-01-24 23:08:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve logs from the last invocation.
|
|
|
|
pub fn get_logs(&self) -> (Option<&String>, Option<&String>) {
|
|
|
|
(self.stdout.as_ref(), self.stderr.as_ref())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Run the command.
|
|
|
|
pub async fn run(&mut self) -> NixResult<()> {
|
|
|
|
self.command.stdin(Stdio::null());
|
|
|
|
self.command.stdout(Stdio::piped());
|
|
|
|
self.command.stderr(Stdio::piped());
|
|
|
|
|
|
|
|
self.stdout = Some(String::new());
|
|
|
|
self.stderr = Some(String::new());
|
|
|
|
|
|
|
|
let mut child = self.command.spawn()?;
|
|
|
|
|
|
|
|
let stdout = BufReader::new(child.stdout.take().unwrap());
|
|
|
|
let stderr = BufReader::new(child.stderr.take().unwrap());
|
|
|
|
|
|
|
|
let futures = join3(
|
2021-02-10 04:28:45 +01:00
|
|
|
capture_stream(stdout, self.progress_bar.clone()),
|
|
|
|
capture_stream(stderr, self.progress_bar.clone()),
|
2021-01-24 23:08:48 +01:00
|
|
|
child.wait(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let (stdout_str, stderr_str, wait) = futures.await;
|
|
|
|
self.stdout = Some(stdout_str);
|
|
|
|
self.stderr = Some(stderr_str);
|
|
|
|
|
|
|
|
let exit = wait?;
|
|
|
|
|
|
|
|
if exit.success() {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
2021-04-29 00:09:40 +02:00
|
|
|
Err(exit.into())
|
2021-01-24 23:08:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-26 08:38:10 +02:00
|
|
|
pub async fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
|
2020-12-29 20:31:19 +01:00
|
|
|
let path = match args.occurrences_of("config") {
|
|
|
|
0 => {
|
|
|
|
// traverse upwards until we find hive.nix
|
|
|
|
let mut cur = std::env::current_dir()?;
|
2021-06-29 10:02:43 +02:00
|
|
|
let mut file_path = None;
|
2020-12-29 20:31:19 +01:00
|
|
|
|
|
|
|
loop {
|
2021-10-28 23:09:35 +02:00
|
|
|
let flake = cur.join("flake.nix");
|
|
|
|
if flake.is_file() {
|
|
|
|
file_path = Some(flake);
|
|
|
|
break;
|
|
|
|
}
|
2020-12-29 20:31:19 +01:00
|
|
|
|
2021-10-28 23:09:35 +02:00
|
|
|
let legacy = cur.join("hive.nix");
|
|
|
|
if legacy.is_file() {
|
|
|
|
file_path = Some(legacy);
|
2020-12-29 20:31:19 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
match cur.parent() {
|
|
|
|
Some(parent) => {
|
|
|
|
cur = parent.to_owned();
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-29 10:02:43 +02:00
|
|
|
if file_path.is_none() {
|
|
|
|
log::error!("Could not find `hive.nix` or `flake.nix` in {:?} or any parent directory", std::env::current_dir()?);
|
2020-12-29 20:31:19 +01:00
|
|
|
}
|
|
|
|
|
2021-06-29 10:02:43 +02:00
|
|
|
file_path.unwrap()
|
2020-12-29 20:31:19 +01:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
let path = args.value_of("config").expect("The config arg should exist").to_owned();
|
2021-06-29 10:02:43 +02:00
|
|
|
let fpath = canonicalize_cli_path(&path);
|
|
|
|
|
|
|
|
if !fpath.exists() && path.contains(":") {
|
|
|
|
// Treat as flake URI
|
2021-10-26 08:38:10 +02:00
|
|
|
let flake = Flake::from_uri(path).await?;
|
|
|
|
let hive_path = HivePath::Flake(flake);
|
2021-06-29 10:02:43 +02:00
|
|
|
let mut hive = Hive::new(hive_path)?;
|
|
|
|
|
|
|
|
if args.is_present("show-trace") {
|
|
|
|
hive.set_show_trace(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(hive);
|
|
|
|
}
|
|
|
|
|
|
|
|
fpath
|
2020-12-29 20:31:19 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-10-26 08:38:10 +02:00
|
|
|
let hive_path = HivePath::from_path(path).await?;
|
2021-06-29 10:02:43 +02:00
|
|
|
let mut hive = Hive::new(hive_path)?;
|
2020-12-29 20:31:19 +01:00
|
|
|
|
|
|
|
if args.is_present("show-trace") {
|
2021-06-29 10:02:43 +02:00
|
|
|
hive.set_show_trace(true);
|
2020-12-29 20:31:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(hive)
|
|
|
|
}
|
|
|
|
|
2021-11-21 08:34:52 +01:00
|
|
|
pub fn filter_nodes(nodes: &HashMap<NodeName, NodeConfig>, filter: &str) -> Vec<NodeName> {
|
2020-12-18 10:27:44 +01:00
|
|
|
let filters: Vec<NodeFilter> = filter.split(",").map(|pattern| {
|
|
|
|
use NodeFilter::*;
|
|
|
|
if let Some(tag_pattern) = pattern.strip_prefix("@") {
|
|
|
|
TagFilter(GlobPattern::new(tag_pattern).unwrap())
|
|
|
|
} else {
|
|
|
|
NameFilter(GlobPattern::new(pattern).unwrap())
|
|
|
|
}
|
|
|
|
}).collect();
|
2020-12-16 05:21:26 +01:00
|
|
|
|
|
|
|
if filters.len() > 0 {
|
2020-12-18 10:27:44 +01:00
|
|
|
nodes.iter().filter_map(|(name, node)| {
|
2020-12-16 05:21:26 +01:00
|
|
|
for filter in filters.iter() {
|
2020-12-18 10:27:44 +01:00
|
|
|
use NodeFilter::*;
|
|
|
|
match filter {
|
|
|
|
TagFilter(pat) => {
|
|
|
|
// Welp
|
|
|
|
for tag in node.tags() {
|
|
|
|
if pat.matches(tag) {
|
|
|
|
return Some(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NameFilter(pat) => {
|
|
|
|
if pat.matches(name) {
|
|
|
|
return Some(name)
|
|
|
|
}
|
|
|
|
}
|
2020-12-16 05:21:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-18 10:27:44 +01:00
|
|
|
None
|
2020-12-16 05:21:26 +01:00
|
|
|
}).cloned().collect()
|
|
|
|
} else {
|
2020-12-18 10:27:44 +01:00
|
|
|
nodes.keys().cloned().collect()
|
2020-12-16 05:21:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-29 06:35:43 +01:00
|
|
|
pub fn register_selector_args<'a, 'b>(command: App<'a, 'b>) -> App<'a, 'b> {
|
|
|
|
command
|
2020-12-16 05:21:26 +01:00
|
|
|
.arg(Arg::with_name("on")
|
|
|
|
.long("on")
|
2021-01-02 05:45:41 +01:00
|
|
|
.value_name("NODES")
|
|
|
|
.help("Node selector")
|
|
|
|
.long_help(r#"Select a list of nodes to deploy to.
|
|
|
|
|
|
|
|
The list is comma-separated and globs are supported. To match tags, prepend the filter by @. Valid examples:
|
2020-12-16 05:21:26 +01:00
|
|
|
|
|
|
|
- host1,host2,host3
|
|
|
|
- edge-*
|
2020-12-18 10:27:44 +01:00
|
|
|
- edge-*,core-*
|
|
|
|
- @a-tag,@tags-can-have-*"#)
|
2020-12-16 05:21:26 +01:00
|
|
|
.takes_value(true))
|
|
|
|
}
|
2020-12-29 20:31:19 +01:00
|
|
|
|
2021-06-29 10:02:43 +02:00
|
|
|
fn canonicalize_cli_path(path: &str) -> PathBuf {
|
2020-12-29 20:31:19 +01:00
|
|
|
if !path.starts_with("/") {
|
|
|
|
format!("./{}", path).into()
|
|
|
|
} else {
|
|
|
|
path.into()
|
|
|
|
}
|
|
|
|
}
|
2021-02-09 23:57:11 +01:00
|
|
|
|
2021-02-10 19:38:03 +01:00
|
|
|
pub async fn capture_stream<R: AsyncRead + Unpin>(mut stream: BufReader<R>, mut progress_bar: TaskProgress) -> String {
|
2021-02-09 23:57:11 +01:00
|
|
|
let mut log = String::new();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let mut line = String::new();
|
|
|
|
let len = stream.read_line(&mut line).await.unwrap();
|
|
|
|
|
|
|
|
if len == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let trimmed = line.trim_end();
|
2021-02-10 04:28:45 +01:00
|
|
|
progress_bar.log(trimmed);
|
2021-02-09 23:57:11 +01:00
|
|
|
|
|
|
|
log += trimmed;
|
|
|
|
log += "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
log
|
|
|
|
}
|