Create ActionError (#79)

* wip

* Fixes

* Fix a comment
This commit is contained in:
Ana Hobden 2022-12-05 08:55:30 -08:00 committed by GitHub
parent 16e886f53b
commit 4ce8d94ac7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 412 additions and 694 deletions

View file

@ -5,11 +5,8 @@ use nix::unistd::{chown, Group, User};
use tokio::fs::{create_dir, remove_dir_all}; use tokio::fs::{create_dir, remove_dir_all};
use crate::action::StatefulAction; use crate::action::{Action, ActionDescription, ActionState};
use crate::{ use crate::action::{ActionError, StatefulAction};
action::{Action, ActionDescription, ActionState},
BoxableError,
};
/** Create a directory at the given location, optionally with an owning user, group, and mode. /** Create a directory at the given location, optionally with an owning user, group, and mode.
@ -33,16 +30,16 @@ impl CreateDirectory {
group: impl Into<Option<String>>, group: impl Into<Option<String>>,
mode: impl Into<Option<u32>>, mode: impl Into<Option<u32>>,
force_prune_on_revert: bool, force_prune_on_revert: bool,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
let path = path.as_ref(); let path = path.as_ref();
let user = user.into(); let user = user.into();
let group = group.into(); let group = group.into();
let mode = mode.into(); let mode = mode.into();
let action_state = if path.exists() { let action_state = if path.exists() {
let metadata = tokio::fs::metadata(path).await.map_err(|e| { let metadata = tokio::fs::metadata(path)
CreateDirectoryError::GettingMetadata(path.to_path_buf(), e).boxed() .await
})?; .map_err(|e| ActionError::GettingMetadata(path.to_path_buf(), e))?;
if metadata.is_dir() { if metadata.is_dir() {
tracing::debug!( tracing::debug!(
"Creating directory `{}` already complete, skipping", "Creating directory `{}` already complete, skipping",
@ -51,14 +48,7 @@ impl CreateDirectory {
// TODO: Validate owner/group... // TODO: Validate owner/group...
ActionState::Skipped ActionState::Skipped
} else { } else {
return Err(CreateDirectoryError::Exists(std::io::Error::new( return Err(ActionError::Exists(path.to_owned()));
std::io::ErrorKind::AlreadyExists,
format!(
"Path `{}` already exists and is not directory",
path.display()
),
))
.boxed());
} }
} else { } else {
ActionState::Uncompleted ActionState::Uncompleted
@ -94,7 +84,7 @@ impl Action for CreateDirectory {
group = self.group, group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))), mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
path, path,
user, user,
@ -106,8 +96,8 @@ impl Action for CreateDirectory {
let gid = if let Some(group) = group { let gid = if let Some(group) = group {
Some( Some(
Group::from_name(group.as_str()) Group::from_name(group.as_str())
.map_err(|e| CreateDirectoryError::GroupId(group.clone(), e).boxed())? .map_err(|e| ActionError::GroupId(group.clone(), e))?
.ok_or(CreateDirectoryError::NoGroup(group.clone()).boxed())? .ok_or(ActionError::NoGroup(group.clone()))?
.gid, .gid,
) )
} else { } else {
@ -116,8 +106,8 @@ impl Action for CreateDirectory {
let uid = if let Some(user) = user { let uid = if let Some(user) = user {
Some( Some(
User::from_name(user.as_str()) User::from_name(user.as_str())
.map_err(|e| CreateDirectoryError::UserId(user.clone(), e).boxed())? .map_err(|e| ActionError::UserId(user.clone(), e))?
.ok_or(CreateDirectoryError::NoUser(user.clone()).boxed())? .ok_or(ActionError::NoUser(user.clone()))?
.uid, .uid,
) )
} else { } else {
@ -126,15 +116,13 @@ impl Action for CreateDirectory {
create_dir(path.clone()) create_dir(path.clone())
.await .await
.map_err(|e| CreateDirectoryError::Creating(path.clone(), e).boxed())?; .map_err(|e| ActionError::CreateDirectory(path.clone(), e))?;
chown(path, uid, gid).map_err(|e| CreateDirectoryError::Chown(path.clone(), e).boxed())?; chown(path, uid, gid).map_err(|e| ActionError::Chown(path.clone(), e))?;
if let Some(mode) = mode { if let Some(mode) = mode {
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode)) tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
.await .await
.map_err(|e| { .map_err(|e| ActionError::SetPermissions(*mode, path.to_owned(), e))?;
CreateDirectoryError::SetPermissions(*mode, path.to_owned(), e).boxed()
})?;
} }
Ok(()) Ok(())
@ -168,7 +156,7 @@ impl Action for CreateDirectory {
group = self.group, group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))), mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
path, path,
user: _, user: _,
@ -179,13 +167,13 @@ impl Action for CreateDirectory {
let is_empty = path let is_empty = path
.read_dir() .read_dir()
.map_err(|e| CreateDirectoryError::ReadDir(path.clone(), e).boxed())? .map_err(|e| ActionError::Read(path.clone(), e))?
.next() .next()
.is_some(); .is_some();
match (is_empty, force_prune_on_revert) { match (is_empty, force_prune_on_revert) {
(true, _) | (false, true) => remove_dir_all(path.clone()) (true, _) | (false, true) => remove_dir_all(path.clone())
.await .await
.map_err(|e| CreateDirectoryError::Removing(path.clone(), e).boxed())?, .map_err(|e| ActionError::Remove(path.clone(), e))?,
(false, false) => { (false, false) => {
tracing::debug!("Not removing `{}`, the folder is not empty", path.display()); tracing::debug!("Not removing `{}`, the folder is not empty", path.display());
}, },
@ -194,29 +182,3 @@ impl Action for CreateDirectory {
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateDirectoryError {
#[error(transparent)]
Exists(std::io::Error),
#[error("Creating directory `{0}`")]
Creating(std::path::PathBuf, #[source] std::io::Error),
#[error("Removing directory `{0}`")]
Removing(std::path::PathBuf, #[source] std::io::Error),
#[error("Getting metadata for {0}`")]
GettingMetadata(std::path::PathBuf, #[source] std::io::Error),
#[error("Reading directory `{0}``")]
ReadDir(std::path::PathBuf, #[source] std::io::Error),
#[error("Set mode `{0}` on `{1}`")]
SetPermissions(u32, std::path::PathBuf, #[source] std::io::Error),
#[error("Chowning directory `{0}`")]
Chown(std::path::PathBuf, #[source] nix::errno::Errno),
#[error("Getting uid for user `{0}`")]
UserId(String, #[source] nix::errno::Errno),
#[error("Getting user `{0}`")]
NoUser(String),
#[error("Getting gid for group `{0}`")]
GroupId(String, #[source] nix::errno::Errno),
#[error("Getting group `{0}`")]
NoGroup(String),
}

View file

@ -6,10 +6,7 @@ use tokio::{
io::AsyncWriteExt, io::AsyncWriteExt,
}; };
use crate::{ use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
action::{Action, ActionDescription, StatefulAction},
BoxableError,
};
/** Create a file at the given location with the provided `buf`, /** Create a file at the given location with the provided `buf`,
optionally with an owning user, group, and mode. optionally with an owning user, group, and mode.
@ -36,11 +33,11 @@ impl CreateFile {
mode: impl Into<Option<u32>>, mode: impl Into<Option<u32>>,
buf: String, buf: String,
force: bool, force: bool,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
if path.exists() && !force { if path.exists() && !force {
return Err(CreateFileError::Exists(path.to_path_buf()).boxed()); return Err(ActionError::Exists(path.to_path_buf()));
} }
Ok(Self { Ok(Self {
@ -71,7 +68,7 @@ impl Action for CreateFile {
group = self.group, group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))), mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
path, path,
user, user,
@ -91,17 +88,17 @@ impl Action for CreateFile {
let mut file = options let mut file = options
.open(&path) .open(&path)
.await .await
.map_err(|e| CreateFileError::OpenFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Open(path.to_owned(), e))?;
file.write_all(buf.as_bytes()) file.write_all(buf.as_bytes())
.await .await
.map_err(|e| CreateFileError::WriteFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Write(path.to_owned(), e))?;
let gid = if let Some(group) = group { let gid = if let Some(group) = group {
Some( Some(
Group::from_name(group.as_str()) Group::from_name(group.as_str())
.map_err(|e| CreateFileError::GroupId(group.clone(), e).boxed())? .map_err(|e| ActionError::GroupId(group.clone(), e))?
.ok_or(CreateFileError::NoGroup(group.clone()).boxed())? .ok_or(ActionError::NoGroup(group.clone()))?
.gid, .gid,
) )
} else { } else {
@ -110,14 +107,14 @@ impl Action for CreateFile {
let uid = if let Some(user) = user { let uid = if let Some(user) = user {
Some( Some(
User::from_name(user.as_str()) User::from_name(user.as_str())
.map_err(|e| CreateFileError::UserId(user.clone(), e).boxed())? .map_err(|e| ActionError::UserId(user.clone(), e))?
.ok_or(CreateFileError::NoUser(user.clone()).boxed())? .ok_or(ActionError::NoUser(user.clone()))?
.uid, .uid,
) )
} else { } else {
None None
}; };
chown(path, uid, gid).map_err(|e| CreateFileError::Chown(path.clone(), e).boxed())?; chown(path, uid, gid).map_err(|e| ActionError::Chown(path.clone(), e))?;
Ok(()) Ok(())
} }
@ -144,7 +141,7 @@ impl Action for CreateFile {
group = self.group, group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))), mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
path, path,
user: _, user: _,
@ -156,30 +153,8 @@ impl Action for CreateFile {
remove_file(&path) remove_file(&path)
.await .await
.map_err(|e| CreateFileError::RemoveFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Remove(path.to_owned(), e))?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateFileError {
#[error("File exists `{0}`")]
Exists(std::path::PathBuf),
#[error("Remove file `{0}`")]
RemoveFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Open file `{0}`")]
OpenFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Write file `{0}`")]
WriteFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Getting uid for user `{0}`")]
UserId(String, #[source] nix::errno::Errno),
#[error("Getting user `{0}`")]
NoUser(String),
#[error("Getting gid for group `{0}`")]
GroupId(String, #[source] nix::errno::Errno),
#[error("Getting group `{0}`")]
NoGroup(String),
#[error("Chowning directory `{0}`")]
Chown(std::path::PathBuf, #[source] nix::errno::Errno),
}

View file

@ -1,11 +1,9 @@
use tokio::process::Command; use tokio::process::Command;
use crate::action::ActionError;
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription, StatefulAction};
action::{Action, ActionDescription, StatefulAction},
BoxableError,
};
/** /**
Create an operating system level user group Create an operating system level user group
@ -43,7 +41,7 @@ impl Action for CreateGroup {
user = self.name, user = self.name,
gid = self.gid, gid = self.gid,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { name, gid } = self; let Self { name, gid } = self;
use target_lexicon::OperatingSystem; use target_lexicon::OperatingSystem;
@ -60,7 +58,8 @@ impl Action for CreateGroup {
.stdin(std::process::Stdio::null()) .stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.status() .status()
.await? .await
.map_err(ActionError::Command)?
.success() .success()
{ {
() ()
@ -80,7 +79,7 @@ impl Action for CreateGroup {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateGroupError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
} }
}, },
_ => { _ => {
@ -91,7 +90,7 @@ impl Action for CreateGroup {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateGroupError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
}, },
}; };
@ -112,7 +111,7 @@ impl Action for CreateGroup {
user = self.name, user = self.name,
gid = self.gid, gid = self.gid,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { name, gid: _ } = self; let Self { name, gid: _ } = self;
use target_lexicon::OperatingSystem; use target_lexicon::OperatingSystem;
@ -142,16 +141,10 @@ impl Action for CreateGroup {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateGroupError::Command(e).boxed())?; .map_err(ActionError::Command)?;
}, },
}; };
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateGroupError {
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
}

View file

@ -10,10 +10,7 @@ use tokio::{
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
}; };
use crate::{ use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
action::{Action, ActionDescription, StatefulAction},
BoxableError,
};
/** Create a file at the given location with the provided `buf`, /** Create a file at the given location with the provided `buf`,
optionally with an owning user, group, and mode. optionally with an owning user, group, and mode.
@ -40,7 +37,7 @@ impl CreateOrAppendFile {
group: impl Into<Option<String>>, group: impl Into<Option<String>>,
mode: impl Into<Option<u32>>, mode: impl Into<Option<u32>>,
buf: String, buf: String,
) -> Result<StatefulAction<Self>, CreateOrAppendFileError> { ) -> Result<StatefulAction<Self>, ActionError> {
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
Ok(Self { Ok(Self {
@ -71,7 +68,7 @@ impl Action for CreateOrAppendFile {
group = self.group, group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))), mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
path, path,
user, user,
@ -86,21 +83,21 @@ impl Action for CreateOrAppendFile {
.read(true) .read(true)
.open(&path) .open(&path)
.await .await
.map_err(|e| CreateOrAppendFileError::OpenFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Open(path.to_owned(), e))?;
file.seek(SeekFrom::End(0)) file.seek(SeekFrom::End(0))
.await .await
.map_err(|e| CreateOrAppendFileError::SeekFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Seek(path.to_owned(), e))?;
file.write_all(buf.as_bytes()) file.write_all(buf.as_bytes())
.await .await
.map_err(|e| CreateOrAppendFileError::WriteFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Write(path.to_owned(), e))?;
let gid = if let Some(group) = group { let gid = if let Some(group) = group {
Some( Some(
Group::from_name(group.as_str()) Group::from_name(group.as_str())
.map_err(|e| CreateOrAppendFileError::GroupId(group.clone(), e).boxed())? .map_err(|e| ActionError::GroupId(group.clone(), e))?
.ok_or(CreateOrAppendFileError::NoGroup(group.clone()).boxed())? .ok_or(ActionError::NoGroup(group.clone()))?
.gid, .gid,
) )
} else { } else {
@ -109,8 +106,8 @@ impl Action for CreateOrAppendFile {
let uid = if let Some(user) = user { let uid = if let Some(user) = user {
Some( Some(
User::from_name(user.as_str()) User::from_name(user.as_str())
.map_err(|e| CreateOrAppendFileError::UserId(user.clone(), e).boxed())? .map_err(|e| ActionError::UserId(user.clone(), e))?
.ok_or(CreateOrAppendFileError::NoUser(user.clone()).boxed())? .ok_or(ActionError::NoUser(user.clone()))?
.uid, .uid,
) )
} else { } else {
@ -120,13 +117,10 @@ impl Action for CreateOrAppendFile {
if let Some(mode) = mode { if let Some(mode) = mode {
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode)) tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
.await .await
.map_err(|e| { .map_err(|e| ActionError::SetPermissions(*mode, path.to_owned(), e))?;
CreateOrAppendFileError::SetPermissions(*mode, path.to_owned(), e).boxed()
})?;
} }
chown(path, uid, gid) chown(path, uid, gid).map_err(|e| ActionError::Chown(path.clone(), e))?;
.map_err(|e| CreateOrAppendFileError::Chown(path.clone(), e).boxed())?;
Ok(()) Ok(())
} }
@ -154,7 +148,7 @@ impl Action for CreateOrAppendFile {
group = self.group, group = self.group,
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))), mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
path, path,
user: _, user: _,
@ -168,12 +162,12 @@ impl Action for CreateOrAppendFile {
.read(true) .read(true)
.open(&path) .open(&path)
.await .await
.map_err(|e| CreateOrAppendFileError::ReadFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Read(path.to_owned(), e))?;
let mut file_contents = String::default(); let mut file_contents = String::default();
file.read_to_string(&mut file_contents) file.read_to_string(&mut file_contents)
.await .await
.map_err(|e| CreateOrAppendFileError::SeekFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Seek(path.to_owned(), e))?;
if let Some(start) = file_contents.rfind(buf.as_str()) { if let Some(start) = file_contents.rfind(buf.as_str()) {
let end = start + buf.len(); let end = start + buf.len();
@ -183,41 +177,15 @@ impl Action for CreateOrAppendFile {
if buf.is_empty() { if buf.is_empty() {
remove_file(&path) remove_file(&path)
.await .await
.map_err(|e| CreateOrAppendFileError::RemoveFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Remove(path.to_owned(), e))?;
} else { } else {
file.seek(SeekFrom::Start(0)) file.seek(SeekFrom::Start(0))
.await .await
.map_err(|e| CreateOrAppendFileError::SeekFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Seek(path.to_owned(), e))?;
file.write_all(file_contents.as_bytes()) file.write_all(file_contents.as_bytes())
.await .await
.map_err(|e| CreateOrAppendFileError::WriteFile(path.to_owned(), e).boxed())?; .map_err(|e| ActionError::Write(path.to_owned(), e))?;
} }
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateOrAppendFileError {
#[error("Remove file `{0}`")]
RemoveFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Remove file `{0}`")]
ReadFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Open file `{0}`")]
OpenFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Write file `{0}`")]
WriteFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Seek file `{0}`")]
SeekFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Getting uid for user `{0}`")]
UserId(String, #[source] nix::errno::Errno),
#[error("Getting user `{0}`")]
NoUser(String),
#[error("Getting gid for group `{0}`")]
GroupId(String, #[source] nix::errno::Errno),
#[error("Getting group `{0}`")]
NoGroup(String),
#[error("Set mode `{0}` on `{1}`")]
SetPermissions(u32, std::path::PathBuf, #[source] std::io::Error),
#[error("Chowning directory `{0}`")]
Chown(std::path::PathBuf, #[source] nix::errno::Errno),
}

View file

@ -1,11 +1,9 @@
use tokio::process::Command; use tokio::process::Command;
use crate::action::ActionError;
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription, StatefulAction};
action::{Action, ActionDescription, StatefulAction},
BoxableError,
};
/** /**
Create an operating system level user in the given group Create an operating system level user in the given group
@ -55,7 +53,7 @@ impl Action for CreateUser {
groupname = self.groupname, groupname = self.groupname,
gid = self.gid, gid = self.gid,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
name, name,
uid, uid,
@ -80,7 +78,8 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()) .stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.status() .status()
.await? .await
.map_err(ActionError::Command)?
.success() .success()
{ {
() ()
@ -92,7 +91,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("/usr/bin/dscl") Command::new("/usr/bin/dscl")
.process_group(0) .process_group(0)
@ -106,7 +105,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("/usr/bin/dscl") Command::new("/usr/bin/dscl")
.process_group(0) .process_group(0)
@ -120,7 +119,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("/usr/bin/dscl") Command::new("/usr/bin/dscl")
.process_group(0) .process_group(0)
@ -134,7 +133,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("/usr/bin/dscl") Command::new("/usr/bin/dscl")
.process_group(0) .process_group(0)
@ -148,7 +147,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("/usr/bin/dscl") Command::new("/usr/bin/dscl")
.process_group(0) .process_group(0)
@ -162,7 +161,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("/usr/bin/dscl") Command::new("/usr/bin/dscl")
.process_group(0) .process_group(0)
@ -170,7 +169,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("/usr/sbin/dseditgroup") Command::new("/usr/sbin/dseditgroup")
.process_group(0) .process_group(0)
@ -183,7 +182,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
} }
}, },
_ => { _ => {
@ -212,7 +211,7 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
}, },
} }
@ -236,7 +235,7 @@ impl Action for CreateUser {
uid = self.uid, uid = self.uid,
gid = self.gid, gid = self.gid,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
name, name,
uid: _, uid: _,
@ -271,16 +270,10 @@ impl Action for CreateUser {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateUserError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
}, },
}; };
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateUserError {
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
}

View file

@ -3,12 +3,7 @@ use std::path::PathBuf;
use bytes::Buf; use bytes::Buf;
use reqwest::Url; use reqwest::Url;
use tokio::task::JoinError; use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
use crate::{
action::{Action, ActionDescription, StatefulAction},
BoxableError,
};
/** /**
Fetch a URL to the given path Fetch a URL to the given path
@ -21,7 +16,7 @@ pub struct FetchAndUnpackNix {
impl FetchAndUnpackNix { impl FetchAndUnpackNix {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan(url: Url, dest: PathBuf) -> Result<StatefulAction<Self>, FetchUrlError> { pub async fn plan(url: Url, dest: PathBuf) -> Result<StatefulAction<Self>, ActionError> {
// TODO(@hoverbear): Check URL exists? // TODO(@hoverbear): Check URL exists?
// TODO(@hoverbear): Check tempdir exists // TODO(@hoverbear): Check tempdir exists
@ -44,16 +39,16 @@ impl Action for FetchAndUnpackNix {
url = %self.url, url = %self.url,
dest = %self.dest.display(), dest = %self.dest.display(),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { url, dest } = self; let Self { url, dest } = self;
let res = reqwest::get(url.clone()) let res = reqwest::get(url.clone())
.await .await
.map_err(|e| FetchUrlError::Reqwest(e).boxed())?; .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
let bytes = res let bytes = res
.bytes() .bytes()
.await .await
.map_err(|e| FetchUrlError::Reqwest(e).boxed())?; .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
// TODO(@Hoverbear): Pick directory // TODO(@Hoverbear): Pick directory
tracing::trace!("Unpacking tar.xz"); tracing::trace!("Unpacking tar.xz");
let dest_clone = dest.clone(); let dest_clone = dest.clone();
@ -62,7 +57,7 @@ impl Action for FetchAndUnpackNix {
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
archive archive
.unpack(&dest_clone) .unpack(&dest_clone)
.map_err(|e| FetchUrlError::Unarchive(e).boxed())?; .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Unarchive(e))))?;
Ok(()) Ok(())
} }
@ -75,7 +70,7 @@ impl Action for FetchAndUnpackNix {
url = %self.url, url = %self.url,
dest = %self.dest.display(), dest = %self.dest.display(),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { url: _, dest: _ } = self; let Self { url: _, dest: _ } = self;
Ok(()) Ok(())
@ -84,12 +79,6 @@ impl Action for FetchAndUnpackNix {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum FetchUrlError { pub enum FetchUrlError {
#[error("Joining spawned async task")]
Join(
#[source]
#[from]
JoinError,
),
#[error("Request error")] #[error("Request error")]
Reqwest( Reqwest(
#[from] #[from]

View file

@ -9,11 +9,11 @@ mod fetch_and_unpack_nix;
mod move_unpacked_nix; mod move_unpacked_nix;
mod setup_default_profile; mod setup_default_profile;
pub use create_directory::{CreateDirectory, CreateDirectoryError}; pub use create_directory::CreateDirectory;
pub use create_file::{CreateFile, CreateFileError}; pub use create_file::CreateFile;
pub use create_group::{CreateGroup, CreateGroupError}; pub use create_group::CreateGroup;
pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError}; pub use create_or_append_file::CreateOrAppendFile;
pub use create_user::{CreateUser, CreateUserError}; pub use create_user::CreateUser;
pub use fetch_and_unpack_nix::{FetchAndUnpackNix, FetchUrlError}; pub use fetch_and_unpack_nix::{FetchAndUnpackNix, FetchUrlError};
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError}; pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError}; pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};

View file

@ -1,9 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::{ use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
action::{Action, ActionDescription, StatefulAction},
BoxableError,
};
const DEST: &str = "/nix/store"; const DEST: &str = "/nix/store";
@ -17,7 +14,7 @@ pub struct MoveUnpackedNix {
impl MoveUnpackedNix { impl MoveUnpackedNix {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan(src: PathBuf) -> Result<StatefulAction<Self>, MoveUnpackedNixError> { pub async fn plan(src: PathBuf) -> Result<StatefulAction<Self>, ActionError> {
// Note: Do NOT try to check for the src/dest since the installer creates those // Note: Do NOT try to check for the src/dest since the installer creates those
Ok(Self { src }.into()) Ok(Self { src }.into())
} }
@ -44,14 +41,14 @@ impl Action for MoveUnpackedNix {
src = %self.src.display(), src = %self.src.display(),
dest = DEST, dest = DEST,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { src } = self; let Self { src } = self;
// TODO(@Hoverbear): I would like to make this less awful // TODO(@Hoverbear): I would like to make this less awful
let found_nix_paths = glob::glob(&format!("{}/nix-*", src.display())) let found_nix_paths = glob::glob(&format!("{}/nix-*", src.display()))
.map_err(|e| e.boxed())? .map_err(|e| ActionError::Custom(Box::new(e)))?
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.map_err(|e| e.boxed())?; .map_err(|e| ActionError::Custom(Box::new(e)))?;
assert_eq!( assert_eq!(
found_nix_paths.len(), found_nix_paths.len(),
1, 1,
@ -63,13 +60,11 @@ impl Action for MoveUnpackedNix {
tracing::trace!(src = %src_store.display(), dest = %dest.display(), "Renaming"); tracing::trace!(src = %src_store.display(), dest = %dest.display(), "Renaming");
tokio::fs::rename(src_store.clone(), dest) tokio::fs::rename(src_store.clone(), dest)
.await .await
.map_err(|e| { .map_err(|e| ActionError::Rename(src_store.clone(), dest.to_owned(), e))?;
MoveUnpackedNixError::Rename(src_store.clone(), dest.to_owned(), e).boxed()
})?;
tokio::fs::remove_dir_all(src) tokio::fs::remove_dir_all(src)
.await .await
.map_err(|e| MoveUnpackedNixError::Rename(src_store, dest.to_owned(), e).boxed())?; .map_err(|e| ActionError::Rename(src_store, dest.to_owned(), e))?;
Ok(()) Ok(())
} }
@ -82,7 +77,7 @@ impl Action for MoveUnpackedNix {
src = %self.src.display(), src = %self.src.display(),
dest = DEST, dest = DEST,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
// Noop // Noop
Ok(()) Ok(())
} }
@ -102,10 +97,4 @@ pub enum MoveUnpackedNixError {
#[source] #[source]
glob::GlobError, glob::GlobError,
), ),
#[error("Rename `{0}` to `{1}`")]
Rename(
std::path::PathBuf,
std::path::PathBuf,
#[source] std::io::Error,
),
} }

View file

@ -1,4 +1,7 @@
use crate::{action::StatefulAction, execute_command, set_env, BoxableError}; use crate::{
action::{ActionError, StatefulAction},
execute_command, set_env,
};
use glob::glob; use glob::glob;
@ -16,9 +19,7 @@ pub struct SetupDefaultProfile {
impl SetupDefaultProfile { impl SetupDefaultProfile {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(channels: Vec<String>) -> Result<StatefulAction<Self>, ActionError> {
channels: Vec<String>,
) -> Result<StatefulAction<Self>, SetupDefaultProfileError> {
Ok(Self { channels }.into()) Ok(Self { channels }.into())
} }
} }
@ -37,15 +38,15 @@ impl Action for SetupDefaultProfile {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
channels = %self.channels.join(","), channels = %self.channels.join(","),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { channels } = self; let Self { channels } = self;
// Find an `nix` package // Find an `nix` package
let nix_pkg_glob = "/nix/store/*-nix-*"; let nix_pkg_glob = "/nix/store/*-nix-*";
let mut found_nix_pkg = None; let mut found_nix_pkg = None;
for entry in for entry in glob(nix_pkg_glob).map_err(|e| {
glob(nix_pkg_glob).map_err(|e| SetupDefaultProfileError::GlobPatternError(e).boxed())? ActionError::Custom(Box::new(SetupDefaultProfileError::GlobPatternError(e)))
{ })? {
match entry { match entry {
Ok(path) => { Ok(path) => {
// TODO(@Hoverbear): Should probably ensure is unique // TODO(@Hoverbear): Should probably ensure is unique
@ -58,15 +59,17 @@ impl Action for SetupDefaultProfile {
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg { let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
nix_pkg nix_pkg
} else { } else {
return Err(Box::new(SetupDefaultProfileError::NoNssCacert)); // TODO(@hoverbear): Fix this error return Err(ActionError::Custom(Box::new(
SetupDefaultProfileError::NoNix,
)));
}; };
// Find an `nss-cacert` package, add it too. // Find an `nss-cacert` package, add it too.
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*"; let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
let mut found_nss_ca_cert_pkg = None; let mut found_nss_ca_cert_pkg = None;
for entry in glob(nss_ca_cert_pkg_glob) for entry in glob(nss_ca_cert_pkg_glob).map_err(|e| {
.map_err(|e| SetupDefaultProfileError::GlobPatternError(e).boxed())? ActionError::Custom(Box::new(SetupDefaultProfileError::GlobPatternError(e)))
{ })? {
match entry { match entry {
Ok(path) => { Ok(path) => {
// TODO(@Hoverbear): Should probably ensure is unique // TODO(@Hoverbear): Should probably ensure is unique
@ -79,7 +82,9 @@ impl Action for SetupDefaultProfile {
let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg { let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
nss_ca_cert_pkg nss_ca_cert_pkg
} else { } else {
return Err(Box::new(SetupDefaultProfileError::NoNssCacert)); return Err(ActionError::Custom(Box::new(
SetupDefaultProfileError::NoNssCacert,
)));
}; };
// Install `nix` itself into the store // Install `nix` itself into the store
@ -93,7 +98,9 @@ impl Action for SetupDefaultProfile {
.stdin(std::process::Stdio::null()) .stdin(std::process::Stdio::null())
.env( .env(
"HOME", "HOME",
dirs::home_dir().ok_or_else(|| SetupDefaultProfileError::NoRootHome.boxed())?, dirs::home_dir().ok_or_else(|| {
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
})?,
) )
.env( .env(
"NIX_SSL_CERT_FILE", "NIX_SSL_CERT_FILE",
@ -101,7 +108,7 @@ impl Action for SetupDefaultProfile {
), /* This is apparently load bearing... */ ), /* This is apparently load bearing... */
) )
.await .await
.map_err(|e| SetupDefaultProfileError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
// Install `nss-cacert` into the store // Install `nss-cacert` into the store
// execute_command( // execute_command(
@ -136,7 +143,7 @@ impl Action for SetupDefaultProfile {
execute_command(&mut command) execute_command(&mut command)
.await .await
.map_err(|e| SetupDefaultProfileError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
} }
Ok(()) Ok(())
@ -152,7 +159,7 @@ impl Action for SetupDefaultProfile {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
channels = %self.channels.join(","), channels = %self.channels.join(","),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
std::env::remove_var("NIX_SSL_CERT_FILE"); std::env::remove_var("NIX_SSL_CERT_FILE");
Ok(()) Ok(())
@ -175,8 +182,8 @@ pub enum SetupDefaultProfileError {
), ),
#[error("Unarchived Nix store did not appear to include a `nss-cacert` location")] #[error("Unarchived Nix store did not appear to include a `nss-cacert` location")]
NoNssCacert, NoNssCacert,
#[error("Failed to execute command")] #[error("Unarchived Nix store did not appear to include a `nix` location")]
Command(#[source] std::io::Error), NoNix,
#[error("No root home found to place channel configuration in")] #[error("No root home found to place channel configuration in")]
NoRootHome, NoRootHome,
} }

