diff --git a/src/flakehub_client.rs b/src/flakehub_client.rs index 5bcb4d5..d0f4604 100644 --- a/src/flakehub_client.rs +++ b/src/flakehub_client.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use color_eyre::eyre::{eyre, Context, Result}; use http::StatusCode; use reqwest::header::HeaderMap; @@ -20,6 +22,7 @@ pub struct Tarball { #[derive(serde::Deserialize)] pub(crate) struct StageResult { pub(crate) s3_upload_url: String, + pub(crate) s3_upload_headers: HashMap, pub(crate) uuid: Uuid, } diff --git a/src/main.rs b/src/main.rs index a185953..e3c2388 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, io::IsTerminal, process::ExitCode}; use clap::Parser; -use color_eyre::eyre::{eyre, Result}; +use color_eyre::eyre::{eyre, Context as _, Result}; use error::Error; use http::StatusCode; use reqwest::Response; @@ -148,7 +148,7 @@ async fn execute() -> Result { let stage_result: StageResult = response .json() .await - .map_err(|_| eyre!("Decoding release metadata POST response"))?; + .context("Decoding release metadata POST response")?; stage_result } @@ -192,8 +192,12 @@ async fn execute() -> Result { } }; - // upload tarball to s3 - s3::upload_release_to_s3(stage_result.s3_upload_url, ctx.tarball).await?; + s3::upload_release_to_s3( + stage_result.s3_upload_url, + stage_result.s3_upload_headers, + ctx.tarball, + ) + .await?; // "publish.rs" - publish the release after upload fhclient.release_publish(stage_result.uuid).await?; diff --git a/src/s3.rs b/src/s3.rs index 013d2e7..637bad9 100644 --- a/src/s3.rs +++ b/src/s3.rs @@ -1,29 +1,29 @@ +use std::collections::HashMap; + use color_eyre::eyre::{eyre, Result, WrapErr}; +use http::header::{CONTENT_LENGTH, CONTENT_TYPE}; +use http::{HeaderName, HeaderValue}; use reqwest::header::HeaderMap; use crate::flakehub_client::Tarball; -pub async fn upload_release_to_s3(presigned_s3_url: String, tarball: Tarball) -> Result<()> { +pub async fn upload_release_to_s3( + presigned_s3_url: String, + s3_headers: HashMap, + tarball: Tarball, +) -> Result<()> { + let overrides: HashMap<&str, String> = HashMap::from_iter([ + (CONTENT_LENGTH.as_str(), tarball.bytes.len().to_string()), + (CONTENT_TYPE.as_str(), "application/gzip".to_string()), + ("x-amz-checksum-sha256", tarball.hash_base64), + ]); + + let headers = build_headers(&s3_headers, &overrides)?; + let client = reqwest::Client::new(); let tarball_put_response = client .put(presigned_s3_url) - .headers({ - let mut header_map = HeaderMap::new(); - header_map.insert( - reqwest::header::CONTENT_LENGTH, - reqwest::header::HeaderValue::from_str(&format!("{}", tarball.bytes.len())) - .unwrap(), - ); - header_map.insert( - reqwest::header::HeaderName::from_static("x-amz-checksum-sha256"), - reqwest::header::HeaderValue::from_str(&tarball.hash_base64).unwrap(), - ); - header_map.insert( - reqwest::header::CONTENT_TYPE, - reqwest::header::HeaderValue::from_str("application/gzip").unwrap(), - ); - header_map - }) + .headers(headers) .body(tarball.bytes) .send() .await @@ -42,3 +42,25 @@ pub async fn upload_release_to_s3(presigned_s3_url: String, tarball: Tarball) -> Ok(()) } + +fn build_headers( + required_headers: &HashMap, + overrides: &HashMap<&str, String>, +) -> Result { + let mut header_map = HeaderMap::with_capacity(required_headers.len()); + for (name, required_value) in required_headers { + let value = overrides + .iter() + .find(|(k, _)| k.eq_ignore_ascii_case(name)) + .map(|(_, v)| v) + .unwrap_or(required_value); + + let header_name = HeaderName::from_bytes(name.as_bytes()) + .wrap_err_with(|| format!("Invalid header name `{name}`"))?; + let header_value = HeaderValue::from_str(value) + .wrap_err_with(|| format!("Invalid header value for `{name}`"))?; + header_map.insert(header_name, header_value); + } + + Ok(header_map) +}