Skip to content

Commit 66441d1

Browse files
rtylerion-elgreco
authored andcommitted
feat: disable AWS configuration resolution for non-AWS S3 storage scenarios
This should reduce bugs, reduce warnings, and improve performance when somebody is using this crate against an S3-alike service rather than AWS S3 directly. Currently I cannot think of a valid scenarijo where one would use AWS_ENDPOINT_URL against actual S3 buckets, so using that as the heuristic
1 parent a72756d commit 66441d1

File tree

4 files changed

+89
-37
lines changed

4 files changed

+89
-37
lines changed

crates/aws/src/constants.rs

+3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ pub const AWS_EC2_METADATA_DISABLED: &str = "AWS_EC2_METADATA_DISABLED";
8484
/// Defaults to 100
8585
pub const AWS_EC2_METADATA_TIMEOUT: &str = "AWS_EC2_METADATA_TIMEOUT";
8686

87+
/// Force the delta-rs to attempt to load AWS credentials
88+
pub const AWS_FORCE_CREDENTIAL_LOAD: &str = "AWS_FORCE_CREDENTIAL_LOAD";
89+
8790
/// The list of option keys owned by the S3 module.
8891
/// Option keys not contained in this list will be added to the `extra_opts`
8992
/// field of [crate::storage::s3::S3StorageOptions].

