Skip to content

Commit 023d964

Browse files
authored
Dynamically load available locations from App Hosting API (#6609)
Instead of hard-coding supported regions, we dynamically fetch the list of supported regions from the API. This is great especially when a org policy is set to restrict region availability for a project!
1 parent 3db6a6f commit 023d964

File tree

5 files changed

+54
-14
lines changed

5 files changed

+54
-14
lines changed

src/commands/frameworks-backends-create.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ensureApiEnabled } from "../gcp/frameworks";
77

88
export const command = new Command("backends:create")
99
.description("Create a backend in a Firebase project")
10+
.option("-l, --location <location>", "Specify the region of the backend", "")
1011
.before(ensureApiEnabled)
1112
.before(requireInteractive)
1213
.action(async (options: Options) => {

src/commands/frameworks-backends-delete.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as gcp from "../gcp/frameworks";
66
import { promptOnce } from "../prompt";
77
import * as utils from "../utils";
88
import { logger } from "../logger";
9-
import { DEFAULT_REGION, ALLOWED_REGIONS } from "../init/features/frameworks/constants";
9+
import { DEFAULT_REGION } from "../init/features/frameworks/constants";
1010
import { ensureApiEnabled } from "../gcp/frameworks";
1111

1212
const Table = require("cli-table");
@@ -36,12 +36,13 @@ export const command = new Command("backends:delete")
3636
}
3737

3838
if (!location) {
39+
const allowedLocations = (await gcp.listLocations(projectId)).map((loc) => loc.locationId);
3940
location = await promptOnce({
4041
name: "region",
4142
type: "list",
4243
default: DEFAULT_REGION,
4344
message: "Please select the region of the backend you'd like to delete:",
44-
choices: ALLOWED_REGIONS,
45+
choices: allowedLocations,
4546
});
4647
}
4748

src/gcp/frameworks.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ export async function getBackend(
125125
): Promise<Backend> {
126126
const name = `projects/${projectId}/locations/${location}/backends/${backendId}`;
127127
const res = await client.get<Backend>(name);
128-
129128
return res.body;
130129
}
131130

@@ -175,6 +174,32 @@ export async function createBuild(
175174
return res.body;
176175
}
177176

177+
export interface Location {
178+
name: string;
179+
locationId: string;
180+
}
181+
182+
interface ListLocationsResponse {
183+
locations: Location[];
184+
nextPageToken?: string;
185+
}
186+
187+
/**
188+
* Lists information about the supported locations.
189+
*/
190+
export async function listLocations(projectId: string): Promise<Location[]> {
191+
let pageToken;
192+
let locations: Location[] = [];
193+
do {
194+
const response = await client.get<ListLocationsResponse>(`projects/${projectId}/locations`);
195+
if (response.body.locations && response.body.locations.length > 0) {
196+
locations = locations.concat(response.body.locations);
197+
}
198+
pageToken = response.body.nextPageToken;
199+
} while (pageToken);
200+
return locations;
201+
}
202+
178203
/**
179204
* Ensure that Frameworks API is enabled on the project.
180205
*/
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export const DEFAULT_REGION = "us-central1";
2-
export const ALLOWED_REGIONS = [{ name: "us-central1 (Iowa)", value: "us-central1" }];
32
export const DEFAULT_DEPLOY_METHOD = "github";
43
export const ALLOWED_DEPLOY_METHODS = [{ name: "Deploy using github", value: "github" }];

src/init/features/frameworks/index.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Repository } from "../../../gcp/cloudbuild";
99
import { API_VERSION } from "../../../gcp/frameworks";
1010
import { FirebaseError } from "../../../error";
1111
import { promptOnce } from "../../../prompt";
12-
import { DEFAULT_REGION, ALLOWED_REGIONS } from "./constants";
12+
import { DEFAULT_REGION } from "./constants";
1313
import { ensure } from "../../../ensureApiEnabled";
1414

1515
const frameworksPollerOptions: Omit<poller.OperationPollerOptions, "operationResourceName"> = {
@@ -32,17 +32,19 @@ export async function doSetup(setup: any, projectId: string): Promise<void> {
3232
ensure(projectId, "artifactregistry.googleapis.com", "frameworks", true),
3333
]);
3434

35+
const allowedLocations = (await gcp.listLocations(projectId)).map((loc) => loc.locationId);
36+
37+
if (setup.location) {
38+
if (!allowedLocations.includes(setup.location)) {
39+
throw new FirebaseError(
40+
`Invalid location ${setup.location}. Valid choices are ${allowedLocations.join(", ")}`
41+
);
42+
}
43+
}
44+
3545
logBullet("First we need a few details to create your backend.");
3646

37-
const location = await promptOnce({
38-
name: "region",
39-
type: "list",
40-
default: DEFAULT_REGION,
41-
message:
42-
"Please select a region " +
43-
`(${clc.yellow("info")}: Your region determines where your backend is located):\n`,
44-
choices: ALLOWED_REGIONS,
45-
});
47+
const location: string = setup.location || (await promptLocation(projectId, allowedLocations));
4648

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

@@ -78,6 +80,18 @@ export async function doSetup(setup: any, projectId: string): Promise<void> {
7880
}
7981
}
8082

83+
async function promptLocation(projectId: string, locations: string[]): Promise<string> {
84+
return await promptOnce({
85+
name: "region",
86+
type: "list",
87+
default: DEFAULT_REGION,
88+
message:
89+
"Please select a region " +
90+
`(${clc.yellow("info")}: Your region determines where your backend is located):\n`,
91+
choices: locations.map((loc) => ({ value: loc })),
92+
});
93+
}
94+
8195
function toBackend(cloudBuildConnRepo: Repository): Omit<Backend, BackendOutputOnlyFields> {
8296
return {
8397
servingLocality: "GLOBAL_ACCESS",

0 commit comments

Comments
 (0)