Prompt for revert on failure

This commit is contained in:
Ana Hobden 2022-09-28 13:20:11 -07:00
parent 0272bacfb0
commit 878a071183
14 changed files with 78 additions and 39 deletions

View file

@ -104,6 +104,7 @@ impl Actionable for ConfigureNix {
tracing::trace!("Already completed: Configuring nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Configuring nix");
if let Some(configure_shell_profile) = configure_shell_profile {
@ -203,6 +204,7 @@ impl Actionable for ConfigureNix {
tracing::trace!("Already reverted: Unconfiguring nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Unconfiguring nix");
configure_nix_daemon_service.revert().await?;

View file

@ -79,6 +79,7 @@ impl Actionable for ConfigureShellProfile {
tracing::trace!("Already completed: Configuring shell profile");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Configuring shell profile");
let mut set = JoinSet::new();
@ -139,6 +140,7 @@ impl Actionable for ConfigureShellProfile {
tracing::trace!("Already reverted: Unconfiguring shell profile");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Unconfiguring shell profile");
let mut set = JoinSet::new();

View file

@ -81,6 +81,7 @@ impl Actionable for CreateNixTree {
tracing::trace!("Already completed: Creating nix tree");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Creating nix tree");
// Just do sequential since parallizing this will have little benefit
@ -127,6 +128,7 @@ impl Actionable for CreateNixTree {
tracing::trace!("Already reverted: Deleting nix tree");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Deleting nix tree");
// Just do sequential since parallizing this will have little benefit

View file

@ -102,6 +102,7 @@ impl Actionable for CreateUsersAndGroup {
tracing::trace!("Already completed: Creating users and groups");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Creating users and groups");
// Create group
@ -191,6 +192,7 @@ impl Actionable for CreateUsersAndGroup {
tracing::trace!("Already reverted: Delete users and groups");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Delete users and groups");
let mut set = JoinSet::new();

View file

@ -75,6 +75,7 @@ impl Actionable for PlaceChannelConfiguration {
tracing::trace!("Already completed: Placing channel configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Placing channel configuration");
create_file.execute().await?;
@ -113,6 +114,7 @@ impl Actionable for PlaceChannelConfiguration {
tracing::trace!("Already reverted: Removing channel configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Removing channel configuration");
create_file.revert().await?;

View file

@ -74,6 +74,7 @@ impl Actionable for PlaceNixConfiguration {
tracing::trace!("Already completed: Placing Nix configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Placing Nix configuration");
create_directory.execute().await?;
@ -109,6 +110,7 @@ impl Actionable for PlaceNixConfiguration {
tracing::trace!("Already reverted: Remove nix configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Remove nix configuration");
create_file.revert().await?;

View file

@ -77,6 +77,7 @@ impl Actionable for ProvisionNix {
tracing::trace!("Already completed: Provisioning Nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Provisioning Nix");
// We fetch nix while doing the rest, then move it over.
@ -134,6 +135,7 @@ impl Actionable for ProvisionNix {
tracing::trace!("Already reverted: Unprovisioning nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Unprovisioning nix");
// We fetch nix while doing the rest, then move it over.

View file

@ -44,6 +44,7 @@ impl Actionable for StartNixDaemon {
tracing::trace!("Already completed: Starting the nix daemon");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Starting the nix daemon");
start_systemd_socket.execute().await?;
@ -72,6 +73,7 @@ impl Actionable for StartNixDaemon {
tracing::trace!("Already reverted: Stop the nix daemon");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Stop the nix daemon");
start_systemd_socket.revert().await?;

View file

@ -34,6 +34,8 @@ pub trait Actionable: DeserializeOwned + Serialize + Into<Action> {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum ActionState {
Completed,
// Only applicable to meta-actions that start multiple sub-actions.
Progress,
Uncompleted,
}

View file

@ -4,6 +4,7 @@ pub(crate) mod subcommand;
use crate::{cli::arg::ChannelValue, interaction};
use clap::{ArgAction, Parser};
use harmonic::{InstallPlan, InstallSettings};
use eyre::eyre;
use std::process::ExitCode;
use self::subcommand::HarmonicSubcommand;
@ -101,7 +102,7 @@ impl CommandExecute for HarmonicCli {
}
if let Err(err) = plan.install().await {
tracing::error!("{err:#?}");
tracing::error!("{:?}", eyre!(err));
if !interaction::confirm(plan.describe_revert()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}

View file

@ -1,7 +1,7 @@
use std::{path::PathBuf, process::ExitCode};
use clap::{ArgAction, Parser};
use eyre::WrapErr;
use eyre::{WrapErr, eyre};
use harmonic::InstallPlan;
use crate::{cli::CommandExecute, interaction};
@ -38,7 +38,7 @@ impl CommandExecute for Execute {
}
if let Err(err) = plan.install().await {
tracing::error!("{err:#?}");
tracing::error!("{:?}", eyre!(err));
if !interaction::confirm(plan.describe_revert()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}

View file

@ -35,12 +35,14 @@ impl CommandExecute for Revert {
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
if !no_confirm {
if !interaction::confirm(plan.describe_execute()).await? {
if !interaction::confirm(plan.describe_revert()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
}
plan.revert().await?;
// TODO(@hoverbear): It would be so nice to catch errors and offer the user a way to keep going...
// However that will require being able to link error -> step and manually setting that step as `Uncompleted`.
Ok(ExitCode::SUCCESS)
}

View file

@ -20,13 +20,17 @@ pub(crate) async fn confirm(question: impl AsRef<str>) -> eyre::Result<bool> {
stdout.write_all(with_confirm.as_bytes()).await?;
stdout.flush().await?;
crossterm::terminal::enable_raw_mode()?;
let mut reader = EventStream::new();
loop {
let retval = loop {
let event = reader.next().fuse().await;
match event {
Some(Ok(event)) => {
if let crossterm::event::Event::Key(key) = event {
match key.code {
KeyCode::Enter => continue, // Many users will hit enter accidently when they are agreeing/declining prompts.
// TODO(@hoverbear): Should maybe actually even wait for it?
KeyCode::Char('y') => break Ok(true),
_ => {
stdout
@ -38,10 +42,12 @@ pub(crate) async fn confirm(question: impl AsRef<str>) -> eyre::Result<bool> {
}
}
},
Some(Err(err)) => return Err(err).wrap_err("Getting response"),
None => return Err(eyre!("Bailed, no confirmation event")),
Some(Err(err)) => break Err(err).wrap_err("Getting response"),
None => break Err(eyre!("Bailed, no confirmation event")),
}
}
};
crossterm::terminal::disable_raw_mode()?;
retval
}
pub(crate) async fn clean_exit_with_message(message: impl AsRef<str>) -> ! {

View file

@ -104,26 +104,24 @@ impl InstallPlan {
// This is **deliberately sequential**.
// Actions which are parallelizable are represented by "group actions" like CreateUsers
// The plan itself represents the concept of the sequence of stages.
self.provision_nix
.execute()
.await
.map_err(|e| ActionError::from(e))?;
self.configure_nix
.execute()
.await
.map_err(|e| ActionError::from(e))?;
self.start_nix_daemon
.execute()
.await
.map_err(|e| ActionError::from(e))?;
let install_receipt_path = PathBuf::from("/nix/receipt.json");
let self_json =
serde_json::to_string_pretty(&self).map_err(HarmonicError::SerializingReceipt)?;
tokio::fs::write(&install_receipt_path, self_json)
.await
.map_err(|e| HarmonicError::RecordingReceipt(install_receipt_path, e))?;
if let Err(err) = self.provision_nix.execute().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
}
if let Err(err) = self.configure_nix.execute().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
}
if let Err(err) = self.start_nix_daemon.execute().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
}
write_receipt(self.clone()).await?;
Ok(())
}
@ -179,19 +177,33 @@ impl InstallPlan {
// This is **deliberately sequential**.
// Actions which are parallelizable are represented by "group actions" like CreateUsers
// The plan itself represents the concept of the sequence of stages.
self.start_nix_daemon
.revert()
.await
.map_err(|e| ActionError::from(e))?;
self.configure_nix
.revert()
.await
.map_err(|e| ActionError::from(e))?;
self.provision_nix
.revert()
.await
.map_err(|e| ActionError::from(e))?;
if let Err(err) = self.start_nix_daemon.revert().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
}
if let Err(err) = self.configure_nix.revert().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
}
if let Err(err) = self.provision_nix.revert().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
}
Ok(())
}
}
async fn write_receipt(plan: InstallPlan) -> Result<(), HarmonicError> {
tokio::fs::create_dir_all("/nix").await
.map_err(|e| HarmonicError::RecordingReceipt(PathBuf::from("/nix"), e))?;
let install_receipt_path = PathBuf::from("/nix/receipt.json");
let self_json =
serde_json::to_string_pretty(&plan).map_err(HarmonicError::SerializingReceipt)?;
tokio::fs::write(&install_receipt_path, self_json)
.await
.map_err(|e| HarmonicError::RecordingReceipt(install_receipt_path, e))?;
Result::<(), HarmonicError>::Ok(())
}