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:
Ana Hobden 2023-06-01 09:23:54 -07:00 committed by GitHub
parent c3bc75f064
commit 0f231b715f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 33 deletions

View file

@ -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)

View file

@ -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,
}

View file

@ -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(())
}
}