From f8caf6562fea44db752edaf91f00ce6fb6986928 Mon Sep 17 00:00:00 2001 From: u228298 Date: Thu, 28 Aug 2025 11:04:51 +0200 Subject: [PATCH] feat(app): add support for S3-compatible services - Extends the S3 client configuration to allow for endpoint overrides and path-style access, enabling the use of S3-compatible object storage solutions like MinIO. - Enhances flexibility and allows the application to run in various environments outside of AWS. Configuration is managed via the standard `AWS_ENDPOINT_URL_S3` and `AWS_S3_FORCE_PATH_STYLE` environment variables. - The deprecated `DefaultCredentialsProvider.create()` has been updated to its modern builder equivalent for safer credential management. --- .../app/infrastructure/GtfsScheduleS3.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/naviqore/app/infrastructure/GtfsScheduleS3.java b/app/src/main/java/org/naviqore/app/infrastructure/GtfsScheduleS3.java index e26a3c01..5a7679e6 100644 --- a/app/src/main/java/org/naviqore/app/infrastructure/GtfsScheduleS3.java +++ b/app/src/main/java/org/naviqore/app/infrastructure/GtfsScheduleS3.java @@ -7,7 +7,9 @@ import org.naviqore.service.repo.GtfsScheduleRepository; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import java.io.IOException; @@ -19,12 +21,17 @@ * Repository for loading a static GTFS feed from an S3 bucket. *

* Expected format for the S3 URI: {@code s3://bucket-name/path/to/gtfs.zip}. AWS credentials and region must be - * provided via environment variables: {@code AWS_ACCESS_KEY_ID}, {@code AWS_SECRET_ACCESS_KEY}, {@code AWS_REGION}. + * provided via standard environment variables. Supports S3-compatible endpoints like MinIO via + * {@code AWS_ENDPOINT_URL_S3} and {@code AWS_S3_FORCE_PATH_STYLE}. */ @Slf4j @RequiredArgsConstructor public class GtfsScheduleS3 implements GtfsScheduleRepository { + private static final String AWS_REGION = "AWS_REGION"; + private static final String AWS_ENDPOINT_URL = "AWS_ENDPOINT_URL"; + private static final String AWS_ENDPOINT_URL_S_3 = "AWS_ENDPOINT_URL_S3"; + private static final String AWS_S_3_FORCE_PATH_STYLE = "AWS_S3_FORCE_PATH_STYLE"; private static final String TMP_DIRECTORY_PREFIX = "tmp_gtfs_"; private static final String FILE_NAME = "gtfs.zip"; @@ -39,13 +46,40 @@ public GtfsSchedule get() throws IOException, InterruptedException { Path tempDir = Files.createTempDirectory(TMP_DIRECTORY_PREFIX); Path filePath = tempDir.resolve(FILE_NAME); - try (S3Client s3 = S3Client.builder().credentialsProvider(DefaultCredentialsProvider.create()).build()) { + // setup default builder + S3ClientBuilder s3ClientBuilder = S3Client.builder() + .credentialsProvider(DefaultCredentialsProvider.builder().build()); + // configure region from environment variable + String region = System.getenv(AWS_REGION); + if (region != null && !region.isBlank()) { + s3ClientBuilder.region(Region.of(region)); + } + + // configure endpoint override; use the S3-specific variable first, then fall back to the generic one + String endpointOverride = System.getenv(AWS_ENDPOINT_URL_S_3); + if (endpointOverride == null || endpointOverride.isBlank()) { + endpointOverride = System.getenv(AWS_ENDPOINT_URL); + } + if (endpointOverride != null && !endpointOverride.isBlank()) { + log.debug("S3 endpoint override is set to: {}", endpointOverride); + s3ClientBuilder.endpointOverride(URI.create(endpointOverride)); + } + + // force path-style access if requested + String forcePathStyle = System.getenv(AWS_S_3_FORCE_PATH_STYLE); + if ("true".equalsIgnoreCase(forcePathStyle)) { + log.info("S3 path-style access is enabled."); + s3ClientBuilder.forcePathStyle(true); + } + + try (S3Client s3 = s3ClientBuilder.build()) { log.info("Downloading file: {}", s3Uri); s3.getObject(GetObjectRequest.builder().bucket(bucket).key(key).build(), ResponseTransformer.toFile(filePath)); return new GtfsScheduleReader().read(filePath.toString()); + } finally { Files.deleteIfExists(filePath); Files.deleteIfExists(tempDir);