summaryrefslogtreecommitdiff
path: root/cliapp/runcmd.py
diff options
context:
space:
mode:
Diffstat (limited to 'cliapp/runcmd.py')
-rw-r--r--cliapp/runcmd.py77
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)
+