From f0134079e3aaff3a6c8d31ff55cd3d01c82f2bc8 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 18 Jan 2018 11:28:33 +1000 Subject: [PATCH] win_script: work when argument exceeds stdin buffer size (#33855) * win_script: work when argument exceeds stdin buffer size * Added test for large argument being passed --- lib/ansible/plugins/action/script.py | 12 ++++-- lib/ansible/plugins/connection/winrm.py | 17 ++++---- .../targets/win_script/tasks/main.yml | 41 +++++++++++++------ 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index 62f35cf8976..ddf7a279767 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -22,8 +22,9 @@ import re import shlex from ansible.errors import AnsibleError, AnsibleAction, AnsibleActionDone, AnsibleActionFail, AnsibleActionSkip -from ansible.module_utils._text import to_native, to_text +from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.plugins.action import ActionBase +from ansible.plugins.shell.powershell import exec_wrapper class ActionModule(ActionBase): @@ -114,9 +115,14 @@ class ActionModule(ActionBase): script_cmd = self._connection._shell.wrap_for_exec(script_cmd) exec_data = None - # HACK: come up with a sane way to pass around env outside the command + # WinRM requires a special wrapper to work with environment variables if self._connection.transport == "winrm": - exec_data = self._connection._create_raw_wrapper_payload(script_cmd, env_dict) + pay = self._connection._create_raw_wrapper_payload(script_cmd, + env_dict) + exec_data = exec_wrapper.replace(b"$json_raw = ''", + b"$json_raw = @'\r\n%s\r\n'@" + % to_bytes(pay)) + script_cmd = "-" result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir)) diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py index 61726fabef2..65ddd3b901c 100644 --- a/lib/ansible/plugins/connection/winrm.py +++ b/lib/ansible/plugins/connection/winrm.py @@ -391,8 +391,6 @@ class Connection(ConnectionBase): stdin_push_failed = False command_id = self.protocol.run_command(self.shell_id, to_bytes(command), map(to_bytes, args), console_mode_stdin=(stdin_iterator is None)) - # TODO: try/except around this, so we can get/return the command result on a broken pipe or other failure (probably more useful than the 500 that - # comes from this) try: if stdin_iterator: for (data, is_last) in stdin_iterator: @@ -400,12 +398,9 @@ class Connection(ConnectionBase): except Exception as ex: from traceback import format_exc - display.warning("FATAL ERROR DURING FILE TRANSFER: %s" % format_exc()) + display.warning("FATAL ERROR DURING FILE TRANSFER: %s" % to_text(ex)) stdin_push_failed = True - if stdin_push_failed: - raise AnsibleError('winrm send_input failed') - # NB: this can hang if the receiver is still running (eg, network failed a Send request but the server's still happy). # FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure. resptuple = self.protocol.get_command_output(self.shell_id, command_id) @@ -423,7 +418,11 @@ class Connection(ConnectionBase): display.vvvvvv('WINRM STDERR %s' % to_text(response.std_err), host=self._winrm_host) if stdin_push_failed: - raise AnsibleError('winrm send_input failed; \nstdout: %s\nstderr %s' % (response.std_out, response.std_err)) + stderr = to_bytes(response.std_err, encoding='utf-8') + if self.is_clixml(stderr): + stderr = self.parse_clixml_stream(stderr) + + raise AnsibleError('winrm send_input failed; \nstdout: %s\nstderr %s' % (response.std_out, stderr)) return response finally: @@ -456,7 +455,9 @@ class Connection(ConnectionBase): 'powershell_modules': {}, 'actions': ['exec'], 'exec': to_text(base64.b64encode(to_bytes(leaf_exec))), - 'environment': environment + 'environment': environment, + 'min_ps_version': None, + 'min_os_version': None } return json.dumps(payload) diff --git a/test/integration/targets/win_script/tasks/main.yml b/test/integration/targets/win_script/tasks/main.yml index a46218da5c7..cee28d3fab3 100644 --- a/test/integration/targets/win_script/tasks/main.yml +++ b/test/integration/targets/win_script/tasks/main.yml @@ -54,6 +54,24 @@ - "test_script_with_args_result is not failed" - "test_script_with_args_result is changed" +# Bug: https://github.com/ansible/ansible/issues/32850 +- name: set fact of long string + set_fact: + long_string: "{{ lookup('pipe', 'printf \"a%.0s\" {1..1000}') }}" + +- name: run test script with args that exceed the stdin buffer + script: test_script_with_args.ps1 {{ long_string }} + register: test_script_with_large_args_result + +- name: check that script ran and received arguments correctly + assert: + that: + - test_script_with_large_args_result.rc == 0 + - test_script_with_large_args_result.stdout == long_string + "\r\n" + - not test_script_with_large_args_result.stderr + - test_script_with_large_args_result is not failed + - test_script_with_large_args_result is changed + - name: run test script that takes parameters passed via splatting script: test_script_with_splatting.ps1 @{ This = 'this'; That = '{{ test_win_script_value }}'; Other = 'other'} register: test_script_with_splatting_result @@ -201,20 +219,17 @@ - "test_script_bool_result.stdout_lines[0] == 'System.Boolean'" - "test_script_bool_result.stdout_lines[1] == 'True'" -# FIXME: re-enable this test once script can run under the wrapper with powershell -#- name: run test script that uses envvars -# script: test_script_with_env.ps1 -# environment: -# taskenv: task -# register: test_script_env_result -# -#- name: ensure that script ran and that environment var was passed -# assert: -# that: -# - test_script_env_result is successful -# - test_script_env_result.stdout_lines[0] == 'task' -# +- name: run test script that uses envvars + script: test_script_with_env.ps1 + environment: + taskenv: task + register: test_script_env_result +- name: ensure that script ran and that environment var was passed + assert: + that: + - test_script_env_result is successful + - test_script_env_result.stdout_lines[0] == 'task' # check mode - name: Run test script that creates a file in check mode