firebase-tools 9.21.0 → 9.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -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 +2 -0
- package/lib/commands/index.js +6 -5
- package/lib/commands/init.js +3 -0
- 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/planner.js +95 -0
- package/lib/deploy/extensions/prepare.js +103 -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/containerCleaner.js +77 -21
- package/lib/deploy/functions/release/fabricator.js +69 -9
- package/lib/deploy/functions/release/index.js +5 -1
- package/lib/deploy/functions/release/planner.js +3 -0
- package/lib/deploy/functions/release/reporter.js +4 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +7 -2
- package/lib/deploy/index.js +1 -0
- package/lib/emulator/functionsEmulator.js +3 -1
- package/lib/extensions/askUserForParam.js +14 -6
- package/lib/extensions/checkProjectBilling.js +7 -7
- 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/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +25 -7
- package/lib/gcp/cloudfunctionsv2.js +10 -2
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +7 -1
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/secretManager.js +27 -6
- package/lib/previews.js +1 -1
- package/package.json +2 -1
- package/schema/firebase-config.json +9 -0
|
@@ -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) => {
|
|
@@ -172,6 +175,16 @@ class Fabricator {
|
|
|
172
175
|
.catch(rethrowAs(endpoint, "set invoker"));
|
|
173
176
|
}
|
|
174
177
|
}
|
|
178
|
+
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
179
|
+
const invoker = endpoint.taskQueueTrigger.invoker;
|
|
180
|
+
if (invoker && !invoker.includes("private")) {
|
|
181
|
+
await this.executor
|
|
182
|
+
.run(async () => {
|
|
183
|
+
await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
|
|
184
|
+
})
|
|
185
|
+
.catch(rethrowAs(endpoint, "set invoker"));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
175
188
|
}
|
|
176
189
|
async createV2Function(endpoint) {
|
|
177
190
|
var _a;
|
|
@@ -214,6 +227,16 @@ class Fabricator {
|
|
|
214
227
|
.catch(rethrowAs(endpoint, "set invoker"));
|
|
215
228
|
}
|
|
216
229
|
}
|
|
230
|
+
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
231
|
+
const invoker = endpoint.taskQueueTrigger.invoker;
|
|
232
|
+
if (invoker && !invoker.includes("private")) {
|
|
233
|
+
await this.executor
|
|
234
|
+
.run(async () => {
|
|
235
|
+
await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
|
|
236
|
+
})
|
|
237
|
+
.catch(rethrowAs(endpoint, "set invoker"));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
217
240
|
await this.setConcurrency(endpoint, serviceName, endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY);
|
|
218
241
|
}
|
|
219
242
|
async updateV1Function(endpoint, scraper) {
|
|
@@ -231,12 +254,16 @@ class Fabricator {
|
|
|
231
254
|
})
|
|
232
255
|
.catch(rethrowAs(endpoint, "update"));
|
|
233
256
|
endpoint.uri = (_a = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _a === void 0 ? void 0 : _a.url;
|
|
234
|
-
|
|
257
|
+
let invoker;
|
|
258
|
+
if (backend.isHttpsTriggered(endpoint)) {
|
|
259
|
+
invoker = endpoint.httpsTrigger.invoker;
|
|
260
|
+
}
|
|
261
|
+
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
262
|
+
invoker = endpoint.taskQueueTrigger.invoker;
|
|
263
|
+
}
|
|
264
|
+
if (invoker) {
|
|
235
265
|
await this.executor
|
|
236
|
-
.run(
|
|
237
|
-
await gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), endpoint.httpsTrigger.invoker);
|
|
238
|
-
return;
|
|
239
|
-
})
|
|
266
|
+
.run(() => gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), invoker))
|
|
240
267
|
.catch(rethrowAs(endpoint, "set invoker"));
|
|
241
268
|
}
|
|
242
269
|
}
|
|
@@ -250,17 +277,24 @@ class Fabricator {
|
|
|
250
277
|
if ((_a = apiFunction.eventTrigger) === null || _a === void 0 ? void 0 : _a.pubsubTopic) {
|
|
251
278
|
delete apiFunction.eventTrigger.pubsubTopic;
|
|
252
279
|
}
|
|
253
|
-
const resultFunction =
|
|
280
|
+
const resultFunction = await this.functionExecutor
|
|
254
281
|
.run(async () => {
|
|
255
282
|
const op = await gcfV2.updateFunction(apiFunction);
|
|
256
283
|
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
|
|
257
284
|
})
|
|
258
|
-
.catch(rethrowAs(endpoint, "update"))
|
|
285
|
+
.catch(rethrowAs(endpoint, "update"));
|
|
259
286
|
endpoint.uri = resultFunction.serviceConfig.uri;
|
|
260
287
|
const serviceName = resultFunction.serviceConfig.service;
|
|
261
|
-
|
|
288
|
+
let invoker;
|
|
289
|
+
if (backend.isHttpsTriggered(endpoint)) {
|
|
290
|
+
invoker = endpoint.httpsTrigger.invoker;
|
|
291
|
+
}
|
|
292
|
+
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
293
|
+
invoker = endpoint.taskQueueTrigger.invoker;
|
|
294
|
+
}
|
|
295
|
+
if (invoker) {
|
|
262
296
|
await this.executor
|
|
263
|
-
.run(() => run.setInvokerUpdate(endpoint.project, serviceName,
|
|
297
|
+
.run(() => run.setInvokerUpdate(endpoint.project, serviceName, invoker))
|
|
264
298
|
.catch(rethrowAs(endpoint, "set invoker"));
|
|
265
299
|
}
|
|
266
300
|
if (endpoint.concurrency) {
|
|
@@ -314,6 +348,9 @@ class Fabricator {
|
|
|
314
348
|
}
|
|
315
349
|
functional_1.assertExhaustive(endpoint.platform);
|
|
316
350
|
}
|
|
351
|
+
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
352
|
+
await this.upsertTaskQueue(endpoint);
|
|
353
|
+
}
|
|
317
354
|
}
|
|
318
355
|
async deleteTrigger(endpoint) {
|
|
319
356
|
if (backend.isScheduleTriggered(endpoint)) {
|
|
@@ -327,6 +364,9 @@ class Fabricator {
|
|
|
327
364
|
}
|
|
328
365
|
functional_1.assertExhaustive(endpoint.platform);
|
|
329
366
|
}
|
|
367
|
+
else if (backend.isTaskQueueTriggered(endpoint)) {
|
|
368
|
+
await this.disableTaskQueue(endpoint);
|
|
369
|
+
}
|
|
330
370
|
}
|
|
331
371
|
async upsertScheduleV1(endpoint) {
|
|
332
372
|
const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
|
|
@@ -337,6 +377,17 @@ class Fabricator {
|
|
|
337
377
|
upsertScheduleV2(endpoint) {
|
|
338
378
|
return Promise.reject(new reporter.DeploymentError(endpoint, "upsert schedule", new Error("Not implemented")));
|
|
339
379
|
}
|
|
380
|
+
async upsertTaskQueue(endpoint) {
|
|
381
|
+
const queue = cloudtasks.queueFromEndpoint(endpoint);
|
|
382
|
+
await this.executor
|
|
383
|
+
.run(() => cloudtasks.upsertQueue(queue))
|
|
384
|
+
.catch(rethrowAs(endpoint, "upsert task queue"));
|
|
385
|
+
if (endpoint.taskQueueTrigger.invoker) {
|
|
386
|
+
await this.executor
|
|
387
|
+
.run(() => cloudtasks.setEnqueuer(queue.name, endpoint.taskQueueTrigger.invoker))
|
|
388
|
+
.catch(rethrowAs(endpoint, "set invoker"));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
340
391
|
async deleteScheduleV1(endpoint) {
|
|
341
392
|
const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
|
|
342
393
|
await this.executor
|
|
@@ -349,6 +400,15 @@ class Fabricator {
|
|
|
349
400
|
deleteScheduleV2(endpoint) {
|
|
350
401
|
return Promise.reject(new reporter.DeploymentError(endpoint, "delete schedule", new Error("Not implemented")));
|
|
351
402
|
}
|
|
403
|
+
async disableTaskQueue(endpoint) {
|
|
404
|
+
const update = {
|
|
405
|
+
name: cloudtasks.queueNameForEndpoint(endpoint),
|
|
406
|
+
state: "DISABLED",
|
|
407
|
+
};
|
|
408
|
+
await this.executor
|
|
409
|
+
.run(() => cloudtasks.updateQueue(update))
|
|
410
|
+
.catch(rethrowAs(endpoint, "disable task queue"));
|
|
411
|
+
}
|
|
352
412
|
logOpStart(op, endpoint) {
|
|
353
413
|
const runtime = runtimes_1.getHumanFriendlyRuntimeName(endpoint.runtime);
|
|
354
414
|
const label = helper.getFunctionLabel(endpoint);
|
|
@@ -45,7 +45,11 @@ 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
|
+
await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints);
|
|
49
53
|
const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
|
|
50
54
|
if (allErrors.length) {
|
|
51
55
|
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);
|
|
@@ -135,7 +135,7 @@ function printQuotaErrors(results) {
|
|
|
135
135
|
}
|
|
136
136
|
function printAbortedErrors(results) {
|
|
137
137
|
const aborted = results.filter((r) => r.error instanceof AbortedDeploymentError);
|
|
138
|
-
if (!aborted) {
|
|
138
|
+
if (!aborted.length) {
|
|
139
139
|
return;
|
|
140
140
|
}
|
|
141
141
|
logger_1.logger.info("");
|
|
@@ -151,6 +151,9 @@ function triggerTag(endpoint) {
|
|
|
151
151
|
if (backend.isScheduleTriggered(endpoint)) {
|
|
152
152
|
return `${prefix}.scheduled`;
|
|
153
153
|
}
|
|
154
|
+
if (backend.isTaskQueueTriggered(endpoint)) {
|
|
155
|
+
return `${prefix}.taskQueue`;
|
|
156
|
+
}
|
|
154
157
|
if (backend.isHttpsTriggered(endpoint)) {
|
|
155
158
|
if ((_a = endpoint.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
|
|
156
159
|
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");
|
|
@@ -68,10 +68,15 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
|
68
68
|
Object.freeze(annotation);
|
|
69
69
|
for (const region of annotation.regions || [api.functionsDefaultRegion]) {
|
|
70
70
|
let triggered;
|
|
71
|
-
|
|
71
|
+
const triggerCount = +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger;
|
|
72
|
+
if (triggerCount != 1) {
|
|
72
73
|
throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
|
|
73
74
|
}
|
|
74
|
-
if (annotation.
|
|
75
|
+
if (annotation.taskQueueTrigger) {
|
|
76
|
+
triggered = { taskQueueTrigger: annotation.taskQueueTrigger };
|
|
77
|
+
want.requiredAPIs["cloudtasks"] = "cloudtasks.googleapis.com";
|
|
78
|
+
}
|
|
79
|
+
else if (annotation.httpsTrigger) {
|
|
75
80
|
const trigger = {};
|
|
76
81
|
if (annotation.failurePolicy) {
|
|
77
82
|
logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
|
package/lib/deploy/index.js
CHANGED
|
@@ -526,6 +526,8 @@ class FunctionsEmulator {
|
|
|
526
526
|
const envs = {};
|
|
527
527
|
envs.FUNCTIONS_EMULATOR = "true";
|
|
528
528
|
envs.TZ = "UTC";
|
|
529
|
+
envs.FIREBASE_DEBUG_MODE = "true";
|
|
530
|
+
envs.FIREBASE_DEBUG_FEATURES = JSON.stringify({ skipTokenVerification: true });
|
|
529
531
|
const firestoreEmulator = this.getEmulatorInfo(types_1.Emulators.FIRESTORE);
|
|
530
532
|
if (firestoreEmulator != null) {
|
|
531
533
|
envs[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(firestoreEmulator);
|
|
@@ -730,7 +732,7 @@ class FunctionsEmulator {
|
|
|
730
732
|
const reqBody = req.rawBody;
|
|
731
733
|
const isCallable = trigger.labels && trigger.labels["deployment-callable"] === "true";
|
|
732
734
|
const authHeader = req.header("Authorization");
|
|
733
|
-
if (authHeader && isCallable) {
|
|
735
|
+
if (authHeader && isCallable && trigger.platform !== "gcfv2") {
|
|
734
736
|
const token = this.tokenFromAuthHeader(authHeader);
|
|
735
737
|
if (token) {
|
|
736
738
|
const contextAuth = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ask = exports.getInquirerDefault = exports.askForParam = exports.checkResponse = void 0;
|
|
3
|
+
exports.ask = exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.checkResponse = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
@@ -148,17 +148,25 @@ async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
|
|
|
148
148
|
return paramSpec.default || "";
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
async function promptCreateSecret(projectId, instanceId, paramSpec) {
|
|
152
|
-
const
|
|
151
|
+
async function promptCreateSecret(projectId, instanceId, paramSpec, secretName) {
|
|
152
|
+
const name = secretName !== null && secretName !== void 0 ? secretName : (await generateSecretName(projectId, instanceId, paramSpec.param));
|
|
153
153
|
const secretValue = await prompt_1.promptOnce({
|
|
154
154
|
name: paramSpec.param,
|
|
155
155
|
type: "password",
|
|
156
156
|
default: paramSpec.default,
|
|
157
|
-
message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${
|
|
157
|
+
message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${name} and managed by Firebase Extensions (Firebase Extensions Service Agent will be granted Secret Admin role on this secret).\nEnter a value for ${paramSpec.label.trim()}:`,
|
|
158
158
|
});
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
if (secretValue === "" && paramSpec.required) {
|
|
160
|
+
logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
|
|
161
|
+
return await promptCreateSecret(projectId, instanceId, paramSpec, name);
|
|
162
|
+
}
|
|
163
|
+
else if (secretValue !== "") {
|
|
164
|
+
const secret = await secretManagerApi.createSecret(projectId, name, secretsUtils.getSecretLabels(instanceId));
|
|
165
|
+
return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
|
|
166
|
+
}
|
|
167
|
+
return secretValue;
|
|
161
168
|
}
|
|
169
|
+
exports.promptCreateSecret = promptCreateSecret;
|
|
162
170
|
async function generateSecretName(projectId, instanceId, paramName) {
|
|
163
171
|
let secretName = `ext-${instanceId}-${paramName}`;
|
|
164
172
|
while (await secretManagerApi.secretExists(projectId, secretName)) {
|
|
@@ -33,13 +33,13 @@ async function openBillingAccount(projectId, url, open) {
|
|
|
33
33
|
});
|
|
34
34
|
return cloudbilling.checkBillingEnabled(projectId);
|
|
35
35
|
}
|
|
36
|
-
async function chooseBillingAccount(projectId,
|
|
36
|
+
async function chooseBillingAccount(projectId, accounts) {
|
|
37
37
|
const choices = accounts.map((m) => m.displayName);
|
|
38
38
|
choices.push(ADD_BILLING_ACCOUNT);
|
|
39
39
|
const answer = await prompt.promptOnce({
|
|
40
40
|
name: "billing",
|
|
41
41
|
type: "list",
|
|
42
|
-
message: `
|
|
42
|
+
message: `Extensions require your project to be upgraded to the Blaze plan. You have access to the following billing accounts.
|
|
43
43
|
Please select the one that you would like to associate with this project:`,
|
|
44
44
|
choices: choices,
|
|
45
45
|
});
|
|
@@ -54,10 +54,10 @@ Please select the one that you would like to associate with this project:`,
|
|
|
54
54
|
}
|
|
55
55
|
return logBillingStatus(billingEnabled, projectId);
|
|
56
56
|
}
|
|
57
|
-
async function setUpBillingAccount(projectId
|
|
57
|
+
async function setUpBillingAccount(projectId) {
|
|
58
58
|
const billingURL = `https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
|
|
59
59
|
logger_1.logger.info();
|
|
60
|
-
logger_1.logger.info(`
|
|
60
|
+
logger_1.logger.info(`Extension require your project to be upgraded to the Blaze plan. Please visit the following link to add a billing account:`);
|
|
61
61
|
logger_1.logger.info();
|
|
62
62
|
logger_1.logger.info(clc.bold.underline(billingURL));
|
|
63
63
|
logger_1.logger.info();
|
|
@@ -70,13 +70,13 @@ async function setUpBillingAccount(projectId, extensionName) {
|
|
|
70
70
|
const billingEnabled = await openBillingAccount(projectId, billingURL, open);
|
|
71
71
|
return logBillingStatus(billingEnabled, projectId);
|
|
72
72
|
}
|
|
73
|
-
async function enableBilling(projectId
|
|
73
|
+
async function enableBilling(projectId) {
|
|
74
74
|
const billingAccounts = await cloudbilling.listBillingAccounts();
|
|
75
75
|
if (billingAccounts) {
|
|
76
76
|
const accounts = billingAccounts.filter((account) => account.open);
|
|
77
77
|
return accounts.length > 0
|
|
78
|
-
? chooseBillingAccount(projectId,
|
|
79
|
-
: setUpBillingAccount(projectId
|
|
78
|
+
? chooseBillingAccount(projectId, accounts)
|
|
79
|
+
: setUpBillingAccount(projectId);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
exports.enableBilling = enableBilling;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeFiles = exports.displayExportInfo = exports.setSecretParamsToLatest = exports.parameterizeProject = void 0;
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const refs = require("./refs");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
const planner_1 = require("../deploy/extensions/planner");
|
|
8
|
+
const deploymentSummary_1 = require("../deploy/extensions/deploymentSummary");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const error_1 = require("../error");
|
|
11
|
+
const prompt_1 = require("../prompt");
|
|
12
|
+
const secretManager_1 = require("../gcp/secretManager");
|
|
13
|
+
const secretsUtils_1 = require("./secretsUtils");
|
|
14
|
+
function parameterizeProject(projectId, projectNumber, spec) {
|
|
15
|
+
const newParams = {};
|
|
16
|
+
for (const [key, val] of Object.entries(spec.params)) {
|
|
17
|
+
const p1 = val.replace(projectId, "${param:PROJECT_ID}");
|
|
18
|
+
const p2 = p1.replace(projectNumber, "${param:PROJECT_NUMBER}");
|
|
19
|
+
newParams[key] = p2;
|
|
20
|
+
}
|
|
21
|
+
const newSpec = Object.assign({}, spec);
|
|
22
|
+
newSpec.params = newParams;
|
|
23
|
+
return newSpec;
|
|
24
|
+
}
|
|
25
|
+
exports.parameterizeProject = parameterizeProject;
|
|
26
|
+
async function setSecretParamsToLatest(spec) {
|
|
27
|
+
const newParams = Object.assign({}, spec.params);
|
|
28
|
+
const extensionVersion = await planner_1.getExtensionVersion(spec);
|
|
29
|
+
const activeSecrets = secretsUtils_1.getActiveSecrets(extensionVersion.spec, newParams);
|
|
30
|
+
for (const [key, val] of Object.entries(newParams)) {
|
|
31
|
+
if (activeSecrets.includes(val)) {
|
|
32
|
+
const parsed = secretManager_1.parseSecretVersionResourceName(val);
|
|
33
|
+
parsed.versionId = "latest";
|
|
34
|
+
newParams[key] = secretManager_1.toSecretVersionResourceName(parsed);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.assign(Object.assign({}, spec), { params: newParams });
|
|
38
|
+
}
|
|
39
|
+
exports.setSecretParamsToLatest = setSecretParamsToLatest;
|
|
40
|
+
function displayExportInfo(withRef, withoutRef) {
|
|
41
|
+
logger_1.logger.info("The following Extension instances will be saved locally:");
|
|
42
|
+
logger_1.logger.info("");
|
|
43
|
+
displaySpecs(withRef);
|
|
44
|
+
if (withoutRef.length) {
|
|
45
|
+
logger_1.logger.info(`Your project also has the following instances installed from local sources. These will not be saved to firebase.json:`);
|
|
46
|
+
for (const spec of withoutRef) {
|
|
47
|
+
logger_1.logger.info(spec.instanceId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.displayExportInfo = displayExportInfo;
|
|
52
|
+
function displaySpecs(specs) {
|
|
53
|
+
for (let i = 0; i < specs.length; i++) {
|
|
54
|
+
const spec = specs[i];
|
|
55
|
+
logger_1.logger.info(`${i + 1}. ${deploymentSummary_1.humanReadable(spec)}`);
|
|
56
|
+
logger_1.logger.info(`Configuration will be written to 'extensions/${spec.instanceId}.env'`);
|
|
57
|
+
for (const p of Object.entries(spec.params)) {
|
|
58
|
+
logger_1.logger.info(`\t${p[0]}=${p[1]}`);
|
|
59
|
+
}
|
|
60
|
+
logger_1.logger.info("");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function writeExtensionsToFirebaseJson(have, existingConfig) {
|
|
64
|
+
const extensions = existingConfig.get("extensions", {});
|
|
65
|
+
for (const s of have) {
|
|
66
|
+
extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
|
|
67
|
+
}
|
|
68
|
+
existingConfig.set("extensions", extensions);
|
|
69
|
+
logger_1.logger.info("Adding Extensions to " + clc.bold("firebase.json") + "...");
|
|
70
|
+
existingConfig.writeProjectFile("firebase.json", existingConfig.src);
|
|
71
|
+
}
|
|
72
|
+
async function writeEnvFile(spec, existingConfig, force) {
|
|
73
|
+
const content = Object.entries(spec.params)
|
|
74
|
+
.map((r) => `${r[0]}=${r[1]}`)
|
|
75
|
+
.join("\n");
|
|
76
|
+
await existingConfig.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
|
|
77
|
+
}
|
|
78
|
+
async function writeFiles(have, options) {
|
|
79
|
+
const existingConfig = config_1.Config.load(options, true);
|
|
80
|
+
if (!existingConfig) {
|
|
81
|
+
throw new error_1.FirebaseError("Not currently in a Firebase directory. Please run `firebase init` to create a Firebase directory.");
|
|
82
|
+
}
|
|
83
|
+
if (existingConfig.has("extensions") &&
|
|
84
|
+
Object.keys(existingConfig.get("extensions")).length &&
|
|
85
|
+
!options.nonInteractive &&
|
|
86
|
+
!options.force) {
|
|
87
|
+
const currentExtensions = Object.entries(existingConfig.get("extensions"))
|
|
88
|
+
.map((i) => `${i[0]}: ${i[1]}`)
|
|
89
|
+
.join("\n\t");
|
|
90
|
+
const overwrite = await prompt_1.promptOnce({
|
|
91
|
+
type: "list",
|
|
92
|
+
message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
|
|
93
|
+
choices: [
|
|
94
|
+
{ name: "Overwrite", value: true },
|
|
95
|
+
{ name: "Merge", value: false },
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
if (overwrite) {
|
|
99
|
+
existingConfig.set("extensions", {});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
writeExtensionsToFirebaseJson(have, existingConfig);
|
|
103
|
+
for (const spec of have) {
|
|
104
|
+
await writeEnvFile(spec, existingConfig, options.force);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.writeFiles = writeFiles;
|