diff --git a/.gitignore b/.gitignore index 80e5be4..7d33764 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .DS_Store +*.log diff --git a/lib/cli/commands/config.js b/lib/cli/commands/config.js index 5e2335b..a99f0a1 100644 --- a/lib/cli/commands/config.js +++ b/lib/cli/commands/config.js @@ -1,93 +1,11 @@ -const chalk = require('chalk') -const filterOptions = require('app/cli/helpers/filterOptions') -const nginxUtil = require('app/nginxUtil') - -exports.command = 'config [options]', -exports.describe = 'Server basic configuration related commands', +exports.command = 'config [options]' +exports.describe = 'Server basic configuration related commands' exports.builder = (yargs) => { return yargs + .usage('Usage: nginrx config [options]') .commandDir('configCmds') - .check(checker) .demandCommand(1, 1) } exports.handler = () => {} - -let cmds = { - test: { - name: 'test', - description: 'Test current nginx configuration', - builder: testBuilder, - handler: testHandler, - }, - output: { - name: 'output', - description: 'Output current nginx configuration in json', - builder: outputBuilder, - handler: outputHandler, - }, -} - - -function testBuilder(yargs) { - return yargs - .usage('Usage: $0 config test') - .check(testChecker) -} - -function outputBuilder(yargs) { - return yargs - .usage('Usage: $0 config output [options]') - .option('f', { alias: 'file', describe: 'Output data to a file' }) - .check(outputChecker) -} - -function checker(argv) { - let extraOptions = filterOptions(argv, []) - if(extraOptions.length > 0) { - throw new Error('Unknown option: '+ extraOptions[0]) - } - if(argv._.length === 1) { - throw new Error('No command provided') - } - if(Object.keys(cmds).indexOf(argv._[1] < 0)) { - throw new Error('Unknown command: '+ argv._[1]) - } - return true -} - -function testChecker(argv) { - let extraOptions = filterOptions(argv, []) - if(extraOptions.length > 0) { - throw new Error('Unknown option: '+ extraOptions[0]) - } - if(argv._.length > 2) { - throw new Error('Unknown command: '+ argv._[2]) - } - return true -} - -function outputChecker(argv) { - let extraOptions = filterOptions(argv, ['file']) - if(extraOptions.length > 0) { - throw new Error('Unknown option: '+ extraOptions[0]) - } - if(argv._.length > 2) { - throw new Error('Unknown command: '+ argv._[2]) - } - return true -} - -function testHandler() { - let result = nginxUtil.testConfig() - if(result.success) { - console.log(chalk.green('>>> Nginx config ok')) - } else { - console.log(chalk.red('>>> result.error')) - } -} - -function outputHandler() { - console.log(chalk.blue('>>> Handled output command')) -} diff --git a/lib/cli/commands/configCmds/output.js b/lib/cli/commands/configCmds/output.js index 96273b6..a8eb989 100644 --- a/lib/cli/commands/configCmds/output.js +++ b/lib/cli/commands/configCmds/output.js @@ -1,25 +1,63 @@ -const chalk = require('chalk') +const fs = require('fs') +const util = require('util') +const nginrx = require('app') +const NginrxError = require('app/errors/nginrxError') const filterOptions = require('app/cli/helpers/filterOptions') -exports.command = 'output [options]', -exports.describe = 'Output current nginx configuration in json', +function checker(argv) { + let extraOptions = filterOptions(argv, ['file', 'f', 'output', 'o']) + if(extraOptions.length > 0) { + throw new Error('Unknown option: '+ extraOptions[0]) + } + return true +} + +exports.command = 'output [options]' +exports.describe = 'Output current nginx configuration in json' exports.builder = (yargs) => { return yargs - .option('f', { alias: 'file', describe: 'Output data to a file' }) + .option('f', { + alias: 'file', + describe: 'Write output data to a file', + }) + .option('o', { + alias: 'output', + describe: 'Data output format', + choices: ['string', 'json', 'object'], + default : 'object', + }) .check(checker) .demandCommand(0, 0) } -exports.handler = () => { console.log(chalk.blue('>>> Handled output command')) } +exports.handler = (argv) => { + let config + try { + config = nginrx.getNginxConfig() + } catch(e) { + throw new NginrxError('Error in getting config', e.message) + } -function checker(argv) { - let extraOptions = filterOptions(argv, ['file']) - if(extraOptions.length > 0) { - throw new Error('Unknown option: '+ extraOptions[0]) + let result = config + if(argv.output === 'json') { + result = config.toJson() + } else if(argv.output === 'string') { + result = config.toString() } - if(argv._.length > 2) { - throw new Error('Unknown command: '+ argv._[2]) + + if(!argv.file) { + if(argv.output === 'object') { + console.log(util.inspect(result, null, null)) + } else { + console.log(result) + } + } else { + try { + fs.writeFileSync(argv.file, result) + } catch(e) { + throw new NginrxError('Error in writing file', e.message) + } + console.log('Config successfully written') } - return true } diff --git a/lib/cli/commands/configCmds/test.js b/lib/cli/commands/configCmds/test.js index a73643c..e5b1196 100644 --- a/lib/cli/commands/configCmds/test.js +++ b/lib/cli/commands/configCmds/test.js @@ -1,10 +1,18 @@ const chalk = require('chalk') const filterOptions = require('app/cli/helpers/filterOptions') const nginxUtil = require('app/nginxUtil') -const NginrxError = require('app/errors/nginrxError') -exports.command = 'test', -exports.describe = 'Test current nginx configuration', +function checker(argv) { + let extraOptions = filterOptions(argv, []) + if(extraOptions.length > 0) { + throw new Error('Unknown option: '+ extraOptions[0]) + } + + return true +} + +exports.command = 'test' +exports.describe = 'Test current nginx configuration' exports.builder = (yargs) => { return yargs @@ -13,21 +21,10 @@ exports.builder = (yargs) => { } exports.handler = () => { - throw new NginrxError('Command not defined yet') let result = nginxUtil.testConfig() if(result.success) { - console.log(chalk.green('>>> Nginx config ok')) + console.log(chalk.green('Nginx config ok')) } else { - console.log(chalk.red('>>> result.error')) + console.log(chalk.red('Nginx config error: ' +result.error)) } } - - -function checker(argv) { - let extraOptions = filterOptions(argv, []) - if(extraOptions.length > 0) { - throw new Error('Unknown option: '+ extraOptions[0]) - } - - return true -} diff --git a/lib/cli/commands/parse.js b/lib/cli/commands/parse.js index 8847be0..368cfa4 100644 --- a/lib/cli/commands/parse.js +++ b/lib/cli/commands/parse.js @@ -1,31 +1,56 @@ const fs = require('fs') +const util = require('util') const filterOptions = require('app/cli/helpers/filterOptions') const parser = require('app/parser') +const NginrxError = require('app/errors/nginrxError') + +function checker(argv) { + let extraOptions = filterOptions(argv, ['f', 'file', 'o', 'output']) + if(extraOptions.length > 0) { + throw new Error('Unknown option: '+ extraOptions[0]) + } + + return true +} exports.command = 'parse [string] [options]' -exports.describe = 'Parse the input Nginx config' +exports.describe = 'Parse the input Nginx config and output to console' exports.builder = (yargs) => { return yargs - .option('f', { alias: 'file', describe: 'Parse this file' }) + .usage('Usage: nginrx parse [string] [options]') + .option('f', { + alias: 'file', + describe: 'Parse this file', + }) + .option('o', { + alias: 'output', + describe: 'Data output format', + choices: ['string', 'json', 'object'], + default : 'object', + }) .check(checker) } exports.handler = (argv) => { + if(!argv.string && !argv.file) { + throw new NginrxError('Need at-least a string or a file to parse') + } + let string if(argv.string) { string = argv.string.toString() } else if(argv.file) { string = fs.readFileSync(argv.file, 'utf-8') } - console.log(require('util').inspect(parser.parse(string), null, null)) -} -function checker(argv) { - let extraOptions = filterOptions(argv, ['f', 'file']) - if(extraOptions.length > 0) { - throw new Error('Unknown option: '+ extraOptions[0]) - } + const config = parser.parse(string) - return true + if(argv.output === 'json') { + console.log(config.toJson()) + } else if(argv.output === 'object') { + console.log(util.inspect(config, null, null)) + } else { + console.log(config.toString()) + } } diff --git a/lib/cli/commands/sites.js b/lib/cli/commands/sites.js index 941120e..52fe5ab 100644 --- a/lib/cli/commands/sites.js +++ b/lib/cli/commands/sites.js @@ -1,10 +1,22 @@ -const NginrxError = require('app/errors/nginrxError') +const filterOptions = require('app/cli/helpers/filterOptions') + +function checker(argv) { + let extraOptions = filterOptions(argv, []) + if(extraOptions.length > 0) { + throw new Error('Unknown option: '+ extraOptions[0]) + } + return true +} exports.command = 'sites [options]' exports.describe = 'Sites/Server blocks related commands' -exports.builder = {} - -exports.handler = () => { - throw new NginrxError('Sites command not defined yet') +exports.builder = (yargs) => { + return yargs + .usage('Usage: nginrx sites [options]') + .commandDir('sitesCmds') + .check(checker) + .demandCommand(1, 1) } + +exports.handler = () => {} diff --git a/lib/cli/commands/sitesCmds/status.js b/lib/cli/commands/sitesCmds/status.js new file mode 100644 index 0000000..ffcf9fc --- /dev/null +++ b/lib/cli/commands/sitesCmds/status.js @@ -0,0 +1,34 @@ +const filterOptions = require('app/cli/helpers/filterOptions') +const nginrx = require('app') + +function checker(argv) { + let extraOptions = filterOptions(argv, []) + if(extraOptions.length > 0) { + throw new Error('Unknown option: '+ extraOptions[0]) + } + + return true +} + +exports.command = 'status' +exports.describe = 'Status of active nginx sites' + +exports.builder = (yargs) => { + return yargs + .check(checker) + .demandCommand(0, 0) +} + +exports.handler = () => { + const config = nginrx.getConfig() + const sites = config.find('block', 'server') + + if(sites.length > 0) { + console.log(sites.length, 'active nginx sites') + sites.forEach((site) => { + console.log(site.server_name) + }) + } else { + console.log('No active nginx site') + } +} diff --git a/lib/cli/commands/status.js b/lib/cli/commands/status.js index f976543..3122a9c 100644 --- a/lib/cli/commands/status.js +++ b/lib/cli/commands/status.js @@ -1,30 +1,32 @@ const chalk = require('chalk') const filterOptions = require('app/cli/helpers/filterOptions') const nginxUtil = require('app/nginxUtil') +const NginrxError = require('app/errors/nginrxError') + +function checker(argv) { + let extraOptions = filterOptions(argv, []) + if(extraOptions.length > 0) { + throw new Error('Unknown option: '+ extraOptions[0]) + } + + return true +} exports.command = 'status' exports.describe = 'Get status of Nginx server' exports.builder = (yargs) => { return yargs + .usage('Usage: nginrx status') .check(checker) .demandCommand(0, 0) } exports.handler = () => { - let nginxVersion = nginxUtil.getVersion() - if(nginxVersion) { - console.log(chalk.green('>>> Nginx installed, Version: '+ nginxVersion)) - } else { - console.log(chalk.red('>>> Nginx not installed')) + try { + let nginxVersion = nginxUtil.getVersion() + console.log(chalk.green('Nginx installed, Version: '+ nginxVersion)) + } catch(e) { + throw new NginrxError(e) } } - -function checker(argv) { - let extraOptions = filterOptions(argv, []) - if(extraOptions.length > 0) { - throw new Error('Unknown option: '+ extraOptions[0]) - } - - return true -} diff --git a/lib/cli/index.js b/lib/cli/index.js index 435eab3..aaba772 100644 --- a/lib/cli/index.js +++ b/lib/cli/index.js @@ -5,7 +5,7 @@ const package = require(__dirname+'/../../package.json') const errors = requireDir('../errors') require('yargs') - .usage('Usage: $0 [action] [options]') + .usage('Usage: nginrx [action] [options]') .check(checker) .commandDir('commands') .demandCommand(1) diff --git a/lib/index.js b/lib/index.js index 1c67e65..74d971b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,7 +4,7 @@ const utils = require('app/utils') module.exports = { parser: parser, getNginxConfig: function() { - const fileData = utils.getNginxConfigContents - return new parser.parse(fileData) + const fileData = utils.getNginxConfigContents() + return parser.parse(fileData) }, } diff --git a/lib/nginxUtil.js b/lib/nginxUtil.js index eead742..3d05036 100644 --- a/lib/nginxUtil.js +++ b/lib/nginxUtil.js @@ -1,5 +1,7 @@ const shell = require('shelljs') +const NginrxError = require('app/errors/nginrxError') + function checkNginxCli() { return shell.which('nginx')? true : false } @@ -9,24 +11,50 @@ function extractVersion(output) { return output.match(/\d+\.?\d+\.\d+/)[0] } -module.exports.isInstalled = function() { +function isInstalled() { if(checkNginxCli()) { return true } return false } -module.exports.getVersion = function() { - if(this.isInstalled()) { - let output = shell.exec('nginx -v', { silent: true }).stderr - // nginx cli outputs to stderr instead of stdout +function extractError(output) { + const firstLine = output.split(/\n/)[0] + const secondLine = output.split(/\n/)[1] + const secondLineArray = secondLine.split(' ') + const lastWord = secondLineArray[secondLineArray.length - 1] - return extractVersion(output) + if(lastWord === 'failed') { + return firstLine.replace('nginx: ', '') } else { return false } } -module.exports.testConfig = function() { +exports.getVersion = function() { + if(!isInstalled()) { throw new NginrxError('Nginx not installed') } + + let output = shell.exec('nginx -v', { silent: true }).stderr + // nginx cli outputs to stderr instead of stdout + + return extractVersion(output) +} + +exports.testConfig = function() { + if(!isInstalled()) { throw new NginrxError('Nginx not installed') } + + let output = shell.exec('nginx -t', { silent: true }).stderr + // nginx cli outputs to stderr instead of stdout + let error = extractError(output) + if(error) { + return { + success: false, + error: error, + } + } else { + return { + success: true, + } + } } diff --git a/lib/ngxConfig/Block.js b/lib/ngxConfig/Block.js index 6e2f206..ce1098c 100644 --- a/lib/ngxConfig/Block.js +++ b/lib/ngxConfig/Block.js @@ -1,14 +1,28 @@ -function Block(name) { - this._name = name -} +class Block { + constructor(definition) { + this._definition = definition.trim() + this._name = this._definition.split(' ')[0] + } -Block.prototype.setChildren = function(children) { - this._children = children -} + get name() { + return this._name + } + + set statements(statements) { + this._statements = statements + } + + get statements() { + return this._statements + } + + toString() { + return this._definition + } -Block.prototype.setConditionAndChildren = function(condition, children) { - this._condition = condition - this._children = children + toJson() { + return '"' + this._definition + '":' + } } module.exports = Block diff --git a/lib/ngxConfig/Comment.js b/lib/ngxConfig/Comment.js index b92664f..378a2bc 100644 --- a/lib/ngxConfig/Comment.js +++ b/lib/ngxConfig/Comment.js @@ -1,5 +1,18 @@ -function Comment(content) { - this._content = content +const Statement = require('./Statement') + +class Comment extends Statement { + constructor(text) { + super(text) + this._value = this._text.split('#')[1] + } + + get value() { + return this._value + } + + toJson() { + return '"#: "'+ this._value + '"' + } } module.exports = Comment diff --git a/lib/ngxConfig/Config.js b/lib/ngxConfig/Config.js index 544f390..212d4b4 100644 --- a/lib/ngxConfig/Config.js +++ b/lib/ngxConfig/Config.js @@ -1,5 +1,60 @@ -function Config(directives) { - this._directives = directives +const Block = require('./Block') +const Comment = require('./Comment') +const indentation = ' ' + +class Config { + constructor(statements) { + this._statements = statements + } + + get statements() { + return this._statements + } + + toString(options) { + options = options? options : {} + if(this.statements && this.statements.length > 0) { + return prettyPrint(this.statements, 0, options) + } else { + return '' + } + } + + toJson(options) { + options = options? options : {} + options.json = true + if(this.statements && this.statements.length > 0) { + return '{\n' + indentation.repeat(1) + prettyPrint(this.statements, 1, options) + '\n}' + } else { + return '{}' + } + } +} + +function prettyPrint(statements, indentCount, options) { + const method = options.json? 'toJson' : 'toString' + const joiner = options.json? ',\n'+indentation.repeat(indentCount) : '\n'+indentation.repeat(indentCount) + + return statements + .filter((statement) => { + return !(!options.comments && statement instanceof Comment) + }) + .map((statement) => { + if(statement instanceof Block) { + if(statement.statements && statement.statements.length > 0) { + return [ + statement[method]() + ' {', + indentation.repeat(indentCount + 1) + prettyPrint(statement.statements, indentCount + 1, options), + indentation.repeat(indentCount) + '}', + ].join('\n') + } else { + return statement[method]() + ' {}' + } + } else { + return statement[method]() + } + }).join(joiner) + } module.exports = Config diff --git a/lib/ngxConfig/Directive.js b/lib/ngxConfig/Directive.js index 6c9c695..dcaf29a 100644 --- a/lib/ngxConfig/Directive.js +++ b/lib/ngxConfig/Directive.js @@ -1,18 +1,30 @@ -function Directive(name) { - this._name = name -} +const Statement = require('./Statement') -Directive.prototype.setValue = function(value) { - this._value = value -} +class Directive extends Statement { + constructor(text) { + super(text) -Directive.prototype.setChildren = function(children) { - this._children = children -} + let noComma = this._text.replace(/;$/, '') + if(noComma.match(/\s/)) { + this._name = noComma.match(/^[^\s]+\s/)[0].trim() + this._value = noComma.match(/\s.*$/)[0].trim() + } else { + this._name = noComma + this._value = null + } + } + + get name() { + return this._name + } + + get value() { + return this._value + } -Directive.prototype.setConditionAndChildren = function(condition, children) { - this._condition = condition - this._children = children + toJson() { + return '"' + this._name +'": "'+ this._value + '"' + } } module.exports = Directive diff --git a/lib/ngxConfig/Statement.js b/lib/ngxConfig/Statement.js new file mode 100644 index 0000000..eb144cd --- /dev/null +++ b/lib/ngxConfig/Statement.js @@ -0,0 +1,19 @@ +class Statement { + constructor(text) { + this._text = text.trim() + } + + get text() { + return this._text + } + + toString() { + return this._text + } + + toJson() { + throw new Error('method \'toJson\' not implemented') + } +} + +module.exports = Statement diff --git a/lib/parser/ngxModules/index.js b/lib/parser/ngxModules/index.js index b7be30b..dc10488 100644 --- a/lib/parser/ngxModules/index.js +++ b/lib/parser/ngxModules/index.js @@ -1,66 +1,16 @@ -const _ = require('lodash') -const glob = require('glob') - -function getDirectives() { - const ngxModules = glob.sync(__dirname + '/!(index.js)') - - return _.reduce(ngxModules, (result, module) => { - return result.concat(require(module).getDirectives()) - }, []) -} - -function getDirectiveLexRules() { - const directives = getDirectives() - return directives.map((directive) => { - return [['INITIAL'], directive.name, 'this.begin("DIR_CTX");return "DIRECTIVE";'] - }) -} - function generateLex() { - const startConditions = { - INITIAL: '// Default block context', - LOC_CTX: '// location block condition context', - TPS_CTX: '// types block context', - DIR_CTX: '// directive context', - } - - const rules = _.concat( - getDirectiveLexRules(), - [ - // [['INITIAL'], 'if', 'this.begin("IF_CTX"); return "IF";'], - [['INITIAL'], 'events', 'return "BLOCK";'], - [['INITIAL'], 'http', 'return "BLOCK";'], - [['INITIAL'], 'mail', 'return "BLOCK";'], - [['INITIAL'], 'server', 'return "BLOCK";'], - [['INITIAL'], 'location', 'this.begin("LOC_CTX"); return "LOC_BLOCK";'], - [['INITIAL'], 'types', 'this.begin("TPS_CTX"); return "TPS_BLOCK";'], - ], - [ - [['INITIAL'], '\\s+', '/* Ignore Whitespace */'], - [['INITIAL'], '#.*\\n', 'return "COMMENT";'], - [['INITIAL'], '{', 'return "{";'], - [['INITIAL'], '}', 'return "}";'], - [['INITIAL'], ';', 'return ";";'], - [['INITIAL'], '$', 'return "EOF";'], - - [['DIR_CTX'], '\\s+', 'return "SPACE";'], - [['DIR_CTX'], '[^\\s^;][^;]*', 'this.popState(); return "VAL";'], - - [['LOC_CTX'], '\\s+', 'return "SPACE";'], - [['LOC_CTX'], '[^\\s^{][^{]*', 'return "CONDITION";'], - [['LOC_CTX'], '{', 'this.popState(); return "{";'], - - [['TPS_CTX'], '\\s+', '/* Ignore Whitespace */'], - [['TPS_CTX'], '{', 'return "{";'], - [['TPS_CTX'], '[A-Za-z0-9\\.\\-\\+]+\\\/[A-Za-z0-9\\.\\-\\+]+', 'this.begin("DIR_CTX"); return "MIMENAME";'], - [['TPS_CTX'], ';', 'return ";";'], - [['TPS_CTX'], '}', 'this.popState(); return "}";'], - ] - ) - const lex = { - startConditions, - rules, + rules: [ + ['\\s+', '// whitespace'], + ['{', 'return "{";'], + ['}', 'return "}";'], + [';', 'return ";";'], + ['$', 'return "EOF";'], + + ['#.*\\n', 'return "COMMENT";'], + ['[^;^{]+;', 'return "DIRECTIVE";'], + ['[^;^{]+', 'return "BLOCK";'], + ], } return lex @@ -69,36 +19,23 @@ function generateLex() { function generateBnf() { return { config: [ - ['statements EOF', '$$ = new yy.Config($1);return $$;'], + ['statements EOF', '$$ = new yy.Config($1); return $$;'], ], + statements: [ ['statements statement', '$$ = $1.concat($2);'], ['statement', '$$ = [$1];'], ], + statement: [ - ['block { statements }', '$1.setChildren($3);'], - ['directive SPACE value ;', '$1.setValue($3);'], - ['COMMENT', '$$ = new yy.Comment($1);'], - ['locBlock SPACE CONDITION { statements }', '$1.setConditionAndChildren($3, $5);'], - ['typBlock { mimeStatements }', '$1.setChildren($3);'], + ['blockDefinition { statements }', '$1.statements = $3; $$ = $1;'], + ['DIRECTIVE', '$$ = new yy.Directive($1);'], + ['COMMENT', '$$ = new yy.Comment($1);'], ], - block: [['BLOCK', '$$ = new yy.Block($1);']], - directive: [['DIRECTIVE', '$$ = new yy.Directive($1);']], - value: [['VAL', '$$ = $1;']], - - locBlock: [['LOC_BLOCK', '$$ = new yy.Block($1);']], - - typBlock: [['TPS_BLOCK', '$$ = new yy.Block($1);']], - - mimeStatements: [ - ['mimeStatements mimeStatement', '$$ = $1.concat($2);'], - ['mimeStatement', '$$ = [$1];'], - ], - mimeStatement: [ - ['mimeName SPACE value ;', '$1.setValue($3);'], + blockDefinition: [ + ['BLOCK', '$$ = new yy.Block($1);'], ], - mimeName: [['MIMENAME', '$$ = new yy.Directive($1);']], } } diff --git a/package.json b/package.json index 9958e4d..d1d7219 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nginrx", - "version": "0.0.1", + "version": "0.1.0", "description": "EngineerX: Easy nginx server config management with cli and api", "main": "../lib/index.js", "bin": { @@ -15,9 +15,14 @@ "url": "git+ssh://git@github.com/yadwinderpaul/nginrx.git" }, "keywords": [ - "nginx" + "nginx", + "nginx-configuration", + "cli", + "nodejs" ], - "author": "Yadwinder Paul Singh", + "author": { + "name": "Yadwinder Paul" + }, "license": "MIT", "bugs": { "url": "https://github.com/yadwinderpaul/nginrx/issues"