Install can detect existing /nix/receipt.json

This commit is contained in:
Ana Hobden 2022-11-08 09:58:53 -08:00
parent 6678b1cdee
commit e57311a807
4 changed files with 66 additions and 15 deletions

View file

@ -1,6 +1,9 @@
use std::{path::PathBuf, process::ExitCode};
use std::{
path::{Path, PathBuf},
process::ExitCode,
};
use crate::BuiltinPlanner;
use crate::{plan::RECEIPT_LOCATION, BuiltinPlanner, InstallPlan, Planner};
use clap::{ArgAction, Parser};
use eyre::{eyre, WrapErr};
@ -8,7 +11,7 @@ use crate::{cli::CommandExecute, interaction};
/// Execute an install (possibly using an existing plan)
#[derive(Debug, Parser)]
#[command(args_conflicts_with_subcommands = true)]
#[command(args_conflicts_with_subcommands = true, arg_required_else_help = true)]
pub struct Install {
#[clap(
long,
@ -29,7 +32,7 @@ pub struct Install {
pub plan: Option<PathBuf>,
#[clap(subcommand)]
pub planner: BuiltinPlanner,
pub planner: Option<BuiltinPlanner>,
}
#[async_trait::async_trait]
@ -43,28 +46,61 @@ impl CommandExecute for Install {
explain,
} = self;
let mut plan = match &plan {
Some(plan_path) => {
let install_plan_string = tokio::fs::read_to_string(&plan_path)
let existing_receipt: Option<InstallPlan> = match Path::new(RECEIPT_LOCATION).exists() {
true => {
let install_plan_string = tokio::fs::read_to_string(&RECEIPT_LOCATION)
.await
.wrap_err("Reading plan")?;
Some(serde_json::from_str(&install_plan_string)?)
},
false => None,
};
let mut install_plan = match (planner, plan) {
(Some(planner), None) => {
let chosen_planner: Box<dyn Planner> = planner.clone().boxed();
match existing_receipt {
Some(existing_receipt) => {
if existing_receipt.planner.typetag_name() == chosen_planner.typetag_name() {
existing_receipt
} else {
return Err(eyre!("Found existing plan in `/nix/receipt.json` which used a different planner, try uninstalling the existing install"))
}
} ,
None => {
planner.plan().await.map_err(|e| eyre!(e))?
},
}
},
(None, Some(plan_path)) => {
let install_plan_string = tokio::fs::read_to_string(&plan_path)
.await
.wrap_err("Reading plan")?;
serde_json::from_str(&install_plan_string)?
},
None => planner.plan().await.map_err(|e| eyre!(e))?,
(None, None) => return Err(eyre!("`--plan` or a planner is required")),
(Some(_), Some(_)) => return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense")),
};
if !no_confirm {
if !interaction::confirm(plan.describe_execute(explain).map_err(|e| eyre!(e))?).await? {
if !interaction::confirm(
install_plan
.describe_execute(explain)
.map_err(|e| eyre!(e))?,
)
.await?
{
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
}
if let Err(err) = plan.install().await {
if let Err(err) = install_plan.install().await {
tracing::error!("{:?}", eyre!(err));
if !interaction::confirm(plan.describe_revert(explain)).await? {
if !interaction::confirm(install_plan.describe_revert(explain)).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
plan.revert().await?
install_plan.revert().await?
}
Ok(ExitCode::SUCCESS)

View file

@ -1,6 +1,6 @@
use std::{path::PathBuf, process::ExitCode};
use crate::InstallPlan;
use crate::{plan::RECEIPT_LOCATION, InstallPlan};
use clap::{ArgAction, Parser};
use eyre::WrapErr;
@ -23,7 +23,7 @@ pub struct Uninstall {
global = true
)]
pub explain: bool,
#[clap(default_value = "/nix/receipt.json")]
#[clap(default_value = RECEIPT_LOCATION)]
pub receipt: PathBuf,
}

View file

@ -8,6 +8,8 @@ use crate::{
HarmonicError,
};
pub const RECEIPT_LOCATION: &str = "/nix/receipt.json";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct InstallPlan {
pub(crate) actions: Vec<Box<dyn Action>>,
@ -166,7 +168,7 @@ async fn write_receipt(plan: InstallPlan) -> Result<(), HarmonicError> {
tokio::fs::create_dir_all("/nix")
.await
.map_err(|e| HarmonicError::RecordingReceipt(PathBuf::from("/nix"), e))?;
let install_receipt_path = PathBuf::from("/nix/receipt.json");
let install_receipt_path = PathBuf::from(RECEIPT_LOCATION);
let self_json =
serde_json::to_string_pretty(&plan).map_err(HarmonicError::SerializingReceipt)?;
tokio::fs::write(&install_receipt_path, self_json)

View file

@ -16,6 +16,12 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
fn describe(
&self,
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>>;
fn boxed(self) -> Box<dyn Planner>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
dyn_clone::clone_trait_object!(Planner);
@ -56,6 +62,13 @@ impl BuiltinPlanner {
BuiltinPlanner::SteamDeck(planner) => planner.plan().await,
}
}
pub fn boxed(self) -> Box<dyn Planner> {
match self {
BuiltinPlanner::LinuxMulti(i) => i.boxed(),
BuiltinPlanner::DarwinMulti(i) => i.boxed(),
BuiltinPlanner::SteamDeck(i) => i.boxed(),
}
}
}
#[derive(thiserror::Error, Debug)]