From 1da6edde80e3029d99084992ec1a4ada7b2cc279 Mon Sep 17 00:00:00 2001 From: Unitech Date: Wed, 11 Jul 2018 12:51:25 +0200 Subject: [PATCH] feature: pm2 plus cli --- bin/pm2 | 45 ++++++++-- lib/API.js | 2 +- lib/API/CliUx.js | 6 +- lib/API/Interaction.js | 48 +++++----- lib/API/Modules/Modularizer.js | 12 +-- lib/API/PM2/PM2IO.js | 155 +++++++++++++++++++++++---------- lib/ProcessUtils.js | 22 +++-- test/e2e/cli/plus.sh | 23 +++++ 8 files changed, 220 insertions(+), 93 deletions(-) create mode 100644 test/e2e/cli/plus.sh diff --git a/bin/pm2 b/bin/pm2 index 608da9318..54eaeb72a 100755 --- a/bin/pm2 +++ b/bin/pm2 @@ -565,7 +565,6 @@ commander.command('report') // PM2 I/O // commander.command('link [secret] [public] [name]') - .alias('interact') .option('--info-node [url]', 'set url info node') .option('--ws', 'websocket mode') .description('link with the pm2 monitoring dashboard') @@ -595,15 +594,47 @@ commander.command('open') pm2.openDashboard(); }); -commander.command('register') - .alias('plus') - .description('register on pm2 monitoring') - .action(function(name) { - pm2.registerToKM(); +commander.command('plus [secret] [public] [name]') + .alias('register') + .option('--info-node [url]', 'set url info node for on-premise pm2 plus') + .option('-d --discrete', 'silent mode') + .option('-a --install-all', 'install all modules (force yes)') + .description('enable pm2 plus') + .action(function(sec, pub, name, _opts) { + var opts = { + infoNode: _opts.infoNode, + discrete: _opts.discrete, + installAll: _opts.installAll, + public: pub, + secret: sec, + name: name, + ws: true + } + + if (sec == 'delete') { + return pm2._pre_interact('delete', null, null, opts, function(err, dt) { + pm2.clearSetup(opts, function() { + console.log(chalk.green('[PM2.IO]') + ' Agent disabled, modules removed, monitoring disabled'); + setTimeout(function() { + pm2.speedList() + }, 1000) + }) + }) + } + + if (opts.public && opts.secret) + pm2._pre_interact(opts.secret, opts.public, opts.name, opts, function(err, dt) { + pm2.minimumSetup(opts, function() { + console.log(chalk.green('[PM2.IO]') + ' Remote dashboard: https://app.pm2.io/#/r/' + dt.public_key); + pm2.speedList() + }) + }) + else + pm2.registerToKM(opts); }); commander.command('login') - .description('use login to link with the pm2 monitoring dashboard') + .description('[DEPRECATED] use plus command instead') .action(function(name) { pm2.loginToKM(); }); diff --git a/lib/API.js b/lib/API.js index 1d71c2fb0..81f38c90f 100644 --- a/lib/API.js +++ b/lib/API.js @@ -1495,7 +1495,7 @@ class API { UX.miniDisplay(list); else if (!commander.silent) { if (that.gl_interact_infos) { - Common.printOut('%s Agent Online | Access: %s | Server: %s | Transport %s', + Common.printOut('%s PM2+ activated | Web: %s | Server: %s | Transport %s', chalk.green.bold('⇆'), chalk.bold('https://app.pm2.io/#/r/' + that.gl_interact_infos.public_key), chalk.bold(that.gl_interact_infos.machine_name), diff --git a/lib/API/CliUx.js b/lib/API/CliUx.js index ce83f15e7..64cd9cf88 100644 --- a/lib/API/CliUx.js +++ b/lib/API/CliUx.js @@ -270,14 +270,10 @@ UX.dispAsTable = function(list, commander) { if (l.pm2_env.axm_options) { var deep_monitored = l.pm2_env.axm_options.tracing_enabled || false; if (deep_monitored == true) { - key = key + ' ' + chalk.green('⏱'); + key = chalk.green('✚') + ' ' + key; } } - if (typeof l.pm2_env._km_monitored === 'boolean' && l.pm2_env._km_monitored === false) { - key = chalk.bold.red('◉') + ' ' + key; - } - if (l.pm2_env.pmx_module == true) { // pm2 ls for Modules obj[key] = []; diff --git a/lib/API/Interaction.js b/lib/API/Interaction.js index 621dbf27e..47feb3938 100644 --- a/lib/API/Interaction.js +++ b/lib/API/Interaction.js @@ -76,14 +76,21 @@ module.exports = function(CLI) { // // Interact // - CLI.prototype._pre_interact = function(cmd, public_key, machine, opts) { + CLI.prototype._pre_interact = function(cmd, public_key, machine, opts, cb) { var that = this; if (cmd == 'stop' || cmd == 'kill') { + that.gl_is_km_linked = false console.log(chalk.cyan('[PM2 agent]') + ' Stopping agent...'); - that.killInteract(function() { + that.killInteract(function(err) { + if (err) { + Common.printError(err); + return process.exit(cst.ERROR_EXIT); + } console.log(chalk.cyan('[PM2 agent]') + ' Stopped'); - return process.exit(cst.SUCCESS_EXIT); + that.reload('all', () => { + return process.exit(cst.SUCCESS_EXIT); + }) }); return false; } @@ -102,7 +109,9 @@ module.exports = function(CLI) { } if (cmd == 'delete') { - that.killInteract(function() { + that.gl_is_km_linked = false + console.log(chalk.cyan('[PM2 agent]') + ' Permanently disable agent...'); + that.killInteract(function(err) { try { fs.unlinkSync(cst.INTERACTION_CONF); } catch(e) { @@ -110,27 +119,13 @@ module.exports = function(CLI) { return process.exit(cst.SUCCESS_EXIT); } console.log(chalk.cyan('[PM2 agent]') + ' Agent interaction ended'); - return process.exit(cst.SUCCESS_EXIT); + if (!cb) + return process.exit(cst.SUCCESS_EXIT); + return cb() }); return false; } - if (cmd == 'start' || cmd == 'restart') { - KMDaemon.launchAndInteract(that._conf, { - public_key : null, - secret_key : null, - machine_name : null, - info_node : null, - pm2_version: pkg.version - }, function(err, dt) { - if (err) { - Common.printError(err); - return that.exitCli(cst.ERROR_EXIT); - } - return that.exitCli(cst.SUCCESS_EXIT); - }); - } - if (cmd && !public_key) { console.error(chalk.cyan('[PM2 agent]') + ' Command [%s] unknown or missing public key', cmd); return process.exit(cst.ERROR_EXIT); @@ -162,11 +157,16 @@ module.exports = function(CLI) { process.env.AGENT_TRANSPORT_AXON = true process.env.AGENT_TRANSPORT_WEBSOCKET = false } - KMDaemon.launchAndInteract(that._conf, infos, function(err, dt) { - if (err) + if (err) { + Common.printError(chalk.red('[PM2 agent] ') + err) + Common.printError(chalk.cyan('[PM2 agent] ') + 'Run `$ pm2 plus` to connect') return that.exitCli(cst.ERROR_EXIT); - return that.exitCli(cst.SUCCESS_EXIT); + } + if (!cb) { + return that.exitCli(cst.SUCCESS_EXIT); + } + return cb(null, dt) }); }; diff --git a/lib/API/Modules/Modularizer.js b/lib/API/Modules/Modularizer.js index f54631d4b..722e4c917 100644 --- a/lib/API/Modules/Modularizer.js +++ b/lib/API/Modules/Modularizer.js @@ -20,7 +20,7 @@ var Modularizer = module.exports = {}; var MODULE_CONF_PREFIX = 'module-db-v2'; -var KNOWN_MODULES = { +var INTERNAL_MODULES = { 'deep-monitoring': { dependencies: [{name: 'v8-profiler-node8'}, {name: 'gc-stats'}, {name: 'event-loop-inspector'}] }, @@ -68,7 +68,7 @@ Modularizer.install = function (CLI, moduleName, opts, cb) { return cb(Common.retErr(e)); } - Modularizer.installMultipleModules(config.dependencies, cb); + Modularizer.installMultipleInternalModules(config.dependencies, cb); return; } @@ -76,11 +76,11 @@ Modularizer.install = function (CLI, moduleName, opts, cb) { var canonicModuleName = Utility.getCanonicModuleName(moduleName); - if (KNOWN_MODULES.hasOwnProperty(moduleName)) { - var currentModule = KNOWN_MODULES[moduleName]; + if (INTERNAL_MODULES.hasOwnProperty(moduleName)) { + var currentModule = INTERNAL_MODULES[moduleName]; if (currentModule && currentModule.hasOwnProperty('dependencies')) { - Modularizer.installMultipleModules(currentModule.dependencies, cb); + Modularizer.installMultipleInternalModules(currentModule.dependencies, cb); } else { installModuleByName(currentModule, cb); } @@ -109,7 +109,7 @@ Modularizer.install = function (CLI, moduleName, opts, cb) { }); }; -Modularizer.installMultipleModules = function (modules, cb) { +Modularizer.installMultipleInternalModules = function (modules, cb) { var functionList = []; for (var i = 0; i < modules.length; i++) { functionList.push((function (index) { diff --git a/lib/API/PM2/PM2IO.js b/lib/API/PM2/PM2IO.js index 698e9c067..464e7c17c 100644 --- a/lib/API/PM2/PM2IO.js +++ b/lib/API/PM2/PM2IO.js @@ -14,7 +14,7 @@ const pkg = require('../../../package.json') const IOAPI = require('@pm2/js-api') const semver = require('semver'); const Modularizer = require('../Modules/Modularizer.js'); - +const promptly = require('promptly') // const CustomStrategy = require('./custom_auth') // const strategy = new CustomStrategy({ @@ -45,9 +45,80 @@ module.exports = function(CLI) { }); }; - CLI.prototype.loginToKM = function(cb) { - var promptly = require('promptly') + CLI.prototype.processesAreAlreadyMonitored = function(cb) { + this.Client.executeRemote('getMonitorData', {}, function(err, list) { + if (err) return cb(false); + var l = list.filter(l => l.pm2_env.km_link == true) + var l2 = list.filter(l => l.name == 'pm2-server-monit') + + return cb(l.length > 0 && l2.length > 0 ? true : false) + }) + } + + CLI.prototype.clearSetup = function (opts, cb) { + const modules = ['pm2-logrotate', 'pm2-server-monit', 'event-loop-inspector'] + this.gl_is_km_linked = false + + if (semver.satisfies(process.version, '< 8.0.0')) { + modules.push('v8-profiler-node8') + } + + async.forEach(modules, (_module, next) => { + Modularizer.uninstall(this, _module, () => { + next() + }); + }, (err) => { + this.reload('all', () => { + return cb() + }) + }) + } + + /** + * Install required package and enable flags for current running processes + */ + CLI.prototype.minimumSetup = function (opts, cb) { + var self = this; + this.gl_is_km_linked = true + + function install(cb) { + const modules = ['pm2-logrotate', 'pm2-server-monit', 'event-loop-inspector'] + + if (semver.satisfies(process.version, '< 8.0.0')) { + modules.push('v8-profiler-node8') + } + + async.forEach(modules, (_module, next) => { + Modularizer.install(self, _module, {}, () => { + next() + }); + }, (err) => { + self.reload('all', () => { + return cb() + }) + }) + } + this.processesAreAlreadyMonitored((already_monitored) => { + if (already_monitored) { + console.log(chalk.cyan('[PM2.IO]') + ' PLUS bundle already installed'); + return cb() + } + + if (opts.installAll) + return install(cb) + + promptly.confirm(chalk.bold('Install all pm2 plus dependencies ? (y/n)'), (err, answer) => { + if (!err && answer === true) + return install(cb) + self.reload('all', () => { + return cb() + }) + }); + }) + } + + CLI.prototype.loginToKM = function(cb) { return CLIAuthStrategy._retrieveTokens((err, tokens) => { if (err) { console.error(`Oups, a error happened : ${err}`) @@ -86,13 +157,13 @@ module.exports = function(CLI) { if (target_bucket == null) { return retryInsertion(); } - return linkOpenExit(target_bucket, cb); + return link(target_bucket, cb); }); })(); } else { var target_bucket = buckets[0]; - linkOpenExit(target_bucket, cb) + link(target_bucket, cb) } }).catch(err => { console.error(chalk.bold.red(`Oups, a error happened : ${err}`)) @@ -101,41 +172,37 @@ module.exports = function(CLI) { }) }; - CLI.prototype.registerToKM = function() { - var promptly = require('promptly'); + CLI.prototype.registerToKM = function(opts) { printMotd(); const self = this + function finalize(bucket) { + self.minimumSetup(opts, () => { + console.log() + console.log(chalk.green('[+] PM2 Plus has been successfully enabled!')) + + console.log(chalk.green('[PM2.IO]') + ' Access to the remote dashboard: https://app.pm2.io/#/r/' + bucket.public_key); + + self.speedList() + setTimeout(function() { + console.log(chalk.bold.green('[+] Exiting now.')) + process.exit(cst.SUCCESS_EXIT); + }, 200) + }) + } + promptly.confirm(chalk.bold('Do you have a pm2.io account? (y/n)'), (err, answer) => { + // Login if (answer == true) { - return CLI.prototype.loginToKM(() => { - promptly.confirm(chalk.bold('Do you want to install monitoring modules (pm2-logrotate, pm2-server-monit, ...) ? (y/n)'), (errModule, answerModule) => { - if(!errModule && answerModule === true) { - const modules = ['pm2-logrotate', 'pm2-server-monit', 'event-loop-inspector'] - - if (semver.satisfies(process.version, '< 8.0.0')) { - modules.push('v8-profiler-node8') - } - - Modularizer.installMultipleModules(modules, () => { - self.reload('all') - setTimeout(function() { - console.log(chalk.bold.green('[+] Exiting now.')) - process.exit(cst.SUCCESS_EXIT); - }, 100); - }); - } else { - self.reload('all') - setTimeout(function() { - console.log(chalk.bold.green('[+] Exiting now.')) - process.exit(cst.SUCCESS_EXIT); - }, 100); - } - }); + return CLI.prototype.loginToKM((err, bucket) => { + self.gl_is_km_linked = true + finalize(bucket) }); } + + // Register CLIAuthStrategy.registerViaCLI((err, data) => { console.log('[-] Creating Bucket...') @@ -144,7 +211,9 @@ module.exports = function(CLI) { }).then(res => { const bucket = res.data.bucket console.log(chalk.bold.green('[+] Bucket created!')) - linkOpenExit(bucket) + link(bucket, () => { + finalize(bucket) + }) }) }) }); @@ -244,7 +313,7 @@ module.exports = function(CLI) { }; - function linkOpenExit(target_bucket, cb) { + function link(target_bucket, cb) { console.log('[-] Linking local PM2 to newly created bucket...') KMDaemon.launchAndInteract(cst, { public_key : target_bucket.public_id, @@ -253,18 +322,16 @@ module.exports = function(CLI) { }, function(err, dt) { console.log(chalk.bold.green('[+] Local PM2 Connected!')) - console.log('[-] Opening Monitoring Interface in Browser...') - setTimeout(function() { - open('https://app.pm2.io/#/r/' + target_bucket.public_id); - - if (cb) { - return cb() - } - console.log(chalk.bold.green('[+] Opened! Exiting now.')) - setTimeout(function() { - process.exit(cst.SUCCESS_EXIT); - }, 100); + if (cb) + return cb(null, target_bucket) + + // console.log('[-] Opening Monitoring Interface in Browser...') + // open('https://app.pm2.io/#/r/' + target_bucket.public_id); + // console.log(chalk.bold.green('[+] Opened! Exiting now.')) + // setTimeout(function() { + // process.exit(cst.SUCCESS_EXIT); + // }, 100); }, 1000) }); } diff --git a/lib/ProcessUtils.js b/lib/ProcessUtils.js index 4dad21969..32b61dd27 100644 --- a/lib/ProcessUtils.js +++ b/lib/ProcessUtils.js @@ -12,19 +12,29 @@ module.exports = { conf = io.conf ? io.conf : conf; } + var activate = process.env.km_link == 'true' || process.env.deep_monitoring === 'true' || false; + let defaultConf = { - transactions: (process.env.trace === 'true' || process.env.deep_monitoring === 'true') || false, - http: process.env.km_link === 'true' || false, - v8: process.env.v8 === 'true' || process.env.deep_monitoring === 'true' || false, - event_loop_dump: process.env.event_loop_inspector === 'true' || process.env.deep_monitoring === 'true' || false, - deep_metrics: process.env.deep_monitoring === 'true' || false + metrics: { + network: activate, + transaction: { + tracing: activate + }, + deepMetrics: activate, + v8: activate + }, + actions: { + eventLoopDump: activate, + profilingCpu: activate, + profilingHeap: activate + } }; const mergedConf = Object.assign(defaultConf, conf); pmx.init(mergedConf); - if(require('semver').satisfies(process.versions.node, '>= 8.0.0')) { + if (activate && require('semver').satisfies(process.versions.node, '>= 8.0.0')) { var url = ''; pmx.action('internal:inspect', function(reply) { const inspector = require('inspector'); diff --git a/test/e2e/cli/plus.sh b/test/e2e/cli/plus.sh new file mode 100644 index 000000000..770bdf7a9 --- /dev/null +++ b/test/e2e/cli/plus.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +SRC=$(cd $(dirname "$0"); pwd) +source "${SRC}/../include.sh" + +echo -e "\033[1mRunning tests:\033[0m" + +cd $file_path + +$pm2 start echo.js +$pm2 prettylist | grep "km_link: false" +spec "should km_link not be enabled" + +$pm2 plus alcz82ewyhy2va6 litfrsovr52celr --install-all + +should 'have started 3 apps' 'online' 3 +should 'all application be monitored' 'km_link: true' 3 + +$pm2 plus delete + +should 'have started 1 apps' 'online' 1 +$pm2 prettylist | grep "km_link: false" +spec "should km_link be disabled"