From fc61f7a902bdc97fb8dd0c0b59eb7e06bc4d24ec Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 7 Dec 2025 19:54:30 -0600 Subject: [PATCH 1/7] Add handling of args in config file and improve keyFile support --- packages/mongodb-runner/src/cli.ts | 4 ++ packages/mongodb-runner/src/mongoserver.ts | 84 ++++++++++++++++++---- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/packages/mongodb-runner/src/cli.ts b/packages/mongodb-runner/src/cli.ts index 6e00db37..c500284c 100644 --- a/packages/mongodb-runner/src/cli.ts +++ b/packages/mongodb-runner/src/cli.ts @@ -90,6 +90,10 @@ import type { MongoClientOptions } from 'mongodb'; .demandCommand(1, 'A command needs to be provided') .help().argv; const [command, ...args] = argv._.map(String); + // Allow args to be provided by the config file. + if (Array.isArray(argv.args)) { + args.push(...argv.args.map(String)); + } if (argv.debug || argv.verbose) { createDebug.enable('mongodb-runner'); } diff --git a/packages/mongodb-runner/src/mongoserver.ts b/packages/mongodb-runner/src/mongoserver.ts index 7e7fab1c..a231757b 100644 --- a/packages/mongodb-runner/src/mongoserver.ts +++ b/packages/mongodb-runner/src/mongoserver.ts @@ -20,6 +20,7 @@ import { debugVerbose, jsonClone, makeConnectionString, + sleep, } from './util'; /** @@ -286,9 +287,11 @@ export class MongoServer extends EventEmitter { logEntryStream.resume(); srv.port = port; - const buildInfoError = await srv._populateBuildInfo('insert-new'); - if (buildInfoError) { - debug('failed to get buildInfo', buildInfoError); + if (!options.args?.includes('--keyFile')) { + const buildInfoError = await srv._populateBuildInfo('insert-new'); + if (buildInfoError) { + debug('failed to get buildInfo', buildInfoError); + } } } catch (err) { await srv.close(); @@ -301,24 +304,77 @@ export class MongoServer extends EventEmitter { async updateDefaultConnectionOptions( options: Partial, ): Promise { + // Assume we need these new options to connect. + this.defaultConnectionOptions = { + ...this.defaultConnectionOptions, + ...options, + }; + + // If there is no auth in the connection options, do an immediate metadata refresh and return. let buildInfoError: Error | null = null; + if (!options.auth) { + buildInfoError = await this._populateBuildInfo('restore-check'); + if (buildInfoError) { + debug( + 'failed to refresh buildInfo when updating connection options', + buildInfoError, + options, + ); + throw buildInfoError; + } + return; + } + + debug('Waiting for authorization on', this.port); + + // Wait until we can get connectionStatus. + let supportsAuth = false; + let error: unknown = null; for (let attempts = 0; attempts < 10; attempts++) { - buildInfoError = await this._populateBuildInfo('restore-check', { - ...options, - }); - if (!buildInfoError) break; + error = null; + try { + supportsAuth = await this.withClient(async (client) => { + const status = await client + .db('admin') + .command({ connectionStatus: 1 }); + if (status.authInfo.authenticatedUsers.length > 0) { + return true; + } + // If the server does not support auth, just get the build info without + // setting the metadata. + debug('Server does not support authorization', this.port); + this.buildInfo = await client.db('admin').command({ buildInfo: 1 }); + return false; + }); + } catch (e) { + error = e; + await sleep(2 ** attempts * 10); + } + if (error === null) { + break; + } + } + + if (error !== null) { + throw error; + } + + if (!supportsAuth) { + return; + } + + const mode = this.hasInsertedMetadataCollEntry + ? 'restore-check' + : 'insert-new'; + buildInfoError = await this._populateBuildInfo(mode); + if (buildInfoError) { debug( - 'failed to get buildInfo when setting new options', + 'failed to refresh buildInfo when updating connection options', buildInfoError, options, - this.connectionString, ); + throw buildInfoError; } - if (buildInfoError) throw buildInfoError; - this.defaultConnectionOptions = { - ...this.defaultConnectionOptions, - ...options, - }; } async close(): Promise { From 3a6eadf95c20ad7c682ddd44a9482dfb3913de04 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 8 Dec 2025 12:12:20 -0600 Subject: [PATCH 2/7] handle requireApiVersion --- packages/mongodb-runner/src/mongocluster.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/mongodb-runner/src/mongocluster.ts b/packages/mongodb-runner/src/mongocluster.ts index 20b0a4eb..7ced7188 100644 --- a/packages/mongodb-runner/src/mongocluster.ts +++ b/packages/mongodb-runner/src/mongocluster.ts @@ -110,6 +110,11 @@ export interface CommonOptions { */ tlsAddClientKey?: boolean; + /** + * Whether to require an API version for commands. + */ + requireApiVersion?: number; + /** * Topology of the cluster. */ @@ -528,6 +533,17 @@ export class MongoCluster extends EventEmitter { } await cluster.addAuthIfNeeded(); + + // Set up requireApiVersion if requested. + if (options.requireApiVersion !== undefined) { + await cluster.withClient(async (client) => { + const admin = client.db('admin'); + await admin.command({ setParameter: 1, requireApiVersion: true }); + }); + await cluster.updateDefaultConnectionOptions({ + serverApi: String(options.requireApiVersion) as '1', + }); + } return cluster; } From a3904c66abd6854bd643066bd851684611145640 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 8 Dec 2025 12:41:37 -0600 Subject: [PATCH 3/7] fix requireApiVersion handling --- packages/mongodb-runner/src/mongocluster.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/mongodb-runner/src/mongocluster.ts b/packages/mongodb-runner/src/mongocluster.ts index 7ced7188..961311dd 100644 --- a/packages/mongodb-runner/src/mongocluster.ts +++ b/packages/mongodb-runner/src/mongocluster.ts @@ -536,10 +536,20 @@ export class MongoCluster extends EventEmitter { // Set up requireApiVersion if requested. if (options.requireApiVersion !== undefined) { - await cluster.withClient(async (client) => { - const admin = client.db('admin'); - await admin.command({ setParameter: 1, requireApiVersion: true }); - }); + if (options.topology === 'replset') { + throw new Error( + 'requireApiVersion is not supported for replica sets, see SERVER-97010', + ); + } + await Promise.all( + [...cluster.servers].map( + async (child) => + await child.withClient(async (client) => { + const admin = client.db('admin'); + await admin.command({ setParameter: 1, requireApiVersion: true }); + }), + ), + ); await cluster.updateDefaultConnectionOptions({ serverApi: String(options.requireApiVersion) as '1', }); From f52db8f5cdba36a9d573aac1f3d38ab93155e45e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 8 Dec 2025 13:08:03 -0600 Subject: [PATCH 4/7] do not set requireApiVersion in shards --- packages/mongodb-runner/src/mongocluster.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mongodb-runner/src/mongocluster.ts b/packages/mongodb-runner/src/mongocluster.ts index 961311dd..20c07a96 100644 --- a/packages/mongodb-runner/src/mongocluster.ts +++ b/packages/mongodb-runner/src/mongocluster.ts @@ -493,6 +493,7 @@ export class MongoCluster extends EventEmitter { ...options, ...s, topology: 'replset', + requireApiVersion: undefined, users: isConfig ? undefined : options.users, // users go on the mongos/config server only for the config set }); return [cluster, isConfig] as const; From 4d776e02bb9ef1aa1ba5c2099575f847d4de3d50 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 8 Dec 2025 14:57:50 -0600 Subject: [PATCH 5/7] allow connection string to be captured by the caller --- packages/mongodb-runner/src/cli.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/mongodb-runner/src/cli.ts b/packages/mongodb-runner/src/cli.ts index c500284c..633eb19a 100644 --- a/packages/mongodb-runner/src/cli.ts +++ b/packages/mongodb-runner/src/cli.ts @@ -115,22 +115,29 @@ import type { MongoClientOptions } from 'mongodb'; async function start() { const { cluster, id } = await utilities.start(argv, args); const cs = new ConnectionString(cluster.connectionString); - console.log(`Server started and running at ${cs.toString()}`); + // Only the connection string should print to stdout so it can be captured + // by a calling process. + console.error(`Server started and running at ${cs.toString()}`); if (cluster.oidcIssuer) { cs.typedSearchParams().set( 'authMechanism', 'MONGODB-OIDC', ); - console.log(`OIDC provider started and running at ${cluster.oidcIssuer}`); - console.log(`Server connection string with OIDC auth: ${cs.toString()}`); + console.error( + `OIDC provider started and running at ${cluster.oidcIssuer}`, + ); + console.error( + `Server connection string with OIDC auth: ${cs.toString()}`, + ); } - console.log('Run the following command to stop the instance:'); - console.log( + console.error('Run the following command to stop the instance:'); + console.error( `${argv.$0} stop --id=${id}` + (argv.runnerDir !== defaultRunnerDir ? `--runnerDir=${argv.runnerDir}` : ''), ); + console.log(cs.toString()); cluster.unref(); } From 892a8ba55dc4548577dfbe0f1cc5ceb28b9aafac Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 9 Dec 2025 08:34:41 -0600 Subject: [PATCH 6/7] address review --- packages/mongodb-runner/src/mongocluster.ts | 48 ++++++++++++--------- packages/mongodb-runner/src/mongoserver.ts | 6 ++- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/mongodb-runner/src/mongocluster.ts b/packages/mongodb-runner/src/mongocluster.ts index 20c07a96..55c4fd80 100644 --- a/packages/mongodb-runner/src/mongocluster.ts +++ b/packages/mongodb-runner/src/mongocluster.ts @@ -534,27 +534,7 @@ export class MongoCluster extends EventEmitter { } await cluster.addAuthIfNeeded(); - - // Set up requireApiVersion if requested. - if (options.requireApiVersion !== undefined) { - if (options.topology === 'replset') { - throw new Error( - 'requireApiVersion is not supported for replica sets, see SERVER-97010', - ); - } - await Promise.all( - [...cluster.servers].map( - async (child) => - await child.withClient(async (client) => { - const admin = client.db('admin'); - await admin.command({ setParameter: 1, requireApiVersion: true }); - }), - ), - ); - await cluster.updateDefaultConnectionOptions({ - serverApi: String(options.requireApiVersion) as '1', - }); - } + await cluster.addRequireApiVersionIfNeeded(options); return cluster; } @@ -563,6 +543,32 @@ export class MongoCluster extends EventEmitter { yield* this.shards; } + async addRequireApiVersionIfNeeded({ + ...options + }: MongoClusterOptions): Promise { + // Set up requireApiVersion if requested. + if (options.requireApiVersion !== undefined) { + return; + } + if (options.topology === 'replset') { + throw new Error( + 'requireApiVersion is not supported for replica sets, see SERVER-97010', + ); + } + await Promise.all( + [...this.servers].map( + async (child) => + await child.withClient(async (client) => { + const admin = client.db('admin'); + await admin.command({ setParameter: 1, requireApiVersion: true }); + }), + ), + ); + await this.updateDefaultConnectionOptions({ + serverApi: String(options.requireApiVersion) as '1', + }); + } + async addAuthIfNeeded(): Promise { if (!this.users?.length) return; // Sleep to give time for a possible replset election to settle. diff --git a/packages/mongodb-runner/src/mongoserver.ts b/packages/mongodb-runner/src/mongoserver.ts index a231757b..1abb6a0b 100644 --- a/packages/mongodb-runner/src/mongoserver.ts +++ b/packages/mongodb-runner/src/mongoserver.ts @@ -287,6 +287,8 @@ export class MongoServer extends EventEmitter { logEntryStream.resume(); srv.port = port; + // If a keyFile is present, we cannot read or write on the server until + // a user is added to the primary. if (!options.args?.includes('--keyFile')) { const buildInfoError = await srv._populateBuildInfo('insert-new'); if (buildInfoError) { @@ -340,8 +342,8 @@ export class MongoServer extends EventEmitter { if (status.authInfo.authenticatedUsers.length > 0) { return true; } - // If the server does not support auth, just get the build info without - // setting the metadata. + // The server is most likely an arbiter, which does not support + // authenticated users but does support getting the buildInfo. debug('Server does not support authorization', this.port); this.buildInfo = await client.db('admin').command({ buildInfo: 1 }); return false; From 56a3abdfefccf41f85b7da2d7794a733039107ed Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 9 Dec 2025 09:28:35 -0600 Subject: [PATCH 7/7] fix logic --- packages/mongodb-runner/src/mongocluster.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongodb-runner/src/mongocluster.ts b/packages/mongodb-runner/src/mongocluster.ts index 55c4fd80..b5b93959 100644 --- a/packages/mongodb-runner/src/mongocluster.ts +++ b/packages/mongodb-runner/src/mongocluster.ts @@ -547,7 +547,7 @@ export class MongoCluster extends EventEmitter { ...options }: MongoClusterOptions): Promise { // Set up requireApiVersion if requested. - if (options.requireApiVersion !== undefined) { + if (options.requireApiVersion === undefined) { return; } if (options.topology === 'replset') {