diff --git a/package-lock.json b/package-lock.json index 146141a8..28d185b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30201,7 +30201,7 @@ "@mongodb-js/saslprep": "^1.6.0", "@peculiar/x509": "^2.1.0", "debug": "^4.4.0", - "mongodb": "^7.5.0", + "mongodb": "~7.5.0", "mongodb-connection-string-url": "^7.0.2", "reflect-metadata": "^0.2.2", "yargs": "^18.2.0" @@ -30291,6 +30291,52 @@ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, + "packages/mongodb-runner/node_modules/mongodb": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.5.0.tgz", + "integrity": "sha512-5FnrEDLnvp6ycUOGLNLLU33BfCx2qmp2mJjGPDwKLruYsVzXVSK5fsGpoDXvsXJwBfBsD7ebMRdawbDxC2814g==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.4.11", + "bson": "^7.2.0", + "mongodb-connection-string-url": "^7.0.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": "^7.2.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, "packages/mongodb-runner/node_modules/string-width": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", diff --git a/packages/mongodb-runner/package.json b/packages/mongodb-runner/package.json index fd7a8188..2d9522a8 100644 --- a/packages/mongodb-runner/package.json +++ b/packages/mongodb-runner/package.json @@ -65,7 +65,7 @@ "@mongodb-js/saslprep": "^1.6.0", "@peculiar/x509": "^2.1.0", "debug": "^4.4.0", - "mongodb": "^7.5.0", + "mongodb": "~7.5.0", "mongodb-connection-string-url": "^7.0.2", "reflect-metadata": "^0.2.2", "yargs": "^18.2.0" diff --git a/packages/mongodb-runner/src/mongocluster.spec.ts b/packages/mongodb-runner/src/mongocluster.spec.ts index 547def3b..5cc595a7 100644 --- a/packages/mongodb-runner/src/mongocluster.spec.ts +++ b/packages/mongodb-runner/src/mongocluster.spec.ts @@ -365,50 +365,17 @@ describe('MongoCluster', function () { if (process.platform !== 'linux') return this.skip(); // No docker }); - it('can spawn a 8.0.x replset using docker', async function () { - cluster = await MongoCluster.start({ - version: '8.0.x', - topology: 'replset', - tmpDir, - docker: 'mongo:8.0', - downloadOptions: { - distro: 'ubuntu2404', - }, - }); - expect(cluster.connectionString).to.be.a('string'); - expect(cluster.serverVersion).to.match(/^8\./); - const hello = await cluster.withClient(async (client) => { - return await client.db('admin').command({ hello: 1 }); - }); - expect(+hello.passives.length + +hello.hosts.length).to.equal(3); - }); - - it('can spawn a 8.0.x sharded env using docker', async function () { - cluster = await MongoCluster.start({ - version: '8.0.x', - topology: 'sharded', - tmpDir, - docker: 'mongo:8.0', - shards: 1, - secondaries: 0, - downloadOptions: { - distro: 'ubuntu2404', - }, - }); - expect(cluster.connectionString).to.be.a('string'); - expect(cluster.serverVersion).to.match(/^8\./); - const hello = await cluster.withClient(async (client) => { - return await client.db('admin').command({ hello: 1 }); - }); - expect(hello.msg).to.equal('isdbgrid'); - }); - - it('can spawn a 8.0.x standalone mongod with TLS enabled and get build info', async function () { - cluster = await MongoCluster.start({ - version: '8.0.x', - topology: 'standalone', - tmpDir, - args: [ + // These exercise the docker code path across server generations. 4.2 is + // included because it is the easiest way to ensure that MongoServer can + // handle the pre-4.4 log format (in the devtools-shared CI, we only test + // ubuntu-latest otherwise). It also requires the legacy TLS arguments. + const dockerVersions = [ + { + version: '4.2.x', + docker: 'mongo:4.2', + replsetDistro: 'ubuntu1804', + otherDistro: 'ubuntu1604', + tlsArgs: [ '--sslMode', 'requireSSL', '--sslPEMKeyFile', @@ -416,18 +383,103 @@ describe('MongoCluster', function () { '--sslCAFile', `/projectroot/test/fixtures/${CA_CERT}`, ], - docker: [ - `--volume=${path.resolve(__dirname, '..')}:/projectroot:ro`, - 'mongo:8.0', + }, + { + version: '8.0.x', + docker: 'mongo:8.0', + replsetDistro: 'ubuntu2404', + otherDistro: 'ubuntu2404', + tlsArgs: [ + '--tlsMode', + 'requireTLS', + '--tlsCertificateKeyFile', + `/projectroot/test/fixtures/${SERVER_KEY}`, + '--tlsCAFile', + `/projectroot/test/fixtures/${CA_CERT}`, + ], + }, + { + version: '9.0.x', + docker: 'mongo:9.0', + replsetDistro: 'ubuntu2404', + otherDistro: 'ubuntu2404', + tlsArgs: [ + '--tlsMode', + 'requireTLS', + '--tlsCertificateKeyFile', + `/projectroot/test/fixtures/${SERVER_KEY}`, + '--tlsCAFile', + `/projectroot/test/fixtures/${CA_CERT}`, ], - downloadOptions: { - distro: 'ubuntu2404', - }, + }, + ]; + + for (const { + version, + docker, + replsetDistro, + otherDistro, + tlsArgs, + } of dockerVersions) { + const serverVersion = new RegExp(`^${version.split('.')[0]}\\.`); + + it(`can spawn a ${version} replset using docker`, async function () { + cluster = await MongoCluster.start({ + version, + topology: 'replset', + tmpDir, + docker, + downloadOptions: { + distro: replsetDistro, + }, + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(serverVersion); + const hello = await cluster.withClient(async (client) => { + return await client.db('admin').command({ hello: 1 }); + }); + expect(+hello.passives.length + +hello.hosts.length).to.equal(3); }); - expect(cluster.connectionString).to.be.a('string'); - expect(cluster.serverVersion).to.match(/^8\./); - expect(cluster.serverVariant).to.equal('community'); - }); + + it(`can spawn a ${version} sharded env using docker`, async function () { + cluster = await MongoCluster.start({ + version, + topology: 'sharded', + tmpDir, + docker, + shards: 1, + secondaries: 0, + downloadOptions: { + distro: otherDistro, + }, + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(serverVersion); + const hello = await cluster.withClient(async (client) => { + return await client.db('admin').command({ hello: 1 }); + }); + expect(hello.msg).to.equal('isdbgrid'); + }); + + it(`can spawn a ${version} standalone mongod with TLS enabled and get build info`, async function () { + cluster = await MongoCluster.start({ + version, + topology: 'standalone', + tmpDir, + args: tlsArgs, + docker: [ + `--volume=${path.resolve(__dirname, '..')}:/projectroot:ro`, + docker, + ], + downloadOptions: { + distro: otherDistro, + }, + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(serverVersion); + expect(cluster.serverVariant).to.equal('community'); + }); + } }); it('can spawn a 6.x enterprise standalone mongod', async function () { diff --git a/packages/mongodb-runner/src/mongologreader.ts b/packages/mongodb-runner/src/mongologreader.ts index 566e5e29..bb36915e 100644 --- a/packages/mongodb-runner/src/mongologreader.ts +++ b/packages/mongodb-runner/src/mongologreader.ts @@ -84,11 +84,21 @@ export function isFailureToSetupListener(entry: LogEntry): boolean { * @returns The port in question, or an -1 if the log line did not match. */ function getPortFromLogEntry(logEntry: LogEntry): number { + let match; // Log message id 23016 has the format // { t: , s: 'I', c: 'NETWORK', id: 23016, ctx: 'listener', msg: '...', attr: { port: 27020 } } if (logEntry.id === 23016) { return logEntry.attr.port; } + // Or, 4.2-style: I NETWORK [listener] waiting for connections on port 27020 + if ( + logEntry.id === undefined && + (match = /^waiting for connections on port (?\d+)( ssl)?$/i.exec( + logEntry.message, + )) + ) { + return +(match.groups?.port ?? '0'); + } if (isFailureToSetupListener(logEntry)) { throw new Error( `Failed to setup listener (${logEntry.message} ${JSON.stringify( @@ -140,11 +150,29 @@ export type BuildInfo = { version: string | null; modules: string[] | null }; * @returns Partial build info. */ function getBuildInfoFromLogEntry(logEntry: LogEntry): Partial { + let match; // Log message id 23403 has the format - // { t: , s: 'I', c: 'CONTROL', id: 23403, ctx: 'initandlisten', msg: '...', attr: { buildInfo: { ... } } } + // { t: , s: 'I', c: 'CONTROL', id: 23403, ctx: 'initandlisten', msg: '...', attr: { buildInfO: { ... } } } if (logEntry.id === 23403) { return logEntry.attr.buildInfo; } + // Or, 4.2-style, split across multiple lines + if ( + logEntry.id === undefined && + (match = /^(?:db|mongos) version v(?.+)$/i.exec(logEntry.message)) + ) { + return { version: match.groups?.version }; + } + if ( + logEntry.id === undefined && + (match = /^modules: (?.+)$/i.exec(logEntry.message)) + ) { + return { + modules: match.groups?.moduleList + ?.split(' ') + ?.filter((module) => module !== 'none'), + }; + } return {}; }