aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornotgne22020-11-22 20:03:04 -0700
committernotgne22020-11-22 20:03:04 -0700
commit45e99a75f968820a0bd14367f70580b51f20a14e (patch)
treebb5c7ff50755879ba5b53e76f29e9ec681d29852
parentcb50d98884f786a3e2aa40befa6ebf1ef2be90da (diff)
Partially add deployment confirmation utilities (for #4)
-rw-r--r--Cargo.lock17
-rw-r--r--Cargo.toml2
-rw-r--r--src/main.rs90
-rw-r--r--src/utils/mod.rs3
4 files changed, 112 insertions, 0 deletions
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]]
@@ -722,6 +724,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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<String, HashMap<String, PromptPart>> = 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<String>,
pub profile_user: Option<String>,
@@ -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>,