Skip to content

Commit 6364abe

Browse files
fix(s3): use EmulatorConfig for FLOCI_HOSTNAME to fix native image crash
S3VirtualHostFilter used @ConfigProperty injection in its constructor, which gets baked into the GraalVM native binary at build time. Setting FLOCI_HOSTNAME at runtime via Docker env var caused an IllegalStateException because the runtime value differed from the build-time value (null). Refactor to inject EmulatorConfig (which uses @ConfigMapping and is runtime-safe) instead of raw @ConfigProperty, following the established pattern used by StorageFactory, ServiceRegistry, RegionResolver, etc. Also make bucket extraction hostname-aware: only treat the first label as a bucket name when the remainder matches the configured base hostname (or a well-known AWS S3 domain). This prevents false positives when Floci sits behind a multi-label hostname like floci.svc.cluster.local. Co-Authored-By: Matej Snuderl <ematej.snuderl@gmail.com>
1 parent 17841d2 commit 6364abe

2 files changed

Lines changed: 38 additions & 23 deletions

File tree

src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package io.github.hectorvent.floci.services.s3;
22

3+
import io.github.hectorvent.floci.config.EmulatorConfig;
34
import jakarta.inject.Inject;
45
import jakarta.ws.rs.container.ContainerRequestContext;
56
import jakarta.ws.rs.container.ContainerRequestFilter;
67
import jakarta.ws.rs.container.PreMatching;
78
import jakarta.ws.rs.core.UriBuilder;
89
import jakarta.ws.rs.ext.Provider;
9-
import org.eclipse.microprofile.config.inject.ConfigProperty;
1010

1111
import java.net.URI;
12-
import java.util.Optional;
1312

1413
@Provider
1514
@PreMatching
@@ -18,13 +17,8 @@ public class S3VirtualHostFilter implements ContainerRequestFilter {
1817
private final String baseHostname;
1918

2019
@Inject
21-
public S3VirtualHostFilter(
22-
@ConfigProperty(name = "floci.base-url", defaultValue = "http://localhost:4566") String baseUrl,
23-
@ConfigProperty(name = "floci.hostname") Optional<String> hostname) {
24-
String effectiveUrl = hostname
25-
.map(h -> baseUrl.replaceFirst("://[^:/]+(:\\d+)?", "://" + h + "$1"))
26-
.orElse(baseUrl);
27-
this.baseHostname = extractHostnameFromUrl(effectiveUrl);
20+
public S3VirtualHostFilter(EmulatorConfig config) {
21+
this.baseHostname = extractHostnameFromUrl(config.effectiveBaseUrl());
2822
}
2923

3024
@Override
@@ -71,19 +65,21 @@ public void filter(ContainerRequestContext requestContext) {
7165
* matches a well-known AWS S3 domain pattern (for DNS-redirect setups).
7266
*
7367
* Examples with baseHostname="localhost":
74-
* my-bucket.localhost:4566 "my-bucket"
75-
* my-bucket.localhost "my-bucket"
76-
* floci.svc.cluster.local null (no bucket prefix, path-style)
77-
* my-svc.floci.svc.cluster.local null (remainder doesn't match "localhost")
68+
* my-bucket.localhost:4566 -> "my-bucket"
69+
* my-bucket.localhost -> "my-bucket"
70+
* floci.svc.cluster.local -> null (no bucket prefix, path-style)
71+
* my-svc.floci.svc.cluster.local -> null (remainder doesn't match "localhost")
7872
*
7973
* Examples with baseHostname="floci.svc.cluster.local":
80-
* my-bucket.floci.svc.cluster.local "my-bucket"
81-
* floci.svc.cluster.local null (no bucket prefix, path-style)
74+
* my-bucket.floci.svc.cluster.local -> "my-bucket"
75+
* floci.svc.cluster.local -> null (no bucket prefix, path-style)
8276
*
8377
* Returns null if the host does not match a virtual-hosted pattern.
8478
*/
8579
static String extractBucket(String host, String baseHostname) {
86-
if (host == null) return null;
80+
if (host == null) {
81+
return null;
82+
}
8783

8884
// Strip port if present
8985
String hostname = stripPort(host);
@@ -117,7 +113,9 @@ static String extractBucket(String host, String baseHostname) {
117113

118114
/** Extracts the hostname (without scheme or port) from a URL string. */
119115
static String extractHostnameFromUrl(String url) {
120-
if (url == null) return null;
116+
if (url == null) {
117+
return null;
118+
}
121119
try {
122120
URI uri = URI.create(url);
123121
return uri.getHost();
@@ -147,11 +145,15 @@ private static boolean isIpv4Address(String hostname) {
147145
return true;
148146
}
149147

150-
/** Returns true for *.s3.amazonaws.com and *.s3.<region>.amazonaws.com domains. */
148+
/** Returns true for *.s3.amazonaws.com and *.s3.region.amazonaws.com domains. */
151149
private static boolean isAwsS3Domain(String remainder) {
152-
if ("s3.amazonaws.com".equals(remainder)) return true;
150+
if ("s3.amazonaws.com".equals(remainder)) {
151+
return true;
152+
}
153153
// s3.<region>.amazonaws.com
154-
if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) return true;
154+
if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) {
155+
return true;
156+
}
155157
return false;
156158
}
157159
}

src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.hectorvent.floci.services.s3;
22

3+
import org.junit.jupiter.api.Test;
34
import org.junit.jupiter.params.ParameterizedTest;
45
import org.junit.jupiter.params.provider.CsvSource;
56
import org.junit.jupiter.params.provider.NullSource;
@@ -9,7 +10,7 @@
910

1011
class S3VirtualHostFilterTest {
1112

12-
// ── Virtual-hosted style: bucket prefix + matching baseHostname ─────────
13+
// --- extractBucket with baseHostname ---
1314

1415
@ParameterizedTest
1516
@CsvSource({
@@ -33,7 +34,7 @@ void extractsBucketFromVirtualHostedStyle(String host, String baseHostname, Stri
3334
assertEquals(expectedBucket, S3VirtualHostFilter.extractBucket(host, baseHostname));
3435
}
3536

36-
// ── Path-style: service hostname alone — must NOT extract a bucket ───────
37+
// --- Path-style: service hostname alone — must NOT extract a bucket ---
3738

3839
@ParameterizedTest
3940
@CsvSource({
@@ -70,7 +71,14 @@ void returnsNullForNullHost(String host) {
7071
assertNull(S3VirtualHostFilter.extractBucket(host, "localhost"));
7172
}
7273

73-
// ── Hostname extraction from URL ─────────────────────────────────────────
74+
@Test
75+
void returnsNullForNullBaseHostname() {
76+
// Without a baseHostname, only AWS S3 domains should match
77+
assertNull(S3VirtualHostFilter.extractBucket("my-bucket.localhost:4566", null));
78+
assertEquals("my-bucket", S3VirtualHostFilter.extractBucket("my-bucket.s3.amazonaws.com", null));
79+
}
80+
81+
// --- Hostname extraction from URL ---
7482

7583
@ParameterizedTest
7684
@CsvSource({
@@ -83,4 +91,9 @@ void returnsNullForNullHost(String host) {
8391
void extractsHostnameFromUrl(String url, String expectedHostname) {
8492
assertEquals(expectedHostname, S3VirtualHostFilter.extractHostnameFromUrl(url));
8593
}
94+
95+
@Test
96+
void extractHostnameFromUrlReturnsNullForNull() {
97+
assertNull(S3VirtualHostFilter.extractHostnameFromUrl(null));
98+
}
8699
}

0 commit comments

Comments
 (0)