diff --git a/scripts/deploy/git.js b/scripts/deploy/git.js index 2a1b6ea0a6e6fbc3cd1e605f51f1f48fa5585d21..bb5a5978f9310e5682e8d883872624ec47945b49 100644 --- a/scripts/deploy/git.js +++ b/scripts/deploy/git.js @@ -80,7 +80,7 @@ exports.push = function(dir, host, pr, cb) { }; exports.pull = function(dir, remote, branch, pr, cb) { - var p = spawn('git', [ 'pull', "-f", remote, branch ], { cwd: dir }); + var p = spawn('git', [ 'pull', "-f", remote, branch + ":" + branch ], { cwd: dir }); p.stdout.on('data', function(c) { splitAndEmit(c, pr); }); p.stderr.on('data', function(c) { splitAndEmit(c, pr); }); diff --git a/scripts/deploy/vm.js b/scripts/deploy/vm.js index ae94b2a11691e3daaab585be16b0429a4f9dbee3..6c6e88bf6964b71525ae44ae23e7bd17051e944f 100644 --- a/scripts/deploy/vm.js +++ b/scripts/deploy/vm.js @@ -15,8 +15,12 @@ function extractInstanceDeets(horribleBlob) { var name = jsel.match('.tagSet :has(.key:val("Name")) > .value', horribleBlob); if (name.length) { instance.fullName = name[0]; - instance.name = name[0].replace('browserid deployment (', '') - .replace(/\)$/, ''); + // if this is a 'browserid deployment', we'll only display the hostname chosen by the + // user + var m = /^browserid deployment \((.*)\)$/.exec(instance.fullName); + instance.name = m ? m[1] : instance.fullName; + } else { + instance.name = instance.instanceId; } return instance; } diff --git a/scripts/deploy_server.js b/scripts/deploy_server.js index fc0e90bf391b5dd86c6a5ca35836f3aba8e90533..1b0935bb5937607db026e9c9042505456c8114f8 100755 --- a/scripts/deploy_server.js +++ b/scripts/deploy_server.js @@ -6,18 +6,21 @@ path = require('path'), util = require('util'), events = require('events'), git = require('./deploy/git.js'), -https = require('https'); +https = require('https'), +vm = require('./deploy/vm.js'), +jsel = require('JSONSelect'), +fs = require('fs'), +express = require('express'); // a class capable of deploying and emmitting events along the way function Deployer() { events.EventEmitter.call(this); // a directory where we'll keep code - this._codeDir = '/tmp/fuck'; // temp.mkdirSync(); + this._codeDir = process.env['CODE_DIR'] || temp.mkdirSync(); console.log("code dir is:", this._codeDir); var self = this; -/* git.init(this._codeDir, function(err) { if (err) { console.log("can't init code dir:", err); @@ -25,26 +28,19 @@ function Deployer() { } self.emit('ready'); }); -*/ - // a directory where we'll deployment logs - this._deployLogDir = temp.mkdirSync(); - console.log("deployment log dir is:", this._deployLogDir); - - process.nextTick(function() { - self.emit('ready'); - }); } util.inherits(Deployer, events.EventEmitter); Deployer.prototype._getLatestRunningSHA = function(cb) { + var self = this; + // failure is not fatal. maybe nothing is running? - function fail(err) { + var fail = function(err) { self.emit('info', { msg: "can't get current running sha", reason: err }); cb(null, null); } - var self = this; https.get({ host: 'dev.diresworb.org', path: '/ver.txt' }, function(res) { var buf = ""; res.on('data', function (c) { buf += c }); @@ -66,11 +62,31 @@ Deployer.prototype._getLatestRunningSHA = function(cb) { } -Deployer.prototype._cleanupOldVMs = function() { - this.emit('error', "not yet implemented"); +Deployer.prototype._cleanUpOldVMs = function() { + var self = this; + // what's our sha + git.currentSHA(self._codeDir, function(err, latest) { + if (err) return self.emit('info', err); + vm.list(function(err, r) { + if (err) return self.emit('info', err); + // only check the vms that have 'dev.diresworb.org' as a name + jsel.forEach("object:has(:root > .name:contains(?))", [ "dev.diresworb.org" ], r, function(o) { + // don't delete the current one + if (o.name.indexOf(latest) == -1) { + self.emit('info', 'decommissioning VM: ' + o.name + ' - ' + o.instanceId); + vm.destroy(o.name, function(err, r) { + if (err) self.emit('info', 'decomissioning failed: ' + err); + else self.emit('info', 'decomissioning succeeded of ' + r); + }) + } + }); + }); + }); } Deployer.prototype._deployNewCode = function(cb) { + var self = this; + function splitAndEmit(chunk) { if (chunk) chunk = chunk.toString(); if (typeof chunk === 'string') { @@ -81,24 +97,35 @@ Deployer.prototype._deployNewCode = function(cb) { } } - var p = spawn('scripts/deploy_dev.js', { cwd: self._codeDir }); + var npmInstall = spawn('npm', [ 'install' ], { cwd: self._codeDir }); + + npmInstall.stdout.on('data', splitAndEmit); + npmInstall.stderr.on('data', splitAndEmit); + + npmInstall.on('exit', function(code, signal) { + if (code != 0) { + self.emit('error', "can't npm install to prepare to run deploy_dev"); + return; + } + var p = spawn('scripts/deploy_dev.js', [], { cwd: self._codeDir }); - p.stdout.on('data', splitAndEmit); - p.stderr.on('data', splitAndEmit); + p.stdout.on('data', splitAndEmit); + p.stderr.on('data', splitAndEmit); - p.on('exit', function(code, signal) { - return cb(code = 0); + p.on('exit', function(code, signal) { + return cb(code != 0); + }); }); }; Deployer.prototype._pullLatest = function(cb) { var self = this; git.pull(this._codeDir, 'git@github.com:mozilla/browserid', 'dev', function(l) { - self.emit(l); + self.emit('progress', l); }, function(err) { - if (err) return self.emit('error', err); + if (err) return cb(err); git.currentSHA(self._codeDir, function(err, latest) { - if (err) return self.emit('error', err); + if (err) return cb(err); self.emit('info', 'latest available sha is ' + latest); self._getLatestRunningSHA(function(err, running) { if (latest != running) { @@ -108,14 +135,15 @@ Deployer.prototype._pullLatest = function(cb) { var startTime = new Date(); self._deployNewCode(function(err, res) { - if (err) return self.emit('error', err); + if (err) return cb(err); // deployment is complete! self.emit('deployment_complete', { sha: latest, time: (startTime - new Date()) }); // finally, let's clean up old servers - self._cleanUpOldVMS(); + self._cleanUpOldVMs(); + cb(null, null); }); } else { self.emit('info', 'up to date'); @@ -128,21 +156,83 @@ Deployer.prototype._pullLatest = function(cb) { // may be invoked any time we suspect updates have occured to re-deploy // if needed -Deployer.prototype.checkForUpdates = function(cb) { +Deployer.prototype.checkForUpdates = function() { var self = this; + + if (this._busy) return; + + this._busy = true; + self.emit('info', 'checking for updates'); + self._pullLatest(function(err, sha) { - if (!err) { - console.log(sha); - } + if (err) self.emit('error', err); + self._busy = false; }); } var deployer = new Deployer(); +var currentLogFile = null; +// a directory where we'll deployment logs +var deployLogDir = process.env['DEPLOY_LOG_DIR'] || temp.mkdirSync(); +console.log("deployment log dir is:", deployLogDir); + [ 'info', 'ready', 'error', 'deployment_begins', 'deployment_complete', 'progress' ].forEach(function(evName) { - deployer.on(evName, function(data) { console.log(evName + ":", data) }); + deployer.on(evName, function(data) { + if (typeof data != 'string') data = JSON.stringify(data, null, 2); + var msg = evName + ": " + data; + console.log(msg) + if (currentLogFile) currentLogFile.write(msg + "\n"); + }); +}); + +// now when deployment begins, we log all events +deployer.on('deployment_begins', function(r) { + currentLogFile = fs.createWriteStream(path.join(deployLogDir, r.sha + ".txt")); + currentLogFile.write("deployment of " + r.sha + " begins\n"); +}); + +function closeLogFile() { + if (currentLogFile) { + currentLogFile.end(); + currentLogFile = null; + } +} + +deployer.on('deployment_complete', function(r) { + closeLogFile(); + + // always check to see if we should try another deployment after one succeeds to handle rapid fire + // commits + deployer.checkForUpdates(); +}); + +deployer.on('error', function(r) { + closeLogFile(); + // on error, try again in 2 minutes + setTimeout(function () { + deployer.checkForUpdates(); + }, 2 * 60 * 1000); }); + +// we check every 30 minutes no mattah what. (checks are cheap) +setInterval(function () { + deployer.checkForUpdates(); +}, (1000 * 60 * 30)); + +// check for updates at startup deployer.on('ready', function() { deployer.checkForUpdates(); + + var app = express.createServer(); + + app.get('/check', function(req, res) { + deployer.checkForUpdates(); + res.send('ok'); + }); + + app.use(express.static(deployLogDir)); + + app.listen(process.env['PORT'] || 8080); });