Skip to content

Commit

Permalink
feature: add YAML support #578
Browse files Browse the repository at this point in the history
  • Loading branch information
Unitech committed Mar 25, 2016
1 parent e64fcf6 commit 739f677
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 54 deletions.
1 change: 0 additions & 1 deletion .travis.yml
@@ -1,4 +1,3 @@
sudo: false
language: node_js
branches:
only:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@ This release is about PM2's internals refactoring, homogenization in action comm
This version has been heavily tested in testing, production environments and deeply monitored in terms of CPU and Memory usage.

- [#133 #1568] Allow to rename a process via pm2 restart app --name "new-name"
- [#578] Add YAML support for application configuration file (in extent to JSON and JSON5 support)
- [#2002 #1921 #1366] Fix CLI/JSON arguments update on restart (args, node_args, name, max-memory)
- [#2012 #1650 #1743] CLI/JSON arguments update on reload
- [#1613] Reload all reload ALL apps (stopped, errored...)
Expand Down
83 changes: 54 additions & 29 deletions lib/CLI.js
Expand Up @@ -103,11 +103,10 @@ CLI.start = function(cmd, opts, cb) {
opts = {};
}

if(util.isArray(opts.watch) && opts.watch.length === 0)
if (util.isArray(opts.watch) && opts.watch.length === 0)
opts.watch = (opts.rawArgs ? !!~opts.rawArgs.indexOf('--watch') : !!~process.argv.indexOf('--watch')) || false;

if ((typeof(cmd) === 'string' && cmd.indexOf('.json') != -1) ||
(typeof(cmd) === 'object'))
if (Utility.isConfigFile(cmd) || (typeof(cmd) === 'object'))
CLI._startJson(cmd, opts, 'restartProcessId', cb);
else
CLI._startScript(cmd, opts, cb);
Expand Down Expand Up @@ -294,24 +293,34 @@ CLI._startScript = function(script, opts, cb) {
* Can receive only option to skip applications
*/
CLI._startJson = function(file, opts, action, pipe, cb) {
var appConf, deployConf, apps_info = [];
var appConf = {};
var deployConf = {};
var apps_info = [];

if (typeof(cb) === 'undefined' && typeof(pipe) === 'function')
cb = pipe;

if (typeof(file) === 'object')
appConf = file;
else if (pipe == 'pipe')
appConf = Utility.parseConfig(file, 'none');
appConf = Utility.parseConfig(file, 'pipe');
else {
var data = null;

try {
data = fs.readFileSync(file);
} catch(e) {
Common.printError(cst.PREFIX_MSG_ERR + 'JSON ' + file +' not found');
Common.printError(cst.PREFIX_MSG_ERR + 'File ' + file +' not found');
return cb ? cb(e) : Common.exitCli(cst.ERROR_EXIT);
}

try {
appConf = Utility.parseConfig(data, file);
} catch(e) {
Common.printError(cst.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
console.error(e);
return cb ? cb(e) : Common.exitCli(cst.ERROR_EXIT);
}
appConf = Utility.parseConfig(data, file);
}

// v2 JSON declaration
Expand Down Expand Up @@ -478,32 +487,48 @@ CLI.getVersion = function(cb) {
* @param {Function}
*/
CLI.actionFromJson = function(action, file, opts, jsonVia, cb) {
var appConf;
var appConf = {};
var ret_processes = [];

//accept programmatic calls
if (typeof file == 'object') {
cb = typeof jsonVia == 'function' ? jsonVia : cb;
appConf = file;
}
else if (jsonVia == 'file') {
var data = fs.readFileSync(file);
appConf = Utility.parseConfig(data, file);
// v2 JSON declaration
if (appConf.apps) appConf = appConf.apps;
var data = null;

try {
data = fs.readFileSync(file);
} catch(e) {
Common.printError(cst.PREFIX_MSG_ERR + 'File ' + file +' not found');
return cb ? cb(e) : Common.exitCli(cst.ERROR_EXIT);
}

try {
appConf = Utility.parseConfig(data, file);
} catch(e) {
Common.printError(cst.PREFIX_MSG_ERR + 'File ' + file + ' malformated');
console.error(e);
return cb ? cb(e) : Common.exitCli(cst.ERROR_EXIT);
}
} else if (jsonVia == 'pipe') {
appConf = Utility.parseConfig(file, 'none');
appConf = Utility.parseConfig(file, 'pipe');
} else {
Common.printError('Bad call to actionFromJson, jsonVia should be one of file, pipe');
return Common.exitCli(cst.ERROR_EXIT);
}

if (!Array.isArray(appConf)) appConf = [appConf]; //convert to array
// Backward compatibility
if (appConf.apps)
appConf = appConf.apps;

if (!Array.isArray(appConf))
appConf = [appConf];

if ((appConf = verifyConfs(appConf)) === null)
return cb ? cb({success:false}) : Common.exitCli(cst.ERROR_EXIT);

var ret_processes = [];

async.eachLimit(appConf, cst.CONCURRENT_ACTIONS, function(proc, next1) {
var name = '';
var new_env;
Expand Down Expand Up @@ -834,11 +859,14 @@ CLI.dump = function(cb) {
* @return
*/
CLI.resurrect = function(cb) {
var apps = {};

Common.printOut(cst.PREFIX_MSG + 'Restoring processes located in %s', cst.DUMP_FILE_PATH);

try {
Common.printOut(cst.PREFIX_MSG + 'Restoring processes located in %s', cst.DUMP_FILE_PATH);
var apps = fs.readFileSync(cst.DUMP_FILE_PATH);
apps = fs.readFileSync(cst.DUMP_FILE_PATH);
} catch(e) {
console.error(cst.PREFIX_MSG + 'No processes saved; DUMP file doesn\'t exist');
Common.printError(cst.PREFIX_MSG_ERR + 'No processes saved; DUMP file doesn\'t exist');
if (cb) return cb(e);
else return Common.exitCli(cst.ERROR_EXIT);
}
Expand All @@ -847,7 +875,7 @@ CLI.resurrect = function(cb) {
if (!apps[0]) return cb ? cb(null, apps) : speedList();
Satan.executeRemote('prepare', apps[0], function(err, dt) {
if (err)
Common.printError('Process %s not launched - (script missing)', apps[0].pm_exec_path);
Common.printError(cst.PREFIX_MSG_ERR + 'Process %s not launched - (script missing)', apps[0].pm_exec_path);
else
Common.printOut(cst.PREFIX_MSG + 'Process %s restored', apps[0].pm_exec_path);

Expand Down Expand Up @@ -918,7 +946,7 @@ CLI.gracefulReload = function(process_name, opts, cb) {
opts = {};
}

if (process_name.indexOf('.json') > 0)
if (Utility.isConfigFile(process_name))
CLI._startJson(process_name, commander, 'softReloadProcessId');
else
CLI._operate('softReloadProcessId', process_name, opts, cb);
Expand All @@ -930,7 +958,7 @@ CLI.reload = function(process_name, opts, cb) {
opts = {};
}

if (process_name.indexOf('.json') > 0)
if (Utility.isConfigFile(process_name))
CLI._startJson(process_name, commander, 'reloadProcessId');
else
CLI._operate('reloadProcessId', process_name, opts, cb);
Expand Down Expand Up @@ -1128,13 +1156,10 @@ CLI.restart = function(cmd, opts, cb) {
CLI.actionFromJson('restartProcessId', param, opts, 'pipe', cb);
});
}
else if (typeof(cmd) === 'object' || cmd.indexOf('.json') > 0) {
// Restart a JSON
else if (Utility.isConfigFile(cmd) || typeof(cmd) === 'object')
CLI._startJson(cmd, opts, 'restartProcessId', cb);
}
else {
else
CLI._restart(cmd, opts, cb);
}
};

/**
Expand Down Expand Up @@ -1191,7 +1216,7 @@ CLI.delete = function(process_name, jsonVia, cb) {

if (jsonVia == 'pipe')
return CLI.actionFromJson('deleteProcessId', process_name, commander, 'pipe', cb);
if (process_name.indexOf('.json') > 0)
if (Utility.isConfigFile(process_name))
return CLI.actionFromJson('deleteProcessId', process_name, commander, 'file', cb);
else
CLI._operate('deleteProcessId', process_name, cb);
Expand All @@ -1209,7 +1234,7 @@ CLI.stop = function(process_name, cb) {
CLI.actionFromJson('stopProcessId', param, commander, 'pipe', cb);
});
}
else if (process_name.indexOf('.json') > 0)
else if (Utility.isConfigFile(process_name))
CLI.actionFromJson('stopProcessId', process_name, commander, 'file', cb);
else
CLI._operate('stopProcessId', process_name, cb);
Expand Down
65 changes: 43 additions & 22 deletions lib/Utility.js
Expand Up @@ -4,15 +4,15 @@
* can be found in the LICENSE file.
*/

var clone = require('./tools/safeclonedeep.js');
var fs = require('fs');
var path = require('path');
var cst = require('../constants.js');
var async = require('async');
var util = require('util');

var vm = require('vm');
var semver = require('semver');
var clone = require('./tools/safeclonedeep.js');
var fs = require('fs');
var path = require('path');
var cst = require('../constants.js');
var async = require('async');
var util = require('util');
var yamljs = require('yamljs');
var vm = require('vm');
var semver = require('semver');

var Utility = module.exports = {
getDate : function() {
Expand Down Expand Up @@ -138,23 +138,44 @@ var Utility = module.exports = {
async.waterfall(flows, callback);
},
/**
* Parses a config file like ecosystem.json. Supported formats: JS, JSON, JSON5.
* Check if filename is a configuration file
* @param {string} filename
* @return {mixed} null if not conf file, json or yaml if conf
*/
isConfigFile : function(filename) {
if (typeof(filename) != 'string')
return null;
if (filename.indexOf('.json') != -1)
return 'json';
if (filename.indexOf('.yml') > -1 || filename.indexOf('.yaml') > -1)
return 'yaml';
return null;
},
/**
* Parses a config file like ecosystem.json. Supported formats: JS, JSON, JSON5, YAML.
* @param {string} confString contents of the config file
* @param {string} filename path to the config file
* @return {Object} config object
*/
parseConfig : function(confString, filename) {
var code = '(' + confString + ')';
var sandbox = {};
if (semver.satisfies(process.version, '>= 0.12.0')) {
return vm.runInThisContext(code, sandbox, {
filename: path.resolve(filename),
displayErrors: false,
timeout: 1000
});
} else {
// Use the Node 0.10 API
return vm.runInNewContext(code, sandbox, filename);
parseConfig : function(confObj, filename) {
if (!filename || filename == 'pipe' || filename == 'none' ||
filename.indexOf('.json') > -1) {
var code = '(' + confObj + ')';
var sandbox = {};
if (semver.satisfies(process.version, '>= 0.12.0')) {
return vm.runInThisContext(code, sandbox, {
filename: path.resolve(filename),
displayErrors: false,
timeout: 1000
});
} else {
// Use the Node 0.10 API
return vm.runInNewContext(code, sandbox, filename);
}
}
else if (filename.indexOf('.yml') > -1 ||
filename.indexOf('.yaml') > -1) {
return yamljs.parse(confObj.toString());
}
}
};
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -171,6 +171,7 @@
"pm2-multimeter" : "0.1.2",

"shelljs" : "0.6.0",
"yamljs" : "0.2.6",
"semver" : "~5.1.0",
"mkdirp" : "0.5.1",
"source-map-support" : "0.4.0"
Expand Down
10 changes: 10 additions & 0 deletions test.yml
@@ -0,0 +1,10 @@
apps:
- name: test
script: examples/echo.js
- name: HTTP
script: examples/child.js
instances: 4
- name: PythonApp
script: examples/echo.py
interpreter: /usr/bin/python3
interpreter_args: -u
2 changes: 0 additions & 2 deletions test/bash/app-config-update.sh
Expand Up @@ -3,8 +3,6 @@
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"

echo -e "\033[1mRunning tests:\033[0m"

cd $file_path

$pm2 start app-config-update/args1.json
Expand Down
33 changes: 33 additions & 0 deletions test/bash/yaml-configuration.sh
@@ -0,0 +1,33 @@
#!/usr/bin/env bash

SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"
cd $file_path/yaml-configuration

$pm2 start non-existent.yaml
should 'should have started 0 processes because file unknown' 'online' 0

$pm2 start malformated.yml
should 'should have started 0 processes because file malformated' 'online' 0

$pm2 start apps.yaml
should 'should have started 6 processes' 'online' 6

$pm2 restart all
should 'should have restarted 6 processes' 'restart_time: 1' 6

$pm2 restart apps.yaml
should 'should have restarted 6 processes' 'restart_time: 2' 6

$pm2 reload all
should 'should have reloaded 6 processes' 'restart_time: 3' 6

$pm2 reload apps.yaml
should 'should have reloaded 6 processes' 'restart_time: 4' 6

$pm2 stop all
should 'should have reloaded 6 processes' 'stopped' 6

$pm2 start apps.yaml
$pm2 delete all
should 'should have deleted 6 processes' 'online' 0
12 changes: 12 additions & 0 deletions test/fixtures/yaml-configuration/apps.yaml
@@ -0,0 +1,12 @@
apps:
- name: test
script: echo.js
- name: HTTP
script: child.js
instances: 4
- name: PythonApp
script: echo.py
interpreter: /usr/bin/python3
interpreter_args: -u
env:
DEFAULT_STR: 'PYTHONENV'
10 changes: 10 additions & 0 deletions test/fixtures/yaml-configuration/apps.yml
@@ -0,0 +1,10 @@
apps:
- name: test
script: echo.js
- name: HTTP
script: child.js
instances: 4
- name: PythonApp
script: echo.py
interpreter: /usr/bin/python3
interpreter_args: -u
19 changes: 19 additions & 0 deletions test/fixtures/yaml-configuration/child.js
@@ -0,0 +1,19 @@



var http = require('http');

http.createServer(function(req, res) {
res.writeHead(200);
res.end('hey');
}).listen(8000);

process.on('message', function(msg) {
if (msg == 'shutdown') {
console.log('Closing all connections...');
setTimeout(function() {
console.log('Finished closing connections');
process.exit(0);
}, 100);
}
});

0 comments on commit 739f677

Please sign in to comment.