-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Azure Cosmos DB module: Introduce CosmosDBEmulatorContainer (#4303)
Co-authored-by: Richard North <[email protected]>
- Loading branch information
Showing
7 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Azure Module | ||
|
||
!!! note | ||
This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy. | ||
|
||
Testcontainers module for the Microsoft Azure's [SDK](https://github.com/Azure/azure-sdk-for-java). | ||
|
||
Currently, the module supports `CosmosDB` emulator. In order to use it, you should use the following class: | ||
|
||
Class | Container Image | ||
-|- | ||
CosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry) | ||
|
||
## Usage example | ||
|
||
### CosmosDB | ||
|
||
Start Azure CosmosDB Emulator during a test: | ||
|
||
<!--codeinclude--> | ||
[Starting a Azure CosmosDB Emulator container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:emulatorContainer | ||
<!--/codeinclude--> | ||
|
||
Prepare KeyStore to use for SSL. | ||
|
||
<!--codeinclude--> | ||
[Building KeyStore from certificate within container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:buildAndSaveNewKeyStore | ||
<!--/codeinclude--> | ||
|
||
Set system trust-store parameters to use already built KeyStore: | ||
|
||
<!--codeinclude--> | ||
[Set system trust-store parameters](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:setSystemTrustStoreParameters | ||
<!--/codeinclude--> | ||
|
||
Build Azure CosmosDB client: | ||
|
||
<!--codeinclude--> | ||
[Build Azure CosmosDB client](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:buildClient | ||
<!--/codeinclude--> | ||
|
||
Test against the Emulator: | ||
|
||
<!--codeinclude--> | ||
[Testing against Azure CosmosDB Emulator container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:testWithClientAgainstEmulatorContainer | ||
<!--/codeinclude--> | ||
|
||
## Adding this module to your project dependencies | ||
|
||
Add the following dependency to your `pom.xml`/`build.gradle` file: | ||
|
||
```groovy tab='Gradle' | ||
testImplementation "org.testcontainers:azure:{{latest_version}}" | ||
``` | ||
|
||
```xml tab='Maven' | ||
<dependency> | ||
<groupId>org.testcontainers</groupId> | ||
<artifactId>azure</artifactId> | ||
<version>{{latest_version}}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
description = "Testcontainers :: Azure" | ||
|
||
dependencies { | ||
api project(':testcontainers') | ||
|
||
testImplementation 'org.assertj:assertj-core:3.15.0' | ||
testImplementation 'com.azure:azure-cosmos:4.16.0' | ||
} |
52 changes: 52 additions & 0 deletions
52
modules/azure/src/main/java/org/testcontainers/containers/CosmosDBEmulatorContainer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package org.testcontainers.containers; | ||
|
||
import org.testcontainers.containers.wait.strategy.Wait; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
import java.security.KeyStore; | ||
|
||
/** | ||
* An Azure CosmosDB container | ||
*/ | ||
public class CosmosDBEmulatorContainer extends GenericContainer<CosmosDBEmulatorContainer> { | ||
|
||
private static final DockerImageName DEFAULT_IMAGE_NAME = | ||
DockerImageName.parse("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator"); | ||
|
||
private static final int PORT = 8081; | ||
|
||
/** | ||
* @param dockerImageName specified docker image name to run | ||
*/ | ||
public CosmosDBEmulatorContainer(final DockerImageName dockerImageName) { | ||
super(dockerImageName); | ||
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); | ||
withExposedPorts(PORT); | ||
waitingFor(Wait.forLogMessage("(?s).*Started\\r\\n$", 1)); | ||
} | ||
|
||
/** | ||
* @return new KeyStore built with PKCS12 | ||
*/ | ||
public KeyStore buildNewKeyStore() { | ||
return KeyStoreBuilder.buildByDownloadingCertificate(getEmulatorEndpoint(), getEmulatorKey()); | ||
} | ||
|
||
/** | ||
* Emulator key is a known constant and specified in Azure Cosmos DB Documents. | ||
* This key is also used as password for emulator certificate file. | ||
* | ||
* @return predefined emulator key | ||
* @see <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=ssl-netstd21#authenticate-requests">Azure Cosmos DB Documents</a> | ||
*/ | ||
public String getEmulatorKey() { | ||
return "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; | ||
} | ||
|
||
/** | ||
* @return secure https emulator endpoint to send requests | ||
*/ | ||
public String getEmulatorEndpoint() { | ||
return "https://" + getHost() + ":" + getMappedPort(PORT); | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
modules/azure/src/main/java/org/testcontainers/containers/KeyStoreBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package org.testcontainers.containers; | ||
|
||
import okhttp3.Cache; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.Request; | ||
import okhttp3.Response; | ||
|
||
import javax.net.ssl.SSLContext; | ||
import javax.net.ssl.SSLSocketFactory; | ||
import javax.net.ssl.TrustManager; | ||
import javax.net.ssl.X509TrustManager; | ||
import java.io.InputStream; | ||
import java.security.KeyStore; | ||
import java.security.SecureRandom; | ||
import java.security.cert.Certificate; | ||
import java.security.cert.CertificateFactory; | ||
import java.security.cert.X509Certificate; | ||
import java.util.Objects; | ||
|
||
final class KeyStoreBuilder { | ||
|
||
static KeyStore buildByDownloadingCertificate(String endpoint, String keyStorePassword) { | ||
OkHttpClient client = null; | ||
Response response = null; | ||
try { | ||
TrustManager[] trustAllManagers = buildTrustAllManagers(); | ||
client = buildTrustAllClient(trustAllManagers); | ||
Request request = buildRequest(endpoint); | ||
response = client.newCall(request).execute(); | ||
return buildKeyStore(response.body().byteStream(), keyStorePassword); | ||
} catch (Exception ex) { | ||
throw new IllegalStateException(ex); | ||
} finally { | ||
closeResponseSilently(response); | ||
closeClientSilently(client); | ||
} | ||
} | ||
|
||
private static TrustManager[] buildTrustAllManagers() { | ||
return new TrustManager[] { | ||
new X509TrustManager() { | ||
@Override | ||
public void checkClientTrusted(X509Certificate[] chain, String authType) { | ||
} | ||
|
||
@Override | ||
public void checkServerTrusted(X509Certificate[] chain, String authType) { | ||
} | ||
|
||
@Override | ||
public X509Certificate[] getAcceptedIssuers() { | ||
return new X509Certificate[]{}; | ||
} | ||
} | ||
}; | ||
} | ||
|
||
private static OkHttpClient buildTrustAllClient(TrustManager[] trustManagers) throws Exception { | ||
SSLContext sslContext = SSLContext.getInstance("SSL"); | ||
sslContext.init(null, trustManagers, new SecureRandom()); | ||
SSLSocketFactory socketFactory = sslContext.getSocketFactory(); | ||
return new OkHttpClient.Builder() | ||
.sslSocketFactory(socketFactory, (X509TrustManager) trustManagers[0]) | ||
.hostnameVerifier((s, sslSession) -> true) | ||
.build(); | ||
} | ||
|
||
private static Request buildRequest(String endpoint) { | ||
return new Request.Builder() | ||
.get() | ||
.url(endpoint + "/_explorer/emulator.pem") | ||
.build(); | ||
} | ||
|
||
private static KeyStore buildKeyStore(InputStream certificateStream, String keyStorePassword) throws Exception { | ||
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(certificateStream); | ||
KeyStore keystore = KeyStore.getInstance("PKCS12"); | ||
keystore.load(null, keyStorePassword.toCharArray()); | ||
keystore.setCertificateEntry("azure-cosmos-emulator", certificate); | ||
return keystore; | ||
} | ||
|
||
private static void closeResponseSilently(Response response) { | ||
try { | ||
if (Objects.nonNull(response)) { | ||
response.close(); | ||
} | ||
} catch (Exception ignored) { | ||
} | ||
} | ||
|
||
private static void closeClientSilently(OkHttpClient client) { | ||
try { | ||
if (Objects.nonNull(client)) { | ||
client.dispatcher().executorService().shutdown(); | ||
client.connectionPool().evictAll(); | ||
Cache cache = client.cache(); | ||
if (Objects.nonNull(cache)) { | ||
cache.close(); | ||
} | ||
} | ||
} catch (Exception ignored) { | ||
} | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.testcontainers.containers; | ||
|
||
import com.azure.cosmos.CosmosAsyncClient; | ||
import com.azure.cosmos.CosmosClientBuilder; | ||
import com.azure.cosmos.models.CosmosContainerResponse; | ||
import com.azure.cosmos.models.CosmosDatabaseResponse; | ||
import org.assertj.core.api.Assertions; | ||
import org.junit.AfterClass; | ||
import org.junit.BeforeClass; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.rules.TemporaryFolder; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
import java.io.FileOutputStream; | ||
import java.nio.file.Path; | ||
import java.security.KeyStore; | ||
import java.util.Properties; | ||
|
||
public class CosmosDBEmulatorContainerTest { | ||
|
||
private static Properties originalSystemProperties; | ||
|
||
@BeforeClass | ||
public static void captureOriginalSystemProperties() { | ||
originalSystemProperties = (Properties) System.getProperties().clone(); | ||
} | ||
|
||
@AfterClass | ||
public static void restoreOriginalSystemProperties() { | ||
System.setProperties(originalSystemProperties); | ||
} | ||
|
||
@Rule | ||
public TemporaryFolder tempFolder = TemporaryFolder.builder().assureDeletion().build(); | ||
|
||
@Rule | ||
// emulatorContainer { | ||
public CosmosDBEmulatorContainer emulator = new CosmosDBEmulatorContainer( | ||
DockerImageName.parse("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest") | ||
); | ||
// } | ||
|
||
@Test | ||
public void testWithCosmosClient() throws Exception { | ||
// buildAndSaveNewKeyStore { | ||
Path keyStoreFile = tempFolder.newFile("azure-cosmos-emulator.keystore").toPath(); | ||
KeyStore keyStore = emulator.buildNewKeyStore(); | ||
keyStore.store(new FileOutputStream(keyStoreFile.toFile()), emulator.getEmulatorKey().toCharArray()); | ||
// } | ||
// setSystemTrustStoreParameters { | ||
System.setProperty("javax.net.ssl.trustStore", keyStoreFile.toString()); | ||
System.setProperty("javax.net.ssl.trustStorePassword", emulator.getEmulatorKey()); | ||
System.setProperty("javax.net.ssl.trustStoreType", "PKCS12"); | ||
// } | ||
// buildClient { | ||
CosmosAsyncClient client = new CosmosClientBuilder() | ||
.gatewayMode() | ||
.endpointDiscoveryEnabled(false) | ||
.endpoint(emulator.getEmulatorEndpoint()) | ||
.key(emulator.getEmulatorKey()) | ||
.buildAsyncClient(); | ||
// } | ||
// testWithClientAgainstEmulatorContainer { | ||
CosmosDatabaseResponse databaseResponse = | ||
client.createDatabaseIfNotExists("Azure").block(); | ||
Assertions.assertThat(databaseResponse.getStatusCode()).isEqualTo(201); | ||
CosmosContainerResponse containerResponse = | ||
client.getDatabase("Azure").createContainerIfNotExists("ServiceContainer", "/name").block(); | ||
Assertions.assertThat(containerResponse.getStatusCode()).isEqualTo(201); | ||
// } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<configuration> | ||
|
||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
<!-- encoders are assigned the type | ||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | ||
<encoder> | ||
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern> | ||
</encoder> | ||
</appender> | ||
|
||
<root level="INFO"> | ||
<appender-ref ref="STDOUT"/> | ||
</root> | ||
|
||
<logger name="org.testcontainers" level="INFO"/> | ||
</configuration> |