This repository has been archived by the owner on Aug 22, 2019. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #157 from mozilla/156-continuous-devployment
156 continuous devployment
- Loading branch information
Showing
8 changed files
with
268 additions
and
209 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
#! /usr/bin/env node | ||
|
||
var util = require('util'); | ||
var http = require('http'); | ||
var program = require('commander'); | ||
var colors = require('colors'); | ||
var path = require('path'); | ||
var spawn = require('child_process').spawn; | ||
|
||
function inspect (thing) { | ||
return util.inspect(thing, undefined, undefined, true); | ||
} | ||
function debug() { | ||
var args = [].slice.call(arguments); | ||
args.unshift('debug'.grey); | ||
console.log.apply(console, args); | ||
} | ||
function error() { | ||
var args = [].slice.call(arguments); | ||
args.unshift(' error'.red); | ||
console.log(); | ||
console.log.apply(console, args); | ||
console.log(); | ||
} | ||
function accept() { | ||
var args = [].slice.call(arguments); | ||
args.unshift('accept'.green); | ||
console.log.apply(console, args); | ||
} | ||
var log = console.log; | ||
|
||
program | ||
.version('0.0.1') | ||
.option('-p, --port <n>', 'port the webhook endpoint should listen on') | ||
.option('-a, --auth <value>', 'authorization key to expect in the header') | ||
.option('-b, --branch <name>', 'branch to watch') | ||
.option('-e, --script <file>', 'file to execute on a successful test run') | ||
.parse(process.argv); | ||
|
||
if (program.port === undefined) { | ||
program.missingArgument('--port'); | ||
} | ||
if (!program.auth) { | ||
program.missingArgument('--auth'); | ||
} | ||
if (!program.script) { | ||
program.missingArgument('--script'); | ||
} | ||
if (!program.branch) { | ||
program.missingArgument('--branch'); | ||
} | ||
if (!path.existsSync(program.script)) { | ||
error('could not find file', program.script.bold); | ||
process.exit(1); | ||
} | ||
|
||
debug('listening on port', program.port.toString().blue); | ||
debug('authorization code', program.auth.toString().blue); | ||
|
||
var lastRunData; | ||
function requestHandler(request, response) { | ||
var auth; | ||
var length; | ||
var headers = request.headers; | ||
|
||
function reject() { | ||
var args = [].slice.call(arguments); | ||
// immediately end the response | ||
response.end('rejected: ' + args.join(' ')); | ||
args.unshift('reject'.red); | ||
console.log.apply(console, args); | ||
} | ||
|
||
if (request.method.match(/get/i)) { | ||
response.setHeader('content-type', 'text/plain; charset=utf-8'); | ||
if (!lastRunData) | ||
return response.end('nothing has been run yet'); | ||
return response.end(lastRunData); | ||
} | ||
|
||
if (!request.method.match(/post/i)) | ||
return reject('got request, but not a POST'); | ||
|
||
if (!(auth = headers['authorization'])) | ||
return reject('missing authorization code'); | ||
|
||
if (auth !== program.auth) | ||
return reject('invalid authorization code, got', auth.bold); | ||
|
||
if (!(length = headers['content-length'])) | ||
return reject('expects a `content-length` header'); | ||
|
||
function parseIncoming(data) { | ||
try { | ||
// data comes in as `payload=...`, so we want to slice that first part off and decode it. | ||
var payload = decodeURIComponent(data.slice(8)); | ||
var hookData = JSON.parse(payload); | ||
return hookData; | ||
} catch (e) { | ||
// well, just don't do anything I guess | ||
return; | ||
} | ||
} | ||
|
||
function executeScript(payload) { | ||
var message = payload.status_message; | ||
var status = payload.status; | ||
var branch = payload.branch; | ||
var lastChar = '\n'; | ||
|
||
if (!message || status === undefined || !branch) { | ||
reject('the payload didn\'t have the expected fields'); | ||
return debug(inspect(payload)) | ||
} | ||
|
||
if (branch !== program.branch) | ||
return reject('ignoring, branch doesn\'t match'); | ||
|
||
if (!message.match(/passed/i) || status !== 0) | ||
return reject('the tests failed, not deploying.'); | ||
|
||
lastRunData = ''; | ||
function echoScript(data) { | ||
var dataString = data.toString(); | ||
if (lastChar === '\n') process.stdin.write(program.script.magenta + ' '); | ||
process.stdin.write(data); | ||
lastRunData += dataString; | ||
lastChar = dataString[dataString.length - 1]; | ||
} | ||
|
||
var script = spawn('./' + program.script); | ||
script.stdout.on('data', echoScript); | ||
script.stderr.on('data', echoScript); | ||
script.on('exit', function (code) { | ||
if (code === 0) return accept('script successfully completed'); | ||
reject('something went wrong the the deploy script'); | ||
}); | ||
return true; | ||
} | ||
|
||
var incoming = ''; | ||
// buffer incoming data; | ||
// TODO: might be more memory friendly to use a real buffer instead? | ||
request.on('data', function (data) { incoming += data }); | ||
request.on('end', function () { | ||
var payload; | ||
if (!(payload = parseIncoming(incoming))) { | ||
reject('the payload was malformed'); | ||
return debug(inspect(incoming)); | ||
} | ||
debug('successful payload recieved, executing script'); | ||
if (executeScript(payload)) | ||
response.end('ok'); | ||
}); | ||
}; | ||
|
||
var server = http.createServer(); | ||
server.on('request', requestHandler); | ||
server.listen(program.port); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#!/usr/bin/env node | ||
var spawn = require('child_process').spawn; | ||
var colors = require('colors'); | ||
var http = require('http'); | ||
var program = require('commander'); | ||
var qs = require('querystring'); | ||
var fs = require('fs'); | ||
var util = require('util'); | ||
|
||
program | ||
.version('0.5.0') | ||
.option('-e, --env [env]', 'run under specified environment') | ||
.option('-f, --pidfile [file]', 'save a pidfile') | ||
.parse(process.argv); | ||
|
||
var restarts = 0; | ||
var runningServer; | ||
|
||
function log () { | ||
var args = [].slice.call(arguments); | ||
args.unshift('runner'.magenta); | ||
console.log.apply(console, args); | ||
}; | ||
|
||
function inspect (thing) { | ||
return util.inspect(thing, undefined, undefined, true); | ||
} | ||
|
||
var spawnServer = function () { | ||
var app, fancypid; | ||
if (++restarts > 5) { | ||
log('too many failures, shut. down. everything.'); | ||
process.exit(); | ||
} | ||
|
||
log('spawning new server'); | ||
app = spawn('node', ['app.js']); | ||
fancypid = ('(' + app.pid + ') ').grey; | ||
|
||
app.stdout.on('data', function (data) { | ||
process.stdout.write(fancypid); | ||
process.stdout.write(data); | ||
}); | ||
|
||
app.stderr.on('data', function (data) { | ||
process.stderr.write(fancypid); | ||
process.stderr.write(data); | ||
}); | ||
|
||
app.on('exit', function (code, sig) { | ||
if (sig) log('server killed with signal ' + sig); | ||
if (code) log('server exited with code ' + code); | ||
runningServer = spawnServer(); | ||
}); | ||
|
||
return app; | ||
}; | ||
|
||
process.on('SIGHUP', function () { | ||
// crash the server | ||
runningServer.kill(); | ||
}) | ||
; | ||
process.on('SIGINT', (function (timeout) { | ||
var seriously = 0; | ||
return function () { | ||
if (seriously) process.exit(); | ||
seriously = true; | ||
setTimeout(function () { | ||
seriously = false | ||
}, timeout); | ||
} | ||
})(1000)); | ||
|
||
process.on('exit', function () { | ||
if (pidfile) { | ||
log('destroying pidfile', pidfile.bold); | ||
try { | ||
fs.unlinkSync(pidfile); | ||
} catch (err) { | ||
console.log('couldn\'t destroy pidfile:', inspect(err)); | ||
} | ||
} | ||
}); | ||
|
||
var pidfile = program.pidfile; | ||
if (program.env) | ||
process.env['NODE_ENV'] = program.env; | ||
|
||
if (pidfile) { | ||
log('saving pid to', pidfile.bold); | ||
fs.writeFileSync(pidfile, process.pid.toString()) | ||
} | ||
|
||
log('runner pid is', process.pid.toString().bold); | ||
runningServer = spawnServer(); | ||
|
||
// reduce restart count every 5 seconds | ||
setInterval(function () { | ||
if (restarts > 0) restarts--; | ||
}, 5000); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/bash | ||
git pull origin development && \ | ||
npm install && \ | ||
npm test && \ | ||
kill -s SIGHUP `cat var/server.pid` | ||
|
Oops, something went wrong.