Add diagnostics reporting (#264)
* Add diagnostics reporting * Some tidying * Remove injected failure * Update URL * Fixups * Fix tests * Use triples instead of architecture
This commit is contained in:
parent
8de35b2477
commit
19dd7a13d4
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -927,6 +927,7 @@ dependencies = [
|
|||
"eyre",
|
||||
"glob",
|
||||
"nix",
|
||||
"os-release",
|
||||
"owo-colors",
|
||||
"plist",
|
||||
"rand 0.8.5",
|
||||
|
@ -935,6 +936,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"strum",
|
||||
"tar",
|
||||
"target-lexicon",
|
||||
"tempdir",
|
||||
|
@ -1004,6 +1006,15 @@ version = "1.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "os-release"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82f29ae2f71b53ec19cc23385f8e4f3d90975195aa3d09171ba3bef7159bec27"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.4.1"
|
||||
|
@ -1541,6 +1552,28 @@ version = "0.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "1.3.1"
|
||||
|
|
|
@ -15,8 +15,9 @@ build-inputs = ["darwin.apple_sdk.frameworks.Security"]
|
|||
build-inputs = ["darwin.apple_sdk.frameworks.Security"]
|
||||
|
||||
[features]
|
||||
default = ["cli"]
|
||||
default = ["cli", "diagnostics"]
|
||||
cli = ["eyre", "color-eyre", "clap", "tracing-subscriber", "tracing-error", "atty"]
|
||||
diagnostics = ["os-release"]
|
||||
|
||||
[[bin]]
|
||||
name = "nix-installer"
|
||||
|
@ -53,6 +54,8 @@ rand = { version = "0.8.5", default-features = false, features = [ "std", "std_r
|
|||
semver = { version = "1.0.14", default-features = false, features = ["serde", "std"] }
|
||||
term = { version = "0.7.0", default-features = false }
|
||||
uuid = { version = "1.2.2", features = ["serde"] }
|
||||
os-release = { version = "0.1.0", default-features = false, optional = true }
|
||||
strum = { version = "0.24.1", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] }
|
||||
|
|
|
@ -133,6 +133,15 @@ impl Planner for MyPlanner {
|
|||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
async fn diagnostic_data(&self) -> Result<nix_installer::diagnostics::DiagnosticData, PlannerError> {
|
||||
Ok(nix_installer::diagnostics::DiagnosticData::new(
|
||||
self.common.diagnostic_endpoint.clone(),
|
||||
self.typetag_name().into(),
|
||||
self.configured_settings().await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
# async fn custom_planner_install() -> color_eyre::Result<()> {
|
||||
|
@ -244,7 +253,7 @@ impl ActionDescription {
|
|||
}
|
||||
|
||||
/// An error occurring during an action
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||
pub enum ActionError {
|
||||
/// A custom error
|
||||
#[error(transparent)]
|
||||
|
|
|
@ -47,6 +47,7 @@ pub struct Install {
|
|||
global = true
|
||||
)]
|
||||
pub explain: bool,
|
||||
|
||||
#[clap(env = "NIX_INSTALLER_PLAN")]
|
||||
pub plan: Option<PathBuf>,
|
||||
|
||||
|
@ -133,12 +134,12 @@ impl CommandExecute for Install {
|
|||
let res = builtin_planner.plan().await;
|
||||
match res {
|
||||
Ok(plan) => plan,
|
||||
Err(e) => {
|
||||
if let Some(expected) = e.expected() {
|
||||
Err(err) => {
|
||||
if let Some(expected) = err.expected() {
|
||||
eprintln!("{}", expected.red());
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
return Err(e.into())
|
||||
return Err(err.into())
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@ pub struct Uninstall {
|
|||
global = true
|
||||
)]
|
||||
pub no_confirm: bool,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
env = "NIX_INSTALLER_EXPLAIN",
|
||||
|
@ -36,6 +37,7 @@ pub struct Uninstall {
|
|||
global = true
|
||||
)]
|
||||
pub explain: bool,
|
||||
|
||||
#[clap(default_value = RECEIPT_LOCATION)]
|
||||
pub receipt: PathBuf,
|
||||
}
|
||||
|
|
156
src/diagnostics.rs
Normal file
156
src/diagnostics.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*! Diagnostic reporting functionality
|
||||
|
||||
When enabled with the `diagnostics` feature (default) this module provides automated install success/failure reporting to an endpoint.
|
||||
|
||||
That endpoint can be a URL such as `https://our.project.org/nix-installer/diagnostics` or `file:///home/$USER/diagnostic.json` which receives a [`DiagnosticReport`] in JSON format.
|
||||
*/
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use os_release::OsRelease;
|
||||
use reqwest::Url;
|
||||
|
||||
/// The static of an action attempt
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub enum DiagnosticStatus {
|
||||
Cancelled,
|
||||
Success,
|
||||
/// This includes the [`strum::IntoStaticStr`] representation of the error, we take special care not to include parameters of the error (which may include secrets)
|
||||
Failure(String),
|
||||
Pending,
|
||||
}
|
||||
|
||||
/// The action attempted
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy)]
|
||||
pub enum DiagnosticAction {
|
||||
Install,
|
||||
Uninstall,
|
||||
}
|
||||
|
||||
/// A report sent to an endpoint
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct DiagnosticReport {
|
||||
pub version: String,
|
||||
pub planner: String,
|
||||
pub configured_settings: Vec<String>,
|
||||
pub os_name: String,
|
||||
pub os_version: String,
|
||||
pub triple: String,
|
||||
pub action: DiagnosticAction,
|
||||
pub status: DiagnosticStatus,
|
||||
}
|
||||
|
||||
/// A preparation of data to be sent to the `endpoint`.
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Default)]
|
||||
pub struct DiagnosticData {
|
||||
version: String,
|
||||
planner: String,
|
||||
configured_settings: Vec<String>,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
triple: String,
|
||||
endpoint: Option<Url>,
|
||||
}
|
||||
|
||||
impl DiagnosticData {
|
||||
pub fn new(endpoint: Option<Url>, planner: String, configured_settings: Vec<String>) -> Self {
|
||||
let (os_name, os_version) = match OsRelease::new() {
|
||||
Ok(os_release) => (os_release.name, os_release.version),
|
||||
Err(_) => ("unknown".into(), "unknown".into()),
|
||||
};
|
||||
Self {
|
||||
endpoint,
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
planner,
|
||||
configured_settings,
|
||||
os_name,
|
||||
os_version,
|
||||
triple: target_lexicon::HOST.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report(&self, action: DiagnosticAction, status: DiagnosticStatus) -> DiagnosticReport {
|
||||
let Self {
|
||||
version,
|
||||
planner,
|
||||
configured_settings,
|
||||
os_name,
|
||||
os_version,
|
||||
triple,
|
||||
endpoint: _,
|
||||
} = self;
|
||||
DiagnosticReport {
|
||||
version: version.clone(),
|
||||
planner: planner.clone(),
|
||||
configured_settings: configured_settings.clone(),
|
||||
os_name: os_name.clone(),
|
||||
os_version: os_version.clone(),
|
||||
triple: triple.clone(),
|
||||
action,
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn send(
|
||||
self,
|
||||
action: DiagnosticAction,
|
||||
status: DiagnosticStatus,
|
||||
) -> Result<(), DiagnosticError> {
|
||||
let serialized = serde_json::to_string_pretty(&self.report(action, status))?;
|
||||
|
||||
let endpoint = match self.endpoint {
|
||||
Some(endpoint) => endpoint,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
match endpoint.scheme() {
|
||||
"https" | "http" => {
|
||||
tracing::debug!("Sending diagnostic to `{endpoint}`");
|
||||
let client = reqwest::Client::new();
|
||||
let res = client
|
||||
.post(endpoint.clone())
|
||||
.body(serialized)
|
||||
.header("Content-Type", "application/json")
|
||||
.timeout(Duration::from_millis(3000))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
if let Err(_err) = res {
|
||||
tracing::info!("Failed to send diagnostic to `{endpoint}`, continuing")
|
||||
}
|
||||
},
|
||||
"file" => {
|
||||
let path = endpoint.path();
|
||||
tracing::debug!("Writing diagnostic to `{path}`");
|
||||
let res = tokio::fs::write(path, serialized).await;
|
||||
|
||||
if let Err(_err) = res {
|
||||
tracing::info!("Failed to send diagnostic to `{path}`, continuing")
|
||||
}
|
||||
},
|
||||
_ => return Err(DiagnosticError::UnknownUrlScheme),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum DiagnosticError {
|
||||
#[error("Unknown url scheme")]
|
||||
UnknownUrlScheme,
|
||||
#[error("Request error")]
|
||||
Reqwest(
|
||||
#[from]
|
||||
#[source]
|
||||
reqwest::Error,
|
||||
),
|
||||
#[error("Write path `{0}`")]
|
||||
Write(std::path::PathBuf, #[source] std::io::Error),
|
||||
#[error("Serializing receipt")]
|
||||
Serializing(
|
||||
#[from]
|
||||
#[source]
|
||||
serde_json::Error,
|
||||
),
|
||||
}
|
30
src/error.rs
30
src/error.rs
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||
use crate::{action::ActionError, planner::PlannerError, settings::InstallSettingsError};
|
||||
|
||||
/// An error occurring during a call defined in this crate
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||
pub enum NixInstallerError {
|
||||
/// An error originating from an [`Action`](crate::action::Action)
|
||||
#[error("Error executing action")]
|
||||
|
@ -53,6 +53,15 @@ pub enum NixInstallerError {
|
|||
#[source]
|
||||
InstallSettingsError,
|
||||
),
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
/// Diagnostic error
|
||||
#[error("Diagnostic error")]
|
||||
Diagnostic(
|
||||
#[from]
|
||||
#[source]
|
||||
crate::diagnostics::DiagnosticError,
|
||||
),
|
||||
}
|
||||
|
||||
pub(crate) trait HasExpectedErrors: std::error::Error + Sized + Send + Sync {
|
||||
|
@ -70,6 +79,25 @@ impl HasExpectedErrors for NixInstallerError {
|
|||
NixInstallerError::SemVer(_) => None,
|
||||
NixInstallerError::Planner(planner_error) => planner_error.expected(),
|
||||
NixInstallerError::InstallSettings(_) => None,
|
||||
#[cfg(feature = "diagnostics")]
|
||||
NixInstallerError::Diagnostic(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(feature = "diagnostics")]
|
||||
// impl NixInstallerError {
|
||||
// pub fn diagnostic_synopsis(&self) -> &'static str {
|
||||
// match self {
|
||||
// NixInstallerError::Action(inner) => inner.into(),
|
||||
// NixInstallerError::Planner(inner) => inner.into(),
|
||||
// NixInstallerError::RecordingReceipt(_, _)
|
||||
// | NixInstallerError::CopyingSelf(_)
|
||||
// | NixInstallerError::SerializingReceipt(_)
|
||||
// | NixInstallerError::Cancelled
|
||||
// | NixInstallerError::SemVer(_)
|
||||
// | NixInstallerError::Diagnostic(_)
|
||||
// | NixInstallerError::InstallSettings(_) => self.into(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -73,6 +73,8 @@ pub mod action;
|
|||
mod channel_value;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod cli;
|
||||
#[cfg(feature = "diagnostics")]
|
||||
pub mod diagnostics;
|
||||
mod error;
|
||||
mod os;
|
||||
mod plan;
|
||||
|
|
102
src/plan.rs
102
src/plan.rs
|
@ -24,17 +24,27 @@ pub struct InstallPlan {
|
|||
pub(crate) actions: Vec<StatefulAction<Box<dyn Action>>>,
|
||||
|
||||
pub(crate) planner: Box<dyn Planner>,
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
pub(crate) diagnostic_data: Option<crate::diagnostics::DiagnosticData>,
|
||||
}
|
||||
|
||||
impl InstallPlan {
|
||||
pub async fn default() -> Result<Self, NixInstallerError> {
|
||||
let planner = BuiltinPlanner::default().await?.boxed();
|
||||
let planner = BuiltinPlanner::default().await?;
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
let diagnostic_data = Some(planner.diagnostic_data().await?);
|
||||
|
||||
let planner = planner.boxed();
|
||||
let actions = planner.plan().await?;
|
||||
|
||||
Ok(Self {
|
||||
planner,
|
||||
actions,
|
||||
version: current_version()?,
|
||||
#[cfg(feature = "diagnostics")]
|
||||
diagnostic_data,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -42,11 +52,16 @@ impl InstallPlan {
|
|||
where
|
||||
P: Planner + 'static,
|
||||
{
|
||||
#[cfg(feature = "diagnostics")]
|
||||
let diagnostic_data = Some(planner.diagnostic_data().await?);
|
||||
|
||||
let actions = planner.plan().await?;
|
||||
Ok(Self {
|
||||
planner: planner.boxed(),
|
||||
actions,
|
||||
version: current_version()?,
|
||||
#[cfg(feature = "diagnostics")]
|
||||
diagnostic_data,
|
||||
})
|
||||
}
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
|
@ -55,6 +70,7 @@ impl InstallPlan {
|
|||
planner,
|
||||
actions,
|
||||
version,
|
||||
..
|
||||
} = self;
|
||||
let buf = format!(
|
||||
"\
|
||||
|
@ -107,11 +123,7 @@ impl InstallPlan {
|
|||
&mut self,
|
||||
cancel_channel: impl Into<Option<Receiver<()>>>,
|
||||
) -> Result<(), NixInstallerError> {
|
||||
let Self {
|
||||
version: _,
|
||||
actions,
|
||||
planner: _,
|
||||
} = self;
|
||||
let Self { actions, .. } = self;
|
||||
let mut cancel_channel = cancel_channel.into();
|
||||
|
||||
// This is **deliberately sequential**.
|
||||
|
@ -125,6 +137,18 @@ impl InstallPlan {
|
|||
if let Err(err) = write_receipt(self.clone()).await {
|
||||
tracing::error!("Error saving receipt: {:?}", err);
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Install,
|
||||
crate::diagnostics::DiagnosticStatus::Cancelled,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
return Err(NixInstallerError::Cancelled);
|
||||
}
|
||||
}
|
||||
|
@ -134,11 +158,35 @@ impl InstallPlan {
|
|||
if let Err(err) = write_receipt(self.clone()).await {
|
||||
tracing::error!("Error saving receipt: {:?}", err);
|
||||
}
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Install,
|
||||
crate::diagnostics::DiagnosticStatus::Failure({
|
||||
let x: &'static str = (&err).into();
|
||||
x.to_string()
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
return Err(NixInstallerError::Action(err));
|
||||
}
|
||||
}
|
||||
|
||||
write_receipt(self.clone()).await?;
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Install,
|
||||
crate::diagnostics::DiagnosticStatus::Success,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -149,6 +197,7 @@ impl InstallPlan {
|
|||
version: _,
|
||||
planner,
|
||||
actions,
|
||||
..
|
||||
} = self;
|
||||
let buf = format!(
|
||||
"\
|
||||
|
@ -207,11 +256,7 @@ impl InstallPlan {
|
|||
&mut self,
|
||||
cancel_channel: impl Into<Option<Receiver<()>>>,
|
||||
) -> Result<(), NixInstallerError> {
|
||||
let Self {
|
||||
version: _,
|
||||
actions,
|
||||
planner: _,
|
||||
} = self;
|
||||
let Self { actions, .. } = self;
|
||||
let mut cancel_channel = cancel_channel.into();
|
||||
|
||||
// This is **deliberately sequential**.
|
||||
|
@ -225,6 +270,17 @@ impl InstallPlan {
|
|||
if let Err(err) = write_receipt(self.clone()).await {
|
||||
tracing::error!("Error saving receipt: {:?}", err);
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||
crate::diagnostics::DiagnosticStatus::Cancelled,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
return Err(NixInstallerError::Cancelled);
|
||||
}
|
||||
}
|
||||
|
@ -234,10 +290,34 @@ impl InstallPlan {
|
|||
if let Err(err) = write_receipt(self.clone()).await {
|
||||
tracing::error!("Error saving receipt: {:?}", err);
|
||||
}
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||
crate::diagnostics::DiagnosticStatus::Failure({
|
||||
let x: &'static str = (&err).into();
|
||||
x.to_string()
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
return Err(NixInstallerError::Action(err));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||
crate::diagnostics::DiagnosticStatus::Success,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,15 @@ impl Planner for Linux {
|
|||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
|
||||
Ok(crate::diagnostics::DiagnosticData::new(
|
||||
self.settings.diagnostic_endpoint.clone(),
|
||||
self.typetag_name().into(),
|
||||
self.configured_settings().await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<BuiltinPlanner> for Linux {
|
||||
|
|
|
@ -169,6 +169,15 @@ impl Planner for Macos {
|
|||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
|
||||
Ok(crate::diagnostics::DiagnosticData::new(
|
||||
self.settings.diagnostic_endpoint.clone(),
|
||||
self.typetag_name().into(),
|
||||
self.configured_settings().await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<BuiltinPlanner> for Macos {
|
||||
|
|
|
@ -52,6 +52,15 @@ impl Planner for MyPlanner {
|
|||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
async fn diagnostic_data(&self) -> Result<nix_installer::diagnostics::DiagnosticData, PlannerError> {
|
||||
Ok(nix_installer::diagnostics::DiagnosticData::new(
|
||||
self.common.diagnostic_endpoint.clone(),
|
||||
self.typetag_name().into(),
|
||||
self.configured_settings().await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
# async fn custom_planner_install() -> color_eyre::Result<()> {
|
||||
|
@ -101,6 +110,23 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
|
|||
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError>;
|
||||
/// The settings being used by the planner
|
||||
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError>;
|
||||
|
||||
async fn configured_settings(&self) -> Result<Vec<String>, PlannerError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let default = Self::default().await?.settings()?;
|
||||
let configured = self.settings()?;
|
||||
|
||||
let mut keys: Vec<String> = Vec::new();
|
||||
for (key, value) in configured.iter() {
|
||||
if default.get(key) != Some(value) {
|
||||
keys.push(key.clone())
|
||||
}
|
||||
}
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
/// A boxed, type erased planner
|
||||
fn boxed(self) -> Box<dyn Planner>
|
||||
where
|
||||
|
@ -108,6 +134,9 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
|
|||
{
|
||||
Box::new(self)
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError>;
|
||||
}
|
||||
|
||||
dyn_clone::clone_trait_object!(Planner);
|
||||
|
@ -171,6 +200,17 @@ impl BuiltinPlanner {
|
|||
Ok(built)
|
||||
}
|
||||
|
||||
pub async fn configured_settings(&self) -> Result<Vec<String>, PlannerError> {
|
||||
match self {
|
||||
#[cfg(target_os = "linux")]
|
||||
BuiltinPlanner::Linux(inner) => inner.configured_settings().await,
|
||||
#[cfg(target_os = "linux")]
|
||||
BuiltinPlanner::SteamDeck(inner) => inner.configured_settings().await,
|
||||
#[cfg(target_os = "macos")]
|
||||
BuiltinPlanner::Macos(inner) => inner.configured_settings().await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn plan(self) -> Result<InstallPlan, NixInstallerError> {
|
||||
match self {
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -213,10 +253,24 @@ impl BuiltinPlanner {
|
|||
BuiltinPlanner::Macos(i) => i.settings(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
pub async fn diagnostic_data(
|
||||
&self,
|
||||
) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
|
||||
match self {
|
||||
#[cfg(target_os = "linux")]
|
||||
BuiltinPlanner::Linux(i) => i.diagnostic_data().await,
|
||||
#[cfg(target_os = "linux")]
|
||||
BuiltinPlanner::SteamDeck(i) => i.diagnostic_data().await,
|
||||
#[cfg(target_os = "macos")]
|
||||
BuiltinPlanner::Macos(i) => i.diagnostic_data().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error originating from a [`Planner`]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||
pub enum PlannerError {
|
||||
/// `nix-installer` does not have a default planner for the target architecture right now
|
||||
#[error("`nix-installer` does not have a default planner for the `{0}` architecture right now, pass a specific archetype")]
|
||||
|
|
|
@ -252,6 +252,15 @@ impl Planner for SteamDeck {
|
|||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
|
||||
Ok(crate::diagnostics::DiagnosticData::new(
|
||||
self.settings.diagnostic_endpoint.clone(),
|
||||
self.typetag_name().into(),
|
||||
self.configured_settings().await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<BuiltinPlanner> for SteamDeck {
|
||||
|
|
|
@ -202,6 +202,31 @@ pub struct CommonSettings {
|
|||
)
|
||||
)]
|
||||
pub(crate) force: bool,
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
/// The URL or file path for an installation diagnostic to be sent
|
||||
///
|
||||
/// Sample of the data sent:
|
||||
///
|
||||
/// {
|
||||
/// "version": "0.3.0",
|
||||
/// "planner": "linux",
|
||||
/// "configured-settings": [ "modify_profile" ],
|
||||
/// "os-name": "Ubuntu",
|
||||
/// "os-version": "22.04.1 LTS (Jammy Jellyfish)",
|
||||
/// "triple": "x86_64-unknown-linux-gnu",
|
||||
/// "action": "Install",
|
||||
/// "status": "Success"
|
||||
/// }
|
||||
///
|
||||
/// To disable diagnostic reporting, unset the default with `--diagnostic-endpoint=`
|
||||
#[clap(
|
||||
long,
|
||||
env = "NIX_INSTALLER_DIAGNOSTIC_ENDPOINT",
|
||||
global = true,
|
||||
default_value = "https://install.determinate.systems/nix/diagnostic"
|
||||
)]
|
||||
pub diagnostic_endpoint: Option<Url>,
|
||||
}
|
||||
|
||||
impl CommonSettings {
|
||||
|
@ -217,19 +242,19 @@ impl CommonSettings {
|
|||
(Architecture::X86_64, OperatingSystem::Linux) => {
|
||||
url = NIX_X64_64_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 3000;
|
||||
nix_build_user_id_base = 30000;
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
(Architecture::X86_32(_), OperatingSystem::Linux) => {
|
||||
url = NIX_I686_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 3000;
|
||||
nix_build_user_id_base = 30000;
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
(Architecture::Aarch64(_), OperatingSystem::Linux) => {
|
||||
url = NIX_AARCH64_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 3000;
|
||||
nix_build_user_id_base = 30000;
|
||||
},
|
||||
#[cfg(target_os = "macos")]
|
||||
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
|
||||
|
@ -267,6 +292,10 @@ impl CommonSettings {
|
|||
nix_package_url: url.parse()?,
|
||||
extra_conf: Default::default(),
|
||||
force: false,
|
||||
#[cfg(feature = "diagnostics")]
|
||||
diagnostic_endpoint: Some(
|
||||
"https://install.determinate.systems/diagnostics".try_into()?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -283,6 +312,8 @@ impl CommonSettings {
|
|||
nix_package_url,
|
||||
extra_conf,
|
||||
force,
|
||||
#[cfg(feature = "diagnostics")]
|
||||
diagnostic_endpoint,
|
||||
} = self;
|
||||
let mut map = HashMap::default();
|
||||
|
||||
|
@ -326,6 +357,12 @@ impl CommonSettings {
|
|||
map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?);
|
||||
map.insert("force".into(), serde_json::to_value(force)?);
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
map.insert(
|
||||
"diagnostic_endpoint".into(),
|
||||
serde_json::to_value(diagnostic_endpoint)?,
|
||||
);
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
|
@ -418,6 +455,13 @@ impl CommonSettings {
|
|||
self.force = force;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
/// The URL or file path for an [`DiagnosticReport`][crate::diagnostics::DiagnosticReport] to be sent
|
||||
pub fn diagnostic_endpoint(&mut self, diagnostic_endpoint: Option<Url>) -> &mut Self {
|
||||
self.diagnostic_endpoint = diagnostic_endpoint;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_with::serde_as]
|
||||
|
|
Loading…
Reference in a new issue