Source: lib/admin.js

  1. "use strict";
  2. var toError = require('./utils').toError,
  3. Define = require('./metadata'),
  4. shallowClone = require('./utils').shallowClone,
  5. assign = require('./utils').assign,
  6. authenticate = require('./authenticate');
  7. /**
  8. * @fileOverview The **Admin** class is an internal class that allows convenient access to
  9. * the admin functionality and commands for MongoDB.
  10. *
  11. * **ADMIN Cannot directly be instantiated**
  12. * @example
  13. * var MongoClient = require('mongodb').MongoClient,
  14. * test = require('assert');
  15. * // Connection url
  16. * var url = 'mongodb://localhost:27017/test';
  17. * // Connect using MongoClient
  18. * MongoClient.connect(url, function(err, db) {
  19. * // Use the admin database for the operation
  20. * var adminDb = db.admin();
  21. *
  22. * // List all the available databases
  23. * adminDb.listDatabases(function(err, dbs) {
  24. * test.equal(null, err);
  25. * test.ok(dbs.databases.length > 0);
  26. * db.close();
  27. * });
  28. * });
  29. */
  30. /**
  31. * Create a new Admin instance (INTERNAL TYPE, do not instantiate directly)
  32. * @class
  33. * @return {Admin} a collection instance.
  34. */
  35. var Admin = function(db, topology, promiseLibrary) {
  36. if(!(this instanceof Admin)) return new Admin(db, topology);
  37. // Internal state
  38. this.s = {
  39. db: db
  40. , topology: topology
  41. , promiseLibrary: promiseLibrary
  42. }
  43. }
  44. var define = Admin.define = new Define('Admin', Admin, false);
  45. /**
  46. * The callback format for results
  47. * @callback Admin~resultCallback
  48. * @param {MongoError} error An error instance representing the error during the execution.
  49. * @param {object} result The result object if the command was executed successfully.
  50. */
  51. /**
  52. * Execute a command
  53. * @method
  54. * @param {object} command The command hash
  55. * @param {object} [options=null] Optional settings.
  56. * @param {(ReadPreference|string)} [options.readPreference=null] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
  57. * @param {number} [options.maxTimeMS=null] Number of milliseconds to wait before aborting the query.
  58. * @param {Admin~resultCallback} [callback] The command result callback
  59. * @return {Promise} returns Promise if no callback passed
  60. */
  61. Admin.prototype.command = function(command, options, callback) {
  62. var self = this;
  63. var args = Array.prototype.slice.call(arguments, 1);
  64. callback = args.pop();
  65. if(typeof callback != 'function') args.push(callback);
  66. options = args.length ? args.shift() : {};
  67. // Execute using callback
  68. if(typeof callback == 'function') return this.s.db.executeDbAdminCommand(command, options, function(err, doc) {
  69. return callback != null ? callback(err, doc) : null;
  70. });
  71. // Return a Promise
  72. return new this.s.promiseLibrary(function(resolve, reject) {
  73. self.s.db.executeDbAdminCommand(command, options, function(err, doc) {
  74. if(err) return reject(err);
  75. resolve(doc);
  76. });
  77. });
  78. }
  79. define.classMethod('command', {callback: true, promise:true});
  80. /**
  81. * Retrieve the server information for the current
  82. * instance of the db client
  83. *
  84. * @param {Admin~resultCallback} [callback] The command result callback
  85. * @return {Promise} returns Promise if no callback passed
  86. */
  87. Admin.prototype.buildInfo = function(callback) {
  88. var self = this;
  89. // Execute using callback
  90. if(typeof callback == 'function') return this.serverInfo(callback);
  91. // Return a Promise
  92. return new this.s.promiseLibrary(function(resolve, reject) {
  93. self.serverInfo(function(err, r) {
  94. if(err) return reject(err);
  95. resolve(r);
  96. });
  97. });
  98. }
  99. define.classMethod('buildInfo', {callback: true, promise:true});
  100. /**
  101. * Retrieve the server information for the current
  102. * instance of the db client
  103. *
  104. * @param {Admin~resultCallback} [callback] The command result callback
  105. * @return {Promise} returns Promise if no callback passed
  106. */
  107. Admin.prototype.serverInfo = function(callback) {
  108. var self = this;
  109. // Execute using callback
  110. if(typeof callback == 'function') return this.s.db.executeDbAdminCommand({buildinfo:1}, function(err, doc) {
  111. if(err != null) return callback(err, null);
  112. callback(null, doc);
  113. });
  114. // Return a Promise
  115. return new this.s.promiseLibrary(function(resolve, reject) {
  116. self.s.db.executeDbAdminCommand({buildinfo:1}, function(err, doc) {
  117. if(err) return reject(err);
  118. resolve(doc);
  119. });
  120. });
  121. }
  122. define.classMethod('serverInfo', {callback: true, promise:true});
  123. /**
  124. * Retrieve this db's server status.
  125. *
  126. * @param {Admin~resultCallback} [callback] The command result callback
  127. * @return {Promise} returns Promise if no callback passed
  128. */
  129. Admin.prototype.serverStatus = function(callback) {
  130. var self = this;
  131. // Execute using callback
  132. if(typeof callback == 'function') return serverStatus(self, callback)
  133. // Return a Promise
  134. return new this.s.promiseLibrary(function(resolve, reject) {
  135. serverStatus(self, function(err, r) {
  136. if(err) return reject(err);
  137. resolve(r);
  138. });
  139. });
  140. };
  141. var serverStatus = function(self, callback) {
  142. self.s.db.executeDbAdminCommand({serverStatus: 1}, function(err, doc) {
  143. if(err == null && doc.ok === 1) {
  144. callback(null, doc);
  145. } else {
  146. if(err) return callback(err, false);
  147. return callback(toError(doc), false);
  148. }
  149. });
  150. }
  151. define.classMethod('serverStatus', {callback: true, promise:true});
  152. /**
  153. * Retrieve the current profiling Level for MongoDB
  154. *
  155. * @param {Admin~resultCallback} [callback] The command result callback
  156. * @return {Promise} returns Promise if no callback passed
  157. */
  158. Admin.prototype.profilingLevel = function(callback) {
  159. var self = this;
  160. // Execute using callback
  161. if(typeof callback == 'function') return profilingLevel(self, callback)
  162. // Return a Promise
  163. return new this.s.promiseLibrary(function(resolve, reject) {
  164. profilingLevel(self, function(err, r) {
  165. if(err) return reject(err);
  166. resolve(r);
  167. });
  168. });
  169. };
  170. var profilingLevel = function(self, callback) {
  171. self.s.db.executeDbAdminCommand({profile:-1}, function(err, doc) {
  172. if(err == null && doc.ok === 1) {
  173. var was = doc.was;
  174. if(was == 0) return callback(null, "off");
  175. if(was == 1) return callback(null, "slow_only");
  176. if(was == 2) return callback(null, "all");
  177. return callback(new Error("Error: illegal profiling level value " + was), null);
  178. } else {
  179. err != null ? callback(err, null) : callback(new Error("Error with profile command"), null);
  180. }
  181. });
  182. }
  183. define.classMethod('profilingLevel', {callback: true, promise:true});
  184. /**
  185. * Ping the MongoDB server and retrieve results
  186. *
  187. * @param {Admin~resultCallback} [callback] The command result callback
  188. * @return {Promise} returns Promise if no callback passed
  189. */
  190. Admin.prototype.ping = function(options, callback) {
  191. var self = this;
  192. var args = Array.prototype.slice.call(arguments, 0);
  193. callback = args.pop();
  194. if(typeof callback != 'function') args.push(callback);
  195. // Execute using callback
  196. if(typeof callback == 'function') return this.s.db.executeDbAdminCommand({ping: 1}, callback);
  197. // Return a Promise
  198. return new this.s.promiseLibrary(function(resolve, reject) {
  199. self.s.db.executeDbAdminCommand({ping: 1}, function(err, r) {
  200. if(err) return reject(err);
  201. resolve(r);
  202. });
  203. });
  204. }
  205. define.classMethod('ping', {callback: true, promise:true});
  206. /**
  207. * Authenticate a user against the server.
  208. * @method
  209. * @param {string} username The username.
  210. * @param {string} [password] The password.
  211. * @param {Admin~resultCallback} [callback] The command result callback
  212. * @return {Promise} returns Promise if no callback passed
  213. * @deprecated This method will no longer be available in the next major release 3.x as MongoDB 3.6 will only allow auth against users in the admin db and will no longer allow multiple credentials on a socket. Please authenticate using MongoClient.connect with auth credentials.
  214. */
  215. Admin.prototype.authenticate = function(username, password, options, callback) {
  216. console.warn("Admin.prototype.authenticate method will no longer be available in the next major release 3.x as MongoDB 3.6 will only allow auth against users in the admin db and will no longer allow multiple credentials on a socket. Please authenticate using MongoClient.connect with auth credentials.");
  217. var finalArguments = [this.s.db];
  218. if(typeof username == 'string') finalArguments.push(username);
  219. if(typeof password == 'string') finalArguments.push(password);
  220. if(typeof options == 'function') {
  221. finalArguments.push({ authdb: 'admin' });
  222. finalArguments.push(options);
  223. } else {
  224. finalArguments.push(assign({}, options, { authdb: 'admin' }));
  225. }
  226. if(typeof callback == 'function') finalArguments.push(callback);
  227. // Execute authenticate method
  228. return authenticate.apply(this.s.db, finalArguments);
  229. }
  230. define.classMethod('authenticate', {callback: true, promise:true});
  231. /**
  232. * Logout user from server, fire off on all connections and remove all auth info
  233. * @method
  234. * @param {Admin~resultCallback} [callback] The command result callback
  235. * @return {Promise} returns Promise if no callback passed
  236. */
  237. Admin.prototype.logout = function(callback) {
  238. var self = this;
  239. // Execute using callback
  240. if(typeof callback == 'function') return this.s.db.logout({dbName: 'admin'}, callback);
  241. // Return a Promise
  242. return new this.s.promiseLibrary(function(resolve, reject) {
  243. self.s.db.logout({dbName: 'admin'}, function(err) {
  244. if(err) return reject(err);
  245. resolve(true);
  246. });
  247. });
  248. }
  249. define.classMethod('logout', {callback: true, promise:true});
  250. // Get write concern
  251. var writeConcern = function(options, db) {
  252. options = shallowClone(options);
  253. // If options already contain write concerns return it
  254. if(options.w || options.wtimeout || options.j || options.fsync) {
  255. return options;
  256. }
  257. // Set db write concern if available
  258. if(db.writeConcern) {
  259. if(options.w) options.w = db.writeConcern.w;
  260. if(options.wtimeout) options.wtimeout = db.writeConcern.wtimeout;
  261. if(options.j) options.j = db.writeConcern.j;
  262. if(options.fsync) options.fsync = db.writeConcern.fsync;
  263. }
  264. // Return modified options
  265. return options;
  266. }
  267. /**
  268. * Add a user to the database.
  269. * @method
  270. * @param {string} username The username.
  271. * @param {string} password The password.
  272. * @param {object} [options=null] Optional settings.
  273. * @param {(number|string)} [options.w=null] The write concern.
  274. * @param {number} [options.wtimeout=null] The write concern timeout.
  275. * @param {boolean} [options.j=false] Specify a journal write concern.
  276. * @param {boolean} [options.fsync=false] Specify a file sync write concern.
  277. * @param {object} [options.customData=null] Custom data associated with the user (only Mongodb 2.6 or higher)
  278. * @param {object[]} [options.roles=null] Roles associated with the created user (only Mongodb 2.6 or higher)
  279. * @param {Admin~resultCallback} [callback] The command result callback
  280. * @return {Promise} returns Promise if no callback passed
  281. */
  282. Admin.prototype.addUser = function(username, password, options, callback) {
  283. var self = this;
  284. var args = Array.prototype.slice.call(arguments, 2);
  285. callback = args.pop();
  286. if(typeof callback != 'function') args.push(callback);
  287. options = args.length ? args.shift() : {};
  288. options = options || {};
  289. // Get the options
  290. options = writeConcern(options, self.s.db)
  291. // Set the db name to admin
  292. options.dbName = 'admin';
  293. // Execute using callback
  294. if(typeof callback == 'function')
  295. return self.s.db.addUser(username, password, options, callback);
  296. // Return a Promise
  297. return new this.s.promiseLibrary(function(resolve, reject) {
  298. self.s.db.addUser(username, password, options, function(err, r) {
  299. if(err) return reject(err);
  300. resolve(r);
  301. });
  302. });
  303. }
  304. define.classMethod('addUser', {callback: true, promise:true});
  305. /**
  306. * Remove a user from a database
  307. * @method
  308. * @param {string} username The username.
  309. * @param {object} [options=null] Optional settings.
  310. * @param {(number|string)} [options.w=null] The write concern.
  311. * @param {number} [options.wtimeout=null] The write concern timeout.
  312. * @param {boolean} [options.j=false] Specify a journal write concern.
  313. * @param {boolean} [options.fsync=false] Specify a file sync write concern.
  314. * @param {Admin~resultCallback} [callback] The command result callback
  315. * @return {Promise} returns Promise if no callback passed
  316. */
  317. Admin.prototype.removeUser = function(username, options, callback) {
  318. var self = this;
  319. var args = Array.prototype.slice.call(arguments, 1);
  320. callback = args.pop();
  321. if(typeof callback != 'function') args.push(callback);
  322. options = args.length ? args.shift() : {};
  323. options = options || {};
  324. // Get the options
  325. options = writeConcern(options, self.s.db)
  326. // Set the db name
  327. options.dbName = 'admin';
  328. // Execute using callback
  329. if(typeof callback == 'function')
  330. return self.s.db.removeUser(username, options, callback);
  331. // Return a Promise
  332. return new this.s.promiseLibrary(function(resolve, reject) {
  333. self.s.db.removeUser(username, options, function(err, r) {
  334. if(err) return reject(err);
  335. resolve(r);
  336. });
  337. });
  338. }
  339. define.classMethod('removeUser', {callback: true, promise:true});
  340. /**
  341. * Set the current profiling level of MongoDB
  342. *
  343. * @param {string} level The new profiling level (off, slow_only, all).
  344. * @param {Admin~resultCallback} [callback] The command result callback.
  345. * @return {Promise} returns Promise if no callback passed
  346. */
  347. Admin.prototype.setProfilingLevel = function(level, callback) {
  348. var self = this;
  349. // Execute using callback
  350. if(typeof callback == 'function') return setProfilingLevel(self, level, callback);
  351. // Return a Promise
  352. return new this.s.promiseLibrary(function(resolve, reject) {
  353. setProfilingLevel(self, level, function(err, r) {
  354. if(err) return reject(err);
  355. resolve(r);
  356. });
  357. });
  358. };
  359. var setProfilingLevel = function(self, level, callback) {
  360. var command = {};
  361. var profile = 0;
  362. if(level == "off") {
  363. profile = 0;
  364. } else if(level == "slow_only") {
  365. profile = 1;
  366. } else if(level == "all") {
  367. profile = 2;
  368. } else {
  369. return callback(new Error("Error: illegal profiling level value " + level));
  370. }
  371. // Set up the profile number
  372. command['profile'] = profile;
  373. self.s.db.executeDbAdminCommand(command, function(err, doc) {
  374. if(err == null && doc.ok === 1)
  375. return callback(null, level);
  376. return err != null ? callback(err, null) : callback(new Error("Error with profile command"), null);
  377. });
  378. }
  379. define.classMethod('setProfilingLevel', {callback: true, promise:true});
  380. /**
  381. * Retrieve the current profiling information for MongoDB
  382. *
  383. * @param {Admin~resultCallback} [callback] The command result callback.
  384. * @return {Promise} returns Promise if no callback passed
  385. * @deprecated Query the system.profile collection directly.
  386. */
  387. Admin.prototype.profilingInfo = function(callback) {
  388. var self = this;
  389. // Execute using callback
  390. if(typeof callback == 'function') return profilingInfo(self, callback);
  391. // Return a Promise
  392. return new this.s.promiseLibrary(function(resolve, reject) {
  393. profilingInfo(self, function(err, r) {
  394. if(err) return reject(err);
  395. resolve(r);
  396. });
  397. });
  398. };
  399. var profilingInfo = function(self, callback) {
  400. try {
  401. self.s.topology.cursor("admin.system.profile", { find: 'system.profile', query: {}}, {}).toArray(callback);
  402. } catch (err) {
  403. return callback(err, null);
  404. }
  405. }
  406. define.classMethod('profilingLevel', {callback: true, promise:true});
  407. /**
  408. * Validate an existing collection
  409. *
  410. * @param {string} collectionName The name of the collection to validate.
  411. * @param {object} [options=null] Optional settings.
  412. * @param {Admin~resultCallback} [callback] The command result callback.
  413. * @return {Promise} returns Promise if no callback passed
  414. */
  415. Admin.prototype.validateCollection = function(collectionName, options, callback) {
  416. var self = this;
  417. var args = Array.prototype.slice.call(arguments, 1);
  418. callback = args.pop();
  419. if(typeof callback != 'function') args.push(callback);
  420. options = args.length ? args.shift() : {};
  421. options = options || {};
  422. // Execute using callback
  423. if(typeof callback == 'function')
  424. return validateCollection(self, collectionName, options, callback);
  425. // Return a Promise
  426. return new this.s.promiseLibrary(function(resolve, reject) {
  427. validateCollection(self, collectionName, options, function(err, r) {
  428. if(err) return reject(err);
  429. resolve(r);
  430. });
  431. });
  432. };
  433. var validateCollection = function(self, collectionName, options, callback) {
  434. var command = {validate: collectionName};
  435. var keys = Object.keys(options);
  436. // Decorate command with extra options
  437. for(var i = 0; i < keys.length; i++) {
  438. if(options.hasOwnProperty(keys[i])) {
  439. command[keys[i]] = options[keys[i]];
  440. }
  441. }
  442. self.s.db.command(command, function(err, doc) {
  443. if(err != null) return callback(err, null);
  444. if(doc.ok === 0)
  445. return callback(new Error("Error with validate command"), null);
  446. if(doc.result != null && doc.result.constructor != String)
  447. return callback(new Error("Error with validation data"), null);
  448. if(doc.result != null && doc.result.match(/exception|corrupt/) != null)
  449. return callback(new Error("Error: invalid collection " + collectionName), null);
  450. if(doc.valid != null && !doc.valid)
  451. return callback(new Error("Error: invalid collection " + collectionName), null);
  452. return callback(null, doc);
  453. });
  454. }
  455. define.classMethod('validateCollection', {callback: true, promise:true});
  456. /**
  457. * List the available databases
  458. *
  459. * @param {Admin~resultCallback} [callback] The command result callback.
  460. * @return {Promise} returns Promise if no callback passed
  461. */
  462. Admin.prototype.listDatabases = function(callback) {
  463. var self = this;
  464. // Execute using callback
  465. if(typeof callback == 'function') return self.s.db.executeDbAdminCommand({listDatabases:1}, {}, callback);
  466. // Return a Promise
  467. return new this.s.promiseLibrary(function(resolve, reject) {
  468. self.s.db.executeDbAdminCommand({listDatabases:1}, {}, function(err, r) {
  469. if(err) return reject(err);
  470. resolve(r);
  471. });
  472. });
  473. }
  474. define.classMethod('listDatabases', {callback: true, promise:true});
  475. /**
  476. * Get ReplicaSet status
  477. *
  478. * @param {Admin~resultCallback} [callback] The command result callback.
  479. * @return {Promise} returns Promise if no callback passed
  480. */
  481. Admin.prototype.replSetGetStatus = function(callback) {
  482. var self = this;
  483. // Execute using callback
  484. if(typeof callback == 'function') return replSetGetStatus(self, callback);
  485. // Return a Promise
  486. return new this.s.promiseLibrary(function(resolve, reject) {
  487. replSetGetStatus(self, function(err, r) {
  488. if(err) return reject(err);
  489. resolve(r);
  490. });
  491. });
  492. };
  493. var replSetGetStatus = function(self, callback) {
  494. self.s.db.executeDbAdminCommand({replSetGetStatus:1}, function(err, doc) {
  495. if(err == null && doc.ok === 1)
  496. return callback(null, doc);
  497. if(err) return callback(err, false);
  498. callback(toError(doc), false);
  499. });
  500. }
  501. define.classMethod('replSetGetStatus', {callback: true, promise:true});
  502. module.exports = Admin;