[cli] collect extra arguments and merge into settings object

This commit is contained in:
Spencer Alger 2015-07-10 02:12:43 -07:00
parent 4f5f523946
commit 52570cbaad
16 changed files with 330 additions and 114 deletions

View file

@ -18,7 +18,7 @@ If Not Exist "%NODE%" (
)
TITLE Kibana Server
"%NODE%" "%DIR%\src\cli" %*
"%NODE%" "%DIR%\src\cli\cli" %*
:finally

View file

@ -22,5 +22,5 @@ if [ ! -x "$NODE" ]; then
fi
CONFIG_PATH="${DIR}/config/kibana.yml" exec "${NODE}" "${DIR}/src/cli" ${@}
CONFIG_PATH="${DIR}/config/kibana.yml" exec "${NODE}" "${DIR}/src/cli/cli" ${@}

61
src/cli/Command.js Normal file
View file

@ -0,0 +1,61 @@
'use strict';
let _ = require('lodash');
let Command = require('commander').Command;
let red = require('./color').red;
let yellow = require('./color').yellow;
let help = require('./help');
Command.prototype.error = function (err) {
if (err && err.message) err = err.message;
console.log(
`
${red(' ERROR ')} ${err}
${help(this, ' ')}
`
);
};
Command.prototype.unknownArgv = function (argv) {
if (argv) this.__unkownArgv = argv;
return (this.__unkownArgv || []).slice(0);
};
Command.prototype.getUnknownOpts = function () {
let opts = {};
let unknowns = this.unknownArgv();
while (unknowns.length) {
let opt = unknowns.shift().split('=');
if (opt[0].slice(0, 2) !== '--') {
this.error(`Extra option "${opt[0]}" must start with "--"`);
}
if (opt.length === 1) {
if (!unknowns.length || unknowns[0][0] === '-') {
this.error(`Extra option "${opt[0]}" must have a value`);
}
opt.push(unknowns.shift());
}
let val = opt[1];
try { val = JSON.parse(opt[1]); }
catch (e) { val = opt[1]; }
_.set(opts, opt[0].slice(2), val);
}
return opts;
};
Command.prototype.parseOptions = _.wrap(Command.prototype.parseOptions, function (parse, argv) {
let opts = parse.call(this, argv);
this.unknownArgv(opts.unknown);
return opts;
});
module.exports = Command;

View file

