UNPKG

firebase-tools

Version:
343 lines (342 loc) 12.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0; exports.endpointTriggerType = endpointTriggerType; exports.isValidMemoryOption = isValidMemoryOption; exports.isValidEgressSetting = isValidEgressSetting; exports.memoryOptionDisplayName = memoryOptionDisplayName; exports.memoryToGen1Cpu = memoryToGen1Cpu; exports.memoryToGen2Cpu = memoryToGen2Cpu; exports.secretVersionName = secretVersionName; exports.isHttpsTriggered = isHttpsTriggered; exports.isDataConnectGraphqlTriggered = isDataConnectGraphqlTriggered; exports.isCallableTriggered = isCallableTriggered; exports.isEventTriggered = isEventTriggered; exports.isScheduleTriggered = isScheduleTriggered; exports.isTaskQueueTriggered = isTaskQueueTriggered; exports.isBlockingTriggered = isBlockingTriggered; exports.empty = empty; exports.of = of; exports.merge = merge; exports.isEmptyBackend = isEmptyBackend; exports.functionName = functionName; exports.scheduleIdForFunction = scheduleIdForFunction; exports.existingBackend = existingBackend; exports.checkAvailability = checkAvailability; exports.allEndpoints = allEndpoints; exports.someEndpoint = someEndpoint; exports.findEndpoint = findEndpoint; exports.matchingBackend = matchingBackend; exports.regionalEndpoints = regionalEndpoints; exports.compareFunctions = compareFunctions; const gcf = require("../../gcp/cloudfunctions"); const gcfV2 = require("../../gcp/cloudfunctionsv2"); const run = require("../../gcp/runv2"); const utils = require("../../utils"); const error_1 = require("../../error"); const functional_1 = require("../../functional"); const logger_1 = require("../../logger"); const experiments = require("../../experiments"); function endpointTriggerType(endpoint) { if (isScheduleTriggered(endpoint)) { return "scheduled"; } else if (isHttpsTriggered(endpoint)) { return "https"; } else if (isDataConnectGraphqlTriggered(endpoint)) { return "dataConnectGraphql"; } else if (isCallableTriggered(endpoint)) { return "callable"; } else if (isEventTriggered(endpoint)) { return endpoint.eventTrigger.eventType; } else if (isTaskQueueTriggered(endpoint)) { return "taskQueue"; } else if (isBlockingTriggered(endpoint)) { return endpoint.blockingTrigger.eventType; } (0, functional_1.assertExhaustive)(endpoint); } exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"]; exports.AllIngressSettings = [ "ALLOW_ALL", "ALLOW_INTERNAL_ONLY", "ALLOW_INTERNAL_AND_GCLB", ]; const allMemoryOptions = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]; function isValidMemoryOption(mem) { return allMemoryOptions.includes(mem); } function isValidEgressSetting(egress) { return egress === "PRIVATE_RANGES_ONLY" || egress === "ALL_TRAFFIC"; } function memoryOptionDisplayName(option) { return { 128: "128MB", 256: "256MB", 512: "512MB", 1024: "1GB", 2048: "2GB", 4096: "4GB", 8192: "8GB", 16384: "16GB", 32768: "32GB", }[option]; } function memoryToGen1Cpu(memory) { return { 128: 0.0833, 256: 0.1666, 512: 0.3333, 1024: 0.5833, 2048: 1, 4096: 2, 8192: 2, 16384: 4, 32768: 8, }[memory]; } function memoryToGen2Cpu(memory) { return { 128: 1, 256: 1, 512: 1, 1024: 1, 2048: 1, 4096: 2, 8192: 2, 16384: 4, 32768: 8, }[memory]; } exports.DEFAULT_CONCURRENCY = 80; exports.DEFAULT_MEMORY = 256; exports.MIN_CPU_FOR_CONCURRENCY = 1; exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" }); function secretVersionName(s) { return `projects/${s.projectId}/secrets/${s.secret}/versions/${s.version ?? "latest"}`; } exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"]; function isHttpsTriggered(triggered) { return {}.hasOwnProperty.call(triggered, "httpsTrigger"); } function isDataConnectGraphqlTriggered(triggered) { return {}.hasOwnProperty.call(triggered, "dataConnectGraphqlTrigger"); } function isCallableTriggered(triggered) { return {}.hasOwnProperty.call(triggered, "callableTrigger"); } function isEventTriggered(triggered) { return {}.hasOwnProperty.call(triggered, "eventTrigger"); } function isScheduleTriggered(triggered) { return {}.hasOwnProperty.call(triggered, "scheduleTrigger"); } function isTaskQueueTriggered(triggered) { return {}.hasOwnProperty.call(triggered, "taskQueueTrigger"); } function isBlockingTriggered(triggered) { return {}.hasOwnProperty.call(triggered, "blockingTrigger"); } function empty() { return { requiredAPIs: [], endpoints: {}, environmentVariables: {}, }; } function of(...endpoints) { const bkend = { ...empty() }; for (const endpoint of endpoints) { bkend.endpoints[endpoint.region] = bkend.endpoints[endpoint.region] || {}; if (bkend.endpoints[endpoint.region][endpoint.id]) { throw new Error("Trying to create a backend with the same endpoint twice"); } bkend.endpoints[endpoint.region][endpoint.id] = endpoint; } return bkend; } function merge(...backends) { const merged = of(...(0, functional_1.flattenArray)(backends.map((b) => allEndpoints(b)))); const apiToReasons = {}; for (const b of backends) { for (const { api, reason } of b.requiredAPIs) { const reasons = apiToReasons[api] || new Set(); if (reason) { reasons.add(reason); } apiToReasons[api] = reasons; } merged.environmentVariables = { ...merged.environmentVariables, ...b.environmentVariables }; } for (const [api, reasons] of Object.entries(apiToReasons)) { merged.requiredAPIs.push({ api, reason: Array.from(reasons).join(" ") }); } return merged; } function isEmptyBackend(backend) { return (Object.keys(backend.requiredAPIs).length === 0 && Object.keys(backend.endpoints).length === 0); } function functionName(cloudFunction) { return `projects/${cloudFunction.project}/locations/${cloudFunction.region}/functions/${cloudFunction.id}`; } function scheduleIdForFunction(cloudFunction) { return `firebase-schedule-${cloudFunction.id}-${cloudFunction.region}`; } function existingBackend(context, forceRefresh) { if (!context.existingBackendPromise || forceRefresh) { context.existingBackendPromise = loadExistingBackend(context); } return context.existingBackendPromise; } async function loadExistingBackend(ctx) { const existingBackend = { ...empty(), }; const unreachableRegions = { gcfV1: [], gcfV2: [], run: [], }; const gcfV1Results = await gcf.listAllFunctions(ctx.projectId); for (const apiFunction of gcfV1Results.functions) { const endpoint = gcf.endpointFromFunction(apiFunction); existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {}; existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint; } unreachableRegions.gcfV1 = gcfV1Results.unreachable; if (experiments.isEnabled("functionsrunapionly")) { try { const runServices = await run.listServices(ctx.projectId); for (const service of runServices) { const endpoint = run.endpointFromService(service); existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {}; existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint; } } catch (err) { logger_1.logger.debug(err.message); unreachableRegions.run = ["unknown"]; } } else { const gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId); for (const apiFunction of gcfV2Results.functions) { const endpoint = gcfV2.endpointFromFunction(apiFunction); existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {}; existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint; } unreachableRegions.gcfV2 = gcfV2Results.unreachable; } ctx.existingBackend = existingBackend; ctx.unreachableRegions = unreachableRegions; return ctx.existingBackend; } async function checkAvailability(context, want) { await existingBackend(context); const gcfV1Regions = new Set(); const gcfV2Regions = new Set(); for (const ep of allEndpoints(want)) { if (ep.platform === "gcfv1") { gcfV1Regions.add(ep.region); } else { gcfV2Regions.add(ep.region); } } const neededUnreachableV1 = context.unreachableRegions?.gcfV1.filter((region) => gcfV1Regions.has(region)); const neededUnreachableV2 = context.unreachableRegions?.gcfV2.filter((region) => gcfV2Regions.has(region)); if (neededUnreachableV1?.length) { throw new error_1.FirebaseError("The following Cloud Functions regions are currently unreachable:\n\t" + neededUnreachableV1.join("\n\t") + "\nThis deployment contains functions in those regions. Please try again in a few minutes, or exclude these regions from your deployment."); } if (neededUnreachableV2?.length) { throw new error_1.FirebaseError("The following Cloud Functions V2 regions are currently unreachable:\n\t" + neededUnreachableV2.join("\n\t") + "\nThis deployment contains functions in those regions. Please try again in a few minutes, or exclude these regions from your deployment."); } if (context.unreachableRegions?.gcfV1.length) { utils.logLabeledWarning("functions", "The following Cloud Functions regions are currently unreachable:\n" + context.unreachableRegions.gcfV1.join("\n") + "\nCloud Functions in these regions won't be deleted."); } if (context.unreachableRegions?.gcfV2.length) { utils.logLabeledWarning("functions", "The following Cloud Functions V2 regions are currently unreachable:\n" + context.unreachableRegions.gcfV2.join("\n") + "\nCloud Functions in these regions won't be deleted."); } if (context.unreachableRegions?.run.length) { utils.logLabeledWarning("functions", "The following Cloud Run regions are currently unreachable:\n" + context.unreachableRegions.run.join("\n") + "\nCloud Run services in these regions won't be deleted."); } } function allEndpoints(backend) { return Object.values(backend.endpoints).reduce((accum, perRegion) => { return [...accum, ...Object.values(perRegion)]; }, []); } function someEndpoint(backend, predicate) { for (const endpoints of Object.values(backend.endpoints)) { if (Object.values(endpoints).some(predicate)) { return true; } } return false; } function findEndpoint(backend, predicate) { for (const endpoints of Object.values(backend.endpoints)) { const endpoint = Object.values(endpoints).find(predicate); if (endpoint) return endpoint; } } function matchingBackend(backend, predicate) { const filtered = { ...backend, endpoints: {}, }; for (const endpoint of allEndpoints(backend)) { if (!predicate(endpoint)) { continue; } filtered.endpoints[endpoint.region] = filtered.endpoints[endpoint.region] || {}; filtered.endpoints[endpoint.region][endpoint.id] = endpoint; } return filtered; } function regionalEndpoints(backend, region) { return backend.endpoints[region] ? Object.values(backend.endpoints[region]) : []; } const hasEndpoint = (backend) => (endpoint) => { return (!!backend.endpoints[endpoint.region] && !!backend.endpoints[endpoint.region][endpoint.id]); }; exports.hasEndpoint = hasEndpoint; const missingEndpoint = (backend) => (endpoint) => { return !(0, exports.hasEndpoint)(backend)(endpoint); }; exports.missingEndpoint = missingEndpoint; function compareFunctions(left, right) { if (left.platform !== right.platform) { return right.platform < left.platform ? -1 : 1; } if (left.region < right.region) { return -1; } if (left.region > right.region) { return 1; } if (left.id < right.id) { return -1; } if (left.id > right.id) { return 1; } return 0; }