Skip to content

Commit

Permalink
Consider builds and rollouts both when choosing the next id (#6876)
Browse files Browse the repository at this point in the history
* Consider builds and rollouts both when choosing the next id

* remove accidental dependency
  • Loading branch information
inlined committed Mar 13, 2024
1 parent 27088cd commit 2b6e3f0
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 18 deletions.
37 changes: 24 additions & 13 deletions src/gcp/apphosting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,30 +526,41 @@ export async function getNextRolloutId(
}

// Note: must use exports here so that listRollouts can be stubbed in tests.
const builds = await (exports as { listRollouts: typeof listRollouts }).listRollouts(
const rolloutsPromise = (exports as { listRollouts: typeof listRollouts }).listRollouts(
projectId,
location,
backendId,
);
if (builds.unreachable?.includes(location)) {
const buildsPromise = (exports as { listBuilds: typeof listBuilds }).listBuilds(
projectId,
location,
backendId,
);
const [rollouts, builds] = await Promise.all([rolloutsPromise, buildsPromise]);

if (builds.unreachable?.includes(location) || rollouts.unreachable?.includes(location)) {
throw new FirebaseError(
`Firebase App Hosting is currently unreachable in location ${location}`,
);
}

let highest = 0;
const test = new RegExp(
`projects/${projectId}/locations/${location}/backends/${backendId}/rollouts/build-${year}-${month}-${day}-(\\d+)`,
`projects/${projectId}/locations/${location}/backends/${backendId}/(rollouts|builds)/build-${year}-${month}-${day}-(\\d+)`,
);
for (const rollout of builds.rollouts) {
const match = rollout.name.match(test);
if (!match) {
continue;
}
const n = Number(match[1]);
if (n > highest) {
highest = n;
const highestId = (input: Array<{ name: string }>): number => {
let highest = 0;
for (const i of input) {
const match = i.name.match(test);
if (!match) {
continue;
}
const n = Number(match[2]);
if (n > highest) {
highest = n;
}
}
}
return highest;
};
const highest = Math.max(highestId(builds.builds), highestId(rollouts.rollouts));
return `build-${year}-${month}-${day}-${String(highest + 1).padStart(3, "0")}`;
}
98 changes: 93 additions & 5 deletions src/test/gcp/apphosting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import * as apphosting from "../../gcp/apphosting";
describe("apphosting", () => {
describe("getNextBuildId", () => {
let listRollouts: sinon.SinonStub;
let listBuilds: sinon.SinonStub;

beforeEach(() => {
listRollouts = sinon.stub(apphosting, "listRollouts");
listBuilds = sinon.stub(apphosting, "listBuilds");
});

afterEach(() => {
listRollouts.restore();
listBuilds.restore();
});

function idPrefix(date: Date): string {
Expand All @@ -27,11 +30,31 @@ describe("apphosting", () => {
expect(listRollouts).to.not.have.been.called;
});

it("should handle missing regions", async () => {
listRollouts.returns({
it("should handle missing regions (rollouts)", async () => {
listRollouts.resolves({
rollouts: [],
unreachable: ["us-central1"],
});
listBuilds.resolves({
builds: [],
unreachable: [],
});

await expect(
apphosting.getNextRolloutId("project", "us-central1", "backend"),
).to.be.rejectedWith(/unreachable .*us-central1/);
expect(listRollouts).to.have.been.calledWith("project", "us-central1", "backend");
});

it("should handle missing regions (builds)", async () => {
listRollouts.resolves({
rollouts: [],
unreachable: [],
});
listBuilds.resolves({
builds: [],
unreachable: ["us-central1"],
});

await expect(
apphosting.getNextRolloutId("project", "us-central1", "backend"),
Expand All @@ -40,10 +63,14 @@ describe("apphosting", () => {
});

it("should handle the first build of a day", async () => {
listRollouts.returns({
listRollouts.resolves({
rollouts: [],
unreachable: [],
});
listBuilds.resolves({
builds: [],
unreachable: [],
});

const id = await apphosting.getNextRolloutId("project", "location", "backend");
expect(id).equals(`${idPrefix(new Date())}-001`);
Expand All @@ -55,7 +82,7 @@ describe("apphosting", () => {
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);

listRollouts.returns({
listRollouts.resolves({
rollouts: [
{
name: `projects/project/locations/location/backends/backend/rollouts/${idPrefix(yesterday)}-005`,
Expand All @@ -66,6 +93,17 @@ describe("apphosting", () => {
],
unreachable: [],
});
listBuilds.resolves({
builds: [
{
name: `projects/project/locations/location/backends/backend/builds/${idPrefix(yesterday)}-005`,
},
{
name: `projects/project/locations/location/backends/backend/builds/${idPrefix(today)}-001`,
},
],
unreachable: [],
});

const id = await apphosting.getNextRolloutId("project", "location", "backend");
expect(id).to.equal(`${idPrefix(today)}-002`);
Expand All @@ -76,18 +114,68 @@ describe("apphosting", () => {
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);

listRollouts.returns({
listRollouts.resolves({
rollouts: [
{
name: `projects/project/locations/location/backends/backend/rollouts/${idPrefix(yesterday)}-005`,
},
],
unreachable: [],
});
listBuilds.resolves({
builds: [
{
name: `projects/project/locations/location/backends/backend/builds/${idPrefix(yesterday)}-005`,
},
],
unreachable: [],
});

const id = await apphosting.getNextRolloutId("project", "location", "backend");
expect(id).to.equal(`${idPrefix(today)}-001`);
});

it("should handle build & rollout names out of sync (build is latest)", async () => {
const today = new Date();
listRollouts.resolves({
rollouts: [
{
name: `projects/project/locations/location/backends/backend/rollouts/${idPrefix(today)}-001`,
},
],
});
listBuilds.resolves({
builds: [
{
name: `projects/project/locations/location/backends/backend/builds/${idPrefix(today)}-002`,
},
],
});

const id = await apphosting.getNextRolloutId("project", "location", "backend");
expect(id).to.equal(`${idPrefix(today)}-003`);
});

it("should handle build & rollout names out of sync (rollout is latest)", async () => {
const today = new Date();
listRollouts.resolves({
rollouts: [
{
name: `projects/project/locations/location/backends/backend/rollouts/${idPrefix(today)}-002`,
},
],
});
listBuilds.resolves({
builds: [
{
name: `projects/project/locations/location/backends/backend/builds/${idPrefix(today)}-001`,
},
],
});

const id = await apphosting.getNextRolloutId("project", "location", "backend");
expect(id).to.equal(`${idPrefix(today)}-003`);
});
});

describe("list APIs", () => {
Expand Down

0 comments on commit 2b6e3f0

Please sign in to comment.