From 9d932b64f07b296b2edb2b6627ce033ca7546e92 Mon Sep 17 00:00:00 2001 From: Daniele Lazzari Date: Mon, 26 Jun 2017 23:01:38 +0200 Subject: [PATCH] New module: Add module to install/remove/register/unregiser windows powershell modules (windows/win_psmodule) (#23604) * Add new windows module win_psmodule * Add checkmode, allow_clobber parameter, integration tests * Add aliases, replace win_raw with win_shell * restore original test_win_group1.yml, add powershel version test * fix var type * add conditional on assert * integration tests conditional tasks review * documentation fix, test fix, adds result.change * fix yml * fix railing whitespace * add nuget_changed and repository_changed in result --- lib/ansible/modules/windows/win_psmodule.ps1 | 186 ++++++++++++++++++ lib/ansible/modules/windows/win_psmodule.py | 109 ++++++++++ test/integration/targets/win_psmodule/aliases | 1 + .../targets/win_psmodule/defaults/main.yml | 5 + .../targets/win_psmodule/tasks/main.yml | 28 +++ .../targets/win_psmodule/tasks/test.yml | 136 +++++++++++++ 6 files changed, 465 insertions(+) create mode 100644 lib/ansible/modules/windows/win_psmodule.ps1 create mode 100644 lib/ansible/modules/windows/win_psmodule.py create mode 100644 test/integration/targets/win_psmodule/aliases create mode 100644 test/integration/targets/win_psmodule/defaults/main.yml create mode 100644 test/integration/targets/win_psmodule/tasks/main.yml create mode 100644 test/integration/targets/win_psmodule/tasks/test.yml diff --git a/lib/ansible/modules/windows/win_psmodule.ps1 b/lib/ansible/modules/windows/win_psmodule.ps1 new file mode 100644 index 0000000000..24595ca3af --- /dev/null +++ b/lib/ansible/modules/windows/win_psmodule.ps1 @@ -0,0 +1,186 @@ +#!powershell +# This file is part of Ansible +# +# Copyright 2017, Daniele Lazzari +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# WANT_JSON +# POWERSHELL_COMMON + +# win_psmodule (Powershell modules Additions/Removal) + +$params = Parse-Args $args -supports_check_mode $true + +$name = Get-AnsibleParam -obj $params "name" -type "str" -failifempty $true +$repo = Get-AnsibleParam -obj $params "repository" -type "str" +$url = Get-AnsibleParam -obj $params "url" -type "str" +$state = Get-AnsibleParam -obj $params "state" -type "str" -default "present" -validateset "present", "absent" +$allow_clobber = Get-AnsibleParam -obj $params "allow_clobber" -type "bool" -default $false +$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false + +$result = @{"changed" = $false + "output" = "" + "nuget_changed" = $false + "repository_changed" = $false} + +Function Install-NugetProvider { + param( + [bool]$CheckMode + ) + $PackageProvider = Get-PackageProvider -ListAvailable|?{($_.name -eq 'Nuget') -and ($_.version -ge "2.8.5.201")} + if (!($PackageProvider)){ + try{ + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction Stop -WhatIf:$CheckMode | out-null + $result.changed = $true + $result.nuget_changed = $true + } + catch{ + $ErrorMessage = "Problems adding package provider: $($_.Exception.Message)" + Fail-Json $result $ErrorMessage + } + } +} + +Function Install-Repository { + Param( + [Parameter(Mandatory=$true)] + [string]$Name, + [Parameter(Mandatory=$true)] + [string]$Url, + [bool]$CheckMode + ) + $Repo = (Get-PSRepository).SourceLocation + + # If repository isn't already present, try to register it as trusted. + if ($Repo -notcontains $Url){ + try { + if (!($CheckMode)) { + Register-PSRepository -Name $Name -SourceLocation $Url -InstallationPolicy Trusted -ErrorAction Stop + } + $result.changed = $true + $result.repository_changed = $true + } + catch { + $ErrorMessage = "Problems adding $($Name) repository: $($_.Exception.Message)" + Fail-Json $result $ErrorMessage + } + } +} + +Function Remove-Repository{ + Param( + [Parameter(Mandatory=$true)] + [string]$Name, + [bool]$CheckMode + ) + + $Repo = (Get-PSRepository).SourceLocation + + # Try to remove the repository + if ($Repo -contains $Name){ + try { + if (!($CheckMode)) { + Unregister-PSRepository -Name $Name -ErrorAction Stop + } + $result.changed = $true + $result.repository_changed = $true + } + catch { + $ErrorMessage = "Problems removing $($Name)repository: $($_.Exception.Message)" + Fail-Json $result $ErrorMessage + } + } +} + +Function Install-PsModule { + param( + [Parameter(Mandatory=$true)] + [string]$Name, + [bool]$AllowClobber, + [bool]$CheckMode + ) + if (Get-Module -Listavailable|?{$_.name -eq $Name}){ + $result.output = "Module $($Name) already present" + } + else { + try{ + # Install NuGet Provider if needed + Install-NugetProvider -CheckMode $CheckMode + + # Check Powershell Version (-AllowClobber was introduced in early version only) + if ($PsVersion.Minor -ge 1){ + Install-Module -Name $Name -Force -ErrorAction Stop -Whatif:$CheckMode -AllowClobber:$AllowClobber | out-null + } + else { + Install-Module -Name $Name -Force -ErrorAction Stop -Whatif:$CheckMode | out-null + } + $result.output = "Module $($Name) installed" + $result.changed = $true + } + catch{ + $ErrorMessage = "Problems installing $($Name) module: $($_.Exception.Message)" + Fail-Json $result $ErrorMessage + } + } +} + +Function Remove-PsModule { + param( + [Parameter(Mandatory=$true)] + [string]$Name, + [bool]$CheckMode + ) + # If module is present, unistalls it. + if (Get-Module -Listavailable|?{$_.name -eq $Name}){ + try{ + Uninstall-Module -Name $Name -Confirm:$false -Force -ErrorAction Stop -WhatIf:$CheckMode | out-null + $result.output = "Module $($Name) removed" + $result.changed = $true + } + catch{ + $ErrorMessage = "Problems removing $($Name) module: $($_.Exception.Message)" + Fail-Json $result $ErrorMessage + } + + } + else{ + $result.output = "Module $($Name) not present" + } +} + +# Check powershell version, fail if < 5.0 +$PsVersion = $PSVersionTable.PSVersion +if ($PsVersion.Major -lt 5){ + $ErrorMessage = "Powershell 5.0 or higher is needed" + Fail-Json $result $ErrorMessage +} + +if ($state -eq "present") { + if (($repo) -and ($url)) { + Install-Repository -Name $repo -Url $url -CheckMode $check_mode + } + else { + $ErrorMessage = "Repository Name and Url are mandatory if you want to add a new repository" + } + Install-PsModule -Name $Name -CheckMode $check_mode -AllowClobber $allow_clobber +} +else { + if ($repo) { + Remove-Repository -Name $repo -CheckMode $check_mode + } + Remove-PsModule -Name $Name -CheckMode $check_mode +} + +Exit-Json $result diff --git a/lib/ansible/modules/windows/win_psmodule.py b/lib/ansible/modules/windows/win_psmodule.py new file mode 100644 index 0000000000..a0b642c6e2 --- /dev/null +++ b/lib/ansible/modules/windows/win_psmodule.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Daniele Lazzari +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: win_psmodule +version_added: "2.4" +short_description: Adds or removes a Powershell Module. +description: + - This module helps to install Powershell modules and register custom modules repository on Windows Server. +options: + name: + description: + - Name of the powershell module that has to be installed. + required: true + allow_clobber: + description: + - If yes imports all commands, even if they have the same names as commands that already exists. Available only in Powershell 5.1 or higher. + default: no + choices: + - no + - yes + repository: + description: + - Name of the custom repository to register. + url: + description: + - Url of the custom repository. + state: + description: + - If present a new module is installed. If absent a module is removed. + default: present + choices: + - present + - absent +notes: + - Powershell 5.0 or higer is needed. + +author: Daniele Lazzari +''' + +EXAMPLES = ''' +--- +- name: Add a powershell module + win_psmodule: + name: PowershellModule + state: present + +- name: Add a powershell module and register a repository + win_psmodule: + name: MyCustomModule + repository: MyRepository + url: https://myrepo.com + state: present + +- name: Remove a powershell module + win_psmodule: + name: PowershellModule + state: absent + +- name: Remove a powershell module and a repository + win_psmodule: + name: MyCustomModule + repository: MyRepository + state: absent +''' + +RETURN = ''' +--- +output: + description: a message describing the task result. + returned: always + sample: "Module PowerShellCookbook installed" + type: string +nuget_changed: + description: true when Nuget package provider is installed + returned: always + type: boolean + sample: True +repository_changed: + description: true when a custom repository is installed or removed + returned: always + type: boolean + sample: True +''' diff --git a/test/integration/targets/win_psmodule/aliases b/test/integration/targets/win_psmodule/aliases new file mode 100644 index 0000000000..90d83328b3 --- /dev/null +++ b/test/integration/targets/win_psmodule/aliases @@ -0,0 +1 @@ +windows/ci/group1 \ No newline at end of file diff --git a/test/integration/targets/win_psmodule/defaults/main.yml b/test/integration/targets/win_psmodule/defaults/main.yml new file mode 100644 index 0000000000..40f6f720fa --- /dev/null +++ b/test/integration/targets/win_psmodule/defaults/main.yml @@ -0,0 +1,5 @@ +--- + +powershell_module: powershell-yaml +wrong_module: powershell_yaml +allow_clobber_module: PowerShellCookbook diff --git a/test/integration/targets/win_psmodule/tasks/main.yml b/test/integration/targets/win_psmodule/tasks/main.yml new file mode 100644 index 0000000000..7daa468550 --- /dev/null +++ b/test/integration/targets/win_psmodule/tasks/main.yml @@ -0,0 +1,28 @@ +# test code for the win_psmodule module when using winrm connection +# (c) 2017, Daniele Lazzari + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + + +- name: get facts + setup: + +- name: Perform integration tests for Powershell 5+ + when: ansible_powershell_version >= 5 + block: + + - name: run all tasks + include: test.yml diff --git a/test/integration/targets/win_psmodule/tasks/test.yml b/test/integration/targets/win_psmodule/tasks/test.yml new file mode 100644 index 0000000000..7a771fdf55 --- /dev/null +++ b/test/integration/targets/win_psmodule/tasks/test.yml @@ -0,0 +1,136 @@ +--- + +- name: install module from Powershell Gallery + win_psmodule: + name: "{{ powershell_module }}" + state: present + register: module_setup + +- name: test Powershell Gallery module setup + assert: + that: + - "module_setup|changed" + - "module_setup.output == 'Module {{ powershell_module }} installed'" + +- name: check idempotency reinstalling module + win_psmodule: + name: "{{ powershell_module }}" + state: present + register: module_reinstall + +- name: test win_psmodule idempotency + assert: + that: + - "not module_reinstall|changed" + +- name: check module install with allow_clobber not active + win_psmodule: + name: "{{ allow_clobber_module }}" + register: fail_allow_clobber + ignore_errors: yes + +- name: test allow_clobber has failed + assert: + that: + - "fail_allow_clobber|failed" + +- name: check module install with allow_clobber active + win_psmodule: + name: "{{ allow_clobber_module }}" + allow_clobber: yes + register: ok_allow_clobber + +- name: test module install with allow_clobber active + assert: + that: + - "ok_allow_clobber|changed" + +- name: check wrong module install attempt + win_psmodule: + name: "{{ wrong_module }}" + state: present + ignore_errors: yes + register: module_fail + +- name: test module setup fails + assert: + that: + - "module_fail|failed" + +- name: check fake custom ps repository registration attempt + win_psmodule: + name: "{{ wrong_module }}" + repository: Fake repository + url: http://my_fake_repo.com/repo/ + ignore_errors: yes + register: repo_fail + +- name: test fake custom ps repository registration attempt + assert: + that: + - "repo_fail|failed" + +- name: check module is installed + win_shell: (Get-Module -Name {{ powershell_module }} -ListAvailable).Name + register: module_check + +- name: test module is installed + assert: + that: + - "module_check.stdout_lines[0] == '{{ powershell_module }}'" + +- name: check allow_clobber module is installed + win_shell: (Get-Module -Name {{ allow_clobber_module }} -ListAvailable).Name + register: allow_clobber_check + +- name: test allow_clobber module is installed + assert: + that: + - "allow_clobber_check.stdout_lines[0] == '{{ allow_clobber_module }}'" + +- name: remove installed powershell module + win_psmodule: + name: powershell-yaml + state: absent + register: module_uninstall + +- name: test powershell module removal + assert: + that: + - "module_uninstall|changed" + - "module_uninstall.output == 'Module {{ powershell_module }} removed'" + +- name: check module is uninstalled + win_shell: (Get-Module -Name {{ powershell_module }} -ListAvailable).Name + register: module_check + +- name: test module is no more present + assert: + that: + - "module_check.stdout == ''" + +- name: check idempotency re-removing module + win_psmodule: + name: "{{ powershell_module }}" + state: absent + register: module_uninstall_2 + +- name: test idempotency + assert: + that: + - "not module_uninstall_2|changed" + +- name: check removing allow_clobber module + win_psmodule: + name: "{{ allow_clobber_module }}" + state: absent + register: module_uninstall_3 + +- name: test removing allow_clobber module + assert: + that: + - "not module_uninstall_2|changed" + - "module_uninstall_3|changed" + + + \ No newline at end of file