firebase-tools 9.21.0 → 9.23.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +7 -3
  2. package/lib/api.js +2 -0
  3. package/lib/apiv2.js +3 -2
  4. package/lib/commands/crashlytics-symbols-upload.js +1 -1
  5. package/lib/commands/deploy.js +9 -1
  6. package/lib/commands/ext-configure.js +1 -1
  7. package/lib/commands/ext-dev-deprecate.js +63 -0
  8. package/lib/commands/ext-dev-undeprecate.js +56 -0
  9. package/lib/commands/ext-export.js +44 -0
  10. package/lib/commands/ext-install.js +1 -1
  11. package/lib/commands/ext-update.js +1 -1
  12. package/lib/commands/functions-delete.js +8 -0
  13. package/lib/commands/index.js +6 -5
  14. package/lib/commands/init.js +3 -0
  15. package/lib/commands/remoteconfig-get.js +6 -5
  16. package/lib/config.js +3 -2
  17. package/lib/deploy/extensions/args.js +2 -0
  18. package/lib/deploy/extensions/deploy.js +49 -0
  19. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  20. package/lib/deploy/extensions/errors.js +31 -0
  21. package/lib/deploy/extensions/index.js +8 -0
  22. package/lib/deploy/extensions/params.js +39 -0
  23. package/lib/deploy/extensions/planner.js +94 -0
  24. package/lib/deploy/extensions/prepare.js +111 -0
  25. package/lib/deploy/extensions/release.js +43 -0
  26. package/lib/deploy/extensions/secrets.js +150 -0
  27. package/lib/deploy/extensions/tasks.js +98 -0
  28. package/lib/deploy/extensions/validate.js +17 -0
  29. package/lib/deploy/functions/backend.js +8 -1
  30. package/lib/deploy/functions/checkIam.js +65 -4
  31. package/lib/deploy/functions/containerCleaner.js +97 -50
  32. package/lib/deploy/functions/eventTypes.js +10 -0
  33. package/lib/deploy/functions/prepare.js +12 -1
  34. package/lib/deploy/functions/release/fabricator.js +75 -10
  35. package/lib/deploy/functions/release/index.js +9 -1
  36. package/lib/deploy/functions/release/planner.js +3 -0
  37. package/lib/deploy/functions/release/reporter.js +8 -1
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
  39. package/lib/deploy/functions/runtimes/node/parseTriggers.js +10 -11
  40. package/lib/deploy/functions/services/index.js +38 -0
  41. package/lib/deploy/functions/services/storage.js +43 -0
  42. package/lib/deploy/functions/triggerRegionHelper.js +6 -30
  43. package/lib/deploy/index.js +10 -1
  44. package/lib/emulator/auth/handlers.js +1 -1
  45. package/lib/emulator/auth/operations.js +27 -9
  46. package/lib/emulator/auth/widget_ui.js +17 -3
  47. package/lib/emulator/functionsEmulator.js +18 -2
  48. package/lib/emulator/functionsEmulatorShared.js +1 -0
  49. package/lib/emulator/pubsubEmulator.js +58 -45
  50. package/lib/emulator/storage/cloudFunctions.js +13 -6
  51. package/lib/ensureApiEnabled.js +11 -14
  52. package/lib/extensions/askUserForParam.js +42 -10
  53. package/lib/extensions/checkProjectBilling.js +7 -7
  54. package/lib/extensions/emulator/triggerHelper.js +1 -0
  55. package/lib/extensions/export.js +107 -0
  56. package/lib/extensions/extensionsApi.js +103 -21
  57. package/lib/extensions/extensionsHelper.js +4 -1
  58. package/lib/extensions/listExtensions.js +16 -11
  59. package/lib/extensions/paramHelper.js +6 -4
  60. package/lib/extensions/provisioningHelper.js +16 -3
  61. package/lib/extensions/refs.js +9 -1
  62. package/lib/extensions/secretsUtils.js +10 -9
  63. package/lib/extensions/updateHelper.js +12 -2
  64. package/lib/extensions/versionHelper.js +14 -0
  65. package/lib/extensions/warnings.js +33 -1
  66. package/lib/functions/env.js +2 -2
  67. package/lib/gcp/artifactregistry.js +16 -0
  68. package/lib/gcp/cloudfunctions.js +27 -7
  69. package/lib/gcp/cloudfunctionsv2.js +45 -5
  70. package/lib/gcp/cloudtasks.js +143 -0
  71. package/lib/gcp/docker.js +36 -2
  72. package/lib/gcp/location.js +44 -0
  73. package/lib/gcp/proto.js +2 -2
  74. package/lib/gcp/secretManager.js +27 -6
  75. package/lib/gcp/storage.js +48 -32
  76. package/lib/init/features/functions/index.js +3 -3
  77. package/lib/init/features/hosting/github.js +3 -0
  78. package/lib/init/features/project.js +2 -1
  79. package/lib/previews.js +1 -1
  80. package/lib/projectUtils.js +10 -1
  81. package/package.json +5 -4
  82. package/schema/firebase-config.json +9 -0
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveVersion = exports.want = exports.have = exports.getExtension = exports.getExtensionVersion = void 0;
4
+ const semver = require("semver");
5
+ const error_1 = require("../../error");
6
+ const extensionsApi = require("../../extensions/extensionsApi");
7
+ const extensionsHelper_1 = require("../../extensions/extensionsHelper");
8
+ const refs = require("../../extensions/refs");
9
+ const params_1 = require("./params");
10
+ const logger_1 = require("../../logger");
11
+ async function getExtensionVersion(i) {
12
+ if (!i.extensionVersion) {
13
+ if (!i.ref) {
14
+ throw new error_1.FirebaseError(`Can't get ExtensionVersion for ${i.instanceId} because it has no ref`);
15
+ }
16
+ i.extensionVersion = await extensionsApi.getExtensionVersion(refs.toExtensionVersionRef(i.ref));
17
+ }
18
+ return i.extensionVersion;
19
+ }
20
+ exports.getExtensionVersion = getExtensionVersion;
21
+ async function getExtension(i) {
22
+ if (!i.ref) {
23
+ throw new error_1.FirebaseError(`Can't get Extensionfor ${i.instanceId} because it has no ref`);
24
+ }
25
+ if (!i.extension) {
26
+ i.extension = await extensionsApi.getExtension(refs.toExtensionRef(i.ref));
27
+ }
28
+ return i.extension;
29
+ }
30
+ exports.getExtension = getExtension;
31
+ async function have(projectId) {
32
+ const instances = await extensionsApi.listInstances(projectId);
33
+ return instances.map((i) => {
34
+ const dep = {
35
+ instanceId: i.name.split("/").pop(),
36
+ params: i.config.params,
37
+ };
38
+ if (i.config.extensionRef) {
39
+ const ref = refs.parse(i.config.extensionRef);
40
+ dep.ref = ref;
41
+ dep.ref.version = i.config.extensionVersion;
42
+ }
43
+ return dep;
44
+ });
45
+ }
46
+ exports.have = have;
47
+ async function want(args) {
48
+ const instanceSpecs = [];
49
+ const errors = [];
50
+ for (const e of Object.entries(args.extensions)) {
51
+ try {
52
+ const instanceId = e[0];
53
+ const ref = refs.parse(e[1]);
54
+ ref.version = await resolveVersion(ref);
55
+ const params = params_1.readParams({
56
+ projectDir: args.projectDir,
57
+ instanceId,
58
+ projectId: args.projectId,
59
+ projectNumber: args.projectNumber,
60
+ aliases: args.aliases,
61
+ });
62
+ const autoPopulatedParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
63
+ const subbedParams = extensionsHelper_1.substituteParams(params, autoPopulatedParams);
64
+ instanceSpecs.push({
65
+ instanceId,
66
+ ref,
67
+ params: subbedParams,
68
+ });
69
+ }
70
+ catch (err) {
71
+ logger_1.logger.debug(`Got error reading extensions entry ${e}: ${err}`);
72
+ errors.push(err);
73
+ }
74
+ }
75
+ if (errors.length) {
76
+ const messages = errors.map((err) => `- ${err.message}`).join("\n");
77
+ throw new error_1.FirebaseError(`Errors while reading 'extensions' in 'firebase.json'\n${messages}`);
78
+ }
79
+ return instanceSpecs;
80
+ }
81
+ exports.want = want;
82
+ async function resolveVersion(ref) {
83
+ if (!ref.version || ref.version == "latest") {
84
+ return "latest";
85
+ }
86
+ const extensionRef = refs.toExtensionRef(ref);
87
+ const versions = await extensionsApi.listExtensionVersions(extensionRef);
88
+ const maxSatisfying = semver.maxSatisfying(versions.map((ev) => ev.spec.version), ref.version);
89
+ if (!maxSatisfying) {
90
+ throw new error_1.FirebaseError(`No version of ${extensionRef} matches requested version ${ref.version}`);
91
+ }
92
+ return maxSatisfying;
93
+ }
94
+ exports.resolveVersion = resolveVersion;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepare = void 0;
4
+ const planner = require("./planner");
5
+ const deploymentSummary = require("./deploymentSummary");
6
+ const prompt = require("../../prompt");
7
+ const refs = require("../../extensions/refs");
8
+ const projectUtils_1 = require("../../projectUtils");
9
+ const logger_1 = require("../../logger");
10
+ const error_1 = require("../../error");
11
+ const requirePermissions_1 = require("../../requirePermissions");
12
+ const extensionsHelper_1 = require("../../extensions/extensionsHelper");
13
+ const secretsUtils_1 = require("../../extensions/secretsUtils");
14
+ const secrets_1 = require("./secrets");
15
+ const warnings_1 = require("../../extensions/warnings");
16
+ async function prepare(context, options, payload) {
17
+ var _a;
18
+ const projectId = projectUtils_1.needProjectId(options);
19
+ const projectNumber = await projectUtils_1.needProjectNumber(options);
20
+ const aliases = projectUtils_1.getAliases(options, projectId);
21
+ await extensionsHelper_1.ensureExtensionsApiEnabled(options);
22
+ await requirePermissions_1.requirePermissions(options, ["firebaseextensions.instances.list"]);
23
+ context.have = await planner.have(projectId);
24
+ context.want = await planner.want({
25
+ projectId,
26
+ projectNumber,
27
+ aliases,
28
+ projectDir: options.config.projectDir,
29
+ extensions: options.config.get("extensions"),
30
+ });
31
+ const usingSecrets = await Promise.all((_a = context.have) === null || _a === void 0 ? void 0 : _a.map(secrets_1.checkSpecForSecrets));
32
+ if (usingSecrets.some((i) => i)) {
33
+ await secretsUtils_1.ensureSecretManagerApiEnabled(options);
34
+ }
35
+ payload.instancesToCreate = context.want.filter((i) => { var _a; return !((_a = context.have) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))); });
36
+ payload.instancesToConfigure = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isConfigure(i)); });
37
+ payload.instancesToUpdate = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isUpdate(i)); });
38
+ payload.instancesToDelete = context.have.filter((i) => { var _a; return !((_a = context.want) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))); });
39
+ if (await warnings_1.displayWarningsForDeploy(payload.instancesToCreate)) {
40
+ if (!options.force && options.nonInteractive) {
41
+ throw new error_1.FirebaseError("Pass the --force flag to acknowledge these terms in non-interactive mode");
42
+ }
43
+ else if (!options.force &&
44
+ !options.nonInteractive &&
45
+ !(await prompt.promptOnce({
46
+ type: "confirm",
47
+ message: `Do you wish to continue deploying these extensions?`,
48
+ default: true,
49
+ }))) {
50
+ throw new error_1.FirebaseError("Deployment cancelled");
51
+ }
52
+ }
53
+ if (await warnings_1.displayWarningsForDeploy(payload.instancesToCreate)) {
54
+ if (!options.force && options.nonInteractive) {
55
+ throw new error_1.FirebaseError("Pass the --force flag to acknowledge these terms in non-interactive mode");
56
+ }
57
+ else if (!options.force &&
58
+ !options.nonInteractive &&
59
+ !(await prompt.promptOnce({
60
+ type: "confirm",
61
+ message: `Do you wish to continue deploying these extensions?`,
62
+ default: true,
63
+ }))) {
64
+ throw new error_1.FirebaseError("Deployment cancelled");
65
+ }
66
+ }
67
+ const permissionsNeeded = [];
68
+ if (payload.instancesToCreate.length) {
69
+ permissionsNeeded.push("firebaseextensions.instances.create");
70
+ logger_1.logger.info(deploymentSummary.createsSummary(payload.instancesToCreate));
71
+ }
72
+ if (payload.instancesToUpdate.length) {
73
+ permissionsNeeded.push("firebaseextensions.instances.update");
74
+ logger_1.logger.info(deploymentSummary.updatesSummary(payload.instancesToUpdate, context.have));
75
+ }
76
+ if (payload.instancesToConfigure.length) {
77
+ permissionsNeeded.push("firebaseextensions.instances.update");
78
+ logger_1.logger.info(deploymentSummary.configuresSummary(payload.instancesToConfigure));
79
+ }
80
+ if (payload.instancesToDelete.length) {
81
+ logger_1.logger.info(deploymentSummary.deletesSummary(payload.instancesToDelete));
82
+ if (!options.force && options.nonInteractive) {
83
+ throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
84
+ }
85
+ else if (!options.force &&
86
+ !options.nonInteractive &&
87
+ !(await prompt.promptOnce({
88
+ type: "confirm",
89
+ message: `Would you like to delete ${payload.instancesToDelete
90
+ .map((i) => i.instanceId)
91
+ .join(", ")}?`,
92
+ default: false,
93
+ }))) {
94
+ payload.instancesToDelete = [];
95
+ }
96
+ else {
97
+ permissionsNeeded.push("firebaseextensions.instances.delete");
98
+ }
99
+ }
100
+ await requirePermissions_1.requirePermissions(options, permissionsNeeded);
101
+ }
102
+ exports.prepare = prepare;
103
+ const matchesInstanceId = (dep) => (test) => {
104
+ return dep.instanceId === test.instanceId;
105
+ };
106
+ const isUpdate = (dep) => (test) => {
107
+ return dep.instanceId === test.instanceId && !refs.equal(dep.ref, test.ref);
108
+ };
109
+ const isConfigure = (dep) => (test) => {
110
+ return dep.instanceId === test.instanceId && refs.equal(dep.ref, test.ref);
111
+ };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.release = void 0;
4
+ const queue_1 = require("../../throttler/queue");
5
+ const tasks = require("./tasks");
6
+ const error_1 = require("../../error");
7
+ const errors_1 = require("./errors");
8
+ const projectUtils_1 = require("../../projectUtils");
9
+ async function release(context, options, payload) {
10
+ var _a, _b, _c, _d;
11
+ const projectId = projectUtils_1.needProjectId(options);
12
+ const errorHandler = new errors_1.ErrorHandler();
13
+ const deploymentQueue = new queue_1.default({
14
+ retries: 5,
15
+ concurrency: 5,
16
+ handler: tasks.extensionsDeploymentHandler(errorHandler),
17
+ });
18
+ for (const creation of (_a = payload.instancesToCreate) !== null && _a !== void 0 ? _a : []) {
19
+ const task = tasks.createExtensionInstanceTask(projectId, creation);
20
+ void deploymentQueue.run(task);
21
+ }
22
+ for (const update of (_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []) {
23
+ const task = tasks.updateExtensionInstanceTask(projectId, update);
24
+ void deploymentQueue.run(task);
25
+ }
26
+ for (const update of (_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : []) {
27
+ const task = tasks.configureExtensionInstanceTask(projectId, update);
28
+ void deploymentQueue.run(task);
29
+ }
30
+ for (const deletion of (_d = payload.instancesToDelete) !== null && _d !== void 0 ? _d : []) {
31
+ const task = tasks.deleteExtensionInstanceTask(projectId, deletion);
32
+ void deploymentQueue.run(task);
33
+ }
34
+ const deploymentPromise = deploymentQueue.wait();
35
+ deploymentQueue.process();
36
+ deploymentQueue.close();
37
+ await deploymentPromise;
38
+ if (errorHandler.hasErrors()) {
39
+ errorHandler.print();
40
+ throw new error_1.FirebaseError(`Extensions deployment failed.`);
41
+ }
42
+ }
43
+ exports.release = release;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkSpecForSecrets = exports.handleSecretParams = void 0;
4
+ const clc = require("cli-color");
5
+ const secretUtils = require("../../extensions/secretsUtils");
6
+ const secretManager = require("../../gcp/secretManager");
7
+ const planner_1 = require("./planner");
8
+ const askUserForParam_1 = require("../../extensions/askUserForParam");
9
+ const extensionsApi_1 = require("../../extensions/extensionsApi");
10
+ const error_1 = require("../../error");
11
+ const logger_1 = require("../../logger");
12
+ const utils_1 = require("../../utils");
13
+ async function handleSecretParams(payload, have, nonInteractive) {
14
+ var _a, _b, _c;
15
+ for (const i of (_a = payload.instancesToCreate) !== null && _a !== void 0 ? _a : []) {
16
+ if (await checkSpecForSecrets(i)) {
17
+ utils_1.logLabeledBullet("extensions", `Verifying secret params for ${clc.bold(i.instanceId)}`);
18
+ await handleSecretsCreateInstance(i, nonInteractive);
19
+ }
20
+ }
21
+ const updates = [...((_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []), ...((_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : [])];
22
+ for (const i of updates) {
23
+ if (await checkSpecForSecrets(i)) {
24
+ utils_1.logLabeledBullet("extensions", `Verifying secret params for ${clc.bold(i.instanceId)}`);
25
+ const previousSpec = have.find((h) => h.instanceId === i.instanceId);
26
+ await handleSecretsUpdateInstance(i, previousSpec, nonInteractive);
27
+ }
28
+ }
29
+ }
30
+ exports.handleSecretParams = handleSecretParams;
31
+ async function checkSpecForSecrets(i) {
32
+ const extensionVersion = await planner_1.getExtensionVersion(i);
33
+ return secretUtils.usesSecrets(extensionVersion.spec);
34
+ }
35
+ exports.checkSpecForSecrets = checkSpecForSecrets;
36
+ const secretsInSpec = (spec) => {
37
+ return spec.params.filter((p) => p.type === extensionsApi_1.ParamType.SECRET);
38
+ };
39
+ async function handleSecretsCreateInstance(i, nonInteractive) {
40
+ const extensionVersion = await planner_1.getExtensionVersion(i);
41
+ const secretParams = secretsInSpec(extensionVersion.spec);
42
+ for (const s of secretParams) {
43
+ await handleSecretParamForCreate(s, i, nonInteractive);
44
+ }
45
+ }
46
+ async function handleSecretsUpdateInstance(i, prevSpec, nonInteractive) {
47
+ const extensionVersion = await planner_1.getExtensionVersion(i);
48
+ const prevExtensionVersion = await planner_1.getExtensionVersion(prevSpec);
49
+ const secretParams = secretsInSpec(extensionVersion.spec);
50
+ for (const s of secretParams) {
51
+ const prevParam = prevExtensionVersion.spec.params.find((p) => p.param === s.param);
52
+ if ((prevParam === null || prevParam === void 0 ? void 0 : prevParam.type) === extensionsApi_1.ParamType.SECRET && prevSpec.params[prevParam === null || prevParam === void 0 ? void 0 : prevParam.param]) {
53
+ await handleSecretParamForUpdate(s, i, prevSpec.params[prevParam === null || prevParam === void 0 ? void 0 : prevParam.param], nonInteractive);
54
+ }
55
+ else {
56
+ await handleSecretParamForCreate(s, i, nonInteractive);
57
+ }
58
+ }
59
+ }
60
+ async function handleSecretParamForCreate(secretParam, i, nonInteractive) {
61
+ var _a;
62
+ const providedValue = i.params[secretParam.param];
63
+ if (!providedValue) {
64
+ return;
65
+ }
66
+ const [, projectId, , secretName, , version] = providedValue.split("/");
67
+ if (!projectId || !secretName || !version) {
68
+ throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}, but expected a secret version.`);
69
+ }
70
+ const secretInfo = await getSecretInfo(projectId, secretName, version);
71
+ if (!secretInfo.secret) {
72
+ await promptForCreateSecret({
73
+ projectId,
74
+ secretName,
75
+ instanceId: i.instanceId,
76
+ secretParam,
77
+ nonInteractive,
78
+ });
79
+ return;
80
+ }
81
+ else if (!secretInfo.secretVersion) {
82
+ throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}. ` +
83
+ `projects/${projectId}/secrets/${secretName} exists, but version ${version} does not. ` +
84
+ `See more information about this secret at ${secretManager.secretManagerConsoleUri(projectId)}`);
85
+ }
86
+ if (!!((_a = secretInfo === null || secretInfo === void 0 ? void 0 : secretInfo.secret) === null || _a === void 0 ? void 0 : _a.labels) &&
87
+ !!(secretInfo === null || secretInfo === void 0 ? void 0 : secretInfo.secret.labels[secretUtils.SECRET_LABEL]) &&
88
+ secretInfo.secret.labels[secretUtils.SECRET_LABEL] !== i.instanceId) {
89
+ throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}. ` +
90
+ `projects/${projectId}/secrets/${secretName} is managed by a different extension instance (${secretInfo.secret.labels[secretUtils.SECRET_LABEL]}), so reusing it here can lead to unexpected behavior. ` +
91
+ "Please choose a different name for this secret, and rerun this command.");
92
+ }
93
+ await secretUtils.grantFirexServiceAgentSecretAdminRole(secretInfo.secret);
94
+ }
95
+ async function handleSecretParamForUpdate(secretParam, i, prevValue, nonInteractive) {
96
+ const providedValue = i.params[secretParam.param];
97
+ if (!providedValue) {
98
+ return;
99
+ }
100
+ const [, projectId, , secretName, , version] = providedValue.split("/");
101
+ if (!projectId || !secretName || !version) {
102
+ throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}, but expected a secret version.`);
103
+ }
104
+ const [, prevProjectId, , prevSecretName] = prevValue.split("/");
105
+ if (prevSecretName !== secretName) {
106
+ throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}, ` +
107
+ `but this instance was previously using a different secret projects/${prevProjectId}/secrets/${prevSecretName}.\n` +
108
+ `Changing secrets is not supported. If you want to change the value of this secret, ` +
109
+ `use a new version of projects/${prevProjectId}/secrets/${prevSecretName}.` +
110
+ `You can create a new version at ${secretManager.secretManagerConsoleUri(projectId)}`);
111
+ }
112
+ const secretInfo = await getSecretInfo(projectId, secretName, version);
113
+ if (!secretInfo.secret) {
114
+ i.params[secretParam.param] = await promptForCreateSecret({
115
+ projectId,
116
+ secretName,
117
+ instanceId: i.instanceId,
118
+ secretParam,
119
+ nonInteractive,
120
+ });
121
+ return;
122
+ }
123
+ else if (!secretInfo.secretVersion) {
124
+ throw new error_1.FirebaseError(`${clc.bold(i.instanceId)}: Found '${providedValue}' for secret param ${secretParam.param}. ` +
125
+ `projects/${projectId}/secrets/${secretName} exists, but version ${version} does not. ` +
126
+ `See more information about this secret at ${secretManager.secretManagerConsoleUri(projectId)}`);
127
+ }
128
+ i.params[secretParam.param] = secretManager.toSecretVersionResourceName(secretInfo.secretVersion);
129
+ await secretUtils.grantFirexServiceAgentSecretAdminRole(secretInfo.secret);
130
+ }
131
+ async function getSecretInfo(projectId, secretName, version) {
132
+ const secretInfo = {};
133
+ try {
134
+ secretInfo.secret = await secretManager.getSecret(projectId, secretName);
135
+ secretInfo.secretVersion = await secretManager.getSecretVersion(projectId, secretName, version);
136
+ }
137
+ catch (err) {
138
+ if (err.status !== 404) {
139
+ throw err;
140
+ }
141
+ }
142
+ return secretInfo;
143
+ }
144
+ async function promptForCreateSecret(args) {
145
+ logger_1.logger.info(`${clc.bold(args.instanceId)}: Secret ${args.projectId}/${args.secretName} doesn't exist yet.`);
146
+ if (args.nonInteractive) {
147
+ throw new error_1.FirebaseError(`To create this secret, run this command in interactive mode, or go to ${secretManager.secretManagerConsoleUri(args.projectId)}`);
148
+ }
149
+ return askUserForParam_1.promptCreateSecret(args.projectId, args.instanceId, args.secretParam, args.secretName);
150
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteExtensionInstanceTask = exports.configureExtensionInstanceTask = exports.updateExtensionInstanceTask = exports.createExtensionInstanceTask = exports.extensionsDeploymentHandler = void 0;
4
+ const clc = require("cli-color");
5
+ const extensionsApi = require("../../extensions/extensionsApi");
6
+ const refs = require("../../extensions/refs");
7
+ const utils = require("../../utils");
8
+ const isRetryable = (err) => err.status == 429 || err.status == 409;
9
+ function extensionsDeploymentHandler(errorHandler) {
10
+ return async (task) => {
11
+ var _a, _b, _c, _d;
12
+ let result;
13
+ try {
14
+ result = await task.run();
15
+ }
16
+ catch (err) {
17
+ if (isRetryable(err)) {
18
+ throw err;
19
+ }
20
+ errorHandler.record(task.spec.instanceId, task.type, (_d = (_c = (_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : err);
21
+ }
22
+ return result;
23
+ };
24
+ }
25
+ exports.extensionsDeploymentHandler = extensionsDeploymentHandler;
26
+ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
27
+ const run = async () => {
28
+ const res = await extensionsApi.createInstance({
29
+ projectId,
30
+ instanceId: instanceSpec.instanceId,
31
+ params: instanceSpec.params,
32
+ extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref),
33
+ validateOnly,
34
+ });
35
+ printSuccess(instanceSpec.instanceId, "create", validateOnly);
36
+ return;
37
+ };
38
+ return {
39
+ run,
40
+ spec: instanceSpec,
41
+ type: "create",
42
+ };
43
+ }
44
+ exports.createExtensionInstanceTask = createExtensionInstanceTask;
45
+ function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
46
+ const run = async () => {
47
+ const res = await extensionsApi.updateInstanceFromRegistry({
48
+ projectId,
49
+ instanceId: instanceSpec.instanceId,
50
+ extRef: refs.toExtensionVersionRef(instanceSpec.ref),
51
+ params: instanceSpec.params,
52
+ validateOnly,
53
+ });
54
+ printSuccess(instanceSpec.instanceId, "update", validateOnly);
55
+ return;
56
+ };
57
+ return {
58
+ run,
59
+ spec: instanceSpec,
60
+ type: "update",
61
+ };
62
+ }
63
+ exports.updateExtensionInstanceTask = updateExtensionInstanceTask;
64
+ function configureExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
65
+ const run = async () => {
66
+ const res = await extensionsApi.configureInstance({
67
+ projectId,
68
+ instanceId: instanceSpec.instanceId,
69
+ params: instanceSpec.params,
70
+ validateOnly,
71
+ });
72
+ printSuccess(instanceSpec.instanceId, "configure", validateOnly);
73
+ return;
74
+ };
75
+ return {
76
+ run,
77
+ spec: instanceSpec,
78
+ type: "configure",
79
+ };
80
+ }
81
+ exports.configureExtensionInstanceTask = configureExtensionInstanceTask;
82
+ function deleteExtensionInstanceTask(projectId, instanceSpec) {
83
+ const run = async () => {
84
+ const res = await extensionsApi.deleteInstance(projectId, instanceSpec.instanceId);
85
+ printSuccess(instanceSpec.instanceId, "delete", false);
86
+ return;
87
+ };
88
+ return {
89
+ run,
90
+ spec: instanceSpec,
91
+ type: "delete",
92
+ };
93
+ }
94
+ exports.deleteExtensionInstanceTask = deleteExtensionInstanceTask;
95
+ function printSuccess(instanceId, type, validateOnly) {
96
+ const action = validateOnly ? `validated ${type} for` : `${type}d`;
97
+ utils.logSuccess(clc.bold.green("extensions") + ` Successfully ${action} ${instanceId}`);
98
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkBilling = void 0;
4
+ const cloudbilling_1 = require("../../gcp/cloudbilling");
5
+ const checkProjectBilling_1 = require("../../extensions/checkProjectBilling");
6
+ const error_1 = require("../../error");
7
+ async function checkBilling(projectId, nonInteractive) {
8
+ const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
9
+ if (!enabled && nonInteractive) {
10
+ throw new error_1.FirebaseError(`Extensions require the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
11
+ `Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project.`);
12
+ }
13
+ else if (!enabled) {
14
+ await checkProjectBilling_1.enableBilling(projectId);
15
+ }
16
+ }
17
+ exports.checkBilling = checkBilling;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
3
+ exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.of = exports.empty = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isHttpsTriggered = exports.SCHEDULED_FUNCTION_LABEL = exports.memoryOptionDisplayName = exports.endpointTriggerType = void 0;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
6
  const utils = require("../../utils");
@@ -16,6 +16,9 @@ function endpointTriggerType(endpoint) {
16
16
  else if (isEventTriggered(endpoint)) {
17
17
  return endpoint.eventTrigger.eventType;
18
18
  }
19
+ else if (isTaskQueueTriggered(endpoint)) {
20
+ return "taskQueue";
21
+ }
19
22
  else {
20
23
  throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
21
24
  }
@@ -46,6 +49,10 @@ function isScheduleTriggered(triggered) {
46
49
  return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
47
50
  }
48
51
  exports.isScheduleTriggered = isScheduleTriggered;
52
+ function isTaskQueueTriggered(triggered) {
53
+ return {}.hasOwnProperty.call(triggered, "taskQueueTrigger");
54
+ }
55
+ exports.isTaskQueueTriggered = isTaskQueueTriggered;
49
56
  function empty() {
50
57
  return {
51
58
  requiredAPIs: {},
@@ -1,19 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkHttpIam = exports.checkServiceAccountIam = void 0;
3
+ exports.ensureServiceAgentRoles = exports.mergeBindings = exports.checkHttpIam = exports.checkServiceAccountIam = 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");
7
7
  const error_1 = require("../../error");
8
- const iam_1 = require("../../gcp/iam");
8
+ const iam = require("../../gcp/iam");
9
9
  const backend = require("./backend");
10
10
  const track = require("../../track");
11
+ const utils = require("../../utils");
12
+ const resourceManager_1 = require("../../gcp/resourceManager");
13
+ const services_1 = require("./services");
11
14
  const PERMISSION = "cloudfunctions.functions.setIamPolicy";
12
15
  async function checkServiceAccountIam(projectId) {
13
16
  const saEmail = `${projectId}@appspot.gserviceaccount.com`;
14
17
  let passed = false;
15
18
  try {
16
- const iamResult = await iam_1.testResourceIamPermissions("https://iam.googleapis.com", "v1", `projects/${projectId}/serviceAccounts/${saEmail}`, ["iam.serviceAccounts.actAs"]);
19
+ const iamResult = await iam.testResourceIamPermissions("https://iam.googleapis.com", "v1", `projects/${projectId}/serviceAccounts/${saEmail}`, ["iam.serviceAccounts.actAs"]);
17
20
  passed = iamResult.passed;
18
21
  }
19
22
  catch (err) {
@@ -41,7 +44,7 @@ async function checkHttpIam(context, options, payload) {
41
44
  logger_1.logger.debug("[functions] found", newHttpsEndpoints.length, "new HTTP functions, testing setIamPolicy permission...");
42
45
  let passed = true;
43
46
  try {
44
- const iamResult = await iam_1.testIamPermissions(context.projectId, [PERMISSION]);
47
+ const iamResult = await iam.testIamPermissions(context.projectId, [PERMISSION]);
45
48
  passed = iamResult.passed;
46
49
  }
47
50
  catch (e) {
@@ -57,3 +60,61 @@ async function checkHttpIam(context, options, payload) {
57
60
  logger_1.logger.debug("[functions] found setIamPolicy permission, proceeding with deploy");
58
61
  }
59
62
  exports.checkHttpIam = checkHttpIam;
63
+ function reduceEventsToServices(services, endpoint) {
64
+ const service = services_1.serviceForEndpoint(endpoint);
65
+ if (service.requiredProjectBindings && !services.find((s) => s.name === service.name)) {
66
+ services.push(service);
67
+ }
68
+ return services;
69
+ }
70
+ function mergeBindings(policy, allRequiredBindings) {
71
+ for (const requiredBindings of allRequiredBindings) {
72
+ if (requiredBindings.length === 0) {
73
+ continue;
74
+ }
75
+ for (const requiredBinding of requiredBindings) {
76
+ const ndx = policy.bindings.findIndex((policyBinding) => policyBinding.role === requiredBinding.role);
77
+ if (ndx === -1) {
78
+ policy.bindings.push(requiredBinding);
79
+ continue;
80
+ }
81
+ requiredBinding.members.forEach((updatedMember) => {
82
+ if (!policy.bindings[ndx].members.find((member) => member === updatedMember)) {
83
+ policy.bindings[ndx].members.push(updatedMember);
84
+ }
85
+ });
86
+ }
87
+ }
88
+ }
89
+ exports.mergeBindings = mergeBindings;
90
+ async function ensureServiceAgentRoles(projectId, want, have) {
91
+ const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
92
+ const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
93
+ const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
94
+ if (newServices.length === 0) {
95
+ return;
96
+ }
97
+ let policy;
98
+ try {
99
+ policy = await resourceManager_1.getIamPolicy(projectId);
100
+ }
101
+ catch (err) {
102
+ utils.logLabeledBullet("functions", "Could not verify the necessary IAM configuration for the following newly-integrated services: " +
103
+ `${newServices.map((service) => service.api).join(", ")}` +
104
+ ". Deployment may fail.", "warn");
105
+ return;
106
+ }
107
+ const findRequiredBindings = [];
108
+ newServices.forEach((service) => findRequiredBindings.push(service.requiredProjectBindings(projectId, policy)));
109
+ const allRequiredBindings = await Promise.all(findRequiredBindings);
110
+ mergeBindings(policy, allRequiredBindings);
111
+ try {
112
+ await resourceManager_1.setIamPolicy(projectId, policy, "bindings");
113
+ }
114
+ catch (err) {
115
+ throw new error_1.FirebaseError("We failed to modify the IAM policy for the project. The functions " +
116
+ "deployment requires specific roles to be granted to service agents," +
117
+ " otherwise the deployment will fail.", { original: err });
118
+ }
119
+ }
120
+ exports.ensureServiceAgentRoles = ensureServiceAgentRoles;