From 5a7ef158996edd7c9c0e6c627d56715cab7c12d1 Mon Sep 17 00:00:00 2001 From: Vladimir Orany Date: Thu, 23 Apr 2020 14:43:45 +0200 Subject: [PATCH] Allow using LocalStackContainer with AWS SDK v2 #1442 (#2579) Closes #1442 --- docs/modules/localstack.md | 14 +++ modules/localstack/build.gradle | 1 + .../localstack/LocalStackContainer.java | 118 ++++++++++++++++-- .../localstack/LocalstackContainerTest.java | 41 ++++-- 4 files changed, 152 insertions(+), 22 deletions(-) diff --git a/docs/modules/localstack.md b/docs/modules/localstack.md index bf520b1fac6..4ff46233b05 100644 --- a/docs/modules/localstack.md +++ b/docs/modules/localstack.md @@ -13,6 +13,7 @@ public LocalStackContainer localstack = new LocalStackContainer() @Test public void someTestMethod() { + // AWS SDK v1 AmazonS3 s3 = AmazonS3ClientBuilder .standard() .withEndpointConfiguration(localstack.getEndpointConfiguration(S3)) @@ -21,6 +22,19 @@ public void someTestMethod() { s3.createBucket("foo"); s3.putObject("foo", "bar", "baz"); + + // AWS SDK v2 + 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(); + + s3.createBucket(b -> b.bucket("foo")); + s3.putObject(b -> b.bucket("foo").key("bar"), RequestBody.fromBytes("baz".getBytes())); ``` Environment variables listed in [Localstack's README](https://github.com/localstack/localstack#configurations) may be used to customize Localstack's configuration. diff --git a/modules/localstack/build.gradle b/modules/localstack/build.gradle index c8bf561b6d1..cd6f3d9f86c 100644 --- a/modules/localstack/build.gradle +++ b/modules/localstack/build.gradle @@ -7,4 +7,5 @@ dependencies { testCompile 'com.amazonaws:aws-java-sdk-s3:1.11.683' testCompile 'com.amazonaws:aws-java-sdk-sqs:1.11.636' testCompile 'com.amazonaws:aws-java-sdk-logs:1.11.762' + testCompile 'software.amazon.awssdk:s3:2.11.10' } diff --git a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java index 0d8c3627f6c..c649d322ead 100644 --- a/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java +++ b/modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java @@ -13,6 +13,8 @@ import org.testcontainers.utility.TestcontainersConfiguration; import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; @@ -90,7 +92,16 @@ public LocalStackContainer withServices(Service... services) { .withCredentials(localstack.getDefaultCredentialsProvider()) .build(); - * + * or for AWS SDK v2 + *
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.

@@ -99,20 +110,41 @@ public LocalStackContainer withServices(Service... services) { * @return an {@link AwsClientBuilder.EndpointConfiguration} */ public AwsClientBuilder.EndpointConfiguration getEndpointConfiguration(Service service) { - final String address = getContainerIpAddress(); - String ipAddress = address; + return new AwsClientBuilder.EndpointConfiguration(getEndpointOverride(service).toString(), getRegion()); + } + + /** + * 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(Service service) { try { + final String address = getContainerIpAddress(); + 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(); - } catch (UnknownHostException ignored) { - - } - - return new AwsClientBuilder.EndpointConfiguration( - "http://" + + return new URI("http://" + ipAddress + ":" + - getMappedPort(service.getPort()), "us-east-1"); + getMappedPort(service.getPort())); + } catch (UnknownHostException | URISyntaxException e) { + throw new IllegalStateException("Cannot obtain endpoint URL", e); + } } /** @@ -124,10 +156,74 @@ public AwsClientBuilder.EndpointConfiguration getEndpointConfiguration(Service s .withCredentials(localstack.getDefaultCredentialsProvider()) .build(); + * or for AWS SDK v2 you can use {@link #getAccessKey()}, {@link #getSecretKey()} directly: + *
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 an {@link AWSCredentialsProvider} */ public AWSCredentialsProvider getDefaultCredentialsProvider() { - return new AWSStaticCredentialsProvider(new BasicAWSCredentials("accesskey", "secretkey")); + return new AWSStaticCredentialsProvider(new BasicAWSCredentials(getAccessKey(), getSecretKey())); + } + + /** + * 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 "us-east-1"; } @RequiredArgsConstructor diff --git a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java index 034ff2f0914..ee5945d0aff 100644 --- a/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java +++ b/modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java @@ -4,8 +4,6 @@ import com.amazonaws.services.logs.AWSLogs; import com.amazonaws.services.logs.AWSLogsClientBuilder; import com.amazonaws.services.logs.model.CreateLogGroupRequest; -import com.amazonaws.services.logs.model.CreateLogGroupResult; -import com.amazonaws.services.logs.model.DescribeLogGroupsRequest; import com.amazonaws.services.logs.model.LogGroup; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; @@ -19,7 +17,6 @@ import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.ClassRule; -import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; @@ -27,10 +24,15 @@ import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; +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.s3.S3Client; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; +import java.util.Optional; import static org.hamcrest.CoreMatchers.containsString; import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals; @@ -67,24 +69,41 @@ public void s3TestOverBridgeNetwork() throws IOException { .withCredentials(localstack.getDefaultCredentialsProvider()) .build(); - s3.createBucket("foo"); - s3.putObject("foo", "bar", "baz"); + final String bucketName = "foo"; + s3.createBucket(bucketName); + s3.putObject(bucketName, "bar", "baz"); final List buckets = s3.listBuckets(); - assertEquals("The created bucket is present", 1, buckets.size()); - final Bucket bucket = buckets.get(0); + final Optional maybeBucket = buckets.stream().filter(b -> b.getName().equals(bucketName)).findFirst(); + assertTrue("The created bucket is present", maybeBucket.isPresent()); + final Bucket bucket = maybeBucket.get(); - assertEquals("The created bucket has the right name", "foo", bucket.getName()); - assertEquals("The created bucket has the right name", "foo", bucket.getName()); + assertEquals("The created bucket has the right name", bucketName, bucket.getName()); - final ObjectListing objectListing = s3.listObjects("foo"); + final ObjectListing objectListing = s3.listObjects(bucketName); assertEquals("The created bucket has 1 item in it", 1, objectListing.getObjectSummaries().size()); - final S3Object object = s3.getObject("foo", "bar"); + final S3Object object = s3.getObject(bucketName, "bar"); final String content = IOUtils.toString(object.getObjectContent(), Charset.forName("UTF-8")); assertEquals("The object can be retrieved", "baz", content); } + @Test + public void s3TestUsingAwsSdkV2() { + 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(); + + final String bucketName = "foov2"; + s3.createBucket(b -> b.bucket(bucketName)); + assertTrue("New bucket was created", s3.listBuckets().buckets().stream().anyMatch(b -> b.name().equals(bucketName))); + } + @Test public void sqsTestOverBridgeNetwork() { AmazonSQS sqs = AmazonSQSClientBuilder.standard()