1 | 'use strict';
|
2 |
|
3 | var path = require('path'),
|
4 | stream = require('stream');
|
5 |
|
6 | var _ = 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 |
|
14 | var PassThrough = stream.PassThrough;
|
15 |
|
16 | var 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 |
|
28 | DockWorker.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 |
|
40 | DockWorker.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 |
|
66 | DockWorker.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 |
|
97 | DockWorker.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 |
|
220 | DockWorker.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 |
|
316 | DockWorker.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 |
|
431 | DockWorker.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 |
|
462 | DockWorker.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 |
|
484 | module.exports = DockWorker;
|