tabvn

Untitled

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