Skip to content

Commit 30691fc

Browse files
authored
Init flow demo (#6218)
* testing * testing * Init demo flow * Create Stack request body changed * Updated CRUD API's for stacks * Updated changes * updated comments * Update CRUD options * resolved comments * resolved comments * resolved comments * Changing stack to backend in APIs * Added changes to modify api * minor change to region * Added error messages * catch block added * Added few changes * Returned stack * Return stack * Added return statements * Removed comments * changed comments * Added minor change * Format code * Format code
1 parent af7992d commit 30691fc

File tree

10 files changed

+211
-49
lines changed

10 files changed

+211
-49
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Command } from "../command";
2+
import { Options } from "../options";
3+
import { needProjectId } from "../projectUtils";
4+
import requireInteractive from "../requireInteractive";
5+
import { doSetup } from "../init/features/frameworks";
6+
7+
export const command = new Command("stacks:create")
8+
.description("Create a stack in a Firebase project")
9+
.before(requireInteractive)
10+
.action(async (options: Options) => {
11+
const projectId = needProjectId(options);
12+
await doSetup(options, projectId);
13+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Command } from "../command";
2+
import { Options } from "../options";
3+
import { needProjectId } from "../projectUtils";
4+
import { FirebaseError } from "../error";
5+
import * as gcp from "../gcp/frameworks";
6+
import { promptOnce } from "../prompt";
7+
import * as utils from "../utils";
8+
9+
export const command = new Command("stacks:delete")
10+
.description("Delete a stack from a Firebase project")
11+
.option("-l, --location <location>", "App Backend location", "us-central1")
12+
.option("-s, --stackId <stackId>", "Stack Id", "")
13+
.withForce()
14+
.action(async (options: Options) => {
15+
const projectId = needProjectId(options);
16+
const location = options.location as string;
17+
const stackId = options.stackId as string;
18+
if (!stackId) {
19+
throw new FirebaseError("Stack id can't be empty.");
20+
}
21+
const confirmDeletion = await promptOnce(
22+
{
23+
type: "confirm",
24+
name: "force",
25+
default: false,
26+
message: "You are about to delete the Stack with id: " + stackId + "\n Are you sure?",
27+
},
28+
options
29+
);
30+
if (!confirmDeletion) {
31+
throw new FirebaseError("Deletion aborted.");
32+
}
33+
34+
try {
35+
await gcp.deleteStack(projectId, location, stackId);
36+
utils.logSuccess(`Successfully deleted the stack: ${stackId}`);
37+
} catch (err: any) {
38+
throw new FirebaseError(
39+
`Failed to delete stack: ${stackId}. Please check the parameters you have provided.`,
40+
{ original: err }
41+
);
42+
}
43+
});

src/commands/frameworks-stacks-get.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Command } from "../command";
2+
import { Options } from "../options";
3+
import { needProjectId } from "../projectUtils";
4+
import * as gcp from "../gcp/frameworks";
5+
import { FirebaseError } from "../error";
6+
import { logger } from "../logger";
7+
8+
export const command = new Command("stacks:get")
9+
.description("Get stack details of a Firebase project")
10+
.option("-l, --location <location>", "App Backend location", "us-central1")
11+
.option("--s, --stackId <stackId>", "Stack Id", "")
12+
.action(async (options: Options) => {
13+
const projectId = needProjectId(options);
14+
const location = options.location as string;
15+
const stackId = options.stackId as string;
16+
if (!stackId) {
17+
throw new FirebaseError("Stack id can't be empty.");
18+
}
19+
20+
let stack;
21+
try {
22+
stack = await gcp.getStack(projectId, location, stackId);
23+
/**
24+
* TODO print this in a prettier way.
25+
*/
26+
logger.info(stack);
27+
} catch (err: any) {
28+
throw new FirebaseError(
29+
`Failed to get stack: ${stackId}. Please check the parameters you have provided.`,
30+
{ original: err }
31+
);
32+
}
33+
34+
return stack;
35+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Command } from "../command";
2+
import { Options } from "../options";
3+
import { needProjectId } from "../projectUtils";
4+
import * as gcp from "../gcp/frameworks";
5+
import { FirebaseError } from "../error";
6+
import { logger } from "../logger";
7+
8+
export const command = new Command("stacks:list")
9+
.description("List stacks of a Firebase project.")
10+
.option("-l, --location <location>", "App Backend location", "us-central1")
11+
.action(async (options: Options) => {
12+
const projectId = needProjectId(options);
13+
const location = options.location as string;
14+
15+
let stacks;
16+
try {
17+
stacks = await gcp.listStack(projectId, location);
18+
/**
19+
* TODO print this in a prettier way.
20+
*/
21+
logger.info(stacks);
22+
} catch (err: any) {
23+
throw new FirebaseError(
24+
`Unable to list stacks present in project: ${projectId}. Please check the parameters you have provided.`,
25+
{ original: err }
26+
);
27+
}
28+
29+
return stacks;
30+
});

src/commands/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ export function load(client: any): any {
151151
client.internaltesting.functions = {};
152152
client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover");
153153
}
154+
if (experiments.isEnabled("internalframeworks")) {
155+
client.frameworks = {};
156+
client.frameworks.stacks = {};
157+
client.frameworks.stacks.list = loadCommand("frameworks-stacks-list");
158+
client.frameworks.stacks.create = loadCommand("frameworks-stacks-create");
159+
client.frameworks.stacks.create = loadCommand("frameworks-stacks-get");
160+
client.frameworks.stacks.create = loadCommand("frameworks-stacks-delete");
161+
}
154162
client.login = loadCommand("login");
155163
client.login.add = loadCommand("login-add");
156164
client.login.ci = loadCommand("login-ci");

src/gcp/frameworks.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface Stack {
2727
uri: string;
2828
}
2929

30-
export type StackOutputOnlyFields = "createTime" | "updateTime" | "uri" | "codebase";
30+
export type StackOutputOnlyFields = "name" | "createTime" | "updateTime" | "uri";
3131

3232
export interface Build {
3333
name: string;
@@ -81,19 +81,23 @@ export interface Operation {
8181
// end oneof result
8282
}
8383

84+
export interface ListStacksResponse {
85+
stacks: Stack[];
86+
}
87+
8488
/**
8589
* Creates a new Stack in a given project and location.
8690
*/
8791
export async function createStack(
8892
projectId: string,
8993
location: string,
90-
stackInput: Omit<Stack, StackOutputOnlyFields>
94+
stackReqBoby: Omit<Stack, StackOutputOnlyFields>,
95+
backendId: string
9196
): Promise<Operation> {
92-
const stackId = stackInput.name;
9397
const res = await client.post<Omit<Stack, StackOutputOnlyFields>, Operation>(
94-
`projects/${projectId}/locations/${location}/stacks`,
95-
stackInput,
96-
{ queryParams: { stackId } }
98+
`projects/${projectId}/locations/${location}/backends`,
99+
stackReqBoby,
100+
{ queryParams: { backendId } }
97101
);
98102

99103
return res.body;
@@ -107,12 +111,36 @@ export async function getStack(
107111
location: string,
108112
stackId: string
109113
): Promise<Stack> {
110-
const name = `projects/${projectId}/locations/${location}/stacks/${stackId}`;
114+
const name = `projects/${projectId}/locations/${location}/backends/${stackId}`;
111115
const res = await client.get<Stack>(name);
112116

113117
return res.body;
114118
}
115119

120+
/**
121+
* List all stacks present in a project and region.
122+
*/
123+
export async function listStack(projectId: string, location: string): Promise<ListStacksResponse> {
124+
const name = `projects/${projectId}/locations/${location}/backends`;
125+
const res = await client.get<ListStacksResponse>(name);
126+
127+
return res.body;
128+
}
129+
130+
/**
131+
* Deletes a Stack with stackId in a given project and location.
132+
*/
133+
export async function deleteStack(
134+
projectId: string,
135+
location: string,
136+
stackId: string
137+
): Promise<Operation> {
138+
const name = `projects/${projectId}/locations/${location}/backends/${stackId}`;
139+
const res = await client.delete<Operation>(name);
140+
141+
return res.body;
142+
}
143+
116144
/**
117145
* Creates a new Build in a given project and location.
118146
*/
@@ -124,7 +152,7 @@ export async function createBuild(
124152
): Promise<Operation> {
125153
const buildId = buildInput.name;
126154
const res = await client.post<Omit<Build, BuildOutputOnlyFields>, Operation>(
127-
`projects/${projectId}/locations/${location}/stacks/${stackId}/builds`,
155+
`projects/${projectId}/locations/${location}/backends/${stackId}/builds`,
128156
buildInput,
129157
{ queryParams: { buildId } }
130158
);

src/init/features/frameworks/index.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ const frameworksPollerOptions: Omit<poller.OperationPollerOptions, "operationRes
2727
/**
2828
* Setup new frameworks project.
2929
*/
30-
export async function doSetup(setup: any): Promise<void> {
31-
const projectId: string = setup?.rcfile?.projects?.default;
30+
export async function doSetup(setup: any, projectId: string): Promise<void> {
3231
setup.frameworks = {};
3332

3433
utils.logBullet("First we need a few details to create your service.");
@@ -71,15 +70,18 @@ export async function doSetup(setup: any): Promise<void> {
7170
setup.frameworks
7271
);
7372

74-
await getOrCreateStack(projectId, setup);
73+
const stack: Stack | undefined = await getOrCreateStack(projectId, setup);
74+
if (stack) {
75+
utils.logSuccess(`Successfully created a stack: ${stack.name}`);
76+
}
7577
}
7678

77-
function toStack(
78-
cloudBuildConnRepo: Repository,
79-
stackId: string
80-
): Omit<Stack, StackOutputOnlyFields> {
79+
function toStack(cloudBuildConnRepo: Repository): Omit<Stack, StackOutputOnlyFields> {
8180
return {
82-
name: stackId,
81+
codebase: {
82+
repository: `${cloudBuildConnRepo.name}`,
83+
rootDirectory: "/",
84+
},
8385
labels: {},
8486
};
8587
}
@@ -96,13 +98,9 @@ export async function getOrCreateStack(projectId: string, setup: any): Promise<S
9698
if ((err as FirebaseError).status === 404) {
9799
logger.info("Creating new stack.");
98100
if (deployMethod === "github") {
99-
const cloudBuildConnRepo = await repo.linkGitHubRepository(
100-
projectId,
101-
location,
102-
setup.frameworks.serviceName
103-
);
104-
const stackDetails = toStack(cloudBuildConnRepo, setup.frameworks.serviceName);
105-
return await createStack(projectId, location, stackDetails);
101+
const cloudBuildConnRepo = await repo.linkGitHubRepository(projectId, location);
102+
const stackDetails = toStack(cloudBuildConnRepo);
103+
return await createStack(projectId, location, stackDetails, setup.frameworks.serviceName);
106104
}
107105
} else {
108106
throw new FirebaseError(
@@ -154,12 +152,13 @@ async function getExistingStack(projectId: string, setup: any, location: string)
154152
export async function createStack(
155153
projectId: string,
156154
location: string,
157-
stackInput: Omit<Stack, StackOutputOnlyFields>
155+
stackReqBoby: Omit<Stack, StackOutputOnlyFields>,
156+
stackId: string
158157
): Promise<Stack> {
159-
const op = await gcp.createStack(projectId, location, stackInput);
158+
const op = await gcp.createStack(projectId, location, stackReqBoby, stackId);
160159
const stack = await poller.pollOperation<Stack>({
161160
...frameworksPollerOptions,
162-
pollerName: `create-${projectId}-${location}-${stackInput.name}`,
161+
pollerName: `create-${projectId}-${location}-${stackId}`,
163162
operationResourceName: op.name,
164163
});
165164

src/init/features/frameworks/repo.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,31 @@ function extractRepoSlugFromURI(remoteUri: string): string | undefined {
2727

2828
/**
2929
* Generates a repository ID.
30-
* N.B. The deterministic nature of the repository ID implies that each
31-
* Cloud Build Connection will have one Cloud Build Repo child resource.
32-
* The current implementation is subject to change in the event that
33-
* the 1:1 Connection-to-Resource relationship no longer holds.
30+
* The relation is 1:* between Cloud Build Connection and Github Repositories.
3431
*/
35-
function generateRepositoryId(): string | undefined {
36-
return `composer-repo`;
32+
function generateRepositoryId(remoteUri: string): string | undefined {
33+
return extractRepoSlugFromURI(remoteUri)?.replaceAll("/", "-");
34+
}
35+
36+
/**
37+
* The 'frameworks-' is prefixed, to seperate the Cloud Build connections created from
38+
* Frameworks platforms with rest of manually created Cloud Build connections.
39+
*
40+
* The reason suffix 'location' is because of
41+
* 1:1 relation between location and Cloud Build connection.
42+
*/
43+
function generateConnectionId(location: string): string {
44+
return `frameworks-${location}`;
3745
}
3846

3947
/**
4048
* Prompts the user to link their stack to a GitHub repository.
4149
*/
4250
export async function linkGitHubRepository(
4351
projectId: string,
44-
location: string,
45-
stackId: string
52+
location: string
4653
): Promise<gcb.Repository> {
47-
const connectionId = stackId;
54+
const connectionId = generateConnectionId(location);
4855
await getOrCreateConnection(projectId, location, connectionId);
4956

5057
let remoteUri = await promptRepositoryURI(projectId, location, connectionId);
@@ -147,7 +154,7 @@ export async function getOrCreateRepository(
147154
connectionId: string,
148155
remoteUri: string
149156
): Promise<gcb.Repository> {
150-
const repositoryId = generateRepositoryId();
157+
const repositoryId = generateRepositoryId(remoteUri);
151158
if (!repositoryId) {
152159
throw new FirebaseError(`Failed to generate repositoryId for URI "${remoteUri}".`);
153160
}

src/test/init/frameworks/index.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@ describe("operationsConverter", () => {
3434
const projectId = "projectId";
3535
const location = "us-central1";
3636
const stackId = "stackId";
37-
const stackInput = {
38-
name: stackId,
39-
labels: {},
40-
};
4137
const op = {
4238
name: `projects/${projectId}/locations/${location}/stacks/${stackId}`,
4339
done: true,
@@ -58,17 +54,23 @@ describe("operationsConverter", () => {
5854
},
5955
};
6056
const cloudBuildConnRepo = {
61-
name: `projects/${projectId}/locations/${location}/stacks/${stackId}`,
57+
name: `projects/${projectId}/locations/${location}/connections/framework-${location}/repositories/repoId`,
6258
remoteUri: "remoteUri",
6359
createTime: "0",
6460
updateTime: "1",
6561
};
66-
62+
const stackInput = {
63+
codebase: {
64+
repository: cloudBuildConnRepo.name,
65+
rootDirectory: "/",
66+
},
67+
labels: {},
68+
};
6769
it("should createStack", async () => {
6870
createStackStub.resolves(op);
6971
pollOperationStub.resolves(completeStack);
7072

71-
await createStack(projectId, location, stackInput);
73+
await createStack(projectId, location, stackInput, stackId);
7274

7375
expect(createStackStub).to.be.calledWith(projectId, location, stackInput);
7476
});
@@ -86,10 +88,8 @@ describe("operationsConverter", () => {
8688
const newStackId = "newStackId";
8789
const newPath = `projects/${projectId}/locations/${location}/stacks/${newStackId}`;
8890
setup.frameworks.serviceName = newStackId;
89-
stackInput.name = newStackId;
9091
op.name = newPath;
9192
completeStack.name = newPath;
92-
cloudBuildConnRepo.name = newPath;
9393
getStackStub.throws(new FirebaseError("error", { status: 404 }));
9494
linkGitHubRepositoryStub.resolves(cloudBuildConnRepo);
9595
createStackStub.resolves(op);

0 commit comments

Comments
 (0)