123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.ask = exports.checkResponse = exports.SecretLocation = void 0;
- const _ = require("lodash");
- const clc = require("colorette");
- const { marked } = require("marked");
- const types_1 = require("./types");
- const secretManagerApi = require("../gcp/secretManager");
- const secretsUtils = require("./secretsUtils");
- const extensionsHelper_1 = require("./extensionsHelper");
- const utils_1 = require("./utils");
- const logger_1 = require("../logger");
- const prompt_1 = require("../prompt");
- const utils = require("../utils");
- const projectUtils_1 = require("../projectUtils");
- var SecretLocation;
- (function (SecretLocation) {
- SecretLocation[SecretLocation["CLOUD"] = 1] = "CLOUD";
- SecretLocation[SecretLocation["LOCAL"] = 2] = "LOCAL";
- })(SecretLocation = exports.SecretLocation || (exports.SecretLocation = {}));
- var SecretUpdateAction;
- (function (SecretUpdateAction) {
- SecretUpdateAction[SecretUpdateAction["LEAVE"] = 1] = "LEAVE";
- SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 2] = "SET_NEW";
- })(SecretUpdateAction || (SecretUpdateAction = {}));
- function checkResponse(response, spec) {
- var _a;
- let valid = true;
- let responses;
- if (spec.required && (response === "" || response === undefined)) {
- utils.logWarning(`Param ${spec.param} is required, but no value was provided.`);
- return false;
- }
- if (spec.type === types_1.ParamType.MULTISELECT) {
- responses = response.split(",");
- }
- else {
- responses = [response];
- }
- if (spec.validationRegex && !!response) {
- const re = new RegExp(spec.validationRegex);
- for (const resp of responses) {
- if ((spec.required || resp !== "") && !re.test(resp)) {
- const genericWarn = `${resp} is not a valid value for ${spec.param} since it` +
- ` does not meet the requirements of the regex validation: "${spec.validationRegex}"`;
- utils.logWarning(spec.validationErrorMessage || genericWarn);
- valid = false;
- }
- }
- }
- if (spec.type && (spec.type === types_1.ParamType.MULTISELECT || spec.type === types_1.ParamType.SELECT)) {
- for (const r of responses) {
- const validChoice = (_a = spec.options) === null || _a === void 0 ? void 0 : _a.some((option) => r === option.value);
- if (r && !validChoice) {
- utils.logWarning(`${r} is not a valid option for ${spec.param}.`);
- valid = false;
- }
- }
- }
- return valid;
- }
- exports.checkResponse = checkResponse;
- async function ask(args) {
- if (_.isEmpty(args.paramSpecs)) {
- logger_1.logger.debug("No params were specified for this extension.");
- return {};
- }
- utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
- const substituted = (0, extensionsHelper_1.substituteParams)(args.paramSpecs, args.firebaseProjectParams);
- const result = {};
- const promises = substituted.map((paramSpec) => {
- return async () => {
- result[paramSpec.param] = await askForParam({
- projectId: args.projectId,
- instanceId: args.instanceId,
- paramSpec: paramSpec,
- reconfiguring: args.reconfiguring,
- });
- };
- });
- await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
- logger_1.logger.info();
- return result;
- }
- exports.ask = ask;
- async function askForParam(args) {
- const paramSpec = args.paramSpec;
- let valid = false;
- let response = "";
- let responseForLocal;
- let secretLocations = [];
- const description = paramSpec.description || "";
- const label = paramSpec.label.trim();
- logger_1.logger.info(`\n${clc.bold(label)}${clc.bold(paramSpec.required ? "" : " (Optional)")}: ${marked(description).trim()}`);
- while (!valid) {
- switch (paramSpec.type) {
- case types_1.ParamType.SELECT:
- response = await (0, prompt_1.promptOnce)({
- name: "input",
- type: "list",
- default: () => {
- if (paramSpec.default) {
- return getInquirerDefault(_.get(paramSpec, "options", []), paramSpec.default);
- }
- },
- message: "Which option do you want enabled for this parameter? " +
- "Select an option with the arrow keys, and use Enter to confirm your choice. " +
- "You may only select one option.",
- choices: (0, utils_1.convertExtensionOptionToLabeledList)(paramSpec.options),
- });
- valid = checkResponse(response, paramSpec);
- break;
- case types_1.ParamType.MULTISELECT:
- response = await (0, utils_1.onceWithJoin)({
- name: "input",
- type: "checkbox",
- default: () => {
- if (paramSpec.default) {
- const defaults = paramSpec.default.split(",");
- return defaults.map((def) => {
- return getInquirerDefault(_.get(paramSpec, "options", []), def);
- });
- }
- },
- message: "Which options do you want enabled for this parameter? " +
- "Press Space to select, then Enter to confirm your choices. ",
- choices: (0, utils_1.convertExtensionOptionToLabeledList)(paramSpec.options),
- });
- valid = checkResponse(response, paramSpec);
- break;
- case types_1.ParamType.SECRET:
- do {
- secretLocations = await promptSecretLocations(paramSpec);
- } while (!isValidSecretLocations(secretLocations, paramSpec));
- if (secretLocations.includes(SecretLocation.CLOUD.toString())) {
- const projectId = (0, projectUtils_1.needProjectId)({ projectId: args.projectId });
- response = args.reconfiguring
- ? await promptReconfigureSecret(projectId, args.instanceId, paramSpec)
- : await promptCreateSecret(projectId, args.instanceId, paramSpec);
- }
- if (secretLocations.includes(SecretLocation.LOCAL.toString())) {
- responseForLocal = await promptLocalSecret(args.instanceId, paramSpec);
- }
- valid = true;
- break;
- default:
- response = await (0, prompt_1.promptOnce)({
- name: paramSpec.param,
- type: "input",
- default: paramSpec.default,
- message: `Enter a value for ${label}:`,
- });
- valid = checkResponse(response, paramSpec);
- }
- }
- return Object.assign({ baseValue: response }, (responseForLocal ? { local: responseForLocal } : {}));
- }
- exports.askForParam = askForParam;
- function isValidSecretLocations(secretLocations, paramSpec) {
- if (paramSpec.required) {
- return !!secretLocations.length;
- }
- return true;
- }
- async function promptSecretLocations(paramSpec) {
- if (paramSpec.required) {
- return await (0, prompt_1.promptOnce)({
- name: "input",
- type: "checkbox",
- message: "Where would you like to store your secrets? You must select at least one value",
- choices: [
- {
- checked: true,
- name: "Google Cloud Secret Manager (Used by deployed extensions and emulator)",
- value: SecretLocation.CLOUD.toString(),
- },
- {
- checked: false,
- name: "Local file (Used by emulator only)",
- value: SecretLocation.LOCAL.toString(),
- },
- ],
- });
- }
- return await (0, prompt_1.promptOnce)({
- name: "input",
- type: "checkbox",
- message: "Where would you like to store your secrets? " +
- "If you don't want to set this optional secret, leave both options unselected to skip it",
- choices: [
- {
- checked: false,
- name: "Google Cloud Secret Manager (Used by deployed extensions and emulator)",
- value: SecretLocation.CLOUD.toString(),
- },
- {
- checked: false,
- name: "Local file (Used by emulator only)",
- value: SecretLocation.LOCAL.toString(),
- },
- ],
- });
- }
- async function promptLocalSecret(instanceId, paramSpec) {
- let value;
- do {
- utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
- value = await (0, prompt_1.promptOnce)({
- name: paramSpec.param,
- type: "input",
- message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
- `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
- });
- } while (!value);
- return value;
- }
- async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
- const action = await (0, prompt_1.promptOnce)({
- type: "list",
- message: `Choose what you would like to do with this secret:`,
- choices: [
- { name: "Leave unchanged", value: SecretUpdateAction.LEAVE },
- { name: "Set new value", value: SecretUpdateAction.SET_NEW },
- ],
- });
- switch (action) {
- case SecretUpdateAction.SET_NEW:
- let secret;
- let secretName;
- if (paramSpec.default) {
- secret = secretManagerApi.parseSecretResourceName(paramSpec.default);
- secretName = secret.name;
- }
- else {
- secretName = await generateSecretName(projectId, instanceId, paramSpec.param);
- }
- const secretValue = await (0, prompt_1.promptOnce)({
- name: paramSpec.param,
- type: "password",
- message: `This secret will be stored in Cloud Secret Manager as ${secretName}.\nEnter new value for ${paramSpec.label.trim()}:`,
- });
- if (secretValue === "" && paramSpec.required) {
- logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
- return promptReconfigureSecret(projectId, instanceId, paramSpec);
- }
- else if (secretValue !== "") {
- if (checkResponse(secretValue, paramSpec)) {
- if (!secret) {
- secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
- }
- return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
- }
- else {
- return promptReconfigureSecret(projectId, instanceId, paramSpec);
- }
- }
- else {
- return "";
- }
- case SecretUpdateAction.LEAVE:
- default:
- return paramSpec.default || "";
- }
- }
- async function promptCreateSecret(projectId, instanceId, paramSpec, secretName) {
- const name = secretName !== null && secretName !== void 0 ? secretName : (await generateSecretName(projectId, instanceId, paramSpec.param));
- const secretValue = await (0, prompt_1.promptOnce)({
- name: paramSpec.param,
- type: "password",
- default: paramSpec.default,
- message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${name} and managed by Firebase Extensions (Firebase Extensions Service Agent will be granted Secret Admin role on this secret).\nEnter a value for ${paramSpec.label.trim()}:`,
- });
- if (secretValue === "" && paramSpec.required) {
- logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
- return promptCreateSecret(projectId, instanceId, paramSpec, name);
- }
- else if (secretValue !== "") {
- if (checkResponse(secretValue, paramSpec)) {
- const secret = await secretManagerApi.createSecret(projectId, name, secretsUtils.getSecretLabels(instanceId));
- return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
- }
- else {
- return promptCreateSecret(projectId, instanceId, paramSpec, name);
- }
- }
- else {
- return "";
- }
- }
- exports.promptCreateSecret = promptCreateSecret;
- async function generateSecretName(projectId, instanceId, paramName) {
- let secretName = `ext-${instanceId}-${paramName}`;
- while (await secretManagerApi.secretExists(projectId, secretName)) {
- secretName += `-${(0, utils_1.getRandomString)(3)}`;
- }
- return secretName;
- }
- async function addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue) {
- const version = await secretManagerApi.addVersion(projectId, secret.name, secretValue);
- await secretsUtils.grantFirexServiceAgentSecretAdminRole(secret);
- return `projects/${version.secret.projectId}/secrets/${version.secret.name}/versions/${version.versionId}`;
- }
- function getInquirerDefault(options, def) {
- const defaultOption = options.find((o) => o.value === def);
- return defaultOption ? defaultOption.label || defaultOption.value : "";
- }
- exports.getInquirerDefault = getInquirerDefault;
|