use ansi_term::Color::{Cyan, Green, Purple, Red, Yellow};
use anyhow::Error;
use massh::{MasshClient, MasshConfig};
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt)]
struct Opt {
#[structopt(subcommand)]
cmd: Command,
#[structopt(short, long, conflicts_with("yaml"), required_unless("yaml"))]
json: Option<PathBuf>,
#[structopt(short, long, conflicts_with("json"), required_unless("json"))]
yaml: Option<PathBuf>,
}
#[derive(StructOpt)]
enum Command {
Execute {
command: String,
},
ScpDownload {
remote_path: PathBuf,
local_path: PathBuf,
},
ScpUpload {
local_path: PathBuf,
remote_path: PathBuf,
},
}
enum Format {
Json,
Yaml,
}
fn main() {
let opt = Opt::from_args();
let (path, format) = if let Some(path) = opt.json {
(path, Format::Json)
} else if let Some(path) = opt.yaml {
(path, Format::Yaml)
} else {
unreachable!();
};
let string = std::fs::read_to_string(&path).unwrap_or_else(|error| {
let message = Red.paint(format!("Failed to read {:?}: {}", path, error));
eprintln!("{}", message);
std::process::exit(1);
});
let result = match format {
Format::Json => MasshConfig::from_json(&string),
Format::Yaml => MasshConfig::from_yaml(&string),
};
let config = result.unwrap_or_else(|error| {
let message = Red.paint(format!("Failed to parse {:?}: {}", path, error));
eprintln!("{}", message);
std::process::exit(1);
});
let massh = MasshClient::from(&config);
let (mut num_success, mut num_warning, mut num_failure) = (0, 0, 0);
match &opt.cmd {
Command::Execute { command } => {
let rx = massh.execute(command);
while let Ok((host, result)) = rx.recv() {
match result {
Ok(output) => {
if output.exit_status == 0 {
print_success(host, &mut num_success);
} else {
print_warning(host, &mut num_warning, output.exit_status);
}
print_bytes(&output.stdout, true);
print_bytes(&output.stderr, false);
}
Err(error) => print_failure(host, &mut num_failure, error),
}
}
}
_ => {
let rx = match &opt.cmd {
Command::ScpDownload {
remote_path,
local_path,
} => massh.scp_download(remote_path, local_path),
Command::ScpUpload {
local_path,
remote_path,
} => massh.scp_upload(local_path, remote_path),
_ => unreachable!(),
};
while let Ok((host, result)) = rx.recv() {
match result {
Ok(()) => print_success(host, &mut num_success),
Err(error) => print_failure(host, &mut num_failure, error),
}
}
}
}
println!();
print_summary("success", num_success);
print_summary("warning", num_warning);
print_summary("failure", num_failure);
}
fn print_summary(label: &str, count: usize) {
if count > 0 {
let color = match label {
"success" => Green,
"warning" => Yellow,
"failure" => Red,
_ => unreachable!(),
};
let noun = if count == 1 { "host" } else { "hosts" };
let message = format!("{}: {} {}", label, count, noun);
println!("{}", color.paint(message));
}
}
fn print_success(host: String, count: &mut usize) {
*count += 1;
let message = Green.paint("success");
println!("[{}]: {}", host, message);
}
fn print_warning(host: String, count: &mut usize, exit_status: i32) {
*count += 1;
let message = Yellow.paint(format!("warning: exit status = {}", exit_status));
println!("[{}]: {}", host, message);
}
fn print_failure(host: String, count: &mut usize, error: Error) {
*count += 1;
let message = Red.paint(format!("failure: {}", error));
println!("[{}]: {}", host, message);
}
fn print_bytes(bytes: &[u8], stdout: bool) {
if !bytes.is_empty() {
let color = if stdout { Cyan } else { Purple };
let label = if stdout { "stdout" } else { "stderr" };
if let Ok(message) = std::str::from_utf8(bytes) {
println!("{}", color.paint(message.trim_end()));
} else {
let message = format!("{} is not UTF-8 ({} bytes)", label, bytes.len());
println!("{}", color.paint(message));
}
}
}