Skip to content

Commit

Permalink
Add gcloud module
Browse files Browse the repository at this point in the history
Based on google cloud sdk docker image. This module provides:

* DatastoreEmulatorContainer
* FirestoreEmulatorContainer
* PubSubEmulatorContainer

`SpannerEmulatorContainer` is also provided but is based on its own
docker image.

See testcontainersgh-2633
  • Loading branch information
eddumelendez committed May 9, 2020
1 parent 8377288 commit 964103c
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 0 deletions.
11 changes: 11 additions & 0 deletions modules/gcloud/build.gradle
@@ -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'
}
@@ -0,0 +1,29 @@
package org.testcontainers.containers;

import org.testcontainers.containers.wait.strategy.Wait;

/**
* A Datastore container that relies in google cloud sdk. The container provides
* additional instructions to install the components needed in the alpine images.
*
* Default port is 8081.
*
* @author Eddú Meléndez
*/
public class DatastoreEmulatorContainer extends GCloudGenericContainer<DatastoreEmulatorContainer> {

private static final String DATASTORE_EMULATOR_START_COMMAND = "gcloud beta emulators datastore start --project dummy-project --host-port 0.0.0.0:8081";

private static final String[] CMDS = {"apk --update add openjdk8-jre",
"gcloud components install beta cloud-datastore-emulator --quiet"};

public DatastoreEmulatorContainer(String image) {
super(image, DATASTORE_EMULATOR_START_COMMAND, CMDS);
withExposedPorts(8081);
setWaitStrategy(Wait.forHttp("/").forStatusCode(200));
}

public DatastoreEmulatorContainer() {
this(DEFAULT_GCLOUD_IMAGE);
}
}
@@ -0,0 +1,31 @@
package org.testcontainers.containers;

import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;

/**
* A Firestore container that relies in google cloud sdk. The container provides
* additional instructions to install the components needed in the alpine images.
*
* Default port is 8080.
*
* @author Eddú Meléndez
*/
public class FirestoreEmulatorContainer extends GCloudGenericContainer<FirestoreEmulatorContainer> {

private static final String FIRESTORE_EMULATOR_START_COMMAND = "gcloud beta emulators firestore start --host-port 0.0.0.0:8080";

private static final String[] CMDS = {"apk --update add openjdk8-jre",
"gcloud components install beta cloud-firestore-emulator --quiet"};

public FirestoreEmulatorContainer(String image) {
super(image, FIRESTORE_EMULATOR_START_COMMAND, CMDS);
withExposedPorts(8080);
setWaitStrategy(new LogMessageWaitStrategy()
.withRegEx("(?s).*running.*$"));
}

public FirestoreEmulatorContainer() {
this(DEFAULT_GCLOUD_IMAGE);
}

}
@@ -0,0 +1,56 @@
package org.testcontainers.containers;

import java.util.Arrays;

import com.github.dockerjava.api.command.InspectContainerResponse;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;

/**
* @author Eddú Meléndez
*/
public class GCloudGenericContainer<SELF extends GCloudGenericContainer<SELF>> extends GenericContainer<SELF> {

public static final String DEFAULT_GCLOUD_IMAGE = "google/cloud-sdk:291.0.0-alpine";

public GCloudGenericContainer(String image) {
super(image);
}

public GCloudGenericContainer(String image, String mainCmd, String[] prerequisiteCmds) {
super(buildImage(image, mainCmd, prerequisiteCmds));
}

@Override
protected void containerIsStarted(InspectContainerResponse containerInfo) {
// withAdditionalCmds();
}

// public void withAdditionalCmds() {
// try {
// if (cmds != null) {
// execInContainer(cmds);
// }
// } catch (IOException | InterruptedException e) {
// logger().error("Failed to execute {}. Exception message: {}", cmds, e.getMessage());
// }
// }

private static ImageFromDockerfile buildImage(String image, String mainCmd, String[] prerequisiteCmds) {
return new ImageFromDockerfile()
.withDockerfileFromBuilder(builder -> {
DockerfileBuilder from = builder
.from(image);
if (prerequisiteCmds != null) {
from.run(parseCmds(prerequisiteCmds));
}
from.cmd(mainCmd);
from.build();
});
}

private static String parseCmds(String... cmds) {
return String.join(" && \n", Arrays.asList(cmds));
}

}
@@ -0,0 +1,30 @@
package org.testcontainers.containers;

import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;

