This is an automated email from the ASF dual-hosted git repository.
mneumann pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs-object-store.git
The following commit(s) were added to refs/heads/main by this push:
new bdcac43 feat: Add support for AWS_ENDPOINT_URL_S3 environment
variable (#590)
bdcac43 is described below
commit bdcac43fc637ac89cc80f18f4e16b3fb66fd0ec8
Author: Rajat Goel <[email protected]>
AuthorDate: Thu Mar 12 04:11:16 2026 -0700
feat: Add support for AWS_ENDPOINT_URL_S3 environment variable (#590)
* feat: Add support for AWS_ENDPOINT_URL_S3 env var
* cargo fmt
* comments
---
src/aws/builder.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 50 insertions(+), 3 deletions(-)
diff --git a/src/aws/builder.rs b/src/aws/builder.rs
index b698cf0..8b767db 100644
--- a/src/aws/builder.rs
+++ b/src/aws/builder.rs
@@ -135,6 +135,8 @@ pub struct AmazonS3Builder {
bucket_name: Option<String>,
/// Endpoint for communicating with AWS S3
endpoint: Option<String>,
+ /// Service-specific S3 endpoint URL (takes precedence over endpoint)
+ s3_endpoint: Option<String>,
/// Token to use for requests
token: Option<String>,
/// Url
@@ -263,6 +265,14 @@ pub enum AmazonS3ConfigKey {
/// - `endpoint_url`
Endpoint,
+ /// Service-specific S3 endpoint URL
+ ///
+ /// When set, takes precedence over [`Endpoint`](Self::Endpoint) in the
build method.
+ ///
+ /// Supported keys:
+ /// - `aws_endpoint_url_s3`
+ S3Endpoint,
+
/// Token to use for requests (passed to underlying provider)
///
/// See [`AmazonS3Builder::with_token`] for details.
@@ -448,6 +458,7 @@ impl AsRef<str> for AmazonS3ConfigKey {
Self::Region => "aws_region",
Self::Bucket => "aws_bucket",
Self::Endpoint => "aws_endpoint",
+ Self::S3Endpoint => "aws_endpoint_url_s3",
Self::Token => "aws_session_token",
Self::ImdsV1Fallback => "aws_imdsv1_fallback",
Self::VirtualHostedStyleRequest =>
"aws_virtual_hosted_style_request",
@@ -485,6 +496,7 @@ impl FromStr for AmazonS3ConfigKey {
"aws_region" | "region" => Ok(Self::Region),
"aws_bucket" | "aws_bucket_name" | "bucket_name" | "bucket" =>
Ok(Self::Bucket),
"aws_endpoint_url" | "aws_endpoint" | "endpoint_url" | "endpoint"
=> Ok(Self::Endpoint),
+ "aws_endpoint_url_s3" => Ok(Self::S3Endpoint),
"aws_session_token" | "aws_token" | "session_token" | "token" =>
Ok(Self::Token),
"aws_virtual_hosted_style_request" |
"virtual_hosted_style_request" => {
Ok(Self::VirtualHostedStyleRequest)
@@ -552,6 +564,7 @@ impl AmazonS3Builder {
/// * `AWS_SECRET_ACCESS_KEY` -> secret_access_key
/// * `AWS_DEFAULT_REGION` -> region
/// * `AWS_ENDPOINT` -> endpoint
+ /// * `AWS_ENDPOINT_URL_S3` -> s3_endpoint (takes precedence over endpoint
in build)
/// * `AWS_SESSION_TOKEN` -> token
/// * `AWS_WEB_IDENTITY_TOKEN_FILE` -> path to file containing web
identity token for AssumeRoleWithWebIdentity
/// * `AWS_ROLE_ARN` -> ARN of the role to assume when using web identity
token
@@ -573,7 +586,6 @@ impl AmazonS3Builder {
/// ```
pub fn from_env() -> Self {
let mut builder: Self = Default::default();
-
for (os_key, os_value) in std::env::vars_os() {
if let (Some(key), Some(value)) = (os_key.to_str(),
os_value.to_str()) {
if key.starts_with("AWS_") {
@@ -583,7 +595,6 @@ impl AmazonS3Builder {
}
}
}
-
builder
}
@@ -620,6 +631,7 @@ impl AmazonS3Builder {
AmazonS3ConfigKey::Region => self.region = Some(value.into()),
AmazonS3ConfigKey::Bucket => self.bucket_name = Some(value.into()),
AmazonS3ConfigKey::Endpoint => self.endpoint = Some(value.into()),
+ AmazonS3ConfigKey::S3Endpoint => self.s3_endpoint =
Some(value.into()),
AmazonS3ConfigKey::Token => self.token = Some(value.into()),
AmazonS3ConfigKey::ImdsV1Fallback =>
self.imdsv1_fallback.parse(value),
AmazonS3ConfigKey::VirtualHostedStyleRequest => {
@@ -703,6 +715,7 @@ impl AmazonS3Builder {
AmazonS3ConfigKey::Region | AmazonS3ConfigKey::DefaultRegion =>
self.region.clone(),
AmazonS3ConfigKey::Bucket => self.bucket_name.clone(),
AmazonS3ConfigKey::Endpoint => self.endpoint.clone(),
+ AmazonS3ConfigKey::S3Endpoint => self.s3_endpoint.clone(),
AmazonS3ConfigKey::Token => self.token.clone(),
AmazonS3ConfigKey::ImdsV1Fallback =>
Some(self.imdsv1_fallback.to_string()),
AmazonS3ConfigKey::VirtualHostedStyleRequest => {
@@ -1192,10 +1205,13 @@ impl AmazonS3Builder {
false => (None, None),
};
+ // S3-specific endpoint takes precedence over generic endpoint
+ let endpoint = self.s3_endpoint.or(self.endpoint);
+
// If `endpoint` is provided it's assumed to be consistent with
`virtual_hosted_style_request` or `s3_express`.
// For example, if `virtual_hosted_style_request` is true then
`endpoint` should have bucket name included.
let virtual_hosted = self.virtual_hosted_style_request.get()?;
- let bucket_endpoint = match (&self.endpoint, zonal_endpoint,
virtual_hosted) {
+ let bucket_endpoint = match (&endpoint, zonal_endpoint,
virtual_hosted) {
(Some(endpoint), _, true) => endpoint.clone(),
(Some(endpoint), _, false) => format!("{}/{}",
endpoint.trim_end_matches("/"), bucket),
(None, Some(endpoint), _) => endpoint,
@@ -1487,6 +1503,37 @@ mod tests {
assert!(builder.unsigned_payload.get().unwrap());
}
+ #[test]
+ fn s3_test_endpoint_url_s3_config() {
+ // Verify aws_endpoint_url_s3 parses to S3Endpoint config key
+ let key: AmazonS3ConfigKey = "aws_endpoint_url_s3".parse().unwrap();
+ assert!(matches!(key, AmazonS3ConfigKey::S3Endpoint));
+
+ // Verify S3Endpoint takes precedence over Endpoint in build,
regardless of order
+ let s3 = AmazonS3Builder::new()
+ .with_config(AmazonS3ConfigKey::Endpoint,
"http://generic-endpoint")
+ .with_config(AmazonS3ConfigKey::S3Endpoint,
"http://s3-specific-endpoint")
+ .with_bucket_name("test-bucket")
+ .build()
+ .unwrap();
+ assert_eq!(
+ s3.client.config.bucket_endpoint,
+ "http://s3-specific-endpoint/test-bucket"
+ );
+
+ // Verify precedence works even when S3Endpoint is set first
+ let s3 = AmazonS3Builder::new()
+ .with_config(AmazonS3ConfigKey::S3Endpoint,
"http://s3-specific-endpoint")
+ .with_config(AmazonS3ConfigKey::Endpoint,
"http://generic-endpoint")
+ .with_bucket_name("test-bucket")
+ .build()
+ .unwrap();
+ assert_eq!(
+ s3.client.config.bucket_endpoint,
+ "http://s3-specific-endpoint/test-bucket"
+ );
+ }
+
#[test]
fn s3_test_config_get_value() {
let aws_access_key_id = "object_store:fake_access_key_id".to_string();