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.

fabricator.js 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Fabricator = void 0;
  4. const clc = require("colorette");
  5. const error_1 = require("../../../error");
  6. const sourceTokenScraper_1 = require("./sourceTokenScraper");
  7. const timer_1 = require("./timer");
  8. const functional_1 = require("../../../functional");
  9. const runtimes_1 = require("../runtimes");
  10. const api_1 = require("../../../api");
  11. const logger_1 = require("../../../logger");
  12. const backend = require("../backend");
  13. const cloudtasks = require("../../../gcp/cloudtasks");
  14. const deploymentTool = require("../../../deploymentTool");
  15. const gcf = require("../../../gcp/cloudfunctions");
  16. const gcfV2 = require("../../../gcp/cloudfunctionsv2");
  17. const eventarc = require("../../../gcp/eventarc");
  18. const helper = require("../functionsDeployHelper");
  19. const poller = require("../../../operation-poller");
  20. const pubsub = require("../../../gcp/pubsub");
  21. const reporter = require("./reporter");
  22. const run = require("../../../gcp/run");
  23. const scheduler = require("../../../gcp/cloudscheduler");
  24. const utils = require("../../../utils");
  25. const services = require("../services");
  26. const v1_1 = require("../../../functions/events/v1");
  27. const checkIam_1 = require("../checkIam");
  28. const gcfV1PollerOptions = {
  29. apiOrigin: api_1.functionsOrigin,
  30. apiVersion: gcf.API_VERSION,
  31. masterTimeout: 25 * 60 * 1000,
  32. maxBackoff: 10000,
  33. };
  34. const gcfV2PollerOptions = {
  35. apiOrigin: api_1.functionsV2Origin,
  36. apiVersion: gcfV2.API_VERSION,
  37. masterTimeout: 25 * 60 * 1000,
  38. maxBackoff: 10000,
  39. };
  40. const eventarcPollerOptions = {
  41. apiOrigin: api_1.eventarcOrigin,
  42. apiVersion: "v1",
  43. masterTimeout: 25 * 60 * 1000,
  44. maxBackoff: 10000,
  45. };
  46. const rethrowAs = (endpoint, op) => (err) => {
  47. logger_1.logger.error(err.message);
  48. throw new reporter.DeploymentError(endpoint, op, err);
  49. };
  50. class Fabricator {
  51. constructor(args) {
  52. this.executor = args.executor;
  53. this.functionExecutor = args.functionExecutor;
  54. this.sources = args.sources;
  55. this.appEngineLocation = args.appEngineLocation;
  56. this.projectNumber = args.projectNumber;
  57. }
  58. async applyPlan(plan) {
  59. const timer = new timer_1.Timer();
  60. const summary = {
  61. totalTime: 0,
  62. results: [],
  63. };
  64. const deployChangesets = Object.values(plan).map(async (changes) => {
  65. const results = await this.applyChangeset(changes);
  66. summary.results.push(...results);
  67. return;
  68. });
  69. const promiseResults = await utils.allSettled(deployChangesets);
  70. const errs = promiseResults
  71. .filter((r) => r.status === "rejected")
  72. .map((r) => r.reason);
  73. if (errs.length) {
  74. logger_1.logger.debug("Fabricator.applyRegionalChanges returned an unhandled exception. This should never happen", JSON.stringify(errs, null, 2));
  75. }
  76. summary.totalTime = timer.stop();
  77. return summary;
  78. }
  79. async applyChangeset(changes) {
  80. const deployResults = [];
  81. const handle = async (op, endpoint, fn) => {
  82. const timer = new timer_1.Timer();
  83. const result = { endpoint };
  84. try {
  85. await fn();
  86. this.logOpSuccess(op, endpoint);
  87. }
  88. catch (err) {
  89. result.error = err;
  90. }
  91. result.durationMs = timer.stop();
  92. deployResults.push(result);
  93. };
  94. const upserts = [];
  95. const scraper = new sourceTokenScraper_1.SourceTokenScraper();
  96. for (const endpoint of changes.endpointsToCreate) {
  97. this.logOpStart("creating", endpoint);
  98. upserts.push(handle("create", endpoint, () => this.createEndpoint(endpoint, scraper)));
  99. }
  100. for (const endpoint of changes.endpointsToSkip) {
  101. utils.logSuccess(this.getLogSuccessMessage("skip", endpoint));
  102. }
  103. for (const update of changes.endpointsToUpdate) {
  104. this.logOpStart("updating", update.endpoint);
  105. upserts.push(handle("update", update.endpoint, () => this.updateEndpoint(update, scraper)));
  106. }
  107. await utils.allSettled(upserts);
  108. if (deployResults.find((r) => r.error)) {
  109. for (const endpoint of changes.endpointsToDelete) {
  110. deployResults.push({
  111. endpoint,
  112. durationMs: 0,
  113. error: new reporter.AbortedDeploymentError(endpoint),
  114. });
  115. }
  116. return deployResults;
  117. }
  118. const deletes = [];
  119. for (const endpoint of changes.endpointsToDelete) {
  120. this.logOpStart("deleting", endpoint);
  121. deletes.push(handle("delete", endpoint, () => this.deleteEndpoint(endpoint)));
  122. }
  123. await utils.allSettled(deletes);
  124. return deployResults;
  125. }
  126. async createEndpoint(endpoint, scraper) {
  127. endpoint.labels = Object.assign(Object.assign({}, endpoint.labels), deploymentTool.labels());
  128. if (endpoint.platform === "gcfv1") {
  129. await this.createV1Function(endpoint, scraper);
  130. }
  131. else if (endpoint.platform === "gcfv2") {
  132. await this.createV2Function(endpoint);
  133. }
  134. else {
  135. (0, functional_1.assertExhaustive)(endpoint.platform);
  136. }
  137. await this.setTrigger(endpoint);
  138. }
  139. async updateEndpoint(update, scraper) {
  140. update.endpoint.labels = Object.assign(Object.assign({}, update.endpoint.labels), deploymentTool.labels());
  141. if (update.deleteAndRecreate) {
  142. await this.deleteEndpoint(update.deleteAndRecreate);
  143. await this.createEndpoint(update.endpoint, scraper);
  144. return;
  145. }
  146. if (update.endpoint.platform === "gcfv1") {
  147. await this.updateV1Function(update.endpoint, scraper);
  148. }
  149. else if (update.endpoint.platform === "gcfv2") {
  150. await this.updateV2Function(update.endpoint);
  151. }
  152. else {
  153. (0, functional_1.assertExhaustive)(update.endpoint.platform);
  154. }
  155. await this.setTrigger(update.endpoint);
  156. }
  157. async deleteEndpoint(endpoint) {
  158. await this.deleteTrigger(endpoint);
  159. if (endpoint.platform === "gcfv1") {
  160. await this.deleteV1Function(endpoint);
  161. }
  162. else {
  163. await this.deleteV2Function(endpoint);
  164. }
  165. }
  166. async createV1Function(endpoint, scraper) {
  167. var _a, _b;
  168. const sourceUrl = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.sourceUrl;
  169. if (!sourceUrl) {
  170. logger_1.logger.debug("Precondition failed. Cannot create a GCF function without sourceUrl");
  171. throw new Error("Precondition failed");
  172. }
  173. const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl);
  174. if (apiFunction.httpsTrigger) {
  175. apiFunction.httpsTrigger.securityLevel = "SECURE_ALWAYS";
  176. }
  177. const resultFunction = await this.functionExecutor
  178. .run(async () => {
  179. apiFunction.sourceToken = await scraper.getToken();
  180. const op = await gcf.createFunction(apiFunction);
  181. return poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
  182. })
  183. .catch(rethrowAs(endpoint, "create"));
  184. endpoint.uri = (_b = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _b === void 0 ? void 0 : _b.url;
  185. if (backend.isHttpsTriggered(endpoint)) {
  186. const invoker = endpoint.httpsTrigger.invoker || ["public"];
  187. if (!invoker.includes("private")) {
  188. await this.executor
  189. .run(async () => {
  190. await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
  191. })
  192. .catch(rethrowAs(endpoint, "set invoker"));
  193. }
  194. }
  195. else if (backend.isCallableTriggered(endpoint)) {
  196. await this.executor
  197. .run(async () => {
  198. await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), ["public"]);
  199. })
  200. .catch(rethrowAs(endpoint, "set invoker"));
  201. }
  202. else if (backend.isTaskQueueTriggered(endpoint)) {
  203. const invoker = endpoint.taskQueueTrigger.invoker;
  204. if (invoker && !invoker.includes("private")) {
  205. await this.executor
  206. .run(async () => {
  207. await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
  208. })
  209. .catch(rethrowAs(endpoint, "set invoker"));
  210. }
  211. }
  212. else if (backend.isBlockingTriggered(endpoint) &&
  213. v1_1.AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType)) {
  214. await this.executor
  215. .run(async () => {
  216. await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), ["public"]);
  217. })
  218. .catch(rethrowAs(endpoint, "set invoker"));
  219. }
  220. }
  221. async createV2Function(endpoint) {
  222. var _a, _b, _c;
  223. const storage = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
  224. if (!storage) {
  225. logger_1.logger.debug("Precondition failed. Cannot create a GCFv2 function without storage");
  226. throw new Error("Precondition failed");
  227. }
  228. const apiFunction = gcfV2.functionFromEndpoint(endpoint, storage);
  229. const topic = (_b = apiFunction.eventTrigger) === null || _b === void 0 ? void 0 : _b.pubsubTopic;
  230. if (topic) {
  231. await this.executor
  232. .run(async () => {
  233. try {
  234. await pubsub.createTopic({ name: topic });
  235. }
  236. catch (err) {
  237. if (err.status === 409) {
  238. return;
  239. }
  240. throw new error_1.FirebaseError("Unexpected error creating Pub/Sub topic", {
  241. original: err,
  242. });
  243. }
  244. })
  245. .catch(rethrowAs(endpoint, "create topic"));
  246. }
  247. const channel = (_c = apiFunction.eventTrigger) === null || _c === void 0 ? void 0 : _c.channel;
  248. if (channel) {
  249. await this.executor
  250. .run(async () => {
  251. try {
  252. const op = await eventarc.createChannel({ name: channel });
  253. return await poller.pollOperation(Object.assign(Object.assign({}, eventarcPollerOptions), { pollerName: `create-${channel}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
  254. }
  255. catch (err) {
  256. if (err.status === 409) {
  257. return;
  258. }
  259. throw new error_1.FirebaseError("Unexpected error creating Eventarc channel", {
  260. original: err,
  261. });
  262. }
  263. })
  264. .catch(rethrowAs(endpoint, "upsert eventarc channel"));
  265. }
  266. const resultFunction = await this.functionExecutor
  267. .run(async () => {
  268. const op = await gcfV2.createFunction(apiFunction);
  269. return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
  270. })
  271. .catch(rethrowAs(endpoint, "create"));
  272. endpoint.uri = resultFunction.serviceConfig.uri;
  273. const serviceName = resultFunction.serviceConfig.service;
  274. if (backend.isHttpsTriggered(endpoint)) {
  275. const invoker = endpoint.httpsTrigger.invoker || ["public"];
  276. if (!invoker.includes("private")) {
  277. await this.executor
  278. .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker))
  279. .catch(rethrowAs(endpoint, "set invoker"));
  280. }
  281. }
  282. else if (backend.isCallableTriggered(endpoint)) {
  283. await this.executor
  284. .run(() => run.setInvokerCreate(endpoint.project, serviceName, ["public"]))
  285. .catch(rethrowAs(endpoint, "set invoker"));
  286. }
  287. else if (backend.isTaskQueueTriggered(endpoint)) {
  288. const invoker = endpoint.taskQueueTrigger.invoker;
  289. if (invoker && !invoker.includes("private")) {
  290. await this.executor
  291. .run(async () => {
  292. await run.setInvokerCreate(endpoint.project, serviceName, invoker);
  293. })
  294. .catch(rethrowAs(endpoint, "set invoker"));
  295. }
  296. }
  297. else if (backend.isBlockingTriggered(endpoint) &&
  298. v1_1.AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType)) {
  299. await this.executor
  300. .run(() => run.setInvokerCreate(endpoint.project, serviceName, ["public"]))
  301. .catch(rethrowAs(endpoint, "set invoker"));
  302. }
  303. else if (backend.isScheduleTriggered(endpoint)) {
  304. const invoker = [(0, checkIam_1.getDefaultComputeServiceAgent)(this.projectNumber)];
  305. await this.executor
  306. .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker))
  307. .catch(rethrowAs(endpoint, "set invoker"));
  308. }
  309. const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
  310. const hasCustomCPU = endpoint.cpu !== backend.memoryToGen1Cpu(mem);
  311. if (!endpoint.concurrency) {
  312. endpoint.concurrency =
  313. endpoint.cpu >= backend.MIN_CPU_FOR_CONCURRENCY
  314. ? backend.DEFAULT_CONCURRENCY
  315. : 1;
  316. }
  317. const hasConcurrency = endpoint.concurrency !== 1;
  318. if (hasCustomCPU || hasConcurrency) {
  319. await this.setRunTraits(serviceName, endpoint);
  320. }
  321. }
  322. async updateV1Function(endpoint, scraper) {
  323. var _a, _b;
  324. const sourceUrl = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.sourceUrl;
  325. if (!sourceUrl) {
  326. logger_1.logger.debug("Precondition failed. Cannot update a GCF function without sourceUrl");
  327. throw new Error("Precondition failed");
  328. }
  329. const apiFunction = gcf.functionFromEndpoint(endpoint, sourceUrl);
  330. const resultFunction = await this.functionExecutor
  331. .run(async () => {
  332. apiFunction.sourceToken = await scraper.getToken();
  333. const op = await gcf.updateFunction(apiFunction);
  334. return await poller.pollOperation(Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
  335. })
  336. .catch(rethrowAs(endpoint, "update"));
  337. endpoint.uri = (_b = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _b === void 0 ? void 0 : _b.url;
  338. let invoker;
  339. if (backend.isHttpsTriggered(endpoint)) {
  340. invoker = endpoint.httpsTrigger.invoker === null ? ["public"] : endpoint.httpsTrigger.invoker;
  341. }
  342. else if (backend.isTaskQueueTriggered(endpoint)) {
  343. invoker = endpoint.taskQueueTrigger.invoker === null ? [] : endpoint.taskQueueTrigger.invoker;
  344. }
  345. else if (backend.isBlockingTriggered(endpoint) &&
  346. v1_1.AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType)) {
  347. invoker = ["public"];
  348. }
  349. if (invoker) {
  350. await this.executor
  351. .run(() => gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), invoker))
  352. .catch(rethrowAs(endpoint, "set invoker"));
  353. }
  354. }
  355. async updateV2Function(endpoint) {
  356. var _a, _b;
  357. const storage = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
  358. if (!storage) {
  359. logger_1.logger.debug("Precondition failed. Cannot update a GCFv2 function without storage");
  360. throw new Error("Precondition failed");
  361. }
  362. const apiFunction = gcfV2.functionFromEndpoint(endpoint, storage);
  363. if ((_b = apiFunction.eventTrigger) === null || _b === void 0 ? void 0 : _b.pubsubTopic) {
  364. delete apiFunction.eventTrigger.pubsubTopic;
  365. }
  366. const resultFunction = await this.functionExecutor
  367. .run(async () => {
  368. const op = await gcfV2.updateFunction(apiFunction);
  369. return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
  370. })
  371. .catch(rethrowAs(endpoint, "update"));
  372. endpoint.uri = resultFunction.serviceConfig.uri;
  373. const serviceName = resultFunction.serviceConfig.service;
  374. let invoker;
  375. if (backend.isHttpsTriggered(endpoint)) {
  376. invoker = endpoint.httpsTrigger.invoker === null ? ["public"] : endpoint.httpsTrigger.invoker;
  377. }
  378. else if (backend.isTaskQueueTriggered(endpoint)) {
  379. invoker = endpoint.taskQueueTrigger.invoker === null ? [] : endpoint.taskQueueTrigger.invoker;
  380. }
  381. else if (backend.isBlockingTriggered(endpoint) &&
  382. v1_1.AUTH_BLOCKING_EVENTS.includes(endpoint.blockingTrigger.eventType)) {
  383. invoker = ["public"];
  384. }
  385. else if (backend.isScheduleTriggered(endpoint)) {
  386. invoker = [(0, checkIam_1.getDefaultComputeServiceAgent)(this.projectNumber)];
  387. }
  388. if (invoker) {
  389. await this.executor
  390. .run(() => run.setInvokerUpdate(endpoint.project, serviceName, invoker))
  391. .catch(rethrowAs(endpoint, "set invoker"));
  392. }
  393. const hasCustomCPU = endpoint.cpu !==
  394. backend.memoryToGen1Cpu(endpoint.availableMemoryMb || backend.DEFAULT_MEMORY);
  395. const explicitConcurrency = endpoint.concurrency !== undefined;
  396. if (hasCustomCPU || explicitConcurrency) {
  397. if (endpoint.concurrency === undefined) {
  398. endpoint.concurrency =
  399. endpoint.cpu < backend.MIN_CPU_FOR_CONCURRENCY
  400. ? 1
  401. : backend.DEFAULT_CONCURRENCY;
  402. }
  403. await this.setRunTraits(serviceName, endpoint);
  404. }
  405. }
  406. async deleteV1Function(endpoint) {
  407. const fnName = backend.functionName(endpoint);
  408. await this.functionExecutor
  409. .run(async () => {
  410. const op = await gcf.deleteFunction(fnName);
  411. const pollerOptions = Object.assign(Object.assign({}, gcfV1PollerOptions), { pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
  412. await poller.pollOperation(pollerOptions);
  413. })
  414. .catch(rethrowAs(endpoint, "delete"));
  415. }
  416. async deleteV2Function(endpoint) {
  417. const fnName = backend.functionName(endpoint);
  418. await this.functionExecutor
  419. .run(async () => {
  420. const op = await gcfV2.deleteFunction(fnName);
  421. const pollerOptions = Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
  422. await poller.pollOperation(pollerOptions);
  423. })
  424. .catch(rethrowAs(endpoint, "delete"));
  425. }
  426. async setRunTraits(serviceName, endpoint) {
  427. await this.functionExecutor
  428. .run(async () => {
  429. const service = await run.getService(serviceName);
  430. let changed = false;
  431. if (service.spec.template.spec.containerConcurrency !== endpoint.concurrency) {
  432. service.spec.template.spec.containerConcurrency = endpoint.concurrency;
  433. changed = true;
  434. }
  435. if (+service.spec.template.spec.containers[0].resources.limits.cpu !== endpoint.cpu) {
  436. service.spec.template.spec.containers[0].resources.limits.cpu = `${endpoint.cpu}`;
  437. changed = true;
  438. }
  439. if (!changed) {
  440. logger_1.logger.debug("Skipping setRunTraits on", serviceName, " because it already matches");
  441. return;
  442. }
  443. delete service.spec.template.metadata.name;
  444. await run.updateService(serviceName, service);
  445. })
  446. .catch(rethrowAs(endpoint, "set concurrency"));
  447. }
  448. async setTrigger(endpoint) {
  449. if (backend.isScheduleTriggered(endpoint)) {
  450. if (endpoint.platform === "gcfv1") {
  451. await this.upsertScheduleV1(endpoint);
  452. return;
  453. }
  454. else if (endpoint.platform === "gcfv2") {
  455. await this.upsertScheduleV2(endpoint);
  456. return;
  457. }
  458. (0, functional_1.assertExhaustive)(endpoint.platform);
  459. }
  460. else if (backend.isTaskQueueTriggered(endpoint)) {
  461. await this.upsertTaskQueue(endpoint);
  462. }
  463. else if (backend.isBlockingTriggered(endpoint)) {
  464. await this.registerBlockingTrigger(endpoint);
  465. }
  466. }
  467. async deleteTrigger(endpoint) {
  468. if (backend.isScheduleTriggered(endpoint)) {
  469. if (endpoint.platform === "gcfv1") {
  470. await this.deleteScheduleV1(endpoint);
  471. return;
  472. }
  473. else if (endpoint.platform === "gcfv2") {
  474. await this.deleteScheduleV2(endpoint);
  475. return;
  476. }
  477. (0, functional_1.assertExhaustive)(endpoint.platform);
  478. }
  479. else if (backend.isTaskQueueTriggered(endpoint)) {
  480. await this.disableTaskQueue(endpoint);
  481. }
  482. else if (backend.isBlockingTriggered(endpoint)) {
  483. await this.unregisterBlockingTrigger(endpoint);
  484. }
  485. }
  486. async upsertScheduleV1(endpoint) {
  487. const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation, this.projectNumber);
  488. await this.executor
  489. .run(() => scheduler.createOrReplaceJob(job))
  490. .catch(rethrowAs(endpoint, "upsert schedule"));
  491. }
  492. async upsertScheduleV2(endpoint) {
  493. const job = scheduler.jobFromEndpoint(endpoint, endpoint.region, this.projectNumber);
  494. await this.executor
  495. .run(() => scheduler.createOrReplaceJob(job))
  496. .catch(rethrowAs(endpoint, "upsert schedule"));
  497. }
  498. async upsertTaskQueue(endpoint) {
  499. const queue = cloudtasks.queueFromEndpoint(endpoint);
  500. await this.executor
  501. .run(() => cloudtasks.upsertQueue(queue))
  502. .catch(rethrowAs(endpoint, "upsert task queue"));
  503. if (endpoint.taskQueueTrigger.invoker) {
  504. await this.executor
  505. .run(() => cloudtasks.setEnqueuer(queue.name, endpoint.taskQueueTrigger.invoker))
  506. .catch(rethrowAs(endpoint, "set invoker"));
  507. }
  508. }
  509. async registerBlockingTrigger(endpoint) {
  510. await this.executor
  511. .run(() => services.serviceForEndpoint(endpoint).registerTrigger(endpoint))
  512. .catch(rethrowAs(endpoint, "register blocking trigger"));
  513. }
  514. async deleteScheduleV1(endpoint) {
  515. const jobName = scheduler.jobNameForEndpoint(endpoint, this.appEngineLocation);
  516. await this.executor
  517. .run(() => scheduler.deleteJob(jobName))
  518. .catch(rethrowAs(endpoint, "delete schedule"));
  519. const topicName = scheduler.topicNameForEndpoint(endpoint);
  520. await this.executor
  521. .run(() => pubsub.deleteTopic(topicName))
  522. .catch(rethrowAs(endpoint, "delete topic"));
  523. }
  524. async deleteScheduleV2(endpoint) {
  525. const jobName = scheduler.jobNameForEndpoint(endpoint, endpoint.region);
  526. await this.executor
  527. .run(() => scheduler.deleteJob(jobName))
  528. .catch(rethrowAs(endpoint, "delete schedule"));
  529. }
  530. async disableTaskQueue(endpoint) {
  531. const update = {
  532. name: cloudtasks.queueNameForEndpoint(endpoint),
  533. state: "DISABLED",
  534. };
  535. await this.executor
  536. .run(() => cloudtasks.updateQueue(update))
  537. .catch(rethrowAs(endpoint, "disable task queue"));
  538. }
  539. async unregisterBlockingTrigger(endpoint) {
  540. await this.executor
  541. .run(() => services.serviceForEndpoint(endpoint).unregisterTrigger(endpoint))
  542. .catch(rethrowAs(endpoint, "unregister blocking trigger"));
  543. }
  544. logOpStart(op, endpoint) {
  545. const runtime = (0, runtimes_1.getHumanFriendlyRuntimeName)(endpoint.runtime);
  546. const label = helper.getFunctionLabel(endpoint);
  547. utils.logLabeledBullet("functions", `${op} ${runtime} function ${clc.bold(label)}...`);
  548. }
  549. logOpSuccess(op, endpoint) {
  550. utils.logSuccess(this.getLogSuccessMessage(op, endpoint));
  551. }
  552. getLogSuccessMessage(op, endpoint) {
  553. const label = helper.getFunctionLabel(endpoint);
  554. switch (op) {
  555. case "skip":
  556. return `${clc.bold(clc.magenta(`functions[${label}]`))} Skipped (No changes detected)`;
  557. default:
  558. return `${clc.bold(clc.green(`functions[${label}]`))} Successful ${op} operation.`;
  559. }
  560. }
  561. getSkippedDeployingNopOpMessage(endpoints) {
  562. const functionNames = endpoints.map((endpoint) => endpoint.id).join(",");
  563. return `${clc.bold(clc.magenta(`functions:`))} You can re-deploy skipped functions with:
  564. ${clc.bold(`firebase deploy --only functions:${functionNames}`)} or ${clc.bold(`FUNCTIONS_DEPLOY_UNCHANGED=true firebase deploy`)}`;
  565. }
  566. }
  567. exports.Fabricator = Fabricator;