diff --git a/package-lock.json b/package-lock.json index 7be2a8c0..cce4f37d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22469,13 +22469,8 @@ } }, "node_modules/mongodb-build-info": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.7.2.tgz", - "integrity": "sha512-eoLFZvCIjcwijYJdxvYupj1c+55VAVm0o4gBJjrcDxxmmpm+bC4Ix9ayZbyhQdVXDZAGDi03NA0GghXjBVXnxg==", - "license": "Apache-2.0", - "dependencies": { - "mongodb-connection-string-url": "^3.0.0" - } + "resolved": "packages/mongodb-build-info", + "link": true }, "node_modules/mongodb-client-encryption": { "version": "6.4.0", @@ -30561,6 +30556,40 @@ "typescript": "^5.0.4" } }, + "packages/mongodb-build-info": { + "version": "1.7.2", + "license": "Apache-2.0", + "dependencies": { + "mongodb-connection-string-url": "^3.0.0" + }, + "devDependencies": { + "@mongodb-js/eslint-config-devtools": "0.9.12", + "@mongodb-js/mocha-config-devtools": "^1.0.5", + "@mongodb-js/prettier-config-devtools": "^1.0.2", + "@mongodb-js/tsconfig-devtools": "^1.0.3", + "@types/mocha": "^9.1.1", + "chai": "^4.5.0", + "depcheck": "^1.4.7", + "eslint": "^7.25.0", + "mocha": "^8.4.0", + "nyc": "^15.1.0", + "typescript": "^5.0.4" + } + }, + "packages/mongodb-build-info/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "packages/mongodb-cloud-info": { "version": "2.2.3", "license": "Apache-2.0", @@ -50644,11 +50673,28 @@ } }, "mongodb-build-info": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/mongodb-build-info/-/mongodb-build-info-1.7.2.tgz", - "integrity": "sha512-eoLFZvCIjcwijYJdxvYupj1c+55VAVm0o4gBJjrcDxxmmpm+bC4Ix9ayZbyhQdVXDZAGDi03NA0GghXjBVXnxg==", + "version": "file:packages/mongodb-build-info", "requires": { - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/eslint-config-devtools": "0.9.12", + "@mongodb-js/mocha-config-devtools": "^1.0.5", + "@mongodb-js/prettier-config-devtools": "^1.0.2", + "@mongodb-js/tsconfig-devtools": "^1.0.3", + "@types/mocha": "^9.1.1", + "chai": "^4.5.0", + "depcheck": "^1.4.7", + "eslint": "^7.25.0", + "mocha": "^8.4.0", + "mongodb-connection-string-url": "^3.0.0", + "nyc": "^15.1.0", + "typescript": "^5.0.4" + }, + "dependencies": { + "typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true + } } }, "mongodb-client-encryption": { diff --git a/packages/mongodb-build-info/.depcheckrc b/packages/mongodb-build-info/.depcheckrc new file mode 100644 index 00000000..e478a8ff --- /dev/null +++ b/packages/mongodb-build-info/.depcheckrc @@ -0,0 +1,4 @@ +ignores: + - '@mongodb-js/prettier-config-devtools' +ignore-patterns: + - 'dist' diff --git a/packages/mongodb-build-info/.eslintignore b/packages/mongodb-build-info/.eslintignore new file mode 100644 index 00000000..85a8a75e --- /dev/null +++ b/packages/mongodb-build-info/.eslintignore @@ -0,0 +1,2 @@ +.nyc-output +dist diff --git a/packages/mongodb-build-info/.eslintrc.js b/packages/mongodb-build-info/.eslintrc.js new file mode 100644 index 00000000..83296d73 --- /dev/null +++ b/packages/mongodb-build-info/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-devtools'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig-lint.json'], + }, +}; diff --git a/packages/mongodb-build-info/.mocharc.cjs b/packages/mongodb-build-info/.mocharc.cjs new file mode 100644 index 00000000..64afeb1f --- /dev/null +++ b/packages/mongodb-build-info/.mocharc.cjs @@ -0,0 +1 @@ +module.exports = require('@mongodb-js/mocha-config-devtools'); diff --git a/packages/mongodb-build-info/.prettierignore b/packages/mongodb-build-info/.prettierignore new file mode 100644 index 00000000..4d28df66 --- /dev/null +++ b/packages/mongodb-build-info/.prettierignore @@ -0,0 +1,3 @@ +.nyc_output +dist +coverage diff --git a/packages/mongodb-build-info/.prettierrc.json b/packages/mongodb-build-info/.prettierrc.json new file mode 100644 index 00000000..dfae21d0 --- /dev/null +++ b/packages/mongodb-build-info/.prettierrc.json @@ -0,0 +1 @@ +"@mongodb-js/prettier-config-devtools" diff --git a/packages/mongodb-build-info/LICENSE b/packages/mongodb-build-info/LICENSE new file mode 100644 index 00000000..628cffed --- /dev/null +++ b/packages/mongodb-build-info/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 MongoDB Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/mongodb-build-info/README.md b/packages/mongodb-build-info/README.md new file mode 100644 index 00000000..4d8cbb82 --- /dev/null +++ b/packages/mongodb-build-info/README.md @@ -0,0 +1,90 @@ +# mongodb-build-info + +Helpful functions to figure out if a connection is on Atlas, Atlas Data Lake, +Enterpise, or DocumentDB/CosmosDB. + +# Usage + +```js +const getBuildInfo = require('mongodb-build-info'); +const MongoClient = require('mongodb').MongoClient; + +MongoClient.connect('localhost:27017', function (err, client) { + const adminDB = client.db('test').admin(); + let buildInfo; + + adminDB.command({ buildInfo: 1 }, {}, parseBuildInfo); + adminDB.command({ getCmdLineOpts: 1 }, {}, parseCmdLineOpts); + + function parseBuildInfo(err, res) { + if (err) console.log('Command failed, ', err); + buildInfo = res; + + const { isDataLake, dlVersion } = getBuildInfo.getDataLake(buildInfo); + const isEnterprise = getBuildInfo.isEnterprise(buildInfo); + } + + function parseCmdLineOpts(err, res) { + if (err) console.log('Command failed', err.message); + + const { isGenuine, serverName } = getGenuineMongoDB(buildInfo, res); + } +}); +``` + +## API + +### getDataLake(buildInfo) + +Returns an object: + +**isDataLake**: boolean. +**dlVersion**: version of dataLake, a string. + +### isEnterprise(buildInfo) + +Returns a boolean. + +### isAtlas(uri) + +Returns a boolean. + +### isLocalAtlas(count: (db: string, coll: string, query: Document) => Promise\) + +Returns a Promise\. + +### isAtlasStream(uri) + +Returns a boolean. + +### isLocalhost(uri) + +Returns a boolean. + +### isDigitalOcean(uri) + +Returns a boolean. + +### getGenuineMongoDB(buildInfo, cmdLineOpts) + +Returns an object: + +**isGenuine**: boolean. +**serverName**: name of the server (mongoDB, cosmosDB, or documentDB). + +### getBuildEnv(buildInfo) + +Returns an object: + +**serverOs**: build's OS version (macOS, linux, windows etc.). +**serverArch**: build's architecture (e.g. x86_64). + +# Installation + +``` +npm install -S mongodb-build-info +``` + +# License + +Apache-2.0 diff --git a/packages/mongodb-build-info/package.json b/packages/mongodb-build-info/package.json new file mode 100644 index 00000000..4f55fd2c --- /dev/null +++ b/packages/mongodb-build-info/package.json @@ -0,0 +1,82 @@ +{ + "name": "mongodb-build-info", + "version": "1.7.2", + "description": "Extract information from mongodb's buildInfo", + "author": "Irina Shestak ", + "contributors": [ + { + "name": "Irina Shestak", + "email": "shestak.irina@gmail.com" + }, + { + "name": "Kræn Hansen", + "email": "kraen.hansen@mongodb.com" + } + ], + "keywords": [ + "buildInfo", + "mongodb", + "compass-tools", + "mongodb.js" + ], + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/COMPASS/issues", + "email": "compass@mongodb.com" + }, + "homepage": "https://github.com/mongodb-js/devtools-shared", + "repository": { + "type": "git", + "url": "https://github.com/mongodb-js/devtools-shared.git", + "directory": "packages/mongodb-build-info" + }, + "license": "Apache-2.0", + "main": "dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "require": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "import": { + "default": "./dist/.esm-wrapper.mjs", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "bootstrap": "npm run compile", + "prepublishOnly": "npm run compile", + "compile": "tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", + "typecheck": "tsc --noEmit", + "eslint": "eslint .", + "prettier": "prettier", + "lint": "npm run eslint . && npm run prettier -- --check .", + "depcheck": "depcheck", + "check": "npm run typecheck && npm run lint && npm run depcheck", + "check-ci": "npm run check", + "test": "mocha **/*.spec.ts", + "test-cov": "nyc -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", + "test-watch": "npm run test -- --watch", + "test-ci": "npm run test-cov", + "reformat": "npm run prettier -- --write .", + "release": "release-it" + }, + "devDependencies": { + "@mongodb-js/eslint-config-devtools": "0.9.12", + "@mongodb-js/mocha-config-devtools": "^1.0.5", + "@mongodb-js/prettier-config-devtools": "^1.0.2", + "@mongodb-js/tsconfig-devtools": "^1.0.3", + "@types/mocha": "^9.1.1", + "chai": "^4.5.0", + "depcheck": "^1.4.7", + "eslint": "^7.25.0", + "mocha": "^8.4.0", + "nyc": "^15.1.0", + "typescript": "^5.0.4" + }, + "dependencies": { + "mongodb-connection-string-url": "^3.0.0" + } +} diff --git a/packages/mongodb-build-info/src/index.ts b/packages/mongodb-build-info/src/index.ts new file mode 100644 index 00000000..ed75e9b6 --- /dev/null +++ b/packages/mongodb-build-info/src/index.ts @@ -0,0 +1,157 @@ +import ConnectionString from 'mongodb-connection-string-url'; + +const ATLAS_REGEX = /\.mongodb(-dev|-qa|-stage)?\.net$/i; +const ATLAS_STREAM_REGEX = /^atlas-stream-.+/i; +const LOCALHOST_REGEX = + /^(localhost|127\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])|0\.0\.0\.0|(?:0*:)*?:?0*1)$/i; +const DIGITAL_OCEAN_REGEX = /\.mongo\.ondigitalocean\.com$/i; +const COSMOS_DB_REGEX = /\.cosmos\.azure\.com$/i; +const DOCUMENT_DB_REGEX = /docdb(-elastic)?\.amazonaws\.com$/i; + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +export function getDataLake(buildInfo: unknown): { + isDataLake: boolean; + dlVersion: string | null; +} { + if ( + isRecord(buildInfo) && + isRecord(buildInfo.dataLake) && + typeof buildInfo.dataLake.version === 'string' + ) { + return { + isDataLake: true, + dlVersion: buildInfo.dataLake.version, + }; + } else { + return { + isDataLake: false, + dlVersion: null, + }; + } +} + +export function isEnterprise(buildInfo: unknown): boolean { + if (!isRecord(buildInfo)) { + return false; + } + + if ( + typeof buildInfo.gitVersion === 'string' && + buildInfo.gitVersion.match(/enterprise/) + ) { + return true; + } + + if ( + Array.isArray(buildInfo.modules) && + buildInfo.modules.indexOf('enterprise') !== -1 + ) { + return true; + } + + return false; +} + +function getHostnameFromHost(host: string): string { + if (host.startsWith('[')) { + // If it's ipv6 return what's in the brackets. + return host.substring(1).split(']')[0]; + } + return host.split(':')[0]; +} + +function getHostnameFromUrl(url: unknown): string { + if (typeof url !== 'string') { + return ''; + } + + try { + const connectionString = new ConnectionString(url); + return getHostnameFromHost(connectionString.hosts[0]); + } catch (e) { + // we assume is already an hostname, will further be checked against regexes + return getHostnameFromHost(url); + } +} + +export function isAtlas(uri: string): boolean { + return !!getHostnameFromUrl(uri).match(ATLAS_REGEX); +} + +type IsLocalAtlasCountFn = ( + db: string, + ns: string, + query: Record, +) => Promise; +export async function isLocalAtlas( + countFn: IsLocalAtlasCountFn, +): Promise { + try { + const count = await countFn('admin', 'atlascli', { + managedClusterType: 'atlasCliLocalDevCluster', + }); + return count > 0; + } catch { + return false; + } +} +export function isAtlasStream(uri: string): boolean { + const host = getHostnameFromUrl(uri); + return !!(host.match(ATLAS_REGEX) && host.match(ATLAS_STREAM_REGEX)); +} +export function isLocalhost(uri: string): boolean { + return !!getHostnameFromUrl(uri).match(LOCALHOST_REGEX); +} +export function isDigitalOcean(uri: string): boolean { + return !!getHostnameFromUrl(uri).match(DIGITAL_OCEAN_REGEX); +} + +export function getGenuineMongoDB(uri: string): { + isGenuine: boolean; + serverName: string; +} { + const hostname = getHostnameFromUrl(uri); + if (hostname.match(COSMOS_DB_REGEX)) { + return { + isGenuine: false, + serverName: 'cosmosdb', + }; + } + + if (hostname.match(DOCUMENT_DB_REGEX)) { + return { + isGenuine: false, + serverName: 'documentdb', + }; + } + + return { + isGenuine: true, + serverName: 'mongodb', + }; +} + +export function getBuildEnv(buildInfo: unknown): { + serverOs: string | null; + serverArch: string | null; +} { + if (!isRecord(buildInfo) || !isRecord(buildInfo.buildEnvironment)) { + return { serverOs: null, serverArch: null }; + } + + const { buildEnvironment } = buildInfo; + + return { + serverOs: + typeof buildEnvironment.target_os === 'string' + ? buildEnvironment.target_os + : null, + serverArch: + typeof buildEnvironment.target_arch === 'string' + ? buildEnvironment.target_arch + : null, + }; +} diff --git a/packages/mongodb-build-info/test/fixtures.ts b/packages/mongodb-build-info/test/fixtures.ts new file mode 100644 index 00000000..775e010e --- /dev/null +++ b/packages/mongodb-build-info/test/fixtures.ts @@ -0,0 +1,112 @@ +export const BUILD_INFO_OLD = { + version: '2.6.11', + gitVersion: 'd00c1735675c457f75a12d530bee85421f0c5548 modules: enterprise', + OpenSSLVersion: 'OpenSSL 1.0.1f 6 Jan 2014', + sysInfo: + 'Linux ip-10-203-203-194 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 BOOST_LIB_VERSION=1_49', + loaderFlags: + '-fPIC -pthread -Wl,-z,now -rdynamic -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-E', + compilerFlags: + '-Wnon-virtual-dtor -Woverloaded-virtual -fPIC -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -pipe -Werror -O3 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -fno-builtin-memcmp', + allocator: 'tcmalloc', + versionArray: [2, 6, 11, 0], + javascriptEngine: 'V8', + bits: 64, + debug: false, + maxBsonObjectSize: 16777216, + ok: 1, +}; + +export const BUILD_INFO_3_2 = { + version: '3.2.0-rc2', + gitVersion: '8a3acb42742182c5e314636041c2df368232bbc5', + modules: ['enterprise'], + allocator: 'system', + javascriptEngine: 'mozjs', + sysInfo: 'deprecated', + versionArray: [3, 2, 0, -48], + openssl: { + running: 'OpenSSL 0.9.8zg 14 July 2015', + compiled: 'OpenSSL 0.9.8y 5 Feb 2013', + }, + buildEnvironment: { + distmod: '', + distarch: 'x86_64', + cc: 'gcc: Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)', + ccflags: + '-fno-omit-frame-pointer -fPIC -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -Werror -O2 -Wno-unused-function -Wno-unused-private-field -Wno-deprecated-declarations -Wno-tautological-constant-out-of-range-compare -Wno-unused-const-variable -Wno-missing-braces -mmacosx-version-min=10.7 -fno-builtin-memcmp', + cxx: 'g++: Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)', + cxxflags: + '-Wnon-virtual-dtor -Woverloaded-virtual -stdlib=libc++ -std=c++11', + linkflags: + '-fPIC -pthread -Wl,-bind_at_load -mmacosx-version-min=10.7 -stdlib=libc++ -fuse-ld=gold', + target_arch: 'x86_64', + target_os: 'osx', + }, + bits: 64, + debug: false, + maxBsonObjectSize: 16777216, + storageEngines: [ + 'devnull', + 'ephemeralForTest', + 'inMemory', + 'mmapv1', + 'wiredTiger', + ], + ok: 1, +}; + +export const CMD_LINE_OPTS = { + argv: [ + '/opt/mongodb-osx-x86_64-enterprise-3.6.3/bin/mongod', + '--dbpath=/Users/user/testdata', + ], + parsed: { + storage: { + dbPath: '/Users/user/testdata', + }, + }, + ok: 1, +}; + +export const COSMOS_DB_URI = [ + 'mongodb://x:y@compass-serverless.mongo.cosmos.azure.com:19555/?ssl=true&retrywrites=false&maxIdleTimeMS=120000&appName=@compass-serverless@', + 'mongodb://x:y@compass.mongo.cosmos.azure.com:19555/?ssl=true&retrywrites=false&maxIdleTimeMS=120000&appName=@compass@', + 'mongodb+srv://x:y@compass-vcore.mongocluster.cosmos.azure.com/?retrywrites=false&maxIdleTimeMS=120000', +]; + +export const DOCUMENT_DB_URIS = [ + 'mongodb://x:y@docdb-2001-01-01-01-01-01.cluster-abc.eu-central-1.docdb.amazonaws.com:27017/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false', + 'mongodb://x:y@elastic-docdb-123456789.eu-central-1.docdb-elastic.amazonaws.com:27017', +]; + +export const COSMOSDB_BUILD_INFO = { + _t: 'BuildInfoResponse', + ok: 1, + version: '3.2.0', + gitVersion: '45d947729a0315accb6d4f15a6b06be6d9c19fe7', + targetMinOS: 'Windows 7/Windows Server 2008 R2', + modules: [], + allocator: 'tcmalloc', + javascriptEngine: 'Chakra', + sysInfo: 'deprecated', + versionArray: [3, 2, 0, 0], + bits: 64, + debug: false, + maxBsonObjectSize: 524288, + openssl: { + running: 'OpenSSL 1.0.1p-fips 9 Jul 2015', + compiled: 'OpenSSL 1.0.1p-fips 9 Jul 2015', + }, +}; + +export const DATALAKE_BUILD_INFO = { + ok: 1, + version: '3.6.0', + versionArray: [3, 6, 0, 0], + dataLake: { + version: 'v20200329', + gitVersion: '0f318ss78bfad79ede3721e91iasj6f61644f', + date: '2020-03-29T15:41:22Z', + }, +}; diff --git a/packages/mongodb-build-info/test/index.spec.ts b/packages/mongodb-build-info/test/index.spec.ts new file mode 100644 index 00000000..ca5989c6 --- /dev/null +++ b/packages/mongodb-build-info/test/index.spec.ts @@ -0,0 +1,431 @@ +import { expect } from 'chai'; +import * as fixtures from './fixtures'; +import { + isAtlas, + isAtlasStream, + getDataLake, + isLocalhost, + isLocalAtlas, + isDigitalOcean, + getBuildEnv, + isEnterprise, + getGenuineMongoDB, +} from '../src/index'; + +describe('mongodb-build-info', function () { + context('isDataLake', function () { + it('reports on DataLake', function () { + const isDL = getDataLake(fixtures.DATALAKE_BUILD_INFO); + expect(isDL.isDataLake).to.be.true; + expect(isDL.dlVersion).to.equal('v20200329'); + }); + + it('does not report on 3.2', function () { + const isDL = getDataLake(fixtures.BUILD_INFO_3_2); + expect(isDL.isDataLake).to.be.false; + expect(isDL.dlVersion).to.equal(null); + }); + + it('does not report on older versions', function () { + const isDL = getDataLake(fixtures.BUILD_INFO_OLD); + expect(isDL.isDataLake).to.be.false; + expect(isDL.dlVersion).to.equal(null); + }); + }); + + context('isEnterprise', function () { + it('detects enterprise module for 2.6 and 3.0', function () { + expect(isEnterprise(fixtures.BUILD_INFO_OLD)).to.be.true; + }); + + it('detects enterprise module for >= 3.2', function () { + expect(isEnterprise(fixtures.BUILD_INFO_3_2)).to.be.true; + }); + + it('returns false when passed invalid argument', function () { + expect(isEnterprise('')).to.be.false; + expect(isEnterprise(123)).to.be.false; + expect(isEnterprise({})).to.be.false; + expect(isEnterprise(undefined)).to.be.false; + expect(isEnterprise(null)).to.be.false; + }); + }); + + context('getBuildEnv', function () { + it('returns server os and server arch', function () { + const buildEnv = getBuildEnv(fixtures.BUILD_INFO_3_2); + expect(buildEnv.serverOs).to.equal('osx'); + expect(buildEnv.serverArch).to.equal('x86_64'); + }); + + it('returns null when passed an invalid argument', function () { + expect(getBuildEnv(null)).to.deep.equal({ + serverOs: null, + serverArch: null, + }); + expect(getBuildEnv('')).to.deep.equal({ + serverOs: null, + serverArch: null, + }); + expect(getBuildEnv(undefined)).to.deep.equal({ + serverOs: null, + serverArch: null, + }); + expect(getBuildEnv({ buildEnvironment: {} })).to.deep.equal({ + serverOs: null, + serverArch: null, + }); + }); + }); + + context('isAtlas', function () { + it('reports on atlas', function () { + expect( + isAtlas( + 'mongodb+srv://admin:catscatscats@cat-data-sets.cats.mongodb.net/admin', + ), + ).to.be.true; + expect( + isAtlas( + 'mongodb://admin:catscatscats@cat-data-sets.cats.mongodb.net/admin', + ), + ).to.be.true; + expect( + isAtlas( + 'mongodb://admin:catscatscats@cat-data-sets.cats1.mongodb.net,cat-data-sets.cats2.mongodb.net/admin', + ), + ).to.be.true; + }); + + it('works with host only', function () { + expect(isAtlas('cat-data-sets.cats.mongodb.net:27017')).to.be.true; + }); + + it('works with hostname', function () { + expect(isAtlas('cat-data-sets.cats.mongodb.net')).to.be.true; + }); + + it('returns true with atlas dev', function () { + expect( + isAtlas( + 'mongodb+srv://admin:catscatscats@cat-data-sets.cats.mongodb-dev.net/admin', + ), + ).to.be.true; + expect(isAtlas('cat-data-sets.cats.mongodb-dev.net')).to.be.true; + }); + + it('returns true with atlas qa', function () { + expect( + isAtlas( + 'mongodb+srv://admin:catscatscats@cat-data-sets.cats.mongodb-qa.net/admin', + ), + ).to.be.true; + expect(isAtlas('cat-data-sets.cats.mongodb-qa.net')).to.be.true; + }); + + it('returns true with atlas staging', function () { + expect( + isAtlas( + 'mongodb+srv://admin:catscatscats@cat-data-sets.cats.mongodb-stage.net/admin', + ), + ).to.be.true; + expect(isAtlas('cat-data-sets.cats.mongodb-stage.net')).to.be.true; + }); + + it('returns false if not atlas', function () { + expect(isAtlas('cat-data-sets.cats.mangodb.net')).to.be.false; + expect(isAtlas('cat-data-sets.catsmongodb.net')).to.be.false; + expect(isAtlas('cat-data-sets.cats.mongodb.netx')).to.be.false; + expect(isAtlas('cat-data-sets.cats.mongodb.com')).to.be.false; + expect(isAtlas('localhost')).to.be.false; + }); + + it('does not throw and returns with invalid argument', function () { + expect(isAtlas('')).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlas(123)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlas({})).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlas(undefined)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlas(null)).to.be.false; + }); + }); + + context('isLocalAtlas', function () { + it('calls counts function with expected args', function (done) { + void isLocalAtlas((db, coll, query) => { + expect(db).to.equal('admin'); + expect(coll).to.equal('atlascli'); + expect(query).to.deep.equal({ + managedClusterType: 'atlasCliLocalDevCluster', + }); + done(); + throw new Error('Not implemented'); + }); + }); + it('returns false when count resolves to 0', function (done) { + void isLocalAtlas(() => Promise.resolve(0)).then((res) => { + expect(res).to.be.false; + done(); + }); + }); + it('returns false when count throws', function (done) { + void isLocalAtlas(() => Promise.reject('No such db')).then((res) => { + expect(res).to.be.false; + done(); + }); + }); + it('returns true when count resolves to 1', function (done) { + void isLocalAtlas(() => Promise.resolve(1)).then((res) => { + expect(res).to.be.true; + done(); + }); + }); + }); + + context('isAtlasStream', function () { + it('reports on atlas', function () { + expect( + isAtlasStream( + 'mongodb://admin:catscatscats@atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb.net/', + ), + ).to.be.true; + expect( + isAtlasStream( + 'mongodb://admin:catscatscats@atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb.net/', + ), + ).to.be.true; + }); + + it('works with host only', function () { + expect( + isAtlasStream( + 'atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb.net:27017', + ), + ).to.be.true; + }); + + it('works with hostname', function () { + expect( + isAtlasStream( + 'atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb.net', + ), + ).to.be.true; + }); + + it('returns true with atlas dev', function () { + expect( + isAtlasStream( + 'mongodb://admin:catscatscats@atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb-dev.net/', + ), + ).to.be.true; + expect( + isAtlasStream( + 'atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb-dev.net', + ), + ).to.be.true; + }); + + it('returns true with atlas qa', function () { + expect( + isAtlasStream( + 'mongodb://admin:catscatscats@atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb-qa.net/', + ), + ).to.be.true; + expect( + isAtlasStream( + 'atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb-qa.net', + ), + ).to.be.true; + }); + + it('returns true with atlas staging', function () { + expect( + isAtlasStream( + 'mongodb://admin:catscatscats@atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb-stage.net/', + ), + ).to.be.true; + expect( + isAtlasStream( + 'atlas-stream-64ba1372b2a9f1545031f34d-gkumd.virginia-usa.a.query.mongodb-stage.net', + ), + ).to.be.true; + }); + + it('returns false if not atlas stream', function () { + expect(isAtlasStream('cat-data-sets.cats.mangodb.net')).to.be.false; + expect(isAtlasStream('cat-data-sets.catsmongodb.net')).to.be.false; + expect(isAtlasStream('cat-data-sets.cats.mongodb.netx')).to.be.false; + expect(isAtlasStream('cat-data-sets.cats.mongodb.com')).to.be.false; + expect(isAtlasStream('localhost')).to.be.false; + }); + + it('does not throw and returns with invalid argument', function () { + expect(isAtlasStream('')).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlasStream(123)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlasStream({})).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlasStream(undefined)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isAtlasStream(null)).to.be.false; + }); + }); + + context('isLocalhost', function () { + it('reports on localhost', function () { + expect(isLocalhost('localhost:27019')).to.be.true; + }); + + it('reports on localhost of type 127.0.0.1', function () { + expect(isLocalhost('127.0.0.1:27019')).to.be.true; + }); + + // Although 127.0.0.1 is usually used for localhost, + // anything in the 127.0.0.1 -> 127.255.255.255 can be used. + it('reports on localhost of type 127.x.x.x', function () { + expect(isLocalhost('127.0.100.1:27019')).to.be.true; + expect(isLocalhost('127.250.100.250:27019')).to.be.true; + expect(isLocalhost('127.10.0.0:27019')).to.be.true; + }); + + // IPv6 loopback addresses. + it('reports on localhost of type [::1]', function () { + expect(isLocalhost('[::1]')).to.be.true; + expect(isLocalhost('mongodb://[::1]/?readPreference=secondary')).to.be + .true; + expect(isLocalhost('[0000:0000:0000:0000:0000:0000:0000:0001]')).to.be + .true; + expect(isLocalhost('[0:0:0:0:0:0:0:1]')).to.be.true; + expect(isLocalhost('[0::1]')).to.be.true; + expect(isLocalhost('[::1]:27019')).to.be.true; + expect(isLocalhost('[0:0:0:0:0:0:0:1]:27019')).to.be.true; + expect(isLocalhost('[0::1]:27019')).to.be.true; + }); + + it('reports on localhost of type 0.0.0.0', function () { + expect(isLocalhost('0.0.0.0:27019')).to.be.true; + }); + + it('works as url', function () { + expect(isLocalhost('mongodb://127.0.0.1:27019')).to.be.true; + expect(isLocalhost('mongodb+srv://127.0.0.1')).to.be.true; + expect(isLocalhost('mongodb://0.0.0.0:27019')).to.be.true; + expect(isLocalhost('mongodb+srv://0.0.0.0')).to.be.true; + expect(isLocalhost('mongodb://localhost')).to.be.true; + expect(isLocalhost('mongodb://localhost:27019')).to.be.true; + }); + + it('works as hostname', function () { + expect(isLocalhost('127.0.0.1')).to.be.true; + expect(isLocalhost('0.0.0.0')).to.be.true; + expect(isLocalhost('localhost')).to.be.true; + }); + + it('does not report if localhost or 127.0.0.1 is not the hostname', function () { + expect(isLocalhost('127.0.0.500')).to.be.false; + expect(isLocalhost('128.0.0.2')).to.be.false; + expect(isLocalhost('0.0.0.1')).to.be.false; + expect(isLocalhost('remotehost')).to.be.false; + expect(isLocalhost('mongodb://remotelocalhost')).to.be.false; + expect(isLocalhost('[test:ipv6::1]')).to.be.false; + expect(isLocalhost('[1:0:0:0:0:0:0:1]')).to.be.false; + expect(isLocalhost('[test:ipv6::1]:27019')).to.be.false; + expect(isLocalhost('[1:0:0:0:0:0:0:1]:27019')).to.be.false; + }); + + it('does not throw and returns with invalid argument', function () { + expect(isLocalhost('')).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isLocalhost(123)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isLocalhost({})).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isLocalhost(undefined)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isLocalhost(null)).to.be.false; + }); + }); + + context('isDigitalOcean', function () { + it('reports on digital ocean', function () { + expect( + isDigitalOcean( + 'mongodb+srv://admin:catscatscats@dave-a1234321.mongo.ondigitalocean.com/test?authSource=admin&replicaSet=dave', + ), + ).to.be.true; + }); + + it('works with hostname only', function () { + expect(isDigitalOcean('dave-a1234321.mongo.ondigitalocean.com')).to.be + .true; + }); + + it('works with host only', function () { + expect(isDigitalOcean('dave-a1234321.mongo.ondigitalocean.com:27017')).to + .be.true; + }); + + it('returns false if not digitalocean', function () { + expect(isDigitalOcean('dave-a1234321.mongo.ondigitalocean.com2')).to.be + .false; + expect(isDigitalOcean('dave-a1234321mongo.ondigitalocean.com')).to.be + .false; + expect(isDigitalOcean('dave-a1234321.mongoxondigitalocean.com')).to.be + .false; + }); + + it('does not throw and returns with invalid argument', function () { + expect(isDigitalOcean('')).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isDigitalOcean(123)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isDigitalOcean({})).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isDigitalOcean(undefined)).to.be.false; + /* @ts-expect-error -- passing invalid arguments */ + expect(isDigitalOcean(null)).to.be.false; + }); + }); + + context('isGenuineMongoDB', function () { + it('reports on CosmosDB', function () { + fixtures.COSMOS_DB_URI.forEach((uri) => { + const isGenuine = getGenuineMongoDB(uri); + expect(isGenuine.isGenuine).to.be.false; + expect(isGenuine.serverName).to.equal('cosmosdb'); + }); + }); + + it('reports on DocumentDB', function () { + fixtures.DOCUMENT_DB_URIS.forEach((uri) => { + const isGenuine = getGenuineMongoDB(uri); + expect(isGenuine.isGenuine).to.be.false; + expect(isGenuine.serverName).to.equal('documentdb'); + }); + }); + + it('does not report on 3.2', function () { + const isGenuine = getGenuineMongoDB( + fixtures.BUILD_INFO_3_2, + /* @ts-expect-error -- passing invalid arguments */ + fixtures.CMD_LINE_OPTS, + ); + expect(isGenuine.isGenuine).to.be.true; + expect(isGenuine.serverName).to.equal('mongodb'); + }); + + it('does not report on older versions', function () { + const isGenuine = getGenuineMongoDB( + fixtures.BUILD_INFO_OLD, + /* @ts-expect-error -- passing invalid arguments */ + fixtures.CMD_LINE_OPTS, + ); + expect(isGenuine.isGenuine).to.be.true; + expect(isGenuine.serverName).to.equal('mongodb'); + }); + }); +}); diff --git a/packages/mongodb-build-info/tsconfig-lint.json b/packages/mongodb-build-info/tsconfig-lint.json new file mode 100644 index 00000000..6bdef84f --- /dev/null +++ b/packages/mongodb-build-info/tsconfig-lint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/mongodb-build-info/tsconfig.json b/packages/mongodb-build-info/tsconfig.json new file mode 100644 index 00000000..d9f9753f --- /dev/null +++ b/packages/mongodb-build-info/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@mongodb-js/tsconfig-devtools/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "allowJs": true, + "strict": true + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["./src/**/*.spec.*", "./test/**/*.ts"] +}