Port processutils to Python 3
Add encoding and errors parameters to execute() and ssh_execute(). By default, use the locale encoding in strict mode on Python 2, or the locale encoding with the 'surrogateescape' error handler on Python 3. Fix also unit tests to use bytes strings for stdin, stdout and stderr. Without this change, tests are failing with Python 3 when run with: PYTHON="python -bb" testr run Using -bb, Python 3 raises a TypeError when a bytes string is casted to a text string, which occurred in many places. Change-Id: I655d5abf932c9a104e3ab487e23c372377f7096a
This commit is contained in:
parent
deb0152cbc
commit
7c7493feb5
@ -23,6 +23,7 @@ import os
|
|||||||
import random
|
import random
|
||||||
import shlex
|
import shlex
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from oslo.utils import importutils
|
from oslo.utils import importutils
|
||||||
@ -142,6 +143,13 @@ def execute(*cmd, **kwargs):
|
|||||||
last attempt, and LOG_ALL_ERRORS requires
|
last attempt, and LOG_ALL_ERRORS requires
|
||||||
logging on each occurence of an error.
|
logging on each occurence of an error.
|
||||||
:type log_errors: integer.
|
:type log_errors: integer.
|
||||||
|
:param encoding: encoding used to decode stdout and stderr,
|
||||||
|
sys.getfilesystemencoding() by default.
|
||||||
|
:type encoding: str
|
||||||
|
:param errors: error handler used to decode stdout and stderr,
|
||||||
|
default: 'surrogateescape' on Python 3,
|
||||||
|
'strict' on Python 2.
|
||||||
|
:type errors: str
|
||||||
:returns: (stdout, stderr) from process execution
|
:returns: (stdout, stderr) from process execution
|
||||||
:raises: :class:`UnknownArgumentError` on
|
:raises: :class:`UnknownArgumentError` on
|
||||||
receiving unknown arguments
|
receiving unknown arguments
|
||||||
@ -160,6 +168,8 @@ def execute(*cmd, **kwargs):
|
|||||||
shell = kwargs.pop('shell', False)
|
shell = kwargs.pop('shell', False)
|
||||||
loglevel = kwargs.pop('loglevel', logging.DEBUG)
|
loglevel = kwargs.pop('loglevel', logging.DEBUG)
|
||||||
log_errors = kwargs.pop('log_errors', None)
|
log_errors = kwargs.pop('log_errors', None)
|
||||||
|
encoding = kwargs.pop('encoding', sys.getfilesystemencoding())
|
||||||
|
errors = kwargs.pop('errors', 'surrogateescape' if six.PY3 else 'strict')
|
||||||
|
|
||||||
if isinstance(check_exit_code, bool):
|
if isinstance(check_exit_code, bool):
|
||||||
ignore_exit_code = not check_exit_code
|
ignore_exit_code = not check_exit_code
|
||||||
@ -214,6 +224,11 @@ def execute(*cmd, **kwargs):
|
|||||||
end_time = time.time() - start_time
|
end_time = time.time() - start_time
|
||||||
LOG.log(loglevel, 'CMD "%s" returned: %s in %ss' %
|
LOG.log(loglevel, 'CMD "%s" returned: %s in %ss' %
|
||||||
(sanitized_cmd, _returncode, end_time))
|
(sanitized_cmd, _returncode, end_time))
|
||||||
|
if result is not None:
|
||||||
|
(stdout, stderr) = result
|
||||||
|
stdout = stdout.decode(encoding, errors)
|
||||||
|
stderr = stderr.decode(encoding, errors)
|
||||||
|
result = (stdout, stderr)
|
||||||
if not ignore_exit_code and _returncode not in check_exit_code:
|
if not ignore_exit_code and _returncode not in check_exit_code:
|
||||||
(stdout, stderr) = result
|
(stdout, stderr) = result
|
||||||
sanitized_stdout = strutils.mask_password(stdout)
|
sanitized_stdout = strutils.mask_password(stdout)
|
||||||
@ -292,7 +307,9 @@ def trycmd(*args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def ssh_execute(ssh, cmd, process_input=None,
|
def ssh_execute(ssh, cmd, process_input=None,
|
||||||
addl_env=None, check_exit_code=True):
|
addl_env=None, check_exit_code=True,
|
||||||
|
encoding=sys.getfilesystemencoding(),
|
||||||
|
errors='surrogateescape' if six.PY3 else 'strict'):
|
||||||
sanitized_cmd = strutils.mask_password(cmd)
|
sanitized_cmd = strutils.mask_password(cmd)
|
||||||
LOG.debug('Running cmd (SSH): %s', sanitized_cmd)
|
LOG.debug('Running cmd (SSH): %s', sanitized_cmd)
|
||||||
if addl_env:
|
if addl_env:
|
||||||
@ -308,8 +325,10 @@ def ssh_execute(ssh, cmd, process_input=None,
|
|||||||
# NOTE(justinsb): This seems suspicious...
|
# NOTE(justinsb): This seems suspicious...
|
||||||
# ...other SSH clients have buffering issues with this approach
|
# ...other SSH clients have buffering issues with this approach
|
||||||
stdout = stdout_stream.read()
|
stdout = stdout_stream.read()
|
||||||
|
stdout = stdout.decode(encoding, errors)
|
||||||
sanitized_stdout = strutils.mask_password(stdout)
|
sanitized_stdout = strutils.mask_password(stdout)
|
||||||
stderr = stderr_stream.read()
|
stderr = stderr_stream.read()
|
||||||
|
stderr = stderr.decode(encoding, errors)
|
||||||
sanitized_stderr = strutils.mask_password(stderr)
|
sanitized_stderr = strutils.mask_password(stderr)
|
||||||
|
|
||||||
stdin_stream.close()
|
stdin_stream.close()
|
||||||
|
@ -295,7 +295,7 @@ grep foo
|
|||||||
|
|
||||||
out, err = processutils.execute('/usr/bin/env', env_variables=env_vars)
|
out, err = processutils.execute('/usr/bin/env', env_variables=env_vars)
|
||||||
|
|
||||||
self.assertIn(b'SUPER_UNIQUE_VAR=The answer is 42', out)
|
self.assertIn('SUPER_UNIQUE_VAR=The answer is 42', out)
|
||||||
|
|
||||||
def test_exception_and_masking(self):
|
def test_exception_and_masking(self):
|
||||||
tmpfilename = self.create_tempfiles(
|
tmpfilename = self.create_tempfiles(
|
||||||
@ -426,7 +426,7 @@ class FakeSshChannel(object):
|
|||||||
return self.rc
|
return self.rc
|
||||||
|
|
||||||
|
|
||||||
class FakeSshStream(six.StringIO):
|
class FakeSshStream(six.BytesIO):
|
||||||
def setup_channel(self, rc):
|
def setup_channel(self, rc):
|
||||||
self.channel = FakeSshChannel(rc)
|
self.channel = FakeSshChannel(rc)
|
||||||
|
|
||||||
@ -436,11 +436,11 @@ class FakeSshConnection(object):
|
|||||||
self.rc = rc
|
self.rc = rc
|
||||||
|
|
||||||
def exec_command(self, cmd):
|
def exec_command(self, cmd):
|
||||||
stdout = FakeSshStream('stdout')
|
stdout = FakeSshStream(b'stdout')
|
||||||
stdout.setup_channel(self.rc)
|
stdout.setup_channel(self.rc)
|
||||||
return (six.StringIO(),
|
return (six.BytesIO(),
|
||||||
stdout,
|
stdout,
|
||||||
six.StringIO('stderr'))
|
six.BytesIO(b'stderr'))
|
||||||
|
|
||||||
|
|
||||||
class SshExecuteTestCase(test_base.BaseTestCase):
|
class SshExecuteTestCase(test_base.BaseTestCase):
|
||||||
@ -465,13 +465,13 @@ class SshExecuteTestCase(test_base.BaseTestCase):
|
|||||||
|
|
||||||
def _test_compromising_ssh(self, rc, check):
|
def _test_compromising_ssh(self, rc, check):
|
||||||
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
||||||
fake_stdin = six.StringIO()
|
fake_stdin = six.BytesIO()
|
||||||
|
|
||||||
fake_stdout = mock.Mock()
|
fake_stdout = mock.Mock()
|
||||||
fake_stdout.channel.recv_exit_status.return_value = rc
|
fake_stdout.channel.recv_exit_status.return_value = rc
|
||||||
fake_stdout.read.return_value = 'password="secret"'
|
fake_stdout.read.return_value = b'password="secret"'
|
||||||
|
|
||||||
fake_stderr = six.StringIO('password="foobar"')
|
fake_stderr = six.BytesIO(b'password="foobar"')
|
||||||
|
|
||||||
command = 'ls --password="bar"'
|
command = 'ls --password="bar"'
|
||||||
|
|
||||||
|
@ -296,7 +296,7 @@ grep foo
|
|||||||
|
|
||||||
out, err = processutils.execute('/usr/bin/env', env_variables=env_vars)
|
out, err = processutils.execute('/usr/bin/env', env_variables=env_vars)
|
||||||
|
|
||||||
self.assertIn(b'SUPER_UNIQUE_VAR=The answer is 42', out)
|
self.assertIn('SUPER_UNIQUE_VAR=The answer is 42', out)
|
||||||
|
|
||||||
def test_exception_and_masking(self):
|
def test_exception_and_masking(self):
|
||||||
tmpfilename = self.create_tempfiles(
|
tmpfilename = self.create_tempfiles(
|
||||||
@ -427,7 +427,7 @@ class FakeSshChannel(object):
|
|||||||
return self.rc
|
return self.rc
|
||||||
|
|
||||||
|
|
||||||
class FakeSshStream(six.StringIO):
|
class FakeSshStream(six.BytesIO):
|
||||||
def setup_channel(self, rc):
|
def setup_channel(self, rc):
|
||||||
self.channel = FakeSshChannel(rc)
|
self.channel = FakeSshChannel(rc)
|
||||||
|
|
||||||
@ -437,11 +437,11 @@ class FakeSshConnection(object):
|
|||||||
self.rc = rc
|
self.rc = rc
|
||||||
|
|
||||||
def exec_command(self, cmd):
|
def exec_command(self, cmd):
|
||||||
stdout = FakeSshStream('stdout')
|
stdout = FakeSshStream(b'stdout')
|
||||||
stdout.setup_channel(self.rc)
|
stdout.setup_channel(self.rc)
|
||||||
return (six.StringIO(),
|
return (six.BytesIO(),
|
||||||
stdout,
|
stdout,
|
||||||
six.StringIO('stderr'))
|
six.BytesIO(b'stderr'))
|
||||||
|
|
||||||
|
|
||||||
class SshExecuteTestCase(test_base.BaseTestCase):
|
class SshExecuteTestCase(test_base.BaseTestCase):
|
||||||
@ -466,13 +466,13 @@ class SshExecuteTestCase(test_base.BaseTestCase):
|
|||||||
|
|
||||||
def _test_compromising_ssh(self, rc, check):
|
def _test_compromising_ssh(self, rc, check):
|
||||||
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
||||||
fake_stdin = six.StringIO()
|
fake_stdin = six.BytesIO()
|
||||||
|
|
||||||
fake_stdout = mock.Mock()
|
fake_stdout = mock.Mock()
|
||||||
fake_stdout.channel.recv_exit_status.return_value = rc
|
fake_stdout.channel.recv_exit_status.return_value = rc
|
||||||
fake_stdout.read.return_value = 'password="secret"'
|
fake_stdout.read.return_value = b'password="secret"'
|
||||||
|
|
||||||
fake_stderr = six.StringIO('password="foobar"')
|
fake_stderr = six.BytesIO(b'password="foobar"')
|
||||||
|
|
||||||
command = 'ls --password="bar"'
|
command = 'ls --password="bar"'
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user