firebase-tools 9.21.0 → 9.23.2

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 (82) hide show
  1. package/CHANGELOG.md +7 -3
  2. package/lib/api.js +2 -0
  3. package/lib/apiv2.js +3 -2
  4. package/lib/commands/crashlytics-symbols-upload.js +1 -1
  5. package/lib/commands/deploy.js +9 -1
  6. package/lib/commands/ext-configure.js +1 -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-export.js +44 -0
  10. package/lib/commands/ext-install.js +1 -1
  11. package/lib/commands/ext-update.js +1 -1
  12. package/lib/commands/functions-delete.js +8 -0
  13. package/lib/commands/index.js +6 -5
  14. package/lib/commands/init.js +3 -0
  15. package/lib/commands/remoteconfig-get.js +6 -5
  16. package/lib/config.js +3 -2
  17. package/lib/deploy/extensions/args.js +2 -0
  18. package/lib/deploy/extensions/deploy.js +49 -0
  19. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  20. package/lib/deploy/extensions/errors.js +31 -0
  21. package/lib/deploy/extensions/index.js +8 -0
  22. package/lib/deploy/extensions/params.js +39 -0
  23. package/lib/deploy/extensions/planner.js +94 -0
  24. package/lib/deploy/extensions/prepare.js +111 -0
  25. package/lib/deploy/extensions/release.js +43 -0
  26. package/lib/deploy/extensions/secrets.js +150 -0
  27. package/lib/deploy/extensions/tasks.js +98 -0
  28. package/lib/deploy/extensions/validate.js +17 -0
  29. package/lib/deploy/functions/backend.js +8 -1
  30. package/lib/deploy/functions/checkIam.js +65 -4
  31. package/lib/deploy/functions/containerCleaner.js +97 -50
  32. package/lib/deploy/functions/eventTypes.js +10 -0
  33. package/lib/deploy/functions/prepare.js +12 -1
  34. package/lib/deploy/functions/release/fabricator.js +75 -10
  35. package/lib/deploy/functions/release/index.js +9 -1
  36. package/lib/deploy/functions/release/planner.js +3 -0
  37. package/lib/deploy/functions/release/reporter.js +8 -1
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
  39. package/lib/deploy/functions/runtimes/node/parseTriggers.js +10 -11
  40. package/lib/deploy/functions/services/index.js +38 -0
  41. package/lib/deploy/functions/services/storage.js +43 -0
  42. package/lib/deploy/functions/triggerRegionHelper.js +6 -30
  43. package/lib/deploy/index.js +10 -1
  44. package/lib/emulator/auth/handlers.js +1 -1
  45. package/lib/emulator/auth/operations.js +27 -9
  46. package/lib/emulator/auth/widget_ui.js +17 -3
  47. package/lib/emulator/functionsEmulator.js +18 -2
  48. package/lib/emulator/functionsEmulatorShared.js +1 -0
  49. package/lib/emulator/pubsubEmulator.js +58 -45
  50. package/lib/emulator/storage/cloudFunctions.js +13 -6
  51. package/lib/ensureApiEnabled.js +11 -14
  52. package/lib/extensions/askUserForParam.js +42 -10
  53. package/lib/extensions/checkProjectBilling.js +7 -7
  54. package/lib/extensions/emulator/triggerHelper.js +1 -0
  55. package/lib/extensions/export.js +107 -0
  56. package/lib/extensions/extensionsApi.js +103 -21
  57. package/lib/extensions/extensionsHelper.js +4 -1
  58. package/lib/extensions/listExtensions.js +16 -11
  59. package/lib/extensions/paramHelper.js +6 -4
  60. package/lib/extensions/provisioningHelper.js +16 -3
  61. package/lib/extensions/refs.js +9 -1
  62. package/lib/extensions/secretsUtils.js +10 -9
  63. package/lib/extensions/updateHelper.js +12 -2
  64. package/lib/extensions/versionHelper.js +14 -0
  65. package/lib/extensions/warnings.js +33 -1
  66. package/lib/functions/env.js +2 -2
  67. package/lib/gcp/artifactregistry.js +16 -0
  68. package/lib/gcp/cloudfunctions.js +27 -7
  69. package/lib/gcp/cloudfunctionsv2.js +45 -5
  70. package/lib/gcp/cloudtasks.js +143 -0
  71. package/lib/gcp/docker.js +36 -2
  72. package/lib/gcp/location.js +44 -0
  73. package/lib/gcp/proto.js +2 -2
  74. package/lib/gcp/secretManager.js +27 -6
  75. package/lib/gcp/storage.js +48 -32
  76. package/lib/init/features/functions/index.js +3 -3
  77. package/lib/init/features/hosting/github.js +3 -0
  78. package/lib/init/features/project.js +2 -1
  79. package/lib/previews.js +1 -1
  80. package/lib/projectUtils.js +10 -1
  81. package/package.json +5 -4
  82. package/schema/firebase-config.json +9 -0
