forked from jhuckaby/Cronicle
-
Notifications
You must be signed in to change notification settings - Fork 0
/
shell-plugin.js
executable file
·148 lines (122 loc) · 4.19 KB
/
shell-plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env node
// Shell Script Runner for Cronicle
// Invoked via the 'Shell Script' Plugin
// Copyright (c) 2015 Joseph Huckaby
// Released under the MIT License
var fs = require('fs');
var os = require('os');
var cp = require('child_process');
var path = require('path');
var JSONStream = require('pixl-json-stream');
var Tools = require('pixl-tools');
require('dotenv').config();
// setup stdin / stdout streams
process.stdin.setEncoding('utf8');
process.stdout.setEncoding('utf8');
var stream = new JSONStream( process.stdin, process.stdout );
stream.on('json', function(job) {
// got job from parent
var script_file = path.join( os.tmpdir(), 'cronicle-script-temp-' + job.id + '.sh' );
fs.writeFileSync( script_file, job.params.script, { mode: "775" } );
if (job.params.tty) { // execute thru script tool
var child = cp.spawn("/usr/bin/script", ["-qec", script_file, "--flush"], {
stdio: ['pipe', 'pipe', 'pipe']
});
}
else {
var child = cp.spawn(script_file, [], {
stdio: ['pipe', 'pipe', 'pipe']
});
}
var kill_timer = null;
var stderr_buffer = '';
// if tty option is checked do not pass stdin (to avoid it popping up in the log)
if (job.params.tty) { var cstream = new JSONStream(child.stdout); }
else { var cstream = new JSONStream(child.stdout, child.stdin); }
cstream.recordRegExp = /^\s*\{.+\}\s*$/;
cstream.on('json', function(data) {
// received JSON data from child, pass along to Cronicle or log
if (job.params.json) stream.write(data);
else cstream.emit('text', JSON.stringify(data) + "\n");
} );
cstream.on('text', function(line) {
// received non-json text from child
// look for plain number from 0 to 100, treat as progress update
if (line.match(/^\s*(\d+)\%\s*$/)) {
var progress = Math.max( 0, Math.min( 100, parseInt( RegExp.$1 ) ) ) / 100;
stream.write({
progress: progress
});
}
else {
// otherwise just log it
if (job.params.annotate) {
var dargs = Tools.getDateArgs( new Date() );
line = '[' + dargs.yyyy_mm_dd + ' ' + dargs.hh_mi_ss + '] ' + line;
}
fs.appendFileSync(job.log_file, line);
}
} );
cstream.on('error', function(err, text) {
// Probably a JSON parse error (child emitting garbage)
if (text) fs.appendFileSync(job.log_file, text + "\n");
} );
child.on('error', function (err) {
// child error
stream.write({
complete: 1,
code: 1,
description: "Script failed: " + Tools.getErrorDescription(err)
});
fs.unlink( script_file, function(err) {;} );
} );
child.on('exit', function (code, signal) {
// child exited
if (kill_timer) clearTimeout(kill_timer);
code = (code || signal || 0);
var data = {
complete: 1,
code: code,
description: code ? ("Script exited with code: " + code) : ""
};
if (stderr_buffer.length && stderr_buffer.match(/\S/)) {
data.html = {
title: "Error Output",
content: "<pre>" + stderr_buffer.replace(/</g, '<').trim() + "</pre>"
};
if (code) {
// possibly augment description with first line of stderr, if not too insane
var stderr_line = stderr_buffer.trim().split(/\n/).shift();
if (stderr_line.length < 256) data.description += ": " + stderr_line;
}
}
stream.write(data);
fs.unlink( script_file, function(err) {;} );
} ); // exit
// silence EPIPE errors on child STDIN
child.stdin.on('error', function(err) {
// ignore
} );
// track stderr separately for display purposes
child.stderr.setEncoding('utf8');
child.stderr.on('data', function(data) {
// keep first 32K in RAM, but log everything
if (stderr_buffer.length < 32768) stderr_buffer += data;
else if (!stderr_buffer.match(/\.\.\.$/)) stderr_buffer += '...';
fs.appendFileSync(job.log_file, data);
});
// pass job down to child process (harmless for shell, useful for php/perl/node)
cstream.write( job );
child.stdin.end();
// Handle shutdown
process.on('SIGTERM', function() {
console.log("Caught SIGTERM, killing child: " + child.pid);
kill_timer = setTimeout( function() {
// child didn't die, kill with prejudice
console.log("Child did not exit, killing harder: " + child.pid);
child.kill('SIGKILL');
}, 9 * 1000 );
// try killing nicely first
child.kill('SIGTERM');
} );
} ); // stream