diff --git a/lib/ansible/modules/windows/win_chocolatey_facts.ps1 b/lib/ansible/modules/windows/win_chocolatey_facts.ps1 new file mode 100644 index 0000000000..6e7b5766c2 --- /dev/null +++ b/lib/ansible/modules/windows/win_chocolatey_facts.ps1 @@ -0,0 +1,182 @@ +#!powershell + +# Copyright: (c) 2018, Ansible Project +# Copyright: (c) 2018, Simon Baerlocher +# Copyright: (c) 2018, ITIGO AG +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#Requires -Module Ansible.ModuleUtils.ArgvParser +#Requires -Module Ansible.ModuleUtils.CommandUtil +#Requires -Module Ansible.ModuleUtils.Legacy + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +# Create a new result object +$result = @{ + changed = $false + ansible_facts = @{ + ansible_chocolatey = @{ + config = @{} + feature = @{} + sources = @() + packages = @() + } + } +} + +$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue +if (-not $choco_app) { + Fail-Json -obj $result -message "Failed to find Chocolatey installation, make sure choco.exe is in the PATH env value" +} + +Function Get-ChocolateyFeature { + + param($choco_app) + + $command = Argv-ToString -arguments $choco_app.Path, "feature", "list", "-r" + $res = Run-Command -command $command + if ($res.rc -ne 0) { + $result.stdout = $res.stdout + $result.stderr = $res.stderr + $result.rc = $res.rc + Fail-Json -obj $result -message "Failed to list Chocolatey features, see stderr" + } + + $feature_info = @{} + $res.stdout -split "`r`n" | Where-Object { $_ -ne "" } | ForEach-Object { + $feature_split = $_ -split "\|" + $feature_info."$($feature_split[0])" = $feature_split[1] -eq "Enabled" + } + $result.ansible_facts.ansible_chocolatey.feature = $feature_info +} + +Function Get-ChocolateyConfig { + + param($choco_app) + + $choco_config_path = "$(Split-Path -Path (Split-Path -Path $choco_app.Path))\config\chocolatey.config" + if (-not (Test-Path -Path $choco_config_path)) { + Fail-Json -obj $result -message "Expecting Chocolatey config file to exist at '$choco_config_path'" + } + + try { + [xml]$choco_config = Get-Content -Path $choco_config_path + } catch { + Fail-Json -obj $result -message "Failed to parse Chocolatey config file at '$choco_config_path': $($_.Exception.Message)" + } + + $config_info = @{} + foreach ($config in $choco_config.chocolatey.config.GetEnumerator()) { + # try and parse as a boot, then an int, fallback to string + try { + $value = [System.Boolean]::Parse($config.value) + } catch { + try { + $value = [System.Int32]::Parse($config.value) + } catch { + $value = $config.value + } + } + $config_info."$($config.key)" = $value + } + $result.ansible_facts.ansible_chocolatey.config = $config_info +} + +Function Get-ChocolateyPackages { + + param($choco_app) + + $command = Argv-ToString -arguments $choco_app.Path, "list", "--local-only", "-r" + $res = Run-Command -command $command + if ($res.rc -ne 0) { + $result.stdout = $res.stdout + $result.stderr = $res.stderr + $result.rc = $res.rc + Fail-Json -obj $result -message "Failed to list Chocolatey Packages, see stderr" + } + + $packages_info = [System.Collections.ArrayList]@() + $res.stdout -split "`r`n" | Where-Object { $_ -ne "" } | ForEach-Object { + $packages_split = $_ -split "\|" + $package_info = @{ + package = $packages_split[0] + version = $packages_split[1] + } + $packages_info.Add($package_info) > $null + } + $result.ansible_facts.ansible_chocolatey.packages = $packages_info +} + +Function Get-ChocolateySources { + param($choco_app) + + $choco_config_path = "$(Split-Path -Path (Split-Path -Path $choco_app.Path))\config\chocolatey.config" + if (-not (Test-Path -LiteralPath $choco_config_path)) { + Fail-Json -obj $result -message "Expecting Chocolatey config file to exist at '$choco_config_path'" + } + + try { + [xml]$choco_config = Get-Content -Path $choco_config_path + } catch { + Fail-Json -obj $result -message "Failed to parse Chocolatey config file at '$choco_config_path': $($_.Exception.Message)" + } + + $sources = [System.Collections.ArrayList]@() + foreach ($xml_source in $choco_config.chocolatey.sources.GetEnumerator()) { + $source_username = $xml_source.Attributes.GetNamedItem("user") + if ($null -ne $source_username) { + $source_username = $source_username.Value + } + + # 0.9.9.9+ + $priority = $xml_source.Attributes.GetNamedItem("priority") + if ($null -ne $priority) { + $priority = [int]$priority.Value + } + + # 0.9.10+ + $certificate = $xml_source.Attributes.GetNamedItem("certificate") + if ($null -ne $certificate) { + $certificate = $certificate.Value + } + + # 0.10.4+ + $bypass_proxy = $xml_source.Attributes.GetNamedItem("bypassProxy") + if ($null -ne $bypass_proxy) { + $bypass_proxy = [System.Convert]::ToBoolean($bypass_proxy.Value) + } + $allow_self_service = $xml_source.Attributes.GetNamedItem("selfService") + if ($null -ne $allow_self_service) { + $allow_self_service = [System.Convert]::ToBoolean($allow_self_service.Value) + } + + # 0.10.8+ + $admin_only = $xml_source.Attributes.GetNamedItem("adminOnly") + if ($null -ne $admin_only) { + $admin_only = [System.Convert]::ToBoolean($admin_only.Value) + } + + $source_info = @{ + name = $xml_source.id + source = $xml_source.value + disabled = [System.Convert]::ToBoolean($xml_source.disabled) + source_username = $source_username + priority = $priority + certificate = $certificate + bypass_proxy = $bypass_proxy + allow_self_service = $allow_self_service + admin_only = $admin_only + } + $sources.Add($source_info) > $null + } + $result.ansible_facts.ansible_chocolatey.sources = $sources +} + +Get-ChocolateyConfig -choco_app $choco_app +Get-ChocolateyFeature -choco_app $choco_app +Get-ChocolateyPackages -choco_app $choco_app +Get-ChocolateySources -choco_app $choco_app + +# Return result +Exit-Json -obj $result diff --git a/lib/ansible/modules/windows/win_chocolatey_facts.py b/lib/ansible/modules/windows/win_chocolatey_facts.py new file mode 100644 index 0000000000..9531619f87 --- /dev/null +++ b/lib/ansible/modules/windows/win_chocolatey_facts.py @@ -0,0 +1,139 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Ansible Project +# Copyright: (c) 2018, Simon Baerlocher +# Copyright: (c) 2018, ITIGO AG +# 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_chocolatey_facts +version_added: '2.8' +short_description: Create a facts collection for Chocolatey +description: +- This module shows information from Chocolatey, such as installed packages, configuration, feature and sources. +author: +- Simon Bärlocher (@sbaerlocher) +- ITIGO AG (@itigoag) +notes: +- Chocolatey must be installed beforehand, use M(win_chocolatey) to do this. +''' + +EXAMPLES = r''' +- name: Gather facts from chocolatey + win_chocolatey_facts: + +- name: Displays the Configuration + debug: + var: ansible_chocolatey.config + +- name: Displays the Feature + debug: + var: ansible_chocolatey.feature + +- name: Displays the Sources + debug: + var: ansible_chocolatey.sources + +- name: Displays the Packages + debug: + var: ansible_chocolatey.packages +''' + +RETURN = r''' +ansible_facts: + description: Detailed information about the Chocolatey installation + returned: always + type: complex + contains: + ansible_chocolatey: + description: Detailed information about the Chocolatey installation + returned: always + type: complex + contains: + config: + description: Detailed information about stored the configurations + returned: always + type: dict + sample: + commandExecutionTimeoutSeconds: 2700 + containsLegacyPackageInstalls: true + feature: + description: Detailed information about enabled and disabled features + returned: always + type: dict + sample: + allowEmptyCheckums: false + autoUninstaller: true + failOnAutoUninstaller: false + sources: + description: List of Chocolatey sources + returned: always + type: complex + contains: + admin_only: + description: Is the source visible to Administrators only + returned: always + type: bool + sample: false + allow_self_service: + description: Is the source allowed to be used with self-service + returned: always + type: bool + sample: false + bypass_proxy: + description: Can the source explicitly bypass configured proxies + returned: always + type: bool + sample: true + certificate: + description: Pth to a PFX certificate for X509 authenticated feeds + returned: always + type: string + sample: C:\chocolatey\cert.pfx + disabled: + description: Is the source disabled + returned: always + type: bool + sample: false + name: + description: Name of the source + returned: always + type: string + sample: chocolatey + priority: + description: The priority order of this source, lower is better, 0 is no priority + returned: always + type: int + sample: 0 + source: + description: The source, can be a folder/file or an url + returned: always + type: string + sample: https://chocolatey.org/api/v2/ + source_username: + description: Username used to access authenticated feeds + returned: always + type: string + sample: username + packages: + description: List of installed Packages + returned: alway + type: complex + contains: + package: + description: Name of the package + returned: always + type: string + sample: vscode + version: + description: Version of the package + returned: always + type: string + sample: '1.27.2' +''' diff --git a/test/integration/targets/win_chocolatey_facts/aliases b/test/integration/targets/win_chocolatey_facts/aliases new file mode 100644 index 0000000000..215e0b0692 --- /dev/null +++ b/test/integration/targets/win_chocolatey_facts/aliases @@ -0,0 +1 @@ +shippable/windows/group4 diff --git a/test/integration/targets/win_chocolatey_facts/tasks/main.yml b/test/integration/targets/win_chocolatey_facts/tasks/main.yml new file mode 100644 index 0000000000..8f2b458034 --- /dev/null +++ b/test/integration/targets/win_chocolatey_facts/tasks/main.yml @@ -0,0 +1,69 @@ +--- +- name: ensure Chocolatey is installed + win_chocolatey: + name: chocolatey + state: present + +- name: create test source + win_chocolatey_source: + name: test|repo # use a pipe as that's a delimiter with Chocolatey, test edge case + state: disabled + admin_only: yes + allow_self_service: yes + bypass_proxy: yes + priority: 9 + source: http://test-server/chocolatey + source_username: test-user + source_password: password + certificate: C:\temp\cert.pfx + + +- name: set a config value + win_chocolatey_config: + name: proxyUser + state: present + value: test-user + +- block: + - name: Gather facts from chocolatey + win_chocolatey_facts: + + always: + - name: remove test source + win_chocolatey_source: + name: test|repo + state: absent + + - name: unset config value + win_chocolatey_config: + name: proxyUser + state: absent + +- name: assert facts from chocolatey + assert: + that: + - ansible_chocolatey is not changed + - ansible_chocolatey.config.commandExecutionTimeoutSeconds == 2700 + - ansible_chocolatey.config.proxyBypassOnLocal == True + - ansible_chocolatey.config.proxyUser == 'test-user' + - ansible_chocolatey.feature.checksumFiles == true + - ansible_chocolatey.packages[0].package == 'chocolatey' + - ansible_chocolatey.packages[0].version is defined + - ansible_chocolatey.sources[0].admin_only == False + - ansible_chocolatey.sources[0].allow_self_service == False + - ansible_chocolatey.sources[0].bypass_proxy == False + - ansible_chocolatey.sources[0].certificate == None + - ansible_chocolatey.sources[0].disabled == False + - ansible_chocolatey.sources[0].name == 'chocolatey' + - ansible_chocolatey.sources[0].priority == 0 + - ansible_chocolatey.sources[0].source == 'https://chocolatey.org/api/v2/' + - ansible_chocolatey.sources[0].source_username == None + - ansible_chocolatey.sources[1].admin_only == True + - ansible_chocolatey.sources[1].allow_self_service == True + - ansible_chocolatey.sources[1].bypass_proxy == True + - ansible_chocolatey.sources[1].certificate == 'C:\\temp\\cert.pfx' + - ansible_chocolatey.sources[1].disabled == True + - ansible_chocolatey.sources[1].name == 'test|repo' + - ansible_chocolatey.sources[1].priority == 9 + - ansible_chocolatey.sources[1].source == 'http://test-server/chocolatey' + - ansible_chocolatey.sources[1].source_username == 'test-user'