goon
Version:
A wrapper for running binaries on Google Cloud Functions
221 lines (178 loc) • 5.34 kB
JavaScript
;
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)
})
}
}