1 |
|
2 | var spawn = require('child_process').spawn;
|
3 | var exec = require('child_process').exec;
|
4 | var chalk = require('chalk');
|
5 | var util = require('util');
|
6 | var fmt = require('../tools/fmt.js');
|
7 | var fs = require('fs');
|
8 | var path = require('path');
|
9 | var cst = require('../../constants.js');
|
10 | var Promise = require('../tools/promise.min.js');
|
11 |
|
12 | function 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 |
|
36 | function 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 |
|
58 |
|
59 |
|
60 | function 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 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 | function 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 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | function 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 |
|
139 | function 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 |
|
171 | module.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 |
|
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 |
|
230 |
|
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 |
|
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 |
|
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 |
|
333 | module.exports.generateDockerfile = generateDockerfile;
|
334 | module.exports.parseAndSwitch = parseAndSwitch;
|
335 | module.exports.switchDockerFile = switchDockerFile;
|