diff --git a/magic-nix-cache/src/api.rs b/magic-nix-cache/src/api.rs index eb9b3dc..d31c921 100644 --- a/magic-nix-cache/src/api.rs +++ b/magic-nix-cache/src/api.rs @@ -63,12 +63,15 @@ async fn workflow_finish( upload_paths(new_paths.clone(), &store_uri).await?; } - let sender = state.shutdown_sender.lock().await.take().unwrap(); - sender.send(()).unwrap(); + if let Some(sender) = state.shutdown_sender.lock().await.take() { + sender + .send(()) + .expect("Cannot send shutdown server message"); - // Wait for the Attic push workers to finish. - if let Some(attic_state) = state.flakehub_state.write().await.take() { - attic_state.push_session.wait().await.unwrap(); + // Wait for the Attic push workers to finish. + if let Some(attic_state) = state.flakehub_state.write().await.take() { + attic_state.push_session.wait().await?; + } } let reply = WorkflowFinishResponse { @@ -93,7 +96,7 @@ fn make_store_uri(self_endpoint: &SocketAddr) -> String { .authority(self_endpoint.to_string()) .path_and_query("/?compression=zstd¶llel-compression=true") .build() - .unwrap() + .expect("Cannot construct URL to self") .to_string() } diff --git a/magic-nix-cache/src/error.rs b/magic-nix-cache/src/error.rs index 4a5d27c..1318c12 100644 --- a/magic-nix-cache/src/error.rs +++ b/magic-nix-cache/src/error.rs @@ -48,6 +48,9 @@ pub enum Error { #[error("Bad URL")] BadUrl(reqwest::Url), + + #[error("Configuration error: {0}")] + Config(String), } impl IntoResponse for Error { diff --git a/magic-nix-cache/src/flakehub.rs b/magic-nix-cache/src/flakehub.rs index 6361102..454aac9 100644 --- a/magic-nix-cache/src/flakehub.rs +++ b/magic-nix-cache/src/flakehub.rs @@ -55,6 +55,20 @@ pub async fn init_cache( .ok_or_else(|| Error::BadUrl(flakehub_cache_server.to_owned()))? .to_string(); + let login = netrc_entry.login.as_ref().ok_or_else(|| { + Error::Config(format!( + "netrc file does not contain a login for '{}'", + flakehub_cache_server + )) + })?; + + let password = netrc_entry.password.as_ref().ok_or_else(|| { + Error::Config(format!( + "netrc file does not contain a password for '{}'", + flakehub_cache_server + )) + })?; + // Append an entry for the FlakeHub cache server to netrc. if !netrc .machines @@ -70,8 +84,7 @@ pub async fn init_cache( .write_all( format!( "\nmachine {} password {}\n\n", - flakehub_cache_server_hostname, - netrc_entry.password.as_ref().unwrap(), + flakehub_cache_server_hostname, password, ) .as_bytes(), ) @@ -80,20 +93,18 @@ pub async fn init_cache( // Get the cache UUID for this project. let cache_name = { - let github_repo = env::var("GITHUB_REPOSITORY") - .expect("GITHUB_REPOSITORY environment variable is not set"); + let github_repo = env::var("GITHUB_REPOSITORY").map_err(|_| { + Error::Config("GITHUB_REPOSITORY environment variable is not set".to_owned()) + })?; let url = flakehub_api_server .join(&format!("project/{}", github_repo)) - .unwrap(); + .map_err(|_| Error::Config(format!("bad URL '{}'", flakehub_api_server)))?; let response = reqwest::Client::new() .get(url.to_owned()) .header("User-Agent", USER_AGENT) - .basic_auth( - netrc_entry.login.as_ref().unwrap(), - netrc_entry.password.as_ref(), - ) + .basic_auth(login, Some(password)) .send() .await?; diff --git a/magic-nix-cache/src/main.rs b/magic-nix-cache/src/main.rs index ae6e1f2..12f6bb9 100644 --- a/magic-nix-cache/src/main.rs +++ b/magic-nix-cache/src/main.rs @@ -28,6 +28,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use ::attic::nix_store::NixStore; +use anyhow::{anyhow, Context, Result}; use axum::{extract::Extension, routing::get, Router}; use clap::Parser; use tempfile::NamedTempFile; @@ -139,34 +140,35 @@ struct StateInner { flakehub_state: RwLock>, } -async fn main_cli() { +async fn main_cli() -> Result<()> { init_logging(); let args = Args::parse(); - create_dir_all(Path::new(&args.nix_conf).parent().unwrap()) - .expect("Creating parent directories of nix.conf"); + if let Some(parent) = Path::new(&args.nix_conf).parent() { + create_dir_all(parent).with_context(|| "Creating parent directories of nix.conf")?; + } let mut nix_conf = OpenOptions::new() .create(true) .append(true) .open(args.nix_conf) - .expect("Opening nix.conf"); + .with_context(|| "Creating nix.conf")?; - let store = Arc::new(NixStore::connect().expect("Connecting to the Nix store")); + let store = Arc::new(NixStore::connect()?); let flakehub_state = if args.use_flakehub { let flakehub_cache_server = args .flakehub_cache_server - .expect("--flakehub-cache-server is required"); + .ok_or_else(|| anyhow!("--flakehub-cache-server is required"))?; let flakehub_api_server_netrc = args .flakehub_api_server_netrc - .expect("--flakehub-api-server-netrc is required"); + .ok_or_else(|| anyhow!("--flakehub-api-server-netrc is required"))?; match flakehub::init_cache( &args .flakehub_api_server - .expect("--flakehub-api-server is required"), + .ok_or_else(|| anyhow!("--flakehub-api-server is required"))?, &flakehub_api_server_netrc, &flakehub_cache_server, store.clone(), @@ -183,7 +185,7 @@ async fn main_cli() { ) .as_bytes(), ) - .expect("Writing to nix.conf"); + .with_context(|| "Writing to nix.conf")?; tracing::info!("FlakeHub cache is enabled."); Some(state) @@ -201,16 +203,27 @@ async fn main_cli() { let api = if args.use_gha_cache { let credentials = if let Some(credentials_file) = &args.credentials_file { tracing::info!("Loading credentials from {:?}", credentials_file); - let bytes = fs::read(credentials_file).expect("Failed to read credentials file"); + let bytes = fs::read(credentials_file).with_context(|| { + format!( + "Failed to read credentials file '{}'", + credentials_file.display() + ) + })?; - serde_json::from_slice(&bytes).expect("Failed to deserialize credentials file") + serde_json::from_slice(&bytes).with_context(|| { + format!( + "Failed to deserialize credentials file '{}'", + credentials_file.display() + ) + })? } else { tracing::info!("Loading credentials from environment"); Credentials::load_from_env() - .expect("Failed to load credentials from environment (see README.md)") + .with_context(|| "Failed to load credentials from environment (see README.md)")? }; - let mut api = Api::new(credentials).expect("Failed to initialize GitHub Actions Cache API"); + let mut api = Api::new(credentials) + .with_context(|| "Failed to initialize GitHub Actions Cache API")?; if let Some(cache_version) = &args.cache_version { api.mutate_version(cache_version.as_bytes()); @@ -218,7 +231,7 @@ async fn main_cli() { nix_conf .write_all(format!("extra-substituters = http://{}?trusted=1&compression=zstd¶llel-compression=true&priority=1\n", args.listen).as_bytes()) - .expect("Writing to nix.conf"); + .with_context(|| "Writing to nix.conf")?; tracing::info!("Native GitHub Action cache is enabled."); Some(api) @@ -231,24 +244,27 @@ async fn main_cli() { * ignores errors, to avoid the Nix build from failing. */ let post_build_hook_script = { let mut file = NamedTempFile::with_prefix("magic-nix-cache-build-hook-") - .expect("Creating a temporary file"); + .with_context(|| "Creating a temporary file for the post-build hook")?; file.write_all( format!( // NOTE(cole-h): We want to exit 0 even if the hook failed, otherwise it'll fail the // build itself "#! /bin/sh\nRUST_BACKTRACE=full {} --server {}\nexit 0\n", std::env::current_exe() - .expect("Getting the path of magic-nix-cache") + .with_context(|| "Getting the path of magic-nix-cache")? .display(), args.listen ) .as_bytes(), ) - .expect("Writing the post-build hook"); - file.keep().unwrap().1 + .with_context(|| "Writing the post-build hook")?; + file.keep() + .with_context(|| "Keeping the post-build hook")? + .1 }; - fs::set_permissions(&post_build_hook_script, fs::Permissions::from_mode(0o755)).unwrap(); + fs::set_permissions(&post_build_hook_script, fs::Permissions::from_mode(0o755)) + .with_context(|| "Setting permissions on the post-build hook")?; /* Update nix.conf. */ nix_conf @@ -259,7 +275,7 @@ async fn main_cli() { ) .as_bytes(), ) - .expect("Writing to nix.conf"); + .with_context(|| "Writing to nix.conf")?; drop(nix_conf); @@ -309,15 +325,15 @@ async fn main_cli() { match response { Ok(response) => { if !response.status().is_success() { - panic!( + Err(anyhow!( "Startup notification returned an error: {}\n{}", response.status(), response.text().await.unwrap_or_else(|_| "".to_owned()) - ); + ))?; } } Err(err) => { - panic!("Startup notification failed: {}", err); + Err(anyhow!("Startup notification failed: {}", err))?; } } } @@ -334,10 +350,12 @@ async fn main_cli() { state.metrics.send(diagnostic_endpoint).await; } - ret.unwrap() + ret?; + + Ok(()) } -async fn post_build_hook(out_paths: &str) { +async fn post_build_hook(out_paths: &str) -> Result<()> { #[derive(Parser, Debug)] struct Args { /// `magic-nix-cache` daemon to connect to. @@ -357,43 +375,33 @@ async fn post_build_hook(out_paths: &str) { let response = reqwest::Client::new() .post(format!("http://{}/api/enqueue-paths", &args.server)) .header(reqwest::header::CONTENT_TYPE, "application/json") - .body(serde_json::to_string(&request).unwrap()) + .body( + serde_json::to_string(&request) + .with_context(|| "Decoding the response from the magic-nix-cache server")?, + ) .send() .await; - let mut err_message = None; match response { - Ok(response) if !response.status().is_success() => { - err_message = Some(format!( - "magic-nix-cache server failed to enqueue the push request: {}\n{}", - response.status(), - response.text().await.unwrap_or_else(|_| "".to_owned()), - )); - } - Ok(response) => { - let enqueue_paths_response = response.json::().await; - if let Err(err) = enqueue_paths_response { - err_message = Some(format!( - "magic-nix-cache-server didn't return a valid response: {}", - err - )) - } - } + Ok(response) if !response.status().is_success() => Err(anyhow!( + "magic-nix-cache server failed to enqueue the push request: {}\n{}", + response.status(), + response.text().await.unwrap_or_else(|_| "".to_owned()), + ))?, + Ok(response) => response + .json::() + .await + .with_context(|| "magic-nix-cache-server didn't return a valid response")?, Err(err) => { - err_message = Some(format!( - "magic-nix-cache server failed to send the enqueue request: {}", - err - )); + Err(err).with_context(|| "magic-nix-cache server failed to send the enqueue request")? } - } + }; - if let Some(err_message) = err_message { - eprintln!("{err_message}"); - } + Ok(()) } #[tokio::main] -async fn main() { +async fn main() -> Result<()> { match std::env::var("OUT_PATHS") { Ok(out_paths) => post_build_hook(&out_paths).await, Err(_) => main_cli().await, diff --git a/magic-nix-cache/src/util.rs b/magic-nix-cache/src/util.rs index 049303b..e9f916e 100644 --- a/magic-nix-cache/src/util.rs +++ b/magic-nix-cache/src/util.rs @@ -53,16 +53,20 @@ pub async fn upload_paths(mut paths: Vec, store_uri: &str) -> Result<() .output() .await? .stdout; - let env_path = String::from_utf8(env_path).expect("PATH contains invalid UTF-8"); + let env_path = String::from_utf8(env_path) + .map_err(|_| Error::Config("PATH contains invalid UTF-8".to_owned()))?; while !paths.is_empty() { let mut batch = Vec::new(); let mut total_len = 0; - while !paths.is_empty() && total_len < 1024 * 1024 { - let p = paths.pop().unwrap(); - total_len += p.as_os_str().len() + 1; - batch.push(p); + while total_len < 1024 * 1024 { + if let Some(p) = paths.pop() { + total_len += p.as_os_str().len() + 1; + batch.push(p); + } else { + break; + } } tracing::debug!("{} paths in this batch", batch.len());