Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/modules/gcloud/src/abstract-gcloud-emulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { GenericContainer, Wait } from "testcontainers";
import { EmulatorFlagsManager } from "./emulator-flags-manager";

export class AbstractGcloudEmulator extends GenericContainer {
private readonly flagsManager: EmulatorFlagsManager;

constructor(
image: string,
port: number,
private readonly cmd: string
) {
super(image);
this.flagsManager = new EmulatorFlagsManager();
this.withExposedPorts(port)
.withFlag("host-port", `0.0.0.0:${port}`)
.withWaitStrategy(Wait.forLogMessage(/.*running.*/))
.withStartupTimeout(120_000);
}

/**
* Adds flag as argument to emulator start command.
* Adding same flag name twice replaces existing flag value.
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
* @param value flag value. May be empty string.
* @returns this instance for chaining.
*/
public withFlag(name: string, value: string) {
this.flagsManager.withFlag(name, value);
return this;
}

public override async beforeContainerCreated(): Promise<void> {
this.withCommand(["/bin/sh", "-c", `${this.cmd} ${this.flagsManager.expandFlags()}`]);
}
}
13 changes: 5 additions & 8 deletions packages/modules/gcloud/src/datastore-emulator-container.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { AbstractStartedContainer, StartedTestContainer } from "testcontainers";
import { AbstractGcloudEmulator } from "./abstract-gcloud-emulator";

const EMULATOR_PORT = 8080;
const CMD = `gcloud beta emulators firestore start --host-port 0.0.0.0:${EMULATOR_PORT} --database-mode=datastore-mode`;
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/cloud-sdk";

