From 58f9a8dc9d7f2963f45d0b7b69bc5a049cfb4768 Mon Sep 17 00:00:00 2001 From: Unitech Date: Sun, 29 Jan 2017 16:47:47 +0100 Subject: [PATCH] (agent) allow to specify --info-node + keep it persistent over updates --- bin/pm2 | 1 + lib/API.js | 8 +- lib/API/Interaction.js | 54 ++++- lib/Interactor/InteractorDaemonizer.js | 79 +++--- test/interface/interactor.daemonizer.mocha.js | 227 +++++++----------- 5 files changed, 175 insertions(+), 194 deletions(-) diff --git a/bin/pm2 b/bin/pm2 index d76d83e5..85636165 100755 --- a/bin/pm2 +++ b/bin/pm2 @@ -519,6 +519,7 @@ commander.command('unset ') commander.command('link [secret_key|command] [public_key] [machine_name]') .alias('interact') + .option('--info-node [url]', 'set url info node') .description('linking action to keymetrics.io - command can be stop|info|delete|restart') .action(pm2._pre_interact.bind(pm2)); diff --git a/lib/API.js b/lib/API.js index 74ae7d93..4679710c 100644 --- a/lib/API.js +++ b/lib/API.js @@ -392,7 +392,7 @@ API.prototype.update = function(cb) { that.resurrect(function() { Common.printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated')); Modularizer.launchAll(that, function() { - KMDaemon.launchAndInteract(that._conf, {}, function(err, data, interactor_proc) { + KMDaemon.launchAndInteract(that._conf, null, function(err, data, interactor_proc) { // Interactor error can be skipped here return cb ? cb(null, {success:true}) : that.speedList(); }); @@ -873,7 +873,7 @@ API.prototype._startJson = function(file, opts, action, pipe, cb) { appConf = config.apps; else appConf = config; - + if (!Array.isArray(appConf)) appConf = [appConf]; //convert to array @@ -1633,7 +1633,7 @@ API.prototype.deepUpdate = function(cb) { /** * API method to launch a process that will serve directory over http - * + * * @param {Object} opts options * @param {String} opts.path path to be served * @param {Number} opts.port port on which http will bind @@ -1656,7 +1656,7 @@ API.prototype.serve = function (opts, cb) { opts.env['SERVE_PORT'] = opts.env['SERVE_PORT'] || servePort; opts.env['SERVE_PATH'] = opts.env['SERVE_PATH'] || servePath; opts.name = opts.name || servePath; - + this.start(path.resolve(__dirname, 'API', 'Serve.js'), opts, function (err, res) { if (err) { diff --git a/lib/API/Interaction.js b/lib/API/Interaction.js index 0d69de9a..872a94d7 100644 --- a/lib/API/Interaction.js +++ b/lib/API/Interaction.js @@ -11,6 +11,8 @@ var KMDaemon = require('../Interactor/InteractorDaemonizer'); module.exports = function(CLI) { /** * Launch interactor + * For programmatic interaction + * http://pm2.keymetrics.io/docs/usage/use-pm2-with-cloud-providers/ * @method interact * @param {string} secret_key * @param {string} public_key @@ -42,10 +44,10 @@ module.exports = function(CLI) { // // Interact // - CLI.prototype._pre_interact = function(secret_key, public_key, machine, opts) { + CLI.prototype._pre_interact = function(cmd, public_key, machine, info_node) { var that = this; - if (secret_key == 'stop' || secret_key == 'kill') { + if (cmd == 'stop' || cmd == 'kill') { console.log(chalk.cyan('[Keymetrics.io]') + ' Stopping agent...'); that.killInteract(function() { console.log(chalk.cyan('[Keymetrics.io]') + ' Stopped'); @@ -53,7 +55,8 @@ module.exports = function(CLI) { }); return false; } - if (secret_key == 'info') { + + if (cmd == 'info') { console.log(chalk.cyan('[Keymetrics.io]') + ' Getting agent information...'); that.interactInfos(function(err, infos) { if (err) { @@ -65,7 +68,8 @@ module.exports = function(CLI) { }); return false; } - if (secret_key == 'delete') { + + if (cmd == 'delete') { that.killInteract(function() { try { fs.unlinkSync(cst.INTERACTION_CONF); @@ -78,13 +82,45 @@ module.exports = function(CLI) { }); return false; } - if (secret_key == 'start' || secret_key == 'restart') - return that.interact(null, null, null); - if (secret_key && !public_key) { - console.error(chalk.cyan('[Keymetrics.io]') + ' Command [%s] unknown or missing public key', secret_key); + + if (cmd == 'start' || cmd == 'restart') { + KMDaemon.launchAndInteract(that._conf, { + public_key : null, + secret_key : null, + machine_name : null, + info_node : null + }, 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('[Keymetrics.io]') + ' Command [%s] unknown or missing public key', cmd); return process.exit(cst.ERROR_EXIT); } - return that.interact(secret_key, public_key, machine); + + var infos; + + if (!cmd) { + infos = null; + } + else + infos = { + public_key : public_key, + secret_key : cmd, + machine_name : machine, + info_node : info_node.infoNode || null + } + + KMDaemon.launchAndInteract(that._conf, infos, function(err, dt) { + if (err) + return that.exitCli(cst.ERROR_EXIT); + return that.exitCli(cst.SUCCESS_EXIT); + }); }; /** diff --git a/lib/Interactor/InteractorDaemonizer.js b/lib/Interactor/InteractorDaemonizer.js index abe46460..141b8e8d 100644 --- a/lib/Interactor/InteractorDaemonizer.js +++ b/lib/Interactor/InteractorDaemonizer.js @@ -13,10 +13,11 @@ var util = require('util'); var rpc = require('pm2-axon-rpc'); var axon = require('pm2-axon'); var chalk = require('chalk'); - +var os = require('os'); var cst = require('../../constants.js'); var Common = require('../Common'); var json5 = require('../tools/json5.js'); +var UX = require('../API/CliUx.js'); var InteractorDaemonizer = module.exports = {}; @@ -187,9 +188,6 @@ function launchOrAttach(conf, infos, cb) { * @param {} cb * @return */ - -var UX = require('../API/CliUx.js'); - var daemonize = function(conf, infos, cb) { var InteractorJS = path.resolve(path.dirname(module.filename), 'Daemon.js'); @@ -216,7 +214,8 @@ var daemonize = function(conf, infos, cb) { PM2_MACHINE_NAME : infos.machine_name, PM2_SECRET_KEY : infos.secret_key, PM2_PUBLIC_KEY : infos.public_key, - PM2_REVERSE_INTERACT : infos.reverse_interact + PM2_REVERSE_INTERACT : infos.reverse_interact, + KEYMETRICS_NODE : infos.info_node }, process.env), stdio : ['ipc', out, err] }); @@ -308,79 +307,76 @@ InteractorDaemonizer.update = function(conf, cb) { }; /** - * Get interaction keys from - * - environment - * - file - * If keys are not set save them to configuration file\ - * - * @param {object|string} secret_key|obj + * Get/Update/Merge agent configuration + * @param {object} _infos */ -InteractorDaemonizer.getSetKeys = function(conf, secret_key, public_key, machine_name, cb) { - var os = require('os'); - - /** - * Default values - */ - var create_file = false; +InteractorDaemonizer.getOrSetConf = function(conf, infos, cb) { var reverse_interact = true; - var version_management_active = true; - var version_management_password = null; - - // If object - if (secret_key && typeof(secret_key) == 'object') { - var cpy = json5.parse(json5.stringify(secret_key)); - cb = public_key; - secret_key = cpy.secret_key; - public_key = cpy.public_key; - machine_name = cpy.machine_name; - } - + var version_management_active = true; + var version_management_password = null; + var secret_key; + var public_key; + var machine_name; + var info_node; + var new_connection = false; + // 1# Load configuration file try { var interaction_conf = json5.parse(fs.readFileSync(conf.INTERACTION_CONF)); + public_key = interaction_conf.public_key; + machine_name = interaction_conf.machine_name; + secret_key = interaction_conf.secret_key; + info_node = interaction_conf.info_node; - public_key = public_key ? public_key : interaction_conf.public_key; - machine_name = machine_name ? machine_name : interaction_conf.machine_name; reverse_interact = interaction_conf.reverse_interact || true; - secret_key = secret_key ? secret_key : interaction_conf.secret_key; if (interaction_conf.version_management) { version_management_password = interaction_conf.version_management.password || version_management_password; version_management_active = interaction_conf.version_management.active || version_management_active; } - } catch (e) { debug('Interaction file does not exists'); - create_file = true; } + // 2# Override with passed informations + if (infos) { + secret_key = infos.secret_key; + public_key = infos.public_key; + machine_name = infos.machine_name; + info_node = infos.info_node; + new_connection = true; + } + + // 3# Override with environment variables (highest-priority conf) if (process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET) secret_key = process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET; if (process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC) public_key = process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC; + if (new_connection && infos.info_node == null) + info_node = process.env.KEYMETRICS_NODE || cst.KEYMETRICS_ROOT_URL; + if (!secret_key) return cb(new Error('secret key is not defined')); if (!public_key) return cb(new Error('public key is not defined')); - if (!machine_name) { + if (!machine_name) machine_name = os.hostname(); - } /** * Write new data to configuration file */ - try { var new_interaction_conf = { secret_key : secret_key, public_key : public_key, machine_name : machine_name, reverse_interact : reverse_interact, + info_node : info_node, version_management : { active : version_management_active, password : version_management_password @@ -389,11 +385,10 @@ InteractorDaemonizer.getSetKeys = function(conf, secret_key, public_key, machine fs.writeFileSync(conf.INTERACTION_CONF, json5.stringify(new_interaction_conf, null, 4)); } catch(e) { console.error('Error when writting configuration file %s', conf.INTERACTION_CONF); + return cb(e); } - /** - * Don't block the event loop - */ + // Don't block the event loop process.nextTick(function() { cb(null, new_interaction_conf); }); @@ -448,7 +443,7 @@ InteractorDaemonizer.launchAndInteract = function(conf, opts, cb) { process.env.PM2_INTERACTOR_PROCESSING = true; - InteractorDaemonizer.getSetKeys(conf, opts, function(err, data) { + InteractorDaemonizer.getOrSetConf(conf, opts, function(err, data) { if (err || !data) { return cb(err); } diff --git a/test/interface/interactor.daemonizer.mocha.js b/test/interface/interactor.daemonizer.mocha.js index bcbd56df..cc968eec 100644 --- a/test/interface/interactor.daemonizer.mocha.js +++ b/test/interface/interactor.daemonizer.mocha.js @@ -7,34 +7,114 @@ var interactorDaemonizer = require('../../lib/Interactor/InteractorDaemonizer'); var json5 = require('../../lib/tools/json5.js'); describe('Daemonizer interactor', function() { - before(function(done) { delete process.env.PM2_SECRET_KEY; delete process.env.PM2_PUBLIC_KEY; + delete process.env.KEYMETRICS_NODE; try { fs.unlinkSync(default_conf.INTERACTION_CONF); - } catch(e) { - } + } catch(e) {} done(); }); describe('General tests', function() { it('should try get set keys but get error because nothing exposed', function(done) { - interactorDaemonizer.getSetKeys(default_conf, null, null, null, function(err, data) { + interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { err.should.not.be.null(); done(); }); }); + }); - it('should work with env variables and create file', function(done) { + describe('Default behavior', function() { + after(function() { + fs.unlinkSync(default_conf.INTERACTION_CONF); + }); + + it('should set right node by default', function(done) { + interactorDaemonizer.getOrSetConf(default_conf, { + secret_key : 'xxx', + public_key : 'yyy', + machine_name : null, + info_node : null + }, function(err, data) { + should(err).be.null(); + data.info_node.should.eql(default_conf.KEYMETRICS_ROOT_URL); + return done(); + }); + }); + + it('should retrieve data from file without env variable', function(done) { + interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { + should(err).be.null(); + data.secret_key.should.eql('xxx'); + data.public_key.should.eql('yyy'); + data.info_node.should.eql(default_conf.KEYMETRICS_ROOT_URL); + return done(); + }); + }); + + it('should set new keys and write in configuration file', function(done) { + interactorDaemonizer.getOrSetConf(default_conf, { + secret_key : 'XXXS2', + public_key : 'XXXP2', + info_node : 'test2.url' + }, function(err, data) { + should(err).be.null(); + data.secret_key.should.eql('XXXS2'); + data.public_key.should.eql('XXXP2'); + data.info_node.should.eql('test2.url'); + + var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); + interaction_conf.secret_key.should.eql('XXXS2'); + interaction_conf.public_key.should.eql('XXXP2'); + interaction_conf.info_node.should.eql('test2.url'); + + should.exist(interaction_conf.version_management.active); + should(interaction_conf.version_management.password).be.null(); + + interaction_conf.machine_name.should.eql(os.hostname()); + return done(); + }); + }); + + it('should retrieve data from file without env variable', function(done) { + interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { + should(err).be.null(); + data.secret_key.should.eql('XXXS2'); + data.public_key.should.eql('XXXP2'); + data.info_node.should.eql('test2.url'); + return done(); + }); + }); + }); + + describe('Environment variable override', function() { + before(function() { process.env.PM2_SECRET_KEY = 'XXXS'; process.env.PM2_PUBLIC_KEY = 'XXXP'; + process.env.KEYMETRICS_NODE = 'test.url'; + }); - interactorDaemonizer.getSetKeys(default_conf, null, null, null, function(err, data) { + after(function() { + delete process.env.PM2_SECRET_KEY; + delete process.env.PM2_PUBLIC_KEY; + delete process.env.KEYMETRICS_NODE; + }); + + it('should work with env variables and create file', function(done) { + + interactorDaemonizer.getOrSetConf(default_conf, { + secret_key : null, + public_key : null, + machine_name : null, + info_node : null + }, function(err, data) { should(err).be.null(); data.secret_key.should.eql('XXXS'); data.public_key.should.eql('XXXP'); + data.info_node.should.eql('test.url'); should.exist(data.version_management.active); should(data.version_management.password).be.null(); @@ -43,149 +123,18 @@ describe('Daemonizer interactor', function() { } catch(e) { return done(e); } - - delete process.env.PM2_SECRET_KEY; - delete process.env.PM2_PUBLIC_KEY; return done(); }); }); it('should retrieve data from file without env variable', function(done) { - interactorDaemonizer.getSetKeys(default_conf, null, null, null, function(err, data) { + interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { should(err).be.null(); data.secret_key.should.eql('XXXS'); data.public_key.should.eql('XXXP'); - return done(); - }); - }); - - it('should set new keys and write in configuration file', function(done) { - interactorDaemonizer.getSetKeys(default_conf, 'XXXS2', 'XXXP2', null, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.secret_key.should.eql('XXXS2'); - interaction_conf.public_key.should.eql('XXXP2'); - - should.exist(interaction_conf.version_management.active); - should(interaction_conf.version_management.password).be.null(); - - interaction_conf.machine_name.should.eql(os.hostname()); - return done(); - }); - }); - - it('should work with object passed instead of direct params', function(done) { - interactorDaemonizer.getSetKeys(default_conf, { - secret_key : 'XXXS3', - public_key : 'XXXP3' - }, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS3'); - data.public_key.should.eql('XXXP3'); - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.secret_key.should.eql('XXXS3'); - interaction_conf.public_key.should.eql('XXXP3'); - interaction_conf.machine_name.should.eql(os.hostname()); + data.info_node.should.eql('test.url'); return done(); }); }); }); - - describe.skip('Recycle option', function() { - it('should handle recycle option', function(done) { - interactorDaemonizer.getSetKeys(default_conf, 'XXXS2', 'XXXP2', null, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - data.recycle.should.be.true; - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.secret_key.should.eql('XXXS2'); - interaction_conf.public_key.should.eql('XXXP2'); - interaction_conf.recycle.should.be.true; - - should.exist(interaction_conf.version_management.active); - should(interaction_conf.version_management.password).be.null(); - - interaction_conf.machine_name.should.eql(os.hostname()); - return done(); - }); - }); - - it('should handle recycle option (obj like)', function(done) { - interactorDaemonizer.getSetKeys({ - secret_key : 'XXXS2', - public_key : 'XXXP2', - machine_name : null, - recycle : true - }, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - data.recycle.should.be.true; - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.secret_key.should.eql('XXXS2'); - interaction_conf.public_key.should.eql('XXXP2'); - interaction_conf.recycle.should.be.true; - - should.exist(interaction_conf.version_management.active); - should(interaction_conf.version_management.password).be.null(); - - interaction_conf.machine_name.should.eql(os.hostname()); - return done(); - }); - }); - - it('should handle recycle option opts2', function(done) { - interactorDaemonizer.getSetKeys(null, null, null, null, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - data.recycle.should.be.true; - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.secret_key.should.eql('XXXS2'); - interaction_conf.public_key.should.eql('XXXP2'); - interaction_conf.recycle.should.be.true; - - should.exist(interaction_conf.version_management.active); - should(interaction_conf.version_management.password).be.null(); - - interaction_conf.machine_name.should.eql(os.hostname()); - return done(); - }); - }); - - it('should stop recycle option if passing secret and pub', function(done) { - interactorDaemonizer.getSetKeys({ - secret_key : 'XXXS2', - public_key : 'XXXP2', - machine_name : null - }, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - data.recycle.should.be.false; - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.secret_key.should.eql('XXXS2'); - interaction_conf.public_key.should.eql('XXXP2'); - interaction_conf.recycle.should.be.false; - - should.exist(interaction_conf.version_management.active); - should(interaction_conf.version_management.password).be.null(); - - interaction_conf.machine_name.should.eql(os.hostname()); - return done(); - }); - }); - - }); - - });