Executes a command and returns its output. If the command's return code is non-zero or the command times out, an exception is raised.
(cmd, cmd_prepend="set -euo pipefail\n", stdout=PIPE, stderr=STDOUT,
timeout_secs=None, **popen_kwargs)
| 60 | |
| 61 | |
| 62 | def shell(cmd, cmd_prepend="set -euo pipefail\n", stdout=PIPE, stderr=STDOUT, |
| 63 | timeout_secs=None, **popen_kwargs): |
| 64 | """Executes a command and returns its output. If the command's return code is non-zero |
| 65 | or the command times out, an exception is raised. |
| 66 | """ |
| 67 | cmd = dedent(cmd.strip()) |
| 68 | if cmd_prepend: |
| 69 | cmd = cmd_prepend + cmd |
| 70 | LOG.debug("Running command with %s timeout: %s" % ( |
| 71 | "no" if timeout_secs is None else ("%s second" % timeout_secs), cmd)) |
| 72 | process = Popen(cmd, shell=True, executable="/bin/bash", stdout=stdout, stderr=stderr, |
| 73 | **popen_kwargs) |
| 74 | |
| 75 | stdout_fileno = process.stdout and process.stdout.fileno() |
| 76 | stderr_fileno = process.stderr and process.stderr.fileno() |
| 77 | remaining_fds = list() |
| 78 | if stdout_fileno is not None: |
| 79 | remaining_fds.append(stdout_fileno) |
| 80 | if stderr_fileno is not None: |
| 81 | remaining_fds.append(stderr_fileno) |
| 82 | stdout = list() |
| 83 | stderr = list() |
| 84 | |
| 85 | def _read_available_output(): |
| 86 | while True: |
| 87 | available_fds, _, _ = select(remaining_fds, [], [], 0) |
| 88 | if not available_fds: |
| 89 | return |
| 90 | for fd in available_fds: |
| 91 | data = os.read(fd, 4096) |
| 92 | if fd == stdout_fileno: |
| 93 | if not data: |
| 94 | del remaining_fds[0] |
| 95 | else: |
| 96 | stdout.append(data.decode()) |
| 97 | elif fd == stderr_fileno: |
| 98 | if not data: |
| 99 | del remaining_fds[-1] |
| 100 | else: |
| 101 | stderr.append(data.decode()) |
| 102 | |
| 103 | deadline = time() + timeout_secs if timeout_secs is not None else None |
| 104 | while True: |
| 105 | # The subprocess docs indicate that stdout/err need to be drained while waiting |
| 106 | # if the PIPE option is used. |
| 107 | _read_available_output() |
| 108 | retcode = process.poll() |
| 109 | if retcode is not None or (deadline and time() > deadline): |
| 110 | break |
| 111 | sleep(0.1) |
| 112 | _read_available_output() |
| 113 | |
| 114 | output = "".join(stdout) |
| 115 | if retcode == 0: |
| 116 | return output |
| 117 | |
| 118 | if not output: |
| 119 | output = "(No stdout)" |