No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

validate.js 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.cpuConfigIsValid = exports.endpointsAreValid = void 0;
  4. const path = require("path");
  5. const clc = require("colorette");
  6. const error_1 = require("../../error");
  7. const secretManager_1 = require("../../gcp/secretManager");
  8. const logger_1 = require("../../logger");
  9. const fsutils = require("../../fsutils");
  10. const backend = require("./backend");
  11. const utils = require("../../utils");
  12. const secrets = require("../../functions/secrets");
  13. const services_1 = require("./services");
  14. function matchingIds(endpoints, filter) {
  15. return endpoints
  16. .filter(filter)
  17. .map((endpoint) => endpoint.id)
  18. .join(",");
  19. }
  20. const mem = (endpoint) => endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
  21. const cpu = (endpoint) => {
  22. var _a;
  23. return endpoint.cpu === "gcf_gen1"
  24. ? backend.memoryToGen1Cpu(mem(endpoint))
  25. : (_a = endpoint.cpu) !== null && _a !== void 0 ? _a : backend.memoryToGen2Cpu(mem(endpoint));
  26. };
  27. function endpointsAreValid(wantBackend) {
  28. const endpoints = backend.allEndpoints(wantBackend);
  29. functionIdsAreValid(endpoints);
  30. for (const ep of endpoints) {
  31. (0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, wantBackend);
  32. }
  33. const gcfV1WithConcurrency = matchingIds(endpoints, (endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1");
  34. if (gcfV1WithConcurrency.length) {
  35. const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency} because they are GCF gen 1`;
  36. throw new error_1.FirebaseError(msg);
  37. }
  38. const tooSmallForConcurrency = matchingIds(endpoints, (endpoint) => {
  39. if ((endpoint.concurrency || 1) === 1) {
  40. return false;
  41. }
  42. return cpu(endpoint) < backend.MIN_CPU_FOR_CONCURRENCY;
  43. });
  44. if (tooSmallForConcurrency.length) {
  45. const msg = "The following functions are configured to allow concurrent " +
  46. "execution and less than one full CPU. This is not supported: " +
  47. tooSmallForConcurrency;
  48. throw new error_1.FirebaseError(msg);
  49. }
  50. cpuConfigIsValid(endpoints);
  51. }
  52. exports.endpointsAreValid = endpointsAreValid;
  53. function cpuConfigIsValid(endpoints) {
  54. const gcfV1WithCPU = matchingIds(endpoints, (endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined");
  55. if (gcfV1WithCPU.length) {
  56. const msg = `Cannot set CPU on the functions ${gcfV1WithCPU} because they are GCF gen 1`;
  57. throw new error_1.FirebaseError(msg);
  58. }
  59. const invalidCPU = matchingIds(endpoints, (endpoint) => {
  60. const c = cpu(endpoint);
  61. if (c < 0.08) {
  62. return true;
  63. }
  64. if (c < 1) {
  65. return false;
  66. }
  67. return ![1, 2, 4, 6, 8].includes(c);
  68. });
  69. if (invalidCPU.length) {
  70. const msg = `The following functions have invalid CPU settings ${invalidCPU}. Valid CPU options are (0.08, 1], 2, 4, 6, 8, or "gcf_gen1"`;
  71. throw new error_1.FirebaseError(msg);
  72. }
  73. const smallCPURegions = ["australia-southeast2", "asia-northeast3", "asia-south2"];
  74. const tooBigCPUForRegion = matchingIds(endpoints, (endpoint) => smallCPURegions.includes(endpoint.region) && cpu(endpoint) > 4);
  75. if (tooBigCPUForRegion) {
  76. const msg = `The functions ${tooBigCPUForRegion} have > 4 CPU in a region that supports a maximum 4 CPU`;
  77. throw new error_1.FirebaseError(msg);
  78. }
  79. const tooSmallCPUSmall = matchingIds(endpoints, (endpoint) => mem(endpoint) > 512 && cpu(endpoint) < 0.5);
  80. if (tooSmallCPUSmall) {
  81. const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 0.5 CPU is needed to set a memory limit greater than 512MiB`;
  82. throw new error_1.FirebaseError(msg);
  83. }
  84. const tooSmallCPUBig = matchingIds(endpoints, (endpoint) => mem(endpoint) > 1024 && cpu(endpoint) < 1);
  85. if (tooSmallCPUBig) {
  86. const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 1 CPU is needed to set a memory limit greater than 1GiB`;
  87. throw new error_1.FirebaseError(msg);
  88. }
  89. const tooSmallMemory4CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 4 && mem(endpoint) < 2 << 10);
  90. if (tooSmallMemory4CPU) {
  91. const msg = `The functions ${tooSmallMemory4CPU} have too little memory for their CPU. Functions with 4 CPU require at least 2GiB`;
  92. throw new error_1.FirebaseError(msg);
  93. }
  94. const tooSmallMemory6CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 6 && mem(endpoint) < 3 << 10);
  95. if (tooSmallMemory6CPU) {
  96. const msg = `The functions ${tooSmallMemory6CPU} have too little memory for their CPU. Functions with 6 CPU require at least 3GiB`;
  97. throw new error_1.FirebaseError(msg);
  98. }
  99. const tooSmallMemory8CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 8 && mem(endpoint) < 4 << 10);
  100. if (tooSmallMemory8CPU) {
  101. const msg = `The functions ${tooSmallMemory8CPU} have too little memory for their CPU. Functions with 8 CPU require at least 4GiB`;
  102. throw new error_1.FirebaseError(msg);
  103. }
  104. }
  105. exports.cpuConfigIsValid = cpuConfigIsValid;
  106. function endpointsAreUnique(backends) {
  107. const endpointToCodebases = {};
  108. for (const [codebase, b] of Object.entries(backends)) {
  109. for (const endpoint of backend.allEndpoints(b)) {
  110. const key = backend.functionName(endpoint);
  111. const cs = endpointToCodebases[key] || new Set();
  112. cs.add(codebase);
  113. endpointToCodebases[key] = cs;
  114. }
  115. }
  116. const conflicts = {};
  117. for (const [fn, codebases] of Object.entries(endpointToCodebases)) {
  118. if (codebases.size > 1) {
  119. conflicts[fn] = Array.from(codebases);
  120. }
  121. }
  122. if (Object.keys(conflicts).length === 0) {
  123. return;
  124. }
  125. const msgs = Object.entries(conflicts).map(([fn, codebases]) => `${fn}: ${codebases.join(",")}`);
  126. throw new error_1.FirebaseError("More than one codebase claims following functions:\n\t" + `${msgs.join("\n\t")}`);
  127. }
  128. exports.endpointsAreUnique = endpointsAreUnique;
  129. function functionsDirectoryExists(sourceDir, projectDir) {
  130. if (!fsutils.dirExistsSync(sourceDir)) {
  131. const sourceDirName = path.relative(projectDir, sourceDir);
  132. const msg = `could not deploy functions because the ${clc.bold('"' + sourceDirName + '"')} ` +
  133. `directory was not found. Please create it or specify a different source directory in firebase.json`;
  134. throw new error_1.FirebaseError(msg);
  135. }
  136. }
  137. exports.functionsDirectoryExists = functionsDirectoryExists;
  138. function functionIdsAreValid(functions) {
  139. const v1FunctionName = /^[a-zA-Z][a-zA-Z0-9_-]{0,62}$/;
  140. const invalidV1Ids = functions.filter((fn) => {
  141. return fn.platform === "gcfv1" && !v1FunctionName.test(fn.id);
  142. });
  143. if (invalidV1Ids.length !== 0) {
  144. const msg = `${invalidV1Ids.map((f) => f.id).join(", ")} function name(s) can only contain letters, ` +
  145. `numbers, hyphens, and not exceed 62 characters in length`;
  146. throw new error_1.FirebaseError(msg);
  147. }
  148. const v2FunctionName = /^[a-z][a-z0-9-]{0,62}$/;
  149. const invalidV2Ids = functions.filter((fn) => {
  150. return fn.platform === "gcfv2" && !v2FunctionName.test(fn.id);
  151. });
  152. if (invalidV2Ids.length !== 0) {
  153. const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contain lower ` +
  154. `case letters, numbers, hyphens, and not exceed 62 characters in length`;
  155. throw new error_1.FirebaseError(msg);
  156. }
  157. }
  158. exports.functionIdsAreValid = functionIdsAreValid;
  159. async function secretsAreValid(projectId, wantBackend) {
  160. const endpoints = backend
  161. .allEndpoints(wantBackend)
  162. .filter((e) => e.secretEnvironmentVariables && e.secretEnvironmentVariables.length > 0);
  163. validatePlatformTargets(endpoints);
  164. await validateSecretVersions(projectId, endpoints);
  165. }
  166. exports.secretsAreValid = secretsAreValid;
  167. const secretsSupportedPlatforms = ["gcfv1", "gcfv2"];
  168. function validatePlatformTargets(endpoints) {
  169. const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
  170. if (unsupported.length > 0) {
  171. const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`);
  172. throw new error_1.FirebaseError(`Tried to set secret environment variables on ${errs.join(", ")}. ` +
  173. `Only ${secretsSupportedPlatforms.join(", ")} support secret environments.`);
  174. }
  175. }
  176. async function validateSecretVersions(projectId, endpoints) {
  177. const toResolve = new Set();
  178. for (const s of secrets.of(endpoints)) {
  179. toResolve.add(s.secret);
  180. }
  181. const results = await utils.allSettled(Array.from(toResolve).map(async (secret) => {
  182. const sv = await (0, secretManager_1.getSecretVersion)(projectId, secret, "latest");
  183. logger_1.logger.debug(`Resolved secret version of ${clc.bold(secret)} to ${clc.bold(sv.versionId)}.`);
  184. return sv;
  185. }));
  186. const secretVersions = {};
  187. const errs = [];
  188. for (const result of results) {
  189. if (result.status === "fulfilled") {
  190. const sv = result.value;
  191. if (sv.state !== "ENABLED") {
  192. errs.push(new error_1.FirebaseError(`Expected secret ${sv.secret.name}@${sv.versionId} to be in state ENABLED not ${sv.state}.`));
  193. }
  194. secretVersions[sv.secret.name] = sv;
  195. }
  196. else {
  197. errs.push(new error_1.FirebaseError(result.reason.message));
  198. }
  199. }
  200. if (errs.length) {
  201. throw new error_1.FirebaseError("Failed to validate secret versions", { children: errs });
  202. }
  203. for (const s of secrets.of(endpoints)) {
  204. s.version = secretVersions[s.secret].versionId;
  205. if (!s.version) {
  206. throw new error_1.FirebaseError("Secret version is unexpectedly undefined. This should never happen.");
  207. }
  208. }
  209. }