UNPKG

3.35 kBJavaScriptView Raw
1var net = require('net');
2var spawn = require('child_process').spawn;
3
4var terminus = require('terminus');
5var through2 = require('through2');
6var Promise = require('bluebird');
7var es = require('event-stream');
8var shellEscape = require('shell-escape');
9
10var winston = require('winston');
11require('winston-papertrail').Papertrail;
12
13module.exports = function(logPort, options){
14 return new Runner(logPort, options);
15};
16
17module.exports.remoteStream = function(){
18 var input = es.stringify();
19 var connection = net.connect.apply(net, arguments);
20 var output = input.pipe(connection)
21 .pipe(es.split())
22 .pipe(es.parse());
23
24 connection.on('error', output.emit.bind(output, 'error'));
25
26 return es.duplex(input, output);
27};
28
29function Runner(logPort, options){
30 this.logPort = logPort;
31 this.options = options || {};
32
33 this.clients = [];
34 return net.createServer(this.onConnection.bind(this));
35}
36
37Runner.prototype.onConnection = function(socket){
38 this.clients.push(socket);
39
40 var self = this;
41
42 socket.on('end', function(){
43 self.clients.splice(self.clients.indexOf(socket), 1);
44 });
45
46 var bash = spawn('bash', [], this.options);
47
48 var logger = new winston.Logger({
49 transports: [
50 new winston.transports.Papertrail({
51 host: 'logs.papertrailapp.com',
52 port: this.logPort
53 })
54 ]
55 });
56
57 var inputLogs = [];
58 var bashLogs = [];
59 bash.stdout.on('data', function(data){
60 bashLogs.push(data.toString());
61 logger.log(data.toString());
62 });
63 bash.stderr.on('data', function(data){
64 bashLogs.push(data.toString());
65 logger.error(data.toString());
66 });
67
68 var validator = this.validate();
69 var responder = es.stringify();
70
71 socket
72 .pipe(es.split())
73 .pipe(es.parse())
74 .pipe(through2.obj(function(chunk, enc, cb){
75 if (typeof chunk === 'object' && chunk.end === true) {
76 bash.stdin.end();
77 } else {
78 this.push(chunk);
79 }
80 cb();
81 }))
82 .pipe(validator)
83 .pipe(this.stringify())
84 .pipe(through2.obj(function(line, enc, cb){
85 inputLogs.push(line);
86 cb(null, line);
87 }))
88 .pipe(bash.stdin);
89
90 responder
91 .pipe(through2.obj(function(chunk, enc, cb){
92 console.log('responding', chunk);
93 cb(null, chunk);
94 }))
95 .pipe(socket);
96
97 validator.on('error', function(err){
98 responder.write({
99 status: 'error',
100 error: err.stack || err
101 });
102 });
103
104 bash.on('exit', function(code, signal){
105 var message = 'bash closed with code '+code+', signal '+signal;
106 if (code === 0) {
107 logger.info(message);
108 responder.write({
109 status: 'success',
110 logs: bashLogs.join('\n'),
111 input: inputLogs.join('')
112 });
113 } else {
114 logger.error(message);
115 responder.write({
116 status: 'error',
117 code: code,
118 signal: signal,
119 logs: bashLogs.join('\n'),
120 input: inputLogs.join('')
121 });
122 }
123 logger.close();
124 responder.end();
125 });
126};
127
128Runner.prototype.stringify = function(){
129 return through2.obj(function(chunk, enc, callback){
130 var out = shellEscape(chunk)+';\n';
131 callback(null, out);
132 });
133};
134Runner.prototype.validate = function(){
135 return through2.obj(function(chunk, enc, callback){
136 if (Array.isArray(chunk)) {
137 callback(null, chunk);
138 } else {
139 callback('invalid: not an array');
140 }
141 });
142};