the-shepherd
Version:
Control a herd of wild processes.
258 lines (237 loc) • 8.01 kB
JavaScript
// Generated by CoffeeScript 2.5.1
(function() {
//!/usr/bin/env coffee
var $, Actions, Daemon, Fs, Net, __VERSION__, _on, basePath, c, carefulUnlink, cmd, configFile, createBasePath, die_soon, doHelp, doInit, echo, exists, exit_soon, expandPath, ignore, isPidAlive, pidFile, readPid, readTimeout, sendServerCmd, socketFile, startupTimeout, statusChangeCommands, verbose, waitForSocket, warn,
indexOf = [].indexOf;
__VERSION__ = '0.3.29';
Fs = require('fs');
Net = require('net');
Daemon = require('../daemon');
({Actions} = require('../actions'));
({$, cmd, echo, warn, verbose, exit_soon, die_soon} = require('../common'));
({exists, pidFile, socketFile, configFile, basePath, createBasePath, expandPath, readPid, carefulUnlink} = require('../files'));
({isPidAlive} = require('../util/process-slim'));
readTimeout = 3000; // how long to wait for a response, to any command
startupTimeout = 1000; // how long to wait after issuing an 'up' before inquiring with 'status'
ignore = function() {};
doInit = function(cb) {
var defaultsFile;
defaultsFile = process.cwd() + "/.shep/defaults";
cb || (cb = ignore);
if (exists(configFile) && !(cmd.f || cmd.force)) {
echo(`Configuration already exists (${configFile})`);
cb(null, false);
} else {
echo("Initializing .shep/config...");
if (exists(defaultsFile)) {
echo("Applying default config...", configFile);
Fs.copyFile(expandPath(defaultsFile), expandPath(configFile), (err) => {
return cb(null, true);
});
} else {
createBasePath(".", cb);
}
}
return null;
};
_on = (s, kv) => {
var k, results, v;
results = [];
for (k in kv) {
v = kv[k];
results.push(s.on(k, v.bind(s)));
}
return results;
};
sendServerCmd = (_cmd, cb) => {
var Tnet, action, on_error, retryConnect, savedPid;
if (!exists(basePath)) {
return echo("No .shep directory found.");
}
if (!exists(socketFile)) {
// If there's no socket to connect to, but a PID is still alive, try to kill it via signal
if (exists(expandPath(pidFile))) {
warn("Stale PID file detected:", pidFile);
isPidAlive(savedPid = readPid(), function(err, alive) {
if (alive) {
warn(`PID (${savedPid}) still running, but not listening on a socket, attempt to kill it.`);
return process.kill(savedPid, "SIGTERM");
} else {
return carefulUnlink(pidFile, function(err) {
if (err) {
return warn(`client/index Error when unlinking PID file (${pidFile}):`, err);
}
});
}
});
}
return die_soon("Status: offline (no socket file).", 3);
}
cb || (cb = ignore);
if (!(action = Actions[_cmd])) {
return warn("No such action:", _cmd);
}
Net = require('net');
Tnet = require('../util/tnet');
on_error = (err) => {
console.error("on_error:", err);
return warn("socket error", $.debugStack(err));
};
(retryConnect = () => {
_on(Net.connect({
path: expandPath(socketFile)
}), {
close: function() {
return cb(null, true);
},
error: function(err) {
var ref;
if (err.code === 'ENOENT') {
if (_cmd === 'start') {
Daemon.doStart(false);
return setTimeout(retryConnect, readTimeout);
} else {
return die_soon(`Status: offline (${err.code}, ${_cmd}).`, 3);
}
} else if ((ref = err.code) === 'EADDRNOTAVAIL' || ref === 'ECONNREFUSED') {
return die_soon(`Status: offline (${err.code}).`, 3);
} else {
return on_error(err);
}
},
connect: function() {
var bytes, err, msg;
try {
msg = action.toMessage(cmd);
if (cmd._.length > 1 && !(('group' in cmd) || ('instance' in cmd))) {
if (/\w+/.test(cmd._[1])) {
msg.auto = cmd._[1];
}
}
bytes = $.TNET.stringify(msg);
} catch (error) {
err = error;
return on_error(err);
}
// Send the command bytes to the server
this.write(bytes, () => {
var perItem, timeout;
if (typeof action.onConnect === "function") {
action.onConnect(this);
}
if (!('onResponse' in action)) {
return this.end();
}
// If there's an onResponse, read the
timeout = $.delay(readTimeout, () => {
warn("Timed-out waiting for a response from the daemon.");
return this.end();
});
perItem = (item) => {
return action.onResponse(item, this);
};
Tnet.read_stream(this, perItem).then(() => {
timeout.cancel();
return this.end();
});
return null;
});
return null;
}
});
return null;
})();
return null;
};
waitForSocket = (timeout, cb) => { // wait for the daemon to connect to the other side of the socketFile
var poll, start;
start = +new Date();
return (poll = () => {
var elapsed;
if ((elapsed = +new Date() - start) > timeout) {
return cb('timeout');
}
if (!exists(socketFile)) {
return setTimeout(poll, 100);
}
return _on(Net.connect({
path: expandPath(socketFile)
}), {
error: function(err) {
this.end(); // close our side
verbose("Ignoring error while waiting:", err);
return setTimeout(poll, 100); // retry
},
connect: function() {
this.end(); // close our side, its just a probe
return cb(null);
}
});
})();
};
doHelp = () => {
var k, o;
console.log(cmd._[1] in Actions ? `shep ${cmd._[1]}\n ` + ((function() {
var i, len, ref, ref1, results;
ref1 = (ref = Actions[cmd._[1]].options) != null ? ref : [["No options."]];
results = [];
for (i = 0, len = ref1.length; i < len; i++) {
o = ref1[i];
results.push(o.join(' - '));
}
return results;
})()).join("\n ") : `shep <${((function() {
var results;
results = [];
for (k in Actions) {
results.push(k);
}
return results;
})()).join("|")}>`);
return process.exit(0);
};
// the subset of commands that cause status changes (and should show status)
statusChangeCommands = ['start', 'stop', 'enable', 'disable', 'add', 'remove', 'scale', 'replace'];
c = cmd._[0];
switch (c) { // some commands get handled without connecting to the daemon
case 'help':
doHelp();
break;
case 'version':
console.log(__VERSION__);
break;
case 'init':
doInit((err) => {
err && warn(err);
return exit_soon((err ? 1 : 0));
});
break;
case 'up':
Daemon.doStart(false);
waitForSocket(5000, (err) => {
if (err === 'timeout') {
warn("Daemon did not start within timeout.");
return exit_soon(1);
} else {
return sendServerCmd('status', () => {
return exit_soon(0);
});
}
});
break;
default:
// disabled special 'down' case, so that we create a 'down' message in the 'else' case below
// when 'down' then Daemon.doStop(true) # this is now called from actions/down
sendServerCmd(c, () => {
if (indexOf.call(statusChangeCommands, c) >= 0) {
return $.delay(500, () => {
return sendServerCmd('status', () => {
return exit_soon(0);
});
});
} else {
return exit_soon(0);
}
});
}
}).call(this);