UNPKG

firebase-tools

Version:
206 lines (205 loc) 8.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadConfigToExportSecrets = exports.loadConfigForEnvironment = exports.exportConfig = exports.maybeAddSecretToYaml = exports.upsertEnv = exports.findEnv = exports.store = exports.load = exports.listAppHostingFilesInPath = exports.discoverBackendRoot = exports.APPHOSTING_YAML_FILE_REGEX = exports.APPHOSTING_LOCAL_YAML_FILE = exports.APPHOSTING_BASE_YAML_FILE = void 0; const path_1 = require("path"); const fs_1 = require("fs"); const yaml = require("yaml"); const fs = require("../fsutils"); const prompt = require("../prompt"); const dialogs = require("./secrets/dialogs"); const yaml_1 = require("./yaml"); const error_1 = require("../error"); const utils_1 = require("./utils"); const secrets_1 = require("./secrets"); const logger_1 = require("../logger"); const utils_2 = require("../utils"); const projects_1 = require("../management/projects"); exports.APPHOSTING_BASE_YAML_FILE = "apphosting.yaml"; exports.APPHOSTING_LOCAL_YAML_FILE = "apphosting.local.yaml"; exports.APPHOSTING_YAML_FILE_REGEX = /^apphosting(\.[a-z0-9_]+)?\.yaml$/; const SECRET_CONFIG = "Secret"; const EXPORTABLE_CONFIG = [SECRET_CONFIG]; function discoverBackendRoot(cwd) { let dir = cwd; while (!fs.fileExistsSync((0, path_1.resolve)(dir, exports.APPHOSTING_BASE_YAML_FILE))) { if (fs.fileExistsSync((0, path_1.resolve)(dir, "firebase.json"))) { return null; } const parent = (0, path_1.dirname)(dir); if (parent === dir) { return null; } dir = parent; } return dir; } exports.discoverBackendRoot = discoverBackendRoot; function listAppHostingFilesInPath(path) { return fs .listFiles(path) .filter((file) => exports.APPHOSTING_YAML_FILE_REGEX.test(file)) .map((file) => (0, path_1.join)(path, file)); } exports.listAppHostingFilesInPath = listAppHostingFilesInPath; function load(yamlPath) { const raw = fs.readFile(yamlPath); return yaml.parseDocument(raw); } exports.load = load; function store(yamlPath, document) { (0, fs_1.writeFileSync)(yamlPath, document.toString()); } exports.store = store; function findEnv(document, variable) { if (!document.has("env")) { return undefined; } const envs = document.get("env"); for (const env of envs.items) { if (env.get("variable") === variable) { return env.toJSON(); } } return undefined; } exports.findEnv = findEnv; function upsertEnv(document, env) { if (!document.has("env")) { document.set("env", document.createNode([env])); return; } const envs = document.get("env"); const envYaml = document.createNode(env); for (let i = 0; i < envs.items.length; i++) { if (envs.items[i].get("variable") === env.variable) { envs.set(i, envYaml); return; } } envs.add(envYaml); } exports.upsertEnv = upsertEnv; async function maybeAddSecretToYaml(secretName) { const dynamicDispatch = exports; const backendRoot = dynamicDispatch.discoverBackendRoot(process.cwd()); let path; let projectYaml; if (backendRoot) { path = (0, path_1.join)(backendRoot, exports.APPHOSTING_BASE_YAML_FILE); projectYaml = dynamicDispatch.load(path); } else { projectYaml = new yaml.Document(); } if (dynamicDispatch.findEnv(projectYaml, secretName)) { return; } const addToYaml = await prompt.confirm({ message: "Would you like to add this secret to apphosting.yaml?", default: true, }); if (!addToYaml) { return; } if (!path) { path = await prompt.promptOnce({ message: "It looks like you don't have an apphosting.yaml yet. Where would you like to store it?", default: process.cwd(), }); path = (0, path_1.join)(path, exports.APPHOSTING_BASE_YAML_FILE); } const envName = await dialogs.envVarForSecret(secretName); dynamicDispatch.upsertEnv(projectYaml, { variable: envName, secret: secretName, }); dynamicDispatch.store(path, projectYaml); } exports.maybeAddSecretToYaml = maybeAddSecretToYaml; async function exportConfig(cwd, projectRoot, backendRoot, projectId, userGivenConfigFile) { const choices = await prompt.prompt({}, [ { type: "checkbox", name: "configurations", message: "What configs would you like to export?", choices: EXPORTABLE_CONFIG, }, ]); if (!choices.configurations.includes(SECRET_CONFIG)) { logger_1.logger.info("No configs selected to export"); return; } if (!projectId) { const project = await (0, projects_1.getOrPromptProject)({}); projectId = project.projectId; } let localAppHostingConfig = yaml_1.AppHostingYamlConfig.empty(); const localAppHostingConfigPath = (0, path_1.resolve)(backendRoot, exports.APPHOSTING_LOCAL_YAML_FILE); if (fs.fileExistsSync(localAppHostingConfigPath)) { localAppHostingConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localAppHostingConfigPath); } const configToExport = await loadConfigToExportSecrets(cwd, userGivenConfigFile); const secretsToExport = configToExport.secrets; if (!secretsToExport) { logger_1.logger.info("No secrets found to export in the chosen App Hosting config files"); return; } const secretMaterial = await (0, secrets_1.fetchSecrets)(projectId, secretsToExport); for (const [key, value] of secretMaterial) { localAppHostingConfig.addEnvironmentVariable({ variable: key, value: value, availability: ["RUNTIME"], }); } localAppHostingConfig.clearSecrets(); localAppHostingConfig.upsertFile(localAppHostingConfigPath); logger_1.logger.info(`Wrote secrets as environment variables to ${exports.APPHOSTING_LOCAL_YAML_FILE}.`); (0, utils_2.updateOrCreateGitignore)(projectRoot, [exports.APPHOSTING_LOCAL_YAML_FILE]); logger_1.logger.info(`${exports.APPHOSTING_LOCAL_YAML_FILE} has been automatically added to your .gitignore.`); } exports.exportConfig = exportConfig; async function loadConfigForEnvironment(envYamlPath, baseYamlPath) { const envYamlConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(envYamlPath); if (baseYamlPath) { const baseConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(baseYamlPath); baseConfig.merge(envYamlConfig); return baseConfig; } return envYamlConfig; } exports.loadConfigForEnvironment = loadConfigForEnvironment; async function loadConfigToExportSecrets(cwd, userGivenConfigFile) { if (userGivenConfigFile && !exports.APPHOSTING_YAML_FILE_REGEX.test(userGivenConfigFile)) { throw new error_1.FirebaseError("Invalid apphosting yaml config file provided. File must be in format: 'apphosting.yaml' or 'apphosting.<environment>.yaml'"); } const allConfigs = getValidConfigs(cwd); let userGivenConfigFilePath; if (userGivenConfigFile) { if (!allConfigs.has(userGivenConfigFile)) { throw new error_1.FirebaseError(`The provided app hosting config file "${userGivenConfigFile}" does not exist`); } userGivenConfigFilePath = allConfigs.get(userGivenConfigFile); } else { userGivenConfigFilePath = await (0, utils_1.promptForAppHostingYaml)(allConfigs, "Which environment would you like to export secrets from Secret Manager for?"); } if (userGivenConfigFile === exports.APPHOSTING_BASE_YAML_FILE) { return yaml_1.AppHostingYamlConfig.loadFromFile(allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE)); } const baseFilePath = allConfigs.get(exports.APPHOSTING_BASE_YAML_FILE); return await loadConfigForEnvironment(userGivenConfigFilePath, baseFilePath); } exports.loadConfigToExportSecrets = loadConfigToExportSecrets; function getValidConfigs(cwd) { const appHostingConfigPaths = listAppHostingFilesInPath(cwd).filter((path) => !path.endsWith(exports.APPHOSTING_LOCAL_YAML_FILE)); if (appHostingConfigPaths.length === 0) { throw new error_1.FirebaseError("No apphosting.*.yaml configs found"); } const fileNameToPathMap = new Map(); for (const path of appHostingConfigPaths) { const fileName = (0, path_1.basename)(path); fileNameToPathMap.set(fileName, path); } return fileNameToPathMap; }