Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed an issue where Cloud Run rewrites in the Hosting emulator would always hit the live Cloud Run API instead of routing to the local functions emulator. (#10588)
- Updated the Firebase Data Connect local toolkit to v3.4.11, which includes the following changes:
- [changed] Updated the Golang dependency version to 1.25.11.
- Fixed issue where `apptesting:execute` command rejects documented `--test-username`, `--test-password`, and `--test-password-file` options.
Expand Down
55 changes: 54 additions & 1 deletion src/hosting/cloudRunProxy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@

import { cloudRunApiOrigin } from "../api";
import cloudRunProxy, { CloudRunProxyOptions, CloudRunProxyRewrite } from "./cloudRunProxy";
import { EmulatorRegistry } from "../emulator/registry";
import { Emulators } from "../emulator/types";
import { FakeEmulator } from "../emulator/testing/fakeEmulator";

describe("cloudRunProxy", () => {
const fakeOptions: CloudRunProxyOptions = {
project: "project-foo",
targets: [],
};
const fakeRewrite: CloudRunProxyRewrite = { run: { serviceId: "helloworld" } };
const cloudRunServiceOrigin = "https://helloworld-hash-uc.a.run.app";

afterEach(() => {
afterEach(async () => {
nock.cleanAll();
await EmulatorRegistry.stopAll();
});

it("should error when not provided a valid Cloud Run service ID", async () => {
Expand Down Expand Up @@ -216,7 +221,7 @@
const mw = await mwGenerator(fakeRewrite);
const spyMw = sinon.spy(mw);
const finalMw = sinon.stub().callsFake((_, res) => {
res.status(404).send("unknown response");

Check warning on line 224 in src/hosting/cloudRunProxy.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe call of an `any` typed value

Check warning on line 224 in src/hosting/cloudRunProxy.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .status on an `any` value

Check warning on line 224 in src/hosting/cloudRunProxy.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe call of an `any` typed value

Check warning on line 224 in src/hosting/cloudRunProxy.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .send on an `any` value
});

const app = express();
Expand Down Expand Up @@ -248,7 +253,7 @@
.expect(200, "cached page")
.then((res) => {
expect(spyMw.calledOnce).to.be.true;
expect(res.header["set-cookie"]).to.be.undefined;

Check warning on line 256 in src/hosting/cloudRunProxy.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access ["set-cookie"] on an `any` value
});
});

Expand All @@ -269,7 +274,7 @@
.expect(200, "live vary version")
.then((res) => {
expect(spyMw.calledOnce).to.be.true;
expect(res.header.vary).to.equal("Other, Authorization, Accept-Encoding, Cookie");

Check warning on line 277 in src/hosting/cloudRunProxy.spec.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe member access .vary on an `any` value
});
});

Expand Down Expand Up @@ -330,4 +335,52 @@
expect(spyMw.calledOnce).to.be.true;
});
});

it("should route to the local functions emulator when targets includes 'functions'", async () => {
const fakeFunctionsEmulator = new FakeEmulator(Emulators.FUNCTIONS, [
{ address: "127.0.0.1", family: "IPv4", port: 7778 },
]);
await EmulatorRegistry.start(fakeFunctionsEmulator);

nock("http://127.0.0.1:7778")
.get("/project-foo/us-central1/helloworld/")
.reply(200, "local version");

const options: CloudRunProxyOptions = { ...fakeOptions, targets: ["functions"] };

const mwGenerator = cloudRunProxy(options);
const mw = await mwGenerator(fakeRewrite);
const spyMw = sinon.spy(mw);

return supertest(spyMw)
.get("/")
.expect(200, "local version")
.then(() => {
expect(spyMw.calledOnce).to.be.true;
});
});

it("should route to the local functions emulator in another region", async () => {
const fakeFunctionsEmulator = new FakeEmulator(Emulators.FUNCTIONS, [
{ address: "127.0.0.1", family: "IPv4", port: 7778 },
]);
await EmulatorRegistry.start(fakeFunctionsEmulator);

nock("http://127.0.0.1:7778")
.get("/project-foo/asia-southeast1/helloworld/")
.reply(200, "local version");

const options: CloudRunProxyOptions = { ...fakeOptions, targets: ["functions"] };

const mwGenerator = cloudRunProxy(options);
const mw = await mwGenerator({ run: { serviceId: "helloworld", region: "asia-southeast1" } });
const spyMw = sinon.spy(mw);

return supertest(spyMw)
.get("/")
.expect(200, "local version")
.then(() => {
expect(spyMw.calledOnce).to.be.true;
});
});
});
15 changes: 15 additions & 0 deletions src/hosting/cloudRunProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import { FirebaseError } from "../error";
import { logger } from "../logger";
import { needProjectId } from "../projectUtils";
import { EmulatorRegistry } from "../emulator/registry";
import { Emulators } from "../emulator/types";
import { FunctionsEmulator } from "../emulator/functionsEmulator";

export interface CloudRunProxyOptions {
project?: string;
targets?: string[];
}

export interface CloudRunProxyRewrite {
Expand All @@ -23,7 +27,7 @@
const apiClient = new Client({ urlPrefix: cloudRunApiOrigin(), apiVersion: "v1" });

async function getCloudRunUrl(rewrite: CloudRunProxyRewrite, projectId: string): Promise<string> {
const alreadyFetched = cloudRunCache[`${rewrite.run.region}/${rewrite.run.serviceId}`];

Check warning on line 30 in src/hosting/cloudRunProxy.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "string | undefined" of template literal expression
if (alreadyFetched) {
return Promise.resolve(alreadyFetched);
}
Expand All @@ -39,10 +43,10 @@
throw new FirebaseError("Cloud Run URL doesn't exist in response.");
}

cloudRunCache[`${rewrite.run.region}/${rewrite.run.serviceId}`] = url;

Check warning on line 46 in src/hosting/cloudRunProxy.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "string | undefined" of template literal expression
return url;
} catch (err: any) {

Check warning on line 48 in src/hosting/cloudRunProxy.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
throw new FirebaseError(`Error looking up URL for Cloud Run service: ${err}`, {

Check warning on line 49 in src/hosting/cloudRunProxy.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Invalid type "any" of template literal expression
original: err,
});
}
Expand Down Expand Up @@ -70,6 +74,17 @@
logger.info(`[hosting] Cloud Run rewrite ${JSON.stringify(rewrite)} triggered`);

const textIdentifier = `Cloud Run service "${rewrite.run.serviceId}" for region "${rewrite.run.region}"`;

if (options.targets?.includes("functions") && EmulatorRegistry.isRunning(Emulators.FUNCTIONS)) {
const projectId = needProjectId(options);
const url = FunctionsEmulator.getHttpFunctionUrl(
projectId,
rewrite.run.serviceId,
rewrite.run.region,
);
return proxyRequestHandler(url, `local ${textIdentifier}`);
}

return getCloudRunUrl(rewrite, needProjectId(options))
.then((url) => proxyRequestHandler(url, textIdentifier))
.catch(errorRequestHandler);
Expand Down
Loading