firebase-tools 9.19.0 → 9.23.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 (101) hide show
  1. package/CHANGELOG.md +1 -3
  2. package/lib/api.js +3 -0
  3. package/lib/apiv2.js +7 -4
  4. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  5. package/lib/commands/deploy.js +9 -1
  6. package/lib/commands/ext-configure.js +3 -1
  7. package/lib/commands/ext-dev-deprecate.js +63 -0
  8. package/lib/commands/ext-dev-undeprecate.js +56 -0
  9. package/lib/commands/ext-dev-unpublish.js +10 -3
  10. package/lib/commands/ext-export.js +44 -0
  11. package/lib/commands/ext-install.js +24 -3
  12. package/lib/commands/ext-uninstall.js +6 -0
  13. package/lib/commands/ext-update.js +10 -3
  14. package/lib/commands/functions-config-export.js +115 -0
  15. package/lib/commands/functions-delete.js +47 -25
  16. package/lib/commands/functions-list.js +12 -12
  17. package/lib/commands/index.js +9 -0
  18. package/lib/commands/init.js +3 -0
  19. package/lib/config.js +3 -2
  20. package/lib/deploy/extensions/args.js +2 -0
  21. package/lib/deploy/extensions/deploy.js +49 -0
  22. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  23. package/lib/deploy/extensions/errors.js +31 -0
  24. package/lib/deploy/extensions/index.js +8 -0
  25. package/lib/deploy/extensions/params.js +39 -0
  26. package/lib/deploy/extensions/planner.js +94 -0
  27. package/lib/deploy/extensions/prepare.js +111 -0
  28. package/lib/deploy/extensions/release.js +43 -0
  29. package/lib/deploy/extensions/secrets.js +150 -0
  30. package/lib/deploy/extensions/tasks.js +98 -0
  31. package/lib/deploy/extensions/validate.js +17 -0
  32. package/lib/deploy/functions/backend.js +93 -115
  33. package/lib/deploy/functions/checkIam.js +8 -8
  34. package/lib/deploy/functions/containerCleaner.js +71 -14
  35. package/lib/deploy/functions/deploy.js +4 -10
  36. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  37. package/lib/deploy/functions/prepare.js +63 -27
  38. package/lib/deploy/functions/pricing.js +17 -17
  39. package/lib/deploy/functions/prompts.js +22 -21
  40. package/lib/deploy/functions/release/executor.js +39 -0
  41. package/lib/deploy/functions/release/fabricator.js +422 -0
  42. package/lib/deploy/functions/release/index.js +73 -0
  43. package/lib/deploy/functions/release/planner.js +162 -0
  44. package/lib/deploy/functions/release/reporter.js +165 -0
  45. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  46. package/lib/deploy/functions/release/timer.js +14 -0
  47. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
  48. package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
  49. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  50. package/lib/deploy/functions/validate.js +1 -24
  51. package/lib/deploy/index.js +10 -1
  52. package/lib/downloadUtils.js +37 -0
  53. package/lib/emulator/auth/apiSpec.js +549 -6
  54. package/lib/emulator/auth/handlers.js +4 -3
  55. package/lib/emulator/auth/operations.js +154 -14
  56. package/lib/emulator/auth/server.js +26 -15
  57. package/lib/emulator/auth/state.js +151 -13
  58. package/lib/emulator/download.js +2 -31
  59. package/lib/emulator/downloadableEmulators.js +7 -7
  60. package/lib/emulator/functionsEmulator.js +18 -4
  61. package/lib/emulator/functionsEmulatorRuntime.js +29 -7
  62. package/lib/emulator/storage/cloudFunctions.js +37 -7
  63. package/lib/extensions/askUserForConsent.js +14 -1
  64. package/lib/extensions/askUserForParam.js +81 -4
  65. package/lib/extensions/checkProjectBilling.js +7 -7
  66. package/lib/extensions/export.js +107 -0
  67. package/lib/extensions/extensionsApi.js +104 -21
  68. package/lib/extensions/extensionsHelper.js +6 -2
  69. package/lib/extensions/listExtensions.js +16 -11
  70. package/lib/extensions/paramHelper.js +9 -6
  71. package/lib/extensions/provisioningHelper.js +16 -3
  72. package/lib/extensions/refs.js +9 -1
  73. package/lib/extensions/secretsUtils.js +59 -0
  74. package/lib/extensions/updateHelper.js +12 -2
  75. package/lib/extensions/versionHelper.js +14 -0
  76. package/lib/extensions/warnings.js +33 -1
  77. package/lib/functional.js +8 -1
  78. package/lib/functions/env.js +10 -4
  79. package/lib/functions/runtimeConfigExport.js +137 -0
  80. package/lib/gcp/artifactregistry.js +16 -0
  81. package/lib/gcp/cloudfunctions.js +20 -74
  82. package/lib/gcp/cloudfunctionsv2.js +12 -90
  83. package/lib/gcp/cloudscheduler.js +22 -16
  84. package/lib/gcp/cloudtasks.js +143 -0
  85. package/lib/gcp/docker.js +7 -1
  86. package/lib/gcp/proto.js +2 -2
  87. package/lib/gcp/pubsub.js +1 -9
  88. package/lib/gcp/secretManager.js +132 -0
  89. package/lib/gcp/storage.js +16 -0
  90. package/lib/projectUtils.js +10 -1
  91. package/lib/requireInteractive.js +12 -0
  92. package/lib/utils.js +30 -1
  93. package/package.json +5 -4
  94. package/schema/firebase-config.json +9 -0
  95. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  96. package/lib/deploy/functions/deploymentTimer.js +0 -23
  97. package/lib/deploy/functions/errorHandler.js +0 -75
  98. package/lib/deploy/functions/release.js +0 -116
  99. package/lib/deploy/functions/tasks.js +0 -324
  100. package/lib/functions/listFunctions.js +0 -10
  101. package/lib/functionsDelete.js +0 -60
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.triggerTag = exports.printAbortedErrors = exports.printErrors = exports.logAndTrackDeployStats = exports.AbortedDeploymentError = exports.DeploymentError = void 0;
4
+ const backend = require("../backend");
5
+ const clc = require("cli-color");
6
+ const logger_1 = require("../../../logger");
7
+ const track = require("../../../track");
8
+ const utils = require("../../../utils");
9
+ const functionsDeployHelper_1 = require("../functionsDeployHelper");
10
+ class DeploymentError extends Error {
11
+ constructor(endpoint, op, original) {
12
+ super(`Failed to ${op} function ${endpoint.id} in region ${endpoint.region}`);
13
+ this.endpoint = endpoint;
14
+ this.op = op;
15
+ this.original = original;
16
+ }
17
+ }
18
+ exports.DeploymentError = DeploymentError;
19
+ class AbortedDeploymentError extends DeploymentError {
20
+ constructor(endpoint) {
21
+ super(endpoint, "delete", new Error("aborted"));
22
+ this.endpoint = endpoint;
23
+ }
24
+ }
25
+ exports.AbortedDeploymentError = AbortedDeploymentError;
26
+ async function logAndTrackDeployStats(summary) {
27
+ let totalTime = 0;
28
+ let totalErrors = 0;
29
+ let totalSuccesses = 0;
30
+ let totalAborts = 0;
31
+ const reports = [];
32
+ for (const result of summary.results) {
33
+ const tag = triggerTag(result.endpoint);
34
+ totalTime += result.durationMs;
35
+ if (!result.error) {
36
+ totalSuccesses++;
37
+ reports.push(track.track("function_deploy_success", tag, result.durationMs));
38
+ }
39
+ else if (result.error instanceof AbortedDeploymentError) {
40
+ totalAborts++;
41
+ reports.push(track.track("function_deploy_abort", tag, result.durationMs));
42
+ }
43
+ else {
44
+ totalErrors++;
45
+ reports.push(track.track("function_deploy_failure", tag, result.durationMs));
46
+ }
47
+ }
48
+ const gcfv1 = summary.results.find((r) => r.endpoint.platform === "gcfv1");
49
+ const gcfv2 = summary.results.find((r) => r.endpoint.platform === "gcfv2");
50
+ const tag = gcfv1 && gcfv2 ? "v1+v2" : gcfv1 ? "v1" : "v2";
51
+ reports.push(track.track("functions_codebase_deploy", tag, summary.results.length));
52
+ const avgTime = totalTime / (totalSuccesses + totalErrors);
53
+ logger_1.logger.debug(`Total Function Deployment time: ${summary.totalTime}`);
54
+ logger_1.logger.debug(`${totalErrors + totalSuccesses + totalAborts} Functions Deployed`);
55
+ logger_1.logger.debug(`${totalErrors} Functions Errored`);
56
+ logger_1.logger.debug(`${totalAborts} Function Deployments Aborted`);
57
+ logger_1.logger.debug(`Average Function Deployment time: ${avgTime}`);
58
+ if (totalErrors + totalSuccesses > 0) {
59
+ if (totalErrors === 0) {
60
+ reports.push(track.track("functions_deploy_result", "success", totalSuccesses));
61
+ }
62
+ else if (totalSuccesses > 0) {
63
+ reports.push(track.track("functions_deploy_result", "partial_success", totalSuccesses));
64
+ reports.push(track.track("functions_deploy_result", "partial_failure", totalErrors));
65
+ reports.push(track.track("functions_deploy_result", "partial_error_ratio", totalErrors / (totalSuccesses + totalErrors)));
66
+ }
67
+ else {
68
+ reports.push(track.track("functions_deploy_result", "failure", totalErrors));
69
+ }
70
+ }
71
+ await utils.allSettled(reports);
72
+ }
73
+ exports.logAndTrackDeployStats = logAndTrackDeployStats;
74
+ function printErrors(summary) {
75
+ const errored = summary.results.filter((r) => r.error);
76
+ if (errored.length === 0) {
77
+ return;
78
+ }
79
+ errored.sort((left, right) => backend.compareFunctions(left.endpoint, right.endpoint));
80
+ logger_1.logger.info("");
81
+ logger_1.logger.info("Functions deploy had errors with the following functions:" +
82
+ errored
83
+ .filter((r) => !(r.error instanceof AbortedDeploymentError))
84
+ .map((result) => `\n\t${functionsDeployHelper_1.getFunctionLabel(result.endpoint)}`)
85
+ .join(""));
86
+ printIamErrors(errored);
87
+ printQuotaErrors(errored);
88
+ printAbortedErrors(errored);
89
+ }
90
+ exports.printErrors = printErrors;
91
+ function printIamErrors(results) {
92
+ const iamFailures = results.filter((r) => r.error instanceof DeploymentError && r.error.op === "set invoker");
93
+ if (!iamFailures.length) {
94
+ return;
95
+ }
96
+ logger_1.logger.info("");
97
+ logger_1.logger.info("Unable to set the invoker for the IAM policy on the following functions:" +
98
+ iamFailures.map((result) => `\n\t${functionsDeployHelper_1.getFunctionLabel(result.endpoint)}`).join(""));
99
+ logger_1.logger.info("");
100
+ logger_1.logger.info("Some common causes of this:");
101
+ logger_1.logger.info("");
102
+ logger_1.logger.info("- You may not have the roles/functions.admin IAM role. Note that " +
103
+ "roles/functions.developer does not allow you to change IAM policies.");
104
+ logger_1.logger.info("");
105
+ logger_1.logger.info("- An organization policy that restricts Network Access on your project.");
106
+ const hadImplicitMakePublic = iamFailures.find((r) => backend.isHttpsTriggered(r.endpoint) && !r.endpoint.httpsTrigger.invoker);
107
+ if (!hadImplicitMakePublic) {
108
+ return;
109
+ }
110
+ logger_1.logger.info("");
111
+ logger_1.logger.info("One or more functions were being implicitly made publicly available on function create.");
112
+ logger_1.logger.info("Functions are not implicitly made public on updates. To try to make " +
113
+ "these functions public on next deploy, configure these functions with " +
114
+ `${clc.bold("invoker")} set to ${clc.bold(`"public"`)}`);
115
+ }
116
+ function printQuotaErrors(results) {
117
+ const hadQuotaError = results.find((r) => {
118
+ var _a, _b, _c, _d, _e, _f;
119
+ if (!(r.error instanceof DeploymentError)) {
120
+ return false;
121
+ }
122
+ const original = r.error.original;
123
+ const code = (original === null || original === void 0 ? void 0 : original.status) || (original === null || original === void 0 ? void 0 : original.code) || ((_b = (_a = original === null || original === void 0 ? void 0 : original.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) || ((_c = original === null || original === void 0 ? void 0 : original.original) === null || _c === void 0 ? void 0 : _c.code) || ((_f = (_e = (_d = original === null || original === void 0 ? void 0 : original.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode);
124
+ return code === 429 || code === 409;
125
+ });
126
+ if (!hadQuotaError) {
127
+ return;
128
+ }
129
+ logger_1.logger.info("");
130
+ logger_1.logger.info("Exceeded maximum retries while deploying functions. " +
131
+ "If you are deploying a large number of functions, " +
132
+ "please deploy your functions in batches by using the --only flag, " +
133
+ "and wait a few minutes before deploying again. " +
134
+ "Go to https://firebase.google.com/docs/cli/#partial_deploys to learn more.");
135
+ }
136
+ function printAbortedErrors(results) {
137
+ const aborted = results.filter((r) => r.error instanceof AbortedDeploymentError);
138
+ if (!aborted.length) {
139
+ return;
140
+ }
141
+ logger_1.logger.info("");
142
+ logger_1.logger.info("Because there were errors creating or updating functions, the following " +
143
+ "functions were not deleted" +
144
+ aborted.map((result) => `\n\t${functionsDeployHelper_1.getFunctionLabel(result.endpoint)}`).join(""));
145
+ logger_1.logger.info(`To delete these, use ${clc.bold("firebase functions:delete")}`);
146
+ }
147
+ exports.printAbortedErrors = printAbortedErrors;
148
+ function triggerTag(endpoint) {
149
+ var _a;
150
+ const prefix = endpoint.platform === "gcfv1" ? "v1" : "v2";
151
+ if (backend.isScheduleTriggered(endpoint)) {
152
+ return `${prefix}.scheduled`;
153
+ }
154
+ if (backend.isTaskQueueTriggered(endpoint)) {
155
+ return `${prefix}.taskQueue`;
156
+ }
157
+ if (backend.isHttpsTriggered(endpoint)) {
158
+ if ((_a = endpoint.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
159
+ return `${prefix}.callable`;
160
+ }
161
+ return `${prefix}.https`;
162
+ }
163
+ return endpoint.eventTrigger.eventType;
164
+ }
165
+ exports.triggerTag = triggerTag;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SourceTokenScraper = void 0;
4
+ const logger_1 = require("../../../logger");
5
+ class SourceTokenScraper {
6
+ constructor() {
7
+ this.firstCall = true;
8
+ this.promise = new Promise((resolve) => (this.resolve = resolve));
9
+ }
10
+ tokenPromise() {
11
+ if (this.firstCall) {
12
+ this.firstCall = false;
13
+ return Promise.resolve(undefined);
14
+ }
15
+ return this.promise;
16
+ }
17
+ get poller() {
18
+ return (op) => {
19
+ var _a, _b, _c, _d, _e;
20
+ if (((_a = op.metadata) === null || _a === void 0 ? void 0 : _a.sourceToken) || op.done) {
21
+ const [, , , region] = ((_c = (_b = op.metadata) === null || _b === void 0 ? void 0 : _b.target) === null || _c === void 0 ? void 0 : _c.split("/")) || [];
22
+ logger_1.logger.debug(`Got source token ${(_d = op.metadata) === null || _d === void 0 ? void 0 : _d.sourceToken} for region ${region}`);
23
+ this.resolve((_e = op.metadata) === null || _e === void 0 ? void 0 : _e.sourceToken);
24
+ }
25
+ };
26
+ }
27
+ }
28
+ exports.SourceTokenScraper = SourceTokenScraper;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Timer = void 0;
4
+ class Timer {
5
+ constructor() {
6
+ this.start = process.hrtime.bigint();
7
+ }
8
+ stop() {
9
+ const stop = process.hrtime.bigint();
10
+ const elapsedNanos = stop - this.start;
11
+ return Number(elapsedNanos / BigInt(1e6));
12
+ }
13
+ }
14
+ exports.Timer = Timer;
@@ -2,151 +2,154 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.backendFromV1Alpha1 = void 0;
4
4
  const backend = require("../../backend");
5
+ const proto_1 = require("../../../../gcp/proto");
5
6
  const parsing_1 = require("./parsing");
7
+ const error_1 = require("../../../../error");
6
8
  function backendFromV1Alpha1(yaml, project, region, runtime) {
7
- const bkend = JSON.parse(JSON.stringify(yaml));
8
- delete bkend.specVersion;
9
- tryValidate(bkend);
10
- fillDefaults(bkend, project, region, runtime);
9
+ const manifest = JSON.parse(JSON.stringify(yaml));
10
+ const bkend = backend.empty();
11
+ bkend.requiredAPIs = parseRequiredAPIs(manifest);
12
+ parsing_1.requireKeys("", manifest, "endpoints");
13
+ parsing_1.assertKeyTypes("", manifest, {
14
+ specVersion: "string",
15
+ requiredAPIs: "object",
16
+ endpoints: "object",
17
+ });
18
+ for (const id of Object.keys(manifest.endpoints)) {
19
+ for (const parsed of parseEndpoints(manifest, id, project, region, runtime)) {
20
+ bkend.endpoints[parsed.region] = bkend.endpoints[parsed.region] || {};
21
+ bkend.endpoints[parsed.region][parsed.id] = parsed;
22
+ }
23
+ }
11
24
  return bkend;
12
25
  }
13
26
  exports.backendFromV1Alpha1 = backendFromV1Alpha1;
14
- function tryValidate(typed) {
15
- var _a, _b;
16
- parsing_1.assertKeyTypes("", typed, {
17
- requiredAPIs: "object",
18
- endpoints: "array",
19
- cloudFunctions: "array",
20
- topics: "array",
21
- schedules: "array",
27
+ function parseRequiredAPIs(manifest) {
28
+ const requiredAPIs = {};
29
+ if (typeof manifest !== "object" || Array.isArray(manifest)) {
30
+ throw new error_1.FirebaseError("Expected requiredApis to be a map of string to string");
31
+ }
32
+ for (const [api, reason] of Object.entries(manifest.requiredAPIs || {})) {
33
+ if (typeof reason !== "string") {
34
+ throw new error_1.FirebaseError(`Invalid reason "${JSON.stringify(reason)} for API ${api}. Expected string`);
35
+ }
36
+ requiredAPIs[api] = reason;
37
+ }
38
+ return requiredAPIs;
39
+ }
40
+ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
41
+ const allParsed = [];
42
+ const prefix = `endpoints[${id}]`;
43
+ const ep = manifest.endpoints[id];
44
+ parsing_1.assertKeyTypes(prefix, ep, {
45
+ region: "array",
46
+ platform: "string",
47
+ entryPoint: "string",
48
+ availableMemoryMb: "number",
49
+ maxInstances: "number",
50
+ minInstances: "number",
51
+ concurrency: "number",
52
+ serviceAccountEmail: "string",
53
+ timeout: "string",
54
+ vpcConnector: "string",
55
+ vpcConnectorEgressSettings: "string",
56
+ labels: "object",
57
+ ingressSettings: "string",
22
58
  environmentVariables: "object",
59
+ httpsTrigger: "object",
60
+ eventTrigger: "object",
61
+ scheduleTrigger: "object",
62
+ taskQueueTrigger: "object",
23
63
  });
24
- parsing_1.requireKeys("", typed, "cloudFunctions");
25
- for (let ndx = 0; ndx < typed.cloudFunctions.length; ndx++) {
26
- const prefix = `cloudFunctions[${ndx}]`;
27
- const func = typed.cloudFunctions[ndx];
28
- parsing_1.requireKeys(prefix, func, "platform", "id", "entryPoint", "trigger");
29
- parsing_1.assertKeyTypes(prefix, func, {
30
- platform: "string",
31
- id: "string",
32
- region: "string",
33
- project: "string",
34
- runtime: "string",
35
- entryPoint: "string",
36
- availableMemoryMb: "number",
37
- maxInstances: "number",
38
- minInstances: "number",
39
- concurrency: "number",
40
- serviceAccountEmail: "string",
41
- timeout: "string",
42
- trigger: "object",
43
- vpcConnector: "string",
44
- vpcConnectorEgressSettings: "string",
45
- labels: "object",
46
- ingressSettings: "string",
47
- environmentVariables: "omit",
48
- uri: "omit",
49
- sourceUploadUrl: "omit",
50
- });
51
- if (backend.isEventTrigger(func.trigger)) {
52
- parsing_1.requireKeys(prefix + ".trigger", func.trigger, "eventType", "eventFilters");
53
- parsing_1.assertKeyTypes(prefix + ".trigger", func.trigger, {
64
+ let triggerCount = 0;
65
+ if (ep.httpsTrigger) {
66
+ triggerCount++;
67
+ }
68
+ if (ep.eventTrigger) {
69
+ triggerCount++;
70
+ }
71
+ if (ep.scheduleTrigger) {
72
+ triggerCount++;
73
+ }
74
+ if (ep.taskQueueTrigger) {
75
+ triggerCount++;
76
+ }
77
+ if (!triggerCount) {
78
+ throw new error_1.FirebaseError("Expected trigger in endpoint" + id);
79
+ }
80
+ if (triggerCount > 1) {
81
+ throw new error_1.FirebaseError("Multiple triggers defined for endpoint" + id);
82
+ }
83
+ for (const region of ep.region || [defaultRegion]) {
84
+ let triggered;
85
+ if (backend.isEventTriggered(ep)) {
86
+ parsing_1.requireKeys(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters");
87
+ parsing_1.assertKeyTypes(prefix + ".eventTrigger", ep.eventTrigger, {
54
88
  eventFilters: "object",
55
89
  eventType: "string",
56
90
  retry: "boolean",
57
91
  region: "string",
58
92
  serviceAccountEmail: "string",
59
93
  });
94
+ triggered = { eventTrigger: ep.eventTrigger };
60
95
  }
61
- else {
62
- parsing_1.assertKeyTypes(prefix + ".trigger", func.trigger, {
96
+ else if (backend.isHttpsTriggered(ep)) {
97
+ parsing_1.assertKeyTypes(prefix + ".httpsTrigger", ep.httpsTrigger, {
63
98
  invoker: "array",
64
99
  });
100
+ triggered = { httpsTrigger: {} };
101
+ proto_1.copyIfPresent(triggered.httpsTrigger, ep.httpsTrigger, "invoker");
65
102
  }
66
- }
67
- for (let ndx = 0; ndx < ((_a = typed.topics) === null || _a === void 0 ? void 0 : _a.length); ndx++) {
68
- let prefix = `topics[${ndx}]`;
69
- const topic = typed.topics[ndx];
70
- parsing_1.requireKeys(prefix, topic, "id", "targetService");
71
- parsing_1.assertKeyTypes(prefix, topic, {
72
- id: "string",
73
- labels: "object",
74
- project: "string",
75
- targetService: "object",
76
- });
77
- prefix += ".targetService";
78
- parsing_1.requireKeys(prefix, topic.targetService, "id");
79
- parsing_1.assertKeyTypes(prefix, topic.targetService, {
80
- id: "string",
81
- project: "string",
82
- region: "string",
83
- });
84
- }
85
- for (let ndx = 0; ndx < ((_b = typed.schedules) === null || _b === void 0 ? void 0 : _b.length); ndx++) {
86
- let prefix = `schedules[${ndx}]`;
87
- const schedule = typed.schedules[ndx];
88
- parsing_1.requireKeys(prefix, schedule, "id", "schedule", "transport", "targetService");
89
- parsing_1.assertKeyTypes(prefix, schedule, {
90
- id: "string",
91
- project: "string",
92
- retryConfig: "object",
93
- schedule: "string",
94
- timeZone: "string",
95
- transport: "string",
96
- targetService: "object",
97
- });
98
- parsing_1.assertKeyTypes(prefix + ".retryConfig", schedule.retryConfig, {
99
- maxBackoffDuration: "string",
100
- minBackoffDuration: "string",
101
- maxDoublings: "number",
102
- maxRetryDuration: "string",
103
- retryCount: "number",
104
- });
105
- parsing_1.requireKeys((prefix = ".targetService"), schedule.targetService, "id");
106
- parsing_1.assertKeyTypes(prefix + ".targetService", schedule.targetService, {
107
- id: "string",
108
- project: "string",
109
- region: "string",
110
- });
111
- }
112
- }
113
- function fillDefaults(want, project, region, runtime) {
114
- want.requiredAPIs = want.requiredAPIs || {};
115
- want.environmentVariables = want.environmentVariables || {};
116
- want.schedules = want.schedules || [];
117
- want.topics = want.topics || [];
118
- want.endpoints = want.endpoints || [];
119
- for (const cloudFunction of want.cloudFunctions) {
120
- if (!cloudFunction.project) {
121
- cloudFunction.project = project;
122
- }
123
- if (!cloudFunction.region) {
124
- cloudFunction.region = region;
125
- }
126
- if (!cloudFunction.runtime) {
127
- cloudFunction.runtime = runtime;
128
- }
129
- }
130
- for (const topic of want.topics) {
131
- if (!topic.project) {
132
- topic.project = project;
133
- }
134
- if (!topic.targetService.project) {
135
- topic.targetService.project = project;
136
- }
137
- if (!topic.targetService.region) {
138
- topic.targetService.region = region;
139
- }
140
- }
141
- for (const schedule of want.schedules) {
142
- if (!schedule.project) {
143
- schedule.project = project;
103
+ else if (backend.isScheduleTriggered(ep)) {
104
+ parsing_1.assertKeyTypes(prefix + ".scheduleTrigger", ep.scheduleTrigger, {
105
+ schedule: "string",
106
+ timeZone: "string",
107
+ retryConfig: "object",
108
+ });
109
+ parsing_1.assertKeyTypes(prefix + ".scheduleTrigger.retryConfig", ep.scheduleTrigger.retryConfig, {
110
+ retryCount: "number",
111
+ maxDoublings: "number",
112
+ minBackoffDuration: "string",
113
+ maxBackoffDuration: "string",
114
+ maxRetryDuration: "string",
115
+ });
116
+ triggered = { scheduleTrigger: ep.scheduleTrigger };
144
117
  }
145
- if (!schedule.targetService.project) {
146
- schedule.targetService.project = project;
118
+ else if (backend.isTaskQueueTriggered(ep)) {
119
+ parsing_1.assertKeyTypes(prefix + ".taskQueueTrigger", ep.taskQueueTrigger, {
120
+ rateLimits: "object",
121
+ retryConfig: "object",
122
+ invoker: "array",
123
+ });
124
+ if (ep.taskQueueTrigger.rateLimits) {
125
+ parsing_1.assertKeyTypes(prefix + ".taskQueueTrigger.rateLimits", ep.taskQueueTrigger.rateLimits, {
126
+ maxBurstSize: "number",
127
+ maxConcurrentDispatches: "number",
128
+ maxDispatchesPerSecond: "number",
129
+ });
130
+ }
131
+ if (ep.taskQueueTrigger.retryConfig) {
132
+ parsing_1.assertKeyTypes(prefix + ".taskQueueTrigger.retryConfig", ep.taskQueueTrigger.retryConfig, {
133
+ maxAttempts: "number",
134
+ maxRetryDuration: "string",
135
+ minBackoff: "string",
136
+ maxBackoff: "string",
137
+ maxDoublings: "number",
138
+ });
139
+ }
140
+ triggered = { taskQueueTrigger: ep.taskQueueTrigger };
147
141
  }
148
- if (!schedule.targetService.region) {
149
- schedule.targetService.region = region;
142
+ else {
143
+ throw new error_1.FirebaseError(`Do not recognize trigger type for endpoint ${id}. Try upgrading ` +
144
+ "firebase-tools with npm install -g firebase-tools@latest");
150
145
  }
146
+ parsing_1.requireKeys(prefix, ep, "entryPoint");
147
+ const parsed = Object.assign({ platform: ep.platform || "gcfv2", id,
148
+ region,
149
+ project,
150
+ runtime, entryPoint: ep.entryPoint }, triggered);
151
+ proto_1.copyIfPresent(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeout", "vpcConnector", "vpcConnectorEgressSettings", "labels", "ingressSettings", "environmentVariables");
152
+ allParsed.push(parsed);
151
153
  }
154
+ return allParsed;
152
155
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addResourcesToBackend = exports.discoverBackend = exports.useStrategy = void 0;
3
+ exports.addResourcesToBackend = exports.discoverBackend = exports.useStrategy = exports.GCS_EVENTS = void 0;
4
4
  const path = require("path");
5
5
  const _ = require("lodash");
6
6
  const child_process_1 = require("child_process");
@@ -10,6 +10,12 @@ const backend = require("../../backend");
10
10
  const api = require("../../../../api");
11
11
  const proto = require("../../../../gcp/proto");
12
12
  const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js");
13
+ exports.GCS_EVENTS = new Set([
14
+ "google.cloud.storage.object.v1.finalized",
15
+ "google.cloud.storage.object.v1.archived",
16
+ "google.cloud.storage.object.v1.deleted",
17
+ "google.cloud.storage.object.v1.metadataUpdated",
18
+ ]);
13
19
  function removeInspectOptions(options) {
14
20
  return options.filter((opt) => !opt.startsWith("--inspect"));
15
21
  }
@@ -58,68 +64,58 @@ async function discoverBackend(projectId, sourceDir, runtime, configValues, envs
58
64
  }
59
65
  exports.discoverBackend = discoverBackend;
60
66
  function addResourcesToBackend(projectId, runtime, annotation, want) {
67
+ var _a;
61
68
  Object.freeze(annotation);
62
69
  for (const region of annotation.regions || [api.functionsDefaultRegion]) {
63
- let trigger;
64
- if (!!annotation.httpsTrigger == !!annotation.eventTrigger) {
70
+ let triggered;
71
+ const triggerCount = +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger;
72
+ if (triggerCount != 1) {
65
73
  throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
66
74
  }
67
- if (annotation.httpsTrigger) {
68
- trigger = {};
75
+ if (annotation.taskQueueTrigger) {
76
+ triggered = { taskQueueTrigger: annotation.taskQueueTrigger };
77
+ want.requiredAPIs["cloudtasks"] = "cloudtasks.googleapis.com";
78
+ }
79
+ else if (annotation.httpsTrigger) {
80
+ const trigger = {};
69
81
  if (annotation.failurePolicy) {
70
82
  logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
71
83
  }
72
- proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker", "invoker");
84
+ proto.copyIfPresent(trigger, annotation.httpsTrigger, "invoker");
85
+ triggered = { httpsTrigger: trigger };
86
+ }
87
+ else if (annotation.schedule) {
88
+ want.requiredAPIs["pubsub"] = "pubsub.googleapis.com";
89
+ want.requiredAPIs["scheduler"] = "cloudscheduler.googleapis.com";
90
+ triggered = { scheduleTrigger: annotation.schedule };
73
91
  }
74
92
  else {
75
- trigger = {
76
- eventType: annotation.eventTrigger.eventType,
77
- eventFilters: {
78
- resource: annotation.eventTrigger.resource,
93
+ triggered = {
94
+ eventTrigger: {
95
+ eventType: annotation.eventTrigger.eventType,
96
+ eventFilters: {
97
+ resource: annotation.eventTrigger.resource,
98
+ },
99
+ retry: !!annotation.failurePolicy,
79
100
  },
80
- retry: !!annotation.failurePolicy,
81
101
  };
102
+ if (exports.GCS_EVENTS.has(((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || "")) {
103
+ triggered.eventTrigger.eventFilters = {
104
+ bucket: annotation.eventTrigger.resource,
105
+ };
106
+ }
82
107
  }
83
- const cloudFunctionName = {
84
- id: annotation.name,
85
- region: region,
86
- project: projectId,
87
- };
88
- const cloudFunction = Object.assign(Object.assign({ platform: annotation.platform || "gcfv1" }, cloudFunctionName), { entryPoint: annotation.entryPoint, runtime: runtime, trigger: trigger });
108
+ const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", id: annotation.name, region: region, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime }, triggered);
89
109
  if (annotation.vpcConnector) {
90
110
  let maybeId = annotation.vpcConnector;
91
111
  if (!maybeId.includes("/")) {
92
112
  maybeId = `projects/${projectId}/locations/${region}/connectors/${maybeId}`;
93
113
  }
94
- cloudFunction.vpcConnector = maybeId;
95
- }
96
- proto.copyIfPresent(cloudFunction, annotation, "concurrency", "serviceAccountEmail", "labels", "vpcConnectorEgressSettings", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
97
- if (annotation.schedule) {
98
- want.requiredAPIs["pubsub"] = "pubsub.googleapis.com";
99
- want.requiredAPIs["scheduler"] = "cloudscheduler.googleapis.com";
100
- const id = backend.scheduleIdForFunction(cloudFunctionName);
101
- const schedule = {
102
- id,
103
- project: projectId,
104
- schedule: annotation.schedule.schedule,
105
- transport: "pubsub",
106
- targetService: cloudFunctionName,
107
- };
108
- proto.copyIfPresent(schedule, annotation.schedule, "timeZone", "retryConfig");
109
- want.schedules.push(schedule);
110
- const topic = {
111
- id,
112
- project: projectId,
113
- labels: backend.SCHEDULED_FUNCTION_LABEL,
114
- targetService: cloudFunctionName,
115
- };
116
- want.topics.push(topic);
117
- if (backend.isEventTrigger(cloudFunction.trigger)) {
118
- cloudFunction.trigger.eventFilters.resource = `${cloudFunction.trigger.eventFilters.resource}/${id}`;
119
- }
120
- cloudFunction.labels = Object.assign(Object.assign({}, cloudFunction.labels), { "deployment-scheduled": "true" });
114
+ endpoint.vpcConnector = maybeId;
121
115
  }
122
- want.cloudFunctions.push(cloudFunction);
116
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "vpcConnectorEgressSettings", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
117
+ want.endpoints[region] = want.endpoints[region] || {};
118
+ want.endpoints[region][endpoint.id] = endpoint;
123
119
  }
124
120
  }
125
121
  exports.addResourcesToBackend = addResourcesToBackend;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lookupMissingTriggerRegions = void 0;
4
+ const backend = require("./backend");
5
+ const storage = require("../../gcp/storage");
6
+ const error_1 = require("../../error");
7
+ const logger_1 = require("../../logger");
8
+ const noop = () => Promise.resolve();
9
+ const LOOKUP_BY_EVENT_TYPE = {
10
+ "google.cloud.pubsub.topic.v1.messagePublished": noop,
11
+ "google.cloud.storage.object.v1.finalized": lookupBucketRegion,
12
+ "google.cloud.storage.object.v1.archived": lookupBucketRegion,
13
+ "google.cloud.storage.object.v1.deleted": lookupBucketRegion,
14
+ "google.cloud.storage.object.v1.metadataUpdated": lookupBucketRegion,
15
+ };
16
+ async function lookupMissingTriggerRegions(want) {
17
+ const regionLookups = [];
18
+ for (const ep of backend.allEndpoints(want)) {
19
+ if (ep.platform === "gcfv1" || !backend.isEventTriggered(ep) || ep.eventTrigger.region) {
20
+ continue;
21
+ }
22
+ const lookup = LOOKUP_BY_EVENT_TYPE[ep.eventTrigger.eventType];
23
+ if (!lookup) {
24
+ logger_1.logger.debug("Don't know how to look up trigger region for event type", ep.eventTrigger.eventType, ". Deploy will fail unless this event type is global");
25
+ continue;
26
+ }
27
+ regionLookups.push(lookup(ep));
28
+ }
29
+ await Promise.all(regionLookups);
30
+ }
31
+ exports.lookupMissingTriggerRegions = lookupMissingTriggerRegions;
32
+ async function lookupBucketRegion(endpoint) {
33
+ try {
34
+ const bucket = await storage.getBucket(endpoint.eventTrigger.eventFilters.bucket);
35
+ endpoint.eventTrigger.region = bucket.location.toLowerCase();
36
+ }
37
+ catch (err) {
38
+ throw new error_1.FirebaseError("Can't find the storage bucket region", { original: err });
39
+ }
40
+ }