Skip to content

Commit 5122651

Browse files
authored
Remove --location from apphosting:rollouts:create and error when more than one backend is found. (#8271)
1 parent 7243acd commit 5122651

File tree

5 files changed

+103
-47
lines changed

5 files changed

+103
-47
lines changed

src/apphosting/backend.spec.ts

+53
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
setDefaultTrafficPolicy,
1414
ensureAppHostingComputeServiceAccount,
1515
getBackendForAmbiguousLocation,
16+
getBackend,
1617
} from "./backend";
1718
import * as deploymentTool from "../deploymentTool";
1819
import { FirebaseError } from "../error";
@@ -327,4 +328,56 @@ describe("apphosting setup functions", () => {
327328
});
328329
});
329330
});
331+
332+
describe("getBackend", () => {
333+
const backendChickenAsia = {
334+
name: `projects/${projectId}/locations/asia-east1/backends/chicken`,
335+
labels: {},
336+
createTime: "0",
337+
updateTime: "1",
338+
uri: "https://placeholder.com",
339+
};
340+
341+
const backendChickenEurope = {
342+
name: `projects/${projectId}/locations/europe-west4/backends/chicken`,
343+
labels: {},
344+
createTime: "0",
345+
updateTime: "1",
346+
uri: "https://placeholder.com",
347+
};
348+
349+
const backendCow = {
350+
name: `projects/${projectId}/locations/us-central1/backends/cow`,
351+
labels: {},
352+
createTime: "0",
353+
updateTime: "1",
354+
uri: "https://placeholder.com",
355+
};
356+
357+
const allBackends = [backendChickenAsia, backendChickenEurope, backendCow];
358+
359+
it("throws if more than one backend is found", async () => {
360+
listBackendsStub.resolves({ backends: allBackends });
361+
362+
await expect(getBackend(projectId, "chicken")).to.be.rejectedWith(
363+
"You have multiple backends with the same chicken ID. " +
364+
"This is not allowed until we can support more locations. " +
365+
"Please delete and recreate any backends that share an ID with another backend.",
366+
);
367+
});
368+
369+
it("throws if no backend is found", async () => {
370+
listBackendsStub.resolves({ backends: allBackends });
371+
372+
await expect(getBackend(projectId, "farmer")).to.be.rejectedWith(
373+
"No backend named farmer found.",
374+
);
375+
});
376+
377+
it("returns backend", async () => {
378+
listBackendsStub.resolves({ backends: allBackends });
379+
380+
await expect(getBackend(projectId, "cow")).to.eventually.equal(backendCow);
381+
});
382+
});
330383
});

src/apphosting/backend.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ export async function getBackendForAmbiguousLocation(
443443
let { unreachable, backends } = await apphosting.listBackends(projectId, "-");
444444
if (unreachable && unreachable.length !== 0) {
445445
logWarning(
446-
`The following locations are currently unreachable: ${unreachable}.\n` +
446+
`The following locations are currently unreachable: ${unreachable.join(", ")}.\n` +
447447
"If your backend is in one of these regions, please try again later.",
448448
);
449449
}
@@ -474,3 +474,33 @@ export async function getBackendForAmbiguousLocation(
474474
});
475475
return backendsByLocation.get(location)!;
476476
}
477+
478+
/**
479+
* Fetches a backend from the server. If there are multiple backends with the name, it will throw an error
480+
* telling the user that there are other backends with the same name that need to be deleted.
481+
*/
482+
export async function getBackend(
483+
projectId: string,
484+
backendId: string,
485+
): Promise<apphosting.Backend> {
486+
let { unreachable, backends } = await apphosting.listBackends(projectId, "-");
487+
backends = backends.filter(
488+
(backend) => apphosting.parseBackendName(backend.name).id === backendId,
489+
);
490+
if (backends.length > 1) {
491+
throw new FirebaseError(
492+
`You have multiple backends with the same ${backendId} ID. This is not allowed until we can support more locations. ` +
493+
"Please delete and recreate any backends that share an ID with another backend.",
494+
);
495+
}
496+
if (backends.length === 1) {
497+
return backends[0];
498+
}
499+
if (unreachable && unreachable.length !== 0) {
500+
logWarning(
501+
`Backends with the following primary regions are unreachable: ${unreachable.join(", ")}.\n` +
502+
"If your backend is in one of these regions, please try again later.",
503+
);
504+
}
505+
throw new FirebaseError(`No backend named ${backendId} found.`);
506+
}

src/apphosting/rollout.spec.ts

