Skip to content

Commit 0ad6e8a

Browse files
committed
add Localstack testcontainers sample for RDS
1 parent b5f1db8 commit 0ad6e8a

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Eclipse
2+
.classpath
3+
.project
4+
.settings/
5+
6+
# Intellij
7+
.idea/
8+
*.iml
9+
*.iws
10+
11+
# Mac
12+
.DS_Store
13+
14+
# Maven
15+
log/
16+
target/
17+
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>org.example</groupId>
8+
<artifactId>LocalStackTestcontainers</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
11+
<properties>
12+
<maven.compiler.source>11</maven.compiler.source>
13+
<maven.compiler.target>11</maven.compiler.target>
14+
</properties>
15+
<dependencyManagement>
16+
<dependencies>
17+
<dependency>
18+
<groupId>software.amazon.awssdk</groupId>
19+
<artifactId>bom</artifactId>
20+
<version>2.17.224</version>
21+
<type>pom</type>
22+
<scope>import</scope>
23+
</dependency>
24+
</dependencies>
25+
</dependencyManagement>
26+
<dependencies>
27+
<dependency>
28+
<groupId>junit</groupId>
29+
<artifactId>junit</artifactId>
30+
<version>4.12</version>
31+
<scope>test</scope>
32+
</dependency>
33+
<dependency>
34+
<groupId>software.amazon.awssdk</groupId>
35+
<artifactId>s3</artifactId>
36+
</dependency>
37+
<dependency>
38+
<groupId>software.amazon.awssdk</groupId>
39+
<artifactId>rds</artifactId>
40+
</dependency>
41+
<dependency>
42+
<groupId>software.amazon.awssdk</groupId>
43+
<artifactId>s3</artifactId>
44+
</dependency>
45+
<dependency>
46+
<groupId>software.amazon.awssdk</groupId>
47+
<artifactId>cognitoidentity</artifactId>
48+
</dependency>
49+
<dependency>
50+
<groupId>software.amazon.awssdk</groupId>
51+
<artifactId>cognitoidentityprovider</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.testcontainers</groupId>
55+
<artifactId>localstack</artifactId>
56+
<version>1.17.6</version>
57+
<scope>test</scope>
58+
59+
</dependency>
60+
<dependency>
61+
<groupId>com.amazonaws</groupId>
62+
<artifactId>aws-java-sdk-s3</artifactId>
63+
<version>1.11.914</version>
64+
<scope>test</scope>
65+
</dependency>
66+
<dependency>
67+
<groupId>org.postgresql</groupId>
68+
<artifactId>postgresql</artifactId>
69+
<version>9.3-1100-jdbc4</version>
70+
</dependency>
71+
</dependencies>
72+
<build>
73+
<plugins>
74+
<plugin>
75+
<groupId>org.apache.maven.plugins</groupId>
76+
<artifactId>maven-compiler-plugin</artifactId>
77+
<version>3.8.1</version>
78+
<configuration>
79+
<source>11</source>
80+
<target>11</target>
81+
</configuration>
82+
</plugin>
83+
</plugins>
84+
</build>
85+
</project>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import java.sql.*;
2+
3+
public class RDS {
4+
private static final String DEFAULT_USER = "test";
5+
private static final String DEFAULT_PW = "test";
6+
7+
/**
8+
* Simple connection to test for demo purpose
9+
* Connects to an existing Postgres Database, creates a table, inserts some dummy data, and selects the data afterwards.
10+
*
11+
* @param hostname name of the database host
12+
* @param port port
13+
* @param dbname database name
14+
* @return String with the result of the select query
15+
*
16+
* @throws SQLException
17+
* @throws ClassNotFoundException
18+
*/
19+
protected static String test_connection(String hostname, Integer port, String dbname) throws SQLException, ClassNotFoundException {
20+
Connection c = null;
21+
22+
try {
23+
Class.forName("org.postgresql.Driver");
24+
String url = String.format("jdbc:postgresql://%s:%d/%s", hostname, port, dbname);
25+
c = DriverManager.getConnection(url, DEFAULT_USER, DEFAULT_PW);
26+
c.setAutoCommit(true);
27+
Statement stmt = c.createStatement();
28+
String sql = "CREATE TABLE HELLO " +
29+
"(ID INT PRIMARY KEY NOT NULL," +
30+
" NAME TEXT NOT NULL) ";
31+
stmt.executeUpdate(sql);
32+
33+
sql = "INSERT INTO HELLO (ID,NAME) "
34+
+ "VALUES (1, 'world');";
35+
stmt.executeUpdate(sql);
36+
ResultSet rs = stmt.executeQuery( "SELECT * FROM HELLO;" );
37+
String response = "";
38+
while( rs.next()) {
39+
int id = rs.getInt("id");
40+
String name = rs.getString("name");
41+
response += String.format("ID = %d\nNAME = %s", id, name);
42+
}
43+
rs.close();
44+
stmt.close();
45+
return response;
46+
} finally {
47+
try {
48+
c.close();
49+
} catch (SQLException e) {
50+
// we can ignore error here
51+
}
52+
}
53+
}
54+
55+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import org.junit.Rule;
2+
import org.junit.Test;
3+
import org.testcontainers.containers.localstack.LocalStackContainer;
4+
import org.testcontainers.utility.DockerImageName;
5+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
6+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
7+
import software.amazon.awssdk.regions.Region;
8+
import software.amazon.awssdk.services.rds.RdsClient;
9+
import software.amazon.awssdk.services.rds.model.CreateDbInstanceRequest;
10+
import software.amazon.awssdk.services.rds.model.CreateDbInstanceResponse;
11+
import software.amazon.awssdk.services.rds.model.DescribeDbInstancesRequest;
12+
import software.amazon.awssdk.services.rds.model.DescribeDbInstancesResponse;
13+
14+
import java.net.InetAddress;
15+
import java.net.URI;
16+
import java.net.URISyntaxException;
17+
import java.net.UnknownHostException;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.fail;
21+
22+
public class TestRDS {
23+
24+
private DockerImageName localstackImage = DockerImageName.parse("localstack/localstack:1.4.0");
25+
private String api_key = System.getenv("LOCALSTACK_API_KEY");
26+
27+
/**
28+
* Start LocalStackContainer with exposed Ports. Those ports are used by services like RDS, where several databases can be started, running on different ports.
29+
* In this sample we only map 5 ports, however, depending on your use case you may need to map ports up to 4559
30+
*/
31+
@Rule
32+
public LocalStackContainer localstack = new LocalStackContainer(localstackImage)
33+
.withExposedPorts(4510, 4511, 4512, 4513, 4514) // TODO the port can have any value between 4510-4559, but LS starts from 4510
34+
.withEnv("LOCALSTACK_API_KEY", api_key) // TODO add your API key here
35+
.withServices(LocalStackContainer.EnabledService.named("rds"));
36+
37+
38+
@Test
39+
public void testRds() throws UnknownHostException, URISyntaxException {
40+
// create the rds client that will connect to the localstack testcontainer
41+
RdsClient rds = RdsClient
42+
.builder()
43+
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.EnabledService.named("rds")))
44+
.credentialsProvider(StaticCredentialsProvider.create(
45+
AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())
46+
)).region(Region.of(localstack.getRegion())).build();
47+
48+
// create a db instance, by default it will use username "test" and password "test"
49+
CreateDbInstanceResponse response = rds.createDBInstance(CreateDbInstanceRequest.builder().dbName("hello").engine("postgres").build());
50+
String identifier = response.dbInstance().dbInstanceIdentifier();
51+
DescribeDbInstancesRequest request = DescribeDbInstancesRequest.builder().dbInstanceIdentifier(identifier).build();
52+
DescribeDbInstancesResponse describedb = rds.describeDBInstances(request);
53+
54+
// wait for db to be ready
55+
while(! describedb.dbInstances().get(0).dbInstanceStatus().equalsIgnoreCase("available")) {
56+
describedb = rds.describeDBInstances(request);
57+
}
58+
59+
// identify the port localstack provides for the instance
60+
int localstack_port = response.dbInstance().endpoint().port();
61+
62+
// get the port it was mapped to, e.g. the one we can reach from host/the test
63+
int mapped_port = localstack.getMappedPort(localstack_port);
64+
try {
65+
// try to connect to database in our example we simply insert some dummy data
66+
String actual = RDS.test_connection(localstack.getHost(), mapped_port, "hello");
67+
String expected = "ID = 1\nNAME = world";
68+
assertEquals(actual, expected);
69+
} catch (Exception e) {
70+
fail("testing connection with database failed");
71+
}
72+
// rds database endpoint
73+
URI rds_database_uri = getMappedAddressForPort(localstack, localstack_port);
74+
System.out.println(rds_database_uri);
75+
76+
// TODO do whatever with RDS instance using the uri/port
77+
78+
}
79+
80+
81+
/**
82+
* Helper method to get the mapped address for any port. For services with varying ports.
83+
* Similar to {@link LocalStackContainer#getEndpointOverride} for fixed mapped services.
84+
*
85+
* @param localstack LocalStackContainer
86+
* @param localstack_port the port returned by LocalStack, e.g. for DescribeDBInstances
87+
* @return URI of the endpoint, reachable from host
88+
* @throws URISyntaxException
89+
* @throws UnknownHostException
90+
*/
91+
public static URI getMappedAddressForPort(LocalStackContainer localstack, int localstack_port) throws URISyntaxException, UnknownHostException {
92+
String ipAddress = InetAddress.getByName(localstack.getHost()).getHostAddress();
93+
int mapped_port = localstack.getMappedPort(localstack_port);
94+
return new URI("http://" + ipAddress + ":" + mapped_port);
95+
96+
}
97+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Example using RDS with Localstack Testcontainers
2+
3+
Testcontainers need a special setup to use services like RDS, which may use any port to expose the database.
4+
The sample explains how the mapping works, and how you need to configure Testcontainers in order to connect to the RDS instance from your test.
5+
6+
## Run Example
7+
* Import the project (e.g. in IntelliJ),
8+
* configure your LOCALSTACK_API_KEY as environment variable,
9+
* and then run the test `TestRDS`.
10+
11+
It will create a LocalStack Testcontainer and a postgres database instance using RDSClient.
12+
The database will then be filled with some data, and queried afterwards.
13+
14+

0 commit comments

Comments
 (0)