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,20 +48,19 @@ impl ConfigureNixDaemonService {
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "configure_nix_daemon")] #[typetag::serde(name = "configure_nix_daemon")]
impl Action for ConfigureNixDaemonService { impl Action for ConfigureNixDaemonService {
fn describe_execute(&self) -> Vec<ActionDescription> { fn tracing_synopsis(&self) -> String {
if self.action_state == ActionState::Completed { "Configure Nix daemon related settings with systemd".to_string()
vec![] }
} else { fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
"Configure Nix daemon related settings with systemd".to_string(), self.tracing_synopsis(),
vec![ vec![
"Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(), "Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(),
"Run `systemctl link {SERVICE_SRC}`".to_string(), "Run `systemctl link {SERVICE_SRC}`".to_string(),
"Run `systemctl link {SOCKET_SRC}`".to_string(), "Run `systemctl link {SOCKET_SRC}`".to_string(),
"Run `systemctl daemon-reload`".to_string(), "Run `systemctl daemon-reload`".to_string(),
], ],
)] )]
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -166,31 +165,20 @@ impl Action for ConfigureNixDaemonService {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted { vec![ActionDescription::new(
vec![] "Unconfigure Nix daemon related settings with systemd".to_string(),
} else { vec![
vec![ActionDescription::new( "Run `systemctl disable {SOCKET_SRC}`".to_string(),
"Unconfigure Nix daemon related settings with systemd".to_string(), "Run `systemctl disable {SERVICE_SRC}`".to_string(),
vec![ "Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string(),
"Run `systemctl disable {SOCKET_SRC}`".to_string(), "Run `systemctl daemon-reload`".to_string(),
"Run `systemctl disable {SERVICE_SRC}`".to_string(), ],
"Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string(), )]
"Run `systemctl daemon-reload`".to_string(),
],
)]
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 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() { match OperatingSystem::host() {
OperatingSystem::MacOSX { OperatingSystem::MacOSX {
major: _, major: _,
@ -278,14 +266,16 @@ impl Action for ConfigureNixDaemonService {
}, },
}; };
tracing::trace!("Unconfigured nix daemon service");
*action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState { fn action_state(&self) -> ActionState {
self.action_state self.action_state
} }
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -39,6 +39,10 @@ impl CreateDirectory {
CreateDirectoryError::GettingMetadata(path.to_path_buf(), e).boxed() CreateDirectoryError::GettingMetadata(path.to_path_buf(), e).boxed()
})?; })?;
if metadata.is_dir() { if metadata.is_dir() {
tracing::debug!(
"Creating directory `{}` already complete, skipping",
path.display(),
);
// TODO: Validate owner/group... // TODO: Validate owner/group...
ActionState::Completed ActionState::Completed
} else { } else {
@ -69,23 +73,12 @@ impl CreateDirectory {
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "create_directory")] #[typetag::serde(name = "create_directory")]
impl Action for CreateDirectory { impl Action for CreateDirectory {
fn describe_execute(&self) -> Vec<ActionDescription> { fn tracing_synopsis(&self) -> String {
let Self { format!("Create directory `{}`", self.path.display())
path, }
user: _,
group: _, fn execute_description(&self) -> Vec<ActionDescription> {
mode: _, vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
force_prune_on_revert: _,
action_state,
} = &self;
if *action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Create the directory `{}`", path.display()),
vec![],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -101,13 +94,8 @@ impl Action for CreateDirectory {
group, group,
mode, mode,
force_prune_on_revert: _, force_prune_on_revert: _,
action_state, action_state: _,
} = self; } = 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 { let gid = if let Some(group) = group {
Some( Some(
@ -143,12 +131,10 @@ impl Action for CreateDirectory {
})?; })?;
} }
tracing::trace!("Created directory");
*action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
let Self { let Self {
path, path,
user: _, user: _,
@ -157,22 +143,18 @@ impl Action for CreateDirectory {
force_prune_on_revert, force_prune_on_revert,
action_state: _, action_state: _,
} = &self; } = &self;
if self.action_state == ActionState::Uncompleted { vec![ActionDescription::new(
vec![] format!(
} else { "Remove the directory `{}`{}",
vec![ActionDescription::new( path.display(),
format!( if *force_prune_on_revert {
"Remove the directory `{}`{}", ""
path.display(), } else {
if *force_prune_on_revert { " if no other contents exists"
"" }
} else { ),
" if no other contents exists" vec![],
} )]
),
vec![],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -188,15 +170,8 @@ impl Action for CreateDirectory {
group: _, group: _,
mode: _, mode: _,
force_prune_on_revert, force_prune_on_revert,
action_state, action_state: _,
} = self; } = 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 let is_empty = path
.read_dir() .read_dir()
@ -207,17 +182,21 @@ impl Action for CreateDirectory {
(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| 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(()) Ok(())
} }
fn action_state(&self) -> ActionState { fn action_state(&self) -> ActionState {
self.action_state self.action_state
} }
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,18 +27,18 @@ impl MoveUnpackedNix {
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "mount_unpacked_nix")] #[typetag::serde(name = "mount_unpacked_nix")]
impl Action for MoveUnpackedNix { impl Action for MoveUnpackedNix {
fn describe_execute(&self) -> Vec<ActionDescription> { fn tracing_synopsis(&self) -> String {
if self.action_state == ActionState::Completed { "Move the downloaded Nix into `/nix`".to_string()
vec![] }
} else {
vec![ActionDescription::new( fn execute_description(&self) -> Vec<ActionDescription> {
format!("Move the downloaded Nix into `/nix`"), vec![ActionDescription::new(
vec![format!( format!("Move the downloaded Nix into `/nix`"),
"Nix is being downloaded to `{}` and should be in `nix`", vec![format!(
self.src.display(), "Nix is being downloaded to `{}` and should be in `nix`",
)], self.src.display(),
)] )],
} )]
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -64,9 +64,9 @@ impl Action for MoveUnpackedNix {
"Did not expect to find multiple nix paths, please report this" "Did not expect to find multiple nix paths, please report this"
); );
let found_nix_path = found_nix_paths.into_iter().next().unwrap(); let found_nix_path = found_nix_paths.into_iter().next().unwrap();
tracing::trace!("Renaming");
let src_store = found_nix_path.join("store"); let src_store = found_nix_path.join("store");
let dest = Path::new(DEST); let dest = Path::new(DEST);
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| {
@ -81,12 +81,8 @@ impl Action for MoveUnpackedNix {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted { vec![/* Deliberately empty -- this is a noop */]
vec![]
} else {
vec![/* Deliberately empty -- this is a noop */]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -94,22 +90,17 @@ impl Action for MoveUnpackedNix {
dest = DEST, dest = DEST,
))] ))]
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { // Noop
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;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState { fn action_state(&self) -> ActionState {
self.action_state self.action_state
} }
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

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

View file

@ -2,7 +2,7 @@ use crate::{
action::{ action::{
base::{ConfigureNixDaemonService, SetupDefaultProfile}, base::{ConfigureNixDaemonService, SetupDefaultProfile},
common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration}, common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
Action, ActionDescription, ActionState, Action, ActionDescription, ActionImplementation, ActionState,
}, },
channel_value::ChannelValue, channel_value::ChannelValue,
BoxableError, CommonSettings, BoxableError, CommonSettings,
@ -65,7 +65,11 @@ impl ConfigureNix {
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "configure_nix")] #[typetag::serde(name = "configure_nix")]
impl Action for ConfigureNix { 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 { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service, configure_nix_daemon_service,
@ -75,18 +79,14 @@ impl Action for ConfigureNix {
action_state: _, action_state: _,
} = &self; } = &self;
if self.action_state == ActionState::Completed { let mut buf = setup_default_profile.execute_description();
vec![] buf.append(&mut configure_nix_daemon_service.execute_description());
} else { buf.append(&mut place_nix_configuration.execute_description());
let mut buf = setup_default_profile.describe_execute(); buf.append(&mut place_channel_configuration.execute_description());
buf.append(&mut configure_nix_daemon_service.describe_execute()); if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut place_nix_configuration.describe_execute()); buf.append(&mut configure_shell_profile.execute_description());
buf.append(&mut place_channel_configuration.describe_execute());
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.describe_execute());
}
buf
} }
buf
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -97,37 +97,29 @@ impl Action for ConfigureNix {
place_nix_configuration, place_nix_configuration,
place_channel_configuration, place_channel_configuration,
configure_shell_profile, configure_shell_profile,
action_state, action_state: _,
} = self; } = 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 { if let Some(configure_shell_profile) = configure_shell_profile {
tokio::try_join!( tokio::try_join!(
async move { setup_default_profile.execute().await }, async move { setup_default_profile.try_execute().await },
async move { place_nix_configuration.execute().await }, async move { place_nix_configuration.try_execute().await },
async move { place_channel_configuration.execute().await }, async move { place_channel_configuration.try_execute().await },
async move { configure_shell_profile.execute().await }, async move { configure_shell_profile.try_execute().await },
)?; )?;
} else { } else {
tokio::try_join!( tokio::try_join!(
async move { setup_default_profile.execute().await }, async move { setup_default_profile.try_execute().await },
async move { place_nix_configuration.execute().await }, async move { place_nix_configuration.try_execute().await },
async move { place_channel_configuration.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(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service, configure_nix_daemon_service,
@ -137,20 +129,16 @@ impl Action for ConfigureNix {
action_state: _, action_state: _,
} = &self; } = &self;
if self.action_state == ActionState::Uncompleted { let mut buf = Vec::default();
vec![] if let Some(configure_shell_profile) = configure_shell_profile {
} else { buf.append(&mut configure_shell_profile.revert_description());
let mut buf = Vec::default();
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.describe_revert());
}
buf.append(&mut place_channel_configuration.describe_revert());
buf.append(&mut place_nix_configuration.describe_revert());
buf.append(&mut configure_nix_daemon_service.describe_revert());
buf.append(&mut setup_default_profile.describe_revert());
buf
} }
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)] #[tracing::instrument(skip_all)]
@ -161,29 +149,25 @@ impl Action for ConfigureNix {
place_nix_configuration, place_nix_configuration,
place_channel_configuration, place_channel_configuration,
configure_shell_profile, configure_shell_profile,
action_state, action_state: _,
} = self; } = 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 { 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_channel_configuration.try_revert().await?;
place_nix_configuration.revert().await?; place_nix_configuration.try_revert().await?;
setup_default_profile.revert().await?; setup_default_profile.try_revert().await?;
tracing::trace!("Unconfigured nix");
*action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState { fn action_state(&self) -> ActionState {
self.action_state 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::base::{CreateOrAppendFile, CreateOrAppendFileError};
use crate::action::{Action, ActionDescription, ActionState}; use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
use crate::BoxableError; use crate::BoxableError;
use std::path::Path; use std::path::Path;
@ -57,29 +57,23 @@ impl ConfigureShellProfile {
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "configure_shell_profile")] #[typetag::serde(name = "configure_shell_profile")]
impl Action for ConfigureShellProfile { impl Action for ConfigureShellProfile {
fn describe_execute(&self) -> Vec<ActionDescription> { fn tracing_synopsis(&self) -> String {
if self.action_state == ActionState::Completed { "Configure the shell profiles".to_string()
vec![] }
} else {
vec![ActionDescription::new( fn execute_description(&self) -> Vec<ActionDescription> {
"Configure the shell profiles".to_string(), vec![ActionDescription::new(
vec!["Update shell profiles to import Nix".to_string()], self.tracing_synopsis(),
)] vec!["Update shell profiles to import Nix".to_string()],
} )]
} }
#[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<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { let Self {
create_or_append_files, create_or_append_files,
action_state, action_state: _,
} = self; } = 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 set = JoinSet::new();
let mut errors = Vec::default(); 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() { 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.execute().await?; create_or_append_file_clone.try_execute().await?;
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok(( Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok((
idx, idx,
create_or_append_file_clone, create_or_append_file_clone,
@ -113,34 +107,22 @@ impl Action for ConfigureShellProfile {
} }
} }
tracing::trace!("Configured shell profile");
*action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted { vec![ActionDescription::new(
vec![] "Unconfigure the shell profiles".to_string(),
} else { vec!["Update shell profiles to no longer import Nix".to_string()],
vec![ActionDescription::new( )]
"Unconfigure the shell profiles".to_string(),
vec!["Update shell profiles to no longer import Nix".to_string()],
)]
}
} }
#[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<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { let Self {
create_or_append_files, create_or_append_files,
action_state, action_state: _,
} = self; } = 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 set = JoinSet::new();
let mut errors = Vec::default(); let mut errors = Vec::default();
@ -174,14 +156,16 @@ impl Action for ConfigureShellProfile {
} }
} }
tracing::trace!("Unconfigured shell profile");
*action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState { fn action_state(&self) -> ActionState {
self.action_state self.action_state
} }
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,5 +1,5 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError}; use crate::action::base::{CreateDirectory, CreateDirectoryError};
use crate::action::{Action, ActionDescription, ActionState}; use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
const PATHS: &[&str] = &[ const PATHS: &[&str] = &[
"/nix/var", "/nix/var",
@ -42,102 +42,86 @@ impl CreateNixTree {
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "create_nix_tree")] #[typetag::serde(name = "create_nix_tree")]
impl Action for CreateNixTree { impl Action for CreateNixTree {
fn describe_execute(&self) -> Vec<ActionDescription> { fn tracing_synopsis(&self) -> String {
if self.action_state == ActionState::Completed { "Create a directory tree in `/nix`".to_string()
vec![] }
} else {
vec![ActionDescription::new( fn execute_description(&self) -> Vec<ActionDescription> {
format!("Create a directory tree in `/nix`"), vec![ActionDescription::new(
vec![ self.tracing_synopsis(),
format!( vec![
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" format!(
), "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
format!( ),
"Creates: {}", format!(
PATHS "Creates: {}",
.iter() PATHS
.map(|v| format!("`{v}`")) .iter()
.collect::<Vec<_>>() .map(|v| format!("`{v}`"))
.join(", ") .collect::<Vec<_>>()
), .join(", ")
], ),
)] ],
} )]
} }
#[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<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { let Self {
create_directories, create_directories,
action_state, action_state: _,
} = self; } = 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 // Just do sequential since parallelizing this will have little benefit
for create_directory in create_directories { 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(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted { vec![ActionDescription::new(
vec![] format!("Remove the directory tree in `/nix`"),
} else { vec![
vec![ActionDescription::new( format!(
format!("Remove the directory tree in `/nix`"), "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
vec![ ),
format!( format!(
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" "Removes: {}",
), PATHS
format!( .iter()
"Removes: {}", .rev()
PATHS .map(|v| format!("`{v}`"))
.iter() .collect::<Vec<_>>()
.rev() .join(", ")
.map(|v| format!("`{v}`")) ),
.collect::<Vec<_>>() ],
.join(", ") )]
),
],
)]
}
} }
#[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<(), Box<dyn std::error::Error + Send + Sync>> {
let Self { let Self {
create_directories, create_directories,
action_state, action_state: _,
} = self; } = 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 // Just do sequential since parallelizing this will have little benefit
for create_directory in create_directories.iter_mut().rev() { for create_directory in create_directories.iter_mut().rev() {
create_directory.revert().await? create_directory.revert().await?
} }
tracing::trace!("Deleted nix tree");
*action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState { fn action_state(&self) -> ActionState {
self.action_state self.action_state
} }
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

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

