Skip to content

Commit fcf6dee

Browse files
merge
2 parents 99a0b80 + a20a86b commit fcf6dee

30 files changed

Lines changed: 1219 additions & 561 deletions

File tree

configs/.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
LOG_ERRORS=true
22
TOKEN_EXPIRES="1 year"
33
REDIS_KEY_EXPIRES="5 minutes"
4+
REDIS_LOCK_EXPIRES="6 minutes"
45
DOCK_PROJECT_LIMIT=100000000
56
DEPLOY_COMMAND="echo DEPLOY"
67
COOKIE_SECRET="\$up3r,$3<r3t"
@@ -17,7 +18,7 @@ GITHUB_DEPLOY_KEY_BITS=2048
1718
HASHIDS_SALT="VvCCYj8x3xaHs44QiDp9"
1819
HASHIDS_LENGTH=6
1920
DOCKER_IMAGE_BUILDER_NAME="runnable/image-builder"
20-
DOCKER_IMAGE_BUILDER_VERSION="d1.3.1-v1.4.2"
21+
DOCKER_IMAGE_BUILDER_VERSION="d1.3.1-v1.4.3"
2122
DOCKER_IMAGE_BUILDER_CACHE="/tmp"
2223
BUILD_END_TIMEOUT=100
2324
DATADOG_HOST="localhost"

lib/middlewares/create-class-middleware.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var keypather = require('keypather')();
44
var fno = require('fn-object');
55
var isString = require('101/is-string');
66
var isObject = require('101/is-object');
7+
var exists = require('101/exists');
78

