diff --git a/build.gradle b/build.gradle index ce93eb35..46a497ec 100644 --- a/build.gradle +++ b/build.gradle @@ -11,3 +11,11 @@ configurations.all { preferProjectModules() } } + +if (System.getenv("SONAR_TOKEN") != null) { + sonarqube { + properties { + property "sonar.exclusions", "**/example/**" + } + } +} diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-base.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-base.gradle index 5908ced7..b965e9bb 100644 --- a/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-base.gradle +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-base.gradle @@ -1,4 +1,3 @@ repositories { mavenCentral() } - diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-native-tests.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-native-tests.gradle index 658705c7..61e1946f 100644 --- a/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-native-tests.gradle +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-native-tests.gradle @@ -4,9 +4,6 @@ plugins { } graalvmNative { - binaries.configureEach { - buildArgs.add '-J--add-exports=java.management/sun.management=ALL-UNNAMED' // https://github.com/oracle/graal/issues/3875 - } metadataRepository { enabled = true } diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-tests.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-tests.gradle index 5db7dc5e..c5836a52 100644 --- a/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-tests.gradle +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.jms-tests.gradle @@ -6,8 +6,10 @@ plugins { micronaut { importMicronautPlatform = false + testRuntime "junit5" testResources { clientTimeout = 300 + version = libs.versions.micronaut.testresources.get() } } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index abd0d3eb..6fa2a1f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ micronaut-docs = "2.0.0" micronaut = "4.0.0-M6" micronaut-test = "4.0.0-M6" +micronaut-testresources = "2.0.0-M9" micronaut-platform = "4.0.0-M2" activemq = '5.18.1' amazon-sqs-messaging = '2.0.3' @@ -20,7 +21,6 @@ kotlin = '1.8.21' spock = "2.3-groovy-4.0" spotbugs = "4.7.3" testcontainers = '1.18.1' -lombok = '1.18.28' micronaut-aws-v2 = '4.0.0-M6' micronaut-validation = "4.0.0-M9" @@ -48,8 +48,6 @@ testcontainers-localstack = { module = 'org.testcontainers:localstack', version. testcontainers-spock = { module = 'org.testcontainers:spock', version.ref = 'testcontainers' } javax-json-api = { module = 'javax.json:javax.json-api', version.ref = 'json-api' } -lombok = { module = 'org.projectlombok:lombok', version.ref = 'lombok' } - #plugins micronaut-gradle-plugin = { module = 'io.micronaut.gradle:micronaut-gradle-plugin', version.ref = 'micronaut-gradle-plugin' } diff --git a/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/proxy-config.json b/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/proxy-config.json deleted file mode 100644 index 5a40a43f..00000000 --- a/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/proxy-config.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - [ - "org.apache.http.conn.HttpClientConnectionManager", - "org.apache.http.pool.ConnPoolControl" - ], - [ - "org.apache.http.conn.ConnectionRequest", - "com.amazonaws.http.conn.Wrapped" - ] -] diff --git a/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/reflect-config.json b/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/reflect-config.json deleted file mode 100644 index abe11fc1..00000000 --- a/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/reflect-config.json +++ /dev/null @@ -1,350 +0,0 @@ -[ - { - "name": "com.sun.org.apache.xml.internal.utils.FastStringBuffer", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "com.amazonaws.jmx.SdkMBeanRegistrySupport", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "org.apache.commons.logging.LogFactory", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "org.apache.commons.logging.impl.LogFactoryImpl", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "org.apache.commons.logging.impl.SLF4JLogFactory", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "io.symphonia.lambda.logging.DefaultLogbackConfigurator", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.DateConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.LevelConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.LineSeparatorConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.LoggerConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.MDCConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.MessageConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.NopThrowableInformationConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.core.pattern.ReplacingCompositeConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.ThrowableProxyConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.classic.pattern.ThreadConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.core.rolling.helper.DateTokenConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "ch.qos.logback.core.rolling.helper.IntegerTokenConverter", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "java.util.HashSet", - "allPublicMethods": true, - "allDeclaredConstructors": true - }, - { - "name": "sun.security.ssl.SSLContextImpl$TLSContext", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "com.sun.crypto.provider.HmacCore$HmacSHA256", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "org.apache.http.client.config.RequestConfig", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "org.apache.http.client.config.RequestConfig$Builder", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.provider.X509Factory", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.AuthorityInfoAccessExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.AuthorityKeyIdentifierExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.BasicConstraintsExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.CertificateIssuerExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.CertificatePoliciesExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.CRLDistributionPointsExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.CRLNumberExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.CRLReasonCodeExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.DeltaCRLIndicatorExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.ExtendedKeyUsageExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.FreshestCRLExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.InhibitAnyPolicyExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.InvalidityDateExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.IssuerAlternativeNameExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.IssuingDistributionPointExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.KeyUsageExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.NameConstraintsExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.NetscapeCertTypeExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.OCSPNoCheckExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.PolicyConstraintsExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.PolicyMappingsExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.PrivateKeyUsageExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.SubjectAlternativeNameExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.SubjectInfoAccessExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.SubjectKeyIdentifierExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - }, - { - "name": "sun.security.x509.UnparseableExtension", - "allDeclaredFields": true, - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredClasses": true - } -] diff --git a/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/resource-config.json b/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/resource-config.json deleted file mode 100644 index c202c484..00000000 --- a/jms-sqs/src/main/resources/META-INF/native-image/io.micronaut.jms.sql/micronaut-jms-sqs/resource-config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "resources": { - "includes": [ - { - "pattern": "org/joda/time/tz/data/.*" - } - ] - } -} diff --git a/tests/tasks-activemq-artemis/build.gradle.kts b/tests/tasks-activemq-artemis/build.gradle.kts index b3338517..a8bbe7da 100644 --- a/tests/tasks-activemq-artemis/build.gradle.kts +++ b/tests/tasks-activemq-artemis/build.gradle.kts @@ -7,6 +7,3 @@ dependencies { implementation(projects.micronautJmsActivemqArtemis) testImplementation(libs.awaitility) } -micronaut { - testRuntime("junit5") -} diff --git a/tests/tasks-activemq-classic/build.gradle.kts b/tests/tasks-activemq-classic/build.gradle.kts index 12d241ef..e559569d 100644 --- a/tests/tasks-activemq-classic/build.gradle.kts +++ b/tests/tasks-activemq-classic/build.gradle.kts @@ -7,6 +7,3 @@ dependencies { implementation(projects.micronautJmsActivemqClassic) testImplementation(libs.awaitility) } -micronaut { - testRuntime("junit5") -} diff --git a/tests/tasks-sqs/build.gradle.kts b/tests/tasks-sqs/build.gradle.kts index 4d6f49b6..c4426c4b 100644 --- a/tests/tasks-sqs/build.gradle.kts +++ b/tests/tasks-sqs/build.gradle.kts @@ -1,14 +1,15 @@ plugins { id("io.micronaut.build.internal.jms-tests") + id("io.micronaut.build.internal.jms-native-tests") } dependencies { implementation(projects.micronautJmsSqs) - testImplementation(libs.testcontainers.localstack) - testCompileOnly(libs.lombok) - testAnnotationProcessor(libs.lombok) + testImplementation(libs.awaitility) } micronaut { - testRuntime("spock") + testResources { + additionalModules.add("localstack-sqs") + } } diff --git a/tests/tasks-sqs/src/main/java/example/SqsClientBuilderCreatedEventListener.java b/tests/tasks-sqs/src/main/java/example/SqsClientBuilderCreatedEventListener.java deleted file mode 100644 index d3e2c4f2..00000000 --- a/tests/tasks-sqs/src/main/java/example/SqsClientBuilderCreatedEventListener.java +++ /dev/null @@ -1,51 +0,0 @@ -package example; - -import io.micronaut.context.annotation.Value; -import io.micronaut.context.event.BeanCreatedEvent; -import io.micronaut.context.event.BeanCreatedEventListener; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.sqs.SqsClientBuilder; - -import java.net.URI; -import java.net.URISyntaxException; - -@Singleton -public class SqsClientBuilderCreatedEventListener implements BeanCreatedEventListener { - - @Inject - @Value("${sqs-region:`http://localhost:4566`}") - public String sqsRegion = "http://localhost:4566"; - - @Inject - @Value("${sqs-url:eu-central-1}") - public String sqsUrl = "eu-central-1"; - - @Inject - @Value("${aws.accessKeyId:foo}") - public String accessKeyId = "foo"; - - @Inject - @Value("${aws.secretAccessKey:bar}") - public String secretAccessKey = "bar"; - - @Override - public SqsClientBuilder onCreated(BeanCreatedEvent event) { - SqsClientBuilder builder = event.getBean(); - builder.region(Region.of(sqsRegion)); - builder.credentialsProvider( - StaticCredentialsProvider.create( - AwsBasicCredentials.create(accessKeyId, secretAccessKey) - ) - ); - try { - builder.endpointOverride(new URI(sqsUrl)); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - return builder; - } -} diff --git a/tests/tasks-sqs/src/main/java/example/SqsClientFactory.java b/tests/tasks-sqs/src/main/java/example/SqsClientFactory.java new file mode 100644 index 00000000..c1ca5175 --- /dev/null +++ b/tests/tasks-sqs/src/main/java/example/SqsClientFactory.java @@ -0,0 +1,36 @@ +package example; + +import io.micronaut.context.annotation.Factory; +import io.micronaut.context.annotation.Replaces; +import jakarta.inject.Singleton; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sqs.SqsClient; +import software.amazon.awssdk.services.sqs.SqsClientBuilder; + +import java.net.URI; +import java.net.URISyntaxException; + +@Factory +@Replaces(factory = io.micronaut.aws.sdk.v2.service.sqs.SqsClientFactory.class) +public class SqsClientFactory { + + @Singleton + SqsClientBuilder createSqsClientBuilder(SqsConfig sqsConfig) throws URISyntaxException { + return SqsClient.builder() + .endpointOverride(new URI(sqsConfig.getSqs().getEndpointOverride())) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create(sqsConfig.getAccessKeyId(), sqsConfig.getSecretKey()) + ) + ) + .region(Region.of(sqsConfig.getRegion())); + } + + @Singleton + SqsClient createSqsClient(SqsClientBuilder sqsClientBuilder) { + return sqsClientBuilder.build(); + } + +} diff --git a/tests/tasks-sqs/src/main/java/example/SqsConfig.java b/tests/tasks-sqs/src/main/java/example/SqsConfig.java new file mode 100644 index 00000000..f5c7cde6 --- /dev/null +++ b/tests/tasks-sqs/src/main/java/example/SqsConfig.java @@ -0,0 +1,56 @@ +package example; + +import io.micronaut.context.annotation.ConfigurationBuilder; +import io.micronaut.context.annotation.ConfigurationProperties; + +@ConfigurationProperties("aws") +public class SqsConfig { + + private String accessKeyId; + private String secretKey; + private String region; + + @ConfigurationBuilder(configurationPrefix = "services.sqs") + final Sqs sqs = new Sqs(); + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public Sqs getSqs() { + return sqs; + } + + public static class Sqs { + + private String endpointOverride; + + public String getEndpointOverride() { + return endpointOverride; + } + + public void setEndpointOverride(String endpointOverride) { + this.endpointOverride = endpointOverride; + } + } +} diff --git a/tests/tasks-sqs/src/main/resources/application.yml b/tests/tasks-sqs/src/main/resources/application.yml index 63cf1c3e..1da6634d 100644 --- a/tests/tasks-sqs/src/main/resources/application.yml +++ b/tests/tasks-sqs/src/main/resources/application.yml @@ -2,8 +2,3 @@ micronaut: jms: sqs: enabled: true - -aws: - region: eu-central-1 - accessKeyId: foo - secretAccessKey: bar diff --git a/tests/tasks-sqs/src/test/groovy/example/LocalStackContainer.java b/tests/tasks-sqs/src/test/groovy/example/LocalStackContainer.java deleted file mode 100644 index 1f485117..00000000 --- a/tests/tasks-sqs/src/test/groovy/example/LocalStackContainer.java +++ /dev/null @@ -1,325 +0,0 @@ -package example; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.FieldDefaults; -import lombok.extern.slf4j.Slf4j; -import org.rnorth.ducttape.Preconditions; -import org.testcontainers.DockerClientFactory; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.utility.ComparableVersion; -import org.testcontainers.utility.DockerImageName; - -import java.net.InetAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -public class LocalStackContainer extends GenericContainer { - - static final int PORT = 4566; - - private static final String HOSTNAME_EXTERNAL_ENV_VAR = "HOSTNAME_EXTERNAL"; - - private final List services = new ArrayList<>(); - - private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("localstack/localstack"); - - private static final String DEFAULT_REGION = "us-east-1"; - - /** - * Whether or to assume that all APIs run on different ports (when true) or are - * exposed on a single port (false). From the Localstack README: - * - *
Note: Starting with version 0.11.0, all APIs are exposed via a single edge - * service [...] The API-specific endpoints below are still left for backward-compatibility but - * may get removed in a future release - please reconfigure your client SDKs to start using the - * single edge endpoint URL!
- *

- * Testcontainers will use the tag of the docker image to infer whether the used version - * of Localstack supports this feature. - */ - private final boolean legacyMode; - - /** - * Starting with version 0.13.0, setting services list on Localstack is not required. When false, - * containers are started lazily. When true, container fails to start if services list is not provided. - * - * Testcontainers will use the tag of the docker image to infer whether the used version - * of Localstack required services list. - */ - private final boolean servicesEnvVarRequired; - - /** - * @param dockerImageName image name to use for Localstack - */ - public LocalStackContainer(final DockerImageName dockerImageName) { - this(dockerImageName, shouldRunInLegacyMode(dockerImageName.getVersionPart())); - } - - /** - * @param dockerImageName image name to use for Localstack - * @param useLegacyMode if true, each AWS service is exposed on a different port - */ - public LocalStackContainer(final DockerImageName dockerImageName, boolean useLegacyMode) { - super(dockerImageName); - dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); - - this.legacyMode = useLegacyMode; - this.servicesEnvVarRequired = isServicesEnvVarRequired(dockerImageName.getVersionPart()); - - withFileSystemBind(DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), "/var/run/docker.sock"); - waitingFor(Wait.forLogMessage(".*Ready\\.\n", 1)); - } - - private static boolean isServicesEnvVarRequired(String version) { - if (version.equals("latest")) { - return false; - } - - ComparableVersion comparableVersion = new ComparableVersion(version); - if (comparableVersion.isSemanticVersion()) { - return comparableVersion.isLessThan("0.13"); - } - - log.warn("Version {} is not a semantic version, services list is required.", version); - - return true; - } - - private static boolean shouldRunInLegacyMode(String version) { - if (version.equals("latest")) { - return false; - } - - ComparableVersion comparableVersion = new ComparableVersion(version); - if (comparableVersion.isSemanticVersion()) { - return comparableVersion.isLessThan("0.11"); - } - - log.warn("Version {} is not a semantic version, LocalStack will run in legacy mode.", version); - log.warn( - "Consider using \"LocalStackContainer(DockerImageName dockerImageName, boolean legacyMode)\" constructor if you want to disable legacy mode." - ); - return true; - } - - @Override - protected void configure() { - super.configure(); - - if (servicesEnvVarRequired) { - Preconditions.check("services list must not be empty", !services.isEmpty()); - } - - if (!services.isEmpty()) { - withEnv("SERVICES", services.stream().map(EnabledService::getName).collect(Collectors.joining(","))); - if (this.servicesEnvVarRequired) { - withEnv("EAGER_SERVICE_LOADING", "1"); - } - } - - String hostnameExternalReason; - if (getEnvMap().containsKey(HOSTNAME_EXTERNAL_ENV_VAR)) { - // do nothing - hostnameExternalReason = "explicitly as environment variable"; - } else if (getNetwork() != null && getNetworkAliases().size() >= 1) { - withEnv(HOSTNAME_EXTERNAL_ENV_VAR, getNetworkAliases().get(getNetworkAliases().size() - 1)); // use the last network alias set - hostnameExternalReason = "to match last network alias on container with non-default network"; - } else { - withEnv(HOSTNAME_EXTERNAL_ENV_VAR, getHost()); - hostnameExternalReason = "to match host-routable address for container"; - } - logger() - .info( - "{} environment variable set to {} ({})", - HOSTNAME_EXTERNAL_ENV_VAR, - getEnvMap().get(HOSTNAME_EXTERNAL_ENV_VAR), - hostnameExternalReason - ); - - exposePorts(); - } - - private void exposePorts() { - if (legacyMode) { - services.stream().map(this::getServicePort).distinct().forEach(this::addExposedPort); - } else { - this.addExposedPort(PORT); - } - } - - public LocalStackContainer withServices(Service... services) { - this.services.addAll(Arrays.asList(services)); - return self(); - } - - /** - * Declare a set of simulated AWS services that should be launched by this container. - * @param services one or more service names - * @return this container object - */ - public LocalStackContainer withServices(EnabledService... services) { - this.services.addAll(Arrays.asList(services)); - return self(); - } - - public URI getEndpointOverride(Service service) { - return getEndpointOverride((EnabledService) service); - } - - /** - * Provides an endpoint override that is preconfigured to communicate with a given simulated service. - * The provided endpoint override should be set in the AWS Java SDK v2 when building a client, e.g.: - *

S3Client s3 = S3Client
-     .builder()
-     .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
-     .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
-     localstack.getAccessKey(), localstack.getSecretKey()
-     )))
-     .region(Region.of(localstack.getRegion()))
-     .build()
-     
- *

