From 342ee34409371625b9e73f55eb1600acf7bf7628 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 15 Nov 2022 15:34:27 -0800 Subject: [PATCH 1/3] Handle the case where process.env.GCLOUD_PROJECT is missing but process.env.GCP_PROJECT is present --- src/common/config.ts | 11 ++++++----- src/common/encoding.ts | 9 ++++++--- src/common/providers/database.ts | 5 +++-- src/common/providers/identity.ts | 3 ++- src/common/utilities/utils.ts | 11 +++++++++++ src/logger/index.ts | 3 ++- src/v1/providers/analytics.ts | 6 ++---- src/v1/providers/auth.ts | 6 ++---- src/v1/providers/firestore.ts | 11 ++--------- src/v1/providers/pubsub.ts | 11 +++-------- src/v1/providers/remoteConfig.ts | 6 ++---- src/v1/providers/testLab.ts | 6 ++---- src/v2/providers/pubsub.ts | 3 ++- 13 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/common/config.ts b/src/common/config.ts index ac634f2e7..06a058d6e 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -3,6 +3,7 @@ import { readFileSync } from "fs"; import * as path from "path"; import * as logger from "../logger"; +import { currentProjectId } from "./utilities/utils"; let cache: AppOptions | null = null; @@ -36,15 +37,15 @@ export function firebaseConfig(): AppOptions | null { return cache; } - if (process.env.GCLOUD_PROJECT) { + const projectId = currentProjectId(); + if (projectId) { logger.warn( "Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail" ); cache = { - databaseURL: - process.env.DATABASE_URL || `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`, - storageBucket: process.env.STORAGE_BUCKET_URL || `${process.env.GCLOUD_PROJECT}.appspot.com`, - projectId: process.env.GCLOUD_PROJECT, + databaseURL: process.env.DATABASE_URL || `https://${projectId}.firebaseio.com`, + storageBucket: process.env.STORAGE_BUCKET_URL || `${projectId}.appspot.com`, + projectId: projectId, }; return cache; } else { diff --git a/src/common/encoding.ts b/src/common/encoding.ts index 88b40ef3f..e3c499102 100644 --- a/src/common/encoding.ts +++ b/src/common/encoding.ts @@ -20,6 +20,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { currentProjectId } from "./utilities/utils"; + // Copied from firebase-tools/src/gcp/proto /** @@ -76,12 +78,13 @@ export function serviceAccountFromShorthand(serviceAccount: string): string | nu if (serviceAccount === "default") { return null; } else if (serviceAccount.endsWith("@")) { - if (!process.env.GCLOUD_PROJECT) { + const projectId = currentProjectId(); + if (!projectId) { throw new Error( - `Unable to determine email for service account '${serviceAccount}' because process.env.GCLOUD_PROJECT is not set.` + `Unable to determine email for service account '${serviceAccount}' (process.env.GCLOUD_PROJECT and process.env.GCP_PROJECT misisng)` ); } - return `${serviceAccount}${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; + return `${serviceAccount}${projectId}.iam.gserviceaccount.com`; } else if (serviceAccount.includes("@")) { return serviceAccount; } else { diff --git a/src/common/providers/database.ts b/src/common/providers/database.ts index e1da47c7d..7dfcbfad8 100644 --- a/src/common/providers/database.ts +++ b/src/common/providers/database.ts @@ -24,6 +24,7 @@ import { App } from "firebase-admin/app"; import * as database from "firebase-admin/database"; import { firebaseConfig } from "../../common/config"; import { joinPath, pathParts } from "../../common/utilities/path"; +import { currentProjectId } from "../utilities/utils"; /** * Interface representing a Firebase Realtime database data snapshot. @@ -60,8 +61,8 @@ export class DataSnapshot implements database.DataSnapshot { this.instance = app.options.databaseURL; } else if (config.databaseURL) { this.instance = config.databaseURL; - } else if (process.env.GCLOUD_PROJECT) { - this.instance = "https://" + process.env.GCLOUD_PROJECT + "-default-rtdb.firebaseio.com"; + } else if (currentProjectId()) { + this.instance = "https://" + currentProjectId() + "-default-rtdb.firebaseio.com"; } this._path = path; diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index 9997d7569..2f2ba01dc 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -26,6 +26,7 @@ import * as logger from "../../logger"; import { EventContext } from "../../v1/cloud-functions"; import { getApp } from "../app"; import { isDebugFeatureEnabled } from "../debug"; +import { currentProjectId } from "../utilities/utils"; import { HttpsError, unsafeDecodeToken } from "./https"; export { HttpsError }; @@ -784,7 +785,7 @@ export function getUpdateMask(authResponse?: BeforeCreateResponse | BeforeSignIn export function wrapHandler(eventType: AuthBlockingEventType, handler: HandlerV1 | HandlerV2) { return async (req: express.Request, res: express.Response): Promise => { try { - const projectId = process.env.GCLOUD_PROJECT; + const projectId = currentProjectId(); if (!isValidRequest(req)) { logger.error("Invalid request, unable to process"); throw new HttpsError("invalid-argument", "Bad Request"); diff --git a/src/common/utilities/utils.ts b/src/common/utilities/utils.ts index 0bcfc1ad1..f50716300 100644 --- a/src/common/utilities/utils.ts +++ b/src/common/utilities/utils.ts @@ -24,6 +24,17 @@ function isObject(obj: any): boolean { return typeof obj === "object" && !!obj; } +/** @internal */ +export function currentProjectId(assertPresence = false): string { + const projectId = process.env.GCLOUD_PROJECT || process.env.GCP_PROJECT; + if (!projectId && assertPresence) { + throw new Error( + `Unable to determine current GCP project--neither process.env.GCLOUD_PROJECT nor process.env.GCP_PROJECT are set.` + ); + } + return projectId; +} + /** @hidden */ export function applyChange(src: any, dest: any) { // if not mergeable, don't merge diff --git a/src/logger/index.ts b/src/logger/index.ts index e50e5088a..36336aff1 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -22,6 +22,7 @@ import { format } from "util"; import { traceContext } from "../common/trace"; +import { currentProjectId } from "../common/utilities/utils"; import { CONSOLE_SEVERITY, UNPATCHED_CONSOLE } from "./common"; @@ -156,7 +157,7 @@ function entryFromArgs(severity: LogSeverity, args: any[]): LogEntry { } return { "logging.googleapis.com/trace": ctx?.traceId - ? `projects/${process.env.GCLOUD_PROJECT}/traces/${ctx.traceId}` + ? `projects/${currentProjectId()}/traces/${ctx.traceId}` : undefined, ...entry, severity, diff --git a/src/v1/providers/analytics.ts b/src/v1/providers/analytics.ts index 63895a7ca..e3bfe00ca 100644 --- a/src/v1/providers/analytics.ts +++ b/src/v1/providers/analytics.ts @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { currentProjectId } from "../../common/utilities/utils"; import { CloudFunction, Event, EventContext, makeCloudFunction } from "../cloud-functions"; import { DeploymentOptions } from "../function-configuration"; @@ -43,10 +44,7 @@ export function event(analyticsEventType: string) { /** @internal */ export function _eventWithOptions(analyticsEventType: string, options: DeploymentOptions) { return new AnalyticsEventBuilder(() => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error("process.env.GCLOUD_PROJECT is not set."); - } - return "projects/" + process.env.GCLOUD_PROJECT + "/events/" + analyticsEventType; + return "projects/" + currentProjectId(true) + "/events/" + analyticsEventType; }, options); } diff --git a/src/v1/providers/auth.ts b/src/v1/providers/auth.ts index edef7b0bb..92e1dc3d2 100644 --- a/src/v1/providers/auth.ts +++ b/src/v1/providers/auth.ts @@ -44,6 +44,7 @@ import { } from "../cloud-functions"; import { DeploymentOptions } from "../function-configuration"; import { initV1Endpoint } from "../../runtime/manifest"; +import { currentProjectId } from "../../common/utilities/utils"; // TODO: yank in next breaking change release export { UserRecord, UserInfo, UserRecordMetadata, userRecordConstructor }; @@ -88,10 +89,7 @@ export function user(userOptions?: UserOptions): UserBuilder { export function _userWithOptions(options: DeploymentOptions, userOptions: UserOptions) { return new UserBuilder( () => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error("process.env.GCLOUD_PROJECT is not set."); - } - return "projects/" + process.env.GCLOUD_PROJECT; + return "projects/" + currentProjectId(true); }, options, userOptions diff --git a/src/v1/providers/firestore.ts b/src/v1/providers/firestore.ts index 57ccf4603..7fd1df725 100644 --- a/src/v1/providers/firestore.ts +++ b/src/v1/providers/firestore.ts @@ -27,6 +27,7 @@ import { getApp } from "../../common/app"; import { Change } from "../../common/change"; import { ParamsOf } from "../../common/params"; import { dateToTimestampProto } from "../../common/utilities/encoder"; +import { currentProjectId } from "../../common/utilities/utils"; import * as logger from "../../logger"; import { CloudFunction, Event, EventContext, makeCloudFunction } from "../cloud-functions"; import { DeploymentOptions } from "../function-configuration"; @@ -101,15 +102,7 @@ export class NamespaceBuilder { document(path: Path) { return new DocumentBuilder(() => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error("process.env.GCLOUD_PROJECT is not set."); - } - const database = posix.join( - "projects", - process.env.GCLOUD_PROJECT, - "databases", - this.database - ); + const database = posix.join("projects", currentProjectId(true), "databases", this.database); return posix.join( database, this.namespace ? `documents@${this.namespace}` : "documents", diff --git a/src/v1/providers/pubsub.ts b/src/v1/providers/pubsub.ts index 57a28803c..709ce9c71 100644 --- a/src/v1/providers/pubsub.ts +++ b/src/v1/providers/pubsub.ts @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { currentProjectId } from "../../common/utilities/utils"; import { CloudFunction, EventContext, makeCloudFunction } from "../cloud-functions"; import { DeploymentOptions, ScheduleRetryConfig } from "../function-configuration"; @@ -46,10 +47,7 @@ export function _topicWithOptions(topic: string, options: DeploymentOptions): To } return new TopicBuilder(() => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error("process.env.GCLOUD_PROJECT is not set."); - } - return `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`; + return `projects/${currentProjectId(true)}/topics/${topic}`; }, options); } @@ -101,11 +99,8 @@ export function _scheduleWithOptions( options: DeploymentOptions ): ScheduleBuilder { const triggerResource = () => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error("process.env.GCLOUD_PROJECT is not set."); - } // The CLI will append the correct topic name based on region and function name - return `projects/${process.env.GCLOUD_PROJECT}/topics`; + return `projects/${currentProjectId(true)}/topics`; }; return new ScheduleBuilder(triggerResource, { ...options, diff --git a/src/v1/providers/remoteConfig.ts b/src/v1/providers/remoteConfig.ts index cf67383dc..70de8fcfc 100644 --- a/src/v1/providers/remoteConfig.ts +++ b/src/v1/providers/remoteConfig.ts @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { currentProjectId } from "../../common/utilities/utils"; import { CloudFunction, EventContext, makeCloudFunction } from "../cloud-functions"; import { DeploymentOptions } from "../function-configuration"; @@ -49,10 +50,7 @@ export function _onUpdateWithOptions( options: DeploymentOptions ): CloudFunction { const triggerResource = () => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error("process.env.GCLOUD_PROJECT is not set."); - } - return `projects/${process.env.GCLOUD_PROJECT}`; + return `projects/${currentProjectId(true)}`; }; return new UpdateBuilder(triggerResource, options).onUpdate(handler); } diff --git a/src/v1/providers/testLab.ts b/src/v1/providers/testLab.ts index d0d0cd06a..49c711326 100644 --- a/src/v1/providers/testLab.ts +++ b/src/v1/providers/testLab.ts @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { currentProjectId } from "../../common/utilities/utils"; import { CloudFunction, Event, EventContext, makeCloudFunction } from "../cloud-functions"; import { DeploymentOptions } from "../function-configuration"; @@ -38,10 +39,7 @@ export function testMatrix() { /** @internal */ export function _testMatrixWithOpts(opts: DeploymentOptions) { return new TestMatrixBuilder(() => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error("process.env.GCLOUD_PROJECT is not set."); - } - return "projects/" + process.env.GCLOUD_PROJECT + "/testMatrices/{matrix}"; + return "projects/" + currentProjectId(true) + "/testMatrices/{matrix}"; }, opts); } diff --git a/src/v2/providers/pubsub.ts b/src/v2/providers/pubsub.ts index 98b966c45..aa4b3191e 100644 --- a/src/v2/providers/pubsub.ts +++ b/src/v2/providers/pubsub.ts @@ -33,6 +33,7 @@ import { wrapTraceContext } from "../trace"; import { Expression } from "../../params"; import * as options from "../options"; import { SecretParam } from "../../params/types"; +import { currentProjectId } from "../../common/utilities/utils"; /** * Google Cloud Pub/Sub is a globally distributed message bus that automatically scales as you need it. @@ -323,7 +324,7 @@ export function onMessagePublished( }, eventTrigger: { eventType: "google.cloud.pubsub.topic.v1.messagePublished", - resource: `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`, + resource: `projects/${currentProjectId()}/topics/${topic}`, }, }; }, From 423fd658ec63df217b32f7fbbc6590b6b8fa178c Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 15 Nov 2022 15:36:34 -0800 Subject: [PATCH 2/3] typo --- src/common/encoding.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/encoding.ts b/src/common/encoding.ts index e3c499102..581a7cef7 100644 --- a/src/common/encoding.ts +++ b/src/common/encoding.ts @@ -81,7 +81,7 @@ export function serviceAccountFromShorthand(serviceAccount: string): string | nu const projectId = currentProjectId(); if (!projectId) { throw new Error( - `Unable to determine email for service account '${serviceAccount}' (process.env.GCLOUD_PROJECT and process.env.GCP_PROJECT misisng)` + `Unable to determine email for service account '${serviceAccount}' (process.env.GCLOUD_PROJECT and process.env.GCP_PROJECT missing)` ); } return `${serviceAccount}${projectId}.iam.gserviceaccount.com`; From c49a4c2f03d1e73c3f64413dcec779e99dfeba9c Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Tue, 15 Nov 2022 15:43:05 -0800 Subject: [PATCH 3/3] use process.env.PROJECT_ID --- src/common/utilities/utils.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/common/utilities/utils.ts b/src/common/utilities/utils.ts index f50716300..3309ac634 100644 --- a/src/common/utilities/utils.ts +++ b/src/common/utilities/utils.ts @@ -24,9 +24,16 @@ function isObject(obj: any): boolean { return typeof obj === "object" && !!obj; } -/** @internal */ +/** + * Retrieves the current GCL project ID from process.env.GCLOUD_PROJECT (which + * is guaranteed to be set during firebase deploy) or process.env.PROJECT_ID + * (which is guaranteed to be set by the Extensions backend). Use this instead + * of directly querying process.env if your code can be used in either context. + * + * @internal + */ export function currentProjectId(assertPresence = false): string { - const projectId = process.env.GCLOUD_PROJECT || process.env.GCP_PROJECT; + const projectId = process.env.GCLOUD_PROJECT || process.env.PROJECT_ID; if (!projectId && assertPresence) { throw new Error( `Unable to determine current GCP project--neither process.env.GCLOUD_PROJECT nor process.env.GCP_PROJECT are set.`