Skip to content

Commit cb806d8

Browse files
author
anandkumarpatel
committed
Merge pull request #383 from CodeNow/image-pull
Image pull
2 parents 4e649a1 + 6d8b2fd commit cb806d8

7 files changed

Lines changed: 148 additions & 64 deletions

File tree

lib/models/apis/docker.js

Lines changed: 64 additions & 39 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,6 +409,17 @@ 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.
400425
* if not stopped in passed in time, the process is kill 9'd

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/mongo/instance.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,9 @@ InstanceSchema.methods.modifyUnsetContainer = function (cb) {
243243
InstanceSchema.methods.modifyContainerCreateErr = function (err, cb) {
244244
Instance.findByIdAndUpdate(this._id, {
245245
$set: {
246-
'container.error': pick(err, ['message', 'stack', 'data'])
246+
container: {
247+
error: pick(err, ['message', 'stack', 'data', 'imageIsPulling'])
248+
}
247249
}
248250
}, cb);
249251
};

lib/models/mongo/schemas/instance.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ var InstanceSchema = module.exports = new Schema({
130130
type: {
131131
message: String,
132132
stack: String,
133-
data: Schema.Types.Mixed
133+
data: Schema.Types.Mixed,
134+
imageIsPulling: Boolean
134135
}
135136
}
136137
}

lib/routes/builds.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ app.post('/builds/:id/actions/build',
282282
next();
283283
},
284284
runnable.create({}, 'sessionUser'),
285-
runnable.model.redeployInstance('instance', 'build._id'))),
285+
runnable.model.redeployInstance('instance', { json: { build: 'build._id.toString()' } }))),
286286
// noop is required!! to prevent res.send(404) after the response has already been sent.
287287
noop
288288
);

lib/routes/instances/index.js

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,22 +113,51 @@ var bodyValidations = flow.series(
113113
var createSaveAndNetworkContainer = flow.series(
114114
// host LOCK must be acquired PRIOR to calling this (not necessary for post instances)!
115115
findBuildContextVersion,
116-
mavis.create(),
117-
mavis.model.findDock('container_run', 'contextVersion.dockerHost'),
118-
mw.req().set('dockerHost', 'mavisResult'),
116+
mw.req('body.forceDock').require()
117+
.then(
118+
mw.req().set('dockerHost', 'body.forceDock'))
119+
.else(
120+
mavis.create(),
121+
mavis.model.findDock('container_run', 'contextVersion.dockerHost'),
122+
mw.req().set('dockerHost', 'mavisResult')),
119123
flow.try(
120124
docker.create('dockerHost'),
121-
docker.model.createContainerForVersion('contextVersion', {
122-
create: { Env: 'instance.env' }
123-
}),
125+
docker.model.createUserContainer('contextVersion', { Env: 'instance.env' }),
124126
mw.req().set('containerInfo', 'dockerResult'))
125127
.catch(
126128
mw.req().setToErr('containerCreateErr'),
127-
instances.model.modifyContainerCreateErr('containerCreateErr')),
128-
// sauron attach container to ip
129-
mw.req('containerCreateErr').require()
130-
.else( // container create was successful
131-
instances.model.inspectAndUpdate('containerInfo', 'dockerHost'),
129+
mw.req('containerCreateErr.output.statusCode').validate(validations.equals(404))
130+
.then(
131+
mw.req().set('containerCreateErr.imageIsPulling', true)),
132+
instances.model.modifyContainerCreateErr('containerCreateErr'),
133+
mw.req('containerCreateErr.imageIsPulling').validate(validations.equals(true))
134+
.then(
135+
docker.model.pullImage('contextVersion'),
136+
function(req, res, next) {
137+
// emitter.emit('dockerResult') need to emit pull event here
138+
req.dockerResult.on('data', function () {
139+
// TODO: pipe to pull-stream
140+
});
141+
// on pull finish, redeploy instance
142+
req.dockerResult.on('end', function () {
143+
flow.series(
144+
runnable.create({}, 'sessionUser'),
145+
runnable.model.redeployInstance('instance', {
146+
forceDock: 'dockerHost',
147+
json: { build: 'build._id.toString()' }
148+
}))(req, res, function (err) {
149+
error.logIfErr(err, req);
150+
});
151+
});
152+
// next immediately, background pull and redeploy
153+
next();
154+
})),
155+
mw.req('containerInfo').require()
156+
.then( // container create was successful
157+
docker.model.startUserContainer('containerInfo'),
158+
docker.model.inspectUserContainer('containerInfo'),
159+
mw.req().set('containerInspect', 'dockerResult'),
160+
instances.model.modifySetContainer('containerInspect', 'dockerHost'),
132161
sauron.create('dockerHost'),
133162
sauron.model.attachHostToContainer(
134163
'instance.network.networkIp',
@@ -260,7 +289,7 @@ app.post('/instances/',
260289
.then(
261290
// deploy build if it is built
262291
runnable.create({}, 'sessionUser'),
263-
runnable.model.redeployInstance('instance', 'build._id'),
292+
runnable.model.redeployInstance('instance', { json: { build: 'build._id.toString()' } }),
264293
mw.res.send(201, 'runnableResult'),
265294
function () {}))
266295
.catch( // dealloc networkIp/hostIp

test/instances/post/201.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ describe('201 POST /instances', {timeout:500}, function () {
5858
cb(createErr);
5959
}
6060
};
61+
var forceImageNotFoundOnCreateErrOnce = function(oldFn) {
62+
return function () {
63+
var cb = last(arguments);
64+
var createErr = new Error("image not found");
65+
extend(createErr, {
66+
statusCode : 404,
67+
reason : "no such container",
68+
json : "no such container\n"
69+
});
70+
if (isFunction(cb)) {
71+
cb(createErr);
72+
}
73+
Dockerode.prototype.createContainer = oldFn;
74+
};
75+
};
6176
function initExpected (done) {
6277
ctx.expected = {
6378
_id: exists,
@@ -223,6 +238,21 @@ describe('201 POST /instances', {timeout:500}, function () {
223238
});
224239
createInstanceTests(ctx);
225240
});
241+
describe('Container create error (Image not on dock)', function() {
242+
beforeEach(function (done) {
243+
extend(ctx.expected, {
244+
containers: exists,
245+
'containers[0].error.message': exists,
246+
'containers[0].error.stack': exists,
247+
'containers[0].error.imageIsPulling': true
248+
});
249+
Dockerode.prototype.createContainer = forceImageNotFoundOnCreateErrOnce(Dockerode.prototype.createContainer);
250+
done();
251+
});
252+
253+
createInstanceTests(ctx);
254+
});
255+
226256
});
227257
});
228258
describe('for Organization by member', function () {

0 commit comments

Comments
 (0)