@@ -1,38 +1,15 @@
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.NoopArtifactRegistryCleaner = exports.ArtifactRegistryCleaner = exports.cleanupBuildImages = void 0;
4
4
  const clc = require("cli-color");
5
+ const error_1 = require("../../error");
5
6
  const api_1 = require("../../api");
6
7
  const logger_1 = require("../../logger");
7
- const docker = require("../../gcp/docker");
8
+ const artifactregistry = require("../../gcp/artifactregistry");
8
9
  const backend = require("./backend");
10
+ const docker = require("../../gcp/docker");
9
11
  const utils = require("../../utils");
10
- const error_1 = require("../../error");
11
- exports.SUBDOMAIN_MAPPING = {
12
- "us-west2": "us",
13
- "us-west3": "us",
14
- "us-west4": "us",
15
- "us-central1": "us",
16
- "us-central2": "us",
17
- "us-east1": "us",
18
- "us-east4": "us",
19
- "northamerica-northeast1": "us",
20
- "southamerica-east1": "us",
21
- "europe-west1": "eu",
22
- "europe-west2": "eu",
23
- "europe-west3": "eu",
24
- "europe-west5": "eu",
25
- "europe-west6": "eu",
26
- "europe-central2": "eu",
27
- "asia-east1": "asia",
28
- "asia-east2": "asia",
29
- "asia-northeast1": "asia",
30
- "asia-northeast2": "asia",
31
- "asia-northeast3": "asia",
32
- "asia-south1": "asia",
33
- "asia-southeast2": "asia",
34
- "australia-southeast1": "asia",
35
- };
12
+ const poller = require("../../operation-poller");
36
13
  async function retry(func) {
37
14
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
38
15
  const MAX_RETRIES = 3;
@@ -47,7 +24,7 @@ async function retry(func) {
47
24
  return await Promise.race([func(), timeout]);
48
25
  }
49
26
  catch (error) {
50
- logger_1.logger.debug("Failed docker command with error", error);
27
+ logger_1.logger.debug("Failed docker command with error ", error);
51
28
  retry += 1;
52
29
  if (retry >= MAX_RETRIES) {
53
30
  throw new error_1.FirebaseError("Failed to clean up artifacts", { original: error });
@@ -56,19 +33,40 @@ async function retry(func) {
56
33
  }
57
34
  }
58
35
  }
59
- async function cleanupBuildImages(functions) {
36
+ async function cleanupBuildImages(haveFunctions, deletedFunctions, cleaners = {}) {
60
37
  utils.logBullet(clc.bold.cyan("functions: ") + "cleaning up build files...");
61
- const gcrCleaner = new ContainerRegistryCleaner();
62
38
  const failedDomains = new Set();
63
- await Promise.all(functions.map((func) => (async () => {
39
+ const cleanup = [];
40
+ const arCleaner = cleaners.ar || new ArtifactRegistryCleaner();
41
+ cleanup.push(...haveFunctions.map(async (func) => {
42
+ try {
43
+ await arCleaner.cleanupFunction(func);
44
+ }
45
+ catch (err) {
46
+ const path = `${func.project}/${func.region}/gcf-artifacts`;
47
+ failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
48
+ }
49
+ }));
50
+ cleanup.push(...deletedFunctions.map(async (func) => {
51
+ try {
52
+ await Promise.all([arCleaner.cleanupFunction(func), arCleaner.cleanupFunctionCache(func)]);
53
+ }
54
+ catch (err) {
55
+ const path = `${func.project}/${func.region}/gcf-artifacts`;
56
+ failedDomains.add(`https://console.cloud.google.com/artifacts/docker/${path}`);
57
+ }
58
+ }));
59
+ const gcrCleaner = cleaners.gcr || new ContainerRegistryCleaner();
60
+ cleanup.push(...[...haveFunctions, ...deletedFunctions].map(async (func) => {
64
61
  try {
65
62
  await gcrCleaner.cleanupFunction(func);
66
63
  }
67
64
  catch (err) {
68
- const path = `${func.project}/${exports.SUBDOMAIN_MAPPING[func.region]}/gcf`;
65
+ const path = `${func.project}/${docker.GCR_SUBDOMAIN_MAPPING[func.region]}/gcf`;
69
66
  failedDomains.add(`https://console.cloud.google.com/gcr/images/${path}`);
70
67
  }
71
- })()));
68
+ }));
69
+ await Promise.all(cleanup);
72
70
  if (failedDomains.size) {
73
71
  let message = "Unhandled error cleaning up build images. This could result in a small monthly bill if not corrected. ";
74
72
  message +=
@@ -83,12 +81,60 @@ async function cleanupBuildImages(functions) {
83
81
  }
84
82
  }
85
83
  exports.cleanupBuildImages = cleanupBuildImages;
84
+ class ArtifactRegistryCleaner {
85
+ static packagePath(func) {
86
+ const encodedId = func.id
87
+ .replace(/_/g, "__")
88
+ .replace(/-/g, "--")
89
+ .replace(/^[A-Z]/, (first) => `${first.toLowerCase()}-${first.toLowerCase()}`)
90
+ .replace(/[A-Z]/g, (upper) => `_${upper.toLowerCase()}`);
91
+ return `projects/${func.project}/locations/${func.region}/repositories/gcf-artifacts/packages/${encodedId}`;
92
+ }
93
+ async cleanupFunction(func) {
94
+ let op;
95
+ try {
96
+ op = await artifactregistry.deletePackage(ArtifactRegistryCleaner.packagePath(func));
97
+ }
98
+ catch (err) {
99
+ if (err.status === 404) {
100
+ return;
101
+ }
102
+ throw err;
103
+ }
104
+ if (op.done) {
105
+ return;
106
+ }
107
+ await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-${func.region}-${func.id}`, operationResourceName: op.name }));
108
+ }
109
+ async cleanupFunctionCache(func) {
110
+ const op = await artifactregistry.deletePackage(`${ArtifactRegistryCleaner.packagePath(func)}%2Fcache`);
111
+ if (op.done) {
112
+ return;
113
+ }
114
+ await poller.pollOperation(Object.assign(Object.assign({}, ArtifactRegistryCleaner.POLLER_OPTIONS), { pollerName: `cleanup-cache-${func.region}-${func.id}`, operationResourceName: op.name }));
115
+ }
116
+ }
117
+ exports.ArtifactRegistryCleaner = ArtifactRegistryCleaner;
118
+ ArtifactRegistryCleaner.POLLER_OPTIONS = {
119
+ apiOrigin: api_1.artifactRegistryDomain,
120
+ apiVersion: artifactregistry.API_VERSION,
121
+ masterTimeout: 5 * 60 * 1000,
122
+ };
123
+ class NoopArtifactRegistryCleaner extends ArtifactRegistryCleaner {
124
+ cleanupFunction() {
125
+ return Promise.resolve();
126
+ }
127
+ cleanupFunctionCache() {
128
+ return Promise.resolve();
129
+ }
130
+ }
131
+ exports.NoopArtifactRegistryCleaner = NoopArtifactRegistryCleaner;
86
132
  class ContainerRegistryCleaner {
87
133
  constructor() {
88
134
  this.helpers = {};
89
135
  }
90
136
  helper(location) {
91
- const subdomain = exports.SUBDOMAIN_MAPPING[location] || "us";
137
+ const subdomain = docker.GCR_SUBDOMAIN_MAPPING[location] || "us";
92
138
  if (!this.helpers[subdomain]) {
93
139
  const origin = `https://${subdomain}.${api_1.containerRegistryDomain}`;
94
140
  this.helpers[subdomain] = new DockerHelper(origin);
@@ -128,14 +174,14 @@ function getHelper(cache, subdomain) {
128
174
  }
129
175
  async function listGcfPaths(projectId, locations, dockerHelpers = {}) {
130
176
  if (!locations) {
131
- locations = Object.keys(exports.SUBDOMAIN_MAPPING);
177
+ locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
132
178
  }
133
- const invalidRegion = locations.find((loc) => !exports.SUBDOMAIN_MAPPING[loc]);
179
+ const invalidRegion = locations.find((loc) => !docker.GCR_SUBDOMAIN_MAPPING[loc]);
134
180
  if (invalidRegion) {
135
181
  throw new error_1.FirebaseError(`Invalid region ${invalidRegion} supplied`);
136
182
  }
137
183
  const locationsSet = new Set(locations);
138
- const subdomains = new Set(Object.values(exports.SUBDOMAIN_MAPPING));
184
+ const subdomains = new Set(Object.values(docker.GCR_SUBDOMAIN_MAPPING));
139
185
  const failedSubdomains = [];
140
186
  const listAll = [];
141
187
  for (const subdomain of subdomains) {
@@ -166,26 +212,27 @@ async function listGcfPaths(projectId, locations, dockerHelpers = {}) {
166
212
  throw new error_1.FirebaseError(`Failed to search the following subdomains: ${failedSubdomains.join(",")}`);
167
213
  }
168
214
  return gcfDirs.map((loc) => {
169
- return `${exports.SUBDOMAIN_MAPPING[loc]}.${api_1.containerRegistryDomain}/${projectId}/gcf/${loc}`;
215
+ return `${docker.GCR_SUBDOMAIN_MAPPING[loc]}.${api_1.containerRegistryDomain}/${projectId}/gcf/${loc}`;
170
216
  });
171
217
  }
172
218
  exports.listGcfPaths = listGcfPaths;
173
219
  async function deleteGcfArtifacts(projectId, locations, dockerHelpers = {}) {
174
220
  if (!locations) {
175
- locations = Object.keys(exports.SUBDOMAIN_MAPPING);
221
+ locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
176
222
  }
177
- const invalidRegion = locations.find((loc) => !exports.SUBDOMAIN_MAPPING[loc]);
223
+ const invalidRegion = locations.find((loc) => !docker.GCR_SUBDOMAIN_MAPPING[loc]);
178
224
  if (invalidRegion) {
179
225
  throw new error_1.FirebaseError(`Invalid region ${invalidRegion} supplied`);
180
226
  }
181
- const subdomains = new Set(Object.values(exports.SUBDOMAIN_MAPPING));
227
+ const subdomains = new Set(Object.values(docker.GCR_SUBDOMAIN_MAPPING));
182
228
  const failedSubdomains = [];
183
229
  const deleteLocations = locations.map((loc) => {
230
+ const subdomain = docker.GCR_SUBDOMAIN_MAPPING[loc];
184
231
  try {
185
- return getHelper(dockerHelpers, exports.SUBDOMAIN_MAPPING[loc]).rm(`${projectId}/gcf/${loc}`);
232
+ return getHelper(dockerHelpers, subdomain).rm(`${projectId}/gcf/${loc}`);
186
233
  }
187
234
  catch (err) {
188
- failedSubdomains.push(exports.SUBDOMAIN_MAPPING[loc]);
235
+ failedSubdomains.push(subdomain);
189
236
  logger_1.logger.debug(err);
190
237
  }
191
238
  });
@@ -217,7 +264,7 @@ class DockerHelper {
217
264
  async rm(path) {
218
265
  let toThrowLater = undefined;
219
266
  const stat = await this.ls(path);
220
- const recursive = stat.children.map((child) => (async () => {
267
+ const recursive = stat.children.map(async (child) => {
221
268
  try {
222
269
  await this.rm(`${path}/${child}`);
223
270
  stat.children.splice(stat.children.indexOf(child), 1);
@@ -225,8 +272,8 @@ class DockerHelper {
225
272
  catch (err) {
226
273
  toThrowLater = err;
227
274
  }
228
- })());
229
- const deleteTags = stat.tags.map((tag) => (async () => {
275
+ });
276
+ const deleteTags = stat.tags.map(async (tag) => {
230
277
  try {
231
278
  await retry(() => this.client.deleteTag(path, tag));
232
279
  stat.tags.splice(stat.tags.indexOf(tag), 1);
@@ -235,9 +282,9 @@ class DockerHelper {
235
282
  logger_1.logger.debug("Got error trying to remove docker tag:", err);
236
283
  toThrowLater = err;
237
284
  }
238
- })());
285
+ });
239
286
  await Promise.all(deleteTags);
240
- const deleteImages = stat.digests.map((digest) => (async () => {
287
+ const deleteImages = stat.digests.map(async (digest) => {
241
288
  try {
242
289
  await retry(() => this.client.deleteImage(path, digest));
243
290
  stat.digests.splice(stat.digests.indexOf(digest), 1);
@@ -246,7 +293,7 @@ class DockerHelper {
246
293
  logger_1.logger.debug("Got error trying to remove docker image:", err);
247
294
  toThrowLater = err;
248
295
  }
249
- })());
296
+ });
250
297
  await Promise.all(deleteImages);
251
298
  await Promise.all(recursive);
252
299
  if (toThrowLater) {
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PUBSUB_V2_EVENT = exports.STORAGE_V2_EVENTS = void 0;
4
+ exports.STORAGE_V2_EVENTS = [
5
+ "google.cloud.storage.object.v1.finalized",
6
+ "google.cloud.storage.object.v1.archived",
7
+ "google.cloud.storage.object.v1.deleted",
8
+ "google.cloud.storage.object.v1.metadataUpdated",
9
+ ];
10
+ exports.PUBSUB_V2_EVENT = "google.cloud.pubsub.topic.v1.messagePublished";
@@ -19,12 +19,20 @@ const validate = require("./validate");
19
19
  const utils = require("../../utils");
20
20
  const logger_1 = require("../../logger");
21
21
  const triggerRegionHelper_1 = require("./triggerRegionHelper");
22
+ const checkIam_1 = require("./checkIam");
22
23
  function hasUserConfig(config) {
23
24
  return Object.keys(config).length > 1;
24
25
  }
25
26
  function hasDotenv(opts) {
26
27
  return previews_1.previews.dotenv && functionsEnv.hasUserEnvs(opts);
27
28
  }
29
+ async function maybeEnableAR(projectId) {
30
+ if (previews_1.previews.artifactregistry) {
31
+ return ensureApiEnabled.check(projectId, "artifactregistry.googleapis.com", "functions", true);
32
+ }
33
+ await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions");
34
+ return true;
35
+ }
28
36
  async function prepare(context, options, payload) {
29
37
  if (!options.config.src.functions) {
30
38
  return;
@@ -39,8 +47,10 @@ async function prepare(context, options, payload) {
39
47
  ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
40
48
  ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
41
49
  ensureCloudBuildEnabled_1.ensureCloudBuildEnabled(projectId),
50
+ maybeEnableAR(projectId),
42
51
  ]);
43
52
  context.runtimeConfigEnabled = checkAPIsEnabled[1];
53
+ context.artifactRegistryEnabled = checkAPIsEnabled[3];
44
54
  const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
45
55
  context.firebaseConfig = firebaseConfig;
46
56
  const runtimeConfig = await prepareFunctionsUpload_1.getFunctionsConfig(context);
@@ -103,8 +113,9 @@ async function prepare(context, options, payload) {
103
113
  return functionsDeployHelper_1.functionMatchesAnyGroup(endpoint, context.filters);
104
114
  });
105
115
  const haveBackend = await backend.existingBackend(context);
116
+ await checkIam_1.ensureServiceAgentRoles(projectId, wantBackend, haveBackend);
106
117
  inferDetailsFromExisting(wantBackend, haveBackend, usedDotenv);
107
- await triggerRegionHelper_1.lookupMissingTriggerRegions(wantBackend);
118
+ await triggerRegionHelper_1.ensureTriggerRegions(wantBackend);
108
119
  await prompts_1.promptForFailurePolicies(options, matchingBackend, haveBackend);
109
120
  await prompts_1.promptForMinInstances(options, matchingBackend, haveBackend);
110
121
  await backend.checkAvailability(context, wantBackend);
@@ -10,6 +10,7 @@ const runtimes_1 = require("../runtimes");
10
10
  const api_1 = require("../../../api");
11
11
  const logger_1 = require("../../../logger");
12
12
  const backend = require("../backend");
13
+ const cloudtasks = require("../../../gcp/cloudtasks");
13
14
  const deploymentTool = require("../../../deploymentTool");
14
15
  const gcf = require("../../../gcp/cloudfunctions");
15
16
  const gcfV2 = require("../../../gcp/cloudfunctionsv2");
@@ -24,11 +25,13 @@ const gcfV1PollerOptions = {
24
25
  apiOrigin: api_1.functionsOrigin,
25
26
  apiVersion: gcf.API_VERSION,
26
27
  masterTimeout: 25 * 60 * 1000,
28
+ maxBackoff: 10000,
27
29
  };
28
30
  const gcfV2PollerOptions = {
29
31
  apiOrigin: api_1.functionsV2Origin,
30
32
  apiVersion: gcfV2.API_VERSION,
31
33
  masterTimeout: 25 * 60 * 1000,
34
+ maxBackoff: 10000,
32
35
  };
33
36
  const DEFAULT_GCFV2_CONCURRENCY = 80;
34
37
  const rethrowAs = (endpoint, op) => (err) => {
@@ -121,7 +124,9 @@ class Fabricator {
121
124
  await this.setTrigger(endpoint);
122
125
  }
123
126
  async updateEndpoint(update, scraper) {
124
- update.endpoint.labels = Object.assign(Object.assign({}, update.endpoint.labels), deploymentTool.labels());
127
+ if (update.deleteAndRecreate || update.endpoint.platform !== "gcfv2") {
128
+ update.endpoint.labels = Object.assign(Object.assign({}, update.endpoint.labels), deploymentTool.labels());
129
+ }
125
130
  if (update.deleteAndRecreate) {
126
131
  await this.deleteEndpoint(update.deleteAndRecreate);
127
132
  await this.createEndpoint(update.endpoint, scraper);
@@ -154,6 +159,9 @@ class Fabricator {
154
159
  throw new Error("Precondition failed");
155
160
  }
156
161
  const apiFunction = gcf.functionFromEndpoint(endpoint, this.sourceUrl);
162
+ if (apiFunction.httpsTrigger) {
163
+ apiFunction.httpsTrigger.securityLevel = "SECURE_ALWAYS";
164
+ }
157
165
  apiFunction.sourceToken = await scraper.tokenPromise();
158
166
  const resultFunction = await this.functionExecutor
159
167
  .run(async () => {
@@ -172,6 +180,16 @@ class Fabricator {
172
180
  .catch(rethrowAs(endpoint, "set invoker"));
173
181
  }
174
182
  }
183
+ else if (backend.isTaskQueueTriggered(endpoint)) {
184
+ const invoker = endpoint.taskQueueTrigger.invoker;
185
+ if (invoker && !invoker.includes("private")) {
186
+ await this.executor
187
+ .run(async () => {
188
+ await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
189
+ })
190
+ .catch(rethrowAs(endpoint, "set invoker"));
191
+ }
192
+ }
175
193
  }
176
194
  async createV2Function(endpoint) {
177
195
  var _a;
@@ -214,6 +232,16 @@ class Fabricator {
214
232
  .catch(rethrowAs(endpoint, "set invoker"));
215
233
  }
216
234
  }
235
+ else if (backend.isTaskQueueTriggered(endpoint)) {
236
+ const invoker = endpoint.taskQueueTrigger.invoker;
237
+ if (invoker && !invoker.includes("private")) {
238
+ await this.executor
239
+ .run(async () => {
240
+ await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
241
+ })
242
+ .catch(rethrowAs(endpoint, "set invoker"));
243
+ }
244
+ }
217
245
  await this.setConcurrency(endpoint, serviceName, endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY);
218
246
  }
219
247
  async updateV1Function(endpoint, scraper) {
@@ -231,12 +259,16 @@ class Fabricator {
231
259
  })
232
260
  .catch(rethrowAs(endpoint, "update"));
233
261
  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) {
262
+ let invoker;
263
+ if (backend.isHttpsTriggered(endpoint)) {
264
+ invoker = endpoint.httpsTrigger.invoker;
265
+ }
266
+ else if (backend.isTaskQueueTriggered(endpoint)) {
267
+ invoker = endpoint.taskQueueTrigger.invoker;
268
+ }
269
+ if (invoker) {
235
270
  await this.executor
236
- .run(async () => {
237
- await gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), endpoint.httpsTrigger.invoker);
238
- return;
239
- })
271
+ .run(() => gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), invoker))
240
272
  .catch(rethrowAs(endpoint, "set invoker"));
241
273
  }
242
274
  }
@@ -250,17 +282,24 @@ class Fabricator {
250
282
  if ((_a = apiFunction.eventTrigger) === null || _a === void 0 ? void 0 : _a.pubsubTopic) {
251
283
  delete apiFunction.eventTrigger.pubsubTopic;
252
284
  }
253
- const resultFunction = (await this.functionExecutor
285
+ const resultFunction = await this.functionExecutor
254
286
  .run(async () => {
255
287
  const op = await gcfV2.updateFunction(apiFunction);
256
288
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
257
289
  })
258
- .catch(rethrowAs(endpoint, "update")));
290
+ .catch(rethrowAs(endpoint, "update"));
259
291
  endpoint.uri = resultFunction.serviceConfig.uri;
260
292
  const serviceName = resultFunction.serviceConfig.service;
261
- if (backend.isHttpsTriggered(endpoint) && endpoint.httpsTrigger.invoker) {
293
+ let invoker;
294
+ if (backend.isHttpsTriggered(endpoint)) {
295
+ invoker = endpoint.httpsTrigger.invoker;
296
+ }
297
+ else if (backend.isTaskQueueTriggered(endpoint)) {
298
+ invoker = endpoint.taskQueueTrigger.invoker;
299
+ }
300
+ if (invoker) {
262
301
  await this.executor
263
- .run(() => run.setInvokerUpdate(endpoint.project, serviceName, endpoint.httpsTrigger.invoker))
302
+ .run(() => run.setInvokerUpdate(endpoint.project, serviceName, invoker))
264
303
  .catch(rethrowAs(endpoint, "set invoker"));
265
304
  }
266
305
  if (endpoint.concurrency) {
@@ -314,6 +353,9 @@ class Fabricator {
314
353
  }
315
354
  functional_1.assertExhaustive(endpoint.platform);
316
355
  }
356
+ else if (backend.isTaskQueueTriggered(endpoint)) {
357
+ await this.upsertTaskQueue(endpoint);
358
+ }
317
359
  }
318
360
  async deleteTrigger(endpoint) {
319
361
  if (backend.isScheduleTriggered(endpoint)) {
@@ -327,6 +369,9 @@ class Fabricator {
327
369
  }
328
370
  functional_1.assertExhaustive(endpoint.platform);
329
371
  }
372
+ else if (backend.isTaskQueueTriggered(endpoint)) {
373
+ await this.disableTaskQueue(endpoint);
374
+ }
330
375
  }
331
376
  async upsertScheduleV1(endpoint) {
332
377
  const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
@@ -337,6 +382,17 @@ class Fabricator {
337
382
  upsertScheduleV2(endpoint) {
338
383
  return Promise.reject(new reporter.DeploymentError(endpoint, "upsert schedule", new Error("Not implemented")));
339
384
  }
385
+ async upsertTaskQueue(endpoint) {
386
+ const queue = cloudtasks.queueFromEndpoint(endpoint);
387
+ await this.executor
388
+ .run(() => cloudtasks.upsertQueue(queue))
389
+ .catch(rethrowAs(endpoint, "upsert task queue"));
390
+ if (endpoint.taskQueueTrigger.invoker) {
391
+ await this.executor
392
+ .run(() => cloudtasks.setEnqueuer(queue.name, endpoint.taskQueueTrigger.invoker))
393
+ .catch(rethrowAs(endpoint, "set invoker"));
394
+ }
395
+ }
340
396
  async deleteScheduleV1(endpoint) {
341
397
  const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
342
398
  await this.executor
@@ -349,6 +405,15 @@ class Fabricator {
349
405
  deleteScheduleV2(endpoint) {
350
406
  return Promise.reject(new reporter.DeploymentError(endpoint, "delete schedule", new Error("Not implemented")));
351
407
  }
408
+ async disableTaskQueue(endpoint) {
409
+ const update = {
410
+ name: cloudtasks.queueNameForEndpoint(endpoint),
411
+ state: "DISABLED",
412
+ };
413
+ await this.executor
414
+ .run(() => cloudtasks.updateQueue(update))
415
+ .catch(rethrowAs(endpoint, "disable task queue"));
416
+ }
352
417
  logOpStart(op, endpoint) {
353
418
  const runtime = runtimes_1.getHumanFriendlyRuntimeName(endpoint.runtime);
354
419
  const label = helper.getFunctionLabel(endpoint);
@@ -45,7 +45,15 @@ async function release(context, options, payload) {
45
45
  await reporter.logAndTrackDeployStats(summary);
46
46
  reporter.printErrors(summary);
47
47
  printTriggerUrls(payload.functions.backend);
48
- await containerCleaner.cleanupBuildImages(backend.allEndpoints(payload.functions.backend));
48
+ const haveEndpoints = backend.allEndpoints(payload.functions.backend);
49
+ const deletedEndpoints = Object.values(plan)
50
+ .map((r) => r.endpointsToDelete)
51
+ .reduce(functional_1.reduceFlat, []);
52
+ const opts = {};
53
+ if (!context.artifactRegistryEnabled) {
54
+ opts.ar = new containerCleaner.NoopArtifactRegistryCleaner();
55
+ }
56
+ await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints, opts);
49
57
  const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
50
58
  if (allErrors.length) {
51
59
  const opts = allErrors.length == 1 ? { original: allErrors[0] } : { children: allErrors };
@@ -138,6 +138,9 @@ function checkForIllegalUpdate(want, have) {
138
138
  else if (backend.isScheduleTriggered(e)) {
139
139
  return "a scheduled";
140
140
  }
141
+ else if (backend.isTaskQueueTriggered(e)) {
142
+ return "a task queue";
143
+ }
141
144
  throw Error("Functions release planner is not able to handle an unknown trigger type");
142
145
  };
143
146
  const wantType = triggerType(want);
@@ -29,8 +29,10 @@ async function logAndTrackDeployStats(summary) {
29
29
  let totalSuccesses = 0;
30
30
  let totalAborts = 0;
31
31
  const reports = [];
32
+ const regions = new Set();
32
33
  for (const result of summary.results) {
33
34
  const tag = triggerTag(result.endpoint);
35
+ regions.add(result.endpoint.region);
34
36
  totalTime += result.durationMs;
35
37
  if (!result.error) {
36
38
  totalSuccesses++;
@@ -45,6 +47,8 @@ async function logAndTrackDeployStats(summary) {
45
47
  reports.push(track.track("function_deploy_failure", tag, result.durationMs));
46
48
  }
47
49
  }
50
+ const regionCountTag = regions.size < 5 ? regions.size.toString() : ">=5";
51
+ reports.push(track.track("functions_region_count", regionCountTag, 1));
48
52
  const gcfv1 = summary.results.find((r) => r.endpoint.platform === "gcfv1");
49
53
  const gcfv2 = summary.results.find((r) => r.endpoint.platform === "gcfv2");
50
54
  const tag = gcfv1 && gcfv2 ? "v1+v2" : gcfv1 ? "v1" : "v2";
@@ -135,7 +139,7 @@ function printQuotaErrors(results) {
135
139
  }
136
140
  function printAbortedErrors(results) {
137
141
  const aborted = results.filter((r) => r.error instanceof AbortedDeploymentError);
138
- if (!aborted) {
142
+ if (!aborted.length) {
139
143
  return;
140
144
  }
141
145
  logger_1.logger.info("");
@@ -151,6 +155,9 @@ function triggerTag(endpoint) {
151
155
  if (backend.isScheduleTriggered(endpoint)) {
152
156
  return `${prefix}.scheduled`;
153
157
  }
158
+ if (backend.isTaskQueueTriggered(endpoint)) {
159
+ return `${prefix}.taskQueue`;
160
+ }
154
161
  if (backend.isHttpsTriggered(endpoint)) {
155
162
  if ((_a = endpoint.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
156
163
  return `${prefix}.callable`;
@@ -59,6 +59,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
59
59
  httpsTrigger: "object",
60
60
  eventTrigger: "object",
61
61
  scheduleTrigger: "object",
62
+ taskQueueTrigger: "object",
62
63
  });
63
64
  let triggerCount = 0;
64
65
  if (ep.httpsTrigger) {
@@ -70,6 +71,9 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
70
71
  if (ep.scheduleTrigger) {
71
72
  triggerCount++;
72
73
  }
74
+ if (ep.taskQueueTrigger) {
75
+ triggerCount++;
76
+ }
73
77
  if (!triggerCount) {
74
78
  throw new error_1.FirebaseError("Expected trigger in endpoint" + id);
75
79
  }
@@ -111,6 +115,30 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
111
115
  });
112
116
  triggered = { scheduleTrigger: ep.scheduleTrigger };
113
117
  }
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 };
141
+ }
114
142
  else {
115
143
  throw new error_1.FirebaseError(`Do not recognize trigger type for endpoint ${id}. Try upgrading ` +
116
144
  "firebase-tools with npm install -g firebase-tools@latest");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addResourcesToBackend = exports.discoverBackend = exports.useStrategy = exports.GCS_EVENTS = void 0;
3
+ exports.addResourcesToBackend = exports.discoverBackend = exports.useStrategy = void 0;
4
4
  const path = require("path");
5
5
  const _ = require("lodash");
6
6
  const child_process_1 = require("child_process");
@@ -9,13 +9,8 @@ const logger_1 = require("../../../../logger");
9
9
  const backend = require("../../backend");
10
10
  const api = require("../../../../api");
11
11
  const proto = require("../../../../gcp/proto");
12
+ const eventTypes_1 = require("../../eventTypes");
12
13
  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
- ]);
19
14
  function removeInspectOptions(options) {
20
15
  return options.filter((opt) => !opt.startsWith("--inspect"));
21
16
  }
@@ -64,14 +59,18 @@ async function discoverBackend(projectId, sourceDir, runtime, configValues, envs
64
59
  }
65
60
  exports.discoverBackend = discoverBackend;
66
61
  function addResourcesToBackend(projectId, runtime, annotation, want) {
67
- var _a;
68
62
  Object.freeze(annotation);
69
63
  for (const region of annotation.regions || [api.functionsDefaultRegion]) {
70
64
  let triggered;
71
- if (!!annotation.httpsTrigger == !!annotation.eventTrigger) {
65
+ const triggerCount = +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger;
66
+ if (triggerCount != 1) {
72
67
  throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
73
68
  }
74
- if (annotation.httpsTrigger) {
69
+ if (annotation.taskQueueTrigger) {
70
+ triggered = { taskQueueTrigger: annotation.taskQueueTrigger };
71
+ want.requiredAPIs["cloudtasks"] = "cloudtasks.googleapis.com";
72
+ }
73
+ else if (annotation.httpsTrigger) {
75
74
  const trigger = {};
76
75
  if (annotation.failurePolicy) {
77
76
  logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
@@ -94,7 +93,7 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
94
93
  retry: !!annotation.failurePolicy,
95
94
  },
96
95
  };
97
- if (exports.GCS_EVENTS.has(((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || "")) {
96
+ if (eventTypes_1.STORAGE_V2_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
98
97
  triggered.eventTrigger.eventFilters = {
99
98
  bucket: annotation.eventTrigger.resource,
100
99
  };