From d820fc85592d6c7f049eb5b8b3d55cbe40a0794e Mon Sep 17 00:00:00 2001 From: Mendred <14924849+Mendred@users.noreply.github.com> Date: Mon, 7 Sep 2026 11:52:53 +0200 Subject: [PATCH] fix: use official io.floci:spring-boot-testcontainers-floci instead of re-implementing it Removes AwsFlociContainerConnectionDetailsFactory and FlociAwsClientFactory, which duplicated the ContainerConnectionDetailsFactory already provided by io.floci:spring-boot-testcontainers-floci. Having both implementations on the classpath at the same time breaks Spring Boot's service connection resolution (two competing factories for the same pair), as reported by the Floci maintainer in awspring/spring-cloud-aws#1604. Depend on io.floci:spring-boot-testcontainers-floci (optional, same scope as before) instead of the low-level io.floci:testcontainers-floci module. This also brings automatic S3 path-style configuration for Floci, which the removed implementation was missing. Ref: https://github.com/awspring/spring-cloud-aws/pull/1604 Ref: https://github.com/floci-io/testcontainers-floci --- spring-cloud-aws-testcontainers/pom.xml | 4 +- ...lociContainerConnectionDetailsFactory.java | 70 ------------------- .../testcontainers/FlociAwsClientFactory.java | 55 --------------- .../FlociAwsClientFactoryTest.java | 46 ------------ ...lociServiceConnectionIntegrationTest.java} | 37 +++++----- 5 files changed, 18 insertions(+), 194 deletions(-) delete mode 100644 spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsFlociContainerConnectionDetailsFactory.java delete mode 100644 spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/FlociAwsClientFactory.java delete mode 100644 spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/FlociAwsClientFactoryTest.java rename spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/{AwsFlociContainerConnectionDetailsFactoryTest.java => FlociServiceConnectionIntegrationTest.java} (80%) diff --git a/spring-cloud-aws-testcontainers/pom.xml b/spring-cloud-aws-testcontainers/pom.xml index f14384bb1..184ec9317 100644 --- a/spring-cloud-aws-testcontainers/pom.xml +++ b/spring-cloud-aws-testcontainers/pom.xml @@ -39,9 +39,9 @@ io.floci - testcontainers-floci + spring-boot-testcontainers-floci true - 2.9.0 + 2.16.1 org.springframework diff --git a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsFlociContainerConnectionDetailsFactory.java b/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsFlociContainerConnectionDetailsFactory.java deleted file mode 100644 index 944b1497d..000000000 --- a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsFlociContainerConnectionDetailsFactory.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2013-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.awspring.cloud.testcontainers; - -import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails; -import io.floci.testcontainers.FlociContainer; -import java.net.URI; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; -import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; - -/** - * {@link ContainerConnectionDetailsFactory} to create {@link AwsConnectionDetails} from a - * {@link ServiceConnection @ServiceConnection}-annotated {@link FlociContainer}. - * - * @author Bastian Hellmann - * @since 4.1.0 - */ -@ConditionalOnClass(FlociContainer.class) - -public class AwsFlociContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { - @Override - protected AwsConnectionDetails getContainerConnectionDetails(ContainerConnectionSource source) { - return new AwsContainerConnectionDetails(source); - } - - private static final class AwsContainerConnectionDetails extends ContainerConnectionDetails - implements AwsConnectionDetails { - - protected AwsContainerConnectionDetails(ContainerConnectionSource source) { - super(source); - } - - @Override - public URI getEndpoint() { - return URI.create(getContainer().getEndpoint()); - } - - @Override - public String getRegion() { - return getContainer().getRegion(); - } - - @Override - public String getAccessKey() { - return getContainer().getAccessKey(); - } - - @Override - public String getSecretKey() { - return getContainer().getSecretKey(); - } - } -} diff --git a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/FlociAwsClientFactory.java b/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/FlociAwsClientFactory.java deleted file mode 100644 index 06c8ae4d8..000000000 --- a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/FlociAwsClientFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.awspring.cloud.testcontainers; - -import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; -import io.awspring.cloud.autoconfigure.core.AwsProperties; -import io.awspring.cloud.core.region.StaticRegionProvider; -import io.floci.testcontainers.FlociContainer; -import java.net.URI; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; - -/** - * {@link AwsClientFactory} implementation that creates containers pointing to Floci instance running through - * {@link FlociContainer}. - * - * @author Bastian Hellmann - * @since 4.1.0 - */ -public class FlociAwsClientFactory implements AwsClientFactory { - private final AwsClientBuilderConfigurer configurer; - - public FlociAwsClientFactory(FlociContainer floci) { - this.configurer = clientBuilderConfigurer(floci); - } - - @Override - public > CLIENT create(BUILDER builder) { - return configurer.configure(builder).build(); - } - - private AwsClientBuilderConfigurer clientBuilderConfigurer(FlociContainer floci) { - AwsProperties properties = new AwsProperties(); - properties.setEndpoint(URI.create(floci.getEndpoint())); - - StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider - .create(AwsBasicCredentials.create(floci.getAccessKey(), floci.getSecretKey())); - StaticRegionProvider regionProvider = new StaticRegionProvider(floci.getRegion()); - return new AwsClientBuilderConfigurer(credentialsProvider, regionProvider, properties); - } -} diff --git a/spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/FlociAwsClientFactoryTest.java b/spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/FlociAwsClientFactoryTest.java deleted file mode 100644 index 21daa71d7..000000000 --- a/spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/FlociAwsClientFactoryTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.awspring.cloud.testcontainers; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.floci.testcontainers.FlociContainer; -import org.junit.jupiter.api.Test; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; -import software.amazon.awssdk.services.s3.S3Client; - -/** - * Tests for {@link FlociAwsClientFactory}. - * - * @author Bastian Hellmann - */ -@Testcontainers -class FlociAwsClientFactoryTest { - - @Container - private FlociContainer flociContainer = new FlociContainer(DockerImageName.parse("floci/floci:1.5.33")); - - @Test - void createsClient() { - var factory = new FlociAwsClientFactory(flociContainer); - try (var s3Client = factory.create(S3Client.builder())) { - s3Client.createBucket(r -> r.bucket("my-bucket")); - assertThat(s3Client.listBuckets().buckets()).hasSize(1); - } - } -} diff --git a/spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/AwsFlociContainerConnectionDetailsFactoryTest.java b/spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/FlociServiceConnectionIntegrationTest.java similarity index 80% rename from spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/AwsFlociContainerConnectionDetailsFactoryTest.java rename to spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/FlociServiceConnectionIntegrationTest.java index 4ee5c189d..0bf6be554 100644 --- a/spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/AwsFlociContainerConnectionDetailsFactoryTest.java +++ b/spring-cloud-aws-testcontainers/src/test/java/io/awspring/cloud/testcontainers/FlociServiceConnectionIntegrationTest.java @@ -24,14 +24,10 @@ import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; import io.awspring.cloud.autoconfigure.dynamodb.DynamoDbAutoConfiguration; import io.awspring.cloud.autoconfigure.s3.S3AutoConfiguration; -import io.awspring.cloud.autoconfigure.s3.S3CrtAsyncClientAutoConfiguration; import io.awspring.cloud.autoconfigure.ses.SesAutoConfiguration; import io.awspring.cloud.autoconfigure.sns.SnsAutoConfiguration; import io.awspring.cloud.autoconfigure.sqs.SqsAutoConfiguration; -import io.awspring.cloud.s3.S3Template; import io.floci.testcontainers.FlociContainer; -import java.net.URL; -import java.time.Duration; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -42,19 +38,31 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.s3.S3AsyncClient; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.ses.SesClient; import software.amazon.awssdk.services.sns.SnsClient; import software.amazon.awssdk.services.sqs.SqsAsyncClient; +/** + * Verifies that a {@link FlociContainer} declared as a {@code @ServiceConnection} is correctly picked up by Spring + * Cloud AWS via the official {@code io.floci:spring-boot-testcontainers-floci} auto-configuration module. + * + *

