Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: c341d76c5d
Fetching contributors…

Cannot retrieve contributors at this time

executable file 328 lines (317 sloc) 9.943 kB
#!/usr/bin/env node
//
// Toss executables (scripts too) in /etc/node-agent.d
// Each program should produce lines as follows:
// <name><whitespace><type>
// or:
// <name><whitespace><type><whitespace><value>
//
// The first indicates the value is present an null
// the second indicates the valus is present and specific
//
var fs = require('fs'),
sys = require('sys'),
http = require('http'),
url = require('url'),
spawn = require('child_process').spawn,
procre = /^(?!\.)([^\/]*?)(?:\..*)$/,
past_results = {},
scripts = {},
scripts_once = {},
creds = {},
generation = 0, do_index = false;
var configdir = '/etc/node-agent.d',
port = 2609,
sslport = 0,
verify = false;
function help(err) {
console.log(process.argv[1] + "\n" +
"\t-h\t\tthis help message\n" +
"\t-i\t\toffline inventory\n" +
"\t-c <configdir>\tconfig dir\n" +
"\t-p <port>\tunsecured port\n" +
"\t-s <secureport>\tsecured port\n" +
"\t-v\t\tverify secured traffic\n");
if(err) {
console.log("\nError: " + err + "\n");
process.exit(-1);
}
}
function index(dir) {
try { process.chdir(dir); }
catch (e) {
console.log("Cannot use configdir: " + configdir);
console.log(e.message);
process.exit(-1);
}
var catalog = {}, active = {}, missing = {};
function build_catalog(dir) {
dir = dir || "";
try {
var c = fs.readFileSync("./" + dir + "/.index.json");
var j = JSON.parse(c);
catalog[dir] = j;
for(var script in j) {
try {
var info = fs.statSync("./" + dir + "/" + script);
if(!info.isFile() || !(info.mode & 0100)) throw Exception("");
} catch(e) { missing[dir + "/" + script] = true; }
}
} catch(e) { }
var files = fs.readdirSync("./" + dir);
for(var i=0; i<files.length; i++) {
var info = fs.statSync("./" + dir + "/" + files[i]);
if(info && info.isDirectory())
build_catalog(dir + (dir ? "/" : "") + files[i]);
}
}
build_catalog();
var files = fs.readdirSync(".");
var base = fs.realpathSync(".") + "/";
for(var i=0; i<files.length; i++) {
var m = procre.exec(files[i]);
if(!m) continue;
var info = fs.lstatSync(files[i]);
var realpath = files[i];
if(info.isSymbolicLink()) info = fs.statSync(files[i]);
if(info.isFile() && (info.mode & 0100)) {
var realpath = fs.realpathSync(files[i]);
if(realpath.indexOf(base) == 0)
realpath = realpath.substr(base.length);
active[realpath] = m[1];
}
}
for(var module in catalog) {
for(var script in catalog[module]) {
var m = procre.exec(script);
if(!m) console.log("! " + script + ": MALFORMED NAME");
else {
var file = module + "/" + script;
var desc = catalog[module][script];
var on = (file in active) ? "*" : " ";
if(file in missing) on = "!";
delete active[file];
console.log(on + " " +
module + "/" + m[1] + ": " + desc);
}
}
}
var first = true;
for(var file in active) {
if(first) { console.log("\n !!! Rogue scripts !!!"); first = false; }
console.log("* " + file + ": ???");
}
}
for(var i=2; i<process.argv.length; i++) {
switch(process.argv[i]) {
case "-h": help(); process.exit(-1);
case "-c": configdir = process.argv[++i]; break;
case "-i": do_index = true; break;
case "-p": port = parseInt(process.argv[++i]); break;
case "-s": sslport = parseInt(process.argv[++i]); break;
case "-v": verify = true; break;
default: help("unknown argument: " + process.argv[i]);
}
}
if(do_index) {
index(configdir);
process.exit(0);
}
if(!port && !sslport) help("must specify at least one of -p and -s");
if(sslport) {
try {
// Setup creds
creds.key = fs.readFileSync(configdir+"/na.key").toString();
creds.crt = fs.readFileSync(configdir+"/na.crt").toString();
if(verify) creds.ca = fs.readFileSync(configdir+"/na.ca").toString();
} catch(e) {
console.log("Make sure:");
console.log("\tyour key is available: " + configdir+"/na.key");
console.log("\tyour cert is available: " + configdir+"/na.crt");
if(verify)
console.log("\tyour ca is available: " + configdir+"/na.ca");
console.log("\n" + e);
process.exit(-1);
}
}
function onchange(cb) { return function(c,p) { if(c.ino != p.ino || c.size != p.size || c.mtime.valueOf() != p.mtime.valueOf() || c.mode != p.mode) cb(); }}
function initial_pass() {
for(which in scripts) {
if(!(which in scripts_once)) {
run_script(scripts[which], function() {});
scripts_once[which] = true;
}
}
}
function rescan_modules() {
var progress = 0;
var sweep = function () {
if(progress != 0) return;
for(var t in scripts) {
if(scripts[t].generation < generation) {
fs.unwatchFile(scripts[t].command);
delete scripts[t];
delete scripts_once[t];
}
}
initial_pass();
}
generation++;
fs.readdir(configdir, function(err, files) {
for(var i = 0; i < files.length; i++) {
var m = procre.exec(files[i]);
if(!m) continue;
var filename = configdir + "/" + files[i];
fs.unwatchFile(filename);
progress++;
fs.stat(filename, (function(filename, name) {
return function(serr, sb) {
if(sb && sb.isFile())
fs.watchFile(filename, onchange(rescan_modules));
if(sb && sb.isFile() && (sb.mode & 0111)) {
if(!(name in scripts)) scripts[name] = {};
var d = scripts[name];
d.name = name;
d.generation = generation;
d.command = filename;
d.running = false;
d.sb = sb;
}
progress--;
sweep();
}
})(filename,m[1]));
}
});
sweep();
}
fs.watchFile(configdir, onchange(rescan_modules));
rescan_modules();
function run_script(d, cb) {
if(d.running) {
cb(d, past_results[d.name]);
return;
}
d.running = true;
var proc_data = { data: '', lines: [], options: {} };
d.last_start = +(new Date());
var cmd = spawn("/bin/sh", ["-c", d.command]);
var kill_func = function() {
cmd.stdin.destroy();
cmd.stdout.destroy();
cmd.stderr.destroy();
cmd.kill();
};
var handle_output = function(d, cb) {
if(proc_data.timeout) clearTimeout(proc_data.timeout);
d.last_finish = +(new Date());
var i, results = {}, parts;
try { results = JSON.parse(proc_data.lines.join(' ')); }
catch(e) {
for(i=0; i<proc_data.lines.length; i++) {
var name, type, space, val, isnull;
parts = /^\s*(metric\s+)?(\S+)\s+(string|int|float|[iIlLns])(\s*)(.*)$/.exec(proc_data.lines[i]);
if (parts) {
name = parts[2];
type = parts[3];
space = parts[4];
val = parts[5];
isnull = (space.length == 0 || val == "[[null]]");
}
type = type.length > 1 ? type === "float" ? "n" : type.substr(0,1) : type;
results[name] = { '_type': type,
'_value': isnull ? null : val
};
}
}
past_results[d.name] = results;
cb(d, results);
};
cmd.stdout.on('data', function(buff) {
var offset;
if(buff == null || buff.length == 0) {
handle_output(d, cb);
}
proc_data.data = proc_data.data + buff;
while((offset = proc_data.data.indexOf('\n')) >= 0) {
var line = proc_data.data.substring(0,
(offset > 0 &&
proc_data.data.charAt(offset-1) == '\r') ?
offset - 1 : offset);
if(line.charAt(0) == '#') {
try { proc_data.options = JSON.parse(line.substring(1)); }
catch(e) { console.log("Error parsing proc otions: " + e); }
if(proc_data.options.timeout)
proc_data.timeout = setTimeout(kill_func,
proc_data.options.timeout * 1000);
}
else {
if(line.length) proc_data.lines.push(line);
else handle_output(d, cb);
}
proc_data.data = proc_data.data.substring(offset+1);
}
});
cmd.on('exit', function(code, signal) {
d.running = false;
handle_output(d, cb);
});
}
function run_scripts(res, which) {
if(which) {
if(!(which in scripts)) {
res.writeHead(404);
res.end();
}
else {
run_script(scripts[which], function(d, results) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(results));
res.end();
});
}
}
else {
var cnt = 0;
for(which in scripts) cnt++;
var set = {};
var send_complete = function() {
if(cnt != 0) return;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(set));
res.end();
};
for(which in scripts) run_script(scripts[which], function(d, results) {
cnt--;
set[d.name] = results;
send_complete();
});
send_complete();
}
}
function handler(req, res) {
req.addListener('end', function() {
var m, path = url.parse(this.url).pathname;
if(/^\/inventory/.test(path)) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(scripts));
}
else if(/^\/(?:run)?$/.test(path)) return run_scripts(res);
else if(m = /^\/run\/(.+)$/.exec(path)) return run_scripts(res, m[1]);
else res.writeHead(404);
res.end();
});
}
try {
if(port) http.createServer(handler).listen(port);
} catch(e) {
console.log("Failed to start server on port " + port + ": " + e);
process.exit(-1);
}
try {
if(sslport)
https.createServer(creds).addListener(handler).listen(sslport);
} catch(e) {
console.log("Failed to start server on port " + sslport + ": " + e);
process.exit(-1);
}
Jump to Line
Something went wrong with that request. Please try again.