diff options
Diffstat (limited to 'test/integration/targets/win_exec_wrapper')
8 files changed, 422 insertions, 0 deletions
diff --git a/test/integration/targets/win_exec_wrapper/aliases b/test/integration/targets/win_exec_wrapper/aliases new file mode 100644 index 0000000..1eed2ec --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/aliases @@ -0,0 +1,2 @@ +shippable/windows/group1 +shippable/windows/smoketest diff --git a/test/integration/targets/win_exec_wrapper/library/test_all_options.ps1 b/test/integration/targets/win_exec_wrapper/library/test_all_options.ps1 new file mode 100644 index 0000000..7c2c9c7 --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/library/test_all_options.ps1 @@ -0,0 +1,12 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy +#Requires -Module Ansible.ModuleUtils.SID +#Requires -Version 3.0 +#AnsibleRequires -OSVersion 6 +#AnsibleRequires -Become + +$output = &whoami.exe +$sid = Convert-ToSID -account_name $output.Trim() + +Exit-Json -obj @{ output = $sid; changed = $false } diff --git a/test/integration/targets/win_exec_wrapper/library/test_common_functions.ps1 b/test/integration/targets/win_exec_wrapper/library/test_common_functions.ps1 new file mode 100644 index 0000000..dde1ebc --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/library/test_common_functions.ps1 @@ -0,0 +1,43 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy + +$ErrorActionPreference = "Stop" + +Function Assert-Equal($actual, $expected) { + if ($actual -cne $expected) { + $call_stack = (Get-PSCallStack)[1] + $error_msg = -join @( + "AssertionError:`r`nActual: `"$actual`" != Expected: `"$expected`"`r`nLine: " + "$($call_stack.ScriptLineNumber), Method: $($call_stack.Position.Text)" + ) + Fail-Json -obj $result -message $error_msg + } +} + +$result = @{ + changed = $false +} + +#ConvertFrom-AnsibleJso +$input_json = '{"string":"string","float":3.1415926,"dict":{"string":"string","int":1},"list":["entry 1","entry 2"],"null":null,"int":1}' +$actual = ConvertFrom-AnsibleJson -InputObject $input_json +Assert-Equal -actual $actual.GetType() -expected ([Hashtable]) +Assert-Equal -actual $actual.string.GetType() -expected ([String]) +Assert-Equal -actual $actual.string -expected "string" +Assert-Equal -actual $actual.int.GetType() -expected ([Int32]) +Assert-Equal -actual $actual.int -expected 1 +Assert-Equal -actual $actual.null -expected $null +Assert-Equal -actual $actual.float.GetType() -expected ([Decimal]) +Assert-Equal -actual $actual.float -expected 3.1415926 +Assert-Equal -actual $actual.list.GetType() -expected ([Object[]]) +Assert-Equal -actual $actual.list.Count -expected 2 +Assert-Equal -actual $actual.list[0] -expected "entry 1" +Assert-Equal -actual $actual.list[1] -expected "entry 2" +Assert-Equal -actual $actual.GetType() -expected ([Hashtable]) +Assert-Equal -actual $actual.dict.string -expected "string" +Assert-Equal -actual $actual.dict.int -expected 1 + +$result.msg = "good" +Exit-Json -obj $result + diff --git a/test/integration/targets/win_exec_wrapper/library/test_fail.ps1 b/test/integration/targets/win_exec_wrapper/library/test_fail.ps1 new file mode 100644 index 0000000..72b89c6 --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/library/test_fail.ps1 @@ -0,0 +1,66 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy + +$params = Parse-Args $args -supports_check_mode $true + +$data = Get-AnsibleParam -obj $params -name "data" -type "str" -default "normal" +$result = @{ + changed = $false +} + +<# +This module tests various error events in PowerShell to verify our hidden trap +catches them all and outputs a pretty error message with a traceback to help +users debug the actual issue + +normal - normal execution, no errors +fail - Calls Fail-Json like normal +throw - throws an exception +error - Write-Error with ErrorActionPreferenceStop +cmdlet_error - Calls a Cmdlet with an invalid error +dotnet_exception - Calls a .NET function that will throw an error +function_throw - Throws an exception in a function +proc_exit_fine - calls an executable with a non-zero exit code with Exit-Json +proc_exit_fail - calls an executable with a non-zero exit code with Fail-Json +#> + +Function Test-ThrowException { + throw "exception in function" +} + +if ($data -eq "normal") { + Exit-Json -obj $result +} +elseif ($data -eq "fail") { + Fail-Json -obj $result -message "fail message" +} +elseif ($data -eq "throw") { + throw [ArgumentException]"module is thrown" +} +elseif ($data -eq "error") { + Write-Error -Message $data +} +elseif ($data -eq "cmdlet_error") { + Get-Item -Path "fake:\path" +} +elseif ($data -eq "dotnet_exception") { + [System.IO.Path]::GetFullPath($null) +} +elseif ($data -eq "function_throw") { + Test-ThrowException +} +elseif ($data -eq "proc_exit_fine") { + # verifies that if no error was actually fired and we have an output, we + # don't use the RC to validate if the module failed + &cmd.exe /c exit 2 + Exit-Json -obj $result +} +elseif ($data -eq "proc_exit_fail") { + &cmd.exe /c exit 2 + Fail-Json -obj $result -message "proc_exit_fail" +} + +# verify no exception were silently caught during our tests +Fail-Json -obj $result -message "end of module" + diff --git a/test/integration/targets/win_exec_wrapper/library/test_invalid_requires.ps1 b/test/integration/targets/win_exec_wrapper/library/test_invalid_requires.ps1 new file mode 100644 index 0000000..89727ef --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/library/test_invalid_requires.ps1 @@ -0,0 +1,9 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy +# Requires -Version 20 +# AnsibleRequires -OSVersion 20 + +# requires statement must be straight after the original # with now space, this module won't fail + +Exit-Json -obj @{ output = "output"; changed = $false } diff --git a/test/integration/targets/win_exec_wrapper/library/test_min_os_version.ps1 b/test/integration/targets/win_exec_wrapper/library/test_min_os_version.ps1 new file mode 100644 index 0000000..39b1ded --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/library/test_min_os_version.ps1 @@ -0,0 +1,8 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy +#AnsibleRequires -OSVersion 20.0 + +# this shouldn't run as no Windows OS will meet the version of 20.0 + +Exit-Json -obj @{ output = "output"; changed = $false } diff --git a/test/integration/targets/win_exec_wrapper/library/test_min_ps_version.ps1 b/test/integration/targets/win_exec_wrapper/library/test_min_ps_version.ps1 new file mode 100644 index 0000000..bb5fd0f --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/library/test_min_ps_version.ps1 @@ -0,0 +1,8 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy +#Requires -Version 20.0.0.0 + +# this shouldn't run as no PS Version will be at 20 in the near future + +Exit-Json -obj @{ output = "output"; changed = $false } diff --git a/test/integration/targets/win_exec_wrapper/tasks/main.yml b/test/integration/targets/win_exec_wrapper/tasks/main.yml new file mode 100644 index 0000000..8fc54f7 --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/tasks/main.yml @@ -0,0 +1,274 @@ +--- +- name: fetch current target date/time for log filtering + raw: '[datetime]::now | Out-String' + register: test_starttime + +- name: test normal module execution + test_fail: + register: normal + +- name: assert test normal module execution + assert: + that: + - not normal is failed + +- name: test fail module execution + test_fail: + data: fail + register: fail_module + ignore_errors: yes + +- name: assert test fail module execution + assert: + that: + - fail_module is failed + - fail_module.msg == "fail message" + - not fail_module.exception is defined + +- name: test module with exception thrown + test_fail: + data: throw + register: throw_module + ignore_errors: yes + +- name: assert test module with exception thrown + assert: + that: + - throw_module is failed + - 'throw_module.msg == "Unhandled exception while executing module: module is thrown"' + - '"throw [ArgumentException]\"module is thrown\"" in throw_module.exception' + +- name: test module with error msg + test_fail: + data: error + register: error_module + ignore_errors: yes + vars: + # Running with coverage means the module is run from a script and not as a psuedo script in a pipeline. This + # results in a different error message being returned so we disable coverage collection for this task. + _ansible_coverage_remote_output: '' + +- name: assert test module with error msg + assert: + that: + - error_module is failed + - 'error_module.msg == "Unhandled exception while executing module: error"' + - '"Write-Error -Message $data" in error_module.exception' + +- name: test module with cmdlet error + test_fail: + data: cmdlet_error + register: cmdlet_error + ignore_errors: yes + +- name: assert test module with cmdlet error + assert: + that: + - cmdlet_error is failed + - 'cmdlet_error.msg == "Unhandled exception while executing module: Cannot find drive. A drive with the name ''fake'' does not exist."' + - '"Get-Item -Path \"fake:\\path\"" in cmdlet_error.exception' + +- name: test module with .NET exception + test_fail: + data: dotnet_exception + register: dotnet_exception + ignore_errors: yes + +- name: assert test module with .NET exception + assert: + that: + - dotnet_exception is failed + - 'dotnet_exception.msg == "Unhandled exception while executing module: Exception calling \"GetFullPath\" with \"1\" argument(s): \"The path is not of a legal form.\""' + - '"[System.IO.Path]::GetFullPath($null)" in dotnet_exception.exception' + +- name: test module with function exception + test_fail: + data: function_throw + register: function_exception + ignore_errors: yes + vars: + _ansible_coverage_remote_output: '' + +- name: assert test module with function exception + assert: + that: + - function_exception is failed + - 'function_exception.msg == "Unhandled exception while executing module: exception in function"' + - '"throw \"exception in function\"" in function_exception.exception' + - '"at Test-ThrowException, <No file>: line" in function_exception.exception' + +- name: test module with fail process but Exit-Json + test_fail: + data: proc_exit_fine + register: proc_exit_fine + +- name: assert test module with fail process but Exit-Json + assert: + that: + - not proc_exit_fine is failed + +- name: test module with fail process but Fail-Json + test_fail: + data: proc_exit_fail + register: proc_exit_fail + ignore_errors: yes + +- name: assert test module with fail process but Fail-Json + assert: + that: + - proc_exit_fail is failed + - proc_exit_fail.msg == "proc_exit_fail" + - not proc_exit_fail.exception is defined + +- name: test out invalid options + test_invalid_requires: + register: invalid_options + +- name: assert test out invalid options + assert: + that: + - invalid_options is successful + - invalid_options.output == "output" + +- name: test out invalid os version + test_min_os_version: + register: invalid_os_version + ignore_errors: yes + +- name: assert test out invalid os version + assert: + that: + - invalid_os_version is failed + - '"This module cannot run on this OS as it requires a minimum version of 20.0, actual was " in invalid_os_version.msg' + +- name: test out invalid powershell version + test_min_ps_version: + register: invalid_ps_version + ignore_errors: yes + +- name: assert test out invalid powershell version + assert: + that: + - invalid_ps_version is failed + - '"This module cannot run as it requires a minimum PowerShell version of 20.0.0.0, actual was " in invalid_ps_version.msg' + +- name: test out environment block for task + win_shell: set + args: + executable: cmd.exe + environment: + String: string value + Int: 1234 + Bool: True + double_quote: 'double " quote' + single_quote: "single ' quote" + hyphen-var: abc@123 + '_-(){}[]<>*+-/\?"''!@#$%^&|;:i,.`~0': '_-(){}[]<>*+-/\?"''!@#$%^&|;:i,.`~0' + '‘key': 'value‚' + register: environment_block + +- name: assert environment block for task + assert: + that: + - '"String=string value" in environment_block.stdout_lines' + - '"Int=1234" in environment_block.stdout_lines' + - '"Bool=True" in environment_block.stdout_lines' + - '"double_quote=double \" quote" in environment_block.stdout_lines' + - '"single_quote=single '' quote" in environment_block.stdout_lines' + - '"hyphen-var=abc@123" in environment_block.stdout_lines' + # yaml escaping rules - (\\ == \), (\" == "), ('' == ') + - '"_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0=_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0" in environment_block.stdout_lines' + - '"‘key=value‚" in environment_block.stdout_lines' + +- name: test out become requires without become_user set + test_all_options: + register: become_system + +- name: assert become requires without become_user set + assert: + that: + - become_system is successful + - become_system.output == "S-1-5-18" + +- set_fact: + become_test_username: ansible_become_test + gen_pw: "{{ 'password123!' + lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}" + +- name: create unprivileged user + win_user: + name: "{{ become_test_username }}" + password: "{{ gen_pw }}" + update_password: always + groups: Users + register: become_test_user_result + +- name: execute tests and ensure that test user is deleted regardless of success/failure + block: + - name: ensure current user is not the become user + win_shell: whoami + register: whoami_out + + - name: verify output + assert: + that: + - not whoami_out.stdout_lines[0].endswith(become_test_username) + + - name: get become user profile dir so we can clean it up later + vars: &become_vars + ansible_become_user: "{{ become_test_username }}" + ansible_become_password: "{{ gen_pw }}" + ansible_become_method: runas + ansible_become: yes + win_shell: $env:USERPROFILE + register: profile_dir_out + + - name: ensure profile dir contains test username (eg, if become fails silently, prevent deletion of real user profile) + assert: + that: + - become_test_username in profile_dir_out.stdout_lines[0] + + - name: test out become requires when become_user set + test_all_options: + vars: *become_vars + register: become_system + + - name: assert become requires when become_user set + assert: + that: + - become_system is successful + - become_system.output == become_test_user_result.sid + + always: + - name: ensure test user is deleted + win_user: + name: "{{ become_test_username }}" + state: absent + + - name: ensure test user profile is deleted + # NB: have to work around powershell limitation of long filenames until win_file fixes it + win_shell: rmdir /S /Q {{ profile_dir_out.stdout_lines[0] }} + args: + executable: cmd.exe + when: become_test_username in profile_dir_out.stdout_lines[0] + +- name: test common functions in exec + test_common_functions: + register: common_functions_res + +- name: assert test common functions in exec + assert: + that: + - not common_functions_res is failed + - common_functions_res.msg == "good" + +- name: get PS events containing module args or envvars created since test start + raw: | + $dt=[datetime]"{{ test_starttime.stdout|trim }}" + (Get-WinEvent -LogName Microsoft-Windows-Powershell/Operational | + ? { $_.TimeCreated -ge $dt -and $_.Message -match "fail_module|hyphen-var" }).Count + register: ps_log_count + +- name: assert no PS events contain module args or envvars + assert: + that: + - ps_log_count.stdout | int == 0 |