Skip to content

Instantly share code, notes, and snippets.

@neckro
Created July 9, 2015 19:55
Show Gist options
  • Save neckro/6b4f92c89e5743e4a1a9 to your computer and use it in GitHub Desktop.
Save neckro/6b4f92c89e5743e4a1a9 to your computer and use it in GitHub Desktop.
Jenkins builder thing
var request = require('https').request;
var querystring = require('querystring');
// logging
var format = require('util').format;
var log = require('util').log;
var _ = require('lodash');
var Promise = require('bluebird');
var defaults = {
username: 'User.Name',
password: 'password',
server: 'jenkins.cdc.bestbuy.com',
newJobPollingInterval: 6, // in seconds
newJobMaxRetries: 10, // 60 seconds max
buildJobPollingInterval: 20, // in seconds
buildJobMaxRetries: 180, // one hour max
projects: []
};
var logf = function() {
return log(format.apply(format, arguments));
};
var Jenkins = module.exports = function Jenkins(config) {
if (!(this instanceof Jenkins)) return new Jenkins(config);
this.cfg = _.extend(defaults, config);
};
_.extend(Jenkins.prototype, {
build: function build(alias) {
var project = this.getProjectCfg(alias);
var lastBuild;
logf(
"Starting build. Alias: %s; Group: %s; Name: %s; Parameters: %s",
project.alias,
project.group,
project.name,
JSON.stringify(project.parameters)
);
return (
this.getLastBuildNumber(alias)
.bind(this)
.then(function(lastBuild) {
this.startBuild(alias);
log('Waiting for build to start');
return this.waitForNewBuild(alias, lastBuild);
})
.then(function(buildNumber) {
logf(
'Build number is %s: %s',
buildNumber,
this.getEndpointURL(alias, buildNumber + '/')
);
return this.waitForBuildCompletion(alias, buildNumber);
})
.then(function(build) {
var result = _.get(build, 'result', 'Unknown');
logf(
'Build of "%s" complete. Result: %s',
alias,
result
);
if (result === 'FAILURE') {
throw "Build failed!";
}
return build;
})
);
},
startBuild: function startBuild(alias) {
var project = this.getProjectCfg(alias);
var endpoint;
if (project.parameters) {
endpoint = 'buildWithParameters?' + querystring.stringify(project.parameters);
} else {
endpoint = 'build';
}
return (
this.apiRequest(alias, endpoint, {
method: 'POST'
})
);
},
waitForNewBuild: function waitForBuild(alias, lastBuildNumber) {
var self = this;
var retries = 0;
return new Promise(function(resolve, reject) {
var interval = setInterval(function() {
retries++;
if (retries > self.cfg.newJobMaxRetries) {
clearInterval(interval);
return reject("Max retries reached");
}
return (
self.getLastBuildNumber(alias)
.then(function(buildNumber) {
if (buildNumber > lastBuildNumber) {
clearInterval(interval);
resolve(buildNumber);
}
})
.caught(function(e) {
// Ignore any errors
})
);
}, self.cfg.newJobPollingInterval * 1000);
});
},
waitForBuildCompletion: function waitForBuildCompletion(alias, buildNumber) {
var self = this;
var retries = 0;
return new Promise(function(resolve, reject) {
var interval = setInterval(function() {
retries++;
if (retries > self.cfg.buildJobMaxRetries) {
clearInterval(interval);
return reject("Max retries reached");
}
return (
self.getBuildData(alias, buildNumber)
.then(function(buildData) {
if (_.get(buildData, 'building') === false) {
clearInterval(interval);
resolve(buildData);
}
})
.caught(function(e) {
// Ignore any errors
})
);
}, self.cfg.buildJobPollingInterval * 1000);
});
},
getLastBuildNumber: function getLastBuildNumber(alias) {
return (
this.getProjectData(alias)
.then(function(v) {
return _.get(v, 'builds[0].number', false);
})
);
},
getProjectData: function getProjectData(alias) {
return this.apiRequest(alias, 'api/json');
},
getBuildData: function getBuildData(alias, buildNumber) {
return this.apiRequest(alias, format('%s/api/json', buildNumber));
},
apiRequest: function apiRequest(alias, endpoint, requestParams) {
requestParams = _.extend({
auth: this.cfg.username + ':' + this.cfg.password,
hostname: this.cfg.server,
port: 443,
path: this.getEndpoint(alias, endpoint),
method: 'POST',
rejectUnauthorized: false
}, requestParams);
return new Promise(_.bind(function(resolve, reject) {
var req = request(requestParams);
req.on('response', function(res) {
// Check for HTTP error
var status = res.statusCode;
if (!_.contains([200, 201, 202, 204, 302], status)) {
logf("ERROR: Got status code: %s", status);
reject(status);
}
// Is there a better way to convert a stream to a string?
var output = '';
res.on('data', function (chunk) {
output += chunk;
});
res.on('error', function(e) {
logf("HTTP ERROR:", e);
});
res.on('end', function() {
if (_.isEmpty(output)) {
return resolve({});
}
try {
return resolve(JSON.parse(output));
} catch (e) {
reject(e);
}
});
});
req.end();
}, this));
},
getProjectCfg: function getProjectCfg(projectAlias) {
var retval = _.find(this.cfg.projects, { alias: projectAlias });
if (!retval) throw format("Can't find project: %s", projectAlias);
return retval;
},
getEndpoint: function getEndpoint(alias, endpoint) {
var project = this.getProjectCfg(alias);
var out = format(
'/jenkins/view/%s/job/%s/%s',
project.group,
project.name,
endpoint ? endpoint : ''
);
return out;
},
getEndpointURL: function getEndpointURL(alias, endpoint) {
return 'https://' + this.cfg.server + this.getEndpoint(alias, endpoint);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment