UNPKG

firebase-tools

Version:
165 lines (164 loc) 8.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.release = release; exports.printTriggerUrls = printTriggerUrls; const clc = require("colorette"); const logger_1 = require("../../../logger"); const functional_1 = require("../../../functional"); const utils = require("../../../utils"); const backend = require("../backend"); const planner = require("./planner"); const fabricator = require("./fabricator"); const reporter = require("./reporter"); const executor = require("./executor"); const prompts = require("../prompts"); const functionsConfig_1 = require("../../../functionsConfig"); const functionsDeployHelper_1 = require("../functionsDeployHelper"); const error_1 = require("../../../error"); const getProjectNumber_1 = require("../../../getProjectNumber"); const extensions_1 = require("../../extensions"); const artifacts = require("../../../functions/artifacts"); const supported_1 = require("../runtimes/supported"); const lifecycle_1 = require("./lifecycle"); async function release(context, options, payload) { if (context.extensions && payload.extensions) { await (0, extensions_1.release)(context.extensions, options, payload.extensions); } if (!context.config) { return; } if (!payload.functions) { return; } if (!context.sources) { return; } const plan = {}; for (const [codebase, { wantBackend, haveBackend, haveRoles, existingManagedSA, managedSA },] of Object.entries(payload.functions)) { plan[codebase] = await planner.createDeploymentPlan({ codebase, wantBackend, haveBackend, projectId: context.projectId, filters: context.filters, haveRoles, existingManagedSA, managedSA, }); } await prompts.promptForSecurityChanges(plan, options); const allRegionalChanges = Object.values(plan) .map((codebasePlan) => Object.values(codebasePlan.regionalChangesets)) .reduce(functional_1.reduceFlat, []); const fnsToDelete = allRegionalChanges .map((regionalChanges) => regionalChanges.endpointsToDelete) .reduce(functional_1.reduceFlat, []); const shouldDelete = await prompts.promptForFunctionDeletion(fnsToDelete, options); if (!shouldDelete) { for (const changes of allRegionalChanges) { changes.endpointsToDelete = []; } } const fnsToUpdate = allRegionalChanges .map((regionalChanges) => regionalChanges.endpointsToUpdate) .reduce(functional_1.reduceFlat, []); const fnsToUpdateSafe = await prompts.promptForUnsafeMigration(fnsToUpdate, options); const safeEndpoints = new Set(fnsToUpdateSafe.map((eu) => eu.endpoint)); for (const changes of allRegionalChanges) { changes.endpointsToUpdate = changes.endpointsToUpdate.filter((eu) => safeEndpoints.has(eu.endpoint)); } const throttlerOptions = { retries: 30, backoff: 20000, concurrency: 40, maxBackoff: 100000, }; const runThrottlerOptions = { ...throttlerOptions, concurrency: 2, }; const projectNumber = options.projectNumber || (await (0, getProjectNumber_1.getProjectNumber)(context.projectId)); const fab = new fabricator.Fabricator({ functionExecutor: new executor.QueueExecutor(throttlerOptions), runFunctionExecutor: new executor.QueueExecutor(runThrottlerOptions), executor: new executor.QueueExecutor(throttlerOptions), sources: context.sources, appEngineLocation: (0, functionsConfig_1.getAppEngineLocation)(context.firebaseConfig), projectNumber: projectNumber, projectId: context.projectId, }); const summary = await fab.applyPlan(plan); await reporter.logAndTrackDeployStats(summary, context); reporter.printErrors(summary); const wantBackend = backend.merge(...Object.values(payload.functions).map((p) => p.wantBackend)); printTriggerUrls(wantBackend, projectNumber); if (backend.someEndpoint(wantBackend, (endpoint) => (0, supported_1.runtimeIsLanguage)(endpoint.runtime, "dart"))) { utils.logLabeledBullet("functions", "Dart functions may not yet be visible in the Firebase Console. " + `View them in the Cloud Console at https://console.cloud.google.com/run/services?project=${context.projectId}`); } const allErrors = summary.results.filter((r) => r.error).map((r) => r.error); if (allErrors.length === 0) { for (const [codebase, { wantBackend: w, haveBackend: h }] of Object.entries(payload.functions)) { await (0, lifecycle_1.executeLifecycleHooks)(w, h, plan, codebase); } } await setupArtifactCleanupPolicies(options, options.projectId, Object.keys(wantBackend.endpoints)); if (allErrors.length) { for (const [codebase, { wantBackend: w, haveBackend: h }] of Object.entries(payload.functions)) { const event = (0, lifecycle_1.determineDeploymentEvent)(h); if (w.lifecycleHooks?.[event]) { utils.logLabeledWarning("functions", `Lifecycle hook "${event}" for codebase "${codebase}" was configured but not executed because one or more function deployments failed.`); } } const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors }; logger_1.logger.debug("Functions deploy failed."); for (const error of allErrors) { logger_1.logger.debug(JSON.stringify(error, null, 2)); } throw new error_1.FirebaseError("There was an error deploying functions", { ...opts, exit: 2 }); } } function printTriggerUrls(results, projectNumber) { const httpsFunctions = backend .allEndpoints(results) .filter((b) => backend.isHttpsTriggered(b) || backend.isDataConnectGraphqlTriggered(b)); if (httpsFunctions.length === 0) { return; } for (const httpsFunc of httpsFunctions) { if (!httpsFunc.uri) { logger_1.logger.debug("Not printing URL for HTTPS function. Typically this means it didn't match a filter or we failed deployment"); continue; } if (backend.isDataConnectGraphqlTriggered(httpsFunc)) { const uri = backend.maybeDeterministicCloudRunUri(httpsFunc, projectNumber); logger_1.logger.info(clc.bold("Function URL"), `(${(0, functionsDeployHelper_1.getFunctionLabel)(httpsFunc)}):`, uri); continue; } logger_1.logger.info(clc.bold("Function URL"), `(${(0, functionsDeployHelper_1.getFunctionLabel)(httpsFunc)}):`, httpsFunc.uri); } } async function setupArtifactCleanupPolicies(options, projectId, locations) { if (locations.length === 0) { return; } const { locationsToSetup, locationsWithErrors: locationsWithCheckErrors } = await artifacts.checkCleanupPolicy(projectId, locations); if (locationsToSetup.length === 0) { return; } const daysToKeep = await prompts.promptForCleanupPolicyDays(options, locationsToSetup); utils.logLabeledBullet("functions", `Configuring cleanup policy for ${locationsToSetup.length > 1 ? "repositories" : "repository"} in ${locationsToSetup.join(", ")}. ` + `Images older than ${daysToKeep} days will be automatically deleted.`); const { locationsWithPolicy, locationsWithErrors: locationsWithSetupErrors } = await artifacts.setCleanupPolicies(projectId, locationsToSetup, daysToKeep); utils.logLabeledBullet("functions", `Configured cleanup policy for ${locationsWithPolicy.length > 1 ? "repositories" : "repository"} in ${locationsToSetup.join(", ")}.`); const locationsWithErrors = [...locationsWithCheckErrors, ...locationsWithSetupErrors]; if (locationsWithErrors.length > 0) { utils.logLabeledWarning("functions", `Failed to set up cleanup policy for repositories in ${locationsWithErrors.length > 1 ? "regions" : "region"} ` + `${locationsWithErrors.join(", ")}.` + "This could result in a small monthly bill as container images accumulate over time."); utils.logLabeledWarning("functions", `Functions successfully deployed but could not set up cleanup policy in ` + `${locationsWithErrors.length > 1 ? "regions" : "region"} ${locationsWithErrors.join(", ")}. ` + `Pass the --force option to automatically set up a cleanup policy or ` + "run 'firebase functions:artifacts:setpolicy' to set up a cleanup policy to automatically delete old images."); } }