pm2/lib/Common.js
2016-04-03 13:58:04 +02:00

434 lines
11 KiB
JavaScript

/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
/**
* @file Common utilities
* @project PM2
*/
/**
* Module dependencies
*/
var fs = require('fs');
var path = require('path');
var util = require('util');
var mkdirp = require('mkdirp');
var cronJob = require('cron').CronJob;
var isBinary = require('./tools/isbinaryfile.js');
var Utility = require('./Utility.js');
var async = require('async');
var cst = require('../constants.js');
var extItps = require('./CLI/interpreter.json');
var shelljs = require('shelljs');
var p = path;
var Satan = require('./Satan.js');
var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js');
/**
* Common methods (used by CLI and God)
*/
var Common = module.exports;
/**
* Resolve app paths and replace missing values with defaults.
* @method prepareAppConf
* @param app {Object}
* @param {} cwd
* @param {} outputter
* @return app
*/
Common.prepareAppConf = function(app, outputter) {
/**
* Minimum validation
*/
if (!app.script)
return new Error('No script path - aborting');
// Forbidden application name
if (app.name == 'push')
return new Error('Push application name is not allowed');
if (app.automation == false)
app.pmx = false;
if (!app.node_args)
app.node_args = [];
if (app.port && app.env)
app.env.PORT = app.port;
// CRON
var ret;
if ((ret = Common.sink.determineCron(app)) instanceof Error)
return ret;
var cwd = null;
if (app.cwd) {
cwd = p.resolve(app.cwd);
process.env.PWD = app.cwd;
}
// CWD option resolving
cwd && (cwd[0] != '/') && (cwd = p.resolve(process.cwd(), cwd));
cwd = cwd || process.cwd();
// Full path script resolution
app.pm_exec_path = p.resolve(cwd, app.script);
// If script does not exists after resolution
if (!fs.existsSync(app.pm_exec_path)) {
var ckd;
// Try resolve command available in $PATH
if ((ckd = shelljs.which(app.script)))
app.pm_exec_path = ckd;
else
// Throw critical error
return new Error('script not found : ' + app.pm_exec_path);
}
/**
* Auto detect .map file and enable source map support automatically
*/
if (app.disable_source_map_support != true) {
try {
if (fs.accessSync) {
fs.accessSync(app.pm_exec_path + '.map', fs.R_OK);
app.source_map_support = true;
}
else {
// Support for Node 0.10.x
if (fs.existsSync(app.pm_exec_path + '.map')) {
app.source_map_support = true;
}
}
} catch(e) {}
delete app.disable_source_map_support;
}
delete app.script;
// Set current env by first adding the process environment and then extending/replacing it
// with env specified on command-line or JSON file.
var env = {};
/**
* Do not copy internal pm2 environment variables if acting on process
* is made from a programmatic script started by PM2
*/
if (process.env.PM2_PROGRAMMATIC)
Common.safeExtend(env, process.env);
else
env = process.env;
app.env = [{}, env, app.env || {}, {pm_cwd: cwd}].reduce(function(e1, e2){
return util._extend(e1, e2);
});
app.pm_cwd = cwd;
// Interpreter
Common.sink.resolveInterpreter(app);
// Exec mode and cluster stuff
Common.sink.determineExecMode(app);
/**
* Scary
*/
var formated_app_name = app.name.replace(/[^a-zA-Z0-9\\.\\-]/g, '-');
['log', 'out', 'error', 'pid'].forEach(function(f){
var af = app[f + '_file'], ps, ext = (f == 'pid' ? 'pid':'log'), isStd = !~['log', 'pid'].indexOf(f);
if ((f == 'log' && typeof af == 'boolean' && af) || (f != 'log' && !af)) {
ps = [cst['DEFAULT_' + ext.toUpperCase() + '_PATH'], formated_app_name + (isStd ? '-' + f : '') + '.' + ext];
} else if (f != 'log' || (f == 'log' && af)) {
ps = [cwd, af];
if (!fs.existsSync(path.dirname(af))) {
Common.printError(cst.PREFIX_MSG_ERR + 'Folder does not exists: ' + path.dirname(af));
Common.printOut(cst.PREFIX_MSG + 'Creating folder: ' + path.dirname(af));
mkdirp(path.dirname(af), function(err) {
if (!err) return;
Common.printError(cst.PREFIX_MSG_ERR + 'Could not create folder: ' + path.dirname(af));
throw new Error('Could not create folder');
});
}
}
// PM2 paths
ps && (app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = p.resolve.apply(null, ps));
delete app[f + '_file'];
});
return app;
};
Common.sink = {};
Common.sink.determineCron = function(app) {
if (app.cron_restart) {
try {
Common.printOut(cst.PREFIX_MSG + 'cron restart at ' + app.cron_restart);
new cronJob(app.cron_restart, function() {
Common.printOut(cst.PREFIX_MSG + 'cron pattern for auto restart detected and valid');
});
} catch(ex) {
return new Error('Cron pattern is not valid, trace: ' + ex.stack);
}
}
};
/**
* Handle alias (fork <=> fork_mode, cluster <=> cluster_mode)
*/
Common.sink.determineExecMode = function(app) {
if (typeof app.instances == 'undefined')
app.instances = 1;
if (app.exec_mode)
app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode');
/**
* Here we put the default exec mode
*/
if (!app.exec_mode && app.instances > 1) {
app.exec_mode = 'cluster_mode';
} else if (!app.exec_mode) {
app.exec_mode = 'fork_mode';
}
};
/**
* Resolve interpreter
*/
Common.sink.resolveInterpreter = function(app) {
var noInterpreter = (!app.exec_interpreter || 'none' == app.exec_interpreter),
extName = p.extname(app.pm_exec_path),
betterInterpreter = extItps[extName];
var thereIsNVMInstalled = false;
// No interpreter defined and correspondance in schema hashmap
if (noInterpreter && betterInterpreter)
app.exec_interpreter = betterInterpreter;
// Else if no Interpreter detect if process is binary
else if (noInterpreter)
app.exec_interpreter = isBinary(app.pm_exec_path) ? 'none' : 'node';
else if (app.exec_interpreter.indexOf('node@') > -1 ||
app.node_version && thereIsNVMInstalled)
console.log('Special interpreter defined');
return app;
};
Common.deepCopy = Common.serialize = Common.clone = function(obj) {
if (obj === null || obj === undefined) return {};
return Utility.clone(obj);
};
/**
* Description
* @method exitCli
* @param {} code
* @return CallExpression
*/
Common.exitCli = function(code) {
InteractorDaemonizer.disconnectRPC(function() {
Satan.disconnectRPC(function() {
code = code || 0;
// Safe exits process after all streams are drained.
// file descriptor flag.
var fds = 0;
// exits process when stdout (1) and sdterr(2) are both drained.
function tryToExit() {
if ((fds & 1) && (fds & 2)) {
process.exit(code);
}
}
[process.stdout, process.stderr].forEach(function(std) {
var fd = std.fd;
if (!std.bufferSize) {
// bufferSize equals 0 means current stream is drained.
fds = fds | fd;
} else {
// Appends nothing to the std queue, but will trigger `tryToExit` event on `drain`.
std.write && std.write('', function() {
fds = fds | fd;
tryToExit();
});
}
// Does not write anything more.
delete std.write;
});
tryToExit();
});
});
};
/**
* Description
* @method printError
* @param {} msg
* @return CallExpression
*/
Common.printError = function(msg) {
if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false;
if (msg instanceof Error)
return console.error(msg.message);
return console.error.apply(console, arguments);
};
/**
* Description
* @method printOut
* @return
*/
Common.printOut = function() {
if (process.env.PM2_SILENT === 'true' || process.env.PM2_PROGRAMMATIC === 'true') return false;
return console.log.apply(console, arguments);
};
Common.getAllModulesId = function(cb) {
var found_proc = [];
Satan.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
Common.printError('Error retrieving process list: ' + err);
return cb(err);
}
list.forEach(function(proc) {
if (proc.pm2_env.pmx_module)
found_proc.push(proc.pm_id);
});
return cb(null, found_proc);
});
};
Common.getAllProcess = function(cb) {
var found_proc = [];
Satan.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
Common.printError('Error retrieving process list: ' + err);
return cb(err);
}
list.forEach(function(proc) {
found_proc.push(proc);
});
return cb(null, found_proc);
});
};
Common.getAllProcessId = function(cb) {
var found_proc = [];
Satan.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
Common.printError('Error retrieving process list: ' + err);
return cb(err);
}
list.forEach(function(proc) {
if (!proc.pm2_env.pmx_module)
found_proc.push(proc.pm_id);
});
return cb(null, found_proc);
});
};
Common.getProcessIdByName = function(name, force_all, cb) {
var found_proc = [];
var full_details = {};
if (typeof(cb) === 'undefined') {
cb = force_all;
force_all = false;
}
if (typeof(name) == 'number')
name = name.toString();
Satan.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
Common.printError('Error retrieving process list: ' + err);
return cb(err);
}
list.forEach(function(proc) {
if ((proc.pm2_env.name == name || proc.pm2_env.pm_exec_path == p.resolve(name)) &&
!(proc.pm2_env.pmx_module && !force_all)) {
found_proc.push(proc.pm_id);
full_details[proc.pm_id] = proc;
}
});
return cb(null, found_proc, full_details);
});
};
Common.getProcessByName = function(name, cb) {
var found_proc = [];
Satan.executeRemote('getMonitorData', {}, function(err, list) {
if (err) {
Common.printError('Error retrieving process list: ' + err);
return cb(err);
}
list.forEach(function(proc) {
if (proc.pm2_env.name == name ||
proc.pm2_env.pm_exec_path == p.resolve(name)) {
found_proc.push(proc);
}
});
return cb(null, found_proc);
});
};
/**
* Raw extend
*/
Common.extend = function(destination, source){
if (!source || typeof source != 'object') return destination;
Object.keys(source).forEach(function(new_key) {
if (source[new_key] != '[object Object]')
destination[new_key] = source[new_key];
});
return destination;
};
/**
* This is useful when starting script programmatically
*/
Common.safeExtend = function(origin, add){
if (!add || typeof add != 'object') return origin;
//Ignore PM2's set environment variables from the nested env
var keysToIgnore = ['name', 'exec_mode', 'env', 'args', 'pm_cwd', 'exec_interpreter', 'pm_exec_path', 'node_args', 'pm_out_log_path', 'pm_err_log_path', 'pm_pid_path', 'pm_id', 'status', 'pm_uptime', 'created_at', 'unstable_restarts', 'restart_time', 'pm_id', 'axm_actions', 'pmx_module', 'command', 'watch', 'versioning', 'vizion_runing', 'MODULE_DEBUG'];
var keys = Object.keys(add);
var i = keys.length;
while (i--) {
//Only copy stuff into the env that we don't have already.
if(keysToIgnore.indexOf(keys[i]) == -1 && add[keys[i]] != '[object Object]')
origin[keys[i]] = add[keys[i]];
}
return origin;
};