UNPKG

jitsu

Version:

Flawless command line deployment of Node.js apps to the cloud

452 lines (398 loc) 13.6 kB
/* * jitsu.js: Top-level include for the jitsu module. * * (C) 2010, Nodejitsu Inc. * */ var path = require('path'), util = require('util'), colors = require('colors'), flatiron = require('flatiron') godaddy = require('./jitsu/commands/godaddy'); var jitsu = module.exports = flatiron.app; // // Setup `jitsu` to use `pkginfo` to expose version // require('pkginfo')(module, 'name', 'version'); // // Configure jitsu to use `flatiron.plugins.cli` // jitsu.use(flatiron.plugins.cli, { version: true, usage: require('./jitsu/usage'), source: path.join(__dirname, 'jitsu', 'commands'), argv: { version: { alias: 'v', description: 'print jitsu version and exit', string: true }, localconf: { description: 'search for .jitsuconf file in ./ and then parent directories', string: true }, jitsuconf: { alias: 'j', description: 'specify file to load configuration from', string: true }, noanalyze: { description: 'skip require-analyzer: do not attempt to dynamically detect dependencies', boolean: true }, colors: { description: '--no-colors will disable output coloring', default: true, boolean: true }, confirm: { alias: 'c', description: 'prevents jitsu from asking before overwriting/removing things', default: false, boolean: true }, release: { alias: 'r', description: 'specify release version number or semantic increment (build, patch, minor, major)', string: true }, raw: { description: 'jitsu will only output line-delimited raw JSON (useful for piping)', boolean: true } } }); jitsu.options.log = { console: { raw: jitsu.argv.raw } }; // // Setup config, users, command aliases and prompt settings // jitsu.prompt.properties = flatiron.common.mixin( jitsu.prompt.properties, require('./jitsu/properties') ); jitsu.prompt.override = jitsu.argv; require('./jitsu/config'); require('./jitsu/alias'); require('./jitsu/commands'); // // Setup other jitsu settings. // jitsu.started = false; jitsu.common = require('./jitsu/common'); jitsu.package = require('./jitsu/package'); jitsu.logFile = new (require('./jitsu/common/logfile').LogFile)(path.join(process.env.HOME, '.jitsulog')); // // Hoist `jitsu.api` from `nodejitsu-api` module. // jitsu.api = {}; jitsu.api.Client = require('nodejitsu-api').Client; jitsu.api.Apps = require('nodejitsu-api').Apps; jitsu.api.Databases = require('nodejitsu-api').Databases; jitsu.api.Logs = require('nodejitsu-api').Logs; jitsu.api.Snapshots = require('nodejitsu-api').Snapshots; jitsu.api.Users = require('nodejitsu-api').Users; jitsu.api.Tokens = require('nodejitsu-api').Tokens; // // ### function welcome () // Print welcome message. // jitsu.welcome = function () { // // If a user is logged in, show username // var username = jitsu.config.get('username') || ''; godaddy.notice(jitsu); jitsu.log.info('Welcome to ' + 'Nodejitsu'.grey + ' ' + username.magenta); jitsu.log.info('jitsu v' + jitsu.version + ', node ' + process.version); jitsu.log.info('It worked if it ends with ' + 'Nodejitsu'.grey + ' ok'.green.bold); }; // // ### function start (command, callback) // #### @command {string} Command to execute once started // #### @callback {function} Continuation to pass control to when complete. // Starts the jitsu CLI and runs the specified command. // jitsu.start = function (callback) { // // Check for --no-colors/--colors option, without hitting the config file // yet // var useColors = (typeof jitsu.argv.colors == 'undefined' || jitsu.argv.colors); useColors || (colors.mode = "none"); // // whoami command should not output anything but username // if (jitsu.argv._[0] === "whoami") { console.log(jitsu.config.get('username') || ''); return; } jitsu.init(function (err) { if (err) { jitsu.welcome(); jitsu.showError(jitsu.argv._.join(' '), err); return callback(err); } jitsu.common.checkVersion(function (err) { if (err) { return callback(); } var minor, username; // // --no-colors option turns off output coloring, and so does setting // colors: false in ~/.jitsuconf (see // https://github.com/nodejitsu/jitsu/issues/101 ) // if ( !jitsu.config.get('colors') || !useColors ) { colors.mode = "none"; jitsu.log.get('default').stripColors = true; jitsu.log.get('default').transports.console.colorize = false; } jitsu.welcome(); minor = process.version.split('.')[1]; if (parseInt(minor, 10) % 2) { jitsu.log.warn('You are using unstable version of node.js. You may experience problems.'); } username = jitsu.config.get('username'); if (!username && jitsu.config.get('requiresAuth').indexOf(jitsu.argv._[0]) !== -1) { return jitsu.commands.users.login(function (err) { if (err) { jitsu.showError(jitsu.argv._.join(' '), err); return callback(err); } var username = jitsu.config.get('username'); jitsu.log.info('Successfully configured user ' + username.magenta); return jitsu.exec(jitsu.argv._, callback); }); } return jitsu.exec(jitsu.argv._, callback); }); }); }; // // ### function exec (command, callback) // #### @command {string} Command to execute // #### @callback {function} Continuation to pass control to when complete. // Runs the specified command in the jitsu CLI. // jitsu.exec = function (command, callback) { function execCommand (err) { if (err) { return callback(err); } // // Remark: This is a temporary fix for aliasing init=>install, // was having a few issues with the alias command on the install resource // if (command[0] === 'init') { command[0] = 'install'; } // Alias db to databases if (command[0] === 'db' || command[0] === 'dbs' || command[0] === 'database') { command[0] = 'databases'; } // Allow `jitsu logs` as a shortcut for `jitsu logs app` if (command[0] === 'logs' && command.length === 1) { command[1] = 'app'; } // Allow `jitsu host` as a shortcut for `jitsu config set remoteHost` if (command[0] === 'host' && command.length === 2) { command[3] = command[1]; command[0] = 'config'; command[1] = 'set'; command[2] = 'remoteHost'; } // // Prevent sign-ups on our alias as well as on the regular route. // if (command[0] === 'signup' || command[0] === 'users' && command[1] === 'create') { godaddy.disabled(jitsu, command.join(' ')); return callback(new Error('Signup is disabled')); } jitsu.log.info('Executing command ' + command.join(' ').magenta); jitsu.router.dispatch('on', command.join(' '), jitsu.log, function (err, shallow) { if (err) { jitsu.showError(command.join(' '), err, shallow); return callback(err); } // // TODO (indexzero): Something here // callback(); }); } return !jitsu.started ? jitsu.setup(execCommand) : execCommand(); }; // // ### function setup (callback) // #### @callback {function} Continuation to pass control to when complete. // Sets up the instances of the Resource clients for jitsu. // there is no io here, yet this function is ASYNC. // jitsu.setup = function (callback) { if (jitsu.started === true) { return callback(); } var rejectUnauthorized = jitsu.config.get('rejectUnauthorized'); if (typeof rejectUnauthorized === 'undefined') { jitsu.config.set('rejectUnauthorized', false); } var userAgent = jitsu.config.get('headers:user-agent'); if (!userAgent) { jitsu.config.set('headers:user-agent', 'jitsu/' + jitsu.version); } ['Users', 'Apps', 'Snapshots', 'Databases', 'Logs', 'Tokens'].forEach(function (key) { var k = key.toLowerCase(); jitsu[k] = new jitsu.api[key](jitsu.config); jitsu[k].on('debug::request', debug); jitsu[k].on('debug::response', debug); function debug (data) { if (jitsu.argv.debug || jitsu.config.get('debug')) { if (data.headers && data.headers['Authorization']) { data = JSON.parse(JSON.stringify(data)); data.headers['Authorization'] = Array(data.headers['Authorization'].length).join('*'); } util.inspect(data, false, null, true).split('\n').forEach(jitsu.log.debug); } }; }); jitsu.started = true; callback(); }; // // ### function showError (command, err, shallow, skip) // #### @command {string} Command which has errored. // #### @err {Error} Error received for the command. // #### @shallow {boolean} Value indicating if a deep stack should be displayed // #### @skip {boolean} Value indicating if this error should be forcibly suppressed. // Displays the `err` to the user for the `command` supplied. // jitsu.showError = function (command, err, shallow, skip) { var username, display, errors, stack; // // ### function unknownError(message, stack) // Displays an unknown error by dumping the call `stack` // and a given `message`. // function unknownError(message, stack) { jitsu.log.error(message); stack.split('\n').forEach(function (line) { jitsu.log.error(line); }) } // // ### function solenoidError(display) // Displays a "solenoid" error. // function solenoidError(display) { jitsu.log.error('Error starting application. This could be a user error.'); display.solenoid.split('\n').forEach(function (line) { jitsu.log.error(line); }); } // // ### function appError(display) // Displays an "application" error. // function appError(display) { if (display.output) { jitsu.log.error('Error output from application. This is usually a user error.'); display.output.split('\n').forEach(function (line) { jitsu.log.error(line); }) } return !display.solenoid ? unknownError(display.message, display.stack) : solenoidError(display); } if (err.statusCode === 403) { //jitsu.log.error('403 ' + err.result.error); } else if (err.statusCode === 503) { if (err.result && err.result.message) { jitsu.log.error(err.result.message); } else { jitsu.log.error('The Nodejitsu cloud is currently at capacity. Please try again later.'); } } else if (!skip) { jitsu.log.error('Error running command ' + command.magenta); if (!jitsu.config.get('nolog')) { jitsu.logFile.log(err); } if (err.result) { if (err.result.message) { jitsu.log.error(err.result.message); } if (err.result.error && typeof err.result.error == 'string') { if (~err.result.error.indexOf('matching versions')) { jitsu.log.error(err.result.error); jitsu.log.error("Do not use a specific engine version, try a generic one (eg: 0.8.x or 0.10.x)"); } else { jitsu.log.error(err.result.error); } } if (err.result.errors && Array.isArray(err.result.errors)) { errors = { connection: err.result.errors.filter(function (err) { return err.blame === 'connection'; }), application: err.result.errors.filter(function (err) { return err.blame === 'application'; }), solenoid: err.result.errors.filter(function (err) { return err.blame === 'solenoid'; }) }; if (errors.application.length) { return appError(errors.application[0]); } else if (errors.solenoid.length) { return solenoidError(errors.solenoid[0]) } return errors.connection.length ? unknownError('Error contacting drone(s):', errors.connection[0].stack) : unknownError('Error returned from Nodejitsu:', err.result.errors[0].stack); } else if (err.result.stack) { return unknownError('Error returned from Nodejitsu:', err.result.stack); } } else { if (err.stack && !shallow) { if (err.message && err.message === 'socket hang up' && err.code && err.code === 'ECONNRESET') { jitsu.log.info('The nodejitsu api reset the connection'); jitsu.log.help('This error may be due to the application or the drone server'); } else if (err.message && err.message === 'ETIMEDOUT'){ jitsu.log.info( 'jitsu\'s client request timed out before the server ' + 'could respond. Please increase your client timeout'); jitsu.log.help('(Example: `jitsu config set timeout 480000`)'); jitsu.log.help('This error may be due to network connection problems'); } else { err.stack.split('\n').forEach(function (trace) { jitsu.log.error(trace); }); } return; } } if (err.message) { jitsu.log.error(err.message); } } jitsu.log.help("For help with this error contact Nodejitsu Support:"); jitsu.log.help(" webchat: <http://webchat.nodejitsu.com/>"); jitsu.log.help(" irc: <irc://chat.freenode.net/#nodejitsu>"); jitsu.log.help(" email: <support@nodejitsu.com>"); jitsu.log.help(""); jitsu.log.help(" Copy and paste this output to a gist (http://gist.github.com/)"); jitsu.log.info('Nodejitsu '.grey + 'not ok'.red.bold); };