<commit_before>#!/usr/bin/env node

'use strict';

var spawn = require('child_process').spawn;
var yaml = require('js-yaml');
var P = require('bluebird');
var fs = P.promisifyAll(require('fs'));
var path = require('path');

// info from the package definition
var pkg;
// info from the service-runner config file
var config;
// target info
var targets;

// the options used in the script
var opts;
// the image name
var img_name;
// the container's name
var name;

// holds the curently running process
var child;

/**
 * Wraps a child process spawn in a promise which resolves
 * when the child process exists.
 *
 * @param {Array} args the command and its arguments to run (uses /usr/bin/env)
 * @param {Boolean} capture whether to capture stdout and return its contents
 * @return {Promise} the promise which is fulfilled once the child exists
 */
function promised_spawn(args, capture) {

    return new P(function(resolve, reject) {
        var options = capture ? undefined : {stdio: 'inherit'};
        var ret = '';
        if(opts.verbose) {
            console.log('# RUNNING: ' + args.join(' ') + "\n" +
                '  (in ' + process.cwd() + ')');
        }
        child = spawn('/usr/bin/env', args, options);
        if(capture) {
            child.stdout.on('data', function(data) {
                ret += data.toString();
            });
        }
        child.on('close', function() {
            child = undefined;
            ret = ret.trim();
            if(ret === '') { ret = undefined; }
            resolve(ret);
        });
    });

}

/**
 * Generates the Dockerfile used to build the image and start the container
 *
 * @return {Promise} the promise which creates the image file
 */
function create_docker_file() {

    var contents = '';
    var base_img;
    var extra_pkgs = ['nodejs', 'nodejs-legacy', 'npm', 'git'];

    // set some defaults
    if(!pkg.deploy) {
        pkg.deploy = {};
    }
    if(!pkg.deploy.target) {
        pkg.deploy.target = 'ubuntu';
    }
    if(!pkg.deploy.dependencies) {
        pkg.deploy.dependencies = {};
    }
    if(!pkg.deploy.dependencies._all) {
        pkg.deploy.dependencies._all = [];
    }

    // set the deploy target
    base_img = targets[pkg.deploy.target];
    // get any additional packages that need to be installed
    Object.keys(pkg.deploy.dependencies).forEach(function(sys) {
        if(sys !== '_all' && (sys === base_img || (new RegExp(sys)).test(base_img))) {
            Array.prototype.push.apply(extra_pkgs, pkg.deploy.dependencies[sys]);
        }
    });
    Array.prototype.push.apply(extra_pkgs, pkg.deploy.dependencies._all);

    if(!base_img || base_img === '') {
        console.error('ERROR: You must specify a valid target!');
        console.error('ERROR: Check the deploy stanza in package.json and targets.yaml');
        process.exit(2);
    }

    contents = 'FROM ' + base_img + "\n" +
        'RUN apt-get update && apt-get install -y ' + extra_pkgs.join(' ') +
        " && rm -rf /var/lib/apt/lists/*\n";

    if(!opts.deploy) {
        contents += "RUN mkdir /opt/service\n" +
            "ADD . /opt/service\n" +
            "WORKDIR /opt/service\n" +
            "RUN npm install\n";
    }

    if(opts.uid !== 0) {
        contents += "RUN groupadd -g " + opts.gid + " -r rungroup && " +
        "useradd -m -r -g rungroup -u " + opts.uid + " runuser\n" +
        "USER runuser\n";
    }

    if(opts.deploy) {
        contents += 'CMD /usr/bin/npm install --production && /usr/bin/npm install heapdump';
    } else if(opts.tests) {
        contents += 'CMD ["/usr/bin/npm", "test"]';
    } else if(opts.coverage) {
        contents += 'CMD ["/usr/bin/npm", "run-script", "coverage"]';
    } else {
        contents += 'CMD ["/usr/bin/npm", "start"]';
    }

    return fs.writeFileAsync('Dockerfile', contents);

}

/**
 * Spawns a docker process which (re)builds the image
 *
 * @return {Promise} the promise starting the build
 */
function build_img() {

    return promised_spawn(['docker', 'build', '-t', img_name, '.']);

}

/**
 * Starts the container and returns once it has finished executing
 *
 * @param {Array} args the array of extra parameters to pass, optional
 * @return {Promise} the promise starting the container
 */
function start_container(args) {

    var cmd = ['docker', 'run', '--name', name, '--rm'];

    // add the extra args as well
    if(args && Array.isArray(args)) {
        Array.prototype.push.apply(cmd, args);
    }

    // list all of the ports defined in the config file
    config.services.forEach(function(srv) {
        srv.conf = srv.conf || {};
        srv.conf.port = srv.conf.port || 8888;
        cmd.push('-p', srv.conf.port + ':' + srv.conf.port);
    });

    // append the image name to create a container from
    cmd.push(img_name);

    // ok, start the container
    return promised_spawn(cmd);

}

/**
 * Updates the deploy repository to current master and
 * rebuilds the node modules, committing and git-review-ing
 * the result
 */
function update_deploy() {

    function promised_git(args) {
        var args_arr = ['git'];
        Array.prototype.push.apply(args_arr, args);
        return promised_spawn(args_arr, true);
    }

    function chained_pgit(args) {
        var arg = args.shift();
        if(!arg) {
            return P.resolve();
        }
        return promised_git(arg)
        .then(function(data) {
            if(args.length === 0) {
                return P.resolve(data);
            }
            return chained_pgit(args);
        });
    }

    // check if there is an alternative repo name defined
    return promised_git(['config', 'deploy.name'])
    .then(function(name) {
        opts.name = name ? name : pkg.name;
        // we need to CHDIR into the deploy dir for subsequent operations
        process.chdir(opts.dir);
        return chained_pgit([
            // make sure we are on master
            ['checkout', 'master'],
            // fetch any possible updates
            ['fetch', 'origin'],
            // work on a topic branch
            ['checkout', '-B', 'sync-repo', 'origin/master'],
            // check if the submodule is present
            ['submodule', 'status']
        ]);
    }).then(function(list) {
        if(list) {
            // the submodule is present
            opts.submodule = list.split(' ')[1];
            // update it fully
            return promised_git(['submodule', 'update', '--init'])
            .then(function() {
                process.chdir(opts.dir + '/' + opts.submodule);
                return chained_pgit([
                    // fetch new commits
                    ['fetch', 'origin'],
                    // inspect what has changed
                    ['diff', '--name-only', 'origin/master']
                ]).then(function(changes) {
                    if(/package\.json/.test(changes)) {
                        // package.json has changed, so we need
                        // to rebuild the node_modules directory
                        opts.need_build = true;
                    }
                    // get the SHA1 of the latest commit on master
                    return promised_git(['rev-parse', '--short', 'origin/master']);
                }).then(function(short_sha1) {
                    opts.commit_msg = 'Update ' + opts.name + ' to ' + short_sha1 + "\n\n";
                    // get a nice list of commits included in the change
                    return promised_git(['log', '..origin/master', '--oneline', '--no-merges', '--reverse', '--color=never']);
                }).then(function(logs) {
                    if(!logs && !opts.need_build) {
                        // no updates have happened, nothing to do here any more but clean up
                        // go back to the root dir
                        process.chdir(opts.dir);
                        // and get back to master
                        return promised_git(['checkout', 'master'])
                        .then(function() {
                            console.log('The deploy repository is up to date already, exiting.');
                            process.exit(0);
                        });
                    } else if(logs) {
                        logs += "\n";
                    } else if(!logs) {
                        logs = '';
                    }
                    opts.commit_msg += "List of changes:\n" + logs;
                    return promised_git(['checkout', 'origin/master']);
                }).then(function() {
                    // go back to the root dir
                    process.chdir(opts.dir);
                    // add the submodule changes
                    return promised_git(['add', opts.submodule]);
                });
            });
        } else {
            // no submodule, need to add it
            opts.submodule = 'src';
            opts.need_build = true;
            opts.commit_msg = 'Initial import of ' + opts.name;
            return promised_git(['submodule', 'add', 'https://gerrit.wikimedia.org/r/mediawiki/services/' + opts.name, opts.submodule]);
        }
    }).then(function() {
        // make sure the package.json symlink is in place
        return fs.symlinkAsync(opts.submodule + '/package.json', 'package.json')
        .catch(function() {}).then(function() {
            return promised_git(['add', 'package.json']);
        });
    }).then(function() {
        if(!opts.need_build) {
            return;
        }
        // update the commit message
        if(!/^initial/i.test(opts.commit_msg)) {
            opts.commit_msg += "xxxxxxx Update node module dependencies\n";
        }
        // a rebuild is needed, start by removing the existing modules
        return promised_git(['rm', '-r', 'node_modules'])
        .then(function() {
            return promised_spawn(['rm', '-rf', 'node_modules'], true);
        }).then(function() {
            // start the container which builds the modules
            return start_container(['-v', opts.dir + ':/opt/service', '-w', '/opt/service']);
        }).then(function() {
            // remove unnecessary files
            return promised_spawn([
                'find',
                'node_modules/',
                '-regextype',
                'posix-egrep',
                '-iregex',
                "(.*\\.git.*|.*\\.md|.*\\.txt|.*readme|.*licence)",
                '-exec', 'rm', '-rf', '{}', ';'
            ], true);
        }).then(function() {
            // add the built submodules
            return promised_git(['add', 'node_modules']);
        });
    }).then(function() {
        // commit the changes
        return promised_git(['commit', '-m', opts.commit_msg]);
    }).then(function() {
        if(!opts.review) {
            console.log("\n\nChanges are sitting in the sync-repo branch in");
            console.log(opts.dir + ' with the commit:');
            console.log(opts.commit_msg);
            return;
        }
        return chained_pgit([
            // send them for review
            ['review', '-R'],
            // get back to master
            ['checkout', 'master']
        ]).then(function() {
            console.log("\n\nChanges sent to Gerrit for review!");
        });
    });

}

/**
 * Determines the UID and GID to run under in the container
 *
 * @return {Promise} a promise resolving when the check is done
 */
function get_uid() {

    if(opts.deploy) {
        // get the deploy repo location
        return promised_spawn(['git', 'config', 'deploy.dir'], true)
        .then(function(dir) {
            if(!dir) {
                console.error('ERROR: You must set the location of the deploy repo!');
                console.error('ERROR: Use git config deploy.dir /full/path/to/deploy/dir');
                process.exit(2);
            }
            opts.dir = dir;
            // make sure that the dir exists and it is a git repo
            return fs.statAsync(dir + '/.git');
        }).then(function(stat) {
            opts.uid = stat.uid;
            opts.gid = stat.gid;
        }).catch(function(err) {
            console.error('ERROR: The deploy repo dir ' + opts.dir + ' does not exist or is not a git repo!');
            process.exit(3);
        });
    }

    // get the uid/gid from statting package.json
    return fs.statAsync('package.json')
    .then(function(stat) {
        opts.uid = stat.uid;
        opts.gid = stat.gid;
    }).catch(function(err) {
        console.error('ERROR: package.json does not exist!');
        process.exit(4);
    });

}

/**
 * Main process signal handler
 */
function sig_handle() {
    if(child) {
        child.kill('SIGINT');
    }
}

