From 3efaec8ef24fc52fdfb94686a0d0e65d47a630f4 Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Thu, 11 Dec 2025 14:30:25 -0800 Subject: [PATCH 1/8] Data Connect deploys should lock down the function to the FDC P4SA. --- src/deploy/functions/prepare.ts | 1 + src/deploy/functions/services/dataconnect.ts | 29 ++++++++++++++++++++ src/deploy/functions/services/index.ts | 4 +-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 0cc513dda0b..5c3b601e035 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -112,6 +112,7 @@ export async function prepare( // This drives GA4 metric `has_runtime_config` in the functions deploy reporter. context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase"); + // TODO: Modify to also load dataconnect schema if `onGraphRequest` is used with `schemaFilePath`? const wantBuilds = await loadCodebases( context.config, options, diff --git a/src/deploy/functions/services/dataconnect.ts b/src/deploy/functions/services/dataconnect.ts index 9ef1042798e..ada7dfeec26 100644 --- a/src/deploy/functions/services/dataconnect.ts +++ b/src/deploy/functions/services/dataconnect.ts @@ -1,5 +1,9 @@ import * as backend from "../backend"; +import { dataconnectOrigin } from "../../../api"; import { FirebaseError } from "../../../error"; +import { iam } from "../../../gcp"; + +const CLOUD_RUN_INVOKER_ROLE = "roles/cloudrun.invoker"; /** * Sets a Firebase Data Connect event trigger's region to the function region. @@ -18,3 +22,28 @@ export function ensureDataConnectTriggerRegion( } return Promise.resolve(); } + +function getServiceAccount(projectNumber: string): string { + if (dataconnectOrigin().includes("autopush")) { + return `service-${projectNumber}@gcp-sa-autopush-dataconnect.iam.gserviceaccount.com`; + } + if (dataconnectOrigin().includes("staging")) { + return `service-${projectNumber}@gcp-sa-staging-dataconnect.iam.gserviceaccount.com`; + } + return `service-${projectNumber}@gcp-sa-firebasedataconnect.iam.gserviceaccount.com`; +} + +/** + * Finds the required project level IAM bindings for the Firebase Data Connect service agent + * @param projectNumber project identifier + */ +export async function obtainDataConnectBindings( + projectNumber: string, +): Promise> { + const dataConnectServiceAgent = `serviceAccount:${getServiceAccount(projectNumber)}`; + const cloudRunInvokerBinding = { + role: CLOUD_RUN_INVOKER_ROLE, + members: [dataConnectServiceAgent], + }; + return [cloudRunInvokerBinding]; +} diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 9b2dbbdc4e7..a85f392b4cd 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -8,7 +8,7 @@ import { ensureDatabaseTriggerRegion } from "./database"; import { ensureRemoteConfigTriggerRegion } from "./remoteConfig"; import { ensureTestLabTriggerRegion } from "./testLab"; import { ensureFirestoreTriggerRegion } from "./firestore"; -import { ensureDataConnectTriggerRegion } from "./dataconnect"; +import { ensureDataConnectTriggerRegion, obtainDataConnectBindings } from "./dataconnect"; /** A standard void No Op */ export const noop = (): Promise => Promise.resolve(); @@ -136,7 +136,7 @@ const firestoreService: Service = { const dataconnectService: Service = { name: "dataconnect", api: "firebasedataconnect.googleapis.com", - requiredProjectBindings: noopProjectBindings, + requiredProjectBindings: obtainDataConnectBindings, ensureTriggerRegion: ensureDataConnectTriggerRegion, validateTrigger: noop, registerTrigger: noop, From 1e2b1e7fddeb054f89d9439de2596939838ec329 Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Thu, 11 Dec 2025 14:52:52 -0800 Subject: [PATCH 2/8] Some style nits. --- src/deploy/functions/services/dataconnect.ts | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/deploy/functions/services/dataconnect.ts b/src/deploy/functions/services/dataconnect.ts index ada7dfeec26..f44ca3d704f 100644 --- a/src/deploy/functions/services/dataconnect.ts +++ b/src/deploy/functions/services/dataconnect.ts @@ -3,7 +3,10 @@ import { dataconnectOrigin } from "../../../api"; import { FirebaseError } from "../../../error"; import { iam } from "../../../gcp"; -const CLOUD_RUN_INVOKER_ROLE = "roles/cloudrun.invoker"; +const CLOUD_RUN_INVOKER_ROLE = "roles/run.invoker"; +const AUTOPUSH_DATACONNECT_SA_DOMAIN = "gcp-sa-autopush-dataconnect.iam.gserviceaccount.com"; +const STAGING_DATACONNECT_SA_DOMAIN = "gcp-sa-staging-dataconnect.iam.gserviceaccount.com"; +const PROD_DATACONNECT_SA_DOMAIN = "gcp-sa-firebasedataconnect.iam.gserviceaccount.com"; /** * Sets a Firebase Data Connect event trigger's region to the function region. @@ -24,26 +27,25 @@ export function ensureDataConnectTriggerRegion( } function getServiceAccount(projectNumber: string): string { - if (dataconnectOrigin().includes("autopush")) { - return `service-${projectNumber}@gcp-sa-autopush-dataconnect.iam.gserviceaccount.com`; + const origin = dataconnectOrigin(); + if (origin.includes("autopush")) { + return `service-${projectNumber}@${AUTOPUSH_DATACONNECT_SA_DOMAIN}`; } - if (dataconnectOrigin().includes("staging")) { - return `service-${projectNumber}@gcp-sa-staging-dataconnect.iam.gserviceaccount.com`; + if (origin.includes("staging")) { + return `service-${projectNumber}@${STAGING_DATACONNECT_SA_DOMAIN}`; } - return `service-${projectNumber}@gcp-sa-firebasedataconnect.iam.gserviceaccount.com`; + return `service-${projectNumber}@${PROD_DATACONNECT_SA_DOMAIN}`; } /** * Finds the required project level IAM bindings for the Firebase Data Connect service agent * @param projectNumber project identifier */ -export async function obtainDataConnectBindings( - projectNumber: string, -): Promise> { +export function obtainDataConnectBindings(projectNumber: string): Promise> { const dataConnectServiceAgent = `serviceAccount:${getServiceAccount(projectNumber)}`; const cloudRunInvokerBinding = { role: CLOUD_RUN_INVOKER_ROLE, members: [dataConnectServiceAgent], }; - return [cloudRunInvokerBinding]; + return Promise.resolve([cloudRunInvokerBinding]); } From 2ecde62593f1200bab16bc28576e7122ddc8023a Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Tue, 16 Dec 2025 14:15:28 -0800 Subject: [PATCH 3/8] Handle the dataConnectHttpsTrigger type. --- src/deploy/functions/backend.ts | 20 +++++++++++++++ src/deploy/functions/build.ts | 25 +++++++++++++++++++ .../functions/runtimes/discovery/v1alpha1.ts | 12 +++++++++ src/deploy/functions/services/index.ts | 4 +++ 4 files changed, 61 insertions(+) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 411ed90d466..70c2be1d362 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -43,6 +43,16 @@ export interface HttpsTriggered { httpsTrigger: HttpsTrigger; } +/** API agnostic version of a Firebase Data Connect HTTPS trigger. */ +export interface DataConnectHttpsTrigger { + invoker?: string[] | null; +} + +/** Something that has a Data Connect HTTPS trigger */ +export interface DataConnectHttpsTriggered { + dataConnectHttpsTrigger: DataConnectHttpsTrigger; +} + /** API agnostic version of a Firebase callable function. */ export type CallableTrigger = { genkitAction?: string; @@ -151,6 +161,8 @@ export function endpointTriggerType(endpoint: Endpoint): string { return "scheduled"; } else if (isHttpsTriggered(endpoint)) { return "https"; + } else if (isDataConnectHttpsTriggered(endpoint)) { + return "dataConnectHttps"; } else if (isCallableTriggered(endpoint)) { return "callable"; } else if (isEventTriggered(endpoint)) { @@ -305,6 +317,7 @@ export type FunctionsPlatform = (typeof AllFunctionsPlatforms)[number]; export type Triggered = | HttpsTriggered + | DataConnectHttpsTriggered | CallableTriggered | EventTriggered | ScheduleTriggered @@ -316,6 +329,13 @@ export function isHttpsTriggered(triggered: Triggered): triggered is HttpsTrigge return {}.hasOwnProperty.call(triggered, "httpsTrigger"); } +/** Whether something has a DataConnectHttpsTrigger */ +export function isDataConnectHttpsTriggered( + triggered: Triggered, +): triggered is DataConnectHttpsTriggered { + return {}.hasOwnProperty.call(triggered, "dataConnectHttpsTrigger"); +} + /** Whether something has a CallableTrigger */ export function isCallableTriggered(triggered: Triggered): triggered is CallableTriggered { return {}.hasOwnProperty.call(triggered, "callableTrigger"); diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index c786a22c907..4ab2ff66eab 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -71,6 +71,12 @@ export interface HttpsTrigger { invoker?: Array> | null; } +export interface DataConnectHttpsTrigger { + // Which service account should be able to trigger this function. No value means that only the + // Firebase Data Connect P4SA can trigger this function. For more, see go/cf3-http-access-control + invoker?: Array> | null; +} + // Trigger definitions for RPCs servers using the HTTP protocol defined at // https://firebase.google.com/docs/functions/callable-reference interface CallableTrigger { @@ -150,6 +156,7 @@ export interface ScheduleTrigger { } export type HttpsTriggered = { httpsTrigger: HttpsTrigger }; +export type DataConnectHttpsTriggered = { dataConnectHttpsTrigger: DataConnectHttpsTrigger }; export type CallableTriggered = { callableTrigger: CallableTrigger }; export type BlockingTriggered = { blockingTrigger: BlockingTrigger }; export type EventTriggered = { eventTrigger: EventTrigger }; @@ -157,6 +164,7 @@ export type ScheduleTriggered = { scheduleTrigger: ScheduleTrigger }; export type TaskQueueTriggered = { taskQueueTrigger: TaskQueueTrigger }; export type Triggered = | HttpsTriggered + | DataConnectHttpsTriggered | CallableTriggered | BlockingTriggered | EventTriggered @@ -168,6 +176,13 @@ export function isHttpsTriggered(triggered: Triggered): triggered is HttpsTrigge return {}.hasOwnProperty.call(triggered, "httpsTrigger"); } +/** Whether something has a DataConnectHttpsTrigger */ +export function isDataConnectHttpsTriggered( + triggered: Triggered, +): triggered is DataConnectHttpsTriggered { + return {}.hasOwnProperty.call(triggered, "dataConnectHttpsTrigger"); +} + /** Whether something has a CallableTrigger */ export function isCallableTriggered(triggered: Triggered): triggered is CallableTriggered { return {}.hasOwnProperty.call(triggered, "callableTrigger"); @@ -559,6 +574,16 @@ function discoverTrigger(endpoint: Endpoint, region: string, r: Resolver): backe httpsTrigger.invoker = endpoint.httpsTrigger.invoker.map(r.resolveString); } return { httpsTrigger }; + } else if (isDataConnectHttpsTriggered(endpoint)) { + const dataConnectHttpsTrigger: backend.DataConnectHttpsTrigger = {}; + if (endpoint.dataConnectHttpsTrigger.invoker === null) { + dataConnectHttpsTrigger.invoker = null; + } else if (typeof endpoint.dataConnectHttpsTrigger.invoker !== "undefined") { + dataConnectHttpsTrigger.invoker = endpoint.dataConnectHttpsTrigger.invoker.map( + r.resolveString, + ); + } + return { dataConnectHttpsTrigger }; } else if (isCallableTriggered(endpoint)) { const trigger: CallableTriggered = { callableTrigger: {} }; proto.copyIfPresent(trigger.callableTrigger, endpoint.callableTrigger, "genkitAction"); diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index 8afaeb1f016..ceba0bd8b8f 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -40,6 +40,7 @@ type WireEventTrigger = build.EventTrigger & { export type WireEndpoint = build.Triggered & Partial & + Partial & Partial & Partial<{ eventTrigger: WireEventTrigger }> & Partial & @@ -158,6 +159,7 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { environmentVariables: "object?", secretEnvironmentVariables: "array?", httpsTrigger: "object", + dataConnectHttpsTrigger: "object", callableTrigger: "object", eventTrigger: "object", scheduleTrigger: "object", @@ -176,6 +178,9 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { if (ep.httpsTrigger) { triggerCount++; } + if (ep.dataConnectHttpsTrigger) { + triggerCount++; + } if (ep.callableTrigger) { triggerCount++; } @@ -213,6 +218,10 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { assertKeyTypes(prefix + ".httpsTrigger", ep.httpsTrigger, { invoker: "array?", }); + } else if (build.isDataConnectHttpsTriggered(ep)) { + assertKeyTypes(prefix + ".dataConnectHttpsTrigger", ep.dataConnectHttpsTrigger, { + invoker: "array?", + }); } else if (build.isCallableTriggered(ep)) { assertKeyTypes(prefix + ".callableTrigger", ep.callableTrigger, { genkitAction: "string?", @@ -311,6 +320,9 @@ function parseEndpointForBuild( } else if (build.isHttpsTriggered(ep)) { triggered = { httpsTrigger: {} }; copyIfPresent(triggered.httpsTrigger, ep.httpsTrigger, "invoker"); + } else if (build.isDataConnectHttpsTriggered(ep)) { + triggered = { dataConnectHttpsTrigger: {} }; + copyIfPresent(triggered.dataConnectHttpsTrigger, ep.dataConnectHttpsTrigger, "invoker"); } else if (build.isCallableTriggered(ep)) { triggered = { callableTrigger: {} }; copyIfPresent(triggered.callableTrigger, ep.callableTrigger, "genkitAction"); diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index a85f392b4cd..206b76f12c0 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -186,5 +186,9 @@ export function serviceForEndpoint(endpoint: backend.Endpoint): Service { return EVENT_SERVICE_MAPPING[endpoint.blockingTrigger.eventType as events.Event] || noOpService; } + if (backend.isDataConnectHttpsTriggered(endpoint)) { + return dataconnectService; + } + return noOpService; } From 8b1684932d3c29aacf1a6e3b568bb1999a765a76 Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Thu, 18 Dec 2025 10:59:51 -0500 Subject: [PATCH 4/8] More handling of dataConnectHttpsTrigger. --- src/deploy/functions/checkIam.ts | 2 +- src/deploy/functions/release/fabricator.ts | 13 +++++++++++++ src/deploy/functions/release/index.ts | 4 +++- src/deploy/functions/release/planner.ts | 2 ++ src/deploy/functions/release/reporter.ts | 4 ++++ src/deploy/functions/validate.ts | 6 +++++- src/emulator/functionsEmulatorShared.ts | 2 ++ 7 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 211aa1a440c..147dbcd82d3 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -77,7 +77,7 @@ export async function checkHttpIam( const filters = context.filters || getEndpointFilters(options, context.config!); const wantBackends = Object.values(payload.functions).map(({ wantBackend }) => wantBackend); const httpEndpoints = [...flattenArray(wantBackends.map((b) => backend.allEndpoints(b)))] - .filter(backend.isHttpsTriggered) + .filter(backend.isHttpsTriggered || backend.isDataConnectHttpsTriggered) .filter((f) => endpointMatchesAnyFilter(f, filters)); const existing = await backend.existingBackend(context); diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index d0c7d5e1ce3..13749583273 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -421,6 +421,14 @@ export class Fabricator { .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker)) .catch(rethrowAs(endpoint, "set invoker")); } + } else if (backend.isDataConnectHttpsTriggered(endpoint)) { + // TODO: Check if this overrides default project bindings. + const invoker = endpoint.dataConnectHttpsTrigger.invoker; + if (invoker && !invoker.includes("private")) { + await this.executor + .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker)) + .catch(rethrowAs(endpoint, "set invoker")); + } } else if (backend.isCallableTriggered(endpoint)) { // Callable functions should always be public await this.executor @@ -547,6 +555,11 @@ export class Fabricator { let invoker: string[] | undefined; if (backend.isHttpsTriggered(endpoint)) { invoker = endpoint.httpsTrigger.invoker === null ? ["public"] : endpoint.httpsTrigger.invoker; + } else if (backend.isDataConnectHttpsTriggered(endpoint)) { + invoker = + endpoint.dataConnectHttpsTrigger.invoker === null + ? undefined + : endpoint.dataConnectHttpsTrigger.invoker; } else if (backend.isTaskQueueTriggered(endpoint)) { invoker = endpoint.taskQueueTrigger.invoker === null ? [] : endpoint.taskQueueTrigger.invoker; } else if ( diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 97705a8cdb2..0804bda7233 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -127,7 +127,9 @@ export async function release( * has updated the URI of endpoints after deploy. */ export function printTriggerUrls(results: backend.Backend): void { - const httpsFunctions = backend.allEndpoints(results).filter(backend.isHttpsTriggered); + const httpsFunctions = backend + .allEndpoints(results) + .filter(backend.isHttpsTriggered || backend.isDataConnectHttpsTriggered); if (httpsFunctions.length === 0) { return; } diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index c125c183bec..c3df80aa5af 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -269,6 +269,8 @@ export function checkForIllegalUpdate(want: backend.Endpoint, have: backend.Endp const triggerType = (e: backend.Endpoint): string => { if (backend.isHttpsTriggered(e)) { return "an HTTPS"; + } else if (backend.isDataConnectHttpsTriggered(e)) { + return "a Data Connect HTTPS"; } else if (backend.isCallableTriggered(e)) { return "a callable"; } else if (backend.isEventTriggered(e)) { diff --git a/src/deploy/functions/release/reporter.ts b/src/deploy/functions/release/reporter.ts index b3e8fc827dc..8dbbc56fcb6 100644 --- a/src/deploy/functions/release/reporter.ts +++ b/src/deploy/functions/release/reporter.ts @@ -265,6 +265,10 @@ export function triggerTag(endpoint: backend.Endpoint): string { return `${prefix}.https`; } + if (backend.isDataConnectHttpsTriggered(endpoint)) { + return `${prefix}.dataConnectHttps`; + } + if (backend.isBlockingTriggered(endpoint)) { return `${prefix}.blocking`; } diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index ddf0d172787..4e9093cf676 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -222,7 +222,11 @@ export function validateTimeoutConfig(endpoints: backend.Endpoint[]): void { limit = MAX_V2_SCHEDULE_TIMEOUT_SECONDS; } else if (backend.isTaskQueueTriggered(ep)) { limit = MAX_V2_TASK_QUEUE_TIMEOUT_SECONDS; - } else if (backend.isHttpsTriggered(ep) || backend.isCallableTriggered(ep)) { + } else if ( + backend.isHttpsTriggered(ep) || + backend.isCallableTriggered(ep) || + backend.isDataConnectHttpsTriggered(ep) + ) { limit = MAX_V2_HTTP_TIMEOUT_SECONDS; } diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index dfddd8a0b58..113c20def82 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -199,6 +199,8 @@ export function emulatedFunctionsFromEndpoints( // process requires it in this form. Need to work in Firestore emulator for a proper fix... if (backend.isHttpsTriggered(endpoint)) { def.httpsTrigger = endpoint.httpsTrigger; + } else if (backend.isDataConnectHttpsTriggered(endpoint)) { + def.httpsTrigger = endpoint.dataConnectHttpsTrigger; } else if (backend.isCallableTriggered(endpoint)) { def.httpsTrigger = {}; def.labels = { ...def.labels, "deployment-callable": "true" }; From 1ff1d50cd6beacfd2a8e6dd20cb38cd7df148596 Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Thu, 18 Dec 2025 16:04:59 -0500 Subject: [PATCH 5/8] remove todo --- src/deploy/functions/release/fabricator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index 13749583273..d1aa33d4c5d 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -422,7 +422,6 @@ export class Fabricator { .catch(rethrowAs(endpoint, "set invoker")); } } else if (backend.isDataConnectHttpsTriggered(endpoint)) { - // TODO: Check if this overrides default project bindings. const invoker = endpoint.dataConnectHttpsTrigger.invoker; if (invoker && !invoker.includes("private")) { await this.executor From 2c261437a67f5f9849e8aefb946255a9bf852d18 Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Fri, 19 Dec 2025 11:42:53 -0500 Subject: [PATCH 6/8] Add some unit tests. --- src/deploy/functions/prepare.ts | 2 +- .../functions/release/fabricator.spec.ts | 59 +++++++++++++++++++ .../runtimes/discovery/v1alpha1.spec.ts | 27 +++++++++ .../functions/services/dataconnect.spec.ts | 54 +++++++++++++++++ 4 files changed, 141 insertions(+), 1 deletion(-) diff --git a/src/deploy/functions/prepare.ts b/src/deploy/functions/prepare.ts index 5c3b601e035..a5dc7da34ed 100644 --- a/src/deploy/functions/prepare.ts +++ b/src/deploy/functions/prepare.ts @@ -112,7 +112,7 @@ export async function prepare( // This drives GA4 metric `has_runtime_config` in the functions deploy reporter. context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase"); - // TODO: Modify to also load dataconnect schema if `onGraphRequest` is used with `schemaFilePath`? + // TODO: Modify to also load dataconnect schema if `onGraphRequest` is used with `schemaFilePath`. const wantBuilds = await loadCodebases( context.config, options, diff --git a/src/deploy/functions/release/fabricator.spec.ts b/src/deploy/functions/release/fabricator.spec.ts index 01ff014b849..51984eb4674 100644 --- a/src/deploy/functions/release/fabricator.spec.ts +++ b/src/deploy/functions/release/fabricator.spec.ts @@ -708,6 +708,48 @@ describe("Fabricator", () => { }); }); + describe("dataConnectHttpsTrigger", () => { + it("doesn't set invoker by default", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint({ dataConnectHttpsTrigger: {} }, { platform: "gcfv2" }); + + await fab.createV2Function(ep, new scraper.SourceTokenScraper()); + expect(run.setInvokerCreate).to.not.have.been.called; + }); + + it("sets explicit invoker", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint( + { + dataConnectHttpsTrigger: { + invoker: ["custom@"], + }, + }, + { platform: "gcfv2" }, + ); + + await fab.createV2Function(ep, new scraper.SourceTokenScraper()); + expect(run.setInvokerCreate).to.have.been.calledWith(ep.project, "service", ["custom@"]); + }); + + it("doesn't set private invoker on create", async () => { + gcfv2.createFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerCreate.resolves(); + const ep = endpoint( + { dataConnectHttpsTrigger: { invoker: ["private"] } }, + { platform: "gcfv2" }, + ); + + await fab.createV2Function(ep, new scraper.SourceTokenScraper()); + expect(run.setInvokerCreate).to.not.have.been.called; + }); + }); + describe("callableTrigger", () => { it("always sets invoker to public", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); @@ -854,6 +896,23 @@ describe("Fabricator", () => { expect(run.setInvokerUpdate).to.have.been.calledWith(ep.project, "service", ["custom@"]); }); + it("sets explicit invoker on dataConnectHttpsTrigger", async () => { + gcfv2.updateFunction.resolves({ name: "op", done: false }); + poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); + run.setInvokerUpdate.resolves(); + const ep = endpoint( + { + dataConnectHttpsTrigger: { + invoker: ["custom@"], + }, + }, + { platform: "gcfv2" }, + ); + + await fab.updateV2Function(ep, new scraper.SourceTokenScraper()); + expect(run.setInvokerUpdate).to.have.been.calledWith(ep.project, "service", ["custom@"]); + }); + it("sets explicit invoker on taskQueueTrigger", async () => { gcfv2.updateFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 5aece13566a..54f8f6ce904 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -162,6 +162,33 @@ describe("buildFromV1Alpha", () => { }); }); + describe("dataConnectHttpsTriggers", () => { + it("invalid value for Data Connect https trigger key invoker", () => { + assertParserError({ + endpoints: { + func: { + ...MIN_ENDPOINT, + dataConnectHttpsTrigger: { invoker: 42 }, + }, + }, + }); + }); + + it("cannot be used with 1st gen", () => { + assertParserError({ + endpoints: { + func: { + ...MIN_ENDPOINT, + platform: "gcfv1", + dataConnectHttpsTrigger: { + invoker: "custom@", + }, + }, + }, + }); + }); + }); + describe("genkitTriggers", () => { it("fails with invalid fields", () => { assertParserError({ diff --git a/src/deploy/functions/services/dataconnect.spec.ts b/src/deploy/functions/services/dataconnect.spec.ts index 359efe80cba..20ce378edde 100644 --- a/src/deploy/functions/services/dataconnect.spec.ts +++ b/src/deploy/functions/services/dataconnect.spec.ts @@ -46,3 +46,57 @@ describe("ensureDatabaseTriggerRegion", () => { ); }); }); + +describe("obtainDataConnectBindings", () => { + let originalEnv: NodeJS.ProcessEnv; + + beforeEach(() => { + originalEnv = { ...process.env }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should return the correct binding for autopush", async () => { + process.env.FIREBASE_DATACONNECT_URL = + "https://autopush-firebasedataconnect.sandbox.googleapis.com"; + + const bindings = await dataconnect.obtainDataConnectBindings(projectNumber); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: "roles/run.invoker", + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-autopush-dataconnect.iam.gserviceaccount.com`, + ], + }); + }); + + it("should return the correct binding for staging", async () => { + process.env.FIREBASE_DATACONNECT_URL = + "https://staging-firebasedataconnect.sandbox.googleapis.com"; + + const bindings = await dataconnect.obtainDataConnectBindings(projectNumber); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: "roles/run.invoker", + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-staging-dataconnect.iam.gserviceaccount.com`, + ], + }); + }); + + it("should return the correct binding for prod", async () => { + const bindings = await dataconnect.obtainDataConnectBindings(projectNumber); + + expect(bindings.length).to.equal(1); + expect(bindings[0]).to.deep.equal({ + role: "roles/run.invoker", + members: [ + `serviceAccount:service-${projectNumber}@gcp-sa-firebasedataconnect.iam.gserviceaccount.com`, + ], + }); + }); +}); From f708775a44bcbc8df7d2d0d9e75670be252278bc Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Fri, 19 Dec 2025 12:24:05 -0500 Subject: [PATCH 7/8] One more unit test. --- src/deploy/functions/build.spec.ts | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/deploy/functions/build.spec.ts b/src/deploy/functions/build.spec.ts index 64b604aa164..9b173889fae 100644 --- a/src/deploy/functions/build.spec.ts +++ b/src/deploy/functions/build.spec.ts @@ -75,7 +75,7 @@ describe("toBackend", () => { expect(Object.keys(backend.endpoints).length).to.equal(0); }); - it("populates multiple specified invokers correctly", () => { + it("populates multiple specified https invokers correctly", () => { const desiredBuild: build.Build = build.of({ func: { platform: "gcfv1", @@ -113,6 +113,46 @@ describe("toBackend", () => { } }); + it("populates multiple specified data connect https invokers correctly", () => { + const desiredBuild: build.Build = build.of({ + func: { + platform: "gcfv2", + region: ["us-central1"], + project: "project", + runtime: "nodejs16", + entryPoint: "func", + maxInstances: 42, + minInstances: 1, + serviceAccount: "service-account-1@", + vpc: { + connector: "projects/project/locations/region/connectors/connector", + egressSettings: "PRIVATE_RANGES_ONLY", + }, + ingressSettings: "ALLOW_ALL", + labels: { + test: "testing", + }, + dataConnectHttpsTrigger: { + invoker: ["service-account-1@", "service-account-2@"], + }, + }, + }); + const backend = build.toBackend(desiredBuild, {}); + expect(Object.keys(backend.endpoints).length).to.equal(1); + const endpointDef = Object.values(backend.endpoints)[0]; + expect(endpointDef).to.not.equal(undefined); + if (endpointDef) { + expect(endpointDef.func.id).to.equal("func"); + expect(endpointDef.func.project).to.equal("project"); + expect(endpointDef.func.region).to.equal("us-central1"); + expect( + "dataConnectHttpsTrigger" in endpointDef.func + ? endpointDef.func.dataConnectHttpsTrigger.invoker + : [], + ).to.have.members(["service-account-1@", "service-account-2@"]); + } + }); + it("populates multiple param values", () => { const desiredBuild: build.Build = build.of({ func: { From 7d93199d84f499b46c7fc4ae360cb1c5c567ce30 Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Fri, 19 Dec 2025 16:52:14 -0500 Subject: [PATCH 8/8] s/dataConnectHttpsTrigger/dataConnectGraphqlTrigger. --- src/deploy/functions/backend.ts | 20 ++++++------- src/deploy/functions/build.spec.ts | 6 ++-- src/deploy/functions/build.ts | 28 +++++++++---------- src/deploy/functions/checkIam.ts | 2 +- .../functions/release/fabricator.spec.ts | 12 ++++---- src/deploy/functions/release/fabricator.ts | 10 +++---- src/deploy/functions/release/index.ts | 2 +- src/deploy/functions/release/planner.ts | 2 +- src/deploy/functions/release/reporter.ts | 4 +-- .../runtimes/discovery/v1alpha1.spec.ts | 6 ++-- .../functions/runtimes/discovery/v1alpha1.ts | 16 +++++------ src/deploy/functions/services/index.ts | 2 +- src/deploy/functions/validate.ts | 2 +- src/emulator/functionsEmulatorShared.ts | 4 +-- 14 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/deploy/functions/backend.ts b/src/deploy/functions/backend.ts index 70c2be1d362..cdca11775af 100644 --- a/src/deploy/functions/backend.ts +++ b/src/deploy/functions/backend.ts @@ -44,13 +44,13 @@ export interface HttpsTriggered { } /** API agnostic version of a Firebase Data Connect HTTPS trigger. */ -export interface DataConnectHttpsTrigger { +export interface DataConnectGraphqlTrigger { invoker?: string[] | null; } /** Something that has a Data Connect HTTPS trigger */ -export interface DataConnectHttpsTriggered { - dataConnectHttpsTrigger: DataConnectHttpsTrigger; +export interface DataConnectGraphqlTriggered { + dataConnectGraphqlTrigger: DataConnectGraphqlTrigger; } /** API agnostic version of a Firebase callable function. */ @@ -161,8 +161,8 @@ export function endpointTriggerType(endpoint: Endpoint): string { return "scheduled"; } else if (isHttpsTriggered(endpoint)) { return "https"; - } else if (isDataConnectHttpsTriggered(endpoint)) { - return "dataConnectHttps"; + } else if (isDataConnectGraphqlTriggered(endpoint)) { + return "dataConnectGraphql"; } else if (isCallableTriggered(endpoint)) { return "callable"; } else if (isEventTriggered(endpoint)) { @@ -317,7 +317,7 @@ export type FunctionsPlatform = (typeof AllFunctionsPlatforms)[number]; export type Triggered = | HttpsTriggered - | DataConnectHttpsTriggered + | DataConnectGraphqlTriggered | CallableTriggered | EventTriggered | ScheduleTriggered @@ -329,11 +329,11 @@ export function isHttpsTriggered(triggered: Triggered): triggered is HttpsTrigge return {}.hasOwnProperty.call(triggered, "httpsTrigger"); } -/** Whether something has a DataConnectHttpsTrigger */ -export function isDataConnectHttpsTriggered( +/** Whether something has a DataConnectGraphqlTrigger */ +export function isDataConnectGraphqlTriggered( triggered: Triggered, -): triggered is DataConnectHttpsTriggered { - return {}.hasOwnProperty.call(triggered, "dataConnectHttpsTrigger"); +): triggered is DataConnectGraphqlTriggered { + return {}.hasOwnProperty.call(triggered, "dataConnectGraphqlTrigger"); } /** Whether something has a CallableTrigger */ diff --git a/src/deploy/functions/build.spec.ts b/src/deploy/functions/build.spec.ts index 9b173889fae..1eba1fdb9a0 100644 --- a/src/deploy/functions/build.spec.ts +++ b/src/deploy/functions/build.spec.ts @@ -132,7 +132,7 @@ describe("toBackend", () => { labels: { test: "testing", }, - dataConnectHttpsTrigger: { + dataConnectGraphqlTrigger: { invoker: ["service-account-1@", "service-account-2@"], }, }, @@ -146,8 +146,8 @@ describe("toBackend", () => { expect(endpointDef.func.project).to.equal("project"); expect(endpointDef.func.region).to.equal("us-central1"); expect( - "dataConnectHttpsTrigger" in endpointDef.func - ? endpointDef.func.dataConnectHttpsTrigger.invoker + "dataConnectGraphqlTrigger" in endpointDef.func + ? endpointDef.func.dataConnectGraphqlTrigger.invoker : [], ).to.have.members(["service-account-1@", "service-account-2@"]); } diff --git a/src/deploy/functions/build.ts b/src/deploy/functions/build.ts index 4ab2ff66eab..20c361b8f1c 100644 --- a/src/deploy/functions/build.ts +++ b/src/deploy/functions/build.ts @@ -71,7 +71,7 @@ export interface HttpsTrigger { invoker?: Array> | null; } -export interface DataConnectHttpsTrigger { +export interface DataConnectGraphqlTrigger { // Which service account should be able to trigger this function. No value means that only the // Firebase Data Connect P4SA can trigger this function. For more, see go/cf3-http-access-control invoker?: Array> | null; @@ -156,7 +156,7 @@ export interface ScheduleTrigger { } export type HttpsTriggered = { httpsTrigger: HttpsTrigger }; -export type DataConnectHttpsTriggered = { dataConnectHttpsTrigger: DataConnectHttpsTrigger }; +export type DataConnectGraphqlTriggered = { dataConnectGraphqlTrigger: DataConnectGraphqlTrigger }; export type CallableTriggered = { callableTrigger: CallableTrigger }; export type BlockingTriggered = { blockingTrigger: BlockingTrigger }; export type EventTriggered = { eventTrigger: EventTrigger }; @@ -164,7 +164,7 @@ export type ScheduleTriggered = { scheduleTrigger: ScheduleTrigger }; export type TaskQueueTriggered = { taskQueueTrigger: TaskQueueTrigger }; export type Triggered = | HttpsTriggered - | DataConnectHttpsTriggered + | DataConnectGraphqlTriggered | CallableTriggered | BlockingTriggered | EventTriggered @@ -176,11 +176,11 @@ export function isHttpsTriggered(triggered: Triggered): triggered is HttpsTrigge return {}.hasOwnProperty.call(triggered, "httpsTrigger"); } -/** Whether something has a DataConnectHttpsTrigger */ -export function isDataConnectHttpsTriggered( +/** Whether something has a DataConnectGraphqlTrigger */ +export function isDataConnectGraphqlTriggered( triggered: Triggered, -): triggered is DataConnectHttpsTriggered { - return {}.hasOwnProperty.call(triggered, "dataConnectHttpsTrigger"); +): triggered is DataConnectGraphqlTriggered { + return {}.hasOwnProperty.call(triggered, "dataConnectGraphqlTrigger"); } /** Whether something has a CallableTrigger */ @@ -574,16 +574,16 @@ function discoverTrigger(endpoint: Endpoint, region: string, r: Resolver): backe httpsTrigger.invoker = endpoint.httpsTrigger.invoker.map(r.resolveString); } return { httpsTrigger }; - } else if (isDataConnectHttpsTriggered(endpoint)) { - const dataConnectHttpsTrigger: backend.DataConnectHttpsTrigger = {}; - if (endpoint.dataConnectHttpsTrigger.invoker === null) { - dataConnectHttpsTrigger.invoker = null; - } else if (typeof endpoint.dataConnectHttpsTrigger.invoker !== "undefined") { - dataConnectHttpsTrigger.invoker = endpoint.dataConnectHttpsTrigger.invoker.map( + } else if (isDataConnectGraphqlTriggered(endpoint)) { + const dataConnectGraphqlTrigger: backend.DataConnectGraphqlTrigger = {}; + if (endpoint.dataConnectGraphqlTrigger.invoker === null) { + dataConnectGraphqlTrigger.invoker = null; + } else if (typeof endpoint.dataConnectGraphqlTrigger.invoker !== "undefined") { + dataConnectGraphqlTrigger.invoker = endpoint.dataConnectGraphqlTrigger.invoker.map( r.resolveString, ); } - return { dataConnectHttpsTrigger }; + return { dataConnectGraphqlTrigger }; } else if (isCallableTriggered(endpoint)) { const trigger: CallableTriggered = { callableTrigger: {} }; proto.copyIfPresent(trigger.callableTrigger, endpoint.callableTrigger, "genkitAction"); diff --git a/src/deploy/functions/checkIam.ts b/src/deploy/functions/checkIam.ts index 147dbcd82d3..2d1c4607eaf 100644 --- a/src/deploy/functions/checkIam.ts +++ b/src/deploy/functions/checkIam.ts @@ -77,7 +77,7 @@ export async function checkHttpIam( const filters = context.filters || getEndpointFilters(options, context.config!); const wantBackends = Object.values(payload.functions).map(({ wantBackend }) => wantBackend); const httpEndpoints = [...flattenArray(wantBackends.map((b) => backend.allEndpoints(b)))] - .filter(backend.isHttpsTriggered || backend.isDataConnectHttpsTriggered) + .filter(backend.isHttpsTriggered || backend.isDataConnectGraphqlTriggered) .filter((f) => endpointMatchesAnyFilter(f, filters)); const existing = await backend.existingBackend(context); diff --git a/src/deploy/functions/release/fabricator.spec.ts b/src/deploy/functions/release/fabricator.spec.ts index 51984eb4674..3525821ae1a 100644 --- a/src/deploy/functions/release/fabricator.spec.ts +++ b/src/deploy/functions/release/fabricator.spec.ts @@ -708,12 +708,12 @@ describe("Fabricator", () => { }); }); - describe("dataConnectHttpsTrigger", () => { + describe("dataConnectGraphqlTrigger", () => { it("doesn't set invoker by default", async () => { gcfv2.createFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); - const ep = endpoint({ dataConnectHttpsTrigger: {} }, { platform: "gcfv2" }); + const ep = endpoint({ dataConnectGraphqlTrigger: {} }, { platform: "gcfv2" }); await fab.createV2Function(ep, new scraper.SourceTokenScraper()); expect(run.setInvokerCreate).to.not.have.been.called; @@ -725,7 +725,7 @@ describe("Fabricator", () => { run.setInvokerCreate.resolves(); const ep = endpoint( { - dataConnectHttpsTrigger: { + dataConnectGraphqlTrigger: { invoker: ["custom@"], }, }, @@ -741,7 +741,7 @@ describe("Fabricator", () => { poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerCreate.resolves(); const ep = endpoint( - { dataConnectHttpsTrigger: { invoker: ["private"] } }, + { dataConnectGraphqlTrigger: { invoker: ["private"] } }, { platform: "gcfv2" }, ); @@ -896,13 +896,13 @@ describe("Fabricator", () => { expect(run.setInvokerUpdate).to.have.been.calledWith(ep.project, "service", ["custom@"]); }); - it("sets explicit invoker on dataConnectHttpsTrigger", async () => { + it("sets explicit invoker on dataConnectGraphqlTrigger", async () => { gcfv2.updateFunction.resolves({ name: "op", done: false }); poller.pollOperation.resolves({ serviceConfig: { service: "service" } }); run.setInvokerUpdate.resolves(); const ep = endpoint( { - dataConnectHttpsTrigger: { + dataConnectGraphqlTrigger: { invoker: ["custom@"], }, }, diff --git a/src/deploy/functions/release/fabricator.ts b/src/deploy/functions/release/fabricator.ts index d1aa33d4c5d..001ab0cfaf9 100644 --- a/src/deploy/functions/release/fabricator.ts +++ b/src/deploy/functions/release/fabricator.ts @@ -421,8 +421,8 @@ export class Fabricator { .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker)) .catch(rethrowAs(endpoint, "set invoker")); } - } else if (backend.isDataConnectHttpsTriggered(endpoint)) { - const invoker = endpoint.dataConnectHttpsTrigger.invoker; + } else if (backend.isDataConnectGraphqlTriggered(endpoint)) { + const invoker = endpoint.dataConnectGraphqlTrigger.invoker; if (invoker && !invoker.includes("private")) { await this.executor .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker)) @@ -554,11 +554,11 @@ export class Fabricator { let invoker: string[] | undefined; if (backend.isHttpsTriggered(endpoint)) { invoker = endpoint.httpsTrigger.invoker === null ? ["public"] : endpoint.httpsTrigger.invoker; - } else if (backend.isDataConnectHttpsTriggered(endpoint)) { + } else if (backend.isDataConnectGraphqlTriggered(endpoint)) { invoker = - endpoint.dataConnectHttpsTrigger.invoker === null + endpoint.dataConnectGraphqlTrigger.invoker === null ? undefined - : endpoint.dataConnectHttpsTrigger.invoker; + : endpoint.dataConnectGraphqlTrigger.invoker; } else if (backend.isTaskQueueTriggered(endpoint)) { invoker = endpoint.taskQueueTrigger.invoker === null ? [] : endpoint.taskQueueTrigger.invoker; } else if ( diff --git a/src/deploy/functions/release/index.ts b/src/deploy/functions/release/index.ts index 0804bda7233..d3ff18f22fc 100644 --- a/src/deploy/functions/release/index.ts +++ b/src/deploy/functions/release/index.ts @@ -129,7 +129,7 @@ export async function release( export function printTriggerUrls(results: backend.Backend): void { const httpsFunctions = backend .allEndpoints(results) - .filter(backend.isHttpsTriggered || backend.isDataConnectHttpsTriggered); + .filter(backend.isHttpsTriggered || backend.isDataConnectGraphqlTriggered); if (httpsFunctions.length === 0) { return; } diff --git a/src/deploy/functions/release/planner.ts b/src/deploy/functions/release/planner.ts index c3df80aa5af..b14b155f6ef 100644 --- a/src/deploy/functions/release/planner.ts +++ b/src/deploy/functions/release/planner.ts @@ -269,7 +269,7 @@ export function checkForIllegalUpdate(want: backend.Endpoint, have: backend.Endp const triggerType = (e: backend.Endpoint): string => { if (backend.isHttpsTriggered(e)) { return "an HTTPS"; - } else if (backend.isDataConnectHttpsTriggered(e)) { + } else if (backend.isDataConnectGraphqlTriggered(e)) { return "a Data Connect HTTPS"; } else if (backend.isCallableTriggered(e)) { return "a callable"; diff --git a/src/deploy/functions/release/reporter.ts b/src/deploy/functions/release/reporter.ts index 8dbbc56fcb6..fa395bc9dab 100644 --- a/src/deploy/functions/release/reporter.ts +++ b/src/deploy/functions/release/reporter.ts @@ -265,8 +265,8 @@ export function triggerTag(endpoint: backend.Endpoint): string { return `${prefix}.https`; } - if (backend.isDataConnectHttpsTriggered(endpoint)) { - return `${prefix}.dataConnectHttps`; + if (backend.isDataConnectGraphqlTriggered(endpoint)) { + return `${prefix}.dataConnectGraphql`; } if (backend.isBlockingTriggered(endpoint)) { diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts index 54f8f6ce904..5c56a22e72b 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.spec.ts @@ -162,13 +162,13 @@ describe("buildFromV1Alpha", () => { }); }); - describe("dataConnectHttpsTriggers", () => { + describe("dataConnectGraphqlTriggers", () => { it("invalid value for Data Connect https trigger key invoker", () => { assertParserError({ endpoints: { func: { ...MIN_ENDPOINT, - dataConnectHttpsTrigger: { invoker: 42 }, + dataConnectGraphqlTrigger: { invoker: 42 }, }, }, }); @@ -180,7 +180,7 @@ describe("buildFromV1Alpha", () => { func: { ...MIN_ENDPOINT, platform: "gcfv1", - dataConnectHttpsTrigger: { + dataConnectGraphqlTrigger: { invoker: "custom@", }, }, diff --git a/src/deploy/functions/runtimes/discovery/v1alpha1.ts b/src/deploy/functions/runtimes/discovery/v1alpha1.ts index ceba0bd8b8f..7c9ddfa0c27 100644 --- a/src/deploy/functions/runtimes/discovery/v1alpha1.ts +++ b/src/deploy/functions/runtimes/discovery/v1alpha1.ts @@ -40,7 +40,7 @@ type WireEventTrigger = build.EventTrigger & { export type WireEndpoint = build.Triggered & Partial & - Partial & + Partial & Partial & Partial<{ eventTrigger: WireEventTrigger }> & Partial & @@ -159,7 +159,7 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { environmentVariables: "object?", secretEnvironmentVariables: "array?", httpsTrigger: "object", - dataConnectHttpsTrigger: "object", + dataConnectGraphqlTrigger: "object", callableTrigger: "object", eventTrigger: "object", scheduleTrigger: "object", @@ -178,7 +178,7 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { if (ep.httpsTrigger) { triggerCount++; } - if (ep.dataConnectHttpsTrigger) { + if (ep.dataConnectGraphqlTrigger) { triggerCount++; } if (ep.callableTrigger) { @@ -218,8 +218,8 @@ function assertBuildEndpoint(ep: WireEndpoint, id: string): void { assertKeyTypes(prefix + ".httpsTrigger", ep.httpsTrigger, { invoker: "array?", }); - } else if (build.isDataConnectHttpsTriggered(ep)) { - assertKeyTypes(prefix + ".dataConnectHttpsTrigger", ep.dataConnectHttpsTrigger, { + } else if (build.isDataConnectGraphqlTriggered(ep)) { + assertKeyTypes(prefix + ".dataConnectGraphqlTrigger", ep.dataConnectGraphqlTrigger, { invoker: "array?", }); } else if (build.isCallableTriggered(ep)) { @@ -320,9 +320,9 @@ function parseEndpointForBuild( } else if (build.isHttpsTriggered(ep)) { triggered = { httpsTrigger: {} }; copyIfPresent(triggered.httpsTrigger, ep.httpsTrigger, "invoker"); - } else if (build.isDataConnectHttpsTriggered(ep)) { - triggered = { dataConnectHttpsTrigger: {} }; - copyIfPresent(triggered.dataConnectHttpsTrigger, ep.dataConnectHttpsTrigger, "invoker"); + } else if (build.isDataConnectGraphqlTriggered(ep)) { + triggered = { dataConnectGraphqlTrigger: {} }; + copyIfPresent(triggered.dataConnectGraphqlTrigger, ep.dataConnectGraphqlTrigger, "invoker"); } else if (build.isCallableTriggered(ep)) { triggered = { callableTrigger: {} }; copyIfPresent(triggered.callableTrigger, ep.callableTrigger, "genkitAction"); diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index 206b76f12c0..24ef6d0fc24 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -186,7 +186,7 @@ export function serviceForEndpoint(endpoint: backend.Endpoint): Service { return EVENT_SERVICE_MAPPING[endpoint.blockingTrigger.eventType as events.Event] || noOpService; } - if (backend.isDataConnectHttpsTriggered(endpoint)) { + if (backend.isDataConnectGraphqlTriggered(endpoint)) { return dataconnectService; } diff --git a/src/deploy/functions/validate.ts b/src/deploy/functions/validate.ts index 4e9093cf676..ec571b4b8e3 100644 --- a/src/deploy/functions/validate.ts +++ b/src/deploy/functions/validate.ts @@ -225,7 +225,7 @@ export function validateTimeoutConfig(endpoints: backend.Endpoint[]): void { } else if ( backend.isHttpsTriggered(ep) || backend.isCallableTriggered(ep) || - backend.isDataConnectHttpsTriggered(ep) + backend.isDataConnectGraphqlTriggered(ep) ) { limit = MAX_V2_HTTP_TIMEOUT_SECONDS; } diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts index 113c20def82..852b8d23857 100644 --- a/src/emulator/functionsEmulatorShared.ts +++ b/src/emulator/functionsEmulatorShared.ts @@ -199,8 +199,8 @@ export function emulatedFunctionsFromEndpoints( // process requires it in this form. Need to work in Firestore emulator for a proper fix... if (backend.isHttpsTriggered(endpoint)) { def.httpsTrigger = endpoint.httpsTrigger; - } else if (backend.isDataConnectHttpsTriggered(endpoint)) { - def.httpsTrigger = endpoint.dataConnectHttpsTrigger; + } else if (backend.isDataConnectGraphqlTriggered(endpoint)) { + def.httpsTrigger = endpoint.dataConnectGraphqlTrigger; } else if (backend.isCallableTriggered(endpoint)) { def.httpsTrigger = {}; def.labels = { ...def.labels, "deployment-callable": "true" };