Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

cloudfunctions.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
  4. const clc = require("colorette");
  5. const error_1 = require("../error");
  6. const logger_1 = require("../logger");
  7. const backend = require("../deploy/functions/backend");
  8. const utils = require("../utils");
  9. const proto = require("./proto");
  10. const runtimes = require("../deploy/functions/runtimes");
  11. const projectConfig = require("../functions/projectConfig");
  12. const apiv2_1 = require("../apiv2");
  13. const api_1 = require("../api");
  14. const constants_1 = require("../functions/constants");
  15. exports.API_VERSION = "v1";
  16. const client = new apiv2_1.Client({ urlPrefix: api_1.functionsOrigin, apiVersion: exports.API_VERSION });
  17. function functionsOpLogReject(funcName, type, err) {
  18. var _a, _b;
  19. if (((_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) === 429) {
  20. utils.logWarning(`${clc.bold(clc.yellow("functions:"))} got "Quota Exceeded" error while trying to ${type} ${funcName}. Waiting to retry...`);
  21. }
  22. else {
  23. utils.logWarning(clc.bold(clc.yellow("functions:")) + " failed to " + type + " function " + funcName);
  24. }
  25. throw new error_1.FirebaseError(`Failed to ${type} function ${funcName}`, {
  26. original: err,
  27. context: { function: funcName },
  28. });
  29. }
  30. async function generateUploadUrl(projectId, location) {
  31. const parent = "projects/" + projectId + "/locations/" + location;
  32. const endpoint = `/${parent}/functions:generateUploadUrl`;
  33. try {
  34. const res = await client.post(endpoint, {}, { retryCodes: [503] });
  35. return res.body.uploadUrl;
  36. }
  37. catch (err) {
  38. logger_1.logger.info("\n\nThere was an issue deploying your functions. Verify that your project has a Google App Engine instance setup at https://console.cloud.google.com/appengine and try again. If this issue persists, please contact support.");
  39. throw err;
  40. }
  41. }
  42. exports.generateUploadUrl = generateUploadUrl;
  43. async function createFunction(cloudFunction) {
  44. const apiPath = cloudFunction.name.substring(0, cloudFunction.name.lastIndexOf("/"));
  45. const endpoint = `/${apiPath}`;
  46. try {
  47. const res = await client.post(endpoint, cloudFunction);
  48. return {
  49. name: res.body.name,
  50. type: "create",
  51. done: false,
  52. };
  53. }
  54. catch (err) {
  55. throw functionsOpLogReject(cloudFunction.name, "create", err);
  56. }
  57. }
  58. exports.createFunction = createFunction;
  59. async function setIamPolicy(options) {
  60. const endpoint = `/${options.name}:setIamPolicy`;
  61. try {
  62. await client.post(endpoint, {
  63. policy: options.policy,
  64. updateMask: Object.keys(options.policy).join(","),
  65. });
  66. }
  67. catch (err) {
  68. throw new error_1.FirebaseError(`Failed to set the IAM Policy on the function ${options.name}`, {
  69. original: err,
  70. });
  71. }
  72. }
  73. exports.setIamPolicy = setIamPolicy;
  74. async function getIamPolicy(fnName) {
  75. const endpoint = `/${fnName}:getIamPolicy`;
  76. try {
  77. const res = await client.get(endpoint);
  78. return res.body;
  79. }
  80. catch (err) {
  81. throw new error_1.FirebaseError(`Failed to get the IAM Policy on the function ${fnName}`, {
  82. original: err,
  83. });
  84. }
  85. }
  86. exports.getIamPolicy = getIamPolicy;
  87. async function setInvokerCreate(projectId, fnName, invoker) {
  88. if (invoker.length === 0) {
  89. throw new error_1.FirebaseError("Invoker cannot be an empty array");
  90. }
  91. const invokerMembers = proto.getInvokerMembers(invoker, projectId);
  92. const invokerRole = "roles/cloudfunctions.invoker";
  93. const bindings = [{ role: invokerRole, members: invokerMembers }];
  94. const policy = {
  95. bindings: bindings,
  96. etag: "",
  97. version: 3,
  98. };
  99. await setIamPolicy({ name: fnName, policy: policy });
  100. }
  101. exports.setInvokerCreate = setInvokerCreate;
  102. async function setInvokerUpdate(projectId, fnName, invoker) {
  103. var _a;
  104. if (invoker.length === 0) {
  105. throw new error_1.FirebaseError("Invoker cannot be an empty array");
  106. }
  107. const invokerMembers = proto.getInvokerMembers(invoker, projectId);
  108. const invokerRole = "roles/cloudfunctions.invoker";
  109. const currentPolicy = await getIamPolicy(fnName);
  110. const currentInvokerBinding = (_a = currentPolicy.bindings) === null || _a === void 0 ? void 0 : _a.find((binding) => binding.role === invokerRole);
  111. if (currentInvokerBinding &&
  112. JSON.stringify(currentInvokerBinding.members.sort()) === JSON.stringify(invokerMembers.sort())) {
  113. return;
  114. }
  115. const bindings = (currentPolicy.bindings || []).filter((binding) => binding.role !== invokerRole);
  116. bindings.push({
  117. role: invokerRole,
  118. members: invokerMembers,
  119. });
  120. const policy = {
  121. bindings: bindings,
  122. etag: currentPolicy.etag || "",
  123. version: 3,
  124. };
  125. await setIamPolicy({ name: fnName, policy: policy });
  126. }
  127. exports.setInvokerUpdate = setInvokerUpdate;
  128. async function updateFunction(cloudFunction) {
  129. const endpoint = `/${cloudFunction.name}`;
  130. const fieldMasks = proto.fieldMasks(cloudFunction, "labels", "environmentVariables", "secretEnvironmentVariables");
  131. try {
  132. const res = await client.patch(endpoint, cloudFunction, {
  133. queryParams: {
  134. updateMask: fieldMasks.join(","),
  135. },
  136. });
  137. return {
  138. done: false,
  139. name: res.body.name,
  140. type: "update",
  141. };
  142. }
  143. catch (err) {
  144. throw functionsOpLogReject(cloudFunction.name, "update", err);
  145. }
  146. }
  147. exports.updateFunction = updateFunction;
  148. async function deleteFunction(name) {
  149. const endpoint = `/${name}`;
  150. try {
  151. const res = await client.delete(endpoint);
  152. return {
  153. done: false,
  154. name: res.body.name,
  155. type: "delete",
  156. };
  157. }
  158. catch (err) {
  159. throw functionsOpLogReject(name, "delete", err);
  160. }
  161. }
  162. exports.deleteFunction = deleteFunction;
  163. async function list(projectId, region) {
  164. const endpoint = "/projects/" + projectId + "/locations/" + region + "/functions";
  165. try {
  166. const res = await client.get(endpoint);
  167. if (res.body.unreachable && res.body.unreachable.length > 0) {
  168. logger_1.logger.debug(`[functions] unable to reach the following regions: ${res.body.unreachable.join(", ")}`);
  169. }
  170. return {
  171. functions: res.body.functions || [],
  172. unreachable: res.body.unreachable || [],
  173. };
  174. }
  175. catch (err) {
  176. logger_1.logger.debug(`[functions] failed to list functions for ${projectId}`);
  177. logger_1.logger.debug(`[functions] ${err === null || err === void 0 ? void 0 : err.message}`);
  178. throw new error_1.FirebaseError(`Failed to list functions for ${projectId}`, {
  179. original: err,
  180. status: err instanceof error_1.FirebaseError ? err.status : undefined,
  181. });
  182. }
  183. }
  184. async function listFunctions(projectId, region) {
  185. const res = await list(projectId, region);
  186. return res.functions;
  187. }
  188. exports.listFunctions = listFunctions;
  189. async function listAllFunctions(projectId) {
  190. return list(projectId, "-");
  191. }
  192. exports.listAllFunctions = listAllFunctions;
  193. function endpointFromFunction(gcfFunction) {
  194. var _a, _b, _c, _d, _e, _f, _g, _h;
  195. const [, project, , region, , id] = gcfFunction.name.split("/");
  196. let trigger;
  197. let uri;
  198. let securityLevel;
  199. if ((_a = gcfFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) {
  200. trigger = {
  201. scheduleTrigger: {},
  202. };
  203. }
  204. else if ((_b = gcfFunction.labels) === null || _b === void 0 ? void 0 : _b["deployment-taskqueue"]) {
  205. trigger = {
  206. taskQueueTrigger: {},
  207. };
  208. }
  209. else if (((_c = gcfFunction.labels) === null || _c === void 0 ? void 0 : _c["deployment-callable"]) ||
  210. ((_d = gcfFunction.labels) === null || _d === void 0 ? void 0 : _d["deployment-callabled"])) {
  211. trigger = {
  212. callableTrigger: {},
  213. };
  214. }
  215. else if ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[constants_1.BLOCKING_LABEL]) {
  216. trigger = {
  217. blockingTrigger: {
  218. eventType: constants_1.BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[constants_1.BLOCKING_LABEL]],
  219. },
  220. };
  221. }
  222. else if (gcfFunction.httpsTrigger) {
  223. trigger = { httpsTrigger: {} };
  224. }
  225. else {
  226. trigger = {
  227. eventTrigger: {
  228. eventType: gcfFunction.eventTrigger.eventType,
  229. eventFilters: { resource: gcfFunction.eventTrigger.resource },
  230. retry: !!((_f = gcfFunction.eventTrigger.failurePolicy) === null || _f === void 0 ? void 0 : _f.retry),
  231. },
  232. };
  233. }
  234. if (gcfFunction.httpsTrigger) {
  235. uri = gcfFunction.httpsTrigger.url;
  236. securityLevel = gcfFunction.httpsTrigger.securityLevel;
  237. }
  238. if (!runtimes.isValidRuntime(gcfFunction.runtime)) {
  239. logger_1.logger.debug("GCFv1 function has a deprecated runtime:", JSON.stringify(gcfFunction, null, 2));
  240. }
  241. const endpoint = Object.assign(Object.assign({ platform: "gcfv1", id,
  242. project,
  243. region }, trigger), { entryPoint: gcfFunction.entryPoint, runtime: gcfFunction.runtime });
  244. if (uri) {
  245. endpoint.uri = uri;
  246. }
  247. if (securityLevel) {
  248. endpoint.securityLevel = securityLevel;
  249. }
  250. proto.copyIfPresent(endpoint, gcfFunction, "minInstances", "maxInstances", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl");
  251. proto.renameIfPresent(endpoint, gcfFunction, "serviceAccount", "serviceAccountEmail");
  252. proto.convertIfPresent(endpoint, gcfFunction, "availableMemoryMb", (raw) => raw);
  253. proto.convertIfPresent(endpoint, gcfFunction, "timeoutSeconds", "timeout", (dur) => dur === null ? null : proto.secondsFromDuration(dur));
  254. if (gcfFunction.vpcConnector) {
  255. endpoint.vpc = { connector: gcfFunction.vpcConnector };
  256. proto.convertIfPresent(endpoint.vpc, gcfFunction, "egressSettings", "vpcConnectorEgressSettings", (raw) => raw);
  257. }
  258. endpoint.codebase = ((_g = gcfFunction.labels) === null || _g === void 0 ? void 0 : _g[constants_1.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
  259. if ((_h = gcfFunction.labels) === null || _h === void 0 ? void 0 : _h[constants_1.HASH_LABEL]) {
  260. endpoint.hash = gcfFunction.labels[constants_1.HASH_LABEL];
  261. }
  262. return endpoint;
  263. }
  264. exports.endpointFromFunction = endpointFromFunction;
  265. function functionFromEndpoint(endpoint, sourceUploadUrl) {
  266. var _a, _b;
  267. if (endpoint.platform !== "gcfv1") {
  268. throw new error_1.FirebaseError("Trying to create a v1 CloudFunction with v2 API. This should never happen");
  269. }
  270. if (!runtimes.isValidRuntime(endpoint.runtime)) {
  271. throw new error_1.FirebaseError("Failed internal assertion. Trying to deploy a new function with a deprecated runtime." +
  272. " This should never happen");
  273. }
  274. const gcfFunction = {
  275. name: backend.functionName(endpoint),
  276. sourceUploadUrl: sourceUploadUrl,
  277. entryPoint: endpoint.entryPoint,
  278. runtime: endpoint.runtime,
  279. dockerRegistry: "ARTIFACT_REGISTRY",
  280. };
  281. if (typeof endpoint.labels !== "undefined") {
  282. gcfFunction.labels = Object.assign({}, endpoint.labels);
  283. }
  284. if (backend.isEventTriggered(endpoint)) {
  285. if (!((_a = endpoint.eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.resource)) {
  286. throw new error_1.FirebaseError("Cannot create v1 function from an eventTrigger without a resource");
  287. }
  288. gcfFunction.eventTrigger = {
  289. eventType: endpoint.eventTrigger.eventType,
  290. resource: endpoint.eventTrigger.eventFilters.resource,
  291. };
  292. gcfFunction.eventTrigger.failurePolicy = endpoint.eventTrigger.retry
  293. ? { retry: {} }
  294. : undefined;
  295. }
  296. else if (backend.isScheduleTriggered(endpoint)) {
  297. const id = backend.scheduleIdForFunction(endpoint);
  298. gcfFunction.eventTrigger = {
  299. eventType: "google.pubsub.topic.publish",
  300. resource: `projects/${endpoint.project}/topics/${id}`,
  301. };
  302. gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-scheduled": "true" });
  303. }
  304. else if (backend.isTaskQueueTriggered(endpoint)) {
  305. gcfFunction.httpsTrigger = {};
  306. gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-taskqueue": "true" });
  307. }
  308. else if (backend.isBlockingTriggered(endpoint)) {
  309. gcfFunction.httpsTrigger = {};
  310. gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.BLOCKING_LABEL]: constants_1.BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
  311. }
  312. else {
  313. gcfFunction.httpsTrigger = {};
  314. if (backend.isCallableTriggered(endpoint)) {
  315. gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-callable": "true" });
  316. }
  317. if (endpoint.securityLevel) {
  318. gcfFunction.httpsTrigger.securityLevel = endpoint.securityLevel;
  319. }
  320. }
  321. proto.copyIfPresent(gcfFunction, endpoint, "minInstances", "maxInstances", "ingressSettings", "environmentVariables", "secretEnvironmentVariables");
  322. proto.renameIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "serviceAccount");
  323. proto.convertIfPresent(gcfFunction, endpoint, "availableMemoryMb", (mem) => mem);
  324. proto.convertIfPresent(gcfFunction, endpoint, "timeout", "timeoutSeconds", (sec) => sec ? proto.durationFromSeconds(sec) : null);
  325. if (endpoint.vpc) {
  326. proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector");
  327. proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
  328. }
  329. else if (endpoint.vpc === null) {
  330. gcfFunction.vpcConnector = null;
  331. gcfFunction.vpcConnectorEgressSettings = null;
  332. }
  333. const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE;
  334. if (codebase !== projectConfig.DEFAULT_CODEBASE) {
  335. gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.CODEBASE_LABEL]: codebase });
  336. }
  337. else {
  338. (_b = gcfFunction.labels) === null || _b === void 0 ? true : delete _b[constants_1.CODEBASE_LABEL];
  339. }
  340. if (endpoint.hash) {
  341. gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.HASH_LABEL]: endpoint.hash });
  342. }
  343. return gcfFunction;
  344. }
  345. exports.functionFromEndpoint = functionFromEndpoint;