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
9 changes: 2 additions & 7 deletions src/commands/frameworks-backends-get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,13 @@ const TABLE_HEAD = [
"Created Date",
"Updated Date",
];
export const command = new Command("backends:get")
export const command = new Command("backends:get <backendId>")
.description("Get backend details of a Firebase project")
.option("-l, --location <location>", "App Backend location", "-")
.option("-b, --backend <backend>", "Backend Id", "")
.before(ensureApiEnabled)
.action(async (options: Options) => {
.action(async (backendId: string, options: Options) => {
const projectId = needProjectId(options);
const location = options.location as string;
const backendId = options.backend as string;
if (!backendId) {
throw new FirebaseError("Backend id can't be empty.");
}

let backendsList: gcp.Backend[] = [];
const table = new Table({
Expand Down
139 changes: 48 additions & 91 deletions src/init/features/frameworks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as clc from "colorette";
import * as repo from "./repo";
import * as poller from "../../../operation-poller";
import * as gcp from "../../../gcp/frameworks";
import { logBullet, logSuccess } from "../../../utils";
import { logBullet, logSuccess, logWarning } from "../../../utils";
import { frameworksOrigin } from "../../../api";
import { Backend, BackendOutputOnlyFields } from "../../../gcp/frameworks";
import { Repository } from "../../../gcp/cloudbuild";
Expand All @@ -26,38 +26,46 @@ export async function doSetup(setup: any, projectId: string): Promise<void> {

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

await promptOnce(
{
name: "serviceName",
const location = await promptOnce({
name: "region",
type: "list",
default: DEFAULT_REGION,
message:
"Please select a region " +
`(${clc.yellow("info")}: Your region determines where your backend is located):\n`,
choices: ALLOWED_REGIONS,
});

logSuccess(`Region set to ${location}.\n`);

let backendId: string;
while (true) {
backendId = await promptOnce({
name: "backendId",
type: "input",
default: "acme-inc-web",
message: "Create a name for your backend [1-30 characters]",
},
setup.frameworks
);

await promptOnce(
{
name: "region",
type: "list",
default: DEFAULT_REGION,
message:
"Please select a region " +
`(${clc.yellow("info")}: Your region determines where your backend is located):\n`,
choices: ALLOWED_REGIONS,
},
setup.frameworks
);

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

const backend: Backend | undefined = await getOrCreateBackend(projectId, setup);
});
try {
await gcp.getBackend(projectId, location, backendId);
} catch (err: any) {
if (err.status === 404) {
break;
}
throw new FirebaseError(
`Failed to check if backend with id ${backendId} already exists in ${location}`,
{ original: err }
);
}
logWarning(`Backend with id ${backendId} already exists in ${location}`);
}
const backend: Backend = await onboardBackend(projectId, location, backendId);

if (backend) {
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}`
`View the rollout status by running:\n\tfirebase backends:get ${backendId} --project ${projectId}`
);
}
}
Expand All @@ -74,75 +82,24 @@ function toBackend(cloudBuildConnRepo: Repository): Omit<Backend, BackendOutputO
}

/**
* Creates backend if it doesn't exist.
* Walkthrough the flow for creating a new backend.
*/
export async function getOrCreateBackend(
projectId: string,
setup: any
): Promise<Backend | undefined> {
const location: string = setup.frameworks.region;
try {
return await getExistingBackend(projectId, setup, location);
} catch (err: unknown) {
if ((err as FirebaseError).status === 404) {
const cloudBuildConnRepo = await repo.linkGitHubRepository(projectId, location);
await promptOnce(
{
name: "branchName",
type: "input",
default: "main",
message: "Which branch do you want to deploy?",
},
setup.frameworks
);
const backendDetails = toBackend(cloudBuildConnRepo);
return await createBackend(projectId, location, backendDetails, setup.frameworks.serviceName);
} else {
throw new FirebaseError(
`Failed to get or create a backend using the given initialization details: ${err}`
);
}
}

return undefined;
}

async function getExistingBackend(
export async function onboardBackend(
projectId: string,
setup: any,
location: string
location: string,
backendId: string
): Promise<Backend> {
let backend = await gcp.getBackend(projectId, location, setup.frameworks.serviceName);
while (backend) {
setup.frameworks.serviceName = undefined;
await promptOnce(
{
name: "existingBackend",
type: "confirm",
default: true,
message:
"A backend already exists for the given serviceName, do you want to use existing backend? (yes/no)",
},
setup.frameworks
);
if (setup.frameworks.existingBackend) {
logBullet("Using the existing backend.");
return backend;
}
await promptOnce(
{
name: "serviceName",
type: "input",
default: "acme-inc-web",
message: "Please enter a new service name [1-30 characters]",
},
setup.frameworks
);
backend = await gcp.getBackend(projectId, location, setup.frameworks.serviceName);
setup.frameworks.existingBackend = undefined;
}

return backend;
const cloudBuildConnRepo = await repo.linkGitHubRepository(projectId, location);
const barnchName = await promptOnce({
name: "branchName",
type: "input",
default: "main",
message: "Which branch do you want to deploy?",
});
// branchName unused for now.
void barnchName;
const backendDetails = toBackend(cloudBuildConnRepo);
return await createBackend(projectId, location, backendDetails, backendId);
}

/**
Expand Down
36 changes: 13 additions & 23 deletions src/test/init/frameworks/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { expect } from "chai";
import * as gcp from "../../../gcp/frameworks";
import * as repo from "../../../init/features/frameworks/repo";
import * as poller from "../../../operation-poller";
import { createBackend, getOrCreateBackend } from "../../../init/features/frameworks/index";
import * as prompt from "../../../prompt";
import { createBackend, onboardBackend } from "../../../init/features/frameworks/index";
import { FirebaseError } from "../../../error";

describe("operationsConverter", () => {
Expand All @@ -14,6 +15,7 @@ describe("operationsConverter", () => {
let createBackendStub: sinon.SinonStub;
let getBackendStub: sinon.SinonStub;
let linkGitHubRepositoryStub: sinon.SinonStub;
let promptOnce: sinon.SinonStub;

beforeEach(() => {
pollOperationStub = sandbox
Expand All @@ -24,42 +26,38 @@ describe("operationsConverter", () => {
linkGitHubRepositoryStub = sandbox
.stub(repo, "linkGitHubRepository")
.throws("Unexpected getBackend call");
promptOnce = sandbox.stub(prompt, "promptOnce").throws("Unexpected promptOnce call");
});

afterEach(() => {
sandbox.verifyAndRestore();
});

describe("createBackend", () => {
describe("onboardBackend", () => {
const projectId = "projectId";
const location = "us-central1";
const backendId = "backendId";

const op = {
name: `projects/${projectId}/locations/${location}/backends/${backendId}`,
done: true,
};

const completeBackend = {
name: `projects/${projectId}/locations/${location}/backends/${backendId}`,
labels: {},
createTime: "0",
updateTime: "1",
uri: "https://placeholder.com",
};
const setup = {
frameworks: {
region: location,
serviceName: backendId,
existingBackend: true,
deployMethod: "github",
branchName: "main",
},
};

const cloudBuildConnRepo = {
name: `projects/${projectId}/locations/${location}/connections/framework-${location}/repositories/repoId`,
remoteUri: "remoteUri",
createTime: "0",
updateTime: "1",
};

const backendInput: Omit<gcp.Backend, gcp.BackendOutputOnlyFields> = {
servingLocality: "GLOBAL_ACCESS",
codebase: {
Expand All @@ -68,6 +66,7 @@ describe("operationsConverter", () => {
},
labels: {},
};

it("should createBackend", async () => {
createBackendStub.resolves(op);
pollOperationStub.resolves(completeBackend);
Expand All @@ -77,27 +76,18 @@ describe("operationsConverter", () => {
expect(createBackendStub).to.be.calledWith(projectId, location, backendInput);
});

it("should return a backend, if user wants use the exiting backend", async () => {
getBackendStub.resolves(completeBackend);

const result = await getOrCreateBackend("projectId", setup);

expect(result).to.deep.equal(completeBackend);
expect(getBackendStub.calledOnceWithExactly(projectId, location, backendId)).to.be.true;
});

it("should create a new backend, if backend doesn't exist", async () => {
it("should onboard a new backend", async () => {
const newBackendId = "newBackendId";
const newPath = `projects/${projectId}/locations/${location}/backends/${newBackendId}`;
setup.frameworks.serviceName = newBackendId;
op.name = newPath;
completeBackend.name = newPath;
getBackendStub.throws(new FirebaseError("error", { status: 404 }));
linkGitHubRepositoryStub.resolves(cloudBuildConnRepo);
createBackendStub.resolves(op);
pollOperationStub.resolves(completeBackend);
promptOnce.resolves("main");

const result = await getOrCreateBackend(projectId, setup);
const result = await onboardBackend(projectId, location, backendId);

expect(result).to.deep.equal(completeBackend);
expect(createBackendStub).to.be.calledWith(projectId, location, backendInput);
Expand Down