"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.calculateChangesets = void 0; const clc = require("colorette"); const functionsDeployHelper_1 = require("../functionsDeployHelper"); const deploymentTool_1 = require("../../../deploymentTool"); const error_1 = require("../../../error"); const utils = require("../../../utils"); const backend = require("../backend"); const v2events = require("../../../functions/events/v2"); function calculateChangesets(want, have, keyFn, deleteAll) { const toCreate = utils.groupBy(Object.keys(want) .filter((id) => !have[id]) .map((id) => want[id]), keyFn); const toDelete = utils.groupBy(Object.keys(have) .filter((id) => !want[id]) .filter((id) => deleteAll || (0, deploymentTool_1.isFirebaseManaged)(have[id].labels || {})) .map((id) => have[id]), keyFn); const toSkipPredicate = (id) => !!(!want[id].targetedByOnly && have[id].hash && want[id].hash && want[id].hash === have[id].hash); const toSkipEndpointsMap = Object.keys(want) .filter((id) => have[id]) .filter((id) => toSkipPredicate(id)) .reduce((memo, id) => { memo[id] = want[id]; return memo; }, {}); const toSkip = utils.groupBy(Object.values(toSkipEndpointsMap), keyFn); if (Object.keys(toSkip).length) { utils.logLabeledBullet("functions", `Skipping the deploy of unchanged functions with ${clc.bold("experimental")} support for skipdeployingnoopfunctions`); } const toUpdate = utils.groupBy(Object.keys(want) .filter((id) => have[id]) .filter((id) => !toSkipEndpointsMap[id]) .map((id) => calculateUpdate(want[id], have[id])), (eu) => keyFn(eu.endpoint)); const result = {}; const keys = new Set([ ...Object.keys(toCreate), ...Object.keys(toDelete), ...Object.keys(toUpdate), ...Object.keys(toSkip), ]); for (const key of keys) { result[key] = { endpointsToCreate: toCreate[key] || [], endpointsToUpdate: toUpdate[key] || [], endpointsToDelete: toDelete[key] || [], endpointsToSkip: toSkip[key] || [], }; } return result; } exports.calculateChangesets = calculateChangesets; function calculateUpdate(want, have) { checkForIllegalUpdate(want, have); const update = { endpoint: want, }; const needsDelete = changedTriggerRegion(want, have) || changedV2PubSubTopic(want, have) || upgradedScheduleFromV1ToV2(want, have); if (needsDelete) { update.deleteAndRecreate = have; } return update; } exports.calculateUpdate = calculateUpdate; function createDeploymentPlan(args) { let { wantBackend, haveBackend, codebase, filters, deleteAll } = args; let deployment = {}; wantBackend = backend.matchingBackend(wantBackend, (endpoint) => { return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters); }); const wantedEndpoint = backend.hasEndpoint(wantBackend); haveBackend = backend.matchingBackend(haveBackend, (endpoint) => { return wantedEndpoint(endpoint) || (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters); }); const regions = new Set([ ...Object.keys(wantBackend.endpoints), ...Object.keys(haveBackend.endpoints), ]); for (const region of regions) { const changesets = calculateChangesets(wantBackend.endpoints[region] || {}, haveBackend.endpoints[region] || {}, (e) => `${codebase}-${e.region}-${e.availableMemoryMb || "default"}`, deleteAll); deployment = Object.assign(Object.assign({}, deployment), changesets); } if (upgradedToGCFv2WithoutSettingConcurrency(wantBackend, haveBackend)) { utils.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " + "which introduces support for concurrent execution. New functions " + "default to 80 concurrent executions, but existing functions keep the " + "old default of 1. You can change this with the 'concurrency' option."); } return deployment; } exports.createDeploymentPlan = createDeploymentPlan; function upgradedToGCFv2WithoutSettingConcurrency(want, have) { return backend.someEndpoint(want, (endpoint) => { var _a, _b; if (((_b = (_a = have.endpoints[endpoint.region]) === null || _a === void 0 ? void 0 : _a[endpoint.id]) === null || _b === void 0 ? void 0 : _b.platform) !== "gcfv1") { return false; } if (endpoint.platform !== "gcfv2") { return false; } if (endpoint.concurrency) { return false; } return true; }); } exports.upgradedToGCFv2WithoutSettingConcurrency = upgradedToGCFv2WithoutSettingConcurrency; function changedTriggerRegion(want, have) { if (want.platform !== "gcfv2") { return false; } if (have.platform !== "gcfv2") { return false; } if (!backend.isEventTriggered(want)) { return false; } if (!backend.isEventTriggered(have)) { return false; } return want.eventTrigger.region !== have.eventTrigger.region; } exports.changedTriggerRegion = changedTriggerRegion; function changedV2PubSubTopic(want, have) { if (want.platform !== "gcfv2") { return false; } if (have.platform !== "gcfv2") { return false; } if (!backend.isEventTriggered(want)) { return false; } if (!backend.isEventTriggered(have)) { return false; } if (want.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) { return false; } if (have.eventTrigger.eventType !== v2events.PUBSUB_PUBLISH_EVENT) { return false; } return have.eventTrigger.eventFilters.topic !== want.eventTrigger.eventFilters.topic; } exports.changedV2PubSubTopic = changedV2PubSubTopic; function upgradedScheduleFromV1ToV2(want, have) { if (have.platform !== "gcfv1") { return false; } if (want.platform !== "gcfv2") { return false; } if (!backend.isScheduleTriggered(have)) { return false; } if (!backend.isScheduleTriggered(want)) { return false; } return true; } exports.upgradedScheduleFromV1ToV2 = upgradedScheduleFromV1ToV2; function checkForIllegalUpdate(want, have) { const triggerType = (e) => { if (backend.isHttpsTriggered(e)) { return "an HTTPS"; } else if (backend.isCallableTriggered(e)) { return "a callable"; } else if (backend.isEventTriggered(e)) { return "a background triggered"; } else if (backend.isScheduleTriggered(e)) { return "a scheduled"; } else if (backend.isTaskQueueTriggered(e)) { return "a task queue"; } else if (backend.isBlockingTriggered(e)) { return e.blockingTrigger.eventType; } throw Error("Functions release planner is not able to handle an unknown trigger type"); }; const wantType = triggerType(want); const haveType = triggerType(have); if (wantType !== haveType) { throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.`); } if (want.platform === "gcfv1" && have.platform === "gcfv2") { throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Functions cannot be downgraded from GCFv2 to GCFv1`); } exports.checkForV2Upgrade(want, have); } exports.checkForIllegalUpdate = checkForIllegalUpdate; function checkForV2Upgrade(want, have) { if (want.platform === "gcfv2" && have.platform === "gcfv1") { throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(have)}] Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.`); } } exports.checkForV2Upgrade = checkForV2Upgrade;