Logging and error tidying

This commit is contained in:
Ana Hobden 2022-09-27 12:05:24 -07:00
parent 47018ad00c
commit b34a352753
23 changed files with 709 additions and 241 deletions

View file

@ -33,16 +33,21 @@ impl ConfigureNixDaemonService {
#[async_trait::async_trait]
impl Actionable for ConfigureNixDaemonService {
type Error = ConfigureNixDaemonServiceError;
fn description(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<ActionDescription> {
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;

View file

@ -50,7 +50,8 @@ impl CreateDirectory {
#[async_trait::async_trait]
impl Actionable for CreateDirectory {
type Error = CreateDirectoryError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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,

View file

@ -52,7 +52,8 @@ impl CreateFile {
#[async_trait::async_trait]
impl Actionable for CreateFile {
type Error = CreateFileError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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,

View file

@ -26,18 +26,23 @@ impl CreateGroup {
#[async_trait::async_trait]
impl Actionable for CreateGroup {
type Error = CreateGroupError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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,

View file

@ -49,7 +49,8 @@ impl CreateOrAppendFile {
#[async_trait::async_trait]
impl Actionable for CreateOrAppendFile {
type Error = CreateOrAppendFileError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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,

View file

@ -28,15 +28,20 @@ impl CreateUser {
#[async_trait::async_trait]
impl Actionable for CreateUser {
type Error = CreateUserError;
fn description(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<ActionDescription> {
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,

View file

@ -31,19 +31,24 @@ impl FetchNix {
#[async_trait::async_trait]
impl Actionable for FetchNix {
type Error = FetchNixError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<FetchNix> 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,

View file

@ -26,18 +26,19 @@ impl MoveUnpackedNix {
#[async_trait::async_trait]
impl Actionable for MoveUnpackedNix {
type Error = MoveUnpackedNixError;
fn description(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<ActionDescription> {
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,

View file

@ -28,11 +28,19 @@ impl SetupDefaultProfile {
#[async_trait::async_trait]
impl Actionable for SetupDefaultProfile {
type Error = SetupDefaultProfileError;
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Setup the default Nix profile".to_string(),
vec!["TODO".to_string()],
)]
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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(","),
))]

View file

@ -24,24 +24,17 @@ impl StartSystemdUnit {
#[async_trait::async_trait]
impl Actionable for StartSystemdUnit {
type Error = StartSystemdUnitError;
fn description(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<ActionDescription> {
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,
))]

View file

@ -66,7 +66,7 @@ impl ConfigureNix {
#[async_trait::async_trait]
impl Actionable for ConfigureNix {
type Error = ConfigureNixError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<ConfigureNix> 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),
}

View file

@ -57,11 +57,16 @@ impl ConfigureShellProfile {
#[async_trait::async_trait]
impl Actionable for ConfigureShellProfile {
type Error = ConfigureShellProfileError;
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Configure the shell profiles".to_string(),
vec!["Update shell profiles to import Nix".to_string()],
)]
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<ConfigureShellProfile> 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::<Vec<_>>().join(" & "))]
MultipleCreateOrAppendFile(Vec<CreateOrAppendFileError>),
#[error(transparent)]
#[error("Joining spawned async task")]
Join(
#[source]
#[from]
#[serde(serialize_with = "crate::serialize_error_to_display")]
JoinError,

View file

@ -47,23 +47,28 @@ impl CreateNixTree {
#[async_trait::async_trait]
impl Actionable for CreateNixTree {
type Error = CreateNixTreeError;
fn description(&self) -> Vec<ActionDescription> {
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::<Vec<_>>()
.join(", ")
),
],
)]
fn describe_execute(&self) -> Vec<ActionDescription> {
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::<Vec<_>>()
.join(", ")
),
],
)]
}
}
#[tracing::instrument(skip_all)]
@ -88,6 +93,30 @@ impl Actionable for CreateNixTree {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
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::<Vec<_>>()
.join(", ")
),
],
)]
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
@ -119,6 +148,6 @@ impl From<CreateNixTree> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateNixTreeError {
#[error(transparent)]
CreateDirectory(#[from] CreateDirectoryError),
#[error("Creating directory")]
CreateDirectory(#[source] #[from] CreateDirectoryError),
}

View file

@ -52,7 +52,8 @@ impl CreateUsersAndGroup {
#[async_trait::async_trait]
impl Actionable for CreateUsersAndGroup {
type Error = CreateUsersAndGroupError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<CreateUsersAndGroup> 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::<Vec<_>>().join(" & "))]
CreateUsers(Vec<CreateUserError>),
#[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,

View file

@ -45,16 +45,21 @@ impl PlaceChannelConfiguration {
#[async_trait::async_trait]
impl Actionable for PlaceChannelConfiguration {
type Error = PlaceChannelConfigurationError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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::<Vec<_>>().join(", "),
))]
@ -110,6 +131,6 @@ impl From<PlaceChannelConfiguration> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum PlaceChannelConfigurationError {
#[error(transparent)]
CreateFile(#[from] CreateFileError),
#[error("Creating file")]
CreateFile(#[source] #[from] CreateFileError),
}

View file

@ -49,14 +49,18 @@ impl PlaceNixConfiguration {
impl Actionable for PlaceNixConfiguration {
type Error = PlaceNixConfigurationError;
fn description(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<ActionDescription> {
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<PlaceNixConfiguration> 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),
}

View file

@ -44,7 +44,7 @@ impl ProvisionNix {
#[async_trait::async_trait]
impl Actionable for ProvisionNix {
type Error = ProvisionNixError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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),
}

View file

@ -26,8 +26,12 @@ impl StartNixDaemon {
impl Actionable for StartNixDaemon {
type Error = StartNixDaemonError;
fn description(&self) -> Vec<ActionDescription> {
self.start_systemd_socket.description()
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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<StartNixDaemon> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum StartNixDaemonError {
#[error(transparent)]
StartSystemdUnit(#[from] StartSystemdUnitError),
#[error("Starting systemd unit")]
StartSystemdUnit(#[source] #[from] StartSystemdUnitError),
}

View file

@ -23,7 +23,8 @@ use self::base::{StartSystemdUnit, StartSystemdUnitError};
pub trait Actionable: DeserializeOwned + Serialize + Into<Action> {
type Error: std::error::Error + std::fmt::Debug + Serialize + Into<ActionError>;
fn description(&self) -> Vec<ActionDescription>;
fn describe_execute(&self) -> Vec<ActionDescription>;
fn describe_revert(&self) -> Vec<ActionDescription>;
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, 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<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
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<ActionDescription> {
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?,

View file

@ -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)
},

View file

@ -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)
}

View file

@ -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;
}
}

View file

@ -36,8 +36,24 @@ pub struct InstallPlan {
}
impl InstallPlan {
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
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::<Vec<_>>()
.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<Self, HarmonicError> {
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::<Vec<_>>()
.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::<Vec<_>>()
.join("\n")
},
)
}
#[tracing::instrument(skip_all)]
pub async fn revert(&mut self) -> Result<(), HarmonicError> {
// This is **deliberately sequential**.