fix Windows env handling

* fixes #22441
* fixes #22655
* moves all env handling into the exec wrapper; this should work for everything but raw, which is consistent with non-Windows.
pull/22927/head
Matt Davis 8 years ago
parent f1ab879bb6
commit cc68212612

@ -831,12 +831,13 @@ def modify_module(module_name, module_path, module_args, task_vars=dict(), modul
return (b_module_data, module_style, to_text(shebang, nonstring='passthru')) return (b_module_data, module_style, to_text(shebang, nonstring='passthru'))
def build_windows_module_payload(module_name, module_path, b_module_data, module_args, task_vars, task, play_context): def build_windows_module_payload(module_name, module_path, b_module_data, module_args, task_vars, task, play_context, environment):
exec_manifest = dict( exec_manifest = dict(
module_entry=base64.b64encode(b_module_data), module_entry=base64.b64encode(b_module_data),
powershell_modules=dict(), powershell_modules=dict(),
module_args=module_args, module_args=module_args,
actions=['exec'] actions=['exec'],
environment=environment
) )
exec_manifest['exec'] = base64.b64encode(to_bytes(leaf_exec)) exec_manifest['exec'] = base64.b64encode(to_bytes(leaf_exec))

@ -162,14 +162,16 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# FUTURE: we'll have to get fancier about this to support powershell over SSH on Windows... # FUTURE: we'll have to get fancier about this to support powershell over SSH on Windows...
if self._connection.transport == "winrm": if self._connection.transport == "winrm":
# WinRM always pipelines, so we need to build up a fancier module payload... # WinRM always pipelines, so we need to build up a fancier module payload...
final_environment = dict()
self._compute_environment_string(final_environment)
module_data = build_windows_module_payload(module_name=module_name, module_path=module_path, module_data = build_windows_module_payload(module_name=module_name, module_path=module_path,
b_module_data=module_data, module_args=module_args, b_module_data=module_data, module_args=module_args,
task_vars=task_vars, task=self._task, task_vars=task_vars, task=self._task,
play_context=self._play_context) play_context=self._play_context, environment=final_environment)
return (module_style, module_shebang, module_data, module_path) return (module_style, module_shebang, module_data, module_path)
def _compute_environment_string(self): def _compute_environment_string(self, raw_environment_out=dict()):
''' '''
Builds the environment string to be used when executing the remote task. Builds the environment string to be used when executing the remote task.
''' '''
@ -195,6 +197,11 @@ class ActionBase(with_metaclass(ABCMeta, object)):
final_environment.update(temp_environment) final_environment.update(temp_environment)
final_environment = self._templar.template(final_environment) final_environment = self._templar.template(final_environment)
if isinstance(raw_environment_out, dict):
raw_environment_out.clear()
raw_environment_out.update(final_environment)
return self._connection._shell.env_prefix(**final_environment) return self._connection._shell.env_prefix(**final_environment)
def _early_needs_tmp_path(self): def _early_needs_tmp_path(self):

@ -81,11 +81,17 @@ class ActionModule(ActionBase):
self._fixup_perms2((tmp, tmp_src), execute=True) self._fixup_perms2((tmp, tmp_src), execute=True)
# add preparation steps to one ssh roundtrip executing the script # add preparation steps to one ssh roundtrip executing the script
env_string = self._compute_environment_string() env_dict = dict()
env_string = self._compute_environment_string(env_dict)
script_cmd = ' '.join([env_string, tmp_src, args]) script_cmd = ' '.join([env_string, tmp_src, args])
script_cmd = self._connection._shell.wrap_for_exec(script_cmd) script_cmd = self._connection._shell.wrap_for_exec(script_cmd)
result.update(self._low_level_execute_command(cmd=script_cmd, sudoable=True)) exec_data = None
# HACK: come up with a sane way to pass around env outside the command
if self._connection.transport == "winrm":
exec_data = self._connection._create_raw_wrapper_payload(script_cmd, env_dict)
result.update(self._low_level_execute_command(cmd=script_cmd, in_data=exec_data, sudoable=True))
# clean up after # clean up after
self._remove_tmp_path(tmp) self._remove_tmp_path(tmp)

@ -305,12 +305,13 @@ class Connection(ConnectionBase):
self.shell_id = None self.shell_id = None
self._connect() self._connect()
def _create_raw_wrapper_payload(self, cmd): def _create_raw_wrapper_payload(self, cmd, environment=dict()):
payload = { payload = {
'module_entry': base64.b64encode(to_bytes(cmd)), 'module_entry': base64.b64encode(to_bytes(cmd)),
'powershell_modules': {}, 'powershell_modules': {},
'actions': ['exec'], 'actions': ['exec'],
'exec': base64.b64encode(to_bytes(leaf_exec)) 'exec': base64.b64encode(to_bytes(leaf_exec)),
'environment': environment
} }
return json.dumps(payload) return json.dumps(payload)

@ -111,6 +111,11 @@ Function Run($payload) {
# redefine Write-Host to dump to output instead of failing- lots of scripts use it # redefine Write-Host to dump to output instead of failing- lots of scripts use it
$ps.AddStatement().AddScript("Function Write-Host(`$msg){ Write-Output `$msg }") | Out-Null $ps.AddStatement().AddScript("Function Write-Host(`$msg){ Write-Output `$msg }") | Out-Null
ForEach ($env_kv in $payload.environment.GetEnumerator()) {
$escaped_env_set = "`$env:{0} = '{1}'" -f $env_kv.Key,$env_kv.Value.Replace("'","''")
$ps.AddStatement().AddScript($escaped_env_set) | Out-Null
}
# dynamically create/load modules # dynamically create/load modules
ForEach ($mod in $payload.powershell_modules.GetEnumerator()) { ForEach ($mod in $payload.powershell_modules.GetEnumerator()) {
$decoded_module = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($mod.Value)) $decoded_module = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($mod.Value))
@ -132,6 +137,7 @@ Function Run($payload) {
If(-not $exit_code) { If(-not $exit_code) {
$exit_code = 1 $exit_code = 1
} }
# need to use this instead of Exit keyword to prevent runspace from crashing with dynamic modules
$host.SetShouldExit($exit_code) $host.SetShouldExit($exit_code)
} }
} }
@ -967,9 +973,8 @@ class ShellModule(object):
return to_text(value, errors='surrogate_or_strict') return to_text(value, errors='surrogate_or_strict')
def env_prefix(self, **kwargs): def env_prefix(self, **kwargs):
env = self.env.copy() # powershell/winrm env handling is handled in the exec wrapper
env.update(kwargs) return ""
return ';'.join(["$env:%s='%s'" % (self.assert_safe_env_key(k), self.safe_env_value(k,v)) for k,v in env.items()])
def join_path(self, *args): def join_path(self, *args):
parts = [] parts = []

