diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 98b280476..d32b0922e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -113,9 +113,11 @@ spock = { module = "org.spockframework:spock-core", version.ref = "spock" } amazon-awssdk-v1-dynamodb = { module = "com.amazonaws:aws-java-sdk-dynamodb", version.ref = "amazon-awssdk-v1" } amazon-awssdk-v1-s3 = { module = "com.amazonaws:aws-java-sdk-s3", version.ref = "amazon-awssdk-v1" } amazon-awssdk-v1-sqs = { module = "com.amazonaws:aws-java-sdk-sqs", version.ref = "amazon-awssdk-v1" } +amazon-awssdk-v1-sns = { module = "com.amazonaws:aws-java-sdk-sns", version.ref = "amazon-awssdk-v1" } amazon-awssdk-v2-dynamodb = { module = "software.amazon.awssdk:dynamodb", version.ref = "amazon-awssdk-v2" } amazon-awssdk-v2-s3 = { module = "software.amazon.awssdk:s3", version.ref = "amazon-awssdk-v2" } amazon-awssdk-v2-sqs = { module = "software.amazon.awssdk:sqs", version.ref = "amazon-awssdk-v2" } +amazon-awssdk-v2-sns = { module = "software.amazon.awssdk:sns", version.ref = "amazon-awssdk-v2" } jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" } # diff --git a/settings.gradle b/settings.gradle index 596d9b619..0e50f222c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -57,7 +57,8 @@ def localstackModules = [ 'core', 's3', 'dynamodb', - 'sqs' + 'sqs', + 'sns' ] include 'test-resources-bom' diff --git a/src/main/docs/guide/modules-localstack.adoc b/src/main/docs/guide/modules-localstack.adoc index b1e377422..02f7acec9 100644 --- a/src/main/docs/guide/modules-localstack.adoc +++ b/src/main/docs/guide/modules-localstack.adoc @@ -7,6 +7,7 @@ The following services are supported: - S3, by providing the `aws.services.s3.endpoint-override` property - DynamoDB, by providing the `aws.services.dynamodb.endpoint-override` property - SQS, by providing the `aws.services.sqs.endpoint-override` property +- SNS, by providing the `aws.services.sns.endpoint-override` property In addition, the following properties will be resolved by test resources: diff --git a/test-resources-localstack/test-resources-localstack-sns/build.gradle b/test-resources-localstack/test-resources-localstack-sns/build.gradle new file mode 100644 index 000000000..aa93ece0e --- /dev/null +++ b/test-resources-localstack/test-resources-localstack-sns/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'io.micronaut.build.internal.localstack-module' +} + +micronautBuild { + binaryCompatibility { + enabledAfter '2.6.0' + } +} + +description = """ +Provides support for Localstack SNS. +""" + +dependencies { + implementation(project(":micronaut-test-resources-localstack-core")) + runtimeOnly(libs.amazon.awssdk.v1.sns) { + because "Localstack requires the AWS SDK in version 1 at runtime" + } + testImplementation(testFixtures(project(":micronaut-test-resources-localstack-core"))) + testImplementation(libs.amazon.awssdk.v2.sns) +} diff --git a/test-resources-localstack/test-resources-localstack-sns/src/main/java/io/micronaut/testresources/localstack/sns/LocalStackSNSService.java b/test-resources-localstack/test-resources-localstack-sns/src/main/java/io/micronaut/testresources/localstack/sns/LocalStackSNSService.java new file mode 100644 index 000000000..05e8cf8dc --- /dev/null +++ b/test-resources-localstack/test-resources-localstack-sns/src/main/java/io/micronaut/testresources/localstack/sns/LocalStackSNSService.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017-2024 original 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.micronaut.testresources.localstack.sns; + +import io.micronaut.testresources.localstack.LocalStackService; +import org.testcontainers.containers.localstack.LocalStackContainer; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Adds support for Localstack SNS. + */ +public class LocalStackSNSService implements LocalStackService { + + private static final String AWS_SNS_ENDPOINT_OVERRIDE = "aws.services.sns.endpoint-override"; + + @Override + public LocalStackContainer.Service getServiceKind() { + return LocalStackContainer.Service.SNS; + } + + @Override + public List getResolvableProperties() { + return Collections.singletonList(AWS_SNS_ENDPOINT_OVERRIDE); + } + + @Override + public Optional resolveProperty(String propertyName, LocalStackContainer container) { + if (AWS_SNS_ENDPOINT_OVERRIDE.equals(propertyName)) { + return Optional.of(container.getEndpointOverride(LocalStackContainer.Service.SNS).toString()); + } + return Optional.empty(); + } +} diff --git a/test-resources-localstack/test-resources-localstack-sns/src/main/resources/META-INF/services/io.micronaut.testresources.localstack.LocalStackService b/test-resources-localstack/test-resources-localstack-sns/src/main/resources/META-INF/services/io.micronaut.testresources.localstack.LocalStackService new file mode 100644 index 000000000..475fd09dd --- /dev/null +++ b/test-resources-localstack/test-resources-localstack-sns/src/main/resources/META-INF/services/io.micronaut.testresources.localstack.LocalStackService @@ -0,0 +1 @@ +io.micronaut.testresources.localstack.sns.LocalStackSNSService diff --git a/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/LocalStackSNSTest.groovy b/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/LocalStackSNSTest.groovy new file mode 100644 index 000000000..5e481661c --- /dev/null +++ b/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/LocalStackSNSTest.groovy @@ -0,0 +1,27 @@ +package io.micronaut.testresources.localstack.sns + +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import io.micronaut.testresources.localstack.AbstractLocalStackSpec +import jakarta.inject.Inject +import org.junit.Assert +import software.amazon.awssdk.services.sns.SnsClient +import software.amazon.awssdk.services.sns.model.Topic + +@MicronautTest +class LocalStackSNSTest extends AbstractLocalStackSpec { + + @Inject + SnsClient client + + def "automatically starts a SNS container"() { + when: + client.createTopic { + it.name("test-topic") + } + + then: + List topics = client.listTopics().topics() + Assert.assertEquals(1, topics.size()) + Assert.assertTrue(topics[0].topicArn().endsWith("test-topic")) + } +} diff --git a/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/TestSnsClientFactory.groovy b/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/TestSnsClientFactory.groovy new file mode 100644 index 000000000..a344e9945 --- /dev/null +++ b/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/TestSnsClientFactory.groovy @@ -0,0 +1,25 @@ +package io.micronaut.testresources.localstack.sns + +import io.micronaut.context.annotation.Factory +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.sns.SnsClient + +@Factory +class TestSnsClientFactory { + + @Singleton + SnsClient snsClient(TestSnsConfig testSnsConfig) { + return SnsClient.builder() + .endpointOverride(new URI(testSnsConfig.sns.endpointOverride)) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create(testSnsConfig.accessKeyId, testSnsConfig.secretKey) + ) + ) + .region(Region.of(testSnsConfig.region)) + .build() + } +} diff --git a/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/TestSnsConfig.groovy b/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/TestSnsConfig.groovy new file mode 100644 index 000000000..2209e39f1 --- /dev/null +++ b/test-resources-localstack/test-resources-localstack-sns/src/test/groovy/io/micronaut/testresources/localstack/sns/TestSnsConfig.groovy @@ -0,0 +1,18 @@ +package io.micronaut.testresources.localstack.sns + +import io.micronaut.context.annotation.ConfigurationBuilder +import io.micronaut.context.annotation.ConfigurationProperties + +@ConfigurationProperties("aws") +class TestSnsConfig { + String accessKeyId + String secretKey + String region + + @ConfigurationBuilder(configurationPrefix = "services.sns") + final Sns sns = new Sns() + + static class Sns { + String endpointOverride + } +} diff --git a/test-resources-localstack/test-resources-localstack-sns/src/test/resources/logback.xml b/test-resources-localstack/test-resources-localstack-sns/src/test/resources/logback.xml new file mode 100644 index 000000000..44b79c40d --- /dev/null +++ b/test-resources-localstack/test-resources-localstack-sns/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + +