From edb03ac28f80b896803336ff0b3b231b012bd9b9 Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Tue, 28 May 2019 10:15:07 -0700 Subject: [PATCH 1/8] Add RTDB emulator support to the functions emulator --- src/emulator/constants.ts | 1 + src/emulator/functionsEmulator.ts | 51 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/emulator/constants.ts b/src/emulator/constants.ts index 459e5712469..78ec9ec85ac 100644 --- a/src/emulator/constants.ts +++ b/src/emulator/constants.ts @@ -13,6 +13,7 @@ const DEFAULT_HOST = "localhost"; export class Constants { static SERVICE_FIRESTORE = "firestore.googleapis.com"; + static SERVICE_REALTIME_DATABASE = "firebaseio.com"; static getDefaultHost(emulator: Emulators): string { return DEFAULT_HOST; diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 41ff442fb6d..031f27d9602 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -481,6 +481,7 @@ You can probably fix this by running "npm install ${ this.triggers = triggerDefinitions; for (const definition of toSetup) { + console.log(require("util").inspect(definition)); if (definition.httpsTrigger) { // TODO(samstern): Right now we only emulate each function in one region, but it's possible // that a developer is running the same function in multiple regions. @@ -503,6 +504,9 @@ You can probably fix this by running "npm install ${ case Constants.SERVICE_FIRESTORE: await this.addFirestoreTrigger(this.projectId, definition); break; + case Constants.SERVICE_REALTIME_DATABASE: + await this.addRealtimeDatabaseTrigger(this.projectId, definition); + break; default: EmulatorLogger.log("DEBUG", `Unsupported trigger: ${JSON.stringify(definition)}`); EmulatorLogger.log( @@ -527,6 +531,53 @@ You can probably fix this by running "npm install ${ return loadTriggers(); } + addRealtimeDatabaseTrigger(projectId: string, definition: EmulatedTriggerDefinition): Promise { + const databasePort = 9000; //EmulatorRegistry.getPort(Emulators.DATABASE); + if (!databasePort) { + EmulatorLogger.log( + "INFO", + `Ignoring trigger "${definition.name}" because the Realtime Database emulator is not running` + ); + return Promise.resolve(); + } + if (definition.eventTrigger === undefined) { + EmulatorLogger.log( + "WARN", + `Event trigger "${definition.name}" has undefined "eventTrigger" member` + ); + return Promise.reject(); + } + console.log("Trigger definition " + require('util').inspect(definition)); + const bundle = JSON.stringify([{ + name: definition.name, + path: definition.eventTrigger.resource, + event: definition.eventTrigger.eventType, + topic: "" + }]); + EmulatorLogger.logLabeled( + "BULLET", + "functions", + `Setting up Realtime Database trigger "${definition.name}"` + ); + logger.debug(`addDatabaseTrigger`, JSON.stringify(bundle)); + return new Promise((resolve, reject) => { + request.put(`http://localhost:${databasePort}/.settings/functionTriggers.json`, + { + body: bundle + }, + (err, res, body) => { + if (err) { + EmulatorLogger.log("WARN", "Error adding trigger: " + err); + reject(); + return; + } + console.log("RTDB Trigger add result: " + require('util').inspect(body)); + resolve(); + } + ) + }); + } + addFirestoreTrigger(projectId: string, definition: EmulatedTriggerDefinition): Promise { const firestorePort = EmulatorRegistry.getPort(Emulators.FIRESTORE); if (!firestorePort) { From ae0ffe4b637de9a1b5f48172429fd4ef256f66c6 Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Wed, 29 May 2019 10:29:18 -0700 Subject: [PATCH 2/8] Add auth header to function trigger setup request --- src/emulator/functionsEmulator.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 031f27d9602..e04a5bce642 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -481,7 +481,6 @@ You can probably fix this by running "npm install ${ this.triggers = triggerDefinitions; for (const definition of toSetup) { - console.log(require("util").inspect(definition)); if (definition.httpsTrigger) { // TODO(samstern): Right now we only emulate each function in one region, but it's possible // that a developer is running the same function in multiple regions. @@ -531,7 +530,10 @@ You can probably fix this by running "npm install ${ return loadTriggers(); } - addRealtimeDatabaseTrigger(projectId: string, definition: EmulatedTriggerDefinition): Promise { + addRealtimeDatabaseTrigger( + projectId: string, + definition: EmulatedTriggerDefinition + ): Promise { const databasePort = 9000; //EmulatorRegistry.getPort(Emulators.DATABASE); if (!databasePort) { EmulatorLogger.log( @@ -547,13 +549,13 @@ You can probably fix this by running "npm install ${ ); return Promise.reject(); } - console.log("Trigger definition " + require('util').inspect(definition)); const bundle = JSON.stringify([{ - name: definition.name, - path: definition.eventTrigger.resource, + name: `projects/${projectId}/locations/_/functions/${definition.name}`, + path: definition.eventTrigger.resource.split("refs")[1], event: definition.eventTrigger.eventType, - topic: "" + topic: `projects/${projectId}/topics/_`, }]); + EmulatorLogger.logLabeled( "BULLET", "functions", @@ -563,6 +565,9 @@ You can probably fix this by running "npm install ${ return new Promise((resolve, reject) => { request.put(`http://localhost:${databasePort}/.settings/functionTriggers.json`, { + auth: { + bearer: "owner" + }, body: bundle }, (err, res, body) => { @@ -571,7 +576,6 @@ You can probably fix this by running "npm install ${ reject(); return; } - console.log("RTDB Trigger add result: " + require('util').inspect(body)); resolve(); } ) From 783d764a23ad189cb5bba197137150ae938b7b87 Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Wed, 29 May 2019 18:38:26 -0700 Subject: [PATCH 3/8] Fix lint errors --- src/emulator/functionsEmulator.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index e04a5bce642..8119ce2f575 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -534,11 +534,13 @@ You can probably fix this by running "npm install ${ projectId: string, definition: EmulatedTriggerDefinition ): Promise { - const databasePort = 9000; //EmulatorRegistry.getPort(Emulators.DATABASE); + const databasePort = EmulatorRegistry.getPort(Emulators.DATABASE); if (!databasePort) { EmulatorLogger.log( "INFO", - `Ignoring trigger "${definition.name}" because the Realtime Database emulator is not running` + `Ignoring trigger "${ + definition.name + }" because the Realtime Database emulator is not running` ); return Promise.resolve(); } @@ -549,12 +551,14 @@ You can probably fix this by running "npm install ${ ); return Promise.reject(); } - const bundle = JSON.stringify([{ - name: `projects/${projectId}/locations/_/functions/${definition.name}`, - path: definition.eventTrigger.resource.split("refs")[1], - event: definition.eventTrigger.eventType, - topic: `projects/${projectId}/topics/_`, - }]); + const bundle = JSON.stringify([ + { + name: `projects/${projectId}/locations/_/functions/${definition.name}`, + path: definition.eventTrigger.resource.split("refs")[1], + event: definition.eventTrigger.eventType, + topic: `projects/${projectId}/topics/_`, + }, + ]); EmulatorLogger.logLabeled( "BULLET", @@ -563,12 +567,13 @@ You can probably fix this by running "npm install ${ ); logger.debug(`addDatabaseTrigger`, JSON.stringify(bundle)); return new Promise((resolve, reject) => { - request.put(`http://localhost:${databasePort}/.settings/functionTriggers.json`, + request.put( + `http://localhost:${databasePort}/.settings/functionTriggers.json`, { auth: { - bearer: "owner" + bearer: "owner", }, - body: bundle + body: bundle, }, (err, res, body) => { if (err) { @@ -578,7 +583,7 @@ You can probably fix this by running "npm install ${ } resolve(); } - ) + ); }); } From 17edb2dd49335b2cca21d29ddc773424a9aed307 Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Thu, 30 May 2019 11:57:08 -0700 Subject: [PATCH 4/8] Use regex to extract database-root-relative paths for trigger creation --- src/emulator/functionsEmulator.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 8119ce2f575..819afa2956e 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -540,7 +540,7 @@ You can probably fix this by running "npm install ${ "INFO", `Ignoring trigger "${ definition.name - }" because the Realtime Database emulator is not running` + }" because the Realtime Database emulator is not running.` ); return Promise.resolve(); } @@ -551,10 +551,26 @@ You can probably fix this by running "npm install ${ ); return Promise.reject(); } + /* + * The Realtime Database emulator expects the `path` field in its trigger + * definition to be relative to the database root. + */ + const pathPattern = new RegExp("^projects/[^/]*/instances/[^/]*/refs(/.*)$"); + const result: string[] | null = pathPattern.exec(definition.eventTrigger.resource); + + if (result === null || result.length !== 2) { + EmulatorLogger.log( + "WARN", + `Event trigger "${definition.name}" has malformed "resource" member. ` + + `${definition.eventTrigger.resource}` + ); + return Promise.reject(); + } + const bundle = JSON.stringify([ { name: `projects/${projectId}/locations/_/functions/${definition.name}`, - path: definition.eventTrigger.resource.split("refs")[1], + path: result[1], // path stored in the first capture group event: definition.eventTrigger.eventType, topic: `projects/${projectId}/topics/_`, }, From fceb7f232e0e614d5823e4f1062a3848de0791b3 Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Thu, 30 May 2019 12:34:13 -0700 Subject: [PATCH 5/8] Make path extractor regex a constant, enforce non-empty instance and project --- src/emulator/functionsEmulator.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index 1ec81bc5981..b5fcdd94545 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -32,6 +32,14 @@ import { EmulatorLogger, Verbosity } from "./emulatorLogger"; const EVENT_INVOKE = "functions:invoke"; +/* + * The Realtime Database emulator expects the `path` field in its trigger + * definition to be relative to the database root. This regex is used to extract + * that path from the `resource` member in the trigger definition used by the + * functions emulator. + */ +const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/[^/]+/refs(/.*)$"); + export interface FunctionsEmulatorArgs { port?: number; host?: string; @@ -538,13 +546,8 @@ You can probably fix this by running "npm install ${ ); return Promise.reject(); } - /* - * The Realtime Database emulator expects the `path` field in its trigger - * definition to be relative to the database root. - */ - const pathPattern = new RegExp("^projects/[^/]*/instances/[^/]*/refs(/.*)$"); - const result: string[] | null = pathPattern.exec(definition.eventTrigger.resource); + const result: string[] | null = DATABASE_PATH_PATTERN.exec(definition.eventTrigger.resource); if (result === null || result.length !== 2) { EmulatorLogger.log( "WARN", From 0485229dc53b25e2ceea2fd9e5a41c4a063c7c6f Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Thu, 30 May 2019 13:03:06 -0700 Subject: [PATCH 6/8] Pass functions emulator arguments to database emulator when present --- src/emulator/controller.ts | 25 +++++++++++++++---------- src/emulator/databaseEmulator.ts | 2 ++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index e1bb2500f9c..b7f101c8c66 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -154,17 +154,22 @@ export async function startAll(options: any): Promise { if (targets.indexOf(Emulators.DATABASE) > -1) { const databaseAddr = Constants.getAddress(Emulators.DATABASE, options); - const databaseEmulator = new DatabaseEmulator({ - host: databaseAddr.host, - port: databaseAddr.port, - }); + let databaseEmulator; + if (targets.indexOf(Emulators.FUNCTIONS) > -1) { + const functionsAddr = Constants.getAddress(Emulators.FUNCTIONS, options); + databaseEmulator = new DatabaseEmulator({ + host: databaseAddr.host, + port: databaseAddr.port, + functionsEmulatorHost: functionsAddr.host, + functionsEmulatorPort: functionsAddr.port, + }); + } else { + databaseEmulator = new DatabaseEmulator({ + host: databaseAddr.host, + port: databaseAddr.port, + }); + } await startEmulator(databaseEmulator); - - // TODO: When the database emulator is integrated with the Functions - // emulator, we will need to pass the port in and remove this warning - utils.logWarning( - `Note: the database emulator is not currently integrated with the functions emulator.` - ); } if (targets.indexOf(Emulators.HOSTING) > -1) { diff --git a/src/emulator/databaseEmulator.ts b/src/emulator/databaseEmulator.ts index b9b9b8b4f43..0b50d4f61ce 100644 --- a/src/emulator/databaseEmulator.ts +++ b/src/emulator/databaseEmulator.ts @@ -5,6 +5,8 @@ import { Constants } from "./constants"; interface DatabaseEmulatorArgs { port?: number; host?: string; + functionsEmulatorPort?: number; + functionsEmulatorHost?: string; } export class DatabaseEmulator implements EmulatorInstance { From 324f0732203ac0964fd34e61eaa55449b0a07395 Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Thu, 30 May 2019 18:16:29 -0700 Subject: [PATCH 7/8] Add trigger definition name to invocation payload topicName --- src/emulator/functionsEmulator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts index b5fcdd94545..3de608b1072 100644 --- a/src/emulator/functionsEmulator.ts +++ b/src/emulator/functionsEmulator.ts @@ -562,7 +562,7 @@ You can probably fix this by running "npm install ${ name: `projects/${projectId}/locations/_/functions/${definition.name}`, path: result[1], // path stored in the first capture group event: definition.eventTrigger.eventType, - topic: `projects/${projectId}/topics/_`, + topic: `projects/${projectId}/topics/${definition.name}`, }, ]); From 425b37375249ea968220ee9e3e92bca2cbb519c4 Mon Sep 17 00:00:00 2001 From: Jan Wyszynski Date: Fri, 31 May 2019 10:58:44 -0700 Subject: [PATCH 8/8] Use snake-case in database emulator args --- src/emulator/controller.ts | 4 ++-- src/emulator/databaseEmulator.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/emulator/controller.ts b/src/emulator/controller.ts index b7f101c8c66..cec97b9ccf4 100644 --- a/src/emulator/controller.ts +++ b/src/emulator/controller.ts @@ -160,8 +160,8 @@ export async function startAll(options: any): Promise { databaseEmulator = new DatabaseEmulator({ host: databaseAddr.host, port: databaseAddr.port, - functionsEmulatorHost: functionsAddr.host, - functionsEmulatorPort: functionsAddr.port, + functions_emulator_host: functionsAddr.host, + functions_emulator_port: functionsAddr.port, }); } else { databaseEmulator = new DatabaseEmulator({ diff --git a/src/emulator/databaseEmulator.ts b/src/emulator/databaseEmulator.ts index 0b50d4f61ce..1449191e938 100644 --- a/src/emulator/databaseEmulator.ts +++ b/src/emulator/databaseEmulator.ts @@ -5,8 +5,8 @@ import { Constants } from "./constants"; interface DatabaseEmulatorArgs { port?: number; host?: string; - functionsEmulatorPort?: number; - functionsEmulatorHost?: string; + functions_emulator_port?: number; + functions_emulator_host?: string; } export class DatabaseEmulator implements EmulatorInstance {