desaroger/pm2-hooks

View on GitHub
src/Pm2Module.js

Summary

Maintainability
A
2 hrs
Test Coverage
/**
 * Created by desaroger on 25/02/17.
 */

let _ = require('lodash');
let childProcess = require('child_process');
let WebhookServer = require('./WebhookServer');
let { log } = require('./utils');

class Pm2Module {

    constructor(processes = [], options = {}) {
        options.routes = Pm2Module._parseProcesses(processes);
        this.routes = options.routes;
        this.webhookServer = new WebhookServer(options);
    }

    start() {
        return this.webhookServer.start()
            .then(() => {
                let msg = 'Started. Routes:\n';
                _.forOwn(this.routes, (route, name) => {
                    msg += ` - ${name}: ${JSON.stringify(route)}\n`;
                });
                log(msg);
            });
    }

    stop() {
        return this.webhookServer.stop()
            .then(() => {
                log('Stopped.');
            });
    }

    /**
     * Converts an array of PM2 processes to an object structured
     * for the WebhookServer routes. It internally uses the _parseProcess
     * method
     *
     * Example 1:
     * - input:
     * [
     *      { pm2_env: { env_hook: { name: 'api', type: 'bitbucket' } } },
     *      { pm2_env: { env_hook: { name: 'panel', type: 'github' } } }
     * ]
     * - output:
     * {
     *      api: { type: 'bitbucket' },
     *      panel: { type: 'github' }
     * }
     *
     * @param processes
     * @returns {*}
     * @private
     */
    static _parseProcesses(processes) {
        return processes
            .map(p => Pm2Module._parseProcess(p))
            .filter(p => !!p)
            .reduce((routes, app) => {
                routes[app.name] = app;
                delete app.name;
                return routes;
            }, {});
    }

    /**
     * Converts a PM2 process object to an object for WebhookServer
     * route.
     *
     * Example 1:
     * - input: { pm2_env: { env_hook: { name: 'api', type: 'bitbucket' } } }
     * - output: { name: 'api', type: 'bitbucket' }
     * Example 2:
     * - input: { pm2_env: { env_hook: { type: 'bitbucket' } } }
     * - output: { name: 'unknown', type: 'bitbucket' }
     *
     * @param app The Pm2 process
     * @returns {object|null} The route object, or null if invalid
     * @private
     */
    static _parseProcess(app) {
        // Check data
        if (!app || !app.pm2_env) {
            return null;
        }
        let config = app.pm2_env.env_hook;
        if (!config) {
            log(`No options found for "${app.name}" route`);
            return null;
        }
        if (config === true) {
            config = {};
        }

        // Config to WebhookServer route
        let self = this;
        let name = app.name || 'unknown';
        let cwd = config.cwd || app.pm_cwd || app.pm2_env.cwd || app.pm2_env.pm_cwd;
        let commandOptions = Object.assign({}, { cwd }, config.commandOptions || {});
        let route = {
            name,
            type: config.type,
            secret: config.secret,
            method(payload) {
                log(`Parsed payload: ${JSON.stringify(payload)}`);
                try {
                    if (config.command) {
                        log(`Running command: ${config.command}`);
                        self._runCommand(config.command, commandOptions)
                            .catch(e => onError(name, e));
                    }
                } catch (e) {
                    onError(name, e);
                }
            }
        };
        route = cleanObj(route);

        return route;

        function onError(routeName, e) {
            let err = e.message || e;
            log(`Error on "${name}" route: ${err}`, 2);
            throw e;
        }
    }

    /**
     * Runs a line command.
     *
     * @param {String} command The line to execute
     * @param {Object} options The object options
     * @returns {Promise<code>} The code of the error, or a void fulfilled promise
     * @private
     */
    static _runCommand(command, options = {}) {
        _.defaults(options, {
            env: process.env,
            shell: true
        });
        return new Promise((resolve, reject) => {
            let child = childProcess.spawn('eval', [command], options);
            child.on('close', (code) => {
                if (!code) {
                    resolve();
                } else {
                    reject(code);
                }
            });
        });
    }
}

module.exports = Pm2Module;

function cleanObj(obj) {
    return _(obj).omitBy(_.isUndefined).value();
}