UNPKG

13.7 kBJavaScriptView Raw
1var nodemiral = require('nodemiral');
2var path = require('path');
3var fs = require('fs');
4var rimraf = require('rimraf');
5var exec = require('child_process').exec;
6var spawn = require('child_process').spawn;
7var uuid = require('uuid');
8var format = require('util').format;
9var extend = require('util')._extend;
10var _ = require('underscore');
11var async = require('async');
12var buildApp = require('./build.js');
13var buildAppNextJS = require('./buildNextJS.js');
14var request = require('request');
15var pkg = require('../package.json');
16
17require('colors');
18
19var NEXTJS_DIR_DEFAULT_EXCLUSIONS = ['node_modules', '__test__', '__tests__'];
20
21module.exports = Actions;
22
23function Actions(config, cwd, options) {
24 this.cwd = cwd;
25 this.config = config;
26 this.sessionsMap = this._createSessionsMap(config);
27 this.settingsFileName = options.settingsFileName;
28 this.configFileName = options.configFileName;
29
30 //get settingsFileName into env
31 var setttingsJsonPath = path.resolve(this.cwd, this.settingsFileName);
32 if (fs.existsSync(setttingsJsonPath)) {
33 this.config.env['METEOR_SETTINGS'] = JSON.stringify(require(setttingsJsonPath));
34 }
35}
36
37Actions.prototype._createSessionsMap = function (config) {
38 var sessionsMap = {};
39
40 config.servers.forEach(function (server) {
41 var host = server.host;
42 var auth = {username: server.username};
43
44 if (server.pem) {
45 auth.pem = fs.readFileSync(path.resolve(server.pem), 'utf8');
46 } else {
47 auth.password = server.password;
48 }
49
50 var nodemiralOptions = {
51 ssh: server.sshOptions,
52 keepAlive: true
53 };
54
55 if (!sessionsMap[server.os]) {
56 sessionsMap[server.os] = {
57 sessions: [],
58 taskListsBuilder: require('./taskLists')(server.os)
59 };
60 }
61
62 var session = nodemiral.session(host, auth, nodemiralOptions);
63 session._serverConfig = server;
64 sessionsMap[server.os].sessions.push(session);
65 });
66
67 return sessionsMap;
68};
69
70Actions.prototype._executePararell = function (actionName, args) {
71 var self = this;
72 var sessionInfoList = _.values(self.sessionsMap);
73 async.map(
74 sessionInfoList,
75 function (sessionsInfo, callback) {
76 var taskList = sessionsInfo.taskListsBuilder[actionName]
77 .apply(sessionsInfo.taskListsBuilder, args);
78 taskList.run(sessionsInfo.sessions, function (summaryMap) {
79 callback(null, summaryMap);
80 });
81 },
82 whenAfterCompleted
83 );
84};
85
86Actions.prototype.setup = function () {
87 if(this.config.containersConfig === true) {
88 var intVersion = parseInt(pkg.version.replace(/\./g, ''));
89 if (!this.config.containerName) {
90 console.error('Missing "containerName" property for setting up a container like instance'.red.bold);
91 process.exit(1);
92 }
93 console.log('Setup of container-like server!! This may take a while... for sure...'.bold);
94 var self = this;
95 var serverHost = this.config.servers[0].host;
96 var taskUrl = 'http://' + serverHost + ':12654/task';
97 request({
98 uri: taskUrl,
99 method: 'POST',
100 timeout: 1200000,
101 json: {
102 action: 'setupContainer',
103 v: intVersion,
104 pwd: self.config.APIPassword,
105 options: {
106 name: self.config.containerName,
107 nodeVersion: self.config.nodeVersion,
108 maxCPUQuota: self.config.maxCPUQuota,
109 openToThisIPs: self.config.openToThisIPs,
110 serverName: serverHost,
111 }
112 }
113 }, function (err, res, body) {
114 if(err) {
115 console.log(('Error while calling: ' + taskUrl).red.bold);
116 console.log('Error obj: '.red.bold, err);
117 process.exit(1);
118 }
119 var containerResponse;
120 var server = self.config.servers[0];
121 if(typeof body === 'string') {
122 containerResponse = JSON.parse(body);
123 } else {
124 containerResponse = body;
125 }
126 if(!server.password || !server.sshOptions || !server.sshOptions.port) {
127 if(!containerResponse) {
128 console.log(('No response from task: ' + taskUrl).red.bold);
129 console.log('Full response object: '.red.bold, res);
130 process.exit(1);
131 }
132 if (containerResponse.error === 'Unauthorized!') {
133 console.log(('The password for the configuration API is not valid').red.bold);
134 process.exit(1);
135 }
136 if (containerResponse.error) {
137 console.log(containerResponse.error.red.bold);
138 process.exit(1);
139 }
140 var port = parseInt(containerResponse._port);
141 if(server.sshOptions) {
142 server.sshOptions.port = port;
143 } else {
144 server.sshOptions = { port: port };
145 }
146 server.password = containerResponse._password;
147 var sshAgent = process.env.SSH_AUTH_SOCK;
148 if (sshAgent) {
149 server.sshOptions.agent = sshAgent;
150 }
151 self.sessionsMap['linux'].sessions[0] = nodemiral.session(serverHost, {username: server.username, password: containerResponse._password}, { ssh: server.sshOptions, keepAlive: true });
152 if (containerResponse._password) {
153 var configFile = path.resolve(self.cwd, self.configFileName);
154 var mupContent = fs.readFileSync(configFile, {encoding: 'utf8'});
155 mupContent = mupContent.replace(/\/\/{{PASSWORD}}/g, '"password":"' + containerResponse._password + '",').replace(/\/\/{{SSH_OPTIONS}}/g, '"sshOptions":{"port":' + port + '},');
156 fs.writeFileSync(configFile, mupContent);
157 }
158 }
159 if (containerResponse.openMongoURL) {
160 try {
161 var settingsFile = path.resolve(self.cwd, self.settingsFileName);
162 var settingsContent = fs.readFileSync(settingsFile, {encoding: 'utf8'});
163 if (!/"openMongoURL"/.test(settingsContent)) {
164 const privateRegExp = /(.*"private".*)/;
165 if (privateRegExp.test(settingsContent)) {
166 settingsContent = settingsContent.replace(/(.*"private".*)/, '$1\r\n "openMongoURL": "' + containerResponse.openMongoURL + '",');
167 fs.writeFileSync(settingsFile, settingsContent);
168 }
169 }
170 } catch (err) {
171 console.log(err);
172 // fs.writeFileSync(settingsFile, '{ "openMongoURL": "' + containerResponse.openMongoURL + '" }');
173 }
174 }
175 request({
176 uri: taskUrl,
177 method: 'POST',
178 timeout: 1200000,
179 json: {
180 action: 'setupNginx',
181 v: intVersion,
182 pwd: self.config.APIPassword,
183 options: {
184 name: self.config.containerName,
185 port: self.config.env && self.config.env.PORT,
186 host: ((self.config.env && self.config.env.ROOT_URL) || '').split('//').pop().replace(/\//g, '')
187 }
188 }
189 }, function (err2, res2, body2) {
190 if(err2) {
191 console.log(('Error while calling: ' + taskUrl).red.bold);
192 console.log('Error obj: '.red.bold, err2);
193 process.exit(1);
194 }
195 var containerResponse;
196 if(typeof body2 === 'string') {
197 containerResponse = JSON.parse(body2);
198 } else {
199 containerResponse = body2;
200 }
201 if(!containerResponse) {
202 console.log(('No response from task: ' + taskUrl).red.bold);
203 console.log('Full response object: '.red.bold, res2);
204 process.exit(1);
205 }
206 if (containerResponse.error === 'Unauthorized!') {
207 console.log(('The password for the configuration API is not valid').red.bold);
208 process.exit(1);
209 }
210 self._executePararell("setup", [self.config]);
211 });
212 });
213 } else {
214 this._executePararell("setup", [this.config]);
215 }
216};
217
218Actions.prototype.deployMeteor = function () {
219 var self = this;
220 var buildLocation = path.resolve('/tmp', uuid.v4());
221 var bundlePath = path.resolve(buildLocation, 'bundle.tar.gz');
222
223 // spawn inherits env vars from process.env
224 // so we can simply set them like this
225 process.env.BUILD_LOCATION = buildLocation;
226
227 var deployCheckWaitTime = this.config.deployCheckWaitTime;
228 var appName = this.config.appName;
229 var appPath = this.config.app;
230 var buildOptions = this.config.buildOptions;
231
232 console.log('Meteor app path : ' + this.config.app);
233 console.log('Using buildOptions : ' + JSON.stringify(buildOptions));
234 buildApp(appPath, buildLocation, buildOptions, function (err) {
235 if (err) {
236 process.exit(1);
237 } else {
238 var sessionsData = [];
239 _.forEach(self.sessionsMap, function (sessionsInfo) {
240 var taskListsBuilder = sessionsInfo.taskListsBuilder;
241 _.forEach(sessionsInfo.sessions, function (session) {
242 sessionsData.push({
243 taskListsBuilder: taskListsBuilder,
244 session: session
245 });
246 });
247 });
248
249 async.mapSeries(
250 sessionsData,
251 function (sessionData, callback) {
252 var session = sessionData.session;
253 var taskListsBuilder = sessionData.taskListsBuilder
254 var env = _.extend({}, self.config.env, session._serverConfig.env);
255 var taskList = taskListsBuilder.deploy(
256 bundlePath, env, self.config);
257 taskList.run(session, function (summaryMap) {
258 callback(null, summaryMap);
259 });
260 },
261 whenAfterDeployed(buildLocation)
262 )
263 }
264 });
265}
266
267Actions.prototype.deployNextJS = function () {
268 var self = this;
269 var buildLocation = path.resolve('/tmp', uuid.v4());
270 var bundlePath = path.resolve(buildLocation, 'bundle.tar.gz');
271 try {
272 fs.mkdirSync(buildLocation);
273 } catch(err) {
274 console.log(err);
275 }
276 process.env.BUILD_LOCATION = buildLocation;
277
278 var deployCheckWaitTime = this.config.deployCheckWaitTime;
279 var appName = this.config.appName;
280 var appPath = this.config.app;
281 var dirExclusions = this.config.dirExclusions && Object.prototype.toString.call(this.config.dirExclusions) === '[object Array]' ? NEXTJS_DIR_DEFAULT_EXCLUSIONS.concat(this.config.dirExclusions) : NEXTJS_DIR_DEFAULT_EXCLUSIONS;
282
283 console.log('NextJS app path : ' + this.config.app);
284 buildAppNextJS(appPath, buildLocation, dirExclusions, function (err) {
285 if (err) {
286 console.log(err);
287 process.exit(1);
288 } else {
289 var sessionsData = [];
290 _.forEach(self.sessionsMap, function (sessionsInfo) {
291 var taskListsBuilder = sessionsInfo.taskListsBuilder;
292 _.forEach(sessionsInfo.sessions, function (session) {
293 sessionsData.push({
294 taskListsBuilder: taskListsBuilder,
295 session: session
296 });
297 });
298 });
299
300 async.mapSeries(
301 sessionsData,
302 function (sessionData, callback) {
303 var session = sessionData.session;
304 var taskListsBuilder = sessionData.taskListsBuilder
305 var env = _.extend({}, self.config.env, session._serverConfig.env);
306 var taskList = taskListsBuilder.deploy(
307 bundlePath, env, self.config);
308 taskList.run(session, function (summaryMap) {
309 callback(null, summaryMap);
310 });
311 },
312 whenAfterDeployed(buildLocation)
313 )
314 }
315 });
316}
317
318Actions.prototype.deploy = function () {
319 if(this.config.nextjs === true) {
320 this.deployNextJS();
321 } else {
322 this.deployMeteor();
323 }
324};
325
326Actions.prototype.reconfig = function () {
327 var self = this;
328 var sessionInfoList = [];
329 for (var os in self.sessionsMap) {
330 var sessionsInfo = self.sessionsMap[os];
331 sessionsInfo.sessions.forEach(function (session) {
332 var env = _.extend({}, self.config.env, session._serverConfig.env);
333 var taskList = sessionsInfo.taskListsBuilder.reconfig(
334 env, self.config);
335 sessionInfoList.push({
336 taskList: taskList,
337 session: session
338 });
339 });
340 }
341
342 async.mapSeries(
343 sessionInfoList,
344 function (sessionsInfo, callback) {
345 sessionsInfo.taskList.run(sessionsInfo.session, function (summaryMap) {
346 callback(null, summaryMap);
347 });
348 },
349 whenAfterCompleted
350 );
351};
352
353Actions.prototype.restart = function () {
354 this._executePararell("restart", [this.config]);
355};
356
357Actions.prototype.stop = function () {
358 this._executePararell("stop", [this.config]);
359};
360
361Actions.prototype.start = function () {
362 this._executePararell("start", [this.config]);
363};
364
365Actions.prototype.logs = function () {
366 var self = this;
367 var tailOptions = process.argv.slice(3).join(" ");
368
369 var sessions = [];
370
371 for (var os in self.sessionsMap) {
372 var sessionsInfo = self.sessionsMap[os];
373 sessionsInfo.sessions.forEach(function (session) {
374 sessions.push(session);
375 });
376 }
377
378 async.map(
379 sessions,
380 function (session, callback) {
381 var hostPrefix = '[' + session._host + '] ';
382 var options = {
383 onStdout: function (data) {
384 process.stdout.write(hostPrefix + data.toString());
385 },
386 onStderr: function (data) {
387 process.stderr.write(hostPrefix + data.toString());
388 }
389 };
390
391 var command = 'sudo docker logs ' + tailOptions + ' ' + self.config.appName;
392 session.execute(command, options, callback);
393 },
394 whenAfterCompleted
395 );
396};
397
398Actions.init = function () {
399 var destMupJson = path.resolve('mup.json');
400 var destSettingsJson = path.resolve('settings.json');
401
402 if (fs.existsSync(destMupJson) || fs.existsSync(destSettingsJson)) {
403 console.error('A Project Already Exists'.bold.red);
404 process.exit(1);
405 }
406
407 var exampleMupJson = path.resolve(__dirname, '../example/mup.json');
408 var exampleSettingsJson = path.resolve(__dirname, '../example/settings.json');
409
410 copyFile(exampleMupJson, destMupJson);
411 copyFile(exampleSettingsJson, destSettingsJson);
412
413 console.log('Empty Project Initialized!'.bold.green);
414
415 function copyFile(src, dest) {
416 var content = fs.readFileSync(src, 'utf8');
417 fs.writeFileSync(dest, content);
418 }
419};
420
421function storeLastNChars(vars, field, limit, color) {
422 return function (data) {
423 vars[field] += data.toString();
424 if (vars[field].length > 1000) {
425 vars[field] = vars[field].substring(vars[field].length - 1000);
426 }
427 }
428}
429
430function whenAfterDeployed(buildLocation) {
431 return function (error, summaryMaps) {
432 rimraf.sync(buildLocation);
433 whenAfterCompleted(error, summaryMaps);
434 };
435}
436
437function whenAfterCompleted(error, summaryMaps) {
438 var errorCode = error || haveSummaryMapsErrors(summaryMaps) ? 1 : 0;
439 process.exit(errorCode);
440}
441
442function haveSummaryMapsErrors(summaryMaps) {
443 return _.some(summaryMaps, hasSummaryMapErrors);
444}
445
446function hasSummaryMapErrors(summaryMap) {
447 return _.some(summaryMap, function (summary) {
448 return summary.error;
449 })
450}