+ * Spring Cloud AWS intentionally does not ship its own {@code ContainerConnectionDetailsFactory} for + * {@link FlociContainer} (unlike for {@code LocalStackContainer}), because the Floci project already provides one in + * {@code io.floci:spring-boot-testcontainers-floci}. Having two competing factories for the same + * {@code FlociContainer -> AwsConnectionDetails} pair on the classpath breaks Spring Boot's service connection + * resolution. See https://github.com/awspring/spring-cloud-aws/pull/1604 for background. + * + * @author Bastian Hellmann + */ @SpringJUnitConfig @Testcontainers(disabledWithoutDocker = true) -class AwsFlociContainerConnectionDetailsFactoryTest { +class FlociServiceConnectionIntegrationTest { @Container @ServiceConnection - static final FlociContainer floci = new FlociContainer(DockerImageName.parse("floci/floci:1.5.33")); + static final FlociContainer floci = new FlociContainer(DockerImageName.parse("floci/floci:2.0.1")); @Autowired(required = false) private AwsConnectionDetails connectionDetails; @@ -91,23 +99,10 @@ void configuresS3ClientWithServiceConnection(@Autowired S3Client client) { assertThatCode(client::listBuckets).doesNotThrowAnyException(); } - @Test - void configuresS3PresignedWithServiceConnection(@Autowired S3Template s3Template) { - URL signedGetURL = s3Template.createSignedGetURL("foo", "bar", Duration.ofMinutes(1)); - assertThat(signedGetURL.getHost()).isNotNull().isNotEqualTo("foo.s3.amazonaws.com") - .as("Signed URL does not point to AWS as the endpoint has been overwritten by @ServiceConnection"); - } - - @Test - void configuresS3AsyncClientWithServiceConnection(@Autowired S3AsyncClient client) { - assertThatCode(client.listBuckets()::join).doesNotThrowAnyException(); - } - @Configuration(proxyBeanMethods = false) @ImportAutoConfiguration({ AwsAutoConfiguration.class, CredentialsProviderAutoConfiguration.class, RegionProviderAutoConfiguration.class, DynamoDbAutoConfiguration.class, SesAutoConfiguration.class, - SqsAutoConfiguration.class, SnsAutoConfiguration.class, S3AutoConfiguration.class, - S3CrtAsyncClientAutoConfiguration.class }) + SqsAutoConfiguration.class, SnsAutoConfiguration.class, S3AutoConfiguration.class }) static class TestConfiguration { }