firebase-tools 10.1.2 → 10.2.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.
Files changed (65) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +92 -48
  3. package/lib/archiveDirectory.js +63 -73
  4. package/lib/auth.js +62 -25
  5. package/lib/commands/ext-configure.js +1 -0
  6. package/lib/commands/ext-dev-usage.js +3 -8
  7. package/lib/commands/ext-install.js +1 -0
  8. package/lib/commands/ext-uninstall.js +1 -0
  9. package/lib/commands/ext-update.js +1 -0
  10. package/lib/commands/functions-secrets-access.js +17 -0
  11. package/lib/commands/functions-secrets-destroy.js +40 -0
  12. package/lib/commands/functions-secrets-get.js +21 -0
  13. package/lib/commands/functions-secrets-prune.js +50 -0
  14. package/lib/commands/functions-secrets-set.js +46 -0
  15. package/lib/commands/index.js +7 -3
  16. package/lib/commands/login.js +1 -1
  17. package/lib/database/metadata.js +16 -24
  18. package/lib/deploy/functions/backend.js +11 -1
  19. package/lib/deploy/functions/ensure.js +112 -0
  20. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  21. package/lib/deploy/functions/prepare.js +13 -19
  22. package/lib/deploy/functions/release/fabricator.js +4 -1
  23. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -0
  24. package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -0
  25. package/lib/deploy/functions/validate.js +83 -1
  26. package/lib/deploy/hosting/convertConfig.js +45 -24
  27. package/lib/deploy/hosting/prepare.js +1 -1
  28. package/lib/emulator/controller.js +3 -1
  29. package/lib/emulator/emulatorLogger.js +7 -0
  30. package/lib/emulator/functionsEmulator.js +113 -79
  31. package/lib/emulator/functionsEmulatorRuntime.js +100 -83
  32. package/lib/emulator/functionsEmulatorShared.js +51 -1
  33. package/lib/emulator/functionsEmulatorShell.js +1 -2
  34. package/lib/emulator/functionsRuntimeWorker.js +1 -1
  35. package/lib/emulator/storage/apis/gcloud.js +2 -2
  36. package/lib/emulator/storage/files.js +8 -3
  37. package/lib/extensions/askUserForParam.js +1 -1
  38. package/lib/extensions/diagnose.js +56 -0
  39. package/lib/extensions/extensionsApi.js +0 -1
  40. package/lib/extensions/extensionsHelper.js +10 -17
  41. package/lib/extensions/resolveSource.js +1 -53
  42. package/lib/extensions/secretsUtils.js +1 -1
  43. package/lib/extensions/updateHelper.js +0 -14
  44. package/lib/extensions/utils.js +4 -2
  45. package/lib/functions/env.js +5 -7
  46. package/lib/functions/secrets.js +112 -0
  47. package/lib/gcp/cloudbilling.js +8 -19
  48. package/lib/gcp/cloudfunctions.js +24 -48
  49. package/lib/gcp/cloudlogging.js +8 -11
  50. package/lib/gcp/cloudmonitoring.js +8 -5
  51. package/lib/gcp/cloudscheduler.js +7 -18
  52. package/lib/gcp/firedata.js +5 -4
  53. package/lib/gcp/firestore.js +5 -5
  54. package/lib/gcp/iam.js +18 -33
  55. package/lib/gcp/resourceManager.js +8 -13
  56. package/lib/gcp/runtimeconfig.js +31 -53
  57. package/lib/gcp/secretManager.js +137 -77
  58. package/lib/gcp/storage.js +25 -29
  59. package/lib/previews.js +1 -1
  60. package/lib/serve/functions.js +2 -2
  61. package/lib/utils.js +6 -1
  62. package/npm-shrinkwrap.json +962 -987
  63. package/package.json +5 -3
  64. package/schema/firebase-config.json +387 -12
  65. package/templates/init/hosting/index.html +1 -1
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const backend = require("../deploy/functions/backend");
4
+ const command_1 = require("../command");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const secrets_1 = require("../functions/secrets");
7
+ const requirePermissions_1 = require("../requirePermissions");
8
+ const deploymentTool_1 = require("../deploymentTool");
9
+ const utils_1 = require("../utils");
10
+ const prompt_1 = require("../prompt");
11
+ const secretManager_1 = require("../gcp/secretManager");
12
+ exports.default = new command_1.Command("functions:secrets:prune")
13
+ .description("Destroys unused secrets")
14
+ .before(requirePermissions_1.requirePermissions, [
15
+ "cloudfunctions.functions.list",
16
+ "secretmanager.secrets.list",
17
+ "secretmanager.versions.list",
18
+ "secretmanager.versions.destroy",
19
+ ])
20
+ .action(async (options) => {
21
+ const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
22
+ const projectId = (0, projectUtils_1.needProjectId)(options);
23
+ (0, utils_1.logBullet)("Loading secrets...");
24
+ const haveBackend = await backend.existingBackend({ projectId });
25
+ const haveEndpoints = backend
26
+ .allEndpoints(haveBackend)
27
+ .filter((e) => (0, deploymentTool_1.isFirebaseManaged)(e.labels || []));
28
+ const pruned = await (0, secrets_1.pruneSecrets)({ projectNumber, projectId }, haveEndpoints);
29
+ if (pruned.length === 0) {
30
+ (0, utils_1.logBullet)("All secrets are in use. Nothing to prune today.");
31
+ return;
32
+ }
33
+ (0, utils_1.logBullet)(`Found ${pruned.length} unused active secret versions:\n\t` +
34
+ pruned.map((sv) => `${sv.secret}@${sv.version}`).join("\n\t"));
35
+ const confirm = await (0, prompt_1.promptOnce)({
36
+ name: "destroy",
37
+ type: "confirm",
38
+ default: true,
39
+ message: `Do you want to destroy unused secret versions?`,
40
+ }, options);
41
+ if (!confirm) {
42
+ (0, utils_1.logBullet)("Run the following commands to destroy each unused secret version:\n\t" +
43
+ pruned
44
+ .map((sv) => `firebase functions:secrets:destroy ${sv.secret}@${sv.version}`)
45
+ .join("\n\t"));
46
+ return;
47
+ }
48
+ await Promise.all(pruned.map((sv) => (0, secretManager_1.destroySecretVersion)(projectId, sv.secret, sv.version)));
49
+ (0, utils_1.logSuccess)("Destroyed all unused secrets!");
50
+ });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tty = require("tty");
4
+ const fs = require("fs");
5
+ const clc = require("cli-color");
6
+ const secrets_1 = require("../functions/secrets");
7
+ const command_1 = require("../command");
8
+ const requirePermissions_1 = require("../requirePermissions");
9
+ const prompt_1 = require("../prompt");
10
+ const utils_1 = require("../utils");
11
+ const projectUtils_1 = require("../projectUtils");
12
+ const secretManager_1 = require("../gcp/secretManager");
13
+ exports.default = new command_1.Command("functions:secrets:set <KEY>")
14
+ .description("Create or update a secret for use in Cloud Functions for Firebase")
15
+ .withForce("Does not ensure input keys are valid or upgrade existing secrets to have Firebase manage them.")
16
+ .before(requirePermissions_1.requirePermissions, [
17
+ "secretmanager.secrets.create",
18
+ "secretmanager.secrets.get",
19
+ "secretmanager.secrets.update",
20
+ "secretmanager.versions.add",
21
+ ])
22
+ .option("--data-file <dataFile>", 'File path from which to read secret data. Set to "-" to read the secret data from stdin.')
23
+ .action(async (unvalidatedKey, options) => {
24
+ const projectId = (0, projectUtils_1.needProjectId)(options);
25
+ const key = await (0, secrets_1.ensureValidKey)(unvalidatedKey, options);
26
+ const secret = await (0, secrets_1.ensureSecret)(projectId, key, options);
27
+ let secretValue;
28
+ if ((!options.dataFile || options.dataFile === "-") && tty.isatty(0)) {
29
+ secretValue = await (0, prompt_1.promptOnce)({
30
+ name: key,
31
+ type: "password",
32
+ message: `Enter a value for ${key}`,
33
+ });
34
+ }
35
+ else {
36
+ let dataFile = 0;
37
+ if (options.dataFile && options.dataFile !== "-") {
38
+ dataFile = options.dataFile;
39
+ }
40
+ secretValue = fs.readFileSync(dataFile, "utf-8");
41
+ }
42
+ const secretVersion = await (0, secretManager_1.addVersion)(projectId, key, secretValue);
43
+ (0, utils_1.logSuccess)(`Created a new secret version ${(0, secretManager_1.toSecretVersionResourceName)(secretVersion)}`);
44
+ (0, utils_1.logBullet)("Please deploy your functions for the change to take effect by running:\n\t" +
45
+ clc.bold("firebase deploy --only functions"));
46
+ });
@@ -91,9 +91,7 @@ module.exports = function (client) {
91
91
  client.functions = {};
92
92
  client.functions.config = {};
93
93
  client.functions.config.clone = loadCommand("functions-config-clone");
94
- if (previews.dotenv) {
95
- client.functions.config.export = loadCommand("functions-config-export");
96
- }
94
+ client.functions.config.export = loadCommand("functions-config-export");
97
95
  client.functions.config.get = loadCommand("functions-config-get");
98
96
  client.functions.config.set = loadCommand("functions-config-set");
99
97
  client.functions.config.unset = loadCommand("functions-config-unset");
@@ -104,6 +102,12 @@ module.exports = function (client) {
104
102
  if (previews.deletegcfartifacts) {
105
103
  client.functions.deletegcfartifacts = loadCommand("functions-deletegcfartifacts");
106
104
  }
105
+ client.functions.secrets = {};
106
+ client.functions.secrets.access = loadCommand("functions-secrets-access");
107
+ client.functions.secrets.destroy = loadCommand("functions-secrets-destroy");
108
+ client.functions.secrets.get = loadCommand("functions-secrets-get");
109
+ client.functions.secrets.prune = loadCommand("functions-secrets-prune");
110
+ client.functions.secrets.set = loadCommand("functions-secrets-set");
107
111
  client.help = loadCommand("help");
108
112
  client.hosting = {};
109
113
  client.hosting.channel = {};
@@ -12,7 +12,7 @@ const auth = require("../auth");
12
12
  const utils_1 = require("../utils");
13
13
  module.exports = new command_1.Command("login")
14
14
  .description("log the CLI into Firebase")
15
- .option("--no-localhost", "copy and paste a code instead of starting a local server for authentication")
15
+ .option("--no-localhost", "login from a device without an accessible localhost")
16
16
  .option("--reauth", "force reauthentication even if already logged in")
17
17
  .action(async (options) => {
18
18
  if (options.nonInteractive) {
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setRulesetLabels = exports.createRuleset = exports.getRulesetLabels = exports.getRuleset = exports.listAllRulesets = void 0;
4
- const api = require("../api");
4
+ const api_1 = require("../api");
5
+ const apiv2_1 = require("../apiv2");
5
6
  const logger_1 = require("../logger");
6
7
  const utils = require("../utils");
7
8
  function handleErrorResponse(response) {
@@ -13,12 +14,9 @@ function handleErrorResponse(response) {
13
14
  code: 2,
14
15
  });
15
16
  }
17
+ const apiClient = new apiv2_1.Client({ urlPrefix: api_1.rtdbMetadataOrigin });
16
18
  async function listAllRulesets(databaseName) {
17
- const response = await api.request("GET", `/namespaces/${databaseName}/rulesets`, {
18
- auth: true,
19
- origin: api.rtdbMetadataOrigin,
20
- json: true,
21
- });
19
+ const response = await apiClient.get(`/namespaces/${databaseName}/rulesets`, { resolveOnHTTPError: true });
22
20
  if (response.status === 200) {
23
21
  return response.body.rulesets;
24
22
  }
@@ -26,11 +24,7 @@ async function listAllRulesets(databaseName) {
26
24
  }
27
25
  exports.listAllRulesets = listAllRulesets;
28
26
  async function getRuleset(databaseName, rulesetId) {
29
- const response = await api.request("GET", `/namespaces/${databaseName}/rulesets/${rulesetId}`, {
30
- auth: true,
31
- origin: api.rtdbMetadataOrigin,
32
- json: true,
33
- });
27
+ const response = await apiClient.get(`/namespaces/${databaseName}/rulesets/${rulesetId}`, { resolveOnHTTPError: true });
34
28
  if (response.status === 200) {
35
29
  return response.body;
36
30
  }
@@ -38,9 +32,8 @@ async function getRuleset(databaseName, rulesetId) {
38
32
  }
39
33
  exports.getRuleset = getRuleset;
40
34
  async function getRulesetLabels(databaseName) {
41
- const response = await api.request("GET", `/namespaces/${databaseName}/ruleset_labels`, {
42
- auth: true,
43
- origin: api.rtdbMetadataOrigin,
35
+ const response = await apiClient.get(`/namespaces/${databaseName}/ruleset_labels`, {
36
+ resolveOnHTTPError: true,
44
37
  });
45
38
  if (response.status === 200) {
46
39
  return response.body;
@@ -49,23 +42,22 @@ async function getRulesetLabels(databaseName) {
49
42
  }
50
43
  exports.getRulesetLabels = getRulesetLabels;
51
44
  async function createRuleset(databaseName, source) {
52
- const response = await api.request("POST", `/.settings/rulesets.json`, {
53
- auth: true,
54
- origin: utils.addSubdomain(api.realtimeOrigin, databaseName),
55
- json: false,
56
- data: source,
45
+ const localApiClient = new apiv2_1.Client({
46
+ urlPrefix: utils.addSubdomain(api_1.realtimeOrigin, databaseName),
57
47
  });
48
+ const response = await localApiClient.post(`/.settings/rulesets.json`, source, { resolveOnHTTPError: true });
58
49
  if (response.status === 200) {
59
- return JSON.parse(response.body).id;
50
+ return response.body.id;
60
51
  }
61
52
  return handleErrorResponse(response);
62
53
  }
63
54
  exports.createRuleset = createRuleset;
64
55
  async function setRulesetLabels(databaseName, labels) {
65
- const response = await api.request("PUT", `/.settings/ruleset_labels.json`, {
66
- auth: true,
67
- origin: utils.addSubdomain(api.realtimeOrigin, databaseName),
68
- data: labels,
56
+ const localApiClient = new apiv2_1.Client({
57
+ urlPrefix: utils.addSubdomain(api_1.realtimeOrigin, databaseName),
58
+ });
59
+ const response = await localApiClient.put(`/.settings/ruleset_labels.json`, labels, {
60
+ resolveOnHTTPError: true,
69
61
  });
70
62
  if (response.status === 200) {
71
63
  return response.body;
@@ -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.isTaskQueueTriggered = 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.findEndpoint = 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.MIN_MEMORY_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = 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");
@@ -36,6 +36,8 @@ function memoryOptionDisplayName(option) {
36
36
  }[option];
37
37
  }
38
38
  exports.memoryOptionDisplayName = memoryOptionDisplayName;
39
+ exports.DEFAULT_MEMORY = 256;
40
+ exports.MIN_MEMORY_FOR_CONCURRENCY = 2048;
39
41
  exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
40
42
  function isHttpsTriggered(triggered) {
41
43
  return {}.hasOwnProperty.call(triggered, "httpsTrigger");
@@ -184,6 +186,14 @@ function someEndpoint(backend, predicate) {
184
186
  return false;
185
187
  }
186
188
  exports.someEndpoint = someEndpoint;
189
+ function findEndpoint(backend, predicate) {
190
+ for (const endpoints of Object.values(backend.endpoints)) {
191
+ const endpoint = Object.values(endpoints).find(predicate);
192
+ if (endpoint)
193
+ return endpoint;
194
+ }
195
+ }
196
+ exports.findEndpoint = findEndpoint;
187
197
  function matchingBackend(backend, predicate) {
188
198
  const filtered = Object.assign({}, empty());
189
199
  for (const endpoint of allEndpoints(backend)) {
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secretAccess = exports.maybeEnableAR = exports.cloudBuildEnabled = exports.defaultServiceAccount = void 0;
4
+ const clc = require("cli-color");
5
+ const ensureApiEnabled_1 = require("../../ensureApiEnabled");
6
+ const error_1 = require("../../error");
7
+ const utils_1 = require("../../utils");
8
+ const secretManager_1 = require("../../gcp/secretManager");
9
+ const previews_1 = require("../../previews");
10
+ const projects_1 = require("../../management/projects");
11
+ const functional_1 = require("../../functional");
12
+ const track = require("../../track");
13
+ const backend = require("./backend");
14
+ const ensureApiEnabled = require("../../ensureApiEnabled");
15
+ const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime";
16
+ const CLOUD_BUILD_API = "cloudbuild.googleapis.com";
17
+ async function defaultServiceAccount(e) {
18
+ const metadata = await (0, projects_1.getFirebaseProject)(e.project);
19
+ if (e.platform === "gcfv1") {
20
+ return `${metadata.projectId}@appspot.gserviceaccount.com`;
21
+ }
22
+ else if (e.platform === "gcfv2") {
23
+ return `${metadata.projectNumber}-compute@developer.gserviceaccount.com`;
24
+ }
25
+ (0, functional_1.assertExhaustive)(e.platform);
26
+ }
27
+ exports.defaultServiceAccount = defaultServiceAccount;
28
+ function nodeBillingError(projectId) {
29
+ track("functions_runtime_notices", "nodejs10_billing_error");
30
+ return new error_1.FirebaseError(`Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL:
31
+
32
+ https://console.firebase.google.com/project/${projectId}/usage/details
33
+
34
+ For additional information about this requirement, see Firebase FAQs:
35
+
36
+ ${FAQ_URL}`, { exit: 1 });
37
+ }
38
+ function nodePermissionError(projectId) {
39
+ track("functions_runtime_notices", "nodejs10_permission_error");
40
+ return new error_1.FirebaseError(`Cloud Functions deployment requires the Cloud Build API to be enabled. The current credentials do not have permission to enable APIs for project ${clc.bold(projectId)}.
41
+
42
+ Please ask a project owner to visit the following URL to enable Cloud Build:
43
+
44
+ https://console.cloud.google.com/apis/library/cloudbuild.googleapis.com?project=${projectId}
45
+
46
+ For additional information about this requirement, see Firebase FAQs:
47
+ ${FAQ_URL}
48
+ `);
49
+ }
50
+ function isPermissionError(e) {
51
+ var _a, _b, _c;
52
+ return ((_c = (_b = (_a = e.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.status) === "PERMISSION_DENIED";
53
+ }
54
+ async function cloudBuildEnabled(projectId) {
55
+ try {
56
+ await (0, ensureApiEnabled_1.ensure)(projectId, CLOUD_BUILD_API, "functions");
57
+ }
58
+ catch (e) {
59
+ if ((0, error_1.isBillingError)(e)) {
60
+ throw nodeBillingError(projectId);
61
+ }
62
+ else if (isPermissionError(e)) {
63
+ throw nodePermissionError(projectId);
64
+ }
65
+ throw e;
66
+ }
67
+ }
68
+ exports.cloudBuildEnabled = cloudBuildEnabled;
69
+ async function maybeEnableAR(projectId) {
70
+ if (!previews_1.previews.artifactregistry) {
71
+ return ensureApiEnabled.check(projectId, "artifactregistry.googleapis.com", "functions", true);
72
+ }
73
+ await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions");
74
+ return true;
75
+ }
76
+ exports.maybeEnableAR = maybeEnableAR;
77
+ async function secretsToServiceAccounts(b) {
78
+ const secretsToSa = {};
79
+ for (const e of backend.allEndpoints(b)) {
80
+ const sa = e.serviceAccountEmail || (await module.exports.defaultServiceAccount(e));
81
+ for (const s of e.secretEnvironmentVariables || []) {
82
+ const serviceAccounts = secretsToSa[s.secret] || new Set();
83
+ serviceAccounts.add(sa);
84
+ secretsToSa[s.secret] = serviceAccounts;
85
+ }
86
+ }
87
+ return secretsToSa;
88
+ }
89
+ async function secretAccess(projectId, wantBackend, haveBackend) {
90
+ var _a, _b;
91
+ const ensureAccess = async (secret, serviceAccounts) => {
92
+ (0, utils_1.logLabeledBullet)("functions", `ensuring ${clc.bold(serviceAccounts.join(", "))} access to secret ${clc.bold(secret)}.`);
93
+ await (0, secretManager_1.ensureServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
94
+ (0, utils_1.logLabeledSuccess)("functions", `ensured ${clc.bold(serviceAccounts.join(", "))} access to ${clc.bold(secret)}.`);
95
+ };
96
+ const wantSecrets = await secretsToServiceAccounts(wantBackend);
97
+ const haveSecrets = await secretsToServiceAccounts(haveBackend);
98
+ for (const [secret, serviceAccounts] of Object.entries(haveSecrets)) {
99
+ for (const serviceAccount of serviceAccounts) {
100
+ (_a = wantSecrets[secret]) === null || _a === void 0 ? void 0 : _a.delete(serviceAccount);
101
+ }
102
+ if (((_b = wantSecrets[secret]) === null || _b === void 0 ? void 0 : _b.size) == 0) {
103
+ delete wantSecrets[secret];
104
+ }
105
+ }
106
+ const ensure = [];
107
+ for (const [secret, serviceAccounts] of Object.entries(wantSecrets)) {
108
+ ensure.push(ensureAccess(secret, Array.from(serviceAccounts)));
109
+ }
110
+ await Promise.all(ensure);
111
+ }
112
+ exports.secretAccess = secretAccess;
@@ -1,50 +1 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureCloudBuildEnabled = void 0;
4
- const cli_color_1 = require("cli-color");
5
- const track = require("../../track");
6
- const ensureApiEnabled_1 = require("../../ensureApiEnabled");
7
- const error_1 = require("../../error");
8
- const FAQ_URL = "https://firebase.google.com/support/faq#functions-runtime";
9
- const CLOUD_BUILD_API = "cloudbuild.googleapis.com";
10
- function nodeBillingError(projectId) {
11
- track("functions_runtime_notices", "nodejs10_billing_error");
12
- return new error_1.FirebaseError(`Cloud Functions deployment requires the pay-as-you-go (Blaze) billing plan. To upgrade your project, visit the following URL:
13
-
14
- https://console.firebase.google.com/project/${projectId}/usage/details
15
-
16
- For additional information about this requirement, see Firebase FAQs:
17
-
18
- ${FAQ_URL}`, { exit: 1 });
19
- }
20
- function nodePermissionError(projectId) {
21
- track("functions_runtime_notices", "nodejs10_permission_error");
22
- return new error_1.FirebaseError(`Cloud Functions deployment requires the Cloud Build API to be enabled. The current credentials do not have permission to enable APIs for project ${(0, cli_color_1.bold)(projectId)}.
23
-
24
- Please ask a project owner to visit the following URL to enable Cloud Build:
25
-
26
- https://console.cloud.google.com/apis/library/cloudbuild.googleapis.com?project=${projectId}
27
-
28
- For additional information about this requirement, see Firebase FAQs:
29
- ${FAQ_URL}
30
- `);
31
- }
32
- function isPermissionError(e) {
33
- var _a, _b, _c;
34
- return ((_c = (_b = (_a = e.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.status) === "PERMISSION_DENIED";
35
- }
36
- async function ensureCloudBuildEnabled(projectId) {
37
- try {
38
- await (0, ensureApiEnabled_1.ensure)(projectId, CLOUD_BUILD_API, "functions");
39
- }
40
- catch (e) {
41
- if ((0, error_1.isBillingError)(e)) {
42
- throw nodeBillingError(projectId);
43
- }
44
- else if (isPermissionError(e)) {
45
- throw nodePermissionError(projectId);
46
- }
47
- throw e;
48
- }
49
- }
50
- exports.ensureCloudBuildEnabled = ensureCloudBuildEnabled;
@@ -2,20 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.inferDetailsFromExisting = exports.prepare = void 0;
4
4
  const clc = require("cli-color");
5
- const ensureCloudBuildEnabled_1 = require("./ensureCloudBuildEnabled");
6
- const functionsDeployHelper_1 = require("./functionsDeployHelper");
7
- const utils_1 = require("../../utils");
8
- const prepareFunctionsUpload_1 = require("./prepareFunctionsUpload");
9
- const prompts_1 = require("./prompts");
10
5
  const backend = require("./backend");
11
6
  const ensureApiEnabled = require("../../ensureApiEnabled");
12
7
  const functionsConfig = require("../../functionsConfig");
13
8
  const functionsEnv = require("../../functions/env");
14
- const previews_1 = require("../../previews");
15
- const projectUtils_1 = require("../../projectUtils");
16
- const track_1 = require("../../track");
17
9
  const runtimes = require("./runtimes");
18
10
  const validate = require("./validate");
11
+ const ensure = require("./ensure");
12
+ const functionsDeployHelper_1 = require("./functionsDeployHelper");
13
+ const utils_1 = require("../../utils");
14
+ const prepareFunctionsUpload_1 = require("./prepareFunctionsUpload");
15
+ const prompts_1 = require("./prompts");
16
+ const projectUtils_1 = require("../../projectUtils");
17
+ const track_1 = require("../../track");
19
18
  const logger_1 = require("../../logger");
20
19
  const triggerRegionHelper_1 = require("./triggerRegionHelper");
21
20
  const checkIam_1 = require("./checkIam");
@@ -24,14 +23,7 @@ function hasUserConfig(config) {
24
23
  return Object.keys(config).length > 1;
25
24
  }
26
25
  function hasDotenv(opts) {
27
- return previews_1.previews.dotenv && functionsEnv.hasUserEnvs(opts);
28
- }
29
- async function maybeEnableAR(projectId) {
30
- if (previews_1.previews.artifactregistry) {
31
- return ensureApiEnabled.check(projectId, "artifactregistry.googleapis.com", "functions", true);
32
- }
33
- await ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "functions");
34
- return true;
26
+ return functionsEnv.hasUserEnvs(opts);
35
27
  }
36
28
  async function prepare(context, options, payload) {
37
29
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -54,8 +46,8 @@ async function prepare(context, options, payload) {
54
46
  const checkAPIsEnabled = await Promise.all([
55
47
  ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
56
48
  ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
57
- (0, ensureCloudBuildEnabled_1.ensureCloudBuildEnabled)(projectId),
58
- maybeEnableAR(projectId),
49
+ ensure.cloudBuildEnabled(projectId),
50
+ ensure.maybeEnableAR(projectId),
59
51
  ]);
60
52
  context.runtimeConfigEnabled = checkAPIsEnabled[1];
61
53
  context.artifactRegistryEnabled = checkAPIsEnabled[3];
@@ -113,7 +105,7 @@ async function prepare(context, options, payload) {
113
105
  await Promise.all(Object.values(wantBackend.requiredAPIs).map((api) => {
114
106
  return ensureApiEnabled.ensure(projectId, api, "functions", false);
115
107
  }));
116
- validate.functionIdsAreValid(backend.allEndpoints(wantBackend));
108
+ validate.endpointsAreValid(wantBackend);
117
109
  context.filters = (0, functionsDeployHelper_1.getFilterGroups)(options);
118
110
  const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => {
119
111
  return (0, functionsDeployHelper_1.functionMatchesAnyGroup)(endpoint, context.filters);
@@ -125,6 +117,8 @@ async function prepare(context, options, payload) {
125
117
  await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
126
118
  await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
127
119
  await backend.checkAvailability(context, wantBackend);
120
+ await validate.secretsAreValid(projectId, matchingBackend);
121
+ await ensure.secretAccess(projectId, matchingBackend, haveBackend);
128
122
  }
129
123
  exports.prepare = prepare;
130
124
  function inferDetailsFromExisting(want, have, usedDotenv) {
@@ -242,7 +242,10 @@ class Fabricator {
242
242
  .catch(rethrowAs(endpoint, "set invoker"));
243
243
  }
244
244
  }
245
- await this.setConcurrency(endpoint, serviceName, endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY);
245
+ const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
246
+ if (mem >= backend.MIN_MEMORY_FOR_CONCURRENCY && endpoint.concurrency != 1) {
247
+ await this.setConcurrency(endpoint, serviceName, endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY);
248
+ }
246
249
  }
247
250
  async updateV1Function(endpoint, scraper) {
248
251
  var _a;
@@ -56,6 +56,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
56
56
  labels: "object",
57
57
  ingressSettings: "string",
58
58
  environmentVariables: "object",
59
+ secretEnvironmentVariables: "array",
59
60
  httpsTrigger: "object",
60
61
  eventTrigger: "object",
61
62
  scheduleTrigger: "object",
@@ -107,6 +107,18 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
107
107
  }
108
108
  endpoint.vpcConnector = maybeId;
109
109
  }
110
+ if (annotation.secrets) {
111
+ const secretEnvs = [];
112
+ for (const secret of annotation.secrets) {
113
+ const secretEnv = {
114
+ secret,
115
+ projectId,
116
+ key: secret,
117
+ };
118
+ secretEnvs.push(secretEnv);
119
+ }
120
+ endpoint.secretEnvironmentVariables = secretEnvs;
121
+ }
110
122
  proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "vpcConnectorEgressSettings", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
111
123
  want.endpoints[region] = want.endpoints[region] || {};
112
124
  want.endpoints[region][endpoint.id] = endpoint;
@@ -1,10 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.functionIdsAreValid = exports.functionsDirectoryExists = void 0;
3
+ exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreValid = void 0;
4
4
  const path = require("path");
5
5
  const clc = require("cli-color");
6
6
  const error_1 = require("../../error");
7
+ const secretManager_1 = require("../../gcp/secretManager");
8
+ const logger_1 = require("../../logger");
7
9
  const fsutils = require("../../fsutils");
10
+ const backend = require("./backend");
11
+ const utils = require("../../utils");
12
+ const secrets = require("../../functions/secrets");
13
+ function endpointsAreValid(wantBackend) {
14
+ functionIdsAreValid(backend.allEndpoints(wantBackend));
15
+ const gcfV1WithConcurrency = backend
16
+ .allEndpoints(wantBackend)
17
+ .filter((endpoint) => (endpoint.concurrency || 1) != 1 && endpoint.platform == "gcfv1")
18
+ .map((endpoint) => endpoint.id);
19
+ if (gcfV1WithConcurrency.length) {
20
+ const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join(",")} because they are GCF gen 1`;
21
+ throw new error_1.FirebaseError(msg);
22
+ }
23
+ const tooSmallForConcurrency = backend
24
+ .allEndpoints(wantBackend)
25
+ .filter((endpoint) => {
26
+ if ((endpoint.concurrency || 1) == 1) {
27
+ return false;
28
+ }
29
+ const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
30
+ return mem < backend.MIN_MEMORY_FOR_CONCURRENCY;
31
+ })
32
+ .map((endpoint) => endpoint.id);
33
+ if (tooSmallForConcurrency.length) {
34
+ const msg = `Cannot set concurency on the functions ${tooSmallForConcurrency.join(",")} because they have fewer than 2GB memory`;
35
+ throw new error_1.FirebaseError(msg);
36
+ }
37
+ }
38
+ exports.endpointsAreValid = endpointsAreValid;
8
39
  function functionsDirectoryExists(sourceDir, projectDir) {
9
40
  if (!fsutils.dirExistsSync(sourceDir)) {
10
41
  const sourceDirName = path.relative(projectDir, sourceDir);
@@ -35,3 +66,54 @@ function functionIdsAreValid(functions) {
35
66
  }
36
67
  }
37
68
  exports.functionIdsAreValid = functionIdsAreValid;
69
+ async function secretsAreValid(projectId, wantBackend) {
70
+ const endpoints = backend
71
+ .allEndpoints(wantBackend)
72
+ .filter((e) => e.secretEnvironmentVariables && e.secretEnvironmentVariables.length > 0);
73
+ validatePlatformTargets(endpoints);
74
+ await validateSecretVersions(projectId, endpoints);
75
+ }
76
+ exports.secretsAreValid = secretsAreValid;
77
+ function validatePlatformTargets(endpoints) {
78
+ const supportedPlatforms = ["gcfv1"];
79
+ const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform));
80
+ if (unsupported.length > 0) {
81
+ const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`);
82
+ throw new error_1.FirebaseError(`Tried to set secret environment variables on ${errs.join(", ")}. ` +
83
+ `Only ${supportedPlatforms.join(", ")} support secret environments.`);
84
+ }
85
+ }
86
+ async function validateSecretVersions(projectId, endpoints) {
87
+ const toResolve = new Set();
88
+ for (const s of secrets.of(endpoints)) {
89
+ toResolve.add(s.secret);
90
+ }
91
+ const results = await utils.allSettled(Array.from(toResolve).map(async (secret) => {
92
+ const sv = await (0, secretManager_1.getSecretVersion)(projectId, secret, "latest");
93
+ logger_1.logger.debug(`Resolved secret version of ${clc.bold(secret)} to ${clc.bold(sv.versionId)}.`);
94
+ return sv;
95
+ }));
96
+ const secretVersions = {};
97
+ const errs = [];
98
+ for (const result of results) {
99
+ if (result.status === "fulfilled") {
100
+ const sv = result.value;
101
+ if (sv.state != "ENABLED") {
102
+ errs.push(new error_1.FirebaseError(`Expected secret ${sv.secret.name}@${sv.versionId} to be in state ENABLED not ${sv.state}.`));
103
+ }
104
+ secretVersions[sv.secret.name] = sv;
105
+ }
106
+ else {
107
+ errs.push(new error_1.FirebaseError(result.reason.message));
108
+ }
109
+ }
110
+ if (errs.length) {
111
+ throw new error_1.FirebaseError("Failed to validate secret versions", { children: errs });
112
+ }
113
+ for (const s of secrets.of(endpoints)) {
114
+ s.version = secretVersions[s.secret].versionId;
115
+ if (!s.version) {
116
+ throw new error_1.FirebaseError("Secret version is unexpectedly undefined. This should never happen.");
117
+ }
118
+ }
119
+ }