Skip to content

Commit

Permalink
Merge pull request #17 from CodeNow/hotfix.cache
Browse files Browse the repository at this point in the history
Hotfix.cache
  • Loading branch information
bkendall committed Mar 26, 2015
2 parents cb3e1b8 + 2b7f383 commit 31ef3db
Show file tree
Hide file tree
Showing 33 changed files with 980 additions and 334 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
layer-cache
image-builder.iml
scripts/*.log
DOCKER_IP
3 changes: 0 additions & 3 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,4 @@ test:
environment:
CACHE_DIR: /tmp/cache
LAYER_CACHE_DIR: /tmp/layer-cache
RUNNABLE_DOCKER: localhost:5555
RUNNABLE_SAURON_HOST: localhost:3100
RUNNABLE_NETWORK_DRIVER: signal
- ./scripts/run-tests.sh
37 changes: 27 additions & 10 deletions lib/external/docker.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
'use strict';

var Docker = require('dockerode');
var url = require('url');

var dockerHost = {};
module.exports = docker;

if (~process.env.RUNNABLE_DOCKER.indexOf('unix://')) {
// format: unix:///var/run/docker.sock
dockerHost.socketPath = process.env.RUNNABLE_DOCKER.replace('unix://', '');
} else if (process.env.RUNNABLE_DOCKER.split(':').length === 2) {
// format: 10.234.129.94:5354
dockerHost.host = process.env.RUNNABLE_DOCKER.split(':')[0];
dockerHost.port = process.env.RUNNABLE_DOCKER.split(':')[1];
}
function docker () {
if (!process.env.RUNNABLE_DOCKER) {
throw new Error('RUNNABLE_DOCKER required');
}
var uri = url.parse(process.env.RUNNABLE_DOCKER);

var dockerHost = {};
if (uri.protocol === 'unix:' && uri.slashes) {
// format: unix:///var/run/docker.sock
dockerHost.socketPath = uri.path;
} else {
// format: tcp://10.234.129.94:5354
if (uri.port && uri.hostname) {
dockerHost.host = uri.hostname;
dockerHost.port = uri.port;
}
}

module.exports = new Docker(dockerHost);
if (Object.keys(dockerHost).length === 0) {
throw new Error('dockerhost not set');
}

return new Docker(dockerHost);
}
74 changes: 44 additions & 30 deletions lib/external/network.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,59 @@
'use strict';

var conire = require('conire');
var Driver = conire(process.env.RUNNABLE_NETWORK_DRIVER, {
signal: './signal-client.js',
sauron: 'sauron-client'
});

var driver = new Driver(
process.env.RUNNABLE_SAURON_HOST.split(':')[0],
process.env.RUNNABLE_SAURON_HOST.split(':')[1]);

module.exports = {
// attach network to current container
attach: function(containerId, cb) {
var networkIp = process.env.RUNNABLE_NETWORK_IP;
var hostIp = process.env.RUNNABLE_HOST_IP;
// TODO: emit this as an event
// "containerId" needs network
// message will include hostIp and containerId and dockIp

// needs to be super robust, retry a few times, and force attach
retryOnError(driver.attachHostToContainer.bind(driver, networkIp, hostIp, {
containerId: containerId,
force: true
}), cb);

module.exports = Network;

function Network () {
if (!process.env.RUNNABLE_SAURON_HOST) {
throw new Error('require sauronHost');
}
};
if (!process.env.RUNNABLE_NETWORK_IP) {
throw new Error('require networkIp');
}
if (!process.env.RUNNABLE_HOST_IP) {
throw new Error('require hostIp');
}
if (!process.env.RUNNABLE_NETWORK_DRIVER) {
throw new Error('require driver');
}

this.networkIp = process.env.RUNNABLE_NETWORK_IP;
this.hostIp = process.env.RUNNABLE_HOST_IP;

function retryOnError(func, cb) {
var Driver = conire(process.env.RUNNABLE_NETWORK_DRIVER, {
signal: './signal-client.js',
sauron: 'sauron-client'
});

this.driver = new Driver(
process.env.RUNNABLE_SAURON_HOST.split(':')[0],
process.env.RUNNABLE_SAURON_HOST.split(':')[1]);
}

// TODO: emit this as an event
// "containerId" needs network
// message will include hostIp and containerId and dockIp
Network.prototype.attach = function(containerId, cb) {
// needs to be super robust, retry a few times, and force attach
var retryCount = 5;
var self = this;
retry();
function retry (err) {
retryCount--;
if (retryCount <= 0) {
if (retryCount < 0) {
return cb(err);
}
func(function(err, res) {
if (err || res.statusCode >= 500) {
return setTimeout(retry.bind(null, err || res.statusCode), 1000);

self.driver.attachHostToContainer(self.networkIp, self.hostIp, {
containerId: containerId,
force: true
}, function(err, res) {
if (err) {
return setTimeout(retry.bind(this, err), 1000);
}

cb(null, res);
});
}
}
};
14 changes: 7 additions & 7 deletions lib/external/signal-client.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use strict';
var docker = require('../external/docker.js');

module.exports = Signal;

Expand All @@ -11,21 +10,22 @@ function Signal (host, port) {
}

// should send sigint to container
Signal.prototype.attachHostToContainer =
Signal.prototype.attachHostToContainer =
function (networkIp, hostIp, opts, cb) {
var containerId = opts.containerId;
// hook to get sauron to fail
if (process.env.SAURON_FAIL) {
return cb(null, {statusCode: 500});
return cb(null, { statusCode: 500 });
}
// i will be shunned for this, but need to wait before calling sigint
// the time when we get the message to time code runs is different
// but no other way due to limited message passing and its only a test
setTimeout(function(){
var docker = require('../external/docker.js')();
setTimeout(function () {
docker.getContainer(containerId).kill({
signal: 'SIGINT'
}, function(err) {
cb(err, {statusCode: 200});
}, function (err) {
cb(err, { statusCode: 200 });
});
}, 1000);
};
};
6 changes: 4 additions & 2 deletions lib/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var path = require('path');
var lockfile = require('lockfile');
var crypto = require('crypto');
var injector = require('./steps/injector.js');
var build = require('./steps/build.js');
var Builder = require('./steps/build.js');

var thisDir = __dirname;

Expand Down Expand Up @@ -364,7 +364,9 @@ var steps = module.exports = {
},

runDockerBuild: function (cb) {
build(steps, cb);
console.log('Building server...'.bold.yellow);
var builder = new Builder(steps);
builder.runDockerBuild(cb);
},

parseBuildLogAndHistory: function (cb) {
Expand Down
174 changes: 107 additions & 67 deletions lib/steps/build.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,118 @@
'use strict';

require('colors');
var tar = require('tar-fs');
var fs = require('fs');
var Network = require('../external/network.js');

var docker = require('../external/docker.js');
var network;
// we only need network if we are using weave
if (process.env.RUNNABLE_WAIT_FOR_WEAVE) {
network = require('../external/network.js');
}
var noop = function () {};

module.exports = Builder;

var noop = function(){};
function Builder (steps) {
this.dockerContext = steps.dirs.dockerContext;
this.dockerBuildLog = steps.logs.dockerBuild;
this.saveToLogs = steps.saveToLogs;

module.exports = function runDockerBuild (steps, cb) {
if (!process.env.RUNNABLE_DOCKERTAG) {
return cb(new Error('RUNNABLE_DOCKERTAG is missing.'));
if (process.env.RUNNABLE_DOCKER) {
this.docker = require('../external/docker.js')();
}
if (!process.env.RUNNABLE_DOCKER_BUILDOPTIONS) {
process.env.RUNNABLE_DOCKER_BUILDOPTIONS = '';
// we only need network if we are using weave
if (process.env.RUNNABLE_WAIT_FOR_WEAVE) {
this.network = new Network();
}
}

Builder.prototype.runDockerBuild = function (cb) {
var self = this;
self.tarContext(function (err) {
if (err) { return cb(err); }

console.log('Building server...'.bold.yellow);
var tarPath = steps.dirs.dockerContext+'.tar';
self.startImageBuild(cb);
});
};

Builder.prototype.tarContext = function (cb) {
this.tarPath = this.dockerContext + '.tar';
tar
.pack(steps.dirs.dockerContext)
.pipe(fs.createWriteStream(tarPath))
.on('finish', function() {
docker.buildImage(tarPath, {
t: process.env.RUNNABLE_DOCKERTAG
}, function (err, response) {
if (err) { return cb(err); }

var buildErr = null;
var needAttach = null;
response.on('data', function(data) {
data = JSON.parse(data);
fs.appendFileSync(steps.logs.dockerBuild, data.error || data.stream);
steps.saveToLogs(noop)(null, data.stream || '', data.error || '');
var out = data.stream;

// TODO: make this a robust state machine
// we only need to be stateful for one event no need to do it now
if (data.error) {
buildErr = data.error;
out = data.error;
} else if (needAttach) {
needAttach = false;
var containerId = data
.stream
.split('Running in ')[1]
.replace('\n','')
.trim();

network.attach(containerId, function(err) {
// something went wrong, kill container to stop the build
if (err) {
process.stderr.write('error attaching to runnable network \n');
process.stderr.write('please rebuild');
docker.getContainer(containerId).kill(function() {
process.exit(1);
});
}
});
} else if (
~data.stream.indexOf(process.env.RUNNABLE_WAIT_FOR_WEAVE)) {
out =
data.stream.replace(process.env.RUNNABLE_WAIT_FOR_WEAVE, '');
needAttach = true;
}

process.stdout.write(out);
});
response.on('end', function() {
cb(buildErr);
});
.pack(this.dockerContext)
.pipe(fs.createWriteStream(this.tarPath))
.on('finish', cb);
};

Builder.prototype.startImageBuild = function (cb) {
var self = this;
this.docker.buildImage(self.tarPath, {
t: process.env.RUNNABLE_DOCKERTAG
}, function (err, response) {
if (err) { return cb(err); }
self.handleBuild(response, cb);
});
};

Builder.prototype.handleBuild = function (response, cb) {
var self = this;

response.on('error', cb);
response.on('data', function (data) {
self.handleBuildData(data);
});
response.on('end', function () {
cb(self.buildErr);
});
};

Builder.prototype.handleBuildData = function (data) {
var self = this;

data = JSON.parse(data);
var out = data.stream || '';
fs.appendFileSync(self.dockerBuildLog, data.error || out);
self.saveToLogs(noop)(null, out, data.error || '');

// TODO: make this a robust state machine
// we only need to be stateful for one event no need to do it now
if (data.error) {
self.buildErr = data.error;
out = data.error;
} else if (this.needAttach) {
this.needAttach = false;
self.handleNetworkAttach(out);
} else if (isWaitForWeaveLine(out)) {
this.needAttach = true;
out = out.replace(process.env.RUNNABLE_WAIT_FOR_WEAVE, '');
}

process.stdout.write(out);
};

function isWaitForWeaveLine (line) {
return ~line.indexOf(process.env.RUNNABLE_WAIT_FOR_WEAVE);
}

Builder.prototype.handleNetworkAttach = function (line) {
var self = this;
// ignore if not Running In aka cache line
if (!~line.indexOf('Running in ')) { return; }

var containerId = line
.split('Running in ')[1]
.replace('\n','')
.trim();

self.network.attach(containerId, self.postNetworkAttach(containerId));
};

Builder.prototype.postNetworkAttach = function (containerId) {
var self = this;
return function (err) {
// something went wrong, kill container to stop the build
if (err) {
process.stderr.write('error attaching to runnable network \n');
process.stderr.write('please rebuild');
self.docker.getContainer(containerId).kill(function () {
process.exit(1);
});
});
};
}
};
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"sinon": "^1.12.2"
},
"scripts": {
"test": "./node_modules/.bin/lab --verbose --coverage --threshold 92",
"test": "./node_modules/.bin/lab --verbose --coverage --threshold 93",
"lint": "./node_modules/.bin/jshint --verbose ."
},
"repository": {
Expand Down
Loading

0 comments on commit 31ef3db

Please sign in to comment.