Skip to content

Commit

Permalink
Refactored last command to new api and cli modules.
Browse files Browse the repository at this point in the history
  • Loading branch information
cliffano committed Feb 1, 2015
1 parent 644ba5d commit 330d3d4
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 180 deletions.
15 changes: 15 additions & 0 deletions lib/api/job.js
Expand Up @@ -40,6 +40,20 @@ function read(name, cb) {
req.request('get', this.url + '/job/' + name + '/api/json', this.opts, cb);
}

/**
* Retrieve information about the latest build of a job.
*
* @param {String} name: Jenkins job name
* @param {Function} cb: standard cb(err, result) callback
*/
function readLatest(name, cb) {

this.opts.handlers[200] = util.passThroughSuccessJson;
this.opts.handlers[404] = util.jobNotFoundError(name);

req.request('get', this.url + '/job/' + name + '/lastBuild/api/json', this.opts, cb);
}

/**
* Update a job with specified configuration
*
Expand Down Expand Up @@ -255,6 +269,7 @@ function parseFeed(name, cb) {

exports.create = create;
exports.read = read;
exports.readLatest = readLatest;
exports.update = update;
exports.delete = _delete;
exports.build = build;
Expand Down
13 changes: 1 addition & 12 deletions lib/cli.js
Expand Up @@ -41,25 +41,13 @@ function __exec(args, cb) {
}
}

function _last(name, args) {
function execCb(jenkins) {
jenkins.last(name, cli.exitCb(null, function (result) {
var resultColor = [COLORS[result.result] || 'grey'];
console.log('%s | %s', name, result.building ? "BUILDING".yellow : result.result[resultColor]);
console.log(' - %s [%s]', result.buildDate, result.buildDateDistance);
}));
}
__exec(args, execCb);
}

/**
* Execute Nestor CLI.
*/
function exec() {

var actions = {
commands: {
'last': { action: _last },
'dashboard' : { action: jenkins.dashboard(__exec) },
'discover' : { action: jenkins.discover(__exec) },
'executor' : { action: jenkins.executor(__exec) },
Expand All @@ -70,6 +58,7 @@ function exec() {
'create-job' : { action: job.create(__exec) },
'job' : { action: job.read(__exec) },
'read-job' : { action: job.read(__exec) },
'last' : { action: job.readLatest(__exec) },
'update' : { action: job.update(__exec) },
'update-job' : { action: job.update(__exec) },
'delete' : { action: job.delete(__exec) },
Expand Down
47 changes: 43 additions & 4 deletions lib/cli/job.js
@@ -1,7 +1,9 @@
var cli = require('bagofcli');
var fs = require('fs');
var text = require('bagoftext');
var util = require('./util');
var cli = require('bagofcli');
var fs = require('fs');
var moment = require('moment');
var text = require('bagoftext');
var util = require('./util');
var _util = require('util');

/**
* Get a handler that calls Jenkins API to create a job with specific configuration.
Expand Down Expand Up @@ -47,6 +49,42 @@ function read(cb) {
};
}

/**
* Get a handler that calls Jenkins API to retrieve information about the latest build of a job.
*
* @param {Function} cb: callback for argument handling
* @return Jenkins API handler function
*/
function readLatest(cb) {
return function (name, args) {

function resultCb(result) {

var status;
var color;
var description;

if (result.building) {
status = 'building';
color = util.colorByStatus(status);
description = _util.format('Started %s', moment(result.timestamp).fromNow());
} else {
status = result.result.toLowerCase();
color = util.colorByStatus(status);
description = _util.format('Finished %s', moment(result.timestamp + result.duration).fromNow());
}

console.log('%s | %s', name, text.__(status)[color]);
console.log(' - %s', description);
}

function jenkinsCb(jenkins) {
jenkins.readLatestJob(name, cli.exitCb(null, resultCb));
}
cb(args, jenkinsCb);
};
}

/**
* Get a handler that calls Jenkins API to update a job with specific configuration.
* Success job update message will be logged when there's no error.
Expand Down Expand Up @@ -255,6 +293,7 @@ function fetchConfig(cb) {

exports.create = create;
exports.read = read;
exports.readLatest = readLatest;
exports.update = update;
exports.delete = _delete;
exports.build = build;
Expand Down
41 changes: 30 additions & 11 deletions lib/cli/util.js
@@ -1,13 +1,18 @@
var _ = require('lodash');

const COLOR_STATUS = {
blue : 'ok',
green : 'ok',
grey : 'aborted',
red : 'fail',
yellow: 'warn'
blue : [ 'ok' ],
green : [ 'ok', 'success' ],
grey : [ 'aborted' ],
red : [ 'fail', 'failure' ],
yellow: [ 'warn', 'building' ]
};

/**
* Get color based on status and Jenkins status color.
* Jenkins status color will take precedence over status because
* Jenkins uses either blue or green as the color for ok status.
*
* Color property can contain either color or status text.
* Hence the need for mapping color to status text in the case where value is really color.
* This is used in conjunction with statusByColor function.
Expand All @@ -18,17 +23,31 @@ const COLOR_STATUS = {
*/
function colorByStatus(status, jenkinsColor) {

// Jenkins color value can contain either a color, color_anime, or status in job.color field,
// hence to get color/status value out of the mix we need to remove the postfix _anime,
// _anime postfix only exists on a job currently being built
jenkinsColor = jenkinsColor.replace(/_anime$/, '');
var color;

if (jenkinsColor) {
// Jenkins color value can contain either a color, color_anime, or status in job.color field,
// hence to get color/status value out of the mix we need to remove the postfix _anime,
// _anime postfix only exists on a job currently being built
jenkinsColor = jenkinsColor.replace(/_anime$/, '');
color = (COLOR_STATUS[jenkinsColor] && COLOR_STATUS[jenkinsColor][0]) ? jenkinsColor : 'grey';
} else {
color = 'grey';
_.keys(COLOR_STATUS).forEach(function (_color) {
COLOR_STATUS[_color].forEach(function (_status) {
if (_status === status) {
color = _color;
}
});
});
}

var color = (COLOR_STATUS[jenkinsColor]) ? jenkinsColor : 'grey';
return color;
}

/**
* Get status based on the value of color property.
*
* Color property can contain either color or status text.
* Hence the need for mapping color to status text in the case where value is really color.
* This is used in conjunction with colorByStatus function.
Expand All @@ -43,7 +62,7 @@ function statusByColor(jenkinsColor) {
// _anime postfix only exists on a job currently being built
jenkinsColor = jenkinsColor.replace(/_anime$/, '');

var status = COLOR_STATUS[jenkinsColor] || jenkinsColor;
var status = (COLOR_STATUS[jenkinsColor]) ? COLOR_STATUS[jenkinsColor][0] : jenkinsColor;
return status;
}

Expand Down
41 changes: 1 addition & 40 deletions lib/jenkins.js
Expand Up @@ -43,46 +43,6 @@ function Jenkins(url) {
};
}

/**
* Retrieve status and date for the most recently completed build.
*
* @param {String} name: Jenkins job name
* @param {Function} cb: standard cb(err, result) callback
*/
Jenkins.prototype.last = function (name, cb) {

function _success(result, cb) {
var _build = JSON.parse(result.body),
buildMoment,
data = {
building: _build.building,
result: _build.result
};

if (_build.building > 0) {
buildMoment = moment(_build.timestamp);
data.buildDateDistance = "Started " + buildMoment.fromNow();

} else {
buildMoment = moment(_build.timestamp + _build.duration);
data.buildDateDistance = "Ended " + buildMoment.fromNow();
}

data.buildDate = buildMoment.toISOString();

cb(null, data);
}

function _notFound(result, cb) {
cb(new Error(text.__('No build could be found for job %s', name)));
}

this.opts.handlers[200] = _success;
this.opts.handlers[404] = _notFound;

req.request('get', this.url + '/job/' + name + '/lastBuild/api/json', this.opts, cb);
};

/**
* Summarise executor information from computers array.
*
Expand Down Expand Up @@ -183,6 +143,7 @@ Jenkins.prototype.version = jenkins.readVersion;

Jenkins.prototype.createJob = job.create;
Jenkins.prototype.readJob = job.read;
Jenkins.prototype.readLatestJob = job.readLatest;
Jenkins.prototype.updateJob = job.update;
Jenkins.prototype.deleteJob = job.delete;
Jenkins.prototype.buildJob = job.build;
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -45,6 +45,7 @@
},
"devDependencies": {
"buster-node": "~0.7.0",
"proxyquire": "~1.3.1",
"referee": "~1.0.1"
},
"scripts": {},
Expand Down
10 changes: 10 additions & 0 deletions test/api/job.js
Expand Up @@ -41,6 +41,16 @@ buster.testCase('api - job', {
});
job.read('somejob', done);
},
'readLatest - should send request to API endpoint': function (done) {
this.stub(req, 'request', function (method, url, opts, cb) {
assert.equals(method, 'get');
assert.equals(url, 'http://localhost:8080/job/somejob/lastBuild/api/json');
assert.defined(opts.handlers[200]);
assert.defined(opts.handlers[404]);
cb();
});
job.readLatest('somejob', done);
},
'update - should send request to API endpoint': function (done) {
this.stub(req, 'request', function (method, url, opts, cb) {
assert.equals(method, 'post');
Expand Down
94 changes: 47 additions & 47 deletions test/cli.js
Expand Up @@ -38,50 +38,50 @@ buster.testCase('cli - exec', {
}
});

buster.testCase('cli - last', {
setUp: function () {
this.mockConsole = this.mock(console);
this.mockProcess = this.mock(process);
this.stub(_cli, 'command', function (base, actions) {
actions.commands.last.action('job1');
});
},
'should log job name, build status and build date when job exists': function () {
this.mockConsole.expects('log').once().withExactArgs('%s | %s', 'job1', 'SUCCESS'.green);
this.mockConsole.expects('log').once().withExactArgs(' - %s [%s]', 'My date', 'My distance');
this.mockProcess.expects('exit').once().withExactArgs(0);
this.stub(Jenkins.prototype, 'last', function (name, cb) {
assert.equals(name, 'job1');
cb(null, {
buildDate: "My date",
buildDateDistance: "My distance",
building: false,
result: "SUCCESS"
});
});
cli.exec();
},
'should log status as BUILDING if job is currently being built': function () {
this.mockConsole.expects('log').once().withExactArgs('%s | %s', 'job1', 'BUILDING'.yellow);
this.mockConsole.expects('log').atLeast(1);
this.mockProcess.expects('exit').once().withExactArgs(0);
this.stub(Jenkins.prototype, 'last', function (name, cb) {
assert.equals(name, 'job1');
cb(null, {
buildDate: "My date",
buildDateDistance: "My distance",
building: true
});
});
cli.exec();
},
'should log not found error when build does not exist': function () {
this.mockConsole.expects('error').once().withExactArgs('someerror'.red);
this.mockProcess.expects('exit').once().withExactArgs(1);
this.stub(Jenkins.prototype, 'last', function (name, cb) {
assert.equals(name, 'job1');
cb(new Error('someerror'));
});
cli.exec();
}
});
// buster.testCase('cli - last', {
// setUp: function () {
// this.mockConsole = this.mock(console);
// this.mockProcess = this.mock(process);
// this.stub(_cli, 'command', function (base, actions) {
// actions.commands.last.action('job1');
// });
// },
// 'should log job name, build status and build date when job exists': function () {
// this.mockConsole.expects('log').once().withExactArgs('%s | %s', 'job1', 'SUCCESS'.green);
// this.mockConsole.expects('log').once().withExactArgs(' - %s [%s]', 'My date', 'My distance');
// this.mockProcess.expects('exit').once().withExactArgs(0);
// this.stub(Jenkins.prototype, 'last', function (name, cb) {
// assert.equals(name, 'job1');
// cb(null, {
// buildDate: "My date",
// buildDateDistance: "My distance",
// building: false,
// result: "SUCCESS"
// });
// });
// cli.exec();
// },
// 'should log status as BUILDING if job is currently being built': function () {
// this.mockConsole.expects('log').once().withExactArgs('%s | %s', 'job1', 'BUILDING'.yellow);
// this.mockConsole.expects('log').atLeast(1);
// this.mockProcess.expects('exit').once().withExactArgs(0);
// this.stub(Jenkins.prototype, 'last', function (name, cb) {
// assert.equals(name, 'job1');
// cb(null, {
// buildDate: "My date",
// buildDateDistance: "My distance",
// building: true
// });
// });
// cli.exec();
// },
// 'should log not found error when build does not exist': function () {
// this.mockConsole.expects('error').once().withExactArgs('someerror'.red);
// this.mockProcess.expects('exit').once().withExactArgs(1);
// this.stub(Jenkins.prototype, 'last', function (name, cb) {
// assert.equals(name, 'job1');
// cb(new Error('someerror'));
// });
// cli.exec();
// }
// });

0 comments on commit 330d3d4

Please sign in to comment.