Skip to content

Commit 75cca8e

Browse files
taeoldannajowangjoehanrosalyntanaalej
authored
Merge next to master (#8360)
* Update "location" => "primary region" in the backend display table. (#8205) * Remove --location from apphosting:backend:list. (#8272) * Remove --location from apphosting:rollouts:create and error when more than one backend is found. (#8271) * Remove --location from apphosting:backends:get and return the first backend if multiple are found. (#8260) * Remove `--location` from apphosting:backends:delete (#8262) * Remove --location from apphosting:backends:create and prompt for primary region. (#8264) * Enable FDC connector evolution and insecure operations linter. (#8281) * Enable FDC connector evolution and insecure operations linter. * Enable in VSCode. * Actually get VSCode working. * Replace pkg with yao-pkg/pkg (#8328) Co-authored-by: aalej <[email protected]> * List regions of duplicate backend in warning/error message (#8285) * Update dataconnect.yaml template to v1. (#8346) * Remove support for node 18 (#8334) --------- Co-authored-by: annajowang <[email protected]> Co-authored-by: Joe Hanley <[email protected]> Co-authored-by: Rosalyn Tan <[email protected]> Co-authored-by: aalej <[email protected]>
1 parent 9018326 commit 75cca8e

File tree

17 files changed

+1490
-1077
lines changed

17 files changed

+1490
-1077
lines changed

.github/workflows/node-test.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ jobs:
4646
strategy:
4747
matrix:
4848
node-version:
49-
- "18"
5049
- "20"
5150
- "22"
5251
steps:
@@ -73,7 +72,6 @@ jobs:
7372
strategy:
7473
matrix:
7574
node-version:
76-
- "18"
7775
- "20"
7876

7977
env:
@@ -132,7 +130,6 @@ jobs:
132130
strategy:
133131
matrix:
134132
node-version:
135-
- "18"
136133
- "20"
137134
- "22"
138135
steps:

firebase-vscode/src/data-connect/toolkit.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ export class DataConnectToolkit implements vscode.Disposable {
3838
async startFDCToolkit(configDir: string, config: Config, RC: RC) {
3939
const port = await findOpenPort(DEFAULT_PORT);
4040
const settings = getSettings();
41+
42+
// Set the conn_evolution preview flag if it's not already set.
43+
const previewFlags = new Set(["conn_evolution"]);
44+
if (settings.extraEnv["DATA_CONNECT_PREVIEW"]) {
45+
settings.extraEnv["DATA_CONNECT_PREVIEW"].split(',').forEach(f => previewFlags.add(f));
46+
}
47+
4148
const toolkitArgs: DataConnectEmulatorArgs = {
4249
projectId: "toolkit",
4350
listen: [{ address: "localhost", port, family: "IPv4" }],
@@ -47,7 +54,7 @@ export class DataConnectToolkit implements vscode.Disposable {
4754
autoconnectToPostgres: false,
4855
enable_output_generated_sdk: true,
4956
enable_output_schema_extensions: true,
50-
extraEnv: settings.extraEnv,
57+
extraEnv: {...settings.extraEnv, ...{"DATA_CONNECT_PREVIEW": Array.from(previewFlags).join(',')}},
5158
};
5259
pluginLogger.info(`Starting Data Connect toolkit (version ${DataConnectToolkitController.getVersion()}) on port ${port}`);
5360
return DataConnectToolkitController.start(toolkitArgs);

npm-shrinkwrap.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
],
7070
"preferGlobal": true,
7171
"engines": {
72-
"node": ">=18.0.0 || >=20.0.0 || >=22.0.0"
72+
"node": ">=20.0.0 || >=22.0.0"
7373
},
7474
"author": "Firebase (https://firebase.google.com/)",
7575
"license": "MIT",

src/apphosting/backend.spec.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {
1212
promptLocation,
1313
setDefaultTrafficPolicy,
1414
ensureAppHostingComputeServiceAccount,
15+
chooseBackends,
1516
getBackendForAmbiguousLocation,
17+
getBackend,
1618
} from "./backend";
1719
import * as deploymentTool from "../deploymentTool";
1820
import { FirebaseError } from "../error";
@@ -266,6 +268,85 @@ describe("apphosting setup functions", () => {
266268
});
267269
});
268270

271+
describe("chooseBackends", () => {
272+
const backendChickenAsia = {
273+
name: `projects/${projectId}/locations/asia-east1/backends/chicken`,
274+
labels: {},
275+
createTime: "0",
276+
updateTime: "1",
277+
uri: "https://placeholder.com",
278+
};
279+
280+
const backendChickenEurope = {
281+
name: `projects/${projectId}/locations/europe-west4/backends/chicken`,
282+
labels: {},
283+
createTime: "0",
284+
updateTime: "1",
285+
uri: "https://placeholder.com",
286+
};
287+
288+
const backendChickenUS = {
289+
name: `projects/${projectId}/locations/us-central1/backends/chicken`,
290+
labels: {},
291+
createTime: "0",
292+
updateTime: "1",
293+
uri: "https://placeholder.com",
294+
};
295+
296+
const backendCow = {
297+
name: `projects/${projectId}/locations/asia-east1/backends/cow`,
298+
labels: {},
299+
createTime: "0",
300+
updateTime: "1",
301+
uri: "https://placeholder.com",
302+
};
303+
304+
const allBackends = [backendChickenAsia, backendChickenEurope, backendChickenUS, backendCow];
305+
306+
it("returns backend if only one is found", async () => {
307+
listBackendsStub.resolves({
308+
backends: allBackends,
309+
});
310+
311+
await expect(chooseBackends(projectId, "cow", /* prompt= */ "")).to.eventually.deep.equal([
312+
backendCow,
313+
]);
314+
});
315+
316+
it("throws if --force is used when multiple backends are found", async () => {
317+
listBackendsStub.resolves({
318+
backends: allBackends,
319+
});
320+
321+
await expect(
322+
chooseBackends(projectId, "chicken", /* prompt= */ "", /* force= */ true),
323+
).to.be.rejectedWith(
324+
"Force cannot be used because multiple backends were found with ID chicken.",
325+
);
326+
});
327+
328+
it("throws if no backend is found", async () => {
329+
listBackendsStub.resolves({
330+
backends: allBackends,
331+
});
332+
333+
await expect(chooseBackends(projectId, "farmer", /* prompt= */ "")).to.be.rejectedWith(
334+
'No backend named "farmer" found.',
335+
);
336+
});
337+
338+
it("lets user choose backends when more than one share a name", async () => {
339+
listBackendsStub.resolves({
340+
backends: allBackends,
341+
});
342+
promptOnceStub.resolves(["chicken(asia-east1)", "chicken(europe-west4)"]);
343+
344+
await expect(chooseBackends(projectId, "chicken", /* prompt= */ "")).to.eventually.deep.equal(
345+
[backendChickenAsia, backendChickenEurope],
346+
);
347+
});
348+
});
349+
269350
describe("getBackendForAmbiguousLocation", () => {
270351
const backendFoo = {
271352
name: `projects/${projectId}/locations/${location}/backends/foo`,
@@ -327,4 +408,57 @@ describe("apphosting setup functions", () => {
327408
});
328409
});
329410
});
411+
412+
describe("getBackend", () => {
413+
const backendChickenAsia = {
414+
name: `projects/${projectId}/locations/asia-east1/backends/chicken`,
415+
labels: {},
416+
createTime: "0",
417+
updateTime: "1",
418+
uri: "https://placeholder.com",
419+
};
420+
421+
const backendChickenEurope = {
422+
name: `projects/${projectId}/locations/europe-west4/backends/chicken`,
423+
labels: {},
424+
createTime: "0",
425+
updateTime: "1",
426+
uri: "https://placeholder.com",
427+
};
428+
429+
const backendCow = {
430+
name: `projects/${projectId}/locations/us-central1/backends/cow`,
431+
labels: {},
432+
createTime: "0",
433+
updateTime: "1",
434+
uri: "https://placeholder.com",
435+
};
436+
437+
const allBackends = [backendChickenAsia, backendChickenEurope, backendCow];
438+
439+
it("throws if more than one backend is found", async () => {
440+
listBackendsStub.resolves({ backends: allBackends });
441+
442+
await expect(getBackend(projectId, "chicken")).to.be.rejectedWith(
443+
"You have multiple backends with the same chicken ID in regions: " +
444+
"asia-east1, europe-west4. " +
445+
"This is not allowed until we can support more locations. " +
446+
"Please delete and recreate any backends that share an ID with another backend.",
447+
);
448+
});
449+
450+
it("throws if no backend is found", async () => {
451+
listBackendsStub.resolves({ backends: allBackends });
452+
453+
await expect(getBackend(projectId, "farmer")).to.be.rejectedWith(
454+
"No backend named farmer found.",
455+
);
456+
});
457+
458+
it("returns backend", async () => {
459+
listBackendsStub.resolves({ backends: allBackends });
460+
461+
await expect(getBackend(projectId, "cow")).to.eventually.equal(backendCow);
462+
});
463+
});
330464
});

