stacker-utils
Version:
Stacker utils plugin
164 lines (138 loc) • 4.02 kB
text/coffeescript
node_spawn = require('child_process').spawn
escapeRegExp = require('./string').escapeRegExp
path = require 'path'
# globals
_ = require 'stacker/_'
{Promise} = require 'stacker/promise'
log = require 'stacker/log'
node_exec = Promise.promisify require('child_process').exec
setOptions = (opts) ->
{log, execDefaults} = opts
execDefaults = (cmd, opts) ->
# remove spaces from cmd and extract the bin name
cmd = cmd.replace /\/(.+?)\s+(.+?)\//g, '/$1$2/'
cmd = cmd.split(' ')[0].split(path.sep).slice(-1)[0]
debug = (type, msg) ->
log.debug type, msg.toString().trim() if msg
msg
_.defaults opts,
stdout: debug.bind null, "#{cmd} stdout >>".grey
stderr: debug.bind null, '#{cmd} stderr >>'.grey
timeout: 1000
###*
# @param {Array} cmds Array of string or array commands.
# String commands will use process.exec.
# Array commands will use process.spawn.
###
# runCmds = (cmds, opts = {}) ->
# _.defaults opts,
# sequential: false
# timeout: 1000
# execFunc = (cmd) ->
# if _.isArray cmd
# spawn.apply null, arguments
# else
# exec.apply null, arguments
# if sequential
# execFunc cmds.shift(), opts
# .then ->
# runCmds cmds, opts
# else
# pcmds = for c in cmds
# execFunc c, opts
# Promise.all pcmds
###*
@return Promise
Usage:
exec(cmd, opts).spread (stdout, stderr) ->
###
exec = (cmd, opts = {}) ->
execDefaults cmd, opts
log.debug "[exec]".grey, cmd
node_exec cmd, opts
.spread (stdout, stderr) ->
opts.stdout stdout
opts.stderr stderr
[stdout, stderr]
###*
@param {mixed} cmd String cmd or Array of [cmd, args]
@return {Number} Process ID
###
spawn = (cmd, args = [], opts = {}) ->
if _.isArray cmd
opts = args
args = cmd[1]
cmd = cmd[0]
execDefaults cmd, opts
# todo: use cancellable and timeout
new Promise (resolve, reject) ->
log.debug "[spawn]".grey, "#{cmd} #{args.join ' '}"
proc = node_spawn cmd, args, opts
if opts.detached
pid = proc.pid
proc.unref()
resolve pid: pid
else
_stdout = ''
_stderr = ''
proc.stdout.on 'data', (data) ->
_stdout += opts.stdout data
proc.stderr.on 'data', (data) ->
_stderr += opts.stderr data
proc.on 'close', (code) ->
ret = stdout: _stdout, stderr: _stderr, code: code
code is 0 and resolve(ret) or reject(ret)
# @return {Array} pids
pgrep = (cmd, opts = {}) ->
_.defaults opts,
timeout: 100
oldest: false
stdout: helpers.silent
flags = if opts.oldest then '-o' else ''
exec "pgrep #{flags} -d ',' -f #{cmd}", opts
.spread (stdout, stderr) ->
pids = stdout.trim().split(',').map (pid) ->
parseInt pid
_.compact pids
.catch (err) ->
# pgrep returns 1 when process is not found
if err.cause and err.cause.code is 1
Promise.resolve []
else
log.error '[pgrep]'.grey, err
Promise.reject err
kill = (pid, signal = 'SIGTERM') ->
if pid
log.debug '[kill]'.grey, "Sending #{signal} to #{pid}"
process.kill pid, signal
killAll = (cmd, signal = 'SIGTERM', opts = {}) ->
pgrep cmd, opts
.then (pids) ->
kill pid, signal for pid in pids
stats = (cmd, opts = {}) ->
# ps -A -c -o pid,%cpu,%mem,rss,time,etime,command | awk 'NR == 1 || /[V]Box/' | sed -e 's/^ */"/' -e 's/$/\"/g' -e $'s/[[:space:]]\{1,\}/","/g'
pscmd = 'ps -A -c -o pid,%cpu,%mem,rss,time,etime,args'
pscmd += " | awk 'NR == 1 || /#{escapeRegExp cmd}/"
pscmd += ' | sed -e \'s/^ */"/\' -e \'s/$/\"/g\' -e $\'s/[[:space:]]\{1,\}/","/g\''
exec pscmd, opts
.spread (stdout, stderr) ->
stdout.trim().split ','
helpers =
intercept: (msg) ->
process.stderr.write msg.toString()
''
# Discard output
ignore: (msg) ->
''
# Suppress debug output
silent: (msg) ->
msg
# API
module.exports =
setOptions: setOptions
exec: exec
spawn: spawn
pgrep: pgrep
kill: kill
killAll: killAll
stats: stats