service_install/tui/
install.rs1use std::collections::VecDeque;
2
3use crate::install::InstallError;
4use crate::install::InstallSteps;
5use crate::install::RollbackError;
6use crate::install::RollbackStep;
7use crate::install::StepOptions;
8use crate::Tense;
9
10use dialoguer::Confirm;
11use dialoguer::Select;
12
13#[derive(Debug, thiserror::Error)]
14pub enum Error {
15 #[error("canceled by the user")]
16 Canceled,
17 #[error("could not get input from the user")]
18 UserInputFailed(
19 #[from]
20 #[source]
21 dialoguer::Error,
22 ),
23 #[error("ran into one or more errors and user chose to abort, errors: {0:#?}")]
24 AbortedAfterError(Vec<InstallError>),
25 #[error("user chose to cancel and rollback however rollback failed")]
26 RollbackFollowingCancel(#[source] RollbackError),
27 #[error("ran into error user chose to abort and rollback however rollback failed")]
28 RollbackFollowingError(#[source] RollbackError),
29}
30
31pub fn start(steps: InstallSteps, detailed: bool) -> Result<(), Error> {
48 let mut errors = Vec::new();
49 let mut rollback_steps = VecDeque::new();
50 for mut step in steps {
51 if detailed {
52 println!("{}", step.describe_detailed(Tense::Questioning));
53 } else {
54 println!("{}", step.describe(Tense::Questioning));
55 }
56
57 match step.options() {
58 Some(StepOptions::YesOrAbort) => {
59 if !Confirm::new().interact()? {
60 rollback_if_user_wants_to(rollback_steps)?;
61 return Err(Error::Canceled);
62 }
63 }
64 None => (),
65 }
66
67 match step.perform() {
68 Ok(None) => (),
69 Ok(Some(rollback)) => rollback_steps.push_front(rollback),
70 Err(e) => {
71 let details = format_error_chain(&e).replace('\n', "\n\t");
72 errors.push(e);
73
74 println!("An error occurred, details:\n\t{details}\t");
75 match Select::new()
76 .with_prompt("What do you want to do?")
77 .items(&["rollback and abort", "abort", "continue"])
78 .default(0)
79 .interact()?
80 {
81 2 => continue,
82 0 => rollback(rollback_steps).map_err(Error::RollbackFollowingError)?,
83 _ => (),
84 }
85 return Err(Error::AbortedAfterError(errors));
86 }
87 }
88 }
89
90 Ok(())
91}
92
93fn rollback_if_user_wants_to(rollback_steps: VecDeque<Box<dyn RollbackStep>>) -> Result<(), Error> {
94 if rollback_steps.is_empty() {
95 println!("Install aborted, no changes have been made");
96 } else if Confirm::new()
97 .with_prompt("Install aborted, do you want to roll back any changes made?")
98 .interact()?
99 {
100 rollback(rollback_steps).map_err(Error::RollbackFollowingCancel)?;
101 }
102
103 Ok(())
104}
105
106fn rollback(mut rollback_steps: VecDeque<Box<dyn RollbackStep>>) -> Result<(), RollbackError> {
107 for step in &mut rollback_steps {
108 let did = step.describe(Tense::Past);
109 step.perform()?;
110 println!("{did}");
111 }
112 Ok(())
113}
114
115fn format_error_chain(root: &dyn std::error::Error) -> String {
116 let mut res = format!("1 {root}\n");
117
118 let mut i = 2;
119 let mut curr = root;
120 while let Some(next) = curr.source() {
121 res.push_str(&format!("{i} {next}\n"));
122 curr = next;
123 i += 1;
124 }
125
126 res
127}