admin.js 18 KB

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