@ -192,3 +192,16 @@
that: that:
- "test_script_bool_result.stdout_lines[0] == 'System.Boolean'" - "test_script_bool_result.stdout_lines[0] == 'System.Boolean'"
- "test_script_bool_result.stdout_lines[1] == 'True'" - "test_script_bool_result.stdout_lines[1] == 'True'"
- 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 | succeeded
- test_script_env_result.stdout_lines[0] == 'task'

@ -17,8 +17,10 @@
- shellout.stdout == "hello from Ansible\r\n" - shellout.stdout == "hello from Ansible\r\n"
- shellout.stdout_lines == ["hello from Ansible"] - shellout.stdout_lines == ["hello from Ansible"]
- name: execute a powershell cmdlet with multi-line output - name: execute a powershell cmdlet with multi-line output that uses environment with embedded quotes
win_shell: Write-Output "hello from Ansible"; Write-Output "another line"; Write-Output "yet another line" win_shell: Write-Output "hello from Ansible"; Write-Output "another line"; Write-Output "yet another line"; Write-Output "envvar was $env:taskvar"
environment:
taskvar: "o'doyle rules"
register: shellout register: shellout
- name: validate result - name: validate result
@ -26,15 +28,15 @@
that: that:
- shellout|success - shellout|success
- shellout|changed - shellout|changed
- shellout.cmd == 'Write-Output "hello from Ansible"; Write-Output "another line"; Write-Output "yet another line"' - shellout.cmd == 'Write-Output "hello from Ansible"; Write-Output "another line"; Write-Output "yet another line"; Write-Output "envvar was $env:taskvar"'
- shellout.delta is match('^\d:(\d){2}:(\d){2}.(\d){6}$') - shellout.delta is match('^\d:(\d){2}:(\d){2}.(\d){6}$')
- shellout.end is match('^(\d){4}\-(\d){2}\-(\d){2} (\d){2}:(\d){2}:(\d){2}.(\d){6}$') - shellout.end is match('^(\d){4}\-(\d){2}\-(\d){2} (\d){2}:(\d){2}:(\d){2}.(\d){6}$')
- shellout.rc == 0 - shellout.rc == 0
- shellout.start is match('^(\d){4}\-(\d){2}\-(\d){2} (\d){2}:(\d){2}:(\d){2}.(\d){6}$') - shellout.start is match('^(\d){4}\-(\d){2}\-(\d){2} (\d){2}:(\d){2}:(\d){2}.(\d){6}$')
# assertion disabled since it does not pass on Windows Server 2016 # assertion disabled since it does not pass on Windows Server 2016
# - shellout.stderr == "" # - shellout.stderr == ""
- shellout.stdout == "hello from Ansible\r\nanother line\r\nyet another line\r\n" - shellout.stdout == "hello from Ansible\r\nanother line\r\nyet another line\r\nenvvar was o'doyle rules\r\n"
- shellout.stdout_lines == ["hello from Ansible","another line", "yet another line"] - shellout.stdout_lines == ["hello from Ansible","another line", "yet another line", "envvar was o'doyle rules"]
- name: execute something nonexistent - name: execute something nonexistent
win_shell: bogus_command1234 win_shell: bogus_command1234

Loading…
Cancel
Save