def test_sanitize_output_use_pyt_false(self): # pty is not used, \r\n shouldn't be replaced with \n input_strs = [ 'foo', 'foo\n', 'foo\r\n', 'foo\nbar\nbaz\n', 'foo\r\nbar\r\nbaz\r\n', ] expected = [ 'foo', 'foo', 'foo', 'foo\nbar\nbaz', 'foo\r\nbar\r\nbaz', ] for input_str, expected_output in zip(input_strs, expected): output = sanitize_output(input_str, uses_pty=False) self.assertEqual(expected_output, output)
def test_sanitize_output_use_pyt_true(self): # pty is used, \r\n should be replaced with \n input_strs = [ "foo", "foo\n", "foo\r\n", "foo\nbar\nbaz\n", "foo\r\nbar\r\nbaz\r\n", ] expected = [ "foo", "foo", "foo", "foo\nbar\nbaz", "foo\nbar\nbaz", ] for input_str, expected_output in zip(input_strs, expected): output = sanitize_output(input_str, uses_pty=True) self.assertEqual(expected_output, output)
def run(self, cmd, timeout=None, quote=False, call_line_handler_func=False): """ Note: This function is based on paramiko's exec_command() method. :param timeout: How long to wait (in seconds) for the command to finish (optional). :type timeout: ``float`` :param call_line_handler_func: True to call handle_stdout_line_func function for each line of received stdout and handle_stderr_line_func for each line of stderr. :type call_line_handler_func: ``bool`` """ if quote: cmd = quote_unix(cmd) extra = {'_cmd': cmd} self.logger.info('Executing command', extra=extra) # Use the system default buffer size bufsize = -1 transport = self.client.get_transport() chan = transport.open_session() start_time = time.time() if cmd.startswith('sudo'): # Note that fabric does this as well. If you set pty, stdout and stderr # streams will be combined into one. # NOTE: If pty is used, every new line character \n will be converted to \r\n which # isn't desired. Because of that we sanitize the output and replace \r\n with \n at the # bottom of this method uses_pty = True chan.get_pty() else: uses_pty = False chan.exec_command(cmd) stdout = StringIO() stderr = StringIO() # Create a stdin file and immediately close it to prevent any # interactive script from hanging the process. stdin = chan.makefile('wb', bufsize) stdin.close() # Receive all the output # Note #1: This is used instead of chan.makefile approach to prevent # buffering issues and hanging if the executed command produces a lot # of output. # # Note #2: If you are going to remove "ready" checks inside the loop # you are going to have a bad time. Trying to consume from a channel # which is not ready will block for indefinitely. exit_status_ready = chan.exit_status_ready() if exit_status_ready: stdout_data = self._consume_stdout( chan=chan, call_line_handler_func=call_line_handler_func) stdout_data = stdout_data.getvalue() stderr_data = self._consume_stderr( chan=chan, call_line_handler_func=call_line_handler_func) stderr_data = stderr_data.getvalue() stdout.write(stdout_data) stderr.write(stderr_data) while not exit_status_ready: current_time = time.time() elapsed_time = (current_time - start_time) if timeout and (elapsed_time > timeout): # TODO: Is this the right way to clean up? chan.close() stdout = sanitize_output(stdout.getvalue(), uses_pty=uses_pty) stderr = sanitize_output(stderr.getvalue(), uses_pty=uses_pty) raise SSHCommandTimeoutError(cmd=cmd, timeout=timeout, stdout=stdout, stderr=stderr) stdout_data = self._consume_stdout( chan=chan, call_line_handler_func=call_line_handler_func) stdout_data = stdout_data.getvalue() stderr_data = self._consume_stderr( chan=chan, call_line_handler_func=call_line_handler_func) stderr_data = stderr_data.getvalue() stdout.write(stdout_data) stderr.write(stderr_data) # We need to check the exit status here, because the command could # print some output and exit during this sleep below. exit_status_ready = chan.exit_status_ready() if exit_status_ready: break # Short sleep to prevent busy waiting concurrency.sleep(self.SLEEP_DELAY) # print('Wait over. Channel must be ready for host: %s' % self.hostname) # Receive the exit status code of the command we ran. status = chan.recv_exit_status() stdout = sanitize_output(stdout.getvalue(), uses_pty=uses_pty) stderr = sanitize_output(stderr.getvalue(), uses_pty=uses_pty) extra = {'_status': status, '_stdout': stdout, '_stderr': stderr} self.logger.debug('Command finished', extra=extra) return [stdout, stderr, status]
def run(self, cmd, timeout=None, quote=False, call_line_handler_func=False): """ Note: This function is based on paramiko's exec_command() method. :param timeout: How long to wait (in seconds) for the command to finish (optional). :type timeout: ``float`` :param call_line_handler_func: True to call handle_stdout_line_func function for each line of received stdout and handle_stderr_line_func for each line of stderr. :type call_line_handler_func: ``bool`` """ if quote: cmd = quote_unix(cmd) extra = {'_cmd': cmd} self.logger.info('Executing command', extra=extra) # Use the system default buffer size bufsize = -1 transport = self.client.get_transport() chan = transport.open_session() start_time = time.time() if cmd.startswith('sudo'): # Note that fabric does this as well. If you set pty, stdout and stderr # streams will be combined into one. # NOTE: If pty is used, every new line character \n will be converted to \r\n which # isn't desired. Because of that we sanitize the output and replace \r\n with \n at the # bottom of this method uses_pty = True chan.get_pty() else: uses_pty = False chan.exec_command(cmd) stdout = StringIO() stderr = StringIO() # Create a stdin file and immediately close it to prevent any # interactive script from hanging the process. stdin = chan.makefile('wb', bufsize) stdin.close() # Receive all the output # Note #1: This is used instead of chan.makefile approach to prevent # buffering issues and hanging if the executed command produces a lot # of output. # # Note #2: If you are going to remove "ready" checks inside the loop # you are going to have a bad time. Trying to consume from a channel # which is not ready will block for indefinitely. exit_status_ready = chan.exit_status_ready() if exit_status_ready: stdout_data = self._consume_stdout(chan=chan, call_line_handler_func=call_line_handler_func) stdout_data = stdout_data.getvalue() stderr_data = self._consume_stderr(chan=chan, call_line_handler_func=call_line_handler_func) stderr_data = stderr_data.getvalue() stdout.write(stdout_data) stderr.write(stderr_data) while not exit_status_ready: current_time = time.time() elapsed_time = (current_time - start_time) if timeout and (elapsed_time > timeout): # TODO: Is this the right way to clean up? chan.close() stdout = sanitize_output(stdout.getvalue(), uses_pty=uses_pty) stderr = sanitize_output(stderr.getvalue(), uses_pty=uses_pty) raise SSHCommandTimeoutError(cmd=cmd, timeout=timeout, stdout=stdout, stderr=stderr) stdout_data = self._consume_stdout(chan=chan, call_line_handler_func=call_line_handler_func) stdout_data = stdout_data.getvalue() stderr_data = self._consume_stderr(chan=chan, call_line_handler_func=call_line_handler_func) stderr_data = stderr_data.getvalue() stdout.write(stdout_data) stderr.write(stderr_data) # We need to check the exit status here, because the command could # print some output and exit during this sleep below. exit_status_ready = chan.exit_status_ready() if exit_status_ready: break # Short sleep to prevent busy waiting eventlet.sleep(self.SLEEP_DELAY) # print('Wait over. Channel must be ready for host: %s' % self.hostname) # Receive the exit status code of the command we ran. status = chan.recv_exit_status() stdout = sanitize_output(stdout.getvalue(), uses_pty=uses_pty) stderr = sanitize_output(stderr.getvalue(), uses_pty=uses_pty) extra = {'_status': status, '_stdout': stdout, '_stderr': stderr} self.logger.debug('Command finished', extra=extra) return [stdout, stderr, status]