From 45e99a75f968820a0bd14367f70580b51f20a14e Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sun, 22 Nov 2020 20:03:04 -0700 Subject: Partially add deployment confirmation utilities (for #4) --- Cargo.lock | 17 +++++++++++ Cargo.toml | 2 ++ src/main.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 3 ++ 4 files changed, 112 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3d7fc7e..5efa7f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,9 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "toml", "whoami", + "yn", ] [[package]] @@ -721,6 +723,15 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +dependencies = [ + "serde", +] + [[package]] name = "unicode-segmentation" version = "1.6.0" @@ -809,3 +820,9 @@ dependencies = [ "winapi 0.2.8", "winapi-build", ] + +[[package]] +name = "yn" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d789b24a50ca067124e6e6ad3061c48151da174043cb09285ba934425e8739ec" diff --git a/Cargo.toml b/Cargo.toml index 9bf9b26..209d812 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,8 @@ inotify = "0.8" futures-util = "0.3.6" fork = "0.1" thiserror = "1.0" +toml = "0.5" +yn = "0.1" [[bin]] diff --git a/src/main.rs b/src/main.rs index 7261965..a4cb149 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,9 @@ // // SPDX-License-Identifier: MPL-2.0 +use std::collections::HashMap; +use std::io::{stdin, stdout, Write}; + use clap::Clap; use std::process::Stdio; @@ -351,6 +354,91 @@ async fn get_deployment_data( Ok(serde_json::from_str(&data_json)?) } +#[derive(Serialize)] +struct PromptPart<'a> { + user: &'a str, + ssh_user: &'a str, + path: &'a str, + hostname: &'a str, + ssh_opts: &'a [String], +} + +#[derive(Error, Debug)] +enum PromptChangesError { + #[error("Failed to make printable TOML of deployment: {0}")] + TomlFormat(#[from] toml::ser::Error), + #[error("Failed to flush stdout prior to query: {0}")] + StdoutFlush(std::io::Error), + #[error("Failed to read line from stdin: {0}")] + StdinRead(std::io::Error), + #[error("User cancelled deployment")] + Cancelled, +} + +fn prompt_changes( + parts: Vec<(&utils::DeployData, &utils::DeployDefs)>, +) -> Result<(), PromptChangesError> { + let mut part_map: HashMap> = HashMap::new(); + + for (data, defs) in parts { + part_map + .entry(data.node_name.to_string()) + .or_insert(HashMap::new()) + .insert( + data.profile_name.to_string(), + PromptPart { + user: &defs.profile_user, + ssh_user: &defs.ssh_user, + path: &data.profile.profile_settings.path, + hostname: &data.node.node_settings.hostname, + ssh_opts: &data.merged_settings.ssh_opts, + }, + ); + } + + let toml = toml::to_string(&part_map)?; + + warn!("The following profiles are going to be deployed:\n{}", toml); + + info!("Are you sure you want to deploy these profiles?"); + print!("> "); + + stdout().flush().map_err(PromptChangesError::StdoutFlush)?; + + let mut s = String::new(); + stdin() + .read_line(&mut s) + .map_err(PromptChangesError::StdinRead)?; + + if !yn::yes(&s) { + if yn::is_somewhat_yes(&s) { + info!("Sounds like you might want to continue, to be more clear please just say \"yes\". Do you want to deploy these profiles?"); + print!("> "); + + stdout().flush().map_err(PromptChangesError::StdoutFlush)?; + + let mut s = String::new(); + stdin() + .read_line(&mut s) + .map_err(PromptChangesError::StdinRead)?; + + if !yn::yes(&s) { + return Err(PromptChangesError::Cancelled); + } + } else { + if !yn::no(&s) { + info!( + "That was unclear, but sounded like a no to me. Please say \"yes\" or \"no\" to be more clear." + ); + } + + return Err(PromptChangesError::Cancelled); + } + } + + Ok(()) +} + #[derive(Error, Debug)] enum RunDeployError { #[error("Failed to deploy profile: {0}")] @@ -369,6 +457,8 @@ enum RunDeployError { ProfileWithoutNode, #[error("Error processing deployment definitions: {0}")] DeployDataDefsError(#[from] utils::DeployDataDefsError), + #[error("{0}")] + PromptChangesError(#[from] PromptChangesError), } async fn run_deploy( diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 19d0948..76d638d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -21,6 +21,7 @@ pub mod data; pub mod deploy; pub mod push; +#[derive(Debug)] pub struct CmdOverrides { pub ssh_user: Option, pub profile_user: Option, @@ -95,6 +96,7 @@ fn test_parse_flake() { ); } +#[derive(Debug)] pub struct DeployData<'a> { pub node_name: &'a str, pub node: &'a data::Node, @@ -106,6 +108,7 @@ pub struct DeployData<'a> { pub merged_settings: data::GenericSettings, } +#[derive(Debug)] pub struct DeployDefs<'a> { pub ssh_user: Cow<'a, str>, pub profile_user: Cow<'a, str>, -- cgit v1.2.3