View file

@ -3,11 +3,10 @@ use crate::{
base::SetupDefaultProfile, base::SetupDefaultProfile,
common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration}, common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
linux::ConfigureNixDaemonService, linux::ConfigureNixDaemonService,
Action, ActionDescription, StatefulAction, Action, ActionDescription, ActionError, StatefulAction,
}, },
channel_value::ChannelValue, channel_value::ChannelValue,
settings::CommonSettings, settings::CommonSettings,
BoxableError,
}; };
use reqwest::Url; use reqwest::Url;
@ -26,9 +25,7 @@ pub struct ConfigureNix {
impl ConfigureNix { impl ConfigureNix {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
settings: &CommonSettings,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
let channels: Vec<(String, Url)> = settings let channels: Vec<(String, Url)> = settings
.channels .channels
.iter() .iter()
@ -36,9 +33,7 @@ impl ConfigureNix {
.collect(); .collect();
let setup_default_profile = let setup_default_profile =
SetupDefaultProfile::plan(channels.iter().map(|(v, _k)| v.clone()).collect()) SetupDefaultProfile::plan(channels.iter().map(|(v, _k)| v.clone()).collect()).await?;
.await
.map_err(|e| e.boxed())?;
let configure_shell_profile = if settings.modify_profile { let configure_shell_profile = if settings.modify_profile {
Some(ConfigureShellProfile::plan().await?) Some(ConfigureShellProfile::plan().await?)
@ -93,7 +88,7 @@ impl Action for ConfigureNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service, configure_nix_daemon_service,
@ -143,7 +138,7 @@ impl Action for ConfigureNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service, configure_nix_daemon_service,

View file

@ -1,9 +1,8 @@
use crate::action::base::{CreateOrAppendFile, CreateOrAppendFileError}; use crate::action::base::CreateOrAppendFile;
use crate::action::{Action, ActionDescription, StatefulAction}; use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
use crate::BoxableError;
use std::path::Path; use std::path::Path;
use tokio::task::{JoinError, JoinSet}; use tokio::task::JoinSet;
const PROFILE_TARGETS: &[&str] = &[ const PROFILE_TARGETS: &[&str] = &[
"/etc/bashrc", "/etc/bashrc",
@ -25,7 +24,7 @@ pub struct ConfigureShellProfile {
impl ConfigureShellProfile { impl ConfigureShellProfile {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
let mut create_or_append_files = Vec::default(); let mut create_or_append_files = Vec::default();
for profile_target in PROFILE_TARGETS { for profile_target in PROFILE_TARGETS {
let path = Path::new(profile_target); let path = Path::new(profile_target);
@ -42,11 +41,8 @@ impl ConfigureShellProfile {
# End Nix\n # End Nix\n
\n", \n",
); );
create_or_append_files.push( create_or_append_files
CreateOrAppendFile::plan(path, None, None, 0o0644, buf) .push(CreateOrAppendFile::plan(path, None, None, 0o0644, buf).await?);
.await
.map_err(|e| e.boxed())?,
);
} }
Ok(Self { Ok(Self {
@ -71,7 +67,7 @@ impl Action for ConfigureShellProfile {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_or_append_files, create_or_append_files,
} = self; } = self;
@ -83,10 +79,7 @@ impl Action for ConfigureShellProfile {
let mut create_or_append_file_clone = create_or_append_file.clone(); let mut create_or_append_file_clone = create_or_append_file.clone();
let _abort_handle = set.spawn(async move { let _abort_handle = set.spawn(async move {
create_or_append_file_clone.try_execute().await?; create_or_append_file_clone.try_execute().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok(( Result::<_, ActionError>::Ok((idx, create_or_append_file_clone))
idx,
create_or_append_file_clone,
))
}); });
} }
@ -95,8 +88,8 @@ impl Action for ConfigureShellProfile {
Ok(Ok((idx, create_or_append_file))) => { Ok(Ok((idx, create_or_append_file))) => {
create_or_append_files[idx] = create_or_append_file create_or_append_files[idx] = create_or_append_file
}, },
Ok(Err(e)) => errors.push(e), Ok(Err(e)) => errors.push(Box::new(e)),
Err(e) => return Err(e.boxed()), Err(e) => return Err(e.into()),
}; };
} }
@ -104,7 +97,7 @@ impl Action for ConfigureShellProfile {
if errors.len() == 1 { if errors.len() == 1 {
return Err(errors.into_iter().next().unwrap().into()); return Err(errors.into_iter().next().unwrap().into());
} else { } else {
return Err(ConfigureShellProfileError::MultipleCreateOrAppendFile(errors).boxed()); return Err(ActionError::Children(errors));
} }
} }
@ -119,22 +112,19 @@ impl Action for ConfigureShellProfile {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_or_append_files, create_or_append_files,
} = self; } = self;
let mut set = JoinSet::new(); let mut set = JoinSet::new();
let mut errors = Vec::default(); let mut errors: Vec<Box<ActionError>> = Vec::default();
for (idx, create_or_append_file) in create_or_append_files.iter().enumerate() { for (idx, create_or_append_file) in create_or_append_files.iter().enumerate() {
let mut create_or_append_file_clone = create_or_append_file.clone(); let mut create_or_append_file_clone = create_or_append_file.clone();
let _abort_handle = set.spawn(async move { let _abort_handle = set.spawn(async move {
create_or_append_file_clone.try_revert().await?; create_or_append_file_clone.try_revert().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok(( Result::<_, _>::Ok((idx, create_or_append_file_clone))
idx,
create_or_append_file_clone,
))
}); });
} }
@ -143,8 +133,8 @@ impl Action for ConfigureShellProfile {
Ok(Ok((idx, create_or_append_file))) => { Ok(Ok((idx, create_or_append_file))) => {
create_or_append_files[idx] = create_or_append_file create_or_append_files[idx] = create_or_append_file
}, },
Ok(Err(e)) => errors.push(e), Ok(Err(e)) => errors.push(Box::new(e)),
Err(e) => return Err(e.boxed()), Err(e) => return Err(e.into()),
}; };
} }
@ -152,34 +142,10 @@ impl Action for ConfigureShellProfile {
if errors.len() == 1 { if errors.len() == 1 {
return Err(errors.into_iter().next().unwrap().into()); return Err(errors.into_iter().next().unwrap().into());
} else { } else {
return Err(ConfigureShellProfileError::MultipleCreateOrAppendFile(errors).boxed()); return Err(ActionError::Children(errors));
} }
} }
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum ConfigureShellProfileError {
#[error("Creating or appending to file")]
CreateOrAppendFile(
#[from]
#[source]
CreateOrAppendFileError,
),
#[error("Multiple errors: {}", .0.iter().map(|v| {
if let Some(source) = v.source() {
format!("{v} ({source})")
} else {
format!("{v}")
}
}).collect::<Vec<_>>().join(" & "))]
MultipleCreateOrAppendFile(Vec<Box<dyn std::error::Error + Send + Sync>>),
#[error("Joining spawned async task")]
Join(
#[source]
#[from]
JoinError,
),
}

View file

@ -1,5 +1,5 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError}; use crate::action::base::CreateDirectory;
use crate::action::{Action, ActionDescription, StatefulAction}; use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
const PATHS: &[&str] = &[ const PATHS: &[&str] = &[
"/nix/var", "/nix/var",
@ -27,7 +27,7 @@ pub struct CreateNixTree {
impl CreateNixTree { impl CreateNixTree {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
let mut create_directories = Vec::default(); let mut create_directories = Vec::default();
for path in PATHS { for path in PATHS {
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right // We use `create_dir` over `create_dir_all` to ensure we always set permissions right
@ -65,7 +65,7 @@ impl Action for CreateNixTree {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { create_directories } = self; let Self { create_directories } = self;
// Just do sequential since parallelizing this will have little benefit // Just do sequential since parallelizing this will have little benefit
@ -97,7 +97,7 @@ impl Action for CreateNixTree {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { create_directories } = self; let Self { create_directories } = self;
// Just do sequential since parallelizing this will have little benefit // Just do sequential since parallelizing this will have little benefit
@ -108,13 +108,3 @@ impl Action for CreateNixTree {
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateNixTreeError {
#[error("Creating directory")]
CreateDirectory(
#[source]
#[from]
CreateDirectoryError,
),
}

View file

@ -1,12 +1,11 @@
use crate::{ use crate::{
action::{ action::{
base::{CreateGroup, CreateGroupError, CreateUser, CreateUserError}, base::{CreateGroup, CreateUser},
Action, ActionDescription, StatefulAction, Action, ActionDescription, ActionError, StatefulAction,
}, },
settings::CommonSettings, settings::CommonSettings,
BoxableError,
}; };
use tokio::task::{JoinError, JoinSet}; use tokio::task::JoinSet;
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUsersAndGroups { pub struct CreateUsersAndGroups {
@ -21,9 +20,7 @@ pub struct CreateUsersAndGroups {
impl CreateUsersAndGroups { impl CreateUsersAndGroups {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(settings: CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
settings: CommonSettings,
) -> Result<StatefulAction<Self>, CreateUsersAndGroupsError> {
// TODO(@hoverbear): CHeck if it exist, error if so // TODO(@hoverbear): CHeck if it exist, error if so
let create_group = CreateGroup::plan( let create_group = CreateGroup::plan(
settings.nix_build_group_name.clone(), settings.nix_build_group_name.clone(),
@ -101,7 +98,7 @@ impl Action for CreateUsersAndGroups {
nix_build_user_prefix = self.nix_build_user_prefix, nix_build_user_prefix = self.nix_build_user_prefix,
nix_build_user_id_base = self.nix_build_user_id_base, nix_build_user_id_base = self.nix_build_user_id_base,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_users, create_users,
create_group, create_group,
@ -130,7 +127,7 @@ impl Action for CreateUsersAndGroups {
}, },
_ => { _ => {
let mut set = JoinSet::new(); let mut set = JoinSet::new();
let mut errors: Vec<Box<dyn std::error::Error + Send + Sync>> = Vec::new(); let mut errors: Vec<Box<ActionError>> = Vec::new();
for (idx, create_user) in create_users.iter_mut().enumerate() { for (idx, create_user) in create_users.iter_mut().enumerate() {
let mut create_user_clone = create_user.clone(); let mut create_user_clone = create_user.clone();
let _abort_handle = set.spawn(async move { let _abort_handle = set.spawn(async move {
@ -142,8 +139,8 @@ impl Action for CreateUsersAndGroups {
while let Some(result) = set.join_next().await { while let Some(result) = set.join_next().await {
match result { match result {
Ok(Ok((idx, success))) => create_users[idx] = success, Ok(Ok((idx, success))) => create_users[idx] = success,
Ok(Err(e)) => errors.push(e), Ok(Err(e)) => errors.push(Box::new(e)),
Err(e) => return Err(e)?, Err(e) => return Err(ActionError::Join(e))?,
}; };
} }
@ -151,7 +148,7 @@ impl Action for CreateUsersAndGroups {
if errors.len() == 1 { if errors.len() == 1 {
return Err(errors.into_iter().next().unwrap().into()); return Err(errors.into_iter().next().unwrap().into());
} else { } else {
return Err(CreateUsersAndGroupsError::CreateUsers(errors).boxed()); return Err(ActionError::Children(errors));
} }
} }
}, },
@ -198,7 +195,7 @@ impl Action for CreateUsersAndGroups {
nix_build_user_prefix = self.nix_build_user_prefix, nix_build_user_prefix = self.nix_build_user_prefix,
nix_build_user_id_base = self.nix_build_user_id_base, nix_build_user_id_base = self.nix_build_user_id_base,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_users, create_users,
create_group, create_group,
@ -216,15 +213,15 @@ impl Action for CreateUsersAndGroups {
let mut create_user_clone = create_user.clone(); let mut create_user_clone = create_user.clone();
let _abort_handle = set.spawn(async move { let _abort_handle = set.spawn(async move {
create_user_clone.try_revert().await?; create_user_clone.try_revert().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok((idx, create_user_clone)) Result::<_, ActionError>::Ok((idx, create_user_clone))
}); });
} }
while let Some(result) = set.join_next().await { while let Some(result) = set.join_next().await {
match result { match result {
Ok(Ok((idx, success))) => create_users[idx] = success, Ok(Ok((idx, success))) => create_users[idx] = success,
Ok(Err(e)) => errors.push(e), Ok(Err(e)) => errors.push(Box::new(e)),
Err(e) => return Err(e.boxed())?, Err(e) => return Err(ActionError::Join(e))?,
}; };
} }
@ -232,7 +229,7 @@ impl Action for CreateUsersAndGroups {
if errors.len() == 1 { if errors.len() == 1 {
return Err(errors.into_iter().next().unwrap().into()); return Err(errors.into_iter().next().unwrap().into());
} else { } else {
return Err(CreateUsersAndGroupsError::CreateUsers(errors).boxed()); return Err(ActionError::Children(errors));
} }
} }
@ -242,33 +239,3 @@ impl Action for CreateUsersAndGroups {
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateUsersAndGroupsError {
#[error("Creating user")]
CreateUser(
#[source]
#[from]
CreateUserError,
),
#[error("Multiple errors: {}", .0.iter().map(|v| {
if let Some(source) = v.source() {
format!("{v} ({source})")
} else {
format!("{v}")
}
}).collect::<Vec<_>>().join(" & "))]
CreateUsers(Vec<Box<dyn std::error::Error + Send + Sync>>),
#[error("Creating group")]
CreateGroup(
#[source]
#[from]
CreateGroupError,
),
#[error("Joining spawned async task")]
Join(
#[source]
#[from]
JoinError,
),
}

View file

@ -10,8 +10,8 @@ mod provision_nix;
pub use configure_nix::ConfigureNix; pub use configure_nix::ConfigureNix;
pub use configure_shell_profile::ConfigureShellProfile; pub use configure_shell_profile::ConfigureShellProfile;
pub use create_nix_tree::{CreateNixTree, CreateNixTreeError}; pub use create_nix_tree::CreateNixTree;
pub use create_users_and_groups::{CreateUsersAndGroups, CreateUsersAndGroupsError}; pub use create_users_and_groups::CreateUsersAndGroups;
pub use place_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError}; pub use place_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError};
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError}; pub use place_nix_configuration::PlaceNixConfiguration;
pub use provision_nix::{ProvisionNix, ProvisionNixError}; pub use provision_nix::ProvisionNix;

View file

@ -1,8 +1,6 @@
use crate::action::base::{CreateFile, CreateFileError}; use crate::action::base::CreateFile;
use crate::{ use crate::action::ActionError;
action::{Action, ActionDescription, StatefulAction}, use crate::action::{Action, ActionDescription, StatefulAction};
BoxableError,
};
use reqwest::Url; use reqwest::Url;
/** /**
@ -19,7 +17,7 @@ impl PlaceChannelConfiguration {
pub async fn plan( pub async fn plan(
channels: Vec<(String, Url)>, channels: Vec<(String, Url)>,
force: bool, force: bool,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
let buf = channels let buf = channels
.iter() .iter()
.map(|(name, url)| format!("{} {}", url, name)) .map(|(name, url)| format!("{} {}", url, name))
@ -27,7 +25,9 @@ impl PlaceChannelConfiguration {
.join("\n"); .join("\n");
let create_file = CreateFile::plan( let create_file = CreateFile::plan(
dirs::home_dir() dirs::home_dir()
.ok_or_else(|| PlaceChannelConfigurationError::NoRootHome.boxed())? .ok_or_else(|| {
ActionError::Custom(Box::new(PlaceChannelConfigurationError::NoRootHome))
})?
.join(".nix-channels"), .join(".nix-channels"),
None, None,
None, None,
@ -61,7 +61,7 @@ impl Action for PlaceChannelConfiguration {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "), channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_file, create_file,
channels: _, channels: _,
@ -85,7 +85,7 @@ impl Action for PlaceChannelConfiguration {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "), channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_file, create_file,
channels: _, channels: _,
@ -99,12 +99,6 @@ impl Action for PlaceChannelConfiguration {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum PlaceChannelConfigurationError { pub enum PlaceChannelConfigurationError {
#[error("Creating file")]
CreateFile(
#[source]
#[from]
CreateFileError,
),
#[error("No root home found to place channel configuration in")] #[error("No root home found to place channel configuration in")]
NoRootHome, NoRootHome,
} }

View file

@ -1,5 +1,5 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError}; use crate::action::base::{CreateDirectory, CreateFile};
use crate::action::{Action, ActionDescription, StatefulAction}; use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
const NIX_CONF_FOLDER: &str = "/etc/nix"; const NIX_CONF_FOLDER: &str = "/etc/nix";
const NIX_CONF: &str = "/etc/nix/nix.conf"; const NIX_CONF: &str = "/etc/nix/nix.conf";
@ -19,7 +19,7 @@ impl PlaceNixConfiguration {
nix_build_group_name: String, nix_build_group_name: String,
extra_conf: Vec<String>, extra_conf: Vec<String>,
force: bool, force: bool,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
let buf = format!( let buf = format!(
"\ "\
{extra_conf}\n\ {extra_conf}\n\
@ -61,7 +61,7 @@ impl Action for PlaceNixConfiguration {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_file, create_file,
create_directory, create_directory,
@ -84,7 +84,7 @@ impl Action for PlaceNixConfiguration {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
create_file, create_file,
create_directory, create_directory,
@ -96,19 +96,3 @@ impl Action for PlaceNixConfiguration {
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum PlaceNixConfigurationError {
#[error("Creating file")]
CreateFile(
#[source]
#[from]
CreateFileError,
),
#[error("Creating directory")]
CreateDirectory(
#[source]
#[from]
CreateDirectoryError,
),
}

View file

@ -1,18 +1,12 @@
use super::{CreateNixTree, CreateUsersAndGroups};
use crate::{ use crate::{
action::{ action::{
base::{ base::{FetchAndUnpackNix, MoveUnpackedNix},
CreateDirectoryError, FetchAndUnpackNix, FetchUrlError, MoveUnpackedNix, Action, ActionDescription, ActionError, StatefulAction,
MoveUnpackedNixError,
},
Action, ActionDescription, StatefulAction,
}, },
settings::CommonSettings, settings::CommonSettings,
BoxableError,
}; };
use std::path::PathBuf; use std::path::PathBuf;
use tokio::task::JoinError;
use super::{CreateNixTree, CreateNixTreeError, CreateUsersAndGroups, CreateUsersAndGroupsError};
/** /**
Place Nix and it's requirements onto the target Place Nix and it's requirements onto the target
@ -27,22 +21,16 @@ pub struct ProvisionNix {
impl ProvisionNix { impl ProvisionNix {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
settings: &CommonSettings,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
let fetch_nix = FetchAndUnpackNix::plan( let fetch_nix = FetchAndUnpackNix::plan(
settings.nix_package_url.clone(), settings.nix_package_url.clone(),
PathBuf::from("/nix/temp-install-dir"), PathBuf::from("/nix/temp-install-dir"),
) )
.await .await?;
.map_err(|e| e.boxed())?; let create_users_and_group = CreateUsersAndGroups::plan(settings.clone()).await?;
let create_users_and_group = CreateUsersAndGroups::plan(settings.clone())
.await
.map_err(|e| e.boxed())?;
let create_nix_tree = CreateNixTree::plan().await?; let create_nix_tree = CreateNixTree::plan().await?;
let move_unpacked_nix = MoveUnpackedNix::plan(PathBuf::from("/nix/temp-install-dir")) let move_unpacked_nix =
.await MoveUnpackedNix::plan(PathBuf::from("/nix/temp-install-dir")).await?;
.map_err(|e| e.boxed())?;
Ok(Self { Ok(Self {
fetch_nix, fetch_nix,
create_users_and_group, create_users_and_group,
@ -78,7 +66,7 @@ impl Action for ProvisionNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
fetch_nix, fetch_nix,
create_nix_tree, create_nix_tree,
@ -90,13 +78,13 @@ impl Action for ProvisionNix {
let mut fetch_nix_clone = fetch_nix.clone(); let mut fetch_nix_clone = fetch_nix.clone();
let fetch_nix_handle = tokio::task::spawn(async { let fetch_nix_handle = tokio::task::spawn(async {
fetch_nix_clone.try_execute().await?; fetch_nix_clone.try_execute().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok(fetch_nix_clone) Result::<_, ActionError>::Ok(fetch_nix_clone)
}); });
create_users_and_group.try_execute().await?; create_users_and_group.try_execute().await?;
create_nix_tree.try_execute().await?; create_nix_tree.try_execute().await?;
*fetch_nix = fetch_nix_handle.await.map_err(|e| e.boxed())??; *fetch_nix = fetch_nix_handle.await.map_err(ActionError::Join)??;
move_unpacked_nix.try_execute().await?; move_unpacked_nix.try_execute().await?;
Ok(()) Ok(())
@ -119,7 +107,7 @@ impl Action for ProvisionNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
fetch_nix, fetch_nix,
create_nix_tree, create_nix_tree,
@ -131,7 +119,7 @@ impl Action for ProvisionNix {
let mut fetch_nix_clone = fetch_nix.clone(); let mut fetch_nix_clone = fetch_nix.clone();
let fetch_nix_handle = tokio::task::spawn(async { let fetch_nix_handle = tokio::task::spawn(async {
fetch_nix_clone.try_revert().await?; fetch_nix_clone.try_revert().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok(fetch_nix_clone) Result::<_, ActionError>::Ok(fetch_nix_clone)
}); });
if let Err(err) = create_users_and_group.try_revert().await { if let Err(err) = create_users_and_group.try_revert().await {
@ -143,49 +131,9 @@ impl Action for ProvisionNix {
return Err(err); return Err(err);
} }
*fetch_nix = fetch_nix_handle.await.map_err(|e| e.boxed())??; *fetch_nix = fetch_nix_handle.await.map_err(ActionError::Join)??;
move_unpacked_nix.try_revert().await?; move_unpacked_nix.try_revert().await?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum ProvisionNixError {
#[error("Fetching Nix")]
FetchNix(
#[source]
#[from]
FetchUrlError,
),
#[error("Joining spawned async task")]
Join(
#[source]
#[from]
JoinError,
),
#[error("Creating directory")]
CreateDirectory(
#[source]
#[from]
CreateDirectoryError,
),
#[error("Creating users and group")]
CreateUsersAndGroup(
#[source]
#[from]
CreateUsersAndGroupsError,
),
#[error("Creating nix tree")]
CreateNixTree(
#[source]
#[from]
CreateNixTreeError,
),
#[error("Moving unpacked nix")]
MoveUnpackedNix(
#[source]
#[from]
MoveUnpackedNixError,
),
}

View file

@ -2,13 +2,10 @@ use std::path::{Path, PathBuf};
use tokio::process::Command; use tokio::process::Command;
use crate::action::StatefulAction; use crate::action::{ActionError, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription};
action::{Action, ActionDescription},
BoxableError,
};
/** /**
Bootstrap and kickstart an APFS volume Bootstrap and kickstart an APFS volume
@ -20,9 +17,7 @@ pub struct BootstrapApfsVolume {
impl BootstrapApfsVolume { impl BootstrapApfsVolume {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(path: impl AsRef<Path>) -> Result<StatefulAction<Self>, ActionError> {
path: impl AsRef<Path>,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
Ok(Self { Ok(Self {
path: path.as_ref().to_path_buf(), path: path.as_ref().to_path_buf(),
} }
@ -44,7 +39,7 @@ impl Action for BootstrapApfsVolume {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %self.path.display(), path = %self.path.display(),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { path } = self; let Self { path } = self;
execute_command( execute_command(
@ -55,7 +50,7 @@ impl Action for BootstrapApfsVolume {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
execute_command( execute_command(
Command::new("launchctl") Command::new("launchctl")
.process_group(0) .process_group(0)
@ -63,7 +58,7 @@ impl Action for BootstrapApfsVolume {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
Ok(()) Ok(())
} }
@ -78,7 +73,7 @@ impl Action for BootstrapApfsVolume {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %self.path.display(), path = %self.path.display(),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { path } = self; let Self { path } = self;
execute_command( execute_command(
@ -89,7 +84,7 @@ impl Action for BootstrapApfsVolume {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
Ok(()) Ok(())
} }

View file

@ -2,13 +2,10 @@ use std::path::{Path, PathBuf};
use tokio::process::Command; use tokio::process::Command;
use crate::action::StatefulAction; use crate::action::{ActionError, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription};
action::{Action, ActionDescription},
BoxableError,
};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateApfsVolume { pub struct CreateApfsVolume {
@ -23,7 +20,7 @@ impl CreateApfsVolume {
disk: impl AsRef<Path>, disk: impl AsRef<Path>,
name: String, name: String,
case_sensitive: bool, case_sensitive: bool,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
Ok(Self { Ok(Self {
disk: disk.as_ref().to_path_buf(), disk: disk.as_ref().to_path_buf(),
name, name,
@ -53,7 +50,7 @@ impl Action for CreateApfsVolume {
name = %self.name, name = %self.name,
case_sensitive = %self.case_sensitive, case_sensitive = %self.case_sensitive,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
disk, disk,
name, name,
@ -78,7 +75,7 @@ impl Action for CreateApfsVolume {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
Ok(()) Ok(())
} }
@ -99,7 +96,7 @@ impl Action for CreateApfsVolume {
name = %self.name, name = %self.name,
case_sensitive = %self.case_sensitive, case_sensitive = %self.case_sensitive,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
disk: _, disk: _,
name, name,
@ -113,7 +110,7 @@ impl Action for CreateApfsVolume {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| CreateVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
Ok(()) Ok(())
} }

View file

@ -1,14 +1,10 @@
use crate::{ use crate::action::{
action::{ base::{CreateFile, CreateOrAppendFile},
base::{CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError},
darwin::{ darwin::{
BootstrapApfsVolume, BootstrapVolumeError, CreateApfsVolume, CreateSyntheticObjects, BootstrapApfsVolume, CreateApfsVolume, CreateSyntheticObjects, EnableOwnership,
CreateSyntheticObjectsError, CreateVolumeError, EnableOwnership, EnableOwnershipError, EncryptApfsVolume, UnmountApfsVolume,
EncryptApfsVolume, EncryptVolumeError, UnmountApfsVolume, UnmountVolumeError,
}, },
Action, ActionDescription, StatefulAction, Action, ActionDescription, ActionError, StatefulAction,
},
BoxableError,
}; };
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -43,7 +39,7 @@ impl CreateNixVolume {
name: String, name: String,
case_sensitive: bool, case_sensitive: bool,
encrypt: bool, encrypt: bool,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
let disk = disk.as_ref(); let disk = disk.as_ref();
let create_or_append_synthetic_conf = CreateOrAppendFile::plan( let create_or_append_synthetic_conf = CreateOrAppendFile::plan(
"/etc/synthetic.conf", "/etc/synthetic.conf",
@ -53,7 +49,7 @@ impl CreateNixVolume {
"nix\n".into(), /* The newline is required otherwise it segfaults */ "nix\n".into(), /* The newline is required otherwise it segfaults */
) )
.await .await
.map_err(|e| e.boxed())?; .map_err(|e| ActionError::Child(Box::new(e)))?;
let create_synthetic_objects = CreateSyntheticObjects::plan().await?; let create_synthetic_objects = CreateSyntheticObjects::plan().await?;
@ -69,7 +65,7 @@ impl CreateNixVolume {
format!("NAME=\"{name}\" /nix apfs rw,noauto,nobrowse,suid,owners"), format!("NAME=\"{name}\" /nix apfs rw,noauto,nobrowse,suid,owners"),
) )
.await .await
.map_err(|e| e.boxed())?; .map_err(|e| ActionError::Child(Box::new(e)))?;
let encrypt_volume = if encrypt { let encrypt_volume = if encrypt {
Some(EncryptApfsVolume::plan(disk, &name).await?) Some(EncryptApfsVolume::plan(disk, &name).await?)
@ -155,7 +151,7 @@ impl Action for CreateNixVolume {
} }
#[tracing::instrument(skip_all, fields(destination,))] #[tracing::instrument(skip_all, fields(destination,))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
disk: _, disk: _,
name: _, name: _,
@ -193,7 +189,7 @@ impl Action for CreateNixVolume {
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.status() .status()
.await .await
.map_err(|e| CreateApfsVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
if status.success() || retry_tokens == 0 { if status.success() || retry_tokens == 0 {
break; break;
} else { } else {
@ -218,7 +214,7 @@ impl Action for CreateNixVolume {
} }
#[tracing::instrument(skip_all, fields(disk, name))] #[tracing::instrument(skip_all, fields(disk, name))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
disk: _, disk: _,
name: _, name: _,
@ -253,25 +249,3 @@ impl Action for CreateNixVolume {
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum CreateApfsVolumeError {
#[error(transparent)]
CreateFile(#[from] CreateFileError),
#[error(transparent)]
DarwinBootstrapVolume(#[from] BootstrapVolumeError),
#[error(transparent)]
DarwinCreateSyntheticObjects(#[from] CreateSyntheticObjectsError),
#[error(transparent)]
DarwinCreateVolume(#[from] CreateVolumeError),
#[error(transparent)]
DarwinEnableOwnership(#[from] EnableOwnershipError),
#[error(transparent)]
DarwinEncryptVolume(#[from] EncryptVolumeError),
#[error(transparent)]
DarwinUnmountVolume(#[from] UnmountVolumeError),
#[error(transparent)]
CreateOrAppendFile(#[from] CreateOrAppendFileError),
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
}

View file

@ -2,7 +2,7 @@ use tokio::process::Command;
use crate::execute_command; use crate::execute_command;
use crate::action::{Action, ActionDescription, StatefulAction}; use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
/// Create the synthetic objects defined in `/etc/syntethic.conf` /// Create the synthetic objects defined in `/etc/syntethic.conf`
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
@ -10,7 +10,7 @@ pub struct CreateSyntheticObjects;
impl CreateSyntheticObjects { impl CreateSyntheticObjects {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
Ok(Self.into()) Ok(Self.into())
} }
} }
@ -30,7 +30,7 @@ impl Action for CreateSyntheticObjects {
} }
#[tracing::instrument(skip_all, fields())] #[tracing::instrument(skip_all, fields())]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261 // Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
execute_command( execute_command(
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util") Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
@ -60,7 +60,7 @@ impl Action for CreateSyntheticObjects {
} }
#[tracing::instrument(skip_all, fields())] #[tracing::instrument(skip_all, fields())]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261 // Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
execute_command( execute_command(
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util") Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")

View file

@ -3,14 +3,11 @@ use std::path::{Path, PathBuf};
use tokio::process::Command; use tokio::process::Command;
use crate::action::StatefulAction; use crate::action::{ActionError, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::action::{Action, ActionDescription};
use crate::os::darwin::DiskUtilOutput; use crate::os::darwin::DiskUtilOutput;
use crate::{
action::{Action, ActionDescription},
BoxableError,
};
/** /**
Enable ownership on a volume Enable ownership on a volume
@ -22,9 +19,7 @@ pub struct EnableOwnership {
impl EnableOwnership { impl EnableOwnership {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(path: impl AsRef<Path>) -> Result<StatefulAction<Self>, ActionError> {
path: impl AsRef<Path>,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
Ok(Self { Ok(Self {
path: path.as_ref().to_path_buf(), path: path.as_ref().to_path_buf(),
} }
@ -46,7 +41,7 @@ impl Action for EnableOwnership {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %self.path.display(), path = %self.path.display(),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { path } = self; let Self { path } = self;
let should_enable_ownership = { let should_enable_ownership = {
@ -57,7 +52,8 @@ impl Action for EnableOwnership {
.arg(&path) .arg(&path)
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await? .await
.map_err(ActionError::Command)?
.stdout; .stdout;
let the_plist: DiskUtilOutput = plist::from_reader(Cursor::new(buf)).unwrap(); let the_plist: DiskUtilOutput = plist::from_reader(Cursor::new(buf)).unwrap();
@ -73,7 +69,7 @@ impl Action for EnableOwnership {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| EnableOwnershipError::Command(e).boxed())?; .map_err(ActionError::Command)?;
} }
Ok(()) Ok(())
@ -86,7 +82,7 @@ impl Action for EnableOwnership {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %self.path.display(), path = %self.path.display(),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
// noop // noop
Ok(()) Ok(())
} }

View file

@ -1,5 +1,7 @@
use crate::{ use crate::{
action::{darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, StatefulAction}, action::{
darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionError, StatefulAction,
},
execute_command, execute_command,
}; };
use rand::Rng; use rand::Rng;
@ -20,7 +22,7 @@ impl EncryptApfsVolume {
pub async fn plan( pub async fn plan(
disk: impl AsRef<Path>, disk: impl AsRef<Path>,
name: impl AsRef<str>, name: impl AsRef<str>,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
let name = name.as_ref().to_owned(); let name = name.as_ref().to_owned();
Ok(Self { Ok(Self {
name, name,
@ -48,7 +50,7 @@ impl Action for EncryptApfsVolume {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
disk = %self.disk.display(), disk = %self.disk.display(),
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { disk, name } = self; let Self { disk, name } = self;
// Generate a random password. // Generate a random password.
@ -69,7 +71,9 @@ impl Action for EncryptApfsVolume {
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */ let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name)).await?; execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name))
.await
.map_err(ActionError::Command)?;
// Add the password to the user keychain so they can unlock it later. // Add the password to the user keychain so they can unlock it later.
execute_command( execute_command(
@ -99,7 +103,8 @@ impl Action for EncryptApfsVolume {
"/Library/Keychains/System.keychain", "/Library/Keychains/System.keychain",
]), ]),
) )
.await?; .await
.map_err(ActionError::Command)?;
// Encrypt the mounted volume // Encrypt the mounted volume
execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([ execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([
@ -111,7 +116,8 @@ impl Action for EncryptApfsVolume {
"-passphrase", "-passphrase",
password.as_str(), password.as_str(),
])) ]))
.await?; .await
.map_err(ActionError::Command)?;
execute_command( execute_command(
Command::new("/usr/sbin/diskutil") Command::new("/usr/sbin/diskutil")
@ -120,7 +126,8 @@ impl Action for EncryptApfsVolume {
.arg("force") .arg("force")
.arg(&name), .arg(&name),
) )
.await?; .await
.map_err(ActionError::Command)?;
Ok(()) Ok(())
} }
@ -138,7 +145,7 @@ impl Action for EncryptApfsVolume {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
disk = %self.disk.display(), disk = %self.disk.display(),
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { disk, name } = self; let Self { disk, name } = self;
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */ let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
@ -162,14 +169,9 @@ impl Action for EncryptApfsVolume {
.as_str(), .as_str(),
]), ]),
) )
.await?; .await
.map_err(ActionError::Command)?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum EncryptVolumeError {
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
}

View file

@ -1,12 +1,9 @@
use tokio::process::Command; use tokio::process::Command;
use crate::action::StatefulAction; use crate::action::{ActionError, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription};
action::{Action, ActionDescription},
BoxableError,
};
/** /**
Kickstart a `launchctl` service Kickstart a `launchctl` service
@ -18,9 +15,7 @@ pub struct KickstartLaunchctlService {
impl KickstartLaunchctlService { impl KickstartLaunchctlService {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(unit: String) -> Result<StatefulAction<Self>, ActionError> {
unit: String,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
Ok(Self { unit }.into()) Ok(Self { unit }.into())
} }
} }
@ -40,7 +35,7 @@ impl Action for KickstartLaunchctlService {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
unit = %self.unit, unit = %self.unit,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { unit } = self; let Self { unit } = self;
execute_command( execute_command(
@ -52,7 +47,7 @@ impl Action for KickstartLaunchctlService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| KickstartLaunchctlServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
Ok(()) Ok(())
} }
@ -64,14 +59,8 @@ impl Action for KickstartLaunchctlService {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
unit = %self.unit, unit = %self.unit,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
// noop // noop
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum KickstartLaunchctlServiceError {
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
}

View file

@ -12,9 +12,9 @@ mod unmount_apfs_volume;
pub use bootstrap_apfs_volume::{BootstrapApfsVolume, BootstrapVolumeError}; pub use bootstrap_apfs_volume::{BootstrapApfsVolume, BootstrapVolumeError};
pub use create_apfs_volume::{CreateApfsVolume, CreateVolumeError}; pub use create_apfs_volume::{CreateApfsVolume, CreateVolumeError};
pub use create_nix_volume::{CreateApfsVolumeError, CreateNixVolume, NIX_VOLUME_MOUNTD_DEST}; pub use create_nix_volume::{CreateNixVolume, NIX_VOLUME_MOUNTD_DEST};
pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError}; pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError};
pub use enable_ownership::{EnableOwnership, EnableOwnershipError}; pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
pub use encrypt_apfs_volume::{EncryptApfsVolume, EncryptVolumeError}; pub use encrypt_apfs_volume::EncryptApfsVolume;
pub use kickstart_launchctl_service::{KickstartLaunchctlService, KickstartLaunchctlServiceError}; pub use kickstart_launchctl_service::KickstartLaunchctlService;
pub use unmount_apfs_volume::{UnmountApfsVolume, UnmountVolumeError}; pub use unmount_apfs_volume::UnmountApfsVolume;

View file

@ -2,13 +2,10 @@ use std::path::{Path, PathBuf};
use tokio::process::Command; use tokio::process::Command;
use crate::action::StatefulAction; use crate::action::{ActionError, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription};
action::{Action, ActionDescription},
BoxableError,
};
/** /**
Unmount an APFS volume Unmount an APFS volume
@ -24,7 +21,7 @@ impl UnmountApfsVolume {
pub async fn plan( pub async fn plan(
disk: impl AsRef<Path>, disk: impl AsRef<Path>,
name: String, name: String,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<StatefulAction<Self>, ActionError> {
let disk = disk.as_ref().to_owned(); let disk = disk.as_ref().to_owned();
Ok(Self { disk, name }.into()) Ok(Self { disk, name }.into())
} }
@ -45,7 +42,7 @@ impl Action for UnmountApfsVolume {
disk = %self.disk.display(), disk = %self.disk.display(),
name = %self.name, name = %self.name,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { disk: _, name } = self; let Self { disk: _, name } = self;
execute_command( execute_command(
@ -56,7 +53,7 @@ impl Action for UnmountApfsVolume {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| UnmountVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
Ok(()) Ok(())
} }
@ -69,7 +66,7 @@ impl Action for UnmountApfsVolume {
disk = %self.disk.display(), disk = %self.disk.display(),
name = %self.name, name = %self.name,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { disk: _, name } = self; let Self { disk: _, name } = self;
execute_command( execute_command(
@ -80,14 +77,8 @@ impl Action for UnmountApfsVolume {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| UnmountVolumeError::Command(e).boxed())?; .map_err(|e| ActionError::Command(e))?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, thiserror::Error)]
pub enum UnmountVolumeError {
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
}

View file

@ -4,13 +4,10 @@ use target_lexicon::OperatingSystem;
use tokio::fs::remove_file; use tokio::fs::remove_file;
use tokio::process::Command; use tokio::process::Command;
use crate::action::StatefulAction; use crate::action::{ActionError, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription};
action::{Action, ActionDescription},
BoxableError,
};
const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service"; 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 SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
@ -26,7 +23,7 @@ pub struct ConfigureNixDaemonService {}
impl ConfigureNixDaemonService { impl ConfigureNixDaemonService {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
match OperatingSystem::host() { match OperatingSystem::host() {
OperatingSystem::MacOSX { OperatingSystem::MacOSX {
major: _, major: _,
@ -36,7 +33,9 @@ impl ConfigureNixDaemonService {
| OperatingSystem::Darwin => (), | OperatingSystem::Darwin => (),
_ => { _ => {
if !Path::new("/run/systemd/system").exists() { if !Path::new("/run/systemd/system").exists() {
return Err(ConfigureNixDaemonServiceError::InitNotSupported.boxed()); return Err(ActionError::Custom(Box::new(
ConfigureNixDaemonServiceError::InitNotSupported,
)));
} }
}, },
}; };
@ -64,7 +63,7 @@ impl Action for ConfigureNixDaemonService {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self {} = self; let Self {} = self;
match OperatingSystem::host() { match OperatingSystem::host() {
@ -78,12 +77,11 @@ impl Action for ConfigureNixDaemonService {
tokio::fs::copy(src.clone(), DARWIN_NIX_DAEMON_DEST) tokio::fs::copy(src.clone(), DARWIN_NIX_DAEMON_DEST)
.await .await
.map_err(|e| { .map_err(|e| {
ConfigureNixDaemonServiceError::Copy( ActionError::Copy(
src.to_path_buf(), src.to_path_buf(),
PathBuf::from(DARWIN_NIX_DAEMON_DEST), PathBuf::from(DARWIN_NIX_DAEMON_DEST),
e, e,
) )
.boxed()
})?; })?;
execute_command( execute_command(
@ -94,19 +92,18 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
}, },
_ => { _ => {
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking"); tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST) tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
.await .await
.map_err(|e| { .map_err(|e| {
ConfigureNixDaemonServiceError::Symlink( ActionError::Symlink(
PathBuf::from(TMPFILES_SRC), PathBuf::from(TMPFILES_SRC),
PathBuf::from(TMPFILES_DEST), PathBuf::from(TMPFILES_DEST),
e, e,
) )
.boxed()
})?; })?;
execute_command( execute_command(
@ -117,7 +114,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
@ -127,7 +124,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
@ -137,7 +134,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
@ -146,7 +143,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
@ -156,7 +153,7 @@ impl Action for ConfigureNixDaemonService {
.arg("nix-daemon.socket"), .arg("nix-daemon.socket"),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
}, },
}; };
@ -176,7 +173,7 @@ impl Action for ConfigureNixDaemonService {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
match OperatingSystem::host() { match OperatingSystem::host() {
OperatingSystem::MacOSX { OperatingSystem::MacOSX {
major: _, major: _,
@ -191,7 +188,7 @@ impl Action for ConfigureNixDaemonService {
.arg(DARWIN_NIX_DAEMON_DEST), .arg(DARWIN_NIX_DAEMON_DEST),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
}, },
_ => { _ => {
// We separate stop and disable (instead of using `--now`) to avoid cases where the service isn't started, but is enabled. // We separate stop and disable (instead of using `--now`) to avoid cases where the service isn't started, but is enabled.
@ -209,7 +206,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
} }
if socket_is_enabled { if socket_is_enabled {
@ -220,7 +217,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
} }
if service_is_active { if service_is_active {
@ -231,7 +228,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
} }
if service_is_enabled { if service_is_enabled {
@ -242,7 +239,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
} }
execute_command( execute_command(
@ -253,12 +250,11 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
remove_file(TMPFILES_DEST).await.map_err(|e| { remove_file(TMPFILES_DEST)
ConfigureNixDaemonServiceError::RemoveFile(PathBuf::from(TMPFILES_DEST), e) .await
.boxed() .map_err(|e| ActionError::Remove(PathBuf::from(TMPFILES_DEST), e))?;
})?;
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
@ -267,7 +263,7 @@ impl Action for ConfigureNixDaemonService {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(ActionError::Command)?;
}, },
}; };
@ -277,34 +273,17 @@ impl Action for ConfigureNixDaemonService {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum ConfigureNixDaemonServiceError { pub enum ConfigureNixDaemonServiceError {
#[error("Symlinking from `{0}` to `{1}`")]
Symlink(
std::path::PathBuf,
std::path::PathBuf,
#[source] std::io::Error,
),
#[error("Set mode `{0}` on `{1}`")]
SetPermissions(u32, std::path::PathBuf, #[source] std::io::Error),
#[error("Command failed to execute")]
Command(#[source] std::io::Error),
#[error("Remove file `{0}`")]
RemoveFile(std::path::PathBuf, #[source] std::io::Error),
#[error("Copying file `{0}` to `{1}`")]
Copy(
std::path::PathBuf,
std::path::PathBuf,
#[source] std::io::Error,
),
#[error("No supported init system found")] #[error("No supported init system found")]
InitNotSupported, InitNotSupported,
} }
async fn is_active(unit: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> { async fn is_active(unit: &str) -> Result<bool, ActionError> {
let output = Command::new("systemctl") let output = Command::new("systemctl")
.arg("is-active") .arg("is-active")
.arg(unit) .arg(unit)
.output() .output()
.await?; .await
.map_err(ActionError::Command)?;
if String::from_utf8(output.stdout)?.starts_with("active") { if String::from_utf8(output.stdout)?.starts_with("active") {
tracing::trace!(%unit, "Is active"); tracing::trace!(%unit, "Is active");
Ok(true) Ok(true)
@ -314,12 +293,13 @@ async fn is_active(unit: &str) -> Result<bool, Box<dyn std::error::Error + Send
} }
} }
async fn is_enabled(unit: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> { async fn is_enabled(unit: &str) -> Result<bool, ActionError> {
let output = Command::new("systemctl") let output = Command::new("systemctl")
.arg("is-enabled") .arg("is-enabled")
.arg(unit) .arg(unit)
.output() .output()
.await?; .await
.map_err(ActionError::Command)?;
let stdout = String::from_utf8(output.stdout)?; let stdout = String::from_utf8(output.stdout)?;
if stdout.starts_with("enabled") || stdout.starts_with("linked") { if stdout.starts_with("enabled") || stdout.starts_with("linked") {
tracing::trace!(%unit, "Is enabled"); tracing::trace!(%unit, "Is enabled");

View file

@ -1,12 +1,9 @@
use tokio::process::Command; use tokio::process::Command;
use crate::action::{ActionState, StatefulAction}; use crate::action::{ActionError, ActionState, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::action::{Action, ActionDescription};
action::{Action, ActionDescription},
BoxableError,
};
/** /**
Start a given systemd unit Start a given systemd unit
@ -18,9 +15,7 @@ pub struct StartSystemdUnit {
impl StartSystemdUnit { impl StartSystemdUnit {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan( pub async fn plan(unit: impl AsRef<str>) -> Result<StatefulAction<Self>, ActionError> {
unit: impl AsRef<str>,
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
Ok(StatefulAction { Ok(StatefulAction {
action: Self { action: Self {
unit: unit.as_ref().to_string(), unit: unit.as_ref().to_string(),
@ -44,7 +39,7 @@ impl Action for StartSystemdUnit {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
unit = %self.unit, unit = %self.unit,
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { unit, .. } = self; let Self { unit, .. } = self;
// TODO(@Hoverbear): Handle proxy vars // TODO(@Hoverbear): Handle proxy vars
@ -57,7 +52,7 @@ impl Action for StartSystemdUnit {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?; .map_err(|e| ActionError::Custom(Box::new(StartSystemdUnitError::Command(e))))?;
Ok(()) Ok(())
} }
@ -72,7 +67,7 @@ impl Action for StartSystemdUnit {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
unit = %self.unit, unit = %self.unit,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { unit, .. } = self; let Self { unit, .. } = self;
execute_command( execute_command(
@ -83,7 +78,7 @@ impl Action for StartSystemdUnit {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?; .map_err(|e| ActionError::Custom(Box::new(StartSystemdUnitError::Command(e))))?;
// We do both to avoid an error doing `disable --now` if the user did stop it already somehow. // We do both to avoid an error doing `disable --now` if the user did stop it already somehow.
execute_command( execute_command(
@ -94,7 +89,7 @@ impl Action for StartSystemdUnit {
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?; .map_err(|e| ActionError::Custom(Box::new(StartSystemdUnitError::Command(e))))?;
Ok(()) Ok(())
} }

View file

@ -50,7 +50,7 @@ use harmonic::{
InstallPlan, InstallPlan,
settings::{CommonSettings, InstallSettingsError}, settings::{CommonSettings, InstallSettingsError},
planner::{Planner, PlannerError, linux::SteamDeck}, planner::{Planner, PlannerError, linux::SteamDeck},
action::{Action, StatefulAction, ActionDescription}, action::{Action, ActionError, StatefulAction, ActionDescription},
}; };
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
@ -59,7 +59,7 @@ pub struct MyAction {}
impl MyAction { impl MyAction {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> { pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
Ok(Self {}.into()) Ok(Self {}.into())
} }
} }
@ -79,7 +79,7 @@ impl Action for MyAction {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
// Tracing fields... // Tracing fields...
))] ))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn execute(&mut self) -> Result<(), ActionError> {
// Execute steps ... // Execute steps ...
Ok(()) Ok(())
} }
@ -91,7 +91,7 @@ impl Action for MyAction {
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
// Tracing fields... // Tracing fields...
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), ActionError> {
// Revert steps... // Revert steps...
Ok(()) Ok(())
} }
@ -158,6 +158,8 @@ pub mod linux;
mod stateful; mod stateful;
pub use stateful::{ActionState, StatefulAction}; pub use stateful::{ActionState, StatefulAction};
use std::error::Error;
use tokio::task::JoinError;
/// An action which can be reverted or completed, with an action state /// An action which can be reverted or completed, with an action state
/// ///
@ -186,13 +188,13 @@ pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
/// If this action calls sub-[`Action`]s, care should be taken to call [`try_execute`][StatefulAction::try_execute], not [`execute`][Action::execute], so that [`ActionState`] is handled correctly and tracing is done. /// If this action calls sub-[`Action`]s, care should be taken to call [`try_execute`][StatefulAction::try_execute], not [`execute`][Action::execute], so that [`ActionState`] is handled correctly and tracing is done.
/// ///
/// This is called by [`InstallPlan::install`](crate::InstallPlan::install) through [`StatefulAction::try_execute`] which handles tracing as well as if the action needs to execute based on its `action_state`. /// This is called by [`InstallPlan::install`](crate::InstallPlan::install) through [`StatefulAction::try_execute`] which handles tracing as well as if the action needs to execute based on its `action_state`.
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>; async fn execute(&mut self) -> Result<(), ActionError>;
/// Perform any revert steps /// Perform any revert steps
/// ///
/// If this action calls sub-[`Action`]s, care should be taken to call [`try_revert`][StatefulAction::try_revert], not [`revert`][Action::revert], so that [`ActionState`] is handled correctly and tracing is done. /// If this action calls sub-[`Action`]s, care should be taken to call [`try_revert`][StatefulAction::try_revert], not [`revert`][Action::revert], so that [`ActionState`] is handled correctly and tracing is done.
/// ///
/// /// This is called by [`InstallPlan::uninstall`](crate::InstallPlan::uninstall) through [`StatefulAction::try_revert`] which handles tracing as well as if the action needs to revert based on its `action_state`. /// /// This is called by [`InstallPlan::uninstall`](crate::InstallPlan::uninstall) through [`StatefulAction::try_revert`] which handles tracing as well as if the action needs to revert based on its `action_state`.
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>; async fn revert(&mut self) -> Result<(), ActionError>;
fn stateful(self) -> StatefulAction<Self> fn stateful(self) -> StatefulAction<Self>
where where
@ -203,7 +205,7 @@ pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
state: ActionState::Uncompleted, state: ActionState::Uncompleted,
} }
} }
// They should also have an `async fn plan(args...) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>>;` // They should also have an `async fn plan(args...) -> Result<StatefulAction<Self>, ActionError>;`
} }
dyn_clone::clone_trait_object!(Action); dyn_clone::clone_trait_object!(Action);
@ -225,3 +227,85 @@ impl ActionDescription {
} }
} }
} }
/// An error occurring during an action
#[derive(thiserror::Error, Debug)]
pub enum ActionError {
/// A custom error
#[error(transparent)]
Custom(Box<dyn std::error::Error + Send + Sync>),
/// A child error
#[error(transparent)]
Child(#[from] Box<ActionError>),
/// Several child errors
#[error("Multiple errors: {}", .0.iter().map(|v| {
if let Some(source) = v.source() {
format!("{v} ({source})")
} else {
format!("{v}")
}
}).collect::<Vec<_>>().join(" & "))]
Children(Vec<Box<ActionError>>),
/// The path already exists
#[error("Path exists `{0}`")]
Exists(std::path::PathBuf),
#[error("Getting metadata for {0}`")]
GettingMetadata(std::path::PathBuf, #[source] std::io::Error),
#[error("Creating directory `{0}`")]
CreateDirectory(std::path::PathBuf, #[source] std::io::Error),
#[error("Symlinking from `{0}` to `{1}`")]
Symlink(
std::path::PathBuf,
std::path::PathBuf,
#[source] std::io::Error,
),
#[error("Set mode `{0}` on `{1}`")]
SetPermissions(u32, std::path::PathBuf, #[source] std::io::Error),
#[error("Remove file `{0}`")]
Remove(std::path::PathBuf, #[source] std::io::Error),
#[error("Copying file `{0}` to `{1}`")]
Copy(
std::path::PathBuf,
std::path::PathBuf,
#[source] std::io::Error,
),
#[error("Rename `{0}` to `{1}`")]
Rename(
std::path::PathBuf,
std::path::PathBuf,
#[source] std::io::Error,
),
#[error("Remove path `{0}`")]
Read(std::path::PathBuf, #[source] std::io::Error),
#[error("Open path `{0}`")]
Open(std::path::PathBuf, #[source] std::io::Error),
#[error("Write path `{0}`")]
Write(std::path::PathBuf, #[source] std::io::Error),
#[error("Seek path `{0}`")]
Seek(std::path::PathBuf, #[source] std::io::Error),
#[error("Getting uid for user `{0}`")]
UserId(String, #[source] nix::errno::Errno),
#[error("Getting user `{0}`")]
NoUser(String),
#[error("Getting gid for group `{0}`")]
GroupId(String, #[source] nix::errno::Errno),
#[error("Getting group `{0}`")]
NoGroup(String),
#[error("Chowning path `{0}`")]
Chown(std::path::PathBuf, #[source] nix::errno::Errno),
/// Failed to execute command
#[error("Failed to execute command")]
Command(#[source] std::io::Error),
#[error("Joining spawned async task")]
Join(
#[source]
#[from]
JoinError,
),
#[error("String from UTF-8 error")]
FromUtf8(
#[source]
#[from]
std::string::FromUtf8Error,
),
}

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{Action, ActionDescription}; use super::{Action, ActionDescription, ActionError};
/// A wrapper around an [`Action`](crate::action::Action) which tracks the [`ActionState`] and /// A wrapper around an [`Action`](crate::action::Action) which tracks the [`ActionState`] and
/// handles some tracing output /// handles some tracing output
@ -44,7 +44,7 @@ impl StatefulAction<Box<dyn Action>> {
/// Perform any execution steps /// Perform any execution steps
/// ///
/// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing /// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing
pub async fn try_execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { pub async fn try_execute(&mut self) -> Result<(), ActionError> {
match self.state { match self.state {
ActionState::Completed => { ActionState::Completed => {
tracing::trace!( tracing::trace!(
@ -70,7 +70,7 @@ impl StatefulAction<Box<dyn Action>> {
/// Perform any revert steps /// Perform any revert steps
/// ///
/// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing /// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing
pub async fn try_revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { pub async fn try_revert(&mut self) -> Result<(), ActionError> {
match self.state { match self.state {
ActionState::Uncompleted => { ActionState::Uncompleted => {
tracing::trace!( tracing::trace!(
@ -129,7 +129,7 @@ where
/// Perform any execution steps /// Perform any execution steps
/// ///
/// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing /// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing
pub async fn try_execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { pub async fn try_execute(&mut self) -> Result<(), ActionError> {
match self.state { match self.state {
ActionState::Completed => { ActionState::Completed => {
tracing::trace!( tracing::trace!(
@ -155,7 +155,7 @@ where
/// Perform any revert steps /// Perform any revert steps
/// ///
/// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing /// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing
pub async fn try_revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { pub async fn try_revert(&mut self) -> Result<(), ActionError> {
match self.state { match self.state {
ActionState::Uncompleted => { ActionState::Uncompleted => {
tracing::trace!( tracing::trace!(

View file

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::{planner::PlannerError, settings::InstallSettingsError}; use crate::{action::ActionError, planner::PlannerError, settings::InstallSettingsError};
/// An error occurring during a call defined in this crate /// An error occurring during a call defined in this crate
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -10,7 +10,7 @@ pub enum HarmonicError {
Action( Action(
#[source] #[source]
#[from] #[from]
Box<dyn std::error::Error + Send + Sync>, ActionError,
), ),
/// An error while writing the [`InstallPlan`](crate::InstallPlan) /// An error while writing the [`InstallPlan`](crate::InstallPlan)
#[error("Recording install receipt")] #[error("Recording install receipt")]

View file

@ -112,14 +112,3 @@ fn set_env(k: impl AsRef<OsStr>, v: impl AsRef<OsStr>) {
tracing::trace!("Setting env"); tracing::trace!("Setting env");
std::env::set_var(k.as_ref(), v.as_ref()); std::env::set_var(k.as_ref(), v.as_ref());
} }
trait BoxableError: std::error::Error + Send + Sync {
fn boxed(self) -> Box<dyn std::error::Error + Send + Sync>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
impl<E> BoxableError for E where E: std::error::Error + Send + Sized + Sync {}

View file

@ -80,7 +80,9 @@ pub mod linux;
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
action::StatefulAction, settings::InstallSettingsError, Action, HarmonicError, InstallPlan, action::{ActionError, StatefulAction},
settings::InstallSettingsError,
Action, HarmonicError, InstallPlan,
}; };
/// Something which can be used to plan out an [`InstallPlan`] /// Something which can be used to plan out an [`InstallPlan`]
@ -165,7 +167,11 @@ pub enum PlannerError {
UnsupportedArchitecture(target_lexicon::Triple), UnsupportedArchitecture(target_lexicon::Triple),
/// Error executing action /// Error executing action
#[error("Error executing action")] #[error("Error executing action")]
Action(#[source] Box<dyn std::error::Error + Send + Sync>), Action(
#[source]
#[from]
ActionError,
),
/// An [`InstallSettingsError`] /// An [`InstallSettingsError`]
#[error(transparent)] #[error(transparent)]
InstallSettings(#[from] InstallSettingsError), InstallSettings(#[from] InstallSettingsError),