Skip to content

Commit 58df5c0

Browse files
authored
Open pywinrm shell sessions with UTF-8 codepage (#6038)
* Drop six use for binary/string type identification With Python 3.6 being the minimum supported version, dropping six here and utilising 3.x type identifiers. * Use UTF-8 codepage in pywinrm shells This change opens a shell with the 65001 codepage, ensuring raw string responses are UTF-8 encoded. Fixes #6034.
1 parent d8a9c97 commit 58df5c0

3 files changed

Lines changed: 26 additions & 17 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Fixed
3131
* Restore Pack integration testing (it was inadvertently skipped) and stop testing against `bionic` and `el7`. #6135
3232
* Fix Popen.pid typo in st2tests. #6184
3333
* Bump tooz package to `6.2.0` to fix TLS. #6220 (@jk464)
34+
* Shells via `pywinrm` are initialized with the 65001 codepage to ensure raw string responses are UTF-8. #6034 (@stealthii)
3435

3536
Changed
3637
~~~~~~~

contrib/runners/winrm_runner/tests/unit/test_winrm_base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,9 @@ def test_winrm_run_cmd(self):
304304

305305
self.assertEqual(result.__dict__, expected_response.__dict__)
306306
mock_protocol.open_shell.assert_called_with(
307-
env_vars={"PATH": "C:\\st2\\bin"}, working_directory="C:\\st2"
307+
working_directory="C:\\st2",
308+
env_vars={"PATH": "C:\\st2\\bin"},
309+
codepage=65001,
308310
)
309311
mock_protocol.run_command.assert_called_with(
310312
123, "fake-command", ["arg1", "arg2"]
@@ -336,7 +338,9 @@ def test_winrm_run_cmd_timeout(self, mock_get_command_output):
336338

337339
self.assertEqual(result.__dict__, expected_response.__dict__)
338340
mock_protocol.open_shell.assert_called_with(
339-
env_vars={"PATH": "C:\\st2\\bin"}, working_directory="C:\\st2"
341+
working_directory="C:\\st2",
342+
env_vars={"PATH": "C:\\st2\\bin"},
343+
codepage=65001,
340344
)
341345
mock_protocol.run_command.assert_called_with(
342346
123, "fake-command", ["arg1", "arg2"]

contrib/runners/winrm_runner/winrm_runner/winrm_base.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import base64
1919
import os
2020
import re
21-
import six
2221
import time
2322

2423
from base64 import b64encode
@@ -194,11 +193,18 @@ def _winrm_get_command_output(self, protocol, shell_id, command_id):
194193
return b"".join(stdout_buffer), b"".join(stderr_buffer), return_code
195194

196195
def _winrm_run_cmd(self, session, command, args=(), env=None, cwd=None):
197-
# NOTE: this is copied from pywinrm because it doesn't support
198-
# passing env and working_directory from the Session.run_cmd.
199-
# It also doesn't support timeouts. All of these things have been
200-
# added
201-
shell_id = session.protocol.open_shell(env_vars=env, working_directory=cwd)
196+
"""Run a command on the remote host and return the standard output and
197+
error as a tuple.
198+
199+
Extended from pywinrm to support passing env and cwd to open_shell,
200+
as well as enforcing the UTF-8 codepage. Also supports timeouts.
201+
202+
"""
203+
shell_id = session.protocol.open_shell(
204+
working_directory=cwd,
205+
env_vars=env,
206+
codepage=65001,
207+
)
202208
command_id = session.protocol.run_command(shell_id, command, args)
203209
# try/catch is for custom timeout handing (StackStorm custom)
204210
try:
@@ -257,10 +263,10 @@ def _translate_response(self, response):
257263
}
258264

259265
# Ensure stdout and stderr is always a string
260-
if isinstance(result["stdout"], six.binary_type):
266+
if isinstance(result["stdout"], bytes):
261267
result["stdout"] = result["stdout"].decode("utf-8")
262268

263-
if isinstance(result["stderr"], six.binary_type):
269+
if isinstance(result["stderr"], bytes):
264270
result["stderr"] = result["stderr"].decode("utf-8")
265271

266272
# automatically convert result stdout/stderr from JSON strings to
@@ -316,7 +322,7 @@ def _upload(self, src_path_or_data, dst_path):
316322

317323
def _upload_chunk(self, dst_path, src_data):
318324
# adapted from https://github.com/diyan/pywinrm/issues/18
319-
if not isinstance(src_data, six.binary_type):
325+
if not isinstance(src_data, bytes):
320326
src_data = src_data.encode("utf-8")
321327

322328
ps = """$filePath = "{dst_path}"
@@ -446,7 +452,7 @@ def _param_to_ps(self, param):
446452
ps_str = ""
447453
if param is None:
448454
ps_str = "$null"
449-
elif isinstance(param, six.string_types):
455+
elif isinstance(param, str):
450456
ps_str = '"' + self._multireplace(param, PS_ESCAPE_SEQUENCES) + '"'
451457
elif isinstance(param, bool):
452458
ps_str = "$true" if param else "$false"
@@ -459,7 +465,7 @@ def _param_to_ps(self, param):
459465
ps_str += "; ".join(
460466
[
461467
(self._param_to_ps(k) + " = " + self._param_to_ps(v))
462-
for k, v in six.iteritems(param)
468+
for k, v in param.items()
463469
]
464470
)
465471
ps_str += "}"
@@ -473,7 +479,7 @@ def _transform_params_to_ps(self, positional_args, named_args):
473479
positional_args[i] = self._param_to_ps(arg)
474480

475481
if named_args:
476-
for key, value in six.iteritems(named_args):
482+
for key, value in named_args.items():
477483
named_args[key] = self._param_to_ps(value)
478484

479485
return positional_args, named_args
@@ -486,9 +492,7 @@ def create_ps_params_string(self, positional_args, named_args):
486492
# concatenate them into a long string
487493
ps_params_str = ""
488494
if named_args:
489-
ps_params_str += " ".join(
490-
[(k + " " + v) for k, v in six.iteritems(named_args)]
491-
)
495+
ps_params_str += " ".join([(k + " " + v) for k, v in named_args.items()])
492496
ps_params_str += " "
493497
if positional_args:
494498
ps_params_str += " ".join(positional_args)

0 commit comments

Comments
 (0)