UNPKG

3.39 kBJavaScriptView Raw
1var net = require('net');
2var spawn = require('child_process').spawn;
3
4var through2 = require('through2');
5var Promise = require('bluebird');
6var es = require('event-stream');
7var shellEscape = require('./shellEscape');
8
9var winston = require('winston');
10
11module.exports = function(logFile, options){
12 return new Runner(logFile, options);
13};
14
15module.exports.remoteStream = function(){
16 var input = es.stringify();
17 var connection = net.connect.apply(net, arguments);
18 var output = input.pipe(connection)
19 .pipe(es.split())
20 .pipe(es.parse());
21
22 connection.on('error', output.emit.bind(output, 'error'));
23
24 return es.duplex(input, output);
25};
26
27function Runner(logFile, options){
28 this.logFile = logFile;
29 this.options = options || {};
30
31 this.logger = new (winston.Logger)({
32 transports: [
33 new (winston.transports.Console)(),
34 new (winston.transports.File)({ filename: logFile })
35 ]
36 });
37
38 this.clients = [];
39 return net.createServer(this.onConnection.bind(this));
40}
41
42Runner.prototype.onConnection = function(socket){
43 this.clients.push(socket);
44
45 var self = this;
46
47 var logger = this.logger;
48
49 socket.on('end', function(){
50 self.clients.splice(self.clients.indexOf(socket), 1);
51 });
52
53 var bash = spawn('bash', [], this.options);
54
55 var inputLogs = [];
56 var bashLogs = [];
57 bash.stdout.on('data', function(data){
58 bashLogs.push(data.toString());
59 logger.info('stdout: '+data.toString());
60 });
61 bash.stderr.on('data', function(data){
62 bashLogs.push(data.toString());
63 logger.error(data.toString());
64 });
65
66 var validator = this.validate();
67 var responder = es.stringify();
68
69 socket
70 .pipe(es.split())
71 .pipe(es.parse())
72 .pipe(through2.obj(function(chunk, enc, cb){
73 if (typeof chunk === 'object' && chunk.end === true) {
74 logger.info('ending bash stdin');
75 bash.stdin.end();
76 } else {
77 this.push(chunk);
78 }
79 cb();
80 }))
81 .pipe(validator)
82 .pipe(this.stringify())
83 .pipe(through2.obj(function(line, enc, cb){
84 inputLogs.push(line);
85 logger.info('executing: '+line);
86 cb(null, line);
87 }))
88 .pipe(bash.stdin);
89
90 responder
91 .pipe(through2.obj(function(chunk, enc, cb){
92 logger.info('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};