UNPKG

goon

Version:

A wrapper for running binaries on Google Cloud Functions

221 lines (178 loc) 5.34 kB
'use strict'; var path = require('path'); var spawn = require('child_process').spawn; var Container = function (binary, opts, req, res) { this.getRawArguments = _getRawArguments; this.getBinaryArguments = _getBinaryArguments; this.run = _run; this.log = _log.bind(this); this.execute = _execute; this._print = _print; this._respond = _respond; opts || (opts={}); opts.redirects || (opts.redirects={}); opts.headers || (opts.headers={}); opts.argument_prefix || (opts.argument_prefix="-"); opts.methods || (opts.methods = ["GET"]); opts.arguments || (opts.arguments=[]); if (typeof opts.arguments == "string") opts.arguments = [opts.arguments]; this.opts = opts; this.binary = path.resolve(binary); }; function _getRawArguments(req) { var query, contentType = req.get('content-type'); this.log('type:'+contentType); switch (contentType) { case 'application/json': query = req.body; break; case 'application/x-www-form-urlencoded': query = req.body; break; default: query = req.query; } return query; } function _getBinaryArguments(rawArgs) { var binArgs = []; this.log('stdin:' + JSON.stringify(rawArgs)); if (this.opts.pass_raw_json) { binArgs.push("-json=" + JSON.stringify(rawArgs)); } else { for (var key in rawArgs) { var redirectedKey = (this.opts.redirects[key] || key) var arg = JSON.stringify(rawArgs[key]) binArgs.push(`${this.opts.argument_prefix}${redirectedKey}=${arg}`); } } return binArgs; } function _log(msg) { if (this.opts.verbose) { this._print(msg); } } function _print(msg) { console.log(msg); } function _run(req, res, extraArgs) { var rawArgs, binArgs; this.log('info: beginning parsing'); rawArgs = this.getRawArguments(req); binArgs = this.getBinaryArguments(rawArgs); binArgs = this.opts.arguments.concat(binArgs); binArgs = (extraArgs || []).concat(binArgs); if (this.opts.forward_request_headers) { var headers = JSON.stringify(req.headers) binArgs.push(`${this.opts.argument_prefix}goon-request-headers=${headers}`) } res.set(this.opts.headers); this.log('info: beginning execution'); this.execute(this.binary, binArgs, res); } function _execute(binary, binArgs, res) { var proc, running, self = this, output = ""; self.log("running:" + binary + " " + JSON.stringify(binArgs)); proc = spawn(binary, binArgs); running = true; proc.stdout.on('data', function (data) { self.log('stdout: ' + data); output += data; }); proc.stderr.on('data', function (data) { self.log('stderr: ' + data); if (running) self._respond(data.toString(), 1, res); running = false; }); proc.on('close', function (code) { self.log('close: ' + output); if (running) { self._respond(output.toString(), code, res); } else { } running = false; }); } function _respond(output, code, res) { try { var oo = JSON.parse(output); var redirect = oo[":goon-redirect"]; if (redirect) { res.redirect(redirect.type, redirect.url); return; } } catch(err) { this.log("Response from binary was not valid json."); } switch (code) { case 0: res.status(200).send(output); break; case 1: res.status(500).json({error: true, msg: "Binary returned error exit code."}); break; case 2: res.status(500).json({error: true, msg: "Binary returned error exit code."}); break; default: res.status(500).json({error: true, msg: "Binary returned non-standard exit code."}) break; } } exports.createContainer = function (binary, options) { return new Container(binary, options); } exports.enableHTTPS = function (binary, options) { var app = require('express')(); var container = exports.createContainer(binary, options); container.opts.methods.forEach(function (method) { method = method.toLowerCase(); app[method]('/:command', (req, res) => { container.log('routed: ' + req.params.command); if (!options.disable_command_forwarding) { container.run(req, res, [req.params.command]); } else { container.run(req, res); } }); app[method]('/', (req, res) => { container.log('routed: /'); container.run(req, res); }); }); return app; } exports.enableEvent = function (binary, options) { var container = exports.createContainer(binary, options); options.arguments || (options.arguments = []) return (event) => { var data = typeof event.data.val == "function"? event.data.val() : event.data.json; var binArgs = options.arguments.concat(["--data="+JSON.stringify(data)]); if (Object.keys(event.params).length) { binArgs = binArgs.concat(["--params="+JSON.stringify(event.params)]) } return new Promise((resolve, reject) => { var resMock = { status() { return resMock }, json(obj) { var out = JSON.stringify(obj); console.log(out); resolve(out); }, send(msg) { console.log(msg); resolve(msg); }, redirect() { var out = "CAN NOT REDIRECT IN EVENTS MODE"; console.warn(out); reject(out); } } container.execute(container.binary, binArgs, resMock) }) } }