crates/aws/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ mod tests {
725725
let factory = S3LogStoreFactory::default();
726726
let store = InMemory::new();
727727
let url = Url::parse("s3://test-bucket").unwrap();
728-
std::env::remove_var(storage::s3_constants::AWS_S3_LOCKING_PROVIDER);
728+
std::env::remove_var(crate::constants::AWS_S3_LOCKING_PROVIDER);
729729
let logstore = factory
730730
.with_options(Arc::new(store), &url, &StorageOptions::from(HashMap::new()))
731731
.unwrap();

crates/aws/src/storage.rs

+76-32
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,7 @@ impl ObjectStoreFactory for S3ObjectStoreFactory {
7474
) -> DeltaResult<(ObjectStoreRef, Path)> {
7575
let options = self.with_env_s3(storage_options);
7676

77-
let sdk_config = if options.0.contains_key(constants::AWS_ENDPOINT_URL) {
78-
None
79-
} else {
80-
Some(execute_sdk_future(
81-
crate::credentials::resolve_credentials(storage_options.clone()),
82-
)??)
83-
};
84-
77+
// All S3-likes should start their builder the same way
8578
let mut builder = AmazonS3Builder::new().with_url(url.to_string());
8679

8780
for (key, value) in options.0.iter() {
@@ -95,14 +88,18 @@ impl ObjectStoreFactory for S3ObjectStoreFactory {
9588
source: Box::new(e),
9689
})?;
9790
let prefix = Path::parse(path)?;
98-
let inner = if let Some(sdk_config) = sdk_config {
99-
builder.with_credentials(Arc::new(crate::credentials::AWSForObjectStore::new(
100-
sdk_config,
101-
)))
102-
} else {
103-
builder
91+
92+
if is_aws(storage_options) {
93+
debug!("Detected AWS S3, resolving credentials");
94+
let sdk_config = execute_sdk_future(crate::credentials::resolve_credentials(
95+
storage_options.clone(),
96+
))??;
97+
builder = builder.with_credentials(Arc::new(
98+
crate::credentials::AWSForObjectStore::new(sdk_config),
99+
));
104100
}
105-
.build()?;
101+
102+
let inner = builder.build()?;
106103

107104
let store = aws_storage_handler(limit_store_handler(inner, &options), &options)?;
108105
debug!("Initialized the object store: {store:?}");
@@ -136,6 +133,26 @@ fn aws_storage_handler(
136133
}
137134
}
138135

136+
// Determine whether this crate is being configured for use with native AWS S3 or an S3-alike
137+
//
138+
// This function will rteturn true in the default case since it's most likely that the absence of
139+
// options will mean default/S3 configuration
140+
fn is_aws(options: &StorageOptions) -> bool {
141+
if options
142+
.0
143+
.contains_key(crate::constants::AWS_FORCE_CREDENTIAL_LOAD)
144+
{
145+
return true;
146+
}
147+
if options
148+
.0
149+
.contains_key(crate::constants::AWS_S3_LOCKING_PROVIDER)
150+
{
151+
return true;
152+
}
153+
!options.0.contains_key(crate::constants::AWS_ENDPOINT_URL)
154+
}
155+
139156
/// Options used to configure the [S3StorageBackend].
140157
///
141158
/// Available options are described in [s3_constants].
@@ -208,12 +225,14 @@ impl S3StorageOptions {
208225

209226
let storage_options = StorageOptions(options.clone());
210227

211-
let sdk_config = if storage_options.0.contains_key(constants::AWS_ENDPOINT_URL) {
212-
None
213-
} else {
214-
Some(execute_sdk_future(
215-
crate::credentials::resolve_credentials(storage_options.clone()),
216-
)??)
228+
let sdk_config = match is_aws(&storage_options) {
229+
false => None,
230+
true => {
231+
debug!("Detected AWS S3, resolving credentials");
232+
Some(execute_sdk_future(
233+
crate::credentials::resolve_credentials(storage_options.clone()),
234+
)??)
235+
}
217236
};
218237

219238
Ok(Self {
@@ -528,10 +547,12 @@ mod tests {
528547
let options = S3StorageOptions::try_default().unwrap();
529548
assert_eq!(
530549
S3StorageOptions {
531-
sdk_config: SdkConfig::builder()
532-
.endpoint_url("http://localhost".to_string())
533-
.region(Region::from_static("us-west-1"))
534-
.build(),
550+
sdk_config: Some(
551+
SdkConfig::builder()
552+
.endpoint_url("http://localhost".to_string())
553+
.region(Region::from_static("us-west-1"))
554+
.build()
555+
),
535556
virtual_hosted_style_request: false,
536557
locking_provider: Some("dynamodb".to_string()),
537558
dynamodb_endpoint: None,
@@ -560,9 +581,11 @@ mod tests {
560581
.unwrap();
561582

562583
let mut expected = S3StorageOptions::try_default().unwrap();
563-
expected.sdk_config = SdkConfig::builder()
564-
.region(Region::from_static("eu-west-1"))
565-
.build();
584+
expected.sdk_config = Some(
585+
SdkConfig::builder()
586+
.region(Region::from_static("eu-west-1"))
587+
.build(),
588+
);
566589
assert_eq!(expected, options);
567590
});
568591
}
@@ -670,10 +693,12 @@ mod tests {
670693

671694
assert_eq!(
672695
S3StorageOptions {
673-
sdk_config: SdkConfig::builder()
674-
.endpoint_url("http://localhost".to_string())
675-
.region(Region::from_static("us-west-2"))
676-
.build(),
696+
sdk_config: Some(
697+
SdkConfig::builder()
698+
.endpoint_url("http://localhost".to_string())
699+
.region(Region::from_static("us-west-2"))
700+
.build()
701+
),
677702
virtual_hosted_style_request: false,
678703
locking_provider: Some("dynamodb".to_string()),
679704
dynamodb_endpoint: Some("http://localhost:dynamodb".to_string()),
@@ -767,4 +792,23 @@ mod tests {
767792
}
768793
});
769794
}
795+
796+
#[test]
797+
fn test_is_aws() {
798+
let options = StorageOptions::default();
799+
assert!(is_aws(&options));
800+
801+
let minio: HashMap<String, String> = hashmap! {
802+
crate::constants::AWS_ENDPOINT_URL.to_string() => "http://minio:8080".to_string(),
803+
};
804+
let options = StorageOptions::from(minio);
805+
assert!(!is_aws(&options));
806+
807+
let localstack: HashMap<String, String> = hashmap! {
808+
crate::constants::AWS_FORCE_CREDENTIAL_LOAD.to_string() => "true".to_string(),
809+
crate::constants::AWS_ENDPOINT_URL.to_string() => "http://minio:8080".to_string(),
810+
};
811+
let options = StorageOptions::from(localstack);
812+
assert!(is_aws(&options));
813+
}
770814
}

crates/aws/tests/integration_s3_dynamodb.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use lazy_static::lazy_static;
2323
use object_store::path::Path;
2424
use serde_json::Value;
2525
use serial_test::serial;
26+
use tracing::log::*;
2627

2728
use maplit::hashmap;
2829
use object_store::{PutOptions, PutPayload};
@@ -43,7 +44,7 @@ lazy_static! {
4344
fn make_client() -> TestResult<DynamoDbLockClient> {
4445
let options: S3StorageOptions = S3StorageOptions::try_default().unwrap();
4546
Ok(DynamoDbLockClient::try_new(
46-
&options.sdk_config,
47+
&options.sdk_config.unwrap(),
4748
None,
4849
None,
4950
None,
@@ -74,7 +75,7 @@ fn client_configs_via_env_variables() -> TestResult<()> {
7475
billing_mode: BillingMode::PayPerRequest,
7576
lock_table_name: "some_table".to_owned(),
7677
max_elapsed_request_time: Duration::from_secs(64),
77-
sdk_config: options.sdk_config,
78+
sdk_config: options.sdk_config.unwrap(),
7879
},
7980
*config,
8081
);
@@ -87,6 +88,7 @@ fn client_configs_via_env_variables() -> TestResult<()> {
8788
#[tokio::test]
8889
#[serial]
8990
async fn test_create_s3_table() -> TestResult<()> {
91+
let _ = pretty_env_logger::try_init();
9092
let context = IntegrationContext::new(Box::new(S3Integration::default()))?;
9193
let _client = make_client()?;
9294
let table_name = format!("{}_{}", "create_test", uuid::Uuid::new_v4());
@@ -98,8 +100,10 @@ async fn test_create_s3_table() -> TestResult<()> {
98100
true,
99101
)]);
100102
let storage_options: HashMap<String, String> = hashmap! {
101-
"AWS_ALLOW_HTTP".into() => "true".into(),
102-
"AWS_ENDPOINT_URL".into() => "http://localhost:4566".into(),
103+
deltalake_aws::constants::AWS_ALLOW_HTTP.into() => "true".into(),
104+
// Despite not being in AWS, we should force credential resolution
105+
deltalake_aws::constants::AWS_FORCE_CREDENTIAL_LOAD.into() => "true".into(),
106+
deltalake_aws::constants::AWS_ENDPOINT_URL.into() => "http://localhost:4566".into(),
103107
};
104108
let log_store = logstore_for(Url::parse(&table_uri)?, storage_options, None)?;
105109

@@ -113,6 +117,7 @@ async fn test_create_s3_table() -> TestResult<()> {
113117
)
114118
.await?;
115119

120+
debug!("creating a CreateBuilder");
116121
let _created = CreateBuilder::new()
117122
.with_log_store(log_store)
118123
.with_partition_columns(vec!["id"])

0 commit comments

Comments
 (0)