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.

backend.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.merge = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.memoryToGen2Cpu = exports.memoryToGen1Cpu = exports.memoryOptionDisplayName = exports.isValidMemoryOption = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
  4. const gcf = require("../../gcp/cloudfunctions");
  5. const gcfV2 = require("../../gcp/cloudfunctionsv2");
  6. const run = require("../../gcp/run");
  7. const utils = require("../../utils");
  8. const error_1 = require("../../error");
  9. const functional_1 = require("../../functional");
  10. function endpointTriggerType(endpoint) {
  11. if (isScheduleTriggered(endpoint)) {
  12. return "scheduled";
  13. }
  14. else if (isHttpsTriggered(endpoint)) {
  15. return "https";
  16. }
  17. else if (isCallableTriggered(endpoint)) {
  18. return "callable";
  19. }
  20. else if (isEventTriggered(endpoint)) {
  21. return endpoint.eventTrigger.eventType;
  22. }
  23. else if (isTaskQueueTriggered(endpoint)) {
  24. return "taskQueue";
  25. }
  26. else if (isBlockingTriggered(endpoint)) {
  27. return endpoint.blockingTrigger.eventType;
  28. }
  29. else {
  30. throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
  31. }
  32. }
  33. exports.endpointTriggerType = endpointTriggerType;
  34. exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];
  35. exports.AllIngressSettings = [
  36. "ALLOW_ALL",
  37. "ALLOW_INTERNAL_ONLY",
  38. "ALLOW_INTERNAL_AND_GCLB",
  39. ];
  40. const allMemoryOptions = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768];
  41. function isValidMemoryOption(mem) {
  42. return allMemoryOptions.includes(mem);
  43. }
  44. exports.isValidMemoryOption = isValidMemoryOption;
  45. function memoryOptionDisplayName(option) {
  46. return {
  47. 128: "128MB",
  48. 256: "256MB",
  49. 512: "512MB",
  50. 1024: "1GB",
  51. 2048: "2GB",
  52. 4096: "4GB",
  53. 8192: "8GB",
  54. 16384: "16GB",
  55. 32768: "32GB",
  56. }[option];
  57. }
  58. exports.memoryOptionDisplayName = memoryOptionDisplayName;
  59. function memoryToGen1Cpu(memory) {
  60. return {
  61. 128: 0.0833,
  62. 256: 0.1666,
  63. 512: 0.3333,
  64. 1024: 0.5833,
  65. 2048: 1,
  66. 4096: 2,
  67. 8192: 2,
  68. 16384: 4,
  69. 32768: 8,
  70. }[memory];
  71. }
  72. exports.memoryToGen1Cpu = memoryToGen1Cpu;
  73. function memoryToGen2Cpu(memory) {
  74. return {
  75. 128: 1,
  76. 256: 1,
  77. 512: 1,
  78. 1024: 1,
  79. 2048: 1,
  80. 4096: 2,
  81. 8192: 2,
  82. 16384: 4,
  83. 32768: 8,
  84. }[memory];
  85. }
  86. exports.memoryToGen2Cpu = memoryToGen2Cpu;
  87. exports.DEFAULT_CONCURRENCY = 80;
  88. exports.DEFAULT_MEMORY = 256;
  89. exports.MIN_CPU_FOR_CONCURRENCY = 1;
  90. exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
  91. function secretVersionName(s) {
  92. var _a;
  93. return `projects/${s.projectId}/secrets/${s.secret}/versions/${(_a = s.version) !== null && _a !== void 0 ? _a : "latest"}`;
  94. }
  95. exports.secretVersionName = secretVersionName;
  96. exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2"];
  97. function isHttpsTriggered(triggered) {
  98. return {}.hasOwnProperty.call(triggered, "httpsTrigger");
  99. }
  100. exports.isHttpsTriggered = isHttpsTriggered;
  101. function isCallableTriggered(triggered) {
  102. return {}.hasOwnProperty.call(triggered, "callableTrigger");
  103. }
  104. exports.isCallableTriggered = isCallableTriggered;
  105. function isEventTriggered(triggered) {
  106. return {}.hasOwnProperty.call(triggered, "eventTrigger");
  107. }
  108. exports.isEventTriggered = isEventTriggered;
  109. function isScheduleTriggered(triggered) {
  110. return {}.hasOwnProperty.call(triggered, "scheduleTrigger");
  111. }
  112. exports.isScheduleTriggered = isScheduleTriggered;
  113. function isTaskQueueTriggered(triggered) {
  114. return {}.hasOwnProperty.call(triggered, "taskQueueTrigger");
  115. }
  116. exports.isTaskQueueTriggered = isTaskQueueTriggered;
  117. function isBlockingTriggered(triggered) {
  118. return {}.hasOwnProperty.call(triggered, "blockingTrigger");
  119. }
  120. exports.isBlockingTriggered = isBlockingTriggered;
  121. function empty() {
  122. return {
  123. requiredAPIs: [],
  124. endpoints: {},
  125. environmentVariables: {},
  126. };
  127. }
  128. exports.empty = empty;
  129. function of(...endpoints) {
  130. const bkend = Object.assign({}, empty());
  131. for (const endpoint of endpoints) {
  132. bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {};
  133. if (bkend.endpoints[endpoint.region][endpoint.id]) {
  134. throw new Error("Trying to create a backend with the same endpiont twice");
  135. }
  136. bkend.endpoints[endpoint.region][endpoint.id] = endpoint;
  137. }
  138. return bkend;
  139. }
  140. exports.of = of;
  141. function merge(...backends) {
  142. const merged = of(...(0, functional_1.flattenArray)(backends.map((b) => allEndpoints(b))));
  143. const apiToReasons = {};
  144. for (const b of backends) {
  145. for (const { api, reason } of b.requiredAPIs) {
  146. const reasons = apiToReasons[api] || new Set();
  147. if (reason) {
  148. reasons.add(reason);
  149. }
  150. apiToReasons[api] = reasons;
  151. }
  152. merged.environmentVariables = Object.assign(Object.assign({}, merged.environmentVariables), b.environmentVariables);
  153. }
  154. for (const [api, reasons] of Object.entries(apiToReasons)) {
  155. merged.requiredAPIs.push({ api, reason: Array.from(reasons).join(" ") });
  156. }
  157. return merged;
  158. }
  159. exports.merge = merge;
  160. function isEmptyBackend(backend) {
  161. return (Object.keys(backend.requiredAPIs).length === 0 && Object.keys(backend.endpoints).length === 0);
  162. }
  163. exports.isEmptyBackend = isEmptyBackend;
  164. function functionName(cloudFunction) {
  165. return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`;
  166. }
  167. exports.functionName = functionName;
  168. function scheduleIdForFunction(cloudFunction) {
  169. return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`;
  170. }
  171. exports.scheduleIdForFunction = scheduleIdForFunction;
  172. async function existingBackend(context, forceRefresh) {
  173. if (!context.loadedExistingBackend || forceRefresh) {
  174. await loadExistingBackend(context);
  175. }
  176. return context.existingBackend;
  177. }
  178. exports.existingBackend = existingBackend;
  179. async function loadExistingBackend(ctx) {
  180. var _a;
  181. ctx.loadedExistingBackend = true;
  182. ctx.existingBackend = Object.assign({}, empty());
  183. ctx.unreachableRegions = {
  184. gcfV1: [],
  185. gcfV2: [],
  186. };
  187. const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
  188. for (const apiFunction of gcfV1Results.functions) {
  189. const endpoint = gcf.endpointFromFunction(apiFunction);
  190. ctx.existingBackend.endpoints[endpoint.region] =
  191. ctx.existingBackend.endpoints[endpoint.region] || {};
  192. ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
  193. }
  194. ctx.unreachableRegions.gcfV1 = gcfV1Results.unreachable;
  195. let gcfV2Results;
  196. try {
  197. gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
  198. const runResults = await Promise.all(gcfV2Results.functions.map((fn) => run.getService(fn.serviceConfig.service)));
  199. for (const [apiFunction, runService] of (0, functional_1.zip)(gcfV2Results.functions, runResults)) {
  200. const endpoint = gcfV2.endpointFromFunction(apiFunction);
  201. endpoint.concurrency = runService.spec.template.spec.containerConcurrency || 1;
  202. endpoint.cpu = +runService.spec.template.spec.containers[0].resources.limits.cpu;
  203. ctx.existingBackend.endpoints[endpoint.region] =
  204. ctx.existingBackend.endpoints[endpoint.region] || {};
  205. ctx.existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
  206. }
  207. ctx.unreachableRegions.gcfV2 = gcfV2Results.unreachable;
  208. }
  209. catch (err) {
  210. if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
  211. return;
  212. }
  213. throw err;
  214. }
  215. }
  216. async function checkAvailability(context, want) {
  217. var _a, _b, _c, _d;
  218. if (!context.loadedExistingBackend) {
  219. await loadExistingBackend(context);
  220. }
  221. const gcfV1Regions = new Set();
  222. const gcfV2Regions = new Set();
  223. for (const ep of allEndpoints(want)) {
  224. if (ep.platform === "gcfv1") {
  225. gcfV1Regions.add(ep.region);
  226. }
  227. else {
  228. gcfV2Regions.add(ep.region);
  229. }
  230. }
  231. const neededUnreachableV1 = (_a = context.unreachableRegions) === null || _a === void 0 ? void 0 : _a.gcfV1.filter((region) => gcfV1Regions.has(region));
  232. const neededUnreachableV2 = (_b = context.unreachableRegions) === null || _b === void 0 ? void 0 : _b.gcfV2.filter((region) => gcfV2Regions.has(region));
  233. if (neededUnreachableV1 === null || neededUnreachableV1 === void 0 ? void 0 : neededUnreachableV1.length) {
  234. throw new error_1.FirebaseError("The following Cloud Functions regions are currently unreachable:\n\t" +
  235. neededUnreachableV1.join("\n\t") +
  236. "\nThis deployment contains functions in those regions. Please try again in a few minutes, or exclude these regions from your deployment.");
  237. }
  238. if (neededUnreachableV2 === null || neededUnreachableV2 === void 0 ? void 0 : neededUnreachableV2.length) {
  239. throw new error_1.FirebaseError("The following Cloud Functions V2 regions are currently unreachable:\n\t" +
  240. neededUnreachableV2.join("\n\t") +
  241. "\nThis deployment contains functions in those regions. Please try again in a few minutes, or exclude these regions from your deployment.");
  242. }
  243. if ((_c = context.unreachableRegions) === null || _c === void 0 ? void 0 : _c.gcfV1.length) {
  244. utils.logLabeledWarning("functions", "The following Cloud Functions regions are currently unreachable:\n" +
  245. context.unreachableRegions.gcfV1.join("\n") +
  246. "\nCloud Functions in these regions won't be deleted.");
  247. }
  248. if ((_d = context.unreachableRegions) === null || _d === void 0 ? void 0 : _d.gcfV2.length) {
  249. utils.logLabeledWarning("functions", "The following Cloud Functions V2 regions are currently unreachable:\n" +
  250. context.unreachableRegions.gcfV2.join("\n") +
  251. "\nCloud Functions in these regions won't be deleted.");
  252. }
  253. }
  254. exports.checkAvailability = checkAvailability;
  255. function allEndpoints(backend) {
  256. return Object.values(backend.endpoints).reduce((accum, perRegion) => {
  257. return [...accum, ...Object.values(perRegion)];
  258. }, []);
  259. }
  260. exports.allEndpoints = allEndpoints;
  261. function someEndpoint(backend, predicate) {
  262. for (const endpoints of Object.values(backend.endpoints)) {
  263. if (Object.values(endpoints).some(predicate)) {
  264. return true;
  265. }
  266. }
  267. return false;
  268. }
  269. exports.someEndpoint = someEndpoint;
  270. function findEndpoint(backend, predicate) {
  271. for (const endpoints of Object.values(backend.endpoints)) {
  272. const endpoint = Object.values(endpoints).find(predicate);
  273. if (endpoint)
  274. return endpoint;
  275. }
  276. }
  277. exports.findEndpoint = findEndpoint;
  278. function matchingBackend(backend, predicate) {
  279. const filtered = Object.assign(Object.assign({}, backend), { endpoints: {} });
  280. for (const endpoint of allEndpoints(backend)) {
  281. if (!predicate(endpoint)) {
  282. continue;
  283. }
  284. filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {};
  285. filtered.endpoints[endpoint.region][endpoint.id] = endpoint;
  286. }
  287. return filtered;
  288. }
  289. exports.matchingBackend = matchingBackend;
  290. function regionalEndpoints(backend, region) {
  291. return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : [];
  292. }
  293. exports.regionalEndpoints = regionalEndpoints;
  294. const hasEndpoint = (backend) => (endpoint) => {
  295. return (!!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id]);
  296. };
  297. exports.hasEndpoint = hasEndpoint;
  298. const missingEndpoint = (backend) => (endpoint) => {
  299. return !(0, exports.hasEndpoint)(backend)(endpoint);
  300. };
  301. exports.missingEndpoint = missingEndpoint;
  302. function compareFunctions(left, right) {
  303. if (left.platform !== right.platform) {
  304. return right.platform < left.platform ? -1 : 1;
  305. }
  306. if (left.region < right.region) {
  307. return -1;
  308. }
  309. if (left.region > right.region) {
  310. return 1;
  311. }
  312. if (left.id < right.id) {
  313. return -1;
  314. }
  315. if (left.id > right.id) {
  316. return 1;
  317. }
  318. return 0;
  319. }
  320. exports.compareFunctions = compareFunctions;