Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 +1,2 @@
- Fixes an issue where dataconnect:sql:migrate still prompts for confirmation even with `--force`. (#7208)
- Fixes an issue where the dataconnect emulator listens on all addresses by default instead of just localhost (#7211).
16 changes: 6 additions & 10 deletions src/commands/dataconnect-sdk-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as path from "path";

import { Command } from "../command";
import { Options } from "../options";
import { DataConnectEmulator, DataConnectEmulatorArgs } from "../emulator/dataconnectEmulator";
import { DataConnectEmulator } from "../emulator/dataconnectEmulator";
import { needProjectId } from "../projectUtils";
import { load } from "../dataconnect/load";
import { readFirebaseJson } from "../dataconnect/fileUtils";
Expand All @@ -21,16 +21,12 @@ export const command = new Command("dataconnect:sdk:generate")
configDir = path.resolve(path.join(cwd), configDir);
}
const serviceInfo = await load(projectId, service.location, configDir);
const args: DataConnectEmulatorArgs = {
projectId,
configDir,
auto_download: true,
rc: options.rc,
locationId: service.location,
};
const dataconnectEmulator = new DataConnectEmulator(args);
for (const conn of serviceInfo.connectorInfo) {
const output = await dataconnectEmulator.generate(conn.connectorYaml.connectorId);
const output = await DataConnectEmulator.generate({
configDir,
locationId: service.location,
connectorId: conn.connectorYaml.connectorId,
});
logger.info(output);
logger.info(`Generated SDKs for ${conn.connectorYaml.connectorId}`);
}
Expand Down
13 changes: 2 additions & 11 deletions src/dataconnect/build.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { DataConnectEmulator, DataConnectEmulatorArgs } from "../emulator/dataconnectEmulator";
import { DataConnectEmulator } from "../emulator/dataconnectEmulator";
import { Options } from "../options";
import { FirebaseError } from "../error";
import { prettify } from "./graphqlError";
import { DeploymentMetadata } from "./types";

export async function build(options: Options, configDir: string): Promise<DeploymentMetadata> {
// We can build even if there is no project declared.
const projectId = options.project ?? "demo-test";
const args: DataConnectEmulatorArgs = {
projectId,
configDir,
auto_download: true,
rc: options.rc,
};
const dataconnectEmulator = new DataConnectEmulator(args);
const buildResult = await dataconnectEmulator.build();
const buildResult = await DataConnectEmulator.build({ configDir });
if (buildResult?.errors?.length) {
throw new FirebaseError(
`There are errors in your schema and connector files:\n${buildResult.errors.map(prettify).join("\n")}`,
Expand Down
4 changes: 1 addition & 3 deletions src/emulator/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,6 @@ export async function startAll(
}

if (listenForEmulator.dataconnect) {
const dataConnectAddr = legacyGetFirstAddr(Emulators.DATACONNECT);
const config = readFirebaseJson(options.config);
if (!config.length) {
throw new FirebaseError("No Data Connect service found in firebase.json");
Expand All @@ -836,8 +835,7 @@ export async function startAll(
configDir = path.resolve(path.join(cwd), configDir);
}
const dataConnectEmulator = new DataConnectEmulator({
host: dataConnectAddr.host,
port: dataConnectAddr.port,
listen: listenForEmulator.dataconnect,
projectId,
auto_download: true,
configDir,
Expand Down
53 changes: 32 additions & 21 deletions src/emulator/dataconnectEmulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,39 @@ import * as childProcess from "child_process";
import { dataConnectLocalConnString } from "../api";
import { Constants } from "./constants";
import { getPID, start, stop, downloadIfNecessary } from "./downloadableEmulators";
import { EmulatorInfo, EmulatorInstance, Emulators } from "./types";
import { EmulatorInfo, EmulatorInstance, Emulators, ListenSpec } from "./types";
import { FirebaseError } from "../error";
import { EmulatorLogger } from "./emulatorLogger";
import { RC } from "../rc";
import { BuildResult, requiresVector } from "../dataconnect/types";
import { listenSpecsToString } from "./portUtils";

export interface DataConnectEmulatorArgs {
projectId?: string;
port?: number;
host?: string;
configDir?: string;
listen: ListenSpec[];
configDir: string;
locationId?: string;
auto_download?: boolean;
rc: RC;
}

const grpcDefaultPort = 9510;
export interface DataConnectGenerateArgs {
configDir: string;
locationId: string;
connectorId: string;
}

export interface DataConnectBuildArgs {
configDir: string;
}

export class DataConnectEmulator implements EmulatorInstance {
constructor(private args: DataConnectEmulatorArgs) {}
private logger = EmulatorLogger.forEmulator(Emulators.DATACONNECT);

async start(): Promise<void> {
const port = this.args.port || Constants.getDefaultPort(Emulators.DATACONNECT);
this.logger.log("DEBUG", `Using Postgres connection string: ${this.getLocalConectionString()}`);
const info = await this.build();
const info = await DataConnectEmulator.build({ configDir: this.args.configDir });
if (requiresVector(info.metadata)) {
if (Constants.isDemoProject(this.args.projectId)) {
this.logger.logLabeled(
Expand All @@ -46,8 +53,7 @@ export class DataConnectEmulator implements EmulatorInstance {
}
return start(Emulators.DATACONNECT, {
...this.args,
http_port: port,
grpc_port: grpcDefaultPort,
listen: listenSpecsToString(this.args.listen),
config_dir: this.args.configDir,
local_connection_string: this.getLocalConectionString(),
project_id: this.args.projectId,
Expand All @@ -65,13 +71,11 @@ export class DataConnectEmulator implements EmulatorInstance {
}

getInfo(): EmulatorInfo {
const host = this.args.host || Constants.getDefaultHost();
const port = this.args.port || Constants.getDefaultPort(Emulators.DATACONNECT);

return {
name: this.getName(),
host,
port,
listen: this.args.listen,
host: this.args.listen[0].address,
port: this.args.listen[0].port,
pid: getPID(Emulators.DATACONNECT),
timeout: 10_000,
};
Expand All @@ -80,26 +84,33 @@ export class DataConnectEmulator implements EmulatorInstance {
return Emulators.DATACONNECT;
}

async generate(connectorId: string): Promise<string> {
static async generate(args: DataConnectGenerateArgs): Promise<string> {
const commandInfo = await downloadIfNecessary(Emulators.DATACONNECT);
const cmd = [
"generate",
`--service_location=${this.args.locationId}`,
`--config_dir=${this.args.configDir}`,
`--connector_id=${connectorId}`,
`--service_location=${args.locationId}`,
`--config_dir=${args.configDir}`,
`--connector_id=${args.connectorId}`,
];
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
if (res.error) {
throw new FirebaseError(`Error starting up Data Connect emulator: ${res.error}`);
throw new FirebaseError(`Error starting up Data Connect generate: ${res.error.message}`, {
original: res.error,
});
}
return res.stdout;
}

async build(): Promise<BuildResult> {
static async build(args: DataConnectBuildArgs): Promise<BuildResult> {
const commandInfo = await downloadIfNecessary(Emulators.DATACONNECT);
const cmd = ["build", `--config_dir=${this.args.configDir}`];
const cmd = ["build", `--config_dir=${args.configDir}`];

const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
if (res.error) {
throw new FirebaseError(`Error starting up Data Connect build: ${res.error.message}`, {
original: res.error,
});
}
if (res.stderr) {
throw new FirebaseError(
`Unable to build your Data Connect schema and connectors: ${res.stderr}`,
Expand Down
2 changes: 1 addition & 1 deletion src/emulator/downloadableEmulators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ const Commands: { [s in DownloadableEmulators]: DownloadableEmulatorCommand } =
dataconnect: {
binary: getExecPath(Emulators.DATACONNECT),
args: ["dev"],
optionalArgs: ["http_port", "grpc_port", "config_dir", "local_connection_string", "project_id"],
optionalArgs: ["listen", "config_dir", "local_connection_string", "project_id"],
joinArgs: true,
shell: true,
},
Expand Down
16 changes: 15 additions & 1 deletion src/emulator/portUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY: Record<PortName, boolean> = {
firestore: true,
"firestore.websocket": true,
pubsub: true,
dataconnect: true,

// External processes that accepts multiple listen specs.
dataconnect: false,

// Listening on multiple addresses to maximize the chance of discovery.
hub: false,
Expand Down Expand Up @@ -463,3 +465,15 @@ function listenSpec(lookup: dns.LookupAddress, port: number): ListenSpec {
port: port,
};
}

/**
* Return a comma-separated list of host:port from specs.
*/
export function listenSpecsToString(specs: ListenSpec[]): string {
return specs
.map((spec) => {
const host = spec.family === "IPv4" ? spec.address : `[${spec.address}]`;
return `${host}:${spec.port}`;
})
.join(",");
}