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.

prepare.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.loadCodebases = exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
  4. const clc = require("colorette");
  5. const backend = require("./backend");
  6. const build = require("./build");
  7. const ensureApiEnabled = require("../../ensureApiEnabled");
  8. const functionsConfig = require("../../functionsConfig");
  9. const functionsEnv = require("../../functions/env");
  10. const runtimes = require("./runtimes");
  11. const validate = require("./validate");
  12. const ensure = require("./ensure");
  13. const functionsDeployHelper_1 = require("./functionsDeployHelper");
  14. const utils_1 = require("../../utils");
  15. const prepareFunctionsUpload_1 = require("./prepareFunctionsUpload");
  16. const prompts_1 = require("./prompts");
  17. const projectUtils_1 = require("../../projectUtils");
  18. const track_1 = require("../../track");
  19. const logger_1 = require("../../logger");
  20. const triggerRegionHelper_1 = require("./triggerRegionHelper");
  21. const checkIam_1 = require("./checkIam");
  22. const error_1 = require("../../error");
  23. const projectConfig_1 = require("../../functions/projectConfig");
  24. const v1_1 = require("../../functions/events/v1");
  25. const serviceusage_1 = require("../../gcp/serviceusage");
  26. const applyHash_1 = require("./cache/applyHash");
  27. const backend_1 = require("./backend");
  28. const functional_1 = require("../../functional");
  29. exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
  30. function hasUserConfig(config) {
  31. return Object.keys(config).length > 1;
  32. }
  33. async function prepare(context, options, payload) {
  34. var _a, _b;
  35. const projectId = (0, projectUtils_1.needProjectId)(options);
  36. const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
  37. context.config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
  38. context.filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
  39. const codebases = (0, functionsDeployHelper_1.targetCodebases)(context.config, context.filters);
  40. if (codebases.length === 0) {
  41. throw new error_1.FirebaseError("No function matches given --only filters. Aborting deployment.");
  42. }
  43. for (const codebase of codebases) {
  44. (0, utils_1.logLabeledBullet)("functions", `preparing codebase ${clc.bold(codebase)} for deployment`);
  45. }
  46. const checkAPIsEnabled = await Promise.all([
  47. ensureApiEnabled.ensure(projectId, "cloudfunctions.googleapis.com", "functions"),
  48. ensureApiEnabled.check(projectId, "runtimeconfig.googleapis.com", "runtimeconfig", true),
  49. ensure.cloudBuildEnabled(projectId),
  50. ensureApiEnabled.ensure(projectId, "artifactregistry.googleapis.com", "artifactregistry"),
  51. ]);
  52. const firebaseConfig = await functionsConfig.getFirebaseConfig(options);
  53. context.firebaseConfig = firebaseConfig;
  54. let runtimeConfig = { firebase: firebaseConfig };
  55. if (checkAPIsEnabled[1]) {
  56. runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), (await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)));
  57. }
  58. const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
  59. const codebaseUsesEnvs = [];
  60. const wantBackends = {};
  61. for (const [codebase, wantBuild] of Object.entries(wantBuilds)) {
  62. const config = (0, projectConfig_1.configForCodebase)(context.config, codebase);
  63. const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
  64. const userEnvOpt = {
  65. functionsSource: options.config.path(config.source),
  66. projectId: projectId,
  67. projectAlias: options.projectAlias,
  68. };
  69. const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
  70. const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
  71. const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend(wantBuild, firebaseConfig, userEnvOpt, userEnvs, options.nonInteractive);
  72. let hasEnvsFromParams = false;
  73. wantBackend.environmentVariables = envs;
  74. for (const envName of Object.keys(resolvedEnvs)) {
  75. const isList = (_a = resolvedEnvs[envName]) === null || _a === void 0 ? void 0 : _a.legalList;
  76. const envValue = (_b = resolvedEnvs[envName]) === null || _b === void 0 ? void 0 : _b.toSDK();
  77. if (envValue &&
  78. !resolvedEnvs[envName].internal &&
  79. (!Object.prototype.hasOwnProperty.call(wantBackend.environmentVariables, envName) || isList)) {
  80. wantBackend.environmentVariables[envName] = envValue;
  81. hasEnvsFromParams = true;
  82. }
  83. }
  84. for (const endpoint of backend.allEndpoints(wantBackend)) {
  85. endpoint.environmentVariables = wantBackend.environmentVariables || {};
  86. let resource;
  87. if (endpoint.platform === "gcfv1") {
  88. resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`;
  89. }
  90. else if (endpoint.platform === "gcfv2") {
  91. resource = `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.id}`;
  92. }
  93. else {
  94. (0, functional_1.assertExhaustive)(endpoint.platform);
  95. }
  96. endpoint.environmentVariables[exports.EVENTARC_SOURCE_ENV] = resource;
  97. endpoint.codebase = codebase;
  98. }
  99. wantBackends[codebase] = wantBackend;
  100. if (functionsEnv.hasUserEnvs(userEnvOpt) || hasEnvsFromParams) {
  101. codebaseUsesEnvs.push(codebase);
  102. }
  103. if (wantBuild.params.length > 0) {
  104. if (wantBuild.params.every((p) => p.type !== "secret")) {
  105. void (0, track_1.track)("functions_params_in_build", "env_only");
  106. }
  107. else {
  108. void (0, track_1.track)("functions_params_in_build", "with_secrets");
  109. }
  110. }
  111. else {
  112. void (0, track_1.track)("functions_params_in_build", "none");
  113. }
  114. }
  115. validate.endpointsAreUnique(wantBackends);
  116. context.sources = {};
  117. for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
  118. const config = (0, projectConfig_1.configForCodebase)(context.config, codebase);
  119. const sourceDirName = config.source;
  120. const sourceDir = options.config.path(sourceDirName);
  121. const source = {};
  122. if (backend.someEndpoint(wantBackend, () => true)) {
  123. (0, utils_1.logLabeledBullet)("functions", `preparing ${clc.bold(sourceDirName)} directory for uploading...`);
  124. }
  125. if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
  126. const packagedSource = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, config);
  127. source.functionsSourceV2 = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.pathToSource;
  128. source.functionsSourceV2Hash = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.hash;
  129. }
  130. if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
  131. const packagedSource = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, config, runtimeConfig);
  132. source.functionsSourceV1 = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.pathToSource;
  133. source.functionsSourceV1Hash = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.hash;
  134. }
  135. context.sources[codebase] = source;
  136. }
  137. payload.functions = {};
  138. const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(await backend.existingBackend(context)));
  139. for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
  140. const haveBackend = haveBackends[codebase] || backend.empty();
  141. payload.functions[codebase] = { wantBackend, haveBackend };
  142. }
  143. for (const [codebase, { wantBackend, haveBackend }] of Object.entries(payload.functions)) {
  144. inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
  145. await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
  146. resolveCpuAndConcurrency(wantBackend);
  147. validate.endpointsAreValid(wantBackend);
  148. inferBlockingDetails(wantBackend);
  149. }
  150. const tag = hasUserConfig(runtimeConfig)
  151. ? codebaseUsesEnvs.length > 0
  152. ? "mixed"
  153. : "runtime_config"
  154. : codebaseUsesEnvs.length > 0
  155. ? "dotenv"
  156. : "none";
  157. void (0, track_1.track)("functions_codebase_deploy_env_method", tag);
  158. const codebaseCnt = Object.keys(payload.functions).length;
  159. void (0, track_1.track)("functions_codebase_deploy_count", codebaseCnt >= 5 ? "5+" : codebaseCnt.toString());
  160. const wantBackend = backend.merge(...Object.values(wantBackends));
  161. const haveBackend = backend.merge(...Object.values(haveBackends));
  162. await Promise.all(Object.values(wantBackend.requiredAPIs).map(({ api }) => {
  163. return ensureApiEnabled.ensure(projectId, api, "functions", false);
  164. }));
  165. if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv2")) {
  166. const V2_APIS = [
  167. "run.googleapis.com",
  168. "eventarc.googleapis.com",
  169. "pubsub.googleapis.com",
  170. "storage.googleapis.com",
  171. ];
  172. const enablements = V2_APIS.map((api) => {
  173. return ensureApiEnabled.ensure(context.projectId, api, "functions");
  174. });
  175. await Promise.all(enablements);
  176. const services = ["pubsub.googleapis.com", "eventarc.googleapis.com"];
  177. const generateServiceAccounts = services.map((service) => {
  178. return (0, serviceusage_1.generateServiceIdentity)(projectNumber, service, "functions");
  179. });
  180. await Promise.all(generateServiceAccounts);
  181. }
  182. const matchingBackend = backend.matchingBackend(wantBackend, (endpoint) => {
  183. return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, context.filters);
  184. });
  185. await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
  186. await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
  187. await backend.checkAvailability(context, matchingBackend);
  188. await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend);
  189. await validate.secretsAreValid(projectId, matchingBackend);
  190. await ensure.secretAccess(projectId, matchingBackend, haveBackend);
  191. updateEndpointTargetedStatus(wantBackends, context.filters || []);
  192. (0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
  193. }
  194. exports.prepare = prepare;
  195. function inferDetailsFromExisting(want, have, usedDotenv) {
  196. var _a;
  197. for (const wantE of backend.allEndpoints(want)) {
  198. const haveE = (_a = have.endpoints[wantE.region]) === null || _a === void 0 ? void 0 : _a[wantE.id];
  199. if (!haveE) {
  200. continue;
  201. }
  202. if (!usedDotenv) {
  203. wantE.environmentVariables = Object.assign(Object.assign({}, haveE.environmentVariables), wantE.environmentVariables);
  204. }
  205. if (typeof wantE.availableMemoryMb === "undefined" && haveE.availableMemoryMb) {
  206. wantE.availableMemoryMb = haveE.availableMemoryMb;
  207. }
  208. if (typeof wantE.cpu === "undefined" && haveE.cpu) {
  209. wantE.cpu = haveE.cpu;
  210. }
  211. wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
  212. maybeCopyTriggerRegion(wantE, haveE);
  213. }
  214. }
  215. exports.inferDetailsFromExisting = inferDetailsFromExisting;
  216. function maybeCopyTriggerRegion(wantE, haveE) {
  217. if (!backend.isEventTriggered(wantE) || !backend.isEventTriggered(haveE)) {
  218. return;
  219. }
  220. if (wantE.eventTrigger.region || !haveE.eventTrigger.region) {
  221. return;
  222. }
  223. if (JSON.stringify(haveE.eventTrigger.eventFilters) !==
  224. JSON.stringify(wantE.eventTrigger.eventFilters)) {
  225. return;
  226. }
  227. wantE.eventTrigger.region = haveE.eventTrigger.region;
  228. }
  229. function updateEndpointTargetedStatus(wantBackends, endpointFilters) {
  230. for (const wantBackend of Object.values(wantBackends)) {
  231. for (const endpoint of (0, backend_1.allEndpoints)(wantBackend)) {
  232. endpoint.targetedByOnly = (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, endpointFilters);
  233. }
  234. }
  235. }
  236. exports.updateEndpointTargetedStatus = updateEndpointTargetedStatus;
  237. function inferBlockingDetails(want) {
  238. var _a, _b, _c;
  239. const authBlockingEndpoints = backend
  240. .allEndpoints(want)
  241. .filter((ep) => backend.isBlockingTriggered(ep) &&
  242. v1_1.AUTH_BLOCKING_EVENTS.includes(ep.blockingTrigger.eventType));
  243. if (authBlockingEndpoints.length === 0) {
  244. return;
  245. }
  246. let accessToken = false;
  247. let idToken = false;
  248. let refreshToken = false;
  249. for (const blockingEp of authBlockingEndpoints) {
  250. accessToken || (accessToken = !!((_a = blockingEp.blockingTrigger.options) === null || _a === void 0 ? void 0 : _a.accessToken));
  251. idToken || (idToken = !!((_b = blockingEp.blockingTrigger.options) === null || _b === void 0 ? void 0 : _b.idToken));
  252. refreshToken || (refreshToken = !!((_c = blockingEp.blockingTrigger.options) === null || _c === void 0 ? void 0 : _c.refreshToken));
  253. }
  254. for (const blockingEp of authBlockingEndpoints) {
  255. if (!blockingEp.blockingTrigger.options) {
  256. blockingEp.blockingTrigger.options = {};
  257. }
  258. blockingEp.blockingTrigger.options.accessToken = accessToken;
  259. blockingEp.blockingTrigger.options.idToken = idToken;
  260. blockingEp.blockingTrigger.options.refreshToken = refreshToken;
  261. }
  262. }
  263. exports.inferBlockingDetails = inferBlockingDetails;
  264. function resolveCpuAndConcurrency(want) {
  265. for (const e of backend.allEndpoints(want)) {
  266. if (e.platform === "gcfv1") {
  267. continue;
  268. }
  269. if (e.cpu === "gcf_gen1") {
  270. e.cpu = backend.memoryToGen1Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY);
  271. }
  272. else if (!e.cpu) {
  273. e.cpu = backend.memoryToGen2Cpu(e.availableMemoryMb || backend.DEFAULT_MEMORY);
  274. }
  275. if (!e.concurrency) {
  276. e.concurrency = e.cpu >= 1 ? backend.DEFAULT_CONCURRENCY : 1;
  277. }
  278. }
  279. }
  280. exports.resolveCpuAndConcurrency = resolveCpuAndConcurrency;
  281. async function loadCodebases(config, options, firebaseConfig, runtimeConfig, filters) {
  282. const codebases = (0, functionsDeployHelper_1.targetCodebases)(config, filters);
  283. const projectId = (0, projectUtils_1.needProjectId)(options);
  284. const wantBuilds = {};
  285. for (const codebase of codebases) {
  286. const codebaseConfig = (0, projectConfig_1.configForCodebase)(config, codebase);
  287. const sourceDirName = codebaseConfig.source;
  288. if (!sourceDirName) {
  289. throw new error_1.FirebaseError(`No functions code detected at default location (./functions), and no functions source defined in firebase.json`);
  290. }
  291. const sourceDir = options.config.path(sourceDirName);
  292. const delegateContext = {
  293. projectId,
  294. sourceDir,
  295. projectDir: options.config.projectDir,
  296. runtime: codebaseConfig.runtime || "",
  297. };
  298. const runtimeDelegate = await runtimes.getRuntimeDelegate(delegateContext);
  299. logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
  300. await runtimeDelegate.validate();
  301. logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
  302. await runtimeDelegate.build();
  303. const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
  304. wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs);
  305. }
  306. return wantBuilds;
  307. }
  308. exports.loadCodebases = loadCodebases;