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
Expand Up @@ -7,6 +7,7 @@
- Increase emulator UI body parser limit to match Storage emulator maximum. (#8329)
- Fixed Data Connect setup issues for fresh databases due to IAM user not being created. (#8335)
- Fixed an issue where `ext:install` used POSIX file seperators on Windows machines. (#8326)
- Fixed an issue where credentials from `firebase login` would not be correctly provided to the Data Connect emulator.
- Updated the Firebase Data Connect local toolkit to v1.9.1, which adds support for generated Angular SDKs and updates Dart SDK fields to follow best practices. (#8340)
- Fixed misleading comments in `firebase init dataconnect` `connector.yaml` template.
- Improved Data Connect SQL permissions to better handle tables owned by IAM roles. (#8339)
3 changes: 3 additions & 0 deletions src/commands/dataconnect-sdk-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { needProjectId } from "../projectUtils";
import { load } from "../dataconnect/load";
import { readFirebaseJson } from "../dataconnect/fileUtils";
import { logger } from "../logger";
import { getProjectDefaultAccount } from "../auth";

type GenerateOptions = Options & { watch?: boolean };

Expand Down Expand Up @@ -42,10 +43,12 @@ export const command = new Command("dataconnect:sdk:generate")
return;
}
for (const conn of serviceInfo.connectorInfo) {
const account = getProjectDefaultAccount(options.projectRoot);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's just fdc build and fdc dev that need the credentials (not fdc generate)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I figured we ought to provide them as future proofing.

const output = await DataConnectEmulator.generate({
configDir,
connectorId: conn.connectorYaml.connectorId,
watch: options.watch,
account,
});
logger.info(output);
logger.info(`Generated SDKs for ${conn.connectorYaml.connectorId}`);
Expand Down
6 changes: 4 additions & 2 deletions src/dataconnect/build.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { DataConnectEmulator } from "../emulator/dataconnectEmulator";
import { DataConnectBuildArgs, DataConnectEmulator } from "../emulator/dataconnectEmulator";
import { Options } from "../options";
import { FirebaseError } from "../error";
import * as experiments from "../experiments";
import { promptOnce } from "../prompt";
import * as utils from "../utils";
import { prettify, prettifyTable } from "./graphqlError";
import { DeploymentMetadata, GraphqlError } from "./types";
import { getProjectDefaultAccount } from "../auth";

export async function build(

Check warning on line 11 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
options: Options,
configDir: string,
dryRun?: boolean,
): Promise<DeploymentMetadata> {
const args: { configDir: string; projectId?: string } = { configDir };
const account = getProjectDefaultAccount(options.projectRoot);
const args: DataConnectBuildArgs = { configDir, account };
if (experiments.isEnabled("fdcconnectorevolution") && options.projectId) {
const flags = process.env["DATA_CONNECT_PREVIEW"];
if (flags) {
Expand All @@ -29,7 +31,7 @@
return buildResult?.metadata ?? {};
}

export async function handleBuildErrors(

Check warning on line 34 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing return type on function

Check warning on line 34 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
errors: GraphqlError[],
nonInteractive: boolean,
force: boolean,
Expand Down Expand Up @@ -71,7 +73,7 @@
"Explicit acknowledgement required for breaking schema or connector changes and new insecure operations. Rerun this command with --force to deploy these changes.",
);
} else if (!nonInteractive && !force && !dryRun) {
const result = await promptOnce({

Check warning on line 76 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
message: "Would you like to proceed with these changes?",
type: "list",
choices,
Expand All @@ -90,7 +92,7 @@
prettifyTable(interactiveAcks),
);
if (!nonInteractive && !force && !dryRun) {
const result = await promptOnce({

Check warning on line 95 in src/dataconnect/build.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unsafe assignment of an `any` value
message: "Would you like to proceed with these changes?",
type: "list",
choices,
Expand Down
10 changes: 10 additions & 0 deletions src/defaultCredentials.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from "fs";
import * as path from "path";
import { auth } from "google-auth-library";

import { clientId, clientSecret } from "./api";
import { Tokens, User, Account } from "./types/auth";
Expand Down Expand Up @@ -106,3 +107,12 @@

return slug;
}

export async function hasDefaultCredentials(): Promise<boolean> {

Check warning on line 111 in src/defaultCredentials.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Missing JSDoc comment
try {
await auth.getApplicationDefault();
return true;
} catch (err: any) {

Check warning on line 115 in src/defaultCredentials.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
return false;
}
}
32 changes: 27 additions & 5 deletions src/emulator/dataconnectEmulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import { PostgresServer, TRUNCATE_TABLES_SQL } from "./dataconnect/pgliteServer";
import { cleanShutdown } from "./controller";
import { connectableHostname } from "../utils";
import { Account } from "../types/auth";
import { getCredentialsEnvironment } from "./env";

export interface DataConnectEmulatorArgs {
projectId: string;
Expand All @@ -43,17 +45,20 @@
importPath?: string;
debug?: boolean;
extraEnv?: Record<string, string>;
account?: Account;
}

export interface DataConnectGenerateArgs {
configDir: string;
connectorId: string;
watch?: boolean;
account?: Account;
}

export interface DataConnectBuildArgs {
configDir: string;
projectId?: string;
account?: Account;
}

// TODO: More concrete typing for events. Can we use string unions?
Expand All @@ -61,11 +66,11 @@

export class DataConnectEmulator implements EmulatorInstance {
private emulatorClient: DataConnectEmulatorClient;
private usingExistingEmulator: boolean = false;

Check warning on line 69 in src/emulator/dataconnectEmulator.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Type boolean trivially inferred from a boolean literal, remove type annotation
private postgresServer: PostgresServer | undefined;

constructor(private args: DataConnectEmulatorArgs) {
this.emulatorClient = new DataConnectEmulatorClient();

Check warning on line 73 in src/emulator/dataconnectEmulator.ts

View workflow job for this annotation

GitHub Actions / lint (20)

'DataConnectEmulatorClient' was used before it was defined
}
private logger = EmulatorLogger.forEmulator(Emulators.DATACONNECT);

Expand All @@ -73,7 +78,10 @@
let resolvedConfigDir;
try {
resolvedConfigDir = this.args.config.path(this.args.configDir);
const info = await DataConnectEmulator.build({ configDir: resolvedConfigDir });
const info = await DataConnectEmulator.build({
configDir: resolvedConfigDir,
account: this.args.account,
});
if (requiresVector(info.metadata)) {
if (Constants.isDemoProject(this.args.projectId)) {
this.logger.logLabeled(
Expand All @@ -89,9 +97,10 @@
);
}
}
} catch (err: any) {

Check warning on line 100 in src/emulator/dataconnectEmulator.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Unexpected any. Specify a different type
this.logger.log("DEBUG", `'fdc build' failed with error: ${err.message}`);
}
const env = await DataConnectEmulator.getEnv(this.args.account, this.args.extraEnv);
await start(
Emulators.DATACONNECT,
{
Expand All @@ -101,7 +110,7 @@
enable_output_schema_extensions: this.args.enable_output_schema_extensions,
enable_output_generated_sdk: this.args.enable_output_generated_sdk,
},
this.args.extraEnv,
env,
);

this.usingExistingEmulator = false;
Expand Down Expand Up @@ -239,7 +248,8 @@
if (args.watch) {
cmd.push("--watch");
}
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
const env = await DataConnectEmulator.getEnv(args.account);
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8", env });
if (isIncomaptibleArchError(res.error)) {
throw new FirebaseError(
`Unknown system error when running the Data Connect toolkit. ` +
Expand Down Expand Up @@ -267,8 +277,8 @@
if (args.projectId) {
cmd.push(`--project_id=${args.projectId}`);
}

const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
const env = await DataConnectEmulator.getEnv(args.account);
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8", env });
if (isIncomaptibleArchError(res.error)) {
throw new FirebaseError(
`Unkown system error when running the Data Connect toolkit. ` +
Expand Down Expand Up @@ -341,6 +351,18 @@
}
return false;
}

static async getEnv(
account?: Account,
extraEnv: Record<string, string> = {},
): Promise<NodeJS.ProcessEnv> {
const credsEnv = await getCredentialsEnvironment(
account,
EmulatorLogger.forEmulator(Emulators.DATACONNECT),
"dataconnect",
);
return { ...process.env, ...extraEnv, ...credsEnv };
}
}

type ConfigureEmulatorRequest = {
Expand Down
29 changes: 29 additions & 0 deletions src/emulator/env.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Constants } from "./constants";
import { EmulatorInfo, Emulators } from "./types";
import { formatHost } from "./functionsEmulatorShared";
import { Account } from "../types/auth/index";
import { EmulatorLogger } from "./emulatorLogger";
import { getCredentialPathAsync, hasDefaultCredentials } from "../defaultCredentials";

/**
* Adds or replaces emulator-related env vars (for Admin SDKs, etc.).
Expand Down Expand Up @@ -46,3 +49,29 @@ export function setEnvVarsForEmulators(
}
}
}

/**
* getCredentialsEnvironment returns any extra env vars beyond process.env that should be provided to emulators to ensure they have credentials.
*/
export async function getCredentialsEnvironment(
account: Account | undefined,
logger: EmulatorLogger,
logLabel: string,
): Promise<Record<string, string>> {
// Provide default application credentials when appropriate
const credentialEnv: Record<string, string> = {};
if (await hasDefaultCredentials()) {
logger.logLabeled(
"WARN",
logLabel,
`Application Default Credentials detected. Non-emulated services will access production using these credentials. Be careful!`,
);
} else if (account) {
const defaultCredPath = await getCredentialPathAsync(account);
if (defaultCredPath) {
logger.log("DEBUG", `Setting GAC to ${defaultCredPath}`);
credentialEnv.GOOGLE_APPLICATION_CREDENTIALS = defaultCredPath;
}
}
return credentialEnv;
}
43 changes: 11 additions & 32 deletions src/emulator/functionsEmulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import { PubsubEmulator } from "./pubsubEmulator";
import { FirebaseError } from "../error";
import { WorkQueue, Work } from "./workQueue";
import { allSettled, connectableHostname, createDestroyer, debounce, randomInt } from "../utils";
import { getCredentialPathAsync } from "../defaultCredentials";
import {
AdminSdkConfig,
constructDefaultAdminSdkConfig,
Expand All @@ -60,7 +59,7 @@ import * as functionsEnv from "../functions/env";
import { AUTH_BLOCKING_EVENTS, BEFORE_CREATE_EVENT } from "../functions/events/v1";
import { BlockingFunctionsConfig } from "../gcp/identityPlatform";
import { resolveBackend } from "../deploy/functions/build";
import { setEnvVarsForEmulators } from "./env";
import { getCredentialsEnvironment, setEnvVarsForEmulators } from "./env";
import { runWithVirtualEnv } from "../functions/python";
import { Runtime } from "../deploy/functions/runtimes/supported";
import { ExtensionsEmulator } from "./extensionsEmulator";
Expand Down Expand Up @@ -271,7 +270,11 @@ export class FunctionsEmulator implements EmulatorInstance {
this.dynamicBackends =
this.args.extensionsEmulator.filterUnemulatedTriggers(unfilteredBackends);
const mode = this.debugMode ? FunctionsExecutionMode.SEQUENTIAL : FunctionsExecutionMode.AUTO;
const credentialEnv = await this.getCredentialsEnvironment();
const credentialEnv = await getCredentialsEnvironment(
this.args.account,
this.logger,
"functions",
);
for (const backend of this.dynamicBackends) {
backend.env = { ...credentialEnv, ...backend.env };
if (this.workerPools[backend.codebase]) {
Expand All @@ -293,34 +296,6 @@ export class FunctionsEmulator implements EmulatorInstance {
}
}

private async getCredentialsEnvironment(): Promise<Record<string, string>> {
// Provide default application credentials when appropriate
const credentialEnv: Record<string, string> = {};
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
this.logger.logLabeled(
"WARN",
"functions",
`Your GOOGLE_APPLICATION_CREDENTIALS environment variable points to ${process.env.GOOGLE_APPLICATION_CREDENTIALS}. Non-emulated services will access production using these credentials. Be careful!`,
);
} else if (this.args.account) {
const defaultCredPath = await getCredentialPathAsync(this.args.account);
if (defaultCredPath) {
this.logger.log("DEBUG", `Setting GAC to ${defaultCredPath}`);
credentialEnv.GOOGLE_APPLICATION_CREDENTIALS = defaultCredPath;
}
} else {
// TODO: It would be safer to set GOOGLE_APPLICATION_CREDENTIALS to /dev/null here but we can't because some SDKs don't work
// without credentials even when talking to the emulator: https://github.com/firebase/firebase-js-sdk/issues/3144
this.logger.logLabeled(
"WARN",
"functions",
"You are not signed in to the Firebase CLI. If you have authorized this machine using gcloud application-default credentials those may be discovered and used to access production services.",
);
}

return credentialEnv;
}

createHubServer(): express.Application {
// TODO(samstern): Should not need this here but some tests are directly calling this method
// because FunctionsEmulator.start() used to not be test safe.
Expand Down Expand Up @@ -458,7 +433,11 @@ export class FunctionsEmulator implements EmulatorInstance {
}

async start(): Promise<void> {
const credentialEnv = await this.getCredentialsEnvironment();
const credentialEnv = await getCredentialsEnvironment(
this.args.account,
this.logger,
"functions",
);
for (const e of this.staticBackends) {
e.env = { ...credentialEnv, ...e.env };
}
Expand Down
3 changes: 3 additions & 0 deletions src/init/features/dataconnect/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { DataConnectEmulator } from "../../../emulator/dataconnectEmulator";
import { FirebaseError } from "../../../error";
import { camelCase, snakeCase, upperFirst } from "lodash";
import { logSuccess, logBullet } from "../../../utils";
import { getGlobalDefaultAccount } from "../../../auth";

export const FDC_APP_FOLDER = "_FDC_APP_FOLDER";
export type SDKInfo = {
Expand Down Expand Up @@ -227,9 +228,11 @@ export async function actuate(sdkInfo: SDKInfo) {
const connectorYamlPath = `${sdkInfo.connectorInfo.directory}/connector.yaml`;
fs.writeFileSync(connectorYamlPath, sdkInfo.connectorYamlContents, "utf8");
logBullet(`Wrote new config to ${connectorYamlPath}`);
const account = getGlobalDefaultAccount();
await DataConnectEmulator.generate({
configDir: sdkInfo.connectorInfo.directory,
connectorId: sdkInfo.connectorInfo.connectorYaml.connectorId,
account,
});
logBullet(`Generated SDK code for ${sdkInfo.connectorInfo.connectorYaml.connectorId}`);
if (sdkInfo.connectorInfo.connectorYaml.generate?.swiftSdk && sdkInfo.displayIOSWarning) {
Expand Down
Loading