Skip to content

Commit 00b10ab

Browse files
fix(storage/s3): default to virtual-host-style addressing
1 parent d472bf8 commit 00b10ab

2 files changed

Lines changed: 59 additions & 3 deletions

File tree

  • crates
    • iceberg/src/io/storage/config
    • storage/opendal/src

crates/iceberg/src/io/storage/config/s3.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,14 @@ pub const S3_DISABLE_CONFIG_LOAD: &str = "s3.disable-config-load";
6969
///
7070
/// This struct contains all the configuration options for connecting to Amazon S3.
7171
/// Use the builder pattern via `S3Config::builder()` to construct instances.
72-
/// ```
73-
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)]
72+
///
73+
/// Defaults follow the Iceberg `S3FileIOProperties` spec (see
74+
/// [`PATH_STYLE_ACCESS_DEFAULT = false`](https://github.com/apache/iceberg/blob/main/aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIOProperties.java)),
75+
/// i.e. virtual-host-style addressing is enabled unless
76+
/// `s3.path-style-access=true` is explicitly set. This matches what
77+
/// Java clients do out of the box and is required for a number of
78+
/// S3-compatible stores that do not support path-style URLs.
79+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TypedBuilder)]
7480
pub struct S3Config {
7581
/// S3 endpoint URL.
7682
#[builder(default, setter(strip_option, into))]
@@ -88,7 +94,9 @@ pub struct S3Config {
8894
#[builder(default, setter(strip_option, into))]
8995
pub region: Option<String>,
9096
/// Enable virtual host style (opposite of path style access).
91-
#[builder(default)]
97+
///
98+
/// Defaults to `true` to match Iceberg `S3FileIOProperties.PATH_STYLE_ACCESS_DEFAULT = false`.
99+
#[builder(default = true)]
92100
pub enable_virtual_host_style: bool,
93101
/// Server side encryption type.
94102
#[builder(default, setter(strip_option, into))]
@@ -125,6 +133,12 @@ pub struct S3Config {
125133
pub disable_config_load: bool,
126134
}
127135

136+
impl Default for S3Config {
137+
fn default() -> Self {
138+
Self::builder().build()
139+
}
140+
}
141+
128142
impl TryFrom<&StorageConfig> for S3Config {
129143
type Error = crate::Error;
130144

@@ -267,6 +281,17 @@ mod tests {
267281
assert_eq!(s3_config.region.as_deref(), Some("eu-west-1"));
268282
}
269283

284+
#[test]
285+
fn test_s3_config_default_is_virtual_host_style() {
286+
// Matches Iceberg S3FileIOProperties.PATH_STYLE_ACCESS_DEFAULT = false.
287+
assert!(S3Config::default().enable_virtual_host_style);
288+
assert!(
289+
S3Config::try_from(&StorageConfig::new())
290+
.unwrap()
291+
.enable_virtual_host_style
292+
);
293+
}
294+
270295
#[test]
271296
fn test_s3_config_path_style_access() {
272297
let storage_config = StorageConfig::new().with_prop(S3_PATH_STYLE_ACCESS, "true");

crates/storage/opendal/src/s3.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ use crate::utils::{from_opendal_error, is_truthy};
3737
/// Parse iceberg props to s3 config.
3838
pub(crate) fn s3_config_parse(mut m: HashMap<String, String>) -> Result<S3Config> {
3939
let mut cfg = S3Config::default();
40+
// Match Iceberg `S3FileIOProperties.PATH_STYLE_ACCESS_DEFAULT = false`:
41+
// virtual-host-style addressing is the spec default. opendal's own
42+
// default is path-style, which disagrees with the Java SDK and breaks
43+
// S3-compatible stores that only accept virtual-hosted-style URLs.
44+
// Any explicit `s3.path-style-access` property below overrides this.
45+
cfg.enable_virtual_host_style = true;
4046
if let Some(endpoint) = m.remove(S3_ENDPOINT) {
4147
cfg.endpoint = Some(endpoint);
4248
};
@@ -177,3 +183,28 @@ impl AwsCredentialLoad for CustomAwsCredentialLoader {
177183
self.0.load_credential(client).await
178184
}
179185
}
186+
187+
#[cfg(test)]
188+
mod tests {
189+
use std::collections::HashMap;
190+
191+
use iceberg::io::S3_PATH_STYLE_ACCESS;
192+
193+
use super::s3_config_parse;
194+
195+
fn parse_with(prop: Option<&str>) -> bool {
196+
let mut props = HashMap::new();
197+
if let Some(v) = prop {
198+
props.insert(S3_PATH_STYLE_ACCESS.to_string(), v.to_string());
199+
}
200+
s3_config_parse(props).unwrap().enable_virtual_host_style
201+
}
202+
203+
#[test]
204+
fn s3_config_parse_path_style_access() {
205+
// Match Iceberg S3FileIOProperties.PATH_STYLE_ACCESS_DEFAULT = false.
206+
assert!(parse_with(None));
207+
assert!(parse_with(Some("false")));
208+
assert!(!parse_with(Some("true")));
209+
}
210+
}

0 commit comments

Comments
 (0)