firebase-tools 10.4.1 → 10.6.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.
Files changed (57) hide show
  1. package/lib/bin/firebase.js +1 -1
  2. package/lib/commands/emulators-start.js +6 -1
  3. package/lib/commands/ext-configure.js +4 -4
  4. package/lib/commands/ext-dev-emulators-start.js +5 -1
  5. package/lib/commands/ext-install.js +5 -4
  6. package/lib/commands/ext-update.js +2 -1
  7. package/lib/commands/functions-secrets-destroy.js +23 -3
  8. package/lib/commands/functions-secrets-prune.js +15 -12
  9. package/lib/commands/functions-secrets-set.js +51 -4
  10. package/lib/deploy/functions/backend.js +1 -5
  11. package/lib/deploy/functions/checkIam.js +44 -1
  12. package/lib/deploy/functions/prepare.js +13 -3
  13. package/lib/deploy/functions/release/fabricator.js +1 -3
  14. package/lib/deploy/functions/release/index.js +21 -0
  15. package/lib/deploy/functions/release/planner.js +1 -2
  16. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +11 -10
  17. package/lib/deploy/functions/runtimes/node/index.js +1 -1
  18. package/lib/deploy/functions/runtimes/node/parseTriggers.js +5 -19
  19. package/lib/deploy/functions/services/firebaseAlerts.js +1 -17
  20. package/lib/deploy/functions/services/index.js +2 -1
  21. package/lib/deploy/functions/services/storage.js +1 -6
  22. package/lib/emulator/auth/operations.js +21 -20
  23. package/lib/emulator/auth/state.js +79 -43
  24. package/lib/emulator/commandUtils.js +72 -2
  25. package/lib/emulator/controller.js +9 -0
  26. package/lib/emulator/downloadableEmulators.js +13 -6
  27. package/lib/emulator/extensions/postinstall.js +41 -0
  28. package/lib/emulator/functionsEmulator.js +8 -18
  29. package/lib/emulator/functionsEmulatorShared.js +41 -19
  30. package/lib/emulator/shared/request.js +19 -0
  31. package/lib/emulator/storage/apis/firebase.js +25 -33
  32. package/lib/emulator/storage/apis/gcloud.js +78 -63
  33. package/lib/emulator/storage/files.js +48 -52
  34. package/lib/emulator/storage/index.js +23 -3
  35. package/lib/emulator/storage/metadata.js +18 -8
  36. package/lib/emulator/storage/rules/manager.js +7 -17
  37. package/lib/emulator/storage/rules/utils.js +11 -3
  38. package/lib/emulator/storage/server.js +38 -12
  39. package/lib/extensions/askUserForParam.js +25 -20
  40. package/lib/extensions/emulator/optionsHelper.js +5 -7
  41. package/lib/extensions/emulator/triggerHelper.js +11 -14
  42. package/lib/extensions/extensionsApi.js +2 -1
  43. package/lib/extensions/extensionsHelper.js +11 -2
  44. package/lib/extensions/manifest.js +1 -1
  45. package/lib/extensions/paramHelper.js +23 -13
  46. package/lib/functions/env.js +10 -2
  47. package/lib/functions/runtimeConfigExport.js +10 -6
  48. package/lib/functions/secrets.js +99 -6
  49. package/lib/gcp/cloudfunctions.js +6 -13
  50. package/lib/gcp/cloudfunctionsv2.js +14 -23
  51. package/lib/gcp/cloudtasks.js +5 -3
  52. package/lib/gcp/secretManager.js +1 -1
  53. package/lib/requirePermissions.js +4 -1
  54. package/lib/utils.js +30 -1
  55. package/npm-shrinkwrap.json +2 -2
  56. package/package.json +1 -1
  57. package/lib/emulator/storage/list.js +0 -18
@@ -20,7 +20,7 @@ marked.setOptions({
20
20
  renderer: new TerminalRenderer(),
21
21
  });
22
22
  const updateMessage = `Update available ${clc.xterm(240)("{currentVersion}")} → ${clc.green("{latestVersion}")}\n` +
23
- `To update to the latest version using npm, run ${clc.cyan("npm install -g firebase-tools")}\n` +
23
+ `To update to the latest version using npm, run\n${clc.cyan("npm install -g firebase-tools")}\n` +
24
24
  `For other CLI management options, visit the ${marked("[CLI documentation](https://firebase.google.com/docs/cli#update-cli)")}`;
25
25
  updateNotifier.notify({ defer: true, isGlobal: true, message: updateMessage });
26
26
  const client = require("..");
@@ -8,6 +8,7 @@ const registry_1 = require("../emulator/registry");
8
8
  const types_1 = require("../emulator/types");
9
9
  const clc = require("cli-color");
10
10
  const constants_1 = require("../emulator/constants");
