Skip to content

Commit

Permalink
Added ability to CRUD vhosts
Browse files Browse the repository at this point in the history
  • Loading branch information
muhammaddadu committed Mar 8, 2016
1 parent 1e170a5 commit 41d01f8
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 12 deletions.
63 changes: 60 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var nginx = new Nginx();
var nginx = new Nginx({
sudo: true,
pidFile: '/run/nginx.pid',
confDir: '/usr/local/etc/nginx/conf.d/'
confDir: '/usr/local/etc/nginx/conf.d' // directory containing .conf files
});
```
The configuration is automatically loaded depending on the operating system. The default configuration lives in /conf. Feel free to contribute.
Expand All @@ -38,15 +38,72 @@ nginx.start();
nginx.stop();
nginx.reload();
// callback
/** callback example */
nginx.start(function (err) {
});
// promises
/** promises example */
nginx.stop().then(function () {
// success
}, function (err) {
// errror
});
```

### Methods for managing vhosts
Programmatically ```create, read, update & delete``` vhosts. This will require your Nginx configuration file to require a http section where their is "an ```include directive``` for all .conf files from a certain folder". (We represent this in our library as ```confDir```); [https://kcode.de/wordpress/2033-nginx-configuration-with-includes](More information.)

#### Create
```
nginx.create({
id: '', // unique identifier
raw: '', // raw server block
template: 'default', // path or string representing template
values: '' // values to be filled in by template
})
.then(nginx.reload) // reload nginx
.then(function (response) {
// success
})
.catch(function (err) {
// error
});
```

An example would be
```
nginx.create({
id: 'test',
template: 'default',
values: {
cname: 'example.com',
port: 80,
proxy_pass: 'http://localhost:8080'
}
})
```
### Read
```
nginx.read('unique identifier');
```

### Update
```
nginx.update('unique identifier', {
/**
* same options as create
*/
template: 'default',
values: {
cname: 'example.com',
port: 80,
proxy_pass: 'http://localhost:8080'
}
});
```

#### Delete
```
nginx.delete('unique identifier')
```
2 changes: 1 addition & 1 deletion conf/default.darwin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
module.exports = {
sudo: false,
pidFile: '/usr/local/var/run/nginx.pid',
confDir: '/usr/local/etc/nginx/servers/'
confDir: '/usr/local/etc/nginx/servers'
};
3 changes: 2 additions & 1 deletion conf/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ module.exports = {
online: false,
sudo: true,
pidFile: '/run/nginx.pid',
confDir: '/usr/local/etc/nginx/conf.d/'
confDir: '/usr/local/etc/nginx/conf.d',
identifier: 'nginxo_',
};
2 changes: 1 addition & 1 deletion lib/Lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Lifecycle.prototype.end = function() {
}

Lifecycle.prototype.reload = function(cb) {
return utils.zombieCommand.apply(this, 'nginx -s reload', cb);
return utils.zombieCommand.apply(this, ['nginx -s reload', cb]);
}

Lifecycle.prototype.start = function(cb) {
Expand Down
7 changes: 5 additions & 2 deletions lib/Nginx.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const
util = require('util'),
utils = require('./utils'),
config = require('./config'),
Lifecycle = require('./Lifecycle');
Server = require('./Server');

function Nginx(opts) {
var self = this;
Expand All @@ -33,4 +33,7 @@ function Nginx(opts) {

Nginx.super_.call(this);
}
util.inherits(Nginx, Lifecycle);
util.inherits(Nginx, Server);

module.exports = Nginx;
Nginx.utils = utils;
140 changes: 140 additions & 0 deletions lib/Server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Nginx Lifecycle
* @author Muhammad Dadu
*/

const
util = require('util'),
utils = require('./utils'),
path = require('path'),
fs = require('fs'),
Lifecycle = require('./Lifecycle'),
Promise = require('promise');

function Server() {
Server.super_.call(this);
}
util.inherits(Server, Lifecycle);

/**
* list conf's
* @param {Function} cb callback
* @return {Promise} promise
*/
Server.prototype.list = function (cb) {
var globPattern = this.confDir + '/**/' + this.identifier + '*.conf',
self = this;
return new Promise(function (resolve, reject) {
(!cb || typeof cb !== 'function') && (cb = function (a, b) { if (a) { return reject(a); } resolve(b); });
var files = utils.getGlobbedFiles(globPattern),
ids = [];
files.forEach(function (file) {
ids.push(file.replace(self.confDir + '/' + self.identifier, '').replace('.conf', ''));
});
cb(null, ids);
});
}

/**
* Create a new vhost
* @param {Object} opts
* @param {String} opts.id unique identifier
* @param {String} opts.raw [text to save]
* @param {String} opts.template [template name or path]
* @param {Object} opts.values [values for the template]
* @param {Function} cb callback
* @return {Promise} promise
*/
Server.prototype.create = function (opts, cb) {
var self = this;
return new Promise(function (resolve, reject) {
(!cb || typeof cb !== 'function') && (cb = function (a, b) { if (a) { return reject(a); } resolve(b); });
!opts.id && (opts.id = String((new Date()).getTime()));
!opts.template && (opts.template = 'default');
var filePath = path.join(self.confDir, self.identifier + opts.id + '.conf');
// verify nonexistance
if (fs.existsSync(filePath)) { return cb('vhost with id `' + opts.id + '` exists'); }
if (!opts.raw && opts.values && !opts.template) { return cb('Missing attributes'); }
if (opts.raw) {
return fs.writeFile(filePath, opts.raw, function (err) {
if (err) { return cb(err); }
return cb(null, opts);
});
}
utils.render(opts.template, opts.values).then(function (html) {
return fs.writeFile(filePath, html, 'utf8', function (err) {
if (err) { return cb(err); }
return cb(null, opts);
});
}, cb);
});
}

/**
* Read a vhost
* @param {String} id unique identifier
* @param {Function} cb callback
* @return {Promise} promise
*/
Server.prototype.read = function (id, cb) {
var self = this;
return new Promise(function (resolve, reject) {
(!cb || typeof cb !== 'function') && (cb = function (a, b) { if (a) { return reject(a); } resolve(b); });
if (!id) { return cb('id is missing'); }
var filePath = path.join(self.confDir, self.identifier + id + '.conf');
// verify existance
if (!fs.existsSync(filePath)) { return cb('vhost with id `' + id + '` does not exist'); }
fs.readFile(filePath, function (err, file) {
if (err) { return cb(err); }
cb(null, file);
});
});
}

/**
* Update a vhost
* @param {String} id unique identifier
* @param {Object} opts
* @param {String} opts.raw [text to save]
* @param {String} opts.template [template name or path]
* @param {Object} opts.values [values for the template]
* @param {Function} cb callback
* @return {Promise} promise
*/
Server.prototype.update = function (id, opts, cb) {
var self = this;
return new Promise(function (resolve, reject) {
(!cb || typeof cb !== 'function') && (cb = function (a, b) { if (a) { return reject(a); } resolve(b); });
if (!id) { return cb('id is missing'); }
var filePath = path.join(self.confDir, self.identifier + id + '.conf');
// verify existance
if (!fs.existsSync(filePath)) { return cb('vhost with id `' + id + '` does not exist'); }
opts.id = id;
self.delete(id).then(self.create(opts)).then(function (a) {
cb(null, a);
}, cb);
});
}

/**
* Delete a vhost
* @param {String} id unique identifier
* @param {Function} cb callback
* @return {Promise} promise
*/
Server.prototype.delete = function (id, cb) {
var self = this;
return new Promise(function (resolve, reject) {
(!cb || typeof cb !== 'function') && (cb = function (a, b) { if (a) { return reject(a); } resolve(b); });
if (!id) { return cb('id is missing'); }
var filePath = path.join(self.confDir, self.identifier + id + '.conf');
// verify existance
if (!fs.existsSync(filePath)) { return cb('vhost with id `' + id + '` does not exist'); }
fs.unlink(filePath, function (err) {
if (err) { return cb(err); }
cb(null);
});
});
}

module.exports = Server;
65 changes: 62 additions & 3 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ const
_ = require('lodash'),
glob = require('glob'),
exec = require('child_process').exec,
Promise = require('promise');
path = require('path'),
fs = require('fs'),
Promise = require('promise'),
Handlebars = require('handlebars');

var utils = module.exports;

const TEMPLATES_DIR = path.join(__dirname, '../templates');

/**
* Get files by glob patterns
*/
Expand All @@ -33,10 +38,64 @@ utils.getGlobbedFiles = function getGlobbedFiles(globPatterns) {
utils.zombieCommand = function zombieCommand(command, cb) {
var sudo = this.sudo || '';
return new Promise(function (resolve, reject) {
!cb && (cb = function (a, b) { if (a) { return reject(a); } resolve(b); });
(!cb || typeof cb !== 'function') && (cb = function (a, b) { if (a) { return reject(a); } resolve(b); });
exec(sudo + command, function (err, stdout, stderr) {
if (err || stderr) { return cb(err || stderr); }
cb(null, stdout.trim());
});
});
};
};

// register helpers
Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
switch (operator){
case "==":
return (v1==v2)?options.fn(this):options.inverse(this);
case "!=":
return (v1!=v2)?options.fn(this):options.inverse(this);
case "===":
return (v1===v2)?options.fn(this):options.inverse(this);
case "!==":
return (v1!==v2)?options.fn(this):options.inverse(this);
case "&&":
return (v1&&v2)?options.fn(this):options.inverse(this);
case "||":
return (v1||v2)?options.fn(this):options.inverse(this);
case "<":
return (v1<v2)?options.fn(this):options.inverse(this);
case "<=":
return (v1<=v2)?options.fn(this):options.inverse(this);
case ">":
return (v1>v2)?options.fn(this):options.inverse(this);
case ">=":
return (v1>=v2)?options.fn(this):options.inverse(this);
default:
return eval(""+v1+operator+v2)?options.fn(this):options.inverse(this)
}
});

utils.render = function (template, context) {
return new Promise(function (resolve, reject) {
var templatePath = null;
(function () {
var toLook = [template,
path.join(TEMPLATES_DIR, template),
path.join(TEMPLATES_DIR, template + '.ejs'),
path.join(TEMPLATES_DIR, template + '.conf'),
path.join(TEMPLATES_DIR, template + '.conf.ejs')];

for (var i = 0, l = toLook.length; i < l; ++i) {
if (fs.existsSync(toLook[i])) {
templatePath = toLook[i];
return;
}
}
})();
if (!templatePath) { return reject('Template not found'); }
// compile template
var tmplate = Handlebars.compile(String(fs.readFileSync(templatePath)));
// generate html
var html = tmplate(context);
resolve(html);
});
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nginx-o",
"version": "0.0.1",
"version": "0.0.2",
"description": "Programmatically manipulate a running Nginx instance",
"main": "lib/Nginx.js",
"scripts": {
Expand All @@ -13,6 +13,7 @@
"license": "ISC",
"dependencies": {
"glob": "^7.0.3",
"handlebars": "^4.0.5",
"lodash": "^4.6.1",
"procinfo": "^0.1.4",
"promise": "^7.1.1"
Expand Down
18 changes: 18 additions & 0 deletions templates/default.conf.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# clout service
server {
listen *:{{port}};

server_name {{cname}};

# access_log /var/log/nginx/{{cname}}.access.log;
# error_log /var/log/nginx/{{cname}}.error.log;

location / {
proxy_pass {{proxy_pass}};
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

0 comments on commit 41d01f8

Please sign in to comment.