UNPKG

12 kBJavaScriptView Raw
1'use strict';
2
3var path = require('path'),
4 stream = require('stream');
5
6var _ = require('lodash'),
7 del = require('del'),
8 Dockerode = require('dockerode'),
9 fs = require('fs-extra'),
10 isolated = require('isolated'),
11 Q = require('q'),
12 tar = require('tar-fs');
13
14var PassThrough = stream.PassThrough;
15
16var DockWorker = function (options) {
17 this.options = options;
18 this.server = new Dockerode({
19 protocol: options.protocol,
20 host: options.host,
21 port: options.port,
22 key: options.keys.privateKey,
23 cert: options.keys.certificate,
24 ca: options.keys.caCertificate
25 });
26};
27
28DockWorker.prototype.ping = function (callback) {
29 if (!callback) {
30 throw new Error('Callback is missing.');
31 }
32
33 try {
34 this.server.ping(callback);
35 } catch (e) {
36 callback(e);
37 }
38};
39
40DockWorker.prototype.hasImage = function (name, callback) {
41 if (!name) {
42 throw new Error('Name is missing.');
43 }
44
45 if (!callback) {
46 throw new Error('Callback is missing.');
47 }
48
49 this.server.listImages(function (err, images) {
50 var hasImage;
51
52 if (err) {
53 return callback(err);
54 }
55
56 hasImage = _.some(images, function (image) {
57 return _.some(image.RepoTags, function (repoTag) {
58 return repoTag.split(':')[0] === name;
59 });
60 });
61
62 callback(null, hasImage);
63 });
64};
65
66DockWorker.prototype.downloadImage = function (name, callback) {
67 if (!name) {
68 throw new Error('Name is missing.');
69 }
70
71 if (!callback) {
72 throw new Error('Callback is missing.');
73 }
74
75 this.server.pull(name, function (err, pullStream) {
76 if (err) {
77 return callback(err);
78 }
79
80 pullStream.on('data', function (data) {
81 data = JSON.parse(data.toString('utf8'));
82
83 if (data.error) {
84 callback(new Error(data.error));
85 pullStream.removeAllListeners();
86 pullStream.resume();
87 }
88 });
89
90 pullStream.once('end', function () {
91 pullStream.removeAllListeners();
92 callback(null);
93 });
94 });
95};
96
97DockWorker.prototype.buildImage = function (options, callback) {
98 var that = this;
99
100 if (!options) {
101 throw new Error('Options are missing.');
102 }
103 if (!options.directory) {
104 throw new Error('Directory is missing.');
105 }
106 if (!options.dockerfile) {
107 throw new Error('Dockerfile is missing.');
108 }
109 if (!options.name) {
110 throw new Error('Name is missing.');
111 }
112 if (!callback) {
113 throw new Error('Callback is missing.');
114 }
115
116 options.preBuild = options.preBuild || function (preBuildOptions, done) {
117 done(null);
118 };
119
120 isolated({
121 files: options.directory,
122 preserveTimestamps: false
123 }, function (errIsolated, tempDirectory) {
124 var tarFile,
125 tarFileName,
126 tarStream,
127 tempAppDirectory;
128
129 if (errIsolated) {
130 return callback(errIsolated);
131 }
132
133 tempAppDirectory = path.join(tempDirectory, path.basename(options.directory));
134 tarFileName = path.join(tempDirectory, 'image.tar');
135
136 fs.copy(options.dockerfile, path.join(tempAppDirectory, 'Dockerfile'), {
137 preserveTimestamps: true
138 }, function (errCopy) {
139 var getFilesToBeDeleted = function (callbackGetFilesToBeDeleted) {
140 if (!options.dockerignore) {
141 return callbackGetFilesToBeDeleted(null, []);
142 }
143 fs.readFile(options.dockerignore, function (errReadFile, data) {
144 if (errReadFile) {
145 return callbackGetFilesToBeDeleted(errReadFile);
146 }
147 callbackGetFilesToBeDeleted(null, data.toString('utf8').split('\n'));
148 });
149 };
150
151 if (errCopy) {
152 return callback(errCopy);
153 }
154
155 getFilesToBeDeleted(function (errGetFilesToBeDeleted, filesToBeDeleted) {
156 if (errGetFilesToBeDeleted) {
157 return callback(errGetFilesToBeDeleted);
158 }
159
160 del(filesToBeDeleted, { cwd: tempAppDirectory }, function (errDel) {
161 if (errDel) {
162 return callback(errDel);
163 }
164
165 options.preBuild({
166 directory: tempAppDirectory
167 }, function (errPreBuild) {
168 if (errPreBuild) {
169 return callback(errPreBuild);
170 }
171
172 tarStream = tar.pack(tempAppDirectory);
173 tarStream.once('error', function (errTarStream) {
174 callback(errTarStream);
175
176 tarStream.removeAllListeners();
177 tarFile.removeAllListeners();
178 });
179
180 tarFile = fs.createWriteStream(tarFileName);
181 tarFile.once('finish', function () {
182 tarStream.removeAllListeners();
183 tarFile.removeAllListeners();
184
185 that.server.buildImage(tarFileName, { t: options.name }, function (errBuildImage, res) {
186 var hadErrors = false;
187
188 if (errBuildImage) {
189 return callback(errBuildImage);
190 }
191
192 res.on('data', function (data) {
193 var status = JSON.parse(data.toString('utf8'));
194
195 if (status.error) {
196 hadErrors = true;
197 callback(new Error(status.error));
198 }
199 });
200
201 res.once('end', function () {
202 if (hadErrors) {
203 return;
204 }
205 callback(null);
206 });
207
208 res.resume();
209 });
210 });
211
212 tarStream.pipe(tarFile);
213 });
214 });
215 });
216 });
217 });
218};
219
220DockWorker.prototype.startContainer = function (options, callback) {
221 var containerOptions;
222
223 if (!options) {
224 throw new Error('Options are missing.');
225 }
226
227 if (!options.image) {
228 throw new Error('Image is missing.');
229 }
230
231 if (!options.name) {
232 throw new Error('Name is missing.');
233 }
234
235 if (!callback) {
236 throw new Error('Callback is missing.');
237 }
238
239 containerOptions = {
240 Image: options.image,
241 name: options.name,
242 HostConfig: {
243 RestartPolicy: {
244 Name: 'no'
245 }
246 }
247 };
248
249 if (options.restart) {
250 containerOptions.HostConfig.RestartPolicy = {
251 Name: 'always'
252 };
253 }
254
255 if (options.env) {
256 containerOptions.Env = [];
257 _.forOwn(options.env, function (value, key) {
258 containerOptions.Env.push(key.toUpperCase() + '=' + value);
259 });
260 }
261
262 if (options.volumes) {
263 containerOptions.Volumes = {};
264 _.forEach(options.volumes, function (volume) {
265 containerOptions.Volumes[volume.container] = {};
266 });
267
268 containerOptions.HostConfig.Binds = [];
269 _.forEach(options.volumes, function (volume) {
270 containerOptions.HostConfig.Binds.push(volume.host + ':' + volume.container);
271 });
272 }
273
274 if (options.ports) {
275 containerOptions.ExposedPorts = {};
276 _.forEach(options.ports, function (portFowarding) {
277 containerOptions.ExposedPorts[portFowarding.container + '/tcp'] = {};
278 });
279
280 containerOptions.HostConfig.PortBindings = {};
281 _.forEach(options.ports, function (portFowarding) {
282 containerOptions.HostConfig.PortBindings[portFowarding.container + '/tcp'] = [
283 { HostPort: '' + portFowarding.host }
284 ];
285 });
286 }
287
288 if (options.links) {
289 containerOptions.HostConfig.Links = [];
290 _.forOwn(options.links, function (link) {
291 containerOptions.HostConfig.Links.push(link.name + ':' + link.alias);
292 });
293 }
294
295 if (options.network && options.network.hosts) {
296 containerOptions.HostConfig.ExtraHosts = [];
297 _.forOwn(options.network.hosts, function (host) {
298 containerOptions.HostConfig.ExtraHosts.push(host.name + ':' + host.ip);
299 });
300 }
301
302 this.server.createContainer(containerOptions, function (errCreateContainer, container) {
303 if (errCreateContainer) {
304 return callback(errCreateContainer);
305 }
306
307 container.start(function (err) {
308 if (err) {
309 return callback(err);
310 }
311 callback(null, container.id);
312 });
313 });
314};
315
316DockWorker.prototype.getRunningContainersFor = function (name, callback) {
317 var that = this;
318
319 if (!name) {
320 throw new Error('Name is missing.');
321 }
322 if (!callback) {
323 throw new Error('Callback is missing.');
324 }
325
326 that.server.listContainers(function (errListContainers, containerInfos) {
327 var inspectContainers = [];
328
329 if (errListContainers) {
330 return callback(errListContainers);
331 }
332
333 _.forEach(containerInfos, function (containerInfo) {
334 var container = that.server.getContainer(containerInfo.Id);
335 var deferred = Q.defer();
336
337 container.inspect(function (errInspect, data) {
338 if (errInspect) {
339 return deferred.reject(errInspect);
340 }
341 deferred.resolve(data);
342 });
343
344 inspectContainers.push(deferred.promise);
345 });
346
347 Q.all(inspectContainers).done(function (containers) {
348 containers = _.filter(containers, function (container) {
349 if ((typeof name === 'object') && (name instanceof RegExp)) {
350 return name.test(container.Config.Image);
351 }
352
353 return container.Config.Image === name;
354 });
355
356 containers = _.map(containers, function (container) {
357 var environmentVariables = container.Config.Env,
358 links = container.HostConfig.Links,
359 network = {
360 hosts: container.HostConfig.ExtraHosts
361 },
362 ports = container.HostConfig.PortBindings,
363 volumes = container.HostConfig.Binds;
364
365 ports = _.map(ports, function (value, key) {
366 return {
367 container: key.split('/')[0] - 0,
368 host: value[0].HostPort - 0
369 };
370 });
371
372 environmentVariables = _.map(environmentVariables, function (environmentVariable) {
373 var parts = environmentVariable.split('=');
374
375 return {
376 key: parts[0],
377 value: parts[1]
378 };
379 });
380
381 environmentVariables = _.object(
382 _.pluck(environmentVariables, 'key'),
383 _.pluck(environmentVariables, 'value')
384 );
385
386 links = _.map(links, function (link) {
387 var parts = link.split(':');
388
389 return {
390 name: parts[0].substring(parts[0].lastIndexOf('/') + 1),
391 alias: parts[1].substring(parts[1].lastIndexOf('/') + 1)
392 };
393 });
394
395 network.hosts = _.map(network.hosts, function (hostEntry) {
396 var parts = hostEntry.split(':');
397
398 return {
399 name: parts[0],
400 ip: parts[1]
401 };
402 });
403
404 volumes = _.map(volumes, function (volume) {
405 var parts = volume.split(':');
406
407 return {
408 container: parts[1],
409 host: parts[0]
410 };
411 });
412
413 return {
414 env: environmentVariables,
415 image: container.Config.Image,
416 links: links,
417 name: container.Name.substring(1),
418 network: network,
419 ports: ports,
420 volumes: volumes
421 };
422 });
423
424 callback(null, containers);
425 }, function (err) {
426 callback(err);
427 });
428 });
429};
430
431DockWorker.prototype.getLogs = function (name, callback) {
432 var container;
433
434 if (!name) {
435 throw new Error('Name is missing.');
436 }
437
438 if (!callback) {
439 throw new Error('Callback is missing.');
440 }
441
442 container = this.server.getContainer(name);
443
444 container.attach({
445 stream: true,
446 stdout: true,
447 stderr: true
448 }, function (err, containerStream) {
449 var streamErr = new PassThrough(),
450 streamOut = new PassThrough();
451
452 if (err) {
453 return callback(err);
454 }
455
456 container.modem.demuxStream(containerStream, streamOut, streamErr);
457
458 callback(null, streamOut, streamErr);
459 });
460};
461
462DockWorker.prototype.stopContainer = function (name, callback) {
463 var container;
464
465 if (!name) {
466 throw new Error('Name is missing.');
467 }
468
469 if (!callback) {
470 throw new Error('Callback is missing.');
471 }
472
473 container = this.server.getContainer(name);
474
475 container.kill(function (err) {
476 if (err) {
477 return callback(err);
478 }
479
480 container.remove({ v: true }, callback);
481 });
482};
483
484module.exports = DockWorker;