-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add GCloud module for Google Cloud Datastore, Firestore, PubSub, and Spanner emulators #2690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c69bfb0
67b0ddd
b05704a
927cc6d
3eae24f
cdc415e
ae43776
1ff7c3d
fe7a55e
1542b17
6991cef
84d1172
0d3ea39
a5fdcb7
548e6bf
802228f
465e487
8109629
70befd3
ac133f0
40dac65
5683998
4d50670
fb43494
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# GCloud 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 Google's [Cloud SDK](https://cloud.google.com/sdk/). | ||
|
||
Currently, the module supports `Datastore`, `Firestore`, `Pub/Sub` and `Spanner` emulators. In order to use it, you should use the following classes: | ||
|
||
* DatastoreEmulatorContainer | ||
* FirestoreEmulatorContainer | ||
* PubSubEmulatorContainer | ||
* SpannerEmulatorContainer | ||
|
||
## Usage example | ||
|
||
Running GCloud as a stand-in for Google Datastore during a test: | ||
|
||
<!--codeinclude--> | ||
[Creating a Datastore container](../../modules/gcloud/src/test/java/org/testcontainers/containers/DatastoreEmulatorContainerTest.java) inside_block:creatingDatastoreEmulatorContainer | ||
<!--/codeinclude--> | ||
|
||
And how to start it: | ||
|
||
<!--codeinclude--> | ||
[Starting a Datastore container](../../modules/gcloud/src/test/java/org/testcontainers/containers/DatastoreEmulatorContainerTest.java) inside_block:startingDatastoreEmulatorContainer | ||
<!--/codeinclude--> | ||
|
||
## Adding this module to your project dependencies | ||
|
||
Add the following dependency to your `pom.xml`/`build.gradle` file: | ||
|
||
```groovy tab='Gradle' | ||
testCompile "org.testcontainers:gcloud:{{latest_version}}" | ||
``` | ||
|
||
```xml tab='Maven' | ||
<dependency> | ||
<groupId>org.testcontainers</groupId> | ||
<artifactId>gcloud</artifactId> | ||
<version>{{latest_version}}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
description = "Testcontainers :: GCloud" | ||
|
||
dependencies { | ||
compile project(':testcontainers') | ||
|
||
testCompile 'com.google.cloud:google-cloud-datastore:1.102.4' | ||
testCompile 'com.google.cloud:google-cloud-firestore:1.33.0' | ||
testCompile 'com.google.cloud:google-cloud-pubsub:1.105.0' | ||
testCompile 'com.google.cloud:google-cloud-spanner:1.50.0' | ||
testCompile 'org.assertj:assertj-core:3.15.0' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package org.testcontainers.containers; | ||
|
||
import org.testcontainers.containers.wait.strategy.Wait; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
/** | ||
* A Datastore container that relies in google cloud sdk. | ||
* | ||
* Default port is 8081. | ||
* | ||
* @author Eddú Meléndez | ||
*/ | ||
public class DatastoreEmulatorContainer extends GenericContainer<DatastoreEmulatorContainer> { | ||
|
||
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk"); | ||
|
||
private static final String CMD = "gcloud beta emulators datastore start --project test-project --host-port 0.0.0.0:8081"; | ||
|
||
public DatastoreEmulatorContainer(final DockerImageName dockerImageName) { | ||
super(dockerImageName); | ||
|
||
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); | ||
|
||
withExposedPorts(8081); | ||
setWaitStrategy(Wait.forHttp("/").forStatusCode(200)); | ||
withCommand("/bin/sh", "-c", CMD); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.testcontainers.containers; | ||
|
||
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
/** | ||
* A Firestore container that relies in google cloud sdk. | ||
* | ||
* Default port is 8080. | ||
* | ||
* @author Eddú Meléndez | ||
*/ | ||
public class FirestoreEmulatorContainer extends GenericContainer<FirestoreEmulatorContainer> { | ||
|
||
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk"); | ||
|
||
private static final String CMD = "gcloud beta emulators firestore start --host-port 0.0.0.0:8080"; | ||
|
||
public FirestoreEmulatorContainer(final DockerImageName dockerImageName) { | ||
super(dockerImageName); | ||
|
||
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); | ||
|
||
withExposedPorts(8080); | ||
setWaitStrategy(new LogMessageWaitStrategy() | ||
.withRegEx("(?s).*running.*$")); | ||
withCommand("/bin/sh", "-c", CMD); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.testcontainers.containers; | ||
|
||
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
/** | ||
* A PubSub container that relies in google cloud sdk. | ||
* | ||
* Default port is 8085. | ||
* | ||
* @author Eddú Meléndez | ||
*/ | ||
public class PubSubEmulatorContainer extends GenericContainer<PubSubEmulatorContainer> { | ||
|
||
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk"); | ||
|
||
private static final String CMD = "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085"; | ||
|
||
public PubSubEmulatorContainer(final DockerImageName dockerImageName) { | ||
super(dockerImageName); | ||
|
||
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); | ||
|
||
withExposedPorts(8085); | ||
setWaitStrategy(new LogMessageWaitStrategy() | ||
.withRegEx("(?s).*started.*$")); | ||
withCommand("/bin/sh", "-c", CMD); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.testcontainers.containers; | ||
|
||
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
/** | ||
* A Spanner container. Default ports: 9010 for GRPC and 9020 for HTTP. | ||
* | ||
* @author Eddú Meléndez | ||
*/ | ||
public class SpannerEmulatorContainer extends GenericContainer<SpannerEmulatorContainer> { | ||
|
||
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator"); | ||
|
||
private static final int GRPC_PORT = 9010; | ||
private static final int HTTP_PORT = 9020; | ||
Comment on lines
+15
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WDYT about not exposing the constants, but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking at some of the other modules, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been persuaded by @bsideup - many of our other modules have some mechanism for getting the mapped ports, address or URL for the running container. It seems like we should do this here, instead of exposing constants, as it's fundamentally more useful for the user. I'll take the action to do this, as we've asked @eddumelendez to do too much already. I'll raise a quick PR tonight, and will merge this PR now. |
||
|
||
public SpannerEmulatorContainer(final DockerImageName dockerImageName) { | ||
super(dockerImageName); | ||
|
||
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); | ||
|
||
withExposedPorts(GRPC_PORT, HTTP_PORT); | ||
setWaitStrategy(new LogMessageWaitStrategy() | ||
.withRegEx(".*Cloud Spanner emulator running\\..*")); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package org.testcontainers.containers; | ||
|
||
import com.google.cloud.NoCredentials; | ||
import com.google.cloud.ServiceOptions; | ||
import com.google.cloud.datastore.Datastore; | ||
import com.google.cloud.datastore.DatastoreOptions; | ||
import com.google.cloud.datastore.Entity; | ||
import com.google.cloud.datastore.Key; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class DatastoreEmulatorContainerTest { | ||
|
||
@Rule | ||
// creatingDatastoreEmulatorContainer { | ||
public DatastoreEmulatorContainer emulator = new DatastoreEmulatorContainer( | ||
DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:313.0.0") | ||
); | ||
// } | ||
|
||
// startingDatastoreEmulatorContainer { | ||
@Test | ||
public void testSimple() { | ||
DatastoreOptions options = DatastoreOptions.newBuilder() | ||
.setHost(emulator.getContainerIpAddress() + ":" + emulator.getMappedPort(8081)) | ||
.setCredentials(NoCredentials.getInstance()) | ||
.setRetrySettings(ServiceOptions.getNoRetrySettings()) | ||
.setProjectId("test-project") | ||
.build(); | ||
Datastore datastore = options.getService(); | ||
|
||
Key key = datastore.newKeyFactory().setKind("Task").newKey("sample"); | ||
Entity entity = Entity.newBuilder(key).set("description", "my description").build(); | ||
datastore.put(entity); | ||
|
||
assertThat(datastore.get(key).getString("description")).isEqualTo("my description"); | ||
} | ||
// } | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package org.testcontainers.containers; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.concurrent.ExecutionException; | ||
|
||
import com.google.api.core.ApiFuture; | ||
import com.google.cloud.NoCredentials; | ||
import com.google.cloud.firestore.CollectionReference; | ||
import com.google.cloud.firestore.DocumentReference; | ||
import com.google.cloud.firestore.Firestore; | ||
import com.google.cloud.firestore.FirestoreOptions; | ||
import com.google.cloud.firestore.QuerySnapshot; | ||
import com.google.cloud.firestore.WriteResult; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class FirestoreEmulatorContainerTest { | ||
|
||
@Rule | ||
public FirestoreEmulatorContainer emulator = new FirestoreEmulatorContainer(DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:313.0.0")); | ||
|
||
@Test | ||
public void testSimple() throws ExecutionException, InterruptedException { | ||
FirestoreOptions options = FirestoreOptions.getDefaultInstance().toBuilder() | ||
.setHost(emulator.getContainerIpAddress() + ":" + emulator.getMappedPort(8080)) | ||
.setCredentials(NoCredentials.getInstance()) | ||
.setProjectId("test-project") | ||
.build(); | ||
Firestore firestore = options.getService(); | ||
|
||
CollectionReference users = firestore.collection("users"); | ||
DocumentReference docRef = users.document("alovelace"); | ||
Map<String, Object> data = new HashMap<>(); | ||
data.put("first", "Ada"); | ||
data.put("last", "Lovelace"); | ||
ApiFuture<WriteResult> result = docRef.set(data); | ||
result.get(); | ||
|
||
ApiFuture<QuerySnapshot> query = users.get(); | ||
QuerySnapshot querySnapshot = query.get(); | ||
|
||
assertThat(querySnapshot.getDocuments().get(0).getData()).containsEntry("first", "Ada"); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package org.testcontainers.containers; | ||
|
||
import java.io.IOException; | ||
|
||
import com.google.api.gax.core.NoCredentialsProvider; | ||
import com.google.api.gax.grpc.GrpcTransportChannel; | ||
import com.google.api.gax.rpc.FixedTransportChannelProvider; | ||
import com.google.api.gax.rpc.TransportChannelProvider; | ||
import com.google.cloud.pubsub.v1.Publisher; | ||
import com.google.cloud.pubsub.v1.SubscriptionAdminClient; | ||
import com.google.cloud.pubsub.v1.SubscriptionAdminSettings; | ||
import com.google.cloud.pubsub.v1.TopicAdminClient; | ||
import com.google.cloud.pubsub.v1.TopicAdminSettings; | ||
import com.google.cloud.pubsub.v1.stub.GrpcSubscriberStub; | ||
import com.google.cloud.pubsub.v1.stub.SubscriberStub; | ||
import com.google.cloud.pubsub.v1.stub.SubscriberStubSettings; | ||
import com.google.protobuf.ByteString; | ||
import com.google.pubsub.v1.ProjectSubscriptionName; | ||
import com.google.pubsub.v1.PubsubMessage; | ||
import com.google.pubsub.v1.PullRequest; | ||
import com.google.pubsub.v1.PullResponse; | ||
import com.google.pubsub.v1.PushConfig; | ||
import com.google.pubsub.v1.TopicName; | ||
import io.grpc.ManagedChannel; | ||
import io.grpc.ManagedChannelBuilder; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class PubSubEmulatorContainerTest { | ||
eddumelendez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public static final String PROJECT_ID = "my-project-id"; | ||
|
||
@Rule | ||
public PubSubEmulatorContainer emulator = new PubSubEmulatorContainer(DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:313.0.0")); | ||
|
||
@Test | ||
public void testSimple() throws IOException { | ||
String hostport = emulator.getContainerIpAddress() + ":" + emulator.getMappedPort(8085); | ||
ManagedChannel channel = ManagedChannelBuilder.forTarget(hostport).usePlaintext().build(); | ||
try { | ||
TransportChannelProvider channelProvider = | ||
FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel)); | ||
NoCredentialsProvider credentialsProvider = NoCredentialsProvider.create(); | ||
|
||
String topicId = "my-topic-id"; | ||
createTopic(topicId, channelProvider, credentialsProvider); | ||
|
||
String subscriptionId = "my-subscription-id"; | ||
createSubscription(subscriptionId, topicId, channelProvider, credentialsProvider); | ||
|
||
Publisher publisher = Publisher.newBuilder(TopicName.of(PROJECT_ID, topicId)) | ||
.setChannelProvider(channelProvider) | ||
.setCredentialsProvider(credentialsProvider) | ||
.build(); | ||
PubsubMessage message = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8("test message")).build(); | ||
publisher.publish(message); | ||
|
||
SubscriberStubSettings subscriberStubSettings = | ||
SubscriberStubSettings.newBuilder() | ||
.setTransportChannelProvider(channelProvider) | ||
.setCredentialsProvider(credentialsProvider) | ||
.build(); | ||
try (SubscriberStub subscriber = GrpcSubscriberStub.create(subscriberStubSettings)) { | ||
PullRequest pullRequest = PullRequest.newBuilder() | ||
.setMaxMessages(1) | ||
.setSubscription(ProjectSubscriptionName.format(PROJECT_ID, subscriptionId)) | ||
.build(); | ||
PullResponse pullResponse = subscriber.pullCallable().call(pullRequest); | ||
|
||
assertThat(pullResponse.getReceivedMessagesList()).hasSize(1); | ||
assertThat(pullResponse.getReceivedMessages(0).getMessage().getData().toStringUtf8()).isEqualTo("test message"); | ||
} | ||
} finally { | ||
channel.shutdown(); | ||
} | ||
} | ||
|
||
private void createTopic(String topicId, TransportChannelProvider channelProvider, NoCredentialsProvider credentialsProvider) throws IOException { | ||
TopicAdminSettings topicAdminSettings = TopicAdminSettings.newBuilder() | ||
.setTransportChannelProvider(channelProvider) | ||
.setCredentialsProvider(credentialsProvider) | ||
.build(); | ||
try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) { | ||
TopicName topicName = TopicName.of(PROJECT_ID, topicId); | ||
topicAdminClient.createTopic(topicName); | ||
} | ||
} | ||
|
||
private void createSubscription(String subscriptionId, String topicId, TransportChannelProvider channelProvider, NoCredentialsProvider credentialsProvider) throws IOException { | ||
SubscriptionAdminSettings subscriptionAdminSettings = SubscriptionAdminSettings.newBuilder() | ||
.setTransportChannelProvider(channelProvider) | ||
.setCredentialsProvider(credentialsProvider) | ||
.build(); | ||
SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create(subscriptionAdminSettings); | ||
ProjectSubscriptionName subscriptionName = ProjectSubscriptionName.of(PROJECT_ID, subscriptionId); | ||
subscriptionAdminClient.createSubscription(subscriptionName, TopicName.of(PROJECT_ID, topicId), PushConfig.getDefaultInstance(), 10); | ||
} | ||
|
||
} |
Uh oh!
There was an error while loading. Please reload this page.