11
+ const utils_1 = require("../utils");
11
12
  const Table = require("cli-table");
12
13
  function stylizeLink(url) {
13
14
  return clc.underline(clc.bold(url));
@@ -22,8 +23,9 @@ module.exports = new command_1.Command("emulators:start")
22
23
  .option(commandUtils.FLAG_EXPORT_ON_EXIT, commandUtils.DESC_EXPORT_ON_EXIT)
23
24
  .action(async (options) => {
24
25
  const killSignalPromise = commandUtils.shutdownWhenKilled(options);
26
+ let deprecationNotices;
25
27
  try {
26
- await controller.startAll(options);
28
+ ({ deprecationNotices } = await controller.startAll(options));
27
29
  }
28
30
  catch (e) {
29
31
  await controller.cleanShutdown();
@@ -85,5 +87,8 @@ ${clc.blackBright(" Other reserved ports:")} ${reservedPortsString}
85
87
 
86
88
  Issues? Report them at ${stylizeLink("https://github.com/firebase/firebase-tools/issues")} and attach the *-debug.log files.
87
89
  `);
90
+ for (const notice of deprecationNotices) {
91
+ (0, utils_1.logLabeledWarning)("emulators", notice, "warn");
92
+ }
88
93
  await killSignalPromise;
89
94
  });
@@ -35,7 +35,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
35
35
  .before(extensionsHelper_1.diagnoseAndFixProject)
36
36
  .action(async (instanceId, options) => {
37
37
  var _a;
38
- const projectId = (0, projectUtils_1.needProjectId)(options);
38
+ const projectId = (0, projectUtils_1.getProjectId)(options);
39
39
  if (options.local) {
40
40
  if (options.nonInteractive) {
41
41
  throw new error_1.FirebaseError(`Command not supported in non-interactive mode, edit ./extensions/${instanceId}.env directly instead`);
@@ -77,7 +77,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
77
77
  try {
78
78
  let existingInstance;
79
79
  try {
80
- existingInstance = await extensionsApi.getInstance(projectId, instanceId);
80
+ existingInstance = await extensionsApi.getInstance((0, projectUtils_1.needProjectId)({ projectId }), instanceId);
81
81
  }
82
82
  catch (err) {
83
83
  if (err.status === 404) {
@@ -113,13 +113,13 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
113
113
  }
114
114
  spinner.start();
115
115
  const res = await extensionsApi.configureInstance({
116
- projectId,
116
+ projectId: (0, projectUtils_1.needProjectId)({ projectId }),
117
117
  instanceId,
118
118
  params: paramBindings,
119
119
  });
120
120
  spinner.stop();
121
121
  utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `successfully configured ${clc.bold(instanceId)}.`);
122
- utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl(projectId, `/extensions/instances/${instanceId}?tab=config`)}`));
122
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl((0, projectUtils_1.needProjectId)({ projectId }), `/extensions/instances/${instanceId}?tab=config`)}`));
123
123
  manifest.showDeprecationWarning();
124
124
  return res;
125
125
  }
@@ -19,9 +19,10 @@ module.exports = new command_1.Command("ext:dev:emulators:start")
19
19
  .action(async (options) => {
20
20
  const killSignalPromise = commandUtils.shutdownWhenKilled(options);
21
21
  const emulatorOptions = await optionsHelper.buildOptions(options);
22
+ let deprecationNotices;
22
23
  try {
23
24
  commandUtils.beforeEmulatorCommand(emulatorOptions);
24
- await controller.startAll(emulatorOptions);
25
+ ({ deprecationNotices } = await controller.startAll(emulatorOptions));
25
26
  }
26
27
  catch (e) {
27
28
  await controller.cleanShutdown();
@@ -31,5 +32,8 @@ module.exports = new command_1.Command("ext:dev:emulators:start")
31
32
  throw e;
32
33
  }
33
34
  utils.logSuccess("All emulators ready, it is now safe to connect.");
35
+ for (const notice of deprecationNotices) {
36
+ utils.logLabeledWarning("emulators", notice, "warn");
37
+ }
34
38
  await killSignalPromise;
35
39
  });
@@ -47,7 +47,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
47
47
  .before(extensionsHelper_1.diagnoseAndFixProject)
48
48
  .action(async (extensionName, options) => {
49
49
  var _a;
50
- const projectId = (0, projectUtils_1.needProjectId)(options);
50
+ const projectId = (0, projectUtils_1.getProjectId)(options);
51
51
  const paramsEnvPath = ((_a = options.params) !== null && _a !== void 0 ? _a : "");
52
52
  let learnMore = false;
53
53
  if (!extensionName) {
@@ -71,7 +71,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
71
71
  if (options.local) {
72
72
  throw new error_1.FirebaseError("Installing a local source locally is not supported yet, please use ext:dev:emulator commands");
73
73
  }
74
- source = await infoInstallBySource(projectId, extensionName);
74
+ source = await infoInstallBySource((0, projectUtils_1.needProjectId)({ projectId }), extensionName);
75
75
  }
76
76
  else {
77
77
  void (0, track_1.track)("Extension Install", "Install by Extension Ref", options.interactive ? 1 : 0);
@@ -120,7 +120,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
120
120
  try {
121
121
  return installExtension({
122
122
  paramsEnvPath,
123
- projectId,
123
+ projectId: projectId,
124
124
  extensionName,
125
125
  source,
126
126
  extVersion,
@@ -195,7 +195,8 @@ async function installToManifest(options) {
195
195
  manifest.showPreviewWarning();
196
196
  }
197
197
  async function installExtension(options) {
198
- const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = options;
198
+ const { extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } = options;
199
+ const projectId = (0, projectUtils_1.needProjectId)({ projectId: options.projectId });
199
200
  const spec = (source === null || source === void 0 ? void 0 : source.spec) || (extVersion === null || extVersion === void 0 ? void 0 : extVersion.spec);
200
201
  if (!spec) {
201
202
  throw new error_1.FirebaseError(`Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.`);
@@ -42,8 +42,8 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
42
42
  .option("--local", "save the update to firebase.json rather than directly update an existing Extension instance on a Firebase project")
43
43
  .action(async (instanceId, updateSource, options) => {
44
44
  var _a, _b;
45
- const projectId = (0, projectUtils_1.needProjectId)(options);
46
45
  if (options.local) {
46
+ const projectId = (0, projectUtils_1.getProjectId)(options);
47
47
  const config = manifest.loadConfig(options);
48
48
  const oldRef = manifest.getInstanceRef(instanceId, config);
49
49
  const oldExtensionVersion = await extensionsApi.getExtensionVersion(refs.toExtensionVersionRef(oldRef));
@@ -95,6 +95,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
95
95
  }
96
96
  const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
97
97
  try {
98
+ const projectId = (0, projectUtils_1.needProjectId)(options);
98
99
  let existingInstance;
99
100
  try {
100
101
  existingInstance = await extensionsApi.getInstance(projectId, instanceId);
@@ -1,21 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const command_1 = require("../command");
4
- const logger_1 = require("../logger");
5
4
  const projectUtils_1 = require("../projectUtils");
6
5
  const secretManager_1 = require("../gcp/secretManager");
7
6
  const prompt_1 = require("../prompt");
7
+ const utils_1 = require("../utils");
8
8
  const secrets = require("../functions/secrets");
9
+ const backend = require("../deploy/functions/backend");
9
10
  exports.default = new command_1.Command("functions:secrets:destroy <KEY>[@version]")
10
11
  .description("Destroy a secret. Defaults to destroying the latest version.")
11
12
  .withForce("Destroys a secret without confirmation.")
12
13
  .action(async (key, options) => {
13
14
  const projectId = (0, projectUtils_1.needProjectId)(options);
15
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
16
+ const haveBackend = await backend.existingBackend({ projectId });
14
17
  let [name, version] = key.split("@");
15
18
  if (!version) {
16
19
  version = "latest";
17
20
  }
18
21
  const sv = await (0, secretManager_1.getSecretVersion)(projectId, name, version);
22
+ if (sv.state === "DESTROYED") {
23
+ (0, utils_1.logBullet)(`Secret ${sv.secret.name}@${version} is already destroyed. Nothing to do.`);
24
+ return;
25
+ }
26
+ const boundEndpoints = backend
27
+ .allEndpoints(haveBackend)
28
+ .filter((e) => secrets.inUse({ projectId, projectNumber }, sv.secret, e));
29
+ if (boundEndpoints.length > 0) {
30
+ const endpointsMsg = boundEndpoints
31
+ .map((e) => `${e.id}[${e.platform}](${e.region})`)
32
+ .join("\t\n");
33
+ (0, utils_1.logWarning)(`Secret ${name}@${version} is currently in use by following functions:\n\t${endpointsMsg}`);
34
+ if (!options.force) {
35
+ (0, utils_1.logWarning)("Refusing to destroy secret in use. Use -f to destroy the secret anyway.");
36
+ return;
37
+ }
38
+ }
19
39
  if (!options.force) {
20
40
  const confirm = await (0, prompt_1.promptOnce)({
21
41
  name: "destroy",
@@ -28,12 +48,12 @@ exports.default = new command_1.Command("functions:secrets:destroy <KEY>[@versio
28
48
  }
29
49
  }
30
50
  await (0, secretManager_1.destroySecretVersion)(projectId, name, version);
31
- logger_1.logger.info(`Destroyed secret version ${name}@${sv.versionId}`);
51
+ (0, utils_1.logBullet)(`Destroyed secret version ${name}@${sv.versionId}`);
32
52
  const secret = await (0, secretManager_1.getSecret)(projectId, name);
33
53
  if (secrets.isFirebaseManaged(secret)) {
34
54
  const versions = await (0, secretManager_1.listSecretVersions)(projectId, name);
35
55
  if (versions.filter((v) => v.state === "ENABLED").length === 0) {
36
- logger_1.logger.info(`No active secret versions left. Destroying secret ${name}`);
56
+ (0, utils_1.logBullet)(`No active secret versions left. Destroying secret ${name}`);
37
57
  await (0, secretManager_1.deleteSecret)(projectId, name);
38
58
  }
39
59
  }
@@ -10,6 +10,7 @@ const utils_1 = require("../utils");
10
10
  const prompt_1 = require("../prompt");
11
11
  const secretManager_1 = require("../gcp/secretManager");
12
12
  exports.default = new command_1.Command("functions:secrets:prune")
13
+ .withForce("Destroys unused secrets without prompt")
13
14
  .description("Destroys unused secrets")
14
15
  .before(requirePermissions_1.requirePermissions, [
15
16
  "cloudfunctions.functions.list",
@@ -32,18 +33,20 @@ exports.default = new command_1.Command("functions:secrets:prune")
32
33
  }
33
34
  (0, utils_1.logBullet)(`Found ${pruned.length} unused active secret versions:\n\t` +
34
35
  pruned.map((sv) => `${sv.secret}@${sv.version}`).join("\n\t"));
35
- const confirm = await (0, prompt_1.promptOnce)({
36
- name: "destroy",
37
- type: "confirm",
38
- default: true,
39
- message: `Do you want to destroy unused secret versions?`,
40
- }, options);
41
- if (!confirm) {
42
- (0, utils_1.logBullet)("Run the following commands to destroy each unused secret version:\n\t" +
43
- pruned
44
- .map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`)
45
- .join("\n\t"));
46
- return;
36
+ if (!options.force) {
37
+ const confirm = await (0, prompt_1.promptOnce)({
38
+ name: "destroy",
39
+ type: "confirm",
40
+ default: true,
41
+ message: `Do you want to destroy unused secret versions?`,
42
+ }, options);
43
+ if (!confirm) {
44
+ (0, utils_1.logBullet)("Run the following commands to destroy each unused secret version:\n\t" +
45
+ pruned
46
+ .map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`)
47
+ .join("\n\t"));
48
+ return;
49
+ }
47
50
  }
48
51
  await Promise.all(pruned.map((sv) => (0, secretManager_1.destroySecretVersion)(projectId, sv.secret, sv.version)));
49
52
  (0, utils_1.logSuccess)("Destroyed all unused secrets!");
@@ -10,9 +10,11 @@ const prompt_1 = require("../prompt");
10
10
  const utils_1 = require("../utils");
11
11
  const projectUtils_1 = require("../projectUtils");
12
12
  const secretManager_1 = require("../gcp/secretManager");
13
+ const secrets = require("../functions/secrets");
14
+ const backend = require("../deploy/functions/backend");
13
15
  exports.default = new command_1.Command("functions:secrets:set <KEY>")
14
- .description("Create or update a secret for use in Cloud Functions for Firebase")
15
- .withForce("Does not ensure input keys are valid or upgrade existing secrets to have Firebase manage them.")
16
+ .description("Create or update a secret for use in Cloud Functions for Firebase.")
17
+ .withForce("Automatically updates functions to use the new secret.")
16
18
  .before(requirePermissions_1.requirePermissions, [
17
19
  "secretmanager.secrets.create",
18
20
  "secretmanager.secrets.get",
@@ -22,6 +24,7 @@ exports.default = new command_1.Command("functions:secrets:set <KEY>")
22
24
  .option("--data-file <dataFile>", 'File path from which to read secret data. Set to "-" to read the secret data from stdin.')
23
25
  .action(async (unvalidatedKey, options) => {
24
26
  const projectId = (0, projectUtils_1.needProjectId)(options);
27
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
25
28
  const key = await (0, secrets_1.ensureValidKey)(unvalidatedKey, options);
26
29
  const secret = await (0, secrets_1.ensureSecret)(projectId, key, options);
27
30
  let secretValue;
@@ -41,6 +44,50 @@ exports.default = new command_1.Command("functions:secrets:set <KEY>")
41
44
  }
42
45
  const secretVersion = await (0, secretManager_1.addVersion)(projectId, key, secretValue);
43
46
  (0, utils_1.logSuccess)(`Created a new secret version ${(0, secretManager_1.toSecretVersionResourceName)(secretVersion)}`);
44
- (0, utils_1.logBullet)("Please deploy your functions for the change to take effect by running:\n\t" +
45
- clc.bold("firebase deploy --only functions"));
47
+ if (!secrets.isFirebaseManaged(secret)) {
48
+ (0, utils_1.logBullet)("Please deploy your functions for the change to take effect by running:\n\t" +
49
+ clc.bold("firebase deploy --only functions"));
50
+ return;
51
+ }
52
+ const haveBackend = await backend.existingBackend({ projectId });
53
+ const endpointsToUpdate = backend
54
+ .allEndpoints(haveBackend)
55
+ .filter((e) => secrets.inUse({ projectId, projectNumber }, secret, e));
56
+ if (endpointsToUpdate.length === 0) {
57
+ return;
58
+ }
59
+ (0, utils_1.logBullet)(`${endpointsToUpdate.length} functions are using stale version of secret ${secret.name}:\n\t` +
60
+ endpointsToUpdate.map((e) => `${e.id}(${e.region})`).join("\n\t"));
61
+ if (!options.force) {
62
+ const confirm = await (0, prompt_1.promptOnce)({
63
+ name: "redeploy",
64
+ type: "confirm",
65
+ default: true,
66
+ message: `Do you want to re-deploy the functions and destroy the stale version of secret ${secret.name}?`,
67
+ }, options);
68
+ if (!confirm) {
69
+ (0, utils_1.logBullet)("Please deploy your functions for the change to take effect by running:\n\t" +
70
+ clc.bold("firebase deploy --only functions"));
71
+ return;
72
+ }
73
+ }
74
+ const updateOps = endpointsToUpdate.map(async (e) => {
75
+ (0, utils_1.logBullet)(`Updating function ${e.id}(${e.region})...`);
76
+ const updated = await secrets.updateEndpointSecret({ projectId, projectNumber }, secretVersion, e);
77
+ (0, utils_1.logBullet)(`Updated function ${e.id}(${e.region}).`);
78
+ return updated;
79
+ });
80
+ const updatedEndpoints = await Promise.all(updateOps);
81
+ (0, utils_1.logBullet)(`Pruning stale secrets...`);
82
+ const prunedResult = await (0, secrets_1.pruneAndDestroySecrets)({ projectId, projectNumber }, updatedEndpoints);
83
+ if (prunedResult.destroyed.length > 0) {
84
+ (0, utils_1.logBullet)(`Detroyed unused secret versions: ${prunedResult.destroyed
85
+ .map((s) => `${s.secret}@${s.version}`)
86
+ .join(", ")}`);
87
+ }
88
+ if (prunedResult.erred.length > 0) {
89
+ (0, utils_1.logWarning)(`Failed to destroy unused secret versions:\n\t${prunedResult.erred
90
+ .map((err) => err.message)
91
+ .join("\n\t")}`);
92
+ }
46
93
  });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compareFunctions = exports.findEventFilter = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
3
+ exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
6
  const utils = require("../../utils");
@@ -230,10 +230,6 @@ const missingEndpoint = (backend) => (endpoint) => {
230
230
  return !(0, exports.hasEndpoint)(backend)(endpoint);
231
231
  };
232
232
  exports.missingEndpoint = missingEndpoint;
233
- function findEventFilter(endpoint, attribute) {
234
- return endpoint.eventTrigger.eventFilters.find((ef) => ef.attribute === attribute);
235
- }
236
- exports.findEventFilter = findEventFilter;
237
233
  function compareFunctions(left, right) {
238
234
  if (left.platform !== right.platform) {
239
235
  return right.platform < left.platform ? -1 : 1;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureServiceAgentRoles = exports.mergeBindings = exports.checkHttpIam = exports.checkServiceAccountIam = void 0;
3
+ exports.ensureServiceAgentRoles = exports.mergeBindings = exports.obtainEventarcServiceAgentBindings = exports.obtainDefaultComputeServiceAgentBindings = exports.obtainPubSubServiceAgentBindings = exports.obtainBinding = exports.checkHttpIam = exports.checkServiceAccountIam = exports.EVENTARC_SERVICE_AGENT_ROLE = exports.EVENTARC_EVENT_RECEIVER_ROLE = exports.RUN_INVOKER_ROLE = exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = void 0;
4
4
  const cli_color_1 = require("cli-color");
5
5
  const logger_1 = require("../../logger");
6
6
  const functionsDeployHelper_1 = require("./functionsDeployHelper");
@@ -12,6 +12,10 @@ const utils = require("../../utils");
12
12
  const resourceManager_1 = require("../../gcp/resourceManager");
13
13
  const services_1 = require("./services");
14
14
  const PERMISSION = "cloudfunctions.functions.setIamPolicy";
15
+ exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = "roles/iam.serviceAccountTokenCreator";
16
+ exports.RUN_INVOKER_ROLE = "roles/run.invoker";
17
+ exports.EVENTARC_EVENT_RECEIVER_ROLE = "roles/eventarc.eventReceiver";
18
+ exports.EVENTARC_SERVICE_AGENT_ROLE = "roles/eventarc.serviceAgent";
15
19
  async function checkServiceAccountIam(projectId) {
16
20
  const saEmail = `${projectId}@appspot.gserviceaccount.com`;
17
21
  let passed = false;
@@ -67,6 +71,37 @@ function reduceEventsToServices(services, endpoint) {
67
71
  }
68
72
  return services;
69
73
  }
74
+ function obtainBinding(existingPolicy, serviceAccount, role) {
75
+ let binding = existingPolicy.bindings.find((b) => b.role === role);
76
+ if (!binding) {
77
+ binding = {
78
+ role,
79
+ members: [],
80
+ };
81
+ }
82
+ if (!binding.members.find((m) => m === serviceAccount)) {
83
+ binding.members.push(serviceAccount);
84
+ }
85
+ return binding;
86
+ }
87
+ exports.obtainBinding = obtainBinding;
88
+ function obtainPubSubServiceAgentBindings(projectNumber, existingPolicy) {
89
+ const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
90
+ return [obtainBinding(existingPolicy, pubsubServiceAgent, exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE)];
91
+ }
92
+ exports.obtainPubSubServiceAgentBindings = obtainPubSubServiceAgentBindings;
93
+ function obtainDefaultComputeServiceAgentBindings(projectNumber, existingPolicy) {
94
+ const defaultComputeServiceAgent = `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`;
95
+ const invokerBinding = obtainBinding(existingPolicy, defaultComputeServiceAgent, exports.RUN_INVOKER_ROLE);
96
+ const eventReceiverBinding = obtainBinding(existingPolicy, defaultComputeServiceAgent, exports.EVENTARC_EVENT_RECEIVER_ROLE);
97
+ return [invokerBinding, eventReceiverBinding];
98
+ }
99
+ exports.obtainDefaultComputeServiceAgentBindings = obtainDefaultComputeServiceAgentBindings;
100
+ function obtainEventarcServiceAgentBindings(projectNumber, existingPolicy) {
101
+ const eventarcServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-eventarc.iam.gserviceaccount.com`;
102
+ return [obtainBinding(existingPolicy, eventarcServiceAgent, exports.EVENTARC_SERVICE_AGENT_ROLE)];
103
+ }
104
+ exports.obtainEventarcServiceAgentBindings = obtainEventarcServiceAgentBindings;
70
105
  function mergeBindings(policy, allRequiredBindings) {
71
106
  for (const requiredBindings of allRequiredBindings) {
72
107
  if (requiredBindings.length === 0) {
@@ -107,6 +142,14 @@ async function ensureServiceAgentRoles(projectNumber, want, have) {
107
142
  const findRequiredBindings = [];
108
143
  newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(projectNumber, policy)));
109
144
  const allRequiredBindings = await Promise.all(findRequiredBindings);
145
+ if (haveServices.length === 0) {
146
+ allRequiredBindings.push(obtainPubSubServiceAgentBindings(projectNumber, policy));
147
+ allRequiredBindings.push(obtainDefaultComputeServiceAgentBindings(projectNumber, policy));
148
+ allRequiredBindings.push(obtainEventarcServiceAgentBindings(projectNumber, policy));
149
+ }
150
+ if (!allRequiredBindings.find((bindings) => bindings.length > 0)) {
151
+ return;
152
+ }
110
153
  mergeBindings(policy, allRequiredBindings);
111
154
  try {
112
155
  await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
@@ -20,6 +20,7 @@ const triggerRegionHelper_1 = require("./triggerRegionHelper");
20
20
  const checkIam_1 = require("./checkIam");
21
21
  const error_1 = require("../../error");
22
22
  const projectConfig_1 = require("../../functions/projectConfig");
23
+ const previews_1 = require("../../previews");
23
24
  function hasUserConfig(config) {
24
25
  return Object.keys(config).length > 1;
25
26
  }
@@ -96,12 +97,21 @@ async function prepare(context, options, payload) {
96
97
  clc.bold(sourceDirName) +
97
98
  " directory for uploading...");
98
99
  }
99
- if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
100
- context.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config, runtimeConfig);
101
- }
102
100
  if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
101
+ if (!previews_1.previews.functionsv2) {
102
+ throw new error_1.FirebaseError("This version of firebase-tools does not support Google Cloud " +
103
+ "Functions gen 2\n" +
104
+ "If Cloud Functions for Firebase gen 2 is still in alpha, sign up " +
105
+ "for the alpha program at " +
106
+ "https://services.google.com/fb/forms/firebasealphaprogram/\n" +
107
+ "If Cloud Functions for Firebase gen 2 is in beta, get the latest " +
108
+ "version of Firebse Tools with `npm i -g firebase-tools@latest`");
109
+ }
103
110
  context.functionsSourceV2 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config);
104
111
  }
