firebase-tools
Version:
Command-Line Interface for Firebase
206 lines (205 loc) • 8.53 kB
JavaScript
;
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;
}