Advertisement
tabvn

Untitled

Sep 3rd, 2017
471
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.44 KB | None | 0 0
  1. /**
  2. * Author: Toan Nguyen Dinh
  3. * Email: Toan@tabvn.com
  4. * Created: Sep 02, 2017
  5. */
  6.  
  7.  
  8. /**
  9. * Requires of modules
  10. */
  11. const fs = require('fs');
  12. const path = require('path');
  13. const spawn = require('child_process').spawn;
  14. const _ = require('lodash');
  15. const moment = require('moment');
  16. const tmp = require('tmp');
  17. tmp.setGracefulCleanup();
  18.  
  19. /**
  20. * Amazone S3
  21. *
  22. */
  23. const AWS = require('aws-sdk');
  24. AWS.config.loadFromPath('aws.json');
  25. const s3 = new AWS.S3();
  26.  
  27.  
  28. /**
  29. * End Amazon S3
  30. */
  31.  
  32. /**
  33. * End requires modules
  34. *
  35. */
  36.  
  37. /**
  38. * Begin Express 4
  39. */
  40. const PORT = 3003;
  41. const express = require('express');
  42. const bodyParser = require('body-parser');
  43. const app = express();
  44. app.use(bodyParser.json()); // for parsing application/json
  45. app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-form-urlencoded
  46.  
  47. /**
  48. * End of Express
  49. */
  50.  
  51. /**
  52. *Begin Variables
  53. */
  54. const DATABASE_BACKUP = 'database';
  55. const CODE_BACKUP = 'code';
  56. const FULL = 'full';
  57.  
  58.  
  59. let production = false;
  60.  
  61. let devConfig = {
  62. mongodb: 'dashboard',
  63. bucket: 'tom.tabvn.com'
  64. };
  65.  
  66. let productionConfig = {
  67. mongodb: 'dashboard',
  68. bucket: 'producer.livex.tv'
  69. };
  70.  
  71. let config = production ? productionConfig : devConfig;
  72.  
  73. let backups = []; // array of processing backup
  74. let backupErrors = []; // Array items of backup error
  75. let restore = null;
  76.  
  77. /**
  78. * End variables
  79. */
  80.  
  81. let upload = (fileName, filePath, callback) => {
  82.  
  83. let file = fs.createReadStream(filePath);
  84. let params = {Bucket: config.bucket, Key: fileName, Body: file};
  85.  
  86. s3.putObject(params, function (err, data) {
  87. if (err) {
  88. console.log(err);
  89. if (callback) {
  90. return callback(err);
  91. }
  92.  
  93. } else {
  94. if (callback) {
  95. return callback(null, data);
  96. }
  97.  
  98. }
  99.  
  100. });
  101. };
  102.  
  103. let getBackupFiles = (callback) => {
  104.  
  105. s3.listObjects({
  106. Bucket: config.bucket,
  107. MaxKeys: 100
  108. }, function (err, data) {
  109. if (callback) {
  110. return callback(err, data);
  111. }
  112. });
  113.  
  114. };
  115.  
  116.  
  117. let deleteBackupFile = (key, callback) => {
  118.  
  119. let params = {
  120. Bucket: config.bucket,
  121. Key: key
  122. };
  123. s3.deleteObject(params, function (err, data) {
  124. if (callback) {
  125. return callback(err, data);
  126. }
  127. });
  128. }
  129.  
  130. let getDownloadUrl = (key = "", callback) => {
  131.  
  132. let ONE_DAY = 60 * 60 * 24;
  133. let params = {
  134. Bucket: config.bucket,
  135. Key: key,
  136. Expires: ONE_DAY
  137. };
  138.  
  139.  
  140. s3.getSignedUrl('getObject', params, (err, data) => {
  141.  
  142. if (err) {
  143. if (callback) {
  144. return callback(err);
  145. }
  146. }
  147. else {
  148. return callback(null, data);
  149.  
  150. }
  151.  
  152.  
  153. });
  154.  
  155. };
  156.  
  157.  
  158. let getBackupFileObject = (key = "", callback) => {
  159.  
  160. let params = {
  161. Bucket: config.bucket,
  162. Key: key
  163. };
  164.  
  165. s3.getObject(params, (err, data) => {
  166.  
  167. if (callback) {
  168. return callback(err, data);
  169. }
  170. })
  171.  
  172.  
  173. };
  174.  
  175.  
  176. let compress = (pathToArchive, directoryPath, callback) => {
  177. let compressProcess = spawn('tar', ['zcvf', pathToArchive, '-C', directoryPath, '.']);
  178.  
  179. compressProcess.on('exit', (code) => {
  180. if (code === 0) {
  181. if (callback) {
  182. return callback(null, true);
  183. }
  184. } else {
  185. let error = new Error("Error", code);
  186. if (callback) {
  187. return callback(error);
  188. }
  189. }
  190. })
  191. };
  192.  
  193. let extract = (pathToArchive, directoryPath, callback) => {
  194.  
  195. let extractProcess = spawn('tar', ['xvzf', pathToArchive, '-C', directoryPath]);
  196. extractProcess.on('exit', (code) => {
  197.  
  198. if (code === 0) {
  199. if (callback) {
  200. return callback(null, true);
  201. }
  202.  
  203. } else {
  204. let error = new Error("Error", code);
  205. if (callback) {
  206. return callback(error);
  207. }
  208. }
  209. });
  210.  
  211. };
  212.  
  213.  
  214. let createFileName = (backup, ext = 'tar.gz') => {
  215.  
  216. let names = [];
  217. let space = "---";
  218. let underSpace = '___';
  219.  
  220. let snapshot = backup.snapshot ? backup.snapshot : "null";
  221. snapshot = _.replace(snapshot, /---/g, " ");
  222. snapshot = _.replace(snapshot, /___/g, " ");
  223. snapshot = _.replace(snapshot, /\//g, " ");
  224. snapshot = _.trim(snapshot);
  225.  
  226. names.push('snapshot' + space + (snapshot));
  227. names.push('backupType' + space + backup.backupType);
  228. names.push('manually' + space + (backup.manually ? "true" : "false"));
  229. names.push('createdAt' + space + moment(backup.createdAt).unix());
  230. names.push('ext' + space + ext + underSpace);
  231. return _.join(names, underSpace) + '.' + ext;
  232. };
  233.  
  234. let getObjectStructFromFileName = (filename) => {
  235.  
  236. let space = "---";
  237. let underSpace = '___';
  238.  
  239. let splitUnderScore = _.split(filename, underSpace);
  240. let snapshot = splitUnderScore && splitUnderScore[0] ? _.split(splitUnderScore[0], space) : null;
  241. let backupType = splitUnderScore && splitUnderScore[1] ? _.split(splitUnderScore[1], space) : null;
  242. let manually = splitUnderScore && splitUnderScore[2] ? _.split(splitUnderScore[2], space) : null;
  243. let createdAt = splitUnderScore && splitUnderScore[3] ? _.split(splitUnderScore[3], space) : null;
  244. let obj = {
  245. key: filename,
  246. snapshot: snapshot && snapshot[1] && snapshot[1] && snapshot[1] !== 'null' ? snapshot[1] : "",
  247. backupType: backupType && backupType[1] ? backupType[1] : null,
  248. manually: manually && manually[1] === 'true' ? true : false,
  249. createdAt: createdAt && createdAt[1] ? moment.unix(createdAt[1]).toDate() : null,
  250. size: 0,
  251. tag: null,
  252. };
  253. return obj;
  254.  
  255. };
  256.  
  257. let backupDatabase = (backup, callback) => {
  258.  
  259.  
  260. let tmpDirGenerate = tmp.dirSync({prefix: "livex-", unsafeCleanup: true});
  261. let tmpDir = tmpDirGenerate.name;
  262. let fileName = createFileName(backup, 'tar.gz');
  263. let dir = path.join(tmpDir, Date.now().toString());
  264.  
  265. let arg = ['--db', config.mongodb, '--out', dir];
  266. let exportDatabaseProcess = spawn('mongodump', arg);
  267. let filePath = path.join(tmpDir, fileName);
  268.  
  269. exportDatabaseProcess.on('exit', (code) => {
  270.  
  271. if (code === 0) {
  272. compress(filePath, dir, (err, success) => {
  273.  
  274. if (err) {
  275. if (callback) {
  276. return callback(err);
  277. }
  278.  
  279. } else {
  280. upload(fileName, filePath, (err, data) => {
  281. // delete the file
  282. tmpDirGenerate.removeCallback();
  283.  
  284. if (err) {
  285. if (callback) {
  286. return callback(err);
  287. }
  288.  
  289. } else {
  290. if (callback) {
  291. return callback(null, data);
  292. }
  293. }
  294.  
  295. });
  296. }
  297. });
  298.  
  299. } else {
  300. console.log("Backup database error with code: ", code);
  301. if (callback) {
  302. return callback(new Error("An error backup with code:", code));
  303. }
  304. }
  305. })
  306.  
  307. };
  308.  
  309. let backupSourceCode = (backup) => {
  310.  
  311.  
  312. };
  313.  
  314. let fullBackup = (backup) => {
  315.  
  316. backupDatabase(backup, (err, success) => {
  317. console.log(err, success);
  318. });
  319.  
  320. backupSourceCode(backup);
  321.  
  322. };
  323.  
  324. let doBackup = (backup) => {
  325.  
  326. if (backup.backupType === DATABASE_BACKUP) {
  327. backupDatabase(backup, (err, success) => {
  328. console.log("backup database process:", err, success);
  329. // so let remove pending backup item.
  330. backups = _.filter(backups, (b) => b.id !== backup.id);
  331. if (err === null && success) {
  332. backups = _.filter(backups, (b) => b.id !== backup.id);
  333. } else {
  334. // if error so we do need add this backup item as error item. log it.
  335. backupErrors.push(backup);
  336. }
  337. });
  338. } else if (backup.backupType === CODE_BACKUP) {
  339. backupSourceCode(backup);
  340. } else {
  341. fullBackup(backup);
  342. }
  343.  
  344. };
  345.  
  346.  
  347. let restoreDatabase = (backup, callback) => {
  348.  
  349.  
  350. let params = {
  351. Bucket: config.bucket,
  352. Key: backup.key
  353. };
  354.  
  355. let tmpDirGenerate = tmp.dirSync({prefix: "livex-restore", unsafeCleanup: true});
  356. let tmpDownloadDir = tmpDirGenerate.name;
  357. let filePath = path.join(tmpDownloadDir, backup.key);
  358. let file = fs.createWriteStream(filePath);
  359.  
  360. file.on('close', function () {
  361. // now need extract the database file
  362. extract(filePath, tmpDownloadDir, (err, success) => {
  363. if (err) {
  364. return callback(err);
  365. } else {
  366. // remove the file
  367. fs.unlinkSync(filePath);
  368. console.log("Begin restore the database");
  369. let arg = ['--db', config.mongodb, path.join(tmpDownloadDir, config.mongodb)];
  370. let restoreMongoProcess = spawn('mongorestore', arg);
  371.  
  372. restoreMongoProcess.stderr.on('data', (data) => {
  373.  
  374. });
  375.  
  376. restoreMongoProcess.on('exit', (code) => {
  377. console.log("exit with code", code);
  378. if (code === 0) {
  379. tmpDirGenerate.removeCallback();
  380. return callback(null, true);
  381. } else {
  382. return callback(new Error("An error restore the database with code: ", code));
  383. }
  384. });
  385.  
  386. }
  387. });
  388. });
  389. s3.getObject(params).createReadStream().on('error', function (err) {
  390. return callback(err);
  391. }).pipe(file);
  392.  
  393.  
  394. };
  395.  
  396. let restoreSourceCode = (backup) => {
  397.  
  398. };
  399.  
  400. let fullRestore = (backup) => {
  401.  
  402.  
  403. };
  404.  
  405. let doRestore = (key) => {
  406. let backupObject = getObjectStructFromFileName(key);
  407.  
  408. restore = backupObject;
  409.  
  410. if (backupObject.backupType === DATABASE_BACKUP) {
  411. restoreDatabase(backupObject, (err, success) => {
  412. console.log("The restore status: ", err, success);
  413. if (err === null && success) {
  414. // success
  415. restore = null;
  416. }
  417. });
  418. } else if (backupObject.backupType === CODE_BACKUP) {
  419. restoreSourceCode(backupObject);
  420. } else {
  421. fullRestore(backupObject);
  422. }
  423. };
  424.  
  425. let handleError = (res, error = null, code = 503) => {
  426. return res.status(code).send(error);
  427. };
  428.  
  429. /**
  430. * GET homepage.
  431. */
  432.  
  433. app.get('/', function (req, res) {
  434. res.send('Livex Service Application.')
  435. });
  436.  
  437.  
  438. /**
  439. * GET router for /backups list all backups files from s3
  440. */
  441. app.get('/backups', (req, res, next) => {
  442.  
  443.  
  444. let backupItems = [];
  445.  
  446.  
  447. getBackupFiles((err, data) => {
  448.  
  449. if (err) {
  450. return next(err);
  451. } else {
  452. if (typeof data !== 'undefined' && data !== null && typeof data.Contents !== 'undefined' && data.Contents !== null) {
  453. _.each(data.Contents, (item) => {
  454. // let destruct from item file name.
  455. let obj = getObjectStructFromFileName(item.Key);
  456. // we also need file size to display.
  457. obj.size = item.Size;
  458. // add ETag to object in case use later to request to s3
  459. obj.tag = _.replace(item.ETag, /"/g, "");
  460. backupItems.push(obj);
  461. });
  462. }
  463. // let sort the array, latest backups items will show first.
  464. backupItems.sort((a, b) => {
  465. let dateA = new Date(moment(a.createdAt));
  466. let dateB = new Date(moment(b.createdAt));
  467. return dateB - dateA;
  468. });
  469.  
  470. return res.json(backupItems);
  471. }
  472. });
  473.  
  474.  
  475. });
  476.  
  477.  
  478. /**
  479. *
  480. * DELETE Router for /backups/:key
  481. */
  482.  
  483. app.delete('/backups/:key', (req, res, next) => {
  484.  
  485.  
  486. if (typeof req.params.key !== "undefined" && req.params.key !== null) {
  487. let key = req.params.key;
  488. deleteBackupFile(key, (err, data) => {
  489. if (err) {
  490. return handleError(res, err, 404);
  491. } else {
  492. return res.json({success: true});
  493. }
  494. })
  495. } else {
  496. return handleError(res, 'Backup does not exist', 404);
  497.  
  498. }
  499.  
  500.  
  501. });
  502.  
  503. /**
  504. * GET download backup
  505. */
  506.  
  507. app.get('/backups/:key/download', (req, res, next) => {
  508. if (typeof req.params.key !== "undefined" && req.params.key !== null) {
  509. let key = req.params.key;
  510. getDownloadUrl(key, (err, data) => {
  511. if (err) {
  512. return handleError(res, err);
  513. } else {
  514. return res.json({url: data});
  515. }
  516.  
  517. });
  518.  
  519.  
  520. } else {
  521. return handleError(res, 'Backup does not exist', 404);
  522.  
  523. }
  524. });
  525.  
  526.  
  527. /**
  528. * POST router for restore a backup
  529. */
  530.  
  531. app.post('/backups/restore', (req, res) => {
  532.  
  533. let data = req.body;
  534. let force = data.force ? data.force : false;
  535. if (restore !== null && !force) {
  536. // we dont allow restore white having restore processs.
  537. return handleError(res, 'Another restore process is running.', 503);
  538. }
  539. if (typeof data !== "undefined" && data !== null && data.key !== null) {
  540. let key = data.key;
  541. getBackupFileObject(key, (err, data) => {
  542. if (err) {
  543. return handleError(res, err, 404);
  544. } else {
  545. doRestore(key);
  546. return res.json({message: "Starting restore process."});
  547. }
  548. });
  549. } else {
  550. return handleError(res, 'Backup Does not exist', 404);
  551. }
  552.  
  553.  
  554. });
  555.  
  556. /**
  557. * GET Router for /backups/pending
  558. * List all processing backups
  559. */
  560.  
  561. app.get('/backups/pending', (req, res) => {
  562. return res.json(backups);
  563. });
  564.  
  565.  
  566. /**
  567. * GET router for /backups/errors
  568. * Log all backups as errors
  569. */
  570. app.get('/backups/errors', (req, res) => {
  571. return res.json(backupErrors);
  572. });
  573.  
  574. /**
  575. * GET router /backups/errors/clear
  576. * Clear all errors logs
  577. */
  578.  
  579. app.get('/backups/errors/clear', (req, res) => {
  580.  
  581. backupErrors = [];
  582. return res.json(backupErrors);
  583.  
  584. });
  585.  
  586. /**
  587. * POST /backups/create
  588. * create new backup
  589. */
  590. app.post('/backups/create', function (req, res) {
  591.  
  592. let data = req.body;
  593.  
  594. if (typeof data === "undefined" || typeof data.id === "undefined" || data.id === null || typeof data.backupType === "undefined" || data.backupType === null || typeof data.manually === "undefined" || data.manually === null) {
  595. return handleError(res, "An error", 503);
  596. }
  597. let backup = {
  598. id: data.id ? data.id : null,
  599. snapshot: data.snapshot ? data.snapshot : "",
  600. manually: data.manually ? data.manually : false,
  601. backupType: data.backupType ? data.backupType : DATABASE_BACKUP,
  602. createdAt: data.createdAt ? data.createdAt : Date.now(),
  603. updatedAt: data.updatedAt ? data.updatedAt : Date.now(),
  604. status: "pending"
  605. };
  606.  
  607. backups.push(backup);
  608. doBackup(backup);
  609. return res.send(req.body);
  610. });
  611.  
  612.  
  613. /**
  614. * Start app service
  615. */
  616. app.listen(PORT, function () {
  617. console.log('Service is running on port:', PORT)
  618. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement