diff --git a/lib/ansible/modules/windows/win_pester.ps1 b/lib/ansible/modules/windows/win_pester.ps1 new file mode 100644 index 00000000000..1fbb68892bf --- /dev/null +++ b/lib/ansible/modules/windows/win_pester.ps1 @@ -0,0 +1,105 @@ +#!powershell +# This file is part of Ansible + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Author: Erwan Quelin (mail:erwan.quelin@gmail.com - GitHub: @equelin - Twitter: @erwanquelin) + +#Requires -Module Ansible.ModuleUtils.Legacy + +$ErrorActionPreference = 'Stop' + +$params = Parse-Args -arguments $args -supports_check_mode $true +$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false +$diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false + +# Modules parameters + +$path = Get-AnsibleParam -obj $params -name "path" -type "string" -failifempty $true +$minimum_version = Get-AnsibleParam -obj $params -name "minimum_version" -type "string" -failifempty $false + +$result = @{ + changed = $false +} + +if ($diff_mode) { + $result.diff = @{} +} + +# CODE +# Test if parameter $version is valid +Try { + $minimum_version = [version]$minimum_version +} +Catch { + Fail-Json -obj $result -message "Value '$minimum_version' for parameter 'minimum_version' is not a valid version format" +} + +# Import Pester module if available +$Module = 'Pester' + +If (-not (Get-Module -Name $Module -ErrorAction SilentlyContinue)) { + If (Get-Module -Name $Module -ListAvailable -ErrorAction SilentlyContinue) { + Import-Module $Module + } else { + Fail-Json -obj $result -message "Cannot find module: $Module. Check if pester is installed, and if it is not, install using win_psmodule or win_chocolatey." + } +} + +# Add actual pester's module version in the ansible's result variable +$Pester_version = (Get-Module -Name $Module).Version.ToString() +$result.pester_version = $Pester_version + +# Test if the Pester module is available with a version greater or equal than the one specified in the $version parameter +If ((-not (Get-Module -Name $Module -ErrorAction SilentlyContinue | Where-Object {$_.Version -ge $minimum_version})) -and ($minimum_version)) { + Fail-Json -obj $result -message "$Module version is not greater or equal to $minimum_version" +} + +# Testing if test file or directory exist +If (-not (Test-Path -LiteralPath $path)) { + Fail-Json -obj $result -message "Cannot find file or directory: '$path' as it does not exist" +} + +#Prepare Invoke-Pester parameters depending of the Pester's version. +#Invoke-Pester output deactivation behave differently depending on the Pester's version +If ($result.pester_version -ge "4.0.0") { + $Parameters = @{ + "show" = "none" + "PassThru" = $True + } +} else { + $Parameters = @{ + "quiet" = $True + "PassThru" = $True + } +} + +# Run Pester tests +If (Test-Path -LiteralPath $path -PathType Leaf) { + if ($check_mode) { + $result.output = "Run pester test in the file: $path" + } else { + try { + $result.output = Invoke-Pester $path @Parameters + } catch { + Fail-Json -obj $result -message $_.Exception + } + } +} else { + # Run Pester tests against all the .ps1 file in the local folder + $files = Get-ChildItem -Path $path | Where-Object {$_.extension -eq ".ps1"} + + if ($check_mode) { + $result.output = "Run pester test(s) who are in the folder: $path" + } else { + try { + $result.output = Invoke-Pester $files.FullName @Parameters + } catch { + Fail-Json -obj $result -message $_.Exception + } + } +} + +$result.changed = $true + +Exit-Json -obj $result \ No newline at end of file diff --git a/lib/ansible/modules/windows/win_pester.py b/lib/ansible/modules/windows/win_pester.py new file mode 100644 index 00000000000..a4defe35a21 --- /dev/null +++ b/lib/ansible/modules/windows/win_pester.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = r''' +--- +module: win_pester +short_description: Run Pester tests on Windows hosts +version_added: "2.6" +description: + - Run Pester tests on Windows hosts. + - Test files have to be available on the remote host. +requirements: + - Pester +options: + path: + description: + - Path to a pester test file or a folder where tests can be found. + - If the path is a folder, the module will consider all ps1 files as Pester tests. + required: true + version: + description: + - Minimum version of the pester module that has to be available on the remote host. +author: + - Erwan Quelin (@erwanquelin) +''' + +RETURN = r''' +pester_version: + description: Version of the pester module found on the remote host. + returned: always + type: string + sample: 4.3.1 +output: + description: Results of the Pester tests. + returned: success + type: list + sample: False +''' + +EXAMPLES = r''' +- name: Get facts + setup: + +- name: Add Pester module + action: + module_name: "{{ 'win_psmodule' if ansible_powershell_version >= 5 else 'win_chocolatey' }}" + name: Pester + state: present + +- name: Run the pester test provided in the path parameter. + win_pester: + path: C:\Pester + +# Run pesters tests files that are present in the specified folder +# ensure that the pester module version available is greater or equal to the version parameter. +- name: Run the pester test present in a folder and check the Pester module version. + win_pester: + path: C:\Pester\test01.test.ps1 + version: 4.1.0 +''' diff --git a/test/integration/targets/win_pester/aliases b/test/integration/targets/win_pester/aliases new file mode 100644 index 00000000000..1ab64312711 --- /dev/null +++ b/test/integration/targets/win_pester/aliases @@ -0,0 +1 @@ +windows/ci/group2 \ No newline at end of file diff --git a/test/integration/targets/win_pester/defaults/main.yml b/test/integration/targets/win_pester/defaults/main.yml new file mode 100644 index 00000000000..a7f3b90269c --- /dev/null +++ b/test/integration/targets/win_pester/defaults/main.yml @@ -0,0 +1,12 @@ +--- +local_test_files: + - files/test01.test.ps1 + - files/test02.test.ps1 +remote_test_files: + - test01.test.ps1 + - test02.test.ps1 +fake_remote_test_file: C:\Pester\fake_test.test.ps1 +fake_remote_folder: C:\Fake_Pester +remote_test_folder: + - C:\Pester\ + - C:\Pester \ No newline at end of file diff --git a/test/integration/targets/win_pester/files/test01.test.ps1 b/test/integration/targets/win_pester/files/test01.test.ps1 new file mode 100644 index 00000000000..edba4db1f66 --- /dev/null +++ b/test/integration/targets/win_pester/files/test01.test.ps1 @@ -0,0 +1,5 @@ +Describe -Name 'Test01' { + It -name 'First Test' { + {Get-Service} | Should Not Throw + } +} \ No newline at end of file diff --git a/test/integration/targets/win_pester/files/test02.test.ps1 b/test/integration/targets/win_pester/files/test02.test.ps1 new file mode 100644 index 00000000000..2ec2450a58c --- /dev/null +++ b/test/integration/targets/win_pester/files/test02.test.ps1 @@ -0,0 +1,5 @@ +Describe -Name 'Test02' { + It -name 'Second Test' { + {Get-Service} | Should Throw + } +} \ No newline at end of file diff --git a/test/integration/targets/win_pester/tasks/main.yml b/test/integration/targets/win_pester/tasks/main.yml new file mode 100644 index 00000000000..d7512754524 --- /dev/null +++ b/test/integration/targets/win_pester/tasks/main.yml @@ -0,0 +1,117 @@ +--- +- name: Get facts + setup: + +- name: Add Pester module + win_psmodule: + name: Pester + state: present + when: ansible_powershell_version >= 5 + +- name: Add Pester module with Chocolatey + win_chocolatey: + name: Pester + state: present + when: ansible_powershell_version < 5 + +- name: Copy test file(s) + win_copy: + src: "{{ item }}" + dest: "{{ remote_test_folder[0] }}" + with_items: "{{local_test_files}}" + +- name: Run Pester test(s) specifying a fake test file + win_pester: + path: "{{ fake_remote_test_file }}" + register: result + ignore_errors: yes + +- name: Assert result + assert: + that: + - result.failed == true + +- name: Run Pester test(s) specifying a fake folder + win_pester: + path: "{{ fake_remote_folder }}" + register: result + ignore_errors: yes + +- name: Assert result + assert: + that: + - result.failed == true + +- name: Run Pester test(s) specifying a test file and a higher pester version + win_pester: + path: "{{ remote_test_folder[0] }}{{ remote_test_files[0] }}" + minimum_version: 6.0.0 + register: result + ignore_errors: yes + +- name: Assert result + assert: + that: + - result.failed == true + +- name: Run Pester test(s) specifying a test file + win_pester: + path: "{{ remote_test_folder[0] }}{{ remote_test_files[0] }}" + register: result + +- name: Assert result + assert: + that: + - result.failed == false + - result.output.TotalCount == 1 + +- name: Run Pester test(s) specifying a test file and with a minimum mandatory Pester version + win_pester: + path: "{{ remote_test_folder[0] }}{{ remote_test_files[0] }}" + minimum_version: 3.0.0 + register: result + +- name: Assert result + assert: + that: + - result.failed == false + - result.output.TotalCount == 1 + +- name: Run Pester test(s) located in a folder. Folder path end with '\' + win_pester: + path: "{{ remote_test_folder[0] }}" + register: result + +- name: Assert result + assert: + that: + - result.failed == false + - result.output.TotalCount == 2 + +- name: Run Pester test(s) located in a folder. Folder path does not end with '\' + win_pester: + path: "{{ remote_test_folder[1] }}" + register: result + +- name: Assert result + assert: + that: + - result.failed == false + - result.output.TotalCount == 2 + +- name: Run Pester test(s) located in a folder and with a minimum mandatory Pester version + win_pester: + path: "{{ remote_test_folder[0] }}" + minimum_version: 3.0.0 + register: result + +- name: Assert result + assert: + that: + - result.failed == false + - result.output.TotalCount == 2 + +- name: Delete test folder + win_file: + path: "{{ remote_test_folder[0] }}" + state: absent \ No newline at end of file