From 8447f25e1043cb16375272cf5490e13ef4c2858f Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 18 Jul 2018 10:36:59 +1000 Subject: [PATCH] win_chocolatey_config: added module to manage Chocolatey config (#42915) --- .../modules/windows/win_chocolatey_config.ps1 | 122 +++++++++++++++ .../modules/windows/win_chocolatey_config.py | 60 ++++++++ .../targets/win_chocolatey_config/aliases | 1 + .../win_chocolatey_config/tasks/main.yml | 32 ++++ .../win_chocolatey_config/tasks/tests.yml | 141 ++++++++++++++++++ 5 files changed, 356 insertions(+) create mode 100644 lib/ansible/modules/windows/win_chocolatey_config.ps1 create mode 100644 lib/ansible/modules/windows/win_chocolatey_config.py create mode 100644 test/integration/targets/win_chocolatey_config/aliases create mode 100644 test/integration/targets/win_chocolatey_config/tasks/main.yml create mode 100644 test/integration/targets/win_chocolatey_config/tasks/tests.yml diff --git a/lib/ansible/modules/windows/win_chocolatey_config.ps1 b/lib/ansible/modules/windows/win_chocolatey_config.ps1 new file mode 100644 index 00000000000..119b12648d0 --- /dev/null +++ b/lib/ansible/modules/windows/win_chocolatey_config.ps1 @@ -0,0 +1,122 @@ +#!powershell + +# Copyright: (c) 2018, Ansible Project +# 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" + +$params = Parse-Args -arguments $args -supports_check_mode $true +$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false +$diff = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false + +$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true +$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent", "present" +$value = Get-AnsibleParam -obj $params -name "value" -type "str" -failifempty ($state -eq "present") + +$result = @{ + changed = $false +} +if ($diff) { + $result.diff = @{ + before = $null + after = $null + } +} + +if ($state -eq "present") { + if ($value -eq "") { + Fail-Json -obj $result -message "Cannot set Chocolatey config as an empty string when state=present, use state=absent instead" + } + # make sure bool values are lower case + if ($value -ceq "True" -or $value -ceq "False") { + $value = $value.ToLower() + } +} + +Function Get-ChocolateyConfig { + param($choco_app) + + # 'choco config list -r' does not display an easily parsable config entries + # It contains config/sources/feature in the one command, and is in the + # structure 'configKey = configValue | description', if the key or value + # contains a = or |, it will make it quite hard to easily parse it, + # compared to reading an XML file that already delimits these values + $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()) { + $config_info."$($config.key)" = $config.value + } + + return ,$config_info +} + +Function Remove-ChocolateyConfig { + param( + $choco_app, + $name + ) + $command = Argv-ToString -arguments @($choco_app.Path, "config", "unset", "--name", $name) + $res = Run-Command -command $command + if ($res.rc -ne 0) { + Fail-Json -obj $result -message "Failed to unset Chocolatey config for '$name': $($res.stderr)" + } +} + +Function Set-ChocolateyConfig { + param( + $choco_app, + $name, + $value + ) + $command = Argv-ToString -arguments @($choco_app.Path, "config", "set", "--name", $name, "--value", $value) + $res = Run-Command -command $command + if ($res.rc -ne 0) { + Fail-Json -obj $result -message "Failed to set Chocolatey config for '$name' to '$value': $($res.stderr)" + } +} + +$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" +} + +$config_info = Get-ChocolateyConfig -choco_app $choco_app +if ($name -notin $config_info.Keys) { + Fail-Json -obj $result -message "The Chocolatey config '$name' is not an existing config value, check the spelling. Valid config names: $($config_info.Keys -join ', ')" +} +if ($diff) { + $result.diff.before = $config_info.$name +} + +if ($state -eq "absent" -and $config_info.$name -ne "") { + if (-not $check_mode) { + Remove-ChocolateyConfig -choco_app $choco_app -name $name + } + $result.changed = $true +# choco.exe config set is not case sensitive, it won't make a change if the +# value is the same but doesn't match +} elseif ($state -eq "present" -and $config_info.$name -ne $value) { + if (-not $check_mode) { + Set-ChocolateyConfig -choco_app $choco_app -name $name -value $value + } + $result.changed = $true + if ($diff) { + $result.diff.after = $value + } +} + +Exit-Json -obj $result diff --git a/lib/ansible/modules/windows/win_chocolatey_config.py b/lib/ansible/modules/windows/win_chocolatey_config.py new file mode 100644 index 00000000000..f027b63996e --- /dev/null +++ b/lib/ansible/modules/windows/win_chocolatey_config.py @@ -0,0 +1,60 @@ +#!/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_chocolatey_config +version_added: '2.7' +short_description: Manages Chocolatey config settings +description: +- Used to manage Chocolatey config settings as well as unset the values. +options: + name: + description: + - The name of the config setting to manage. + - See U(https://chocolatey.org/docs/chocolatey-configuration) for a list of + valid configuration settings that can be changed. + - Any config values that contain encrypted values like a password are not + idempotent as the plaintext value cannot be read. + required: yes + state: + description: + - When C(absent), it will ensure the setting is unset or blank. + - When C(present), it will ensure the setting is set to the value of + I(value). + choices: + - absent + - present + default: present + value: + description: + - Used when C(state=present) that contains the value to set for the config + setting. + - Cannot be null or an empty string, use C(state=absent) to unset a config + value instead. +author: +- Jordan Borean (@jborean93) +''' + +EXAMPLES = r''' +- name: set the cache location + win_chocolatey_config: + name: cacheLocation + state: present + value: D:\chocolatey_temp + +- name: unset the cache location + win_chocolatey_config: + name: cacheLocation + state: absent +''' + +RETURN = r''' +''' diff --git a/test/integration/targets/win_chocolatey_config/aliases b/test/integration/targets/win_chocolatey_config/aliases new file mode 100644 index 00000000000..10e03fc2bf7 --- /dev/null +++ b/test/integration/targets/win_chocolatey_config/aliases @@ -0,0 +1 @@ +windows/ci/group1 diff --git a/test/integration/targets/win_chocolatey_config/tasks/main.yml b/test/integration/targets/win_chocolatey_config/tasks/main.yml new file mode 100644 index 00000000000..046ed78a1e2 --- /dev/null +++ b/test/integration/targets/win_chocolatey_config/tasks/main.yml @@ -0,0 +1,32 @@ +--- +- name: ensure Chocolatey is installed + win_chocolatey: + name: chocolatey + state: present + +- name: create a copy of the existing config file + win_copy: + src: C:\ProgramData\chocolatey\config\chocolatey.config + dest: C:\ProgramData\chocolatey\config\chocolatey.config.ansiblebak + remote_src: yes + +- name: unset config setting as baseline + win_chocolatey_config: + name: cacheLocation + state: absent + +- block: + - name: run tests + include_tasks: tests.yml + + always: + - name: restore config file + win_copy: + src: C:\ProgramData\chocolatey\config\chocolatey.config.ansiblebak + dest: C:\ProgramData\chocolatey\config\chocolatey.config + remote_src: yes + + - name: remove the backup config file + win_file: + path: C:\ProgramData\chocolatey\config\chocolatey.config.ansiblebak + state: absent diff --git a/test/integration/targets/win_chocolatey_config/tasks/tests.yml b/test/integration/targets/win_chocolatey_config/tasks/tests.yml new file mode 100644 index 00000000000..3ed538e8381 --- /dev/null +++ b/test/integration/targets/win_chocolatey_config/tasks/tests.yml @@ -0,0 +1,141 @@ +--- +- name: fail if value is not set and state=present + win_chocolatey_config: + name: cacheLocation + state: present + register: fail_no_value + failed_when: 'fail_no_value.msg != "Get-AnsibleParam: Missing required argument: value"' + +- name: fail to set invalid config name + win_chocolatey_config: + name: fake + state: present + value: value + register: fail_invalid_name + failed_when: '"The Chocolatey config ''fake'' is not an existing config value, check the spelling. Valid config names: " not in fail_invalid_name.msg' + +- name: set config setting (check mode) + win_chocolatey_config: + name: cacheLocation + state: present + value: C:\temp + check_mode: yes + register: set_check + +- name: get actual config setting (check mode) + win_command: choco.exe config get -r --name cacheLocation + register: set_actual_check + +- name: assert set config setting (check mode) + assert: + that: + - set_check is changed + - set_actual_check.stdout_lines == [""] + +- name: set config setting + win_chocolatey_config: + name: cacheLocation + state: present + value: C:\temp + register: set + +- name: get actual config setting + win_command: choco.exe config get -r --name cacheLocation + register: set_actual + +- name: assert set config setting + assert: + that: + - set is changed + - set_actual.stdout_lines == ["C:\\temp"] + +- name: change config value (check mode) + win_chocolatey_config: + name: cacheLocation + state: present + value: C:\temp2 + check_mode: yes + register: change_check + +- name: get actual config setting (check mode) + win_command: choco.exe config get -r --name cacheLocation + register: change_actual_check + +- name: assert change config value (check mode) + assert: + that: + - change_check is changed + - change_actual_check.stdout_lines == ["C:\\temp"] + +- name: change config value + win_chocolatey_config: + name: cacheLocation + state: present + value: C:\temp2 + register: change + +- name: get actual config setting + win_command: choco.exe config get -r --name cacheLocation + register: change_actual + +- name: assert change config value + assert: + that: + - change is changed + - change_actual.stdout_lines == ["C:\\temp2"] + +- name: change config value (idempotent) + win_chocolatey_config: + name: cacheLocation + state: present + value: C:\temp2 + register: change_again + +- name: assert change config value (idempotent) + assert: + that: + - not change_again is changed + +- name: unset config value (check mode) + win_chocolatey_config: + name: cacheLocation + state: absent + check_mode: yes + register: unset_check + +- name: get actual config setting (check mode) + win_command: choco.exe config get -r --name cacheLocation + register: unset_actual_check + +- name: assert unset config value (check mode) + assert: + that: + - unset_check is changed + - unset_actual_check.stdout_lines == ["C:\\temp2"] + +- name: unset config value + win_chocolatey_config: + name: cacheLocation + state: absent + register: unset + +- name: get actual config setting + win_command: choco.exe config get -r --name cacheLocation + register: unset_actual + +- name: assert unset config value + assert: + that: + - unset is changed + - unset_actual.stdout_lines == [""] + +- name: unset config value (idempotent) + win_chocolatey_config: + name: cacheLocation + state: absent + register: unset_again + +- name: assert unset config value (idempotent) + assert: + that: + - not unset_again is changed