UNPKG

10.1 kBJavaScriptView Raw
1
2var Configuration = require('../../Configuration.js');
3var cst = require('../../../constants.js');
4var Common = require('../../Common');
5var forEachLimit = require('async/forEachLimit');
6const sexec = require('../../tools/sexec.js')
7
8var path = require('path');
9var fs = require('fs');
10var os = require('os');
11var spawn = require('child_process').spawn;
12var exec = require('child_process').exec;
13var execSync = require('child_process').execSync;
14
15module.exports = {
16 install,
17 uninstall,
18 start,
19 publish,
20 package
21}
22
23/**
24 * Module management to manage tarball packages
25 *
26 * pm2 install http.tar.gz
27 * pm2 uninstall http
28 *
29 * - the first and only folder in the tarball must be called module (tar zcvf http module/)
30 * - a package.json must be present with attribute "name", "version" and "pm2" to declare apps to run
31 */
32
33function install(PM2, module_filepath, opts, cb) {
34 // Remote file retrieval
35 if (module_filepath.includes('http') === true) {
36 var target_file = module_filepath.split('/').pop()
37 var target_filepath = path.join(os.tmpdir(), target_file)
38
39 opts.install_url = module_filepath
40
41 return retrieveRemote(module_filepath, target_filepath, (err) => {
42 if (err) {
43 Common.errMod(err)
44 process.exit(1)
45 }
46 installLocal(PM2, target_filepath, opts, cb)
47 })
48 }
49
50 // Local install
51 installLocal(PM2, module_filepath, opts, cb)
52}
53
54function retrieveRemote(url, dest, cb) {
55 Common.logMod(`Retrieving remote package ${url}...`)
56
57 var wget = spawn('wget', [url, '-O', dest, '-q'], {
58 stdio : 'inherit',
59 env: process.env,
60 shell : true
61 })
62
63 wget.on('error', (err) => {
64 console.error(err.stack || err)
65 })
66
67 wget.on('close', (code) => {
68 if (code !== 0)
69 return cb(new Error('Could not download'))
70 return cb(null)
71 })
72}
73
74function installLocal(PM2, module_filepath, opts, cb) {
75 Common.logMod(`Installing package ${module_filepath}`)
76
77 // Get module name by unpacking the module/package.json only and read the name attribute
78 getModuleName(module_filepath, function(err, module_name) {
79 if (err) return cb(err)
80
81 Common.logMod(`Module name is ${module_name}`)
82
83 Common.logMod(`Depackaging module...`)
84
85 var install_path = path.join(cst.DEFAULT_MODULE_PATH, module_name);
86
87 require('mkdirp').sync(install_path)
88
89 var install_instance = spawn('tar', ['zxf', module_filepath, '-C', install_path, '--strip-components 1'], {
90 stdio : 'inherit',
91 env: process.env,
92 shell : true
93 })
94
95 install_instance.on('close', function(code) {
96 Common.logMod(`Module depackaged in ${install_path}`)
97 if (code == 0)
98 return runInstall(PM2, install_path, module_name, opts, cb)
99 return PM2.exitCli(1)
100 });
101
102 install_instance.on('error', function (err) {
103 console.error(err.stack || err);
104 });
105 })
106}
107
108function deleteModulePath(module_name) {
109 var sanitized = module_name.replace(/\./g, '')
110 execSync(`rm -r ${path.join(cst.DEFAULT_MODULE_PATH, module_name)}`, { silent: true })
111}
112
113function runInstall(PM2, target_path, module_name, opts, cb) {
114 var config_file = path.join(target_path, 'package.json')
115 var conf
116
117 try {
118 conf = require(config_file)
119 module_name = conf.name
120 } catch(e) {
121 Common.errMod(new Error('Cannot find package.json file with name attribute at least'));
122 }
123
124 // Force with the name in the package.json
125 opts.started_as_module = true
126 opts.cwd = target_path
127
128 if (needPrefix(conf))
129 opts.name_prefix = module_name
130
131 if (opts.install) {
132 Common.logMod(`Running YARN install...`)
133
134 sexec(`cd ${target_path} ; yarn install`, {silent: false}, function(code) {
135 // Start apps under "apps" or "pm2" attribute
136 Common.logMod(`Starting ${target_path}`)
137 PM2.start(conf, opts, function(err, data) {
138 if (err) return cb(err)
139
140 Configuration.setSync(`${cst.MODULE_CONF_PREFIX_TAR}:${module_name}`, {
141 source: 'tarball',
142 install_url: opts.install_url,
143 installed_at: Date.now()
144 })
145
146 Common.logMod(`Module INSTALLED and STARTED`)
147 return cb(null, 'Module installed & Started')
148 })
149 })
150 }
151 else {
152 PM2.start(conf, opts, function(err, data) {
153 if (err) return cb(err)
154
155 Configuration.setSync(`${cst.MODULE_CONF_PREFIX_TAR}:${module_name}`, {
156 source: 'tarball',
157 install_url: opts.install_url,
158 installed_at: Date.now()
159 })
160
161 Common.logMod(`Module INSTALLED and STARTED`)
162 return cb(null, 'Module installed & Started')
163 })
164 }
165}
166
167function start(PM2, module_name, cb) {
168 var module_path = path.join(cst.DEFAULT_MODULE_PATH, module_name);
169 Common.printOut(cst.PREFIX_MSG_MOD + 'Starting TAR module ' + module_name);
170 var package_json_path = path.join(module_path, 'package.json');
171 var module_conf = Configuration.getSync(`${cst.MODULE_CONF_PREFIX_TAR}:${module_name}`)
172
173 try {
174 var conf = require(package_json_path)
175 } catch(e) {
176 Common.printError(`Could not find package.json as ${package_json_path}`)
177 return cb()
178 }
179
180 var opts = {};
181
182 opts.started_as_module = true
183 opts.cwd = module_path
184
185 if (module_conf.install_url)
186 opts.install_url = module_conf.install_url
187
188 if (needPrefix(conf))
189 opts.name_prefix = module_name
190
191 PM2.start(conf, opts, function(err, data) {
192 if (err) {
193 Common.printError(`Could not start ${module_name} ${module_path}`)
194 return cb()
195 }
196
197 Common.printOut(`${cst.PREFIX_MSG_MOD} Module ${module_name} STARTED`)
198 return cb();
199 })
200}
201
202/**
203 * Retrieve from module package.json the name of each application
204 * delete process and delete folder
205 */
206function uninstall(PM2, module_name, cb) {
207 var module_path = path.join(cst.DEFAULT_MODULE_PATH, module_name);
208
209 Common.logMod(`Removing ${module_name} from auto startup`)
210
211 try {
212 var pkg = require(path.join(module_path, 'package.json'))
213 } catch(e) {
214 Common.errMod('Could not retrieve module package.json');
215 return cb(e)
216 }
217
218 var apps = pkg.apps || pkg.pm2
219 apps = [].concat(apps);
220
221 /**
222 * Some time a module can have multiple processes
223 */
224 forEachLimit(apps, 1, (app, next) => {
225 var app_name
226
227 if (!app.name) {
228 Common.renderApplicationName(app)
229 }
230
231 if (apps.length > 1)
232 app_name = `${module_name}:${app.name}`
233 else if (apps.length == 1 && pkg.name != apps[0].name)
234 app_name = `${module_name}:${app.name}`
235 else
236 app_name = app.name
237
238 PM2._operate('deleteProcessId', app_name, () => {
239 deleteModulePath(module_name)
240 next()
241 })
242 }, () => {
243 Configuration.unsetSync(`${cst.MODULE_CONF_PREFIX_TAR}:${module_name}`)
244 cb(null)
245 })
246}
247
248
249/**
250 * Uncompress only module/package.json and retrieve the "name" attribute in the package.json
251 */
252function getModuleName(module_filepath, cb) {
253 var tmp_folder = path.join(os.tmpdir(), cst.MODULE_BASEFOLDER)
254
255 var install_instance = spawn('tar', ['zxf', module_filepath, '-C', os.tmpdir(), `${cst.MODULE_BASEFOLDER}/package.json`], {
256 stdio : 'inherit',
257 env: process.env,
258 shell : true
259 })
260
261 install_instance.on('close', function(code) {
262 try {
263 var pkg = JSON.parse(fs.readFileSync(path.join(tmp_folder, `package.json`)))
264 return cb(null, pkg.name)
265 } catch(e) {
266 return cb(e)
267 }
268 });
269}
270
271function package(module_path, target_path, cb) {
272 var base_folder = path.dirname(module_path)
273 var module_folder_name = path.basename(module_path)
274 var pkg = require(path.join(module_path, 'package.json'))
275 var pkg_name = `${module_folder_name}-v${pkg.version.replace(/\./g, '-')}.tar.gz`
276 var target_fullpath = path.join(target_path, pkg_name)
277
278 var cmd = `tar zcf ${target_fullpath} -C ${base_folder} --transform 's,${module_folder_name},module,' ${module_folder_name}`
279
280 Common.logMod(`Gziping ${module_path} to ${target_fullpath}`)
281
282 var tar = exec(cmd, (err, sto, ste) => {
283 if (err) {
284 console.log(sto.toString().trim())
285 console.log(ste.toString().trim())
286 }
287 })
288
289 tar.on('close', function (code) {
290 cb(code == 0 ? null : code, {
291 package_name: pkg_name,
292 path: target_fullpath
293 })
294 })
295}
296
297function publish(PM2, folder, cb) {
298 var target_folder = folder ? path.resolve(folder) : process.cwd()
299
300 try {
301 var pkg = JSON.parse(fs.readFileSync(path.join(target_folder, 'package.json')).toString())
302 } catch(e) {
303 Common.errMod(`${process.cwd()} module does not contain any package.json`)
304 process.exit(1)
305 }
306
307 if (!pkg.name) throw new Error('Attribute name should be present')
308 if (!pkg.version) throw new Error('Attribute version should be present')
309 if (!pkg.pm2 && !pkg.apps) throw new Error('Attribute apps should be present')
310
311 var current_path = target_folder
312 var module_name = path.basename(current_path)
313 var target_path = os.tmpdir()
314
315 Common.logMod(`Starting publishing procedure for ${module_name}@${pkg.version}`)
316
317 package(current_path, target_path, (err, res) => {
318 if (err) {
319 Common.errMod('Can\'t package, exiting')
320 process.exit(1)
321 }
322
323 Common.logMod(`Package [${pkg.name}] created in path ${res.path}`)
324
325 var data = {
326 module_data: {
327 file: res.path,
328 content_type: 'content/gzip'
329 },
330 id: pkg.name,
331 name: pkg.name,
332 version: pkg.version
333 };
334
335 var uri = `${PM2.pm2_configuration.registry}/api/v1/modules`
336 Common.logMod(`Sending Package to remote ${pkg.name} ${uri}`)
337
338 require('needle')
339 .post(uri, data, { multipart: true }, function(err, res, body) {
340 if (err) {
341 Common.errMod(err)
342 process.exit(1)
343 }
344 if (res.statusCode !== 200) {
345 Common.errMod(`${pkg.name}-${pkg.version}: ${res.body.msg}`)
346 process.exit(1)
347 }
348 Common.logMod(`Module ${module_name} published under version ${pkg.version}`)
349 process.exit(0)
350 })
351 })
352}
353
354function needPrefix(conf) {
355 if ((conf.apps && conf.apps.length > 1) ||
356 (conf.pm2 && conf.pm2.length > 1) ||
357 (conf.apps.length == 1 && conf.name != conf.apps[0].name))
358 return true
359 return false
360}