Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions src/init/features/frameworks/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import * as clc from "colorette";
import * as utils from "../../../utils";
import * as repo from "./repo";
import * as poller from "../../../operation-poller";
import * as gcp from "../../../gcp/frameworks";
import { logBullet, logSuccess } from "../../../utils";
import { frameworksOrigin } from "../../../api";
import { Backend, BackendOutputOnlyFields } from "../../../gcp/frameworks";
import { Repository } from "../../../gcp/cloudbuild";
import { API_VERSION } from "../../../gcp/frameworks";
import { FirebaseError } from "../../../error";
import { logger } from "../../../logger";
import { promptOnce } from "../../../prompt";
import { DEFAULT_REGION, ALLOWED_REGIONS } from "./constants";

Expand All @@ -25,7 +24,7 @@ const frameworksPollerOptions: Omit<poller.OperationPollerOptions, "operationRes
export async function doSetup(setup: any, projectId: string): Promise<void> {
setup.frameworks = {};

utils.logBullet("First we need a few details to create your backend.");
logBullet("First we need a few details to create your backend.");

await promptOnce(
{
Expand All @@ -50,20 +49,16 @@ export async function doSetup(setup: any, projectId: string): Promise<void> {
setup.frameworks
);

utils.logSuccess(`Region set to ${setup.frameworks.region}.`);
logSuccess(`Region set to ${setup.frameworks.region}.\n`);

const backend: Backend | undefined = await getOrCreateBackend(projectId, setup);

if (backend) {
logger.info();
utils.logSuccess(`Successfully created backend:\n ${backend.name}`);
logger.info();
utils.logSuccess(`Your site is being deployed at:\n https://${backend.uri}`);
logger.info();
utils.logSuccess(
`View the rollout status by running:\n firebase backends:get --backend=${backend.name}`
logSuccess(`Successfully created backend:\n\t${backend.name}`);
logSuccess(`Your site is being deployed at:\n\thttps://${backend.uri}`);
logSuccess(
`View the rollout status by running:\n\tfirebase backends:get --backend=${backend.name}`
);
logger.info();
}
}

Expand Down Expand Up @@ -91,7 +86,6 @@ export async function getOrCreateBackend(
} catch (err: unknown) {
if ((err as FirebaseError).status === 404) {
const cloudBuildConnRepo = await repo.linkGitHubRepository(projectId, location);
logger.info();
await promptOnce(
{
name: "branchName",
Expand All @@ -102,7 +96,6 @@ export async function getOrCreateBackend(
setup.frameworks
);
const backendDetails = toBackend(cloudBuildConnRepo);
logger.info(clc.bold(`\n${clc.white("===")} Creating your backend`));
return await createBackend(projectId, location, backendDetails, setup.frameworks.serviceName);
} else {
throw new FirebaseError(
Expand Down Expand Up @@ -133,7 +126,7 @@ async function getExistingBackend(
setup.frameworks
);
if (setup.frameworks.existingBackend) {
logger.info("Using the existing backend.");
logBullet("Using the existing backend.");
return backend;
}
await promptOnce(
Expand Down
37 changes: 23 additions & 14 deletions src/init/features/frameworks/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as poller from "../../../operation-poller";
import * as utils from "../../../utils";
import { cloudbuildOrigin } from "../../../api";
import { FirebaseError } from "../../../error";
import { logger } from "../../../logger";
import { promptOnce } from "../../../prompt";
import { getProjectNumber } from "../../../getProjectNumber";

Expand Down Expand Up @@ -80,7 +79,7 @@ export async function linkGitHubRepository(
projectId: string,
location: string
): Promise<gcb.Repository> {
logger.info(clc.bold(`\n${clc.yellow("===")} Connect a GitHub repository`));
utils.logBullet(clc.bold(`${clc.yellow("===")} Set up a GitHub connection`));
const existingConns = await listFrameworksConnections(projectId);
if (existingConns.length < 1) {
const grantSuccess = await promptSecretManagerAdminGrant(projectId);
Expand Down Expand Up @@ -124,8 +123,8 @@ export async function linkGitHubRepository(
appInstallationId: connection.githubConfig?.appInstallationId,
});
const repo = await getOrCreateRepository(projectId, location, connectionId, remoteUri);
logger.info();
utils.logSuccess(`Successfully linked GitHub repository at remote URI:\n ${remoteUri}`);
utils.logSuccess(`Successfully linked GitHub repository at remote URI`);
utils.logSuccess(`\t${remoteUri}`);
return repo;
}

Expand Down Expand Up @@ -165,37 +164,47 @@ async function promptRepositoryUri(
async function promptSecretManagerAdminGrant(projectId: string): Promise<Boolean> {
const projectNumber = await getProjectNumber({ projectId });
const cbsaEmail = gcb.serviceAgentEmail(projectNumber);
logger.info(

const alreadyGranted = await rm.serviceAccountHasRoles(
projectId,
cbsaEmail,
["roles/secretmanager.admin"],
true
);
if (alreadyGranted) {
return true;
}

utils.logBullet(
"To create a new GitHub connection, Secret Manager Admin role (roles/secretmanager.admin) is required on the Cloud Build Service Agent."
);
const grant = await promptOnce({
type: "confirm",
message: "Grant the required role to the Cloud Build Service Agent?",
});
if (!grant) {
logger.info(
utils.logBullet(
"You, or your project administrator, should run the following command to grant the required role:\n\n" +
"You, or your project adminstrator, can run the following command to grant the required role manually:\n\n" +
`\tgcloud projects add-iam-policy-binding ${projectId} \\\n` +
`\t --member="serviceAccount:${cbsaEmail} \\\n` +
`\t --role="roles/secretmanager.admin\n`
);
return false;
}
await rm.addServiceAccountToRoles(projectId, cbsaEmail, ["roles/secretmanager.admin"], true);
logger.info("Successfully granted the required role to the Cloud Build Service Agent!");
utils.logSuccess("Successfully granted the required role to the Cloud Build Service Agent!");
return true;
}

async function promptConnectionAuth(conn: gcb.Connection): Promise<gcb.Connection> {
logger.info("You must authorize the Cloud Build GitHub app.");
logger.info();
logger.info("Sign in to GitHub and authorize Cloud Build GitHub app:");
utils.logBullet("You must authorize the Cloud Build GitHub app.");
utils.logBullet("Sign in to GitHub and authorize Cloud Build GitHub app:");
const { url, cleanup } = await utils.openInBrowserPopup(
conn.installationState.actionUri,
"Authorize the GitHub app"
);
logger.info(`\t${url}`);
logger.info();
utils.logBullet(`\t${url}`);
await promptOnce({
type: "input",
message: "Press Enter once you have authorized the app",
Expand All @@ -206,9 +215,9 @@ async function promptConnectionAuth(conn: gcb.Connection): Promise<gcb.Connectio
}

async function promptAppInstall(conn: gcb.Connection): Promise<gcb.Connection> {
logger.info("Now, install the Cloud Build GitHub app:");
utils.logBullet("Install the Cloud Build GitHub app to enable access to GitHub repositories");
const targetUri = conn.installationState.actionUri.replace("install_v2", "direct_install_v2");
logger.info(targetUri);
utils.logBullet(targetUri);
await utils.openInBrowser(targetUri);
await promptOnce({
type: "input",
Expand Down