Permalink
Browse files

Add jenkins#monitor and ninja command.

  • Loading branch information...
1 parent 2896a3a commit 6865f12c6c5482db12f4a36640e32f3d0b5f40ae @cliffano committed Apr 2, 2013
Showing with 256 additions and 16 deletions.
  1. +1 −0 CHANGELOG.md
  2. +6 −1 README.md
  3. +4 −1 conf/commands.json
  4. +29 −1 lib/cli.js
  5. +44 −4 lib/jenkins.js
  6. +2 −0 package.json
  7. +96 −1 test/cli.js
  8. +74 −8 test/jenkins.js
View
@@ -1,6 +1,7 @@
### 0.1.2-pre
* Move proxy environment variable handling to bag.http.request and bag.http.proxy
* Add feed command
+* Add jenkins#monitor and ninja command
### 0.1.1
* Move status colouring to cli so that when lib/jenkins is used programatically then it gets plain uncoloured text
View
@@ -63,12 +63,17 @@ Start an IRC bot:
View builds feed of all jobs:
- nestor feed
+ nestor feed
View builds feed of a job:
nestor feed <job>
+Monitor build status and notify Ninja Blocks RGB LED device:
+
+ export NINJABLOCKS_TOKEN=<token>
+ nestor ninja [job] ["cron_schedule"]
+
Programmatically:
var nestor = new (require('nestor'))(
View
@@ -37,7 +37,10 @@
"desc": "Start an IRC bot\nUsage: nestor irc <host> <channel> [nick]"
},
"feed": {
- "desc": "Retrieve Jenkins feed\nUsage: nestor feed [<job>]"
+ "desc": "Retrieve Jenkins feed\nUsage: nestor feed [job]"
+ },
+ "ninja": {
+ "desc": "Display latest build status on Ninja Blocks RGB LED\nUsage: NINJABLOCKS_TOKEN=<token> nestor ninja [job] [cron_schedule]"
}
}
}
View
@@ -11,6 +11,7 @@ var _ = require('underscore'),
colors = require('colors'),
irc = require('./irc'),
Jenkins = require('./jenkins'),
+ NinjaBlocks = require('./ninjablocks'),
jenkins = new Jenkins();
function _build(jobName, params, args) {
@@ -137,6 +138,32 @@ function _feed(jobName) {
}));
}
+function _ninja(jobName, schedule) {
+ if (!_.isString(jobName) || !_.isString(schedule)) {
+ // no arg specified
+ if (!_.isString(jobName) && !_.isString(schedule)) {
+ jobName = null;
+ schedule = null;
+ // one arg specified, cron schedule
+ } else if (!_.isString(schedule) && jobName.match(/^.+\s.+\s.+\s.+\s.+/)) {
+ schedule = jobName;
+ jobName = null;
+ // one arg specified, job name
+ } else {
+ schedule = null;
+ }
+ }
+
+ var ninjaBlocks = new NinjaBlocks(process.env.NINJABLOCKS_TOKEN);
+ jenkins.monitor(jobName, schedule, function (err, result) {
+ if (err) {
+ console.error(err.message);
+ } else {
+ ninjaBlocks.notify(result);
+ }
+ });
+}
+
/**
* Execute Nestor CLI.
*/
@@ -154,7 +181,8 @@ function exec() {
queue: { action: _queue },
ver: { action: _version },
irc: { action: _irc },
- feed: { action: _feed }
+ feed: { action: _feed },
+ ninja: { action: _ninja }
}
};
View
@@ -2,6 +2,7 @@
var _ = require('underscore'),
async = require('async'),
bag = require('bagofholding'),
+ cron = require('cron'),
dgram = require('dgram'),
feedparser = require('feedparser'),
request = require('request'),
@@ -178,7 +179,7 @@ Jenkins.prototype.dashboard = function (cb) {
data = [];
if (!_.isEmpty(jobs)) {
jobs.forEach(function (job) {
- data.push({ status: self._status(job.color), name: job.name });
+ data.push({ status: self._colorStatus(job.color), name: job.name });
});
}
cb(null, data);
@@ -269,7 +270,7 @@ Jenkins.prototype.job = function (name, cb) {
function _success(result, cb) {
var _job = JSON.parse(result.body),
data = {};
- data.status = self._status(_job.color);
+ data.status = self._colorStatus(_job.color);
data.reports = [];
_job.healthReport.forEach(function (report) {
data.reports.push(report.description);
@@ -335,7 +336,7 @@ Jenkins.prototype.version = function (cb) {
/**
* Retrieve Jenkins feed or job feed.
*
- * @param {String} jobName: Jenkins job name
+ * @param {String} jobName: Jenkins job name, unspecified means all jobs
* @param {Function} cb: standard cb(err, result) callback
*/
Jenkins.prototype.feed = function (jobName, cb) {
@@ -351,7 +352,27 @@ Jenkins.prototype.feed = function (jobName, cb) {
});
};
-Jenkins.prototype._status = function (color) {
+/**
+ * Monitor Jenkins latest build status on a set interval.
+ *
+ * @param {String} jobName: Jenkins job name, unspecified means all jobs
+ * @param {Object} schedule: cron scheduling definition in standard * * * * * * format
+ * @param {Function} cb: standard cb(err, result) callback
+ */
+Jenkins.prototype.monitor = function (jobName, schedule, cb) {
+ var self = this;
+
+ function _notify() {
+ self.feed(jobName, function (err, result) {
+ cb(err, !_.isEmpty(result) ? self._titleStatus(result[0].title) : null);
+ });
+ }
+
+ _notify();
+ new cron.CronJob(schedule || '0 * * * * *', _notify).start();
+};
+
+Jenkins.prototype._colorStatus = function (color) {
const STATUS = {
blue: 'OK',
@@ -369,4 +390,23 @@ Jenkins.prototype._status = function (color) {
return (STATUS[color]) || color.toUpperCase();
};
+Jenkins.prototype._titleStatus = function (title) {
+
+ title = String(title.match('[(].+[)]$')).replace(/[(]/, '').replace(/[)]/, '').toLowerCase();
+
+ var status;
+ // deriving status from feed article title is not the best solution but
+ // unfortunately feed does not provide any status info
+ if (title === 'stable' || title === 'back to normal') {
+ status = 'OK';
+ } else if (title.match(/^broken.*/) || title.match(/failures?$/)) {
+ status = 'FAIL';
+ } else if (title.match(/.*failing.*/) || title.match(/fail$/)) {
+ status = 'WARN';
+ } else {
+ status = title.toUpperCase();
+ }
+ return status;
+};
+
module.exports = Jenkins;
View
@@ -33,8 +33,10 @@
"async": "0.2.6",
"bagofholding": "0.1.2",
"colors": "0.6.0-1",
+ "cron": "1.0.1",
"feedparser": "0.14.0",
"irc": "0.3.6",
+ "ninja-blocks": "0.1.3",
"request": "2.16.6",
"underscore": "1.4.4",
"xml2js": "0.2.6"
View
@@ -2,7 +2,8 @@ var bag = require('bagofholding'),
buster = require('buster'),
cli = require('../lib/cli'),
irc = require('../lib/irc'),
- Jenkins = new require('../lib/jenkins');
+ Jenkins = new require('../lib/jenkins'),
+ NinjaBlocks = require('../lib/ninjablocks');
buster.testCase('cli - exec', {
'should contain commands with actions': function (done) {
@@ -19,6 +20,7 @@ buster.testCase('cli - exec', {
assert.defined(actions.commands.ver.action);
assert.defined(actions.commands.irc.action);
assert.defined(actions.commands.feed.action);
+ assert.defined(actions.commands.ninja.action);
done();
};
this.stub(bag, 'cli', { command: mockCommand });
@@ -515,3 +517,96 @@ buster.testCase('cli - feed', {
cli.exec();
}
});
+
+buster.testCase('cli - ninja', {
+ setUp: function () {
+ this.mockConsole = this.mock(console);
+ },
+ 'should log monitoring error': function () {
+ this.stub(bag, 'cli', {
+ command: function (base, actions) {
+ actions.commands.ninja.action('somejob', '* * * * * *');
+ },
+ exitCb: bag.cli.exitCb
+ });
+ this.mockConsole.expects('error').once().withExactArgs('some error');
+ this.stub(Jenkins.prototype, 'monitor', function (jobName, schedule, cb) {
+ assert.equals(jobName, 'somejob');
+ assert.equals(schedule, '* * * * * *');
+ cb(new Error('some error'));
+ });
+ cli.exec();
+ },
+ 'should notify ninjablocks when there is no monitoring error': function (done) {
+ this.stub(bag, 'cli', {
+ command: function (base, actions) {
+ actions.commands.ninja.action('somejob', '* * * * * *');
+ },
+ exitCb: bag.cli.exitCb
+ });
+ this.stub(Jenkins.prototype, 'monitor', function (jobName, schedule, cb) {
+ assert.equals(jobName, 'somejob');
+ assert.equals(schedule, '* * * * * *');
+ cb(null, 'OK');
+ });
+ this.stub(NinjaBlocks.prototype, 'notify', function (result) {
+ assert.equals(result, 'OK');
+ done();
+ });
+ cli.exec();
+ },
+ 'should set job and schedule to null when there is no string argument': function (done) {
+ this.stub(bag, 'cli', {
+ command: function (base, actions) {
+ actions.commands.ninja.action({});
+ },
+ exitCb: bag.cli.exitCb
+ });
+ this.stub(Jenkins.prototype, 'monitor', function (jobName, schedule, cb) {
+ assert.isNull(jobName);
+ assert.isNull(schedule);
+ cb(null, 'OK');
+ });
+ this.stub(NinjaBlocks.prototype, 'notify', function (result) {
+ assert.equals(result, 'OK');
+ done();
+ });
+ cli.exec();
+ },
+ 'should set first arg as schedule and job name as null when first arg matches cron schedule format': function (done) {
+ this.stub(bag, 'cli', {
+ command: function (base, actions) {
+ actions.commands.ninja.action('*/30 * * * * *');
+ },
+ exitCb: bag.cli.exitCb
+ });
+ this.stub(Jenkins.prototype, 'monitor', function (jobName, schedule, cb) {
+ assert.isNull(jobName);
+ assert.equals(schedule, '*/30 * * * * *');
+ cb(null, 'OK');
+ });
+ this.stub(NinjaBlocks.prototype, 'notify', function (result) {
+ assert.equals(result, 'OK');
+ done();
+ });
+ cli.exec();
+ },
+ 'should set first arg as job name and schedule to null when first arg does not match cron schedule format': function (done) {
+ this.stub(bag, 'cli', {
+ command: function (base, actions) {
+ actions.commands.ninja.action('somejob');
+ },
+ exitCb: bag.cli.exitCb
+ });
+ this.stub(Jenkins.prototype, 'monitor', function (jobName, schedule, cb) {
+ assert.equals(jobName, 'somejob');
+ assert.isNull(schedule);
+ cb(null, 'OK');
+ });
+ this.stub(NinjaBlocks.prototype, 'notify', function (result) {
+ assert.equals(result, 'OK');
+ done();
+ });
+ cli.exec();
+ }
+});
Oops, something went wrong.

0 comments on commit 6865f12

Please sign in to comment.