diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-26 04:05:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-26 04:05:56 +0000 |
commit | 67c6a4d1dccb62159b9d9b2dea4e2f487446e276 (patch) | |
tree | 9ccbb35137f480bbbdb899accbda52a8135d3416 /ansible_collections/community/general | |
parent | Adding upstream version 9.4.0+dfsg. (diff) | |
download | ansible-67c6a4d1dccb62159b9d9b2dea4e2f487446e276.tar.xz ansible-67c6a4d1dccb62159b9d9b2dea4e2f487446e276.zip |
Adding upstream version 9.5.1+dfsg.upstream/9.5.1+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/general')
95 files changed, 2904 insertions, 455 deletions
diff --git a/ansible_collections/community/general/.azure-pipelines/azure-pipelines.yml b/ansible_collections/community/general/.azure-pipelines/azure-pipelines.yml index 163d71b62..be8f011bd 100644 --- a/ansible_collections/community/general/.azure-pipelines/azure-pipelines.yml +++ b/ansible_collections/community/general/.azure-pipelines/azure-pipelines.yml @@ -73,40 +73,40 @@ stages: - test: 3 - test: 4 - test: extra - - stage: Sanity_2_16 - displayName: Sanity 2.16 + - stage: Sanity_2_17 + displayName: Sanity 2.17 dependsOn: [] jobs: - template: templates/matrix.yml parameters: nameFormat: Test {0} - testFormat: 2.16/sanity/{0} + testFormat: 2.17/sanity/{0} targets: - test: 1 - test: 2 - test: 3 - test: 4 - - stage: Sanity_2_15 - displayName: Sanity 2.15 + - stage: Sanity_2_16 + displayName: Sanity 2.16 dependsOn: [] jobs: - template: templates/matrix.yml parameters: nameFormat: Test {0} - testFormat: 2.15/sanity/{0} + testFormat: 2.16/sanity/{0} targets: - test: 1 - test: 2 - test: 3 - test: 4 - - stage: Sanity_2_14 - displayName: Sanity 2.14 + - stage: Sanity_2_15 + displayName: Sanity 2.15 dependsOn: [] jobs: - template: templates/matrix.yml parameters: nameFormat: Test {0} - testFormat: 2.14/sanity/{0} + testFormat: 2.15/sanity/{0} targets: - test: 1 - test: 2 @@ -122,12 +122,22 @@ stages: nameFormat: Python {0} testFormat: devel/units/{0}/1 targets: - - test: 3.7 - test: 3.8 - test: 3.9 - test: '3.10' - test: '3.11' - test: '3.12' + - stage: Units_2_17 + displayName: Units 2.17 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + nameFormat: Python {0} + testFormat: 2.17/units/{0}/1 + targets: + - test: 3.7 + - test: "3.12" - stage: Units_2_16 displayName: Units 2.16 dependsOn: [] @@ -151,16 +161,6 @@ stages: targets: - test: 3.5 - test: "3.10" - - stage: Units_2_14 - displayName: Units 2.14 - dependsOn: [] - jobs: - - template: templates/matrix.yml - parameters: - nameFormat: Python {0} - testFormat: 2.14/units/{0}/1 - targets: - - test: 3.9 ## Remote - stage: Remote_devel_extra_vms @@ -191,14 +191,26 @@ stages: test: macos/14.3 - name: RHEL 9.3 test: rhel/9.3 - - name: FreeBSD 13.3 - test: freebsd/13.3 - name: FreeBSD 14.0 test: freebsd/14.0 groups: - 1 - 2 - 3 + - stage: Remote_2_17 + displayName: Remote 2.17 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.17/{0} + targets: + - name: FreeBSD 13.3 + test: freebsd/13.3 + groups: + - 1 + - 2 + - 3 - stage: Remote_2_16 displayName: Remote 2.16 dependsOn: [] @@ -241,24 +253,6 @@ stages: - 1 - 2 - 3 - - stage: Remote_2_14 - displayName: Remote 2.14 - dependsOn: [] - jobs: - - template: templates/matrix.yml - parameters: - testFormat: 2.14/{0} - targets: - #- name: macOS 12.0 - # test: macos/12.0 - - name: RHEL 9.0 - test: rhel/9.0 - #- name: FreeBSD 12.4 - # test: freebsd/12.4 - groups: - - 1 - - 2 - - 3 ### Docker - stage: Docker_devel @@ -275,6 +269,18 @@ stages: test: ubuntu2004 - name: Ubuntu 22.04 test: ubuntu2204 + groups: + - 1 + - 2 + - 3 + - stage: Docker_2_17 + displayName: Docker 2.17 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.17/linux/{0} + targets: - name: Alpine 3.19 test: alpine319 groups: @@ -315,20 +321,6 @@ stages: - 1 - 2 - 3 - - stage: Docker_2_14 - displayName: Docker 2.14 - dependsOn: [] - jobs: - - template: templates/matrix.yml - parameters: - testFormat: 2.14/linux/{0} - targets: - - name: Alpine 3 - test: alpine3 - groups: - - 1 - - 2 - - 3 ### Community Docker - stage: Docker_community_devel @@ -360,6 +352,17 @@ stages: nameFormat: Python {0} testFormat: devel/generic/{0}/1 targets: + - test: '3.8' + - test: '3.11' + - stage: Generic_2_17 + displayName: Generic 2.17 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + nameFormat: Python {0} + testFormat: 2.17/generic/{0}/1 + targets: - test: '3.7' - test: '3.12' - stage: Generic_2_16 @@ -384,42 +387,32 @@ stages: testFormat: 2.15/generic/{0}/1 targets: - test: '3.9' - - stage: Generic_2_14 - displayName: Generic 2.14 - dependsOn: [] - jobs: - - template: templates/matrix.yml - parameters: - nameFormat: Python {0} - testFormat: 2.14/generic/{0}/1 - targets: - - test: '3.10' - stage: Summary condition: succeededOrFailed() dependsOn: - Sanity_devel + - Sanity_2_17 - Sanity_2_16 - Sanity_2_15 - - Sanity_2_14 - Units_devel + - Units_2_17 - Units_2_16 - Units_2_15 - - Units_2_14 - Remote_devel_extra_vms - Remote_devel + - Remote_2_17 - Remote_2_16 - Remote_2_15 - - Remote_2_14 - Docker_devel + - Docker_2_17 - Docker_2_16 - Docker_2_15 - - Docker_2_14 - Docker_community_devel # Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled. # - Generic_devel +# - Generic_2_17 # - Generic_2_16 # - Generic_2_15 -# - Generic_2_14 jobs: - template: templates/coverage.yml diff --git a/ansible_collections/community/general/.github/BOTMETA.yml b/ansible_collections/community/general/.github/BOTMETA.yml index 64cbc7021..e21d0c81c 100644 --- a/ansible_collections/community/general/.github/BOTMETA.yml +++ b/ansible_collections/community/general/.github/BOTMETA.yml @@ -780,6 +780,8 @@ files: maintainers: laurpaum $modules/keycloak_component_info.py: maintainers: desand01 + $modules/keycloak_client_rolescope.py: + maintainers: desand01 $modules/keycloak_user_rolemapping.py: maintainers: bratwurzt $modules/keycloak_realm_rolemapping.py: @@ -1445,6 +1447,8 @@ files: ignore: matze labels: zypper maintainers: $team_suse + $plugin_utils/unsafe.py: + maintainers: felixfontein $tests/a_module.py: maintainers: felixfontein $tests/fqdn_valid.py: @@ -1501,7 +1505,6 @@ macros: becomes: plugins/become caches: plugins/cache callbacks: plugins/callback - cliconfs: plugins/cliconf connections: plugins/connection doc_fragments: plugins/doc_fragments filters: plugins/filter @@ -1509,7 +1512,7 @@ macros: lookups: plugins/lookup module_utils: plugins/module_utils modules: plugins/modules - terminals: plugins/terminal + plugin_utils: plugins/plugin_utils tests: plugins/test team_ansible_core: team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross diff --git a/ansible_collections/community/general/.github/workflows/ansible-test.yml b/ansible_collections/community/general/.github/workflows/ansible-test.yml index bc9daaa43..ecfc36565 100644 --- a/ansible_collections/community/general/.github/workflows/ansible-test.yml +++ b/ansible_collections/community/general/.github/workflows/ansible-test.yml @@ -30,6 +30,7 @@ jobs: matrix: ansible: - '2.13' + - '2.14' # Ansible-test on various stable branches does not yet work well with cgroups v2. # Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04 # image for these stable branches. The list of branches where this is necessary will @@ -72,6 +73,8 @@ jobs: python: '2.7' - ansible: '2.13' python: '3.8' + - ansible: '2.14' + python: '3.9' steps: - name: >- @@ -148,11 +151,29 @@ jobs: docker: alpine3 python: '' target: azp/posix/3/ + # 2.14 + - ansible: '2.14' + docker: alpine3 + python: '' + target: azp/posix/1/ + - ansible: '2.14' + docker: alpine3 + python: '' + target: azp/posix/2/ + - ansible: '2.14' + docker: alpine3 + python: '' + target: azp/posix/3/ # Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled. # - ansible: '2.13' # docker: default # python: '3.9' # target: azp/generic/1/ + # Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled. + # - ansible: '2.14' + # docker: default + # python: '3.10' + # target: azp/generic/1/ steps: - name: >- diff --git a/ansible_collections/community/general/CHANGELOG.md b/ansible_collections/community/general/CHANGELOG.md index 08cac8a97..8f23931fe 100644 --- a/ansible_collections/community/general/CHANGELOG.md +++ b/ansible_collections/community/general/CHANGELOG.md @@ -2,71 +2,131 @@ **Topics** -- <a href="#v8-5-0">v8\.5\.0</a> +- <a href="#v8-6-0">v8\.6\.0</a> - <a href="#release-summary">Release Summary</a> - <a href="#minor-changes">Minor Changes</a> - - <a href="#security-fixes">Security Fixes</a> + - <a href="#deprecated-features">Deprecated Features</a> - <a href="#bugfixes">Bugfixes</a> - <a href="#new-modules">New Modules</a> -- <a href="#v8-4-0">v8\.4\.0</a> +- <a href="#v8-5-0">v8\.5\.0</a> - <a href="#release-summary-1">Release Summary</a> - <a href="#minor-changes-1">Minor Changes</a> + - <a href="#security-fixes">Security Fixes</a> - <a href="#bugfixes-1">Bugfixes</a> - - <a href="#new-plugins">New Plugins</a> - - <a href="#callback">Callback</a> - - <a href="#filter">Filter</a> - <a href="#new-modules-1">New Modules</a> -- <a href="#v8-3-0">v8\.3\.0</a> +- <a href="#v8-4-0">v8\.4\.0</a> - <a href="#release-summary-2">Release Summary</a> - <a href="#minor-changes-2">Minor Changes</a> - - <a href="#deprecated-features">Deprecated Features</a> - <a href="#bugfixes-2">Bugfixes</a> + - <a href="#new-plugins">New Plugins</a> + - <a href="#callback">Callback</a> + - <a href="#filter">Filter</a> - <a href="#new-modules-2">New Modules</a> -- <a href="#v8-2-0">v8\.2\.0</a> +- <a href="#v8-3-0">v8\.3\.0</a> - <a href="#release-summary-3">Release Summary</a> - <a href="#minor-changes-3">Minor Changes</a> + - <a href="#deprecated-features-1">Deprecated Features</a> - <a href="#bugfixes-3">Bugfixes</a> + - <a href="#new-modules-3">New Modules</a> +- <a href="#v8-2-0">v8\.2\.0</a> + - <a href="#release-summary-4">Release Summary</a> + - <a href="#minor-changes-4">Minor Changes</a> + - <a href="#bugfixes-4">Bugfixes</a> - <a href="#new-plugins-1">New Plugins</a> - <a href="#connection">Connection</a> - <a href="#filter-1">Filter</a> - <a href="#lookup">Lookup</a> - - <a href="#new-modules-3">New Modules</a> + - <a href="#new-modules-4">New Modules</a> - <a href="#v8-1-0">v8\.1\.0</a> - - <a href="#release-summary-4">Release Summary</a> - - <a href="#minor-changes-4">Minor Changes</a> - - <a href="#bugfixes-4">Bugfixes</a> + - <a href="#release-summary-5">Release Summary</a> + - <a href="#minor-changes-5">Minor Changes</a> + - <a href="#bugfixes-5">Bugfixes</a> - <a href="#new-plugins-2">New Plugins</a> - <a href="#lookup-1">Lookup</a> - <a href="#test">Test</a> - - <a href="#new-modules-4">New Modules</a> + - <a href="#new-modules-5">New Modules</a> - <a href="#v8-0-2">v8\.0\.2</a> - - <a href="#release-summary-5">Release Summary</a> - - <a href="#bugfixes-5">Bugfixes</a> -- <a href="#v8-0-1">v8\.0\.1</a> - <a href="#release-summary-6">Release Summary</a> - <a href="#bugfixes-6">Bugfixes</a> -- <a href="#v8-0-0">v8\.0\.0</a> +- <a href="#v8-0-1">v8\.0\.1</a> - <a href="#release-summary-7">Release Summary</a> - - <a href="#minor-changes-5">Minor Changes</a> + - <a href="#bugfixes-7">Bugfixes</a> +- <a href="#v8-0-0">v8\.0\.0</a> + - <a href="#release-summary-8">Release Summary</a> + - <a href="#minor-changes-6">Minor Changes</a> - <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a> - - <a href="#deprecated-features-1">Deprecated Features</a> + - <a href="#deprecated-features-2">Deprecated Features</a> - <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a> - - <a href="#bugfixes-7">Bugfixes</a> + - <a href="#bugfixes-8">Bugfixes</a> - <a href="#known-issues">Known Issues</a> - <a href="#new-plugins-3">New Plugins</a> - <a href="#lookup-2">Lookup</a> - - <a href="#new-modules-5">New Modules</a> + - <a href="#new-modules-6">New Modules</a> This changelog describes changes after version 7\.0\.0\. +<a id="v8-6-0"></a> +## v8\.6\.0 + +<a id="release-summary"></a> +### Release Summary + +Regular bugfix and features release\. + +<a id="minor-changes"></a> +### Minor Changes + +* Use offset\-aware <code>datetime\.datetime</code> objects \(with timezone UTC\) instead of offset\-naive UTC timestamps\, which are deprecated in Python 3\.12 \([https\://github\.com/ansible\-collections/community\.general/pull/8222](https\://github\.com/ansible\-collections/community\.general/pull/8222)\)\. +* apt\_rpm \- add new states <code>latest</code> and <code>present\_not\_latest</code>\. The value <code>latest</code> is equivalent to the current behavior of <code>present</code>\, which will upgrade a package if a newer version exists\. <code>present\_not\_latest</code> does what most users would expect <code>present</code> to do\: it does not upgrade if the package is already installed\. The current behavior of <code>present</code> will be deprecated in a later version\, and eventually changed to that of <code>present\_not\_latest</code> \([https\://github\.com/ansible\-collections/community\.general/issues/8217](https\://github\.com/ansible\-collections/community\.general/issues/8217)\, [https\://github\.com/ansible\-collections/community\.general/pull/8247](https\://github\.com/ansible\-collections/community\.general/pull/8247)\)\. +* bitwarden lookup plugin \- add support to filter by organization ID \([https\://github\.com/ansible\-collections/community\.general/pull/8188](https\://github\.com/ansible\-collections/community\.general/pull/8188)\)\. +* filesystem \- add bcachefs support \([https\://github\.com/ansible\-collections/community\.general/pull/8126](https\://github\.com/ansible\-collections/community\.general/pull/8126)\)\. +* ini\_file \- add an optional parameter <code>section\_has\_values</code>\. If the target ini file contains more than one <code>section</code>\, use <code>section\_has\_values</code> to specify which one should be updated \([https\://github\.com/ansible\-collections/community\.general/pull/7505](https\://github\.com/ansible\-collections/community\.general/pull/7505)\)\. +* java\_cert \- add <code>cert\_content</code> argument \([https\://github\.com/ansible\-collections/community\.general/pull/8153](https\://github\.com/ansible\-collections/community\.general/pull/8153)\)\. +* keycloak\_client\, keycloak\_clientscope\, keycloak\_clienttemplate \- added <code>docker\-v2</code> protocol support\, enhancing alignment with Keycloak\'s protocol options \([https\://github\.com/ansible\-collections/community\.general/issues/8215](https\://github\.com/ansible\-collections/community\.general/issues/8215)\, [https\://github\.com/ansible\-collections/community\.general/pull/8216](https\://github\.com/ansible\-collections/community\.general/pull/8216)\)\. +* nmcli \- adds OpenvSwitch support with new <code>type</code> values <code>ovs\-port</code>\, <code>ovs\-interface</code>\, and <code>ovs\-bridge</code>\, and new <code>slave\_type</code> value <code>ovs\-port</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8154](https\://github\.com/ansible\-collections/community\.general/pull/8154)\)\. +* osx\_defaults \- add option <code>check\_types</code> to enable changing the type of existing defaults on the fly \([https\://github\.com/ansible\-collections/community\.general/pull/8173](https\://github\.com/ansible\-collections/community\.general/pull/8173)\)\. +* passwordstore lookup \- add <code>missing\_subkey</code> parameter defining the behavior of the lookup when a passwordstore subkey is missing \([https\://github\.com/ansible\-collections/community\.general/pull/8166](https\://github\.com/ansible\-collections/community\.general/pull/8166)\)\. +* portage \- adds the possibility to explicitely tell portage to write packages to world file \([https\://github\.com/ansible\-collections/community\.general/issues/6226](https\://github\.com/ansible\-collections/community\.general/issues/6226)\, [https\://github\.com/ansible\-collections/community\.general/pull/8236](https\://github\.com/ansible\-collections/community\.general/pull/8236)\)\. +* redfish\_command \- add command <code>ResetToDefaults</code> to reset manager to default state \([https\://github\.com/ansible\-collections/community\.general/issues/8163](https\://github\.com/ansible\-collections/community\.general/issues/8163)\)\. +* redfish\_info \- add boolean return value <code>MultipartHttpPush</code> to <code>GetFirmwareUpdateCapabilities</code> \([https\://github\.com/ansible\-collections/community\.general/issues/8194](https\://github\.com/ansible\-collections/community\.general/issues/8194)\, [https\://github\.com/ansible\-collections/community\.general/pull/8195](https\://github\.com/ansible\-collections/community\.general/pull/8195)\)\. +* ssh\_config \- allow <code>accept\-new</code> as valid value for <code>strict\_host\_key\_checking</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8257](https\://github\.com/ansible\-collections/community\.general/pull/8257)\)\. + +<a id="deprecated-features"></a> +### Deprecated Features + +* hipchat callback plugin \- the hipchat service has been discontinued and the self\-hosted variant has been End of Life since 2020\. The callback plugin is therefore deprecated and will be removed from community\.general 10\.0\.0 if nobody provides compelling reasons to still keep it \([https\://github\.com/ansible\-collections/community\.general/issues/8184](https\://github\.com/ansible\-collections/community\.general/issues/8184)\, [https\://github\.com/ansible\-collections/community\.general/pull/8189](https\://github\.com/ansible\-collections/community\.general/pull/8189)\)\. + +<a id="bugfixes"></a> +### Bugfixes + +* aix\_filesystem \- fix <code>\_validate\_vg</code> not passing VG name to <code>lsvg\_cmd</code> \([https\://github\.com/ansible\-collections/community\.general/issues/8151](https\://github\.com/ansible\-collections/community\.general/issues/8151)\)\. +* apt\_rpm \- when checking whether packages were installed after running <code>apt\-get \-y install \<packages\></code>\, only the last package name was checked \([https\://github\.com/ansible\-collections/community\.general/pull/8263](https\://github\.com/ansible\-collections/community\.general/pull/8263)\)\. +* bitwarden\_secrets\_manager lookup plugin \- implements retry with exponential backoff to avoid lookup errors when Bitwardn\'s API rate limiting is encountered \([https\://github\.com/ansible\-collections/community\.general/issues/8230](https\://github\.com/ansible\-collections/community\.general/issues/8230)\, [https\://github\.com/ansible\-collections/community\.general/pull/8238](https\://github\.com/ansible\-collections/community\.general/pull/8238)\)\. +* from\_ini filter plugin \- disabling interpolation of <code>ConfigParser</code> to allow converting values with a <code>\%</code> sign \([https\://github\.com/ansible\-collections/community\.general/issues/8183](https\://github\.com/ansible\-collections/community\.general/issues/8183)\, [https\://github\.com/ansible\-collections/community\.general/pull/8185](https\://github\.com/ansible\-collections/community\.general/pull/8185)\)\. +* gitlab\_issue\, gitlab\_label\, gitlab\_milestone \- avoid crash during version comparison when the python\-gitlab Python module is not installed \([https\://github\.com/ansible\-collections/community\.general/pull/8158](https\://github\.com/ansible\-collections/community\.general/pull/8158)\)\. +* haproxy \- fix an issue where HAProxy could get stuck in DRAIN mode when the backend was unreachable \([https\://github\.com/ansible\-collections/community\.general/issues/8092](https\://github\.com/ansible\-collections/community\.general/issues/8092)\)\. +* inventory plugins \- add unsafe wrapper to avoid marking strings that do not contain <code>\{</code> or <code>\}</code> as unsafe\, to work around a bug in AWX \(\([https\://github\.com/ansible\-collections/community\.general/issues/8212](https\://github\.com/ansible\-collections/community\.general/issues/8212)\, [https\://github\.com/ansible\-collections/community\.general/pull/8225](https\://github\.com/ansible\-collections/community\.general/pull/8225)\)\. +* ipa \- fix get version regex in IPA module\_utils \([https\://github\.com/ansible\-collections/community\.general/pull/8175](https\://github\.com/ansible\-collections/community\.general/pull/8175)\)\. +* keycloak\_client \- add sorted <code>defaultClientScopes</code> and <code>optionalClientScopes</code> to normalizations \([https\://github\.com/ansible\-collections/community\.general/pull/8223](https\://github\.com/ansible\-collections/community\.general/pull/8223)\)\. +* keycloak\_realm \- add normalizations for <code>enabledEventTypes</code> and <code>supportedLocales</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8224](https\://github\.com/ansible\-collections/community\.general/pull/8224)\)\. +* puppet \- add option <code>environment\_lang</code> to set the environment language encoding\. Defaults to lang <code>C</code>\. It is recommended to set it to <code>C\.UTF\-8</code> or <code>en\_US\.UTF\-8</code> depending on what is available on your system\. \([https\://github\.com/ansible\-collections/community\.general/issues/8000](https\://github\.com/ansible\-collections/community\.general/issues/8000)\) +* riak \- support <code>riak admin</code> sub\-command in newer Riak KV versions beside the legacy <code>riak\-admin</code> main command \([https\://github\.com/ansible\-collections/community\.general/pull/8211](https\://github\.com/ansible\-collections/community\.general/pull/8211)\)\. +* to\_ini filter plugin \- disabling interpolation of <code>ConfigParser</code> to allow converting values with a <code>\%</code> sign \([https\://github\.com/ansible\-collections/community\.general/issues/8183](https\://github\.com/ansible\-collections/community\.general/issues/8183)\, [https\://github\.com/ansible\-collections/community\.general/pull/8185](https\://github\.com/ansible\-collections/community\.general/pull/8185)\)\. +* xml \- make module work with lxml 5\.1\.1\, which removed some internals that the module was relying on \([https\://github\.com/ansible\-collections/community\.general/pull/8169](https\://github\.com/ansible\-collections/community\.general/pull/8169)\)\. + +<a id="new-modules"></a> +### New Modules + +* keycloak\_client\_rolescope \- Allows administration of Keycloak client roles scope to restrict the usage of certain roles to a other specific client applications\. + <a id="v8-5-0"></a> ## v8\.5\.0 -<a id="release-summary"></a> +<a id="release-summary-1"></a> ### Release Summary Regular feature and bugfix release with security fixes\. -<a id="minor-changes"></a> +<a id="minor-changes-1"></a> ### Minor Changes * bitwarden lookup plugin \- allows to fetch all records of a given collection ID\, by allowing to pass an empty value for <code>search\_value</code> when <code>collection\_id</code> is provided \([https\://github\.com/ansible\-collections/community\.general/pull/8013](https\://github\.com/ansible\-collections/community\.general/pull/8013)\)\. @@ -84,7 +144,7 @@ Regular feature and bugfix release with security fixes\. * cobbler\, gitlab\_runners\, icinga2\, linode\, lxd\, nmap\, online\, opennebula\, proxmox\, scaleway\, stackpath\_compute\, virtualbox\, and xen\_orchestra inventory plugin \- make sure all data received from the remote servers is marked as unsafe\, so remote code execution by obtaining texts that can be evaluated as templates is not possible \([https\://www\.die\-welt\.net/2024/03/remote\-code\-execution\-in\-ansible\-dynamic\-inventory\-plugins/](https\://www\.die\-welt\.net/2024/03/remote\-code\-execution\-in\-ansible\-dynamic\-inventory\-plugins/)\, [https\://github\.com/ansible\-collections/community\.general/pull/8098](https\://github\.com/ansible\-collections/community\.general/pull/8098)\)\. -<a id="bugfixes"></a> +<a id="bugfixes-1"></a> ### Bugfixes * aix\_filesystem \- fix issue with empty list items in crfs logic and option order \([https\://github\.com/ansible\-collections/community\.general/pull/8052](https\://github\.com/ansible\-collections/community\.general/pull/8052)\)\. @@ -98,7 +158,7 @@ Regular feature and bugfix release with security fixes\. * pam\_limits \- when the file does not exist\, do not create it in check mode \([https\://github\.com/ansible\-collections/community\.general/issues/8050](https\://github\.com/ansible\-collections/community\.general/issues/8050)\, [https\://github\.com/ansible\-collections/community\.general/pull/8057](https\://github\.com/ansible\-collections/community\.general/pull/8057)\)\. * proxmox\_kvm \- fixed status check getting from node\-specific API endpoint \([https\://github\.com/ansible\-collections/community\.general/issues/7817](https\://github\.com/ansible\-collections/community\.general/issues/7817)\)\. -<a id="new-modules"></a> +<a id="new-modules-1"></a> ### New Modules * usb\_facts \- Allows listing information about USB devices @@ -106,12 +166,12 @@ Regular feature and bugfix release with security fixes\. <a id="v8-4-0"></a> ## v8\.4\.0 -<a id="release-summary-1"></a> +<a id="release-summary-2"></a> ### Release Summary Regular bugfix and feature release\. -<a id="minor-changes-1"></a> +<a id="minor-changes-2"></a> ### Minor Changes * bitwarden lookup plugin \- add <code>bw\_session</code> option\, to pass session key instead of reading from env \([https\://github\.com/ansible\-collections/community\.general/pull/7994](https\://github\.com/ansible\-collections/community\.general/pull/7994)\)\. @@ -124,7 +184,7 @@ Regular bugfix and feature release\. * sudoers \- add support for the <code>NOEXEC</code> tag in sudoers rules \([https\://github\.com/ansible\-collections/community\.general/pull/7983](https\://github\.com/ansible\-collections/community\.general/pull/7983)\)\. * terraform \- fix <code>diff\_mode</code> in state <code>absent</code> and when terraform <code>resource\_changes</code> does not exist \([https\://github\.com/ansible\-collections/community\.general/pull/7963](https\://github\.com/ansible\-collections/community\.general/pull/7963)\)\. -<a id="bugfixes-1"></a> +<a id="bugfixes-2"></a> ### Bugfixes * cargo \- fix idempotency issues when using a custom installation path for packages \(using the <code>\-\-path</code> parameter\)\. The initial installation runs fine\, but subsequent runs use the <code>get\_installed\(\)</code> function which did not check the given installation location\, before running <code>cargo install</code>\. This resulted in a false <code>changed</code> state\. Also the removal of packeges using <code>state\: absent</code> failed\, as the installation check did not use the given parameter \([https\://github\.com/ansible\-collections/community\.general/pull/7970](https\://github\.com/ansible\-collections/community\.general/pull/7970)\)\. @@ -153,7 +213,7 @@ Regular bugfix and feature release\. * lists\_symmetric\_difference \- Symmetric Difference of lists with a predictive order * lists\_union \- Union of lists with a predictive order -<a id="new-modules-1"></a> +<a id="new-modules-2"></a> ### New Modules * gitlab\_group\_access\_token \- Manages GitLab group access tokens @@ -162,12 +222,12 @@ Regular bugfix and feature release\. <a id="v8-3-0"></a> ## v8\.3\.0 -<a id="release-summary-2"></a> +<a id="release-summary-3"></a> ### Release Summary Regular bugfix and feature release\. -<a id="minor-changes-2"></a> +<a id="minor-changes-3"></a> ### Minor Changes * consul\_auth\_method\, consul\_binding\_rule\, consul\_policy\, consul\_role\, consul\_session\, consul\_token \- added action group <code>community\.general\.consul</code> \([https\://github\.com/ansible\-collections/community\.general/pull/7897](https\://github\.com/ansible\-collections/community\.general/pull/7897)\)\. @@ -180,12 +240,12 @@ Regular bugfix and feature release\. * redfish\_info \- add command <code>GetServiceIdentification</code> to get service identification \([https\://github\.com/ansible\-collections/community\.general/issues/7882](https\://github\.com/ansible\-collections/community\.general/issues/7882)\)\. * terraform \- add support for <code>diff\_mode</code> for terraform resource\_changes \([https\://github\.com/ansible\-collections/community\.general/pull/7896](https\://github\.com/ansible\-collections/community\.general/pull/7896)\)\. -<a id="deprecated-features"></a> +<a id="deprecated-features-1"></a> ### Deprecated Features * consul\_acl \- the module has been deprecated and will be removed in community\.general 10\.0\.0\. <code>consul\_token</code> and <code>consul\_policy</code> can be used instead \([https\://github\.com/ansible\-collections/community\.general/pull/7901](https\://github\.com/ansible\-collections/community\.general/pull/7901)\)\. -<a id="bugfixes-2"></a> +<a id="bugfixes-3"></a> ### Bugfixes * homebrew \- detect already installed formulae and casks using JSON output from <code>brew info</code> \([https\://github\.com/ansible\-collections/community\.general/issues/864](https\://github\.com/ansible\-collections/community\.general/issues/864)\)\. @@ -196,7 +256,7 @@ Regular bugfix and feature release\. * nmcli \- fix <code>connection\.slave\-type</code> wired to <code>bond</code> and not with parameter <code>slave\_type</code> in case of connection type <code>wifi</code> \([https\://github\.com/ansible\-collections/community\.general/issues/7389](https\://github\.com/ansible\-collections/community\.general/issues/7389)\)\. * proxmox \- fix updating a container config if the setting does not already exist \([https\://github\.com/ansible\-collections/community\.general/pull/7872](https\://github\.com/ansible\-collections/community\.general/pull/7872)\)\. -<a id="new-modules-2"></a> +<a id="new-modules-3"></a> ### New Modules * consul\_acl\_bootstrap \- Bootstrap ACLs in Consul @@ -209,12 +269,12 @@ Regular bugfix and feature release\. <a id="v8-2-0"></a> ## v8\.2\.0 -<a id="release-summary-3"></a> +<a id="release-summary-4"></a> ### Release Summary Regular bugfix and feature release\. -<a id="minor-changes-3"></a> +<a id="minor-changes-4"></a> ### Minor Changes * ipa\_dnsrecord \- adds ability to manage NS record types \([https\://github\.com/ansible\-collections/community\.general/pull/7737](https\://github\.com/ansible\-collections/community\.general/pull/7737)\)\. @@ -230,7 +290,7 @@ Regular bugfix and feature release\. * ssh\_config \- new feature to set <code>IdentitiesOnly</code> option to <code>yes</code> or <code>no</code> \([https\://github\.com/ansible\-collections/community\.general/pull/7704](https\://github\.com/ansible\-collections/community\.general/pull/7704)\)\. * xcc\_redfish\_command \- added support for raw POSTs \(<code>command\=PostResource</code> in <code>category\=Raw</code>\) without a specific action info \([https\://github\.com/ansible\-collections/community\.general/pull/7746](https\://github\.com/ansible\-collections/community\.general/pull/7746)\)\. -<a id="bugfixes-3"></a> +<a id="bugfixes-4"></a> ### Bugfixes * keycloak\_identity\_provider \- <code>mappers</code> processing was not idempotent if the mappers configuration list had not been sorted by name \(in ascending order\)\. Fix resolves the issue by sorting mappers in the desired state using the same key which is used for obtaining existing state \([https\://github\.com/ansible\-collections/community\.general/pull/7418](https\://github\.com/ansible\-collections/community\.general/pull/7418)\)\. @@ -258,7 +318,7 @@ Regular bugfix and feature release\. * github\_app\_access\_token \- Obtain short\-lived Github App Access tokens -<a id="new-modules-3"></a> +<a id="new-modules-4"></a> ### New Modules * dnf\_config\_manager \- Enable or disable dnf repositories using config\-manager @@ -270,12 +330,12 @@ Regular bugfix and feature release\. <a id="v8-1-0"></a> ## v8\.1\.0 -<a id="release-summary-4"></a> +<a id="release-summary-5"></a> ### Release Summary Regular bugfix and feature release\. -<a id="minor-changes-4"></a> +<a id="minor-changes-5"></a> ### Minor Changes * bitwarden lookup plugin \- when looking for items using an item ID\, the item is now accessed directly with <code>bw get item</code> instead of searching through all items\. This doubles the lookup speed \([https\://github\.com/ansible\-collections/community\.general/pull/7468](https\://github\.com/ansible\-collections/community\.general/pull/7468)\)\. @@ -312,7 +372,7 @@ Regular bugfix and feature release\. * redfish\_info \- adding the <code>BootProgress</code> property when getting <code>Systems</code> info \([https\://github\.com/ansible\-collections/community\.general/pull/7626](https\://github\.com/ansible\-collections/community\.general/pull/7626)\)\. * ssh\_config \- adds <code>controlmaster</code>\, <code>controlpath</code> and <code>controlpersist</code> parameters \([https\://github\.com/ansible\-collections/community\.general/pull/7456](https\://github\.com/ansible\-collections/community\.general/pull/7456)\)\. -<a id="bugfixes-4"></a> +<a id="bugfixes-5"></a> ### Bugfixes * apt\-rpm \- the module did not upgrade packages if a newer version exists\. Now the package will be reinstalled if the candidate is newer than the installed version \([https\://github\.com/ansible\-collections/community\.general/issues/7414](https\://github\.com/ansible\-collections/community\.general/issues/7414)\)\. @@ -343,7 +403,7 @@ Regular bugfix and feature release\. * fqdn\_valid \- Validates fully\-qualified domain names against RFC 1123 -<a id="new-modules-4"></a> +<a id="new-modules-5"></a> ### New Modules * git\_config\_info \- Read git configuration @@ -353,12 +413,12 @@ Regular bugfix and feature release\. <a id="v8-0-2"></a> ## v8\.0\.2 -<a id="release-summary-5"></a> +<a id="release-summary-6"></a> ### Release Summary Bugfix release for inclusion in Ansible 9\.0\.0rc1\. -<a id="bugfixes-5"></a> +<a id="bugfixes-6"></a> ### Bugfixes * ocapi\_utils\, oci\_utils\, redfish\_utils module utils \- replace <code>type\(\)</code> calls with <code>isinstance\(\)</code> calls \([https\://github\.com/ansible\-collections/community\.general/pull/7501](https\://github\.com/ansible\-collections/community\.general/pull/7501)\)\. @@ -367,12 +427,12 @@ Bugfix release for inclusion in Ansible 9\.0\.0rc1\. <a id="v8-0-1"></a> ## v8\.0\.1 -<a id="release-summary-6"></a> +<a id="release-summary-7"></a> ### Release Summary Bugfix release for inclusion in Ansible 9\.0\.0b1\. -<a id="bugfixes-6"></a> +<a id="bugfixes-7"></a> ### Bugfixes * gitlab\_group\_members \- fix gitlab constants call in <code>gitlab\_group\_members</code> module \([https\://github\.com/ansible\-collections/community\.general/issues/7467](https\://github\.com/ansible\-collections/community\.general/issues/7467)\)\. @@ -385,12 +445,12 @@ Bugfix release for inclusion in Ansible 9\.0\.0b1\. <a id="v8-0-0"></a> ## v8\.0\.0 -<a id="release-summary-7"></a> +<a id="release-summary-8"></a> ### Release Summary This is release 8\.0\.0 of <code>community\.general</code>\, released on 2023\-11\-01\. -<a id="minor-changes-5"></a> +<a id="minor-changes-6"></a> ### Minor Changes * The collection will start using semantic markup \([https\://github\.com/ansible\-collections/community\.general/pull/6539](https\://github\.com/ansible\-collections/community\.general/pull/6539)\)\. @@ -529,7 +589,7 @@ This is release 8\.0\.0 of <code>community\.general</code>\, released on 2023\-1 * vardict module utils \- <code>VarDict</code> will no longer accept variables named <code>\_var</code>\, <code>get\_meta</code>\, and <code>as\_dict</code> \([https\://github\.com/ansible\-collections/community\.general/pull/6647](https\://github\.com/ansible\-collections/community\.general/pull/6647)\)\. * version module util \- remove fallback for ansible\-core 2\.11\. All modules and plugins that do version collections no longer work with ansible\-core 2\.11 \([https\://github\.com/ansible\-collections/community\.general/pull/7269](https\://github\.com/ansible\-collections/community\.general/pull/7269)\)\. -<a id="deprecated-features-1"></a> +<a id="deprecated-features-2"></a> ### Deprecated Features * CmdRunner module utils \- deprecate <code>cmd\_runner\_fmt\.as\_default\_type\(\)</code> formatter \([https\://github\.com/ansible\-collections/community\.general/pull/6601](https\://github\.com/ansible\-collections/community\.general/pull/6601)\)\. @@ -589,7 +649,7 @@ This is release 8\.0\.0 of <code>community\.general</code>\, released on 2023\-1 * proxmox module utils \- removed unused imports \([https\://github\.com/ansible\-collections/community\.general/pull/6873](https\://github\.com/ansible\-collections/community\.general/pull/6873)\)\. * xfconf \- the deprecated <code>disable\_facts</code> option was removed \([https\://github\.com/ansible\-collections/community\.general/pull/7358](https\://github\.com/ansible\-collections/community\.general/pull/7358)\)\. -<a id="bugfixes-7"></a> +<a id="bugfixes-8"></a> ### Bugfixes * CmdRunner module utils \- does not attempt to resolve path if executable is a relative or absolute path \([https\://github\.com/ansible\-collections/community\.general/pull/7200](https\://github\.com/ansible\-collections/community\.general/pull/7200)\)\. @@ -684,7 +744,7 @@ This is release 8\.0\.0 of <code>community\.general</code>\, released on 2023\-1 * bitwarden\_secrets\_manager \- Retrieve secrets from Bitwarden Secrets Manager -<a id="new-modules-5"></a> +<a id="new-modules-6"></a> ### New Modules * consul\_policy \- Manipulate Consul policies diff --git a/ansible_collections/community/general/CHANGELOG.rst b/ansible_collections/community/general/CHANGELOG.rst index da10a021b..5a5a0cb7c 100644 --- a/ansible_collections/community/general/CHANGELOG.rst +++ b/ansible_collections/community/general/CHANGELOG.rst @@ -6,6 +6,60 @@ Community General Release Notes This changelog describes changes after version 7.0.0. +v8.6.0 +====== + +Release Summary +--------------- + +Regular bugfix and features release. + +Minor Changes +------------- + +- Use offset-aware ``datetime.datetime`` objects (with timezone UTC) instead of offset-naive UTC timestamps, which are deprecated in Python 3.12 (https://github.com/ansible-collections/community.general/pull/8222). +- apt_rpm - add new states ``latest`` and ``present_not_latest``. The value ``latest`` is equivalent to the current behavior of ``present``, which will upgrade a package if a newer version exists. ``present_not_latest`` does what most users would expect ``present`` to do: it does not upgrade if the package is already installed. The current behavior of ``present`` will be deprecated in a later version, and eventually changed to that of ``present_not_latest`` (https://github.com/ansible-collections/community.general/issues/8217, https://github.com/ansible-collections/community.general/pull/8247). +- bitwarden lookup plugin - add support to filter by organization ID (https://github.com/ansible-collections/community.general/pull/8188). +- filesystem - add bcachefs support (https://github.com/ansible-collections/community.general/pull/8126). +- ini_file - add an optional parameter ``section_has_values``. If the target ini file contains more than one ``section``, use ``section_has_values`` to specify which one should be updated (https://github.com/ansible-collections/community.general/pull/7505). +- java_cert - add ``cert_content`` argument (https://github.com/ansible-collections/community.general/pull/8153). +- keycloak_client, keycloak_clientscope, keycloak_clienttemplate - added ``docker-v2`` protocol support, enhancing alignment with Keycloak's protocol options (https://github.com/ansible-collections/community.general/issues/8215, https://github.com/ansible-collections/community.general/pull/8216). +- nmcli - adds OpenvSwitch support with new ``type`` values ``ovs-port``, ``ovs-interface``, and ``ovs-bridge``, and new ``slave_type`` value ``ovs-port`` (https://github.com/ansible-collections/community.general/pull/8154). +- osx_defaults - add option ``check_types`` to enable changing the type of existing defaults on the fly (https://github.com/ansible-collections/community.general/pull/8173). +- passwordstore lookup - add ``missing_subkey`` parameter defining the behavior of the lookup when a passwordstore subkey is missing (https://github.com/ansible-collections/community.general/pull/8166). +- portage - adds the possibility to explicitely tell portage to write packages to world file (https://github.com/ansible-collections/community.general/issues/6226, https://github.com/ansible-collections/community.general/pull/8236). +- redfish_command - add command ``ResetToDefaults`` to reset manager to default state (https://github.com/ansible-collections/community.general/issues/8163). +- redfish_info - add boolean return value ``MultipartHttpPush`` to ``GetFirmwareUpdateCapabilities`` (https://github.com/ansible-collections/community.general/issues/8194, https://github.com/ansible-collections/community.general/pull/8195). +- ssh_config - allow ``accept-new`` as valid value for ``strict_host_key_checking`` (https://github.com/ansible-collections/community.general/pull/8257). + +Deprecated Features +------------------- + +- hipchat callback plugin - the hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020. The callback plugin is therefore deprecated and will be removed from community.general 10.0.0 if nobody provides compelling reasons to still keep it (https://github.com/ansible-collections/community.general/issues/8184, https://github.com/ansible-collections/community.general/pull/8189). + +Bugfixes +-------- + +- aix_filesystem - fix ``_validate_vg`` not passing VG name to ``lsvg_cmd`` (https://github.com/ansible-collections/community.general/issues/8151). +- apt_rpm - when checking whether packages were installed after running ``apt-get -y install <packages>``, only the last package name was checked (https://github.com/ansible-collections/community.general/pull/8263). +- bitwarden_secrets_manager lookup plugin - implements retry with exponential backoff to avoid lookup errors when Bitwardn's API rate limiting is encountered (https://github.com/ansible-collections/community.general/issues/8230, https://github.com/ansible-collections/community.general/pull/8238). +- from_ini filter plugin - disabling interpolation of ``ConfigParser`` to allow converting values with a ``%`` sign (https://github.com/ansible-collections/community.general/issues/8183, https://github.com/ansible-collections/community.general/pull/8185). +- gitlab_issue, gitlab_label, gitlab_milestone - avoid crash during version comparison when the python-gitlab Python module is not installed (https://github.com/ansible-collections/community.general/pull/8158). +- haproxy - fix an issue where HAProxy could get stuck in DRAIN mode when the backend was unreachable (https://github.com/ansible-collections/community.general/issues/8092). +- inventory plugins - add unsafe wrapper to avoid marking strings that do not contain ``{`` or ``}`` as unsafe, to work around a bug in AWX ((https://github.com/ansible-collections/community.general/issues/8212, https://github.com/ansible-collections/community.general/pull/8225). +- ipa - fix get version regex in IPA module_utils (https://github.com/ansible-collections/community.general/pull/8175). +- keycloak_client - add sorted ``defaultClientScopes`` and ``optionalClientScopes`` to normalizations (https://github.com/ansible-collections/community.general/pull/8223). +- keycloak_realm - add normalizations for ``enabledEventTypes`` and ``supportedLocales`` (https://github.com/ansible-collections/community.general/pull/8224). +- puppet - add option ``environment_lang`` to set the environment language encoding. Defaults to lang ``C``. It is recommended to set it to ``C.UTF-8`` or ``en_US.UTF-8`` depending on what is available on your system. (https://github.com/ansible-collections/community.general/issues/8000) +- riak - support ``riak admin`` sub-command in newer Riak KV versions beside the legacy ``riak-admin`` main command (https://github.com/ansible-collections/community.general/pull/8211). +- to_ini filter plugin - disabling interpolation of ``ConfigParser`` to allow converting values with a ``%`` sign (https://github.com/ansible-collections/community.general/issues/8183, https://github.com/ansible-collections/community.general/pull/8185). +- xml - make module work with lxml 5.1.1, which removed some internals that the module was relying on (https://github.com/ansible-collections/community.general/pull/8169). + +New Modules +----------- + +- keycloak_client_rolescope - Allows administration of Keycloak client roles scope to restrict the usage of certain roles to a other specific client applications. + v8.5.0 ====== diff --git a/ansible_collections/community/general/FILES.json b/ansible_collections/community/general/FILES.json index c84d62a88..cbe1d8808 100644 --- a/ansible_collections/community/general/FILES.json +++ b/ansible_collections/community/general/FILES.json @@ -109,7 +109,7 @@ "name": ".azure-pipelines/azure-pipelines.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3a0a3c682a3f4926434b08f73ab1d362629d17db898358d8970c0a78d6195a74", + "chksum_sha256": "370fe873607691433d32772c07c53712f66f3745026442838d9d7ca9af953e48", "format": 1 }, { @@ -165,7 +165,7 @@ "name": ".github/workflows/ansible-test.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "881d33effb01a3b9c94026a3720af4fc2cc3f1c7fa93d48d4ffd2584b9e2db9d", + "chksum_sha256": "ef0a066aa753e5667b6b1d4e2887f13add9b692f6373bbac695bcbe43e73e73f", "format": 1 }, { @@ -193,7 +193,7 @@ "name": ".github/BOTMETA.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "2f3ee06f8b6dad417086bf9cc5e0e7ff4519aa65139373c43e3fbe7cff572f3b", + "chksum_sha256": "5d44cab4aa242ff60409d95adf43c4be0bc3d4eace180b0f04da04bcf2d0e119", "format": 1 }, { @@ -312,7 +312,7 @@ "name": "changelogs/changelog.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "cabe396ff93a6bc5327c16686af1c75f29d1b239ce7ceb6e25930f33690da3da", + "chksum_sha256": "cc2e8e92892d291b60d26135122e9017acb600db7f386a6ccd71d77846436776", "format": 1 }, { @@ -771,6 +771,13 @@ "format": 1 }, { + "name": "docs/docsite/config.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0c5ec9ff76cf4db33b5d3f771419ef50d448e5d510cb7a98fc07dd9ecee69c4e", + "format": 1 + }, + { "name": "docs/docsite/extra-docs.yml", "ftype": "file", "chksum_type": "sha256", @@ -795,7 +802,7 @@ "name": "meta/runtime.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "893ee2f56034e3e8ac5b9a73e689fef80921b692ba6da0e5db2fd330aad2a617", + "chksum_sha256": "7aed4acf161705fece142c2d79ed08c7a5fdf3239644406203cb494b2e74c7a2", "format": 1 }, { @@ -991,7 +998,7 @@ "name": "plugins/callback/hipchat.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "987c2a8b697623f30f2de2d4e70b027096190c0a4a6c99703db36ce0b1b9bb63", + "chksum_sha256": "3b5b9a0b3bf7fa339aff42b2d8e0695fd7c596c252b9773b3373863ff8cab720", "format": 1 }, { @@ -1012,7 +1019,7 @@ "name": "plugins/callback/loganalytics.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "41ee07d1e548f4d03475f0001e084645124d77d850e031c17d78d81b5bfc2e31", + "chksum_sha256": "33b365b6aa6ede1129ab535c212d87828c933ecb1ba6aa9875e5e4e449dcd397", "format": 1 }, { @@ -1033,7 +1040,7 @@ "name": "plugins/callback/logstash.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "411199e9f8925c20601b623056b6288f02000815efcb0c25254e69051a826aee", + "chksum_sha256": "cd3dc96b5ad3fbd489739967e1ca022cacd1e54d760d78ed7e777ed35387895d", "format": 1 }, { @@ -1089,14 +1096,14 @@ "name": "plugins/callback/splunk.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "552c6c10b5ceed03a4728c8dee70d6211a1b208095c4ff4748ebf8c2110f4bf9", + "chksum_sha256": "e275389d7786a2a693ea6b313686dee7da6c1b7afc3c25e4ed7538efca6c157e", "format": 1 }, { "name": "plugins/callback/sumologic.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3265907f5d62128170d48eefda9901eb1f3d6bc36d9f93e5f900403850724d73", + "chksum_sha256": "de3553075bbf8bb7bc9c91efcea1ad418ea0a2e908d47cc90f975ba3889b054e", "format": 1 }, { @@ -1537,7 +1544,7 @@ "name": "plugins/filter/from_ini.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "aa388a5cdd894e1c3d1e66580dbd2947abc2a60b8a30b05fee1a5fb8bb8cc642", + "chksum_sha256": "eeb40072eda1075f377f210310a0a74f3ab81618575b1bfec3e9bc6ca42896c5", "format": 1 }, { @@ -1656,7 +1663,7 @@ "name": "plugins/filter/to_ini.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "d1012ffb9f4d8626c948dc52bf36a4c42287e1deaf65eca1ecf954170d61fdc0", + "chksum_sha256": "40e4926d88132be0f03c6857df46789931a450a8107cf248bee01c9725310d7b", "format": 1 }, { @@ -1733,91 +1740,91 @@ "name": "plugins/inventory/cobbler.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "2a6cd6114bae928361af7ec60e99a4e85f9ed3b437c943bc60e23f4c21401c64", + "chksum_sha256": "f061ed54c78be6b39aed094d77480d8a90148a5b5cd92fd3167f23482d93226d", "format": 1 }, { "name": "plugins/inventory/gitlab_runners.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1fd2de379dbf83ba5fddafeb3fecb693ef5d35157ef5a8f0e9718d984ca4b9f4", + "chksum_sha256": "2c4e173a50c5838eb3b1a7b0525a60eeddbc8aad3a31f53af6087a53323f68a3", "format": 1 }, { "name": "plugins/inventory/icinga2.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "f9bdc281508bc222fd6e1f19c34575cd9dc5056ca3ca1d7a8f4481de1d24cf27", + "chksum_sha256": "9a9bcca6fa0b8fb673ae533d8a5455317a131294ec69ac292512a07ba5047e77", "format": 1 }, { "name": "plugins/inventory/linode.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "d2375902a364f0c8b8cc9d6f88512be7b4371d6dbfcda4e5707df404b5336ae2", + "chksum_sha256": "fe2339ec5b8572f3e9c9c1414c961068ebf1e968ba255110d3fc882740ba2015", "format": 1 }, { "name": "plugins/inventory/lxd.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "6f501b691589f29ad2cf5954d98e2576e2944f88f9d23d74cece0f0850c77888", + "chksum_sha256": "1aee550b5e6222dadc8ed4a6950a4b471f40892627d92badba12dc3208163585", "format": 1 }, { "name": "plugins/inventory/nmap.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "76f3c42cba7bb30fa841ba9193a71c9ce857303c9d83d9d8d1ff04dfc4f1c726", + "chksum_sha256": "81d1bc141fc09acddf6da7500ea3834c252b097aacaa13dd38205b04cbe2250b", "format": 1 }, { "name": "plugins/inventory/online.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "533c5edf6b17e4b25628aad13fc5d7aba7c9ea1ae86e12d091c50aef12a8f775", + "chksum_sha256": "f6af33b129af56d23bc8bf66072f656e336b0cf7fb5dab958c992f2392fcdf9d", "format": 1 }, { "name": "plugins/inventory/opennebula.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "76f28fa3696a031e014d66ea1617fb5d98319dc6809dac3218f7a4d800ceff45", + "chksum_sha256": "954bb22612557fbe3be3510d3ebc7e230164cb3dcb689345a287270b85234970", "format": 1 }, { "name": "plugins/inventory/proxmox.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "01313ffb0af966b9a4e2b6f95c1497359a98ae5847be4b9c324aae3c5e412f9c", + "chksum_sha256": "a4ceafbac13b19794a5ee15b778407fc0e7879ec56dccc13c9affa9008f9a81c", "format": 1 }, { "name": "plugins/inventory/scaleway.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5663c4b332dda4720df71285671ef999c8eaa3e4e3335bca1e8e376e65585c46", + "chksum_sha256": "2a8adf3084473bf8a7f7f6983ecbbac1a7f18ccfe073b018ea939d933fefa095", "format": 1 }, { "name": "plugins/inventory/stackpath_compute.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "38003a1f85c8781d73ae5352d461c87c5a66db67810aa3113d5f15b1e07d3e06", + "chksum_sha256": "5ee8154d186e72b3cbbd0b4ce8232f18b06e028010779a6d2ea3d494aed2774f", "format": 1 }, { "name": "plugins/inventory/virtualbox.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0547e7940ec2940cb9ef4bd07b321b45a458792e6b2d572c2abceb72c4e78d26", + "chksum_sha256": "40f8697615b3e767265ef7e0c24735fdb4d6bc8d354d6bb2296c12f4e7a99a5b", "format": 1 }, { "name": "plugins/inventory/xen_orchestra.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "d9b079b0215b6775490192fffae78fe46f088675348ae4129d7825aeda59bb0a", + "chksum_sha256": "4622368d48743bb29d2d42c70956bc953265c062a2a9e33d03d7635bbd0bcb0b", "format": 1 }, { @@ -1831,14 +1838,14 @@ "name": "plugins/lookup/bitwarden.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5f0e069d8c299d2bb3087726049d7df8d03a4e545ba3d8df9fd429ed6767125d", + "chksum_sha256": "e894798a425bfd34301056e5cdf03ce61d18c82d03db8c95dcd7778d15707923", "format": 1 }, { "name": "plugins/lookup/bitwarden_secrets_manager.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5453a43746aad1262ded5a08d8ebad62f32f26ca22d1dd873320b3ba042345b3", + "chksum_sha256": "0e1034a5fcf7fa8646b9b606efd34b709a99b64dcb418bc5d3d4f2ab8edc451a", "format": 1 }, { @@ -2013,7 +2020,7 @@ "name": "plugins/lookup/passwordstore.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "bf39d1661bdd94a94996a594cb00c55adc3bfa44e0041cf9c859f4125442565e", + "chksum_sha256": "37381b1f5be5edd85f5d3fd29ab95c05b728b11adb4f236c2face2f3d9f4af53", "format": 1 }, { @@ -2090,7 +2097,7 @@ "name": "plugins/module_utils/identity/keycloak/keycloak.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "b3f1080b3b46444e8eebd03321d1bba8a599b845b8d8bbc846ae2225ee2a33fe", + "chksum_sha256": "05a7911218ce6acf0cdcb2d0f0b251c07aaa6fa10ebbd45fa6bdaa7157ef0376", "format": 1 }, { @@ -2346,6 +2353,13 @@ "format": 1 }, { + "name": "plugins/module_utils/datetime.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "49ede572b9ee1cd97b75cae58ea5ed573a91978ffc1853e6c1b6be2c78b28075", + "format": 1 + }, + { "name": "plugins/module_utils/deps.py", "ftype": "file", "chksum_type": "sha256", @@ -2384,7 +2398,7 @@ "name": "plugins/module_utils/gitlab.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "11504e0b1cc93424e0ab0c496737eb412537558268624b36ab7860ca0a89e6ec", + "chksum_sha256": "f0edcbd8ae4a68f9c003e48360e3c4247768a801e34580ed035d04f15d6857a4", "format": 1 }, { @@ -2426,7 +2440,7 @@ "name": "plugins/module_utils/ipa.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "2fbe8b6da9a2d3be12640df88c4ce8907f52706e0175228a8f2e03aacf0f0f60", + "chksum_sha256": "e2739a328e34cbfea207e502fb2b1a6e95a6062b5dd64f81cee00b5d7eec8bc3", "format": 1 }, { @@ -2552,7 +2566,7 @@ "name": "plugins/module_utils/puppet.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3e1957ab2d41bb194a5f100737467e054580c8e48b4997757c484b99fc02ab27", + "chksum_sha256": "d0c7e2106d6247342b11f2de422240b828a122acd98d9c4ffe5b91899b8893ff", "format": 1 }, { @@ -2573,7 +2587,7 @@ "name": "plugins/module_utils/redfish_utils.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "bc9400ba787e5570d9344c685596e7c3e04013ce32215c3874d168f2af57d980", + "chksum_sha256": "ce4679e15df51fc1a102f5f6f8ef1cb8f8153cfb5a896786c5531829d76ce0e2", "format": 1 }, { @@ -2608,7 +2622,7 @@ "name": "plugins/module_utils/scaleway.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "bf3a6babbac970f45b6c9473704684b0980330f7e8cfc71e45513e9fa22aea35", + "chksum_sha256": "5a86772778ddfbaca5cc50138f33ac5ed259da4f365925d1f7032b3f0d65beff", "format": 1 }, { @@ -2713,7 +2727,7 @@ "name": "plugins/modules/aix_filesystem.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "76c6c1b52220f8808fd8c2c4a06a9a0e9be8cff0f8abc5336cdc49a5d9ae52f3", + "chksum_sha256": "e8e4d96b11af1dcdcf0cdd5ff1eee2efec0619ee6639563fa93b9dd3e0046647", "format": 1 }, { @@ -2804,7 +2818,7 @@ "name": "plugins/modules/apt_rpm.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0b60fc7bf785127673dd9eac1396da1d3cb759efe84e9f30b98530b82e5980c9", + "chksum_sha256": "f0570e79b53c66ebfb69e175ff0edb87f3ea9c10ce4efc791ed0720644459350", "format": 1 }, { @@ -3056,14 +3070,14 @@ "name": "plugins/modules/cobbler_sync.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "69bc8b3cd61a7d22073eaa6944e645ffd8133c7ed858b5cd130fc343d074b5ea", + "chksum_sha256": "8f888f082b8266fbe3eb76212b06fdcb8d3e95cadccb1cd2a6d7e4138a5e0293", "format": 1 }, { "name": "plugins/modules/cobbler_system.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "de819b4c8494a9aa92e04609c29027c3357fc682aa4903f4fc3569289b088b02", + "chksum_sha256": "0e12cad44f5adf1f3c33242bce11e90b6b94f63159f83df3352dbbaee775fb53", "format": 1 }, { @@ -3336,14 +3350,14 @@ "name": "plugins/modules/filesystem.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5c68cc92970f2e756d3f153b6724d53413819f13bd87967fba8c723779f0fbf5", + "chksum_sha256": "ba0eb413952ad67723b8d06b82adfe05ee7fe63274d0b78bbb1df246a272df0f", "format": 1 }, { "name": "plugins/modules/flatpak.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "6a1ff04ef312b0acec7ec57c654bc462201dfd026f73bab41dd585ad726d00f4", + "chksum_sha256": "083768938533021fb5ad0e92ea8b7a1cf859c68a365b0d21c16df6c53905f728", "format": 1 }, { @@ -3427,7 +3441,7 @@ "name": "plugins/modules/github_key.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "79de6029d98cea3b3b2c2591e8c59168de2cc3395ef397c58b89dac9fc0df6a2", + "chksum_sha256": "5981c162d708eaca4be13a165ae2cd89492a5a2f1d50df1e87963ed0781a0cc6", "format": 1 }, { @@ -3518,14 +3532,14 @@ "name": "plugins/modules/gitlab_issue.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c862d68cc3e2201f3bc3af96f67bf17a3780786c7f76e59544a3386a88c5c468", + "chksum_sha256": "0c60e0a22b6e72cc1983ac526ddb856e314e2abe24b4007e278d8e242bef39b8", "format": 1 }, { "name": "plugins/modules/gitlab_label.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "25fff68c8d2541d8ef914428987a9a8141d66938694fa56693fc1309215361c3", + "chksum_sha256": "85d923d3db2f0381470a430df78c8bb3edeb91f20e70108904b800cae04f9bce", "format": 1 }, { @@ -3539,7 +3553,7 @@ "name": "plugins/modules/gitlab_milestone.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3c6d14ab0a6c17a49e557946de3fbf80aa7dd07d9345a9aab2b1a11769978f80", + "chksum_sha256": "4d869613511d223e21b3173aa16b7131cb4058badd6645bc553832f5a2e9355c", "format": 1 }, { @@ -3616,7 +3630,7 @@ "name": "plugins/modules/haproxy.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "816d15aa1df4f09211a1e2e194609db779f32a9c1ae7f12c37fc52895c0e1a1a", + "chksum_sha256": "3c574e4b671db22e9176bbfe1a772e7de68dacb3f67d555f521fdfe5c48473bf", "format": 1 }, { @@ -3889,7 +3903,7 @@ "name": "plugins/modules/imc_rest.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3b212b98cbc6821f9f079ad0d28596d375f1da710bc9d8110bc6a6f754a77ede", + "chksum_sha256": "eb18b5c19057c4aed0548b9cbf0277fd4b666d5aab2638ca03ca1488a542a967", "format": 1 }, { @@ -3945,7 +3959,7 @@ "name": "plugins/modules/ini_file.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "37638878ed3b6e5c2860382a8b2940eb20906b12da727c67c4c1e04f38e68255", + "chksum_sha256": "c1fbd2a5aa3de2746f9b894b312e7c27542c98dd3cd8cbb7fd5ed22a4c92d56f", "format": 1 }, { @@ -4183,7 +4197,7 @@ "name": "plugins/modules/java_cert.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "f325def87c1b6ab8e1e1acf9829b9e0f7b22c773a35e5a9eee2116f1b4f37d9b", + "chksum_sha256": "dc5ddeb9830187191cf78572ee079e8abf7ca8dda7a0d34a710d3c4079db6730", "format": 1 }, { @@ -4309,7 +4323,7 @@ "name": "plugins/modules/keycloak_client.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "d1c5ee5cfeaaa3ea1653e2a3a43c642e95d6e837269a8f5bb967ae9f74e26139", + "chksum_sha256": "9065934124dc12b01077cd9c2caea2640c2f76a5d00541af2a1b1500cbbcba57", "format": 1 }, { @@ -4320,10 +4334,17 @@ "format": 1 }, { + "name": "plugins/modules/keycloak_client_rolescope.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24ddf67b981ad224debe4cc8eff03a9afb8924e9e3ab8da474569e4cb73889f1", + "format": 1 + }, + { "name": "plugins/modules/keycloak_clientscope.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "944a9d01857144ae760d704e124b4dd3da31121358efd17058bf4024e3e111d5", + "chksum_sha256": "c712e8e6d6b9d27297f7fbed371d7338aea6c92c6fc940fea1cf9e8c4b897c71", "format": 1 }, { @@ -4351,7 +4372,7 @@ "name": "plugins/modules/keycloak_clienttemplate.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "51ae2bb14c80773bc7dbfe159a9fda0337609ebea371904f0ac75010ce64e689", + "chksum_sha256": "84508ab4b96dbb81f9fa4cd94c4babb45ad01065a434a62d96d9dca7518d1609", "format": 1 }, { @@ -4379,7 +4400,7 @@ "name": "plugins/modules/keycloak_realm.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ded26fe5a5bd4422a8f91c31a266d502c23653087128af2753347601d4178391", + "chksum_sha256": "9ec1ddefe1b7badaf842680f2799f343c6e4d1c7cb8aec769a002e7383b14e6f", "format": 1 }, { @@ -4610,7 +4631,7 @@ "name": "plugins/modules/lxd_container.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "232e7553c192bd73a35bd0689e9d47a6eb29ca60690d4da6e763cf81dc079ee9", + "chksum_sha256": "3421f907d434f83dd6a346507a607c5ad7cc0b727673b74badf5003841974b4d", "format": 1 }, { @@ -4876,7 +4897,7 @@ "name": "plugins/modules/nmcli.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "04fef80fc4f380de8caa7d9b81a40b1ad6e224b9a34b7d384d82f0982ab0dad6", + "chksum_sha256": "963b12d10e1bd6f4ec0bdc01aa6527074381b6f6ba7ab57fe0aa8307f324b033", "format": 1 }, { @@ -5212,7 +5233,7 @@ "name": "plugins/modules/osx_defaults.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "25083c7e2f8c702fd3c2739fe6527c69f3976e9bcef0263c3f8bdfd4a28746a0", + "chksum_sha256": "bc7858ccd08df77b492c2c54c38dc67b75706abda671e7bc619d9933cf1ccd8e", "format": 1 }, { @@ -5303,7 +5324,7 @@ "name": "plugins/modules/pagerduty.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "6da5163291f420d74945b2723ed5bf0acf0e43f9280793c17554143bbef6cb75", + "chksum_sha256": "515d603cd19dcd4d102ab907148930c08383906eede2029c5e8605642518c709", "format": 1 }, { @@ -5317,7 +5338,7 @@ "name": "plugins/modules/pagerduty_change.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "13f3a20302ece4ae73d94292180ea986b8d24cf05c5c5c587481dd3fc55cdd2c", + "chksum_sha256": "26d9bd41d8fd2c404b93fd60812e0f0c5e9776afa1d5387e0b4fe03a581cf3fd", "format": 1 }, { @@ -5443,7 +5464,7 @@ "name": "plugins/modules/portage.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "70474d22e85a3d8d191e870dce4ef4c4e0804ded42e6cb258454a313ef2f4155", + "chksum_sha256": "5c207e389af0f727d74e2402c099eaafe29afba44e9bd1fd91da053714fa38e3", "format": 1 }, { @@ -5646,7 +5667,7 @@ "name": "plugins/modules/puppet.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "853adf09fa19769befaa28faf6c986ecfe5d85890a4db71fa0a0e64f08f9ec42", + "chksum_sha256": "2e8fa36104ad52a85bcbfffadfe873b1dadf91eb5e9f5e1fe843e712ec972708", "format": 1 }, { @@ -5863,7 +5884,7 @@ "name": "plugins/modules/redfish_command.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e6875f2566585e7029e4d21f8f0dab3532c2032e95d907a248c71a4c15159f74", + "chksum_sha256": "38a449fe1a3ce5e62028e851bece4137f2f919f2ab6869f0fc23dc09dfd735f2", "format": 1 }, { @@ -5961,7 +5982,7 @@ "name": "plugins/modules/riak.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c657448de8067ae71413bf74587e2df9dedbd923b9f13ba97bc977b14b2632ed", + "chksum_sha256": "8011c6e8cd40eedbbeb157ad8cf9f069a98d59fd91cefb570dddc1f48af2ac2f", "format": 1 }, { @@ -6031,7 +6052,7 @@ "name": "plugins/modules/scaleway_compute.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "325e4bdbe81c4c280e847a290cd5b972d37ce11bb9cef0e25b152e17f4284f67", + "chksum_sha256": "cace7cc6514792ce6a84aa709fabcbc15989d722df346c60827863c6741f845d", "format": 1 }, { @@ -6087,7 +6108,7 @@ "name": "plugins/modules/scaleway_database_backup.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c4f6d4e9fffcf2964debd7d40703841b7c841bd4f3335155698b548ab7f42ad1", + "chksum_sha256": "04512a57c17deadde4b7e2eb233185d8dad896b0d75348e40f6185f2b64ef6ca", "format": 1 }, { @@ -6143,7 +6164,7 @@ "name": "plugins/modules/scaleway_lb.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c405f1d1f0e8763657353c861b5d99dbb7468c355f4723abbdba3ebdccbe33ac", + "chksum_sha256": "541e2627c7a3d18554053f9da1002b73f3d33baf61d6d381c54080f8e64752ed", "format": 1 }, { @@ -6409,7 +6430,7 @@ "name": "plugins/modules/ssh_config.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "233e53db7d3b7cdd61178d3c7b5fccb2ca8e8afff7bd3113ee116e72b7b3ecd2", + "chksum_sha256": "ec6d57292e4a58ac53f919a274a66667d570e9143016d4b6a273e2b148059ccc", "format": 1 }, { @@ -6437,7 +6458,7 @@ "name": "plugins/modules/statusio_maintenance.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "afd8833c6c9896e742a7492aca76799e42d2a2a819efc511caade8a2d2091746", + "chksum_sha256": "7b1a8122e644afafe445900e99f5a6452133fa927e3b18e5b56f19ea3d56634b", "format": 1 }, { @@ -6899,7 +6920,7 @@ "name": "plugins/modules/xml.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "801820433ca4c1410b22055ae52a7ab438787a232462e0d8125d43b180edd870", + "chksum_sha256": "a25e92ac7a5d3b78282387f8f00d08fd55b29afb4f8e6b886b86309d68f81a8e", "format": 1 }, { @@ -6966,6 +6987,20 @@ "format": 1 }, { + "name": "plugins/plugin_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/plugin_utils/unsafe.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93ccc2e18634405c7ff21a91a1df4f17261ac24b0f41bf3c46dbec2f254538c3", + "format": 1 + }, + { "name": "plugins/test", "ftype": "dir", "chksum_type": null, @@ -8950,7 +8985,7 @@ "name": "tests/integration/targets/filesystem/defaults/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "08185326e7b809ec8248cc5bf8276368f27b14a8cb0131cc2ae95676871ff8cc", + "chksum_sha256": "18cbba57e5defc719b0fe8a22a5de6bd5cc5d13ebb813adfd94379ba02e22f50", "format": 1 }, { @@ -8999,7 +9034,7 @@ "name": "tests/integration/targets/filesystem/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "6a587d4489a54a86e204cbef30a808fe543fef4be6e9e9f475b22287fd2d4f58", + "chksum_sha256": "57a0f04cc772d3e0b3c958b070d15fce52c780c6caeeb908b0485406de97fa74", "format": 1 }, { @@ -9041,7 +9076,7 @@ "name": "tests/integration/targets/filesystem/tasks/setup.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "abcc4f8bf8d6df58581ff4f19e47a6afed3bcd456a7f04a66687069389c6d8a3", + "chksum_sha256": "4fb6cd4374447a8e54ea9b6cc924806b6e91cb2fdbacde34616e6c4c4113d93d", "format": 1 }, { @@ -9216,7 +9251,7 @@ "name": "tests/integration/targets/filter_from_ini/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "a07f8a902771d4130fb6735fb0acff4a9f8aead0f123cb43a539406e54184a59", + "chksum_sha256": "1b8aaecd2d2f86721c17c2981cf6ab0170d1df72540f8dda076973e8fc8999ff", "format": 1 }, { @@ -9643,7 +9678,7 @@ "name": "tests/integration/targets/filter_to_ini/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7e22a177f1018cb3fb7d33f211b3d67febfdf94c4ad3a473a5b4d94ce608e696", + "chksum_sha256": "64a9ad21479d050a38d966ace64d5d43574d3dbd065cff2a02655c5eff398e65", "format": 1 }, { @@ -9776,7 +9811,7 @@ "name": "tests/integration/targets/flatpak/tasks/check_mode.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "daec12df262fbf5c01327d4ce20cb2c2c92670d222ba6164ea2b205c1e1b6166", + "chksum_sha256": "70231a939f8cd69e757fb8b5cc1e1ccf92b9a8b98359945d3ae8386a05c52152", "format": 1 }, { @@ -9797,7 +9832,7 @@ "name": "tests/integration/targets/flatpak/tasks/test.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "07cb037e4f5c2d08971d4af2b80d2cd6a03de40e4b41ff02779e84d002131fcd", + "chksum_sha256": "2f212b96c1d3bf7577b2d8b1cde180aac1e99f541c4d01090fbd36d43678df1d", "format": 1 }, { @@ -11957,10 +11992,17 @@ "format": 1 }, { + "name": "tests/integration/targets/ini_file/tasks/tests/08-section.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1eb82965442fa3fe06af0bdad8e66cf7d86e182b1d3b42ce613d116aec5b2df1", + "format": 1 + }, + { "name": "tests/integration/targets/ini_file/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3747adf416c4b3fa837c686860ec828985edd6a3b7e0c5993dd9da892085e7d6", + "chksum_sha256": "4e85805c4a6a145f06ef0b95dfd764becb92080deffd0be4ae5a8315ef99a60c", "format": 1 }, { @@ -13140,6 +13182,55 @@ "format": 1 }, { + "name": "tests/integration/targets/keycloak_client_rolescope", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/keycloak_client_rolescope/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/keycloak_client_rolescope/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "91f554dc3a0d7573b135d56866e499e09265fb5f3a2e7ccafe293c427f251634", + "format": 1 + }, + { + "name": "tests/integration/targets/keycloak_client_rolescope/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/keycloak_client_rolescope/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "69645aad3b700fb61a727383e30d7e8bbaf0a56a36e60d9013508e5b4a1d0698", + "format": 1 + }, + { + "name": "tests/integration/targets/keycloak_client_rolescope/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f643953e308b8c565cfa4514f126995c37225d1dd9b7dc6b72f1cb00de90c3aa", + "format": 1 + }, + { + "name": "tests/integration/targets/keycloak_client_rolescope/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "efa136c759c51db61ae085d265a6d6402c567e9bb00875292d45dbb00c1ed892", + "format": 1 + }, + { "name": "tests/integration/targets/keycloak_clientscope_type", "ftype": "dir", "chksum_type": null, @@ -14543,7 +14634,7 @@ "name": "tests/integration/targets/lookup_lmdb_kv/test.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1cedf373477c36de601ffdc2d8335d4536fa1d4927376c149a1c7c2a3fe46109", + "chksum_sha256": "f5a7ee1f47774aa767f2184faad85470c6ebe98b40b1db202f5f1acbdc72e2ea", "format": 1 }, { @@ -22408,6 +22499,20 @@ "format": 1 }, { + "name": "tests/sanity/ignore-2.18.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4aa6d180dd919a19b80a38b282923231f636556e4bf343b2cf36527d999b2124", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.18.txt.license", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6eb915239f9f35407fa68fdc41ed6522f1fdcce11badbdcd6057548023179ac1", + "format": 1 + }, + { "name": "tests/unit", "ftype": "dir", "chksum_type": null, @@ -22600,7 +22705,7 @@ "name": "tests/unit/plugins/callback/test_loganalytics.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "45af49a11f9253655c2ae1b0ddde646ec20f4fbce8a5e8bddf468c4bb438a70c", + "chksum_sha256": "558bb03fa8ba17c60eb1afb30bb224f4b1b4c8db47a0f078a779304e3fdff713", "format": 1 }, { @@ -22614,7 +22719,7 @@ "name": "tests/unit/plugins/callback/test_splunk.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "561373bea8138ef0cc8901b4d9ce1f80ff841f3cec112a4449b89ecc49a235fd", + "chksum_sha256": "e0acc6f91db31e76594b6ec362752373167d4417033053ccf6fa6671616dc483", "format": 1 }, { @@ -22887,7 +22992,7 @@ "name": "tests/unit/plugins/lookup/test_bitwarden.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "bc4558d979772df76cea11362731e59a669327b817f91be20e43f5619d1a1a22", + "chksum_sha256": "3acd0fbfd344baa81cbc868642f74e3c276164877c83cb073064459c9f873fdc", "format": 1 }, { @@ -27640,7 +27745,7 @@ "name": "CHANGELOG.md", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8ac8a8d20d770c47d3d6d743711f948dc353bab92ee076a1a915ea8fbb939110", + "chksum_sha256": "aa9310c1dd52b555a85d449e50a3ba1bc9de9e5a3474c66e156488463be1cc36", "format": 1 }, { @@ -27654,7 +27759,7 @@ "name": "CHANGELOG.rst", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1754aca18ee20d425f062cab756afaf70000861eb78cd1c885e2387048c87d56", + "chksum_sha256": "7192b128389810ee514466d24b34c430fd3e044eafc1db4c3b092a3a630d9985", "format": 1 }, { @@ -27682,7 +27787,7 @@ "name": "README.md", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "af93da2f8ac939f7b71c6ed23caeb602a0601924282f8de07616270113c99e16", + "chksum_sha256": "980347a93d90e0b8f8507f59be3be73f0f3026521be645861f2b30880c7ee9fe", "format": 1 }, { diff --git a/ansible_collections/community/general/MANIFEST.json b/ansible_collections/community/general/MANIFEST.json index 715cd85ea..4d700a4c7 100644 --- a/ansible_collections/community/general/MANIFEST.json +++ b/ansible_collections/community/general/MANIFEST.json @@ -2,7 +2,7 @@ "collection_info": { "namespace": "community", "name": "general", - "version": "8.5.0", + "version": "8.6.0", "authors": [ "Ansible (https://github.com/ansible)" ], @@ -23,7 +23,7 @@ "name": "FILES.json", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "a5ae0318b05662e01298f7e11a607b9d674196586dfa5665caccb08b58fc5f69", + "chksum_sha256": "42be361f601f0df607b62621323c3fb70f485cd57cd2d7eecca85d6590836b01", "format": 1 }, "format": 1 diff --git a/ansible_collections/community/general/README.md b/ansible_collections/community/general/README.md index a63ae3201..162cc06b0 100644 --- a/ansible_collections/community/general/README.md +++ b/ansible_collections/community/general/README.md @@ -24,7 +24,7 @@ If you encounter abusive behavior violating the [Ansible Code of Conduct](https: ## Tested with Ansible -Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16 releases and the current development version of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases. +Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, ansible-core 2.17 releases and the current development version of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases. ## External requirements diff --git a/ansible_collections/community/general/changelogs/changelog.yaml b/ansible_collections/community/general/changelogs/changelog.yaml index 411df6ed2..b45d41276 100644 --- a/ansible_collections/community/general/changelogs/changelog.yaml +++ b/ansible_collections/community/general/changelogs/changelog.yaml @@ -1320,3 +1320,115 @@ releases: name: usb_facts namespace: '' release_date: '2024-03-25' + 8.6.0: + changes: + bugfixes: + - aix_filesystem - fix ``_validate_vg`` not passing VG name to ``lsvg_cmd`` + (https://github.com/ansible-collections/community.general/issues/8151). + - apt_rpm - when checking whether packages were installed after running ``apt-get + -y install <packages>``, only the last package name was checked (https://github.com/ansible-collections/community.general/pull/8263). + - bitwarden_secrets_manager lookup plugin - implements retry with exponential + backoff to avoid lookup errors when Bitwardn's API rate limiting is encountered + (https://github.com/ansible-collections/community.general/issues/8230, https://github.com/ansible-collections/community.general/pull/8238). + - from_ini filter plugin - disabling interpolation of ``ConfigParser`` to allow + converting values with a ``%`` sign (https://github.com/ansible-collections/community.general/issues/8183, + https://github.com/ansible-collections/community.general/pull/8185). + - gitlab_issue, gitlab_label, gitlab_milestone - avoid crash during version + comparison when the python-gitlab Python module is not installed (https://github.com/ansible-collections/community.general/pull/8158). + - haproxy - fix an issue where HAProxy could get stuck in DRAIN mode when the + backend was unreachable (https://github.com/ansible-collections/community.general/issues/8092). + - inventory plugins - add unsafe wrapper to avoid marking strings that do not + contain ``{`` or ``}`` as unsafe, to work around a bug in AWX ((https://github.com/ansible-collections/community.general/issues/8212, + https://github.com/ansible-collections/community.general/pull/8225). + - ipa - fix get version regex in IPA module_utils (https://github.com/ansible-collections/community.general/pull/8175). + - keycloak_client - add sorted ``defaultClientScopes`` and ``optionalClientScopes`` + to normalizations (https://github.com/ansible-collections/community.general/pull/8223). + - keycloak_realm - add normalizations for ``enabledEventTypes`` and ``supportedLocales`` + (https://github.com/ansible-collections/community.general/pull/8224). + - puppet - add option ``environment_lang`` to set the environment language encoding. + Defaults to lang ``C``. It is recommended to set it to ``C.UTF-8`` or ``en_US.UTF-8`` + depending on what is available on your system. (https://github.com/ansible-collections/community.general/issues/8000) + - riak - support ``riak admin`` sub-command in newer Riak KV versions beside + the legacy ``riak-admin`` main command (https://github.com/ansible-collections/community.general/pull/8211). + - to_ini filter plugin - disabling interpolation of ``ConfigParser`` to allow + converting values with a ``%`` sign (https://github.com/ansible-collections/community.general/issues/8183, + https://github.com/ansible-collections/community.general/pull/8185). + - xml - make module work with lxml 5.1.1, which removed some internals that + the module was relying on (https://github.com/ansible-collections/community.general/pull/8169). + deprecated_features: + - hipchat callback plugin - the hipchat service has been discontinued and the + self-hosted variant has been End of Life since 2020. The callback plugin is + therefore deprecated and will be removed from community.general 10.0.0 if + nobody provides compelling reasons to still keep it (https://github.com/ansible-collections/community.general/issues/8184, + https://github.com/ansible-collections/community.general/pull/8189). + minor_changes: + - Use offset-aware ``datetime.datetime`` objects (with timezone UTC) instead + of offset-naive UTC timestamps, which are deprecated in Python 3.12 (https://github.com/ansible-collections/community.general/pull/8222). + - 'apt_rpm - add new states ``latest`` and ``present_not_latest``. The value + ``latest`` is equivalent to the current behavior of ``present``, which will + upgrade a package if a newer version exists. ``present_not_latest`` does what + most users would expect ``present`` to do: it does not upgrade if the package + is already installed. The current behavior of ``present`` will be deprecated + in a later version, and eventually changed to that of ``present_not_latest`` + (https://github.com/ansible-collections/community.general/issues/8217, https://github.com/ansible-collections/community.general/pull/8247).' + - bitwarden lookup plugin - add support to filter by organization ID (https://github.com/ansible-collections/community.general/pull/8188). + - filesystem - add bcachefs support (https://github.com/ansible-collections/community.general/pull/8126). + - ini_file - add an optional parameter ``section_has_values``. If the target + ini file contains more than one ``section``, use ``section_has_values`` to + specify which one should be updated (https://github.com/ansible-collections/community.general/pull/7505). + - java_cert - add ``cert_content`` argument (https://github.com/ansible-collections/community.general/pull/8153). + - keycloak_client, keycloak_clientscope, keycloak_clienttemplate - added ``docker-v2`` + protocol support, enhancing alignment with Keycloak's protocol options (https://github.com/ansible-collections/community.general/issues/8215, + https://github.com/ansible-collections/community.general/pull/8216). + - nmcli - adds OpenvSwitch support with new ``type`` values ``ovs-port``, ``ovs-interface``, + and ``ovs-bridge``, and new ``slave_type`` value ``ovs-port`` (https://github.com/ansible-collections/community.general/pull/8154). + - osx_defaults - add option ``check_types`` to enable changing the type of existing + defaults on the fly (https://github.com/ansible-collections/community.general/pull/8173). + - passwordstore lookup - add ``missing_subkey`` parameter defining the behavior + of the lookup when a passwordstore subkey is missing (https://github.com/ansible-collections/community.general/pull/8166). + - portage - adds the possibility to explicitely tell portage to write packages + to world file (https://github.com/ansible-collections/community.general/issues/6226, + https://github.com/ansible-collections/community.general/pull/8236). + - redfish_command - add command ``ResetToDefaults`` to reset manager to default + state (https://github.com/ansible-collections/community.general/issues/8163). + - redfish_info - add boolean return value ``MultipartHttpPush`` to ``GetFirmwareUpdateCapabilities`` + (https://github.com/ansible-collections/community.general/issues/8194, https://github.com/ansible-collections/community.general/pull/8195). + - ssh_config - allow ``accept-new`` as valid value for ``strict_host_key_checking`` + (https://github.com/ansible-collections/community.general/pull/8257). + release_summary: Regular bugfix and features release. + fragments: + - 7505-ini_file-section_has.yml + - 8.6.0.yml + - 8100-haproxy-drain-fails-on-down-backend.yml + - 8126-filesystem-bcachefs-support.yaml + - 8151-fix-lsvg_cmd-failed.yml + - 8153-java_cert-add-cert_content-arg.yml + - 8154-add-ovs-commands-to-nmcli-module.yml + - 8158-gitlab-version-check.yml + - 8163-redfish-implementing-reset-to-defaults.yml + - 8166-password-store-lookup-missing-subkey.yml + - 8169-lxml.yml + - 8173-osx_defaults-check_type.yml + - 8175-get_ipa_version_regex.yml + - 8183-from_ini_to_ini.yml + - 8188-bitwarden-add-organization_id.yml + - 8194-redfish-add-multipart-to-capabilities.yml + - 8211-riak-admin-sub-command-support.yml + - 8215-add-docker-v2-protocol.yml + - 8222-datetime.yml + - 8223-keycloak_client-additional-normalizations.yaml + - 8224-keycloak_realm-add-normalizations.yaml + - 8225-unsafe.yml + - 8236-portage-select-feature.yml + - 8238-bitwarden-secrets-manager-rate-limit-retry-with-backoff.yml + - 8247-apt_rpm-latest.yml + - 8257-ssh-config-hostkey-support-accept-new.yaml + - 8263-apt_rpm-install-check.yml + - hipchat.yml + - puppet_lang_force.yml + modules: + - description: Allows administration of Keycloak client roles scope to restrict + the usage of certain roles to a other specific client applications. + name: keycloak_client_rolescope + namespace: '' + release_date: '2024-04-22' diff --git a/ansible_collections/community/general/docs/docsite/config.yml b/ansible_collections/community/general/docs/docsite/config.yml new file mode 100644 index 000000000..1d6cf8554 --- /dev/null +++ b/ansible_collections/community/general/docs/docsite/config.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +changelog: + write_changelog: true diff --git a/ansible_collections/community/general/meta/runtime.yml b/ansible_collections/community/general/meta/runtime.yml index 4fe80ca25..a9354aab3 100644 --- a/ansible_collections/community/general/meta/runtime.yml +++ b/ansible_collections/community/general/meta/runtime.yml @@ -13,6 +13,28 @@ action_groups: - consul_session - consul_token plugin_routing: + callback: + actionable: + tombstone: + removal_version: 2.0.0 + warning_text: Use the 'default' callback plugin with 'display_skipped_hosts + = no' and 'display_ok_hosts = no' options. + full_skip: + tombstone: + removal_version: 2.0.0 + warning_text: Use the 'default' callback plugin with 'display_skipped_hosts + = no' option. + hipchat: + deprecation: + removal_version: 10.0.0 + warning_text: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020. + osx_say: + redirect: community.general.say + stderr: + tombstone: + removal_version: 2.0.0 + warning_text: Use the 'default' callback plugin with 'display_failed_stderr + = yes' option. connection: docker: redirect: community.docker.docker @@ -4707,24 +4729,6 @@ plugin_routing: redirect: dellemc.openmanage.dellemc_idrac remote_management.dellemc.ome: redirect: dellemc.openmanage.ome - callback: - actionable: - tombstone: - removal_version: 2.0.0 - warning_text: Use the 'default' callback plugin with 'display_skipped_hosts - = no' and 'display_ok_hosts = no' options. - full_skip: - tombstone: - removal_version: 2.0.0 - warning_text: Use the 'default' callback plugin with 'display_skipped_hosts - = no' option. - osx_say: - redirect: community.general.say - stderr: - tombstone: - removal_version: 2.0.0 - warning_text: Use the 'default' callback plugin with 'display_failed_stderr - = yes' option. inventory: docker_machine: redirect: community.docker.docker_machine diff --git a/ansible_collections/community/general/plugins/callback/hipchat.py b/ansible_collections/community/general/plugins/callback/hipchat.py index 3e10b69e7..afd9e2055 100644 --- a/ansible_collections/community/general/plugins/callback/hipchat.py +++ b/ansible_collections/community/general/plugins/callback/hipchat.py @@ -18,6 +18,10 @@ DOCUMENTATION = ''' description: - This callback plugin sends status updates to a HipChat channel during playbook execution. - Before 2.4 only environment variables were available for configuring this plugin. + deprecated: + removed_in: 10.0.0 + why: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020. + alternative: There is none. options: token: description: HipChat API token for v1 or v2 API. diff --git a/ansible_collections/community/general/plugins/callback/loganalytics.py b/ansible_collections/community/general/plugins/callback/loganalytics.py index fbcdc6f89..ed7e47b2e 100644 --- a/ansible_collections/community/general/plugins/callback/loganalytics.py +++ b/ansible_collections/community/general/plugins/callback/loganalytics.py @@ -59,13 +59,16 @@ import uuid import socket import getpass -from datetime import datetime from os.path import basename from ansible.module_utils.urls import open_url from ansible.parsing.ajson import AnsibleJSONEncoder from ansible.plugins.callback import CallbackBase +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + class AzureLogAnalyticsSource(object): def __init__(self): @@ -93,7 +96,7 @@ class AzureLogAnalyticsSource(object): return "https://{0}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01".format(workspace_id) def __rfc1123date(self): - return datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') + return now().strftime('%a, %d %b %Y %H:%M:%S GMT') def send_event(self, workspace_id, shared_key, state, result, runtime): if result._task_fields['args'].get('_ansible_check_mode') is True: @@ -167,7 +170,7 @@ class CallbackModule(CallbackBase): def _seconds_since_start(self, result): return ( - datetime.utcnow() - + now() - self.start_datetimes[result._task._uuid] ).total_seconds() @@ -185,10 +188,10 @@ class CallbackModule(CallbackBase): self.loganalytics.ansible_playbook = basename(playbook._file_name) def v2_playbook_on_task_start(self, task, is_conditional): - self.start_datetimes[task._uuid] = datetime.utcnow() + self.start_datetimes[task._uuid] = now() def v2_playbook_on_handler_task_start(self, task): - self.start_datetimes[task._uuid] = datetime.utcnow() + self.start_datetimes[task._uuid] = now() def v2_runner_on_ok(self, result, **kwargs): self.loganalytics.send_event( diff --git a/ansible_collections/community/general/plugins/callback/logstash.py b/ansible_collections/community/general/plugins/callback/logstash.py index 144e1f991..f3725e465 100644 --- a/ansible_collections/community/general/plugins/callback/logstash.py +++ b/ansible_collections/community/general/plugins/callback/logstash.py @@ -99,7 +99,6 @@ from ansible import context import socket import uuid import logging -from datetime import datetime try: import logstash @@ -109,6 +108,10 @@ except ImportError: from ansible.plugins.callback import CallbackBase +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + class CallbackModule(CallbackBase): @@ -126,7 +129,7 @@ class CallbackModule(CallbackBase): "pip install python-logstash for Python 2" "pip install python3-logstash for Python 3") - self.start_time = datetime.utcnow() + self.start_time = now() def _init_plugin(self): if not self.disabled: @@ -185,7 +188,7 @@ class CallbackModule(CallbackBase): self.logger.info("ansible start", extra=data) def v2_playbook_on_stats(self, stats): - end_time = datetime.utcnow() + end_time = now() runtime = end_time - self.start_time summarize_stat = {} for host in stats.processed.keys(): diff --git a/ansible_collections/community/general/plugins/callback/splunk.py b/ansible_collections/community/general/plugins/callback/splunk.py index d15547f44..a3e401bc2 100644 --- a/ansible_collections/community/general/plugins/callback/splunk.py +++ b/ansible_collections/community/general/plugins/callback/splunk.py @@ -88,13 +88,16 @@ import uuid import socket import getpass -from datetime import datetime from os.path import basename from ansible.module_utils.urls import open_url from ansible.parsing.ajson import AnsibleJSONEncoder from ansible.plugins.callback import CallbackBase +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + class SplunkHTTPCollectorSource(object): def __init__(self): @@ -134,7 +137,7 @@ class SplunkHTTPCollectorSource(object): else: time_format = '%Y-%m-%d %H:%M:%S +0000' - data['timestamp'] = datetime.utcnow().strftime(time_format) + data['timestamp'] = now().strftime(time_format) data['host'] = self.host data['ip_address'] = self.ip_address data['user'] = self.user @@ -181,7 +184,7 @@ class CallbackModule(CallbackBase): def _runtime(self, result): return ( - datetime.utcnow() - + now() - self.start_datetimes[result._task._uuid] ).total_seconds() @@ -220,10 +223,10 @@ class CallbackModule(CallbackBase): self.splunk.ansible_playbook = basename(playbook._file_name) def v2_playbook_on_task_start(self, task, is_conditional): - self.start_datetimes[task._uuid] = datetime.utcnow() + self.start_datetimes[task._uuid] = now() def v2_playbook_on_handler_task_start(self, task): - self.start_datetimes[task._uuid] = datetime.utcnow() + self.start_datetimes[task._uuid] = now() def v2_runner_on_ok(self, result, **kwargs): self.splunk.send_event( diff --git a/ansible_collections/community/general/plugins/callback/sumologic.py b/ansible_collections/community/general/plugins/callback/sumologic.py index 46ab3f0f7..0304b9de5 100644 --- a/ansible_collections/community/general/plugins/callback/sumologic.py +++ b/ansible_collections/community/general/plugins/callback/sumologic.py @@ -46,13 +46,16 @@ import uuid import socket import getpass -from datetime import datetime from os.path import basename from ansible.module_utils.urls import open_url from ansible.parsing.ajson import AnsibleJSONEncoder from ansible.plugins.callback import CallbackBase +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + class SumologicHTTPCollectorSource(object): def __init__(self): @@ -84,8 +87,7 @@ class SumologicHTTPCollectorSource(object): data['uuid'] = result._task._uuid data['session'] = self.session data['status'] = state - data['timestamp'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S ' - '+0000') + data['timestamp'] = now().strftime('%Y-%m-%d %H:%M:%S +0000') data['host'] = self.host data['ip_address'] = self.ip_address data['user'] = self.user @@ -123,7 +125,7 @@ class CallbackModule(CallbackBase): def _runtime(self, result): return ( - datetime.utcnow() - + now() - self.start_datetimes[result._task._uuid] ).total_seconds() @@ -144,10 +146,10 @@ class CallbackModule(CallbackBase): self.sumologic.ansible_playbook = basename(playbook._file_name) def v2_playbook_on_task_start(self, task, is_conditional): - self.start_datetimes[task._uuid] = datetime.utcnow() + self.start_datetimes[task._uuid] = now() def v2_playbook_on_handler_task_start(self, task): - self.start_datetimes[task._uuid] = datetime.utcnow() + self.start_datetimes[task._uuid] = now() def v2_runner_on_ok(self, result, **kwargs): self.sumologic.send_event( diff --git a/ansible_collections/community/general/plugins/filter/from_ini.py b/ansible_collections/community/general/plugins/filter/from_ini.py index d68b51092..6fe83875e 100644 --- a/ansible_collections/community/general/plugins/filter/from_ini.py +++ b/ansible_collections/community/general/plugins/filter/from_ini.py @@ -57,7 +57,7 @@ class IniParser(ConfigParser): ''' Implements a configparser which is able to return a dict ''' def __init__(self): - super().__init__() + super().__init__(interpolation=None) self.optionxform = str def as_dict(self): diff --git a/ansible_collections/community/general/plugins/filter/to_ini.py b/ansible_collections/community/general/plugins/filter/to_ini.py index 22ef16d72..bdf2dde27 100644 --- a/ansible_collections/community/general/plugins/filter/to_ini.py +++ b/ansible_collections/community/general/plugins/filter/to_ini.py @@ -63,7 +63,7 @@ class IniParser(ConfigParser): ''' Implements a configparser which sets the correct optionxform ''' def __init__(self): - super().__init__() + super().__init__(interpolation=None) self.optionxform = str diff --git a/ansible_collections/community/general/plugins/inventory/cobbler.py b/ansible_collections/community/general/plugins/inventory/cobbler.py index 8ca36f426..cdef9944a 100644 --- a/ansible_collections/community/general/plugins/inventory/cobbler.py +++ b/ansible_collections/community/general/plugins/inventory/cobbler.py @@ -117,7 +117,8 @@ from ansible.errors import AnsibleError from ansible.module_utils.common.text.converters import to_text from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, to_safe_group_name from ansible.module_utils.six import text_type -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe # xmlrpc try: diff --git a/ansible_collections/community/general/plugins/inventory/gitlab_runners.py b/ansible_collections/community/general/plugins/inventory/gitlab_runners.py index 536f4bb1b..bd29e8d31 100644 --- a/ansible_collections/community/general/plugins/inventory/gitlab_runners.py +++ b/ansible_collections/community/general/plugins/inventory/gitlab_runners.py @@ -83,7 +83,8 @@ keyed_groups: from ansible.errors import AnsibleError, AnsibleParserError from ansible.module_utils.common.text.converters import to_native from ansible.plugins.inventory import BaseInventoryPlugin, Constructable -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe try: import gitlab diff --git a/ansible_collections/community/general/plugins/inventory/icinga2.py b/ansible_collections/community/general/plugins/inventory/icinga2.py index 6746bb8e0..d1f2bc617 100644 --- a/ansible_collections/community/general/plugins/inventory/icinga2.py +++ b/ansible_collections/community/general/plugins/inventory/icinga2.py @@ -102,7 +102,8 @@ from ansible.errors import AnsibleParserError from ansible.plugins.inventory import BaseInventoryPlugin, Constructable from ansible.module_utils.urls import open_url from ansible.module_utils.six.moves.urllib.error import HTTPError -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe class InventoryModule(BaseInventoryPlugin, Constructable): diff --git a/ansible_collections/community/general/plugins/inventory/linode.py b/ansible_collections/community/general/plugins/inventory/linode.py index fc79f12c5..e161e086e 100644 --- a/ansible_collections/community/general/plugins/inventory/linode.py +++ b/ansible_collections/community/general/plugins/inventory/linode.py @@ -122,7 +122,8 @@ compose: from ansible.errors import AnsibleError from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe try: diff --git a/ansible_collections/community/general/plugins/inventory/lxd.py b/ansible_collections/community/general/plugins/inventory/lxd.py index c803f47dd..cf64f4ee8 100644 --- a/ansible_collections/community/general/plugins/inventory/lxd.py +++ b/ansible_collections/community/general/plugins/inventory/lxd.py @@ -175,7 +175,7 @@ from ansible.module_utils.six import raise_from from ansible.errors import AnsibleError, AnsibleParserError from ansible.module_utils.six.moves.urllib.parse import urlencode from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe try: import ipaddress diff --git a/ansible_collections/community/general/plugins/inventory/nmap.py b/ansible_collections/community/general/plugins/inventory/nmap.py index 3a28007a3..2ca474a1f 100644 --- a/ansible_collections/community/general/plugins/inventory/nmap.py +++ b/ansible_collections/community/general/plugins/inventory/nmap.py @@ -126,7 +126,8 @@ from ansible.errors import AnsibleParserError from ansible.module_utils.common.text.converters import to_native, to_text from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.module_utils.common.process import get_bin_path -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): diff --git a/ansible_collections/community/general/plugins/inventory/online.py b/ansible_collections/community/general/plugins/inventory/online.py index b3a9ecd37..9355d9d41 100644 --- a/ansible_collections/community/general/plugins/inventory/online.py +++ b/ansible_collections/community/general/plugins/inventory/online.py @@ -68,7 +68,8 @@ from ansible.plugins.inventory import BaseInventoryPlugin from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.ansible_release import __version__ as ansible_version from ansible.module_utils.six.moves.urllib.parse import urljoin -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe class InventoryModule(BaseInventoryPlugin): diff --git a/ansible_collections/community/general/plugins/inventory/opennebula.py b/ansible_collections/community/general/plugins/inventory/opennebula.py index 3babfa232..b097307c3 100644 --- a/ansible_collections/community/general/plugins/inventory/opennebula.py +++ b/ansible_collections/community/general/plugins/inventory/opennebula.py @@ -97,7 +97,8 @@ except ImportError: from ansible.errors import AnsibleError from ansible.plugins.inventory import BaseInventoryPlugin, Constructable from ansible.module_utils.common.text.converters import to_native -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe from collections import namedtuple import os diff --git a/ansible_collections/community/general/plugins/inventory/proxmox.py b/ansible_collections/community/general/plugins/inventory/proxmox.py index ed55ef1b6..774833c48 100644 --- a/ansible_collections/community/general/plugins/inventory/proxmox.py +++ b/ansible_collections/community/general/plugins/inventory/proxmox.py @@ -226,9 +226,9 @@ from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.six import string_types from ansible.module_utils.six.moves.urllib.parse import urlencode from ansible.utils.display import Display -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe from ansible_collections.community.general.plugins.module_utils.version import LooseVersion +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe # 3rd party imports try: diff --git a/ansible_collections/community/general/plugins/inventory/scaleway.py b/ansible_collections/community/general/plugins/inventory/scaleway.py index 601129f56..dc24a17da 100644 --- a/ansible_collections/community/general/plugins/inventory/scaleway.py +++ b/ansible_collections/community/general/plugins/inventory/scaleway.py @@ -121,10 +121,10 @@ else: from ansible.errors import AnsibleError from ansible.plugins.inventory import BaseInventoryPlugin, Constructable from ansible_collections.community.general.plugins.module_utils.scaleway import SCALEWAY_LOCATION, parse_pagination_link +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe from ansible.module_utils.urls import open_url from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.six import raise_from -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe import ansible.module_utils.six.moves.urllib.parse as urllib_parse diff --git a/ansible_collections/community/general/plugins/inventory/stackpath_compute.py b/ansible_collections/community/general/plugins/inventory/stackpath_compute.py index 9a556d39e..6b48a49f1 100644 --- a/ansible_collections/community/general/plugins/inventory/stackpath_compute.py +++ b/ansible_collections/community/general/plugins/inventory/stackpath_compute.py @@ -72,7 +72,8 @@ from ansible.plugins.inventory import ( Cacheable ) from ansible.utils.display import Display -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe display = Display() diff --git a/ansible_collections/community/general/plugins/inventory/virtualbox.py b/ansible_collections/community/general/plugins/inventory/virtualbox.py index 8604808e1..79b04ec72 100644 --- a/ansible_collections/community/general/plugins/inventory/virtualbox.py +++ b/ansible_collections/community/general/plugins/inventory/virtualbox.py @@ -62,7 +62,8 @@ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_ from ansible.module_utils.common._collections_compat import MutableMapping from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.module_utils.common.process import get_bin_path -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe + +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable): diff --git a/ansible_collections/community/general/plugins/inventory/xen_orchestra.py b/ansible_collections/community/general/plugins/inventory/xen_orchestra.py index 96dd99770..4094af246 100644 --- a/ansible_collections/community/general/plugins/inventory/xen_orchestra.py +++ b/ansible_collections/community/general/plugins/inventory/xen_orchestra.py @@ -82,9 +82,9 @@ from time import sleep from ansible.errors import AnsibleError from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe from ansible_collections.community.general.plugins.module_utils.version import LooseVersion +from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe # 3rd party imports try: diff --git a/ansible_collections/community/general/plugins/lookup/bitwarden.py b/ansible_collections/community/general/plugins/lookup/bitwarden.py index 2cb2d19a1..7584cd98a 100644 --- a/ansible_collections/community/general/plugins/lookup/bitwarden.py +++ b/ansible_collections/community/general/plugins/lookup/bitwarden.py @@ -29,7 +29,7 @@ DOCUMENTATION = """ - Field to retrieve, for example V(name) or V(id). - If set to V(id), only zero or one element can be returned. Use the Jinja C(first) filter to get the only list element. - - When O(collection_id) is set, this field can be undefined to retrieve the whole collection records. + - If set to V(None) or V(''), or if O(_terms) is empty, records are not filtered by fields. type: str default: name version_added: 5.7.0 @@ -40,6 +40,10 @@ DOCUMENTATION = """ description: Collection ID to filter results by collection. Leave unset to skip filtering. type: str version_added: 6.3.0 + organization_id: + description: Organization ID to filter results by organization. Leave unset to skip filtering. + type: str + version_added: 8.5.0 bw_session: description: Pass session key instead of reading from env. type: str @@ -142,45 +146,44 @@ class Bitwarden(object): raise BitwardenException(err) return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict') - def _get_matches(self, search_value, search_field, collection_id=None): + def _get_matches(self, search_value, search_field, collection_id=None, organization_id=None): """Return matching records whose search_field is equal to key. """ # Prepare set of params for Bitwarden CLI - if search_value: - if search_field == 'id': - params = ['get', 'item', search_value] - else: - params = ['list', 'items', '--search', search_value] - if collection_id: - params.extend(['--collectionid', collection_id]) + if search_field == 'id': + params = ['get', 'item', search_value] else: - if not collection_id: - raise AnsibleError("search_value is required if collection_id is not set.") + params = ['list', 'items'] + if search_value: + params.extend(['--search', search_value]) - params = ['list', 'items', '--collectionid', collection_id] + if collection_id: + params.extend(['--collectionid', collection_id]) + if organization_id: + params.extend(['--organizationid', organization_id]) out, err = self._run(params) # This includes things that matched in different fields. initial_matches = AnsibleJSONDecoder().raw_decode(out)[0] - if search_field == 'id' or not search_value: + if search_field == 'id': if initial_matches is None: initial_matches = [] else: initial_matches = [initial_matches] # Filter to only include results from the right field. - return [item for item in initial_matches if item[search_field] == search_value] + return [item for item in initial_matches if not search_value or item[search_field] == search_value] - def get_field(self, field, search_value=None, search_field="name", collection_id=None): + def get_field(self, field, search_value, search_field="name", collection_id=None, organization_id=None): """Return a list of the specified field for records whose search_field match search_value and filtered by collection if collection has been provided. If field is None, return the whole record for each match. """ - matches = self._get_matches(search_value, search_field, collection_id) + matches = self._get_matches(search_value, search_field, collection_id, organization_id) if not field: return matches field_matches = [] @@ -215,15 +218,16 @@ class LookupModule(LookupBase): field = self.get_option('field') search_field = self.get_option('search') collection_id = self.get_option('collection_id') + organization_id = self.get_option('organization_id') _bitwarden.session = self.get_option('bw_session') if not _bitwarden.unlocked: raise AnsibleError("Bitwarden Vault locked. Run 'bw unlock'.") if not terms: - return [_bitwarden.get_field(field, None, search_field, collection_id)] + terms = [None] - return [_bitwarden.get_field(field, term, search_field, collection_id) for term in terms] + return [_bitwarden.get_field(field, term, search_field, collection_id, organization_id) for term in terms] _bitwarden = Bitwarden() diff --git a/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py b/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py index 2d6706bee..8cabc693f 100644 --- a/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py +++ b/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py @@ -70,6 +70,7 @@ RETURN = """ """ from subprocess import Popen, PIPE +from time import sleep from ansible.errors import AnsibleLookupError from ansible.module_utils.common.text.converters import to_text @@ -84,11 +85,29 @@ class BitwardenSecretsManagerException(AnsibleLookupError): class BitwardenSecretsManager(object): def __init__(self, path='bws'): self._cli_path = path + self._max_retries = 3 + self._retry_delay = 1 @property def cli_path(self): return self._cli_path + def _run_with_retry(self, args, stdin=None, retries=0): + out, err, rc = self._run(args, stdin) + + if rc != 0: + if retries >= self._max_retries: + raise BitwardenSecretsManagerException("Max retries exceeded. Unable to retrieve secret.") + + if "Too many requests" in err: + delay = self._retry_delay * (2 ** retries) + sleep(delay) + return self._run_with_retry(args, stdin, retries + 1) + else: + raise BitwardenSecretsManagerException(f"Command failed with return code {rc}: {err}") + + return out, err, rc + def _run(self, args, stdin=None): p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE) out, err = p.communicate(stdin) @@ -107,7 +126,7 @@ class BitwardenSecretsManager(object): 'get', 'secret', secret_id ] - out, err, rc = self._run(params) + out, err, rc = self._run_with_retry(params) if rc != 0: raise BitwardenSecretsManagerException(to_text(err)) diff --git a/ansible_collections/community/general/plugins/lookup/passwordstore.py b/ansible_collections/community/general/plugins/lookup/passwordstore.py index 7a6fca7a0..9814fe133 100644 --- a/ansible_collections/community/general/plugins/lookup/passwordstore.py +++ b/ansible_collections/community/general/plugins/lookup/passwordstore.py @@ -139,6 +139,21 @@ DOCUMENTATION = ''' type: bool default: true version_added: 8.1.0 + missing_subkey: + description: + - Preference about what to do if the password subkey is missing. + - If set to V(error), the lookup will error out if the subkey does not exist. + - If set to V(empty) or V(warn), will return a V(none) in case the subkey does not exist. + version_added: 8.6.0 + type: str + default: empty + choices: + - error + - warn + - empty + ini: + - section: passwordstore_lookup + key: missing_subkey notes: - The lookup supports passing all options as lookup parameters since community.general 6.0.0. ''' @@ -147,6 +162,7 @@ ansible.cfg: | [passwordstore_lookup] lock=readwrite locktimeout=45s + missing_subkey=warn tasks.yml: | --- @@ -432,6 +448,20 @@ class LookupModule(LookupBase): if self.paramvals['subkey'] in self.passdict: return self.passdict[self.paramvals['subkey']] else: + if self.paramvals["missing_subkey"] == "error": + raise AnsibleError( + "passwordstore: subkey {0} for passname {1} not found and missing_subkey=error is set".format( + self.paramvals["subkey"], self.passname + ) + ) + + if self.paramvals["missing_subkey"] == "warn": + display.warning( + "passwordstore: subkey {0} for passname {1} not found".format( + self.paramvals["subkey"], self.passname + ) + ) + return None @contextmanager @@ -481,6 +511,7 @@ class LookupModule(LookupBase): 'umask': self.get_option('umask'), 'timestamp': self.get_option('timestamp'), 'preserve': self.get_option('preserve'), + "missing_subkey": self.get_option("missing_subkey"), } def run(self, terms, variables, **kwargs): diff --git a/ansible_collections/community/general/plugins/module_utils/datetime.py b/ansible_collections/community/general/plugins/module_utils/datetime.py new file mode 100644 index 000000000..c7899f68d --- /dev/null +++ b/ansible_collections/community/general/plugins/module_utils/datetime.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023 Felix Fontein <felix@fontein.de> +# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause) +# SPDX-License-Identifier: BSD-2-Clause + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import datetime as _datetime +import sys + + +_USE_TIMEZONE = sys.version_info >= (3, 6) + + +def ensure_timezone_info(value): + if not _USE_TIMEZONE or value.tzinfo is not None: + return value + return value.astimezone(_datetime.timezone.utc) + + +def fromtimestamp(value): + if _USE_TIMEZONE: + return _datetime.fromtimestamp(value, tz=_datetime.timezone.utc) + return _datetime.utcfromtimestamp(value) + + +def now(): + if _USE_TIMEZONE: + return _datetime.datetime.now(tz=_datetime.timezone.utc) + return _datetime.datetime.utcnow() diff --git a/ansible_collections/community/general/plugins/module_utils/gitlab.py b/ansible_collections/community/general/plugins/module_utils/gitlab.py index f9872b877..b1354d8a9 100644 --- a/ansible_collections/community/general/plugins/module_utils/gitlab.py +++ b/ansible_collections/community/general/plugins/module_utils/gitlab.py @@ -81,16 +81,23 @@ def find_group(gitlab_instance, identifier): return group -def ensure_gitlab_package(module): +def ensure_gitlab_package(module, min_version=None): if not HAS_GITLAB_PACKAGE: module.fail_json( msg=missing_required_lib("python-gitlab", url='https://python-gitlab.readthedocs.io/en/stable/'), exception=GITLAB_IMP_ERR ) + gitlab_version = gitlab.__version__ + if min_version is not None and LooseVersion(gitlab_version) < LooseVersion(min_version): + module.fail_json( + msg="This module requires python-gitlab Python module >= %s " + "(installed version: %s). Please upgrade python-gitlab to version %s or above." + % (min_version, gitlab_version, min_version) + ) -def gitlab_authentication(module): - ensure_gitlab_package(module) +def gitlab_authentication(module, min_version=None): + ensure_gitlab_package(module, min_version=min_version) gitlab_url = module.params['api_url'] validate_certs = module.params['validate_certs'] diff --git a/ansible_collections/community/general/plugins/module_utils/identity/keycloak/keycloak.py b/ansible_collections/community/general/plugins/module_utils/identity/keycloak/keycloak.py index 9e1c3f4d9..b2a189250 100644 --- a/ansible_collections/community/general/plugins/module_utils/identity/keycloak/keycloak.py +++ b/ansible_collections/community/general/plugins/module_utils/identity/keycloak/keycloak.py @@ -28,6 +28,9 @@ URL_CLIENT_ROLES = "{url}/admin/realms/{realm}/clients/{id}/roles" URL_CLIENT_ROLE = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}" URL_CLIENT_ROLE_COMPOSITES = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}/composites" +URL_CLIENT_ROLE_SCOPE_CLIENTS = "{url}/admin/realms/{realm}/clients/{id}/scope-mappings/clients/{scopeid}" +URL_CLIENT_ROLE_SCOPE_REALM = "{url}/admin/realms/{realm}/clients/{id}/scope-mappings/realm" + URL_REALM_ROLES = "{url}/admin/realms/{realm}/roles" URL_REALM_ROLE = "{url}/admin/realms/{realm}/roles/{name}" URL_REALM_ROLEMAPPINGS = "{url}/admin/realms/{realm}/users/{id}/role-mappings/realm" @@ -3049,6 +3052,105 @@ class KeycloakAPI(object): except Exception: return False + def get_client_role_scope_from_client(self, clientid, clientscopeid, realm="master"): + """ Fetch the roles associated with the client's scope for a specific client on the Keycloak server. + :param clientid: ID of the client from which to obtain the associated roles. + :param clientscopeid: ID of the client who owns the roles. + :param realm: Realm from which to obtain the scope. + :return: The client scope of roles from specified client. + """ + client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid) + try: + return json.loads(to_native(open_url(client_role_scope_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, + timeout=self.connection_timeout, + validate_certs=self.validate_certs).read())) + except Exception as e: + self.fail_open_url(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + + def update_client_role_scope_from_client(self, payload, clientid, clientscopeid, realm="master"): + """ Update and fetch the roles associated with the client's scope on the Keycloak server. + :param payload: List of roles to be added to the scope. + :param clientid: ID of the client to update scope. + :param clientscopeid: ID of the client who owns the roles. + :param realm: Realm from which to obtain the clients. + :return: The client scope of roles from specified client. + """ + client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid) + try: + open_url(client_role_scope_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, + data=json.dumps(payload), validate_certs=self.validate_certs) + + except Exception as e: + self.fail_open_url(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + + return self.get_client_role_scope_from_client(clientid, clientscopeid, realm) + + def delete_client_role_scope_from_client(self, payload, clientid, clientscopeid, realm="master"): + """ Delete the roles contains in the payload from the client's scope on the Keycloak server. + :param payload: List of roles to be deleted. + :param clientid: ID of the client to delete roles from scope. + :param clientscopeid: ID of the client who owns the roles. + :param realm: Realm from which to obtain the clients. + :return: The client scope of roles from specified client. + """ + client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid) + try: + open_url(client_role_scope_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, + data=json.dumps(payload), validate_certs=self.validate_certs) + + except Exception as e: + self.fail_open_url(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + + return self.get_client_role_scope_from_client(clientid, clientscopeid, realm) + + def get_client_role_scope_from_realm(self, clientid, realm="master"): + """ Fetch the realm roles from the client's scope on the Keycloak server. + :param clientid: ID of the client from which to obtain the associated realm roles. + :param realm: Realm from which to obtain the clients. + :return: The client realm roles scope. + """ + client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) + try: + return json.loads(to_native(open_url(client_role_scope_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, + timeout=self.connection_timeout, + validate_certs=self.validate_certs).read())) + except Exception as e: + self.fail_open_url(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + + def update_client_role_scope_from_realm(self, payload, clientid, realm="master"): + """ Update and fetch the realm roles from the client's scope on the Keycloak server. + :param payload: List of realm roles to add. + :param clientid: ID of the client to update scope. + :param realm: Realm from which to obtain the clients. + :return: The client realm roles scope. + """ + client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) + try: + open_url(client_role_scope_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, + data=json.dumps(payload), validate_certs=self.validate_certs) + + except Exception as e: + self.fail_open_url(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + + return self.get_client_role_scope_from_realm(clientid, realm) + + def delete_client_role_scope_from_realm(self, payload, clientid, realm="master"): + """ Delete the realm roles contains in the payload from the client's scope on the Keycloak server. + :param payload: List of realm roles to delete. + :param clientid: ID of the client to delete roles from scope. + :param realm: Realm from which to obtain the clients. + :return: The client realm roles scope. + """ + client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) + try: + open_url(client_role_scope_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, + data=json.dumps(payload), validate_certs=self.validate_certs) + + except Exception as e: + self.fail_open_url(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + + return self.get_client_role_scope_from_realm(clientid, realm) + def fail_open_url(self, e, msg, **kwargs): try: if isinstance(e, HTTPError): diff --git a/ansible_collections/community/general/plugins/module_utils/ipa.py b/ansible_collections/community/general/plugins/module_utils/ipa.py index eda9b4132..fb63d5556 100644 --- a/ansible_collections/community/general/plugins/module_utils/ipa.py +++ b/ansible_collections/community/general/plugins/module_utils/ipa.py @@ -104,7 +104,7 @@ class IPAClient(object): def get_ipa_version(self): response = self.ping()['summary'] - ipa_ver_regex = re.compile(r'IPA server version (\d\.\d\.\d).*') + ipa_ver_regex = re.compile(r'IPA server version (\d+\.\d+\.\d+).*') version_match = ipa_ver_regex.match(response) ipa_version = None if version_match: diff --git a/ansible_collections/community/general/plugins/module_utils/puppet.py b/ansible_collections/community/general/plugins/module_utils/puppet.py index 8d553a2d2..f05b0673f 100644 --- a/ansible_collections/community/general/plugins/module_utils/puppet.py +++ b/ansible_collections/community/general/plugins/module_utils/puppet.py @@ -107,5 +107,6 @@ def puppet_runner(module): verbose=cmd_runner_fmt.as_bool("--verbose"), ), check_rc=False, + force_lang=module.params["environment_lang"], ) return runner diff --git a/ansible_collections/community/general/plugins/module_utils/redfish_utils.py b/ansible_collections/community/general/plugins/module_utils/redfish_utils.py index 4c2057129..6935573d0 100644 --- a/ansible_collections/community/general/plugins/module_utils/redfish_utils.py +++ b/ansible_collections/community/general/plugins/module_utils/redfish_utils.py @@ -1149,6 +1149,54 @@ class RedfishUtils(object): return response return {'ret': True, 'changed': True} + def manager_reset_to_defaults(self, command): + return self.reset_to_defaults(command, self.manager_uri, + '#Manager.ResetToDefaults') + + def reset_to_defaults(self, command, resource_uri, action_name): + key = "Actions" + reset_type_values = ['ResetAll', + 'PreserveNetworkAndUsers', + 'PreserveNetwork'] + + if command not in reset_type_values: + return {'ret': False, 'msg': 'Invalid Command (%s)' % command} + + # read the resource and get the current power state + response = self.get_request(self.root_uri + resource_uri) + if response['ret'] is False: + return response + data = response['data'] + + # get the reset Action and target URI + if key not in data or action_name not in data[key]: + return {'ret': False, 'msg': 'Action %s not found' % action_name} + reset_action = data[key][action_name] + if 'target' not in reset_action: + return {'ret': False, + 'msg': 'target URI missing from Action %s' % action_name} + action_uri = reset_action['target'] + + # get AllowableValues + ai = self._get_all_action_info_values(reset_action) + allowable_values = ai.get('ResetType', {}).get('AllowableValues', []) + + # map ResetType to an allowable value if needed + if allowable_values and command not in allowable_values: + return {'ret': False, + 'msg': 'Specified reset type (%s) not supported ' + 'by service. Supported types: %s' % + (command, allowable_values)} + + # define payload + payload = {'ResetType': command} + + # POST to Action URI + response = self.post_request(self.root_uri + action_uri, payload) + if response['ret'] is False: + return response + return {'ret': True, 'changed': True} + def _find_account_uri(self, username=None, acct_id=None): if not any((username, acct_id)): return {'ret': False, 'msg': @@ -1549,6 +1597,8 @@ class RedfishUtils(object): data = response['data'] + result['multipart_supported'] = 'MultipartHttpPushUri' in data + if "Actions" in data: actions = data['Actions'] if len(actions) > 0: diff --git a/ansible_collections/community/general/plugins/module_utils/scaleway.py b/ansible_collections/community/general/plugins/module_utils/scaleway.py index 67b821103..1310ba560 100644 --- a/ansible_collections/community/general/plugins/module_utils/scaleway.py +++ b/ansible_collections/community/general/plugins/module_utils/scaleway.py @@ -17,6 +17,10 @@ from ansible.module_utils.basic import env_fallback, missing_required_lib from ansible.module_utils.urls import fetch_url from ansible.module_utils.six.moves.urllib.parse import urlencode +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + SCALEWAY_SECRET_IMP_ERR = None try: from passlib.hash import argon2 @@ -306,10 +310,10 @@ class Scaleway(object): # Prevent requesting the resource status too soon time.sleep(wait_sleep_time) - start = datetime.datetime.utcnow() + start = now() end = start + datetime.timedelta(seconds=wait_timeout) - while datetime.datetime.utcnow() < end: + while now() < end: self.module.debug("We are going to wait for the resource to finish its transition") state = self.fetch_state(resource) diff --git a/ansible_collections/community/general/plugins/modules/aix_filesystem.py b/ansible_collections/community/general/plugins/modules/aix_filesystem.py index 6abf6317f..4a3775c67 100644 --- a/ansible_collections/community/general/plugins/modules/aix_filesystem.py +++ b/ansible_collections/community/general/plugins/modules/aix_filesystem.py @@ -242,7 +242,7 @@ def _validate_vg(module, vg): if rc != 0: module.fail_json(msg="Failed executing %s command." % lsvg_cmd) - rc, current_all_vgs, err = module.run_command([lsvg_cmd, "%s"]) + rc, current_all_vgs, err = module.run_command([lsvg_cmd]) if rc != 0: module.fail_json(msg="Failed executing %s command." % lsvg_cmd) diff --git a/ansible_collections/community/general/plugins/modules/apt_rpm.py b/ansible_collections/community/general/plugins/modules/apt_rpm.py index de1b57411..03b87e78f 100644 --- a/ansible_collections/community/general/plugins/modules/apt_rpm.py +++ b/ansible_collections/community/general/plugins/modules/apt_rpm.py @@ -37,7 +37,17 @@ options: state: description: - Indicates the desired package state. - choices: [ absent, present, installed, removed ] + - Please note that V(present) and V(installed) are equivalent to V(latest) right now. + This will change in the future. To simply ensure that a package is installed, without upgrading + it, use the V(present_not_latest) state. + - The states V(latest) and V(present_not_latest) have been added in community.general 8.6.0. + choices: + - absent + - present + - present_not_latest + - installed + - removed + - latest default: present type: str update_cache: @@ -180,7 +190,7 @@ def check_package_version(module, name): return False -def query_package_provides(module, name): +def query_package_provides(module, name, allow_upgrade=False): # rpm -q returns 0 if the package is installed, # 1 if it is not installed if name.endswith('.rpm'): @@ -195,10 +205,11 @@ def query_package_provides(module, name): rc, out, err = module.run_command("%s -q --provides %s" % (RPM_PATH, name)) if rc == 0: + if not allow_upgrade: + return True if check_package_version(module, name): return True - else: - return False + return False def update_package_db(module): @@ -255,14 +266,14 @@ def remove_packages(module, packages): return (False, "package(s) already absent") -def install_packages(module, pkgspec): +def install_packages(module, pkgspec, allow_upgrade=False): if pkgspec is None: return (False, "Empty package list") packages = "" for package in pkgspec: - if not query_package_provides(module, package): + if not query_package_provides(module, package, allow_upgrade=allow_upgrade): packages += "'%s' " % package if len(packages) != 0: @@ -270,8 +281,8 @@ def install_packages(module, pkgspec): rc, out, err = module.run_command("%s -y install %s" % (APT_PATH, packages), environ_update={"LANG": "C"}) installed = True - for packages in pkgspec: - if not query_package_provides(module, package): + for package in pkgspec: + if not query_package_provides(module, package, allow_upgrade=False): installed = False # apt-rpm always have 0 for exit code if --force is used @@ -286,7 +297,7 @@ def install_packages(module, pkgspec): def main(): module = AnsibleModule( argument_spec=dict( - state=dict(type='str', default='present', choices=['absent', 'installed', 'present', 'removed']), + state=dict(type='str', default='present', choices=['absent', 'installed', 'present', 'removed', 'present_not_latest', 'latest']), update_cache=dict(type='bool', default=False), clean=dict(type='bool', default=False), dist_upgrade=dict(type='bool', default=False), @@ -320,8 +331,8 @@ def main(): output += out packages = p['package'] - if p['state'] in ['installed', 'present']: - (m, out) = install_packages(module, packages) + if p['state'] in ['installed', 'present', 'present_not_latest', 'latest']: + (m, out) = install_packages(module, packages, allow_upgrade=p['state'] != 'present_not_latest') modified = modified or m output += out diff --git a/ansible_collections/community/general/plugins/modules/cobbler_sync.py b/ansible_collections/community/general/plugins/modules/cobbler_sync.py index 4ec87c96c..27f57028b 100644 --- a/ansible_collections/community/general/plugins/modules/cobbler_sync.py +++ b/ansible_collections/community/general/plugins/modules/cobbler_sync.py @@ -75,13 +75,16 @@ RETURN = r''' # Default return values ''' -import datetime import ssl from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six.moves import xmlrpc_client from ansible.module_utils.common.text.converters import to_text +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + def main(): module = AnsibleModule( @@ -110,7 +113,7 @@ def main(): changed=True, ) - start = datetime.datetime.utcnow() + start = now() ssl_context = None if not validate_certs: @@ -142,7 +145,7 @@ def main(): except Exception as e: module.fail_json(msg="Failed to sync Cobbler. {error}".format(error=to_text(e))) - elapsed = datetime.datetime.utcnow() - start + elapsed = now() - start module.exit_json(elapsed=elapsed.seconds, **result) diff --git a/ansible_collections/community/general/plugins/modules/cobbler_system.py b/ansible_collections/community/general/plugins/modules/cobbler_system.py index cecc02f71..a327ede84 100644 --- a/ansible_collections/community/general/plugins/modules/cobbler_system.py +++ b/ansible_collections/community/general/plugins/modules/cobbler_system.py @@ -152,7 +152,6 @@ system: type: dict ''' -import datetime import ssl from ansible.module_utils.basic import AnsibleModule @@ -160,6 +159,10 @@ from ansible.module_utils.six import iteritems from ansible.module_utils.six.moves import xmlrpc_client from ansible.module_utils.common.text.converters import to_text +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + IFPROPS_MAPPING = dict( bondingopts='bonding_opts', bridgeopts='bridge_opts', @@ -232,7 +235,7 @@ def main(): changed=False, ) - start = datetime.datetime.utcnow() + start = now() ssl_context = None if not validate_certs: @@ -340,7 +343,7 @@ def main(): if module._diff: result['diff'] = dict(before=system, after=result['system']) - elapsed = datetime.datetime.utcnow() - start + elapsed = now() - start module.exit_json(elapsed=elapsed.seconds, **result) diff --git a/ansible_collections/community/general/plugins/modules/filesystem.py b/ansible_collections/community/general/plugins/modules/filesystem.py index ec361245b..73e8c79c6 100644 --- a/ansible_collections/community/general/plugins/modules/filesystem.py +++ b/ansible_collections/community/general/plugins/modules/filesystem.py @@ -40,11 +40,12 @@ options: default: present version_added: 1.3.0 fstype: - choices: [ btrfs, ext2, ext3, ext4, ext4dev, f2fs, lvm, ocfs2, reiserfs, xfs, vfat, swap, ufs ] + choices: [ bcachefs, btrfs, ext2, ext3, ext4, ext4dev, f2fs, lvm, ocfs2, reiserfs, xfs, vfat, swap, ufs ] description: - Filesystem type to be created. This option is required with O(state=present) (or if O(state) is omitted). - ufs support has been added in community.general 3.4.0. + - bcachefs support has been added in community.general 8.6.0. type: str aliases: [type] dev: @@ -67,7 +68,7 @@ options: resizefs: description: - If V(true), if the block device and filesystem size differ, grow the filesystem into the space. - - Supported for C(btrfs), C(ext2), C(ext3), C(ext4), C(ext4dev), C(f2fs), C(lvm), C(xfs), C(ufs) and C(vfat) filesystems. + - Supported for C(bcachefs), C(btrfs), C(ext2), C(ext3), C(ext4), C(ext4dev), C(f2fs), C(lvm), C(xfs), C(ufs) and C(vfat) filesystems. Attempts to resize other filesystem types will fail. - XFS Will only grow if mounted. Currently, the module is based on commands from C(util-linux) package to perform operations, so resizing of XFS is @@ -86,7 +87,7 @@ options: - The UUID options specified in O(opts) take precedence over this value. - See xfs_admin(8) (C(xfs)), tune2fs(8) (C(ext2), C(ext3), C(ext4), C(ext4dev)) for possible values. - For O(fstype=lvm) the value is ignored, it resets the PV UUID if set. - - Supported for O(fstype) being one of C(ext2), C(ext3), C(ext4), C(ext4dev), C(lvm), or C(xfs). + - Supported for O(fstype) being one of C(bcachefs), C(ext2), C(ext3), C(ext4), C(ext4dev), C(lvm), or C(xfs). - This is B(not idempotent). Specifying this option will always result in a change. - Mutually exclusive with O(resizefs). type: str @@ -405,6 +406,48 @@ class Reiserfs(Filesystem): MKFS_FORCE_FLAGS = ['-q'] +class Bcachefs(Filesystem): + MKFS = 'mkfs.bcachefs' + MKFS_FORCE_FLAGS = ['--force'] + MKFS_SET_UUID_OPTIONS = ['-U', '--uuid'] + INFO = 'bcachefs' + GROW = 'bcachefs' + GROW_MAX_SPACE_FLAGS = ['device', 'resize'] + + def get_fs_size(self, dev): + """Return size in bytes of filesystem on device (integer).""" + dummy, stdout, dummy = self.module.run_command([self.module.get_bin_path(self.INFO), + 'show-super', str(dev)], check_rc=True) + + for line in stdout.splitlines(): + if "Size: " in line: + parts = line.split() + unit = parts[2] + + base = None + exp = None + + units_2 = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"] + units_10 = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + + try: + exp = units_2.index(unit) + base = 1024 + except ValueError: + exp = units_10.index(unit) + base = 1000 + + if exp == 0: + value = int(parts[1]) + else: + value = float(parts[1]) + + if base is not None and exp is not None: + return int(value * pow(base, exp)) + + raise ValueError(repr(stdout)) + + class Btrfs(Filesystem): MKFS = 'mkfs.btrfs' INFO = 'btrfs' @@ -567,6 +610,7 @@ class UFS(Filesystem): FILESYSTEMS = { + 'bcachefs': Bcachefs, 'ext2': Ext2, 'ext3': Ext3, 'ext4': Ext4, diff --git a/ansible_collections/community/general/plugins/modules/flatpak.py b/ansible_collections/community/general/plugins/modules/flatpak.py index 80dbabdfa..15e404d45 100644 --- a/ansible_collections/community/general/plugins/modules/flatpak.py +++ b/ansible_collections/community/general/plugins/modules/flatpak.py @@ -26,7 +26,9 @@ extends_documentation_fragment: - community.general.attributes attributes: check_mode: - support: full + support: partial + details: + - If O(state=latest), the module will always return C(changed=true). diff_mode: support: none options: @@ -53,12 +55,12 @@ options: - Both C(https://) and C(http://) URLs are supported. - When supplying a reverse DNS name, you can use the O(remote) option to specify on what remote to look for the flatpak. An example for a reverse DNS name is C(org.gnome.gedit). - - When used with O(state=absent), it is recommended to specify the name in the reverse DNS - format. - - When supplying a URL with O(state=absent), the module will try to match the - installed flatpak based on the name of the flatpakref to remove it. However, there is no - guarantee that the names of the flatpakref file and the reverse DNS name of the installed - flatpak do match. + - When used with O(state=absent) or O(state=latest), it is recommended to specify the name in + the reverse DNS format. + - When supplying a URL with O(state=absent) or O(state=latest), the module will try to match the + installed flatpak based on the name of the flatpakref to remove or update it. However, there + is no guarantee that the names of the flatpakref file and the reverse DNS name of the + installed flatpak do match. type: list elements: str required: true @@ -82,7 +84,8 @@ options: state: description: - Indicates the desired package state. - choices: [ absent, present ] + - The value V(latest) is supported since community.general 8.6.0. + choices: [ absent, present, latest ] type: str default: present ''' @@ -118,6 +121,37 @@ EXAMPLES = r''' - org.inkscape.Inkscape - org.mozilla.firefox +- name: Update the spotify flatpak + community.general.flatpak: + name: https://s3.amazonaws.com/alexlarsson/spotify-repo/spotify.flatpakref + state: latest + +- name: Update the gedit flatpak package without dependencies (not recommended) + community.general.flatpak: + name: https://git.gnome.org/browse/gnome-apps-nightly/plain/gedit.flatpakref + state: latest + no_dependencies: true + +- name: Update the gedit package from flathub for current user + community.general.flatpak: + name: org.gnome.gedit + state: latest + method: user + +- name: Update the Gnome Calendar flatpak from the gnome remote system-wide + community.general.flatpak: + name: org.gnome.Calendar + state: latest + remote: gnome + +- name: Update multiple packages + community.general.flatpak: + name: + - org.gimp.GIMP + - org.inkscape.Inkscape + - org.mozilla.firefox + state: latest + - name: Remove the gedit flatpak community.general.flatpak: name: org.gnome.gedit @@ -195,6 +229,28 @@ def install_flat(module, binary, remote, names, method, no_dependencies): result['changed'] = True +def update_flat(module, binary, names, method, no_dependencies): + """Update existing flatpaks.""" + global result # pylint: disable=global-variable-not-assigned + installed_flat_names = [ + _match_installed_flat_name(module, binary, name, method) + for name in names + ] + command = [binary, "update", "--{0}".format(method)] + flatpak_version = _flatpak_version(module, binary) + if LooseVersion(flatpak_version) < LooseVersion('1.1.3'): + command += ["-y"] + else: + command += ["--noninteractive"] + if no_dependencies: + command += ["--no-deps"] + command += installed_flat_names + stdout = _flatpak_command(module, module.check_mode, command) + result["changed"] = ( + True if module.check_mode else stdout.find("Nothing to do.") == -1 + ) + + def uninstall_flat(module, binary, names, method): """Remove existing flatpaks.""" global result # pylint: disable=global-variable-not-assigned @@ -313,7 +369,7 @@ def main(): method=dict(type='str', default='system', choices=['user', 'system']), state=dict(type='str', default='present', - choices=['absent', 'present']), + choices=['absent', 'present', 'latest']), no_dependencies=dict(type='bool', default=False), executable=dict(type='path', default='flatpak') ), @@ -338,10 +394,13 @@ def main(): module.fail_json(msg="Executable '%s' was not found on the system." % executable, **result) installed, not_installed = flatpak_exists(module, binary, name, method) - if state == 'present' and not_installed: - install_flat(module, binary, remote, not_installed, method, no_dependencies) - elif state == 'absent' and installed: + if state == 'absent' and installed: uninstall_flat(module, binary, installed, method) + else: + if state == 'latest' and installed: + update_flat(module, binary, installed, method, no_dependencies) + if state in ('present', 'latest') and not_installed: + install_flat(module, binary, remote, not_installed, method, no_dependencies) module.exit_json(**result) diff --git a/ansible_collections/community/general/plugins/modules/github_key.py b/ansible_collections/community/general/plugins/modules/github_key.py index fa3a0a01f..a74ead984 100644 --- a/ansible_collections/community/general/plugins/modules/github_key.py +++ b/ansible_collections/community/general/plugins/modules/github_key.py @@ -91,12 +91,17 @@ EXAMPLES = ''' pubkey: "{{ lookup('ansible.builtin.file', '/home/foo/.ssh/id_rsa.pub') }}" ''' +import datetime import json import re from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.urls import fetch_url +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + API_BASE = 'https://api.github.com' @@ -151,14 +156,13 @@ def get_all_keys(session): def create_key(session, name, pubkey, check_mode): if check_mode: - from datetime import datetime - now = datetime.utcnow() + now_t = now() return { 'id': 0, 'key': pubkey, 'title': name, 'url': 'http://example.com/CHECK_MODE_GITHUB_KEY', - 'created_at': datetime.strftime(now, '%Y-%m-%dT%H:%M:%SZ'), + 'created_at': datetime.strftime(now_t, '%Y-%m-%dT%H:%M:%SZ'), 'read_only': False, 'verified': False } diff --git a/ansible_collections/community/general/plugins/modules/gitlab_issue.py b/ansible_collections/community/general/plugins/modules/gitlab_issue.py index 6d95bf6cf..3277c4f1a 100644 --- a/ansible_collections/community/general/plugins/modules/gitlab_issue.py +++ b/ansible_collections/community/general/plugins/modules/gitlab_issue.py @@ -143,7 +143,6 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.api import basic_auth_argument_spec from ansible.module_utils.common.text.converters import to_native, to_text -from ansible_collections.community.general.plugins.module_utils.version import LooseVersion from ansible_collections.community.general.plugins.module_utils.gitlab import ( auth_argument_spec, gitlab_authentication, gitlab, find_project, find_group ) @@ -330,13 +329,8 @@ def main(): state_filter = module.params['state_filter'] title = module.params['title'] - gitlab_version = gitlab.__version__ - if LooseVersion(gitlab_version) < LooseVersion('2.3.0'): - module.fail_json(msg="community.general.gitlab_issue requires python-gitlab Python module >= 2.3.0 (installed version: [%s])." - " Please upgrade python-gitlab to version 2.3.0 or above." % gitlab_version) - # check prerequisites and connect to gitlab server - gitlab_instance = gitlab_authentication(module) + gitlab_instance = gitlab_authentication(module, min_version='2.3.0') this_project = find_project(gitlab_instance, project) if this_project is None: diff --git a/ansible_collections/community/general/plugins/modules/gitlab_label.py b/ansible_collections/community/general/plugins/modules/gitlab_label.py index f2c8393f2..635033ab6 100644 --- a/ansible_collections/community/general/plugins/modules/gitlab_label.py +++ b/ansible_collections/community/general/plugins/modules/gitlab_label.py @@ -222,9 +222,8 @@ labels_obj: from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.api import basic_auth_argument_spec -from ansible_collections.community.general.plugins.module_utils.version import LooseVersion from ansible_collections.community.general.plugins.module_utils.gitlab import ( - auth_argument_spec, gitlab_authentication, ensure_gitlab_package, find_group, find_project, gitlab + auth_argument_spec, gitlab_authentication, ensure_gitlab_package, find_group, find_project ) @@ -450,14 +449,7 @@ def main(): label_list = module.params['labels'] state = module.params['state'] - gitlab_version = gitlab.__version__ - _min_gitlab = '3.2.0' - if LooseVersion(gitlab_version) < LooseVersion(_min_gitlab): - module.fail_json(msg="community.general.gitlab_label requires python-gitlab Python module >= %s " - "(installed version: [%s]). Please upgrade " - "python-gitlab to version %s or above." % (_min_gitlab, gitlab_version, _min_gitlab)) - - gitlab_instance = gitlab_authentication(module) + gitlab_instance = gitlab_authentication(module, min_version='3.2.0') # find_project can return None, but the other must exist gitlab_project_id = find_project(gitlab_instance, gitlab_project) diff --git a/ansible_collections/community/general/plugins/modules/gitlab_milestone.py b/ansible_collections/community/general/plugins/modules/gitlab_milestone.py index 0a616ea47..4b8b933cc 100644 --- a/ansible_collections/community/general/plugins/modules/gitlab_milestone.py +++ b/ansible_collections/community/general/plugins/modules/gitlab_milestone.py @@ -206,9 +206,8 @@ milestones_obj: from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.api import basic_auth_argument_spec -from ansible_collections.community.general.plugins.module_utils.version import LooseVersion from ansible_collections.community.general.plugins.module_utils.gitlab import ( - auth_argument_spec, gitlab_authentication, ensure_gitlab_package, find_group, find_project, gitlab + auth_argument_spec, gitlab_authentication, ensure_gitlab_package, find_group, find_project ) from datetime import datetime @@ -452,14 +451,7 @@ def main(): milestone_list = module.params['milestones'] state = module.params['state'] - gitlab_version = gitlab.__version__ - _min_gitlab = '3.2.0' - if LooseVersion(gitlab_version) < LooseVersion(_min_gitlab): - module.fail_json(msg="community.general.gitlab_milestone requires python-gitlab Python module >= %s " - "(installed version: [%s]). Please upgrade " - "python-gitlab to version %s or above." % (_min_gitlab, gitlab_version, _min_gitlab)) - - gitlab_instance = gitlab_authentication(module) + gitlab_instance = gitlab_authentication(module, min_version='3.2.0') # find_project can return None, but the other must exist gitlab_project_id = find_project(gitlab_instance, gitlab_project) diff --git a/ansible_collections/community/general/plugins/modules/haproxy.py b/ansible_collections/community/general/plugins/modules/haproxy.py index 05f52d55c..cbaa43833 100644 --- a/ansible_collections/community/general/plugins/modules/haproxy.py +++ b/ansible_collections/community/general/plugins/modules/haproxy.py @@ -343,7 +343,7 @@ class HAProxy(object): if state is not None: self.execute(Template(cmd).substitute(pxname=backend, svname=svname)) - if self.wait: + if self.wait and not (wait_for_status == "DRAIN" and state == "DOWN"): self.wait_until_status(backend, svname, wait_for_status) def get_state_for(self, pxname, svname): diff --git a/ansible_collections/community/general/plugins/modules/imc_rest.py b/ansible_collections/community/general/plugins/modules/imc_rest.py index 113d341e8..7f5a5e081 100644 --- a/ansible_collections/community/general/plugins/modules/imc_rest.py +++ b/ansible_collections/community/general/plugins/modules/imc_rest.py @@ -268,7 +268,6 @@ output: errorDescr="XML PARSING ERROR: Element 'computeRackUnit', attribute 'admin_Power': The attribute 'admin_Power' is not allowed.\n"/> ''' -import datetime import os import traceback @@ -292,6 +291,10 @@ from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.six.moves import zip_longest from ansible.module_utils.urls import fetch_url +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + def imc_response(module, rawoutput, rawinput=''): ''' Handle IMC returned data ''' @@ -375,14 +378,14 @@ def main(): else: module.fail_json(msg='Cannot find/access path:\n%s' % path) - start = datetime.datetime.utcnow() + start = now() # Perform login first url = '%s://%s/nuova' % (protocol, hostname) data = '<aaaLogin inName="%s" inPassword="%s"/>' % (username, password) resp, auth = fetch_url(module, url, data=data, method='POST', timeout=timeout) if resp is None or auth['status'] != 200: - result['elapsed'] = (datetime.datetime.utcnow() - start).seconds + result['elapsed'] = (now() - start).seconds module.fail_json(msg='Task failed with error %(status)s: %(msg)s' % auth, **result) result.update(imc_response(module, resp.read())) @@ -415,7 +418,7 @@ def main(): # Perform actual request resp, info = fetch_url(module, url, data=data, method='POST', timeout=timeout) if resp is None or info['status'] != 200: - result['elapsed'] = (datetime.datetime.utcnow() - start).seconds + result['elapsed'] = (now() - start).seconds module.fail_json(msg='Task failed with error %(status)s: %(msg)s' % info, **result) # Merge results with previous results @@ -431,7 +434,7 @@ def main(): result['changed'] = ('modified' in results) # Report success - result['elapsed'] = (datetime.datetime.utcnow() - start).seconds + result['elapsed'] = (now() - start).seconds module.exit_json(**result) finally: logout(module, url, cookie, timeout) diff --git a/ansible_collections/community/general/plugins/modules/ini_file.py b/ansible_collections/community/general/plugins/modules/ini_file.py index ec71a9473..affee2a4f 100644 --- a/ansible_collections/community/general/plugins/modules/ini_file.py +++ b/ansible_collections/community/general/plugins/modules/ini_file.py @@ -44,6 +44,30 @@ options: - If being omitted, the O(option) will be placed before the first O(section). - Omitting O(section) is also required if the config format does not support sections. type: str + section_has_values: + type: list + elements: dict + required: false + suboptions: + option: + type: str + description: Matching O(section) must contain this option. + required: true + value: + type: str + description: Matching O(section_has_values[].option) must have this specific value. + values: + description: + - The string value to be associated with an O(section_has_values[].option). + - Mutually exclusive with O(section_has_values[].value). + - O(section_has_values[].value=v) is equivalent to O(section_has_values[].values=[v]). + type: list + elements: str + description: + - Among possibly multiple sections of the same name, select the first one that contains matching options and values. + - With O(state=present), if a suitable section is not found, a new section will be added, including the required options. + - With O(state=absent), at most one O(section) is removed if it contains the values. + version_added: 8.6.0 option: description: - If set (required for changing a O(value)), this is the name of the option. @@ -182,6 +206,57 @@ EXAMPLES = r''' option: beverage value: lemon juice state: present + +- name: Remove the peer configuration for 10.128.0.11/32 + community.general.ini_file: + path: /etc/wireguard/wg0.conf + section: Peer + section_has_values: + - option: AllowedIps + value: 10.128.0.11/32 + mode: '0600' + state: absent + +- name: Add "beverage=lemon juice" outside a section in specified file + community.general.ini_file: + path: /etc/conf + option: beverage + value: lemon juice + state: present + +- name: Update the public key for peer 10.128.0.12/32 + community.general.ini_file: + path: /etc/wireguard/wg0.conf + section: Peer + section_has_values: + - option: AllowedIps + value: 10.128.0.12/32 + option: PublicKey + value: xxxxxxxxxxxxxxxxxxxx + mode: '0600' + state: present + +- name: Remove the peer configuration for 10.128.0.11/32 + community.general.ini_file: + path: /etc/wireguard/wg0.conf + section: Peer + section_has_values: + - option: AllowedIps + value: 10.4.0.11/32 + mode: '0600' + state: absent + +- name: Update the public key for peer 10.128.0.12/32 + community.general.ini_file: + path: /etc/wireguard/wg0.conf + section: Peer + section_has_values: + - option: AllowedIps + value: 10.4.0.12/32 + option: PublicKey + value: xxxxxxxxxxxxxxxxxxxx + mode: '0600' + state: present ''' import io @@ -222,7 +297,19 @@ def update_section_line(option, changed, section_lines, index, changed_lines, ig return (changed, msg) -def do_ini(module, filename, section=None, option=None, values=None, +def check_section_has_values(section_has_values, section_lines): + if section_has_values is not None: + for condition in section_has_values: + for line in section_lines: + match = match_opt(condition["option"], line) + if match and (len(condition["values"]) == 0 or match.group(7) in condition["values"]): + break + else: + return False + return True + + +def do_ini(module, filename, section=None, section_has_values=None, option=None, values=None, state='present', exclusive=True, backup=False, no_extra_spaces=False, ignore_spaces=False, create=True, allow_no_value=False, modify_inactive_option=True, follow=False): @@ -307,14 +394,22 @@ def do_ini(module, filename, section=None, option=None, values=None, section_pattern = re.compile(to_text(r'^\[\s*%s\s*]' % re.escape(section.strip()))) for index, line in enumerate(ini_lines): + # end of section: + if within_section and line.startswith(u'['): + if check_section_has_values( + section_has_values, ini_lines[section_start:index] + ): + section_end = index + break + else: + # look for another section + within_section = False + section_start = section_end = 0 + # find start and end of section if section_pattern.match(line): within_section = True section_start = index - elif line.startswith(u'['): - if within_section: - section_end = index - break before = ini_lines[0:section_start] section_lines = ini_lines[section_start:section_end] @@ -435,6 +530,18 @@ def do_ini(module, filename, section=None, option=None, values=None, if not within_section and state == 'present': ini_lines.append(u'[%s]\n' % section) msg = 'section and option added' + if section_has_values: + for condition in section_has_values: + if condition['option'] != option: + if len(condition['values']) > 0: + for value in condition['values']: + ini_lines.append(assignment_format % (condition['option'], value)) + elif allow_no_value: + ini_lines.append(u'%s\n' % condition['option']) + elif not exclusive: + for value in condition['values']: + if value not in values: + values.append(value) if option and values: for value in values: ini_lines.append(assignment_format % (option, value)) @@ -476,6 +583,11 @@ def main(): argument_spec=dict( path=dict(type='path', required=True, aliases=['dest']), section=dict(type='str'), + section_has_values=dict(type='list', elements='dict', options=dict( + option=dict(type='str', required=True), + value=dict(type='str'), + values=dict(type='list', elements='str') + ), default=None, mutually_exclusive=[['value', 'values']]), option=dict(type='str'), value=dict(type='str'), values=dict(type='list', elements='str'), @@ -498,6 +610,7 @@ def main(): path = module.params['path'] section = module.params['section'] + section_has_values = module.params['section_has_values'] option = module.params['option'] value = module.params['value'] values = module.params['values'] @@ -519,8 +632,16 @@ def main(): elif values is None: values = [] + if section_has_values: + for condition in section_has_values: + if condition['value'] is not None: + condition['values'] = [condition['value']] + elif condition['values'] is None: + condition['values'] = [] +# raise Exception("section_has_values: {}".format(section_has_values)) + (changed, backup_file, diff, msg) = do_ini( - module, path, section, option, values, state, exclusive, backup, + module, path, section, section_has_values, option, values, state, exclusive, backup, no_extra_spaces, ignore_spaces, create, allow_no_value, modify_inactive_option, follow) if not module.check_mode and os.path.exists(path): diff --git a/ansible_collections/community/general/plugins/modules/java_cert.py b/ansible_collections/community/general/plugins/modules/java_cert.py index 72302b12c..e2d04b71e 100644 --- a/ansible_collections/community/general/plugins/modules/java_cert.py +++ b/ansible_collections/community/general/plugins/modules/java_cert.py @@ -28,7 +28,7 @@ options: cert_url: description: - Basic URL to fetch SSL certificate from. - - Exactly one of O(cert_url), O(cert_path), or O(pkcs12_path) is required to load certificate. + - Exactly one of O(cert_url), O(cert_path), O(cert_content), or O(pkcs12_path) is required to load certificate. type: str cert_port: description: @@ -39,8 +39,14 @@ options: cert_path: description: - Local path to load certificate from. - - Exactly one of O(cert_url), O(cert_path), or O(pkcs12_path) is required to load certificate. + - Exactly one of O(cert_url), O(cert_path), O(cert_content), or O(pkcs12_path) is required to load certificate. type: path + cert_content: + description: + - Content of the certificate used to create the keystore. + - Exactly one of O(cert_url), O(cert_path), O(cert_content), or O(pkcs12_path) is required to load certificate. + type: str + version_added: 8.6.0 cert_alias: description: - Imported certificate alias. @@ -55,10 +61,10 @@ options: pkcs12_path: description: - Local path to load PKCS12 keystore from. - - Unlike O(cert_url) and O(cert_path), the PKCS12 keystore embeds the private key matching + - Unlike O(cert_url), O(cert_path) and O(cert_content), the PKCS12 keystore embeds the private key matching the certificate, and is used to import both the certificate and its private key into the java keystore. - - Exactly one of O(cert_url), O(cert_path), or O(pkcs12_path) is required to load certificate. + - Exactly one of O(cert_url), O(cert_path), O(cert_content), or O(pkcs12_path) is required to load certificate. type: path pkcs12_password: description: @@ -149,6 +155,19 @@ EXAMPLES = r''' cert_alias: LE_RootCA trust_cacert: true +- name: Import trusted CA from the SSL certificate stored in the cert_content variable + community.general.java_cert: + cert_content: | + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + keystore_path: /tmp/cacerts + keystore_pass: changeit + keystore_create: true + state: present + cert_alias: LE_RootCA + trust_cacert: true + - name: Import SSL certificate from google.com to a keystore, create it if it doesn't exist community.general.java_cert: cert_url: google.com @@ -487,6 +506,7 @@ def main(): argument_spec = dict( cert_url=dict(type='str'), cert_path=dict(type='path'), + cert_content=dict(type='str'), pkcs12_path=dict(type='path'), pkcs12_password=dict(type='str', no_log=True), pkcs12_alias=dict(type='str'), @@ -503,11 +523,11 @@ def main(): module = AnsibleModule( argument_spec=argument_spec, - required_if=[['state', 'present', ('cert_path', 'cert_url', 'pkcs12_path'), True], + required_if=[['state', 'present', ('cert_path', 'cert_url', 'cert_content', 'pkcs12_path'), True], ['state', 'absent', ('cert_url', 'cert_alias'), True]], required_together=[['keystore_path', 'keystore_pass']], mutually_exclusive=[ - ['cert_url', 'cert_path', 'pkcs12_path'] + ['cert_url', 'cert_path', 'cert_content', 'pkcs12_path'] ], supports_check_mode=True, add_file_common_args=True, @@ -515,6 +535,7 @@ def main(): url = module.params.get('cert_url') path = module.params.get('cert_path') + content = module.params.get('cert_content') port = module.params.get('cert_port') pkcs12_path = module.params.get('pkcs12_path') @@ -582,6 +603,10 @@ def main(): # certificate to stdout so we don't need to do any transformations. new_certificate = path + elif content: + with open(new_certificate, "w") as f: + f.write(content) + elif url: # Getting the X509 digest from a URL is the same as from a path, we just have # to download the cert first diff --git a/ansible_collections/community/general/plugins/modules/keycloak_client.py b/ansible_collections/community/general/plugins/modules/keycloak_client.py index b151e4541..cd9c60bac 100644 --- a/ansible_collections/community/general/plugins/modules/keycloak_client.py +++ b/ansible_collections/community/general/plugins/modules/keycloak_client.py @@ -248,8 +248,9 @@ options: description: - Type of client. - At creation only, default value will be V(openid-connect) if O(protocol) is omitted. + - The V(docker-v2) value was added in community.general 8.6.0. type: str - choices: ['openid-connect', 'saml'] + choices: ['openid-connect', 'saml', 'docker-v2'] full_scope_allowed: description: @@ -393,7 +394,7 @@ options: protocol: description: - This specifies for which protocol this protocol mapper is active. - choices: ['openid-connect', 'saml'] + choices: ['openid-connect', 'saml', 'docker-v2'] type: str protocolMapper: @@ -724,6 +725,7 @@ import copy PROTOCOL_OPENID_CONNECT = 'openid-connect' PROTOCOL_SAML = 'saml' +PROTOCOL_DOCKER_V2 = 'docker-v2' CLIENT_META_DATA = ['authorizationServicesEnabled'] @@ -742,6 +744,12 @@ def normalise_cr(clientrep, remove_ids=False): if 'attributes' in clientrep: clientrep['attributes'] = list(sorted(clientrep['attributes'])) + if 'defaultClientScopes' in clientrep: + clientrep['defaultClientScopes'] = list(sorted(clientrep['defaultClientScopes'])) + + if 'optionalClientScopes' in clientrep: + clientrep['optionalClientScopes'] = list(sorted(clientrep['optionalClientScopes'])) + if 'redirectUris' in clientrep: clientrep['redirectUris'] = list(sorted(clientrep['redirectUris'])) @@ -785,7 +793,7 @@ def main(): consentText=dict(type='str'), id=dict(type='str'), name=dict(type='str'), - protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML]), + protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML, PROTOCOL_DOCKER_V2]), protocolMapper=dict(type='str'), config=dict(type='dict'), ) @@ -819,7 +827,7 @@ def main(): authorization_services_enabled=dict(type='bool', aliases=['authorizationServicesEnabled']), public_client=dict(type='bool', aliases=['publicClient']), frontchannel_logout=dict(type='bool', aliases=['frontchannelLogout']), - protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML]), + protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML, PROTOCOL_DOCKER_V2]), attributes=dict(type='dict'), full_scope_allowed=dict(type='bool', aliases=['fullScopeAllowed']), node_re_registration_timeout=dict(type='int', aliases=['nodeReRegistrationTimeout']), diff --git a/ansible_collections/community/general/plugins/modules/keycloak_client_rolescope.py b/ansible_collections/community/general/plugins/modules/keycloak_client_rolescope.py new file mode 100644 index 000000000..cca72f0dd --- /dev/null +++ b/ansible_collections/community/general/plugins/modules/keycloak_client_rolescope.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) Ansible project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: keycloak_client_rolescope + +short_description: Allows administration of Keycloak client roles scope to restrict the usage of certain roles to a other specific client applications. + +version_added: 8.6.0 + +description: + - This module allows you to add or remove Keycloak roles from clients scope via the Keycloak REST API. + It requires access to the REST API via OpenID Connect; the user connecting and the client being + used must have the requisite access rights. In a default Keycloak installation, admin-cli + and an admin user would work, as would a separate client definition with the scope tailored + to your needs and a user having the expected roles. + + - Client O(client_id) must have O(community.general.keycloak_client#module:full_scope_allowed) set to V(false). + + - Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and will + be returned that way by this module. You may pass single values for attributes when calling the module, + and this will be translated into a list suitable for the API. + +attributes: + check_mode: + support: full + diff_mode: + support: full + +options: + state: + description: + - State of the role mapping. + - On V(present), all roles in O(role_names) will be mapped if not exists yet. + - On V(absent), all roles mapping in O(role_names) will be removed if it exists. + default: 'present' + type: str + choices: + - present + - absent + + realm: + type: str + description: + - The Keycloak realm under which clients resides. + default: 'master' + + client_id: + type: str + required: true + description: + - Roles provided in O(role_names) while be added to this client scope. + + client_scope_id: + type: str + description: + - If the O(role_names) are client role, the client ID under which it resides. + - If this parameter is absent, the roles are considered a realm role. + role_names: + required: true + type: list + elements: str + description: + - Names of roles to manipulate. + - If O(client_scope_id) is present, all roles must be under this client. + - If O(client_scope_id) is absent, all roles must be under the realm. + + +extends_documentation_fragment: + - community.general.keycloak + - community.general.attributes + +author: + - Andre Desrosiers (@desand01) +''' + +EXAMPLES = ''' +- name: Add roles to public client scope + community.general.keycloak_client_rolescope: + auth_keycloak_url: https://auth.example.com/auth + auth_realm: master + auth_username: USERNAME + auth_password: PASSWORD + realm: MyCustomRealm + client_id: frontend-client-public + client_scope_id: backend-client-private + role_names: + - backend-role-admin + - backend-role-user + +- name: Remove roles from public client scope + community.general.keycloak_client_rolescope: + auth_keycloak_url: https://auth.example.com/auth + auth_realm: master + auth_username: USERNAME + auth_password: PASSWORD + realm: MyCustomRealm + client_id: frontend-client-public + client_scope_id: backend-client-private + role_names: + - backend-role-admin + state: absent + +- name: Add realm roles to public client scope + community.general.keycloak_client_rolescope: + auth_keycloak_url: https://auth.example.com/auth + auth_realm: master + auth_username: USERNAME + auth_password: PASSWORD + realm: MyCustomRealm + client_id: frontend-client-public + role_names: + - realm-role-admin + - realm-role-user +''' + +RETURN = ''' +msg: + description: Message as to what action was taken. + returned: always + type: str + sample: "Client role scope for frontend-client-public has been updated" + +end_state: + description: Representation of role role scope after module execution. + returned: on success + type: list + elements: dict + sample: [ + { + "clientRole": false, + "composite": false, + "containerId": "MyCustomRealm", + "id": "47293104-59a6-46f0-b460-2e9e3c9c424c", + "name": "backend-role-admin" + }, + { + "clientRole": false, + "composite": false, + "containerId": "MyCustomRealm", + "id": "39c62a6d-542c-4715-92d2-41021eb33967", + "name": "backend-role-user" + } + ] +''' + +from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \ + keycloak_argument_spec, get_token, KeycloakError +from ansible.module_utils.basic import AnsibleModule + + +def main(): + """ + Module execution + + :return: + """ + argument_spec = keycloak_argument_spec() + + meta_args = dict( + client_id=dict(type='str', required=True), + client_scope_id=dict(type='str'), + realm=dict(type='str', default='master'), + role_names=dict(type='list', elements='str', required=True), + state=dict(type='str', default='present', choices=['present', 'absent']), + ) + + argument_spec.update(meta_args) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + result = dict(changed=False, msg='', diff={}, end_state={}) + + # Obtain access token, initialize API + try: + connection_header = get_token(module.params) + except KeycloakError as e: + module.fail_json(msg=str(e)) + + kc = KeycloakAPI(module, connection_header) + + realm = module.params.get('realm') + clientid = module.params.get('client_id') + client_scope_id = module.params.get('client_scope_id') + role_names = module.params.get('role_names') + state = module.params.get('state') + + objRealm = kc.get_realm_by_id(realm) + if not objRealm: + module.fail_json(msg="Failed to retrive realm '{realm}'".format(realm=realm)) + + objClient = kc.get_client_by_clientid(clientid, realm) + if not objClient: + module.fail_json(msg="Failed to retrive client '{realm}.{clientid}'".format(realm=realm, clientid=clientid)) + if objClient["fullScopeAllowed"] and state == "present": + module.fail_json(msg="FullScopeAllowed is active for Client '{realm}.{clientid}'".format(realm=realm, clientid=clientid)) + + if client_scope_id: + objClientScope = kc.get_client_by_clientid(client_scope_id, realm) + if not objClientScope: + module.fail_json(msg="Failed to retrive client '{realm}.{client_scope_id}'".format(realm=realm, client_scope_id=client_scope_id)) + before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], objClientScope["id"], realm) + else: + before_role_mapping = kc.get_client_role_scope_from_realm(objClient["id"], realm) + + if client_scope_id: + # retrive all role from client_scope + client_scope_roles_by_name = kc.get_client_roles_by_id(objClientScope["id"], realm) + else: + # retrive all role from realm + client_scope_roles_by_name = kc.get_realm_roles(realm) + + # convert to indexed Dict by name + client_scope_roles_by_name = {role["name"]: role for role in client_scope_roles_by_name} + role_mapping_by_name = {role["name"]: role for role in before_role_mapping} + role_mapping_to_manipulate = [] + + if state == "present": + # update desired + for role_name in role_names: + if role_name not in client_scope_roles_by_name: + if client_scope_id: + module.fail_json(msg="Failed to retrive role '{realm}.{client_scope_id}.{role_name}'" + .format(realm=realm, client_scope_id=client_scope_id, role_name=role_name)) + else: + module.fail_json(msg="Failed to retrive role '{realm}.{role_name}'".format(realm=realm, role_name=role_name)) + if role_name not in role_mapping_by_name: + role_mapping_to_manipulate.append(client_scope_roles_by_name[role_name]) + role_mapping_by_name[role_name] = client_scope_roles_by_name[role_name] + else: + # remove role if present + for role_name in role_names: + if role_name in role_mapping_by_name: + role_mapping_to_manipulate.append(role_mapping_by_name[role_name]) + del role_mapping_by_name[role_name] + + before_role_mapping = sorted(before_role_mapping, key=lambda d: d['name']) + desired_role_mapping = sorted(role_mapping_by_name.values(), key=lambda d: d['name']) + + result['changed'] = len(role_mapping_to_manipulate) > 0 + + if result['changed']: + result['diff'] = dict(before=before_role_mapping, after=desired_role_mapping) + + if not result['changed']: + # no changes + result['end_state'] = before_role_mapping + result['msg'] = "No changes required for client role scope {name}.".format(name=clientid) + elif state == "present": + # doing update + if module.check_mode: + result['end_state'] = desired_role_mapping + elif client_scope_id: + result['end_state'] = kc.update_client_role_scope_from_client(role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm) + else: + result['end_state'] = kc.update_client_role_scope_from_realm(role_mapping_to_manipulate, objClient["id"], realm) + result['msg'] = "Client role scope for {name} has been updated".format(name=clientid) + else: + # doing delete + if module.check_mode: + result['end_state'] = desired_role_mapping + elif client_scope_id: + result['end_state'] = kc.delete_client_role_scope_from_client(role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm) + else: + result['end_state'] = kc.delete_client_role_scope_from_realm(role_mapping_to_manipulate, objClient["id"], realm) + result['msg'] = "Client role scope for {name} has been deleted".format(name=clientid) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/community/general/plugins/modules/keycloak_clientscope.py b/ansible_collections/community/general/plugins/modules/keycloak_clientscope.py index d37af5f0c..d24e0f1f2 100644 --- a/ansible_collections/community/general/plugins/modules/keycloak_clientscope.py +++ b/ansible_collections/community/general/plugins/modules/keycloak_clientscope.py @@ -79,7 +79,8 @@ options: protocol: description: - Type of client. - choices: ['openid-connect', 'saml', 'wsfed'] + - The V(docker-v2) value was added in community.general 8.6.0. + choices: ['openid-connect', 'saml', 'wsfed', 'docker-v2'] type: str protocol_mappers: @@ -95,7 +96,7 @@ options: description: - This specifies for which protocol this protocol mapper. - is active. - choices: ['openid-connect', 'saml', 'wsfed'] + choices: ['openid-connect', 'saml', 'wsfed', 'docker-v2'] type: str protocolMapper: @@ -330,7 +331,7 @@ def main(): protmapper_spec = dict( id=dict(type='str'), name=dict(type='str'), - protocol=dict(type='str', choices=['openid-connect', 'saml', 'wsfed']), + protocol=dict(type='str', choices=['openid-connect', 'saml', 'wsfed', 'docker-v2']), protocolMapper=dict(type='str'), config=dict(type='dict'), ) @@ -341,7 +342,7 @@ def main(): id=dict(type='str'), name=dict(type='str'), description=dict(type='str'), - protocol=dict(type='str', choices=['openid-connect', 'saml', 'wsfed']), + protocol=dict(type='str', choices=['openid-connect', 'saml', 'wsfed', 'docker-v2']), attributes=dict(type='dict'), protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']), ) diff --git a/ansible_collections/community/general/plugins/modules/keycloak_clienttemplate.py b/ansible_collections/community/general/plugins/modules/keycloak_clienttemplate.py index cd7f6c09b..7bffb5cbb 100644 --- a/ansible_collections/community/general/plugins/modules/keycloak_clienttemplate.py +++ b/ansible_collections/community/general/plugins/modules/keycloak_clienttemplate.py @@ -68,7 +68,8 @@ options: protocol: description: - Type of client template. - choices: ['openid-connect', 'saml'] + - The V(docker-v2) value was added in community.general 8.6.0. + choices: ['openid-connect', 'saml', 'docker-v2'] type: str full_scope_allowed: @@ -107,7 +108,7 @@ options: protocol: description: - This specifies for which protocol this protocol mapper is active. - choices: ['openid-connect', 'saml'] + choices: ['openid-connect', 'saml', 'docker-v2'] type: str protocolMapper: @@ -292,7 +293,7 @@ def main(): consentText=dict(type='str'), id=dict(type='str'), name=dict(type='str'), - protocol=dict(type='str', choices=['openid-connect', 'saml']), + protocol=dict(type='str', choices=['openid-connect', 'saml', 'docker-v2']), protocolMapper=dict(type='str'), config=dict(type='dict'), ) @@ -304,7 +305,7 @@ def main(): id=dict(type='str'), name=dict(type='str'), description=dict(type='str'), - protocol=dict(type='str', choices=['openid-connect', 'saml']), + protocol=dict(type='str', choices=['openid-connect', 'saml', 'docker-v2']), attributes=dict(type='dict'), full_scope_allowed=dict(type='bool'), protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec), diff --git a/ansible_collections/community/general/plugins/modules/keycloak_realm.py b/ansible_collections/community/general/plugins/modules/keycloak_realm.py index 9f2e72b52..6128c9e4c 100644 --- a/ansible_collections/community/general/plugins/modules/keycloak_realm.py +++ b/ansible_collections/community/general/plugins/modules/keycloak_realm.py @@ -582,6 +582,27 @@ from ansible_collections.community.general.plugins.module_utils.identity.keycloa from ansible.module_utils.basic import AnsibleModule +def normalise_cr(realmrep): + """ Re-sorts any properties where the order is important so that diff's is minimised and the change detection is more effective. + + :param realmrep: the realmrep dict to be sanitized + :return: normalised realmrep dict + """ + # Avoid the dict passed in to be modified + realmrep = realmrep.copy() + + if 'enabledEventTypes' in realmrep: + realmrep['enabledEventTypes'] = list(sorted(realmrep['enabledEventTypes'])) + + if 'otpSupportedApplications' in realmrep: + realmrep['otpSupportedApplications'] = list(sorted(realmrep['otpSupportedApplications'])) + + if 'supportedLocales' in realmrep: + realmrep['supportedLocales'] = list(sorted(realmrep['supportedLocales'])) + + return realmrep + + def sanitize_cr(realmrep): """ Removes probably sensitive details from a realm representation. @@ -595,7 +616,7 @@ def sanitize_cr(realmrep): if 'saml.signing.private.key' in result['attributes']: result['attributes'] = result['attributes'].copy() result['attributes']['saml.signing.private.key'] = '********' - return result + return normalise_cr(result) def main(): @@ -777,9 +798,11 @@ def main(): result['changed'] = True if module.check_mode: # We can only compare the current realm with the proposed updates we have + before_norm = normalise_cr(before_realm) + desired_norm = normalise_cr(desired_realm) if module._diff: - result['diff'] = dict(before=before_realm_sanitized, - after=sanitize_cr(desired_realm)) + result['diff'] = dict(before=sanitize_cr(before_norm), + after=sanitize_cr(desired_norm)) result['changed'] = (before_realm != desired_realm) module.exit_json(**result) diff --git a/ansible_collections/community/general/plugins/modules/lxd_container.py b/ansible_collections/community/general/plugins/modules/lxd_container.py index 9fd1b183b..b82e2be9b 100644 --- a/ansible_collections/community/general/plugins/modules/lxd_container.py +++ b/ansible_collections/community/general/plugins/modules/lxd_container.py @@ -86,8 +86,8 @@ options: source: description: - 'The source for the instance - (for example V({ "type": "image", "mode": "pull", "server": "https://images.linuxcontainers.org", - "protocol": "lxd", "alias": "ubuntu/xenial/amd64" })).' + (for example V({ "type": "image", "mode": "pull", "server": "https://cloud-images.ubuntu.com/releases/", + "protocol": "simplestreams", "alias": "22.04" })).' - 'See U(https://documentation.ubuntu.com/lxd/en/latest/api/) for complete API documentation.' - 'Note that C(protocol) accepts two choices: V(lxd) or V(simplestreams).' required: false @@ -205,6 +205,9 @@ notes: - You can copy a file in the created instance to the localhost with C(command=lxc file pull instance_name/dir/filename filename). See the first example below. + - linuxcontainers.org has phased out LXC/LXD support with March 2024 + (U(https://discuss.linuxcontainers.org/t/important-notice-for-lxd-users-image-server/18479)). + Currently only Ubuntu is still providing images. ''' EXAMPLES = ''' @@ -220,9 +223,9 @@ EXAMPLES = ''' source: type: image mode: pull - server: https://images.linuxcontainers.org - protocol: lxd # if you get a 404, try setting protocol: simplestreams - alias: ubuntu/xenial/amd64 + server: https://cloud-images.ubuntu.com/releases/ + protocol: simplestreams + alias: "22.04" profiles: ["default"] wait_for_ipv4_addresses: true timeout: 600 @@ -264,6 +267,26 @@ EXAMPLES = ''' wait_for_ipv4_addresses: true timeout: 600 +# An example of creating a ubuntu-minial container +- hosts: localhost + connection: local + tasks: + - name: Create a started container + community.general.lxd_container: + name: mycontainer + ignore_volatile_options: true + state: started + source: + type: image + mode: pull + # Provides Ubuntu minimal images + server: https://cloud-images.ubuntu.com/minimal/releases/ + protocol: simplestreams + alias: "22.04" + profiles: ["default"] + wait_for_ipv4_addresses: true + timeout: 600 + # An example for creating container in project other than default - hosts: localhost connection: local @@ -278,8 +301,8 @@ EXAMPLES = ''' protocol: simplestreams type: image mode: pull - server: https://images.linuxcontainers.org - alias: ubuntu/20.04/cloud + server: https://cloud-images.ubuntu.com/releases/ + alias: "22.04" profiles: ["default"] wait_for_ipv4_addresses: true timeout: 600 @@ -347,7 +370,7 @@ EXAMPLES = ''' source: type: image mode: pull - alias: ubuntu/xenial/amd64 + alias: "22.04" target: node01 - name: Create container on another node @@ -358,7 +381,7 @@ EXAMPLES = ''' source: type: image mode: pull - alias: ubuntu/xenial/amd64 + alias: "22.04" target: node02 # An example for creating a virtual machine diff --git a/ansible_collections/community/general/plugins/modules/nmcli.py b/ansible_collections/community/general/plugins/modules/nmcli.py index 9360ce37d..6f0884da9 100644 --- a/ansible_collections/community/general/plugins/modules/nmcli.py +++ b/ansible_collections/community/general/plugins/modules/nmcli.py @@ -64,13 +64,16 @@ options: - Type V(infiniband) is added in community.general 2.0.0. - Type V(loopback) is added in community.general 8.1.0. - Type V(macvlan) is added in community.general 6.6.0. + - Type V(ovs-bridge) is added in community.general 8.6.0. + - Type V(ovs-interface) is added in community.general 8.6.0. + - Type V(ovs-port) is added in community.general 8.6.0. - Type V(wireguard) is added in community.general 4.3.0. - Type V(vpn) is added in community.general 5.1.0. - Using V(bond-slave), V(bridge-slave), or V(team-slave) implies V(ethernet) connection type with corresponding O(slave_type) option. - If you want to control non-ethernet connection attached to V(bond), V(bridge), or V(team) consider using O(slave_type) option. type: str choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, macvlan, sit, team, team-slave, vlan, vxlan, - wifi, gsm, wireguard, vpn, loopback ] + wifi, gsm, wireguard, ovs-bridge, ovs-port, ovs-interface, vpn, loopback ] mode: description: - This is the type of device or network connection that you wish to create for a bond or bridge. @@ -86,12 +89,13 @@ options: slave_type: description: - Type of the device of this slave's master connection (for example V(bond)). + - Type V(ovs-port) is added in community.general 8.6.0. type: str - choices: [ 'bond', 'bridge', 'team' ] + choices: [ 'bond', 'bridge', 'team', 'ovs-port' ] version_added: 7.0.0 master: description: - - Master <master (ifname, or connection UUID or conn_name) of bridge, team, bond master connection profile. + - Master <master (ifname, or connection UUID or conn_name) of bridge, team, bond, ovs-port master connection profile. - Mandatory if O(slave_type) is defined. type: str ip4: @@ -1505,6 +1509,32 @@ EXAMPLES = r''' table: "production" routing_rules4: - "priority 0 from 192.168.1.50 table 200" + +## Creating an OVS bridge and attaching a port +- name: Create OVS Bridge + community.general.nmcli: + conn_name: ovs-br-conn + ifname: ovs-br + type: ovs-bridge + state: present + +- name: Create OVS Port for OVS Bridge Interface + community.general.nmcli: + conn_name: ovs-br-interface-port-conn + ifname: ovs-br-interface-port + master: ovs-br + type: ovs-port + state: present + +## Adding an ethernet interface to an OVS bridge port +- name: Add Ethernet Interface to OVS Port + community.general.nmcli: + conn_name: eno1 + ifname: eno1 + master: ovs-br-interface-port + slave_type: ovs-port + type: ethernet + state: present ''' RETURN = r"""# @@ -1678,7 +1708,8 @@ class Nmcli(object): } # IP address options. - if self.ip_conn_type and not self.master: + # The ovs-interface type can be both ip_conn_type and have a master + if (self.ip_conn_type and not self.master) or self.type == "ovs-interface": options.update({ 'ipv4.addresses': self.enforce_ipv4_cidr_notation(self.ip4), 'ipv4.dhcp-client-id': self.dhcp_client_id, @@ -1939,6 +1970,7 @@ class Nmcli(object): 'wireguard', 'vpn', 'loopback', + 'ovs-interface', ) @property @@ -2005,6 +2037,8 @@ class Nmcli(object): 'team-slave', 'wifi', 'infiniband', + 'ovs-port', + 'ovs-interface', ) @property @@ -2400,7 +2434,7 @@ def main(): state=dict(type='str', required=True, choices=['absent', 'present']), conn_name=dict(type='str', required=True), master=dict(type='str'), - slave_type=dict(type='str', choices=['bond', 'bridge', 'team']), + slave_type=dict(type='str', choices=['bond', 'bridge', 'team', 'ovs-port']), ifname=dict(type='str'), type=dict(type='str', choices=[ @@ -2425,6 +2459,9 @@ def main(): 'wireguard', 'vpn', 'loopback', + 'ovs-interface', + 'ovs-bridge', + 'ovs-port', ]), ip4=dict(type='list', elements='str'), gw4=dict(type='str'), diff --git a/ansible_collections/community/general/plugins/modules/osx_defaults.py b/ansible_collections/community/general/plugins/modules/osx_defaults.py index 336e95332..db5d889a3 100644 --- a/ansible_collections/community/general/plugins/modules/osx_defaults.py +++ b/ansible_collections/community/general/plugins/modules/osx_defaults.py @@ -50,6 +50,13 @@ options: type: str choices: [ array, bool, boolean, date, float, int, integer, string ] default: string + check_type: + description: + - Checks if the type of the provided O(value) matches the type of an existing default. + - If the types do not match, raises an error. + type: bool + default: true + version_added: 8.6.0 array_add: description: - Add new elements to the array for a key which has an array as its value. @@ -158,6 +165,7 @@ class OSXDefaults(object): self.domain = module.params['domain'] self.host = module.params['host'] self.key = module.params['key'] + self.check_type = module.params['check_type'] self.type = module.params['type'] self.array_add = module.params['array_add'] self.value = module.params['value'] @@ -349,10 +357,11 @@ class OSXDefaults(object): self.delete() return True - # There is a type mismatch! Given type does not match the type in defaults - value_type = type(self.value) - if self.current_value is not None and not isinstance(self.current_value, value_type): - raise OSXDefaultsException("Type mismatch. Type in defaults: %s" % type(self.current_value).__name__) + # Check if there is a type mismatch, e.g. given type does not match the type in defaults + if self.check_type: + value_type = type(self.value) + if self.current_value is not None and not isinstance(self.current_value, value_type): + raise OSXDefaultsException("Type mismatch. Type in defaults: %s" % type(self.current_value).__name__) # Current value matches the given value. Nothing need to be done. Arrays need extra care if self.type == "array" and self.current_value is not None and not self.array_add and \ @@ -383,6 +392,7 @@ def main(): domain=dict(type='str', default='NSGlobalDomain'), host=dict(type='str'), key=dict(type='str', no_log=False), + check_type=dict(type='bool', default=True), type=dict(type='str', default='string', choices=['array', 'bool', 'boolean', 'date', 'float', 'int', 'integer', 'string']), array_add=dict(type='bool', default=False), value=dict(type='raw'), diff --git a/ansible_collections/community/general/plugins/modules/pagerduty.py b/ansible_collections/community/general/plugins/modules/pagerduty.py index 596c4f4da..853bd6d79 100644 --- a/ansible_collections/community/general/plugins/modules/pagerduty.py +++ b/ansible_collections/community/general/plugins/modules/pagerduty.py @@ -151,6 +151,10 @@ import json from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.urls import fetch_url +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + class PagerDutyRequest(object): def __init__(self, module, name, user, token): @@ -206,9 +210,9 @@ class PagerDutyRequest(object): return [{'id': service, 'type': 'service_reference'}] def _compute_start_end_time(self, hours, minutes): - now = datetime.datetime.utcnow() - later = now + datetime.timedelta(hours=int(hours), minutes=int(minutes)) - start = now.strftime("%Y-%m-%dT%H:%M:%SZ") + now_t = now() + later = now_t + datetime.timedelta(hours=int(hours), minutes=int(minutes)) + start = now_t.strftime("%Y-%m-%dT%H:%M:%SZ") end = later.strftime("%Y-%m-%dT%H:%M:%SZ") return start, end diff --git a/ansible_collections/community/general/plugins/modules/pagerduty_change.py b/ansible_collections/community/general/plugins/modules/pagerduty_change.py index 1a1e50dcf..acd31fb44 100644 --- a/ansible_collections/community/general/plugins/modules/pagerduty_change.py +++ b/ansible_collections/community/general/plugins/modules/pagerduty_change.py @@ -110,7 +110,10 @@ EXAMPLES = ''' from ansible.module_utils.urls import fetch_url from ansible.module_utils.basic import AnsibleModule -from datetime import datetime + +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) def main(): @@ -161,8 +164,7 @@ def main(): if module.params['environment']: custom_details['environment'] = module.params['environment'] - now = datetime.utcnow() - timestamp = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + timestamp = now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") payload = { 'summary': module.params['summary'], diff --git a/ansible_collections/community/general/plugins/modules/portage.py b/ansible_collections/community/general/plugins/modules/portage.py index 112f6d2d7..8ae8efb08 100644 --- a/ansible_collections/community/general/plugins/modules/portage.py +++ b/ansible_collections/community/general/plugins/modules/portage.py @@ -121,6 +121,14 @@ options: type: bool default: false + select: + description: + - If set to V(true), explicitely add the package to the world file. + - Please note that this option is not used for idempotency, it is only used + when actually installing a package. + type: bool + version_added: 8.6.0 + sync: description: - Sync package repositories first @@ -374,6 +382,7 @@ def emerge_packages(module, packages): 'loadavg': '--load-average', 'backtrack': '--backtrack', 'withbdeps': '--with-bdeps', + 'select': '--select', } for flag, arg in emerge_flags.items(): @@ -523,6 +532,7 @@ def main(): nodeps=dict(default=False, type='bool'), onlydeps=dict(default=False, type='bool'), depclean=dict(default=False, type='bool'), + select=dict(default=None, type='bool'), quiet=dict(default=False, type='bool'), verbose=dict(default=False, type='bool'), sync=dict(default=None, choices=['yes', 'web', 'no']), @@ -543,6 +553,7 @@ def main(): ['quiet', 'verbose'], ['quietbuild', 'verbose'], ['quietfail', 'verbose'], + ['oneshot', 'select'], ], supports_check_mode=True, ) diff --git a/ansible_collections/community/general/plugins/modules/puppet.py b/ansible_collections/community/general/plugins/modules/puppet.py index 86eac062a..b28583fe0 100644 --- a/ansible_collections/community/general/plugins/modules/puppet.py +++ b/ansible_collections/community/general/plugins/modules/puppet.py @@ -116,6 +116,15 @@ options: - Whether to print file changes details type: bool default: false + environment_lang: + description: + - The lang environment to use when running the puppet agent. + - The default value, V(C), is supported on every system, but can lead to encoding errors if UTF-8 is used in the output + - Use V(C.UTF-8) or V(en_US.UTF-8) or similar UTF-8 supporting locales in case of problems. You need to make sure + the selected locale is supported on the system the puppet agent runs on. + type: str + default: C + version_added: 8.6.0 requirements: - puppet author: @@ -208,6 +217,7 @@ def main(): debug=dict(type='bool', default=False), verbose=dict(type='bool', default=False), use_srv_records=dict(type='bool'), + environment_lang=dict(type='str', default='C'), ), supports_check_mode=True, mutually_exclusive=[ diff --git a/ansible_collections/community/general/plugins/modules/redfish_command.py b/ansible_collections/community/general/plugins/modules/redfish_command.py index e66380493..06224235a 100644 --- a/ansible_collections/community/general/plugins/modules/redfish_command.py +++ b/ansible_collections/community/general/plugins/modules/redfish_command.py @@ -281,6 +281,12 @@ options: - BIOS attributes that needs to be verified in the given server. type: dict version_added: 6.4.0 + reset_to_defaults_mode: + description: + - Mode to apply when reseting to default. + type: str + choices: [ ResetAll, PreserveNetworkAndUsers, PreserveNetwork ] + version_added: 8.6.0 author: - "Jose Delarosa (@jose-delarosa)" @@ -714,6 +720,13 @@ EXAMPLES = ''' command: PowerReboot resource_id: BMC + - name: Factory reset manager to defaults + community.general.redfish_command: + category: Manager + command: ResetToDefaults + resource_id: BMC + reset_to_defaults_mode: ResetAll + - name: Verify BIOS attributes community.general.redfish_command: category: Systems @@ -764,6 +777,7 @@ CATEGORY_COMMANDS_ALL = { "UpdateAccountServiceProperties"], "Sessions": ["ClearSessions", "CreateSession", "DeleteSession"], "Manager": ["GracefulRestart", "ClearLogs", "VirtualMediaInsert", + "ResetToDefaults", "VirtualMediaEject", "PowerOn", "PowerForceOff", "PowerForceRestart", "PowerGracefulRestart", "PowerGracefulShutdown", "PowerReboot"], "Update": ["SimpleUpdate", "MultipartHTTPPushUpdate", "PerformRequestedOperations"], @@ -825,6 +839,7 @@ def main(): ) ), strip_etag_quotes=dict(type='bool', default=False), + reset_to_defaults_mode=dict(choices=['ResetAll', 'PreserveNetworkAndUsers', 'PreserveNetwork']), bios_attributes=dict(type="dict") ), required_together=[ @@ -1017,6 +1032,8 @@ def main(): result = rf_utils.virtual_media_insert(virtual_media, category) elif command == 'VirtualMediaEject': result = rf_utils.virtual_media_eject(virtual_media, category) + elif command == 'ResetToDefaults': + result = rf_utils.manager_reset_to_defaults(module.params['reset_to_defaults_mode']) elif category == "Update": # execute only if we find UpdateService resources diff --git a/ansible_collections/community/general/plugins/modules/riak.py b/ansible_collections/community/general/plugins/modules/riak.py index fe295d2d6..438263da2 100644 --- a/ansible_collections/community/general/plugins/modules/riak.py +++ b/ansible_collections/community/general/plugins/modules/riak.py @@ -93,7 +93,7 @@ from ansible.module_utils.urls import fetch_url def ring_check(module, riak_admin_bin): - cmd = '%s ringready' % riak_admin_bin + cmd = riak_admin_bin + ['ringready'] rc, out, err = module.run_command(cmd) if rc == 0 and 'TRUE All nodes agree on the ring' in out: return True @@ -127,6 +127,7 @@ def main(): # make sure riak commands are on the path riak_bin = module.get_bin_path('riak') riak_admin_bin = module.get_bin_path('riak-admin') + riak_admin_bin = [riak_admin_bin] if riak_admin_bin is not None else [riak_bin, 'admin'] timeout = time.time() + 120 while True: @@ -164,7 +165,7 @@ def main(): module.fail_json(msg=out) elif command == 'kv_test': - cmd = '%s test' % riak_admin_bin + cmd = riak_admin_bin + ['test'] rc, out, err = module.run_command(cmd) if rc == 0: result['kv_test'] = out @@ -175,7 +176,7 @@ def main(): if nodes.count(node_name) == 1 and len(nodes) > 1: result['join'] = 'Node is already in cluster or staged to be in cluster.' else: - cmd = '%s cluster join %s' % (riak_admin_bin, target_node) + cmd = riak_admin_bin + ['cluster', 'join', target_node] rc, out, err = module.run_command(cmd) if rc == 0: result['join'] = out @@ -184,7 +185,7 @@ def main(): module.fail_json(msg=out) elif command == 'plan': - cmd = '%s cluster plan' % riak_admin_bin + cmd = riak_admin_bin + ['cluster', 'plan'] rc, out, err = module.run_command(cmd) if rc == 0: result['plan'] = out @@ -194,7 +195,7 @@ def main(): module.fail_json(msg=out) elif command == 'commit': - cmd = '%s cluster commit' % riak_admin_bin + cmd = riak_admin_bin + ['cluster', 'commit'] rc, out, err = module.run_command(cmd) if rc == 0: result['commit'] = out @@ -206,7 +207,7 @@ def main(): if wait_for_handoffs: timeout = time.time() + wait_for_handoffs while True: - cmd = '%s transfers' % riak_admin_bin + cmd = riak_admin_bin + ['transfers'] rc, out, err = module.run_command(cmd) if 'No transfers active' in out: result['handoffs'] = 'No transfers active.' @@ -216,7 +217,7 @@ def main(): module.fail_json(msg='Timeout waiting for handoffs.') if wait_for_service: - cmd = [riak_admin_bin, 'wait_for_service', 'riak_%s' % wait_for_service, node_name] + cmd = riak_admin_bin + ['wait_for_service', 'riak_%s' % wait_for_service, node_name] rc, out, err = module.run_command(cmd) result['service'] = out diff --git a/ansible_collections/community/general/plugins/modules/scaleway_compute.py b/ansible_collections/community/general/plugins/modules/scaleway_compute.py index 7f85bc668..58a321505 100644 --- a/ansible_collections/community/general/plugins/modules/scaleway_compute.py +++ b/ansible_collections/community/general/plugins/modules/scaleway_compute.py @@ -183,6 +183,7 @@ import datetime import time from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.general.plugins.module_utils.datetime import now from ansible_collections.community.general.plugins.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway SCALEWAY_SERVER_STATES = ( @@ -235,9 +236,9 @@ def wait_to_complete_state_transition(compute_api, server, wait=None): wait_timeout = compute_api.module.params["wait_timeout"] wait_sleep_time = compute_api.module.params["wait_sleep_time"] - start = datetime.datetime.utcnow() + start = now() end = start + datetime.timedelta(seconds=wait_timeout) - while datetime.datetime.utcnow() < end: + while now() < end: compute_api.module.debug("We are going to wait for the server to finish its transition") if fetch_state(compute_api, server) not in SCALEWAY_TRANSITIONS_STATES: compute_api.module.debug("It seems that the server is not in transition anymore.") diff --git a/ansible_collections/community/general/plugins/modules/scaleway_database_backup.py b/ansible_collections/community/general/plugins/modules/scaleway_database_backup.py index 592ec0b7f..1d0c17fb6 100644 --- a/ansible_collections/community/general/plugins/modules/scaleway_database_backup.py +++ b/ansible_collections/community/general/plugins/modules/scaleway_database_backup.py @@ -170,6 +170,9 @@ import datetime import time from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) from ansible_collections.community.general.plugins.module_utils.scaleway import ( Scaleway, scaleway_argument_spec, @@ -189,9 +192,9 @@ def wait_to_complete_state_transition(module, account_api, backup=None): if backup is None or backup['status'] in stable_states: return backup - start = datetime.datetime.utcnow() + start = now() end = start + datetime.timedelta(seconds=wait_timeout) - while datetime.datetime.utcnow() < end: + while now() < end: module.debug('We are going to wait for the backup to finish its transition') response = account_api.get('/rdb/v1/regions/%s/backups/%s' % (module.params.get('region'), backup['id'])) diff --git a/ansible_collections/community/general/plugins/modules/scaleway_lb.py b/ansible_collections/community/general/plugins/modules/scaleway_lb.py index 3e43a8ae2..5bd16c3f4 100644 --- a/ansible_collections/community/general/plugins/modules/scaleway_lb.py +++ b/ansible_collections/community/general/plugins/modules/scaleway_lb.py @@ -161,6 +161,7 @@ RETURNS = ''' import datetime import time from ansible.module_utils.basic import AnsibleModule +from ansible_collections.community.general.plugins.module_utils.datetime import now from ansible_collections.community.general.plugins.module_utils.scaleway import SCALEWAY_REGIONS, SCALEWAY_ENDPOINT, scaleway_argument_spec, Scaleway STABLE_STATES = ( @@ -208,9 +209,9 @@ def wait_to_complete_state_transition(api, lb, force_wait=False): wait_timeout = api.module.params["wait_timeout"] wait_sleep_time = api.module.params["wait_sleep_time"] - start = datetime.datetime.utcnow() + start = now() end = start + datetime.timedelta(seconds=wait_timeout) - while datetime.datetime.utcnow() < end: + while now() < end: api.module.debug("We are going to wait for the load-balancer to finish its transition") state = fetch_state(api, lb) if state in STABLE_STATES: diff --git a/ansible_collections/community/general/plugins/modules/ssh_config.py b/ansible_collections/community/general/plugins/modules/ssh_config.py index e89e087b3..d974f4537 100644 --- a/ansible_collections/community/general/plugins/modules/ssh_config.py +++ b/ansible_collections/community/general/plugins/modules/ssh_config.py @@ -88,7 +88,8 @@ options: strict_host_key_checking: description: - Whether to strictly check the host key when doing connections to the remote host. - choices: [ 'yes', 'no', 'ask' ] + - The value V(accept-new) is supported since community.general 8.6.0. + choices: [ 'yes', 'no', 'ask', 'accept-new' ] type: str proxycommand: description: @@ -370,7 +371,7 @@ def main(): strict_host_key_checking=dict( type='str', default=None, - choices=['yes', 'no', 'ask'] + choices=['yes', 'no', 'ask', 'accept-new'], ), controlmaster=dict(type='str', default=None, choices=['yes', 'no', 'ask', 'auto', 'autoask']), controlpath=dict(type='str', default=None), diff --git a/ansible_collections/community/general/plugins/modules/statusio_maintenance.py b/ansible_collections/community/general/plugins/modules/statusio_maintenance.py index e6b34b709..0a96d0fb4 100644 --- a/ansible_collections/community/general/plugins/modules/statusio_maintenance.py +++ b/ansible_collections/community/general/plugins/modules/statusio_maintenance.py @@ -188,6 +188,10 @@ from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.urls import open_url +from ansible_collections.community.general.plugins.module_utils.datetime import ( + now, +) + def get_api_auth_headers(api_id, api_key, url, statuspage): @@ -270,11 +274,11 @@ def get_date_time(start_date, start_time, minutes): except (NameError, ValueError): return 1, None, "Couldn't work out a valid date" else: - now = datetime.datetime.utcnow() - delta = now + datetime.timedelta(minutes=minutes) + now_t = now() + delta = now_t + datetime.timedelta(minutes=minutes) # start_date - returned_date.append(now.strftime("%m/%d/%Y")) - returned_date.append(now.strftime("%H:%M")) + returned_date.append(now_t.strftime("%m/%d/%Y")) + returned_date.append(now_t.strftime("%H:%M")) # end_date returned_date.append(delta.strftime("%m/%d/%Y")) returned_date.append(delta.strftime("%H:%M")) diff --git a/ansible_collections/community/general/plugins/modules/xml.py b/ansible_collections/community/general/plugins/modules/xml.py index a3c12b8ee..f5cdbeac3 100644 --- a/ansible_collections/community/general/plugins/modules/xml.py +++ b/ansible_collections/community/general/plugins/modules/xml.py @@ -436,11 +436,16 @@ def is_attribute(tree, xpath, namespaces): """ Test if a given xpath matches and that match is an attribute An xpath attribute search will only match one item""" + + # lxml 5.1.1 removed etree._ElementStringResult, so we can no longer simply assume it's there + # (https://github.com/lxml/lxml/commit/eba79343d0e7ad1ce40169f60460cdd4caa29eb3) + ElementStringResult = getattr(etree, '_ElementStringResult', None) + if xpath_matches(tree, xpath, namespaces): match = tree.xpath(xpath, namespaces=namespaces) - if isinstance(match[0], etree._ElementStringResult): + if isinstance(match[0], etree._ElementUnicodeResult): return True - elif isinstance(match[0], etree._ElementUnicodeResult): + elif ElementStringResult is not None and isinstance(match[0], ElementStringResult): return True return False diff --git a/ansible_collections/community/general/plugins/plugin_utils/unsafe.py b/ansible_collections/community/general/plugins/plugin_utils/unsafe.py new file mode 100644 index 000000000..1eb61bea0 --- /dev/null +++ b/ansible_collections/community/general/plugins/plugin_utils/unsafe.py @@ -0,0 +1,41 @@ +# Copyright (c) 2023, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils.six import binary_type, text_type +from ansible.module_utils.common._collections_compat import Mapping, Set +from ansible.module_utils.common.collections import is_sequence +from ansible.utils.unsafe_proxy import ( + AnsibleUnsafe, + wrap_var as _make_unsafe, +) + +_RE_TEMPLATE_CHARS = re.compile(u'[{}]') +_RE_TEMPLATE_CHARS_BYTES = re.compile(b'[{}]') + + +def make_unsafe(value): + if value is None or isinstance(value, AnsibleUnsafe): + return value + + if isinstance(value, Mapping): + return dict((make_unsafe(key), make_unsafe(val)) for key, val in value.items()) + elif isinstance(value, Set): + return set(make_unsafe(elt) for elt in value) + elif is_sequence(value): + return type(value)(make_unsafe(elt) for elt in value) + elif isinstance(value, binary_type): + if _RE_TEMPLATE_CHARS_BYTES.search(value): + value = _make_unsafe(value) + return value + elif isinstance(value, text_type): + if _RE_TEMPLATE_CHARS.search(value): + value = _make_unsafe(value) + return value + + return value diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml index ec446d241..7ff30bcd5 100644 --- a/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml @@ -15,6 +15,7 @@ tested_filesystems: # - 1.7.0 requires at least 30Mo # - 1.10.0 requires at least 38Mo # - resizefs asserts when initial fs is smaller than 60Mo and seems to require 1.10.0 + bcachefs: {fssize: 20, grow: true, new_uuid: null} ext4: {fssize: 10, grow: true, new_uuid: 'random'} ext4dev: {fssize: 10, grow: true, new_uuid: 'random'} ext3: {fssize: 10, grow: true, new_uuid: 'random'} diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml index 0c15c2155..51361079c 100644 --- a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml @@ -36,7 +36,7 @@ # Not available: btrfs, lvm, f2fs, ocfs2 # All BSD systems use swap fs, but only Linux needs mkswap # Supported: ext2/3/4 (e2fsprogs), xfs (xfsprogs), reiserfs (progsreiserfs), vfat - - 'not (ansible_system == "FreeBSD" and item.0.key in ["btrfs", "f2fs", "swap", "lvm", "ocfs2"])' + - 'not (ansible_system == "FreeBSD" and item.0.key in ["bcachefs", "btrfs", "f2fs", "swap", "lvm", "ocfs2"])' # Available on FreeBSD but not on testbed (util-linux conflicts with e2fsprogs): wipefs, mkfs.minix - 'not (ansible_system == "FreeBSD" and item.1 in ["overwrite_another_fs", "remove_fs"])' @@ -46,6 +46,10 @@ # Other limitations and corner cases + # bcachefs only on Alpine > 3.18 and Arch Linux for now + # other distributions have too old versions of bcachefs-tools and/or util-linux (blkid for UUID tests) + - 'ansible_distribution == "Alpine" and ansible_distribution_version is version("3.18", ">") and item.0.key == "bcachefs"' + - 'ansible_distribution == "Archlinux" and item.0.key == "bcachefs"' # f2fs-tools and reiserfs-utils packages not available with RHEL/CentOS on CI - 'not (ansible_distribution in ["CentOS", "RedHat"] and item.0.key in ["f2fs", "reiserfs"])' - 'not (ansible_os_family == "RedHat" and ansible_distribution_major_version is version("8", ">=") and diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/setup.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/setup.yml index 97dafaeee..77c028aca 100644 --- a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/setup.yml +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/setup.yml @@ -16,6 +16,16 @@ - e2fsprogs - xfsprogs +- name: "Install bcachefs tools" + ansible.builtin.package: + name: bcachefs-tools + state: present + when: + # bcachefs only on Alpine > 3.18 and Arch Linux for now + # other distributions have too old versions of bcachefs-tools and/or util-linux (blkid for UUID tests) + - ansible_distribution == "Alpine" and ansible_distribution_version is version("3.18", ">") + - ansible_distribution == "Archlinux" + - name: "Install btrfs progs" ansible.builtin.package: name: btrfs-progs diff --git a/ansible_collections/community/general/tests/integration/targets/filter_from_ini/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_from_ini/tasks/main.yml index a2eca36a6..abb92dfc5 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_from_ini/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/filter_from_ini/tasks/main.yml @@ -12,15 +12,21 @@ another_section: connection: 'ssh' + interpolate_test: + interpolate_test_key: '%' + - name: 'Write INI file that reflects ini_test_dict to {{ ini_test_file }}' ansible.builtin.copy: dest: '{{ ini_test_file }}' content: | [section_name] - key_name=key value + key_name = key value [another_section] - connection=ssh + connection = ssh + + [interpolate_test] + interpolate_test_key = % - name: 'Slurp the test file: {{ ini_test_file }}' ansible.builtin.slurp: diff --git a/ansible_collections/community/general/tests/integration/targets/filter_to_ini/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_to_ini/tasks/main.yml index 877d4471d..e16aa98a5 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_to_ini/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/filter_to_ini/tasks/main.yml @@ -16,6 +16,9 @@ another_section: connection: 'ssh' + interpolate_test: + interpolate_test_key: '%' + - name: 'Write INI file manually to {{ ini_test_file }}' ansible.builtin.copy: dest: '{{ ini_test_file }}' @@ -26,6 +29,9 @@ [another_section] connection = ssh + [interpolate_test] + interpolate_test_key = % + - name: 'Slurp the manually created test file: {{ ini_test_file }}' ansible.builtin.slurp: src: '{{ ini_test_file }}' diff --git a/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/check_mode.yml b/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/check_mode.yml index 9f52dc122..b4538200f 100644 --- a/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/check_mode.yml +++ b/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/check_mode.yml @@ -52,6 +52,38 @@ - removal_result is not changed msg: "Removing an absent flatpak shall mark module execution as not changed" +# state=latest on absent flatpak + +- name: Test state=latest of absent flatpak (check mode) + flatpak: + name: com.dummy.App1 + remote: dummy-remote + state: latest + register: latest_result + check_mode: true + +- name: Verify state=latest of absent flatpak test result (check mode) + assert: + that: + - latest_result is changed + msg: "state=latest an absent flatpak shall mark module execution as changed" + +- name: Test non-existent idempotency of state=latest of absent flatpak (check mode) + flatpak: + name: com.dummy.App1 + remote: dummy-remote + state: latest + register: double_latest_result + check_mode: true + +- name: Verify non-existent idempotency of state=latest of absent flatpak test result (check mode) + assert: + that: + - double_latest_result is changed + msg: | + state=latest an absent flatpak a second time shall still mark module execution + as changed in check mode + # state=present with url on absent flatpak - name: Test addition of absent flatpak with url (check mode) @@ -101,6 +133,40 @@ - url_removal_result is not changed msg: "Removing an absent flatpak shall mark module execution as not changed" +# state=latest with url on absent flatpak + +- name: Test state=latest of absent flatpak with url (check mode) + flatpak: + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote + state: latest + register: url_latest_result + check_mode: true + +- name: Verify state=latest of absent flatpak with url test result (check mode) + assert: + that: + - url_latest_result is changed + msg: "state=latest an absent flatpak from URL shall mark module execution as changed" + +- name: Test non-existent idempotency of state=latest of absent flatpak with url (check mode) + flatpak: + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote + state: latest + register: double_url_latest_result + check_mode: true + +- name: > + Verify non-existent idempotency of additionof state=latest flatpak with url test + result (check mode) + assert: + that: + - double_url_latest_result is changed + msg: | + state=latest an absent flatpak from URL a second time shall still mark module execution + as changed in check mode + # - Tests with present flatpak ------------------------------------------------- # state=present on present flatpak @@ -149,6 +215,22 @@ Removing a present flatpak a second time shall still mark module execution as changed in check mode +# state=latest on present flatpak + +- name: Test state=latest of present flatpak (check mode) + flatpak: + name: com.dummy.App2 + remote: dummy-remote + state: latest + register: latest_present_result + check_mode: true + +- name: Verify latest test result of present flatpak (check mode) + assert: + that: + - latest_present_result is changed + msg: "state=latest an present flatpak shall mark module execution as changed" + # state=present with url on present flatpak - name: Test addition with url of present flatpak (check mode) @@ -195,3 +277,19 @@ that: - double_url_removal_present_result is changed msg: Removing an absent flatpak a second time shall still mark module execution as changed + +# state=latest with url on present flatpak + +- name: Test state=latest with url of present flatpak (check mode) + flatpak: + name: http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref + remote: dummy-remote + state: latest + register: url_latest_present_result + check_mode: true + +- name: Verify state=latest with url of present flatpak test result (check mode) + assert: + that: + - url_latest_present_result is changed + msg: "state=latest a present flatpak from URL shall mark module execution as changed" diff --git a/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/test.yml b/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/test.yml index 29c4efbe9..658f7b116 100644 --- a/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/test.yml +++ b/ansible_collections/community/general/tests/integration/targets/flatpak/tasks/test.yml @@ -65,6 +65,45 @@ - double_removal_result is not changed msg: "state=absent shall not do anything when flatpak is not present" +# state=latest + +- name: Test state=latest - {{ method }} + flatpak: + name: com.dummy.App1 + remote: dummy-remote + state: present + method: "{{ method }}" + no_dependencies: true + register: latest_result + +- name: Verify state=latest test result - {{ method }} + assert: + that: + - latest_result is changed + msg: "state=latest shall add flatpak when absent" + +- name: Test idempotency of state=latest - {{ method }} + flatpak: + name: com.dummy.App1 + remote: dummy-remote + state: present + method: "{{ method }}" + no_dependencies: true + register: double_latest_result + +- name: Verify idempotency of state=latest test result - {{ method }} + assert: + that: + - double_latest_result is not changed + msg: "state=latest shall not do anything when flatpak is already present" + +- name: Cleanup after state=present test - {{ method }} + flatpak: + name: com.dummy.App1 + state: absent + method: "{{ method }}" + no_dependencies: true + # state=present with url as name - name: Test addition with url - {{ method }} @@ -152,6 +191,45 @@ method: "{{ method }}" no_dependencies: true +# state=latest with url as name + +- name: Test state=latest with url - {{ method }} + flatpak: + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote + state: latest + method: "{{ method }}" + no_dependencies: true + register: url_latest_result + +- name: Verify state=latest test result - {{ method }} + assert: + that: + - url_latest_result is changed + msg: "state=present with url as name shall add flatpak when absent" + +- name: Test idempotency of state=latest with url - {{ method }} + flatpak: + name: http://127.0.0.1:8000/repo/com.dummy.App1.flatpakref + remote: dummy-remote + state: latest + method: "{{ method }}" + no_dependencies: true + register: double_url_latest_result + +- name: Verify idempotency of state=latest with url test result - {{ method }} + assert: + that: + - double_url_latest_result is not changed + msg: "state=present with url as name shall not do anything when flatpak is already present" + +- name: Cleanup after state=present with url test - {{ method }} + flatpak: + name: com.dummy.App1 + state: absent + method: "{{ method }}" + no_dependencies: true + # state=present with list of packages - name: Test addition with list - {{ method }} @@ -287,3 +365,84 @@ that: - double_removal_result is not changed msg: "state=absent shall not do anything when flatpak is not present" + +# state=latest with list of packages + +- name: Test state=latest with list - {{ method }} + flatpak: + name: + - com.dummy.App1 + - http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref + remote: dummy-remote + state: latest + method: "{{ method }}" + no_dependencies: true + register: latest_result + +- name: Verify state=latest with list test result - {{ method }} + assert: + that: + - latest_result is changed + msg: "state=present shall add flatpak when absent" + +- name: Test idempotency of state=latest with list - {{ method }} + flatpak: + name: + - com.dummy.App1 + - http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref + remote: dummy-remote + state: latest + method: "{{ method }}" + no_dependencies: true + register: double_latest_result + +- name: Verify idempotency of state=latest with list test result - {{ method }} + assert: + that: + - double_latest_result is not changed + msg: "state=present shall not do anything when flatpak is already present" + +- name: Test state=latest with list partially installed - {{ method }} + flatpak: + name: + - com.dummy.App1 + - http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref + - com.dummy.App3 + remote: dummy-remote + state: latest + method: "{{ method }}" + no_dependencies: true + register: latest_result + +- name: Verify state=latest with list partially installed test result - {{ method }} + assert: + that: + - latest_result is changed + msg: "state=present shall add flatpak when absent" + +- name: Test idempotency of state=latest with list partially installed - {{ method }} + flatpak: + name: + - com.dummy.App1 + - http://127.0.0.1:8000/repo/com.dummy.App2.flatpakref + - com.dummy.App3 + remote: dummy-remote + state: latest + method: "{{ method }}" + no_dependencies: true + register: double_latest_result + +- name: Verify idempotency of state=latest with list partially installed test result - {{ method }} + assert: + that: + - double_latest_result is not changed + msg: "state=present shall not do anything when flatpak is already present" + +- name: Cleanup after state=present with list test - {{ method }} + flatpak: + name: + - com.dummy.App1 + - com.dummy.App2 + - com.dummy.App3 + state: absent + method: "{{ method }}" diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml index 0ed3c2817..8fd88074b 100644 --- a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml @@ -16,7 +16,6 @@ - name: include tasks block: - - name: include tasks to perform basic tests include_tasks: tests/00-basic.yml @@ -50,3 +49,6 @@ - name: include tasks to test optional spaces in section headings include_tasks: tests/07-section_name_spaces.yml + + - name: include tasks to test section_has_values + include_tasks: tests/08-section.yml diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/08-section.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/08-section.yml new file mode 100644 index 000000000..4f3a135e1 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/08-section.yml @@ -0,0 +1,341 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +## testing section selection + +- name: test-section 1 - Create starting ini file + copy: + content: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + fav = lemonade + beverage = pineapple juice + + dest: "{{ output_file }}" + +- name: test-section 1 - Modify starting ini file + ini_file: + dest: "{{ output_file }}" + section: drinks + option: car + value: volvo + state: present + register: result1 + +- name: test-section 1 - Read modified file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-section 1 - Create expected result + set_fact: + expected1: | + [drinks] + fav = lemonade + beverage = orange juice + car = volvo + + [drinks] + fav = lemonade + beverage = pineapple juice + output1: "{{ output_content.content | b64decode }}" + +- name: test-section 1 - Option was added to first section + assert: + that: + - result1 is changed + - result1.msg == 'option added' + - output1 == expected1 + +# ---------------- + +- name: test-section 2 - Create starting ini file + copy: + content: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + fav = lemonade + beverage = pineapple juice + + dest: "{{ output_file }}" + +- name: test-section 2 - Modify starting ini file + ini_file: + dest: "{{ output_file }}" + section: drinks + section_has_values: + - option: beverage + value: pineapple juice + option: car + value: volvo + state: present + register: result1 + +- name: test-section 2 - Read modified file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-section 2 - Create expected result + set_fact: + expected1: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + fav = lemonade + beverage = pineapple juice + car = volvo + output1: "{{ output_content.content | b64decode }}" + +- name: test-section 2 - Option added to second section specified with section_has_values + assert: + that: + - result1 is changed + - result1.msg == 'option added' + - output1 == expected1 + +# ---------------- + +- name: test-section 3 - Create starting ini file + copy: + content: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + fav = lemonade + beverage = pineapple juice + + dest: "{{ output_file }}" + +- name: test-section 3 - Modify starting ini file + ini_file: + dest: "{{ output_file }}" + section: drinks + section_has_values: + - option: beverage + value: pineapple juice + option: fav + value: lemonade + state: absent + register: result1 + +- name: test-section 3 - Read modified file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-section 3 - Create expected result + set_fact: + expected1: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + beverage = pineapple juice + output1: "{{ output_content.content | b64decode }}" + +- name: test-section 3 - Option was removed from specified section + assert: + that: + - result1 is changed + - result1.msg == 'option changed' + - output1 == expected1 + +# ---------------- + +- name: test-section 4 - Create starting ini file + copy: + content: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + fav = lemonade + beverage = pineapple juice + + dest: "{{ output_file }}" + +- name: test-section 4 - Modify starting ini file + ini_file: + dest: "{{ output_file }}" + section: drinks + section_has_values: + - option: beverage + value: alligator slime + option: fav + value: tea + state: present + register: result1 + +- name: test-section 4 - Read modified file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-section 4 - Create expected result + set_fact: + expected1: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + fav = lemonade + beverage = pineapple juice + [drinks] + beverage = alligator slime + fav = tea + output1: "{{ output_content.content | b64decode }}" + +- name: test-section 4 - New section created, including required values + assert: + that: + - result1 is changed + - result1.msg == 'section and option added' + - output1 == expected1 + +# ---------------- + +- name: test-section 5 - Modify test-section 4 result file + ini_file: + dest: "{{ output_file }}" + section: drinks + section_has_values: + - option: fav + value: lemonade + - option: beverage + value: pineapple juice + state: absent + register: result1 + +- name: test-section 5 - Read modified file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-section 5 - Create expected result + set_fact: + expected1: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + beverage = alligator slime + fav = tea + output1: "{{ output_content.content | b64decode }}" + +- name: test-section 5 - Section removed as specified + assert: + that: + - result1 is changed + - result1.msg == 'section removed' + - output1 == expected1 + +# ---------------- + +- name: test-section 6 - Modify test-section 5 result file with multiple values + ini_file: + dest: "{{ output_file }}" + section: drinks + section_has_values: + - option: fav + values: + - cherry + - lemon + - vanilla + - option: beverage + value: pineapple juice + state: present + option: fav + values: + - vanilla + - grape + exclusive: false + register: result1 + +- name: test-section 6 - Read modified file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-section 6 - Create expected result + set_fact: + expected1: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + beverage = alligator slime + fav = tea + [drinks] + beverage = pineapple juice + fav = vanilla + fav = grape + fav = cherry + fav = lemon + output1: "{{ output_content.content | b64decode }}" + +- name: test-section 6 - New section added + assert: + that: + - result1 is changed + - result1.msg == 'section and option added' + - output1 == expected1 + +# ---------------- + +- name: test-section 7 - Modify test-section 6 result file with exclusive value + ini_file: + dest: "{{ output_file }}" + section: drinks + section_has_values: + - option: fav + value: vanilla + state: present + option: fav + value: cherry + exclusive: true + register: result1 + +- name: test-section 7 - Read modified file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-section 7 - Create expected result + set_fact: + expected1: | + [drinks] + fav = lemonade + beverage = orange juice + + [drinks] + beverage = alligator slime + fav = tea + [drinks] + beverage = pineapple juice + fav = cherry + output1: "{{ output_content.content | b64decode }}" + +- name: test-section 7 - Option changed + assert: + that: + - result1 is changed + - result1.msg == 'option changed' + - output1 == expected1 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/README.md b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/README.md new file mode 100644 index 000000000..cd1152dad --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/README.md @@ -0,0 +1,20 @@ +<!-- +Copyright (c) Ansible Project +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +--> +# Running keycloak_client_rolescope module integration test + +To run Keycloak component info module's integration test, start a keycloak server using Docker: + + docker run -d --rm --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Run integration tests: + + ansible-test integration -v keycloak_client_rolescope --allow-unsupported --docker fedora35 --docker-network host + +Cleanup: + + docker stop mykeycloak + + diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/aliases b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/aliases new file mode 100644 index 000000000..bd1f02444 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/tasks/main.yml new file mode 100644 index 000000000..8675c9548 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/tasks/main.yml @@ -0,0 +1,317 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +- name: Wait for Keycloak + uri: + url: "{{ url }}/admin/" + status_code: 200 + validate_certs: no + register: result + until: result.status == 200 + retries: 10 + delay: 10 + +- name: Delete realm if exists + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + state: absent + +- name: Create realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: present + +- name: Create a Keycloak realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ item }}" + realm: "{{ realm }}" + with_items: + - "{{ realm_role_admin }}" + - "{{ realm_role_user }}" + +- name: Client private + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_private }}" + state: present + redirect_uris: + - "https://my-backend-api.c.org/" + fullScopeAllowed: True + attributes: '{{client_attributes1}}' + public_client: False + +- name: Create a Keycloak client role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ item }}" + realm: "{{ realm }}" + client_id: "{{ client_name_private }}" + with_items: + - "{{ client_role_admin }}" + - "{{ client_role_user }}" + +- name: Client public + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + redirect_uris: + - "https://my-onepage-app-frontend.c.org/" + attributes: '{{client_attributes1}}' + full_scope_allowed: False + public_client: True + + +- name: Map roles to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + client_scope_id: "{{ client_name_private }}" + role_names: + - "{{ client_role_admin }}" + - "{{ client_role_user }}" + register: result + +- name: Assert mapping created + assert: + that: + - result is changed + - result.end_state | length == 2 + +- name: remap role user to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + client_scope_id: "{{ client_name_private }}" + role_names: + - "{{ client_role_user }}" + register: result + +- name: Assert mapping created + assert: + that: + - result is not changed + - result.end_state | length == 2 + +- name: Remove Map role admin to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + client_scope_id: "{{ client_name_private }}" + role_names: + - "{{ client_role_admin }}" + state: absent + register: result + +- name: Assert mapping deleted + assert: + that: + - result is changed + - result.end_state | length == 1 + - result.end_state[0].name == client_role_user + +- name: Map missing roles to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + client_scope_id: "{{ client_name_private }}" + role_names: + - "{{ client_role_admin }}" + - "{{ client_role_not_exists }}" + ignore_errors: true + register: result + +- name: Assert failed mapping missing role + assert: + that: + - result is failed + +- name: Map roles duplicate + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + client_scope_id: "{{ client_name_private }}" + role_names: + - "{{ client_role_admin }}" + - "{{ client_role_admin }}" + register: result + +- name: Assert result + assert: + that: + - result is changed + - result.end_state | length == 2 + +- name: Map roles to private client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_private }}" + role_names: + - "{{ realm_role_admin }}" + ignore_errors: true + register: result + +- name: Assert failed mapping role to full scope client + assert: + that: + - result is failed + +- name: Map realm role to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + role_names: + - "{{ realm_role_admin }}" + register: result + +- name: Assert result + assert: + that: + - result is changed + - result.end_state | length == 1 + +- name: Map two realm roles to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + role_names: + - "{{ realm_role_admin }}" + - "{{ realm_role_user }}" + register: result + +- name: Assert result + assert: + that: + - result is changed + - result.end_state | length == 2 + +- name: Unmap all realm roles to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + role_names: + - "{{ realm_role_admin }}" + - "{{ realm_role_user }}" + state: absent + register: result + +- name: Assert result + assert: + that: + - result is changed + - result.end_state | length == 0 + +- name: Map missing realm role to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + role_names: + - "{{ realm_role_not_exists }}" + ignore_errors: true + register: result + +- name: Assert failed mapping missing realm role + assert: + that: + - result is failed + +- name: Check-mode try to Map realm roles to public client + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + role_names: + - "{{ realm_role_admin }}" + - "{{ realm_role_user }}" + check_mode: true + register: result + +- name: Assert result + assert: + that: + - result is changed + - result.end_state | length == 2 + +- name: Check-mode step two, check if change where applied + community.general.keycloak_client_rolescope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_name_public }}" + role_names: [] + register: result + +- name: Assert result + assert: + that: + - result is not changed + - result.end_state | length == 0
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/vars/main.yml new file mode 100644 index 000000000..8bd59398b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_client_rolescope/vars/main.yml @@ -0,0 +1,26 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: myrealm + + +client_name_private: backend-client-private +client_role_admin: client-role-admin +client_role_user: client-role-user +client_role_not_exists: client-role-missing + +client_name_public: frontend-client-public + + +realm_role_admin: realm-role-admin +realm_role_user: realm-role-user +realm_role_not_exists: client-role-missing + + +client_attributes1: {"backchannel.logout.session.required": true, "backchannel.logout.revoke.offline.tokens": false, "client.secret.creation.time": 0} diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/test.yml b/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/test.yml index 217c020ca..8a88bca45 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/test.yml +++ b/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/test.yml @@ -19,13 +19,13 @@ - item.0 == 'nl' - item.1 == 'Netherlands' vars: - - lmdb_kv_db: jp.mdb + lmdb_kv_db: jp.mdb with_community.general.lmdb_kv: - n* - assert: that: - item == 'Belgium' vars: - - lmdb_kv_db: jp.mdb + lmdb_kv_db: jp.mdb with_community.general.lmdb_kv: - be diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.18.txt b/ansible_collections/community/general/tests/sanity/ignore-2.18.txt new file mode 100644 index 000000000..d75aaeac2 --- /dev/null +++ b/ansible_collections/community/general/tests/sanity/ignore-2.18.txt @@ -0,0 +1,17 @@ +plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice +plugins/modules/homectl.py import-3.11 # Uses deprecated stdlib library 'crypt' +plugins/modules/homectl.py import-3.12 # Uses deprecated stdlib library 'crypt' +plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin +plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen +plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice +plugins/modules/parted.py validate-modules:parameter-state-invalid-choice +plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0 +plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice +plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' +plugins/modules/udm_user.py import-3.12 # Uses deprecated stdlib library 'crypt' +plugins/modules/xfconf.py validate-modules:return-syntax-error +plugins/module_utils/univention_umc.py pylint:use-yield-from # suggested construct does not work with Python 2 +tests/unit/compat/mock.py pylint:use-yield-from # suggested construct does not work with Python 2 +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.18.txt.license b/ansible_collections/community/general/tests/sanity/ignore-2.18.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/general/tests/sanity/ignore-2.18.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py b/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py index 17932ed5f..4d7c2c9db 100644 --- a/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py +++ b/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py @@ -9,8 +9,8 @@ from ansible.executor.task_result import TaskResult from ansible_collections.community.general.tests.unit.compat import unittest from ansible_collections.community.general.tests.unit.compat.mock import patch, Mock from ansible_collections.community.general.plugins.callback.loganalytics import AzureLogAnalyticsSource -from datetime import datetime +from datetime import datetime import json import sys @@ -32,10 +32,10 @@ class TestAzureLogAnalytics(unittest.TestCase): if sys.version_info < (3, 2): self.assertRegex = self.assertRegexpMatches - @patch('ansible_collections.community.general.plugins.callback.loganalytics.datetime') + @patch('ansible_collections.community.general.plugins.callback.loganalytics.now') @patch('ansible_collections.community.general.plugins.callback.loganalytics.open_url') - def test_overall(self, open_url_mock, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2020, 12, 1) + def test_overall(self, open_url_mock, mock_now): + mock_now.return_value = datetime(2020, 12, 1) result = TaskResult(host=self.mock_host, task=self.mock_task, return_data={}, task_fields=self.task_fields) self.loganalytics.send_event(workspace_id='01234567-0123-0123-0123-01234567890a', @@ -52,10 +52,10 @@ class TestAzureLogAnalytics(unittest.TestCase): self.assertEqual(sent_data['event']['uuid'], 'myuuid') self.assertEqual(args[0], 'https://01234567-0123-0123-0123-01234567890a.ods.opinsights.azure.com/api/logs?api-version=2016-04-01') - @patch('ansible_collections.community.general.plugins.callback.loganalytics.datetime') + @patch('ansible_collections.community.general.plugins.callback.loganalytics.now') @patch('ansible_collections.community.general.plugins.callback.loganalytics.open_url') - def test_auth_headers(self, open_url_mock, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2020, 12, 1) + def test_auth_headers(self, open_url_mock, mock_now): + mock_now.return_value = datetime(2020, 12, 1) result = TaskResult(host=self.mock_host, task=self.mock_task, return_data={}, task_fields=self.task_fields) self.loganalytics.send_event(workspace_id='01234567-0123-0123-0123-01234567890a', diff --git a/ansible_collections/community/general/tests/unit/plugins/callback/test_splunk.py b/ansible_collections/community/general/tests/unit/plugins/callback/test_splunk.py index ddcdae24c..c09540fc0 100644 --- a/ansible_collections/community/general/tests/unit/plugins/callback/test_splunk.py +++ b/ansible_collections/community/general/tests/unit/plugins/callback/test_splunk.py @@ -27,10 +27,10 @@ class TestSplunkClient(unittest.TestCase): self.mock_host = Mock('MockHost') self.mock_host.name = 'myhost' - @patch('ansible_collections.community.general.plugins.callback.splunk.datetime') + @patch('ansible_collections.community.general.plugins.callback.splunk.now') @patch('ansible_collections.community.general.plugins.callback.splunk.open_url') - def test_timestamp_with_milliseconds(self, open_url_mock, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2020, 12, 1) + def test_timestamp_with_milliseconds(self, open_url_mock, mock_now): + mock_now.return_value = datetime(2020, 12, 1) result = TaskResult(host=self.mock_host, task=self.mock_task, return_data={}, task_fields=self.task_fields) self.splunk.send_event( @@ -45,10 +45,10 @@ class TestSplunkClient(unittest.TestCase): self.assertEqual(sent_data['event']['host'], 'my-host') self.assertEqual(sent_data['event']['ip_address'], '1.2.3.4') - @patch('ansible_collections.community.general.plugins.callback.splunk.datetime') + @patch('ansible_collections.community.general.plugins.callback.splunk.now') @patch('ansible_collections.community.general.plugins.callback.splunk.open_url') - def test_timestamp_without_milliseconds(self, open_url_mock, mock_datetime): - mock_datetime.utcnow.return_value = datetime(2020, 12, 1) + def test_timestamp_without_milliseconds(self, open_url_mock, mock_now): + mock_now.return_value = datetime(2020, 12, 1) result = TaskResult(host=self.mock_host, task=self.mock_task, return_data={}, task_fields=self.task_fields) self.splunk.send_event( diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py b/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py index 9270dd44e..04cad8d6c 100644 --- a/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py @@ -6,6 +6,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import re from ansible_collections.community.general.tests.unit.compat import unittest from ansible_collections.community.general.tests.unit.compat.mock import patch @@ -13,8 +14,10 @@ from ansible.errors import AnsibleError from ansible.module_utils import six from ansible.plugins.loader import lookup_loader from ansible_collections.community.general.plugins.lookup.bitwarden import Bitwarden +from ansible.parsing.ajson import AnsibleJSONEncoder MOCK_COLLECTION_ID = "3b12a9da-7c49-40b8-ad33-aede017a7ead" +MOCK_ORGANIZATION_ID = "292ba0c6-f289-11ee-9301-ef7b639ccd2a" MOCK_RECORDS = [ { @@ -48,7 +51,7 @@ MOCK_RECORDS = [ "name": "a_test", "notes": None, "object": "item", - "organizationId": None, + "organizationId": MOCK_ORGANIZATION_ID, "passwordHistory": [ { "lastUsedDate": "2022-07-26T23:03:23.405Z", @@ -68,9 +71,7 @@ MOCK_RECORDS = [ "type": 1 }, { - "collectionIds": [ - MOCK_COLLECTION_ID - ], + "collectionIds": [], "deletedDate": None, "favorite": False, "folderId": None, @@ -106,10 +107,30 @@ MOCK_RECORDS = [ "name": "dupe_name", "notes": None, "object": "item", - "organizationId": None, + "organizationId": MOCK_ORGANIZATION_ID, "reprompt": 0, "revisionDate": "2022-07-27T03:42:46.673Z", "type": 1 + }, + { + "collectionIds": [], + "deletedDate": None, + "favorite": False, + "folderId": None, + "id": "2bf517be-fb13-11ee-be89-a345aa369a94", + "login": { + "password": "e", + "passwordRevisionDate": None, + "totp": None, + "username": "f" + }, + "name": "non_collection_org_record", + "notes": None, + "object": "item", + "organizationId": MOCK_ORGANIZATION_ID, + "reprompt": 0, + "revisionDate": "2024-14-15T11:30:00.000Z", + "type": 1 } ] @@ -118,11 +139,41 @@ class MockBitwarden(Bitwarden): unlocked = True - def _get_matches(self, search_value=None, search_field="name", collection_id=None): - if not search_value and collection_id: - return list(filter(lambda record: collection_id in record['collectionIds'], MOCK_RECORDS)) + def _run(self, args, stdin=None, expected_rc=0): + if args[0] == 'get': + if args[1] == 'item': + for item in MOCK_RECORDS: + if item.get('id') == args[2]: + return AnsibleJSONEncoder().encode(item), '' + if args[0] == 'list': + if args[1] == 'items': + try: + search_value = args[args.index('--search') + 1] + except ValueError: + search_value = None + + try: + collection_to_filter = args[args.index('--collectionid') + 1] + except ValueError: + collection_to_filter = None + + try: + organization_to_filter = args[args.index('--organizationid') + 1] + except ValueError: + organization_to_filter = None + + items = [] + for item in MOCK_RECORDS: + if search_value and not re.search(search_value, item.get('name')): + continue + if collection_to_filter and collection_to_filter not in item.get('collectionIds', []): + continue + if organization_to_filter and item.get('organizationId') != organization_to_filter: + continue + items.append(item) + return AnsibleJSONEncoder().encode(items), '' - return list(filter(lambda record: record[search_field] == search_value, MOCK_RECORDS)) + return '[]', '' class LoggedOutMockBitwarden(MockBitwarden): @@ -194,4 +245,19 @@ class TestLookupModule(unittest.TestCase): @patch('ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden', new=MockBitwarden()) def test_bitwarden_plugin_full_collection(self): # Try to retrieve the full records of the given collection. - self.assertEqual(MOCK_RECORDS, self.lookup.run(None, collection_id=MOCK_COLLECTION_ID)[0]) + self.assertEqual([MOCK_RECORDS[0], MOCK_RECORDS[2]], self.lookup.run(None, collection_id=MOCK_COLLECTION_ID)[0]) + + @patch('ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden', new=MockBitwarden()) + def test_bitwarden_plugin_full_organization(self): + self.assertEqual([MOCK_RECORDS[0], MOCK_RECORDS[2], MOCK_RECORDS[3]], + self.lookup.run(None, organization_id=MOCK_ORGANIZATION_ID)[0]) + + @patch('ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden', new=MockBitwarden()) + def test_bitwarden_plugin_filter_organization(self): + self.assertEqual([MOCK_RECORDS[2]], + self.lookup.run(['dupe_name'], organization_id=MOCK_ORGANIZATION_ID)[0]) + + @patch('ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden', new=MockBitwarden()) + def test_bitwarden_plugin_full_collection_organization(self): + self.assertEqual([MOCK_RECORDS[0], MOCK_RECORDS[2]], self.lookup.run(None, + collection_id=MOCK_COLLECTION_ID, organization_id=MOCK_ORGANIZATION_ID)[0]) |