112
+ if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
113
+ context.functionsSourceV1 = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, context.config, runtimeConfig);
114
+ }
105
115
  for (const endpoint of backend.allEndpoints(wantBackend)) {
106
116
  endpoint.environmentVariables = wantBackend.environmentVariables;
107
117
  }
@@ -124,9 +124,7 @@ class Fabricator {
124
124
  await this.setTrigger(endpoint);
125
125
  }
126
126
  async updateEndpoint(update, scraper) {
127
- if (update.deleteAndRecreate || update.endpoint.platform !== "gcfv2") {
128
- update.endpoint.labels = Object.assign(Object.assign({}, update.endpoint.labels), deploymentTool.labels());
129
- }
127
+ update.endpoint.labels = Object.assign(Object.assign({}, update.endpoint.labels), deploymentTool.labels());
130
128
  if (update.deleteAndRecreate) {
131
129
  await this.deleteEndpoint(update.deleteAndRecreate);
132
130
  await this.createEndpoint(update.endpoint, scraper);
@@ -11,9 +11,12 @@ const fabricator = require("./fabricator");
11
11
  const reporter = require("./reporter");
12
12
  const executor = require("./executor");
13
13
  const prompts = require("../prompts");
14
+ const secrets = require("../../../functions/secrets");
14
15
  const functionsConfig_1 = require("../../../functionsConfig");
15
16
  const functionsDeployHelper_1 = require("../functionsDeployHelper");
16
17
  const error_1 = require("../../../error");
18
+ const projectUtils_1 = require("../../../projectUtils");
19
+ const utils_1 = require("../../../utils");
17
20
  async function release(context, options, payload) {
18
21
  if (!context.config) {
19
22
  return;
@@ -59,6 +62,24 @@ async function release(context, options, payload) {
59
62
  const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors };
60
63
  throw new error_1.FirebaseError("There was an error deploying functions", Object.assign(Object.assign({}, opts), { exit: 2 }));
61
64
  }
65
+ else {
66
+ if (secrets.of(haveEndpoints).length > 0) {
67
+ const projectId = (0, projectUtils_1.needProjectId)(options);
68
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
69
+ const reloadedBackend = await backend.existingBackend({ projectId });
70
+ const prunedResult = await secrets.pruneAndDestroySecrets({ projectId, projectNumber }, backend.allEndpoints(reloadedBackend));
71
+ if (prunedResult.destroyed.length > 0) {
72
+ (0, utils_1.logLabeledBullet)("functions", `Destroyed unused secret versions: ${prunedResult.destroyed
73
+ .map((s) => `${s.secret}@${s.version}`)
74
+ .join(", ")}`);
75
+ }
76
+ if (prunedResult.erred.length > 0) {
77
+ (0, utils_1.logLabeledWarning)("functions", `Failed to destroy unused secret versions:\n\t${prunedResult.erred
78
+ .map((err) => err.message)
79
+ .join("\n\t")}`);
80
+ }
81
+ }
82
+ }
62
83
  }
63
84
  exports.release = release;
64
85
  function printTriggerUrls(results) {
@@ -103,7 +103,6 @@ function changedTriggerRegion(want, have) {
103
103
  }
104
104
  exports.changedTriggerRegion = changedTriggerRegion;
105
105
  function changedV2PubSubTopic(want, have) {
106
- var _a, _b;
107
106
  if (want.platform !== "gcfv2") {
108
107
  return false;
109
108
  }
@@ -122,7 +121,7 @@ function changedV2PubSubTopic(want, have) {
122
121
  if (have.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) {
123
122
  return false;
124
123
  }
125
- return (((_a = backend.findEventFilter(have, "topic")) === null || _a === void 0 ? void 0 : _a.value) !== ((_b = backend.findEventFilter(want, "topic")) === null || _b === void 0 ? void 0 : _b.value));
124
+ return have.eventTrigger.eventFilters.topic !== want.eventTrigger.eventFilters.topic;
126
125
  }
127
126
  exports.changedV2PubSubTopic = changedV2PubSubTopic;
128
127
  function upgradedScheduleFromV1ToV2(want, have) {
@@ -49,7 +49,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
49
49
  minInstances: "number",
50
50
  concurrency: "number",
51
51
  serviceAccountEmail: "string",
52
- timeout: "string",
52
+ timeoutSeconds: "number",
53
53
  vpc: "object",
54
54
  labels: "object",
55
55
  ingressSettings: "string",
@@ -88,16 +88,18 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
88
88
  if (backend.isEventTriggered(ep)) {
89
89
  (0, parsing_1.requireKeys)(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters");
90
90
  (0, parsing_1.assertKeyTypes)(prefix + ".eventTrigger", ep.eventTrigger, {
91
- eventFilters: "array",
91
+ eventFilters: "object",
92
+ eventFilterPathPatterns: "object",
92
93
  eventType: "string",
93
94
  retry: "boolean",
94
95
  region: "string",
95
96
  serviceAccountEmail: "string",
97
+ channel: "string",
96
98
  });
97
99
  triggered = { eventTrigger: ep.eventTrigger };
98
- for (const eventFilter of triggered.eventTrigger.eventFilters) {
99
- if (eventFilter.attribute === "topic" && !eventFilter.value.startsWith("projects/")) {
100
- eventFilter.value = `projects/${project}/topics/${eventFilter.value}`;
100
+ for (const [k, v] of Object.entries(triggered.eventTrigger.eventFilters)) {
101
+ if (k === "topic" && !v.startsWith("projects/")) {
102
+ triggered.eventTrigger.eventFilters[k] = `projects/${project}/topics/${v}`;
101
103
  }
102
104
  }
103
105
  }
@@ -134,7 +136,6 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
134
136
  });
135
137
  if (ep.taskQueueTrigger.rateLimits) {
136
138
  (0, parsing_1.assertKeyTypes)(prefix + ".taskQueueTrigger.rateLimits", ep.taskQueueTrigger.rateLimits, {
137
- maxBurstSize: "number",
138
139
  maxConcurrentDispatches: "number",
139
140
  maxDispatchesPerSecond: "number",
140
141
  });
@@ -142,9 +143,9 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
142
143
  if (ep.taskQueueTrigger.retryConfig) {
143
144
  (0, parsing_1.assertKeyTypes)(prefix + ".taskQueueTrigger.retryConfig", ep.taskQueueTrigger.retryConfig, {
144
145
  maxAttempts: "number",
145
- maxRetryDuration: "string",
146
- minBackoff: "string",
147
- maxBackoff: "string",
146
+ maxRetrySeconds: "number",
147
+ minBackoffSeconds: "number",
148
+ maxBackoffSeconds: "number",
148
149
  maxDoublings: "number",
149
150
  });
150
151
  }
@@ -159,7 +160,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
159
160
  region,
160
161
  project,
161
162
  runtime, entryPoint: ep.entryPoint }, triggered);
162
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeout", "vpc", "labels", "ingressSettings", "environmentVariables");
163
+ (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables");
163
164
  allParsed.push(parsed);
164
165
  }
165
166
  return allParsed;
@@ -17,7 +17,7 @@ const discovery = require("../discovery");
17
17
  const validate = require("./validate");
18
18
  const versioning = require("./versioning");
19
19
  const parseTriggers = require("./parseTriggers");
20
- const MIN_FUNCTIONS_SDK_VERSION = "3.19.0";
20
+ const MIN_FUNCTIONS_SDK_VERSION = "3.20.0";
21
21
  async function tryCreateDelegate(context) {
22
22
  const packageJsonPath = path.join(context.sourceDir, "package.json");
23
23
  if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
@@ -113,31 +113,16 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
113
113
  triggered = {
114
114
  eventTrigger: {
115
115
  eventType: annotation.eventTrigger.eventType,
116
- eventFilters: [
117
- {
118
- attribute: "resource",
119
- value: annotation.eventTrigger.resource,
120
- },
121
- ],
116
+ eventFilters: { resource: annotation.eventTrigger.resource },
122
117
  retry: !!annotation.failurePolicy,
123
118
  },
124
119
  };
125
120
  if (annotation.platform === "gcfv2") {
126
121
  if (annotation.eventTrigger.eventType === v2events.PUBSUB_PUBLISH_EVENT) {
127
- triggered.eventTrigger.eventFilters = [
128
- {
129
- attribute: "topic",
130
- value: annotation.eventTrigger.resource,
131
- },
132
- ];
122
+ triggered.eventTrigger.eventFilters = { topic: annotation.eventTrigger.resource };
133
123
  }
134
124
  if (v2events.STORAGE_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
135
- triggered.eventTrigger.eventFilters = [
136
- {
137
- attribute: "bucket",
138
- value: annotation.eventTrigger.resource,
139
- },
140
- ];
125
+ triggered.eventTrigger.eventFilters = { bucket: annotation.eventTrigger.resource };
141
126
  }
142
127
  }
143
128
  }
@@ -162,7 +147,8 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
162
147
  }
163
148
  endpoint.secretEnvironmentVariables = secretEnvs;
164
149
  }
165
- proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
150
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "ingressSettings", "maxInstances", "minInstances", "availableMemoryMb");
151
+ proto.renameIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
166
152
  want.endpoints[region] = want.endpoints[region] || {};
167
153
  want.endpoints[region][endpoint.id] = endpoint;
168
154
  mergeRequiredAPIs(want);