From 6364abe24416a9c489eb4ea3a6e9a140f1e21f0a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:08:45 +0000 Subject: [PATCH 1/3] 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 --- .../services/s3/S3VirtualHostFilter.java | 42 ++++++++++--------- .../services/s3/S3VirtualHostFilterTest.java | 19 +++++++-- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java index cd4fbed8..8ca8227a 100644 --- a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java +++ b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java @@ -1,15 +1,14 @@ package io.github.hectorvent.floci.services.s3; +import io.github.hectorvent.floci.config.EmulatorConfig; import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.ext.Provider; -import org.eclipse.microprofile.config.inject.ConfigProperty; import java.net.URI; -import java.util.Optional; @Provider @PreMatching @@ -18,13 +17,8 @@ public class S3VirtualHostFilter implements ContainerRequestFilter { private final String baseHostname; @Inject - public S3VirtualHostFilter( - @ConfigProperty(name = "floci.base-url", defaultValue = "http://localhost:4566") String baseUrl, - @ConfigProperty(name = "floci.hostname") Optional hostname) { - String effectiveUrl = hostname - .map(h -> baseUrl.replaceFirst("://[^:/]+(:\\d+)?", "://" + h + "$1")) - .orElse(baseUrl); - this.baseHostname = extractHostnameFromUrl(effectiveUrl); + public S3VirtualHostFilter(EmulatorConfig config) { + this.baseHostname = extractHostnameFromUrl(config.effectiveBaseUrl()); } @Override @@ -71,19 +65,21 @@ public void filter(ContainerRequestContext requestContext) { * matches a well-known AWS S3 domain pattern (for DNS-redirect setups). * * Examples with baseHostname="localhost": - * my-bucket.localhost:4566 → "my-bucket" - * my-bucket.localhost → "my-bucket" - * floci.svc.cluster.local → null (no bucket prefix, path-style) - * my-svc.floci.svc.cluster.local → null (remainder doesn't match "localhost") + * my-bucket.localhost:4566 -> "my-bucket" + * my-bucket.localhost -> "my-bucket" + * floci.svc.cluster.local -> null (no bucket prefix, path-style) + * my-svc.floci.svc.cluster.local -> null (remainder doesn't match "localhost") * * Examples with baseHostname="floci.svc.cluster.local": - * my-bucket.floci.svc.cluster.local → "my-bucket" - * floci.svc.cluster.local → null (no bucket prefix, path-style) + * my-bucket.floci.svc.cluster.local -> "my-bucket" + * floci.svc.cluster.local -> null (no bucket prefix, path-style) * * Returns null if the host does not match a virtual-hosted pattern. */ static String extractBucket(String host, String baseHostname) { - if (host == null) return null; + if (host == null) { + return null; + } // Strip port if present String hostname = stripPort(host); @@ -117,7 +113,9 @@ static String extractBucket(String host, String baseHostname) { /** Extracts the hostname (without scheme or port) from a URL string. */ static String extractHostnameFromUrl(String url) { - if (url == null) return null; + if (url == null) { + return null; + } try { URI uri = URI.create(url); return uri.getHost(); @@ -147,11 +145,15 @@ private static boolean isIpv4Address(String hostname) { return true; } - /** Returns true for *.s3.amazonaws.com and *.s3..amazonaws.com domains. */ + /** Returns true for *.s3.amazonaws.com and *.s3.region.amazonaws.com domains. */ private static boolean isAwsS3Domain(String remainder) { - if ("s3.amazonaws.com".equals(remainder)) return true; + if ("s3.amazonaws.com".equals(remainder)) { + return true; + } // s3..amazonaws.com - if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) return true; + if (remainder.startsWith("s3.") && remainder.endsWith(".amazonaws.com")) { + return true; + } return false; } } diff --git a/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java b/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java index 3bd18599..b84616bc 100644 --- a/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java +++ b/src/test/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilterTest.java @@ -1,5 +1,6 @@ package io.github.hectorvent.floci.services.s3; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.NullSource; @@ -9,7 +10,7 @@ class S3VirtualHostFilterTest { - // ── Virtual-hosted style: bucket prefix + matching baseHostname ───────── + // --- extractBucket with baseHostname --- @ParameterizedTest @CsvSource({ @@ -33,7 +34,7 @@ void extractsBucketFromVirtualHostedStyle(String host, String baseHostname, Stri assertEquals(expectedBucket, S3VirtualHostFilter.extractBucket(host, baseHostname)); } - // ── Path-style: service hostname alone — must NOT extract a bucket ─────── + // --- Path-style: service hostname alone — must NOT extract a bucket --- @ParameterizedTest @CsvSource({ @@ -70,7 +71,14 @@ void returnsNullForNullHost(String host) { assertNull(S3VirtualHostFilter.extractBucket(host, "localhost")); } - // ── Hostname extraction from URL ───────────────────────────────────────── + @Test + void returnsNullForNullBaseHostname() { + // Without a baseHostname, only AWS S3 domains should match + assertNull(S3VirtualHostFilter.extractBucket("my-bucket.localhost:4566", null)); + assertEquals("my-bucket", S3VirtualHostFilter.extractBucket("my-bucket.s3.amazonaws.com", null)); + } + + // --- Hostname extraction from URL --- @ParameterizedTest @CsvSource({ @@ -83,4 +91,9 @@ void returnsNullForNullHost(String host) { void extractsHostnameFromUrl(String url, String expectedHostname) { assertEquals(expectedHostname, S3VirtualHostFilter.extractHostnameFromUrl(url)); } + + @Test + void extractHostnameFromUrlReturnsNullForNull() { + assertNull(S3VirtualHostFilter.extractHostnameFromUrl(null)); + } } From 9534bc0e086c67ff1928b784a79b5d81c55d367c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:36:42 +0000 Subject: [PATCH 2/3] fix(s3): use ConfigProvider for runtime config lookup in S3VirtualHostFilter Replace @Inject EmulatorConfig with programmatic ConfigProvider.getConfig() to fix SRCFG00027 error. @PreMatching JAX-RS filters run before CDI beans are fully initialized, so @ConfigMapping beans like EmulatorConfig are not available during filter construction. Co-Authored-By: Matej Snuderl --- .../floci/services/s3/S3VirtualHostFilter.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java index 8ca8227a..e91cfaa5 100644 --- a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java +++ b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java @@ -1,14 +1,14 @@ package io.github.hectorvent.floci.services.s3; -import io.github.hectorvent.floci.config.EmulatorConfig; -import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.ext.Provider; +import org.eclipse.microprofile.config.ConfigProvider; import java.net.URI; +import java.util.Optional; @Provider @PreMatching @@ -16,9 +16,16 @@ public class S3VirtualHostFilter implements ContainerRequestFilter { private final String baseHostname; - @Inject - public S3VirtualHostFilter(EmulatorConfig config) { - this.baseHostname = extractHostnameFromUrl(config.effectiveBaseUrl()); + public S3VirtualHostFilter() { + String baseUrl = ConfigProvider.getConfig() + .getOptionalValue("floci.base-url", String.class) + .orElse("http://localhost:4566"); + Optional hostname = ConfigProvider.getConfig() + .getOptionalValue("floci.hostname", String.class); + String effectiveUrl = hostname + .map(h -> baseUrl.replaceFirst("://[^:/]+(:\\d+)?", "://" + h + "$1")) + .orElse(baseUrl); + this.baseHostname = extractHostnameFromUrl(effectiveUrl); } @Override From cfae4211852f47237ffdfa3cdfb0102310563e1d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:45:58 +0000 Subject: [PATCH 3/3] docs: add comment explaining why ConfigProvider is used instead of @Inject Co-Authored-By: Matej Snuderl --- .../hectorvent/floci/services/s3/S3VirtualHostFilter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java index e91cfaa5..81e4ee69 100644 --- a/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java +++ b/src/main/java/io/github/hectorvent/floci/services/s3/S3VirtualHostFilter.java @@ -16,6 +16,9 @@ public class S3VirtualHostFilter implements ContainerRequestFilter { private final String baseHostname; + // Cannot use @Inject with EmulatorConfig here: @PreMatching JAX-RS filters are + // instantiated before CDI beans are fully initialized, causing SRCFG00027 at startup. + // Use programmatic ConfigProvider lookup instead. public S3VirtualHostFilter() { String baseUrl = ConfigProvider.getConfig() .getOptionalValue("floci.base-url", String.class)