UNPKG

procfiled

Version:

Node.js process manager for Procfile-based applications

356 lines (288 loc) 8.77 kB
#!/usr/bin/env node // Copyright IBM Corp. 2012,2015. All Rights Reserved. // Node module: procfiled // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT var path = require("path"); var events = require("events"); var fs = require("fs"); var colors = require("./lib/colors"); var quote = require("shell-quote").quote; var program = require("commander"); var display = require("./lib/console").Console; var nf = require("./package.json"); program.version(nf.version); program.option("-j, --procfile <FILE>", "load procfile FILE", "Procfile"); program.option( "-e, --env <FILE>", "load environment from FILE, a comma-separated list", ".env" ); program.option( "-p, --port <PORT>", "start indexing ports at number PORT", 0 ); // Procfiled Event Bus/Emitter // var emitter = new events.EventEmitter(); emitter.once("killall", function (signal) { display.Done("Killing all processes with signal ", signal); }); emitter.setMaxListeners(50); var _proc = require("./lib/proc"); var start = _proc.start; var once = _proc.once; var _procfile = require("./lib/procfile"); var loadProc = _procfile.loadProc; var _envs = require("./lib/envs"); var loadEnvs = _envs.loadEnvs; var _requirements = require("./lib/requirements"); var getreqs = _requirements.getreqs; var calculatePadding = _requirements.calculatePadding; var startProxies = require("./lib/proxy").startProxies; var startForward = require("./lib/forward").startForward; // Kill All Child Processes on SIGINT process.once("SIGINT", function () { display.Warn("Interrupted by User"); emitter.emit("killall", "SIGINT"); }); program .command("start [procs]") .usage("[Options] [Processes] e.g. web=1,log=2,api") .option("-s, --showenvs", "show ENV variables on start", false) .option("-x, --proxy <PORT>", "start a load balancing proxy on PORT") .option("--ssl-key <KEY FILE>", "a key file to use when proxying SSL") .option("--ssl-cert <CERT FILE>", "a cert file to use when proxying SSL") .option("-f, --forward <PORT>", "start a forward proxy on PORT") .option( "-i, --intercept <HOSTNAME>", "set forward proxy to intercept HOSTNAME", null ) .option( "-r, --raw", "raw log output with no app name, timestamp, wrap or trim", false ) .option("-t, --trim <N>", "trim logs to N characters", 0) .option("-w, --wrap", "wrap logs (negates trim)") .description("Start the jobs in the Procfile") .action(function (args) { var command = this; var envs = loadEnvs(program.env); var proc = loadProc(program.procfile); if (!proc) { return; } if (command.showenvs) { for (var key in envs) { display.Alert("env %s=%s", key, envs[key]); } } var reqs = getreqs(args, proc); display.padding = calculatePadding(reqs); display.raw = command.raw; if (command.wrap) { display.wrapline = process.stdout.columns - display.padding - 7; display.trimline = 0; display.Alert("Wrapping display Output to %d Columns", display.wrapline); } else { display.trimline = command.trim; if (display.trimline > 0) { display.Alert( "Trimming display Output to %d Columns", display.trimline ); } } if (command.forward) { startForward(command.forward, command.intercept, emitter); } startProxies( reqs, proc, command, emitter, program.port || envs.PORT || process.env.PORT || 5000 ); start( proc, reqs, envs, program.port || envs.PORT || process.env.PORT || 5000, emitter ); }); program .command("run <COMMAND...>") .usage("[Options]") .option("-s, --showenvs", "show ENV variables on start", false) .description("Run a one off process using the ENV variables") .action(function (args) { var command = this; var envs = loadEnvs(program.env); var callback = function (code) { process.exit(code); }; if (!args || !args.length) { return; } var input = quote(args); if (command.showenvs) { for (var key in envs) { display.Alert("env %s=%s", key, envs[key]); } } display.trimline = process.stdout.columns - 5; once(input, envs, callback); }); var exporters = require("./lib/exporters"); program .command("export [PROCS]") .option( "-a, --app <NAME>", "export upstart application as NAME", "procfiled" ) .option("-u, --user <NAME>", "export upstart user as NAME", "root") .option("-o, --out <DIR>", "export upstart files to DIR", ".") .option("-c, --cwd <DIR>", "change current working directory to DIR") .option("-g, --gid <GID>", "set gid of upstart config to GID") .option("-l, --log <DIR>", "specify upstart log directory", "/var/log") .option( "-t, --type <TYPE>", "export file to TYPE (default upstart)", "upstart" ) .option("-m, --template <DIR>", "use template folder") .description("Export to an upstart job independent of procfiled") .action(function (procArgs) { var command = this; var envs = loadEnvs(program.env); var procs = loadProc(program.procfile); if (!procs) { return; } var req = getreqs(procArgs, procs); // Variables for Upstart Template var config = { application: command.app, cwd: path.resolve(process.cwd(), command.cwd || ""), user: command.user, logs: command.log, envs: envs, group: command.gid || command.user, template: command.template, }; config.envfile = path.resolve(program.env); var writeout; if (exporters[command.type]) { writeout = exporters[command.type]; } else { display.Error("Unknown Export Format", command.type); process.exit(1); } // Check for Upstart User // friendly warning - does not stop export var userExists = false; fs.readFileSync("/etc/passwd") .toString() .split(/\n/) .forEach(function (line) { if (line.match(/^[^:]*/)[0] == config.user) { userExists = true; } }); if (!userExists) { display.Warn( display.fmt("User %s Does Not Exist on System", config.user) ); } var baseport = parseInt( program.port || envs.PORT || process.env.PORT || 5000 ); var baseport_i = 0; var baseport_j = 0; var envl = []; config.processes = []; // This is ugly because of shitty support for array copying // Cleanup is definitely required for (var key in req) { var c = {}; var cmd = procs[key]; if (!cmd) { display.Warn( "Required Key '%s' Does Not Exist in Procfile Definition", key ); continue; } var n = req[key]; config.processes.push({ process: key, n: n }); c.process = key; c.command = cmd; for (var _ in config) { c[_] = config[_]; } c.numbers = []; for (var i = 1; i <= n; i++) { var conf = {}; conf.number = i; for (_ in c) { conf[_] = c[_]; } conf.port = baseport + baseport_i + baseport_j * 100; envl = []; for (key in envs) { envl.push({ key: key, value: envs[key], }); } envl.push({ key: "PORT", value: conf.port }); envl.push({ key: "PROCFILED_WORKER_NAME", value: conf.process + "." + conf.number, }); conf.envs = envl; // Write the APP-PROCESS-N.conf File writeout.procfiled_app_n(conf, command.out); baseport_i++; c.numbers.push({ number: i }); } envl = []; for (key in envs) { envl.push({ key: key, value: envs[key], }); } c.envs = envl; // Write the APP-Process.conf File writeout.procfiled_app(c, command.out); baseport_i = 0; baseport_j++; } // Write the APP.conf File writeout.procfiled(config, command.out); }); program.parse(process.argv); if (!process.argv.slice(2).length) { const logo = ` ┏━┓┏━┓┏━┓┏━╸┏━╸╻╻ ┏━╸╺┳┓ ┣━┛┣┳┛┃ ┃┃ ┣╸ ┃┃ ┣╸ ┃┃ ╹ ╹┗╸┗━┛┗━╸╹ ╹┗━╸┗━╸╺┻┛ `; const logoLines = logo.split("\n"); const colorFunctions = [ colors.cyan, colors.yellow, colors.magenta, colors.green, ]; logoLines.forEach((line, index) => { console.log(colorFunctions[index % colorFunctions.length](line)); }); program.outputHelp(); process.exit(1); }