@ -1,71 +1,40 @@
'use strict';
let _ = require('lodash');
let KbnServer = require('../server/KbnServer');
let program = require('commander');
let fromRoot = require('../utils/fromRoot');
let pkg = require('../utils/closestPackageJson').getSync();
let readYamlConfig = require('./readYamlConfig');
let Command = require('./Command');
program.description(
let argv = require('cluster').isWorker ? JSON.parse(process.env.kbnWorkerArgv) : process.argv.slice();
let program = new Command('bin/kibana');
program
.version(pkg.version)
.description(
'Kibana is an open source (Apache Licensed), browser based analytics ' +
'and search dashboard for Elasticsearch.'
);
program.version(pkg.version);
program.option('-e, --elasticsearch <uri>', 'Elasticsearch instance');
program.option('-c, --config <path>', 'Path to the config file');
program.option('-p, --port <port>', 'The port to bind to', parseInt);
program.option('-q, --quiet', 'Turns off logging');
program.option('--verbose', 'Turns on verbose logging');
program.option('-H, --host <host>', 'The host to bind to');
program.option('-l, --log-file <path>', 'The file to log to');
program.option(
'--plugin-dir <path>',
'A path to scan for plugins, this can be specified multiple ' +
'times to specify multiple directories'
);
program.option(
'--plugin-path <path>',
'A path to a plugin which should be included by the server, ' +
'this can be specified multiple times to specify multiple paths'
);
program.option('--plugins <path>', 'an alias for --plugin-dir');
program.option('--dev', 'Run the server with development mode defaults');
program.option(
'--watch',
'Enable watching the source tree and automatically restarting, ' +
'enabled by --dev, use --no-watch to disable'
);
program.parse(process.argv);
let settings = readYamlConfig(program.config || fromRoot('config/kibana.yml'));
let set = _.partial(_.set, settings);
let get = _.partial(_.get, settings);
// attach commands
var serve = require('./commands/serve/serve')(program);
if (program.dev) {
set('env', 'development');
set('optimize.watch', true);
// check for no command name
if (!argv[2] || argv[2][0] === '-') {
argv.splice(2, 0, ['serve']);
}
if (program.watch != null) set('optimize.watch', program.watch);
if (program.elasticsearch) set('elasticsearch.url', program.elasticsearch);
if (program.port) set('server.port', program.port);
if (program.host) set('server.host', program.host);
if (program.quiet) set('logging.quiet', program.quiet);
if (program.logFile) set('logging.dest', program.logFile);
set('plugins.scanDirs', _.compact([].concat(
get('plugins.scanDirs'),
program.plugins,
program.pluginDir,
fromRoot('src/plugins')
)));
set('plugins.paths', [].concat(program.pluginPath || []));
// Start the KbnServer server with the settings fromt he CLI and YAML file
let kibana = new KbnServer(settings);
kibana.listen().catch(function (err) {
console.log(err.stack);
process.exit(1);
program
.command('help <command>')
.description('Get the help for a specific command')
.action(function (cmdName) {
var cmd = _.find(program.commands, { _name: cmdName });
if (!cmd) return this.error(`unknown command ${cmd}`);
cmd.help();
});
program
.command('*', null, { noHelp: true })
.action(function (cmd, options) {
program.error(`unknown command ${cmd}`);
});
program.parse(argv);

7
src/cli/color.js Normal file
View file

@ -0,0 +1,7 @@
var _ = require('lodash');
var ansicolors = require('ansicolors');
exports.green = _.flow(ansicolors.black, ansicolors.bgGreen);
exports.red = _.flow(ansicolors.white, ansicolors.bgRed);
exports.yellow = _.flow(ansicolors.black, ansicolors.bgYellow);

View file

@ -0,0 +1,4 @@
{
"name": "devMode",
"version": "1.0.0"
}

View file

@ -0,0 +1,67 @@
'use strict';
let _ = require('lodash');
let readYamlConfig = require('../../readYamlConfig');
let fromRoot = require('../../../utils/fromRoot');
let KbnServer = require('../../../server/KbnServer');
module.exports = function (program) {
program
.command('serve')
.description('Run the kibana server')
.allowUnknownOption()
.option('-e, --elasticsearch <uri>', 'Elasticsearch instance')
.option('-c, --config <path>', 'Path to the config file')
.option('-p, --port <port>', 'The port to bind to', parseInt)
.option('-q, --quiet', 'Turns off logging')
.option('--verbose', 'Turns on verbose logging')
.option('-H, --host <host>', 'The host to bind to')
.option('-l, --log-file <path>', 'The file to log to')
.option(
'--plugin-dir <path>',
'A path to scan for plugins, this can be specified multiple ' +
'times to specify multiple directories'
)
.option(
'--plugin-path <path>',
'A path to a plugin which should be included by the server, ' +
'this can be specified multiple times to specify multiple paths'
)
.option('--plugins <path>', 'an alias for --plugin-dir')
.option('--dev', 'Run the server with development mode defaults')
.option('--no-watch', 'Prevent watching, use with --dev to prevent server restarts')
.action(function (opts) {
if (opts.dev && !opts.noWatch && !require('cluster').isWorker) {
// stop processing the action and handoff to watch cluster manager
return require('./watch');
}
let settings = readYamlConfig(opts.config || fromRoot('config/kibana.yml'));
let set = _.partial(_.set, settings);
let get = _.partial(_.get, settings);
if (opts.dev) {
set('env', 'development');
set('optimize.watch', opts.watch);
}
if (opts.elasticsearch) set('elasticsearch.url', opts.elasticsearch);
if (opts.port) set('server.port', opts.port);
if (opts.host) set('server.host', opts.host);
if (opts.quiet) set('logging.quiet', opts.quiet);
if (opts.logFile) set('logging.dest', opts.logFile);
set('plugins.scanDirs', _.compact([].concat(
get('plugins.scanDirs'),
opts.plugins,
opts.pluginDir,
fromRoot('src/plugins')
)));
set('plugins.paths', [].concat(opts.pluginPath || []));
return new KbnServer(_.merge(settings, this.getUnknownOpts()));
});
};

View file

@ -0,0 +1,88 @@
'use strict';
let _ = require('lodash');
let Gaze = require('gaze').Gaze;
let join = require('path').join;
let cluster = require('cluster');
let ansicolors = require('ansicolors');
let cliPath = join(__dirname, '..', 'cli.js');
let green = require('../color').green;
let red = require('../color').red;
let yellow = require('../color').yellow;
console.log(yellow(' Kibana starting '), 'and watching for changes');
cluster.setupMaster({
exec: cliPath,
silent: false
});
let baseArgv = [process.execPath, cliPath].concat(_.difference(process.argv.slice(2), ['--no-watch']));
let serverArgv = JSON.stringify(baseArgv.concat(['--optimize.enable=false']));
let optimizerArgv = JSON.stringify(baseArgv.concat(['--plugins.initialize=false']));
let changedFiles = [];
let server;
let startServer = _.debounce(function () {
if (server && server.isDead()) {
server.kill(); // once "exit" event is received with 0 status, startServer() is called again
return;
}
server = cluster.fork({ kbnWorkerArgv: serverArgv });
server.on('online', function () {
if (!changedFiles.length) {
console.log(green(' Kibana Started '));
return;
}
let files = changedFiles.splice(0);
let prefix = files.length > 1 ? '\n - ' : '';
let fileList = files.reduce(function (list, file, i, files) {
return `${list || ''}${prefix}"${file}"`;
}, '');
console.log(yellow(' Kibana Restarted '), `due to changes in ${fileList}`);
});
}, 200);
let optimizer = cluster.fork({ kbnWorkerArgv: optimizerArgv });
optimizer.on('online', startServer);
cluster.on('exit', function (worker, code) {
if (worker === server) {
if (code > 0) {
console.log(red(' Kibana Crashed '), 'with status code', code);
} else {
// graceful shutdowns should only happen if we are restarting
startServer();
}
return;
}
if (worker === optimizer) {
console.log(red(' optimizer crashed '), 'with status code', code);
process.exit(1);
}
});
let gaze = new Gaze([
'src/**/*.{js,json,tmpl,yml}',
'!src/**/public/',
'config/',
], {
cwd: join(__dirname, '..', '..', '..')
});
// A file has been added/changed/deleted
gaze.on('all', function (event, path) {
changedFiles.push(path);
startServer();
});
gaze.on('error', function (err) {
console.log(red(' failed to watch files \n'), err.stack);
process.exit(1);
});

View file

@ -1,5 +0,0 @@
{
"name": "devmode",
"main": "devmode.js",
"version": "1.0.0"
}

View file

@ -1,47 +0,0 @@
'use strict';
let _ = require('lodash');
let nodemon = require('nodemon');
let join = require('path').join;
let relative = require('path').relative;
let normalize = require('path').normalize;
let ansicolors = require('ansicolors');
let root = join(__dirname, '..', '..', '..');
let fromRoot = _.restParam(function (segs) {
return normalize(join.apply(null, [root].concat(segs)));
});
let rel = _.partial(relative, root);
let green = _.flow(ansicolors.black, ansicolors.bgGreen);
let red = _.flow(ansicolors.white, ansicolors.bgRed);
let yellow = _.flow(ansicolors.black, ansicolors.bgYellow);
let crash = red(' Kibana Crashed ');
let restart = yellow(' Kibana Restarted ');
let start = green(' Kibana Started ');
let args = _.without(process.argv.slice(2), '--watch');
console.log(yellow(' Kibana starting '), 'and watching for changes');
nodemon({
script: fromRoot('src/cli/index.js'),
args: args,
watch: [
fromRoot('src/'),
fromRoot('config/'),
],
ignore: fromRoot('src/**/public/'),
ext: 'js,json,tmpl,yml'
});
nodemon.on('start', _.bindKey(console, 'log', start));
nodemon.on('crash', _.bindKey(console, 'log', crash));
nodemon.on('restart', function (files) {
var prefix = files.length > 1 ? '\n - ' : '';
var fileList = files.reduce(function (list, file, i, files) {
return `${list || ''}${prefix}"${rel(file)}"`;
}, '');
console.log(`${restart} due to changes in ${fileList}`);
});

74
src/cli/help.js Normal file
View file

@ -0,0 +1,74 @@
'use strict';
var _ = require('lodash');
module.exports = function (command, spaces) {
if (!_.size(command.commands)) {
return command.outputHelp();
}
let defCmd = _.find(command.commands, function (cmd) {
return cmd._name === 'serve';
});
let desc = !command.description() ? '' : command.description();
let cmdDef = !defCmd ? '' : `=${defCmd._name}`;
return _.trim(
`
Usage: ${command._name} [command${cmdDef}] [options]
${desc}
Commands:
${indent(commandsSummary(command), 2)}
${cmdHelp(defCmd)}
`
).replace(/^/gm, spaces || '');
};
function indent(str, n) {
return String(str || '').trim().replace(/^/gm, _.repeat(' ', n));
}
function commandsSummary(program) {
let cmds = _.compact(program.commands.map(function (cmd) {
let name = cmd._name;
if (name === '*') return;
let opts = cmd.options.length ? ' [options]' : '';
let args = cmd._args.map(function (arg) {
return humanReadableArgName(arg);
}).join(' ');
return [
`${name} ${opts} ${args}`,
cmd.description()
];
}));
let cmdLColWidth = cmds.reduce(function (width, cmd) {
return Math.max(width, cmd[0].length);
}, 0);
return cmds.reduce(function (help, cmd) {
return `${help || ''}${_.padRight(cmd[0], cmdLColWidth)} ${cmd[1] || ''}\n`;
}, '');
}
function cmdHelp(cmd) {
if (!cmd) return '';
return _.trim(
`
"${cmd._name}" Options:
${indent(cmd.optionHelp(), 2)}
`
);
}
function humanReadableArgName(arg) {
var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
}

View file

@ -1,2 +0,0 @@
if (process.argv.slice(2).indexOf('--watch') > -1) require('./dev/watch');
else require('./cli');