89
module.exports = function (key, Model) {
910
return new ClassMiddleware(key, Model);
@@ -88,7 +89,8 @@ function valIsFunction (obj) {
8889
function replacePlaceholders (req) {
8990
return function (arg) {
9091
if (isString(arg)) {
91-
return keypather.get(req, arg) || arg;
92+
var value = keypather.get(req, arg);
93+
return exists(value) ? value : arg;
9294
}
9395
else if (Array.isArray(arg)) {
9496
return arg.map(replacePlaceholders(req));

lib/models/apis/docker.js

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -264,22 +264,45 @@ Docker.prototype.getLogs = function (containerId, tail, cb) {
264264
}
265265
};
266266

267-
268-
269-
Docker.prototype.createAndInspectContainer = function (version, opts, cb) {
270-
debug('createAndInspectContainer', formatArgs(arguments));
271-
var self = this;
272-
if (typeof opts === 'function') {
267+
/**
268+
* creates a user container
269+
* @param version: version object which containes build
270+
* @param opts: opts to pass to docker
271+
* @param cb: Callback
272+
*/
273+
Docker.prototype.createUserContainer = function (version, opts, cb) {
274+
debug('createUserContainer', formatArgs(arguments));
275+
if (!version.build || !version.build.dockerTag) {
276+
return cb(Boom.badRequest('Cannot create a container for an unbuilt version', {
277+
debug: { versionId: version._id.toString() }
278+
}));
279+
}
280+
if (isFunction(opts)) {
273281
cb = opts;
274282
opts = {};
275283
}
276-
// Create opts
277-
opts.create = opts.create || {};
278-
extend(opts.create, {
284+
extend(opts, {
279285
Image: version.build.dockerTag
280286
});
281-
// Start opts
282-
opts.start = opts.start || {};
287+
288+
this.createContainer(opts, cb);
289+
};
290+
291+
/**
292+
* start a user container
293+
* @param container: container object to start
294+
* @param opts: opts to pass to docker
295+
* @param cb: Callback
296+
*/
297+
Docker.prototype.startUserContainer = function (container, opts, cb) {
298+
debug('startUserContainer', formatArgs(arguments));
299+
if (!container) {
300+
return cb(Boom.badRequest('Container must be provided to start'));
301+
}
302+
if (isFunction(opts)) {
303+
cb = opts;
304+
opts = {};
305+
}
283306

284307
// order matters here , our custom DNS should come first
285308
var dns = [];
@@ -288,40 +311,31 @@ Docker.prototype.createAndInspectContainer = function (version, opts, cb) {
288311
}
289312
dns.push(process.env.DNS_DEFAULT_IPADDRESS);
290313

291-
extend(opts.start, {
314+
extend(opts, {
292315
PublishAllPorts: true,
293316
Dns: dns
294317
});
295318

296-
async.waterfall([
297-
createContainer,
298-
startContainer
299-
], cb);
300-
function createContainer (cb) {
301-
self.createContainer(opts.create, cb);
302-
}
303-
function startContainer (container, cb) {
304-
self.startContainer(container, opts.start, function (err) {
305-
cb(err, container);
306-
});
307-
}
319+
this.startContainer(container, opts, cb);
308320
};
309321

310-
Docker.prototype.createContainerForVersion = function (version, opts, cb) {
311-
debug('createContainerForVersion', formatArgs(arguments));
312-
if (typeof opts === 'function') {
313-
cb = opts;
314-
opts = {};
315-
}
316-
if (!version.build || !version.build.dockerImage) {
317-
return cb(Boom.badRequest('Cannot create a container for an unbuilt version', {
318-
debug: { versionId: version._id.toString() }
319-
}));
322+
/**
323+
* inspect a user container
324+
* @param container: container object to inspect
325+
* @param cb: Callback
326+
*/
327+
Docker.prototype.inspectUserContainer = function (container, cb) {
328+
debug('inspectUserContainer', formatArgs(arguments));
329+
if (!container) {
330+
return cb(Boom.badRequest('Container must be provided to start'));
320331
}
321-
this.createAndInspectContainer(version, opts, cb);
332+
var self = this;
333+
self.inspectContainer(container, function(err, inspect) {
334+
if (err) { return cb(err); }
335+
inspect.dockerHost = self.dockerHost;
336+
cb(null, inspect);
337+
});
322338
};
323-
324-
325339
/**
326340
* CONTAINER METHODS - START
327341
*/
@@ -334,7 +348,7 @@ Docker.prototype.createContainerForVersion = function (version, opts, cb) {
334348
Docker.prototype.createContainer = function (opts, cb) {
335349
debug('createContainer', formatArgs(arguments));
336350
var self = this;
337-
if (typeof opts === 'function') {
351+
if (isFunction(opts)) {
338352
cb = opts;
339353
opts = {};
340354
}
@@ -370,7 +384,7 @@ Docker.prototype.startContainer = function (container, opts, cb) {
370384
debug('startContainer', formatArgs(arguments));
371385
var self = this;
372386
var containerId = container.dockerContainer || container.Id;
373-
if (typeof opts === 'function') {
387+
if (isFunction(opts)) {
374388
cb = opts;
375389
opts = {};
376390
}
@@ -395,9 +409,20 @@ Docker.prototype.restartContainer = function (container, cb) {
395409
self.handleErr(cb, 'Restart container failed', { containerId: containerId }));
396410
};
397411

412+
/**
413+
* pulls image and returns stream
414+
* @param contextVersion - format 'myrepo/myname:tag'
415+
* @param cb: Callback
416+
*/
417+
Docker.prototype.pullImage = function (version, cb) {
418+
debug('pullImage', formatArgs(arguments));
419+
var self = this;
420+
self.docker.pull(version.build.dockerTag, cb);
421+
};
422+
398423
/**
399424
* attempts to stop a running container.
400-
* if not stoped in passed in time, the process is kill 9'd
425+
* if not stopped in passed in time, the process is kill 9'd
401426
* @param container docker container or mongo container object
402427
* @param force Force stop a container. Ignores 'already stopped' error.
403428
* @param cb callback

lib/models/apis/mavis.js

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ var ApiClient = require('simple-api-client');
66
var util = require('util');
77
var Boom = require('dat-middleware').Boom;
88
var debug = require('debug')('runnable-api:mavis:model');
9+
var isObject = require('101/is-object');
10+
var isFunction = require('101/is-function');
911

1012
module.exports = Mavis;
1113

@@ -23,7 +25,37 @@ util.inherits(Mavis, ApiClient);
2325
*/
2426
Mavis.prototype.findDockForNetwork = function (cb) {
2527
debug('findDockForNetwork', formatArgs(arguments));
26-
this.findDock('container_run', cb);
28+
this.findDockForContainer({}, cb);
29+
};
30+
31+
Mavis.prototype.findDockForBuild = function (contextVersion, cb) {
32+
debug('findDockForBuild', formatArgs(arguments));
33+
34+
if (!isObject(contextVersion)) {
35+
return cb(new Error('missing contextVersion'));
36+
}
37+
38+
var opts = {
39+
type: 'container_build'
40+
};
41+
opts.prevDuration = contextVersion.duration || 0;
42+
opts.prevImage = contextVersion.dockerTag || null;
43+
this.findDock(opts, cb);
44+
};
45+
46+
Mavis.prototype.findDockForContainer = function (contextVersion, cb) {
47+
debug('findDockForContainer', formatArgs(arguments));
48+
49+
if (!isObject(contextVersion)) {
50+
return cb(new Error('missing contextVersion'));
51+
}
52+
53+
var opts = {
54+
type: 'container_run'
55+
};
56+
// if dockerHost is not an address, its invalid
57+
opts.prevDock = contextVersion.dockerHost || null;
58+
this.findDock(opts, cb);
2759
};
2860

2961
/**
@@ -32,23 +64,11 @@ Mavis.prototype.findDockForNetwork = function (cb) {
3264
* @param prevDock: previous dock this image was run on
3365
* @param cb: Callback
3466
*/
35-
Mavis.prototype.findDock = function (taskType, prevDock, cb) {
36-
if (typeof prevDock === 'function') {
37-
cb = prevDock;
38-
prevDock = null;
39-
}
40-
41-
// if prevDock is not an address, its invalid
42-
if(prevDock && !~prevDock.indexOf('http')){
43-
prevDock = null;
44-
}
67+
Mavis.prototype.findDock = function (opts, cb) {
4568
// path must have trailing slash to ensure this is a file
4669
var self = this;
4770
this.post('dock', {
48-
json: {
49-
type: taskType,
50-
prevDock: prevDock
51-
}
71+
json: opts
5272
}, function (err, res) {
5373
if (err) {
5474
var boomErr = Boom.create(504, 'Unable to find dock', {
@@ -99,10 +119,6 @@ function responseErr (res) {
99119
});
100120
}
101121

102-
// TODO: add all the findDock types
103-
// Mavis.prototype.findDockForContainerCreate
104-
105-
var isFunction = require('101/is-function');
106122
function formatArgs (args) {
107123
return Array.prototype.slice.call(args)
108124
.map(function (arg) {

lib/models/apis/runnable.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ var RunnableUser = require('runnable');
1010
var Base = require('runnable/lib/models/base');
1111
var debug = require('debug')('runnable-api:runnable:model');
1212

13+
var isFunction = require('101/is-function');
14+
1315
Base.prototype.parse = function (attrs) {
1416
if (attrs.toJSON) {
1517
attrs = attrs.toJSON();
@@ -61,25 +63,20 @@ util.inherits(Runnable, RunnableUser);
6163
* @param instances
6264
* @param cb
6365
*/
64-
Runnable.prototype.redeployInstance = function (instance, buildId, cb) {
66+
Runnable.prototype.redeployInstance = function (instance, opts, cb) {
6567
debug('redeployInstance', formatArgs(arguments));
66-
if (typeof buildId === 'function') {
67-
// (instance, cb)
68-
cb = buildId;
69-
buildId = null;
68+
if (isFunction(opts)) {
69+
cb = opts;
70+
opts = {};
7071
}
71-
var opts = (buildId) ?
72-
{ json: { build: buildId.toString() } } :
73-
{};
74-
7572
var instanceModel = this.newInstance(instance.shortHash);
7673
var retries = 0;
7774
var maxRetries = 15;
7875
redeploy();
7976
function redeploy () {
8077
instanceModel.redeploy(opts, function (err) {
8178
if (err) {
82-
if (err.output.statusCode === 409 && buildId) {
79+
if (err.output.statusCode === 409 && opts.json.build) {
8380
// only attempt retries if buildId is provided
8481
setTimeout(function () {
8582
attemptRetry(err);

lib/models/events/docker.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,28 @@ DockerEvents.prototype.listen = function (cb) {
2323
debug('listen', arguments);
2424
cb = cb || noop;
2525
if (this.closeHandler) {
26-
return cb(new Error('closing'));
26+
return cb(Boom.conflict('closing events listener is in progress'));
2727
}
2828
this.subscribeAll();
2929
cb();
3030
};
3131

3232
DockerEvents.prototype.close = function (cb) {
3333
debug('close', arguments, this.eventLockCount);
34+
cb = cb || noop;
3435
if (this.closeHandler) {
35-
return cb(new Error('already closing'));
36+
return cb(Boom.conflict('already closing events listener'));
3637
}
38+
var self = this;
3739
this.unsubscribeAll();
38-
this.closeHandler = cb || noop;
40+
this.closeHandler = cb;
3941
if (this.eventLockCount <= 0) {
4042
this.eventLockCount = 0; // to be safe
41-
cb();
42-
delete this.closeHandler;
43+
// prevent sync callback
44+
process.nextTick(function () {
45+
self.closeHandler();
46+
delete self.closeHandler;
47+
});
4348
}
4449
};
4550

@@ -110,7 +115,12 @@ DockerEvents.prototype.getEventLock = function (eventId, cb) {
110115

111116
DockerEvents.prototype.handleDie = function (data) {
112117
debug('handleDie', arguments);
113-
if (this.closeHandler) { return; } // this api is closing and will not handle any new events.
118+
// this api is closing and will not handle any new events.
119+
if (this.closeHandler) {
120+
// this debug statement is covered with unit test. Don't change/remove it.
121+
// see unit/docker-events.js
122+
return debug('events are stopping');
123+
}
114124
var self = this;
115125
var errDebug = {
116126
event: 'die',
@@ -126,7 +136,11 @@ DockerEvents.prototype.handleDie = function (data) {
126136
var containerId = data.id;
127137
activeApi.isMe(function (err, meIsActiveApi) {
128138
if (err) { return logErr(err); }
129-
if (!meIsActiveApi) { return debug('not active api'); }
139+
if (!meIsActiveApi) {
140+
// this debug statement is covered with unit test. Don't change/remove it.
141+
// see unit/docker-events.js
142+
return debug('not active api');
143+
}
130144
self.getEventLock(data.uuid, function (err, eventMutex) {
131145
if (err) { return logErr(err); }
132146
var userStoppedContainer = new UserStoppedContainer(containerId);

0 commit comments

Comments
 (0)