TAR.js 9.9 KB

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