Skip to content

Commit f0206c2

Browse files
authored
Add utility commands for app hosting. (#6616)
We haven't finalized the design of these commands, but they sure are handy for local testing.
1 parent f35a365 commit f0206c2

File tree

8 files changed

+149
-17
lines changed

8 files changed

+149
-17
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as apphosting from "../gcp/apphosting";
2+
import { logger } from "../logger";
3+
import { Command } from "../command";
4+
import { Options } from "../options";
5+
import { generateId } from "../utils";
6+
import { needProjectId } from "../projectUtils";
7+
8+
export const command = new Command("apphosting:builds:create <backendId>")
9+
.description("Create a build for an App Hosting backend")
10+
.option("-l, --location <location>", "Specify the region of the backend", "us-central1")
11+
.option("-i, --id <buildId>", "Id of the build. If not present, autogenerate a random id", "")
12+
.option("-b, --branch <branch>", "Repository branch to deploy. Defaults to 'main'", "main")
13+
.before(apphosting.ensureApiEnabled)
14+
.action(async (backendId: string, options: Options) => {
15+
const projectId = needProjectId(options);
16+
const location = options.location as string;
17+
const buildId = (options.buildId as string) || generateId();
18+
const branch = options.branch as string;
19+
20+
const op = await apphosting.createBuild(projectId, location, backendId, buildId, {
21+
source: {
22+
codebase: {
23+
branch: "main",
24+
},
25+
},
26+
});
27+
28+
logger.info(`Started a build for backend ${backendId} on branch ${branch}.`);
29+
logger.info("Check status by running:");
30+
logger.info(`\tfirebase apphosting:builds:get ${backendId} ${buildId} --location ${location}`);
31+
return op;
32+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as apphosting from "../gcp/apphosting";
2+
import { logger } from "../logger";
3+
import { Command } from "../command";
4+
import { Options } from "../options";
5+
import { needProjectId } from "../projectUtils";
6+
7+
export const command = new Command("apphosting:builds:get <backendId> <buildId>")
8+
.description("Create a build for an App Hosting backend")
9+
.option("-l, --location <location>", "Specify the region of the backend", "us-central1")
10+
.before(apphosting.ensureApiEnabled)
11+
.action(async (backendId: string, buildId: string, options: Options) => {
12+
const projectId = needProjectId(options);
13+
const location = options.location as string;
14+
const build = await apphosting.getBuild(projectId, location, backendId, buildId);
15+
logger.info(JSON.stringify(build, null, 2));
16+
return build;
17+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as apphosting from "../gcp/apphosting";
2+
import { logger } from "../logger";
3+
import { Command } from "../command";
4+
import { Options } from "../options";
5+
import { needProjectId } from "../projectUtils";
6+
import { generateId } from "../utils";
7+
8+
export const command = new Command("apphosting:rollouts:create <backendId> <buildId>")
9+
.description("Create a build for an App Hosting backend")
10+
.option("-l, --location <location>", "Specify the region of the backend", "us-central1")
11+
.option("-i, --id <rolloutId>", "Id of the rollout. If not present, autogenerate a random id", "")
12+
.before(apphosting.ensureApiEnabled)
13+
.action(async (backendId: string, buildId: string, options: Options) => {
14+
const projectId = needProjectId(options);
15+
const location = options.location as string;
16+
const rolloutId = (options.buildId as string) || generateId();
17+
const build = `projects/${projectId}/backends/${backendId}/builds/${buildId}`;
18+
const op = await apphosting.createRollout(projectId, location, backendId, rolloutId, {
19+
build,
20+
});
21+
logger.info(`Started a rollout for backend ${backendId} with build ${buildId}.`);
22+
logger.info("Check status by running:");
23+
logger.info(`\tfirebase apphosting:rollouts:list --location ${location}`);
24+
return op;
25+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as apphosting from "../gcp/apphosting";
2+
import { logger } from "../logger";
3+
import { Command } from "../command";
4+
import { Options } from "../options";
5+
import { needProjectId } from "../projectUtils";
6+
7+
export const command = new Command("apphosting:rollouts:list <backendId>")
8+
.description("List rollouts of an App Hosting backend")
9+
.option(
10+
"-l, --location <location>",
11+
"Rgion of the rollouts. Defaults to listing rollouts from all regions",
12+
"-"
13+
)
14+
.before(apphosting.ensureApiEnabled)
15+
.action(async (backendId: string, options: Options) => {
16+
const projectId = needProjectId(options);
17+
const location = options.location as string;
18+
const rollouts = await apphosting.listRollouts(projectId, location, backendId);
19+
logger.info(JSON.stringify(rollouts, null, 2));
20+
return rollouts;
21+
});

src/commands/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ export function load(client: any): any {
157157
client.apphosting.backends.create = loadCommand("apphosting-backends-create");
158158
client.apphosting.backends.get = loadCommand("apphosting-backends-get");
159159
client.apphosting.backends.delete = loadCommand("apphosting-backends-delete");
160+
client.apphosting.builds = {};
161+
client.apphosting.builds.get = loadCommand("apphosting-builds-get");
162+
client.apphosting.builds.create = loadCommand("apphosting-builds-create");
163+
client.apphosting.rollouts = {};
164+
client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
165+
client.apphosting.rollouts.list = loadCommand("apphosting-rollouts-list");
160166
}
161167
client.login = loadCommand("login");
162168
client.login.add = loadCommand("login-add");

src/gcp/apphosting.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,20 @@ export async function deleteBackend(
303303
return res.body;
304304
}
305305

306+
/**
307+
* Get a Build by Id
308+
*/
309+
export async function getBuild(
310+
projectId: string,
311+
location: string,
312+
backendId: string,
313+
buildId: string
314+
): Promise<Build> {
315+
const name = `projects/${projectId}/locations/${location}/backends/${backendId}/builds/${buildId}`;
316+
const res = await client.get<Build>(name);
317+
return res.body;
318+
}
319+
306320
/**
307321
* Creates a new Build in a given project and location.
308322
*/
@@ -322,7 +336,7 @@ export async function createBuild(
322336
}
323337

324338
/**
325-
* Create a new rollout for a backend
339+
* Create a new rollout for a backend.
326340
*/
327341
export async function createRollout(
328342
projectId: string,
@@ -340,7 +354,21 @@ export async function createRollout(
340354
}
341355

342356
/**
343-
* Update traffic of a backend
357+
* List all rollouts for a backend.
358+
*/
359+
export async function listRollouts(
360+
projectId: string,
361+
location: string,
362+
backendId: string
363+
): Promise<Rollout[]> {
364+
const res = await client.get<{ rollouts: Rollout[] }>(
365+
`projects/${projectId}/locations/${location}/backends/${backendId}/rollouts`
366+
);
367+
return res.body.rollouts;
368+
}
369+
370+
/**
371+
* Update traffic of a backend.
344372
*/
345373
export async function updateTraffic(
346374
projectId: string,

src/init/features/apphosting/index.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as clc from "colorette";
22
import * as repo from "./repo";
33
import * as poller from "../../../operation-poller";
44
import * as apphosting from "../../../gcp/apphosting";
5-
import { logBullet, logSuccess, logWarning } from "../../../utils";
5+
import { generateId, logBullet, logSuccess, logWarning } from "../../../utils";
66
import { apphostingOrigin } from "../../../api";
77
import {
88
Backend,
@@ -205,17 +205,3 @@ export async function onboardRollout(
205205
logSuccess("Rollout completed.");
206206
return { rollout, build };
207207
}
208-
209-
/**
210-
* Only lowercase, digits, and hyphens; must begin with letter, and cannot end with hyphen
211-
*/
212-
function generateId(n = 6): string {
213-
const letters = "abcdefghijklmnopqrstuvwxyz";
214-
const allChars = "01234567890-abcdefghijklmnopqrstuvwxyz";
215-
let id = letters[Math.floor(Math.random() * letters.length)];
216-
for (let i = 1; i < n; i++) {
217-
const idx = Math.floor(Math.random() * allChars.length);
218-
id += allChars[idx];
219-
}
220-
return id;
221-
}

src/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,3 +810,20 @@ export function getHostnameFromUrl(url: string): string | null {
810810
return null;
811811
}
812812
}
813+
814+
/**
815+
* Generate id meeting the following criterias:
816+
* - Lowercase, digits, and hyphens only
817+
* - Must begin with letter
818+
* - Cannot end with hyphen
819+
*/
820+
export function generateId(n = 6): string {
821+
const letters = "abcdefghijklmnopqrstuvwxyz";
822+
const allChars = "01234567890-abcdefghijklmnopqrstuvwxyz";
823+
let id = letters[Math.floor(Math.random() * letters.length)];
824+
for (let i = 1; i < n; i++) {
825+
const idx = Math.floor(Math.random() * allChars.length);
826+
id += allChars[idx];
827+
}
828+
return id;
829+
}

0 commit comments

Comments
 (0)