in src/commands/ext-install.ts [56:217]
async function installExtension(options: InstallExtensionOptions): Promise<void> {
const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } =
options;
const spec = source?.spec || extVersion?.spec;
if (!spec) {
throw new FirebaseError(
`Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.`
);
}
const spinner = ora();
try {
await provisioningHelper.checkProductsProvisioned(projectId, spec);
const usesSecrets = secretsUtils.usesSecrets(spec);
if (spec.billingRequired || usesSecrets) {
const enabled = await checkBillingEnabled(projectId);
if (!enabled && nonInteractive) {
throw new FirebaseError(
`This extension requires the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
marked(
"Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project."
)
);
} else if (!enabled) {
await displayNode10CreateBillingNotice(spec, false);
await enableBilling(projectId);
} else {
await displayNode10CreateBillingNotice(spec, !nonInteractive);
}
}
const apis = spec.apis || [];
if (usesSecrets) {
apis.push({
apiName: "secretmanager.googleapis.com",
reason: `To access and manage secrets which are used by this extension. By using this product you agree to the terms and conditions of the following license: https://console.cloud.google.com/tos?id=cloud&project=${projectId}`,
});
}
if (apis.length) {
askUserForConsent.displayApis(spec.displayName || spec.name, projectId, apis);
const consented = await confirm({ nonInteractive, force, default: true });
if (!consented) {
throw new FirebaseError(
"Without explicit consent for the APIs listed, we cannot deploy this extension."
);
}
}
if (usesSecrets) {
await secretsUtils.ensureSecretManagerApiEnabled(options);
}
const roles = spec.roles ? spec.roles.map((role: extensionsApi.Role) => role.role) : [];
if (roles.length) {
await askUserForConsent.displayRoles(spec.displayName || spec.name, projectId, roles);
const consented = await confirm({ nonInteractive, force, default: true });
if (!consented) {
throw new FirebaseError(
"Without explicit consent for the roles listed, we cannot deploy this extension."
);
}
}
let instanceId = spec.name;
let choice: "updateExisting" | "installNew" | "cancel";
const anotherInstanceExists = await instanceIdExists(projectId, instanceId);
if (anotherInstanceExists) {
if (!nonInteractive) {
choice = await promptForRepeatInstance(projectId, spec.name);
} else if (nonInteractive && force) {
choice = "updateExisting";
} else {
throw new FirebaseError(
`An extension with the ID '${clc.bold(
extensionName
)}' already exists in the project '${clc.bold(projectId)}'.` +
` To update or reconfigure this instance instead, rerun this command with the --force flag.`
);
}
} else {
choice = "installNew";
}
let params: Record<string, string>;
switch (choice) {
case "installNew":
instanceId = await promptForValidInstanceId(`${instanceId}-${getRandomString(4)}`);
params = await paramHelper.getParams({
projectId,
paramSpecs: spec.params,
nonInteractive,
paramsEnvPath,
instanceId,
});
spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes...";
spinner.start();
await extensionsApi.createInstance({
projectId,
instanceId,
extensionSource: source,
extensionVersionRef: extVersion?.ref,
params,
});
spinner.stop();
utils.logLabeledSuccess(
logPrefix,
`Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
`Its Instance ID is ${clc.bold(instanceId)}.`
);
break;
case "updateExisting":
params = await paramHelper.getParams({
projectId,
paramSpecs: spec.params,
nonInteractive,
paramsEnvPath,
instanceId,
});
spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes...";
spinner.start();
await update({
projectId,
instanceId,
source,
extRef: extVersion?.ref,
params,
});
spinner.stop();
utils.logLabeledSuccess(
logPrefix,
`Successfully updated your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
`Its Instance ID is ${clc.bold(instanceId)}.`
);
break;
case "cancel":
return;
}
utils.logLabeledBullet(
logPrefix,
marked(
"Go to the Firebase console to view instructions for using your extension, " +
`which may include some required post-installation tasks: ${utils.consoleUrl(
projectId,
`/extensions/instances/${instanceId}?tab=usage`
)}`
)
);
logger.info(
marked(
"You can run `firebase ext` to view available Firebase Extensions commands, " +
"including those to update, reconfigure, or delete your installed extension."
)
);
} catch (err: any) {
if (spinner.isSpinning) {
spinner.fail();
}
if (err instanceof FirebaseError) {
throw err;
}
throw new FirebaseError(`Error occurred installing extension: ${err.message}`, {
original: err,
});
}
}