firebase-tools 9.17.0 → 9.21.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 (95) hide show
  1. package/CHANGELOG.md +3 -7
  2. package/lib/api.js +1 -0
  3. package/lib/apiv2.js +5 -3
  4. package/lib/appdistribution/client.js +84 -72
  5. package/lib/appdistribution/distribution.js +8 -26
  6. package/lib/appdistribution/options-parser-util.js +51 -0
  7. package/lib/command.js +1 -1
  8. package/lib/commands/appdistribution-distribute.js +74 -91
  9. package/lib/commands/appdistribution-testers-add.js +18 -0
  10. package/lib/commands/appdistribution-testers-remove.js +32 -0
  11. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  12. package/lib/commands/ext-configure.js +9 -1
  13. package/lib/commands/ext-dev-extension-delete.js +2 -1
  14. package/lib/commands/ext-dev-publish.js +11 -4
  15. package/lib/commands/ext-dev-unpublish.js +12 -4
  16. package/lib/commands/ext-install.js +115 -48
  17. package/lib/commands/ext-uninstall.js +6 -0
  18. package/lib/commands/ext-update.js +61 -18
  19. package/lib/commands/functions-config-export.js +115 -0
  20. package/lib/commands/functions-delete.js +45 -25
  21. package/lib/commands/functions-list.js +54 -0
  22. package/lib/commands/functions-log.js +5 -22
  23. package/lib/commands/hosting-channel-deploy.js +6 -4
  24. package/lib/commands/index.js +12 -0
  25. package/lib/deploy/functions/backend.js +118 -121
  26. package/lib/deploy/functions/checkIam.js +8 -8
  27. package/lib/deploy/functions/containerCleaner.js +5 -1
  28. package/lib/deploy/functions/deploy.js +11 -15
  29. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  30. package/lib/deploy/functions/prepare.js +67 -33
  31. package/lib/deploy/functions/pricing.js +17 -17
  32. package/lib/deploy/functions/prompts.js +24 -41
  33. package/lib/deploy/functions/release/executor.js +39 -0
  34. package/lib/deploy/functions/release/fabricator.js +362 -0
  35. package/lib/deploy/functions/release/index.js +69 -0
  36. package/lib/deploy/functions/release/planner.js +159 -0
  37. package/lib/deploy/functions/release/reporter.js +162 -0
  38. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  39. package/lib/deploy/functions/release/timer.js +14 -0
  40. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +102 -126
  41. package/lib/deploy/functions/runtimes/node/parseTriggers.js +34 -50
  42. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  43. package/lib/deploy/functions/validate.js +1 -24
  44. package/lib/downloadUtils.js +37 -0
  45. package/lib/emulator/auth/apiSpec.js +1788 -403
  46. package/lib/emulator/auth/handlers.js +6 -5
  47. package/lib/emulator/auth/operations.js +439 -40
  48. package/lib/emulator/auth/server.js +32 -11
  49. package/lib/emulator/auth/state.js +205 -5
  50. package/lib/emulator/auth/widget_ui.js +2 -2
  51. package/lib/emulator/download.js +2 -31
  52. package/lib/emulator/downloadableEmulators.js +7 -7
  53. package/lib/emulator/emulatorLogger.js +0 -3
  54. package/lib/emulator/events/types.js +16 -0
  55. package/lib/emulator/functionsEmulator.js +117 -20
  56. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  57. package/lib/emulator/functionsEmulatorShared.js +51 -7
  58. package/lib/emulator/functionsEmulatorShell.js +1 -1
  59. package/lib/emulator/pubsubEmulator.js +61 -40
  60. package/lib/emulator/storage/cloudFunctions.js +37 -7
  61. package/lib/extensions/askUserForConsent.js +16 -13
  62. package/lib/extensions/askUserForParam.js +72 -3
  63. package/lib/extensions/billingMigrationHelper.js +1 -11
  64. package/lib/extensions/changelog.js +2 -1
  65. package/lib/extensions/displayExtensionInfo.js +35 -33
  66. package/lib/extensions/emulator/optionsHelper.js +3 -3
  67. package/lib/extensions/emulator/triggerHelper.js +2 -32
  68. package/lib/extensions/extensionsApi.js +67 -94
  69. package/lib/extensions/extensionsHelper.js +49 -35
  70. package/lib/extensions/paramHelper.js +79 -36
  71. package/lib/extensions/refs.js +59 -0
  72. package/lib/extensions/secretsUtils.js +58 -0
  73. package/lib/extensions/updateHelper.js +21 -45
  74. package/lib/extensions/warnings.js +1 -7
  75. package/lib/functional.js +64 -0
  76. package/lib/functions/env.js +26 -13
  77. package/lib/functions/functionslog.js +40 -0
  78. package/lib/functions/runtimeConfigExport.js +137 -0
  79. package/lib/gcp/cloudfunctions.js +46 -38
  80. package/lib/gcp/cloudfunctionsv2.js +47 -47
  81. package/lib/gcp/cloudlogging.js +27 -21
  82. package/lib/gcp/cloudscheduler.js +22 -16
  83. package/lib/gcp/pubsub.js +1 -9
  84. package/lib/gcp/secretManager.js +111 -0
  85. package/lib/gcp/storage.js +16 -0
  86. package/lib/previews.js +1 -1
  87. package/lib/requireInteractive.js +12 -0
  88. package/lib/utils.js +30 -1
  89. package/package.json +5 -4
  90. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  91. package/lib/deploy/functions/deploymentTimer.js +0 -23
  92. package/lib/deploy/functions/errorHandler.js +0 -75
  93. package/lib/deploy/functions/release.js +0 -116
  94. package/lib/deploy/functions/tasks.js +0 -324
  95. package/lib/functionsDelete.js +0 -60