src/apphosting/backend.ts

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ async function awaitTlsReady(url: string): Promise<void> {
7373
export async function doSetup(
7474
projectId: string,
7575
webAppName: string | null,
76-
location: string | null,
7776
serviceAccount: string | null,
7877
): Promise<void> {
7978
await Promise.all([
@@ -89,17 +88,13 @@ export async function doSetup(
8988
// possible to reduce the likelihood that the subsequent Cloud Build fails. See b/336862200.
9089
await ensureAppHostingComputeServiceAccount(projectId, serviceAccount);
9190

92-
const allowedLocations = (await apphosting.listLocations(projectId)).map((loc) => loc.locationId);
93-
if (location) {
94-
if (!allowedLocations.includes(location)) {
95-
throw new FirebaseError(
96-
`Invalid location ${location}. Valid choices are ${allowedLocations.join(", ")}`,
97-
);
98-
}
99-
}
100-
101-
location =
102-
location || (await promptLocation(projectId, "Select a location to host your backend:\n"));
91+
// TODO(https://github.com/firebase/firebase-tools/issues/8283): The "primary region"
92+
// is still "locations" in the V1 API. This will change in the V2 API and we may need to update
93+
// the variables and API methods we're calling under the hood when fetching "primary region".
94+
const location = await promptLocation(
95+
projectId,
96+
"Select a primary region to host your backend:\n",
97+
);
10398

10499
const gitRepositoryLink: GitRepositoryLink = await githubConnections.linkGitHubRepository(
105100
projectId,
@@ -429,6 +424,64 @@ export async function getBackendForLocation(
429424
}
430425
}
431426

427+
/**
428+
* Fetches backends of the given backendId and lets the user choose if more than one is found.
429+
*/
430+
export async function chooseBackends(
431+
projectId: string,
432+
backendId: string,
433+
chooseBackendPrompt: string,
434+
force?: boolean,
435+
): Promise<apphosting.Backend[]> {
436+
let { unreachable, backends } = await apphosting.listBackends(projectId, "-");
437+
if (unreachable && unreachable.length !== 0) {
438+
logWarning(
439+
`The following locations are currently unreachable: ${unreachable.join(",")}.\n` +
440+
"If your backend is in one of these regions, please try again later.",
441+
);
442+
}
443+
backends = backends.filter(
444+
(backend) => apphosting.parseBackendName(backend.name).id === backendId,
445+
);
446+
if (backends.length === 0) {
447+
throw new FirebaseError(`No backend named "${backendId}" found.`);
448+
}
449+
if (backends.length === 1) {
450+
return backends;
451+
}
452+
453+
if (force) {
454+
throw new FirebaseError(
455+
`Force cannot be used because multiple backends were found with ID ${backendId}.`,
456+
);
457+
}
458+
const backendsByDisplay = new Map<string, apphosting.Backend>();
459+
backends.forEach((backend) => {
460+
const { location, id } = apphosting.parseBackendName(backend.name);
461+
backendsByDisplay.set(`${id}(${location})`, backend);
462+
});
463+
const chosenBackendDisplays = await promptOnce({
464+
name: "backend",
465+
type: "checkbox",
466+
message: chooseBackendPrompt,
467+
choices: Array.from(backendsByDisplay.keys(), (name) => {
468+
return {
469+
checked: false,
470+
name: name,
471+
value: name,
472+
};
473+
}),
474+
});
475+
const chosenBackends: apphosting.Backend[] = [];
476+
chosenBackendDisplays.forEach((backendDisplay) => {
477+
const backend = backendsByDisplay.get(backendDisplay);
478+
if (backend !== undefined) {
479+
chosenBackends.push(backend);
480+
}
481+
});
482+
return chosenBackends;
483+
}
484+
432485
/**
433486
* Fetches a backend from the server. If there are multiple backends with that name (ie multi-regional backends),
434487
* prompts the user to disambiguate. If the force option is specified and multiple backends have the same name,
@@ -443,7 +496,7 @@ export async function getBackendForAmbiguousLocation(
443496
let { unreachable, backends } = await apphosting.listBackends(projectId, "-");
444497
if (unreachable && unreachable.length !== 0) {
445498
logWarning(
446-
`The following locations are currently unreachable: ${unreachable}.\n` +
499+
`The following locations are currently unreachable: ${unreachable.join(", ")}.\n` +
447500
"If your backend is in one of these regions, please try again later.",
448501
);
449502
}
@@ -474,3 +527,34 @@ export async function getBackendForAmbiguousLocation(
474527
});
475528
return backendsByLocation.get(location)!;
476529
}
530+
531+
/**
532+
* Fetches a backend from the server. If there are multiple backends with the name, it will throw an error
533+
* telling the user that there are other backends with the same name that need to be deleted.
534+
*/
535+
export async function getBackend(
536+
projectId: string,
537+
backendId: string,
538+
): Promise<apphosting.Backend> {
539+
let { unreachable, backends } = await apphosting.listBackends(projectId, "-");
540+
backends = backends.filter(
541+
(backend) => apphosting.parseBackendName(backend.name).id === backendId,
542+
);
543+
if (backends.length > 1) {
544+
const locations = backends.map((b) => apphosting.parseBackendName(b.name).location);
545+
throw new FirebaseError(
546+
`You have multiple backends with the same ${backendId} ID in regions: ${locations.join(", ")}. This is not allowed until we can support more locations. ` +
547+
"Please delete and recreate any backends that share an ID with another backend.",
548+
);
549+
}
550+
if (backends.length === 1) {
551+
return backends[0];
552+
}
553+
if (unreachable && unreachable.length !== 0) {
554+
logWarning(
555+
`Backends with the following primary regions are unreachable: ${unreachable.join(", ")}.\n` +
556+
"If your backend is in one of these regions, please try again later.",
557+
);
558+
}
559+
throw new FirebaseError(`No backend named ${backendId} found.`);
560+
}

0 commit comments

Comments
 (0)