Skip to content

Commit

Permalink
Allow using LocalStackContainer with AWS SDK v2 #1442 (#2579)
Browse files Browse the repository at this point in the history
Closes #1442
  • Loading branch information
musketyr committed Apr 23, 2020
1 parent 2c20417 commit 12de2b4
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 22 deletions.
14 changes: 14 additions & 0 deletions docs/modules/localstack.md
Expand Up @@ -13,6 +13,7 @@ public LocalStackContainer localstack = new LocalStackContainer()

@Test
public void someTestMethod() {
// AWS SDK v1
AmazonS3 s3 = AmazonS3ClientBuilder
.standard()
.withEndpointConfiguration(localstack.getEndpointConfiguration(S3))
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions modules/localstack/build.gradle
Expand Up @@ -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'
}
Expand Up @@ -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;
Expand Down Expand Up @@ -90,7 +92,16 @@ public LocalStackContainer withServices(Service... services) {
.withCredentials(localstack.getDefaultCredentialsProvider())
.build();
</code></pre>
*
* or for AWS SDK v2
* <pre><code>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()
</code></pre>
* <p><strong>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.</strong></p>
Expand All @@ -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.:
* <pre><code>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()
</code></pre>
* <p><strong>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.</strong></p>
*
* @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);
}
}

/**
Expand All @@ -124,10 +156,74 @@ public AwsClientBuilder.EndpointConfiguration getEndpointConfiguration(Service s
.withCredentials(localstack.getDefaultCredentialsProvider())
.build();
</code></pre>
* or for AWS SDK v2 you can use {@link #getAccessKey()}, {@link #getSecretKey()} directly:
* <pre><code>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()
</code></pre>
* @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:
* <pre><code>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()
</code></pre>
* @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:
* <pre><code>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()
</code></pre>
* @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:
* <pre><code>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()
</code></pre>
* @return a default region
*/
public String getRegion() {
return "us-east-1";
}

@RequiredArgsConstructor
Expand Down
Expand Up @@ -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;
Expand All @@ -19,18 +17,22 @@
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;
import org.testcontainers.DockerClientFactory;
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;
Expand Down Expand Up @@ -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<Bucket> buckets = s3.listBuckets();
assertEquals("The created bucket is present", 1, buckets.size());
final Bucket bucket = buckets.get(0);
final Optional<Bucket> 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()
Expand Down

0 comments on commit 12de2b4

Please sign in to comment.