firebase-tools 10.7.2 → 10.8.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.
- package/lib/commands/ext-configure.js +26 -15
- package/lib/commands/ext-export.js +14 -5
- package/lib/commands/ext-install.js +31 -2
- package/lib/commands/ext-update.js +17 -10
- package/lib/commands/functions-secrets-set.js +1 -13
- package/lib/deploy/extensions/planner.js +12 -0
- package/lib/deploy/extensions/tasks.js +13 -0
- package/lib/deploy/functions/backend.js +47 -10
- package/lib/deploy/functions/build.js +9 -1
- package/lib/deploy/functions/checkIam.js +65 -46
- package/lib/deploy/functions/functionsDeployHelper.js +1 -1
- package/lib/deploy/functions/prepare.js +42 -6
- package/lib/deploy/functions/pricing.js +2 -2
- package/lib/deploy/functions/release/fabricator.js +66 -11
- package/lib/deploy/functions/release/index.js +0 -21
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +13 -1
- package/lib/deploy/functions/services/storage.js +6 -12
- package/lib/deploy/functions/validate.js +33 -6
- package/lib/emulator/extensionsEmulator.js +3 -0
- package/lib/extensions/askUserForEventsConfig.js +97 -0
- package/lib/extensions/export.js +7 -0
- package/lib/extensions/extensionsApi.js +47 -7
- package/lib/extensions/manifest.js +1 -1
- package/lib/extensions/updateHelper.js +7 -1
- package/lib/extensions/warnings.js +3 -3
- package/lib/gcp/cloudfunctions.js +1 -1
- package/lib/gcp/cloudfunctionsv2.js +7 -3
- package/lib/gcp/serviceusage.js +24 -0
- package/lib/previews.js +1 -1
- package/lib/throttler/throttler.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/templates/extensions/typescript/package.lint.json +2 -1
- package/templates/extensions/typescript/package.nolint.json +2 -1
- package/templates/init/functions/typescript/package.lint.json +1 -0
- package/templates/init/functions/typescript/package.nolint.json +1 -0
|
@@ -19,6 +19,7 @@ const refs = require("../extensions/refs");
|
|
|
19
19
|
const manifest = require("../extensions/manifest");
|
|
20
20
|
const functional_1 = require("../functional");
|
|
21
21
|
const paramHelper_1 = require("../extensions/paramHelper");
|
|
22
|
+
const askUserForEventsConfig = require("../extensions/askUserForEventsConfig");
|
|
22
23
|
marked.setOptions({
|
|
23
24
|
renderer: new TerminalRenderer(),
|
|
24
25
|
});
|
|
@@ -67,6 +68,15 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
67
68
|
instanceId,
|
|
68
69
|
reconfiguring: true,
|
|
69
70
|
});
|
|
71
|
+
const eventsConfig = spec.events
|
|
72
|
+
? await askUserForEventsConfig.askForEventsConfig(spec.events, "${param:PROJECT_ID}", instanceId)
|
|
73
|
+
: undefined;
|
|
74
|
+
if (eventsConfig) {
|
|
75
|
+
mutableParamsBindingOptions.EVENTARC_CHANNEL = { baseValue: eventsConfig.channel };
|
|
76
|
+
mutableParamsBindingOptions.ALLOWED_EVENT_TYPES = {
|
|
77
|
+
baseValue: eventsConfig.allowedEventTypes.join(","),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
70
80
|
const newParamOptions = Object.assign(Object.assign({}, (0, paramHelper_1.buildBindingOptionsWithBaseValue)(oldParamValues)), mutableParamsBindingOptions);
|
|
71
81
|
await manifest.writeToManifest([
|
|
72
82
|
{
|
|
@@ -83,20 +93,12 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
83
93
|
manifest.showPreviewWarning();
|
|
84
94
|
return;
|
|
85
95
|
}
|
|
96
|
+
if (!projectId) {
|
|
97
|
+
throw new error_1.FirebaseError(`Project ID must be provided when re-configuring an instance outside of local mode.`);
|
|
98
|
+
}
|
|
86
99
|
const spinner = ora(`Configuring ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
|
|
87
100
|
try {
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
existingInstance = await extensionsApi.getInstance((0, projectUtils_1.needProjectId)({ projectId }), instanceId);
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
if (err.status === 404) {
|
|
94
|
-
return utils.reject(`No extension instance ${instanceId} found in project ${projectId}.`, {
|
|
95
|
-
exit: 1,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
throw err;
|
|
99
|
-
}
|
|
101
|
+
const existingInstance = await extensionsApi.getInstance((0, projectUtils_1.needProjectId)({ projectId }), instanceId);
|
|
100
102
|
const paramSpecWithNewDefaults = paramHelper.getParamsWithCurrentValuesAsDefaults(existingInstance);
|
|
101
103
|
const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => param.immutable);
|
|
102
104
|
const paramBindingOptions = await paramHelper.getParams({
|
|
@@ -121,12 +123,21 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
121
123
|
: "To set a different value for this param") +
|
|
122
124
|
", uninstall the extension, then install a new instance of this extension.");
|
|
123
125
|
}
|
|
126
|
+
const pId = (0, projectUtils_1.needProjectId)({ projectId });
|
|
127
|
+
const spec = existingInstance ? existingInstance.config.source.spec : undefined;
|
|
128
|
+
const eventsConfig = spec.events
|
|
129
|
+
? await askUserForEventsConfig.askForEventsConfig(spec.events, pId, instanceId)
|
|
130
|
+
: undefined;
|
|
124
131
|
spinner.start();
|
|
125
|
-
const
|
|
126
|
-
projectId:
|
|
132
|
+
const configureOptions = {
|
|
133
|
+
projectId: pId,
|
|
127
134
|
instanceId,
|
|
128
135
|
params: paramBindings,
|
|
129
|
-
|
|
136
|
+
canEmitEvents: eventsConfig ? true : false,
|
|
137
|
+
eventarcChannel: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.channel,
|
|
138
|
+
allowedEventTypes: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.allowedEventTypes,
|
|
139
|
+
};
|
|
140
|
+
const res = await extensionsApi.configureInstance(configureOptions);
|
|
130
141
|
spinner.stop();
|
|
131
142
|
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `successfully configured ${clc.bold(instanceId)}.`);
|
|
132
143
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl((0, projectUtils_1.needProjectId)({ projectId }), `/extensions/instances/${instanceId}?tab=config`)}`));
|
|
@@ -43,11 +43,20 @@ module.exports = new command_1.Command("ext:export")
|
|
|
43
43
|
logger_1.logger.info("Exiting. No changes made.");
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
const manifestSpecs =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
const manifestSpecs = withRefSubbed.map((spec) => {
|
|
47
|
+
const paramCopy = Object.assign({}, spec.params);
|
|
48
|
+
if (spec.eventarcChannel) {
|
|
49
|
+
paramCopy.EVENTARC_CHANNEL = spec.eventarcChannel;
|
|
50
|
+
}
|
|
51
|
+
if (spec.allowedEventTypes) {
|
|
52
|
+
paramCopy.ALLOWED_EVENT_TYPES = spec.allowedEventTypes.join(",");
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
instanceId: spec.instanceId,
|
|
56
|
+
ref: spec.ref,
|
|
57
|
+
params: (0, paramHelper_1.buildBindingOptionsWithBaseValue)(paramCopy),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
51
60
|
const existingConfig = manifest.loadConfig(options);
|
|
52
61
|
await manifest.writeToManifest(manifestSpecs, existingConfig, {
|
|
53
62
|
nonInteractive: options.nonInteractive,
|
|
@@ -6,6 +6,7 @@ const ora = require("ora");
|
|
|
6
6
|
const TerminalRenderer = require("marked-terminal");
|
|
7
7
|
const askUserForConsent = require("../extensions/askUserForConsent");
|
|
8
8
|
const displayExtensionInfo_1 = require("../extensions/displayExtensionInfo");
|
|
9
|
+
const askUserForEventsConfig = require("../extensions/askUserForEventsConfig");
|
|
9
10
|
const billingMigrationHelper_1 = require("../extensions/billingMigrationHelper");
|
|
10
11
|
const checkProjectBilling_1 = require("../extensions/checkProjectBilling");
|
|
11
12
|
const cloudbilling_1 = require("../gcp/cloudbilling");
|
|
@@ -166,6 +167,15 @@ async function installToManifest(options) {
|
|
|
166
167
|
paramsEnvPath,
|
|
167
168
|
instanceId,
|
|
168
169
|
});
|
|
170
|
+
const eventsConfig = spec.events
|
|
171
|
+
? await askUserForEventsConfig.askForEventsConfig(spec.events, "${param:PROJECT_ID}", instanceId)
|
|
172
|
+
: undefined;
|
|
173
|
+
if (eventsConfig) {
|
|
174
|
+
paramBindingOptions.EVENTARC_CHANNEL = { baseValue: eventsConfig.channel };
|
|
175
|
+
paramBindingOptions.ALLOWED_EVENT_TYPES = {
|
|
176
|
+
baseValue: eventsConfig.allowedEventTypes.join(","),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
169
179
|
const ref = extVersion ? refs.parse(extVersion.ref) : undefined;
|
|
170
180
|
await manifest.writeToManifest([
|
|
171
181
|
{
|
|
@@ -210,6 +220,12 @@ async function installExtension(options) {
|
|
|
210
220
|
reason: `To access and manage secrets which are used by this extension. By using this product you agree to the terms and conditions of the following license: https://console.cloud.google.com/tos?id=cloud&project=${projectId}`,
|
|
211
221
|
});
|
|
212
222
|
}
|
|
223
|
+
if (spec.events && spec.events.length > 0) {
|
|
224
|
+
apis.push({
|
|
225
|
+
apiName: "eventarc.googleapis.com",
|
|
226
|
+
reason: `When events are enabled, the Eventarc API is required to provision an event channel and publish events.`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
213
229
|
if (apis.length) {
|
|
214
230
|
askUserForConsent.displayApis(spec.displayName || spec.name, projectId, apis);
|
|
215
231
|
const consented = await (0, extensionsHelper_1.confirm)({ nonInteractive, force, default: true });
|
|
@@ -248,6 +264,7 @@ async function installExtension(options) {
|
|
|
248
264
|
}
|
|
249
265
|
let paramBindingOptions;
|
|
250
266
|
let paramBindings;
|
|
267
|
+
let eventsConfig;
|
|
251
268
|
switch (choice) {
|
|
252
269
|
case "installNew":
|
|
253
270
|
instanceId = await (0, extensionsHelper_1.promptForValidInstanceId)(`${instanceId}-${(0, utils_1.getRandomString)(4)}`);
|
|
@@ -258,6 +275,9 @@ async function installExtension(options) {
|
|
|
258
275
|
paramsEnvPath,
|
|
259
276
|
instanceId,
|
|
260
277
|
});
|
|
278
|
+
eventsConfig = spec.events
|
|
279
|
+
? await askUserForEventsConfig.askForEventsConfig(spec.events, projectId, instanceId)
|
|
280
|
+
: undefined;
|
|
261
281
|
paramBindings = (0, paramHelper_1.getBaseParamBindings)(paramBindingOptions);
|
|
262
282
|
spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes...";
|
|
263
283
|
spinner.start();
|
|
@@ -267,6 +287,8 @@ async function installExtension(options) {
|
|
|
267
287
|
extensionSource: source,
|
|
268
288
|
extensionVersionRef: extVersion === null || extVersion === void 0 ? void 0 : extVersion.ref,
|
|
269
289
|
params: paramBindings,
|
|
290
|
+
allowedEventTypes: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.allowedEventTypes,
|
|
291
|
+
eventarcChannel: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.channel,
|
|
270
292
|
});
|
|
271
293
|
spinner.stop();
|
|
272
294
|
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
|
|
@@ -280,16 +302,23 @@ async function installExtension(options) {
|
|
|
280
302
|
paramsEnvPath,
|
|
281
303
|
instanceId,
|
|
282
304
|
});
|
|
305
|
+
eventsConfig = spec.events
|
|
306
|
+
? await askUserForEventsConfig.askForEventsConfig(spec.events, projectId, instanceId)
|
|
307
|
+
: undefined;
|
|
283
308
|
paramBindings = (0, paramHelper_1.getBaseParamBindings)(paramBindingOptions);
|
|
284
309
|
spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes...";
|
|
285
310
|
spinner.start();
|
|
286
|
-
|
|
311
|
+
const updateOptions = {
|
|
287
312
|
projectId,
|
|
288
313
|
instanceId,
|
|
289
314
|
source,
|
|
315
|
+
canEmitEvents: eventsConfig ? true : false,
|
|
316
|
+
eventarcChannel: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.channel,
|
|
317
|
+
allowedEventTypes: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.allowedEventTypes,
|
|
290
318
|
extRef: extVersion === null || extVersion === void 0 ? void 0 : extVersion.ref,
|
|
291
319
|
params: paramBindings,
|
|
292
|
-
}
|
|
320
|
+
};
|
|
321
|
+
await (0, updateHelper_1.update)(updateOptions);
|
|
293
322
|
spinner.stop();
|
|
294
323
|
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully updated your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
|
|
295
324
|
`Its Instance ID is ${clc.bold(instanceId)}.`);
|
|
@@ -23,6 +23,7 @@ const requirePermissions_1 = require("../requirePermissions");
|
|
|
23
23
|
const utils = require("../utils");
|
|
24
24
|
const previews_1 = require("../previews");
|
|
25
25
|
const manifest = require("../extensions/manifest");
|
|
26
|
+
const askUserForEventsConfig = require("../extensions/askUserForEventsConfig");
|
|
26
27
|
marked.setOptions({
|
|
27
28
|
renderer: new TerminalRenderer(),
|
|
28
29
|
});
|
|
@@ -86,6 +87,15 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
86
87
|
nonInteractive: options.nonInteractive,
|
|
87
88
|
instanceId,
|
|
88
89
|
});
|
|
90
|
+
const eventsConfig = newExtensionVersion.spec.events
|
|
91
|
+
? await askUserForEventsConfig.askForEventsConfig(newExtensionVersion.spec.events, "${param:PROJECT_ID}", instanceId)
|
|
92
|
+
: undefined;
|
|
93
|
+
if (eventsConfig) {
|
|
94
|
+
newParamBindingOptions.EVENTARC_CHANNEL = { baseValue: eventsConfig.channel };
|
|
95
|
+
newParamBindingOptions.ALLOWED_EVENT_TYPES = {
|
|
96
|
+
baseValue: eventsConfig.allowedEventTypes.join(","),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
89
99
|
await manifest.writeToManifest([
|
|
90
100
|
{
|
|
91
101
|
instanceId,
|
|
@@ -104,16 +114,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
104
114
|
const spinner = ora(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
|
|
105
115
|
try {
|
|
106
116
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
existingInstance = await extensionsApi.getInstance(projectId, instanceId);
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
if (err.status === 404) {
|
|
113
|
-
throw new error_1.FirebaseError(`Extension instance '${clc.bold(instanceId)}' not found in project '${clc.bold(projectId)}'.`);
|
|
114
|
-
}
|
|
115
|
-
throw err;
|
|
116
|
-
}
|
|
117
|
+
const existingInstance = await extensionsApi.getInstance(projectId, instanceId);
|
|
117
118
|
const existingSpec = existingInstance.config.source.spec;
|
|
118
119
|
if (existingInstance.config.source.state === "DELETED") {
|
|
119
120
|
throw new error_1.FirebaseError(`Instance '${clc.bold(instanceId)}' cannot be updated anymore because the underlying extension was unpublished from Firebase's registry of extensions. Going forward, you will only be able to re-configure or uninstall this instance.`);
|
|
@@ -214,11 +215,17 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
214
215
|
nonInteractive: options.nonInteractive,
|
|
215
216
|
instanceId,
|
|
216
217
|
});
|
|
218
|
+
const eventsConfig = newSpec.events
|
|
219
|
+
? await askUserForEventsConfig.askForEventsConfig(newSpec.events, projectId, instanceId)
|
|
220
|
+
: undefined;
|
|
217
221
|
const newParams = paramHelper.getBaseParamBindings(newParamBindings);
|
|
218
222
|
spinner.start();
|
|
219
223
|
const updateOptions = {
|
|
220
224
|
projectId,
|
|
221
225
|
instanceId,
|
|
226
|
+
canEmitEvents: eventsConfig ? true : false,
|
|
227
|
+
eventarcChannel: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.channel,
|
|
228
|
+
allowedEventTypes: eventsConfig === null || eventsConfig === void 0 ? void 0 : eventsConfig.allowedEventTypes,
|
|
222
229
|
};
|
|
223
230
|
if (newSourceName.includes("publisher")) {
|
|
224
231
|
updateOptions.extRef = refs.toExtensionVersionRef(refs.parse(newSourceName));
|
|
@@ -77,17 +77,5 @@ exports.default = new command_1.Command("functions:secrets:set <KEY>")
|
|
|
77
77
|
(0, utils_1.logBullet)(`Updated function ${e.id}(${e.region}).`);
|
|
78
78
|
return updated;
|
|
79
79
|
});
|
|
80
|
-
|
|
81
|
-
(0, utils_1.logBullet)(`Pruning stale secrets...`);
|
|
82
|
-
const prunedResult = await (0, secrets_1.pruneAndDestroySecrets)({ projectId, projectNumber }, updatedEndpoints);
|
|
83
|
-
if (prunedResult.destroyed.length > 0) {
|
|
84
|
-
(0, utils_1.logBullet)(`Detroyed unused secret versions: ${prunedResult.destroyed
|
|
85
|
-
.map((s) => `${s.secret}@${s.version}`)
|
|
86
|
-
.join(", ")}`);
|
|
87
|
-
}
|
|
88
|
-
if (prunedResult.erred.length > 0) {
|
|
89
|
-
(0, utils_1.logWarning)(`Failed to destroy unused secret versions:\n\t${prunedResult.erred
|
|
90
|
-
.map((err) => err.message)
|
|
91
|
-
.join("\n\t")}`);
|
|
92
|
-
}
|
|
80
|
+
await Promise.all(updateOps);
|
|
93
81
|
});
|
|
@@ -52,6 +52,8 @@ async function have(projectId) {
|
|
|
52
52
|
const dep = {
|
|
53
53
|
instanceId: i.name.split("/").pop(),
|
|
54
54
|
params: i.config.params,
|
|
55
|
+
allowedEventTypes: i.config.allowedEventTypes,
|
|
56
|
+
eventarcChannel: i.config.eventarcChannel,
|
|
55
57
|
};
|
|
56
58
|
if (i.config.extensionRef) {
|
|
57
59
|
const ref = refs.parse(i.config.extensionRef);
|
|
@@ -78,11 +80,19 @@ async function want(args) {
|
|
|
78
80
|
});
|
|
79
81
|
const autoPopulatedParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId, args.emulatorMode);
|
|
80
82
|
const subbedParams = (0, extensionsHelper_1.substituteParams)(params, autoPopulatedParams);
|
|
83
|
+
const allowedEventTypes = subbedParams.ALLOWED_EVENT_TYPES !== undefined
|
|
84
|
+
? subbedParams.ALLOWED_EVENT_TYPES.split(",").filter((e) => e !== "")
|
|
85
|
+
: undefined;
|
|
86
|
+
const eventarcChannel = subbedParams.EVENTARC_CHANNEL;
|
|
87
|
+
delete subbedParams["EVENTARC_CHANNEL"];
|
|
88
|
+
delete subbedParams["ALLOWED_EVENT_TYPES"];
|
|
81
89
|
if ((0, extensionsHelper_1.isLocalPath)(e[1])) {
|
|
82
90
|
instanceSpecs.push({
|
|
83
91
|
instanceId,
|
|
84
92
|
localPath: e[1],
|
|
85
93
|
params: subbedParams,
|
|
94
|
+
allowedEventTypes: allowedEventTypes,
|
|
95
|
+
eventarcChannel: eventarcChannel,
|
|
86
96
|
});
|
|
87
97
|
}
|
|
88
98
|
else {
|
|
@@ -92,6 +102,8 @@ async function want(args) {
|
|
|
92
102
|
instanceId,
|
|
93
103
|
ref,
|
|
94
104
|
params: subbedParams,
|
|
105
|
+
allowedEventTypes: allowedEventTypes,
|
|
106
|
+
eventarcChannel: eventarcChannel,
|
|
95
107
|
});
|
|
96
108
|
}
|
|
97
109
|
}
|
|
@@ -33,6 +33,8 @@ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
|
|
|
33
33
|
instanceId: instanceSpec.instanceId,
|
|
34
34
|
params: instanceSpec.params,
|
|
35
35
|
extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref),
|
|
36
|
+
allowedEventTypes: instanceSpec.allowedEventTypes,
|
|
37
|
+
eventarcChannel: instanceSpec.eventarcChannel,
|
|
36
38
|
validateOnly,
|
|
37
39
|
});
|
|
38
40
|
}
|
|
@@ -43,6 +45,8 @@ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
|
|
|
43
45
|
instanceId: instanceSpec.instanceId,
|
|
44
46
|
params: instanceSpec.params,
|
|
45
47
|
extensionSource,
|
|
48
|
+
allowedEventTypes: instanceSpec.allowedEventTypes,
|
|
49
|
+
eventarcChannel: instanceSpec.eventarcChannel,
|
|
46
50
|
validateOnly,
|
|
47
51
|
});
|
|
48
52
|
}
|
|
@@ -67,6 +71,9 @@ function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
|
|
|
67
71
|
instanceId: instanceSpec.instanceId,
|
|
68
72
|
extRef: refs.toExtensionVersionRef(instanceSpec.ref),
|
|
69
73
|
params: instanceSpec.params,
|
|
74
|
+
canEmitEvents: !!instanceSpec.allowedEventTypes,
|
|
75
|
+
allowedEventTypes: instanceSpec.allowedEventTypes,
|
|
76
|
+
eventarcChannel: instanceSpec.eventarcChannel,
|
|
70
77
|
validateOnly,
|
|
71
78
|
});
|
|
72
79
|
}
|
|
@@ -77,6 +84,9 @@ function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
|
|
|
77
84
|
instanceId: instanceSpec.instanceId,
|
|
78
85
|
extensionSource,
|
|
79
86
|
validateOnly,
|
|
87
|
+
canEmitEvents: !!instanceSpec.allowedEventTypes,
|
|
88
|
+
allowedEventTypes: instanceSpec.allowedEventTypes,
|
|
89
|
+
eventarcChannel: instanceSpec.eventarcChannel,
|
|
80
90
|
});
|
|
81
91
|
}
|
|
82
92
|
else {
|
|
@@ -99,6 +109,9 @@ function configureExtensionInstanceTask(projectId, instanceSpec, validateOnly =
|
|
|
99
109
|
projectId,
|
|
100
110
|
instanceId: instanceSpec.instanceId,
|
|
101
111
|
params: instanceSpec.params,
|
|
112
|
+
canEmitEvents: !!instanceSpec.allowedEventTypes,
|
|
113
|
+
allowedEventTypes: instanceSpec.allowedEventTypes,
|
|
114
|
+
eventarcChannel: instanceSpec.eventarcChannel,
|
|
102
115
|
validateOnly,
|
|
103
116
|
});
|
|
104
117
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.merge = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.
|
|
3
|
+
exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.merge = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.memoryToGen2Cpu = exports.memoryToGen1Cpu = exports.memoryOptionDisplayName = exports.AllMemoryOptions = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
|
|
4
4
|
const gcf = require("../../gcp/cloudfunctions");
|
|
5
5
|
const gcfV2 = require("../../gcp/cloudfunctionsv2");
|
|
6
|
+
const run = require("../../gcp/run");
|
|
6
7
|
const utils = require("../../utils");
|
|
7
8
|
const error_1 = require("../../error");
|
|
8
9
|
const previews_1 = require("../../previews");
|
|
@@ -37,7 +38,9 @@ exports.AllIngressSettings = [
|
|
|
37
38
|
"ALLOW_INTERNAL_ONLY",
|
|
38
39
|
"ALLOW_INTERNAL_AND_GCLB",
|
|
39
40
|
];
|
|
40
|
-
exports.AllMemoryOptions = [
|
|
41
|
+
exports.AllMemoryOptions = [
|
|
42
|
+
128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
|
|
43
|
+
];
|
|
41
44
|
function memoryOptionDisplayName(option) {
|
|
42
45
|
return {
|
|
43
46
|
128: "128MB",
|
|
@@ -47,11 +50,42 @@ function memoryOptionDisplayName(option) {
|
|
|
47
50
|
2048: "2GB",
|
|
48
51
|
4096: "4GB",
|
|
49
52
|
8192: "8GB",
|
|
53
|
+
16384: "16GB",
|
|
54
|
+
32768: "32GB",
|
|
50
55
|
}[option];
|
|
51
56
|
}
|
|
52
57
|
exports.memoryOptionDisplayName = memoryOptionDisplayName;
|
|
58
|
+
function memoryToGen1Cpu(memory) {
|
|
59
|
+
return {
|
|
60
|
+
128: 0.0833,
|
|
61
|
+
256: 0.1666,
|
|
62
|
+
512: 0.3333,
|
|
63
|
+
1024: 0.5833,
|
|
64
|
+
2048: 1,
|
|
65
|
+
4096: 2,
|
|
66
|
+
8192: 2,
|
|
67
|
+
16384: 3,
|
|
68
|
+
32768: 4,
|
|
69
|
+
}[memory];
|
|
70
|
+
}
|
|
71
|
+
exports.memoryToGen1Cpu = memoryToGen1Cpu;
|
|
72
|
+
function memoryToGen2Cpu(memory) {
|
|
73
|
+
return {
|
|
74
|
+
128: 1,
|
|
75
|
+
256: 1,
|
|
76
|
+
512: 1,
|
|
77
|
+
1024: 1,
|
|
78
|
+
2048: 1,
|
|
79
|
+
4096: 2,
|
|
80
|
+
8192: 2,
|
|
81
|
+
16384: 3,
|
|
82
|
+
32768: 4,
|
|
83
|
+
}[memory];
|
|
84
|
+
}
|
|
85
|
+
exports.memoryToGen2Cpu = memoryToGen2Cpu;
|
|
86
|
+
exports.DEFAULT_CONCURRENCY = 80;
|
|
53
87
|
exports.DEFAULT_MEMORY = 256;
|
|
54
|
-
exports.
|
|
88
|
+
exports.MIN_CPU_FOR_CONCURRENCY = 1;
|
|
55
89
|
exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
|
|
56
90
|
function secretVersionName(s) {
|
|
57
91
|
var _a;
|
|
@@ -164,6 +198,16 @@ async function loadExistingBackend(ctx) {
|
|
|
164
198
|
let gcfV2Results;
|
|
165
199
|
try {
|
|
166
200
|
gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
|
|
201
|
+
const runResults = await Promise.all(gcfV2Results.functions.map((fn) => run.getService(fn.serviceConfig.service)));
|
|
202
|
+
for (const [apiFunction, runService] of (0, functional_1.zip)(gcfV2Results.functions, runResults)) {
|
|
203
|
+
const endpoint = gcfV2.endpointFromFunction(apiFunction);
|
|
204
|
+
endpoint.concurrency = runService.spec.template.spec.containerConcurrency || 1;
|
|
205
|
+
endpoint.cpu = +runService.spec.template.spec.containers[0].resources.limits.cpu;
|
|
206
|
+
ctx.existingBackend.endpoints[endpoint.region] =
|
|
207
|
+
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
208
|
+
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
209
|
+
}
|
|
210
|
+
ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
|
|
167
211
|
}
|
|
168
212
|
catch (err) {
|
|
169
213
|
if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
|
|
@@ -171,13 +215,6 @@ async function loadExistingBackend(ctx) {
|
|
|
171
215
|
}
|
|
172
216
|
throw err;
|
|
173
217
|
}
|
|
174
|
-
for (const apiFunction of gcfV2Results.functions) {
|
|
175
|
-
const endpoint = gcfV2.endpointFromFunction(apiFunction);
|
|
176
|
-
ctx.existingBackend.endpoints[endpoint.region] =
|
|
177
|
-
ctx.existingBackend.endpoints[endpoint.region] || {};
|
|
178
|
-
ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
|
|
179
|
-
}
|
|
180
|
-
ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
|
|
181
218
|
}
|
|
182
219
|
async function checkAvailability(context, want) {
|
|
183
220
|
const ctx = context;
|
|
@@ -50,7 +50,15 @@ function resolveBoolean(from) {
|
|
|
50
50
|
function isMemoryOption(value) {
|
|
51
51
|
return value == null || [128, 256, 512, 1024, 2048, 4096, 8192].includes(value);
|
|
52
52
|
}
|
|
53
|
-
function resolveBackend(build) {
|
|
53
|
+
function resolveBackend(build, userEnvs) {
|
|
54
|
+
for (const param of build.params) {
|
|
55
|
+
const expectedEnv = param.param;
|
|
56
|
+
if (!userEnvs.hasOwnProperty(expectedEnv)) {
|
|
57
|
+
throw new error_1.FirebaseError("Build specified parameter " +
|
|
58
|
+
expectedEnv +
|
|
59
|
+
" but it was not present in the user dotenv files");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
54
62
|
const bkEndpoints = [];
|
|
55
63
|
for (const endpointId of Object.keys(build.endpoints)) {
|
|
56
64
|
const endpoint = build.endpoints[endpointId];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureServiceAgentRoles = exports.mergeBindings = exports.obtainDefaultComputeServiceAgentBindings = exports.obtainPubSubServiceAgentBindings = exports.
|
|
3
|
+
exports.ensureServiceAgentRoles = exports.mergeBindings = exports.obtainDefaultComputeServiceAgentBindings = exports.obtainPubSubServiceAgentBindings = exports.checkHttpIam = exports.checkServiceAccountIam = exports.EVENTARC_EVENT_RECEIVER_ROLE = exports.RUN_INVOKER_ROLE = exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE = void 0;
|
|
4
4
|
const cli_color_1 = require("cli-color");
|
|
5
5
|
const logger_1 = require("../../logger");
|
|
6
6
|
const functionsDeployHelper_1 = require("./functionsDeployHelper");
|
|
@@ -67,6 +67,12 @@ async function checkHttpIam(context, options, payload) {
|
|
|
67
67
|
logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
|
|
68
68
|
}
|
|
69
69
|
exports.checkHttpIam = checkHttpIam;
|
|
70
|
+
function getPubsubServiceAgent(projectNumber) {
|
|
71
|
+
return `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
72
|
+
}
|
|
73
|
+
function getDefaultComputeServiceAgent(projectNumber) {
|
|
74
|
+
return `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`;
|
|
75
|
+
}
|
|
70
76
|
function reduceEventsToServices(services, endpoint) {
|
|
71
77
|
const service = (0, services_1.serviceForEndpoint)(endpoint);
|
|
72
78
|
if (service.requiredProjectBindings && !services.find((s) => s.name === service.name)) {
|
|
@@ -74,84 +80,97 @@ function reduceEventsToServices(services, endpoint) {
|
|
|
74
80
|
}
|
|
75
81
|
return services;
|
|
76
82
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
if (!binding.members.find((m) => m === serviceAccount)) {
|
|
86
|
-
binding.members.push(serviceAccount);
|
|
87
|
-
}
|
|
88
|
-
return binding;
|
|
89
|
-
}
|
|
90
|
-
exports.obtainBinding = obtainBinding;
|
|
91
|
-
function obtainPubSubServiceAgentBindings(projectNumber, existingPolicy) {
|
|
92
|
-
const pubsubServiceAgent = `serviceAccount:service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
93
|
-
return [obtainBinding(existingPolicy, pubsubServiceAgent, exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE)];
|
|
83
|
+
function obtainPubSubServiceAgentBindings(projectNumber) {
|
|
84
|
+
const serviceAccountTokenCreatorBinding = {
|
|
85
|
+
role: exports.SERVICE_ACCOUNT_TOKEN_CREATOR_ROLE,
|
|
86
|
+
members: [getPubsubServiceAgent(projectNumber)],
|
|
87
|
+
};
|
|
88
|
+
return [serviceAccountTokenCreatorBinding];
|
|
94
89
|
}
|
|
95
90
|
exports.obtainPubSubServiceAgentBindings = obtainPubSubServiceAgentBindings;
|
|
96
|
-
function obtainDefaultComputeServiceAgentBindings(projectNumber
|
|
97
|
-
const defaultComputeServiceAgent =
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
function obtainDefaultComputeServiceAgentBindings(projectNumber) {
|
|
92
|
+
const defaultComputeServiceAgent = getDefaultComputeServiceAgent(projectNumber);
|
|
93
|
+
const runInvokerBinding = {
|
|
94
|
+
role: exports.RUN_INVOKER_ROLE,
|
|
95
|
+
members: [defaultComputeServiceAgent],
|
|
96
|
+
};
|
|
97
|
+
const eventarcEventReceiverBinding = {
|
|
98
|
+
role: exports.EVENTARC_EVENT_RECEIVER_ROLE,
|
|
99
|
+
members: [defaultComputeServiceAgent],
|
|
100
|
+
};
|
|
101
|
+
return [runInvokerBinding, eventarcEventReceiverBinding];
|
|
101
102
|
}
|
|
102
103
|
exports.obtainDefaultComputeServiceAgentBindings = obtainDefaultComputeServiceAgentBindings;
|
|
103
|
-
function mergeBindings(policy,
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
function mergeBindings(policy, requiredBindings) {
|
|
105
|
+
let updated = false;
|
|
106
|
+
for (const requiredBinding of requiredBindings) {
|
|
107
|
+
const match = policy.bindings.find((b) => b.role === requiredBinding.role);
|
|
108
|
+
if (!match) {
|
|
109
|
+
updated = true;
|
|
110
|
+
policy.bindings.push(requiredBinding);
|
|
106
111
|
continue;
|
|
107
112
|
}
|
|
108
|
-
for (const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
continue;
|
|
113
|
+
for (const requiredMember of requiredBinding.members) {
|
|
114
|
+
if (!match.members.find((m) => m === requiredMember)) {
|
|
115
|
+
updated = true;
|
|
116
|
+
match.members.push(requiredMember);
|
|
113
117
|
}
|
|
114
|
-
requiredBinding.members.forEach((updatedMember) => {
|
|
115
|
-
if (!policy.bindings[ndx].members.find((member) => member === updatedMember)) {
|
|
116
|
-
policy.bindings[ndx].members.push(updatedMember);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
118
|
}
|
|
120
119
|
}
|
|
120
|
+
return updated;
|
|
121
121
|
}
|
|
122
122
|
exports.mergeBindings = mergeBindings;
|
|
123
|
-
|
|
123
|
+
function printManualIamConfig(requiredBindings, projectId) {
|
|
124
|
+
utils.logLabeledBullet("functions", "Failed to verify the project has the correct IAM bindings for a successful deployment.", "warn");
|
|
125
|
+
utils.logLabeledBullet("functions", "You can either re-run `firebase deploy` as a project owner or manually run the following set of `gcloud` commands:", "warn");
|
|
126
|
+
for (const binding of requiredBindings) {
|
|
127
|
+
for (const member of binding.members) {
|
|
128
|
+
utils.logLabeledBullet("functions", `\`gcloud projects add-iam-policy-binding ${projectId} ` +
|
|
129
|
+
`--member=${member} ` +
|
|
130
|
+
`--role=${binding.role}\``, "warn");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
|
|
124
135
|
const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
|
|
125
136
|
const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
|
|
126
137
|
const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
|
|
127
138
|
if (newServices.length === 0) {
|
|
128
139
|
return;
|
|
129
140
|
}
|
|
141
|
+
const requiredBindingsPromises = [];
|
|
142
|
+
for (const service of newServices) {
|
|
143
|
+
requiredBindingsPromises.push(service.requiredProjectBindings(projectNumber));
|
|
144
|
+
}
|
|
145
|
+
const nestedRequiredBindings = await Promise.all(requiredBindingsPromises);
|
|
146
|
+
const requiredBindings = [...(0, functional_1.flattenArray)(nestedRequiredBindings)];
|
|
147
|
+
if (haveServices.length === 0) {
|
|
148
|
+
requiredBindings.push(...obtainPubSubServiceAgentBindings(projectNumber));
|
|
149
|
+
requiredBindings.push(...obtainDefaultComputeServiceAgentBindings(projectNumber));
|
|
150
|
+
}
|
|
151
|
+
if (requiredBindings.length === 0) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
130
154
|
let policy;
|
|
131
155
|
try {
|
|
132
156
|
policy = await (0, resourceManager_1.getIamPolicy)(projectNumber);
|
|
133
157
|
}
|
|
134
158
|
catch (err) {
|
|
159
|
+
printManualIamConfig(requiredBindings, projectId);
|
|
135
160
|
utils.logLabeledBullet("functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " +
|
|
136
161
|
`${newServices.map((service) => service.api).join(", ")}` +
|
|
137
162
|
". Deployment may fail.", "warn");
|
|
138
163
|
return;
|
|
139
164
|
}
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
const allRequiredBindings = await Promise.all(findRequiredBindings);
|
|
143
|
-
if (haveServices.length === 0) {
|
|
144
|
-
allRequiredBindings.push(obtainPubSubServiceAgentBindings(projectNumber, policy));
|
|
145
|
-
allRequiredBindings.push(obtainDefaultComputeServiceAgentBindings(projectNumber, policy));
|
|
146
|
-
}
|
|
147
|
-
if (!allRequiredBindings.find((bindings) => bindings.length > 0)) {
|
|
165
|
+
const hasUpdatedBindings = mergeBindings(policy, requiredBindings);
|
|
166
|
+
if (!hasUpdatedBindings) {
|
|
148
167
|
return;
|
|
149
168
|
}
|
|
150
|
-
mergeBindings(policy, allRequiredBindings);
|
|
151
169
|
try {
|
|
152
170
|
await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
|
|
153
171
|
}
|
|
154
172
|
catch (err) {
|
|
173
|
+
printManualIamConfig(requiredBindings, projectId);
|
|
155
174
|
throw new error_1.FirebaseError("We failed to modify the IAM policy for the project. The functions " +
|
|
156
175
|
"deployment requires specific roles to be granted to service agents," +
|
|
157
176
|
" otherwise the deployment will fail.", { original: err });
|
|
@@ -70,7 +70,7 @@ exports.getEndpointFilters = getEndpointFilters;
|
|
|
70
70
|
function getFunctionLabel(fn) {
|
|
71
71
|
let id = `${fn.id}(${fn.region})`;
|
|
72
72
|
if (fn.codebase && fn.codebase !== projectConfig_1.DEFAULT_CODEBASE) {
|
|
73
|
-
id =
|
|
73
|
+
id = `${fn.codebase}:${id}`;
|
|
74
74
|
}
|
|
75
75
|
return id;
|
|
76
76
|
}
|