Ponder plan/revert
This commit is contained in:
parent
3fccbef2da
commit
b04d26abf1
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -437,11 +437,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
|
@ -621,6 +620,7 @@ dependencies = [
|
|||
"nix",
|
||||
"owo-colors",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"tar",
|
||||
"target-lexicon",
|
||||
"tempdir",
|
||||
|
@ -630,6 +630,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"valuable",
|
||||
"walkdir",
|
||||
"xz2",
|
||||
|
@ -729,11 +730,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
@ -860,12 +860,6 @@ dependencies = [
|
|||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
|
@ -982,9 +976,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
|
@ -1277,6 +1271,20 @@ name = "serde"
|
|||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
|
@ -1680,14 +1688,14 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
|||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -31,3 +31,5 @@ bytes = "1.2.1"
|
|||
tar = "0.4.38"
|
||||
nix = { version = "0.25.0", features = ["user", "fs"], default-features = false }
|
||||
walkdir = "2.3.2"
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
url = { version = "2.3.1", features = ["serde"] }
|
||||
|
|
48
src/actions/create_user.rs
Normal file
48
src/actions/create_user.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use crate::{settings::InstallSettings, HarmonicError};
|
||||
|
||||
use super::{Actionable, ActionReceipt, Revertable};
|
||||
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct CreateUser {
|
||||
name: String,
|
||||
uid: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct CreateUserReceipt {
|
||||
name: String,
|
||||
uid: usize,
|
||||
}
|
||||
|
||||
impl CreateUser {
|
||||
pub fn plan(name: String, uid: usize) -> Self {
|
||||
Self { name, uid }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Actionable<'a> for CreateUser {
|
||||
fn description(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
||||
let Self { name, uid } = self;
|
||||
Ok(ActionReceipt::CreateUser(CreateUserReceipt { name, uid }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Revertable<'a> for CreateUserReceipt {
|
||||
fn description(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn revert(self) -> Result<(), HarmonicError> {
|
||||
todo!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
90
src/actions/create_users.rs
Normal file
90
src/actions/create_users.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use tokio::task::JoinSet;
|
||||
|
||||
use crate::{settings::InstallSettings, HarmonicError};
|
||||
|
||||
use super::{Actionable, CreateUser, ActionReceipt, create_user::CreateUserReceipt, Revertable};
|
||||
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct CreateUsers {
|
||||
children: Vec<CreateUser>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct CreateUsersReceipt {
|
||||
children: Vec<ActionReceipt>,
|
||||
}
|
||||
|
||||
impl CreateUsers {
|
||||
pub fn plan(nix_build_user_prefix: &String, nix_build_user_id_base: usize, daemon_user_count: usize) -> Self {
|
||||
let children = (0..daemon_user_count).map(|count| CreateUser::plan(
|
||||
format!("{nix_build_user_prefix}{count}"),
|
||||
nix_build_user_id_base + count
|
||||
)).collect();
|
||||
Self { children }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Actionable<'a> for CreateUsers {
|
||||
fn description(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
||||
// TODO(@hoverbear): Abstract this, it will be common
|
||||
let Self { children } = self;
|
||||
let mut set = JoinSet::new();
|
||||
let mut successes = Vec::with_capacity(children.len());
|
||||
let mut errors = Vec::default();
|
||||
|
||||
for child in children {
|
||||
let _abort_handle = set.spawn(async move { child.execute().await });
|
||||
}
|
||||
|
||||
while let Some(result) = set.join_next().await {
|
||||
match result {
|
||||
Ok(Ok(success)) => successes.push(success),
|
||||
Ok(Err(e)) => errors.push(e),
|
||||
Err(e) => errors.push(e.into()),
|
||||
};
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
// If we got an error in a child, we need to revert the successful ones:
|
||||
let mut failed_reverts = Vec::default();
|
||||
for success in successes {
|
||||
match success.revert().await {
|
||||
Ok(()) => (),
|
||||
Err(e) => failed_reverts.push(e),
|
||||
}
|
||||
}
|
||||
|
||||
if !failed_reverts.is_empty() {
|
||||
return Err(HarmonicError::FailedReverts(errors, failed_reverts));
|
||||
}
|
||||
|
||||
if errors.len() == 1 {
|
||||
return Err(errors.into_iter().next().unwrap())
|
||||
} else {
|
||||
return Err(HarmonicError::Multiple(errors))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ActionReceipt::CreateUsers(CreateUsersReceipt{ children: successes }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Revertable<'a> for CreateUsersReceipt {
|
||||
fn description(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn revert(self) -> Result<(), HarmonicError> {
|
||||
todo!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
73
src/actions/mod.rs
Normal file
73
src/actions/mod.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
mod start_nix_daemon_service;
|
||||
mod create_users;
|
||||
mod create_user;
|
||||
pub use start_nix_daemon_service::{StartNixDaemonService, StartNixDaemonServiceReceipt};
|
||||
pub use create_user::{CreateUser, CreateUserReceipt};
|
||||
pub use create_users::{CreateUsers, CreateUsersReceipt};
|
||||
|
||||
use crate::{HarmonicError, settings::InstallSettings};
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub enum Action {
|
||||
CreateUsers(CreateUsers),
|
||||
CreateUser(CreateUser),
|
||||
StartNixDaemonService(StartNixDaemonService),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub enum ActionReceipt {
|
||||
CreateUsers(CreateUsersReceipt),
|
||||
CreateUser(CreateUserReceipt),
|
||||
StartNixDaemonService(StartNixDaemonServiceReceipt),
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Actionable<'a> for Action {
|
||||
fn description(&self) -> String {
|
||||
match self {
|
||||
Action::StartNixDaemonService(i) => i.description(),
|
||||
Action::CreateUser(i) => i.description(),
|
||||
Action::CreateUsers(i) => i.description(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
||||
match self {
|
||||
Action::StartNixDaemonService(i) => i.execute().await,
|
||||
Action::CreateUser(i) => i.execute().await,
|
||||
Action::CreateUsers(i) => i.execute().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Revertable<'a> for ActionReceipt {
|
||||
fn description(&self) -> String {
|
||||
match self {
|
||||
ActionReceipt::StartNixDaemonService(i) => i.description(),
|
||||
ActionReceipt::CreateUser(i) => i.description(),
|
||||
ActionReceipt::CreateUsers(i) => i.description(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn revert(self) -> Result<(), HarmonicError> {
|
||||
match self {
|
||||
ActionReceipt::StartNixDaemonService(i) => i.revert().await,
|
||||
ActionReceipt::CreateUser(i) => i.revert().await,
|
||||
ActionReceipt::CreateUsers(i) => i.revert().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
||||
fn description(&self) -> String;
|
||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Revertable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
||||
fn description(&self) -> String;
|
||||
async fn revert(self) -> Result<(), HarmonicError>;
|
||||
}
|
45
src/actions/start_nix_daemon_service.rs
Normal file
45
src/actions/start_nix_daemon_service.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use crate::{settings::InstallSettings, HarmonicError};
|
||||
|
||||
use super::{Actionable, ActionReceipt, Revertable};
|
||||
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct StartNixDaemonService {
|
||||
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct StartNixDaemonServiceReceipt {
|
||||
}
|
||||
|
||||
impl StartNixDaemonService {
|
||||
pub fn plan() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Actionable<'a> for StartNixDaemonService {
|
||||
fn description(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> Revertable<'a> for StartNixDaemonServiceReceipt {
|
||||
fn description(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn revert(self) -> Result<(), HarmonicError> {
|
||||
todo!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
use serde::de::value::Error;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum HarmonicError {
|
||||
#[error("Request error")]
|
||||
|
@ -47,4 +49,8 @@ pub enum HarmonicError {
|
|||
GroupId(String, nix::errno::Errno),
|
||||
#[error("Getting group `{0}`")]
|
||||
NoGroup(String),
|
||||
#[error("Errors with additional failures during reverts: {}\nDuring Revert:{}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "), .1.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
|
||||
FailedReverts(Vec<HarmonicError>, Vec<HarmonicError>),
|
||||
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
|
||||
Multiple(Vec<HarmonicError>)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
mod error;
|
||||
mod actions;
|
||||
mod plan;
|
||||
mod settings;
|
||||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::Permissions,
|
||||
|
|
69
src/plan.rs
Normal file
69
src/plan.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{settings::InstallSettings, actions::{Action, StartNixDaemonService, Actionable, ActionReceipt, Revertable}, HarmonicError};
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
struct InstallPlan {
|
||||
settings: InstallSettings,
|
||||
|
||||
/** Bootstrap the install
|
||||
|
||||
* There are roughly three phases:
|
||||
* download_nix --------------------------------------> move_downloaded_nix
|
||||
* create_group -> create_users -> create_directories -> move_downloaded_nix
|
||||
* place_channel_configuration
|
||||
* place_nix_configuration
|
||||
* ---
|
||||
* setup_default_profile
|
||||
* configure_nix_daemon_service
|
||||
* configure_shell_profile
|
||||
* ---
|
||||
* start_nix_daemon_service
|
||||
*/
|
||||
actions: Vec<Action>,
|
||||
}
|
||||
|
||||
impl InstallPlan {
|
||||
async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
||||
let start_nix_daemon_service = StartNixDaemonService::plan();
|
||||
|
||||
let actions = vec![
|
||||
Action::StartNixDaemonService(start_nix_daemon_service),
|
||||
];
|
||||
Ok(Self { settings, actions })
|
||||
}
|
||||
async fn install(self) -> Result<Receipt, HarmonicError> {
|
||||
let mut receipt = Receipt::default();
|
||||
// This is **deliberately sequential**.
|
||||
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
||||
// The plan itself represents the concept of the sequence of stages.
|
||||
for action in self.actions {
|
||||
match action.execute().await {
|
||||
Ok(action_receipt) => receipt.actions.push(action_receipt),
|
||||
Err(err) => {
|
||||
let mut revert_errs = Vec::default();
|
||||
|
||||
for action_receipt in receipt.actions {
|
||||
if let Err(err) = action_receipt.revert().await {
|
||||
revert_errs.push(err);
|
||||
}
|
||||
}
|
||||
if !revert_errs.is_empty() {
|
||||
return Err(HarmonicError::FailedReverts(vec![err], revert_errs))
|
||||
}
|
||||
|
||||
return Err(err)
|
||||
|
||||
},
|
||||
};
|
||||
}
|
||||
Ok(receipt)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
struct Receipt {
|
||||
actions: Vec<ActionReceipt>,
|
||||
}
|
55
src/settings.rs
Normal file
55
src/settings.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use url::Url;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct InstallSettings {
|
||||
dry_run: bool,
|
||||
daemon_user_count: usize,
|
||||
channels: Vec<(String, Url)>,
|
||||
modify_profile: bool,
|
||||
nix_build_group_name: String,
|
||||
nix_build_group_id: usize,
|
||||
nix_build_user_prefix: String,
|
||||
nix_build_user_id_base: usize,
|
||||
}
|
||||
|
||||
// Builder Pattern
|
||||
impl InstallSettings {
|
||||
pub fn dry_run(&mut self, dry_run: bool) -> &mut Self {
|
||||
self.dry_run = dry_run;
|
||||
self
|
||||
}
|
||||
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
|
||||
self.daemon_user_count = count;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
|
||||
self.channels = channels.into_iter().collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn modify_profile(&mut self, toggle: bool) -> &mut Self {
|
||||
self.modify_profile = toggle;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nix_build_group_name(&mut self, val: String) -> &mut Self {
|
||||
self.nix_build_group_name = val;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nix_build_group_id(&mut self, count: usize) -> &mut Self {
|
||||
self.nix_build_group_id = count;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nix_build_user_prefix(&mut self, val: String) -> &mut Self {
|
||||
self.nix_build_user_prefix = val;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nix_build_user_id_base(&mut self, count: usize) -> &mut Self {
|
||||
self.nix_build_user_id_base = count;
|
||||
self
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue