Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at python-updates 99 lines 3.5 kB view raw
1use anyhow::bail; 2use backoff::{retry, ExponentialBackoff}; 3use data_encoding::BASE64; 4use digest::Digest; 5use isahc::{ 6 config::{CaCertificate, Configurable, RedirectPolicy, SslOption}, 7 Body, Request, RequestExt, 8}; 9use log::info; 10use nix_nar::{Encoder, NarError}; 11use serde_json::{Map, Value}; 12use sha2::Sha256; 13use std::{ 14 env, 15 io::{self, Read}, 16 path::Path, 17}; 18use url::Url; 19 20pub fn get_url(url: &Url) -> Result<Body, anyhow::Error> { 21 let mut request = Request::get(url.as_str()).redirect_policy(RedirectPolicy::Limit(10)); 22 23 // Respect SSL_CERT_FILE if environment variable exists 24 if let Ok(ssl_cert_file) = env::var("SSL_CERT_FILE") { 25 if Path::new(&ssl_cert_file).exists() { 26 // When file exists, use it. NIX_SSL_CERT_FILE will still override. 27 request = request.ssl_ca_certificate(CaCertificate::file(ssl_cert_file)); 28 } else if env::var("outputHash").is_ok() { 29 // When file does not exist, assume we are downloading in a FOD and 30 // therefore do not need to check certificates, since the output is 31 // already hashed. 32 request = request.ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS); 33 } 34 } 35 36 // Respect NIX_NPM_TOKENS environment variable, which should be a JSON mapping in the shape of: 37 // `{ "registry.example.com": "example-registry-bearer-token", ... }` 38 if let Some(host) = url.host_str() { 39 if let Ok(npm_tokens) = env::var("NIX_NPM_TOKENS") { 40 if let Ok(tokens) = serde_json::from_str::<Map<String, Value>>(&npm_tokens) { 41 if let Some(token) = tokens.get(host).and_then(serde_json::Value::as_str) { 42 info!("Found NPM token for {}. Adding authorization header to request.", host); 43 request = request.header("Authorization", format!("Bearer {token}")); 44 } 45 } 46 } 47 } 48 49 let res = request.body(())?.send()?; 50 if !res.status().is_success() { 51 if res.status().is_client_error() { 52 bail!("Client error: {}", res.status()); 53 } 54 if res.status().is_server_error() { 55 bail!("Server error: {}", res.status()); 56 } 57 bail!("{}", res.status()); 58 } 59 Ok(res.into_body()) 60} 61 62pub fn get_url_body_with_retry(url: &Url) -> Result<Vec<u8>, anyhow::Error> { 63 retry(ExponentialBackoff::default(), || { 64 get_url(url) 65 .and_then(|mut body| { 66 let mut buf = Vec::new(); 67 68 body.read_to_end(&mut buf)?; 69 70 Ok(buf) 71 }) 72 .map_err(|err| match err.downcast_ref::<isahc::Error>() { 73 Some(isahc_err) => { 74 if isahc_err.is_network() || isahc_err.is_timeout() { 75 backoff::Error::transient(err) 76 } else { 77 backoff::Error::permanent(err) 78 } 79 } 80 None => backoff::Error::permanent(err), 81 }) 82 }) 83 .map_err(|backoff_err| match backoff_err { 84 backoff::Error::Permanent(err) 85 | backoff::Error::Transient { 86 err, 87 retry_after: _, 88 } => err, 89 }) 90} 91 92pub fn make_sri_hash(path: &Path) -> Result<String, NarError> { 93 let mut encoder = Encoder::new(path)?; 94 let mut hasher = Sha256::new(); 95 96 io::copy(&mut encoder, &mut hasher)?; 97 98 Ok(format!("sha256-{}", BASE64.encode(&hasher.finalize()))) 99}