function main(options, configuration) {

    // set up working options
    if(options.hasOwnProperty('forceBuild')) {
        // called from service-runner, translate
        opts = {
            tests: options.dockerTest,
            coverage: false,
            deploy: options.build,
            verbose: false,
            need_build: options.forceBuild,
            review: options.sendReview,
            path: options.basePath
        };
    } else {
        // called from the command line
        opts = options;
    }

    // config and package info
    config = configuration;
    if(!config) {
        config = yaml.safeLoad(fs.readFileSync(path.join(opts.path, 'config.yaml')));
    }
    pkg = config.package;
    if(!pkg) {
        pkg = require(path.join(opts.path, 'package.json'));
    }

    // target info
    try {
        targets = yaml.safeLoad(fs.readFileSync(path.join(opts.path, 'targets.yaml')));
    } catch(e) {
        // no such file or wrong format,
        // set the defaults
        targets = {
            debian: 'buildpack-deps:jessie',
            ubuntu: 'ubuntu:14.04'
        };
    }

    // use the package's name as the image name
    img_name = pkg.name;
    if(opts.deploy) { img_name += 'deploy'; }
    // the container's name
    name = pkg.name + '-' + Date.now() + '-' + Math.floor(Math.random() * 1000);

    // trap exit signals
    process.on('SIGINT', sig_handle);
    process.on('SIGTERM', sig_handle);

    // change the dir
    process.chdir(opts.path);

    // start the process
    return get_uid()
    .then(create_docker_file)
    .then(build_img)
    .then(function() {
        if(opts.deploy) {
            return update_deploy();
        } else {
            return start_container();
        }
    });

}

if(module.parent === null) {

    var opts = {
        tests: false,
        coverage: false,
        deploy: false,
        verbose: false,
        need_build: false,
        review: false,
        path: path.resolve(__dirname, '..')
    };

    // check for command-line args
    var args = process.argv.slice(2);
    var arg;
    while((arg = args.shift()) !== undefined) {
        switch(arg) {
            case '-t':
            case '--test':
                opts.tests = true;
                break;
            case '-c':
            case '--cover':
                opts.coverage = true;
                break;
            case '-b':
            case '--build-deploy':
                opts.need_build = true;
                /* falls through */
            case '-d':
            case '--update-deploy':
                opts.deploy = true;
                break;
            case '-v':
            case '--verbose':
                opts.verbose = true;
                break;
            default:
                console.log('This is a utility script for starting service containers using docker.');
                console.log('Usage: ' + process.argv.slice(0, 2).join(' ') + ' [OPTIONS]');
                console.log('Options are:');
                console.log('  -t, --test           instead of starting the service, run the tests');
                console.log('  -c, --cover          run the tests and report the coverage info');
                console.log('  -d, --update-deploy  update the deploy repo');
                console.log('  -b, --build-deploy   update the deploy repo and force module rebuild');
                console.log('  -v, --verbose        output the commands being run');
                process.exit(/^-(h|-help)/.test(arg) ? 0 : 1);
        }
    }

    // start the process
    main(opts);

} else {

    module.exports = main;

}

<commit_msg>Remove command-line args from lib/docker.js<commit_after>#!/usr/bin/env node

'use strict';

var spawn = require('child_process').spawn;
var yaml = require('js-yaml');
var P = require('bluebird');
var fs = P.promisifyAll(require('fs'));
var path = require('path');

// info from the package definition
var pkg;
// info from the service-runner config file
var config;
// target info
var targets;

// the options used in the script
var opts;
// the image name
var img_name;
// the container's name
var name;

// holds the curently running process
var child;

/**
 * Wraps a child process spawn in a promise which resolves
 * when the child process exists.
 *
 * @param {Array} args the command and its arguments to run (uses /usr/bin/env)
 * @param {Boolean} capture whether to capture stdout and return its contents
 * @return {Promise} the promise which is fulfilled once the child exists
 */
function promised_spawn(args, capture) {

    return new P(function(resolve, reject) {
        var options = capture ? undefined : {stdio: 'inherit'};
        var ret = '';
        if(opts.verbose) {
            console.log('# RUNNING: ' + args.join(' ') + "\n" +
                '  (in ' + process.cwd() + ')');
        }
        child = spawn('/usr/bin/env', args, options);
        if(capture) {
            child.stdout.on('data', function(data) {
                ret += data.toString();
            });
        }
        child.on('close', function() {
            child = undefined;
            ret = ret.trim();
            if(ret === '') { ret = undefined; }
            resolve(ret);
        });
    });

}

/**
 * Generates the Dockerfile used to build the image and start the container
 *
 * @return {Promise} the promise which creates the image file
 */
function create_docker_file() {

    var contents = '';
    var base_img;
    var extra_pkgs = ['nodejs', 'nodejs-legacy', 'npm', 'git'];

    // set some defaults
    if(!pkg.deploy) {
        pkg.deploy = {};
    }
    if(!pkg.deploy.target) {
        pkg.deploy.target = 'ubuntu';
    }
    if(!pkg.deploy.dependencies) {
        pkg.deploy.dependencies = {};
    }
    if(!pkg.deploy.dependencies._all) {
        pkg.deploy.dependencies._all = [];
    }

    // set the deploy target
    base_img = targets[pkg.deploy.target];
    // get any additional packages that need to be installed
    Object.keys(pkg.deploy.dependencies).forEach(function(sys) {
        if(sys !== '_all' && (sys === base_img || (new RegExp(sys)).test(base_img))) {
            Array.prototype.push.apply(extra_pkgs, pkg.deploy.dependencies[sys]);
        }
    });
    Array.prototype.push.apply(extra_pkgs, pkg.deploy.dependencies._all);

    if(!base_img || base_img === '') {
        console.error('ERROR: You must specify a valid target!');
        console.error('ERROR: Check the deploy stanza in package.json and targets.yaml');
        process.exit(2);
    }

    contents = 'FROM ' + base_img + "\n" +
        'RUN apt-get update && apt-get install -y ' + extra_pkgs.join(' ') +
        " && rm -rf /var/lib/apt/lists/*\n";

    if(!opts.deploy) {
        contents += "RUN mkdir /opt/service\n" +
            "ADD . /opt/service\n" +
            "WORKDIR /opt/service\n" +
            "RUN npm install\n";
    }

    if(opts.uid !== 0) {
        contents += "RUN groupadd -g " + opts.gid + " -r rungroup && " +
        "useradd -m -r -g rungroup -u " + opts.uid + " runuser\n" +
        "USER runuser\n";
    }

    if(opts.deploy) {
        contents += 'CMD /usr/bin/npm install --production && /usr/bin/npm install heapdump';
    } else if(opts.tests) {
        contents += 'CMD ["/usr/bin/npm", "test"]';
    } else if(opts.coverage) {
        contents += 'CMD ["/usr/bin/npm", "run-script", "coverage"]';
    } else {
        contents += 'CMD ["/usr/bin/npm", "start"]';
    }

    return fs.writeFileAsync('Dockerfile', contents);

}

/**
 * Spawns a docker process which (re)builds the image
 *
 * @return {Promise} the promise starting the build
 */
function build_img() {

    return promised_spawn(['docker', 'build', '-t', img_name, '.']);

}

/**
 * Starts the container and returns once it has finished executing
 *
 * @param {Array} args the array of extra parameters to pass, optional
 * @return {Promise} the promise starting the container
 */
function start_container(args) {

    var cmd = ['docker', 'run', '--name', name, '--rm'];

    // add the extra args as well
    if(args && Array.isArray(args)) {
        Array.prototype.push.apply(cmd, args);
    }

    // list all of the ports defined in the config file
    config.services.forEach(function(srv) {
        srv.conf = srv.conf || {};
        srv.conf.port = srv.conf.port || 8888;
        cmd.push('-p', srv.conf.port + ':' + srv.conf.port);
    });

    // append the image name to create a container from
    cmd.push(img_name);

    // ok, start the container
    return promised_spawn(cmd);

}

/**
 * Updates the deploy repository to current master and
 * rebuilds the node modules, committing and git-review-ing
 * the result
 */
function update_deploy() {

    function promised_git(args) {
        var args_arr = ['git'];
        Array.prototype.push.apply(args_arr, args);
        return promised_spawn(args_arr, true);
    }

    function chained_pgit(args) {
        var arg = args.shift();
        if(!arg) {
            return P.resolve();
        }
        return promised_git(arg)
        .then(function(data) {
            if(args.length === 0) {
                return P.resolve(data);
            }
            return chained_pgit(args);
        });
    }

    // check if there is an alternative repo name defined
    return promised_git(['config', 'deploy.name'])
    .then(function(name) {
        opts.name = name ? name : pkg.name;
        // we need to CHDIR into the deploy dir for subsequent operations
        process.chdir(opts.dir);
        return chained_pgit([
            // make sure we are on master
            ['checkout', 'master'],
            // fetch any possible updates
            ['fetch', 'origin'],
            // work on a topic branch
            ['checkout', '-B', 'sync-repo', 'origin/master'],
            // check if the submodule is present
            ['submodule', 'status']
        ]);
    }).then(function(list) {
        if(list) {
            // the submodule is present
            opts.submodule = list.split(' ')[1];
            // update it fully
            return promised_git(['submodule', 'update', '--init'])
            .then(function() {
                process.chdir(opts.dir + '/' + opts.submodule);
                return chained_pgit([
                    // fetch new commits
                    ['fetch', 'origin'],
                    // inspect what has changed
                    ['diff', '--name-only', 'origin/master']
                ]).then(function(changes) {
                    if(/package\.json/.test(changes)) {
                        // package.json has changed, so we need
                        // to rebuild the node_modules directory
                        opts.need_build = true;
                    }
                    // get the SHA1 of the latest commit on master
                    return promised_git(['rev-parse', '--short', 'origin/master']);
                }).then(function(short_sha1) {
                    opts.commit_msg = 'Update ' + opts.name + ' to ' + short_sha1 + "\n\n";
                    // get a nice list of commits included in the change
                    return promised_git(['log', '..origin/master', '--oneline', '--no-merges', '--reverse', '--color=never']);
                }).then(function(logs) {
                    if(!logs && !opts.need_build) {
                        // no updates have happened, nothing to do here any more but clean up
                        // go back to the root dir
                        process.chdir(opts.dir);
                        // and get back to master
                        return promised_git(['checkout', 'master'])
                        .then(function() {
                            console.log('The deploy repository is up to date already, exiting.');
                            process.exit(0);
                        });
                    } else if(logs) {
                        logs += "\n";
                    } else if(!logs) {
                        logs = '';
                    }
                    opts.commit_msg += "List of changes:\n" + logs;
                    return promised_git(['checkout', 'origin/master']);
                }).then(function() {
                    // go back to the root dir
                    process.chdir(opts.dir);
                    // add the submodule changes
                    return promised_git(['add', opts.submodule]);
                });
            });
        } else {
            // no submodule, need to add it
            opts.submodule = 'src';
            opts.need_build = true;
            opts.commit_msg = 'Initial import of ' + opts.name;
            return promised_git(['submodule', 'add', 'https://gerrit.wikimedia.org/r/mediawiki/services/' + opts.name, opts.submodule]);
        }
    }).then(function() {
        // make sure the package.json symlink is in place
        return fs.symlinkAsync(opts.submodule + '/package.json', 'package.json')
        .catch(function() {}).then(function() {
            return promised_git(['add', 'package.json']);
        });
    }).then(function() {
        if(!opts.need_build) {
            return;
        }
        // update the commit message
        if(!/^initial/i.test(opts.commit_msg)) {
            opts.commit_msg += "xxxxxxx Update node module dependencies\n";
        }
        // a rebuild is needed, start by removing the existing modules
        return promised_git(['rm', '-r', 'node_modules'])
        .then(function() {
            return promised_spawn(['rm', '-rf', 'node_modules'], true);
        }).then(function() {
            // start the container which builds the modules
            return start_container(['-v', opts.dir + ':/opt/service', '-w', '/opt/service']);
        }).then(function() {
            // remove unnecessary files
            return promised_spawn([
                'find',
                'node_modules/',
                '-regextype',
                'posix-egrep',
                '-iregex',
                "(.*\\.git.*|.*\\.md|.*\\.txt|.*readme|.*licence)",
                '-exec', 'rm', '-rf', '{}', ';'
            ], true);
        }).then(function() {
            // add the built submodules
            return promised_git(['add', 'node_modules']);
        });
    }).then(function() {
        // commit the changes
        return promised_git(['commit', '-m', opts.commit_msg]);
    }).then(function() {
        if(!opts.review) {
            console.log("\n\nChanges are sitting in the sync-repo branch in");
            console.log(opts.dir + ' with the commit:');
            console.log(opts.commit_msg);
            return;
        }
        return chained_pgit([
            // send them for review
            ['review', '-R'],
            // get back to master
            ['checkout', 'master']
        ]).then(function() {
            console.log("\n\nChanges sent to Gerrit for review!");
        });
    });

}

