UNPKG

11.2 kBJavaScriptView Raw
1
2var spawn = require('child_process').spawn;
3var exec = require('child_process').exec;
4var chalk = require('chalk');
5var util = require('util');
6var fmt = require('../tools/fmt.js');
7var fs = require('fs');
8var path = require('path');
9var cst = require('../../constants.js');
10var Promise = require('../tools/promise.min.js');
11
12function pspawn(cmd) {
13 return new Promise(function(resolve, reject) {
14 var p_cmd = cmd.split(' ');
15
16 var install_instance = spawn(p_cmd[0], p_cmd.splice(1, cmd.length), {
17 stdio : 'inherit',
18 env : process.env,
19 shell : true
20 });
21
22 install_instance.on('close', function(code) {
23 if (code != 0) {
24 console.log(chalk.bold.red('Command failed'));
25 return reject(new Error('Bad cmd return'));
26 }
27 return resolve();
28 });
29
30 install_instance.on('error', function (err) {
31 return reject(err);
32 });
33 });
34}
35
36function checkDockerSetup() {
37 return new Promise(function(resolve, reject) {
38 exec("docker version -f '{{.Client.Version}}'", function(err, stdout, stderr) {
39 if (err) {
40 console.error(chalk.red.bold('[Docker access] Error while trying to use docker command'));
41 if (err.message && err.message.indexOf('Cannot connect to the Docker') > -1) {
42 console.log();
43 console.log(chalk.blue.bold('[Solution] Setup Docker to be able to be used without sudo rights:'));
44 console.log(chalk.bold('$ sudo groupadd docker'));
45 console.log(chalk.bold('$ sudo usermod -aG docker $USER'));
46 console.log(chalk.bold('Then LOGOUT and LOGIN your Linux session'));
47 console.log('Read more: http://bit.ly/29JGdCE');
48 }
49 return reject(err);
50 }
51 return resolve();
52 });
53 });
54}
55
56/**
57 * Switch Dockerfile mode
58 * check test/programmatic/containerizer.mocha.js
59 */
60function parseAndSwitch(file_content, main_file, opts) {
61 var lines = file_content.split('\n');
62 var mode = opts.mode;
63
64 lines[0] = 'FROM keymetrics/pm2:' + opts.node_version;
65
66 for (var i = 0; i < lines.length; i++) {
67 var line = lines[i];
68
69 if (['## DISTRIBUTION MODE', '## DEVELOPMENT MODE'].indexOf(line) > -1 ||
70 i == lines.length - 1) {
71 lines.splice(i, lines.length);
72 lines[i] = '## ' + mode.toUpperCase() + ' MODE';
73 lines[i + 1] = 'ENV NODE_ENV=' + (mode == 'distribution' ? 'production' : mode);
74
75 if (mode == 'distribution') {
76 lines[i + 2] = 'COPY . /var/app';
77 lines[i + 3] = 'CMD ["pm2-docker", "' + main_file + '", "--env", "production"]';
78 }
79 if (mode == 'development') {
80 lines[i + 2] = 'CMD ["pm2-dev", "' + main_file + '", "--env", "development"]';
81 }
82 break;
83 }
84 };
85 lines = lines.join('\n');
86 return lines;
87};
88
89/**
90 * Replace ENV, COPY and CMD depending on the mode
91 * @param {String} docker_filepath Dockerfile absolute path
92 * @param {String} main_file Main file to start in container
93 * @param {String} mode Mode to switch the Dockerfile
94 */
95function switchDockerFile(docker_filepath, main_file, opts) {
96 return new Promise(function(resolve, reject) {
97 var data = fs.readFileSync(docker_filepath, 'utf8').toString();
98
99 if (['distribution', 'development'].indexOf(opts.mode) == -1)
100 return reject(new Error('Unknown mode'));
101
102 var lines = parseAndSwitch(data, main_file, opts)
103 fs.writeFile(docker_filepath, lines, function(err) {
104 if (err) return reject(err);
105 resolve({
106 Dockerfile_path : docker_filepath,
107 Dockerfile : lines,
108 CMD : ''
109 });
110 })
111 });
112}
113
114/**
115 * Generate sample Dockerfile (lib/templates/Dockerfiles)
116 * @param {String} docker_filepath Dockerfile absolute path
117 * @param {String} main_file Main file to start in container
118 * @param {String} mode Mode to switch the Dockerfile
119 */
120function generateDockerfile(docker_filepath, main_file, opts) {
121 return new Promise(function(resolve, reject) {
122 var tpl_file = path.join(cst.TEMPLATE_FOLDER, cst.DOCKERFILE_NODEJS);
123 var template = fs.readFileSync(tpl_file, {encoding: 'utf8'});
124 var CMD;
125
126 template = parseAndSwitch(template, main_file, opts);
127
128 fs.writeFile(docker_filepath, template, function(err) {
129 if (err) return reject(err);
130 resolve({
131 Dockerfile_path : docker_filepath,
132 Dockerfile : template,
133 CMD : CMD
134 });
135 });
136 });
137}
138
139function handleExit(CLI, opts, mode) {
140 process.on('SIGINT', function() {
141 CLI.disconnect();
142
143 if (mode != 'distribution')
144 return false;
145
146 exec('docker ps -lq', function(err, stdout, stderr) {
147 if (err) {
148 console.error(err);
149 }
150 require('vizion').analyze({folder : process.cwd()}, function recur_path(err, meta){
151 if (!err && meta.revision) {
152 var commit_id = util.format('#%s(%s) %s',
153 meta.branch,
154 meta.revision.slice(0, 5),
155 meta.comment);
156
157 console.log(chalk.bold.magenta('$ docker commit -m "%s" %s %s'),
158 commit_id,
159 stdout.replace('\n', ''),
160 opts.imageName);
161 }
162 else
163 console.log(chalk.bold.magenta('$ docker commit %s %s'), stdout.replace('\n', ''), opts.imageName);
164
165 console.log(chalk.bold.magenta('$ docker push %s'), opts.imageName);
166 });
167 });
168 });
169}
170
171module.exports = function(CLI) {
172 CLI.prototype.generateDockerfile = function(script, opts) {
173 var docker_filepath = path.join(process.cwd(), 'Dockerfile');
174 var that = this;
175
176 fs.stat(docker_filepath, function(err, stat) {
177 if (err || opts.force == true) {
178 generateDockerfile(docker_filepath, script, {
179 mode : 'development'
180 })
181 .then(function() {
182 console.log(chalk.bold('New Dockerfile generated in current folder'));
183 console.log(chalk.bold('You can now run\n$ pm2 docker:dev <file|config>'));
184 return that.exitCli(cst.SUCCESS_EXIT);
185 });
186 return false;
187 }
188 console.log(chalk.red.bold('Dockerfile already exists in this folder, use --force if you want to replace it'));
189 that.exitCli(cst.ERROR_EXIT);
190 });
191 };
192
193 CLI.prototype.dockerMode = function(script, opts, mode) {
194 var promptly = require('promptly');
195 var self = this;
196 handleExit(self, opts, mode);
197
198 if (mode == 'distribution' && !opts.imageName) {
199 console.error(chalk.bold.red('--image-name [name] option is missing'));
200 return self.exitCli(cst.ERROR_EXIT);
201 }
202
203 var template;
204 var app_path, main_script;
205 var image_name;
206 var node_version = opts.nodeVersion ? opts.nodeVersion.split('.')[0] : 'latest';
207
208 image_name = opts.imageName || require('crypto').randomBytes(6).toString('hex');
209
210 if (script.indexOf('/') > -1) {
211 app_path = path.join(process.cwd(), path.dirname(script));
212 main_script = path.basename(script);
213 }
214 else {
215 app_path = process.cwd();
216 main_script = script;
217 }
218
219 checkDockerSetup()
220 .then(function() {
221 /////////////////////////
222 // Generate Dockerfile //
223 /////////////////////////
224 return new Promise(function(resolve, reject) {
225 var docker_filepath = path.join(process.cwd(), 'Dockerfile');
226
227 fs.stat(docker_filepath, function(err, stat) {
228 if (err) {
229 // Dockerfile does not exist, generate one
230 // console.log(chalk.blue.bold('Generating new Dockerfile'));
231 if (opts.force == true) {
232 return resolve(generateDockerfile(docker_filepath, main_script, {
233 node_version : node_version,
234 mode : mode
235 }));
236 }
237 if (opts.dockerdaemon)
238 return resolve(generateDockerfile(docker_filepath, main_script, {
239 node_version : node_version,
240 mode : mode
241 }));
242 promptly.prompt('No Dockerfile in current directory, ok to generate a new one? (y/n)', function(err, value) {
243 if (value == 'y')
244 return resolve(generateDockerfile(docker_filepath, main_script, {
245 node_version : node_version,
246 mode : mode
247 }));
248 else
249 return self.exitCli(cst.SUCCESS_EXIT);
250 });
251 return false;
252 }
253 return resolve(switchDockerFile(docker_filepath, main_script, {
254 node_version : node_version,
255 mode : mode
256 }));
257 });
258 });
259 })
260 .then(function(_template) {
261 template = _template;
262 return Promise.resolve();
263 })
264 .then(function() {
265 //////////////////
266 // Docker build //
267 //////////////////
268
269 var docker_build = util.format('docker build -t %s -f %s',
270 image_name,
271 template.Dockerfile_path);
272
273 if (opts.fresh == true)
274 docker_build += ' --no-cache';
275 docker_build += ' .';
276
277 console.log();
278 fmt.sep();
279 fmt.title('Building Boot System');
280 fmt.field('Type', chalk.cyan.bold('Docker'));
281 fmt.field('Mode', mode);
282 fmt.field('Image name', image_name);
283 fmt.field('Docker build command', docker_build);
284 fmt.field('Dockerfile path', template.Dockerfile_path);
285 fmt.sep();
286
287 return pspawn(docker_build);
288 })
289 .then(function() {
290 ////////////////
291 // Docker run //
292 ////////////////
293
294 var docker_run = 'docker run --net host';
295
296 if (opts.dockerdaemon == true)
297 docker_run += ' -d';
298 if (mode != 'distribution')
299 docker_run += util.format(' -v %s:/var/app -v /var/app/node_modules', app_path);
300 docker_run += ' ' + image_name;
301 var dockerfile_parsed = template.Dockerfile.split('\n');
302 var base_image = dockerfile_parsed[0];
303 var run_cmd = dockerfile_parsed[dockerfile_parsed.length - 1];
304
305 console.log();
306 fmt.sep();
307 fmt.title('Booting');
308 fmt.field('Type', chalk.cyan.bold('Docker'));
309 fmt.field('Mode', mode);
310 fmt.field('Base Image', base_image);
311 fmt.field('Image Name', image_name);
312 fmt.field('Docker Command', docker_run);
313 fmt.field('RUN Command', run_cmd);
314 fmt.field('CWD', app_path);
315 fmt.sep();
316 return pspawn(docker_run);
317 })
318 .then(function() {
319 console.log(chalk.blue.bold('>>> Leaving Docker instance uuid=%s'), image_name);
320 self.disconnect();
321 return Promise.resolve();
322 })
323 .catch(function(err) {
324 console.log();
325 console.log(chalk.grey('Raw error=', err.message));
326 self.disconnect();
327 });
328
329 };
330
331};
332
333module.exports.generateDockerfile = generateDockerfile;
334module.exports.parseAndSwitch = parseAndSwitch;
335module.exports.switchDockerFile = switchDockerFile;