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.
- package/CHANGELOG.md +7 -3
- package/lib/api.js +2 -0
- package/lib/apiv2.js +3 -2
- package/lib/commands/crashlytics-symbols-upload.js +1 -1
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +1 -1
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +1 -1
- package/lib/commands/ext-update.js +1 -1
- package/lib/commands/functions-delete.js +8 -0
- package/lib/commands/index.js +6 -5
- package/lib/commands/init.js +3 -0
- package/lib/commands/remoteconfig-get.js +6 -5
- package/lib/config.js +3 -2
- package/lib/deploy/extensions/args.js +2 -0
- package/lib/deploy/extensions/deploy.js +49 -0
- package/lib/deploy/extensions/deploymentSummary.js +52 -0
- package/lib/deploy/extensions/errors.js +31 -0
- package/lib/deploy/extensions/index.js +8 -0
- package/lib/deploy/extensions/params.js +39 -0
- package/lib/deploy/extensions/planner.js +94 -0
- package/lib/deploy/extensions/prepare.js +111 -0
- package/lib/deploy/extensions/release.js +43 -0
- package/lib/deploy/extensions/secrets.js +150 -0
- package/lib/deploy/extensions/tasks.js +98 -0
- package/lib/deploy/extensions/validate.js +17 -0
- package/lib/deploy/functions/backend.js +8 -1
- package/lib/deploy/functions/checkIam.js +65 -4
- package/lib/deploy/functions/containerCleaner.js +97 -50
- package/lib/deploy/functions/eventTypes.js +10 -0
- package/lib/deploy/functions/prepare.js +12 -1
- package/lib/deploy/functions/release/fabricator.js +75 -10
- package/lib/deploy/functions/release/index.js +9 -1
- package/lib/deploy/functions/release/planner.js +3 -0
- package/lib/deploy/functions/release/reporter.js +8 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +10 -11
- package/lib/deploy/functions/services/index.js +38 -0
- package/lib/deploy/functions/services/storage.js +43 -0
- package/lib/deploy/functions/triggerRegionHelper.js +6 -30
- package/lib/deploy/index.js +10 -1
- package/lib/emulator/auth/handlers.js +1 -1
- package/lib/emulator/auth/operations.js +27 -9
- package/lib/emulator/auth/widget_ui.js +17 -3
- package/lib/emulator/functionsEmulator.js +18 -2
- package/lib/emulator/functionsEmulatorShared.js +1 -0
- package/lib/emulator/pubsubEmulator.js +58 -45
- package/lib/emulator/storage/cloudFunctions.js +13 -6
- package/lib/ensureApiEnabled.js +11 -14
- package/lib/extensions/askUserForParam.js +42 -10
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/emulator/triggerHelper.js +1 -0
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +103 -21
- package/lib/extensions/extensionsHelper.js +4 -1
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +6 -4
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +9 -1
- package/lib/extensions/secretsUtils.js +10 -9
- package/lib/extensions/updateHelper.js +12 -2
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/functions/env.js +2 -2
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +27 -7
- package/lib/gcp/cloudfunctionsv2.js +45 -5
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +36 -2
- package/lib/gcp/location.js +44 -0
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/secretManager.js +27 -6
- package/lib/gcp/storage.js +48 -32
- package/lib/init/features/functions/index.js +3 -3
- package/lib/init/features/hosting/github.js +3 -0
- package/lib/init/features/project.js +2 -1
- package/lib/previews.js +1 -1
- package/lib/projectUtils.js +10 -1
- package/package.json +5 -4
- 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.
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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}/${
|
|
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 =
|
|
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(
|
|
177
|
+
locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
|
|
132
178
|
}
|
|
133
|
-
const invalidRegion = locations.find((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(
|
|
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 `${
|
|
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(
|
|
221
|
+
locations = Object.keys(docker.GCR_SUBDOMAIN_MAPPING);
|
|
176
222
|
}
|
|
177
|
-
const invalidRegion = locations.find((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(
|
|
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,
|
|
232
|
+
return getHelper(dockerHelpers, subdomain).rm(`${projectId}/gcf/${loc}`);
|
|
186
233
|
}
|
|
187
234
|
catch (err) {
|
|
188
|
-
failedSubdomains.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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
|
};
|