firebase-tools 9.16.6 → 9.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -3
- package/lib/api.js +1 -0
- package/lib/apiv2.js +1 -1
- package/lib/appdistribution/client.js +84 -72
- package/lib/appdistribution/distribution.js +8 -26
- package/lib/appdistribution/options-parser-util.js +51 -0
- package/lib/command.js +8 -6
- package/lib/commands/appdistribution-distribute.js +74 -91
- package/lib/commands/appdistribution-testers-add.js +18 -0
- package/lib/commands/appdistribution-testers-remove.js +32 -0
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/ext-configure.js +9 -1
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-init.js +18 -9
- package/lib/commands/ext-dev-publish.js +11 -4
- package/lib/commands/ext-dev-unpublish.js +2 -1
- package/lib/commands/ext-install.js +115 -48
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +67 -43
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +44 -35
- package/lib/commands/functions-list.js +54 -0
- package/lib/commands/functions-log.js +5 -22
- package/lib/commands/hosting-channel-deploy.js +6 -4
- package/lib/commands/index.js +12 -0
- package/lib/deploy/functions/backend.js +47 -12
- package/lib/deploy/functions/containerCleaner.js +5 -1
- package/lib/deploy/functions/deploy.js +7 -5
- package/lib/deploy/functions/prepare.js +9 -7
- package/lib/deploy/functions/prompts.js +3 -21
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
- package/lib/deploy/functions/runtimes/index.js +2 -1
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +4 -3
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +14 -9
- package/lib/deploy/functions/triggerRegionHelper.js +32 -0
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1758 -404
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +429 -40
- package/lib/emulator/auth/server.js +18 -11
- package/lib/emulator/auth/state.js +186 -5
- package/lib/emulator/auth/widget_ui.js +2 -2
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/emulatorLogger.js +0 -3
- package/lib/emulator/events/types.js +16 -0
- package/lib/emulator/functionsEmulator.js +102 -17
- package/lib/emulator/functionsEmulatorRuntime.js +46 -121
- package/lib/emulator/functionsEmulatorShared.js +51 -7
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/pubsubEmulator.js +61 -40
- package/lib/extensions/askUserForConsent.js +16 -13
- package/lib/extensions/askUserForParam.js +72 -3
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +93 -0
- package/lib/extensions/displayExtensionInfo.js +38 -38
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/extensionsApi.js +69 -95
- package/lib/extensions/extensionsHelper.js +75 -50
- package/lib/extensions/paramHelper.js +79 -36
- package/lib/extensions/refs.js +59 -0
- package/lib/extensions/resolveSource.js +2 -20
- package/lib/extensions/secretsUtils.js +58 -0
- package/lib/extensions/updateHelper.js +39 -105
- package/lib/extensions/warnings.js +1 -7
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/functionslog.js +40 -0
- package/lib/functions/listFunctions.js +10 -0
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/cloudfunctions.js +84 -9
- package/lib/gcp/cloudfunctionsv2.js +99 -7
- package/lib/gcp/cloudlogging.js +27 -21
- package/lib/gcp/secretManager.js +111 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/package.json +5 -4
- package/schema/firebase-config.json +2 -1
- package/templates/extensions/CHANGELOG.md +7 -0
- package/templates/init/hosting/index.html +10 -10
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const functionsEnv = require("../functions/env");
|
|
4
|
-
const error_1 = require("../error");
|
|
5
3
|
const types_1 = require("./types");
|
|
6
4
|
const functionsEmulatorShared_1 = require("./functionsEmulatorShared");
|
|
7
|
-
const constants_1 = require("./constants");
|
|
8
5
|
const functionsEmulatorUtils_1 = require("./functionsEmulatorUtils");
|
|
9
6
|
const express = require("express");
|
|
10
7
|
const path = require("path");
|
|
11
8
|
const bodyParser = require("body-parser");
|
|
12
|
-
const fs = require("fs");
|
|
13
9
|
const url_1 = require("url");
|
|
14
10
|
const _ = require("lodash");
|
|
15
11
|
let triggers;
|
|
@@ -263,13 +259,35 @@ async function initializeFirebaseFunctionsStubs(frb) {
|
|
|
263
259
|
};
|
|
264
260
|
const onCallInnerMethodName = "_onCallWithOptions";
|
|
265
261
|
const onCallMethodOriginal = httpsProvider[onCallInnerMethodName];
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
262
|
+
if (onCallMethodOriginal.length === 3) {
|
|
263
|
+
httpsProvider[onCallInnerMethodName] = (opts, handler, deployOpts) => {
|
|
264
|
+
const wrapped = wrapCallableHandler(handler);
|
|
265
|
+
const cf = onCallMethodOriginal(opts, wrapped, deployOpts);
|
|
266
|
+
return cf;
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
httpsProvider[onCallInnerMethodName] = (handler, opts) => {
|
|
271
|
+
const wrapped = wrapCallableHandler(handler);
|
|
272
|
+
const cf = onCallMethodOriginal(wrapped, opts);
|
|
273
|
+
return cf;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
httpsProvider.onCall = function (optsOrHandler, handler) {
|
|
277
|
+
if (onCallMethodOriginal.length === 3) {
|
|
278
|
+
let opts;
|
|
279
|
+
if (arguments.length === 1) {
|
|
280
|
+
opts = {};
|
|
281
|
+
handler = optsOrHandler;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
opts = optsOrHandler;
|
|
285
|
+
}
|
|
286
|
+
return httpsProvider[onCallInnerMethodName](opts, handler, {});
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
return httpsProvider[onCallInnerMethodName](optsOrHandler, {});
|
|
290
|
+
}
|
|
273
291
|
};
|
|
274
292
|
}
|
|
275
293
|
function wrapCallableHandler(handler) {
|
|
@@ -393,108 +411,6 @@ function warnAboutAuthProd(frb) {
|
|
|
393
411
|
}
|
|
394
412
|
new types_1.EmulatorLog("WARN_ONCE", "runtime-status", "The Firebase Authentication emulator is not running, so calls to Firebase Authentication will affect production.").log();
|
|
395
413
|
}
|
|
396
|
-
async function initializeEnvironmentalVariables(frb) {
|
|
397
|
-
var _a;
|
|
398
|
-
process.env.TZ = "UTC";
|
|
399
|
-
process.env.GCLOUD_PROJECT = frb.projectId;
|
|
400
|
-
process.env.FUNCTIONS_EMULATOR = "true";
|
|
401
|
-
if (functionsEnv.hasUserEnvs({ functionsSource: frb.cwd, projectId: "local" })) {
|
|
402
|
-
try {
|
|
403
|
-
const userEnvs = functionsEnv.loadUserEnvs({ functionsSource: frb.cwd, projectId: "local" });
|
|
404
|
-
for (const [k, v] of Object.entries(userEnvs)) {
|
|
405
|
-
process.env[k] = v;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
catch (e) {
|
|
409
|
-
let message = e.message || `${e}`;
|
|
410
|
-
if (e instanceof error_1.FirebaseError) {
|
|
411
|
-
for (const child of e.children) {
|
|
412
|
-
if (child instanceof Error) {
|
|
413
|
-
message += `\n- ${child.message}`;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
new types_1.EmulatorLog("SYSTEM", "function-env-load-failed", message).log();
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
const configPath = `${frb.cwd}/.runtimeconfig.json`;
|
|
421
|
-
try {
|
|
422
|
-
const configContent = fs.readFileSync(configPath, "utf8");
|
|
423
|
-
if (configContent) {
|
|
424
|
-
try {
|
|
425
|
-
JSON.parse(configContent.toString());
|
|
426
|
-
logDebug(`Found local functions config: ${configPath}`);
|
|
427
|
-
process.env.CLOUD_RUNTIME_CONFIG = configContent.toString();
|
|
428
|
-
}
|
|
429
|
-
catch (e) {
|
|
430
|
-
new types_1.EmulatorLog("SYSTEM", "function-runtimeconfig-json-invalid", "").log();
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
catch (e) {
|
|
435
|
-
}
|
|
436
|
-
const functionsResolution = await assertResolveDeveloperNodeModule(frb, "firebase-functions");
|
|
437
|
-
const functionsGt380 = functionsEmulatorUtils_1.compareVersionStrings(functionsResolution.version, "3.8.0") >= 0;
|
|
438
|
-
let emulatedDatabaseURL = undefined;
|
|
439
|
-
if (frb.emulators.database && functionsGt380) {
|
|
440
|
-
let ns = frb.projectId;
|
|
441
|
-
if (frb.adminSdkConfig.databaseURL) {
|
|
442
|
-
const asUrl = new url_1.URL(frb.adminSdkConfig.databaseURL);
|
|
443
|
-
ns = asUrl.hostname.split(".")[0];
|
|
444
|
-
}
|
|
445
|
-
emulatedDatabaseURL = `http://${formatHost(frb.emulators.database)}/?ns=${ns}`;
|
|
446
|
-
}
|
|
447
|
-
process.env.FIREBASE_CONFIG = JSON.stringify({
|
|
448
|
-
storageBucket: frb.adminSdkConfig.storageBucket,
|
|
449
|
-
databaseURL: emulatedDatabaseURL || frb.adminSdkConfig.databaseURL,
|
|
450
|
-
projectId: frb.projectId,
|
|
451
|
-
});
|
|
452
|
-
if (frb.triggerId) {
|
|
453
|
-
const service = frb.targetName || "";
|
|
454
|
-
const target = service.replace(/-/g, ".");
|
|
455
|
-
const mode = frb.triggerType === functionsEmulatorShared_1.EmulatedTriggerType.BACKGROUND ? "event" : "http";
|
|
456
|
-
let nodeVersion = 0;
|
|
457
|
-
if (frb.nodeMajorVersion) {
|
|
458
|
-
nodeVersion = frb.nodeMajorVersion;
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
const pkg = requirePackageJson(frb);
|
|
462
|
-
if ((_a = pkg === null || pkg === void 0 ? void 0 : pkg.engines) === null || _a === void 0 ? void 0 : _a.node) {
|
|
463
|
-
const nodeSemVer = functionsEmulatorUtils_1.parseVersionString(pkg.engines.node);
|
|
464
|
-
nodeVersion = nodeSemVer.major;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
if (nodeVersion >= 10) {
|
|
468
|
-
setNode10EnvVars(target, mode, service);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
if (frb.emulators.firestore) {
|
|
472
|
-
process.env[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = formatHost(frb.emulators.firestore);
|
|
473
|
-
}
|
|
474
|
-
if (frb.emulators.database) {
|
|
475
|
-
process.env[constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST] = formatHost(frb.emulators.database);
|
|
476
|
-
}
|
|
477
|
-
if (frb.emulators.auth) {
|
|
478
|
-
process.env[constants_1.Constants.FIREBASE_AUTH_EMULATOR_HOST] = formatHost(frb.emulators.auth);
|
|
479
|
-
}
|
|
480
|
-
if (frb.emulators.storage) {
|
|
481
|
-
process.env[constants_1.Constants.FIREBASE_STORAGE_EMULATOR_HOST] = formatHost(frb.emulators.storage);
|
|
482
|
-
process.env[constants_1.Constants.CLOUD_STORAGE_EMULATOR_HOST] = `http://${formatHost(frb.emulators.storage)}`;
|
|
483
|
-
}
|
|
484
|
-
if (frb.emulators.pubsub) {
|
|
485
|
-
const pubsubHost = formatHost(frb.emulators.pubsub);
|
|
486
|
-
process.env.PUBSUB_EMULATOR_HOST = pubsubHost;
|
|
487
|
-
logDebug(`Set PUBSUB_EMULATOR_HOST to ${pubsubHost}`);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function formatHost(info) {
|
|
491
|
-
if (info.host.includes(":")) {
|
|
492
|
-
return `[${info.host}]:${info.port}`;
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
return `${info.host}:${info.port}`;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
414
|
async function initializeFunctionsConfigHelper(frb) {
|
|
499
415
|
const functionsResolution = await assertResolveDeveloperNodeModule(frb, "firebase-functions");
|
|
500
416
|
const localFunctionsModule = require(functionsResolution.resolution);
|
|
@@ -594,9 +510,12 @@ async function processHTTPS(frb, trigger) {
|
|
|
594
510
|
instance.on("error", rejectEphemeralServer);
|
|
595
511
|
});
|
|
596
512
|
}
|
|
597
|
-
async function processBackground(frb, trigger) {
|
|
513
|
+
async function processBackground(frb, trigger, signature) {
|
|
598
514
|
const proto = frb.proto;
|
|
599
515
|
logDebug("ProcessBackground", proto);
|
|
516
|
+
if (signature === "cloudevent") {
|
|
517
|
+
return runCloudEvent(proto, trigger.getRawFunction());
|
|
518
|
+
}
|
|
600
519
|
const data = proto.data;
|
|
601
520
|
delete proto.data;
|
|
602
521
|
const context = proto.context ? proto.context : proto;
|
|
@@ -627,6 +546,12 @@ async function runBackground(proto, func) {
|
|
|
627
546
|
return func(proto.data, proto.context);
|
|
628
547
|
});
|
|
629
548
|
}
|
|
549
|
+
async function runCloudEvent(event, func) {
|
|
550
|
+
logDebug("RunCloudEvent", event);
|
|
551
|
+
await runFunction(() => {
|
|
552
|
+
return func(event);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
630
555
|
async function runHTTPS(args, func) {
|
|
631
556
|
if (args.length < 2) {
|
|
632
557
|
throw new Error("Function must be passed 2 args.");
|
|
@@ -665,8 +590,8 @@ async function invokeTrigger(frb, triggers) {
|
|
|
665
590
|
}).log();
|
|
666
591
|
const trigger = triggers[frb.triggerId];
|
|
667
592
|
logDebug("triggerDefinition", trigger.definition);
|
|
668
|
-
const
|
|
669
|
-
logDebug(`Running ${frb.triggerId} in
|
|
593
|
+
const signature = functionsEmulatorShared_1.getSignatureType(trigger.definition);
|
|
594
|
+
logDebug(`Running ${frb.triggerId} in signature ${signature}`);
|
|
670
595
|
let seconds = 0;
|
|
671
596
|
const timerId = setInterval(() => {
|
|
672
597
|
seconds++;
|
|
@@ -679,11 +604,12 @@ async function invokeTrigger(frb, triggers) {
|
|
|
679
604
|
throw new Error("Function timed out.");
|
|
680
605
|
}, trigger.timeoutMs);
|
|
681
606
|
}
|
|
682
|
-
switch (
|
|
683
|
-
case "
|
|
684
|
-
|
|
607
|
+
switch (signature) {
|
|
608
|
+
case "event":
|
|
609
|
+
case "cloudevent":
|
|
610
|
+
await processBackground(frb, triggers[frb.triggerId], signature);
|
|
685
611
|
break;
|
|
686
|
-
case "
|
|
612
|
+
case "http":
|
|
687
613
|
await processHTTPS(frb, triggers[frb.triggerId]);
|
|
688
614
|
break;
|
|
689
615
|
}
|
|
@@ -700,7 +626,6 @@ async function initializeRuntime(frb, serializedFunctionTrigger, extensionTrigge
|
|
|
700
626
|
new types_1.EmulatorLog("INFO", "runtime-status", `Your functions could not be parsed due to an issue with your node_modules (see above)`).log();
|
|
701
627
|
return;
|
|
702
628
|
}
|
|
703
|
-
await initializeEnvironmentalVariables(frb);
|
|
704
629
|
initializeNetworkFiltering(frb);
|
|
705
630
|
await initializeFunctionsConfigHelper(frb);
|
|
706
631
|
await initializeFirebaseFunctionsStubs(frb);
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.findModuleRoot = exports.waitForBody = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.EmulatedTrigger = exports.HttpConstants =
|
|
3
|
+
exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.EmulatedTrigger = exports.HttpConstants = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const os = require("os");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const fs = require("fs");
|
|
8
|
-
|
|
9
|
-
(function (EmulatedTriggerType) {
|
|
10
|
-
EmulatedTriggerType["BACKGROUND"] = "BACKGROUND";
|
|
11
|
-
EmulatedTriggerType["HTTPS"] = "HTTPS";
|
|
12
|
-
})(EmulatedTriggerType = exports.EmulatedTriggerType || (exports.EmulatedTriggerType = {}));
|
|
8
|
+
const constants_1 = require("./constants");
|
|
13
9
|
const memoryLookup = {
|
|
14
10
|
"128MB": 128,
|
|
15
11
|
"256MB": 256,
|
|
@@ -82,12 +78,44 @@ function getTemporarySocketPath(pid, cwd) {
|
|
|
82
78
|
}
|
|
83
79
|
exports.getTemporarySocketPath = getTemporarySocketPath;
|
|
84
80
|
function getFunctionService(def) {
|
|
81
|
+
var _a;
|
|
85
82
|
if (def.eventTrigger) {
|
|
86
|
-
return def.eventTrigger.service;
|
|
83
|
+
return (_a = def.eventTrigger.service) !== null && _a !== void 0 ? _a : getServiceFromEventType(def.eventTrigger.eventType);
|
|
87
84
|
}
|
|
88
85
|
return "unknown";
|
|
89
86
|
}
|
|
90
87
|
exports.getFunctionService = getFunctionService;
|
|
88
|
+
function getServiceFromEventType(eventType) {
|
|
89
|
+
if (eventType.includes("firestore")) {
|
|
90
|
+
return constants_1.Constants.SERVICE_FIRESTORE;
|
|
91
|
+
}
|
|
92
|
+
if (eventType.includes("database")) {
|
|
93
|
+
return constants_1.Constants.SERVICE_REALTIME_DATABASE;
|
|
94
|
+
}
|
|
95
|
+
if (eventType.includes("pubsub")) {
|
|
96
|
+
return constants_1.Constants.SERVICE_PUBSUB;
|
|
97
|
+
}
|
|
98
|
+
if (eventType.includes("storage")) {
|
|
99
|
+
return constants_1.Constants.SERVICE_STORAGE;
|
|
100
|
+
}
|
|
101
|
+
if (eventType.includes("analytics")) {
|
|
102
|
+
return constants_1.Constants.SERVICE_ANALYTICS;
|
|
103
|
+
}
|
|
104
|
+
if (eventType.includes("auth")) {
|
|
105
|
+
return constants_1.Constants.SERVICE_AUTH;
|
|
106
|
+
}
|
|
107
|
+
if (eventType.includes("crashlytics")) {
|
|
108
|
+
return constants_1.Constants.SERVICE_CRASHLYTICS;
|
|
109
|
+
}
|
|
110
|
+
if (eventType.includes("remoteconfig")) {
|
|
111
|
+
return constants_1.Constants.SERVICE_REMOTE_CONFIG;
|
|
112
|
+
}
|
|
113
|
+
if (eventType.includes("testing")) {
|
|
114
|
+
return constants_1.Constants.SERVICE_TEST_LAB;
|
|
115
|
+
}
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
exports.getServiceFromEventType = getServiceFromEventType;
|
|
91
119
|
function waitForBody(req) {
|
|
92
120
|
let data = "";
|
|
93
121
|
return new Promise((resolve) => {
|
|
@@ -124,3 +152,19 @@ function findModuleRoot(moduleName, filepath) {
|
|
|
124
152
|
return "";
|
|
125
153
|
}
|
|
126
154
|
exports.findModuleRoot = findModuleRoot;
|
|
155
|
+
function formatHost(info) {
|
|
156
|
+
if (info.host.includes(":")) {
|
|
157
|
+
return `[${info.host}]:${info.port}`;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
return `${info.host}:${info.port}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.formatHost = formatHost;
|
|
164
|
+
function getSignatureType(def) {
|
|
165
|
+
if (def.httpsTrigger) {
|
|
166
|
+
return "http";
|
|
167
|
+
}
|
|
168
|
+
return def.platform === "gcfv2" ? "cloudevent" : "event";
|
|
169
|
+
}
|
|
170
|
+
exports.getSignatureType = getSignatureType;
|
|
@@ -42,7 +42,7 @@ class FunctionsEmulatorShell {
|
|
|
42
42
|
auth: opts.auth,
|
|
43
43
|
data,
|
|
44
44
|
};
|
|
45
|
-
this.emu.startFunctionRuntime(trigger.id, trigger.name, functionsEmulatorShared_1.
|
|
45
|
+
this.emu.startFunctionRuntime(trigger.id, trigger.name, functionsEmulatorShared_1.getSignatureType(trigger), proto);
|
|
46
46
|
}
|
|
47
47
|
getTrigger(name) {
|
|
48
48
|
const result = this.triggers.find((trigger) => {
|
|
@@ -19,8 +19,8 @@ class PubsubEmulator {
|
|
|
19
19
|
apiEndpoint: `${host}:${port}`,
|
|
20
20
|
projectId: this.args.projectId,
|
|
21
21
|
});
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
22
|
+
this.triggersForTopic = new Map();
|
|
23
|
+
this.subscriptionForTopic = new Map();
|
|
24
24
|
}
|
|
25
25
|
async start() {
|
|
26
26
|
return downloadableEmulators.start(types_1.Emulators.PUBSUB, this.args);
|
|
@@ -44,10 +44,11 @@ class PubsubEmulator {
|
|
|
44
44
|
getName() {
|
|
45
45
|
return types_1.Emulators.PUBSUB;
|
|
46
46
|
}
|
|
47
|
-
async addTrigger(topicName,
|
|
48
|
-
this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
47
|
+
async addTrigger(topicName, triggerKey, signatureType) {
|
|
48
|
+
this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
|
|
49
|
+
const triggers = this.triggersForTopic.get(topicName) || [];
|
|
50
|
+
if (triggers.some((t) => t.triggerKey === triggerKey) &&
|
|
51
|
+
this.subscriptionForTopic.has(topicName)) {
|
|
51
52
|
this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
@@ -82,53 +83,73 @@ class PubsubEmulator {
|
|
|
82
83
|
sub.on("message", (message) => {
|
|
83
84
|
this.onMessage(topicName, message);
|
|
84
85
|
});
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
86
|
+
triggers.push({ triggerKey, signatureType });
|
|
87
|
+
this.triggersForTopic.set(topicName, triggers);
|
|
88
|
+
this.subscriptionForTopic.set(topicName, sub);
|
|
89
|
+
}
|
|
90
|
+
getRequestOptions(topic, message, signatureType) {
|
|
91
|
+
const baseOpts = {
|
|
92
|
+
origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS).getInfo())}`,
|
|
93
|
+
};
|
|
94
|
+
if (signatureType === "event") {
|
|
95
|
+
return Object.assign(Object.assign({}, baseOpts), { data: {
|
|
96
|
+
context: {
|
|
97
|
+
eventId: uuid.v4(),
|
|
98
|
+
resource: {
|
|
99
|
+
service: "pubsub.googleapis.com",
|
|
100
|
+
name: `projects/${this.args.projectId}/topics/${topic}`,
|
|
101
|
+
},
|
|
102
|
+
eventType: "google.pubsub.topic.publish",
|
|
103
|
+
timestamp: message.publishTime.toISOString(),
|
|
104
|
+
},
|
|
105
|
+
data: {
|
|
106
|
+
data: message.data,
|
|
107
|
+
attributes: message.attributes,
|
|
108
|
+
},
|
|
109
|
+
} });
|
|
110
|
+
}
|
|
111
|
+
else if (signatureType === "cloudevent") {
|
|
112
|
+
const data = {
|
|
113
|
+
message: {
|
|
114
|
+
messageId: message.id,
|
|
115
|
+
publishTime: message.publishTime,
|
|
116
|
+
attributes: message.attributes,
|
|
117
|
+
orderingKey: message.orderingKey,
|
|
118
|
+
data: message.data.toString("base64"),
|
|
119
|
+
},
|
|
120
|
+
subscription: this.subscriptionForTopic.get(topic).name,
|
|
121
|
+
};
|
|
122
|
+
const ce = {
|
|
123
|
+
specVersion: 1,
|
|
124
|
+
type: "google.cloud.pubsub.topic.v1.messagePublished",
|
|
125
|
+
source: `//pubsub.googleapis.com/projects/${this.args.projectId}/topics/${topic}`,
|
|
126
|
+
data,
|
|
127
|
+
};
|
|
128
|
+
return Object.assign(Object.assign({}, baseOpts), { headers: { "Content-Type": "application/cloudevents+json; charset=UTF-8" }, data: ce });
|
|
129
|
+
}
|
|
130
|
+
throw new error_1.FirebaseError(`Unsupported trigger signature: ${signatureType}`);
|
|
88
131
|
}
|
|
89
132
|
async onMessage(topicName, message) {
|
|
90
133
|
this.logger.logLabeled("DEBUG", "pubsub", `onMessage(${topicName}, ${message.id})`);
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
134
|
+
const triggers = this.triggersForTopic.get(topicName);
|
|
135
|
+
if (!triggers || triggers.length === 0) {
|
|
93
136
|
throw new error_1.FirebaseError(`No trigger for topic: ${topicName}`);
|
|
94
137
|
}
|
|
95
|
-
|
|
96
|
-
if (!functionsEmu) {
|
|
138
|
+
if (!registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS)) {
|
|
97
139
|
throw new error_1.FirebaseError(`Attempted to execute pubsub trigger for topic ${topicName} but could not find Functions emulator`);
|
|
98
140
|
}
|
|
99
|
-
this.logger.logLabeled("DEBUG", "pubsub", `Executing ${
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const body = {
|
|
103
|
-
context: {
|
|
104
|
-
eventId: uuid.v4(),
|
|
105
|
-
resource: {
|
|
106
|
-
service: "pubsub.googleapis.com",
|
|
107
|
-
name: `projects/${this.args.projectId}/topics/${topicName}`,
|
|
108
|
-
},
|
|
109
|
-
eventType: "google.pubsub.topic.publish",
|
|
110
|
-
timestamp: message.publishTime.toISOString(),
|
|
111
|
-
},
|
|
112
|
-
data: {
|
|
113
|
-
data: message.data,
|
|
114
|
-
attributes: message.attributes,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
141
|
+
this.logger.logLabeled("DEBUG", "pubsub", `Executing ${triggers.length} matching triggers (${JSON.stringify(triggers.map((t) => t.triggerKey))})`);
|
|
142
|
+
for (const { triggerKey, signatureType } of triggers) {
|
|
143
|
+
const reqOpts = this.getRequestOptions(topicName, message, signatureType);
|
|
117
144
|
try {
|
|
118
|
-
await api.request("POST", `/functions/projects/${this.args.projectId}/triggers/${
|
|
119
|
-
origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(functionsEmu.getInfo())}`,
|
|
120
|
-
data: body,
|
|
121
|
-
});
|
|
145
|
+
await api.request("POST", `/functions/projects/${this.args.projectId}/triggers/${triggerKey}`, reqOpts);
|
|
122
146
|
}
|
|
123
147
|
catch (e) {
|
|
124
148
|
this.logger.logLabeled("DEBUG", "pubsub", e);
|
|
125
149
|
}
|
|
126
|
-
remaining--;
|
|
127
|
-
if (remaining <= 0) {
|
|
128
|
-
this.logger.logLabeled("DEBUG", "pubsub", `Acking message ${message.id}`);
|
|
129
|
-
message.ack();
|
|
130
|
-
}
|
|
131
150
|
}
|
|
151
|
+
this.logger.logLabeled("DEBUG", "pubsub", `Acking message ${message.id}`);
|
|
152
|
+
message.ack();
|
|
132
153
|
}
|
|
133
154
|
}
|
|
134
155
|
exports.PubsubEmulator = PubsubEmulator;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.promptForPublisherTOS = exports.
|
|
3
|
+
exports.promptForPublisherTOS = exports.displayApis = exports.displayRoles = exports.retrieveRoleInfo = exports.formatDescription = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
@@ -27,24 +27,27 @@ async function retrieveRoleInfo(role) {
|
|
|
27
27
|
return `- ${res.title} (${res.description})`;
|
|
28
28
|
}
|
|
29
29
|
exports.retrieveRoleInfo = retrieveRoleInfo;
|
|
30
|
-
async function
|
|
31
|
-
if (!roles
|
|
30
|
+
async function displayRoles(extensionName, projectId, roles) {
|
|
31
|
+
if (!roles.length) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
const message = await formatDescription(extensionName, projectId, roles);
|
|
35
35
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, message);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
const consented = await prompt_1.promptOnce(question);
|
|
43
|
-
if (!consented) {
|
|
44
|
-
throw new error_1.FirebaseError("Without explicit consent for the roles listed, we cannot deploy this extension.");
|
|
36
|
+
}
|
|
37
|
+
exports.displayRoles = displayRoles;
|
|
38
|
+
function displayApis(extensionName, projectId, apis) {
|
|
39
|
+
if (!apis.length) {
|
|
40
|
+
return;
|
|
45
41
|
}
|
|
42
|
+
const question = `${clc.bold(extensionName)} will enable the following APIs for project ${clc.bold(projectId)}`;
|
|
43
|
+
const results = apis.map((api) => {
|
|
44
|
+
return `- ${api.apiName}: ${api.reason}`;
|
|
45
|
+
});
|
|
46
|
+
results.unshift(question);
|
|
47
|
+
const message = results.join("\n");
|
|
48
|
+
utils.logLabeledBullet(extensionsHelper_1.logPrefix, message);
|
|
46
49
|
}
|
|
47
|
-
exports.
|
|
50
|
+
exports.displayApis = displayApis;
|
|
48
51
|
async function promptForPublisherTOS() {
|
|
49
52
|
const termsOfServiceMsg = "By registering as a publisher, you confirm that you have read the Firebase Extensions Publisher Terms and Conditions (linked below) and you, on behalf of yourself and the organization you represent, agree to comply with it. Here is a brief summary of the highlights of our terms and conditions:\n" +
|
|
50
53
|
" - You ensure extensions you publish comply with all laws and regulations; do not include any viruses, spyware, Trojan horses, or other malicious code; and do not violate any person’s rights, including intellectual property, privacy, and security rights.\n" +
|
|
@@ -5,11 +5,18 @@ const _ = require("lodash");
|
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
7
7
|
const extensionsApi_1 = require("./extensionsApi");
|
|
8
|
+
const secretManagerApi = require("../gcp/secretManager");
|
|
9
|
+
const secretsUtils = require("./secretsUtils");
|
|
8
10
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
9
11
|
const utils_1 = require("./utils");
|
|
10
12
|
const logger_1 = require("../logger");
|
|
11
13
|
const prompt_1 = require("../prompt");
|
|
12
14
|
const utils = require("../utils");
|
|
15
|
+
var SecretUpdateAction;
|
|
16
|
+
(function (SecretUpdateAction) {
|
|
17
|
+
SecretUpdateAction[SecretUpdateAction["LEAVE"] = 0] = "LEAVE";
|
|
18
|
+
SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 1] = "SET_NEW";
|
|
19
|
+
})(SecretUpdateAction || (SecretUpdateAction = {}));
|
|
13
20
|
function checkResponse(response, spec) {
|
|
14
21
|
let valid = true;
|
|
15
22
|
let responses;
|
|
@@ -48,7 +55,7 @@ function checkResponse(response, spec) {
|
|
|
48
55
|
return valid;
|
|
49
56
|
}
|
|
50
57
|
exports.checkResponse = checkResponse;
|
|
51
|
-
async function askForParam(paramSpec) {
|
|
58
|
+
async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
|
|
52
59
|
let valid = false;
|
|
53
60
|
let response = "";
|
|
54
61
|
const description = paramSpec.description || "";
|
|
@@ -89,6 +96,11 @@ async function askForParam(paramSpec) {
|
|
|
89
96
|
choices: utils_1.convertExtensionOptionToLabeledList(paramSpec.options),
|
|
90
97
|
});
|
|
91
98
|
break;
|
|
99
|
+
case extensionsApi_1.ParamType.SECRET:
|
|
100
|
+
response = reconfiguring
|
|
101
|
+
? await promptReconfigureSecret(projectId, instanceId, paramSpec)
|
|
102
|
+
: await promptCreateSecret(projectId, instanceId, paramSpec);
|
|
103
|
+
break;
|
|
92
104
|
default:
|
|
93
105
|
response = await prompt_1.promptOnce({
|
|
94
106
|
name: paramSpec.param,
|
|
@@ -102,6 +114,63 @@ async function askForParam(paramSpec) {
|
|
|
102
114
|
return response;
|
|
103
115
|
}
|
|
104
116
|
exports.askForParam = askForParam;
|
|
117
|
+
async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
|
|
118
|
+
const action = await prompt_1.promptOnce({
|
|
119
|
+
type: "list",
|
|
120
|
+
message: `Choose what you would like to do with this secret:`,
|
|
121
|
+
choices: [
|
|
122
|
+
{ name: "Leave unchanged", value: SecretUpdateAction.LEAVE },
|
|
123
|
+
{ name: "Set new value", value: SecretUpdateAction.SET_NEW },
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
switch (action) {
|
|
127
|
+
case SecretUpdateAction.SET_NEW:
|
|
128
|
+
let secret;
|
|
129
|
+
let secretName;
|
|
130
|
+
if (paramSpec.default) {
|
|
131
|
+
secret = secretManagerApi.parseSecretResourceName(paramSpec.default);
|
|
132
|
+
secretName = secret.name;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
secretName = await generateSecretName(projectId, instanceId, paramSpec.param);
|
|
136
|
+
}
|
|
137
|
+
const secretValue = await prompt_1.promptOnce({
|
|
138
|
+
name: paramSpec.param,
|
|
139
|
+
type: "password",
|
|
140
|
+
message: `This secret will be stored in Cloud Secret Manager as ${secretName}.\nEnter new value for ${paramSpec.label.trim()}:`,
|
|
141
|
+
});
|
|
142
|
+
if (!secret) {
|
|
143
|
+
secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
|
|
144
|
+
}
|
|
145
|
+
return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
|
|
146
|
+
case SecretUpdateAction.LEAVE:
|
|
147
|
+
default:
|
|
148
|
+
return paramSpec.default || "";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function promptCreateSecret(projectId, instanceId, paramSpec) {
|
|
152
|
+
const secretName = await generateSecretName(projectId, instanceId, paramSpec.param);
|
|
153
|
+
const secretValue = await prompt_1.promptOnce({
|
|
154
|
+
name: paramSpec.param,
|
|
155
|
+
type: "password",
|
|
156
|
+
default: paramSpec.default,
|
|
157
|
+
message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${secretName} and managed by Firebase Extensions (Firebase Extensions Service Agent will be granted Secret Admin role on this secret).\nEnter a value for ${paramSpec.label.trim()}:`,
|
|
158
|
+
});
|
|
159
|
+
const secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
|
|
160
|
+
return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
|
|
161
|
+
}
|
|
162
|
+
async function generateSecretName(projectId, instanceId, paramName) {
|
|
163
|
+
let secretName = `ext-${instanceId}-${paramName}`;
|
|
164
|
+
while (await secretManagerApi.secretExists(projectId, secretName)) {
|
|
165
|
+
secretName += `-${utils_1.getRandomString(3)}`;
|
|
166
|
+
}
|
|
167
|
+
return secretName;
|
|
168
|
+
}
|
|
169
|
+
async function addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue) {
|
|
170
|
+
const version = await secretManagerApi.addVersion(secret, secretValue);
|
|
171
|
+
await secretsUtils.grantFirexServiceAgentSecretAdminRole(secret);
|
|
172
|
+
return `projects/${version.secret.projectId}/secrets/${version.secret.name}/versions/${version.versionId}`;
|
|
173
|
+
}
|
|
105
174
|
function getInquirerDefault(options, def) {
|
|
106
175
|
const defaultOption = _.find(options, (option) => {
|
|
107
176
|
return option.value === def;
|
|
@@ -109,7 +178,7 @@ function getInquirerDefault(options, def) {
|
|
|
109
178
|
return defaultOption ? defaultOption.label || defaultOption.value : "";
|
|
110
179
|
}
|
|
111
180
|
exports.getInquirerDefault = getInquirerDefault;
|
|
112
|
-
async function ask(paramSpecs, firebaseProjectParams) {
|
|
181
|
+
async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
|
|
113
182
|
if (_.isEmpty(paramSpecs)) {
|
|
114
183
|
logger_1.logger.debug("No params were specified for this extension.");
|
|
115
184
|
return {};
|
|
@@ -119,7 +188,7 @@ async function ask(paramSpecs, firebaseProjectParams) {
|
|
|
119
188
|
const result = {};
|
|
120
189
|
const promises = _.map(substituted, (paramSpec) => {
|
|
121
190
|
return async () => {
|
|
122
|
-
result[paramSpec.param] = await askForParam(paramSpec);
|
|
191
|
+
result[paramSpec.param] = await askForParam(projectId, instanceId, paramSpec, reconfiguring);
|
|
123
192
|
};
|
|
124
193
|
});
|
|
125
194
|
await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
|
|
@@ -36,19 +36,9 @@ function hasRuntime(spec, runtime) {
|
|
|
36
36
|
const resources = spec.resources || [];
|
|
37
37
|
return resources.some((r) => { var _a; return runtime === (((_a = r.properties) === null || _a === void 0 ? void 0 : _a.runtime) || defaultRuntime); });
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
function displayNode10UpdateBillingNotice(curSpec, newSpec) {
|
|
40
40
|
if (hasRuntime(curSpec, "nodejs8") && hasRuntime(newSpec, "nodejs10")) {
|
|
41
41
|
utils.logLabeledWarning(extensionsHelper_1.logPrefix, marked(billingMsgUpdate));
|
|
42
|
-
if (prompt) {
|
|
43
|
-
const continueUpdate = await prompt_1.promptOnce({
|
|
44
|
-
type: "confirm",
|
|
45
|
-
message: "Do you wish to continue?",
|
|
46
|
-
default: true,
|
|
47
|
-
});
|
|
48
|
-
if (!continueUpdate) {
|
|
49
|
-
throw new error_1.FirebaseError(`Cancelled.`, { exit: 2 });
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
42
|
}
|
|
53
43
|
}
|
|
54
44
|
exports.displayNode10UpdateBillingNotice = displayNode10UpdateBillingNotice;
|