Improve messaging when /nix/receipt.json is already found (#491)
* Improve messaing when receipt already found * Further improvement * Use a const for message
This commit is contained in:
parent
c3bc75f064
commit
0f231b715f
|
@ -24,6 +24,12 @@ use color_eyre::{
|
|||
};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
const EXISTING_INCOMPATIBLE_PLAN_GUIDANCE: &'static str = "\
|
||||
If you are trying to upgrade Nix, try running `sudo -i nix upgrade-nix` instead.\n\
|
||||
If you are trying to install Nix over an existing install (from an incompatible `nix-installer` install), try running `/nix/nix-installer uninstall` then try to install again.\n\
|
||||
If you are using `nix-installer` in an automated curing process and seeing this message, consider pinning the version you use via https://github.com/DeterminateSystems/nix-installer#accessing-other-versions.\
|
||||
";
|
||||
|
||||
/// Execute an install (possibly using an existing plan)
|
||||
///
|
||||
/// To pass custom options, select a planner, for example `nix-installer install linux-multi --help`
|
||||
|
@ -78,7 +84,11 @@ impl CommandExecute for Install {
|
|||
let install_plan_string = tokio::fs::read_to_string(&RECEIPT_LOCATION)
|
||||
.await
|
||||
.wrap_err("Reading plan")?;
|
||||
Some(serde_json::from_str(&install_plan_string)?)
|
||||
Some(
|
||||
serde_json::from_str(&install_plan_string).wrap_err_with(|| {
|
||||
format!("Unable to parse existing receipt `{RECEIPT_LOCATION}`, it may be from an incompatible version of `nix-installer`. Try running `/nix/nix-installer uninstall`, then installing again.")
|
||||
})?,
|
||||
)
|
||||
},
|
||||
false => None,
|
||||
};
|
||||
|
@ -94,6 +104,18 @@ impl CommandExecute for Install {
|
|||
|
||||
match existing_receipt {
|
||||
Some(existing_receipt) => {
|
||||
if let Err(e) = existing_receipt.check_compatible() {
|
||||
eprintln!(
|
||||
"{}",
|
||||
format!("\
|
||||
{e}\n\
|
||||
\n\
|
||||
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
|
||||
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
|
||||
").red()
|
||||
);
|
||||
return Ok(ExitCode::FAILURE)
|
||||
}
|
||||
if existing_receipt.planner.typetag_name() != chosen_planner.typetag_name() {
|
||||
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
|
||||
return Ok(ExitCode::FAILURE)
|
||||
|
@ -133,6 +155,18 @@ impl CommandExecute for Install {
|
|||
|
||||
match existing_receipt {
|
||||
Some(existing_receipt) => {
|
||||
if let Err(e) = existing_receipt.check_compatible() {
|
||||
eprintln!(
|
||||
"{}",
|
||||
format!("\
|
||||
{e}\n\
|
||||
\n\
|
||||
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
|
||||
{EXISTING_INCOMPATIBLE_PLAN_GUIDANCE}\n\
|
||||
").red()
|
||||
);
|
||||
return Ok(ExitCode::FAILURE)
|
||||
}
|
||||
if existing_receipt.planner.typetag_name() != builtin_planner.typetag_name() {
|
||||
eprintln!("{}", format!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install with `{uninstall_command}`").red());
|
||||
return Ok(ExitCode::FAILURE)
|
||||
|
|
16
src/error.rs
16
src/error.rs
|
@ -1,5 +1,7 @@
|
|||
use std::{error::Error, path::PathBuf};
|
||||
|
||||
use semver::Version;
|
||||
|
||||
use crate::{action::ActionError, planner::PlannerError, settings::InstallSettingsError};
|
||||
|
||||
/// An error occurring during a call defined in this crate
|
||||
|
@ -68,6 +70,15 @@ pub enum NixInstallerError {
|
|||
#[source]
|
||||
crate::diagnostics::DiagnosticError,
|
||||
),
|
||||
/// Could not parse the value as a version requirement in order to ensure it's compatible
|
||||
#[error("Could not parse `{0}` as a version requirement in order to ensure it's compatible")]
|
||||
InvalidVersionRequirement(String, semver::Error),
|
||||
/// Could not parse `nix-installer`'s version as a valid version according to Semantic Versioning, therefore the plan version compatibility cannot be checked
|
||||
#[error("Could not parse `nix-installer`'s version `{0}` as a valid version according to Semantic Versioning, therefore the plan version compatibility cannot be checked")]
|
||||
InvalidCurrentVersion(String, semver::Error),
|
||||
/// This version of `nix-installer` is not compatible with this plan's version
|
||||
#[error("`nix-installer` version `{}` is not compatible with this plan's version `{}`", .binary, .plan)]
|
||||
IncompatibleVersion { binary: Version, plan: Version },
|
||||
}
|
||||
|
||||
pub(crate) trait HasExpectedErrors: std::error::Error + Sized + Send + Sync {
|
||||
|
@ -86,6 +97,11 @@ impl HasExpectedErrors for NixInstallerError {
|
|||
NixInstallerError::SemVer(_) => None,
|
||||
NixInstallerError::Planner(planner_error) => planner_error.expected(),
|
||||
NixInstallerError::InstallSettings(_) => None,
|
||||
this @ NixInstallerError::InvalidVersionRequirement(_, _) => Some(Box::new(this)),
|
||||
this @ NixInstallerError::InvalidCurrentVersion(_, _) => Some(Box::new(this)),
|
||||
this @ NixInstallerError::IncompatibleVersion { binary: _, plan: _ } => {
|
||||
Some(Box::new(this))
|
||||
},
|
||||
#[cfg(feature = "diagnostics")]
|
||||
NixInstallerError::Diagnostic(_) => None,
|
||||
}
|
||||
|
|
56
src/plan.rs
56
src/plan.rs
|
@ -7,7 +7,6 @@ use crate::{
|
|||
};
|
||||
use owo_colors::OwoColorize;
|
||||
use semver::{Version, VersionReq};
|
||||
use serde::{de::Error, Deserialize, Deserializer};
|
||||
use tokio::sync::broadcast::Receiver;
|
||||
|
||||
pub const RECEIPT_LOCATION: &str = "/nix/receipt.json";
|
||||
|
@ -18,7 +17,6 @@ revert
|
|||
*/
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct InstallPlan {
|
||||
#[serde(deserialize_with = "ensure_version")]
|
||||
pub(crate) version: Version,
|
||||
|
||||
pub(crate) actions: Vec<StatefulAction<Box<dyn Action>>>,
|
||||
|
@ -144,6 +142,7 @@ impl InstallPlan {
|
|||
&mut self,
|
||||
cancel_channel: impl Into<Option<Receiver<()>>>,
|
||||
) -> Result<(), NixInstallerError> {
|
||||
self.check_compatible()?;
|
||||
let Self { actions, .. } = self;
|
||||
let mut cancel_channel = cancel_channel.into();
|
||||
|
||||
|
@ -293,6 +292,7 @@ impl InstallPlan {
|
|||
&mut self,
|
||||
cancel_channel: impl Into<Option<Receiver<()>>>,
|
||||
) -> Result<(), NixInstallerError> {
|
||||
self.check_compatible()?;
|
||||
let Self { actions, .. } = self;
|
||||
let mut cancel_channel = cancel_channel.into();
|
||||
let mut errors = vec![];
|
||||
|
@ -359,6 +359,21 @@ impl InstallPlan {
|
|||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_compatible(&self) -> Result<(), NixInstallerError> {
|
||||
let self_version_string = self.version.to_string();
|
||||
let req = VersionReq::parse(&self_version_string)
|
||||
.map_err(|e| NixInstallerError::InvalidVersionRequirement(self_version_string, e))?;
|
||||
let nix_installer_version = current_version()?;
|
||||
if req.matches(&nix_installer_version) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NixInstallerError::IncompatibleVersion {
|
||||
binary: nix_installer_version,
|
||||
plan: self.version.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_receipt(plan: InstallPlan) -> Result<(), NixInstallerError> {
|
||||
|
@ -374,30 +389,11 @@ async fn write_receipt(plan: InstallPlan) -> Result<(), NixInstallerError> {
|
|||
Result::<(), NixInstallerError>::Ok(())
|
||||
}
|
||||
|
||||
fn current_version() -> Result<Version, semver::Error> {
|
||||
fn current_version() -> Result<Version, NixInstallerError> {
|
||||
let nix_installer_version_str = env!("CARGO_PKG_VERSION");
|
||||
Version::from_str(nix_installer_version_str)
|
||||
}
|
||||
|
||||
fn ensure_version<'de, D: Deserializer<'de>>(d: D) -> Result<Version, D::Error> {
|
||||
let plan_version = Version::deserialize(d)?;
|
||||
let req = VersionReq::parse(&plan_version.to_string()).map_err(|_e| {
|
||||
D::Error::custom(&format!(
|
||||
"Could not parse version `{plan_version}` as a version requirement, please report this",
|
||||
))
|
||||
})?;
|
||||
let nix_installer_version = current_version().map_err(|_e| {
|
||||
D::Error::custom(&format!(
|
||||
"Could not parse `nix-installer`'s version `{}` as a valid version according to Semantic Versioning, therefore the plan version ({plan_version}) compatibility cannot be checked", env!("CARGO_PKG_VERSION")
|
||||
))
|
||||
})?;
|
||||
if req.matches(&nix_installer_version) {
|
||||
Ok(plan_version)
|
||||
} else {
|
||||
Err(D::Error::custom(&format!(
|
||||
"This version of `nix-installer` ({nix_installer_version}) is not compatible with this plan's version ({plan_version}), you probably are trying to install with a new version of `nix-installer` which is not compatible with version {plan_version} plans. To upgrade Nix, try `sudo -i nix upgrade-nix`. To reinstall Nix, try `/nix/nix-installer uninstall` then installing again from the instructions on https://github.com/DeterminateSystems/nix-installer. To continue using this plan, download the matching release from https://github.com/DeterminateSystems/nix-installer/releases.",
|
||||
)))
|
||||
}
|
||||
Version::from_str(nix_installer_version_str).map_err(|e| {
|
||||
NixInstallerError::InvalidCurrentVersion(nix_installer_version_str.to_string(), e)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -415,8 +411,8 @@ mod test {
|
|||
"version": good_version,
|
||||
"actions": [],
|
||||
});
|
||||
let maybe_plan: Result<InstallPlan, serde_json::Error> = serde_json::from_value(value);
|
||||
maybe_plan.unwrap();
|
||||
let maybe_plan: InstallPlan = serde_json::from_value(value)?;
|
||||
maybe_plan.check_compatible()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -429,10 +425,8 @@ mod test {
|
|||
"version": bad_version,
|
||||
"actions": [],
|
||||
});
|
||||
let maybe_plan: Result<InstallPlan, serde_json::Error> = serde_json::from_value(value);
|
||||
assert!(maybe_plan.is_err());
|
||||
let err = maybe_plan.unwrap_err();
|
||||
assert!(err.is_data());
|
||||
let maybe_plan: InstallPlan = serde_json::from_value(value)?;
|
||||
assert!(maybe_plan.check_compatible().is_err());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue