firebase-tools 9.18.0 → 9.22.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 (114) hide show
  1. package/CHANGELOG.md +3 -6
  2. package/lib/api.js +3 -0
  3. package/lib/apiv2.js +8 -5
  4. package/lib/command.js +1 -1
  5. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  6. package/lib/commands/deploy.js +9 -1
  7. package/lib/commands/ext-configure.js +9 -2
  8. package/lib/commands/ext-dev-deprecate.js +63 -0
  9. package/lib/commands/ext-dev-extension-delete.js +2 -1
  10. package/lib/commands/ext-dev-publish.js +10 -4
  11. package/lib/commands/ext-dev-undeprecate.js +56 -0
  12. package/lib/commands/ext-dev-unpublish.js +12 -4
  13. package/lib/commands/ext-export.js +44 -0
  14. package/lib/commands/ext-install.js +50 -13
  15. package/lib/commands/ext-uninstall.js +6 -0
  16. package/lib/commands/ext-update.js +60 -18
  17. package/lib/commands/functions-config-export.js +115 -0
  18. package/lib/commands/functions-delete.js +47 -25
  19. package/lib/commands/functions-list.js +12 -12
  20. package/lib/commands/index.js +9 -0
  21. package/lib/commands/init.js +3 -0
  22. package/lib/config.js +3 -2
  23. package/lib/deploy/extensions/args.js +2 -0
  24. package/lib/deploy/extensions/deploy.js +49 -0
  25. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  26. package/lib/deploy/extensions/errors.js +31 -0
  27. package/lib/deploy/extensions/index.js +8 -0
  28. package/lib/deploy/extensions/planner.js +95 -0
  29. package/lib/deploy/extensions/prepare.js +103 -0
  30. package/lib/deploy/extensions/release.js +43 -0
  31. package/lib/deploy/extensions/secrets.js +150 -0
  32. package/lib/deploy/extensions/tasks.js +98 -0
  33. package/lib/deploy/extensions/validate.js +17 -0
  34. package/lib/deploy/functions/backend.js +93 -115
  35. package/lib/deploy/functions/checkIam.js +8 -8
  36. package/lib/deploy/functions/containerCleaner.js +82 -22
  37. package/lib/deploy/functions/deploy.js +4 -10
  38. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  39. package/lib/deploy/functions/prepare.js +62 -27
  40. package/lib/deploy/functions/pricing.js +17 -17
  41. package/lib/deploy/functions/prompts.js +22 -21
  42. package/lib/deploy/functions/release/executor.js +39 -0
  43. package/lib/deploy/functions/release/fabricator.js +422 -0
  44. package/lib/deploy/functions/release/index.js +73 -0
  45. package/lib/deploy/functions/release/planner.js +162 -0
  46. package/lib/deploy/functions/release/reporter.js +165 -0
  47. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  48. package/lib/deploy/functions/release/timer.js +14 -0
  49. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
  50. package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
  51. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  52. package/lib/deploy/functions/validate.js +1 -24
  53. package/lib/deploy/index.js +1 -0
  54. package/lib/downloadUtils.js +37 -0
  55. package/lib/emulator/auth/apiSpec.js +1788 -403
  56. package/lib/emulator/auth/handlers.js +6 -5
  57. package/lib/emulator/auth/operations.js +439 -40
  58. package/lib/emulator/auth/server.js +32 -11
  59. package/lib/emulator/auth/state.js +205 -5
  60. package/lib/emulator/auth/widget_ui.js +2 -2
  61. package/lib/emulator/download.js +2 -31
  62. package/lib/emulator/downloadableEmulators.js +7 -7
  63. package/lib/emulator/emulatorLogger.js +0 -3
  64. package/lib/emulator/events/types.js +16 -0
  65. package/lib/emulator/functionsEmulator.js +120 -21
  66. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  67. package/lib/emulator/functionsEmulatorShared.js +51 -7
  68. package/lib/emulator/functionsEmulatorShell.js +1 -1
  69. package/lib/emulator/pubsubEmulator.js +61 -40
  70. package/lib/emulator/storage/cloudFunctions.js +37 -7
  71. package/lib/extensions/askUserForConsent.js +14 -1
  72. package/lib/extensions/askUserForParam.js +81 -4
  73. package/lib/extensions/billingMigrationHelper.js +1 -11
  74. package/lib/extensions/changelog.js +2 -1
  75. package/lib/extensions/checkProjectBilling.js +7 -7
  76. package/lib/extensions/displayExtensionInfo.js +35 -33
  77. package/lib/extensions/emulator/optionsHelper.js +3 -3
  78. package/lib/extensions/emulator/triggerHelper.js +2 -32
  79. package/lib/extensions/export.js +107 -0
  80. package/lib/extensions/extensionsApi.js +149 -97
  81. package/lib/extensions/extensionsHelper.js +36 -32
  82. package/lib/extensions/listExtensions.js +16 -11
  83. package/lib/extensions/paramHelper.js +73 -40
  84. package/lib/extensions/provisioningHelper.js +16 -3
  85. package/lib/extensions/refs.js +67 -0
  86. package/lib/extensions/secretsUtils.js +59 -0
  87. package/lib/extensions/updateHelper.js +33 -47
  88. package/lib/extensions/versionHelper.js +14 -0
  89. package/lib/extensions/warnings.js +33 -1
  90. package/lib/functional.js +64 -0
  91. package/lib/functions/env.js +26 -13
  92. package/lib/functions/runtimeConfigExport.js +137 -0
  93. package/lib/gcp/artifactregistry.js +16 -0
  94. package/lib/gcp/cloudfunctions.js +65 -35
  95. package/lib/gcp/cloudfunctionsv2.js +56 -43
  96. package/lib/gcp/cloudscheduler.js +22 -16
  97. package/lib/gcp/cloudtasks.js +143 -0
  98. package/lib/gcp/docker.js +7 -1
  99. package/lib/gcp/proto.js +2 -2
  100. package/lib/gcp/pubsub.js +1 -9
  101. package/lib/gcp/secretManager.js +132 -0
  102. package/lib/gcp/storage.js +16 -0
  103. package/lib/previews.js +1 -1
  104. package/lib/requireInteractive.js +12 -0
  105. package/lib/utils.js +30 -1
  106. package/package.json +6 -4
  107. package/schema/firebase-config.json +9 -0
  108. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  109. package/lib/deploy/functions/deploymentTimer.js +0 -23
  110. package/lib/deploy/functions/errorHandler.js +0 -75
  111. package/lib/deploy/functions/release.js +0 -116
  112. package/lib/deploy/functions/tasks.js +0 -324
  113. package/lib/functions/listFunctions.js +0 -10
  114. package/lib/functionsDelete.js +0 -60
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkBilling = void 0;
4
+ const cloudbilling_1 = require("../../gcp/cloudbilling");
5
+ const checkProjectBilling_1 = require("../../extensions/checkProjectBilling");
6
+ const error_1 = require("../../error");
7
+ async function checkBilling(projectId, nonInteractive) {
8
+ const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
9
+ if (!enabled && nonInteractive) {
10
+ throw new error_1.FirebaseError(`Extensions require the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
11
+ `Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project.`);
12
+ }
13
+ else if (!enabled) {
14
+ await checkProjectBilling_1.enableBilling(projectId);
15
+ }
16
+ }
17
+ exports.checkBilling = checkBilling;
@@ -1,38 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compareFunctions = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.topicName = exports.scheduleName = exports.sameFunctionName = exports.functionName = exports.isEmptyBackend = exports.empty = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.triggerTag = exports.isEventTrigger = void 0;
3
+ exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = 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.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = 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");
7
7
  const error_1 = require("../../error");
8
8
  const previews_1 = require("../../previews");
9
- function isEventTrigger(trigger) {
10
- return "eventType" in trigger;
11
- }
12
- exports.isEventTrigger = isEventTrigger;
13
- function triggerTag(fn) {
14
- var _a, _b;
15
- if ((_a = fn.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) {
16
- if (fn.platform === "gcfv1") {
17
- return "v1.scheduled";
18
- }
19
- return "v2.scheduled";
9
+ function endpointTriggerType(endpoint) {
10
+ if (isScheduleTriggered(endpoint)) {
11
+ return "scheduled";
20
12
  }
21
- if ((_b = fn.labels) === null || _b === void 0 ? void 0 : _b["deployment-callable"]) {
22
- if (fn.platform === "gcfv1") {
23
- return "v1.callable";
24
- }
25
- return "v2.callable";
13
+ else if (isHttpsTriggered(endpoint)) {
14
+ return "https";
26
15
  }
27
- if (!isEventTrigger(fn.trigger)) {
28
- if (fn.platform === "gcfv1") {
29
- return "v1.https";
30
- }
31
- return "v2.https";
16
+ else if (isEventTriggered(endpoint)) {
17
+ return endpoint.eventTrigger.eventType;
18
+ }
19
+ else if (isTaskQueueTriggered(endpoint)) {
20
+ return "taskQueue";
21
+ }
22
+ else {
23
+ throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
32
24
  }
33
- return fn.trigger.eventType;
34
25
  }
35
- exports.triggerTag = triggerTag;
26
+ exports.endpointTriggerType = endpointTriggerType;
36
27
  function memoryOptionDisplayName(option) {
37
28
  return {
38
29
  128: "128MB",
@@ -58,39 +49,38 @@ function isScheduleTriggered(triggered) {
58
49
  return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
59
50
  }
60
51
  exports.isScheduleTriggered = isScheduleTriggered;
52
+ function isTaskQueueTriggered(triggered) {
53
+ return {}.hasOwnProperty.call(triggered, "taskQueueTrigger");
54
+ }
55
+ exports.isTaskQueueTriggered = isTaskQueueTriggered;
61
56
  function empty() {
62
57
  return {
63
58
  requiredAPIs: {},
64
- endpoints: [],
65
- cloudFunctions: [],
66
- schedules: [],
67
- topics: [],
59
+ endpoints: {},
68
60
  environmentVariables: {},
69
61
  };
70
62
  }
71
63
  exports.empty = empty;
64
+ function of(...endpoints) {
65
+ const bkend = Object.assign({}, empty());
66
+ for (const endpoint of endpoints) {
67
+ bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {};
68
+ if (bkend.endpoints[endpoint.region][endpoint.id]) {
69
+ throw new Error("Trying to create a backend with the same endpiont twice");
70
+ }
71
+ bkend.endpoints[endpoint.region][endpoint.id] = endpoint;
72
+ }
73
+ return bkend;
74
+ }
75
+ exports.of = of;
72
76
  function isEmptyBackend(backend) {
73
- return (Object.keys(backend.requiredAPIs).length == 0 &&
74
- backend.cloudFunctions.length === 0 &&
75
- backend.schedules.length === 0 &&
76
- backend.topics.length === 0);
77
+ return (Object.keys(backend.requiredAPIs).length == 0 && Object.keys(backend.endpoints).length === 0);
77
78
  }
78
79
  exports.isEmptyBackend = isEmptyBackend;
79
80
  function functionName(cloudFunction) {
80
81
  return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`;
81
82
  }
82
83
  exports.functionName = functionName;
83
- exports.sameFunctionName = (func) => (test) => {
84
- return func.id === test.id && func.region === test.region && func.project == test.project;
85
- };
86
- function scheduleName(schedule, appEngineLocation) {
87
- return `projects/${schedule.project}/locations/${appEngineLocation}/jobs/${schedule.id}`;
88
- }
89
- exports.scheduleName = scheduleName;
90
- function topicName(topic) {
91
- return `projects/${topic.project}/topics/${topic.id}`;
92
- }
93
- exports.topicName = topicName;
94
84
  function scheduleIdForFunction(cloudFunction) {
95
85
  return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`;
96
86
  }
@@ -104,7 +94,7 @@ async function existingBackend(context, forceRefresh) {
104
94
  }
105
95
  exports.existingBackend = existingBackend;
106
96
  async function loadExistingBackend(ctx) {
107
- var _a, _b, _c;
97
+ var _a;
108
98
  ctx.loadedExistingBackend = true;
109
99
  ctx.existingBackend = Object.assign({}, empty());
110
100
  ctx.unreachableRegions = {
@@ -113,79 +103,30 @@ async function loadExistingBackend(ctx) {
113
103
  };
114
104
  const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
115
105
  for (const apiFunction of gcfV1Results.functions) {
116
- const specFunction = gcf.specFromFunction(apiFunction);
117
- ctx.existingBackend.cloudFunctions.push(specFunction);
118
- const isScheduled = ((_a = apiFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) === "true";
119
- if (isScheduled) {
120
- const id = scheduleIdForFunction(specFunction);
121
- ctx.existingBackend.schedules.push({
122
- id,
123
- project: specFunction.project,
124
- transport: "pubsub",
125
- targetService: {
126
- id: specFunction.id,
127
- region: specFunction.region,
128
- project: specFunction.project,
129
- },
130
- });
131
- ctx.existingBackend.topics.push({
132
- id,
133
- project: specFunction.project,
134
- labels: exports.SCHEDULED_FUNCTION_LABEL,
135
- targetService: {
136
- id: specFunction.id,
137
- region: specFunction.region,
138
- project: specFunction.project,
139
- },
140
- });
141
- }
106
+ const endpoint = gcf.endpointFromFunction(apiFunction);
107
+ ctx.existingBackend.endpoints[endpoint.region] =
108
+ ctx.existingBackend.endpoints[endpoint.region] || {};
109
+ ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
142
110
  }
143
111
  ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable;
144
112
  if (!previews_1.previews.functionsv2) {
145
113
  return;
146
114
  }
147
- const gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
148
- for (const apiFunction of gcfV2Results.functions) {
149
- const specFunction = gcfV2.specFromFunction(apiFunction);
150
- ctx.existingBackend.cloudFunctions.push(specFunction);
151
- const pubsubScheduled = ((_b = apiFunction.labels) === null || _b === void 0 ? void 0 : _b["deployment-scheduled"]) === "true";
152
- const httpsScheduled = ((_c = apiFunction.labels) === null || _c === void 0 ? void 0 : _c["deployment-scheduled"]) === "https";
153
- if (pubsubScheduled) {
154
- const id = scheduleIdForFunction(specFunction);
155
- ctx.existingBackend.schedules.push({
156
- id,
157
- project: specFunction.project,
158
- transport: "pubsub",
159
- targetService: {
160
- id: specFunction.id,
161
- region: specFunction.region,
162
- project: specFunction.project,
163
- },
164
- });
165
- ctx.existingBackend.topics.push({
166
- id,
167
- project: specFunction.project,
168
- labels: exports.SCHEDULED_FUNCTION_LABEL,
169
- targetService: {
170
- id: specFunction.id,
171
- region: specFunction.region,
172
- project: specFunction.project,
173
- },
174
- });
175
- }
176
- if (httpsScheduled) {
177
- const id = scheduleIdForFunction(specFunction);
178
- ctx.existingBackend.schedules.push({
179
- id,
180
- project: specFunction.project,
181
- transport: "https",
182
- targetService: {
183
- id: specFunction.id,
184
- region: specFunction.region,
185
- project: specFunction.project,
186
- },
187
- });
115
+ let gcfV2Results;
116
+ try {
117
+ gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
118
+ }
119
+ catch (err) {
120
+ if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
121
+ return;
188
122
  }
123
+ throw err;
124
+ }
125
+ for (const apiFunction of gcfV2Results.functions) {
126
+ const endpoint = gcfV2.endpointFromFunction(apiFunction);
127
+ ctx.existingBackend.endpoints[endpoint.region] =
128
+ ctx.existingBackend.endpoints[endpoint.region] || {};
129
+ ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
189
130
  }
190
131
  ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
191
132
  }
@@ -196,12 +137,12 @@ async function checkAvailability(context, want) {
196
137
  }
197
138
  const gcfV1Regions = new Set();
198
139
  const gcfV2Regions = new Set();
199
- for (const fn of want.cloudFunctions) {
200
- if (fn.platform == "gcfv1") {
201
- gcfV1Regions.add(fn.region);
140
+ for (const ep of allEndpoints(want)) {
141
+ if (ep.platform == "gcfv1") {
142
+ gcfV1Regions.add(ep.region);
202
143
  }
203
144
  else {
204
- gcfV2Regions.add(fn.region);
145
+ gcfV2Regions.add(ep.region);
205
146
  }
206
147
  }
207
148
  const neededUnreachableV1 = ctx.unreachableRegions.gcfV1.filter((region) => gcfV1Regions.has(region));
@@ -228,6 +169,43 @@ async function checkAvailability(context, want) {
228
169
  }
229
170
  }
230
171
  exports.checkAvailability = checkAvailability;
172
+ function allEndpoints(backend) {
173
+ return Object.values(backend.endpoints).reduce((accum, perRegion) => {
174
+ return [...accum, ...Object.values(perRegion)];
175
+ }, []);
176
+ }
177
+ exports.allEndpoints = allEndpoints;
178
+ function someEndpoint(backend, predicate) {
179
+ for (const endpoints of Object.values(backend.endpoints)) {
180
+ if (Object.values(endpoints).some(predicate)) {
181
+ return true;
182
+ }
183
+ }
184
+ return false;
185
+ }
186
+ exports.someEndpoint = someEndpoint;
187
+ function matchingBackend(backend, predicate) {
188
+ const filtered = Object.assign({}, empty());
189
+ for (const endpoint of allEndpoints(backend)) {
190
+ if (!predicate(endpoint)) {
191
+ continue;
192
+ }
193
+ filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {};
194
+ filtered.endpoints[endpoint.region][endpoint.id] = endpoint;
195
+ }
196
+ return filtered;
197
+ }
198
+ exports.matchingBackend = matchingBackend;
199
+ function regionalEndpoints(backend, region) {
200
+ return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : [];
201
+ }
202
+ exports.regionalEndpoints = regionalEndpoints;
203
+ exports.hasEndpoint = (backend) => (endpoint) => {
204
+ return !!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id];
205
+ };
206
+ exports.missingEndpoint = (backend) => (endpoint) => {
207
+ return !exports.hasEndpoint(backend)(endpoint);
208
+ };
231
209
  function compareFunctions(left, right) {
232
210
  if (left.platform != right.platform) {
233
211
  return right.platform < left.platform ? -1 : 1;
@@ -28,17 +28,17 @@ async function checkServiceAccountIam(projectId) {
28
28
  }
29
29
  exports.checkServiceAccountIam = checkServiceAccountIam;
30
30
  async function checkHttpIam(context, options, payload) {
31
- const functions = payload.functions.backend.cloudFunctions;
32
31
  const filterGroups = context.filters || functionsDeployHelper_1.getFilterGroups(options);
33
- const httpFunctions = functions
34
- .filter((f) => !backend.isEventTrigger(f.trigger))
32
+ const httpEndpoints = backend
33
+ .allEndpoints(payload.functions.backend)
34
+ .filter(backend.isHttpsTriggered)
35
35
  .filter((f) => functionsDeployHelper_1.functionMatchesAnyGroup(f, filterGroups));
36
- const existingFunctions = (await backend.existingBackend(context)).cloudFunctions;
37
- const newHttpFunctions = httpFunctions.filter((func) => !existingFunctions.find(backend.sameFunctionName(func)));
38
- if (newHttpFunctions.length === 0) {
36
+ const existing = await backend.existingBackend(context);
37
+ const newHttpsEndpoints = httpEndpoints.filter(backend.missingEndpoint(existing));
38
+ if (newHttpsEndpoints.length === 0) {
39
39
  return;
40
40
  }
41
- logger_1.logger.debug("[functions] found", newHttpFunctions.length, "new HTTP functions, testing setIamPolicy permission...");
41
+ logger_1.logger.debug("[functions] found", newHttpsEndpoints.length, "new HTTP functions, testing setIamPolicy permission...");
42
42
  let passed = true;
43
43
  try {
44
44
  const iamResult = await iam_1.testIamPermissions(context.projectId, [PERMISSION]);
@@ -51,7 +51,7 @@ async function checkHttpIam(context, options, payload) {
51
51
  if (!passed) {
52
52
  track("Error (User)", "deploy:functions:http_create_missing_iam");
53
53
  throw new error_1.FirebaseError(`Missing required permission on project ${cli_color_1.bold(context.projectId)} to deploy new HTTPS functions. The permission ${cli_color_1.bold(PERMISSION)} is required to deploy the following functions:\n\n- ` +
54
- newHttpFunctions.map((func) => func.id).join("\n- ") +
54
+ newHttpsEndpoints.map((func) => func.id).join("\n- ") +
55
55
  `\n\nTo address this error, please ask a project Owner to assign your account the "Cloud Functions Admin" role at the following URL:\n\nhttps://console.cloud.google.com/iam-admin/iam?project=${context.projectId}`);
56
56
  }
57
57
  logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
@@ -1,13 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.cleanupBuildImages = exports.SUBDOMAIN_MAPPING = void 0;
3
+ exports.DockerHelper = exports.deleteGcfArtifacts = exports.listGcfPaths = exports.ContainerRegistryCleaner = exports.ArtifactRegistryCleaner = exports.cleanupBuildImages = exports.SUBDOMAIN_MAPPING = void 0;
4
4
  const clc = require("cli-color");
5
+ const error_1 = require("../../error");
6
+ const previews_1 = require("../../previews");
5
7
  const api_1 = require("../../api");
6
8
  const logger_1 = require("../../logger");
7
- const docker = require("../../gcp/docker");
9
+ const artifactregistry = require("../../gcp/artifactregistry");
8
10
  const backend = require("./backend");
11
+ const docker = require("../../gcp/docker");
9
12
  const utils = require("../../utils");
10
- const error_1 = require("../../error");
13
+ const poller = require("../../operation-poller");
11
14
  exports.SUBDOMAIN_MAPPING = {
12
15
  "us-west2": "us",
13
16
  "us-west3": "us",
@@ -37,13 +40,17 @@ async function retry(func) {
37
40
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
38
41
  const MAX_RETRIES = 3;
39
42
  const INITIAL_BACKOFF = 100;
43
+ const TIMEOUT_MS = 10000;
40
44
  let retry = 0;
41
45
  while (true) {
42
46
  try {
43
- return await func();
47
+ const timeout = new Promise((resolve, reject) => {
48
+ setTimeout(() => reject(new Error("Timeout")), TIMEOUT_MS);
49
+ });
50
+ return await Promise.race([func(), timeout]);
44
51
  }
45
52
  catch (error) {
46
- logger_1.logger.debug("Failed docker command with error", error);
53
+ logger_1.logger.debug("Failed docker command with error ", error);
47
54
  retry += 1;
48
55
  if (retry >= MAX_RETRIES) {
49
56
  throw new error_1.FirebaseError("Failed to clean up artifacts", { original: error });
@@ -52,19 +59,47 @@ async function retry(func) {
52
59
  }
53
60
  }
54
61
  }
55
- async function cleanupBuildImages(functions) {
62
+ async function cleanupBuildImages(haveFunctions, deletedFunctions, cleaners = {}) {
56
63
  utils.logBullet(clc.bold.cyan("functions: ") + "cleaning up build files...");
57
- const gcrCleaner = new ContainerRegistryCleaner();
58
64
  const failedDomains = new Set();
59
- await Promise.all(functions.map((func) => (async () => {
60
- try {
61
- await gcrCleaner.cleanupFunction(func);
62
- }
63
- catch (err) {
64
- const path = `${func.project}/${exports.SUBDOMAIN_MAPPING[func.region]}/gcf`;
65
- failedDomains.add(`https://console.cloud.google.com/gcr/images/${path}`);
66
- }
67
- })()));
65
+ if (previews_1.previews.artifactregistry) {
66
+ const arCleaner = cleaners.ar || new ArtifactRegistryCleaner();
67
+ await Promise.all([
68
+ ...haveFunctions.map(async (func) => {
69
+ try {
70
+ await arCleaner.cleanupFunction(func);
71
+ }
72
+ catch (err) {
73
+ const path = `${func.project}/${func.region}/gcf-artifacts`;
74
+ failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
75
+ }
76
+ }),
77
+ ...deletedFunctions.map(async (func) => {
78
+ try {
79
+ await Promise.all([
80
+ arCleaner.cleanupFunction(func),
81
+ arCleaner.cleanupFunctionCache(func),
82
+ ]);
83
+ }
84
+ catch (err) {
85
+ const path = `${func.project}/${func.region}/gcf-artifacts`;
86
+ failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
87
+ }
88
+ }),
89
+ ]);
90
+ }
91
+ else {
92
+ const gcrCleaner = cleaners.gcr || new ContainerRegistryCleaner();
93
+ await Promise.all([...haveFunctions, ...deletedFunctions].map(async (func) => {
94
+ try {
95
+ await gcrCleaner.cleanupFunction(func);
96
+ }
97
+ catch (err) {
98
+ const path = `${func.project}/${exports.SUBDOMAIN_MAPPING[func.region]}/gcf`;
99
+ failedDomains.add(`https://console.cloud.google.com/gcr/images/${path}`);
100
+ }
101
+ }));
102
+ }
68
103
  if (failedDomains.size) {
69
104
  let message = "Unhandled error cleaning up build images. This could result in a small monthly bill if not corrected. ";
70
105
  message +=
@@ -79,6 +114,31 @@ async function cleanupBuildImages(functions) {
79
114
  }
80
115
  }
81
116
  exports.cleanupBuildImages = cleanupBuildImages;
117
+ class ArtifactRegistryCleaner {
118
+ static packagePath(func) {
119
+ return `projects/${func.project}/locations/${func.region}/repositories/gcf-artifacts/packages/${func.id}`;
120
+ }
121
+ async cleanupFunction(func) {
122
+ const op = await artifactregistry.deletePackage(ArtifactRegistryCleaner.packagePath(func));
123
+ if (op.done) {
124
+ return;
125
+ }
126
+ await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-${func.region}-${func.id}`, operationResourceName: op.name }));
127
+ }
128
+ async cleanupFunctionCache(func) {
129
+ const op = await artifactregistry.deletePackage(`${ArtifactRegistryCleaner.packagePath(func)}%2Fcache`);
130
+ if (op.done) {
131
+ return;
132
+ }
133
+ await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-cache-${func.region}-${func.id}`, operationResourceName: op.name }));
134
+ }
135
+ }
136
+ exports.ArtifactRegistryCleaner = ArtifactRegistryCleaner;
137
+ ArtifactRegistryCleaner.POLLER_OPTIONS = {
138
+ apiOrigin: api_1.artifactRegistryDomain,
139
+ apiVersion: artifactregistry.API_VERSION,
140
+ masterTimeout: 5 * 60 * 1000,
141
+ };
82
142
  class ContainerRegistryCleaner {
83
143
  constructor() {
84
144
  this.helpers = {};
@@ -213,7 +273,7 @@ class DockerHelper {
213
273
  async rm(path) {
214
274
  let toThrowLater = undefined;
215
275
  const stat = await this.ls(path);
216
- const recursive = stat.children.map((child) => (async () => {
276
+ const recursive = stat.children.map(async (child) => {
217
277
  try {
218
278
  await this.rm(`${path}/${child}`);
219
279
  stat.children.splice(stat.children.indexOf(child), 1);
@@ -221,8 +281,8 @@ class DockerHelper {
221
281
  catch (err) {
222
282
  toThrowLater = err;
223
283
  }
224
- })());
225
- const deleteTags = stat.tags.map((tag) => (async () => {
284
+ });
285
+ const deleteTags = stat.tags.map(async (tag) => {
226
286
  try {
227
287
  await retry(() => this.client.deleteTag(path, tag));
228
288
  stat.tags.splice(stat.tags.indexOf(tag), 1);
@@ -231,9 +291,9 @@ class DockerHelper {
231
291
  logger_1.logger.debug("Got error trying to remove docker tag:", err);
232
292
  toThrowLater = err;
233
293
  }
234
- })());
294
+ });
235
295
  await Promise.all(deleteTags);
236
- const deleteImages = stat.digests.map((digest) => (async () => {
296
+ const deleteImages = stat.digests.map(async (digest) => {
237
297
  try {
238
298
  await retry(() => this.client.deleteImage(path, digest));
239
299
  stat.digests.splice(stat.digests.indexOf(digest), 1);
@@ -242,7 +302,7 @@ class DockerHelper {
242
302
  logger_1.logger.debug("Got error trying to remove docker image:", err);
243
303
  toThrowLater = err;
244
304
  }
245
- })());
305
+ });
246
306
  await Promise.all(deleteImages);
247
307
  await Promise.all(recursive);
248
308
  if (toThrowLater) {
@@ -11,6 +11,7 @@ const gcs = require("../../gcp/storage");
11
11
  const gcf = require("../../gcp/cloudfunctions");
12
12
  const gcfv2 = require("../../gcp/cloudfunctionsv2");
13
13
  const utils = require("../../utils");
14
+ const backend = require("./backend");
14
15
  const GCP_REGION = api_1.functionsUploadRegion;
15
16
  tmp_1.setGracefulCleanup();
16
17
  async function uploadSourceV1(context) {
@@ -44,18 +45,11 @@ async function deploy(context, options, payload) {
44
45
  try {
45
46
  const want = payload.functions.backend;
46
47
  const uploads = [];
47
- if (want.cloudFunctions.some((fn) => fn.platform === "gcfv1")) {
48
+ if (backend.allEndpoints(want).some((endpoint) => endpoint.platform === "gcfv1")) {
48
49
  uploads.push(uploadSourceV1(context));
49
50
  }
50
- if (want.cloudFunctions.some((fn) => fn.platform === "gcfv2")) {
51
- const functions = payload.functions.backend.cloudFunctions;
52
- const regions = [];
53
- for (const func of functions) {
54
- if (func.platform === "gcfv2" && -1 === regions.indexOf(func.region)) {
55
- regions.push(func.region);
56
- }
57
- }
58
- for (const region of regions) {
51
+ for (const region of Object.keys(want.endpoints)) {
52
+ if (backend.regionalEndpoints(want, region).some((e) => e.platform === "gcfv2")) {
59
53
  uploads.push(uploadSourceV2(context, region));
60
54
  }
61
55
  }
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printTriggerUrls = exports.printSuccess = exports.logAndTrackDeployStats = exports.getFunctionLabel = exports.getFunctionId = exports.getFilterGroups = exports.functionMatchesGroup = exports.functionMatchesAnyGroup = void 0;
4
- const clc = require("cli-color");
5
- const logger_1 = require("../../logger");
6
- const backend = require("./backend");
7
- const track = require("../../track");
8
- const utils = require("../../utils");
3
+ exports.getFunctionLabel = exports.getFilterGroups = exports.functionMatchesGroup = exports.functionMatchesAnyGroup = void 0;
9
4
  function functionMatchesAnyGroup(func, filterGroups) {
10
5
  if (!filterGroups.length) {
11
6
  return true;
@@ -40,67 +35,7 @@ function getFilterGroups(options) {
40
35
  });
41
36
  }
42
37
  exports.getFilterGroups = getFilterGroups;
43
- function getFunctionId(fullName) {
44
- return fullName.split("/")[5];
45
- }
46
- exports.getFunctionId = getFunctionId;
47
- function getRegion(fullName) {
48
- return fullName.split("/")[3];
49
- }
50
- function getFunctionLabel(fnOrName) {
51
- if (typeof fnOrName === "string") {
52
- return getFunctionId(fnOrName) + "(" + getRegion(fnOrName) + ")";
53
- }
54
- else {
55
- return `${fnOrName.id}(${fnOrName.region})`;
56
- }
38
+ function getFunctionLabel(fn) {
39
+ return `${fn.id}(${fn.region})`;
57
40
  }
58
41
  exports.getFunctionLabel = getFunctionLabel;
59
- function logAndTrackDeployStats(queue, errorHandler) {
60
- const stats = queue.stats();
61
- logger_1.logger.debug(`Total Function Deployment time: ${stats.elapsed}`);
62
- logger_1.logger.debug(`${stats.total} Functions Deployed`);
63
- logger_1.logger.debug(`${errorHandler.errors.length} Functions Errored`);
64
- logger_1.logger.debug(`Average Function Deployment time: ${stats.avg}`);
65
- if (stats.total > 0) {
66
- if (errorHandler.errors.length === 0) {
67
- track("functions_deploy_result", "success", stats.total);
68
- }
69
- else if (errorHandler.errors.length < stats.total) {
70
- track("functions_deploy_result", "partial_success", stats.total - errorHandler.errors.length);
71
- track("functions_deploy_result", "partial_failure", errorHandler.errors.length);
72
- track("functions_deploy_result", "partial_error_ratio", errorHandler.errors.length / stats.total);
73
- }
74
- else {
75
- track("functions_deploy_result", "failure", stats.total);
76
- }
77
- }
78
- }
79
- exports.logAndTrackDeployStats = logAndTrackDeployStats;
80
- function printSuccess(func, type) {
81
- utils.logSuccess(clc.bold.green("functions[" + getFunctionLabel(func) + "]: ") +
82
- "Successful " +
83
- type +
84
- " operation. ");
85
- }
86
- exports.printSuccess = printSuccess;
87
- async function printTriggerUrls(context, want) {
88
- const have = await backend.existingBackend(context, true);
89
- const httpsFunctions = have.cloudFunctions.filter((fn) => {
90
- if (backend.isEventTrigger(fn.trigger)) {
91
- return false;
92
- }
93
- return want.cloudFunctions.some(backend.sameFunctionName(fn));
94
- });
95
- if (httpsFunctions.length === 0) {
96
- return;
97
- }
98
- for (const httpsFunc of httpsFunctions) {
99
- if (!httpsFunc.uri) {
100
- logger_1.logger.debug("Missing URI for HTTPS function in printTriggerUrls. This shouldn't happen");
101
- continue;
102
- }
103
- logger_1.logger.info(clc.bold("Function URL"), `(${getFunctionLabel(httpsFunc)}):`, httpsFunc.uri);
104
- }
105
- }
106
- exports.printTriggerUrls = printTriggerUrls;