diff --git a/scripts/deploy/git.js b/scripts/deploy/git.js index 757e6678ff1046abc464df1f5a38197f01c1590a..2a1b6ea0a6e6fbc3cd1e605f51f1f48fa5585d21 100644 --- a/scripts/deploy/git.js +++ b/scripts/deploy/git.js @@ -53,6 +53,16 @@ exports.currentSHA = function(dir, cb) { }); }; +function splitAndEmit(chunk, cb) { + if (chunk) chunk = chunk.toString(); + if (typeof chunk === 'string') { + chunk.split('\n').forEach(function (line) { + line = line.trim(); + if (line.length) cb(line); + }); + } +} + exports.push = function(dir, host, pr, cb) { if (typeof host === 'function' && cb === undefined) { cb = pr; @@ -62,9 +72,27 @@ exports.push = function(dir, host, pr, cb) { } var p = spawn('git', [ 'push', 'app@' + host + ":git", 'dev:master' ], { cwd: dir }); - p.stdout.on('data', pr); - p.stderr.on('data', pr); + p.stdout.on('data', function(c) { splitAndEmit(c, pr); }); + p.stderr.on('data', function(c) { splitAndEmit(c, pr); }); + p.on('exit', function(code, signal) { + return cb(code = 0); + }); +}; + +exports.pull = function(dir, remote, branch, pr, cb) { + var p = spawn('git', [ 'pull', "-f", remote, branch ], { cwd: dir }); + + p.stdout.on('data', function(c) { splitAndEmit(c, pr); }); + p.stderr.on('data', function(c) { splitAndEmit(c, pr); }); + p.on('exit', function(code, signal) { return cb(code = 0); }); -}; \ No newline at end of file +} + +exports.init = function(dir, cb) { + var p = spawn('git', [ 'init' ], { cwd: dir }); + p.on('exit', function(code, signal) { + return cb(code = 0); + }); +}; diff --git a/scripts/deploy_server.js b/scripts/deploy_server.js new file mode 100755 index 0000000000000000000000000000000000000000..fc0e90bf391b5dd86c6a5ca35836f3aba8e90533 --- /dev/null +++ b/scripts/deploy_server.js @@ -0,0 +1,148 @@ +#!/usr/bin/env node + +const +temp = require('temp'), +path = require('path'), +util = require('util'), +events = require('events'), +git = require('./deploy/git.js'), +https = require('https'); + +// 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(); + 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); + process.exit(1); + } + 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) { + // failure is not fatal. maybe nothing is running? + function fail(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 }); + res.on('end', function() { + try { + var sha = buf.split(' ')[0]; + if (sha.length == 7) { + self.emit('info', 'latest running is ' + sha); + return cb(null, sha); + } + fail('malformed ver.txt: ' + buf); + } catch(e) { + fail(e); + } + }); + }).on('error', function(err) { + fail(err); + }); + +} + +Deployer.prototype._cleanupOldVMs = function() { + this.emit('error', "not yet implemented"); +} + +Deployer.prototype._deployNewCode = function(cb) { + function splitAndEmit(chunk) { + if (chunk) chunk = chunk.toString(); + if (typeof chunk === 'string') { + chunk.split('\n').forEach(function (line) { + line = line.trim(); + if (line.length) self.emit('progress', line); + }); + } + } + + var p = spawn('scripts/deploy_dev.js', { cwd: self._codeDir }); + + p.stdout.on('data', splitAndEmit); + p.stderr.on('data', splitAndEmit); + + 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); + }, function(err) { + if (err) return self.emit('error', err); + git.currentSHA(self._codeDir, function(err, latest) { + if (err) return self.emit('error', err); + self.emit('info', 'latest available sha is ' + latest); + self._getLatestRunningSHA(function(err, running) { + if (latest != running) { + self.emit('deployment_begins', { + sha: latest, + }); + var startTime = new Date(); + + self._deployNewCode(function(err, res) { + if (err) return self.emit('error', err); + // deployment is complete! + self.emit('deployment_complete', { + sha: latest, + time: (startTime - new Date()) + }); + // finally, let's clean up old servers + self._cleanUpOldVMS(); + }); + } else { + self.emit('info', 'up to date'); + cb(null, null); + } + }); + }); + }); +} + +// may be invoked any time we suspect updates have occured to re-deploy +// if needed +Deployer.prototype.checkForUpdates = function(cb) { + var self = this; + self._pullLatest(function(err, sha) { + if (!err) { + console.log(sha); + } + }); +} + +var deployer = new Deployer(); + +[ 'info', 'ready', 'error', 'deployment_begins', 'deployment_complete', 'progress' ].forEach(function(evName) { + deployer.on(evName, function(data) { console.log(evName + ":", data) }); +}); + +deployer.on('ready', function() { + deployer.checkForUpdates(); +});