/**
 * Determines the UID and GID to run under in the container
 *
 * @return {Promise} a promise resolving when the check is done
 */
function get_uid() {

    if(opts.deploy) {
        // get the deploy repo location
        return promised_spawn(['git', 'config', 'deploy.dir'], true)
        .then(function(dir) {
            if(!dir) {
                console.error('ERROR: You must set the location of the deploy repo!');
                console.error('ERROR: Use git config deploy.dir /full/path/to/deploy/dir');
                process.exit(2);
            }
            opts.dir = dir;
            // make sure that the dir exists and it is a git repo
            return fs.statAsync(dir + '/.git');
        }).then(function(stat) {
            opts.uid = stat.uid;
            opts.gid = stat.gid;
        }).catch(function(err) {
            console.error('ERROR: The deploy repo dir ' + opts.dir + ' does not exist or is not a git repo!');
            process.exit(3);
        });
    }

    // get the uid/gid from statting package.json
    return fs.statAsync('package.json')
    .then(function(stat) {
        opts.uid = stat.uid;
        opts.gid = stat.gid;
    }).catch(function(err) {
        console.error('ERROR: package.json does not exist!');
        process.exit(4);
    });

}

/**
 * Main process signal handler
 */
function sig_handle() {
    if(child) {
        child.kill('SIGINT');
    }
}

function main(options, configuration) {

    opts = {
        tests: options.dockerTest,
        coverage: false,
        deploy: options.buildDeploy,
        verbose: options.verbose,
        need_build: options.buildDeploy && options.force,
        review: options.sendReview,
        path: options.basePath
    };

    // config and package info
    config = configuration;
    if(!config) {
        config = yaml.safeLoad(fs.readFileSync(path.join(opts.path, 'config.yaml')));
    }
    pkg = config.package;
    if(!pkg) {
        pkg = require(path.join(opts.path, 'package.json'));
    }

    // target info
    try {
        targets = yaml.safeLoad(fs.readFileSync(path.join(opts.path, 'targets.yaml')));
    } catch(e) {
        // no such file or wrong format,
        // set the defaults
        targets = {
            debian: 'buildpack-deps:jessie',
            ubuntu: 'ubuntu:14.04'
        };
    }

    // use the package's name as the image name
    img_name = pkg.name;
    if(opts.deploy) { img_name += 'deploy'; }
    // the container's name
    name = pkg.name + '-' + Date.now() + '-' + Math.floor(Math.random() * 1000);

    // trap exit signals
    process.on('SIGINT', sig_handle);
    process.on('SIGTERM', sig_handle);

    // change the dir
    process.chdir(opts.path);

    // start the process
    return get_uid()
    .then(create_docker_file)
    .then(build_img)
    .then(function() {
        if(opts.deploy) {
            return update_deploy();
        } else {
            return start_container();
        }
    });

}

module.exports = main;

<|endoftext|>


<commit_before>'use strict';

var noIndex = require('./noIndex.js');

function Driver(options) {
  var self = this;

  if (!(self instanceof Driver)) {
    return new Driver(options);
  }

  if (!noIndex.util.isObject()) {
    options = {};
  }

  self.prefix = {};
  self.key_get = options.get;
  self.key_set = options.set;
  self.key_del = options.del;
  self.key_hget = options.hget;
  self.key_hset = options.hset;
  self.key_hdel = options.hdel;
}

Driver.isDriver = function(arg) {
  return (arg instanceof Driver);
};

Driver.prototype.get = function(key) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_get)) {
      self.key_get(resolve, reject, self, key);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.set = function(key, value) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_set)) {
      self.key_set(resolve, reject, self, key, value);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.del = function(key) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_del)) {
      self.key_del(resolve, reject, self, key);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.hget = function(key, hash) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_hget)) {
      self.key_hget(resolve, reject, self, key, hash);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.hset = function(key, hash, value) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_hset)) {
      self.key_hset(resolve, reject, self, key, hash, value);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.hdel = function(key, hash) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_hdel)) {
      self.key_hdel(resolve, reject, self, key, hash);
    } else {
      reject(null);
    }
  });
};

module.exports = Driver;<commit_msg>Update driver<commit_after>'use strict';

var noIndex = require('./noIndex.js');

function Driver(options) {
  var self = this;

  if (!(self instanceof Driver)) {
    return new Driver(options);
  }

  if (!noIndex.util.isObject(options)) {
    options = {};
  }

  self.key_get = options.get;
  self.key_set = options.set;
  self.key_del = options.del;
  self.key_hget = options.hget;
  self.key_hset = options.hset;
  self.key_hdel = options.hdel;
}

Driver.isDriver = function(arg) {
  return (arg instanceof Driver);
};

Driver.prototype.get = function(key, options) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_get)) {
      self.key_get(resolve, reject, self, key, options);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.set = function(key, value, options) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_set)) {
      self.key_set(resolve, reject, self, key, value, options);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.del = function(key, options) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_del)) {
      self.key_del(resolve, reject, self, key, options);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.hget = function(key, hash, options) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_hget)) {
      self.key_hget(resolve, reject, self, key, hash, options);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.hset = function(key, hash, value, options) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_hset)) {
      self.key_hset(resolve, reject, self, key, hash, value, options);
    } else {
      reject(null);
    }
  });
};

Driver.prototype.hdel = function(key, hash, options) {
  var self = this;

  return noIndex.$q(function(resolve, reject) {
    if (noIndex.util.isFunction(self.key_hdel)) {
      self.key_hdel(resolve, reject, self, key, hash, options);
    } else {
      reject(null);
    }
  });
};

module.exports = Driver;<|endoftext|>


<commit_before>// ext. libs
var http = require('http');
var Q = require('Q');

module.exports = function (WebDriver) {

  /**
   *
   */

  var Driver = {};

  /**
   *
   */

  Driver.parseUrl = function (url, options) {
    Object.keys(options).forEach(function (option) {
      url = url.replace(':' + option, options[option]);
    }.bind(this));

    return url;
  };

  /**
   *
   */

  Driver.generateParamset = function (requestedParams, providedParams) {
    var params = {};

    if (!requestedParams) {
      return params;
    }

    requestedParams.forEach(function (param, idx) {
      params[param] = providedParams[idx];
    });

    return params;
  };

  /**
   *
   */

  Driver.generateBody = function (options, cb, wd, params) {
    if (!cb) {
      return JSON.stringify({desiredCapabilities: {browserName: 'chrome', version: '', platform: 'ANY'}});
    }

    return JSON.stringify(cb.call(wd, params));;
  };

  /**
   *
   */

  Driver.generateRequestOptions = function (hostname, port, prefix, url, method, body) {
        var options = {
            hostname: hostname,
            port: port,
            path: prefix + url,
            method: method,
            headers: {
                "Content-Type": "application/json",
                "Content-Length": body.length
            }
        };

    return options;
  };

  /**
   *
   */

  Driver.addCommand = function (remote) {
    if (remote.name) {
      WebDriver.prototype[remote.name] = function () {
        var deferred = Q.defer();

        var params = Driver.generateParamset(remote.params, arguments);
        var body = Driver.generateBody({}, remote.onRequest, this, params);
        var options = Driver.generateRequestOptions(this.opts.host, this.opts.port, this.opts.path, Driver.parseUrl(remote.url, this.options), remote.method, body);
        //var options = Driver.generateRequestOptions('localhost', 9000, '/', Driver.parseUrl(remote.url, this.options), remote.method, body);
        var req = new http.ClientRequest(options);

        var data = '';

        req.on('response', function (response) {
            response.on('data', function (chunk) { data += chunk; });
            response.on('end', function () {
              if (response.statusCode === 500) {
                if (remote.onError) {
                  remote.onError.call(this, response, remote, options, deferred, data);
                }
              }
              if (response.statusCode === 200 || response.statusCode === 303) {
                if (remote.onResponse) {
                  remote.onResponse.call(this, response, remote, options, deferred, data);
                } else {
                  deferred.resolve(data);
                }
              }
            }.bind(this));
        }.bind(this));

        req.end(body);
        return deferred.promise;
      };
    }
  };

  return Driver;
}
<commit_msg>Fixed type in require statement<commit_after>// ext. libs
var http = require('http');
var Q = require('q');

