diff --git a/crates/binstalk-fetchers/src/common.rs b/crates/binstalk-fetchers/src/common.rs index 310efead..ad9e1d68 100644 --- a/crates/binstalk-fetchers/src/common.rs +++ b/crates/binstalk-fetchers/src/common.rs @@ -25,7 +25,7 @@ static WARN_RATE_LIMIT_ONCE: Once = Once::new(); static WARN_UNAUTHORIZED_ONCE: Once = Once::new(); /// Return Ok(Some(api_artifact_url)) if exists, or Ok(None) if it doesn't. -/// +/// /// Caches info on all artifacts matching (repo, tag). pub(super) async fn get_gh_release_artifact_url( gh_api_client: GhApiClient, @@ -58,7 +58,7 @@ pub(super) async fn get_gh_release_artifact_url( } /// Check if the URL exists by querying the GitHub API. -/// +/// /// Caches info on all artifacts matching (repo, tag). /// /// This function returns a future where its size should be at most size of diff --git a/crates/binstalk-fetchers/src/quickinstall.rs b/crates/binstalk-fetchers/src/quickinstall.rs index 434f7d49..55d7d42b 100644 --- a/crates/binstalk-fetchers/src/quickinstall.rs +++ b/crates/binstalk-fetchers/src/quickinstall.rs @@ -1,7 +1,7 @@ use std::{ borrow::Cow, path::Path, - sync::{Arc, OnceLock}, + sync::{Arc, Mutex, OnceLock}, }; use binstalk_downloader::remote::Method; @@ -16,7 +16,8 @@ use crate::{ }; const BASE_URL: &str = "https://github.com/cargo-bins/cargo-quickinstall/releases/download"; -pub const QUICK_INSTALL_STATS_URL: &str = "https://warehouse-clerk-tmp.vercel.app/api/crate"; +pub const QUICK_INSTALL_STATS_URL: &str = + "https://cargo-quickinstall-stats-server.fly.dev/record-install"; const QUICKINSTALL_SIGN_KEY: Cow<'static, str> = Cow::Borrowed("RWTdnnab2pAka9OdwgCMYyOE66M/BlQoFWaJ/JjwcPV+f3n24IRTj97t"); @@ -58,6 +59,7 @@ pub struct QuickInstall { gh_api_client: GhApiClient, is_supported_v: OnceCell, + data: Arc, package: String, package_url: Url, signature_url: Url, @@ -67,6 +69,30 @@ pub struct QuickInstall { target_data: Arc, signature_verifier: OnceLock, + status: Mutex, +} + +#[derive(Debug, Clone, Copy)] +enum Status { + Start, + NotFound, + Found, + AttemptingInstall, + InvalidSignature, + InstalledFromTarball, +} + +impl Status { + fn as_str(&self) -> &'static str { + match self { + Status::Start => "start", + Status::NotFound => "not-found", + Status::Found => "found", + Status::AttemptingInstall => "attempting-install", + Status::InvalidSignature => "invalid-signature", + Status::InstalledFromTarball => "installed-from-tarball", + } + } } impl QuickInstall { @@ -116,6 +142,14 @@ impl QuickInstall { } }) } + + fn get_status(&self) -> Status { + *self.status.lock().unwrap() + } + + fn set_status(&self, status: Status) { + *self.status.lock().unwrap() = status; + } } #[async_trait::async_trait] @@ -137,6 +171,7 @@ impl super::Fetcher for QuickInstall { Arc::new(Self { client, + data, gh_api_client, is_supported_v: OnceCell::new(), @@ -144,7 +179,7 @@ impl super::Fetcher for QuickInstall { .expect("package_url is pre-generated and should never be invalid url"), signature_url: Url::parse(&format!("{url}.sig")) .expect("signature_url is pre-generated and should never be invalid url"), - stats_url: Url::parse(&format!("{QUICK_INSTALL_STATS_URL}/{package}.tar.gz",)) + stats_url: Url::parse("{QUICK_INSTALL_STATS_URL}") .expect("stats_url is pre-generated and should never be invalid url"), package, signature_policy, @@ -152,6 +187,7 @@ impl super::Fetcher for QuickInstall { target_data, signature_verifier: OnceLock::new(), + status: Mutex::new(Status::Start), }) } @@ -171,6 +207,7 @@ impl super::Fetcher for QuickInstall { .await?; if !is_found { + self.set_status(Status::NotFound); return Ok(false); } @@ -182,6 +219,7 @@ impl super::Fetcher for QuickInstall { panic!("::find is run twice"); } + self.set_status(Status::Found); Ok(true) }) } @@ -209,6 +247,7 @@ by rust officially."#, } async fn fetch_and_extract(&self, dst: &Path) -> Result { + self.set_status(Status::AttemptingInstall); let Some(verifier) = self.signature_verifier.get() else { panic!("::find has not been called yet!") }; @@ -227,8 +266,10 @@ by rust officially."#, if let Some(info) = verifier.info() { info!("Verified signature for package '{}': {info}", self.package); } + self.set_status(Status::InstalledFromTarball); Ok(files) } else { + self.set_status(Status::InvalidSignature); Err(FetchError::InvalidSignature) } } @@ -280,10 +321,19 @@ impl QuickInstall { return Ok(()); } - let url = self.stats_url.clone(); + let mut url = self.stats_url.clone(); + url.query_pairs_mut() + .append_pair("crate", &self.data.name) + .append_pair("version", &self.data.version) + .append_pair("target", &self.target_data.target) + .append_pair( + "agent", + concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")), + ) + .append_pair("status", self.get_status().as_str()); debug!("Sending installation report to quickinstall ({url})"); - self.client.request(Method::HEAD, url).send(true).await?; + self.client.request(Method::POST, url).send(true).await?; Ok(()) } diff --git a/crates/binstalk-git-repo-api/src/gh_api_client.rs b/crates/binstalk-git-repo-api/src/gh_api_client.rs index df5008cf..5b9405d9 100644 --- a/crates/binstalk-git-repo-api/src/gh_api_client.rs +++ b/crates/binstalk-git-repo-api/src/gh_api_client.rs @@ -247,7 +247,7 @@ pub struct GhReleaseArtifactUrl(Url); impl GhApiClient { /// Return `Ok(Some(api_artifact_url))` if exists. - /// + /// /// Caches info on all artifacts matching (repo, tag). /// /// The returned future is guaranteed to be pointer size. diff --git a/crates/binstalk/src/ops/resolve.rs b/crates/binstalk/src/ops/resolve.rs index 59940c94..1666464f 100644 --- a/crates/binstalk/src/ops/resolve.rs +++ b/crates/binstalk/src/ops/resolve.rs @@ -203,6 +203,7 @@ async fn resolve_inner( { Ok(bin_files) => { if !bin_files.is_empty() { + fetcher.report_to_upstream(); return Ok(Resolution::Fetch(Box::new(ResolutionFetch { fetcher: fetcher.clone(), new_version: package_info.version, @@ -250,6 +251,8 @@ async fn resolve_inner( } } + // At this point, we don't know whether fallback to cargo install is allowed, or whether it will + // succeed, but things start to get convoluted when try to include that data, so this will do. if !opts.disable_telemetry { for fetcher in handles { fetcher.report_to_upstream();