export class DatastoreEmulatorContainer extends GenericContainer {
export class DatastoreEmulatorContainer extends AbstractGcloudEmulator {
constructor(image = DEFAULT_IMAGE) {
super(image);
this.withExposedPorts(EMULATOR_PORT)
.withCommand(["/bin/sh", "-c", CMD])
.withWaitStrategy(Wait.forLogMessage(RegExp(".*running.*"), 1))
.withStartupTimeout(120_000);
super(image, EMULATOR_PORT, "gcloud beta emulators firestore start");
this.withFlag("database-mode", `datastore-mode`);
}

public override async start(): Promise<StartedDatastoreEmulatorContainer> {
Expand Down
62 changes: 62 additions & 0 deletions packages/modules/gcloud/src/emulator-flags-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { EmulatorFlagsManager } from "./emulator-flags-manager";

describe("EmulatorFlagsManager", () => {
it("should add flag without --", async () => {
const flagsManager = new EmulatorFlagsManager().withFlag("database-mode", "firestore-native");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=firestore-native");
});

it("should add flag with --", async () => {
const flagsManager = new EmulatorFlagsManager().withFlag("--database-mode", "firestore-native");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=firestore-native");
});

it("should add many flags", async () => {
const flagsManager = new EmulatorFlagsManager()
.withFlag("database-mode", "firestore-native")
.withFlag("--host-port", "0.0.0.0:8080");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=firestore-native --host-port=0.0.0.0:8080");
});

it("should overwrite same flag if added more than once", async () => {
const flagsManager = new EmulatorFlagsManager()
.withFlag("database-mode", "firestore-native")
.withFlag("--database-mode", "datastore-mode");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode=datastore-mode");
});

it("should add flag with no value", async () => {
const flagsManager = new EmulatorFlagsManager().withFlag("database-mode", "").withFlag("--host-port", "");

const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("--database-mode --host-port");
});

it("should throw if flag name not set", async () => {
expect(() => new EmulatorFlagsManager().withFlag("", "firestore-native")).toThrowError();
});

it("should clear all flags added", async () => {
const flagsManager = new EmulatorFlagsManager()
.withFlag("database-mode", "firestore-native")
.withFlag("host-port", "0.0.0.0:8080");

flagsManager.clearFlags();
const flags = flagsManager.expandFlags();

expect(flags.trim()).toEqual("");
});
});
36 changes: 36 additions & 0 deletions packages/modules/gcloud/src/emulator-flags-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export class EmulatorFlagsManager {
private flags: { [name: string]: string } = {};

/**
* Adds flag as argument to emulator start command.
* Adding same flag name twice replaces existing flag value.
* @param name flag name. Must be set to non-empty string. May optionally contain -- prefix.
* @param value flag value. May be empty string.
* @returns this instance for chaining.
*/
public withFlag(name: string, value: string): this {
if (!name) throw new Error("Flag name must be set.");
if (name.startsWith("--")) this.flags[name] = value;
else this.flags[`--${name}`] = value;
return this;
}

private flagToString(name: string, value: string): string {
return `${name}${value ? "=" + value : ""}`;
}

/**
*
* @returns string with all flag names and values, concatenated in same order they were added.
*/
public expandFlags(): string {
return `${Object.keys(this.flags).reduce((p, c) => p + " " + this.flagToString(c, this.flags[c]), "")}`;
}

/**
* Clears all added flags.
*/
public clearFlags() {
this.flags = {};
}
}
12 changes: 4 additions & 8 deletions packages/modules/gcloud/src/firestore-emulator-container.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { AbstractStartedContainer, StartedTestContainer } from "testcontainers";
import { AbstractGcloudEmulator } from "./abstract-gcloud-emulator";

const EMULATOR_PORT = 8080;
const CMD = `gcloud beta emulators firestore start --host-port 0.0.0.0:${EMULATOR_PORT}`;
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/cloud-sdk";

export class FirestoreEmulatorContainer extends GenericContainer {
export class FirestoreEmulatorContainer extends AbstractGcloudEmulator {
constructor(image = DEFAULT_IMAGE) {
super(image);
this.withExposedPorts(EMULATOR_PORT)
.withCommand(["/bin/sh", "-c", CMD])
.withWaitStrategy(Wait.forLogMessage(RegExp(".*running.*"), 1))
.withStartupTimeout(120_000);
super(image, EMULATOR_PORT, "gcloud beta emulators firestore start");
}

public override async start(): Promise<StartedFirestoreEmulatorContainer> {
Expand Down
26 changes: 10 additions & 16 deletions packages/modules/gcloud/src/pubsub-emulator-container.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
import type { StartedTestContainer } from "testcontainers";
import { AbstractStartedContainer, GenericContainer, Wait } from "testcontainers";
import { AbstractStartedContainer, StartedTestContainer, Wait } from "testcontainers";
import { AbstractGcloudEmulator } from "./abstract-gcloud-emulator";

const EMULATOR_PORT = 8085;
const CMD = "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085";
const DEFAULT_IMAGE = "gcr.io/google.com/cloudsdktool/google-cloud-cli";

export class PubSubEmulatorContainer extends GenericContainer {
private _projectId?: string;
export class PubSubEmulatorContainer extends AbstractGcloudEmulator {
private projectId?: string;

constructor(image = DEFAULT_IMAGE) {
super(image);

this.withExposedPorts(EMULATOR_PORT)
.withWaitStrategy(Wait.forLogMessage(/Server started/g, 1))
.withStartupTimeout(120_000);
super(image, EMULATOR_PORT, "gcloud beta emulators pubsub start");
this.withWaitStrategy(Wait.forLogMessage(/Server started/g));
}

public withProjectId(projectId: string): PubSubEmulatorContainer {
this._projectId = projectId;
public withProjectId(projectId: string): this {
this.projectId = projectId;
return this;
}

public override async start(): Promise<StartedPubSubEmulatorContainer> {
// Determine the valid command-line prompt when starting the Pub/Sub emulator
const selectedProjectId = this._projectId ?? "test-project";
const commandLine = `${CMD} --project=${selectedProjectId}`;
this.withCommand(["/bin/sh", "-c", commandLine]);
const selectedProjectId = this.projectId ?? "test-project";
this.withFlag("project", selectedProjectId);

return new StartedPubSubEmulatorContainer(await super.start(), selectedProjectId);
}
Expand Down