diff --git a/src/actions/base/configure_nix_daemon_service.rs b/src/actions/base/configure_nix_daemon_service.rs index 79e78ed..a685808 100644 --- a/src/actions/base/configure_nix_daemon_service.rs +++ b/src/actions/base/configure_nix_daemon_service.rs @@ -1,10 +1,11 @@ use std::path::{Path, PathBuf}; +use serde::Serialize; use tokio::process::Command; use crate::{execute_command, HarmonicError}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service"; const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket"; @@ -25,8 +26,8 @@ impl ConfigureNixDaemonService { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for ConfigureNixDaemonService { - type Receipt = ConfigureNixDaemonServiceReceipt; +impl Actionable for ActionState { + type Error = ConfigureNixDaemonServiceError; fn description(&self) -> Vec { vec![ActionDescription::new( "Configure Nix daemon related settings with systemd".to_string(), @@ -40,7 +41,7 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { tracing::info!("Configuring nix daemon service"); tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking"); @@ -68,30 +69,31 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService { execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?; - Ok(Self::Receipt {}) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct ConfigureNixDaemonServiceReceipt {} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for ConfigureNixDaemonServiceReceipt { - fn description(&self) -> Vec { - vec![ - ActionDescription::new( - "Stop the systemd Nix daemon".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)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + + + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::ConfigureNixDaemonService(v)), + ActionState::Planned(_) => ActionState::Planned(Action::ConfigureNixDaemonService(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureNixDaemonService(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum ConfigureNixDaemonServiceError { + +} diff --git a/src/actions/base/create_directory.rs b/src/actions/base/create_directory.rs index 85077cc..1865039 100644 --- a/src/actions/base/create_directory.rs +++ b/src/actions/base/create_directory.rs @@ -1,11 +1,12 @@ use std::path::{Path, PathBuf}; use nix::unistd::{chown, Group, User}; +use serde::Serialize; use tokio::fs::create_dir; use crate::HarmonicError; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateDirectory { @@ -46,8 +47,8 @@ impl CreateDirectory { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateDirectory { - type Receipt = CreateDirectoryReceipt; +impl Actionable for ActionState { + type Error = CreateDirectoryError; fn description(&self) -> Vec { let Self { path, @@ -65,7 +66,7 @@ impl<'a> Actionable<'a> for CreateDirectory { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { path, user, @@ -88,45 +89,31 @@ impl<'a> Actionable<'a> for CreateDirectory { .map_err(|e| HarmonicError::CreateDirectory(path.clone(), e))?; chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?; - Ok(CreateDirectoryReceipt { - path, - user, - group, - mode, - }) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateDirectoryReceipt { - path: PathBuf, - user: String, - group: String, - mode: u32, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateDirectoryReceipt { - fn description(&self) -> Vec { - vec![ActionDescription::new( - format!("Create the directory `/nix`"), - vec![format!( - "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" - )], - )] - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { - let Self { - path, - user, - group, - mode, - } = self; - + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::CreateDirectory(v)), + ActionState::Planned(_) => ActionState::Planned(Action::CreateDirectory(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::CreateDirectory(v)), + } + } +} + + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateDirectoryError { + +} diff --git a/src/actions/base/create_file.rs b/src/actions/base/create_file.rs index b979e60..e4e43bc 100644 --- a/src/actions/base/create_file.rs +++ b/src/actions/base/create_file.rs @@ -1,13 +1,14 @@ use nix::unistd::{chown, Group, User}; +use serde::Serialize; use std::path::{Path, PathBuf}; use tokio::{ fs::{create_dir_all, OpenOptions}, io::AsyncWriteExt, }; -use crate::HarmonicError; +use crate::{HarmonicError, actions::{ActionState, Action}}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateFile { @@ -53,8 +54,8 @@ impl CreateFile { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateFile { - type Receipt = CreateFileReceipt; +impl Actionable for ActionState { + type Error = CreateFileError; fn description(&self) -> Vec { let Self { path, @@ -73,7 +74,7 @@ impl<'a> Actionable<'a> for CreateFile { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { path, user, @@ -107,35 +108,29 @@ impl<'a> Actionable<'a> for CreateFile { tracing::trace!(path = %path.display(), "Chowning file"); chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?; - Ok(Self::Receipt { - path, - user, - group, - mode, - buf, - }) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateFileReceipt { - path: PathBuf, - user: String, - group: String, - mode: u32, - buf: String, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateFileReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::CreateFile(v)), + ActionState::Planned(_) => ActionState::Planned(Action::CreateFile(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::CreateFile(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateFileError { + +} diff --git a/src/actions/base/create_group.rs b/src/actions/base/create_group.rs index 39b1752..8e7417e 100644 --- a/src/actions/base/create_group.rs +++ b/src/actions/base/create_group.rs @@ -1,8 +1,9 @@ +use serde::Serialize; use tokio::process::Command; use crate::{HarmonicError, execute_command}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateGroup { @@ -18,8 +19,8 @@ impl CreateGroup { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateGroup { - type Receipt = CreateGroupReceipt; +impl Actionable for ActionState { + type Error = CreateOrAppendFileError; fn description(&self) -> Vec { let Self { name, gid } = &self; vec![ActionDescription::new( @@ -31,7 +32,7 @@ impl<'a> Actionable<'a> for CreateGroup { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { name, gid } = self; execute_command( @@ -39,26 +40,27 @@ impl<'a> Actionable<'a> for CreateGroup { false, ).await?; - Ok(CreateGroupReceipt { name, gid }) - } -} - -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateGroupReceipt { - name: String, - gid: usize, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateGroupReceipt { - fn description(&self) -> Vec { - todo!() + Ok(()) } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::CreateGroup(v)), + ActionState::Planned(_) => ActionState::Planned(Action::CreateGroup(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::CreateGroup(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateGroupError { +} \ No newline at end of file diff --git a/src/actions/base/create_or_append_file.rs b/src/actions/base/create_or_append_file.rs index 6b34977..a9fe38f 100644 --- a/src/actions/base/create_or_append_file.rs +++ b/src/actions/base/create_or_append_file.rs @@ -1,4 +1,5 @@ use nix::unistd::{chown, Group, User}; +use serde::Serialize; use std::{ io::SeekFrom, path::{Path, PathBuf}, @@ -8,9 +9,9 @@ use tokio::{ io::{AsyncSeekExt, AsyncWriteExt}, }; -use crate::HarmonicError; +use crate::{HarmonicError, actions::{ActionState, Action}}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateOrAppendFile { @@ -43,8 +44,8 @@ impl CreateOrAppendFile { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateOrAppendFile { - type Receipt = CreateOrAppendFileReceipt; +impl Actionable for ActionState { + type Error = CreateOrAppendFileError; fn description(&self) -> Vec { let Self { path, @@ -62,7 +63,7 @@ impl<'a> Actionable<'a> for CreateOrAppendFile { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { path, user, @@ -99,40 +100,30 @@ impl<'a> Actionable<'a> for CreateOrAppendFile { tracing::trace!(path = %path.display(), "Chowning"); chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?; - Ok(Self::Receipt { - path, - user, - group, - mode, - buf, - }) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateOrAppendFileReceipt { - path: PathBuf, - user: String, - group: String, - mode: u32, - buf: String, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateOrAppendFileReceipt { - fn description(&self) -> Vec { - vec![ActionDescription::new( - format!("Create the directory `/nix`"), - vec![format!( - "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" - )], - )] - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::CreateOrAppendFile(v)), + ActionState::Planned(_) => ActionState::Planned(Action::CreateOrAppendFile(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::CreateOrAppendFile(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateOrAppendFileError { + +} diff --git a/src/actions/base/create_user.rs b/src/actions/base/create_user.rs index 799af0f..5f9fa63 100644 --- a/src/actions/base/create_user.rs +++ b/src/actions/base/create_user.rs @@ -1,8 +1,9 @@ +use serde::Serialize; use tokio::process::Command; use crate::{HarmonicError, execute_command}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateUser { @@ -19,8 +20,8 @@ impl CreateUser { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateUser { - type Receipt = CreateUserReceipt; +impl Actionable for ActionState { + type Error = CreateUserError; fn description(&self) -> Vec { let name = &self.name; let uid = &self.uid; @@ -33,7 +34,7 @@ impl<'a> Actionable<'a> for CreateUser { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { name, uid, gid } = self; execute_command(Command::new("useradd").args([ @@ -56,27 +57,29 @@ impl<'a> Actionable<'a> for CreateUser { &name.to_string(), ]), false).await?; - Ok(CreateUserReceipt { name, uid, gid }) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateUserReceipt { - name: String, - uid: usize, - gid: usize, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateUserReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::CreateUser(v)), + ActionState::Planned(_) => ActionState::Planned(Action::CreateUser(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::CreateUser(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateUserError { + +} diff --git a/src/actions/base/fetch_nix.rs b/src/actions/base/fetch_nix.rs index df1b9f5..2c2b1c0 100644 --- a/src/actions/base/fetch_nix.rs +++ b/src/actions/base/fetch_nix.rs @@ -2,11 +2,12 @@ use std::path::{PathBuf}; use bytes::Buf; use reqwest::Url; +use serde::Serialize; use tokio::task::spawn_blocking; use crate::HarmonicError; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct FetchNix { @@ -16,7 +17,7 @@ pub struct FetchNix { impl FetchNix { #[tracing::instrument(skip_all)] - pub async fn plan(url: Url, destination: PathBuf) -> Result { + pub async fn plan(url: Url, destination: PathBuf) -> Result { // TODO(@hoverbear): Check URL exists? // TODO(@hoverbear): Check tempdir exists @@ -25,8 +26,8 @@ impl FetchNix { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for FetchNix { - type Receipt = FetchNixReceipt; +impl Actionable for ActionState { + type Error = FetchNixError; fn description(&self) -> Vec { let Self { url, destination } = &self; vec![ActionDescription::new( @@ -39,7 +40,7 @@ impl<'a> Actionable<'a> for FetchNix { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { url, destination } = self; tracing::trace!(%url, "Fetching url"); @@ -61,26 +62,29 @@ impl<'a> Actionable<'a> for FetchNix { handle?; - Ok(FetchNixReceipt { url, destination }) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct FetchNixReceipt { - url: Url, - destination: PathBuf, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for FetchNixReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::FetchNix(v)), + ActionState::Planned(_) => ActionState::Planned(Action::FetchNix(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::FetchNix(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum FetchNixError { + +} diff --git a/src/actions/base/mod.rs b/src/actions/base/mod.rs index 5f8a08e..dcfd0bc 100644 --- a/src/actions/base/mod.rs +++ b/src/actions/base/mod.rs @@ -14,18 +14,18 @@ mod setup_default_profile; mod start_systemd_unit; pub use configure_nix_daemon_service::{ - ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, + ConfigureNixDaemonService, ConfigureNixDaemonServiceError, }; -pub use create_directory::{CreateDirectory, CreateDirectoryReceipt}; -pub use create_file::{CreateFile, CreateFileReceipt}; -pub use create_group::{CreateGroup, CreateGroupReceipt}; -pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileReceipt}; -pub use create_user::{CreateUser, CreateUserReceipt}; -pub use fetch_nix::{FetchNix, FetchNixReceipt}; -pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixReceipt}; +pub use create_directory::{CreateDirectory, CreateDirectoryError}; +pub use create_file::{CreateFile, CreateFileError}; +pub use create_group::{CreateGroup, CreateGroupError}; +pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError}; +pub use create_user::{CreateUser, CreateUserError}; +pub use fetch_nix::{FetchNix, FetchNixError}; +pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError}; pub use place_channel_configuration::{ - PlaceChannelConfiguration, PlaceChannelConfigurationReceipt, + PlaceChannelConfiguration, PlaceChannelConfigurationError, }; -pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt}; -pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt}; -pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitReceipt}; +pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError}; +pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError}; +pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError}; diff --git a/src/actions/base/move_unpacked_nix.rs b/src/actions/base/move_unpacked_nix.rs index ee40054..06438a4 100644 --- a/src/actions/base/move_unpacked_nix.rs +++ b/src/actions/base/move_unpacked_nix.rs @@ -1,8 +1,10 @@ use std::path::{Path, PathBuf}; +use serde::Serialize; + use crate::HarmonicError; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct MoveUnpackedNix { @@ -18,8 +20,8 @@ impl MoveUnpackedNix { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for MoveUnpackedNix { - type Receipt = MoveUnpackedNixReceipt; +impl Actionable for ActionState { + type Error = MoveUnpackedNixError; fn description(&self) -> Vec { let Self { source } = &self; vec![ActionDescription::new( @@ -32,7 +34,7 @@ impl<'a> Actionable<'a> for MoveUnpackedNix { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { source } = self; // TODO(@Hoverbear): I would like to make this less awful @@ -51,23 +53,29 @@ impl<'a> Actionable<'a> for MoveUnpackedNix { .await .map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?; - Ok(MoveUnpackedNixReceipt {}) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct MoveUnpackedNixReceipt {} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for MoveUnpackedNixReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::MoveUnpackedNix(v)), + ActionState::Planned(_) => ActionState::Planned(Action::MoveUnpackedNix(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::MoveUnpackedNix(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum MoveUnpackedNixError { + +} diff --git a/src/actions/base/place_channel_configuration.rs b/src/actions/base/place_channel_configuration.rs index 3c72861..f2dcc1b 100644 --- a/src/actions/base/place_channel_configuration.rs +++ b/src/actions/base/place_channel_configuration.rs @@ -1,10 +1,11 @@ use reqwest::Url; +use serde::Serialize; use crate::HarmonicError; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; -use super::{CreateFile, CreateFileReceipt}; +use super::{CreateFile, CreateFileError}; const NIX_CHANNELS_PATH: &str = "/root/.nix-channels"; @@ -32,8 +33,8 @@ impl PlaceChannelConfiguration { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for PlaceChannelConfiguration { - type Receipt = PlaceChannelConfigurationReceipt; +impl Actionable for ActionState { + type Error = PlaceChannelConfigurationError; fn description(&self) -> Vec { let Self { channels, @@ -46,35 +47,38 @@ impl<'a> Actionable<'a> for PlaceChannelConfiguration { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { create_file, channels, } = self; - let create_file = create_file.execute().await?; - Ok(Self::Receipt { - create_file, - channels, - }) + + create_file.execute().await?; + + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct PlaceChannelConfigurationReceipt { - channels: Vec<(String, Url)>, - create_file: CreateFileReceipt, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for PlaceChannelConfigurationReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::PlaceChannelConfiguration(v)), + ActionState::Planned(_) => ActionState::Planned(Action::PlaceChannelConfiguration(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::PlaceChannelConfiguration(v)), + } + } +} + + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum PlaceChannelConfigurationError { + +} diff --git a/src/actions/base/place_nix_configuration.rs b/src/actions/base/place_nix_configuration.rs index e339fb9..898a605 100644 --- a/src/actions/base/place_nix_configuration.rs +++ b/src/actions/base/place_nix_configuration.rs @@ -1,8 +1,10 @@ +use serde::Serialize; + use crate::HarmonicError; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; -use super::{CreateFile, CreateFileReceipt, CreateDirectory, CreateDirectoryReceipt}; +use super::{CreateFile, CreateFileError, CreateDirectory, CreateDirectoryError}; const NIX_CONF_FOLDER: &str = "/etc/nix"; const NIX_CONF: &str = "/etc/nix/nix.conf"; @@ -35,8 +37,9 @@ impl PlaceNixConfiguration { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for PlaceNixConfiguration { - type Receipt = PlaceNixConfigurationReceipt; +impl Actionable for ActionState { + type Error = PlaceNixConfigurationError; + fn description(&self) -> Vec { vec![ActionDescription::new( format!("Place the nix configuration in `{NIX_CONF}`"), @@ -45,30 +48,36 @@ impl<'a> Actionable<'a> for PlaceNixConfiguration { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { create_file, create_directory } = self; - let create_directory = create_directory.execute().await?; - let create_file = create_file.execute().await?; - Ok(Self::Receipt { create_file, create_directory }) - } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct PlaceNixConfigurationReceipt { - create_directory: CreateDirectoryReceipt, - create_file: CreateFileReceipt, -} + create_directory.execute().await?; + create_file.execute().await?; -#[async_trait::async_trait] -impl<'a> Revertable<'a> for PlaceNixConfigurationReceipt { - fn description(&self) -> Vec { - todo!() + Ok(()) } + #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::PlaceNixConfiguration(v)), + ActionState::Planned(_) => ActionState::Planned(Action::PlaceNixConfiguration(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::PlaceNixConfiguration(v)), + } + } +} + + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum PlaceNixConfigurationError { + +} \ No newline at end of file diff --git a/src/actions/base/setup_default_profile.rs b/src/actions/base/setup_default_profile.rs index 947ae0e..29ed443 100644 --- a/src/actions/base/setup_default_profile.rs +++ b/src/actions/base/setup_default_profile.rs @@ -1,9 +1,10 @@ -use crate::{execute_command, HarmonicError}; +use crate::{execute_command, HarmonicError, actions::{ActionState, Action}}; use glob::glob; +use serde::Serialize; use tokio::process::Command; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct SetupDefaultProfile { @@ -18,8 +19,8 @@ impl SetupDefaultProfile { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for SetupDefaultProfile { - type Receipt = SetupDefaultProfileReceipt; +impl Actionable for ActionState { + type Error = SetupDefaultProfileError; fn description(&self) -> Vec { vec![ActionDescription::new( "Setup the default Nix profile".to_string(), @@ -28,7 +29,7 @@ impl<'a> Actionable<'a> for SetupDefaultProfile { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { channels } = self; tracing::info!("Setting up default profile"); @@ -110,26 +111,30 @@ impl<'a> Actionable<'a> for SetupDefaultProfile { false => return Err(HarmonicError::CommandFailedStatus(command_str)), } } - Ok(Self::Receipt {}) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct SetupDefaultProfileReceipt {} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for SetupDefaultProfileReceipt { - fn description(&self) -> Vec { - vec![ActionDescription::new( - "Unset the default Nix profile".to_string(), - vec!["TODO".to_string()], - )] - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::SetupDefaultProfile(v)), + ActionState::Planned(_) => ActionState::Planned(Action::SetupDefaultProfile(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::SetupDefaultProfile(v)), + } + } +} + + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum SetupDefaultProfileError { + +} diff --git a/src/actions/base/start_systemd_unit.rs b/src/actions/base/start_systemd_unit.rs index c082492..ec51077 100644 --- a/src/actions/base/start_systemd_unit.rs +++ b/src/actions/base/start_systemd_unit.rs @@ -1,8 +1,10 @@ +use serde::Serialize; use tokio::process::Command; +use crate::actions::meta::StartNixDaemon; use crate::{execute_command, HarmonicError}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct StartSystemdUnit { @@ -11,56 +13,83 @@ pub struct StartSystemdUnit { impl StartSystemdUnit { #[tracing::instrument(skip_all)] - pub async fn plan(unit: String) -> Result { - Ok(Self { unit }) + pub async fn plan(unit: String) -> Result, StartSystemdUnitError> { + Ok(ActionState::Planned(Self { unit })) } } #[async_trait::async_trait] -impl<'a> Actionable<'a> for StartSystemdUnit { - type Receipt = StartSystemdUnitReceipt; +impl Actionable for ActionState { + type Error = StartSystemdUnitError; fn description(&self) -> Vec { - 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() - ] - ), - ] + match self { + ActionState::Planned(v) => 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() + ] + ), + ], + ActionState::Reverted(_) => vec![ + ActionDescription::new( + "Stopped 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)] - async fn execute(self) -> Result { - let Self { unit } = self; + async fn execute(&mut self) -> Result<(), ActionError> { + let StartSystemdUnit { unit } = match self { + ActionState::Completed(_) => return Err(ActionError::AlreadyExecuted(self.clone().into())), + ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())), + ActionState::Planned(v) => v, + }; // TODO(@Hoverbear): Handle proxy vars - execute_command( Command::new("systemctl") .arg("enable") .arg("--now") .arg(format!("{unit}")), - false, ) - .await?; + .await.map_err(StartSystemdUnitError::Command)?; - Ok(Self::Receipt {}) - } -} - -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct StartSystemdUnitReceipt {} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for StartSystemdUnitReceipt { - fn description(&self) -> Vec { - todo!() + Ok(()) } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), ActionError> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::StartSystemdUnit(v)), + ActionState::Planned(_) => ActionState::Planned(Action::StartSystemdUnit(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::StartSystemdUnit(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum StartSystemdUnitError { + #[error("Failed to execute command")] + #[serde(serialize_with = "crate::serialize_std_io_error_to_display")] + Command(#[source] std::io::Error) +} diff --git a/src/actions/meta/configure_nix.rs b/src/actions/meta/configure_nix.rs index b8a8ec6..795d2d4 100644 --- a/src/actions/meta/configure_nix.rs +++ b/src/actions/meta/configure_nix.rs @@ -1,13 +1,15 @@ +use serde::Serialize; + use crate::actions::{ base::{ - ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, PlaceNixConfiguration, - PlaceNixConfigurationReceipt, SetupDefaultProfile, SetupDefaultProfileReceipt, PlaceChannelConfiguration, PlaceChannelConfigurationReceipt, + ConfigureNixDaemonService, ConfigureNixDaemonServiceError, PlaceNixConfiguration, + PlaceNixConfigurationError, SetupDefaultProfile, SetupDefaultProfileError, PlaceChannelConfiguration, PlaceChannelConfigurationError, }, - meta::{ConfigureShellProfile, ConfigureShellProfileReceipt}, + meta::{ConfigureShellProfile, ConfigureShellProfileError}, ActionState, Action, }; use crate::{HarmonicError, InstallSettings}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct ConfigureNix { @@ -20,7 +22,7 @@ pub struct ConfigureNix { impl ConfigureNix { #[tracing::instrument(skip_all)] - pub async fn plan(settings: InstallSettings) -> Result { + pub async fn plan(settings: InstallSettings) -> Result, ConfigureNixError> { let channels = settings .channels .iter() @@ -51,8 +53,8 @@ impl ConfigureNix { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for ConfigureNix { - type Receipt = ConfigureNixReceipt; +impl Actionable for ActionState { + type Error = ConfigureNixError; fn description(&self) -> Vec { let Self { setup_default_profile, @@ -74,7 +76,7 @@ impl<'a> Actionable<'a> for ConfigureNix { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { setup_default_profile, configure_nix_daemon_service, @@ -114,34 +116,27 @@ impl<'a> Actionable<'a> for ConfigureNix { configure_shell_profile, }) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct ConfigureNixReceipt { - setup_default_profile: SetupDefaultProfileReceipt, - configure_shell_profile: Option, - place_nix_configuration: PlaceNixConfigurationReceipt, - place_channel_configuration: PlaceChannelConfigurationReceipt, - configure_nix_daemon_service: ConfigureNixDaemonServiceReceipt, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for ConfigureNixReceipt { - fn description(&self) -> Vec { - vec![ - ActionDescription::new( - "Stop the systemd Nix daemon".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)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::ConfigureNix(v)), + ActionState::Planned(_) => ActionState::Planned(Action::ConfigureNix(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureNix(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum ConfigureNixError { + +} \ No newline at end of file diff --git a/src/actions/meta/configure_shell_profile.rs b/src/actions/meta/configure_shell_profile.rs index e05cb2e..f38f5ae 100644 --- a/src/actions/meta/configure_shell_profile.rs +++ b/src/actions/meta/configure_shell_profile.rs @@ -1,11 +1,12 @@ use std::path::Path; +use serde::Serialize; use tokio::task::JoinSet; use crate::HarmonicError; -use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileReceipt}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileError}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; const PROFILE_TARGETS: &[&str] = &[ "/etc/bashrc", @@ -24,7 +25,7 @@ pub struct ConfigureShellProfile { impl ConfigureShellProfile { #[tracing::instrument(skip_all)] - pub async fn plan() -> Result { + pub async fn plan() -> Result, ConfigureShellProfileError> { let mut create_or_append_files = Vec::default(); for profile_target in PROFILE_TARGETS { let path = Path::new(profile_target); @@ -54,8 +55,8 @@ impl ConfigureShellProfile { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for ConfigureShellProfile { - type Receipt = ConfigureShellProfileReceipt; +impl Actionable for ActionState { + type Error = ConfigureShellProfileError; fn description(&self) -> Vec { vec![ActionDescription::new( "Configure the shell profiles".to_string(), @@ -64,7 +65,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { create_or_append_files, } = self; @@ -81,7 +82,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile { while let Some(result) = set.join_next().await { match result { - Ok(Ok(success)) => successes.push(success), + Ok(Ok(())) => (), Ok(Err(e)) => errors.push(e), Err(e) => errors.push(e.into()), }; @@ -95,27 +96,29 @@ impl<'a> Actionable<'a> for ConfigureShellProfile { } } - Ok(Self::Receipt { - create_or_append_files: successes, - }) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct ConfigureShellProfileReceipt { - create_or_append_files: Vec, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for ConfigureShellProfileReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::ConfigureShellProfile(v)), + ActionState::Planned(_) => ActionState::Planned(Action::ConfigureShellProfile(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureShellProfile(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum ConfigureShellProfileError { + +} diff --git a/src/actions/meta/create_nix_tree.rs b/src/actions/meta/create_nix_tree.rs index cb96b8f..e0b51e3 100644 --- a/src/actions/meta/create_nix_tree.rs +++ b/src/actions/meta/create_nix_tree.rs @@ -1,7 +1,9 @@ +use serde::Serialize; + use crate::HarmonicError; -use crate::actions::base::{CreateDirectory, CreateDirectoryReceipt}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::base::{CreateDirectory, CreateDirectoryError}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; const PATHS: &[&str] = &[ "/nix", @@ -41,8 +43,8 @@ impl CreateNixTree { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateNixTree { - type Receipt = CreateNixTreeReceipt; +impl Actionable for ActionState { + type Error = CreateNixTreeError; fn description(&self) -> Vec { vec![ActionDescription::new( format!("Create a directory tree in `/nix`"), @@ -54,36 +56,38 @@ impl<'a> Actionable<'a> for CreateNixTree { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), HarmonicError> { let Self { create_directories } = self; let mut successes = Vec::with_capacity(create_directories.len()); // Just do sequential since parallizing this will have little benefit for create_directory in create_directories { - successes.push(create_directory.execute().await?) + create_directory.execute().await? } - Ok(CreateNixTreeReceipt { - create_directories: successes, - }) + Ok(()) } -} -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateNixTreeReceipt { - create_directories: Vec, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateNixTreeReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::CreateNixTree(v)), + ActionState::Planned(_) => ActionState::Planned(Action::CreateNixTree(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::CreateNixTree(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateNixTreeError { + +} diff --git a/src/actions/meta/create_users_and_group.rs b/src/actions/meta/create_users_and_group.rs index 7c0ce1d..79210be 100644 --- a/src/actions/meta/create_users_and_group.rs +++ b/src/actions/meta/create_users_and_group.rs @@ -1,9 +1,10 @@ +use serde::Serialize; use tokio::task::JoinSet; use crate::{HarmonicError, InstallSettings}; -use crate::actions::base::{CreateGroup, CreateGroupReceipt, CreateUserReceipt}; -use crate::actions::{ActionDescription, Actionable, CreateUser, Revertable}; +use crate::actions::base::{CreateGroup, CreateGroupError, CreateUserError}; +use crate::actions::{ActionDescription, Actionable, CreateUser, ActionState, Action}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateUsersAndGroup { @@ -47,8 +48,8 @@ impl CreateUsersAndGroup { } #[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateUsersAndGroup { - type Receipt = CreateUsersAndGroupReceipt; +impl Actionable for ActionState { + type Error = CreateUsersAndGroupError; fn description(&self) -> Vec { let Self { daemon_user_count, @@ -72,7 +73,7 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), HarmonicError> { let Self { create_users, create_group, @@ -114,24 +115,29 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup { create_users: successes, }) } -} - -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateUsersAndGroupReceipt { - create_group: CreateGroupReceipt, - create_users: Vec, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateUsersAndGroupReceipt { - fn description(&self) -> Vec { - todo!() - } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::CreateUsersAndGroup(v)), + ActionState::Planned(_) => ActionState::Planned(Action::CreateUsersAndGroup(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::CreateUsersAndGroup(v)), + } + } +} + + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateUsersAndGroupError { + +} + diff --git a/src/actions/meta/mod.rs b/src/actions/meta/mod.rs index db17395..75d1a76 100644 --- a/src/actions/meta/mod.rs +++ b/src/actions/meta/mod.rs @@ -7,9 +7,9 @@ mod create_users_and_group; mod provision_nix; mod start_nix_daemon; -pub use configure_nix::{ConfigureNix, ConfigureNixReceipt}; -pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt}; -pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt}; -pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupReceipt}; -pub use provision_nix::{ProvisionNix, ProvisionNixReceipt}; -pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt}; +pub use configure_nix::{ConfigureNix, ConfigureNixError}; +pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileError}; +pub use create_nix_tree::{CreateNixTree, CreateNixTreeError}; +pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupError}; +pub use provision_nix::{ProvisionNix, ProvisionNixError}; +pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonError}; diff --git a/src/actions/meta/provision_nix.rs b/src/actions/meta/provision_nix.rs index c4cdd2e..0418960 100644 --- a/src/actions/meta/provision_nix.rs +++ b/src/actions/meta/provision_nix.rs @@ -1,13 +1,14 @@ +use serde::Serialize; use tempdir::TempDir; -use crate::actions::base::{FetchNix, FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt}; +use crate::actions::base::{FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError}; use crate::{HarmonicError, InstallSettings}; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action}; use super::{ - CreateNixTree, CreateNixTreeReceipt, - CreateUsersAndGroup, CreateUsersAndGroupReceipt, + CreateNixTree, CreateNixTreeError, + CreateUsersAndGroup, CreateUsersAndGroupError, }; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] @@ -20,8 +21,8 @@ pub struct ProvisionNix { impl ProvisionNix { #[tracing::instrument(skip_all)] - pub async fn plan(settings: InstallSettings) -> Result { - let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?; + pub async fn plan(settings: InstallSettings) -> Result, ProvisionNixError> { + let tempdir = TempDir::new("nix").map_err(ProvisionNixError::TempDir)?; let fetch_nix = FetchNix::plan( settings.nix_package_url.clone(), @@ -31,19 +32,24 @@ impl ProvisionNix { let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()).await?; let create_nix_tree = CreateNixTree::plan(settings.force).await?; let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf()).await?; - Ok(Self { + Ok(ActionState::Planned(Self { fetch_nix, create_users_and_group, create_nix_tree, move_unpacked_nix, - }) + })) } } #[async_trait::async_trait] -impl<'a> Actionable<'a> for ProvisionNix { - type Receipt = ProvisionNixReceipt; +impl Actionable for ActionState { + type Error = ProvisionNixError; fn description(&self) -> Vec { + match self { + ActionState::Completed(action) => action.start_systemd_socket.description(), + ActionState::Planned(action) => action.start_systemd_socket.description(), + ActionState::Reverted(_) => todo!(), + } let Self { fetch_nix, create_users_and_group, @@ -60,7 +66,7 @@ impl<'a> Actionable<'a> for ProvisionNix { } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { + async fn execute(&mut self) -> Result<(), Self::Error> { let Self { fetch_nix, create_nix_tree, @@ -71,39 +77,37 @@ impl<'a> Actionable<'a> for ProvisionNix { // We fetch nix while doing the rest, then move it over. let fetch_nix_handle = tokio::spawn(async move { fetch_nix.execute().await }); - let create_users_and_group = create_users_and_group.execute().await?; - let create_nix_tree = create_nix_tree.execute().await?; + create_users_and_group.execute().await?; + create_nix_tree.execute().await?; - let fetch_nix = fetch_nix_handle.await??; - let move_unpacked_nix = move_unpacked_nix.execute().await?; + fetch_nix_handle.await??; + move_unpacked_nix.execute().await?; - Ok(ProvisionNixReceipt { - fetch_nix, - create_users_and_group, - create_nix_tree, - move_unpacked_nix, - }) - } -} - -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct ProvisionNixReceipt { - fetch_nix: FetchNixReceipt, - create_users_and_group: CreateUsersAndGroupReceipt, - create_nix_tree: CreateNixTreeReceipt, - move_unpacked_nix: MoveUnpackedNixReceipt, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for ProvisionNixReceipt { - fn description(&self) -> Vec { - todo!() + Ok(()) } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { + async fn revert(&mut self) -> Result<(), Self::Error> { todo!(); Ok(()) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::ProvisionNix(v)), + ActionState::Planned(_) => ActionState::Planned(Action::ProvisionNix(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::ProvisionNix(v)), + } + } +} + + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum ProvisionNixError { + #[error("Failed create tempdir")] + #[serde(serialize_with = "crate::serialize_std_io_error_to_display")] + TempDir(#[source] std::io::Error) +} diff --git a/src/actions/meta/start_nix_daemon.rs b/src/actions/meta/start_nix_daemon.rs index 6906ae3..ed47c36 100644 --- a/src/actions/meta/start_nix_daemon.rs +++ b/src/actions/meta/start_nix_daemon.rs @@ -1,62 +1,80 @@ -use crate::actions::base::{StartSystemdUnit, StartSystemdUnitReceipt}; +use serde::Serialize; + +use crate::actions::base::{StartSystemdUnit, StartSystemdUnitError}; use crate::HarmonicError; -use crate::actions::{ActionDescription, Actionable, Revertable}; +use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError}; /// This is mostly indirection for supporting non-systemd #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct StartNixDaemon { - start_systemd_socket: StartSystemdUnit, + start_systemd_socket: ActionState, } impl StartNixDaemon { #[tracing::instrument(skip_all)] - pub async fn plan() -> Result { + pub async fn plan() -> Result, StartNixDaemonError> { let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?; - let start_systemd_service = StartSystemdUnit::plan("nix-daemon.service".into()).await?; - Ok(Self { + Ok(ActionState::Planned(Self { start_systemd_socket, - }) + })) } } #[async_trait::async_trait] -impl<'a> Actionable<'a> for StartNixDaemon { - type Receipt = StartNixDaemonReceipt; +impl Actionable for ActionState { + type Error = StartNixDaemonError; + fn description(&self) -> Vec { - let Self { - start_systemd_socket, - } = &self; + let StartNixDaemon { start_systemd_socket } = match self { + ActionState::Completed(v) | ActionState::Reverted(v) | ActionState::Planned(v) => v, + }; start_systemd_socket.description() } #[tracing::instrument(skip_all)] - async fn execute(self) -> Result { - let Self { - start_systemd_socket, - } = self; - let start_systemd_socket = start_systemd_socket.execute().await?; - Ok(Self::Receipt { - start_systemd_socket, - }) - } -} + async fn execute(self) -> Result { + let StartNixDaemon { start_systemd_socket } = match self { + ActionState::Planned(v) => v, + ActionState::Completed(_) => return Err(ActionError::AlreadyExecuted(self.clone().into())), + ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())), + }; -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct StartNixDaemonReceipt { - start_systemd_socket: StartSystemdUnitReceipt, -} + start_systemd_socket.execute().await?; -#[async_trait::async_trait] -impl<'a> Revertable<'a> for StartNixDaemonReceipt { - fn description(&self) -> Vec { - todo!() + Ok(Self::Completed(StartNixDaemon { + start_systemd_socket, + })) } #[tracing::instrument(skip_all)] - async fn revert(self) -> Result<(), HarmonicError> { - todo!(); + async fn revert(self) -> Result { + let StartNixDaemon { start_systemd_socket } = match self { + ActionState::Planned(v) => return Err(ActionError::NotExecuted(self.clone().into())), + ActionState::Completed(v) => v, + ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())), + }; - Ok(()) + start_systemd_socket.revert().await?; + + Ok(Self::Reverted(StartNixDaemon { + start_systemd_socket, + })) } } + +impl From> for ActionState { + fn from(v: ActionState) -> Self { + match v { + ActionState::Completed(_) => ActionState::Completed(Action::StartNixDaemon(v)), + ActionState::Planned(_) => ActionState::Planned(Action::StartNixDaemon(v)), + ActionState::Reverted(_) => ActionState::Reverted(Action::StartNixDaemon(v)), + } + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum StartNixDaemonError { + #[error(transparent)] + StartSystemdUnit(#[from] StartSystemdUnitError) +} diff --git a/src/actions/mod.rs b/src/actions/mod.rs index dd9eab7..3739273 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -1,33 +1,45 @@ pub mod base; pub mod meta; +use std::{error::Error, fmt::Display}; + use base::{ - ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, CreateDirectory, - CreateDirectoryReceipt, CreateFile, CreateFileReceipt, CreateGroup, CreateGroupReceipt, - CreateOrAppendFile, CreateOrAppendFileReceipt, CreateUser, CreateUserReceipt, FetchNix, - FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt, PlaceChannelConfiguration, - PlaceChannelConfigurationReceipt, PlaceNixConfiguration, PlaceNixConfigurationReceipt, - SetupDefaultProfile, SetupDefaultProfileReceipt, + ConfigureNixDaemonService, ConfigureNixDaemonServiceError, CreateDirectory, + CreateDirectoryError, CreateFile, CreateFileError, CreateGroup, CreateGroupError, + CreateOrAppendFile, CreateOrAppendFileError, CreateUser, CreateUserError, FetchNix, + FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, PlaceChannelConfiguration, + PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError, + SetupDefaultProfile, SetupDefaultProfileError, }; use meta::{ - ConfigureNix, ConfigureNixReceipt, ConfigureShellProfile, ConfigureShellProfileReceipt, - CreateNixTree, CreateNixTreeReceipt, CreateUsersAndGroup, CreateUsersAndGroupReceipt, - ProvisionNix, ProvisionNixReceipt, StartNixDaemon, StartNixDaemonReceipt, + ConfigureNix, ConfigureNixError, ConfigureShellProfile, ConfigureShellProfileError, + CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError, + ProvisionNix, ProvisionNixError, StartNixDaemon, StartNixDaemonError, }; +use serde::{Deserialize, de::DeserializeOwned, Serialize}; -use crate::HarmonicError; + +use self::base::{StartSystemdUnit, StartSystemdUnitError}; #[async_trait::async_trait] -pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize { - type Receipt; +pub trait Actionable: DeserializeOwned + Serialize + Into> { + type Error: std::error::Error + std::fmt::Debug + Serialize; + fn description(&self) -> Vec; - async fn execute(self) -> Result; + + // They should also have an `async fn plan(args...) -> Result, Self::Error>;` + async fn execute(self) -> Result; + async fn revert(self) -> Result; } -#[async_trait::async_trait] -pub trait Revertable<'a>: serde::de::Deserialize<'a> + serde::Serialize { - fn description(&self) -> Vec; - async fn revert(self) -> Result<(), HarmonicError>; +#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)] +pub enum ActionState

where P: Serialize + DeserializeOwned + Clone { + #[serde(bound = "P: DeserializeOwned")] + Completed(P), + #[serde(bound = "P: DeserializeOwned")] + Planned(P), + #[serde(bound = "P: DeserializeOwned")] + Reverted(P), } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] @@ -48,51 +60,80 @@ impl ActionDescription { #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub enum Action { - ConfigureNixDaemonService(ConfigureNixDaemonService), - ConfigureNix(ConfigureNix), - ConfigureShellProfile(ConfigureShellProfile), - CreateDirectory(CreateDirectory), - CreateFile(CreateFile), - CreateGroup(CreateGroup), - CreateOrAppendFile(CreateOrAppendFile), - CreateNixTree(CreateNixTree), - CreateUser(CreateUser), - CreateUsersAndGroup(CreateUsersAndGroup), - FetchNix(FetchNix), - MoveUnpackedNix(MoveUnpackedNix), - PlaceChannelConfiguration(PlaceChannelConfiguration), - PlaceNixConfiguration(PlaceNixConfiguration), - SetupDefaultProfile(SetupDefaultProfile), - StartNixDaemon(StartNixDaemon), - ProvisionNix(ProvisionNix), + ConfigureNixDaemonService(ActionState), + ConfigureNix(ActionState), + ConfigureShellProfile(ActionState), + CreateDirectory(ActionState), + CreateFile(ActionState), + CreateGroup(ActionState), + CreateOrAppendFile(ActionState), + CreateNixTree(ActionState), + CreateUser(ActionState), + CreateUsersAndGroup(ActionState), + FetchNix(ActionState), + MoveUnpackedNix(ActionState), + PlaceChannelConfiguration(ActionState), + PlaceNixConfiguration(ActionState), + SetupDefaultProfile(ActionState), + StartNixDaemon(ActionState), + StartSystemdUnit(ActionState), + ProvisionNix(ActionState), } -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub enum ActionReceipt { - ConfigureNixDaemonService(ConfigureNixDaemonServiceReceipt), - ConfigureNix(ConfigureNixReceipt), - ConfigureShellProfile(ConfigureShellProfileReceipt), - CreateDirectory(CreateDirectoryReceipt), - CreateFile(CreateFileReceipt), - CreateGroup(CreateGroupReceipt), - CreateOrAppendFile(CreateOrAppendFileReceipt), - CreateNixTree(CreateNixTreeReceipt), - CreateUser(CreateUserReceipt), - CreateUsersAndGroup(CreateUsersAndGroupReceipt), - FetchNix(FetchNixReceipt), - MoveUnpackedNix(MoveUnpackedNixReceipt), - PlaceChannelConfiguration(PlaceChannelConfigurationReceipt), - PlaceNixConfiguration(PlaceNixConfigurationReceipt), - SetupDefaultProfile(SetupDefaultProfileReceipt), - StartNixDaemon(StartNixDaemonReceipt), - ProvisionNix(ProvisionNixReceipt), +#[derive(Debug, thiserror::Error, serde::Serialize)] +pub enum ActionError { + #[error("Attempted to revert an unexecuted action")] + NotExecuted(ActionState), + #[error("Attempted to execute an already executed action")] + AlreadyExecuted(ActionState), + #[error("Attempted to revert an already reverted action")] + AlreadyReverted(ActionState), + #[error(transparent)] + ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError), + #[error(transparent)] + ConfigureNix(#[from] ConfigureNixError), + #[error(transparent)] + ConfigureShellProfile(#[from] ConfigureShellProfileError), + #[error(transparent)] + CreateDirectory(#[from] CreateDirectoryError), + #[error(transparent)] + CreateFile(#[from] CreateFileError), + #[error(transparent)] + CreateGroup(#[from] CreateGroupError), + #[error(transparent)] + CreateOrAppendFile(#[from] CreateOrAppendFileError), + #[error(transparent)] + CreateNixTree(#[from] CreateNixTreeError), + #[error(transparent)] + CreateUser(#[from] CreateUserError), + #[error(transparent)] + CreateUsersAndGroup(#[from] CreateUsersAndGroupError), + #[error(transparent)] + FetchNix(#[from] FetchNixError), + #[error(transparent)] + MoveUnpackedNix(#[from] MoveUnpackedNixError), + #[error(transparent)] + PlaceChannelConfiguration(#[from] PlaceChannelConfigurationError), + #[error(transparent)] + PlaceNixConfiguration(#[from] PlaceNixConfigurationError), + #[error(transparent)] + SetupDefaultProfile(#[from] SetupDefaultProfileError), + #[error(transparent)] + StartNixDaemon(#[from] StartNixDaemonError), + #[error(transparent)] + StartSystemdUnit(#[from] StartSystemdUnitError), + #[error(transparent)] + ProvisionNix(#[from] ProvisionNixError), } #[async_trait::async_trait] -impl<'a> Actionable<'a> for Action { - type Receipt = ActionReceipt; +impl Actionable for ActionState { + type Error = ActionError; fn description(&self) -> Vec { - match self { + let inner = match self { + ActionState::Planned(p) | ActionState::Completed(p) | ActionState::Reverted(p) => p, + }; + match inner { Action::ConfigureNixDaemonService(i) => i.description(), Action::ConfigureNix(i) => i.description(), Action::ConfigureShellProfile(i) => i.description(), @@ -109,92 +150,64 @@ impl<'a> Actionable<'a> for Action { 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(), } } - async fn execute(self) -> Result { - match self { - Action::ConfigureNixDaemonService(i) => i - .execute() - .await - .map(ActionReceipt::ConfigureNixDaemonService), - Action::ConfigureNix(i) => i.execute().await.map(ActionReceipt::ConfigureNix), - Action::ConfigureShellProfile(i) => { - i.execute().await.map(ActionReceipt::ConfigureShellProfile) - }, - Action::CreateDirectory(i) => i.execute().await.map(ActionReceipt::CreateDirectory), - Action::CreateFile(i) => i.execute().await.map(ActionReceipt::CreateFile), - Action::CreateGroup(i) => i.execute().await.map(ActionReceipt::CreateGroup), - Action::CreateOrAppendFile(i) => { - i.execute().await.map(ActionReceipt::CreateOrAppendFile) - }, - Action::CreateNixTree(i) => i.execute().await.map(ActionReceipt::CreateNixTree), - Action::CreateUser(i) => i.execute().await.map(ActionReceipt::CreateUser), - Action::CreateUsersAndGroup(i) => { - i.execute().await.map(ActionReceipt::CreateUsersAndGroup) - }, - Action::FetchNix(i) => i.execute().await.map(ActionReceipt::FetchNix), - Action::MoveUnpackedNix(i) => i.execute().await.map(ActionReceipt::MoveUnpackedNix), - Action::PlaceChannelConfiguration(i) => i - .execute() - .await - .map(ActionReceipt::PlaceChannelConfiguration), - Action::PlaceNixConfiguration(i) => { - i.execute().await.map(ActionReceipt::PlaceNixConfiguration) - }, - Action::SetupDefaultProfile(i) => { - i.execute().await.map(ActionReceipt::SetupDefaultProfile) - }, - Action::StartNixDaemon(i) => i.execute().await.map(ActionReceipt::StartNixDaemon), - Action::ProvisionNix(i) => i.execute().await.map(ActionReceipt::ProvisionNix), - } - } -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for ActionReceipt { - fn description(&self) -> Vec { - match self { - ActionReceipt::ConfigureNixDaemonService(i) => i.description(), - ActionReceipt::ConfigureNix(i) => i.description(), - ActionReceipt::ConfigureShellProfile(i) => i.description(), - ActionReceipt::CreateDirectory(i) => i.description(), - ActionReceipt::CreateFile(i) => i.description(), - ActionReceipt::CreateGroup(i) => i.description(), - ActionReceipt::CreateOrAppendFile(i) => i.description(), - ActionReceipt::CreateNixTree(i) => i.description(), - ActionReceipt::CreateUser(i) => i.description(), - ActionReceipt::CreateUsersAndGroup(i) => i.description(), - ActionReceipt::FetchNix(i) => i.description(), - ActionReceipt::MoveUnpackedNix(i) => i.description(), - ActionReceipt::PlaceChannelConfiguration(i) => i.description(), - ActionReceipt::PlaceNixConfiguration(i) => i.description(), - ActionReceipt::SetupDefaultProfile(i) => i.description(), - ActionReceipt::StartNixDaemon(i) => i.description(), - ActionReceipt::ProvisionNix(i) => i.description(), + async fn execute(&mut self) -> Result<(), Self::Error> { + let inner = match self { + ActionState::Completed(p) => todo!(), + ActionState::Planned(p) => p, + ActionState::Reverted(p) => todo!(), + }; + match inner { + Action::ConfigureNixDaemonService(i) => i.execute().await, + Action::ConfigureNix(i) => i.execute().await, + Action::ConfigureShellProfile(i) => i.execute().await, + Action::CreateDirectory(i) => i.execute().await, + Action::CreateFile(i) => i.execute().await, + Action::CreateGroup(i) => i.execute().await, + Action::CreateOrAppendFile(i) => i.execute().await, + Action::CreateNixTree(i) => i.execute().await, + Action::CreateUser(i) => i.execute().await, + Action::CreateUsersAndGroup(i) => i.execute().await, + Action::FetchNix(i) => i.execute().await, + Action::MoveUnpackedNix(i) => i.execute().await, + Action::PlaceChannelConfiguration(i) => i.execute().await, + Action::PlaceNixConfiguration(i) => i.execute().await, + Action::SetupDefaultProfile(i) => i.execute().await, + Action::StartNixDaemon(i) => i.execute().await, + Action::StartSystemdUnit(i) => i.execute().await, + Action::ProvisionNix(i) => i.execute().await, } } - async fn revert(self) -> Result<(), HarmonicError> { - match self { - ActionReceipt::ConfigureNixDaemonService(i) => i.revert().await, - ActionReceipt::ConfigureNix(i) => i.revert().await, - ActionReceipt::ConfigureShellProfile(i) => i.revert().await, - ActionReceipt::CreateDirectory(i) => i.revert().await, - ActionReceipt::CreateFile(i) => i.revert().await, - ActionReceipt::CreateGroup(i) => i.revert().await, - ActionReceipt::CreateOrAppendFile(i) => i.revert().await, - ActionReceipt::CreateNixTree(i) => i.revert().await, - ActionReceipt::CreateUser(i) => i.revert().await, - ActionReceipt::CreateUsersAndGroup(i) => i.revert().await, - ActionReceipt::FetchNix(i) => i.revert().await, - ActionReceipt::MoveUnpackedNix(i) => i.revert().await, - ActionReceipt::PlaceChannelConfiguration(i) => i.revert().await, - ActionReceipt::PlaceNixConfiguration(i) => i.revert().await, - ActionReceipt::SetupDefaultProfile(i) => i.revert().await, - ActionReceipt::StartNixDaemon(i) => i.revert().await, - ActionReceipt::ProvisionNix(i) => i.revert().await, + async fn revert(&mut self) -> Result<(), Self::Error> { + let inner = match self { + ActionState::Planned(p) => todo!(), + ActionState::Completed(p) => p, + ActionState::Reverted(p) => todo!(), + }; + match inner { + Action::ConfigureNixDaemonService(i) => i.revert().await, + Action::ConfigureNix(i) => i.revert().await, + Action::ConfigureShellProfile(i) => i.revert().await, + Action::CreateDirectory(i) => i.revert().await, + Action::CreateFile(i) => i.revert().await, + Action::CreateGroup(i) => i.revert().await, + Action::CreateOrAppendFile(i) => i.revert().await, + Action::CreateNixTree(i) => i.revert().await, + Action::CreateUser(i) => i.revert().await, + Action::CreateUsersAndGroup(i) => i.revert().await, + Action::FetchNix(i) => i.revert().await, + Action::MoveUnpackedNix(i) => i.revert().await, + Action::PlaceChannelConfiguration(i) => i.revert().await, + Action::PlaceNixConfiguration(i) => i.revert().await, + Action::SetupDefaultProfile(i) => i.revert().await, + Action::StartNixDaemon(i) => i.revert().await, + Action::StartSystemdUnit(i) => i.revert().await, + Action::ProvisionNix(i) => i.revert().await, } } } diff --git a/src/lib.rs b/src/lib.rs index 87258c6..d437152 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use std::{ pub use error::HarmonicError; pub use plan::InstallPlan; +use serde::Serializer; pub use settings::InstallSettings; use bytes::Buf; @@ -26,402 +27,6 @@ use tokio::{ task::spawn_blocking, }; -// This uses a Rust builder pattern -#[derive(Debug)] -pub struct Harmonic { - dry_run: bool, - daemon_user_count: usize, - channels: Vec<(String, Url)>, - modify_profile: bool, - nix_build_group_name: String, - nix_build_group_id: usize, - nix_build_user_prefix: String, - nix_build_user_id_base: usize, -} - -impl Harmonic { - pub fn dry_run(&mut self, dry_run: bool) -> &mut Self { - self.dry_run = dry_run; - self - } - pub fn daemon_user_count(&mut self, count: usize) -> &mut Self { - self.daemon_user_count = count; - self - } - - pub fn channels(&mut self, channels: impl IntoIterator) -> &mut Self { - self.channels = channels.into_iter().collect(); - self - } - - pub fn modify_profile(&mut self, toggle: bool) -> &mut Self { - self.modify_profile = toggle; - self - } -} - -impl Harmonic { - #[tracing::instrument(skip_all)] - pub async fn fetch_nix(&self) -> Result<(), HarmonicError> { - tracing::info!("Fetching nix"); - // TODO(@hoverbear): architecture specific download - // TODO(@hoverbear): hash check - // TODO(@hoverbear): custom url - let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?; - fetch_url_and_unpack_xz( - "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz", - tempdir.path(), - self.dry_run, - ) - .await?; - - let found_nix_path = if !self.dry_run { - // TODO(@Hoverbear): I would like to make this less awful - let found_nix_paths = glob::glob(&format!("{}/nix-*", tempdir.path().display()))? - .collect::, _>>()?; - assert_eq!( - found_nix_paths.len(), - 1, - "Did not expect to find multiple nix paths, please report this" - ); - found_nix_paths.into_iter().next().unwrap() - } else { - PathBuf::from("/nix/nix-*") - }; - rename(found_nix_path.join("store"), "/nix/store", self.dry_run).await?; - - Ok(()) - } - - #[tracing::instrument(skip_all)] - pub async fn create_group(&self) -> Result<(), HarmonicError> { - tracing::info!("Creating group"); - execute_command( - Command::new("groupadd") - .arg("-g") - .arg(self.nix_build_group_id.to_string()) - .arg("--system") - .arg(&self.nix_build_group_name), - self.dry_run, - ) - .await?; - Ok(()) - } - - #[tracing::instrument(skip_all)] - pub async fn create_users(&self) -> Result<(), HarmonicError> { - tracing::info!("Creating users"); - for index in 1..=self.daemon_user_count { - let user_name = format!("{}{index}", self.nix_build_user_prefix); - let user_id = self.nix_build_user_id_base + index; - execute_command( - Command::new("useradd").args([ - "--home-dir", - "/var/empty", - "--comment", - &format!("\"Nix build user {user_id}\""), - "--gid", - &self.nix_build_group_name.to_string(), - "--groups", - &self.nix_build_group_name.to_string(), - "--no-user-group", - "--system", - "--shell", - "/sbin/nologin", - "--uid", - &user_id.to_string(), - "--password", - "\"!\"", - &user_name.to_string(), - ]), - self.dry_run, - ) - .await?; - } - Ok(()) - } - - #[tracing::instrument(skip_all)] - pub async fn create_directories(&self) -> Result<(), HarmonicError> { - tracing::info!("Creating directories"); - create_directory("/nix", self.dry_run).await?; - set_permissions( - "/nix", - None, - Some("root".to_string()), - Some(self.nix_build_group_name.clone()), - self.dry_run, - ) - .await?; - - let permissions = Permissions::from_mode(0o0755); - let paths = [ - "/nix/var", - "/nix/var/log", - "/nix/var/log/nix", - "/nix/var/log/nix/drvs", - "/nix/var/nix", - "/nix/var/nix/db", - "/nix/var/nix/gcroots", - "/nix/var/nix/gcroots/per-user", - "/nix/var/nix/profiles", - "/nix/var/nix/profiles/per-user", - "/nix/var/nix/temproots", - "/nix/var/nix/userpool", - "/nix/var/nix/daemon-socket", - ]; - - for path in paths { - // We use `create_dir` over `create_dir_all` to ensure we always set permissions right - create_directory(path, self.dry_run).await?; - set_permissions(path, Some(permissions.clone()), None, None, self.dry_run).await?; - } - create_directory("/nix/store", self.dry_run).await?; - set_permissions( - "/nix/store", - Some(Permissions::from_mode(0o1775)), - None, - Some(self.nix_build_group_name.clone()), - self.dry_run, - ) - .await?; - create_directory("/etc/nix", self.dry_run).await?; - set_permissions( - "/etc/nix", - Some(Permissions::from_mode(0o0555)), - None, - None, - self.dry_run, - ) - .await?; - Ok(()) - } - - #[tracing::instrument(skip_all)] - pub async fn place_channel_configuration(&self) -> Result<(), HarmonicError> { - tracing::info!("Placing channel configuration"); - let buf = self - .channels - .iter() - .map(|(name, url)| format!("{} {}", url, name)) - .collect::>() - .join("\n"); - - create_file_if_not_exists("/root/.nix-channels", buf, self.dry_run).await?; - set_permissions( - "/root/.nix-channels", - Some(Permissions::from_mode(0o0664)), - None, - None, - self.dry_run, - ) - .await - } - - #[tracing::instrument(skip_all)] - pub async fn configure_shell_profile(&self) -> Result<(), HarmonicError> { - tracing::info!("Configuring shell profile"); - const PROFILE_TARGETS: &[&str] = &[ - "/etc/bashrc", - "/etc/profile.d/nix.sh", - "/etc/zshrc", - "/etc/bash.bashrc", - "/etc/zsh/zshrc", - ]; - const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"; - for profile_target in PROFILE_TARGETS { - let path = Path::new(profile_target); - let buf = format!( - "\n\ - # Nix\n\ - if [ -e '{PROFILE_NIX_FILE}' ]; then\n\ - . '{PROFILE_NIX_FILE}'\n\ - fi\n\ - # End Nix\n - \n", - ); - create_or_append_file(path, buf, self.dry_run).await?; - } - Ok(()) - } - - #[tracing::instrument(skip_all)] - pub async fn setup_default_profile(&self) -> Result<(), HarmonicError> { - tracing::info!("Setting up default profile"); - // Find an `nix` package - let nix_pkg_glob = "/nix/store/*-nix-*"; - let found_nix_pkg = if !self.dry_run { - let mut found_pkg = None; - for entry in glob(nix_pkg_glob).map_err(HarmonicError::GlobPatternError)? { - match entry { - Ok(path) => { - // TODO(@Hoverbear): Should probably ensure is unique - found_pkg = Some(path); - break; - }, - Err(_) => continue, /* Ignore it */ - }; - } - found_pkg - } else { - // This is a mock for dry running. - Some(PathBuf::from(nix_pkg_glob)) - }; - let nix_pkg = if let Some(nix_pkg) = found_nix_pkg { - nix_pkg - } else { - return Err(HarmonicError::NoNssCacert); - }; - - execute_command( - Command::new(nix_pkg.join("bin/nix-env")) - .arg("-i") - .arg(&nix_pkg), - self.dry_run, - ) - .await?; - - // Find an `nss-cacert` package, add it too. - let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*"; - let found_nss_ca_cert_pkg = if !self.dry_run { - let mut found_pkg = None; - for entry in glob(nss_ca_cert_pkg_glob).map_err(HarmonicError::GlobPatternError)? { - match entry { - Ok(path) => { - // TODO(@Hoverbear): Should probably ensure is unique - found_pkg = Some(path); - break; - }, - Err(_) => continue, /* Ignore it */ - }; - } - found_pkg - } else { - // This is a mock for dry running. - Some(PathBuf::from(nss_ca_cert_pkg_glob)) - }; - - if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg { - execute_command( - Command::new(nix_pkg.join("bin/nix-env")) - .arg("-i") - .arg(&nss_ca_cert_pkg), - self.dry_run, - ) - .await?; - set_env( - "NIX_SSL_CERT_FILE", - "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt", - self.dry_run, - ); - nss_ca_cert_pkg - } else { - return Err(HarmonicError::NoNssCacert); - }; - if !self.channels.is_empty() { - execute_command( - Command::new(nix_pkg.join("bin/nix-channel")) - .arg("--update") - .arg("nixpkgs") - .env( - "NIX_SSL_CERT_FILE", - "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt", - ), - self.dry_run, - ) - .await?; - } - Ok(()) - } - - #[tracing::instrument(skip_all)] - pub async fn place_nix_configuration(&self) -> Result<(), HarmonicError> { - tracing::info!("Placing nix configuration"); - let buf = format!( - "\ - {extra_conf}\n\ - build-users-group = {build_group_name}\n\ - ", - extra_conf = "", // TODO(@Hoverbear): populate me - build_group_name = self.nix_build_group_name, - ); - create_file_if_not_exists("/etc/nix/nix.conf", buf, self.dry_run).await - } - - #[tracing::instrument(skip_all)] - pub async fn configure_nix_daemon_service(&self) -> Result<(), HarmonicError> { - tracing::info!("Configuring nix daemon service"); - if Path::new("/run/systemd/system").exists() { - const SERVICE_SRC: &str = - "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service"; - - const SOCKET_SRC: &str = - "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket"; - - const TMPFILES_SRC: &str = - "/nix/var/nix/profiles/default//lib/tmpfiles.d/nix-daemon.conf"; - const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf"; - - symlink(TMPFILES_SRC, TMPFILES_DEST, self.dry_run).await?; - execute_command( - Command::new("systemd-tmpfiles") - .arg("--create") - .arg("--prefix=/nix/var/nix"), - self.dry_run, - ) - .await?; - execute_command( - Command::new("systemctl").arg("link").arg(SERVICE_SRC), - self.dry_run, - ) - .await?; - execute_command( - Command::new("systemctl").arg("enable").arg(SOCKET_SRC), - self.dry_run, - ) - .await?; - // TODO(@Hoverbear): Handle proxy vars - execute_command(Command::new("systemctl").arg("daemon-reload"), self.dry_run).await?; - execute_command( - Command::new("systemctl") - .arg("start") - .arg("nix-daemon.socket"), - self.dry_run, - ) - .await?; - execute_command( - Command::new("systemctl") - .arg("restart") - .arg("nix-daemon.service"), - self.dry_run, - ) - .await?; - } else { - return Err(HarmonicError::InitNotSupported); - } - Ok(()) - } -} - -impl Default for Harmonic { - fn default() -> Self { - Self { - dry_run: true, - channels: vec![( - "nixpkgs".to_string(), - "https://nixos.org/channels/nixpkgs-unstable" - .parse::() - .unwrap(), - )], - daemon_user_count: 32, - modify_profile: true, - nix_build_group_name: String::from("nixbld"), - nix_build_group_id: 30000, - nix_build_user_prefix: String::from("nixbld"), - nix_build_user_id_base: 30000, - } - } -} #[tracing::instrument(skip_all, fields( path = %path.as_ref().display(), @@ -497,27 +102,15 @@ async fn create_directory(path: impl AsRef, dry_run: bool) -> Result<(), H #[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))] async fn execute_command( command: &mut Command, - dry_run: bool, -) -> Result { - if !dry_run { - tracing::trace!("Executing"); - let command_str = format!("{:?}", command.as_std()); - let status = command - .status() - .await - .map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?; - match status.success() { - true => Ok(status), - false => Err(HarmonicError::CommandFailedStatus(command_str)), - } - } else { - tracing::info!("Dry run: Would execute"); - // You cannot conjure "good" exit status in Rust without breaking the rules - // So... we conjure one from `true` - Command::new("true") - .status() - .await - .map_err(|e| HarmonicError::CommandFailedExec(String::from("true"), e)) +) -> Result { + tracing::trace!("Executing"); + let command_str = format!("{:?}", command.as_std()); + let status = command + .status() + .await?; + match status.success() { + true => Ok(status), + false => Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed status")), } } @@ -686,3 +279,7 @@ fn set_env(k: impl AsRef, v: impl AsRef, dry_run: bool) { tracing::info!("Dry run: Would set env"); } } + +fn serialize_std_io_error_to_display(err: &std::io::Error, ser: S) -> Result where S: Serializer { + ser.serialize_str(&format!("{err:#}")) +} \ No newline at end of file diff --git a/src/plan.rs b/src/plan.rs index c0d80bd..284fea0 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ actions::{ meta::{ConfigureNix, ProvisionNix, StartNixDaemon}, - Action, ActionDescription, ActionReceipt, Actionable, Revertable, + Action, ActionDescription, Actionable, ActionState, ActionError, }, settings::InstallSettings, HarmonicError, @@ -30,7 +30,9 @@ pub struct InstallPlan { * "Start Nix" * start_nix_daemon_service */ - actions: Vec, + provision_nix: ActionState, + configure_nix: ActionState, + start_nix_daemon: ActionState, } impl InstallPlan { @@ -55,68 +57,48 @@ impl InstallPlan { .map(|(name, url)| format!("{name}={url}")) .collect::>() .join(","), - actions = self - .actions - .iter() - .flat_map(|action| action.description()) - .map(|desc| { - let ActionDescription { - description, - explanation, - } = desc; + actions = { + let mut buf = self.provision_nix.description(); + buf.append(&mut self.configure_nix.description()); + buf.append(&mut self.start_nix_daemon.description()); + 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")); + 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"), + buf + }) + .collect::>() + .join("\n") + }, ) } - pub async fn new(settings: InstallSettings) -> Result { - let actions = vec![ - Action::ProvisionNix(ProvisionNix::plan(settings.clone()).await?), - Action::ConfigureNix(ConfigureNix::plan(settings.clone()).await?), - Action::StartNixDaemon(StartNixDaemon::plan().await?), - ]; - Ok(Self { settings, actions }) + pub async fn new(settings: InstallSettings) -> Result { + Ok(Self { + settings: settings.clone(), + provision_nix: ProvisionNix::plan(settings.clone()).await?, + configure_nix: ConfigureNix::plan(settings).await?, + start_nix_daemon: StartNixDaemon::plan().await?, + }) } #[tracing::instrument(skip_all)] - pub async fn install(self) -> Result { - let mut receipt = Receipt::default(); + pub async fn install(&mut self) -> Result<(), ActionError> { // 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. - for action in self.actions { - match action.execute().await { - Ok(action_receipt) => receipt.actions.push(action_receipt), - Err(err) => { - return Err(err); - // TODO - // let mut revert_errs = Vec::default(); - - // for action_receipt in receipt.actions { - // if let Err(err) = action_receipt.revert().await { - // revert_errs.push(err); - // } - // } - // if !revert_errs.is_empty() { - // return Err(HarmonicError::FailedReverts(vec![err], revert_errs)); - // } - }, - }; - } - Ok(receipt) + self.provision_nix.execute().await?; + self.configure_nix.execute().await?; + self.start_nix_daemon.execute().await?; + Ok(()) } } - -#[derive(Default, Debug, Serialize, Deserialize)] -pub struct Receipt { - actions: Vec, -}