firebase-tools
Version:
Command-Line Interface for Firebase
245 lines (244 loc) • 9.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parameterizeProject = parameterizeProject;
exports.setSecretParamsToLatest = setSecretParamsToLatest;
exports.displayExportInfo = displayExportInfo;
exports.memoryToMb = memoryToMb;
exports.parseMemory = parseMemory;
exports.resolveMigratedMemory = resolveMigratedMemory;
exports.functionsEnvFromInstance = functionsEnvFromInstance;
exports.secretsNeedingEjection = secretsNeedingEjection;
exports.ejectSecretsFromInstance = ejectSecretsFromInstance;
const planner_1 = require("../deploy/extensions/planner");
const deploymentSummary_1 = require("../deploy/extensions/deploymentSummary");
const logger_1 = require("../logger");
const secretManager_1 = require("../gcp/secretManager");
const secretsUtils_1 = require("./secretsUtils");
const secrets_1 = require("../deploy/extensions/secrets");
const error_1 = require("../error");
const utils_1 = require("../utils");
function parameterizeProject(projectId, projectNumber, spec) {
const newParams = {};
for (const [key, val] of Object.entries(spec.params)) {
const p1 = val.replace(projectId, "${param:PROJECT_ID}");
const p2 = p1.replace(projectNumber, "${param:PROJECT_NUMBER}");
newParams[key] = p2;
}
const newSpec = { ...spec };
newSpec.params = newParams;
return newSpec;
}
async function setSecretParamsToLatest(spec) {
const newParams = { ...spec.params };
const extensionVersion = await (0, planner_1.getExtensionVersion)(spec);
const activeSecrets = (0, secretsUtils_1.getActiveSecrets)(extensionVersion.spec, newParams);
for (const [key, val] of Object.entries(newParams)) {
if (activeSecrets.includes(val)) {
const parsed = (0, secretManager_1.parseSecretVersionResourceName)(val);
parsed.versionId = "latest";
newParams[key] = (0, secretManager_1.toSecretVersionResourceName)(parsed);
}
}
return { ...spec, params: newParams };
}
function displayExportInfo(withRef, withoutRef) {
logger_1.logger.info("The following Extension instances will be saved locally:");
logger_1.logger.info("");
displaySpecs(withRef);
if (withoutRef.length) {
logger_1.logger.info(`Your project also has the following instances installed from local sources. These will not be saved to firebase.json:`);
for (const spec of withoutRef) {
logger_1.logger.info(spec.instanceId);
}
}
}
function displaySpecs(specs) {
for (let i = 0; i < specs.length; i++) {
const spec = specs[i];
logger_1.logger.info(`${i + 1}. ${(0, deploymentSummary_1.humanReadable)(spec)}`);
logger_1.logger.info(`Configuration will be written to 'extensions/${spec.instanceId}.env'`);
for (const p of Object.entries(spec.params)) {
logger_1.logger.info(`\t${p[0]}=${p[1]}`);
}
if (spec.allowedEventTypes?.length) {
logger_1.logger.info(`\tALLOWED_EVENTS=${spec.allowedEventTypes}`);
}
if (spec.eventarcChannel) {
logger_1.logger.info(`\tEVENTARC_CHANNEL=${spec.eventarcChannel}`);
}
logger_1.logger.info("");
}
}
const GIB_REGEX = /^\d+(?:\.\d+)?(?:Gi|GiB|G|GB)$/i;
const MIB_REGEX = /^\d+(?:\.\d+)?(?:Mi|MiB|M|MB)?$/i;
function memoryToMb(memory) {
if (!memory) {
return 0;
}
const trimmed = memory.trim();
if (GIB_REGEX.test(trimmed)) {
return Math.round(parseFloat(trimmed) * 1024);
}
if (MIB_REGEX.test(trimmed)) {
const parsed = parseFloat(trimmed);
return isNaN(parsed) || parsed < 0 ? 0 : parsed;
}
return 0;
}
const MB_TO_MEMORY_OPTION = {
128: "128MiB",
256: "256MiB",
512: "512MiB",
1024: "1GiB",
2048: "2GiB",
4096: "4GiB",
8192: "8GiB",
16384: "16GiB",
32768: "32GiB",
};
function parseMemory(raw) {
if (!raw) {
return undefined;
}
const mb = memoryToMb(raw);
return MB_TO_MEMORY_OPTION[mb];
}
const V1_MEMORY_PARAM = "firebaseextensions.v1beta.function/memory";
const V2_MEMORY_PARAM = "firebaseextensions.v1beta.v2function/memory";
const MEMORY_PARAMS = new Set([V1_MEMORY_PARAM, V2_MEMORY_PARAM]);
function resolveMigratedMemory(liveSystemParams = {}, specSystemParams = []) {
const getParam = (paramName) => {
if (paramName in liveSystemParams) {
return liveSystemParams[paramName];
}
const defaultVal = specSystemParams.find((p) => p.param === paramName)?.default;
return defaultVal !== undefined ? String(defaultVal) : undefined;
};
const v1 = getParam(V1_MEMORY_PARAM);
const v2 = getParam(V2_MEMORY_PARAM);
if (v1 && v2) {
return memoryToMb(v1) > memoryToMb(v2) ? v1 : v2;
}
return v2 ?? v1;
}
function resolveLiveSecret(specParam, liveParams) {
const val = liveParams[specParam.param];
if (!val && !specParam.required) {
return undefined;
}
if (!val) {
throw new error_1.FirebaseError(`Secret ${specParam.param} was defined in the extension spec, but is missing in live deployed secrets.`, { exit: 1 });
}
return val;
}
function functionsEnvFromInstance(instance) {
const liveParams = instance.config?.params || {};
const liveSystemParams = instance.config?.systemParams || {};
const specParams = instance.config?.source?.spec?.params || [];
const specSystemParams = instance.config?.source?.spec?.systemParams || [];
const envs = {};
for (const specParam of specParams) {
if (specParam.type === "SECRET") {
const val = resolveLiveSecret(specParam, liveParams);
if (!val) {
continue;
}
const renamed = "FIREBASE_SECRET_REF_" + specParam.param;
envs[renamed] = val;
}
else if (specParam.param in liveParams) {
envs[specParam.param] = liveParams[specParam.param];
}
else {
envs[specParam.param] = specParam.default ?? "";
}
}
for (const [sysParamName, sysParamValue] of Object.entries(liveSystemParams)) {
if (MEMORY_PARAMS.has(sysParamName)) {
continue;
}
let renamed = sysParamName
.replace(/^firebaseextensions\.v1beta\.(v2)?function\//, "EXT_MIGRATED_SYSTEM_")
.toUpperCase();
if (renamed === "EXT_MIGRATED_SYSTEM_LOCATION") {
renamed = "FUNCTION_DEFAULT_REGION";
}
envs[renamed] = sysParamValue;
}
for (const specSystemParam of specSystemParams) {
if (specSystemParam.param in liveSystemParams || MEMORY_PARAMS.has(specSystemParam.param)) {
continue;
}
if ("default" in specSystemParam) {
let renamed = specSystemParam.param
.replace(/^firebaseextensions\.v1beta\.(v2)?function\//, "EXT_MIGRATED_SYSTEM_")
.toUpperCase();
if (renamed === "EXT_MIGRATED_SYSTEM_LOCATION") {
renamed = "FUNCTION_DEFAULT_REGION";
}
envs[renamed] = String(specSystemParam.default ?? "");
}
}
const memory = resolveMigratedMemory(liveSystemParams, specSystemParams);
if (memory) {
envs["EXT_MIGRATED_SYSTEM_MEMORY"] = parseMemory(memory) ?? memory;
}
if (typeof instance.config.allowedEventTypes !== "undefined") {
envs["EXT_SELECTED_EVENTS"] = instance.config.allowedEventTypes.toString();
}
if (typeof instance.config.eventarcChannel !== "undefined") {
envs["EVENTARC_CHANNEL"] = instance.config.eventarcChannel;
}
return envs;
}
async function secretsNeedingEjection(instance) {
const liveParams = instance.config?.params || {};
const secretParams = (instance.config?.source?.spec?.params ?? []).filter((p) => p.type === "SECRET");
const checks = secretParams.map(async (specParam) => {
const resourceName = resolveLiveSecret(specParam, liveParams);
if (!resourceName) {
return undefined;
}
const match = resourceName.match(secretManager_1.SECRET_VERSION_NAME_REGEX);
if (!match?.groups) {
throw new error_1.FirebaseError("Invalid secret version resource name [" + resourceName + "].");
}
const projectId = match.groups.project;
const secretId = match.groups.secret;
const hasLabel = await (0, secrets_1.secretHasExtensionsLabel)(projectId, secretId);
return hasLabel ? `${projectId}/${secretId}` : undefined;
});
const results = await Promise.all(checks);
return results.filter((r) => r !== undefined);
}
async function ejectSecretsFromInstance(instance) {
const success = [];
const fail = [];
const liveParams = instance.config?.params || {};
for (const specParam of instance.config?.source?.spec?.params ?? []) {
if (specParam.type !== "SECRET") {
continue;
}
const resourceName = resolveLiveSecret(specParam, liveParams);
if (!resourceName) {
continue;
}
const match = resourceName.match(secretManager_1.SECRET_VERSION_NAME_REGEX);
if (!match?.groups) {
throw new error_1.FirebaseError(`Invalid secret version resource name [${resourceName}].`);
}
const projectId = match.groups.project;
const secretId = match.groups.secret;
const combinedId = `${projectId}/${secretId}`;
try {
await (0, secrets_1.transferSecretToKits)(projectId, secretId);
success.push(combinedId);
}
catch (err) {
fail.push(combinedId);
const message = err instanceof Error ? err.message : String(err);
(0, utils_1.logLabeledError)("extensions", "failed to change labels on " + combinedId + ": " + message);
}
}
return { success: success, fail: fail };
}