module.exports = function (WebDriver) {

  /**
   *
   */

  var Driver = {};

  /**
   *
   */

  Driver.parseUrl = function (url, options) {
    Object.keys(options).forEach(function (option) {
      url = url.replace(':' + option, options[option]);
    }.bind(this));

    return url;
  };

  /**
   *
   */

  Driver.generateParamset = function (requestedParams, providedParams) {
    var params = {};

    if (!requestedParams) {
      return params;
    }

    requestedParams.forEach(function (param, idx) {
      params[param] = providedParams[idx];
    });

    return params;
  };

  /**
   *
   */

  Driver.generateBody = function (options, cb, wd, params) {
    if (!cb) {
      return JSON.stringify({desiredCapabilities: {browserName: 'chrome', version: '', platform: 'ANY'}});
    }

    return JSON.stringify(cb.call(wd, params));;
  };

  /**
   *
   */

  Driver.generateRequestOptions = function (hostname, port, prefix, url, method, body) {
        var options = {
            hostname: hostname,
            port: port,
            path: prefix + url,
            method: method,
            headers: {
                "Content-Type": "application/json",
                "Content-Length": body.length
            }
        };

    return options;
  };

  /**
   *
   */

  Driver.addCommand = function (remote) {
    if (remote.name) {
      WebDriver.prototype[remote.name] = function () {
        var deferred = Q.defer();

        var params = Driver.generateParamset(remote.params, arguments);
        var body = Driver.generateBody({}, remote.onRequest, this, params);
        var options = Driver.generateRequestOptions(this.opts.host, this.opts.port, this.opts.path, Driver.parseUrl(remote.url, this.options), remote.method, body);
        //var options = Driver.generateRequestOptions('localhost', 9000, '/', Driver.parseUrl(remote.url, this.options), remote.method, body);
        var req = new http.ClientRequest(options);

        var data = '';

        req.on('response', function (response) {
            response.on('data', function (chunk) { data += chunk; });
            response.on('end', function () {
              if (response.statusCode === 500) {
                if (remote.onError) {
                  remote.onError.call(this, response, remote, options, deferred, data);
                }
              }
              if (response.statusCode === 200 || response.statusCode === 303) {
                if (remote.onResponse) {
                  remote.onResponse.call(this, response, remote, options, deferred, data);
                } else {
                  deferred.resolve(data);
                }
              }
            }.bind(this));
        }.bind(this));

        req.end(body);
        return deferred.promise;
      };
    }
  };

  return Driver;
}
<|endoftext|>


<commit_before>var path = require('path');
var roost = require('roost');

// ---

var providers = require(path.join(__dirname, 'providers.js'));
var logger = require(path.join(__dirname, 'logger.js'));

// ---

function shell(provider, name, node, manifest, callback) {
	provider.shellSpec(name, node, function (err, spec) {
		if (err) {
			return callback(err);
		}

		logger.debug('shell spec is', spec);

		if (!spec.match(/^ssh:/)) {
			return callback(new Error('unsupported shell specification: ' + spec));
		}

		var connection = new roost.ssh.Connection({meta:{location:manifest.meta.location}});

		logger.silly('entering interactive session');

		connection.connect(spec, function (err) {
			if (err) {
				return callback(err);
			}

			connection.shell(function (err) {
				if (err) {
					return callback(err);
				}

				connection.disconnect({}, function (err) {
					if (err) {
						return callback(err);
					}

					return callback();
				});
			});
		});
	});
}

function provision(provider, name, node, manifest, callback) {
	if (!node.hasOwnProperty('roost')) {
		return callback(new Error('no roost configuration defined'));
	}

	roost.logger.setGlobalLevel(logger.level);
	roost.logger.setGlobalColorization(logger.colorize);

	var roostManifest = node.roost;

	if (typeof(roostManifest) == 'string' || roostManifest instanceof String) {
		try {
			roostManifest = roost.manifest.load(path.resolve(path.dirname(manifest.meta.location), roostManifest));
		} catch (e) {
			return callback(e);
		}
	} else {
		roostManifest.meta = {
			location: manifest.meta.location
		};
	}

	logger.debug('start provision with roost manifest', roostManifest);

	var roostPlugins;

	try {
		roostPlugins = roost.plugins.obtain(roostManifest);
	} catch (e) {
		return callback(e);
	}

	if (roostPlugins.length > 0) {
		logger.debug('loaded roost plugins', roostPlugins);
	} else {
		logger.debug('no roost plugins loaded');
	}

	provider.shellSpec(name, node, function (err, spec) {
		if (err) {
			return callback(err);
		}

		logger.debug('shell spec is', spec);

		var roostTarget;

		try {
			roostTarget = roost.targets.instance(spec, roostManifest);
		} catch (e) {
			return callback(e);
		}

		try {
			roost.engine.launch({options:{}, argv:[]}, roostManifest, roostPlugins, roostTarget, callback);
		} catch (e) {
			return callback(e);
		}
	});
}

function status(provider, name, node, manifest, callback) {
	provider.status(name, node, function (err, state) {
		if (err) {
			return callback(err);
		}

		console.log(name + ':', state);

		callback(null);
	});
}

function boot(provider, name, node, manifest, callback) {
	provider.boot(name, node, function (err, state) {
		if (err) {
			return callback(err);
		}

		exports.provision(provider, name, node, manifest, callback);
	});
}

function halt(provider, name, node, manifest, callback) {
	provider.halt(name, node, function (err, state) {
		if (err) {
			return callback(err);
		}

		exports.status(provider, name, node, manifest, callback);
	});
}

function reload(provider, name, node, manifest, callback) {
	exports.halt(provider, name, node, manifest, function (err) {
		if (err) {
			return callback(err);
		}

		exports.boot(provider, name, node, manifest, callback);
	});
}

// ---

function launch(opt, manifest, plugins, provider, action, callback) {
	(plugins || []).forEach(function (plugin) {
		plugin.vortex(opt, manifest, provider, action);
	});

	if (!(action in exports) || action == 'launch') {
		throw new Error('action ' + action + ' not recognized');
	}

	if (!manifest.hasOwnProperty('nodes')) {
		return callback(null);
	}

	var map = {};

	Object.keys(manifest.nodes).forEach(function (name) {
		var node = manifest.nodes[name];
		var nodeProvider = provider;

		if (!nodeProvider) {
			nodeProvider = node.hasOwnProperty('default_provider') ? node.default_provider : null;

			if (nodeProvider) {
				nodeProvider = providers.instance(nodeProvider, manifest);
			}
		}

		if (!nodeProvider) {
			nodeProvider = manifest.hasOwnProperty('default_provider') ? manifest.default_provider : null;

			if (nodeProvider) {
				nodeProvider = providers.instance(nodeProvider, manifest);
			}
		}

		if (!nodeProvider) {
			nodeProvider = providers.instance('VirtualBox', manifest);
		}

		map[name] = {
			nodeProvider: nodeProvider,
			node: node
		};
	});

	var notCompleted = 0;

	Object.keys(map).forEach(function (name) {
		notCompleted += 1;

		exports[action](map[name].nodeProvider, name, map[name].node, manifest, function (err) {
			if (err) {
				return callback(err);
			}

			notCompleted -= 1;

			if (notCompleted == 0) {
				return callback(null);
			}
		});
	});
}

// ---

exports.shell = shell;
exports.provision = provision;
exports.status = status;
exports.boot = boot;
exports.halt = halt;
exports.reload = reload;
exports.launch = launch;
<commit_msg>Command line options are now passed to each action.<commit_after>var path = require('path');
var roost = require('roost');

// ---

var providers = require(path.join(__dirname, 'providers.js'));
var logger = require(path.join(__dirname, 'logger.js'));

// ---

function shell(opt, provider, name, node, manifest, callback) {
	provider.shellSpec(name, node, function (err, spec) {
		if (err) {
			return callback(err);
		}

		logger.debug('shell spec is', spec);

		if (!spec.match(/^ssh:/)) {
			return callback(new Error('unsupported shell specification: ' + spec));
		}

		var connection = new roost.ssh.Connection({meta:{location:manifest.meta.location}});

		logger.silly('entering interactive session');

		connection.connect(spec, function (err) {
			if (err) {
				return callback(err);
			}

			connection.shell(function (err) {
				if (err) {
					return callback(err);
				}

				connection.disconnect({}, function (err) {
					if (err) {
						return callback(err);
					}

					return callback();
				});
			});
		});
	});
}

function provision(opt, provider, name, node, manifest, callback) {
	if (!node.hasOwnProperty('roost')) {
		return callback(new Error('no roost configuration defined'));
	}

	roost.logger.setGlobalLevel(logger.level);
	roost.logger.setGlobalColorization(logger.colorize);

	var roostManifest = node.roost;

	if (typeof(roostManifest) == 'string' || roostManifest instanceof String) {
		try {
			roostManifest = roost.manifest.load(path.resolve(path.dirname(manifest.meta.location), roostManifest));
		} catch (e) {
			return callback(e);
		}
	} else {
		roostManifest.meta = {
			location: manifest.meta.location
		};
	}

	logger.debug('start provision with roost manifest', roostManifest);

	var roostPlugins;

	try {
		roostPlugins = roost.plugins.obtain(roostManifest);
	} catch (e) {
		return callback(e);
	}

	if (roostPlugins.length > 0) {
		logger.debug('loaded roost plugins', roostPlugins);
	} else {
		logger.debug('no roost plugins loaded');
	}

	provider.shellSpec(name, node, function (err, spec) {
		if (err) {
			return callback(err);
		}

		logger.debug('shell spec is', spec);

		var roostTarget;

		try {
			roostTarget = roost.targets.instance(spec, roostManifest);
		} catch (e) {
			return callback(e);
		}

		try {
			roost.engine.launch({options:{}, argv:[]}, roostManifest, roostPlugins, roostTarget, callback);
		} catch (e) {
			return callback(e);
		}
	});
}

function status(opt, provider, name, node, manifest, callback) {
	provider.status(name, node, function (err, state) {
		if (err) {
			return callback(err);
		}

		console.log(name + ':', state);

		callback(null);
	});
}

function boot(opt, provider, name, node, manifest, callback) {
	provider.boot(name, node, function (err, state) {
		if (err) {
			return callback(err);
		}

		exports.provision(provider, name, node, manifest, callback);
	});
}

function halt(opt, provider, name, node, manifest, callback) {
	provider.halt(name, node, function (err, state) {
		if (err) {
			return callback(err);
		}

		exports.status(provider, name, node, manifest, callback);
	});
}

function reload(opt, provider, name, node, manifest, callback) {
	exports.halt(provider, name, node, manifest, function (err) {
		if (err) {
			return callback(err);
		}

		exports.boot(provider, name, node, manifest, callback);
	});
}

// ---

function launch(opt, manifest, plugins, provider, action, callback) {
	(plugins || []).forEach(function (plugin) {
		plugin.vortex(opt, manifest, provider, action);
	});

	if (!(action in exports) || action == 'launch') {
		throw new Error('action ' + action + ' not recognized');
	}

	if (!manifest.hasOwnProperty('nodes')) {
		return callback(null);
	}

	var map = {};

	Object.keys(manifest.nodes).forEach(function (name) {
		var node = manifest.nodes[name];
		var nodeProvider = provider;

		if (!nodeProvider) {
			nodeProvider = node.hasOwnProperty('default_provider') ? node.default_provider : null;

			if (nodeProvider) {
				nodeProvider = providers.instance(nodeProvider, manifest);
			}
		}

		if (!nodeProvider) {
			nodeProvider = manifest.hasOwnProperty('default_provider') ? manifest.default_provider : null;

			if (nodeProvider) {
				nodeProvider = providers.instance(nodeProvider, manifest);
			}
		}

		if (!nodeProvider) {
			nodeProvider = providers.instance('VirtualBox', manifest);
		}

		map[name] = {
			nodeProvider: nodeProvider,
			node: node
		};
	});

	var notCompleted = 0;

	Object.keys(map).forEach(function (name) {
		notCompleted += 1;

		exports[action](opt, map[name].nodeProvider, name, map[name].node, manifest, function (err) {
			if (err) {
				return callback(err);
			}

			notCompleted -= 1;

			if (notCompleted == 0) {
				return callback(null);
			}
		});
	});
}

// ---