View file

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

View file

@ -1,5 +1,5 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError}; 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_FOLDER: &str = "/etc/nix";
const NIX_CONF: &str = "/etc/nix/nix.conf"; const NIX_CONF: &str = "/etc/nix/nix.conf";
@ -44,18 +44,18 @@ impl PlaceNixConfiguration {
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "place_nix_configuration")] #[typetag::serde(name = "place_nix_configuration")]
impl Action for PlaceNixConfiguration { impl Action for PlaceNixConfiguration {
fn describe_execute(&self) -> Vec<ActionDescription> { fn tracing_synopsis(&self) -> String {
if self.action_state == ActionState::Completed { format!("Place the nix configuration in `{NIX_CONF}`")
vec![] }
} else {
vec![ActionDescription::new( fn execute_description(&self) -> Vec<ActionDescription> {
format!("Place the nix configuration in `{NIX_CONF}`"), vec![ActionDescription::new(
vec![ self.tracing_synopsis(),
"This file is read by the Nix daemon to set its configuration options at runtime." vec![
.to_string(), "This file is read by the Nix daemon to set its configuration options at runtime."
], .to_string(),
)] ],
} )]
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -63,35 +63,23 @@ impl Action for PlaceNixConfiguration {
let Self { let Self {
create_file, create_file,
create_directory, create_directory,
action_state, action_state: _,
} = self; } = 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_directory.try_execute().await?;
create_file.execute().await?; create_file.try_execute().await?;
tracing::trace!("Placed Nix configuration");
*action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted { vec![ActionDescription::new(
vec![] format!("Remove the nix configuration in `{NIX_CONF}`"),
} else { vec![
vec![ActionDescription::new( "This file is read by the Nix daemon to set its configuration options at runtime."
format!("Remove the nix configuration in `{NIX_CONF}`"), .to_string(),
vec![ ],
"This file is read by the Nix daemon to set its configuration options at runtime." )]
.to_string(),
],
)]
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -99,26 +87,22 @@ impl Action for PlaceNixConfiguration {
let Self { let Self {
create_file, create_file,
create_directory, create_directory,
action_state, action_state: _,
} = self; } = 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_file.revert().await?;
create_directory.revert().await?; create_directory.revert().await?;
tracing::trace!("Removed nix configuration");
*action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState { fn action_state(&self) -> ActionState {
self.action_state self.action_state
} }
fn set_action_state(&mut self, action_state: ActionState) {
self.action_state = action_state;
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,18 +5,76 @@ pub mod linux;
use serde::{Deserialize, Serialize}; 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] #[async_trait::async_trait]
#[typetag::serde(tag = "action")] #[typetag::serde(tag = "action")]
pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone { pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
fn describe_execute(&self) -> Vec<ActionDescription>; fn tracing_synopsis(&self) -> String;
fn describe_revert(&self) -> Vec<ActionDescription>; fn execute_description(&self) -> Vec<ActionDescription>;
fn revert_description(&self) -> Vec<ActionDescription>;
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Box<dyn std::error::Error + Send + Sync>>;` /// 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>>; 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>>; async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
fn action_state(&self) -> ActionState; 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); dyn_clone::clone_trait_object!(Action);
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy)] #[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"); let error = eyre!(err).wrap_err("Install failure");
if !no_confirm { if !no_confirm {
tracing::error!("{:?}", error); 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; interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
} }
let rx2 = tx.subscribe(); 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)?; let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
if !no_confirm { 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; 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> { 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 // 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()); let command_str = format!("{:?}", command.as_std());
tracing::trace!("Executing `{command_str}`");
let output = command.output().await?; let output = command.output().await?;
match output.status.success() { match output.status.success() {
true => Ok(output), true => Ok(output),

View file

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