/**
* A PubSub container that relies in google cloud sdk. The container provides
* additional instructions to install the components needed in the alpine images.
*
* Default port is 8085.
*
* @author Eddú Meléndez
*/
public class PubSubEmulatorContainer extends GCloudGenericContainer<PubSubEmulatorContainer> {

private static final String PUBSUB_EMULATOR_START_COMMAND = "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085";

private static final String[] CMDS = {"apk --update add openjdk7-jre",
"gcloud components install beta pubsub-emulator --quiet"};

public PubSubEmulatorContainer(String image) {
super(image, PUBSUB_EMULATOR_START_COMMAND, CMDS);
withExposedPorts(8085);
setWaitStrategy(new LogMessageWaitStrategy()
.withRegEx("(?s).*started.*$"));
}

public PubSubEmulatorContainer() {
this(DEFAULT_GCLOUD_IMAGE);
}
}
@@ -0,0 +1,25 @@
package org.testcontainers.containers;

import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;

/**
* A Spanner container. Default ports: 9010 for GRPC and 9020 for HTTP.
*
* @author Eddú Meléndez
*/
public class SpannerEmulatorContainer extends GCloudGenericContainer<SpannerEmulatorContainer> {

private static final int GRPC_PORT = 9010;
private static final int HTTP_PORT = 9020;

public SpannerEmulatorContainer(String image) {
super(image);
withExposedPorts(GRPC_PORT, HTTP_PORT);
setWaitStrategy(new LogMessageWaitStrategy()
.withRegEx(".*Cloud Spanner emulator running\\..*"));
}

public SpannerEmulatorContainer() {
this("gcr.io/cloud-spanner-emulator/emulator:0.7.28");
}
}
@@ -0,0 +1,35 @@
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 static org.assertj.core.api.Assertions.assertThat;

public class DatastoreEmulatorContainerTest {

@Rule
public DatastoreEmulatorContainer emulator = new DatastoreEmulatorContainer();

@Test
public void testSimple() {
DatastoreOptions options = DatastoreOptions.newBuilder()
.setHost(emulator.getContainerIpAddress() + ":" + emulator.getMappedPort(8081))
.setCredentials(NoCredentials.getInstance())
.setRetrySettings(ServiceOptions.getNoRetrySettings())
.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");
}

}
@@ -0,0 +1,48 @@
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 static org.assertj.core.api.Assertions.assertThat;

public class FirestoreEmulatorContainerTest {

@Rule
public FirestoreEmulatorContainer emulator = new FirestoreEmulatorContainer();

@Test
public void testSimple() throws ExecutionException, InterruptedException {
FirestoreOptions options = FirestoreOptions.getDefaultInstance().toBuilder()
.setHost(emulator.getContainerIpAddress() + ":" + emulator.getMappedPort(8080))
.setCredentials(NoCredentials.getInstance())
.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);

System.out.println(result.get().getUpdateTime());

ApiFuture<QuerySnapshot> query = users.get();
QuerySnapshot querySnapshot = query.get();

assertThat(querySnapshot.getDocuments().get(0).getData()).containsEntry("first", "Ada");
}

}
@@ -0,0 +1,72 @@
package org.testcontainers.containers;

import java.io.IOException;

import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutureCallback;
import com.google.api.core.ApiFutures;
import com.google.api.gax.core.CredentialsProvider;
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.TopicAdminClient;
import com.google.cloud.pubsub.v1.TopicAdminSettings;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.pubsub.v1.PubsubMessage;
import com.google.pubsub.v1.Topic;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.junit.Rule;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class PubSubEmulatorContainerTest {

@Rule
public PubSubEmulatorContainer emulator = new PubSubEmulatorContainer();

@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));
CredentialsProvider credentialsProvider = NoCredentialsProvider.create();

TopicAdminClient topicClient =
TopicAdminClient.create(
TopicAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build());
Topic topic = Topic.newBuilder().setName("projects/my-project-id/topics/my-topic-id").build();
topicClient.createTopic(topic);

Publisher publisher = Publisher.newBuilder(topic.getName()).build();
PubsubMessage message = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8("test message")).build();
ApiFuture<String> future = publisher.publish(message);
ApiFutures.addCallback(future, new ApiFutureCallback<String>() {
@Override
public void onFailure(Throwable t) {

}

@Override
public void onSuccess(String result) {
assertThat(result).isNotNull();
}
}, MoreExecutors.directExecutor());
} finally {
channel.shutdown();
}


assertThat(emulator.getMappedPort(8085)).isNotNull();
}

}

0 comments on commit 964103c

Please sign in to comment.