From b34a352753838a86b75e86385c10ed9ccdfa21b7 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Tue, 27 Sep 2022 12:05:24 -0700 Subject: [PATCH] Logging and error tidying --- .../base/configure_nix_daemon_service.rs | 41 ++++++--- src/actions/base/create_directory.rs | 40 +++++++-- src/actions/base/create_file.rs | 42 +++++++-- src/actions/base/create_group.rs | 38 ++++++-- src/actions/base/create_or_append_file.rs | 40 +++++++-- src/actions/base/create_user.rs | 39 ++++++-- src/actions/base/fetch_nix.rs | 32 +++++-- src/actions/base/move_unpacked_nix.rs | 34 ++++--- src/actions/base/setup_default_profile.rs | 31 +++++-- src/actions/base/start_systemd_unit.rs | 42 +++++---- src/actions/meta/configure_nix.rs | 68 ++++++++++---- src/actions/meta/configure_shell_profile.rs | 38 ++++++-- src/actions/meta/create_nix_tree.rs | 67 ++++++++++---- src/actions/meta/create_users_and_group.rs | 65 ++++++++++---- .../meta/place_channel_configuration.rs | 35 ++++++-- src/actions/meta/place_nix_configuration.rs | 42 ++++++--- src/actions/meta/provision_nix.rs | 59 ++++++++---- src/actions/meta/start_nix_daemon.rs | 20 ++++- src/actions/mod.rs | 64 ++++++++----- src/cli/mod.rs | 12 ++- src/cli/subcommand/execute.rs | 10 ++- src/cli/subcommand/revert.rs | 2 +- src/plan.rs | 89 ++++++++++++++----- 23 files changed, 709 insertions(+), 241 deletions(-) diff --git a/src/actions/base/configure_nix_daemon_service.rs b/src/actions/base/configure_nix_daemon_service.rs index 623c601..76f1b80 100644 --- a/src/actions/base/configure_nix_daemon_service.rs +++ b/src/actions/base/configure_nix_daemon_service.rs @@ -33,16 +33,21 @@ impl ConfigureNixDaemonService { #[async_trait::async_trait] impl Actionable for ConfigureNixDaemonService { type Error = ConfigureNixDaemonServiceError; - fn description(&self) -> Vec { - vec![ActionDescription::new( - "Configure Nix daemon related settings with systemd".to_string(), - vec![ - "Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(), - "Run `systemctl link {SERVICE_SRC}`".to_string(), - "Run `systemctl link {SOCKET_SRC}`".to_string(), - "Run `systemctl daemon-reload`".to_string(), - ], - )] + + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + "Configure Nix daemon related settings with systemd".to_string(), + vec![ + "Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(), + "Run `systemctl link {SERVICE_SRC}`".to_string(), + "Run `systemctl link {SOCKET_SRC}`".to_string(), + "Run `systemctl daemon-reload`".to_string(), + ], + )] + } } #[tracing::instrument(skip_all)] @@ -86,6 +91,22 @@ impl Actionable for ConfigureNixDaemonService { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + "Unconfigure Nix daemon related settings with systemd".to_string(), + vec![ + "Run `systemctl disable {SOCKET_SRC}`".to_string(), + "Run `systemctl disable {SERVICE_SRC}`".to_string(), + "Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string(), + "Run `systemctl daemon-reload`".to_string(), + ], + )] + } + } + #[tracing::instrument(skip_all)] async fn revert(&mut self) -> Result<(), Self::Error> { let Self { action_state } = self; diff --git a/src/actions/base/create_directory.rs b/src/actions/base/create_directory.rs index 748c0c0..3f872f1 100644 --- a/src/actions/base/create_directory.rs +++ b/src/actions/base/create_directory.rs @@ -50,7 +50,8 @@ impl CreateDirectory { #[async_trait::async_trait] impl Actionable for CreateDirectory { type Error = CreateDirectoryError; - fn description(&self) -> Vec { + + fn describe_execute(&self) -> Vec { let Self { path, user, @@ -58,13 +59,17 @@ impl Actionable for CreateDirectory { mode, action_state: _, } = &self; - vec![ActionDescription::new( - format!("Create the directory `{}`", path.display()), - vec![format!( - "Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`", - path.display() - )], - )] + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Create the directory `{}`", path.display()), + vec![format!( + "Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`", + path.display() + )], + )] + } } #[tracing::instrument(skip_all, fields( @@ -110,6 +115,25 @@ impl Actionable for CreateDirectory { Ok(()) } + + fn describe_revert(&self) -> Vec { + let Self { + path, + user: _, + group: _, + mode: _, + action_state: _, + } = &self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + format!("Remove the directory `{}`", path.display()), + vec![], + )] + } + } + #[tracing::instrument(skip_all, fields( path = %self.path.display(), user = self.user, diff --git a/src/actions/base/create_file.rs b/src/actions/base/create_file.rs index 883cdfa..cdfc3e9 100644 --- a/src/actions/base/create_file.rs +++ b/src/actions/base/create_file.rs @@ -52,7 +52,8 @@ impl CreateFile { #[async_trait::async_trait] impl Actionable for CreateFile { type Error = CreateFileError; - fn description(&self) -> Vec { + + fn describe_execute(&self) -> Vec { let Self { path, user, @@ -62,12 +63,16 @@ impl Actionable for CreateFile { force: _, action_state: _, } = &self; - vec![ActionDescription::new( - format!("Create or overwrite file `{}`", path.display()), - vec![format!( - "Create or overwrite `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display() - )], - )] + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Create or overwrite file `{}`", path.display()), + vec![format!( + "Create or overwrite `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display() + )], + )] + } } #[tracing::instrument(skip_all, fields( @@ -121,6 +126,29 @@ impl Actionable for CreateFile { Ok(()) } + + fn describe_revert(&self) -> Vec { + let Self { + path, + user: _, + group: _, + mode: _, + buf: _, + force: _, + action_state: _, + } = &self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + format!("Delete file `{}`", path.display()), + vec![format!( + "Delete file `{}`", path.display() + )], + )] + } + } + #[tracing::instrument(skip_all, fields( path = %self.path.display(), user = self.user, diff --git a/src/actions/base/create_group.rs b/src/actions/base/create_group.rs index bcbf13c..902f10b 100644 --- a/src/actions/base/create_group.rs +++ b/src/actions/base/create_group.rs @@ -26,18 +26,23 @@ impl CreateGroup { #[async_trait::async_trait] impl Actionable for CreateGroup { type Error = CreateGroupError; - fn description(&self) -> Vec { + + fn describe_execute(&self) -> Vec { let Self { name, gid, action_state: _, } = &self; - vec![ActionDescription::new( - format!("Create group {name} with GID {gid}"), - vec![format!( - "The nix daemon requires a system user group its system users can be part of" - )], - )] + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Create group {name} with GID {gid}"), + vec![format!( + "The nix daemon requires a system user group its system users can be part of" + )], + )] + } } #[tracing::instrument(skip_all, fields( @@ -65,6 +70,25 @@ impl Actionable for CreateGroup { Ok(()) } + + fn describe_revert(&self) -> Vec { + let Self { + name, + gid: _, + action_state: _, + } = &self; + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Delete group {name}"), + vec![format!( + "The nix daemon requires a system user group its system users can be part of" + )], + )] + } + } + #[tracing::instrument(skip_all, fields( user = self.name, gid = self.gid, diff --git a/src/actions/base/create_or_append_file.rs b/src/actions/base/create_or_append_file.rs index 4cc7e02..138ba37 100644 --- a/src/actions/base/create_or_append_file.rs +++ b/src/actions/base/create_or_append_file.rs @@ -49,7 +49,8 @@ impl CreateOrAppendFile { #[async_trait::async_trait] impl Actionable for CreateOrAppendFile { type Error = CreateOrAppendFileError; - fn description(&self) -> Vec { + + fn describe_execute(&self) -> Vec { let Self { path, user, @@ -58,12 +59,16 @@ impl Actionable for CreateOrAppendFile { buf, action_state: _, } = &self; - vec![ActionDescription::new( - format!("Create or append file `{}`", path.display()), - vec![format!( - "Create or append `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display() - )], - )] + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Create or append file `{}`", path.display()), + vec![format!( + "Create or append `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display() + )], + )] + } } #[tracing::instrument(skip_all, fields( @@ -123,6 +128,27 @@ impl Actionable for CreateOrAppendFile { Ok(()) } + fn describe_revert(&self) -> Vec { + let Self { + path, + user: _, + group: _, + mode: _, + buf, + action_state: _, + } = &self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + format!("Delete Nix related fragment from file `{}`", path.display()), + vec![format!( + "Delete Nix related fragment from file `{}`. Fragment: `{buf}`", path.display() + )], + )] + } + } + #[tracing::instrument(skip_all, fields( path = %self.path.display(), user = self.user, diff --git a/src/actions/base/create_user.rs b/src/actions/base/create_user.rs index caa03b9..355cf7b 100644 --- a/src/actions/base/create_user.rs +++ b/src/actions/base/create_user.rs @@ -28,15 +28,20 @@ impl CreateUser { #[async_trait::async_trait] impl Actionable for CreateUser { type Error = CreateUserError; - fn description(&self) -> Vec { - let name = &self.name; - let uid = &self.uid; - vec![ActionDescription::new( - format!("Create user {name} with UID {uid}"), - vec![format!( - "The nix daemon requires system users it can act as in order to build" - )], - )] + + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + let Self { name, uid, gid, action_state: _ } = self; + + vec![ActionDescription::new( + format!("Create user {name} with UID {uid} with group {gid}"), + vec![format!( + "The nix daemon requires system users it can act as in order to build" + )], + )] + } } #[tracing::instrument(skip_all, fields( @@ -84,6 +89,22 @@ impl Actionable for CreateUser { Ok(()) } + + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + let Self { name, uid, gid, action_state: _ } = self; + + vec![ActionDescription::new( + format!("Delete user {name} with UID {uid} with group {gid}"), + vec![format!( + "The nix daemon requires system users it can act as in order to build" + )], + )] + } + } + #[tracing::instrument(skip_all, fields( user = self.name, uid = self.uid, diff --git a/src/actions/base/fetch_nix.rs b/src/actions/base/fetch_nix.rs index d6f0415..9d47465 100644 --- a/src/actions/base/fetch_nix.rs +++ b/src/actions/base/fetch_nix.rs @@ -31,19 +31,24 @@ impl FetchNix { #[async_trait::async_trait] impl Actionable for FetchNix { type Error = FetchNixError; - fn description(&self) -> Vec { + + fn describe_execute(&self) -> Vec { let Self { url, dest, action_state: _, } = &self; - vec![ActionDescription::new( - format!("Fetch Nix from `{url}`"), - vec![format!( - "Unpack it to `{}` (moved later)", - dest.display() - )], - )] + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Fetch Nix from `{url}`"), + vec![format!( + "Unpack it to `{}` (moved later)", + dest.display() + )], + )] + } } #[tracing::instrument(skip_all, fields( @@ -85,6 +90,14 @@ impl Actionable for FetchNix { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![/* Deliberately empty -- this is a noop */] + } + } + #[tracing::instrument(skip_all, fields( url = %self.url, dest = %self.dest.display(), @@ -114,8 +127,9 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum FetchNixError { - #[error(transparent)] + #[error("Joining spawned async task")] Join( + #[source] #[from] #[serde(serialize_with = "crate::serialize_error_to_display")] JoinError, diff --git a/src/actions/base/move_unpacked_nix.rs b/src/actions/base/move_unpacked_nix.rs index 7e9a0fe..07fb5a4 100644 --- a/src/actions/base/move_unpacked_nix.rs +++ b/src/actions/base/move_unpacked_nix.rs @@ -26,18 +26,19 @@ impl MoveUnpackedNix { #[async_trait::async_trait] impl Actionable for MoveUnpackedNix { type Error = MoveUnpackedNixError; - fn description(&self) -> Vec { - let Self { - src, - action_state: _, - } = &self; - vec![ActionDescription::new( - format!("Move the downloaded Nix into `/nix`"), - vec![format!( - "Nix is being downloaded to `{}` and should be in `nix`", - src.display(), - )], - )] + + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Move the downloaded Nix into `/nix`"), + vec![format!( + "Nix is being downloaded to `{}` and should be in `nix`", + self.src.display(), + )], + )] + } } #[tracing::instrument(skip_all, fields( @@ -76,6 +77,15 @@ impl Actionable for MoveUnpackedNix { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![/* Deliberately empty -- this is a noop */] + } + } + + #[tracing::instrument(skip_all, fields( src = %self.src.display(), dest = DEST, diff --git a/src/actions/base/setup_default_profile.rs b/src/actions/base/setup_default_profile.rs index 038f92c..7558165 100644 --- a/src/actions/base/setup_default_profile.rs +++ b/src/actions/base/setup_default_profile.rs @@ -28,11 +28,19 @@ impl SetupDefaultProfile { #[async_trait::async_trait] impl Actionable for SetupDefaultProfile { type Error = SetupDefaultProfileError; - fn description(&self) -> Vec { - vec![ActionDescription::new( - "Setup the default Nix profile".to_string(), - vec!["TODO".to_string()], - )] + + + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + "Setup the default Nix profile".to_string(), + vec![ + "TODO".to_string() + ] + )] + } } #[tracing::instrument(skip_all, fields( @@ -131,6 +139,19 @@ impl Actionable for SetupDefaultProfile { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + "Unset the default Nix profile".to_string(), + vec![ + "TODO".to_string() + ] + )] + } + } + #[tracing::instrument(skip_all, fields( channels = %self.channels.join(","), ))] diff --git a/src/actions/base/start_systemd_unit.rs b/src/actions/base/start_systemd_unit.rs index cbb027f..43294e1 100644 --- a/src/actions/base/start_systemd_unit.rs +++ b/src/actions/base/start_systemd_unit.rs @@ -24,24 +24,17 @@ impl StartSystemdUnit { #[async_trait::async_trait] impl Actionable for StartSystemdUnit { type Error = StartSystemdUnitError; - fn description(&self) -> Vec { - match self.action_state { - ActionState::Uncompleted => vec![ - ActionDescription::new( - "Start the systemd Nix service and socket".to_string(), - vec![ - "The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() - ] - ), - ], - ActionState::Completed => vec![ - ActionDescription::new( - "Stop the systemd Nix service and socket".to_string(), - vec![ - "The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() - ] - ), - ], + + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + "Start the systemd Nix service and socket".to_string(), + vec![ + "The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() + ] + )] } } @@ -71,6 +64,19 @@ impl Actionable for StartSystemdUnit { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + "Stop the systemd Nix service and socket".to_string(), + vec![ + "The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() + ] + )] + } + } + #[tracing::instrument(skip_all, fields( unit = %self.unit, ))] diff --git a/src/actions/meta/configure_nix.rs b/src/actions/meta/configure_nix.rs index d4305ab..5f14837 100644 --- a/src/actions/meta/configure_nix.rs +++ b/src/actions/meta/configure_nix.rs @@ -66,7 +66,7 @@ impl ConfigureNix { #[async_trait::async_trait] impl Actionable for ConfigureNix { type Error = ConfigureNixError; - fn description(&self) -> Vec { + fn describe_execute(&self) -> Vec { let Self { setup_default_profile, configure_nix_daemon_service, @@ -76,15 +76,18 @@ impl Actionable for ConfigureNix { action_state: _, } = &self; - let mut buf = setup_default_profile.description(); - buf.append(&mut configure_nix_daemon_service.description()); - buf.append(&mut place_nix_configuration.description()); - buf.append(&mut place_channel_configuration.description()); - if let Some(configure_shell_profile) = configure_shell_profile { - buf.append(&mut configure_shell_profile.description()); + if self.action_state == ActionState::Completed { + vec![] + } else { + let mut buf = setup_default_profile.describe_execute(); + buf.append(&mut configure_nix_daemon_service.describe_execute()); + buf.append(&mut place_nix_configuration.describe_execute()); + buf.append(&mut place_channel_configuration.describe_execute()); + if let Some(configure_shell_profile) = configure_shell_profile { + buf.append(&mut configure_shell_profile.describe_execute()); + } + buf } - - buf } #[tracing::instrument(skip_all)] @@ -159,6 +162,33 @@ impl Actionable for ConfigureNix { Ok(()) } + fn describe_revert(&self) -> Vec { + let Self { + setup_default_profile, + configure_nix_daemon_service, + place_nix_configuration, + place_channel_configuration, + configure_shell_profile, + action_state: _, + } = &self; + + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + let mut buf = Vec::default(); + if let Some(configure_shell_profile) = configure_shell_profile { + buf.append(&mut configure_shell_profile.describe_revert()); + } + buf.append(&mut place_channel_configuration.describe_revert()); + buf.append(&mut place_nix_configuration.describe_revert()); + buf.append(&mut configure_nix_daemon_service.describe_revert()); + buf.append(&mut setup_default_profile.describe_revert()); + + buf + } + } + + #[tracing::instrument(skip_all)] async fn revert(&mut self) -> Result<(), Self::Error> { let Self { @@ -197,14 +227,14 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum ConfigureNixError { - #[error(transparent)] - SetupDefaultProfile(#[from] SetupDefaultProfileError), - #[error(transparent)] - PlaceNixConfiguration(#[from] PlaceNixConfigurationError), - #[error(transparent)] - PlaceChannelConfiguration(#[from] PlaceChannelConfigurationError), - #[error(transparent)] - ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError), - #[error(transparent)] - ConfigureShellProfile(#[from] ConfigureShellProfileError), + #[error("Setting up default profile")] + SetupDefaultProfile(#[source] #[from] SetupDefaultProfileError), + #[error("Placing Nix configuration")] + PlaceNixConfiguration(#[source] #[from] PlaceNixConfigurationError), + #[error("Placing channel configuration")] + PlaceChannelConfiguration(#[source] #[from] PlaceChannelConfigurationError), + #[error("Configuring Nix daemon")] + ConfigureNixDaemonService(#[source] #[from] ConfigureNixDaemonServiceError), + #[error("Configuring shell profile")] + ConfigureShellProfile(#[source] #[from] ConfigureShellProfileError), } diff --git a/src/actions/meta/configure_shell_profile.rs b/src/actions/meta/configure_shell_profile.rs index 340b2fd..cd3fee7 100644 --- a/src/actions/meta/configure_shell_profile.rs +++ b/src/actions/meta/configure_shell_profile.rs @@ -57,11 +57,16 @@ impl ConfigureShellProfile { #[async_trait::async_trait] impl Actionable for ConfigureShellProfile { type Error = ConfigureShellProfileError; - fn description(&self) -> Vec { - vec![ActionDescription::new( - "Configure the shell profiles".to_string(), - vec!["Update shell profiles to import Nix".to_string()], - )] + + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + "Configure the shell profiles".to_string(), + vec!["Update shell profiles to import Nix".to_string()], + )] + } } #[tracing::instrument(skip_all)] @@ -112,6 +117,18 @@ impl Actionable for ConfigureShellProfile { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + "Unconfigure the shell profiles".to_string(), + vec!["Update shell profiles to no longer import Nix".to_string()], + )] + } + } + + #[tracing::instrument(skip_all)] async fn revert(&mut self) -> Result<(), Self::Error> { let Self { @@ -169,12 +186,17 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum ConfigureShellProfileError { - #[error(transparent)] - CreateOrAppendFile(#[from] CreateOrAppendFileError), + #[error("Creating or appending to file")] + CreateOrAppendFile( + #[from] + #[source] + CreateOrAppendFileError + ), #[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::>().join(" & "))] MultipleCreateOrAppendFile(Vec), - #[error(transparent)] + #[error("Joining spawned async task")] Join( + #[source] #[from] #[serde(serialize_with = "crate::serialize_error_to_display")] JoinError, diff --git a/src/actions/meta/create_nix_tree.rs b/src/actions/meta/create_nix_tree.rs index 0c3919c..01ebf5d 100644 --- a/src/actions/meta/create_nix_tree.rs +++ b/src/actions/meta/create_nix_tree.rs @@ -47,23 +47,28 @@ impl CreateNixTree { #[async_trait::async_trait] impl Actionable for CreateNixTree { type Error = CreateNixTreeError; - fn description(&self) -> Vec { - vec![ActionDescription::new( - format!("Create a directory tree in `/nix`"), - vec![ - format!( - "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" - ), - format!( - "Creates: {}", - PATHS - .iter() - .map(|v| format!("`{v}`")) - .collect::>() - .join(", ") - ), - ], - )] + + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Create a directory tree in `/nix`"), + vec![ + format!( + "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" + ), + format!( + "Creates: {}", + PATHS + .iter() + .map(|v| format!("`{v}`")) + .collect::>() + .join(", ") + ), + ], + )] + } } #[tracing::instrument(skip_all)] @@ -88,6 +93,30 @@ impl Actionable for CreateNixTree { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + format!("Remove the directory tree in `/nix`"), + vec![ + format!( + "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" + ), + format!( + "Removes: {}", + PATHS + .iter() + .rev() + .map(|v| format!("`{v}`")) + .collect::>() + .join(", ") + ), + ], + )] + } + } + #[tracing::instrument(skip_all)] async fn revert(&mut self) -> Result<(), Self::Error> { let Self { @@ -119,6 +148,6 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum CreateNixTreeError { - #[error(transparent)] - CreateDirectory(#[from] CreateDirectoryError), + #[error("Creating directory")] + CreateDirectory(#[source] #[from] CreateDirectoryError), } diff --git a/src/actions/meta/create_users_and_group.rs b/src/actions/meta/create_users_and_group.rs index 5498081..5aa7be9 100644 --- a/src/actions/meta/create_users_and_group.rs +++ b/src/actions/meta/create_users_and_group.rs @@ -52,7 +52,8 @@ impl CreateUsersAndGroup { #[async_trait::async_trait] impl Actionable for CreateUsersAndGroup { type Error = CreateUsersAndGroupError; - fn description(&self) -> Vec { + + fn describe_execute(&self) -> Vec { let Self { daemon_user_count, nix_build_group_name, @@ -63,17 +64,20 @@ impl Actionable for CreateUsersAndGroup { create_users: _, action_state: _, } = &self; - - vec![ - ActionDescription::new( - format!("Create build users and group"), - vec![ - format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"), - format!("Create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"), - format!("Create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"), - ], - ) - ] + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ + ActionDescription::new( + format!("Create build users and group"), + vec![ + format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"), + format!("Create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"), + format!("Create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"), + ], + ) + ] + } } #[tracing::instrument(skip_all, fields( @@ -138,6 +142,32 @@ impl Actionable for CreateUsersAndGroup { Ok(()) } + fn describe_revert(&self) -> Vec { + let Self { + daemon_user_count, + nix_build_group_name, + nix_build_group_id, + nix_build_user_prefix, + nix_build_user_id_base, + create_group: _, + create_users: _, + action_state: _, + } = &self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ + ActionDescription::new( + format!("Remove build users and group"), + vec![ + format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"), + format!("Create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"), + format!("Create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"), + ], + ) + ] + } + } #[tracing::instrument(skip_all, fields( daemon_user_count = self.daemon_user_count, @@ -208,14 +238,15 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum CreateUsersAndGroupError { - #[error(transparent)] - CreateUser(#[from] CreateUserError), + #[error("Creating user")] + CreateUser(#[source] #[from] CreateUserError), #[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::>().join(" & "))] CreateUsers(Vec), - #[error(transparent)] - CreateGroup(#[from] CreateGroupError), - #[error(transparent)] + #[error("Creating group")] + CreateGroup(#[source] #[from] CreateGroupError), + #[error("Joining spawned async task")] Join( + #[source] #[from] #[serde(serialize_with = "crate::serialize_error_to_display")] JoinError, diff --git a/src/actions/meta/place_channel_configuration.rs b/src/actions/meta/place_channel_configuration.rs index aa1d5ee..33144da 100644 --- a/src/actions/meta/place_channel_configuration.rs +++ b/src/actions/meta/place_channel_configuration.rs @@ -45,16 +45,21 @@ impl PlaceChannelConfiguration { #[async_trait::async_trait] impl Actionable for PlaceChannelConfiguration { type Error = PlaceChannelConfigurationError; - fn description(&self) -> Vec { + + fn describe_execute(&self) -> Vec { let Self { channels: _, create_file: _, action_state: _, } = self; - vec![ActionDescription::new( - "Place a channel configuration".to_string(), - vec!["Place a configuration at `{NIX_CHANNELS_PATH}` setting the channels".to_string()], - )] + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + "Place channel configuration at `{NIX_CHANNELS_PATH}`".to_string(), + vec!["Place channel configuration at `{NIX_CHANNELS_PATH}`".to_string()], + )] + } } #[tracing::instrument(skip_all, fields( @@ -79,6 +84,22 @@ impl Actionable for PlaceChannelConfiguration { Ok(()) } + fn describe_revert(&self) -> Vec { + let Self { + channels: _, + create_file: _, + action_state: _, + } = self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + "Remove channel configuration at `{NIX_CHANNELS_PATH}`".to_string(), + vec!["Remove channel configuration at `{NIX_CHANNELS_PATH}`".to_string()], + )] + } + } + #[tracing::instrument(skip_all, fields( channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::>().join(", "), ))] @@ -110,6 +131,6 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum PlaceChannelConfigurationError { - #[error(transparent)] - CreateFile(#[from] CreateFileError), + #[error("Creating file")] + CreateFile(#[source] #[from] CreateFileError), } diff --git a/src/actions/meta/place_nix_configuration.rs b/src/actions/meta/place_nix_configuration.rs index 6550cd2..ca4934a 100644 --- a/src/actions/meta/place_nix_configuration.rs +++ b/src/actions/meta/place_nix_configuration.rs @@ -49,14 +49,18 @@ impl PlaceNixConfiguration { impl Actionable for PlaceNixConfiguration { type Error = PlaceNixConfigurationError; - fn description(&self) -> Vec { - vec![ActionDescription::new( - format!("Place the nix configuration in `{NIX_CONF}`"), - vec![ - "This file is read by the Nix daemon to set its configuration options at runtime." - .to_string(), - ], - )] + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Place the nix configuration in `{NIX_CONF}`"), + vec![ + "This file is read by the Nix daemon to set its configuration options at runtime." + .to_string(), + ], + )] + } } #[tracing::instrument(skip_all)] @@ -80,6 +84,20 @@ impl Actionable for PlaceNixConfiguration { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + format!("Remove the nix configuration in `{NIX_CONF}`"), + vec![ + "This file is read by the Nix daemon to set its configuration options at runtime." + .to_string(), + ], + )] + } + } + #[tracing::instrument(skip_all)] async fn revert(&mut self) -> Result<(), Self::Error> { let Self { @@ -110,8 +128,8 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum PlaceNixConfigurationError { - #[error(transparent)] - CreateFile(#[from] CreateFileError), - #[error(transparent)] - CreateDirectory(#[from] CreateDirectoryError), + #[error("Creating file")] + CreateFile(#[source] #[from] CreateFileError), + #[error("Creating directory")] + CreateDirectory(#[source] #[from] CreateDirectoryError), } diff --git a/src/actions/meta/provision_nix.rs b/src/actions/meta/provision_nix.rs index 8cf9cb1..298c6b4 100644 --- a/src/actions/meta/provision_nix.rs +++ b/src/actions/meta/provision_nix.rs @@ -44,7 +44,7 @@ impl ProvisionNix { #[async_trait::async_trait] impl Actionable for ProvisionNix { type Error = ProvisionNixError; - fn description(&self) -> Vec { + fn describe_execute(&self) -> Vec { let Self { fetch_nix, create_users_and_group, @@ -52,13 +52,16 @@ impl Actionable for ProvisionNix { move_unpacked_nix, action_state: _, } = &self; - - let mut buf = fetch_nix.description(); - buf.append(&mut create_users_and_group.description()); - buf.append(&mut create_nix_tree.description()); - buf.append(&mut move_unpacked_nix.description()); - - buf + if self.action_state == ActionState::Completed { + vec![] + } else { + let mut buf = fetch_nix.describe_execute(); + buf.append(&mut create_users_and_group.describe_execute()); + buf.append(&mut create_nix_tree.describe_execute()); + buf.append(&mut move_unpacked_nix.describe_execute()); + + buf + } } #[tracing::instrument(skip_all)] @@ -97,6 +100,27 @@ impl Actionable for ProvisionNix { Ok(()) } + fn describe_revert(&self) -> Vec { + let Self { + fetch_nix, + create_users_and_group, + create_nix_tree, + move_unpacked_nix, + action_state: _, + } = &self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + let mut buf = Vec::default(); + buf.append(&mut move_unpacked_nix.describe_revert()); + buf.append(&mut create_nix_tree.describe_revert()); + buf.append(&mut create_users_and_group.describe_revert()); + buf.append(&mut fetch_nix.describe_revert()); + buf + } + } + + #[tracing::instrument(skip_all)] async fn revert(&mut self) -> Result<(), Self::Error> { let Self { @@ -148,18 +172,19 @@ pub enum ProvisionNixError { #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error, ), - #[error(transparent)] - FetchNix(#[from] FetchNixError), - #[error(transparent)] + #[error("Fetching Nix")] + FetchNix(#[source] #[from] FetchNixError), + #[error("Joining spawned async task")] Join( + #[source] #[from] #[serde(serialize_with = "crate::serialize_error_to_display")] JoinError, ), - #[error(transparent)] - CreateUsersAndGroup(#[from] CreateUsersAndGroupError), - #[error(transparent)] - CreateNixTree(#[from] CreateNixTreeError), - #[error(transparent)] - MoveUnpackedNix(#[from] MoveUnpackedNixError), + #[error("Creating users and group")] + CreateUsersAndGroup(#[source] #[from] CreateUsersAndGroupError), + #[error("Creating nix tree")] + CreateNixTree(#[source] #[from] CreateNixTreeError), + #[error("Moving unpacked nix")] + MoveUnpackedNix(#[source] #[from] MoveUnpackedNixError), } diff --git a/src/actions/meta/start_nix_daemon.rs b/src/actions/meta/start_nix_daemon.rs index 0f7886c..e43d70d 100644 --- a/src/actions/meta/start_nix_daemon.rs +++ b/src/actions/meta/start_nix_daemon.rs @@ -26,8 +26,12 @@ impl StartNixDaemon { impl Actionable for StartNixDaemon { type Error = StartNixDaemonError; - fn description(&self) -> Vec { - self.start_systemd_socket.description() + fn describe_execute(&self) -> Vec { + if self.action_state == ActionState::Completed { + vec![] + } else { + self.start_systemd_socket.describe_execute() + } } #[tracing::instrument(skip_all)] @@ -49,6 +53,14 @@ impl Actionable for StartNixDaemon { Ok(()) } + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + self.start_systemd_socket.describe_revert() + } + } + #[tracing::instrument(skip_all)] async fn revert(&mut self) -> Result<(), Self::Error> { let Self { @@ -78,6 +90,6 @@ impl From for Action { #[derive(Debug, thiserror::Error, Serialize)] pub enum StartNixDaemonError { - #[error(transparent)] - StartSystemdUnit(#[from] StartSystemdUnitError), + #[error("Starting systemd unit")] + StartSystemdUnit(#[source] #[from] StartSystemdUnitError), } diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 00bb2b0..f87cae3 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -23,7 +23,8 @@ use self::base::{StartSystemdUnit, StartSystemdUnitError}; pub trait Actionable: DeserializeOwned + Serialize + Into { type Error: std::error::Error + std::fmt::Debug + Serialize + Into; - fn description(&self) -> Vec; + fn describe_execute(&self) -> Vec; + fn describe_revert(&self) -> Vec; // They should also have an `async fn plan(args...) -> Result, Self::Error>;` async fn execute(&mut self) -> Result<(), Self::Error>; @@ -123,26 +124,26 @@ pub enum ActionError { #[async_trait::async_trait] impl Actionable for Action { type Error = ActionError; - fn description(&self) -> Vec { + fn describe_execute(&self) -> Vec { match self { - Action::ConfigureNixDaemonService(i) => i.description(), - Action::ConfigureNix(i) => i.description(), - Action::ConfigureShellProfile(i) => i.description(), - Action::CreateDirectory(i) => i.description(), - Action::CreateFile(i) => i.description(), - Action::CreateGroup(i) => i.description(), - Action::CreateOrAppendFile(i) => i.description(), - Action::CreateNixTree(i) => i.description(), - Action::CreateUser(i) => i.description(), - Action::CreateUsersAndGroup(i) => i.description(), - Action::FetchNix(i) => i.description(), - Action::MoveUnpackedNix(i) => i.description(), - Action::PlaceChannelConfiguration(i) => i.description(), - Action::PlaceNixConfiguration(i) => i.description(), - Action::SetupDefaultProfile(i) => i.description(), - Action::StartNixDaemon(i) => i.description(), - Action::StartSystemdUnit(i) => i.description(), - Action::ProvisionNix(i) => i.description(), + Action::ConfigureNixDaemonService(i) => i.describe_execute(), + Action::ConfigureNix(i) => i.describe_execute(), + Action::ConfigureShellProfile(i) => i.describe_execute(), + Action::CreateDirectory(i) => i.describe_execute(), + Action::CreateFile(i) => i.describe_execute(), + Action::CreateGroup(i) => i.describe_execute(), + Action::CreateOrAppendFile(i) => i.describe_execute(), + Action::CreateNixTree(i) => i.describe_execute(), + Action::CreateUser(i) => i.describe_execute(), + Action::CreateUsersAndGroup(i) => i.describe_execute(), + Action::FetchNix(i) => i.describe_execute(), + Action::MoveUnpackedNix(i) => i.describe_execute(), + Action::PlaceChannelConfiguration(i) => i.describe_execute(), + Action::PlaceNixConfiguration(i) => i.describe_execute(), + Action::SetupDefaultProfile(i) => i.describe_execute(), + Action::StartNixDaemon(i) => i.describe_execute(), + Action::StartSystemdUnit(i) => i.describe_execute(), + Action::ProvisionNix(i) => i.describe_execute(), } } @@ -170,6 +171,29 @@ impl Actionable for Action { Ok(()) } + fn describe_revert(&self) -> Vec { + match self { + Action::ConfigureNixDaemonService(i) => i.describe_revert(), + Action::ConfigureNix(i) => i.describe_revert(), + Action::ConfigureShellProfile(i) => i.describe_revert(), + Action::CreateDirectory(i) => i.describe_revert(), + Action::CreateFile(i) => i.describe_revert(), + Action::CreateGroup(i) => i.describe_revert(), + Action::CreateOrAppendFile(i) => i.describe_revert(), + Action::CreateNixTree(i) => i.describe_revert(), + Action::CreateUser(i) => i.describe_revert(), + Action::CreateUsersAndGroup(i) => i.describe_revert(), + Action::FetchNix(i) => i.describe_revert(), + Action::MoveUnpackedNix(i) => i.describe_revert(), + Action::PlaceChannelConfiguration(i) => i.describe_revert(), + Action::PlaceNixConfiguration(i) => i.describe_revert(), + Action::SetupDefaultProfile(i) => i.describe_revert(), + Action::StartNixDaemon(i) => i.describe_revert(), + Action::StartSystemdUnit(i) => i.describe_revert(), + Action::ProvisionNix(i) => i.describe_revert(), + } + } + async fn revert(&mut self) -> Result<(), Self::Error> { match self { Action::ConfigureNixDaemonService(i) => i.revert().await?, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ef9dd8f..3c3960b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -96,11 +96,17 @@ impl CommandExecute for HarmonicCli { let mut plan = InstallPlan::new(settings).await?; // TODO(@Hoverbear): Make this smarter - if !interaction::confirm(plan.description()).await? { + if !interaction::confirm(plan.describe_execute()).await? { interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; } - - let _receipt = plan.install().await?; + + if let Err(err) = plan.install().await { + tracing::error!("{err:#?}"); + if !interaction::confirm(plan.describe_revert()).await? { + interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; + } + plan.revert().await? + } Ok(ExitCode::SUCCESS) }, diff --git a/src/cli/subcommand/execute.rs b/src/cli/subcommand/execute.rs index 30d2b50..249cc0d 100644 --- a/src/cli/subcommand/execute.rs +++ b/src/cli/subcommand/execute.rs @@ -32,12 +32,18 @@ impl CommandExecute for Execute { let mut plan: InstallPlan = serde_json::from_str(&install_plan_string)?; if !no_confirm { - if !interaction::confirm(plan.description()).await? { + if !interaction::confirm(plan.describe_execute()).await? { interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; } } - plan.install().await?; + if let Err(err) = plan.install().await { + tracing::error!("{err:#?}"); + if !interaction::confirm(plan.describe_revert()).await? { + interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; + } + plan.revert().await? + } Ok(ExitCode::SUCCESS) } diff --git a/src/cli/subcommand/revert.rs b/src/cli/subcommand/revert.rs index 7846b23..c7bd08b 100644 --- a/src/cli/subcommand/revert.rs +++ b/src/cli/subcommand/revert.rs @@ -35,7 +35,7 @@ impl CommandExecute for Revert { let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?; if !no_confirm { - if !interaction::confirm(plan.description()).await? { + if !interaction::confirm(plan.describe_execute()).await? { interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; } } diff --git a/src/plan.rs b/src/plan.rs index e90d9c3..84eafc9 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -36,8 +36,24 @@ pub struct InstallPlan { } impl InstallPlan { + pub async fn new(settings: InstallSettings) -> Result { + Ok(Self { + settings: settings.clone(), + provision_nix: ProvisionNix::plan(settings.clone()) + .await + .map_err(|e| ActionError::from(e))?, + configure_nix: ConfigureNix::plan(settings) + .await + .map_err(|e| ActionError::from(e))?, + start_nix_daemon: StartNixDaemon::plan() + .await + .map_err(|e| ActionError::from(e))?, + }) + } + #[tracing::instrument(skip_all)] - pub fn description(&self) -> String { + pub fn describe_execute(&self) -> String { + let Self { settings, provision_nix, configure_nix, start_nix_daemon } = self; format!( "\ This Nix install is for:\n\ @@ -50,17 +66,16 @@ impl InstallPlan { ", os_type = "Linux", init_type = "systemd", - nix_channels = self - .settings + nix_channels = settings .channels .iter() .map(|(name, url)| format!("{name}={url}")) .collect::>() .join(","), actions = { - let mut buf = self.provision_nix.description(); - buf.append(&mut self.configure_nix.description()); - buf.append(&mut self.start_nix_daemon.description()); + let mut buf = provision_nix.describe_execute(); + buf.append(&mut configure_nix.describe_execute()); + buf.append(&mut start_nix_daemon.describe_execute()); buf.iter() .map(|desc| { let ActionDescription { @@ -82,20 +97,7 @@ impl InstallPlan { }, ) } - pub async fn new(settings: InstallSettings) -> Result { - Ok(Self { - settings: settings.clone(), - provision_nix: ProvisionNix::plan(settings.clone()) - .await - .map_err(|e| ActionError::from(e))?, - configure_nix: ConfigureNix::plan(settings) - .await - .map_err(|e| ActionError::from(e))?, - start_nix_daemon: StartNixDaemon::plan() - .await - .map_err(|e| ActionError::from(e))?, - }) - } + #[tracing::instrument(skip_all)] pub async fn install(&mut self) -> Result<(), HarmonicError> { @@ -125,6 +127,53 @@ impl InstallPlan { Ok(()) } + #[tracing::instrument(skip_all)] + pub fn describe_revert(&self) -> String { + let Self { settings, provision_nix, configure_nix, start_nix_daemon } = self; + format!( + "\ + This Nix uninstall is for:\n\ + Operating System: {os_type}\n\ + Init system: {init_type}\n\ + Nix channels: {nix_channels}\n\ + \n\ + The following actions will be taken:\n\ + {actions} + ", + os_type = "Linux", + init_type = "systemd", + nix_channels = settings + .channels + .iter() + .map(|(name, url)| format!("{name}={url}")) + .collect::>() + .join(","), + actions = { + let mut buf = provision_nix.describe_revert(); + buf.append(&mut configure_nix.describe_revert()); + buf.append(&mut start_nix_daemon.describe_revert()); + buf.iter() + .map(|desc| { + let ActionDescription { + description, + explanation, + } = desc; + + let mut buf = String::default(); + buf.push_str(&format!("* {description}\n")); + if self.settings.explain { + for line in explanation { + buf.push_str(&format!(" {line}\n")); + } + } + buf + }) + .collect::>() + .join("\n") + }, + ) + } + #[tracing::instrument(skip_all)] pub async fn revert(&mut self) -> Result<(), HarmonicError> { // This is **deliberately sequential**.