diff options
Diffstat (limited to 'cliapp/runcmd.py')
-rw-r--r-- | cliapp/runcmd.py | 77 |
1 files changed, 61 insertions, 16 deletions
diff --git a/cliapp/runcmd.py b/cliapp/runcmd.py index f45d42b..c578059 100644 --- a/cliapp/runcmd.py +++ b/cliapp/runcmd.py @@ -40,19 +40,26 @@ def runcmd(argv, *args, **kwargs): ''' - if 'ignore_fail' in kwargs: - ignore_fail = kwargs['ignore_fail'] - del kwargs['ignore_fail'] - else: - ignore_fail = False + our_options = ( + ('ignore_fail', False), + ('log_error', True), + ) + opts = {} + for name, default in our_options: + opts[name] = default + if name in kwargs: + opts[name] = kwargs[name] + del kwargs[name] exit, out, err = runcmd_unchecked(argv, *args, **kwargs) if exit != 0: msg = 'Command failed: %s\n%s' % (' '.join(argv), err) - if ignore_fail: - logging.info(msg) + if opts['ignore_fail']: + if opts['log_error']: + logging.info(msg) else: - logging.error(msg) + if opts['log_error']: + logging.error(msg) raise cliapp.AppException(msg) return out @@ -124,12 +131,6 @@ def _build_pipeline(argvs, pipe_stdin, pipe_stdout, pipe_stderr, kwargs): def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr): - logging.debug('PIPE=%d' % subprocess.PIPE) - logging.debug('STDOUT=%d' % subprocess.STDOUT) - logging.debug('pipe_stdin=%s' % repr(pipe_stdin)) - logging.debug('pipe_stdout=%s' % repr(pipe_stdout)) - logging.debug('pipe_stderr=%s' % repr(pipe_stderr)) - stdout_eof = False stderr_eof = False out = [] @@ -138,7 +139,6 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr): io_size = 1024 def set_nonblocking(fd): - logging.debug('set nonblocking fd=%d' % fd) flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) flags = flags | os.O_NONBLOCK fcntl.fcntl(fd, fcntl.F_SETFL, flags) @@ -202,8 +202,53 @@ def _run_pipeline(procs, feed_stdin, pipe_stdin, pipe_stdout, pipe_stderr): while still_running(): for p in procs: if p.returncode is None: - logging.debug('Waiting for child pid=%d to terminate' % p.pid) p.wait() return procs[-1].returncode, ''.join(out), ''.join(err) + + +def shell_quote(s): + '''Return a shell-quoted version of s.''' + + lower_ascii = 'abcdefghijklmnopqrstuvwxyz' + upper_ascii = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + digits = '0123456789' + punctuation = '-_/=.,:' + safe = set(lower_ascii + upper_ascii + digits + punctuation) + + quoted = [] + for c in s: + if c in safe: + quoted.append(c) + elif c == "'": + quoted.append('"\'"') + else: + quoted.append("'%c'" % c) + + return ''.join(quoted) + + +def ssh_runcmd(target, argv, **kwargs): # pragma: no cover + '''Run command in argv on remote host target. + + This is similar to runcmd, but the command is run on the remote + machine. The command is given as an argv array; elements in the + array are automatically quoted so they get passed to the other + side correctly. + + The target is given as-is to ssh, and may use any syntax ssh + accepts. + + Environment variables may or may not be passed to the remote + machine: this is dependent on the ssh and sshd configurations. + Invoke env(1) explicitly to pass in the variables you need to + exist on the other end. + + Pipelines are not supported. + + ''' + + local_argv = ['ssh', target, '--'] + map(shell_quote, argv) + return runcmd(local_argv, **kwargs) + |