@@ -102,52 +102,52 @@ const MB_TO_GHZ = {
102
102
  4096: 4.8,
103
103
  8192: 4.8,
104
104
  };
105
- function canCalculateMinInstanceCost(functionSpec) {
106
- if (!functionSpec.minInstances) {
105
+ function canCalculateMinInstanceCost(endpoint) {
106
+ if (!endpoint.minInstances) {
107
107
  return true;
108
108
  }
109
- if (functionSpec.platform == "gcfv1") {
110
- if (!MB_TO_GHZ[functionSpec.availableMemoryMb || 256]) {
109
+ if (endpoint.platform == "gcfv1") {
110
+ if (!MB_TO_GHZ[endpoint.availableMemoryMb || 256]) {
111
111
  return false;
112
112
  }
113
- if (!V1_REGION_TO_TIER[functionSpec.region]) {
113
+ if (!V1_REGION_TO_TIER[endpoint.region]) {
114
114
  return false;
115
115
  }
116
116
  return true;
117
117
  }
118
- if (!V2_REGION_TO_TIER[functionSpec.region]) {
118
+ if (!V2_REGION_TO_TIER[endpoint.region]) {
119
119
  return false;
120
120
  }
121
121
  return true;
122
122
  }
123
123
  exports.canCalculateMinInstanceCost = canCalculateMinInstanceCost;
124
124
  const SECONDS_PER_MONTH = 30 * 24 * 60 * 60;
125
- function monthlyMinInstanceCost(functions) {
125
+ function monthlyMinInstanceCost(endpoints) {
126
126
  const usage = {
127
127
  gcfv1: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
128
128
  gcfv2: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
129
129
  };
130
- for (const func of functions) {
131
- if (!func.minInstances) {
130
+ for (const endpoint of endpoints) {
131
+ if (!endpoint.minInstances) {
132
132
  continue;
133
133
  }
134
- const ramMb = func.availableMemoryMb || 256;
134
+ const ramMb = endpoint.availableMemoryMb || 256;
135
135
  const ramGb = ramMb / 1024;
136
- if (func.platform === "gcfv1") {
136
+ if (endpoint.platform === "gcfv1") {
137
137
  const cpu = MB_TO_GHZ[ramMb];
138
- const tier = V1_REGION_TO_TIER[func.region];
138
+ const tier = V1_REGION_TO_TIER[endpoint.region];
139
139
  usage["gcfv1"][tier].ram =
140
- usage["gcfv1"][tier].ram + ramGb * SECONDS_PER_MONTH * func.minInstances;
140
+ usage["gcfv1"][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
141
141
  usage["gcfv1"][tier].cpu =
142
- usage["gcfv1"][tier].cpu + MB_TO_GHZ[ramMb] * SECONDS_PER_MONTH * func.minInstances;
142
+ usage["gcfv1"][tier].cpu + cpu * SECONDS_PER_MONTH * endpoint.minInstances;
143
143
  }
144
144
  else {
145
145
  const cpu = 1;
146
- const tier = V2_REGION_TO_TIER[func.region];
146
+ const tier = V2_REGION_TO_TIER[endpoint.region];
147
147
  usage["gcfv2"][tier].ram =
148
- usage["gcfv2"][tier].ram + ramGb * SECONDS_PER_MONTH * func.minInstances;
148
+ usage["gcfv2"][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
149
149
  usage["gcfv2"][tier].cpu =
150
- usage["gcfv2"][tier].cpu + cpu * SECONDS_PER_MONTH * func.minInstances;
150
+ usage["gcfv2"][tier].cpu + cpu * SECONDS_PER_MONTH * endpoint.minInstances;
151
151
  }
152
152
  }
153
153
  let v1MemoryBill = usage["gcfv1"][1].ram * exports.V1_RATES.memoryGb[1] + usage["gcfv1"][2].ram * exports.V1_RATES.memoryGb[2];
@@ -9,42 +9,23 @@ const logger_1 = require("../../logger");
9
9
  const backend = require("./backend");
10
10
  const pricing = require("./pricing");
11
11
  const utils = require("../../utils");
12
- function compareFunctions(left, right) {
13
- if (left.platform != right.platform) {
14
- return right.platform < left.platform ? -1 : 1;
15
- }
16
- if (left.region < right.region) {
17
- return -1;
18
- }
19
- if (left.region > right.region) {
20
- return 1;
21
- }
22
- if (left.id < right.id) {
23
- return -1;
24
- }
25
- if (left.id > right.id) {
26
- return 1;
27
- }
28
- return 0;
29
- }
30
12
  async function promptForFailurePolicies(options, want, have) {
31
- const retryFunctions = want.filter((fn) => {
32
- return backend.isEventTrigger(fn.trigger) && fn.trigger.retry;
13
+ const retryEndpoints = backend.allEndpoints(want).filter((e) => {
14
+ return backend.isEventTriggered(e) && e.eventTrigger.retry;
33
15
  });
34
- if (retryFunctions.length === 0) {
16
+ if (retryEndpoints.length === 0) {
35
17
  return;
36
18
  }
37
- const existingRetryFunctions = have.filter((fn) => {
38
- return backend.isEventTrigger(fn.trigger) && fn.trigger.retry;
39
- });
40
- const newRetryFunctions = retryFunctions.filter((fn) => {
41
- return !existingRetryFunctions.some(backend.sameFunctionName(fn));
19
+ const newRetryEndpoints = retryEndpoints.filter((endpoint) => {
20
+ var _a;
21
+ const existing = (_a = have.endpoints[endpoint.region]) === null || _a === void 0 ? void 0 : _a[endpoint.id];
22
+ return !(existing && backend.isEventTriggered(existing) && existing.eventTrigger.retry);
42
23
  });
43
- if (newRetryFunctions.length == 0) {
24
+ if (newRetryEndpoints.length == 0) {
44
25
  return;
45
26
  }
46
27
  const warnMessage = "The following functions will newly be retried in case of failure: " +
47
- clc.bold(newRetryFunctions.sort(compareFunctions).map(functionsDeployHelper_1.getFunctionLabel).join(", ")) +
28
+ clc.bold(newRetryEndpoints.sort(backend.compareFunctions).map(functionsDeployHelper_1.getFunctionLabel).join(", ")) +
48
29
  ". " +
49
30
  "Retried executions are billed as any other execution, and functions are retried repeatedly until they either successfully execute or the maximum retry period has elapsed, which can be up to 7 days. " +
50
31
  "For safety, you might want to ensure that your functions are idempotent; see https://firebase.google.com/docs/functions/retries to learn more.";
@@ -74,7 +55,7 @@ async function promptForFunctionDeletion(functionsToDelete, force, nonInteractiv
74
55
  return true;
75
56
  }
76
57
  const deleteList = functionsToDelete
77
- .sort(compareFunctions)
58
+ .sort(backend.compareFunctions)
78
59
  .map((fn) => "\t" + functionsDeployHelper_1.getFunctionLabel(fn))
79
60
  .join("\n");
80
61
  if (nonInteractive) {
@@ -108,21 +89,22 @@ async function promptForMinInstances(options, want, have) {
108
89
  if (options.force) {
109
90
  return;
110
91
  }
111
- const increasesCost = want.some((wantFn) => {
112
- if (!pricing.canCalculateMinInstanceCost(wantFn)) {
92
+ const increasesCost = backend.someEndpoint(want, (wantE) => {
93
+ var _a;
94
+ if (!pricing.canCalculateMinInstanceCost(wantE)) {
113
95
  return true;
114
96
  }
115
- const wantCost = pricing.monthlyMinInstanceCost([wantFn]);
116
- const haveFn = have.find(backend.sameFunctionName(wantFn));
97
+ const wantCost = pricing.monthlyMinInstanceCost([wantE]);
98
+ const haveE = (_a = have.endpoints[wantE.region]) === null || _a === void 0 ? void 0 : _a[wantE.id];
117
99
  let haveCost;
118
- if (!haveFn) {
100
+ if (!haveE) {
119
101
  haveCost = 0;
120
102
  }
121
- else if (!pricing.canCalculateMinInstanceCost(wantFn)) {
103
+ else if (!pricing.canCalculateMinInstanceCost(wantE)) {
122
104
  return true;
123
105
  }
124
106
  else {
125
- haveCost = pricing.monthlyMinInstanceCost([haveFn]);
107
+ haveCost = pricing.monthlyMinInstanceCost([haveE]);
126
108
  }
127
109
  return wantCost > haveCost;
128
110
  });
@@ -134,9 +116,10 @@ async function promptForMinInstances(options, want, have) {
134
116
  exit: 1,
135
117
  });
136
118
  }
137
- const functionLines = want
119
+ const functionLines = backend
120
+ .allEndpoints(want)
138
121
  .filter((fn) => fn.minInstances)
139
- .sort(compareFunctions)
122
+ .sort(backend.compareFunctions)
140
123
  .map((fn) => {
141
124
  return (`\t${functionsDeployHelper_1.getFunctionLabel(fn)}: ${fn.minInstances} instances, ` +
142
125
  backend.memoryOptionDisplayName(fn.availableMemoryMb || 256) +
@@ -144,17 +127,17 @@ async function promptForMinInstances(options, want, have) {
144
127
  })
145
128
  .join("\n");
146
129
  let costLine;
147
- if (want.some((fn) => !pricing.canCalculateMinInstanceCost(fn))) {
130
+ if (backend.someEndpoint(want, (fn) => !pricing.canCalculateMinInstanceCost(fn))) {
148
131
  costLine =
149
132
  "Cannot calculate the minimum monthly bill for this configuration. Consider running " +
150
133
  clc.bold("npm install -g firebase-tools");
151
134
  }
152
135
  else {
153
- const cost = pricing.monthlyMinInstanceCost(want).toFixed(2);
136
+ const cost = pricing.monthlyMinInstanceCost(backend.allEndpoints(want)).toFixed(2);
154
137
  costLine = `With these options, your minimum bill will be $${cost} in a 30-day month`;
155
138
  }
156
139
  let cudAnnotation = "";
157
- if (want.some((fn) => fn.platform == "gcfv2" && fn.minInstances)) {
140
+ if (backend.someEndpoint(want, (fn) => fn.platform == "gcfv2" && !!fn.minInstances)) {
158
141
  cudAnnotation =
159
142
  "\nThis bill can be lowered with a one year commitment. See https://cloud.google.com/run/cud for more";
160
143
  }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InlineExecutor = exports.QueueExecutor = void 0;
4
+ const queue_1 = require("../../../throttler/queue");
5
+ async function handler(op) {
6
+ var _a, _b, _c, _d, _e, _f;
7
+ try {
8
+ op.result = await op.func();
9
+ }
10
+ catch (err) {
11
+ const code = err.status ||
12
+ err.code || ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) || ((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) || ((_f = (_e = (_d = err.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);
13
+ if (code === 429 || code === 409) {
14
+ throw err;
15
+ }
16
+ op.error = err;
17
+ }
18
+ return;
19
+ }
20
+ class QueueExecutor {
21
+ constructor(options) {
22
+ this.queue = new queue_1.Queue(Object.assign(Object.assign({}, options), { handler }));
23
+ }
24
+ async run(func) {
25
+ const op = { func };
26
+ await this.queue.run(op);
27
+ if (op.error) {
28
+ throw op.error;
29
+ }
30
+ return op.result;
31
+ }
32
+ }
33
+ exports.QueueExecutor = QueueExecutor;
34
+ class InlineExecutor {
35
+ run(func) {
36
+ return func();
37
+ }
38
+ }
39
+ exports.InlineExecutor = InlineExecutor;
@@ -0,0 +1,362 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Fabricator = void 0;
4
+ const clc = require("cli-color");
5
+ const error_1 = require("../../../error");
6
+ const sourceTokenScraper_1 = require("./sourceTokenScraper");
7
+ const timer_1 = require("./timer");
8
+ const functional_1 = require("../../../functional");
9
+ const runtimes_1 = require("../runtimes");
10
+ const api_1 = require("../../../api");
11
+ const logger_1 = require("../../../logger");
12
+ const backend = require("../backend");
13
+ const deploymentTool = require("../../../deploymentTool");
14
+ const gcf = require("../../../gcp/cloudfunctions");
15
+ const gcfV2 = require("../../../gcp/cloudfunctionsv2");
16
+ const helper = require("../functionsDeployHelper");
17
+ const poller = require("../../../operation-poller");
18
+ const pubsub = require("../../../gcp/pubsub");
19
+ const reporter = require("./reporter");
20
+ const run = require("../../../gcp/run");
21
+ const scheduler = require("../../../gcp/cloudscheduler");
22
+ const utils = require("../../../utils");
23
+ const gcfV1PollerOptions = {
24
+ apiOrigin: api_1.functionsOrigin,
25
+ apiVersion: gcf.API_VERSION,
26
+ masterTimeout: 25 * 60 * 1000,
27
+ };
28
+ const gcfV2PollerOptions = {
29
+ apiOrigin: api_1.functionsV2Origin,
30
+ apiVersion: gcfV2.API_VERSION,
31
+ masterTimeout: 25 * 60 * 1000,
32
+ };
33
+ const DEFAULT_GCFV2_CONCURRENCY = 80;
34
+ const rethrowAs = (endpoint, op) => (err) => {
35
+ throw new reporter.DeploymentError(endpoint, op, err);
36
+ };
37
+ class Fabricator {
38
+ constructor(args) {
39
+ this.executor = args.executor;
40
+ this.functionExecutor = args.functionExecutor;
41
+ this.sourceUrl = args.sourceUrl;
42
+ this.storage = args.storage;
43
+ this.appEngineLocation = args.appEngineLocation;
44
+ }
45
+ async applyPlan(plan) {
46
+ const timer = new timer_1.Timer();
47
+ const summary = {
48
+ totalTime: 0,
49
+ results: [],
50
+ };
51
+ const deployRegions = Object.values(plan).map(async (changes) => {
52
+ const results = await this.applyRegionalChanges(changes);
53
+ summary.results.push(...results);
54
+ return;
55
+ });
56
+ const promiseResults = await utils.allSettled(deployRegions);
57
+ const errs = promiseResults
58
+ .filter((r) => r.status === "rejected")
59
+ .map((r) => r.reason);
60
+ if (errs.length) {
61
+ logger_1.logger.debug("Fabricator.applyRegionalChanges returned an unhandled exception. This should never happen", JSON.stringify(errs, null, 2));
62
+ }
63
+ summary.totalTime = timer.stop();
64
+ return summary;
65
+ }
66
+ async applyRegionalChanges(changes) {
67
+ const deployResults = [];
68
+ const handle = async (op, endpoint, fn) => {
69
+ const timer = new timer_1.Timer();
70
+ const result = { endpoint };
71
+ try {
72
+ await fn();
73
+ this.logOpSuccess(op, endpoint);
74
+ }
75
+ catch (err) {
76
+ result.error = err;
77
+ }
78
+ result.durationMs = timer.stop();
79
+ deployResults.push(result);
80
+ };
81
+ const upserts = [];
82
+ const scraper = new sourceTokenScraper_1.SourceTokenScraper();
83
+ for (const endpoint of changes.endpointsToCreate) {
84
+ this.logOpStart("creating", endpoint);
85
+ upserts.push(handle("create", endpoint, () => this.createEndpoint(endpoint, scraper)));
86
+ }
87
+ for (const update of changes.endpointsToUpdate) {
88
+ this.logOpStart("updating", update.endpoint);
89
+ upserts.push(handle("update", update.endpoint, () => this.updateEndpoint(update, scraper)));
90
+ }
91
+ await utils.allSettled(upserts);
92
+ if (deployResults.find((r) => r.error)) {
93
+ for (const endpoint of changes.endpointsToDelete) {
94
+ deployResults.push({
95
+ endpoint,
96
+ durationMs: 0,
97
+ error: new reporter.AbortedDeploymentError(endpoint),
98
+ });
99
+ }
100
+ return deployResults;
101
+ }
102
+ const deletes = [];
103
+ for (const endpoint of changes.endpointsToDelete) {
104
+ this.logOpStart("deleting", endpoint);
105
+ deletes.push(handle("delete", endpoint, () => this.deleteEndpoint(endpoint)));
106
+ }
107
+ await utils.allSettled(deletes);
108
+ return deployResults;
109
+ }
110
+ async createEndpoint(endpoint, scraper) {
111
+ endpoint.labels = Object.assign(Object.assign({}, endpoint.labels), deploymentTool.labels());
112
+ if (endpoint.platform === "gcfv1") {
113
+ await this.createV1Function(endpoint, scraper);
114
+ }
115
+ else if (endpoint.platform === "gcfv2") {
116
+ await this.createV2Function(endpoint);
117
+ }
118
+ else {
119
+ functional_1.assertExhaustive(endpoint.platform);
120
+ }
121
+ await this.setTrigger(endpoint);
122
+ }
123
+ async updateEndpoint(update, scraper) {
124
+ update.endpoint.labels = Object.assign(Object.assign({}, update.endpoint.labels), deploymentTool.labels());
125
+ if (update.deleteAndRecreate) {
126
+ await this.deleteEndpoint(update.deleteAndRecreate);
127
+ await this.createEndpoint(update.endpoint, scraper);
128
+ return;
129
+ }
130
+ if (update.endpoint.platform === "gcfv1") {
131
+ await this.updateV1Function(update.endpoint, scraper);
132
+ }
133
+ else if (update.endpoint.platform === "gcfv2") {
134
+ await this.updateV2Function(update.endpoint);
135
+ }
136
+ else {
137
+ functional_1.assertExhaustive(update.endpoint.platform);
138
+ }
139
+ await this.setTrigger(update.endpoint);
140
+ }
141
+ async deleteEndpoint(endpoint) {
142
+ await this.deleteTrigger(endpoint);
143
+ if (endpoint.platform === "gcfv1") {
144
+ await this.deleteV1Function(endpoint);
145
+ }
146
+ else {
147
+ await this.deleteV2Function(endpoint);
148
+ }
149
+ }
150
+ async createV1Function(endpoint, scraper) {
151
+ var _a;
152
+ if (!this.sourceUrl) {
153
+ logger_1.logger.debug("Precondition failed. Cannot create a GCF function without sourceUrl");
154
+ throw new Error("Precondition failed");
155
+ }
156
+ const apiFunction = gcf.functionFromEndpoint(endpoint, this.sourceUrl);
157
+ apiFunction.sourceToken = await scraper.tokenPromise();
158
+ const resultFunction = await this.functionExecutor
159
+ .run(async () => {
160
+ const op = await gcf.createFunction(apiFunction);
161
+ return poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `create-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
162
+ })
163
+ .catch(rethrowAs(endpoint, "create"));
164
+ endpoint.uri = (_a = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _a === void 0 ? void 0 : _a.url;
165
+ if (backend.isHttpsTriggered(endpoint)) {
166
+ const invoker = endpoint.httpsTrigger.invoker || ["public"];
167
+ if (!invoker.includes("private")) {
168
+ await this.executor
169
+ .run(async () => {
170
+ await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
171
+ })
172
+ .catch(rethrowAs(endpoint, "set invoker"));
173
+ }
174
+ }
175
+ }
176
+ async createV2Function(endpoint) {
177
+ var _a;
178
+ if (!this.storage) {
179
+ logger_1.logger.debug("Precondition failed. Cannot create a GCFv2 function without storage");
180
+ throw new Error("Precondition failed");
181
+ }
182
+ const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage[endpoint.region]);
183
+ const topic = (_a = apiFunction.eventTrigger) === null || _a === void 0 ? void 0 : _a.pubsubTopic;
184
+ if (topic) {
185
+ await this.executor
186
+ .run(async () => {
187
+ try {
188
+ await pubsub.createTopic({ name: topic });
189
+ }
190
+ catch (err) {
191
+ if (err.status === 409) {
192
+ return;
193
+ }
194
+ throw new error_1.FirebaseError("Unexpected error creating Pub/Sub topic", {
195
+ original: err,
196
+ });
197
+ }
198
+ })
199
+ .catch(rethrowAs(endpoint, "create topic"));
200
+ }
201
+ const resultFunction = (await this.functionExecutor
202
+ .run(async () => {
203
+ const op = await gcfV2.createFunction(apiFunction);
204
+ return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
205
+ })
206
+ .catch(rethrowAs(endpoint, "create")));
207
+ endpoint.uri = resultFunction.serviceConfig.uri;
208
+ const serviceName = resultFunction.serviceConfig.service;
209
+ if (backend.isHttpsTriggered(endpoint)) {
210
+ const invoker = endpoint.httpsTrigger.invoker || ["public"];
211
+ if (!invoker.includes("private")) {
212
+ await this.executor
213
+ .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker))
214
+ .catch(rethrowAs(endpoint, "set invoker"));
215
+ }
216
+ }
217
+ await this.setConcurrency(endpoint, serviceName, endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY);
218
+ }
219
+ async updateV1Function(endpoint, scraper) {
220
+ var _a;
221
+ if (!this.sourceUrl) {
222
+ logger_1.logger.debug("Precondition failed. Cannot update a GCF function without sourceUrl");
223
+ throw new Error("Precondition failed");
224
+ }
225
+ const apiFunction = gcf.functionFromEndpoint(endpoint, this.sourceUrl);
226
+ apiFunction.sourceToken = await scraper.tokenPromise();
227
+ const resultFunction = await this.functionExecutor
228
+ .run(async () => {
229
+ const op = await gcf.updateFunction(apiFunction);
230
+ return await poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `update-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
231
+ })
232
+ .catch(rethrowAs(endpoint, "update"));
233
+ endpoint.uri = (_a = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _a === void 0 ? void 0 : _a.url;
234
+ if (backend.isHttpsTriggered(endpoint) && endpoint.httpsTrigger.invoker) {
235
+ await this.executor
236
+ .run(async () => {
237
+ await gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), endpoint.httpsTrigger.invoker);
238
+ return;
239
+ })
240
+ .catch(rethrowAs(endpoint, "set invoker"));
241
+ }
242
+ }
243
+ async updateV2Function(endpoint) {
244
+ var _a;
245
+ if (!this.storage) {
246
+ logger_1.logger.debug("Precondition failed. Cannot update a GCFv2 function without storage");
247
+ throw new Error("Precondition failed");
248
+ }
249
+ const apiFunction = gcfV2.functionFromEndpoint(endpoint, this.storage[endpoint.region]);
250
+ if ((_a = apiFunction.eventTrigger) === null || _a === void 0 ? void 0 : _a.pubsubTopic) {
251
+ delete apiFunction.eventTrigger.pubsubTopic;
252
+ }
253
+ const resultFunction = (await this.functionExecutor
254
+ .run(async () => {
255
+ const op = await gcfV2.updateFunction(apiFunction);
256
+ return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
257
+ })
258
+ .catch(rethrowAs(endpoint, "update")));
259
+ endpoint.uri = resultFunction.serviceConfig.uri;
260
+ const serviceName = resultFunction.serviceConfig.service;
261
+ if (backend.isHttpsTriggered(endpoint) && endpoint.httpsTrigger.invoker) {
262
+ await this.executor
263
+ .run(() => run.setInvokerUpdate(endpoint.project, serviceName, endpoint.httpsTrigger.invoker))
264
+ .catch(rethrowAs(endpoint, "set invoker"));
265
+ }
266
+ if (endpoint.concurrency) {
267
+ await this.setConcurrency(endpoint, serviceName, endpoint.concurrency);
268
+ }
269
+ }
270
+ async deleteV1Function(endpoint) {
271
+ const fnName = backend.functionName(endpoint);
272
+ await this.functionExecutor
273
+ .run(async () => {
274
+ const op = await gcf.deleteFunction(fnName);
275
+ const pollerOptions = Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `delete-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
276
+ await poller.pollOperation(pollerOptions);
277
+ })
278
+ .catch(rethrowAs(endpoint, "delete"));
279
+ }
280
+ async deleteV2Function(endpoint) {
281
+ const fnName = backend.functionName(endpoint);
282
+ await this.functionExecutor
283
+ .run(async () => {
284
+ const op = await gcfV2.deleteFunction(fnName);
285
+ const pollerOptions = Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `delete-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
286
+ await poller.pollOperation(pollerOptions);
287
+ })
288
+ .catch(rethrowAs(endpoint, "delete"));
289
+ }
290
+ async setConcurrency(endpoint, serviceName, concurrency) {
291
+ await this.functionExecutor
292
+ .run(async () => {
293
+ const service = await run.getService(serviceName);
294
+ if (service.spec.template.spec.containerConcurrency === concurrency) {
295
+ logger_1.logger.debug("Skipping setConcurrency on", serviceName, " because it already matches");
296
+ return;
297
+ }
298
+ delete service.status;
299
+ delete service.spec.template.metadata.name;
300
+ service.spec.template.spec.containerConcurrency = concurrency;
301
+ await run.replaceService(serviceName, service);
302
+ })
303
+ .catch(rethrowAs(endpoint, "set concurrency"));
304
+ }
305
+ async setTrigger(endpoint) {
306
+ if (backend.isScheduleTriggered(endpoint)) {
307
+ if (endpoint.platform === "gcfv1") {
308
+ await this.upsertScheduleV1(endpoint);
309
+ return;
310
+ }
311
+ else if (endpoint.platform === "gcfv2") {
312
+ await this.upsertScheduleV2(endpoint);
313
+ return;
314
+ }
315
+ functional_1.assertExhaustive(endpoint.platform);
316
+ }
317
+ }
318
+ async deleteTrigger(endpoint) {
319
+ if (backend.isScheduleTriggered(endpoint)) {
320
+ if (endpoint.platform === "gcfv1") {
321
+ await this.deleteScheduleV1(endpoint);
322
+ return;
323
+ }
324
+ else if (endpoint.platform === "gcfv2") {
325
+ await this.deleteScheduleV2(endpoint);
326
+ return;
327
+ }
328
+ functional_1.assertExhaustive(endpoint.platform);
329
+ }
330
+ }
331
+ async upsertScheduleV1(endpoint) {
332
+ const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
333
+ await this.executor
334
+ .run(() => scheduler.createOrReplaceJob(job))
335
+ .catch(rethrowAs(endpoint, "upsert schedule"));
336
+ }
337
+ upsertScheduleV2(endpoint) {
338
+ return Promise.reject(new reporter.DeploymentError(endpoint, "upsert schedule", new Error("Not implemented")));
339
+ }
340
+ async deleteScheduleV1(endpoint) {
341
+ const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
342
+ await this.executor
343
+ .run(() => scheduler.deleteJob(job.name))
344
+ .catch(rethrowAs(endpoint, "delete schedule"));
345
+ await this.executor
346
+ .run(() => pubsub.deleteTopic(job.pubsubTarget.topicName))
347
+ .catch(rethrowAs(endpoint, "delete topic"));
348
+ }
349
+ deleteScheduleV2(endpoint) {
350
+ return Promise.reject(new reporter.DeploymentError(endpoint, "delete schedule", new Error("Not implemented")));
351
+ }
352
+ logOpStart(op, endpoint) {
353
+ const runtime = runtimes_1.getHumanFriendlyRuntimeName(endpoint.runtime);
354
+ const label = helper.getFunctionLabel(endpoint);
355
+ utils.logBullet(`${clc.bold.cyan("functions:")} ${op} ${runtime} function ${clc.bold(label)}...`);
356
+ }
357
+ logOpSuccess(op, endpoint) {
358
+ const label = helper.getFunctionLabel(endpoint);
359
+ utils.logSuccess(`${clc.bold.green(`functions[${label}]`)} Successful ${op} operation.`);
360
+ }
361
+ }
362
+ exports.Fabricator = Fabricator;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printTriggerUrls = exports.release = void 0;
4
+ const clc = require("cli-color");
5
+ const logger_1 = require("../../../logger");
6
+ const functional_1 = require("../../../functional");
7
+ const backend = require("../backend");
8
+ const containerCleaner = require("../containerCleaner");
9
+ const planner = require("./planner");
10
+ const fabricator = require("./fabricator");
11
+ const reporter = require("./reporter");
12
+ const executor = require("./executor");
13
+ const prompts = require("../prompts");
14
+ const functionsConfig_1 = require("../../../functionsConfig");
15
+ const functionsDeployHelper_1 = require("../functionsDeployHelper");
16
+ const error_1 = require("../../../error");
17
+ async function release(context, options, payload) {
18
+ if (!options.config.has("functions")) {
19
+ return;
20
+ }
21
+ const plan = planner.createDeploymentPlan(payload.functions.backend, await backend.existingBackend(context), { filters: context.filters });
22
+ const fnsToDelete = Object.values(plan)
23
+ .map((regionalChanges) => regionalChanges.endpointsToDelete)
24
+ .reduce(functional_1.reduceFlat, []);
25
+ const shouldDelete = await prompts.promptForFunctionDeletion(fnsToDelete, options.force, options.nonInteractive);
26
+ if (!shouldDelete) {
27
+ for (const change of Object.values(plan)) {
28
+ change.endpointsToDelete = [];
29
+ }
30
+ }
31
+ const functionExecutor = new executor.QueueExecutor({
32
+ retries: 30,
33
+ backoff: 20000,
34
+ concurrency: 40,
35
+ maxBackoff: 40000,
36
+ });
37
+ const fab = new fabricator.Fabricator({
38
+ functionExecutor,
39
+ executor: new executor.QueueExecutor({}),
40
+ sourceUrl: context.uploadUrl,
41
+ storage: context.storage,
42
+ appEngineLocation: functionsConfig_1.getAppEngineLocation(context.firebaseConfig),
43
+ });
44
+ const summary = await fab.applyPlan(plan);
45
+ await reporter.logAndTrackDeployStats(summary);
46
+ reporter.printErrors(summary);
47
+ printTriggerUrls(payload.functions.backend);
48
+ await containerCleaner.cleanupBuildImages(backend.allEndpoints(payload.functions.backend));
49
+ const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
50
+ if (allErrors.length) {
51
+ const opts = allErrors.length == 1 ? { original: allErrors[0] } : { children: allErrors };
52
+ throw new error_1.FirebaseError("There was an error deploying functions", Object.assign(Object.assign({}, opts), { exit: 2 }));
53
+ }
54
+ }
55
+ exports.release = release;
56
+ function printTriggerUrls(results) {
57
+ const httpsFunctions = backend.allEndpoints(results).filter(backend.isHttpsTriggered);
58
+ if (httpsFunctions.length === 0) {
59
+ return;
60
+ }
61
+ for (const httpsFunc of httpsFunctions) {
62
+ if (!httpsFunc.uri) {
63
+ logger_1.logger.debug("Missing URI for HTTPS function in printTriggerUrls. This shouldn't happen");
64
+ continue;
65
+ }
66
+ logger_1.logger.info(clc.bold("Function URL"), `(${functionsDeployHelper_1.getFunctionLabel(httpsFunc)}):`, httpsFunc.uri);
67
+ }
68
+ }
69
+ exports.printTriggerUrls = printTriggerUrls;