win_script: add support for become and centralise exec wrapper builder (#45582)
* win_script: add support for become and centralise exec wrapper builder * satisfying the pep8 gods * do not scan for module dependencies when running as a script
This commit is contained in:
parent
03dbb1d9c4
commit
d81249994e
7 changed files with 101 additions and 73 deletions
2
changelogs/fragments/win_script-become.yaml
Normal file
2
changelogs/fragments/win_script-become.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- win_script - added support for running a script with become
|
|
@ -688,6 +688,69 @@ def _is_binary(b_module_data):
|
|||
return bool(start.translate(None, textchars))
|
||||
|
||||
|
||||
def _create_powershell_wrapper(b_module_data, module_args, environment,
|
||||
async_timeout, become, become_method,
|
||||
become_user, become_password, become_flags,
|
||||
scan_dependencies=True):
|
||||
# creates the manifest/wrapper used in PowerShell modules to enable things
|
||||
# like become and async - this is also called in action/script.py
|
||||
exec_manifest = dict(
|
||||
module_entry=to_text(base64.b64encode(b_module_data)),
|
||||
powershell_modules=dict(),
|
||||
module_args=module_args,
|
||||
actions=['exec'],
|
||||
environment=environment
|
||||
)
|
||||
|
||||
exec_manifest['exec'] = to_text(base64.b64encode(to_bytes(leaf_exec)))
|
||||
|
||||
if async_timeout > 0:
|
||||
exec_manifest["actions"].insert(0, 'async_watchdog')
|
||||
exec_manifest["async_watchdog"] = to_text(
|
||||
base64.b64encode(to_bytes(async_watchdog)))
|
||||
exec_manifest["actions"].insert(0, 'async_wrapper')
|
||||
exec_manifest["async_wrapper"] = to_text(
|
||||
base64.b64encode(to_bytes(async_wrapper)))
|
||||
exec_manifest["async_jid"] = str(random.randint(0, 999999999999))
|
||||
exec_manifest["async_timeout_sec"] = async_timeout
|
||||
|
||||
if become and become_method == 'runas':
|
||||
exec_manifest["actions"].insert(0, 'become')
|
||||
exec_manifest["become_user"] = become_user
|
||||
exec_manifest["become_password"] = become_password
|
||||
exec_manifest['become_flags'] = become_flags
|
||||
exec_manifest["become"] = to_text(
|
||||
base64.b64encode(to_bytes(become_wrapper)))
|
||||
|
||||
finder = PSModuleDepFinder()
|
||||
|
||||
# we don't want to scan for any module_utils or other module related flags
|
||||
# if scan_dependencies=False - action/script sets to False
|
||||
if scan_dependencies:
|
||||
finder.scan_module(b_module_data)
|
||||
|
||||
for name, data in finder.modules.items():
|
||||
b64_data = to_text(base64.b64encode(data))
|
||||
exec_manifest['powershell_modules'][name] = b64_data
|
||||
|
||||
exec_manifest['min_ps_version'] = finder.ps_version
|
||||
exec_manifest['min_os_version'] = finder.os_version
|
||||
if finder.become and 'become' not in exec_manifest['actions']:
|
||||
exec_manifest['actions'].insert(0, 'become')
|
||||
exec_manifest['become_user'] = 'SYSTEM'
|
||||
exec_manifest['become_password'] = None
|
||||
exec_manifest['become_flags'] = None
|
||||
exec_manifest['become'] = to_text(
|
||||
base64.b64encode(to_bytes(become_wrapper)))
|
||||
|
||||
# FUTURE: smuggle this back as a dict instead of serializing here;
|
||||
# the connection plugin may need to modify it
|
||||
b_json = to_bytes(json.dumps(exec_manifest))
|
||||
b_data = exec_wrapper.replace(b"$json_raw = ''",
|
||||
b"$json_raw = @'\r\n%s\r\n'@" % b_json)
|
||||
return b_data
|
||||
|
||||
|
||||
def _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression, async_timeout, become,
|
||||
become_method, become_user, become_password, become_flags, environment):
|
||||
"""
|
||||
|
@ -867,53 +930,14 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|||
# it can fail in the presence of the UTF8 BOM commonly added by
|
||||
# Windows text editors
|
||||
shebang = u'#!powershell'
|
||||
|
||||
exec_manifest = dict(
|
||||
module_entry=to_text(base64.b64encode(b_module_data)),
|
||||
powershell_modules=dict(),
|
||||
module_args=module_args,
|
||||
actions=['exec'],
|
||||
environment=environment
|
||||
# create the common exec wrapper payload and set that as the module_data
|
||||
# bytes
|
||||
b_module_data = _create_powershell_wrapper(
|
||||
b_module_data, module_args, environment, async_timeout, become,
|
||||
become_method, become_user, become_password, become_flags,
|
||||
scan_dependencies=True
|
||||
)
|
||||
|
||||
exec_manifest['exec'] = to_text(base64.b64encode(to_bytes(leaf_exec)))
|
||||
|
||||
if async_timeout > 0:
|
||||
exec_manifest["actions"].insert(0, 'async_watchdog')
|
||||
exec_manifest["async_watchdog"] = to_text(base64.b64encode(to_bytes(async_watchdog)))
|
||||
exec_manifest["actions"].insert(0, 'async_wrapper')
|
||||
exec_manifest["async_wrapper"] = to_text(base64.b64encode(to_bytes(async_wrapper)))
|
||||
exec_manifest["async_jid"] = str(random.randint(0, 999999999999))
|
||||
exec_manifest["async_timeout_sec"] = async_timeout
|
||||
|
||||
if become and become_method == 'runas':
|
||||
exec_manifest["actions"].insert(0, 'become')
|
||||
exec_manifest["become_user"] = become_user
|
||||
exec_manifest["become_password"] = become_password
|
||||
exec_manifest['become_flags'] = become_flags
|
||||
exec_manifest["become"] = to_text(base64.b64encode(to_bytes(become_wrapper)))
|
||||
|
||||
finder = PSModuleDepFinder()
|
||||
finder.scan_module(b_module_data)
|
||||
|
||||
for name, data in finder.modules.items():
|
||||
b64_data = to_text(base64.b64encode(data))
|
||||
exec_manifest['powershell_modules'][name] = b64_data
|
||||
|
||||
exec_manifest['min_ps_version'] = finder.ps_version
|
||||
exec_manifest['min_os_version'] = finder.os_version
|
||||
if finder.become and 'become' not in exec_manifest['actions']:
|
||||
exec_manifest['actions'].insert(0, 'become')
|
||||
exec_manifest['become_user'] = 'SYSTEM'
|
||||
exec_manifest['become_password'] = None
|
||||
exec_manifest['become_flags'] = None
|
||||
exec_manifest['become'] = to_text(base64.b64encode(to_bytes(become_wrapper)))
|
||||
|
||||
# FUTURE: smuggle this back as a dict instead of serializing here; the connection plugin may need to modify it
|
||||
module_json = json.dumps(exec_manifest)
|
||||
|
||||
b_module_data = exec_wrapper.replace(b"$json_raw = ''", b"$json_raw = @'\r\n%s\r\n'@" % to_bytes(module_json))
|
||||
|
||||
elif module_substyle == 'jsonargs':
|
||||
module_args_json = to_bytes(json.dumps(module_args))
|
||||
|
||||
|
|
|
@ -22,9 +22,9 @@ import re
|
|||
import shlex
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail, AnsibleActionSkip
|
||||
from ansible.executor.module_common import _create_powershell_wrapper
|
||||
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):
|
||||
|
@ -124,13 +124,16 @@ class ActionModule(ActionBase):
|
|||
script_cmd = self._connection._shell.wrap_for_exec(script_cmd)
|
||||
|
||||
exec_data = None
|
||||
# WinRM requires a special wrapper to work with environment variables
|
||||
if self._connection.transport == "winrm":
|
||||
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))
|
||||
# PowerShell runs the script in a special wrapper to enable things
|
||||
# like become and environment args
|
||||
if self._connection._shell.SHELL_FAMILY == "powershell":
|
||||
# FIXME: use a more public method to get the exec payload
|
||||
pc = self._play_context
|
||||
exec_data = _create_powershell_wrapper(
|
||||
to_bytes(script_cmd), {}, env_dict, self._task.async_val,
|
||||
pc.become, pc.become_method, pc.become_user,
|
||||
pc.become_pass, pc.become_flags, scan_dependencies=False
|
||||
)
|
||||
script_cmd = "-"
|
||||
|
||||
result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True, chdir=chdir))
|
||||
|
|
|
@ -119,7 +119,6 @@ from ansible.module_utils.six.moves.urllib.parse import urlunsplit
|
|||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.module_utils.six import binary_type
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.plugins.shell.powershell import leaf_exec
|
||||
from ansible.utils.hashing import secure_hash
|
||||
from ansible.utils.path import makedirs_safe
|
||||
|
||||
|
@ -488,21 +487,6 @@ class Connection(ConnectionBase):
|
|||
self.shell_id = None
|
||||
self._connect()
|
||||
|
||||
def _create_raw_wrapper_payload(self, cmd, environment=None):
|
||||
environment = {} if environment is None else environment
|
||||
|
||||
payload = {
|
||||
'module_entry': to_text(base64.b64encode(to_bytes(cmd))),
|
||||
'powershell_modules': {},
|
||||
'actions': ['exec'],
|
||||
'exec': to_text(base64.b64encode(to_bytes(leaf_exec))),
|
||||
'environment': environment,
|
||||
'min_ps_version': None,
|
||||
'min_os_version': None
|
||||
}
|
||||
|
||||
return json.dumps(payload)
|
||||
|
||||
def _wrapper_payload_stream(self, payload, buffer_size=200000):
|
||||
payload_bytes = to_bytes(payload)
|
||||
byte_count = len(payload_bytes)
|
||||
|
|
|
@ -987,7 +987,7 @@ $exec_wrapper = {
|
|||
|
||||
$output = $entrypoint.Run($payload)
|
||||
# base64 encode the output so the non-ascii characters are preserved
|
||||
Write-Output ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Write-Output $output))))
|
||||
Write-Output ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((Write-Output ($output | Out-String)))))
|
||||
} # end exec_wrapper
|
||||
|
||||
Function Dump-Error ($excep, $msg=$null) {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
whoami.exe
|
||||
Write-Output "finished"
|
|
@ -210,14 +210,14 @@
|
|||
# - "test_cmd_result is changed"
|
||||
|
||||
- name: run test script that takes a boolean parameter
|
||||
script: test_script_bool.ps1 $true
|
||||
script: test_script_bool.ps1 $false # use false as that can pick up more errors
|
||||
register: test_script_bool_result
|
||||
|
||||
- name: check that the script ran and the parameter was treated as a boolean
|
||||
assert:
|
||||
that:
|
||||
- "test_script_bool_result.stdout_lines[0] == 'System.Boolean'"
|
||||
- "test_script_bool_result.stdout_lines[1] == 'True'"
|
||||
- test_script_bool_result.stdout_lines[0] == 'System.Boolean'
|
||||
- test_script_bool_result.stdout_lines[1] == 'False'
|
||||
|
||||
- name: run test script that uses envvars
|
||||
script: test_script_with_env.ps1
|
||||
|
@ -272,3 +272,16 @@
|
|||
that:
|
||||
- test_script_removes_file_check_mode is changed
|
||||
- remove_file_stat.stat.exists
|
||||
|
||||
- name: run test script with become that outputs 2 lines
|
||||
script: test_script_whoami.ps1
|
||||
register: test_script_result_become
|
||||
become: yes
|
||||
become_user: SYSTEM
|
||||
become_method: runas
|
||||
|
||||
- name: check that the script ran and we get both outputs on new lines
|
||||
assert:
|
||||
that:
|
||||
- test_script_result_become.stdout_lines[0]|lower == 'nt authority\\system'
|
||||
- test_script_result_become.stdout_lines[1] == 'finished'
|
||||
|
|
Loading…
Reference in a new issue