Skip to content

Commit 12de2b4

Browse files
authored
Allow using LocalStackContainer with AWS SDK v2 #1442 (#2579)
Closes #1442
1 parent 2c20417 commit 12de2b4

File tree

4 files changed

+152
-22
lines changed

4 files changed

+152
-22
lines changed

docs/modules/localstack.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public LocalStackContainer localstack = new LocalStackContainer()
1313

1414
@Test
1515
public void someTestMethod() {
16+
// AWS SDK v1
1617
AmazonS3 s3 = AmazonS3ClientBuilder
1718
.standard()
1819
.withEndpointConfiguration(localstack.getEndpointConfiguration(S3))
@@ -21,6 +22,19 @@ public void someTestMethod() {
2122

2223
s3.createBucket("foo");
2324
s3.putObject("foo", "bar", "baz");
25+
26+
// AWS SDK v2
27+
S3Client s3 = S3Client
28+
.builder()
29+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
30+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
31+
localstack.getAccessKey(), localstack.getSecretKey()
32+
)))
33+
.region(Region.of(localstack.getRegion()))
34+
.build();
35+
36+
s3.createBucket(b -> b.bucket("foo"));
37+
s3.putObject(b -> b.bucket("foo").key("bar"), RequestBody.fromBytes("baz".getBytes()));
2438
```
2539

2640
Environment variables listed in [Localstack's README](https://github.com/localstack/localstack#configurations) may be used to customize Localstack's configuration.

modules/localstack/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ dependencies {
77
testCompile 'com.amazonaws:aws-java-sdk-s3:1.11.683'
88
testCompile 'com.amazonaws:aws-java-sdk-sqs:1.11.636'
99
testCompile 'com.amazonaws:aws-java-sdk-logs:1.11.762'
10+
testCompile 'software.amazon.awssdk:s3:2.11.10'
1011
}

modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java

Lines changed: 107 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import org.testcontainers.utility.TestcontainersConfiguration;
1414

1515
import java.net.InetAddress;
16+
import java.net.URI;
17+
import java.net.URISyntaxException;
1618
import java.net.UnknownHostException;
1719
import java.util.ArrayList;
1820
import java.util.Arrays;
@@ -90,7 +92,16 @@ public LocalStackContainer withServices(Service... services) {
9092
.withCredentials(localstack.getDefaultCredentialsProvider())
9193
.build();
9294
</code></pre>
93-
*
95+
* or for AWS SDK v2
96+
* <pre><code>S3Client s3 = S3Client
97+
.builder()
98+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
99+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
100+
localstack.getAccessKey(), localstack.getSecretKey()
101+
)))
102+
.region(Region.of(localstack.getRegion()))
103+
.build()
104+
</code></pre>
94105
* <p><strong>Please note that this method is only intended to be used for configuring AWS SDK clients
95106
* that are running on the test host. If other containers need to call this one, they should be configured
96107
* specifically to do so using a Docker network and appropriate addressing.</strong></p>
@@ -99,20 +110,41 @@ public LocalStackContainer withServices(Service... services) {
99110
* @return an {@link AwsClientBuilder.EndpointConfiguration}
100111
*/
101112
public AwsClientBuilder.EndpointConfiguration getEndpointConfiguration(Service service) {
102-
final String address = getContainerIpAddress();
103-
String ipAddress = address;
113+
return new AwsClientBuilder.EndpointConfiguration(getEndpointOverride(service).toString(), getRegion());
114+
}
115+
116+
/**
117+
* Provides an endpoint override that is preconfigured to communicate with a given simulated service.
118+
* The provided endpoint override should be set in the AWS Java SDK v2 when building a client, e.g.:
119+
* <pre><code>S3Client s3 = S3Client
120+
.builder()
121+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
122+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
123+
localstack.getAccessKey(), localstack.getSecretKey()
124+
)))
125+
.region(Region.of(localstack.getRegion()))
126+
.build()
127+
</code></pre>
128+
* <p><strong>Please note that this method is only intended to be used for configuring AWS SDK clients
129+
* that are running on the test host. If other containers need to call this one, they should be configured
130+
* specifically to do so using a Docker network and appropriate addressing.</strong></p>
131+
*
132+
* @param service the service that is to be accessed
133+
* @return an {@link URI} endpoint override
134+
*/
135+
public URI getEndpointOverride(Service service) {
104136
try {
137+
final String address = getContainerIpAddress();
138+
String ipAddress = address;
105139
// resolve IP address and use that as the endpoint so that path-style access is automatically used for S3
106140
ipAddress = InetAddress.getByName(address).getHostAddress();
107-
} catch (UnknownHostException ignored) {
108-
109-
}
110-
111-
return new AwsClientBuilder.EndpointConfiguration(
112-
"http://" +
141+
return new URI("http://" +
113142
ipAddress +
114143
":" +
115-
getMappedPort(service.getPort()), "us-east-1");
144+
getMappedPort(service.getPort()));
145+
} catch (UnknownHostException | URISyntaxException e) {
146+
throw new IllegalStateException("Cannot obtain endpoint URL", e);
147+
}
116148
}
117149

118150
/**
@@ -124,10 +156,74 @@ public AwsClientBuilder.EndpointConfiguration getEndpointConfiguration(Service s
124156
.withCredentials(localstack.getDefaultCredentialsProvider())
125157
.build();
126158
</code></pre>
159+
* or for AWS SDK v2 you can use {@link #getAccessKey()}, {@link #getSecretKey()} directly:
160+
* <pre><code>S3Client s3 = S3Client
161+
.builder()
162+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
163+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
164+
localstack.getAccessKey(), localstack.getSecretKey()
165+
)))
166+
.region(Region.of(localstack.getRegion()))
167+
.build()
168+
</code></pre>
127169
* @return an {@link AWSCredentialsProvider}
128170
*/
129171
public AWSCredentialsProvider getDefaultCredentialsProvider() {
130-
return new AWSStaticCredentialsProvider(new BasicAWSCredentials("accesskey", "secretkey"));
172+
return new AWSStaticCredentialsProvider(new BasicAWSCredentials(getAccessKey(), getSecretKey()));
173+
}
174+
175+
/**
176+
* Provides a default access key that is preconfigured to communicate with a given simulated service.
177+
* The access key can be used to construct AWS SDK v2 clients:
178+
* <pre><code>S3Client s3 = S3Client
179+
.builder()
180+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
181+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
182+
localstack.getAccessKey(), localstack.getSecretKey()
183+
)))
184+
.region(Region.of(localstack.getRegion()))
185+
.build()
186+
</code></pre>
187+
* @return a default access key
188+
*/
189+
public String getAccessKey() {
190+
return "accesskey";
191+
}
192+
193+
/**
194+
* Provides a default secret key that is preconfigured to communicate with a given simulated service.
195+
* The secret key can be used to construct AWS SDK v2 clients:
196+
* <pre><code>S3Client s3 = S3Client
197+
.builder()
198+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
199+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
200+
localstack.getAccessKey(), localstack.getSecretKey()
201+
)))
202+
.region(Region.of(localstack.getRegion()))
203+
.build()
204+
</code></pre>
205+
* @return a default secret key
206+
*/
207+
public String getSecretKey() {
208+
return "secretkey";
209+
}
210+
211+
/**
212+
* Provides a default region that is preconfigured to communicate with a given simulated service.
213+
* The region can be used to construct AWS SDK v2 clients:
214+
* <pre><code>S3Client s3 = S3Client
215+
.builder()
216+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
217+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
218+
localstack.getAccessKey(), localstack.getSecretKey()
219+
)))
220+
.region(Region.of(localstack.getRegion()))
221+
.build()
222+
</code></pre>
223+
* @return a default region
224+
*/
225+
public String getRegion() {
226+
return "us-east-1";
131227
}
132228

133229
@RequiredArgsConstructor

modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackContainerTest.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import com.amazonaws.services.logs.AWSLogs;
55
import com.amazonaws.services.logs.AWSLogsClientBuilder;
66
import com.amazonaws.services.logs.model.CreateLogGroupRequest;
7-
import com.amazonaws.services.logs.model.CreateLogGroupResult;
8-
import com.amazonaws.services.logs.model.DescribeLogGroupsRequest;
97
import com.amazonaws.services.logs.model.LogGroup;
108
import com.amazonaws.services.s3.AmazonS3;
119
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
@@ -19,18 +17,22 @@
1917
import org.apache.commons.io.IOUtils;
2018
import org.junit.Assert;
2119
import org.junit.ClassRule;
22-
import org.junit.Ignore;
2320
import org.junit.Test;
2421
import org.junit.experimental.runners.Enclosed;
2522
import org.junit.runner.RunWith;
2623
import org.testcontainers.DockerClientFactory;
2724
import org.testcontainers.containers.Container;
2825
import org.testcontainers.containers.GenericContainer;
2926
import org.testcontainers.containers.Network;
27+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
28+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
29+
import software.amazon.awssdk.regions.Region;
30+
import software.amazon.awssdk.services.s3.S3Client;
3031

3132
import java.io.IOException;
3233
import java.nio.charset.Charset;
3334
import java.util.List;
35+
import java.util.Optional;
3436

3537
import static org.hamcrest.CoreMatchers.containsString;
3638
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
@@ -67,24 +69,41 @@ public void s3TestOverBridgeNetwork() throws IOException {
6769
.withCredentials(localstack.getDefaultCredentialsProvider())
6870
.build();
6971

70-
s3.createBucket("foo");
71-
s3.putObject("foo", "bar", "baz");
72+
final String bucketName = "foo";
73+
s3.createBucket(bucketName);
74+
s3.putObject(bucketName, "bar", "baz");
7275

7376
final List<Bucket> buckets = s3.listBuckets();
74-
assertEquals("The created bucket is present", 1, buckets.size());
75-
final Bucket bucket = buckets.get(0);
77+
final Optional<Bucket> maybeBucket = buckets.stream().filter(b -> b.getName().equals(bucketName)).findFirst();
78+
assertTrue("The created bucket is present", maybeBucket.isPresent());
79+
final Bucket bucket = maybeBucket.get();
7680

77-
assertEquals("The created bucket has the right name", "foo", bucket.getName());
78-
assertEquals("The created bucket has the right name", "foo", bucket.getName());
81+
assertEquals("The created bucket has the right name", bucketName, bucket.getName());
7982

80-
final ObjectListing objectListing = s3.listObjects("foo");
83+
final ObjectListing objectListing = s3.listObjects(bucketName);
8184
assertEquals("The created bucket has 1 item in it", 1, objectListing.getObjectSummaries().size());
8285

83-
final S3Object object = s3.getObject("foo", "bar");
86+
final S3Object object = s3.getObject(bucketName, "bar");
8487
final String content = IOUtils.toString(object.getObjectContent(), Charset.forName("UTF-8"));
8588
assertEquals("The object can be retrieved", "baz", content);
8689
}
8790

91+
@Test
92+
public void s3TestUsingAwsSdkV2() {
93+
S3Client s3 = S3Client
94+
.builder()
95+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
96+
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(
97+
localstack.getAccessKey(), localstack.getSecretKey()
98+
)))
99+
.region(Region.of(localstack.getRegion()))
100+
.build();
101+
102+
final String bucketName = "foov2";
103+
s3.createBucket(b -> b.bucket(bucketName));
104+
assertTrue("New bucket was created", s3.listBuckets().buckets().stream().anyMatch(b -> b.name().equals(bucketName)));
105+
}
106+
88107
@Test
89108
public void sqsTestOverBridgeNetwork() {
90109
AmazonSQS sqs = AmazonSQSClientBuilder.standard()

0 commit comments

Comments
 (0)