+14-25
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ describe("apphosting rollouts", () => {
2222
const gitRepoLinkId = `${user}-${repo}`;
2323
const buildAndRolloutId = "build-2024-10-01-001";
2424

25-
let getBackendForLocationStub: sinon.SinonStub;
26-
let getBackendForAmbiguousLocationStub: sinon.SinonStub;
25+
let getBackend: sinon.SinonStub;
2726
let getRepoDetailsFromBackendStub: sinon.SinonStub;
2827
let listAllBranchesStub: sinon.SinonStub;
2928
let getGitHubBranchStub: sinon.SinonStub;
@@ -36,12 +35,7 @@ describe("apphosting rollouts", () => {
3635
let sleepStub: sinon.SinonStub;
3736

3837
beforeEach(() => {
39-
getBackendForLocationStub = sinon
40-
.stub(backend, "getBackendForLocation")
41-
.throws("unexpected getBackendForLocation call");
42-
getBackendForAmbiguousLocationStub = sinon
43-
.stub(backend, "getBackendForAmbiguousLocation")
44-
.throws("unexpected getBackendForAmbiguousLocation call");
38+
getBackend = sinon.stub(backend, "getBackend").throws("unexpected getBackend call");
4539
getRepoDetailsFromBackendStub = sinon
4640
.stub(devConnect, "getRepoDetailsFromBackend")
4741
.throws("unexpected getRepoDetailsFromBackend call");
@@ -149,8 +143,7 @@ describe("apphosting rollouts", () => {
149143

150144
describe("createRollout", () => {
151145
it("should create a new rollout from user-specified branch", async () => {
152-
getBackendForLocationStub.resolves(backend);
153-
getBackendForAmbiguousLocationStub.resolves(backend);
146+
getBackend.resolves(backend);
154147
getRepoDetailsFromBackendStub.resolves(repoLinkDetails);
155148
listAllBranchesStub.resolves(branches);
156149
getGitHubBranchStub.resolves(branchInfo);
@@ -160,16 +153,15 @@ describe("apphosting rollouts", () => {
160153
pollOperationStub.onFirstCall().resolves(rollout);
161154
pollOperationStub.onSecondCall().resolves(build);
162155

163-
await createRollout(backendId, projectId, location, branchId, undefined, true);
156+
await createRollout(backendId, projectId, branchId, undefined, true);
164157

165158
expect(createBuildStub).to.be.called;
166159
expect(createRolloutStub).to.be.called;
167160
expect(pollOperationStub).to.be.called;
168161
});
169162

170163
it("should create a new rollout from user-specified commit", async () => {
171-
getBackendForLocationStub.resolves(backend);
172-
getBackendForAmbiguousLocationStub.resolves(backend);
164+
getBackend.resolves(backend);
173165
getRepoDetailsFromBackendStub.resolves(repoLinkDetails);
174166
getGitHubCommitStub.resolves(commitInfo);
175167
getNextRolloutIdStub.resolves(buildAndRolloutId);
@@ -178,16 +170,15 @@ describe("apphosting rollouts", () => {
178170
pollOperationStub.onFirstCall().resolves(rollout);
179171
pollOperationStub.onSecondCall().resolves(build);
180172

181-
await createRollout(backendId, projectId, location, undefined, commitSha, true);
173+
await createRollout(backendId, projectId, undefined, commitSha, true);
182174

183175
expect(createBuildStub).to.be.called;
184176
expect(createRolloutStub).to.be.called;
185177
expect(pollOperationStub).to.be.called;
186178
});
187179

188180
it("should prompt user for a branch if branch or commit ID is not specified", async () => {
189-
getBackendForLocationStub.resolves(backend);
190-
getBackendForAmbiguousLocationStub.resolves(backend);
181+
getBackend.resolves(backend);
191182
getRepoDetailsFromBackendStub.resolves(repoLinkDetails);
192183
promptGitHubBranchStub.resolves(branchId);
193184
getGitHubBranchStub.resolves(branchInfo);
@@ -197,7 +188,7 @@ describe("apphosting rollouts", () => {
197188
pollOperationStub.onFirstCall().resolves(rollout);
198189
pollOperationStub.onSecondCall().resolves(build);
199190

200-
await createRollout(backendId, projectId, location, undefined, undefined, false);
191+
await createRollout(backendId, projectId, undefined, undefined, false);
201192

202193
expect(promptGitHubBranchStub).to.be.called;
203194
expect(createBuildStub).to.be.called;
@@ -206,33 +197,31 @@ describe("apphosting rollouts", () => {
206197
});
207198

208199
it("should throw an error if GitHub branch is not found", async () => {
209-
getBackendForLocationStub.resolves(backend);
210-
getBackendForAmbiguousLocationStub.resolves(backend);
200+
getBackend.resolves(backend);
211201
getRepoDetailsFromBackendStub.resolves(repoLinkDetails);
212202
listAllBranchesStub.resolves(branches);
213203

214204
await expect(
215-
createRollout(backendId, projectId, location, "invalid-branch", undefined, true),
205+
createRollout(backendId, projectId, "invalid-branch", undefined, true),
216206
).to.be.rejectedWith(/Unrecognized git branch/);
217207
});
218208

219209
it("should throw an error if GitHub commit is not found", async () => {
220-
getBackendForLocationStub.resolves(backend);
221-
getBackendForAmbiguousLocationStub.resolves(backend);
210+
getBackend.resolves(backend);
222211
getRepoDetailsFromBackendStub.resolves(repoLinkDetails);
223212
getGitHubCommitStub.rejects(new FirebaseError("error", { status: 422 }));
224213

225214
await expect(
226-
createRollout(backendId, projectId, location, undefined, commitSha, true),
215+
createRollout(backendId, projectId, undefined, commitSha, true),
227216
).to.be.rejectedWith(/Unrecognized git commit/);
228217
});
229218

230219
it("should throw an error if --force flag is specified but --git-branch and --git-commit are missing", async () => {
231-
getBackendForLocationStub.resolves(backend);
220+
getBackend.resolves(backend);
232221
getRepoDetailsFromBackendStub.resolves(repoLinkDetails);
233222

234223
await expect(
235-
createRollout(backendId, projectId, location, undefined, undefined, true),
224+
createRollout(backendId, projectId, undefined, undefined, true),
236225
).to.be.rejectedWith(/Failed to create rollout with --force option/);
237226
});
238227
});

src/apphosting/rollout.ts

+4-14
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as poller from "../operation-poller";
1313
import { logBullet, sleep } from "../utils";
1414
import { apphostingOrigin, consoleOrigin } from "../api";
1515
import { DeepOmit } from "../metaprogramming";
16-
import { getBackendForAmbiguousLocation, getBackendForLocation } from "./backend";
16+
import { getBackend } from "./backend";
1717

1818
const apphostingPollerOptions: Omit<poller.OperationPollerOptions, "operationResourceName"> = {
1919
apiOrigin: apphostingOrigin(),
@@ -31,29 +31,19 @@ const GIT_COMMIT_SHA_REGEX = /^(?:[0-9a-f]{40}|[0-9a-f]{7})$/;
3131
export async function createRollout(
3232
backendId: string,
3333
projectId: string,
34-
location: string,
3534
branch?: string,
3635
commit?: string,
3736
force?: boolean,
3837
): Promise<void> {
39-
let backend: apphosting.Backend;
40-
if (location === "-" || location === "") {
41-
backend = await getBackendForAmbiguousLocation(
42-
projectId,
43-
backendId,
44-
"Please select the location of the backend you'd like to roll out:",
45-
force,
46-
);
47-
location = apphosting.parseBackendName(backend.name).location;
48-
} else {
49-
backend = await getBackendForLocation(projectId, location, backendId);
50-
}
38+
const backend = await getBackend(projectId, backendId);
5139

5240
if (!backend.codebase.repository) {
5341
throw new FirebaseError(
5442
`Backend ${backendId} is misconfigured due to missing a connected repository. You can delete and recreate your backend using 'firebase apphosting:backends:delete' and 'firebase apphosting:backends:create'.`,
5543
);
5644
}
45+
46+
const { location } = apphosting.parseBackendName(backend.name);
5747
const { repoLink, owner, repo, readToken } = await getRepoDetailsFromBackend(
5848
projectId,
5949
location,

src/commands/apphosting-rollouts-create.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import { Options } from "../options";
44
import { needProjectId } from "../projectUtils";
55
import { FirebaseError } from "../error";
66
import { createRollout } from "../apphosting/rollout";
7-
import { logWarning } from "../utils";
87

98
export const command = new Command("apphosting:rollouts:create <backendId>")
109
.description("create a rollout using a build for an App Hosting backend")
11-
.option("-l, --location <location>", "specify the region of the backend")
1210
.option(
1311
"-b, --git-branch <gitBranch>",
1412
"repository branch to deploy (mutually exclusive with -g)",
@@ -18,10 +16,6 @@ export const command = new Command("apphosting:rollouts:create <backendId>")
1816
.before(apphosting.ensureApiEnabled)
1917
.action(async (backendId: string, options: Options) => {
2018
const projectId = needProjectId(options);
21-
if (options.location !== undefined) {
22-
logWarning("--location is being removed in the next major release.");
23-
}
24-
const location = (options.location as string) ?? "-";
2519

2620
const branch = options.gitBranch as string | undefined;
2721
const commit = options.gitCommit as string | undefined;
@@ -31,5 +25,5 @@ export const command = new Command("apphosting:rollouts:create <backendId>")
3125
);
3226
}
3327

34-
await createRollout(backendId, projectId, location, branch, commit, options.force);
28+
await createRollout(backendId, projectId, branch, commit, options.force);
3529
});

0 commit comments

Comments
 (0)