Please note that this method is only intended to be used for configuring AWS SDK clients - * that are running on the test host. If other containers need to call this one, they should be configured - * specifically to do so using a Docker network and appropriate addressing.

- * - * @param service the service that is to be accessed - * @return an {@link URI} endpoint override - */ - public URI getEndpointOverride(EnabledService service) { - try { - final String address = getHost(); - String ipAddress = address; - // resolve IP address and use that as the endpoint so that path-style access is automatically used for S3 - ipAddress = InetAddress.getByName(address).getHostAddress(); - return new URI("http://" + ipAddress + ":" + getMappedPort(getServicePort(service))); - } catch (UnknownHostException | URISyntaxException e) { - throw new IllegalStateException("Cannot obtain endpoint URL", e); - } - } - - private int getServicePort(EnabledService service) { - return legacyMode ? service.getPort() : PORT; - } - - /** - * Provides a default access key that is preconfigured to communicate with a given simulated service. - * The access key can be used to construct AWS SDK v2 clients: - *
S3Client s3 = S3Client
-     .builder()
-     .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
-     .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
-     localstack.getAccessKey(), localstack.getSecretKey()
-     )))
-     .region(Region.of(localstack.getRegion()))
-     .build()
-     
- * @return a default access key - */ - public String getAccessKey() { - return "accesskey"; - } - - /** - * Provides a default secret key that is preconfigured to communicate with a given simulated service. - * The secret key can be used to construct AWS SDK v2 clients: - *
S3Client s3 = S3Client
-     .builder()
-     .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
-     .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
-     localstack.getAccessKey(), localstack.getSecretKey()
-     )))
-     .region(Region.of(localstack.getRegion()))
-     .build()
-     
- * @return a default secret key - */ - public String getSecretKey() { - return "secretkey"; - } - - /** - * Provides a default region that is preconfigured to communicate with a given simulated service. - * The region can be used to construct AWS SDK v2 clients: - *
S3Client s3 = S3Client
-     .builder()
-     .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
-     .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
-     localstack.getAccessKey(), localstack.getSecretKey()
-     )))
-     .region(Region.of(localstack.getRegion()))
-     .build()
-     
- * @return a default region - */ - public String getRegion() { - return this.getEnvMap().getOrDefault("DEFAULT_REGION", DEFAULT_REGION); - } - - public interface EnabledService { - static EnabledService named(String name) { - return () -> name; - } - - String getName(); - - default int getPort() { - return PORT; - } - } - - @RequiredArgsConstructor - @Getter - @FieldDefaults(makeFinal = true) - public enum Service implements EnabledService { - API_GATEWAY("apigateway", 4567), - EC2("ec2", 4597), - KINESIS("kinesis", 4568), - DYNAMODB("dynamodb", 4569), - DYNAMODB_STREAMS("dynamodbstreams", 4570), - // TODO: Clarify usage for ELASTICSEARCH and ELASTICSEARCH_SERVICE - // ELASTICSEARCH("es", 4571), - S3("s3", 4572), - FIREHOSE("firehose", 4573), - LAMBDA("lambda", 4574), - SNS("sns", 4575), - SQS("sqs", 4576), - REDSHIFT("redshift", 4577), - // ELASTICSEARCH_SERVICE("", 4578), - SES("ses", 4579), - ROUTE53("route53", 4580), - CLOUDFORMATION("cloudformation", 4581), - CLOUDWATCH("cloudwatch", 4582), - SSM("ssm", 4583), - SECRETSMANAGER("secretsmanager", 4584), - STEPFUNCTIONS("stepfunctions", 4585), - CLOUDWATCHLOGS("logs", 4586), - STS("sts", 4592), - IAM("iam", 4593), - KMS("kms", 4599); - - String localStackName; - - int port; - - @Override - public String getName() { - return localStackName; - } - - @Deprecated - /* - Since version 0.11, LocalStack exposes all services on a single (4566) port. - */ - public int getPort() { - return port; - } - } -} diff --git a/tests/tasks-sqs/src/test/groovy/example/TasksSpec.groovy b/tests/tasks-sqs/src/test/groovy/example/TasksSpec.groovy deleted file mode 100644 index f6b4d513..00000000 --- a/tests/tasks-sqs/src/test/groovy/example/TasksSpec.groovy +++ /dev/null @@ -1,40 +0,0 @@ -package example - -import io.micronaut.context.ApplicationContext -import io.micronaut.http.client.BlockingHttpClient -import io.micronaut.http.client.HttpClient -import io.micronaut.runtime.server.EmbeddedServer -import org.testcontainers.utility.DockerImageName -import spock.lang.Specification -import spock.util.concurrent.PollingConditions - -import static example.LocalStackContainer.Service.SQS - -class TasksSpec extends Specification { - - void 'should process tasks'() { - when: - LocalStackContainer localstack = new LocalStackContainer(DockerImageName.parse('localstack/localstack')).withServices(SQS) - localstack.start() - EmbeddedServer server = ApplicationContext.run( - EmbeddedServer, - [ - 'sqs-url' : localstack.getEndpointOverride(SQS).toString(), - 'sqs-region': localstack.region, - ] - ) - HttpClient httpClient = server.applicationContext.createBean(HttpClient, server.URL) - BlockingHttpClient client = httpClient.toBlocking() - - then: - new PollingConditions(initialDelay: 2, timeout: 100).eventually { - client.retrieve('/tasks/processed-count', Integer) > 3 - } - - cleanup: - localstack.close() - client.close() - httpClient.close() - server.close() - } -} diff --git a/tests/tasks-sqs/src/test/java/example/TasksTest.java b/tests/tasks-sqs/src/test/java/example/TasksTest.java new file mode 100644 index 00000000..69b83792 --- /dev/null +++ b/tests/tasks-sqs/src/test/java/example/TasksTest.java @@ -0,0 +1,24 @@ +package example; + +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; + +@MicronautTest +class TasksTest { + + @Test + void testShouldProcessTasks(@Client("/") HttpClient client) { + await().atMost(30, SECONDS).until(() -> + { + Integer result = client.toBlocking().retrieve("/tasks/processed-count", Integer.class); + return result != null && result > 3; + } + ); + } + +}