exports.shell = shell;
exports.provision = provision;
exports.status = status;
exports.boot = boot;
exports.halt = halt;
exports.reload = reload;
exports.launch = launch;
<|endoftext|>


<commit_before>/*
 * feathers-errors
 * https://github.com/feathersjs/feathers-errors
 *
 * Copyright (c) 2014 Eric Kryski
 * Licensed under the MIT license.
 */

'use strict';

var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var errors = require('./error-types');
var html = fs.readFileSync(path.resolve(__dirname, '..', 'public/error.html')).toString();

/**
* Escape the given string of `html`.
*
* @param {String} html
* @return {String}
* @api private
*/
var escapeHTML = function(html){
  return String(html)
    .replace(/&(?!\w+;)/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"');
};

/**
 * Method for a 404 middleware
 * See http://expressjs.com/guide.html#error-handling
 * @param  {Error} err - An error
 * @param  {Object} req - the request object
 * @param  {Object} res - the response object
 * @param  {Function} next - callback to call for next step in middleware chain
 */
var fourOhFour = function(req, res, next) {
  next(new errors.NotFound('Page not found.'));
};

/**
 * The error handler middleware.
 * See http://expressjs.com/guide.html#error-handling
 * @param  {Error} err - An error
 * @param  {Object} req - the request object
 * @param  {Object} res - the response object
 * @param  {Function} next - callback to call for next step in middleware chain
 *
 */

/* jshint unused:false */
var handler = function(err, req, res, next) {
  if (typeof err === 'string') {
    err = new errors.GeneralError(err);
  }
  else if (!(err instanceof errors.AbstractError)) {
    var oldError = err;
    err = new errors.GeneralError(oldError.message);

    if (oldError.stack) {
      err.stack = oldError.stack;
    }
  }

  var statusCode = typeof err.code === 'number' ? err.code : 500;

  res.status(statusCode);

  if (req.app.logger && typeof req.app.logger.error === 'function') {
      req.app.logger.error(req.url, err.stack || err);
  }
  else if (typeof req.app.error === 'function') {
    req.app.error(req.url, err.stack || err);
  }

  res.format({
    'text/html': function(){
      // If we have a rendering engine don't show the
      // default feathers error page.
      // TODO (EK): We should let people specify this instead
      // of making a shitty assumption.
      if (req.app.get('view engine') !== undefined) {
        if (err.code === 404) {
          return res.redirect('/404');
        }

        return res.redirect('/500');
      }

      var stack = (err.stack || '')
          .split('\n')
          .slice(1)
          .map(function(v) {
             return '
' + v + '
';
           })
          .join('');

      var errorPage = html
          .replace('{stack}', stack)
          .replace('{title}', err.message)
          .replace('{statusCode}', err.code)
          .replace(/\{error\}/g, escapeHTML(err.toString().replace(/\n/g, '
')));
      res.send(errorPage);
    },

    'application/json': function(){
      res.json({
        'code': err.code,
        'name': err.name,
        'message': err.message,
        'errors': err.errors || {}
      });
    },

    'text/plain': function(){
      res.send(err.message);
    }
  });
};

exports = module.exports = function (config) {
  if (typeof config === 'function') {
    handler = config;
    config = {};
  }

  config = _.defaults({}, config, {
    handler: handler,
    fourOhFour: fourOhFour
  });

  return function () {
    var app = this;

    // Enable the errors Plugin
    app.enable('feathers errors');

    // Set the error handlers
    app.use(config.fourOhFour).use(config.handler);

    // Set the available errors on the app for convenience
    app.errors = errors;
  };
};

exports.fourOhFour = fourOhFour;
exports.handler = handler;
exports.types = errors;<commit_msg>not showing stack traces for 404 errors<commit_after>/*
 * feathers-errors
 * https://github.com/feathersjs/feathers-errors
 *
 * Copyright (c) 2014 Eric Kryski
 * Licensed under the MIT license.
 */

'use strict';

var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var errors = require('./error-types');
var html = fs.readFileSync(path.resolve(__dirname, '..', 'public/error.html')).toString();

/**
* Escape the given string of `html`.
*
* @param {String} html
* @return {String}
* @api private
*/
var escapeHTML = function(html){
  return String(html)
    .replace(/&(?!\w+;)/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"');
};

/**
 * Method for a 404 middleware
 * See http://expressjs.com/guide.html#error-handling
 * @param  {Error} err - An error
 * @param  {Object} req - the request object
 * @param  {Object} res - the response object
 * @param  {Function} next - callback to call for next step in middleware chain
 */
var fourOhFour = function(req, res, next) {
  next(new errors.NotFound('Page not found.'));
};

/**
 * The error handler middleware.
 * See http://expressjs.com/guide.html#error-handling
 * @param  {Error} err - An error
 * @param  {Object} req - the request object
 * @param  {Object} res - the response object
 * @param  {Function} next - callback to call for next step in middleware chain
 *
 */

/* jshint unused:false */
var handler = function(err, req, res, next) {
  if (typeof err === 'string') {
    err = new errors.GeneralError(err);
  }
  else if (!(err instanceof errors.AbstractError)) {
    var oldError = err;
    err = new errors.GeneralError(oldError.message);

    if (oldError.stack) {
      err.stack = oldError.stack;
    }
  }

  // Don't show stack trace if it is a 404 error
  if (err.code === 404) {
    err.stack = null;
  }

  var statusCode = typeof err.code === 'number' ? err.code : 500;

  res.status(statusCode);

  if (req.app.logger && typeof req.app.logger.error === 'function') {
    req.app.logger.error(err.code, req.url, err.stack || err);
  }
  else if (typeof req.app.error === 'function') {
    req.app.error(err.code, req.url, err.stack || err);
  }

  res.format({
    'text/html': function(){
      // If we have a rendering engine don't show the
      // default feathers error page.
      // TODO (EK): We should let people specify this instead
      // of making a shitty assumption.
      if (req.app.get('view engine') !== undefined) {
        if (err.code === 404) {
          return res.redirect('/404');
        }

        return res.redirect('/500');
      }

      var stack = (err.stack || '')
          .split('\n')
          .slice(1)
          .map(function(v) {
             return '
' + v + '
';
           })
          .join('');

      var errorPage = html
          .replace('{stack}', stack)
          .replace('{title}', err.message)
          .replace('{statusCode}', err.code)
          .replace(/\{error\}/g, escapeHTML(err.toString().replace(/\n/g, '
')));
      res.send(errorPage);
    },

    'application/json': function(){
      res.json({
        'code': err.code,
        'name': err.name,
        'message': err.message,
        'errors': err.errors || {}
      });
    },

    'text/plain': function(){
      res.send(err.message);
    }
  });
};

exports = module.exports = function (config) {
  if (typeof config === 'function') {
    handler = config;
    config = {};
  }

  config = _.defaults({}, config, {
    handler: handler,
    fourOhFour: fourOhFour
  });

  return function () {
    var app = this;

    // Enable the errors Plugin
    app.enable('feathers errors');

    // Set the error handlers
    app.use(config.fourOhFour).use(config.handler);

    // Set the available errors on the app for convenience
    app.errors = errors;
  };
};

exports.fourOhFour = fourOhFour;
exports.handler = handler;
exports.types = errors;<|endoftext|>


<commit_before>'use strict';

var AuthHelper = require('./authhelper');
var Canonicalizer = require('./canonicalizer');
var utils = require('./utils');

var Escher = function(configToMerge) {

  var config = utils.mergeOptions({
    algoPrefix: 'ESR',
    vendorKey: 'ESCHER',
    hashAlgo: 'SHA256',
    date: new Date(),
    credentialScope: 'escher_request',
    authHeaderName: 'X-Escher-Auth',
    dateHeaderName: 'X-Escher-Date',
    clockSkew: 300
  }, configToMerge);

  // validate the configuration
  if (typeof config.vendorKey !== 'string' || config.vendorKey !== config.vendorKey.toUpperCase()) {
    throw new Error('Vendor key should be an uppercase string');
  }

  if (typeof config.algoPrefix !== 'string') {
    throw new Error('Algorithm prefix should be a string');
  }

  if (typeof config.hashAlgo !== 'string' || ['SHA256', 'SHA512'].indexOf(config.hashAlgo) === -1) {
    throw new Error('Only SHA256 and SHA512 hash algorithms are allowed');
  }

  if (!(config.date instanceof Date)) {
    throw new Error('Date should be a JavaScript Date object');
  }

  this._config = config;
};

Escher.prototype = {
  preSignUrl: function(url, expires) {
    return new AuthHelper(this._config).generatePreSignedUrl(url, expires);
  },

  signRequest: function(requestOptions, body, headersToSign) {
    this.validateRequest(requestOptions);
    headersToSign = ['host', this._config.dateHeaderName.toLowerCase()].concat(headersToSign || []);
    var formattedDate = (this._config.dateHeaderName.toLowerCase() === 'date' ? utils.toHeaderDateFormat(this._config
      .date) : utils.toLongDate(this._config.date));
    var defaultHeaders = utils.normalizeHeaders([
      [this._config.dateHeaderName, formattedDate]
    ]);
    utils.addDefaultHeaders(defaultHeaders, requestOptions);
    requestOptions.headers.push([this._config.authHeaderName.toLowerCase(), new AuthHelper(this._config).generateHeader(
      requestOptions, body, headersToSign)]);
    return requestOptions;
  },

  authenticate: function(request, keyDB) {
    this.validateRequest(request);
    var uri = utils.parseUrl(request.url, true);
    var isPresignedUrl = uri.query.hasOwnProperty(this._queryParamKey('Signature')) && request.method === 'GET';

    if (isPresignedUrl) {
      var requestDate = utils.parseLongDate(uri.query[this._queryParamKey('Date')]);
      var parsedAuthParts = new AuthHelper(this._config).parseFromQuery(uri.query, requestDate, keyDB);
      var requestBody = 'UNSIGNED-PAYLOAD';
      var expires = parseInt(uri.query[this._queryParamKey('Expires')]);
      var canonicalizedQueryString = new Canonicalizer().canonicalizeQuery(utils.filterKeysFrom(uri.query, [
        this._queryParamKey('Signature')
      ]));
      request.url = uri.pathname + (canonicalizedQueryString ? '?' + canonicalizedQueryString : '');
    } else {
      var requestDate = this._config.dateHeaderName.toLowerCase() === 'date' ? new Date(utils.getHeader(
        request, this._config.dateHeaderName)) : utils.parseLongDate(utils.getHeader(request, this._config
        .dateHeaderName));
      var parsedAuthParts = new AuthHelper(this._config).parseAuthHeader(utils.getHeader(request, this._config
        .authHeaderName), requestDate, keyDB);
      var requestBody = request.body || '';
      var expires = 0;
    }

    if (!request.host) {
      request.host = utils.getHeader(request, 'host');
    }

    var mandatoryHeaders = isPresignedUrl ? ['host'] : ['host', this._config.dateHeaderName.toLowerCase()];
    mandatoryHeaders.forEach(function(mandatoryHeader) {
      if (parsedAuthParts.signedHeaders.indexOf(mandatoryHeader) === -1) {
        throw new Error('The ' + mandatoryHeader + ' header is not signed');
      }
    });

    if (parsedAuthParts.config.credentialScope !== this._config.credentialScope) {
      throw new Error('Invalid Credential Scope');
    }

    if (['SHA256', 'SHA512'].indexOf(parsedAuthParts.config.hashAlgo) === -1) {
      throw new Error('Invalid hash algorithm, only SHA256 and SHA512 are allowed');
    }

    if (parsedAuthParts.shortDate !== utils.toShortDate(requestDate)) {
      throw new Error('Invalid date in authorization header, it should equal with date header');
    }

    var requestTime = requestDate.getTime();
    var currentTime = this._config.date.getTime();
    if (!this._isDateWithinRange(requestTime, currentTime, expires)) {
      throw new Error('The request date is not within the accepted time range');
    }

    var generatedAuthParts = new AuthHelper(parsedAuthParts.config).buildAuthParts(request, requestBody,
      parsedAuthParts.signedHeaders);
    if (parsedAuthParts.signature !== generatedAuthParts.signature) {
      throw new Error('The signatures do not match');
    }

    return parsedAuthParts.config.accessKeyId;
  },

  _isDateWithinRange: function(requestTime, currentTime, expires) {
    return (requestTime - this._config.clockSkew * 1000 <= currentTime) &&
      (currentTime < requestTime + expires * 1000 + this._config.clockSkew * 1000);
  },

  _queryParamKey: function(param) {
    return 'X-' + this._config.vendorKey + '-' + param;
  },

  validateRequest: function(request) {
    if (typeof request.method !== 'string' || ['GET', 'POST'].indexOf(request.method) === -1) {
      throw new Error('The request method is invalid');
    }

    if (['POST'].indexOf(request.method) !== -1 && typeof request.body !== 'string') {
      throw new Error("The request body shouldn't be empty if the request method is POST");
    }

    if (request.url.indexOf("http://") == 0 || request.url.indexOf("https://") == 0){
      throw new Error("The request url shouldn't contains http or https");
    }
  }
};

Escher.create = function(configToMerge) {
  return new Escher(configToMerge);
};

module.exports = Escher;
<commit_msg>SUITEDEV-5229 Fix requestValidation function<commit_after>'use strict';

var AuthHelper = require('./authhelper');
var Canonicalizer = require('./canonicalizer');
var utils = require('./utils');

var Escher = function(configToMerge) {

  var config = utils.mergeOptions({
    algoPrefix: 'ESR',
    vendorKey: 'ESCHER',
    hashAlgo: 'SHA256',
    date: new Date(),
    credentialScope: 'escher_request',
    authHeaderName: 'X-Escher-Auth',
    dateHeaderName: 'X-Escher-Date',
    clockSkew: 300
  }, configToMerge);

  // validate the configuration
  if (typeof config.vendorKey !== 'string' || config.vendorKey !== config.vendorKey.toUpperCase()) {
    throw new Error('Vendor key should be an uppercase string');
  }

  if (typeof config.algoPrefix !== 'string') {
    throw new Error('Algorithm prefix should be a string');
  }

  if (typeof config.hashAlgo !== 'string' || ['SHA256', 'SHA512'].indexOf(config.hashAlgo) === -1) {
    throw new Error('Only SHA256 and SHA512 hash algorithms are allowed');
  }

  if (!(config.date instanceof Date)) {
    throw new Error('Date should be a JavaScript Date object');
  }

  this._config = config;
};

Escher.prototype = {
  preSignUrl: function(url, expires) {
    return new AuthHelper(this._config).generatePreSignedUrl(url, expires);
  },

  signRequest: function(requestOptions, body, headersToSign) {
    this.validateRequest(requestOptions, body);
    headersToSign = ['host', this._config.dateHeaderName.toLowerCase()].concat(headersToSign || []);
    var formattedDate = (this._config.dateHeaderName.toLowerCase() === 'date' ? utils.toHeaderDateFormat(this._config
      .date) : utils.toLongDate(this._config.date));
    var defaultHeaders = utils.normalizeHeaders([
      [this._config.dateHeaderName, formattedDate]
    ]);
    utils.addDefaultHeaders(defaultHeaders, requestOptions);
    requestOptions.headers.push([this._config.authHeaderName.toLowerCase(), new AuthHelper(this._config).generateHeader(
      requestOptions, body, headersToSign)]);
    return requestOptions;
  },

  authenticate: function(request, keyDB) {
    this.validateRequest(request);
    var uri = utils.parseUrl(request.url, true);
    var isPresignedUrl = uri.query.hasOwnProperty(this._queryParamKey('Signature')) && request.method === 'GET';

    if (isPresignedUrl) {
      var requestDate = utils.parseLongDate(uri.query[this._queryParamKey('Date')]);
      var parsedAuthParts = new AuthHelper(this._config).parseFromQuery(uri.query, requestDate, keyDB);
      var requestBody = 'UNSIGNED-PAYLOAD';
      var expires = parseInt(uri.query[this._queryParamKey('Expires')]);
      var canonicalizedQueryString = new Canonicalizer().canonicalizeQuery(utils.filterKeysFrom(uri.query, [
        this._queryParamKey('Signature')
      ]));
      request.url = uri.pathname + (canonicalizedQueryString ? '?' + canonicalizedQueryString : '');
    } else {
      var requestDate = this._config.dateHeaderName.toLowerCase() === 'date' ? new Date(utils.getHeader(
        request, this._config.dateHeaderName)) : utils.parseLongDate(utils.getHeader(request, this._config
        .dateHeaderName));
      var parsedAuthParts = new AuthHelper(this._config).parseAuthHeader(utils.getHeader(request, this._config
        .authHeaderName), requestDate, keyDB);
      var requestBody = request.body || '';
      var expires = 0;
    }

    if (!request.host) {
      request.host = utils.getHeader(request, 'host');
    }

    var mandatoryHeaders = isPresignedUrl ? ['host'] : ['host', this._config.dateHeaderName.toLowerCase()];
    mandatoryHeaders.forEach(function(mandatoryHeader) {
      if (parsedAuthParts.signedHeaders.indexOf(mandatoryHeader) === -1) {
        throw new Error('The ' + mandatoryHeader + ' header is not signed');
      }
    });

    if (parsedAuthParts.config.credentialScope !== this._config.credentialScope) {
      throw new Error('Invalid Credential Scope');
    }

    if (['SHA256', 'SHA512'].indexOf(parsedAuthParts.config.hashAlgo) === -1) {
      throw new Error('Invalid hash algorithm, only SHA256 and SHA512 are allowed');
    }

    if (parsedAuthParts.shortDate !== utils.toShortDate(requestDate)) {
      throw new Error('Invalid date in authorization header, it should equal with date header');
    }

    var requestTime = requestDate.getTime();
    var currentTime = this._config.date.getTime();
    if (!this._isDateWithinRange(requestTime, currentTime, expires)) {
      throw new Error('The request date is not within the accepted time range');
    }

    var generatedAuthParts = new AuthHelper(parsedAuthParts.config).buildAuthParts(request, requestBody,
      parsedAuthParts.signedHeaders);
    if (parsedAuthParts.signature !== generatedAuthParts.signature) {
      throw new Error('The signatures do not match');
    }

    return parsedAuthParts.config.accessKeyId;
  },

  _isDateWithinRange: function(requestTime, currentTime, expires) {
    return (requestTime - this._config.clockSkew * 1000 <= currentTime) &&
      (currentTime < requestTime + expires * 1000 + this._config.clockSkew * 1000);
  },

  _queryParamKey: function(param) {
    return 'X-' + this._config.vendorKey + '-' + param;
  },

  validateRequest: function(request, body) {
    if (typeof request.method !== 'string' || ['GET', 'POST'].indexOf(request.method) === -1) {
      throw new Error('The request method is invalid');
    }

    var reqBody = (typeof body != 'undefined') ? body : request.body;
    if (['POST'].indexOf(request.method) !== -1 && typeof reqBody !== 'string') {
      throw new Error("The request body shouldn't be empty if the request method is POST");
    }

    if (request.url.indexOf("http://") == 0 || request.url.indexOf("https://") == 0){
      throw new Error("The request url shouldn't contains http or https");
    }
  }
};

Escher.create = function(configToMerge) {
  return new Escher(configToMerge);
};

module.exports = Escher;
<|endoftext|>


<commit_before>var config = require('./config');
var db = require('./db');

var ld = require('lodash');

var mqtt = require('mqtt');
var Redis = require('ioredis');

/**
 * Allow the logging message client to also save the events to the database.  This
 * would normally be done by the event hub or the listener responsible for processing
 * the events.  However, the logging message client doesn't broadcast anything so it
 * needs to do it.
 *
 * @param event {Object} The standard event object.
 */
function saveEventToDB(event) {

    var logger = require('./loggers').getLogger();

    if (ld.isString(event)) {
        try {
            event = JSON.parse(event);
        } catch (e) {
            logger.error('could not parse event string', event, e);
            return;
        }
    }

    logger.debug('saving event to db', event);

    var newEvent  = {
        _data : event,
        _permissions: {
            owner: event.username || 'unchanged',
            rights: {
                owner: ["r"],
                realm: ["r"],
                roles: {}
            }
        }
    };

    var collectionName = 'events';

    db.getCollection(event.account, event.realm, collectionName, function (err, coll) {

        if (err) {
            logger.error('could not get collection: ' + event.account + ' -> ' + event.realm + '.' + collectionName, err);
            return;
        }

        coll.insert(newEvent, {w: 1}, function (err, result) {

            if (err) {
                logger.error('could not save event', err);
                return;
            }

            logger.debug('successfully saved event', result);
        });

    });
}
module.exports.saveEventToDB = saveEventToDB;

/**
 * Constructs and returns a messaging client that simply logs events rather
 * than try to publish them to a message broker.  This is suitable for when
 * a service is under development and you don't want to have to run the
 * entire infrastructure.  Events are still saved to the database.
 *
 * @returns {Object} Messaging client that logs event and saves them to the
 *                   database.
 */
function getLogClient() {

    var logger = require('./loggers').getLogger();
    logger.info('creating log based messaging publisher');

    return {
        connected: true,
        publish: function (topic, message, cb) {
            logger.info('events being published via log client', topic, message);
            saveEventToDB(message);
            cb();
        }
    };
}

/**
 * Constructs and returns a messaging client that uses the MQTT protocol.
 *
 * @returns {Object} MQTT messaging client
 */
function getMQTTClient(messagesURL, cb) {

    //For durable subscriptions, the clientId should be something "permanent".
    var clientId = 'icesage_mqtt_client';
    var suffix = '_' + config.service.name;
    if(process.env.MSGS_NAME){
        suffix = process.env.MSGS_NAME.replace(/\//g,'_');
    }
    clientId = clientId + suffix;

    var logger = require('./loggers').getLogger();
    logger.debug('mqtt client id', clientId);

    //For durable subscriptions, the clientId has to be something "permanent" and
    //session cleaning has to be set to false.
    var mqttClient = mqtt.connect(messagesURL,
        {
            "keepalive": 0,
            "clean": false,
            "clientId": clientId
        });

    function handleConnect() {
        var logger = require('./loggers').getLogger();
        logger.info('mqtt client connected', messagesURL);
        cb(null, mqttClient);
    }

    mqttClient.on('connect', handleConnect);

    function handleReconnect() {
        var logger = require('./loggers').getLogger();
        logger.debug('mqtt client reconnected', messagesURL);
    }

    mqttClient.on('reconnect', handleReconnect);

    function handleClose() {
        var logger = require('./loggers').getLogger();
        logger.debug('mqtt client closed', messagesURL);
    }

    mqttClient.on('close', handleClose);

    function handleError(err) {
        var logger = require('./loggers').getLogger();
        logger.warn('mqtt client error', err);
    }

    mqttClient.on('error', handleError);
}

/**
 * Constructs and returns a messaging client that uses the MQTT protocol.
 *
 * @returns {Object} MQTT messaging client
 */
function getRedisClient(messagesURL, cb) {
    var redisClient = new Redis(messagesURL);

    function handleConnect() {
        var logger = require('./loggers').getLogger();
        logger.info('redis client connected', messagesURL);
    }

    redisClient.on('connect', handleConnect);

    function handleReady() {
        var logger = require('./loggers').getLogger();
        logger.debug('redis client ready', messagesURL);
        redisClient.connected = true;
        cb(null, redisClient);
    }

    redisClient.on('ready', handleReady);

    function handleClose() {
        var logger = require('./loggers').getLogger();
        logger.debug('redis client closed', messagesURL);
    }

    redisClient.on('close', handleClose);
}

var messagingClient;

//Determine and get a messaging client based on the config.messages.scheme
function getMessagingClient(customConfig, cb) {

    //If there's already a client created, use that.
    if (messagingClient) {
        cb(null, messagingClient);
        return;
    }

    var logger = require('./loggers').getLogger();

    var cfg = customConfig || config;

    var messagesURL = cfg.messages.scheme + '://' + cfg.messages.host + ':' + cfg.messages.port;
    if(config.env.hosts && config.env.hosts.msgs){
        messagesURL = cfg.messages.scheme + '://' + config.env.hosts.msgs;
    }
    logger.debug('messaging client URL', messagesURL);

    if (config.messages.scheme === 'log') {
        messagingClient = getLogClient();
        cb(null, messagingClient);
        return;
    }

    if (config.messages.scheme === 'tcp') {
        getMQTTClient(messagesURL, function (err, client) {
            if (err) {
                logger.warn('problem getting MQTT messaging client', messagesURL);
                cb(err);
                return;
            }
            messagingClient = client;
            cb(null, messagingClient);
            return;
        });
    }

    if (config.messages.scheme === 'redis') {
        getRedisClient(messagesURL, function (err, client) {
            if (err) {
                logger.warn('problem getting Redis messaging client', messagesURL);
                cb(err);
                return;
            }
            messagingClient = client;
            cb(null, messagingClient);
            return;
        });
    }

}
module.exports.getMessagingClient = getMessagingClient;

/**
 *
 * @param account {String}  The name of the account. Required.
 * @param realm {String}    The name of the realm. Required
 * @param service {String}  The name of the service (e.g. docs). Required.
 * @param event {String}    The name of the event (e.g. create). Required
 * @param type {String}     The type of the resource (e.g. blob, doc, location, etc.). Required.
 * @param username {String} The name of the user that initiated the event. Required.
 * @param data {Object}     Custom data for this event, specific to the service that published it.  Optional.
 * @returns {Object}        A standard event object created from the parameters.
 */
function createEvent(account, realm, service, event, type, username, data) {

    var logger = require('./loggers').getLogger();

    if (arguments.length < 6 || arguments.length > 7) {
        logger.warn('could not create event, should have 6-7 arguments', arguments.length);
        return null;
    }

    var officialEvent = {
        "time": new Date().toISOString(),
        "account": account,
        "realm": realm,
        "service": service,
        "event": event,
        "type": type,
        "username": username
    };

    officialEvent.data = data || {};

    return officialEvent;
}
module.exports.createEvent = createEvent;

/**
 * A barrel typically contains all the information required to create an event so
 * this function is a convenience that pulls the relevant info and then calls through
 * to createEvent(account, realm, service, event, type, username, data).
 *
 * @param barrel {Object}  The general container for all properties carried through a
 *                         services waterfall of functions.
 * @returns {Object}       A standard event object created from the barrel.
 */
function createEventFromBarrel(barrel) {

    var logger = require('./loggers').getLogger();

    logger.debug('creating service event from barrel');

    //First check to see if barrel is missing any required information.
    var paramErrors = [];
    if (!barrel.account && !barrel.accountId) {
        paramErrors.push('barrel.account or barrel.accountId');
    }

    if (!barrel.realmId) {
        paramErrors.push('barrel.realmId');
    }

    if (!config.service.name) {
        paramErrors.push('config.service.name');
    }

    if (!barrel.serviceEvent) {
        paramErrors.push('barrel.serviceEvent');
    }

    if (!barrel.resourceType) {
        paramErrors.push('barrel.resourceType');
    }

    if (!barrel.username) {
        paramErrors.push('barrel.username');
    }

    if (paramErrors.length > 0) {
        logger.warn('missing parameters to create event = ', paramErrors);
        return null;
    }

    //Then massage the information we have so that we can create an event.
    var account = barrel.account || barrel.accountId;

    var customData = barrel.serviceEventData || {};

    if (barrel.validatedResourceId || barrel.resourceId) {
        customData.resourceId = barrel.validatedResourceId || barrel.resourceId;
    }
    if (barrel.req && barrel.req.headers && barrel.req.headers.host) {
        customData.origin = barrel.req.headers.host;
    }
    if (barrel.startTime) {
        customData.processTime = new Date().getTime() - barrel.startTime;
    }

    if (barrel.resourceId) {
        if (!barrel.serviceEventData) {
            barrel.serviceEventData = {};
        }
        barrel.serviceEventData.resourceId = barrel.resourceId;
    }

    return createEvent(
        account,
        barrel.realmId,
        config.service.name,
        barrel.serviceEvent,
        barrel.resourceType,
        barrel.username,
        customData);
}
module.exports.createEventFromBarrel = createEventFromBarrel;

/**
 * Publishes a message to the specified topic.
 *
 * @param topic {String} A hierarchical topic string that follows a set pattern
 *                       (e.g. events/myaccount/myrealm/docs/create).
 * @param message {String} The message to publish.  If an Object is provided, it
 *                         will be converted to a String.
 *
 */
function publishMessage(topic, message) {

    var logger = require('./loggers').getLogger();
    logger.debug('publishing request', topic, message);

    if (!topic || !ld.isString(topic)) {
        logger.warn('topic must be a string (e.g. events/myaccount/myrealm/docs/create)', topic);
        return;
    }

    if (!message || (!ld.isString(message) && !ld.isObject(message))) {
        logger.warn('message must be a string or an object that can be converted to a String', message);
        return;
    }

    var safeMessage = message;

    if (ld.isString(message)) {
        try {
            JSON.parse(message);
        } catch (e) {
            safeMessage = {"msg": message};
        }
    }

    if (ld.isObject(message)) {
        try {
            safeMessage = JSON.stringify(message);
        } catch (e) {
            logger.warn('cannot JSON.stringify message', message);
            return;
        }
    }

    getMessagingClient(null, function (err, msgClient) {

        if(err){
            logger.warn('could not get messaging client', err);
            return;
        }

        logger.debug('publishing', ', connected =', msgClient.connected, ', topic =', topic, ', message =', safeMessage);

        if (msgClient.connected) {
            msgClient.publish(topic, safeMessage, {qos: 2}, function () {
                logger.debug('published', topic, safeMessage);
            });
        } else {
            logger.warn('messaging client not connected, cannot send message', topic, safeMessage);
        }
    });
}
module.exports.publishMessage = publishMessage;

/**
 * Convenience method for publishing a service event.  For details of required and optional properties
 * of the event, see createEvent().
 *
 * @param event
 */
function publishServiceEvent(event) {
    var topic = 'events/' + event.account + '/' + event.realm + '/' + event.service + '/' + event.event;
    publishMessage(topic, event);
}
module.exports.publishServiceEvent = publishServiceEvent;

/**
 * Convenience method for publishing a service event based on the information in a barrel.  For details
 * of required and optional properties of the event, see createEvent().
 *
 * @param barrel
 */
function publishServiceEventFromBarrel(barrel) {
    var event = createEventFromBarrel(barrel);
    publishServiceEvent(event);
}
module.exports.publishServiceEventFromBarrel = publishServiceEventFromBarrel;
<commit_msg>NTFY-323 - Missing parameter<commit_after>var config = require('./config');
var db = require('./db');

var ld = require('lodash');

var mqtt = require('mqtt');
var Redis = require('ioredis');

/**
 * Allow the logging message client to also save the events to the database.  This
 * would normally be done by the event hub or the listener responsible for processing
 * the events.  However, the logging message client doesn't broadcast anything so it
 * needs to do it.
 *
 * @param event {Object} The standard event object.
 */
function saveEventToDB(event) {

    var logger = require('./loggers').getLogger();

    if (ld.isString(event)) {
        try {
            event = JSON.parse(event);
        } catch (e) {
            logger.error('could not parse event string', event, e);
            return;
        }
    }

    logger.debug('saving event to db', event);

    var newEvent  = {
        _data : event,
        _permissions: {
            owner: event.username || 'unchanged',
            rights: {
                owner: ["r"],
                realm: ["r"],
                roles: {}
            }
        }
    };

    var collectionName = 'events';

    db.getCollection(event.account, event.realm, collectionName, function (err, coll) {

        if (err) {
            logger.error('could not get collection: ' + event.account + ' -> ' + event.realm + '.' + collectionName, err);
            return;
        }

        coll.insert(newEvent, {w: 1}, function (err, result) {

            if (err) {
                logger.error('could not save event', err);
                return;
            }

            logger.debug('successfully saved event', result);
        });

    });
}
module.exports.saveEventToDB = saveEventToDB;

/**
 * Constructs and returns a messaging client that simply logs events rather
 * than try to publish them to a message broker.  This is suitable for when
 * a service is under development and you don't want to have to run the
 * entire infrastructure.  Events are still saved to the database.
 *
 * @returns {Object} Messaging client that logs event and saves them to the
 *                   database.
 */
function getLogClient() {

    var logger = require('./loggers').getLogger();
    logger.info('creating log based messaging publisher');

    return {
        connected: true,
        publish: function (topic, message, options, cb) {
            logger.info('events being published via log client', topic, message);
            saveEventToDB(message);
            cb();
        }
    };
}

/**
 * Constructs and returns a messaging client that uses the MQTT protocol.
 *
 * @returns {Object} MQTT messaging client
 */
function getMQTTClient(messagesURL, cb) {

    //For durable subscriptions, the clientId should be something "permanent".
    var clientId = 'icesage_mqtt_client';
    var suffix = '_' + config.service.name;
    if(process.env.MSGS_NAME){
        suffix = process.env.MSGS_NAME.replace(/\//g,'_');
    }
    clientId = clientId + suffix;

    var logger = require('./loggers').getLogger();
    logger.debug('mqtt client id', clientId);

    //For durable subscriptions, the clientId has to be something "permanent" and
    //session cleaning has to be set to false.
    var mqttClient = mqtt.connect(messagesURL,
        {
            "keepalive": 0,
            "clean": false,
            "clientId": clientId
        });

    function handleConnect() {
        var logger = require('./loggers').getLogger();
        logger.info('mqtt client connected', messagesURL);
        cb(null, mqttClient);
    }

    mqttClient.on('connect', handleConnect);

    function handleReconnect() {
        var logger = require('./loggers').getLogger();
        logger.debug('mqtt client reconnected', messagesURL);
    }

    mqttClient.on('reconnect', handleReconnect);

    function handleClose() {
        var logger = require('./loggers').getLogger();
        logger.debug('mqtt client closed', messagesURL);
    }

    mqttClient.on('close', handleClose);

    function handleError(err) {
        var logger = require('./loggers').getLogger();
        logger.warn('mqtt client error', err);
    }

    mqttClient.on('error', handleError);
}

/**
 * Constructs and returns a messaging client that uses the MQTT protocol.
 *
 * @returns {Object} MQTT messaging client
 */
function getRedisClient(messagesURL, cb) {
    var redisClient = new Redis(messagesURL);

    function handleConnect() {
        var logger = require('./loggers').getLogger();
        logger.info('redis client connected', messagesURL);
    }

    redisClient.on('connect', handleConnect);

    function handleReady() {
        var logger = require('./loggers').getLogger();
        logger.debug('redis client ready', messagesURL);
        redisClient.connected = true;
        cb(null, redisClient);
    }

    redisClient.on('ready', handleReady);

    function handleClose() {
        var logger = require('./loggers').getLogger();
        logger.debug('redis client closed', messagesURL);
    }

    redisClient.on('close', handleClose);
}

var messagingClient;

//Determine and get a messaging client based on the config.messages.scheme
function getMessagingClient(customConfig, cb) {

    //If there's already a client created, use that.
    if (messagingClient) {
        cb(null, messagingClient);
        return;
    }

    var logger = require('./loggers').getLogger();

    var cfg = customConfig || config;

    var messagesURL = cfg.messages.scheme + '://' + cfg.messages.host + ':' + cfg.messages.port;
    if(config.env.hosts && config.env.hosts.msgs){
        messagesURL = cfg.messages.scheme + '://' + config.env.hosts.msgs;
    }
    logger.debug('messaging client URL', messagesURL);

    if (config.messages.scheme === 'log') {
        messagingClient = getLogClient();
        cb(null, messagingClient);
        return;
    }

    if (config.messages.scheme === 'tcp') {
        getMQTTClient(messagesURL, function (err, client) {
            if (err) {
                logger.warn('problem getting MQTT messaging client', messagesURL);
                cb(err);
                return;
            }
            messagingClient = client;
            cb(null, messagingClient);
            return;
        });
    }

    if (config.messages.scheme === 'redis') {
        getRedisClient(messagesURL, function (err, client) {
            if (err) {
                logger.warn('problem getting Redis messaging client', messagesURL);
                cb(err);
                return;
            }
            messagingClient = client;
            cb(null, messagingClient);
            return;
        });
    }

}
module.exports.getMessagingClient = getMessagingClient;

/**
 *
 * @param account {String}  The name of the account. Required.
 * @param realm {String}    The name of the realm. Required
 * @param service {String}  The name of the service (e.g. docs). Required.
 * @param event {String}    The name of the event (e.g. create). Required
 * @param type {String}     The type of the resource (e.g. blob, doc, location, etc.). Required.
 * @param username {String} The name of the user that initiated the event. Required.
 * @param data {Object}     Custom data for this event, specific to the service that published it.  Optional.
 * @returns {Object}        A standard event object created from the parameters.
 */
function createEvent(account, realm, service, event, type, username, data) {

    var logger = require('./loggers').getLogger();

    if (arguments.length < 6 || arguments.length > 7) {
        logger.warn('could not create event, should have 6-7 arguments', arguments.length);
        return null;
    }

    var officialEvent = {
        "time": new Date().toISOString(),
        "account": account,
        "realm": realm,
        "service": service,
        "event": event,
        "type": type,
        "username": username
    };

    officialEvent.data = data || {};

    return officialEvent;
}
module.exports.createEvent = createEvent;

/**
 * A barrel typically contains all the information required to create an event so
 * this function is a convenience that pulls the relevant info and then calls through
 * to createEvent(account, realm, service, event, type, username, data).
 *
 * @param barrel {Object}  The general container for all properties carried through a
 *                         services waterfall of functions.
 * @returns {Object}       A standard event object created from the barrel.
 */
function createEventFromBarrel(barrel) {

    var logger = require('./loggers').getLogger();

    logger.debug('creating service event from barrel');

    //First check to see if barrel is missing any required information.
    var paramErrors = [];
    if (!barrel.account && !barrel.accountId) {
        paramErrors.push('barrel.account or barrel.accountId');
    }

    if (!barrel.realmId) {
        paramErrors.push('barrel.realmId');
    }

    if (!config.service.name) {
        paramErrors.push('config.service.name');
    }

    if (!barrel.serviceEvent) {
        paramErrors.push('barrel.serviceEvent');
    }

    if (!barrel.resourceType) {
        paramErrors.push('barrel.resourceType');
    }

    if (!barrel.username) {
        paramErrors.push('barrel.username');
    }

    if (paramErrors.length > 0) {
        logger.warn('missing parameters to create event = ', paramErrors);
        return null;
    }

    //Then massage the information we have so that we can create an event.
    var account = barrel.account || barrel.accountId;

    var customData = barrel.serviceEventData || {};

    if (barrel.validatedResourceId || barrel.resourceId) {
        customData.resourceId = barrel.validatedResourceId || barrel.resourceId;
    }
    if (barrel.req && barrel.req.headers && barrel.req.headers.host) {
        customData.origin = barrel.req.headers.host;
    }
    if (barrel.startTime) {
        customData.processTime = new Date().getTime() - barrel.startTime;
    }

    if (barrel.resourceId) {
        if (!barrel.serviceEventData) {
            barrel.serviceEventData = {};
        }
        barrel.serviceEventData.resourceId = barrel.resourceId;
    }

    return createEvent(
        account,
        barrel.realmId,
        config.service.name,
        barrel.serviceEvent,
        barrel.resourceType,
        barrel.username,
        customData);
}
module.exports.createEventFromBarrel = createEventFromBarrel;

/**
 * Publishes a message to the specified topic.
 *
 * @param topic {String} A hierarchical topic string that follows a set pattern
 *                       (e.g. events/myaccount/myrealm/docs/create).
 * @param message {String} The message to publish.  If an Object is provided, it
 *                         will be converted to a String.
 *
 */
function publishMessage(topic, message) {

    var logger = require('./loggers').getLogger();
    logger.debug('publishing request', topic, message);

    if (!topic || !ld.isString(topic)) {
        logger.warn('topic must be a string (e.g. events/myaccount/myrealm/docs/create)', topic);
        return;
    }

    if (!message || (!ld.isString(message) && !ld.isObject(message))) {
        logger.warn('message must be a string or an object that can be converted to a String', message);
        return;
    }

    var safeMessage = message;

    if (ld.isString(message)) {
        try {
            JSON.parse(message);
        } catch (e) {
            safeMessage = {"msg": message};
        }
    }

    if (ld.isObject(message)) {
        try {
            safeMessage = JSON.stringify(message);
        } catch (e) {
            logger.warn('cannot JSON.stringify message', message);
            return;
        }
    }

    getMessagingClient(null, function (err, msgClient) {

        if(err){
            logger.warn('could not get messaging client', err);
            return;
        }

        logger.debug('publishing', ', connected =', msgClient.connected, ', topic =', topic, ', message =', safeMessage);

        if (msgClient.connected) {
            msgClient.publish(topic, safeMessage, {qos: 2}, function () {
                logger.debug('published', topic, safeMessage);
            });
        } else {
            logger.warn('messaging client not connected, cannot send message', topic, safeMessage);
        }
    });
}
module.exports.publishMessage = publishMessage;

/**
 * Convenience method for publishing a service event.  For details of required and optional properties
 * of the event, see createEvent().
 *
 * @param event
 */
function publishServiceEvent(event) {
    var topic = 'events/' + event.account + '/' + event.realm + '/' + event.service + '/' + event.event;
    publishMessage(topic, event);
}
module.exports.publishServiceEvent = publishServiceEvent;

/**
 * Convenience method for publishing a service event based on the information in a barrel.  For details
 * of required and optional properties of the event, see createEvent().
 *
 * @param barrel
 */
function publishServiceEventFromBarrel(barrel) {
    var event = createEventFromBarrel(barrel);
    publishServiceEvent(event);
}
module.exports.publishServiceEventFromBarrel = publishServiceEventFromBarrel;
<|endoftext|>


<commit_before>(function(window){
  var routes = [];

  function hrouter(path, fn) {
    var length = arguments.length
      , route  = {}
      , keys = []
      , regexp = pathtoregexp(path, keys);

    route.keys = keys;
    route.regexp = regexp;
    route.callback = fn;
    routes.push(route);
  }

  function pathtoregexp(path, keys) {
    path = path
    .replace(/\//,'')
    .replace(/:(\w+)(:?)/g,function (m, key) {
      return '([^\/].*)';
    });
    return new RegExp("^(?:" + path + ")$");
  };

  function dispatch(url) {
    var length = routes.length;
    for (var i = 0 ; i < length; i++) {
      //console.log(routes[i]);
      if (routes[i].regexp.test(url)) {
        m = routes[i].regexp.exec(url);
        routes[i].callback.apply(routes[i], m.slice(1, m.length));
      }
    }
  };
  window.hrouter = hrouter;
}(this));<commit_msg>adding event handler<commit_after>/**
* @todo implement hash event handler
*/
(function(window){
  var routes = [];

  function hrouter(path, fn) {
    var length = arguments.length
      , route  = {}
      , keys = []
      , regexp = pathtoregexp(path, keys);

    route.keys = keys;
    route.regexp = regexp;
    route.callback = fn;
    routes.push(route);
  }

  function pathtoregexp(path, keys) {
    path = path
    .replace(/\//,'')
    .replace(/:(\w+)(:?)/g,function (m, key) {
      return '([^\/].*)';
    });
    return new RegExp("^(?:" + path + ")$");
  };

  function dispatch(url) {
    var length = routes.length;
    if (length < 1) return;

    for (var i = 0 ; i < length; i++) {
      //console.log(routes[i]);
      if (routes[i].regexp.test(url)) {
        m = routes[i].regexp.exec(url);
        routes[i].callback.apply(routes[i], m.slice(1, m.length));
      }
    }
  };

  function addEvent(elem, event, fn) {
    if (elem.addEventListener) {
        elem.addEventListener(event, fn, false);
    } else {
        elem.attachEvent("on" + event, function() {
            return(fn.call(elem, window.event));
        });
    }
  }

  function trigger() {
    var hash = location.hash.replace(/#/,"");
    dispatch(hash);
  }

  addEvent('hashchange',trigger);
  trigger();

  window.hrouter = hrouter;
}(this));<|endoftext|>