From bcff340643f63a587338f070e362ede120f45dbf Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 3 Jul 2026 09:56:26 -0700 Subject: [PATCH] fixup: give required presigned headers directly to the client We still override ones with what we calculated (so if something went wrong, the upload will fail because of that), but there could be some additional headers that are unknowable to the client that we just have to send along verbatim. --- src/flakehub_client.rs | 3 +++ src/main.rs | 12 ++++++--- src/s3.rs | 58 +++++++++++++++++++++++++++++------------- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/flakehub_client.rs b/src/flakehub_client.rs index 5bcb4d55..d0f46042 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 a185953b..e3c2388d 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 013d2e74..637bad93 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) +}