Prompt for revert on failure
This commit is contained in:
parent
0272bacfb0
commit
878a071183
|
@ -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?;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>) -> ! {
|
||||
|
|
74
src/plan.rs
74
src/plan.rs
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue