Tidy tracing (#57)

* Tidy tracing

* Forgot a few changes

* Remove more boilerplate

* Repair Plan::describe_revert

* More valid default settings

* fmt

* Use correct execute/revert calls

* Split up Linux Daemon disable and stop

* Detect state and act on it

* Fixup pathes

* Add a missing step to the mac bits

* Unload instead of disable

* Prune out again

* Squelch some stdout

* Clean lint

* Better log for no-delete-directory case

* Even more verbose messages on CreateDirectory

* Less broken code

* Use try_execute where it should be used

* Final tweaks

* Add some docs
This commit is contained in:
Ana Hobden 2022-11-23 09:18:38 -08:00 committed by GitHub
parent 7255c7e5a1
commit 38ac180052
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 796 additions and 1194 deletions

View file

@ -48,12 +48,12 @@ impl ConfigureNixDaemonService {
#[async_trait::async_trait]
#[typetag::serde(name = "configure_nix_daemon")]
impl Action for ConfigureNixDaemonService {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
fn tracing_synopsis(&self) -> String {
"Configure Nix daemon related settings with systemd".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Configure Nix daemon related settings with systemd".to_string(),
self.tracing_synopsis(),
vec![
"Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(),
"Run `systemctl link {SERVICE_SRC}`".to_string(),
@ -62,7 +62,6 @@ impl Action for ConfigureNixDaemonService {
],
)]
}
}
#[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -166,10 +165,7 @@ impl Action for ConfigureNixDaemonService {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Unconfigure Nix daemon related settings with systemd".to_string(),
vec![
@ -180,17 +176,9 @@ impl Action for ConfigureNixDaemonService {
],
)]
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unconfiguring nix daemon service");
return Ok(());
}
tracing::debug!("Unconfiguring nix daemon service");
match OperatingSystem::host() {
OperatingSystem::MacOSX {
major: _,
@ -278,14 +266,16 @@ impl Action for ConfigureNixDaemonService {
},
};
tracing::trace!("Unconfigured nix daemon service");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -39,6 +39,10 @@ impl CreateDirectory {
CreateDirectoryError::GettingMetadata(path.to_path_buf(), e).boxed()
})?;
if metadata.is_dir() {
tracing::debug!(
"Creating directory `{}` already complete, skipping",
path.display(),
);
// TODO: Validate owner/group...
ActionState::Completed
} else {
@ -69,23 +73,12 @@ impl CreateDirectory {
#[async_trait::async_trait]
#[typetag::serde(name = "create_directory")]
impl Action for CreateDirectory {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
group: _,
mode: _,
force_prune_on_revert: _,
action_state,
} = &self;
if *action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Create the directory `{}`", path.display()),
vec![],
)]
fn tracing_synopsis(&self) -> String {
format!("Create directory `{}`", self.path.display())
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -101,13 +94,8 @@ impl Action for CreateDirectory {
group,
mode,
force_prune_on_revert: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating directory");
return Ok(());
}
tracing::debug!("Creating directory");
let gid = if let Some(group) = group {
Some(
@ -143,12 +131,10 @@ impl Action for CreateDirectory {
})?;
}
tracing::trace!("Created directory");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
@ -157,9 +143,6 @@ impl Action for CreateDirectory {
force_prune_on_revert,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!(
"Remove the directory `{}`{}",
@ -173,7 +156,6 @@ impl Action for CreateDirectory {
vec![],
)]
}
}
#[tracing::instrument(skip_all, fields(
path = %self.path.display(),
@ -188,15 +170,8 @@ impl Action for CreateDirectory {
group: _,
mode: _,
force_prune_on_revert,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Removing directory");
return Ok(());
}
tracing::debug!("Removing directory");
tracing::trace!(path = %path.display(), "Removing directory");
let is_empty = path
.read_dir()
@ -207,17 +182,21 @@ impl Action for CreateDirectory {
(true, _) | (false, true) => remove_dir_all(path.clone())
.await
.map_err(|e| CreateDirectoryError::Removing(path.clone(), e).boxed())?,
(false, false) => {},
(false, false) => {
tracing::debug!("Not removing `{}`, the folder is not empty", path.display());
},
};
tracing::trace!("Removed directory");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -53,24 +53,11 @@ impl CreateFile {
#[async_trait::async_trait]
#[typetag::serde(name = "create_file")]
impl Action for CreateFile {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
group: _,
mode: _,
buf: _,
force: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Create or overwrite file `{}`", path.display()),
vec![],
)]
fn tracing_synopsis(&self) -> String {
format!("Create or overwrite file `{}`", self.path.display())
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -87,13 +74,8 @@ impl Action for CreateFile {
mode,
buf,
force: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating file");
return Ok(());
}
tracing::debug!("Creating file");
let mut options = OpenOptions::new();
options.create_new(true).write(true).read(true);
@ -133,12 +115,10 @@ impl Action for CreateFile {
};
chown(path, uid, gid).map_err(|e| CreateFileError::Chown(path.clone(), e).boxed())?;
tracing::trace!("Created file");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
@ -148,15 +128,12 @@ impl Action for CreateFile {
force: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Delete file `{}`", path.display()),
vec![format!("Delete file `{}`", path.display())],
)]
}
}
#[tracing::instrument(skip_all, fields(
path = %self.path.display(),
@ -172,26 +149,23 @@ impl Action for CreateFile {
mode: _,
buf: _,
force: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Deleting file");
return Ok(());
}
tracing::debug!("Deleting file");
remove_file(&path)
.await
.map_err(|e| CreateFileError::RemoveFile(path.to_owned(), e).boxed())?;
tracing::trace!("Deleted file");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -28,23 +28,22 @@ impl CreateGroup {
#[async_trait::async_trait]
#[typetag::serde(name = "create_group")]
impl Action for CreateGroup {
fn describe_execute(&self) -> Vec<ActionDescription> {
fn tracing_synopsis(&self) -> String {
format!("Create group `{}` with GID `{}`", self.name, self.gid)
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
name,
gid,
name: _,
gid: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Create group {name} with GID {gid}"),
self.tracing_synopsis(),
vec![format!(
"The nix daemon requires a system user group its system users can be part of"
)],
)]
}
}
#[tracing::instrument(skip_all, fields(
user = self.name,
@ -73,6 +72,7 @@ impl Action for CreateGroup {
if Command::new("/usr/bin/dscl")
.args([".", "-read", &format!("/Groups/{name}")])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.status()
.await?
.success()
@ -112,15 +112,12 @@ impl Action for CreateGroup {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
name,
gid: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Delete group {name}"),
vec![format!(
@ -128,7 +125,6 @@ impl Action for CreateGroup {
)],
)]
}
}
#[tracing::instrument(skip_all, fields(
user = self.name,
@ -138,13 +134,8 @@ impl Action for CreateGroup {
let Self {
name,
gid: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Deleting group");
return Ok(());
}
tracing::debug!("Deleting group");
use target_lexicon::OperatingSystem;
match target_lexicon::OperatingSystem::host() {
@ -176,14 +167,16 @@ impl Action for CreateGroup {
},
};
tracing::trace!("Deleted group");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -50,23 +50,12 @@ impl CreateOrAppendFile {
#[async_trait::async_trait]
#[typetag::serde(name = "create_or_append_file")]
impl Action for CreateOrAppendFile {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
group: _,
mode: _,
buf: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Create or append file `{}`", path.display()),
vec![],
)]
fn tracing_synopsis(&self) -> String {
format!("Create or append file `{}`", self.path.display())
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -82,13 +71,8 @@ impl Action for CreateOrAppendFile {
group,
mode,
buf,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating or appending fragment to file");
return Ok(());
}
tracing::debug!("Creating or appending fragment to file");
let mut file = OpenOptions::new()
.create(true)
@ -138,12 +122,10 @@ impl Action for CreateOrAppendFile {
chown(path, uid, gid)
.map_err(|e| CreateOrAppendFileError::Chown(path.clone(), e).boxed())?;
tracing::trace!("Created or appended fragment to file");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
@ -152,9 +134,6 @@ impl Action for CreateOrAppendFile {
buf,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Delete Nix related fragment from file `{}`", path.display()),
vec![format!(
@ -163,7 +142,6 @@ impl Action for CreateOrAppendFile {
)],
)]
}
}
#[tracing::instrument(skip_all, fields(
path = %self.path.display(),
@ -178,14 +156,8 @@ impl Action for CreateOrAppendFile {
group: _,
mode: _,
buf,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already completed: Removing fragment from file (and deleting it if it becomes empty)");
return Ok(());
}
tracing::debug!("Removing fragment from file (and deleting it if it becomes empty)");
let mut file = OpenOptions::new()
.create(false)
.write(true)
@ -208,8 +180,6 @@ impl Action for CreateOrAppendFile {
remove_file(&path)
.await
.map_err(|e| CreateOrAppendFileError::RemoveFile(path.to_owned(), e).boxed())?;
tracing::trace!("Removed file (since all content was removed)");
} else {
file.seek(SeekFrom::Start(0))
.await
@ -217,16 +187,17 @@ impl Action for CreateOrAppendFile {
file.write_all(file_contents.as_bytes())
.await
.map_err(|e| CreateOrAppendFileError::WriteFile(path.to_owned(), e).boxed())?;
tracing::trace!("Removed fragment from from file");
}
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -32,26 +32,20 @@ impl CreateUser {
#[async_trait::async_trait]
#[typetag::serde(name = "create_user")]
impl Action for CreateUser {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
let Self {
name,
uid,
groupname: _,
gid,
action_state: _,
} = self;
fn tracing_synopsis(&self) -> String {
format!(
"Create user `{}` with UID `{}` with group `{}` (GID {})",
self.name, self.uid, self.groupname, self.gid
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Create user {name} with UID {uid} with group {gid}"),
self.tracing_synopsis(),
vec![format!(
"The nix daemon requires system users it can act as in order to build"
)],
)]
}
}
#[tracing::instrument(skip_all, fields(
user = self.name,
@ -65,13 +59,8 @@ impl Action for CreateUser {
uid,
groupname,
gid,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating user");
return Ok(());
}
tracing::debug!("Creating user");
use target_lexicon::OperatingSystem;
match OperatingSystem::host() {
@ -87,6 +76,7 @@ impl Action for CreateUser {
if Command::new("/usr/bin/dscl")
.args([".", "-read", &format!("/Users/{name}")])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.status()
.await?
.success()
@ -215,15 +205,10 @@ impl Action for CreateUser {
},
}
tracing::trace!("Created user");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
name,
uid,
@ -239,7 +224,6 @@ impl Action for CreateUser {
)],
)]
}
}
#[tracing::instrument(skip_all, fields(
user = self.name,
@ -252,13 +236,8 @@ impl Action for CreateUser {
uid: _,
groupname: _,
gid: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already completed: Deleting user");
return Ok(());
}
tracing::debug!("Deleting user");
use target_lexicon::OperatingSystem;
match target_lexicon::OperatingSystem::host() {
@ -290,14 +269,16 @@ impl Action for CreateUser {
},
};
tracing::trace!("Deleted user");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -34,20 +34,18 @@ impl FetchNix {
#[async_trait::async_trait]
#[typetag::serde(name = "fetch_nix")]
impl Action for FetchNix {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
url,
dest,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Fetch Nix from `{url}`"),
vec![format!("Unpack it to `{}` (moved later)", dest.display())],
)]
fn tracing_synopsis(&self) -> String {
format!("Fetch Nix from `{}`", self.url)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
self.tracing_synopsis(),
vec![format!(
"Unpack it to `{}` (moved later)",
self.dest.display()
)],
)]
}
#[tracing::instrument(skip_all, fields(
@ -58,13 +56,8 @@ impl Action for FetchNix {
let Self {
url,
dest,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Fetching Nix");
return Ok(());
}
tracing::debug!("Fetching Nix");
let res = reqwest::get(url.clone())
.await
@ -83,18 +76,12 @@ impl Action for FetchNix {
.unpack(&dest_clone)
.map_err(|e| FetchNixError::Unarchive(e).boxed())?;
tracing::trace!("Fetched Nix");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![/* Deliberately empty -- this is a noop */]
}
}
#[tracing::instrument(skip_all, fields(
url = %self.url,
@ -104,21 +91,19 @@ impl Action for FetchNix {
let Self {
url: _,
dest: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unfetch Nix (noop)");
return Ok(());
}
tracing::debug!("Unfetch Nix (noop)");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -27,10 +27,11 @@ impl MoveUnpackedNix {
#[async_trait::async_trait]
#[typetag::serde(name = "mount_unpacked_nix")]
impl Action for MoveUnpackedNix {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
fn tracing_synopsis(&self) -> String {
"Move the downloaded Nix into `/nix`".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Move the downloaded Nix into `/nix`"),
vec![format!(
@ -39,7 +40,6 @@ impl Action for MoveUnpackedNix {
)],
)]
}
}
#[tracing::instrument(skip_all, fields(
src = %self.src.display(),
@ -64,9 +64,9 @@ impl Action for MoveUnpackedNix {
"Did not expect to find multiple nix paths, please report this"
);
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
tracing::trace!("Renaming");
let src_store = found_nix_path.join("store");
let dest = Path::new(DEST);
tracing::trace!(src = %src_store.display(), dest = %dest.display(), "Renaming");
tokio::fs::rename(src_store.clone(), dest)
.await
.map_err(|e| {
@ -81,35 +81,26 @@ impl Action for MoveUnpackedNix {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![/* Deliberately empty -- this is a noop */]
}
}
#[tracing::instrument(skip_all, fields(
src = %self.src.display(),
dest = DEST,
))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
src: _,
action_state,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unmove Nix (noop)");
return Ok(());
}
tracing::debug!("Unmove Nix (noop)");
*action_state = ActionState::Uncompleted;
// Noop
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -25,15 +25,12 @@ impl SetupDefaultProfile {
#[async_trait::async_trait]
#[typetag::serde(name = "setup_default_profile")]
impl Action for SetupDefaultProfile {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Setup the default Nix profile".to_string(),
vec!["TODO".to_string()],
)]
fn tracing_synopsis(&self) -> String {
"Setup the default Nix profile".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -42,13 +39,8 @@ impl Action for SetupDefaultProfile {
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
channels,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Setting up default profile");
return Ok(());
}
tracing::debug!("Setting up default profile");
// Find an `nix` package
let nix_pkg_glob = "/nix/store/*-nix-*";
@ -147,46 +139,32 @@ impl Action for SetupDefaultProfile {
.map_err(|e| SetupDefaultProfileError::Command(e).boxed())?;
}
tracing::trace!("Set up default profile");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Unset the default Nix profile".to_string(),
vec!["TODO".to_string()],
)]
}
}
#[tracing::instrument(skip_all, fields(
channels = %self.channels.join(","),
))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
channels: _,
action_state,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unset default profile");
return Ok(());
}
tracing::debug!("Unsetting default profile (mostly noop)");
std::env::remove_var("NIX_SSL_CERT_FILE");
tracing::trace!("Unset default profile (mostly noop)");
*action_state = ActionState::Completed;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -2,7 +2,7 @@ use crate::{
action::{
base::{ConfigureNixDaemonService, SetupDefaultProfile},
common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
Action, ActionDescription, ActionState,
Action, ActionDescription, ActionImplementation, ActionState,
},
channel_value::ChannelValue,
BoxableError, CommonSettings,
@ -65,7 +65,11 @@ impl ConfigureNix {
#[async_trait::async_trait]
#[typetag::serde(name = "configure_nix")]
impl Action for ConfigureNix {
fn describe_execute(&self) -> Vec<ActionDescription> {
fn tracing_synopsis(&self) -> String {
"Configure Nix".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
setup_default_profile,
configure_nix_daemon_service,
@ -75,19 +79,15 @@ impl Action for ConfigureNix {
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
let mut buf = setup_default_profile.describe_execute();
buf.append(&mut configure_nix_daemon_service.describe_execute());
buf.append(&mut place_nix_configuration.describe_execute());
buf.append(&mut place_channel_configuration.describe_execute());
let mut buf = setup_default_profile.execute_description();
buf.append(&mut configure_nix_daemon_service.execute_description());
buf.append(&mut place_nix_configuration.execute_description());
buf.append(&mut place_channel_configuration.execute_description());
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.describe_execute());
buf.append(&mut configure_shell_profile.execute_description());
}
buf
}
}
#[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -97,37 +97,29 @@ impl Action for ConfigureNix {
place_nix_configuration,
place_channel_configuration,
configure_shell_profile,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Configuring nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Configuring nix");
if let Some(configure_shell_profile) = configure_shell_profile {
tokio::try_join!(
async move { setup_default_profile.execute().await },
async move { place_nix_configuration.execute().await },
async move { place_channel_configuration.execute().await },
async move { configure_shell_profile.execute().await },
async move { setup_default_profile.try_execute().await },
async move { place_nix_configuration.try_execute().await },
async move { place_channel_configuration.try_execute().await },
async move { configure_shell_profile.try_execute().await },
)?;
} else {
tokio::try_join!(
async move { setup_default_profile.execute().await },
async move { place_nix_configuration.execute().await },
async move { place_channel_configuration.execute().await },
async move { setup_default_profile.try_execute().await },
async move { place_nix_configuration.try_execute().await },
async move { place_channel_configuration.try_execute().await },
)?;
};
configure_nix_daemon_service.execute().await?;
configure_nix_daemon_service.try_execute().await?;
tracing::trace!("Configured nix");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
setup_default_profile,
configure_nix_daemon_service,
@ -137,21 +129,17 @@ impl Action for ConfigureNix {
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
let mut buf = Vec::default();
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.describe_revert());
buf.append(&mut configure_shell_profile.revert_description());
}
buf.append(&mut place_channel_configuration.describe_revert());
buf.append(&mut place_nix_configuration.describe_revert());
buf.append(&mut configure_nix_daemon_service.describe_revert());
buf.append(&mut setup_default_profile.describe_revert());
buf.append(&mut place_channel_configuration.revert_description());
buf.append(&mut place_nix_configuration.revert_description());
buf.append(&mut configure_nix_daemon_service.revert_description());
buf.append(&mut setup_default_profile.revert_description());
buf
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -161,29 +149,25 @@ impl Action for ConfigureNix {
place_nix_configuration,
place_channel_configuration,
configure_shell_profile,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unconfiguring nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Unconfiguring nix");
configure_nix_daemon_service.revert().await?;
configure_nix_daemon_service.try_revert().await?;
if let Some(configure_shell_profile) = configure_shell_profile {
configure_shell_profile.revert().await?;
configure_shell_profile.try_revert().await?;
}
place_channel_configuration.revert().await?;
place_nix_configuration.revert().await?;
setup_default_profile.revert().await?;
place_channel_configuration.try_revert().await?;
place_nix_configuration.try_revert().await?;
setup_default_profile.try_revert().await?;
tracing::trace!("Unconfigured nix");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}

View file

@ -1,5 +1,5 @@
use crate::action::base::{CreateOrAppendFile, CreateOrAppendFileError};
use crate::action::{Action, ActionDescription, ActionState};
use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
use crate::BoxableError;
use std::path::Path;
@ -57,29 +57,23 @@ impl ConfigureShellProfile {
#[async_trait::async_trait]
#[typetag::serde(name = "configure_shell_profile")]
impl Action for ConfigureShellProfile {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
fn tracing_synopsis(&self) -> String {
"Configure the shell profiles".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Configure the shell profiles".to_string(),
self.tracing_synopsis(),
vec!["Update shell profiles to import Nix".to_string()],
)]
}
}
#[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
create_or_append_files,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Configuring shell profile");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Configuring shell profile");
let mut set = JoinSet::new();
let mut errors = Vec::default();
@ -87,7 +81,7 @@ impl Action for ConfigureShellProfile {
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 _abort_handle = set.spawn(async move {
create_or_append_file_clone.execute().await?;
create_or_append_file_clone.try_execute().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok((
idx,
create_or_append_file_clone,
@ -113,34 +107,22 @@ impl Action for ConfigureShellProfile {
}
}
tracing::trace!("Configured shell profile");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Unconfigure the shell profiles".to_string(),
vec!["Update shell profiles to no longer import Nix".to_string()],
)]
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
create_or_append_files,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unconfiguring shell profile");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Unconfiguring shell profile");
let mut set = JoinSet::new();
let mut errors = Vec::default();
@ -174,14 +156,16 @@ impl Action for ConfigureShellProfile {
}
}
tracing::trace!("Unconfigured shell profile");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -1,5 +1,5 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError};
use crate::action::{Action, ActionDescription, ActionState};
use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
const PATHS: &[&str] = &[
"/nix/var",
@ -42,12 +42,13 @@ impl CreateNixTree {
#[async_trait::async_trait]
#[typetag::serde(name = "create_nix_tree")]
impl Action for CreateNixTree {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
fn tracing_synopsis(&self) -> String {
"Create a directory tree in `/nix`".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Create a directory tree in `/nix`"),
self.tracing_synopsis(),
vec![
format!(
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
@ -63,35 +64,23 @@ impl Action for CreateNixTree {
],
)]
}
}
#[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
create_directories,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating nix tree");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Creating nix tree");
// Just do sequential since parallelizing this will have little benefit
for create_directory in create_directories {
create_directory.execute().await?
create_directory.try_execute().await?
}
tracing::trace!("Created nix tree");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Remove the directory tree in `/nix`"),
vec![
@ -110,34 +99,29 @@ impl Action for CreateNixTree {
],
)]
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
create_directories,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Deleting nix tree");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Deleting nix tree");
// Just do sequential since parallelizing this will have little benefit
for create_directory in create_directories.iter_mut().rev() {
create_directory.revert().await?
}
tracing::trace!("Deleted nix tree");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -2,7 +2,7 @@ use crate::CommonSettings;
use crate::{
action::{
base::{CreateGroup, CreateGroupError, CreateUser, CreateUserError},
Action, ActionDescription, ActionState,
Action, ActionDescription, ActionImplementation, ActionState,
},
BoxableError,
};
@ -55,7 +55,11 @@ impl CreateUsersAndGroup {
#[async_trait::async_trait]
#[typetag::serde(name = "create_users_and_group")]
impl Action for CreateUsersAndGroup {
fn describe_execute(&self) -> Vec<ActionDescription> {
fn tracing_synopsis(&self) -> String {
"Create build users and group".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
daemon_user_count,
nix_build_group_name,
@ -66,12 +70,10 @@ impl Action for CreateUsersAndGroup {
create_users: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![
ActionDescription::new(
format!("Create build users and group"),
self.tracing_synopsis(),
vec![
format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"),
format!("Create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"),
@ -80,7 +82,6 @@ impl Action for CreateUsersAndGroup {
)
]
}
}
#[tracing::instrument(skip_all, fields(
daemon_user_count = self.daemon_user_count,
@ -98,23 +99,17 @@ impl Action for CreateUsersAndGroup {
nix_build_group_id: _,
nix_build_user_prefix: _,
nix_build_user_id_base: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating users and groups");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Creating users and groups");
// Create group
create_group.execute().await?;
create_group.try_execute().await?;
// Mac is apparently not threadsafe here...
for create_user in create_users.iter_mut() {
// let mut create_user_clone = create_user.clone();
// let _abort_handle = set.spawn(async move {
create_user.execute().await?;
create_user.try_execute().await?;
// Result::<_, CreateUserError>::Ok((idx, create_user_clone))
// });
}
@ -135,12 +130,10 @@ impl Action for CreateUsersAndGroup {
// }
// }
tracing::trace!("Created users and groups");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
daemon_user_count,
nix_build_group_name,
@ -183,15 +176,8 @@ impl Action for CreateUsersAndGroup {
nix_build_group_id: _,
nix_build_user_prefix: _,
nix_build_user_id_base: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Delete users and groups");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Delete users and groups");
let mut set = JoinSet::new();
let mut errors = Vec::default();
@ -223,14 +209,16 @@ impl Action for CreateUsersAndGroup {
// Create group
create_group.revert().await?;
tracing::trace!("Deleted users and groups");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -1,6 +1,6 @@
use crate::action::base::{CreateFile, CreateFileError};
use crate::{
action::{Action, ActionDescription, ActionState},
action::{Action, ActionDescription, ActionImplementation, ActionState},
BoxableError,
};
use reqwest::Url;
@ -45,23 +45,15 @@ impl PlaceChannelConfiguration {
#[async_trait::async_trait]
#[typetag::serde(name = "place_channel_configuration")]
impl Action for PlaceChannelConfiguration {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
channels: _,
create_file,
action_state: _,
} = self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
fn tracing_synopsis(&self) -> String {
format!(
"Place channel configuration at `{}`",
create_file.path.display()
),
vec![],
)]
self.create_file.path.display()
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -71,40 +63,23 @@ impl Action for PlaceChannelConfiguration {
let Self {
create_file,
channels: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Placing channel configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Placing channel configuration");
create_file.execute().await?;
create_file.try_execute().await?;
tracing::trace!("Placed channel configuration");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
channels: _,
create_file,
action_state: _,
} = self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!(
"Remove channel configuration at `{}`",
create_file.path.display()
self.create_file.path.display()
),
vec![],
)]
}
}
#[tracing::instrument(skip_all, fields(
channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "),
@ -113,25 +88,21 @@ impl Action for PlaceChannelConfiguration {
let Self {
create_file,
channels: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Removing channel configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Removing channel configuration");
create_file.revert().await?;
tracing::debug!("Removed channel configuration");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -1,5 +1,5 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
use crate::action::{Action, ActionDescription, ActionState};
use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
const NIX_CONF_FOLDER: &str = "/etc/nix";
const NIX_CONF: &str = "/etc/nix/nix.conf";
@ -44,46 +44,35 @@ impl PlaceNixConfiguration {
#[async_trait::async_trait]
#[typetag::serde(name = "place_nix_configuration")]
impl Action for PlaceNixConfiguration {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
fn tracing_synopsis(&self) -> String {
format!("Place the nix configuration in `{NIX_CONF}`")
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Place the nix configuration in `{NIX_CONF}`"),
self.tracing_synopsis(),
vec![
"This file is read by the Nix daemon to set its configuration options at runtime."
.to_string(),
],
)]
}
}
#[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
create_file,
create_directory,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Placing Nix configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Placing Nix configuration");
create_directory.execute().await?;
create_file.execute().await?;
create_directory.try_execute().await?;
create_file.try_execute().await?;
tracing::trace!("Placed Nix configuration");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Remove the nix configuration in `{NIX_CONF}`"),
vec![
@ -92,33 +81,28 @@ impl Action for PlaceNixConfiguration {
],
)]
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
create_file,
create_directory,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Remove nix configuration");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Remove nix configuration");
create_file.revert().await?;
create_directory.revert().await?;
tracing::trace!("Removed nix configuration");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -3,7 +3,7 @@ use crate::action::base::{
};
use crate::CommonSettings;
use crate::{
action::{Action, ActionDescription, ActionState},
action::{Action, ActionDescription, ActionImplementation, ActionState},
BoxableError,
};
use std::path::PathBuf;
@ -51,7 +51,11 @@ impl ProvisionNix {
#[async_trait::async_trait]
#[typetag::serde(name = "provision_nix")]
impl Action for ProvisionNix {
fn describe_execute(&self) -> Vec<ActionDescription> {
fn tracing_synopsis(&self) -> String {
"Provision Nix".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
fetch_nix,
create_users_and_group,
@ -59,18 +63,15 @@ impl Action for ProvisionNix {
move_unpacked_nix,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
let mut buf = Vec::default();
buf.append(&mut fetch_nix.describe_execute());
buf.append(&mut create_users_and_group.describe_execute());
buf.append(&mut create_nix_tree.describe_execute());
buf.append(&mut move_unpacked_nix.describe_execute());
buf.append(&mut fetch_nix.execute_description());
buf.append(&mut create_users_and_group.execute_description());
buf.append(&mut create_nix_tree.execute_description());
buf.append(&mut move_unpacked_nix.execute_description());
buf
}
}
#[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -79,34 +80,26 @@ impl Action for ProvisionNix {
create_nix_tree,
create_users_and_group,
move_unpacked_nix,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Provisioning Nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Provisioning Nix");
// We fetch nix while doing the rest, then move it over.
let mut fetch_nix_clone = fetch_nix.clone();
let fetch_nix_handle = tokio::task::spawn(async {
fetch_nix_clone.execute().await?;
fetch_nix_clone.try_execute().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok(fetch_nix_clone)
});
create_users_and_group.execute().await?;
create_nix_tree.execute().await?;
create_users_and_group.try_execute().await?;
create_nix_tree.try_execute().await?;
*fetch_nix = fetch_nix_handle.await.map_err(|e| e.boxed())??;
move_unpacked_nix.execute().await?;
move_unpacked_nix.try_execute().await?;
tracing::trace!("Provisioned Nix");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self {
fetch_nix,
create_users_and_group,
@ -114,17 +107,14 @@ impl Action for ProvisionNix {
move_unpacked_nix,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
let mut buf = Vec::default();
buf.append(&mut move_unpacked_nix.describe_revert());
buf.append(&mut create_nix_tree.describe_revert());
buf.append(&mut create_users_and_group.describe_revert());
buf.append(&mut fetch_nix.describe_revert());
buf.append(&mut move_unpacked_nix.revert_description());
buf.append(&mut create_nix_tree.revert_description());
buf.append(&mut create_users_and_group.revert_description());
buf.append(&mut fetch_nix.revert_description());
buf
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -133,27 +123,21 @@ impl Action for ProvisionNix {
create_nix_tree,
create_users_and_group,
move_unpacked_nix,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unprovisioning nix");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Unprovisioning nix");
// We fetch nix while doing the rest, then move it over.
let mut fetch_nix_clone = fetch_nix.clone();
let fetch_nix_handle = tokio::task::spawn(async {
fetch_nix_clone.revert().await?;
fetch_nix_clone.try_revert().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok(fetch_nix_clone)
});
if let Err(err) = create_users_and_group.revert().await {
if let Err(err) = create_users_and_group.try_revert().await {
fetch_nix_handle.abort();
return Err(err);
}
if let Err(err) = create_nix_tree.revert().await {
if let Err(err) = create_nix_tree.try_revert().await {
fetch_nix_handle.abort();
return Err(err);
}
@ -161,14 +145,16 @@ impl Action for ProvisionNix {
*fetch_nix = fetch_nix_handle.await.map_err(|e| e.boxed())??;
move_unpacked_nix.revert().await?;
tracing::trace!("Unprovisioned Nix");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -30,27 +30,22 @@ impl BootstrapVolume {
#[async_trait::async_trait]
#[typetag::serde(name = "bootstrap_volume")]
impl Action for BootstrapVolume {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Bootstrap and kickstart `{}`", self.path.display()),
vec![],
)]
fn tracing_synopsis(&self) -> String {
format!("Bootstrap and kickstart `{}`", self.path.display())
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
path = %self.path.display(),
))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { path, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Bootstrapping volume");
return Ok(());
}
tracing::debug!("Bootstrapping volume");
let Self {
path,
action_state: _,
} = self;
execute_command(
Command::new("launchctl")
@ -68,32 +63,24 @@ impl Action for BootstrapVolume {
.await
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
tracing::trace!("Bootstrapped volume");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Stop `{}`", self.path.display()),
vec![],
)]
}
}
#[tracing::instrument(skip_all, fields(
path = %self.path.display(),
))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { path, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stop volume");
return Ok(());
}
tracing::debug!("Stop volume");
let Self {
path,
action_state: _,
} = self;
execute_command(
Command::new("launchctl")
@ -104,14 +91,16 @@ impl Action for BootstrapVolume {
.await
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
tracing::trace!("Stopped volume");
*action_state = ActionState::Completed;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -7,7 +7,7 @@ use crate::{
EnableOwnershipError, EncryptVolume, EncryptVolumeError, UnmountVolume,
UnmountVolumeError,
},
Action, ActionDescription, ActionState,
Action, ActionDescription, ActionImplementation, ActionState,
},
BoxableError,
};
@ -140,23 +140,19 @@ impl CreateApfsVolume {
#[async_trait::async_trait]
#[typetag::serde(name = "create_apfs_volume")]
impl Action for CreateApfsVolume {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
disk,
name,
action_state: _,
..
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Create an APFS volume `{name}` on `{}`", disk.display()),
vec![format!(
"Create a writable, persistent systemd system extension.",
)],
)]
fn tracing_synopsis(&self) -> String {
format!(
"Create an APFS volume `{}` on `{}`",
self.name,
self.disk.display()
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
let Self {
disk: _, name: _, ..
} = &self;
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(destination,))]
@ -175,25 +171,20 @@ impl Action for CreateApfsVolume {
setup_volume_daemon,
bootstrap_volume,
enable_ownership,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating APFS volume");
return Ok(());
}
tracing::debug!("Creating APFS volume");
create_or_append_synthetic_conf.execute().await?;
create_synthetic_objects.execute().await?;
unmount_volume.execute().await.ok(); // We actually expect this may fail.
create_volume.execute().await?;
create_or_append_fstab.execute().await?;
create_or_append_synthetic_conf.try_execute().await?;
create_synthetic_objects.try_execute().await?;
unmount_volume.try_execute().await.ok(); // We actually expect this may fail.
create_volume.try_execute().await?;
create_or_append_fstab.try_execute().await?;
if let Some(encrypt_volume) = encrypt_volume {
encrypt_volume.execute().await?;
encrypt_volume.try_execute().await?;
}
setup_volume_daemon.execute().await?;
setup_volume_daemon.try_execute().await?;
bootstrap_volume.execute().await?;
bootstrap_volume.try_execute().await?;
let mut retry_tokens: usize = 50;
loop {
@ -213,23 +204,13 @@ impl Action for CreateApfsVolume {
tokio::time::sleep(Duration::from_millis(100)).await;
}
enable_ownership.execute().await?;
enable_ownership.try_execute().await?;
tracing::trace!("Created APFS volume");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
disk,
name,
action_state,
..
} = &self;
if *action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
let Self { disk, name, .. } = &self;
vec![ActionDescription::new(
format!("Remove the APFS volume `{name}` on `{}`", disk.display()),
vec![format!(
@ -237,7 +218,6 @@ impl Action for CreateApfsVolume {
)],
)]
}
}
#[tracing::instrument(skip_all, fields(disk, name))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
@ -255,37 +235,34 @@ impl Action for CreateApfsVolume {
setup_volume_daemon,
bootstrap_volume,
enable_ownership,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Removing APFS volume");
return Ok(());
}
tracing::debug!("Removing APFS volume");
enable_ownership.revert().await?;
bootstrap_volume.revert().await?;
setup_volume_daemon.revert().await?;
enable_ownership.try_revert().await?;
bootstrap_volume.try_revert().await?;
setup_volume_daemon.try_revert().await?;
if let Some(encrypt_volume) = encrypt_volume {
encrypt_volume.revert().await?;
encrypt_volume.try_revert().await?;
}
create_or_append_fstab.revert().await?;
create_or_append_fstab.try_revert().await?;
unmount_volume.revert().await?;
create_volume.revert().await?;
unmount_volume.try_revert().await?;
create_volume.try_revert().await?;
// Purposefully not reversed
create_or_append_synthetic_conf.revert().await?;
create_synthetic_objects.revert().await?;
create_or_append_synthetic_conf.try_revert().await?;
create_synthetic_objects.try_revert().await?;
tracing::trace!("Removed APFS volume");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -21,26 +21,19 @@ impl CreateSyntheticObjects {
#[async_trait::async_trait]
#[typetag::serde(name = "create_synthetic_objects")]
impl Action for CreateSyntheticObjects {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
fn tracing_synopsis(&self) -> String {
"Create objects defined in `/etc/synthetic.conf`".to_string()
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Create objects defined in `/etc/synthetic.conf`".to_string(),
self.tracing_synopsis(),
vec!["Populates the `/nix` path".to_string()],
)]
}
}
#[tracing::instrument(skip_all, fields())]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating synthetic objects");
return Ok(());
}
tracing::debug!("Creating synthetic objects");
// 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(
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
@ -57,31 +50,18 @@ impl Action for CreateSyntheticObjects {
.await
.ok(); // Deliberate
tracing::trace!("Created synthetic objects");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Refresh the objects defined in `/etc/synthetic.conf`".to_string(),
vec!["Will remove the `/nix` path".to_string()],
)]
}
}
#[tracing::instrument(skip_all, fields())]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Refreshing synthetic objects");
return Ok(());
}
tracing::debug!("Refreshing synthetic objects");
// 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(
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
@ -98,14 +78,16 @@ impl Action for CreateSyntheticObjects {
.await
.ok(); // Deliberate
tracing::trace!("Refreshed synthetic objects");
*action_state = ActionState::Completed;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -36,19 +36,16 @@ impl CreateVolume {
#[async_trait::async_trait]
#[typetag::serde(name = "create_volume")]
impl Action for CreateVolume {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
fn tracing_synopsis(&self) -> String {
format!(
"Create a volume on `{}` named `{}`",
self.disk.display(),
self.name
),
vec![],
)]
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -61,13 +58,8 @@ impl Action for CreateVolume {
disk,
name,
case_sensitive,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating volume");
return Ok(());
}
tracing::debug!("Creating volume");
execute_command(
Command::new("/usr/sbin/diskutil")
@ -88,15 +80,10 @@ impl Action for CreateVolume {
.await
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
tracing::trace!("Created volume");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!(
"Remove the volume on `{}` named `{}`",
@ -106,7 +93,6 @@ impl Action for CreateVolume {
vec![],
)]
}
}
#[tracing::instrument(skip_all, fields(
disk = %self.disk.display(),
@ -118,13 +104,8 @@ impl Action for CreateVolume {
disk: _,
name,
case_sensitive: _,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Deleting volume");
return Ok(());
}
tracing::debug!("Deleting volume");
execute_command(
Command::new("/usr/sbin/diskutil")
@ -134,14 +115,16 @@ impl Action for CreateVolume {
.await
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
tracing::trace!("Deleted volume");
*action_state = ActionState::Completed;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -32,27 +32,22 @@ impl EnableOwnership {
#[async_trait::async_trait]
#[typetag::serde(name = "enable_ownership")]
impl Action for EnableOwnership {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Enable ownership on {}", self.path.display()),
vec![],
)]
fn tracing_synopsis(&self) -> String {
format!("Enable ownership on {}", self.path.display())
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
path = %self.path.display(),
))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { path, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Enabling ownership");
return Ok(());
}
tracing::debug!("Enabling ownership");
let Self {
path,
action_state: _,
} = self;
let should_enable_ownership = {
let buf = execute_command(
@ -79,41 +74,28 @@ impl Action for EnableOwnership {
.map_err(|e| EnableOwnershipError::Command(e).boxed())?;
}
tracing::trace!("Enabled ownership");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![]
} else {
vec![]
}
}
#[tracing::instrument(skip_all, fields(
path = %self.path.display(),
))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
path: _,
action_state,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unenabling ownership (noop)");
return Ok(());
}
tracing::debug!("Unenabling ownership (noop)");
tracing::trace!("Unenabled ownership (noop)");
*action_state = ActionState::Completed;
// noop
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -31,15 +31,16 @@ impl EncryptVolume {
#[async_trait::async_trait]
#[typetag::serde(name = "encrypt_volume")]
impl Action for EncryptVolume {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Encrypt volume `{}`", self.disk.display()),
vec![],
)]
fn tracing_synopsis(&self) -> String {
format!(
"Encrypt volume `{}` on disk `{}`",
self.name,
self.disk.display()
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -49,13 +50,8 @@ impl Action for EncryptVolume {
let Self {
disk,
name,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Encrypting volume");
return Ok(());
}
tracing::debug!("Encrypting volume");
// Generate a random password.
let password: String = {
@ -127,17 +123,17 @@ impl Action for EncryptVolume {
)
.await?;
tracing::trace!("Encrypted volume");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![]
}
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!(
"Remove encryption keys for volume `{}`",
self.disk.display()
),
vec![],
)]
}
#[tracing::instrument(skip_all, fields(
@ -147,13 +143,8 @@ impl Action for EncryptVolume {
let Self {
disk,
name,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unencrypted volume");
return Ok(());
}
tracing::debug!("Unencrypted volume");
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
@ -178,14 +169,16 @@ impl Action for EncryptVolume {
)
.await?;
tracing::trace!("Unencrypted volume");
*action_state = ActionState::Completed;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -26,30 +26,23 @@ impl KickstartLaunchctlService {
#[async_trait::async_trait]
#[typetag::serde(name = "kickstart_launchctl_service")]
impl Action for KickstartLaunchctlService {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Kickstart the launchctl unit `{unit}`"),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
fn tracing_synopsis(&self) -> String {
let Self { unit, .. } = self;
format!("Kickstart the launchctl unit `{unit}`")
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Kickstarting launchctl unit");
return Ok(());
}
tracing::debug!("Kickstarting launchctl unit");
let Self {
unit,
action_state: _,
} = self;
execute_command(
Command::new("launchctl")
@ -61,46 +54,28 @@ impl Action for KickstartLaunchctlService {
.await
.map_err(|e| KickstartLaunchctlServiceError::Command(e).boxed())?;
tracing::trace!("Kickstarted launchctl unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![]
} else {
vec![ActionDescription::new(
"Kick".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
unit: _,
action_state,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unkickstart launchctl unit (noop)");
return Ok(());
}
tracing::debug!("Unkickstart launchctl unit (noop)");
tracing::trace!("Unkickstart launchctl unit (noop)");
*action_state = ActionState::Completed;
// noop
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -34,17 +34,12 @@ impl UnmountVolume {
#[async_trait::async_trait]
#[typetag::serde(name = "unmount_volume")]
impl Action for UnmountVolume {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
fn tracing_synopsis(&self) -> String {
format!("Unmount the `{}` volume", self.name)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -55,13 +50,8 @@ impl Action for UnmountVolume {
let Self {
disk: _,
name,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Unmounting volume");
return Ok(());
}
tracing::debug!("Unmounting volume");
execute_command(
Command::new("/usr/sbin/diskutil")
@ -72,22 +62,11 @@ impl Action for UnmountVolume {
.await
.map_err(|e| UnmountVolumeError::Command(e).boxed())?;
tracing::trace!("Unmounted volume");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -98,16 +77,11 @@ impl Action for UnmountVolume {
let Self {
disk: _,
name,
action_state,
action_state: _,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Unmounting Nix Store volume");
return Ok(());
}
tracing::debug!("Unmounting Nix Store volume");
execute_command(
Command::new(" /usr/sbin/diskutil")
Command::new("/usr/sbin/diskutil")
.args(["unmount", "force"])
.arg(name)
.stdin(std::process::Stdio::null()),
@ -115,14 +89,16 @@ impl Action for UnmountVolume {
.await
.map_err(|e| UnmountVolumeError::Command(e).boxed())?;
tracing::trace!("Unmounted Nix Store volume");
*action_state = ActionState::Completed;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -1,6 +1,6 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
use crate::{
action::{Action, ActionDescription, ActionState},
action::{Action, ActionDescription, ActionImplementation, ActionState},
BoxableError,
};
use std::path::{Path, PathBuf};
@ -93,101 +93,79 @@ impl CreateSystemdSysext {
#[async_trait::async_trait]
#[typetag::serde(name = "create_systemd_sysext")]
impl Action for CreateSystemdSysext {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
action_state: _,
destination,
create_bind_mount_unit: _,
create_directories: _,
create_extension_release: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
fn tracing_synopsis(&self) -> String {
format!(
"Create a systemd sysext at `{}`",
self.destination.display()
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Create a systemd sysext at `{}`", destination.display()),
self.tracing_synopsis(),
vec![format!(
"Create a writable, persistent systemd system extension.",
)],
)]
}
}
#[tracing::instrument(skip_all, fields(destination,))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
destination: _,
action_state,
action_state: _,
create_directories,
create_extension_release,
create_bind_mount_unit,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating sysext");
return Ok(());
}
tracing::debug!("Creating sysext");
for create_directory in create_directories {
create_directory.execute().await?;
create_directory.try_execute().await?;
}
create_extension_release.execute().await?;
create_bind_mount_unit.execute().await?;
create_extension_release.try_execute().await?;
create_bind_mount_unit.try_execute().await?;
tracing::trace!("Created sysext");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
destination,
action_state: _,
create_directories: _,
create_extension_release: _,
create_bind_mount_unit: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Remove the sysext located at `{}`", destination.display()),
format!(
"Remove the sysext located at `{}`",
self.destination.display()
),
vec![],
)]
}
}
#[tracing::instrument(skip_all, fields(destination,))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self {
destination: _,
action_state,
action_state: _,
create_directories,
create_extension_release,
create_bind_mount_unit,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Removing sysext");
return Ok(());
}
tracing::debug!("Removing sysext");
create_bind_mount_unit.revert().await?;
create_bind_mount_unit.try_revert().await?;
create_extension_release.revert().await?;
create_extension_release.try_revert().await?;
for create_directory in create_directories.iter_mut().rev() {
create_directory.revert().await?;
create_directory.try_revert().await?;
}
tracing::trace!("Removed sysext");
*action_state = ActionState::Uncompleted;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -1,7 +1,5 @@
mod create_systemd_sysext;
mod start_systemd_unit;
mod systemd_sysext_merge;
pub use create_systemd_sysext::{CreateSystemdSysext, CreateSystemdSysextError};
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError};
pub use systemd_sysext_merge::{SystemdSysextMerge, SystemdSysextMergeError};

View file

@ -26,29 +26,19 @@ impl StartSystemdUnit {
#[async_trait::async_trait]
#[typetag::serde(name = "start_systemd_unit")]
impl Action for StartSystemdUnit {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
fn tracing_synopsis(&self) -> String {
format!("Enable (and start) the systemd unit {}", self.unit)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting systemd unit");
return Ok(());
}
tracing::debug!("Starting systemd unit");
let Self { unit, .. } = self;
// TODO(@Hoverbear): Handle proxy vars
execute_command(
@ -61,34 +51,21 @@ impl Action for StartSystemdUnit {
.await
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?;
tracing::trace!("Started systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
format!("Disable (and stop) the systemd unit {}", self.unit),
vec![],
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Stopping systemd unit");
let Self { unit, .. } = self;
// TODO(@Hoverbear): Handle proxy vars
execute_command(
@ -100,14 +77,26 @@ impl Action for StartSystemdUnit {
.await
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?;
tracing::trace!("Stopped systemd unit");
*action_state = ActionState::Completed;
// We do both to avoid an error doing `disable --now` if the user did stop it already somehow.
execute_command(
Command::new("systemctl")
.arg("stop")
.arg(format!("{unit}"))
.stdin(std::process::Stdio::null()),
)
.await
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -25,19 +25,12 @@ impl SystemdSysextMerge {
#[async_trait::async_trait]
#[typetag::serde(name = "systemd_sysext_merge")]
impl Action for SystemdSysextMerge {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
action_state,
device,
} = self;
if *action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Run `systemd-sysext merge `{}`", device.display()),
vec![],
)]
fn tracing_synopsis(&self) -> String {
format!("Run `systemd-sysext merge `{}`", device.display())
}
fn describe_execute(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(skip_all, fields(
@ -48,11 +41,6 @@ impl Action for SystemdSysextMerge {
device,
action_state,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Merging systemd-sysext");
return Ok(());
}
tracing::debug!("Merging systemd-sysext");
execute_command(
Command::new("systemd-sysext")
@ -63,15 +51,10 @@ impl Action for SystemdSysextMerge {
.await
.map_err(|e| SystemdSysextMergeError::Command(e).boxed())?;
tracing::trace!("Merged systemd-sysext");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
@ -79,7 +62,6 @@ impl Action for SystemdSysextMerge {
]
)]
}
}
#[tracing::instrument(skip_all, fields(
device = %self.device.display(),
@ -89,11 +71,6 @@ impl Action for SystemdSysextMerge {
device,
action_state,
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Unmrging systemd-sysext");
// TODO(@Hoverbear): Handle proxy vars
execute_command(
@ -105,14 +82,16 @@ impl Action for SystemdSysextMerge {
.await
.map_err(|e| SystemdSysextMergeError::Command(e).boxed())?;
tracing::trace!("Unmerged systemd-sysext");
*action_state = ActionState::Completed;
Ok(())
}
fn action_state(&self) -> ActionState {
self.action_state
}
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
}
#[derive(Debug, thiserror::Error)]

View file

@ -5,18 +5,76 @@ pub mod linux;
use serde::{Deserialize, Serialize};
/// An action which can be reverted or completed, with an action state
///
/// This trait interacts with [`ActionImplementation`] which does the [`ActionState`] manipulation and provides some tracing facilities.
///
/// Instead of calling [`execute`][Action::execute] or [`revert`][Action::revert], you should prefer [`try_execute`][ActionImplementation::try_execute] and [`try_revert`][ActionImplementation::try_revert]
#[async_trait::async_trait]
#[typetag::serde(tag = "action")]
pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
fn describe_execute(&self) -> Vec<ActionDescription>;
fn describe_revert(&self) -> Vec<ActionDescription>;
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Box<dyn std::error::Error + Send + Sync>>;`
fn tracing_synopsis(&self) -> String;
fn execute_description(&self) -> Vec<ActionDescription>;
fn revert_description(&self) -> Vec<ActionDescription>;
/// Instead of calling [`execute`][Action::execute], you should prefer [`try_execute`][ActionImplementation::try_execute], so [`ActionState`] is handled correctly and tracing is done.
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
/// Instead of calling [`revert`][Action::revert], you should prefer [`try_revert`][ActionImplementation::try_revert], so [`ActionState`] is handled correctly and tracing is done.
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
fn action_state(&self) -> ActionState;
fn set_action_state(&mut self, new_state: ActionState);
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Box<dyn std::error::Error + Send + Sync>>;`
}
/// The main wrapper around [`Action`], handling [`ActionState`] and tracing.
#[async_trait::async_trait]
pub trait ActionImplementation: Action {
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state() == ActionState::Completed {
return vec![];
}
return self.execute_description();
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state() == ActionState::Uncompleted {
return vec![];
}
return self.revert_description();
}
/// You should prefer this ([`try_execute`][ActionImplementation::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing.
async fn try_execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if self.action_state() == ActionState::Completed {
tracing::trace!("Completed: (Already done) {}", self.tracing_synopsis());
return Ok(());
}
self.set_action_state(ActionState::Progress);
tracing::debug!("Executing: {}", self.tracing_synopsis());
self.execute().await?;
self.set_action_state(ActionState::Completed);
tracing::debug!("Completed: {}", self.tracing_synopsis());
Ok(())
}
/// You should prefer this ([`try_revert`][ActionImplementation::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing.
async fn try_revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if self.action_state() == ActionState::Uncompleted {
tracing::trace!("Reverted: (Already done) {}", self.tracing_synopsis());
return Ok(());
}
self.set_action_state(ActionState::Progress);
tracing::debug!("Reverting: {}", self.tracing_synopsis());
self.revert().await?;
tracing::debug!("Reverted: {}", self.tracing_synopsis());
self.set_action_state(ActionState::Uncompleted);
Ok(())
}
}
impl ActionImplementation for dyn Action {}
impl<A> ActionImplementation for A where A: Action {}
dyn_clone::clone_trait_object!(Action);
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy)]

View file

@ -120,7 +120,13 @@ impl CommandExecute for Install {
let error = eyre!(err).wrap_err("Install failure");
if !no_confirm {
tracing::error!("{:?}", error);
if !interaction::confirm(install_plan.describe_revert(explain)).await? {
if !interaction::confirm(
install_plan
.describe_revert(explain)
.map_err(|e| eyre!(e))?,
)
.await?
{
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
let rx2 = tx.subscribe();

View file

@ -53,7 +53,7 @@ impl CommandExecute for Uninstall {
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
if !no_confirm {
if !interaction::confirm(plan.describe_revert(explain)).await? {
if !interaction::confirm(plan.describe_revert(explain).map_err(|e| eyre!(e))?).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
}

View file

@ -25,8 +25,8 @@ use tokio::process::Command;
async fn execute_command(command: &mut Command) -> Result<Output, std::io::Error> {
// TODO(@hoverbear): When tokio releases past 1.21.2, add a process group https://github.com/DeterminateSystems/harmonic/issues/41#issuecomment-1309513073
tracing::trace!("Executing");
let command_str = format!("{:?}", command.as_std());
tracing::trace!("Executing `{command_str}`");
let output = command.output().await?;
match output.status.success() {
true => Ok(output),

View file

@ -2,10 +2,9 @@ use std::path::PathBuf;
use crossterm::style::Stylize;
use tokio::sync::broadcast::Receiver;
use tokio_util::sync::CancellationToken;
use crate::{
action::{Action, ActionDescription},
action::{Action, ActionDescription, ActionImplementation},
planner::Planner,
HarmonicError,
};
@ -103,7 +102,7 @@ impl InstallPlan {
}
}
if let Err(err) = action.execute().await {
if let Err(err) = action.try_execute().await {
if let Err(err) = write_receipt(self.clone()).await {
tracing::error!("Error saving receipt: {:?}", err);
}
@ -116,23 +115,37 @@ impl InstallPlan {
}
#[tracing::instrument(skip_all)]
pub fn describe_revert(&self, explain: bool) -> String {
pub fn describe_revert(
&self,
explain: bool,
) -> Result<String, Box<dyn std::error::Error + Sync + Send>> {
let Self { planner, actions } = self;
format!(
let buf = format!(
"\
This Nix uninstall is for:\n\
Operating System: {os_type}\n\
Init system: {init_type}\n\
Nix channels: {nix_channels}\n\
Nix uninstall plan\n\
\n\
Created by planner: {planner:?}
Planner: {planner}\n\
\n\
The following actions will be taken:\n\
{actions}
Planner settings:\n\
\n\
{plan_settings}\n\
\n\
The following actions will be taken{maybe_explain}:\n\
\n\
{actions}\n\
",
os_type = "Linux",
init_type = "systemd",
nix_channels = "todo",
maybe_explain = if !explain {
" (`--explain` for more context)"
} else {
""
},
planner = planner.typetag_name(),
plan_settings = planner
.settings()?
.into_iter()
.map(|(k, v)| format!("* {k}: {v}", k = k.bold().white()))
.collect::<Vec<_>>()
.join("\n"),
actions = actions
.iter()
.rev()
@ -145,17 +158,18 @@ impl InstallPlan {
} = desc;
let mut buf = String::default();
buf.push_str(&format!("* {description}\n"));
buf.push_str(&format!("* {description}"));
if explain {
for line in explanation {
buf.push_str(&format!(" {line}\n"));
buf.push_str(&format!("\n {line}"));
}
}
buf
})
.collect::<Vec<_>>()
.join("\n"),
)
);
Ok(buf)
}
#[tracing::instrument(skip_all)]
@ -184,7 +198,7 @@ impl InstallPlan {
}
}
if let Err(err) = action.revert().await {
if let Err(err) = action.try_revert().await {
if let Err(err) = write_receipt(self.clone()).await {
tracing::error!("Error saving receipt: {:?}", err);
}