mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 18:36:28 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
069b785cb2 | ||
|
|
090d3f3709 | ||
|
|
68a9b66966 | ||
|
|
f96c6476fe | ||
|
|
fc0f677535 | ||
|
|
9a986473bd | ||
|
|
e9f0e49283 | ||
|
|
5eff31e760 | ||
|
|
39c58d5469 | ||
|
|
20d7be4f38 | ||
|
|
2d26fba0b9 | ||
|
|
d6168a196b | ||
|
|
02de81c39e | ||
|
|
4096b9fa5a | ||
|
|
fe3a3a7638 | ||
|
|
7cac741e77 | ||
|
|
f84ebed63f | ||
|
|
f905a1bc94 | ||
|
|
b0470f2e59 | ||
|
|
42175e38b2 | ||
|
|
8e79844b75 | ||
|
|
1338db358a | ||
|
|
06c4439a1c | ||
|
|
16d5d5fc57 | ||
|
|
71af3226f3 | ||
|
|
b3037a46be | ||
|
|
f7df19adbd | ||
|
|
3bca21aa1b | ||
|
|
1bb3d41e15 | ||
|
|
f214f206c3 | ||
|
|
9b8011d692 | ||
|
|
f227038f38 | ||
|
|
86a2996814 | ||
|
|
eb154003cf | ||
|
|
212871fcaf | ||
|
|
1795a67b8e | ||
|
|
a71c0af9cc | ||
|
|
569cde6c3e | ||
|
|
f0db1d1f6b | ||
|
|
5a36e84b86 | ||
|
|
a74c6db77f | ||
|
|
9a14980ca7 | ||
|
|
8c9effce1f | ||
|
|
51ec3594dd | ||
|
|
802f8ea224 | ||
|
|
23af148021 | ||
|
|
1a2c2d0a64 |
@@ -53,7 +53,7 @@ variables:
|
||||
resources:
|
||||
containers:
|
||||
- container: default
|
||||
image: quay.io/ansible/azure-pipelines-test-container:4.0.1
|
||||
image: quay.io/ansible/azure-pipelines-test-container:6.0.0
|
||||
|
||||
pool: Standard
|
||||
|
||||
@@ -127,6 +127,7 @@ stages:
|
||||
- test: '3.10'
|
||||
- test: '3.11'
|
||||
- test: '3.12'
|
||||
- test: '3.13'
|
||||
- stage: Units_2_17
|
||||
displayName: Units 2.17
|
||||
dependsOn: []
|
||||
@@ -354,6 +355,7 @@ stages:
|
||||
targets:
|
||||
- test: '3.8'
|
||||
- test: '3.11'
|
||||
- test: '3.13'
|
||||
- stage: Generic_2_17
|
||||
displayName: Generic 2.17
|
||||
dependsOn: []
|
||||
|
||||
22
.github/BOTMETA.yml
vendored
22
.github/BOTMETA.yml
vendored
@@ -157,6 +157,8 @@ files:
|
||||
$filters/jc.py:
|
||||
maintainers: kellyjonbrazil
|
||||
$filters/json_query.py: {}
|
||||
$filters/keep_keys.py:
|
||||
maintainers: vbotka
|
||||
$filters/lists.py:
|
||||
maintainers: cfiehe
|
||||
$filters/lists_difference.yml:
|
||||
@@ -170,6 +172,10 @@ files:
|
||||
$filters/lists_union.yml:
|
||||
maintainers: cfiehe
|
||||
$filters/random_mac.py: {}
|
||||
$filters/remove_keys.py:
|
||||
maintainers: vbotka
|
||||
$filters/replace_keys.py:
|
||||
maintainers: vbotka
|
||||
$filters/time.py:
|
||||
maintainers: resmo
|
||||
$filters/to_days.yml:
|
||||
@@ -502,12 +508,16 @@ files:
|
||||
maintainers: tintoy
|
||||
$modules/discord.py:
|
||||
maintainers: cwollinger
|
||||
$modules/django_check.py:
|
||||
maintainers: russoz
|
||||
$modules/django_command.py:
|
||||
maintainers: russoz
|
||||
$modules/django_createcachetable.py:
|
||||
maintainers: russoz
|
||||
$modules/django_manage.py:
|
||||
ignore: scottanderson42 tastychutney
|
||||
labels: django_manage
|
||||
maintainers: russoz
|
||||
$modules/django_command.py:
|
||||
maintainers: russoz
|
||||
$modules/dnf_versionlock.py:
|
||||
maintainers: moreda
|
||||
$modules/dnf_config_manager.py:
|
||||
@@ -1415,6 +1425,8 @@ files:
|
||||
ignore: matze
|
||||
labels: zypper
|
||||
maintainers: $team_suse
|
||||
$plugin_utils/keys_filter.py:
|
||||
maintainers: vbotka
|
||||
$plugin_utils/unsafe.py:
|
||||
maintainers: felixfontein
|
||||
$tests/a_module.py:
|
||||
@@ -1454,6 +1466,10 @@ files:
|
||||
maintainers: baldwinSPC nurfet-becirevic t0mk teebes
|
||||
docs/docsite/rst/guide_scaleway.rst:
|
||||
maintainers: $team_scaleway
|
||||
docs/docsite/rst/guide_deps.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/guide_vardict.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/test_guide.rst:
|
||||
maintainers: felixfontein
|
||||
#########################
|
||||
@@ -1485,7 +1501,7 @@ macros:
|
||||
team_ansible_core:
|
||||
team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross
|
||||
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
|
||||
team_consul: sgargan apollo13
|
||||
team_consul: sgargan apollo13 Ilgmi
|
||||
team_cyberark_conjur: jvanderhoof ryanprior
|
||||
team_e_spirit: MatrixCrawler getjack
|
||||
team_flatpak: JayKayy oolongbrothers
|
||||
|
||||
137
CHANGELOG.md
137
CHANGELOG.md
@@ -2,35 +2,144 @@
|
||||
|
||||
**Topics**
|
||||
|
||||
- <a href="#v9-0-0">v9\.0\.0</a>
|
||||
- <a href="#v9-1-0">v9\.1\.0</a>
|
||||
- <a href="#release-summary">Release Summary</a>
|
||||
- <a href="#minor-changes">Minor Changes</a>
|
||||
- <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
|
||||
- <a href="#deprecated-features">Deprecated Features</a>
|
||||
- <a href="#bugfixes">Bugfixes</a>
|
||||
- <a href="#known-issues">Known Issues</a>
|
||||
- <a href="#new-plugins">New Plugins</a>
|
||||
- <a href="#filter">Filter</a>
|
||||
- <a href="#new-modules">New Modules</a>
|
||||
- <a href="#v9-0-1">v9\.0\.1</a>
|
||||
- <a href="#release-summary-1">Release Summary</a>
|
||||
- <a href="#minor-changes-1">Minor Changes</a>
|
||||
- <a href="#bugfixes-1">Bugfixes</a>
|
||||
- <a href="#v9-0-0">v9\.0\.0</a>
|
||||
- <a href="#release-summary-2">Release Summary</a>
|
||||
- <a href="#minor-changes-2">Minor Changes</a>
|
||||
- <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
|
||||
- <a href="#deprecated-features-1">Deprecated Features</a>
|
||||
- <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a>
|
||||
- <a href="#security-fixes">Security Fixes</a>
|
||||
- <a href="#bugfixes">Bugfixes</a>
|
||||
- <a href="#new-plugins">New Plugins</a>
|
||||
- <a href="#bugfixes-2">Bugfixes</a>
|
||||
- <a href="#new-plugins-1">New Plugins</a>
|
||||
- <a href="#become">Become</a>
|
||||
- <a href="#callback">Callback</a>
|
||||
- <a href="#connection">Connection</a>
|
||||
- <a href="#filter">Filter</a>
|
||||
- <a href="#filter-1">Filter</a>
|
||||
- <a href="#lookup">Lookup</a>
|
||||
- <a href="#test">Test</a>
|
||||
- <a href="#new-modules">New Modules</a>
|
||||
- <a href="#new-modules-1">New Modules</a>
|
||||
This changelog describes changes after version 8\.0\.0\.
|
||||
|
||||
<a id="v9-0-0"></a>
|
||||
## v9\.0\.0
|
||||
<a id="v9-1-0"></a>
|
||||
## v9\.1\.0
|
||||
|
||||
<a id="release-summary"></a>
|
||||
### Release Summary
|
||||
|
||||
This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-05\-20\.
|
||||
Regular feature and bugfix release\.
|
||||
|
||||
<a id="minor-changes"></a>
|
||||
### Minor Changes
|
||||
|
||||
* CmdRunner module util \- argument formats can be specified as plain functions without calling <code>cmd\_runner\_fmt\.as\_func\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8479](https\://github\.com/ansible\-collections/community\.general/pull/8479)\)\.
|
||||
* ansible\_galaxy\_install \- add upgrade feature \([https\://github\.com/ansible\-collections/community\.general/pull/8431](https\://github\.com/ansible\-collections/community\.general/pull/8431)\, [https\://github\.com/ansible\-collections/community\.general/issues/8351](https\://github\.com/ansible\-collections/community\.general/issues/8351)\)\.
|
||||
* cargo \- add option <code>directory</code>\, which allows source directory to be specified \([https\://github\.com/ansible\-collections/community\.general/pull/8480](https\://github\.com/ansible\-collections/community\.general/pull/8480)\)\.
|
||||
* cmd\_runner module utils \- add decorator <code>cmd\_runner\_fmt\.stack</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8415](https\://github\.com/ansible\-collections/community\.general/pull/8415)\)\.
|
||||
* cmd\_runner\_fmt module utils \- simplify implementation of <code>cmd\_runner\_fmt\.as\_bool\_not\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8512](https\://github\.com/ansible\-collections/community\.general/pull/8512)\)\.
|
||||
* ipa\_dnsrecord \- adds <code>SSHFP</code> record type for managing SSH fingerprints in FreeIPA DNS \([https\://github\.com/ansible\-collections/community\.general/pull/8404](https\://github\.com/ansible\-collections/community\.general/pull/8404)\)\.
|
||||
* keycloak\_client \- assign auth flow by name \([https\://github\.com/ansible\-collections/community\.general/pull/8428](https\://github\.com/ansible\-collections/community\.general/pull/8428)\)\.
|
||||
* openbsd\_pkg \- adds diff support to show changes in installed package list\. This does not yet work for check mode \([https\://github\.com/ansible\-collections/community\.general/pull/8402](https\://github\.com/ansible\-collections/community\.general/pull/8402)\)\.
|
||||
* proxmox \- allow specification of the API port when using proxmox\_\* \([https\://github\.com/ansible\-collections/community\.general/issues/8440](https\://github\.com/ansible\-collections/community\.general/issues/8440)\, [https\://github\.com/ansible\-collections/community\.general/pull/8441](https\://github\.com/ansible\-collections/community\.general/pull/8441)\)\.
|
||||
* proxmox\_vm\_info \- add <code>network</code> option to retrieve current network information \([https\://github\.com/ansible\-collections/community\.general/pull/8471](https\://github\.com/ansible\-collections/community\.general/pull/8471)\)\.
|
||||
* redfish\_command \- add <code>wait</code> and <code>wait\_timeout</code> options to allow a user to block a command until a service is accessible after performing the requested command \([https\://github\.com/ansible\-collections/community\.general/issues/8051](https\://github\.com/ansible\-collections/community\.general/issues/8051)\, [https\://github\.com/ansible\-collections/community\.general/pull/8434](https\://github\.com/ansible\-collections/community\.general/pull/8434)\)\.
|
||||
* redfish\_info \- add command <code>CheckAvailability</code> to check if a service is accessible \([https\://github\.com/ansible\-collections/community\.general/issues/8051](https\://github\.com/ansible\-collections/community\.general/issues/8051)\, [https\://github\.com/ansible\-collections/community\.general/pull/8434](https\://github\.com/ansible\-collections/community\.general/pull/8434)\)\.
|
||||
* redis\_info \- adds support for getting cluster info \([https\://github\.com/ansible\-collections/community\.general/pull/8464](https\://github\.com/ansible\-collections/community\.general/pull/8464)\)\.
|
||||
|
||||
<a id="deprecated-features"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* CmdRunner module util \- setting the value of the <code>ignore\_none</code> parameter within a <code>CmdRunner</code> context is deprecated and that feature should be removed in community\.general 12\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/8479](https\://github\.com/ansible\-collections/community\.general/pull/8479)\)\.
|
||||
* git\_config \- the <code>list\_all</code> option has been deprecated and will be removed in community\.general 11\.0\.0\. Use the <code>community\.general\.git\_config\_info</code> module instead \([https\://github\.com/ansible\-collections/community\.general/pull/8453](https\://github\.com/ansible\-collections/community\.general/pull/8453)\)\.
|
||||
* git\_config \- using <code>state\=present</code> without providing <code>value</code> is deprecated and will be disallowed in community\.general 11\.0\.0\. Use the <code>community\.general\.git\_config\_info</code> module instead to read a value \([https\://github\.com/ansible\-collections/community\.general/pull/8453](https\://github\.com/ansible\-collections/community\.general/pull/8453)\)\.
|
||||
|
||||
<a id="bugfixes"></a>
|
||||
### Bugfixes
|
||||
|
||||
* git\_config \- fix behavior of <code>state\=absent</code> if <code>value</code> is present \([https\://github\.com/ansible\-collections/community\.general/issues/8436](https\://github\.com/ansible\-collections/community\.general/issues/8436)\, [https\://github\.com/ansible\-collections/community\.general/pull/8452](https\://github\.com/ansible\-collections/community\.general/pull/8452)\)\.
|
||||
* keycloak\_realm \- add normalizations for <code>attributes</code> and <code>protocol\_mappers</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8496](https\://github\.com/ansible\-collections/community\.general/pull/8496)\)\.
|
||||
* launched \- correctly report changed status in check mode \([https\://github\.com/ansible\-collections/community\.general/pull/8406](https\://github\.com/ansible\-collections/community\.general/pull/8406)\)\.
|
||||
* opennebula inventory plugin \- fix invalid reference to IP when inventory runs against NICs with no IPv4 address \([https\://github\.com/ansible\-collections/community\.general/pull/8489](https\://github\.com/ansible\-collections/community\.general/pull/8489)\)\.
|
||||
* opentelemetry callback \- do not save the JSON response when using the <code>ansible\.builtin\.uri</code> module \([https\://github\.com/ansible\-collections/community\.general/pull/8430](https\://github\.com/ansible\-collections/community\.general/pull/8430)\)\.
|
||||
* opentelemetry callback \- do not save the content response when using the <code>ansible\.builtin\.slurp</code> module \([https\://github\.com/ansible\-collections/community\.general/pull/8430](https\://github\.com/ansible\-collections/community\.general/pull/8430)\)\.
|
||||
* paman \- do not fail if an empty list of packages has been provided and there is nothing to do \([https\://github\.com/ansible\-collections/community\.general/pull/8514](https\://github\.com/ansible\-collections/community\.general/pull/8514)\)\.
|
||||
|
||||
<a id="known-issues"></a>
|
||||
### Known Issues
|
||||
|
||||
* homectl \- the module does not work under Python 3\.13 or newer\, since it relies on the removed <code>crypt</code> standard library module \([https\://github\.com/ansible\-collections/community\.general/issues/4691](https\://github\.com/ansible\-collections/community\.general/issues/4691)\, [https\://github\.com/ansible\-collections/community\.general/pull/8497](https\://github\.com/ansible\-collections/community\.general/pull/8497)\)\.
|
||||
* udm\_user \- the module does not work under Python 3\.13 or newer\, since it relies on the removed <code>crypt</code> standard library module \([https\://github\.com/ansible\-collections/community\.general/issues/4690](https\://github\.com/ansible\-collections/community\.general/issues/4690)\, [https\://github\.com/ansible\-collections/community\.general/pull/8497](https\://github\.com/ansible\-collections/community\.general/pull/8497)\)\.
|
||||
|
||||
<a id="new-plugins"></a>
|
||||
### New Plugins
|
||||
|
||||
<a id="filter"></a>
|
||||
#### Filter
|
||||
|
||||
* community\.general\.keep\_keys \- Keep specific keys from dictionaries in a list\.
|
||||
* community\.general\.remove\_keys \- Remove specific keys from dictionaries in a list\.
|
||||
* community\.general\.replace\_keys \- Replace specific keys in a list of dictionaries\.
|
||||
|
||||
<a id="new-modules"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.consul\_agent\_check \- Add\, modify\, and delete checks within a consul cluster\.
|
||||
* community\.general\.consul\_agent\_service \- Add\, modify and delete services within a consul cluster\.
|
||||
* community\.general\.django\_check \- Wrapper for C\(django\-admin check\)\.
|
||||
* community\.general\.django\_createcachetable \- Wrapper for C\(django\-admin createcachetable\)\.
|
||||
|
||||
<a id="v9-0-1"></a>
|
||||
## v9\.0\.1
|
||||
|
||||
<a id="release-summary-1"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix release for inclusion in Ansible 10\.0\.0rc1\.
|
||||
|
||||
<a id="minor-changes-1"></a>
|
||||
### Minor Changes
|
||||
|
||||
* ansible\_galaxy\_install \- minor refactor in the module \([https\://github\.com/ansible\-collections/community\.general/pull/8413](https\://github\.com/ansible\-collections/community\.general/pull/8413)\)\.
|
||||
|
||||
<a id="bugfixes-1"></a>
|
||||
### Bugfixes
|
||||
|
||||
* cpanm \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* django module utils \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* gconftool2\_info \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* homebrew \- do not fail when brew prints warnings \([https\://github\.com/ansible\-collections/community\.general/pull/8406](https\://github\.com/ansible\-collections/community\.general/pull/8406)\, [https\://github\.com/ansible\-collections/community\.general/issues/7044](https\://github\.com/ansible\-collections/community\.general/issues/7044)\)\.
|
||||
* hponcfg \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* kernel\_blacklist \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* keycloak\_client \- fix TypeError when sanitizing the <code>saml\.signing\.private\.key</code> attribute in the module\'s diff or state output\. The <code>sanitize\_cr</code> function expected a dict where in some cases a list might occur \([https\://github\.com/ansible\-collections/community\.general/pull/8403](https\://github\.com/ansible\-collections/community\.general/pull/8403)\)\.
|
||||
* locale\_gen \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* mksysb \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* pipx\_info \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* snap \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* snap\_alias \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
|
||||
<a id="v9-0-0"></a>
|
||||
## v9\.0\.0
|
||||
|
||||
<a id="release-summary-2"></a>
|
||||
### Release Summary
|
||||
|
||||
This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-05\-20\.
|
||||
|
||||
<a id="minor-changes-2"></a>
|
||||
### Minor Changes
|
||||
|
||||
* PythonRunner module utils \- specialisation of <code>CmdRunner</code> to execute Python scripts \([https\://github\.com/ansible\-collections/community\.general/pull/8289](https\://github\.com/ansible\-collections/community\.general/pull/8289)\)\.
|
||||
* 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)\)\.
|
||||
* aix\_lvol \- refactor module to pass list of arguments to <code>module\.run\_command\(\)</code> instead of relying on interpretation by a shell \([https\://github\.com/ansible\-collections/community\.general/pull/8264](https\://github\.com/ansible\-collections/community\.general/pull/8264)\)\.
|
||||
@@ -157,7 +266,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
* django\_manage \- the module will now fail if <code>virtualenv</code> is specified but no virtual environment exists at that location \([https\://github\.com/ansible\-collections/community\.general/pull/8198](https\://github\.com/ansible\-collections/community\.general/pull/8198)\)\.
|
||||
* redfish\_command\, redfish\_config\, redfish\_info \- change the default for <code>timeout</code> from 10 to 60 \([https\://github\.com/ansible\-collections/community\.general/pull/8198](https\://github\.com/ansible\-collections/community\.general/pull/8198)\)\.
|
||||
|
||||
<a id="deprecated-features"></a>
|
||||
<a id="deprecated-features-1"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* MH DependencyCtxMgr module\_utils \- deprecate <code>module\_utils\.mh\.mixin\.deps\.DependencyCtxMgr</code> in favour of <code>module\_utils\.deps</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8280](https\://github\.com/ansible\-collections/community\.general/pull/8280)\)\.
|
||||
@@ -198,7 +307,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
* 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)\)\.
|
||||
* keycloak\_identity\_provider \- the client secret was not correctly sanitized by the module\. The return values <code>proposed</code>\, <code>existing</code>\, and <code>end\_state</code>\, as well as the diff\, did contain the client secret unmasked \([https\://github\.com/ansible\-collections/community\.general/pull/8355](https\://github\.com/ansible\-collections/community\.general/pull/8355)\)\.
|
||||
|
||||
<a id="bugfixes"></a>
|
||||
<a id="bugfixes-2"></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)\)\.
|
||||
@@ -273,7 +382,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
* 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-plugins"></a>
|
||||
<a id="new-plugins-1"></a>
|
||||
### New Plugins
|
||||
|
||||
<a id="become"></a>
|
||||
@@ -292,7 +401,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
|
||||
* community\.general\.incus \- Run tasks in Incus instances via the Incus CLI\.
|
||||
|
||||
<a id="filter"></a>
|
||||
<a id="filter-1"></a>
|
||||
#### Filter
|
||||
|
||||
* community\.general\.from\_ini \- Converts INI text input into a dictionary\.
|
||||
@@ -313,7 +422,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
|
||||
* community\.general\.fqdn\_valid \- Validates fully\-qualified domain names against RFC 1123\.
|
||||
|
||||
<a id="new-modules"></a>
|
||||
<a id="new-modules-1"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.consul\_acl\_bootstrap \- Bootstrap ACLs in Consul\.
|
||||
|
||||
@@ -6,6 +6,102 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 8.0.0.
|
||||
|
||||
v9.1.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular feature and bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- CmdRunner module util - argument formats can be specified as plain functions without calling ``cmd_runner_fmt.as_func()`` (https://github.com/ansible-collections/community.general/pull/8479).
|
||||
- ansible_galaxy_install - add upgrade feature (https://github.com/ansible-collections/community.general/pull/8431, https://github.com/ansible-collections/community.general/issues/8351).
|
||||
- cargo - add option ``directory``, which allows source directory to be specified (https://github.com/ansible-collections/community.general/pull/8480).
|
||||
- cmd_runner module utils - add decorator ``cmd_runner_fmt.stack`` (https://github.com/ansible-collections/community.general/pull/8415).
|
||||
- cmd_runner_fmt module utils - simplify implementation of ``cmd_runner_fmt.as_bool_not()`` (https://github.com/ansible-collections/community.general/pull/8512).
|
||||
- ipa_dnsrecord - adds ``SSHFP`` record type for managing SSH fingerprints in FreeIPA DNS (https://github.com/ansible-collections/community.general/pull/8404).
|
||||
- keycloak_client - assign auth flow by name (https://github.com/ansible-collections/community.general/pull/8428).
|
||||
- openbsd_pkg - adds diff support to show changes in installed package list. This does not yet work for check mode (https://github.com/ansible-collections/community.general/pull/8402).
|
||||
- proxmox - allow specification of the API port when using proxmox_* (https://github.com/ansible-collections/community.general/issues/8440, https://github.com/ansible-collections/community.general/pull/8441).
|
||||
- proxmox_vm_info - add ``network`` option to retrieve current network information (https://github.com/ansible-collections/community.general/pull/8471).
|
||||
- redfish_command - add ``wait`` and ``wait_timeout`` options to allow a user to block a command until a service is accessible after performing the requested command (https://github.com/ansible-collections/community.general/issues/8051, https://github.com/ansible-collections/community.general/pull/8434).
|
||||
- redfish_info - add command ``CheckAvailability`` to check if a service is accessible (https://github.com/ansible-collections/community.general/issues/8051, https://github.com/ansible-collections/community.general/pull/8434).
|
||||
- redis_info - adds support for getting cluster info (https://github.com/ansible-collections/community.general/pull/8464).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- CmdRunner module util - setting the value of the ``ignore_none`` parameter within a ``CmdRunner`` context is deprecated and that feature should be removed in community.general 12.0.0 (https://github.com/ansible-collections/community.general/pull/8479).
|
||||
- git_config - the ``list_all`` option has been deprecated and will be removed in community.general 11.0.0. Use the ``community.general.git_config_info`` module instead (https://github.com/ansible-collections/community.general/pull/8453).
|
||||
- git_config - using ``state=present`` without providing ``value`` is deprecated and will be disallowed in community.general 11.0.0. Use the ``community.general.git_config_info`` module instead to read a value (https://github.com/ansible-collections/community.general/pull/8453).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- git_config - fix behavior of ``state=absent`` if ``value`` is present (https://github.com/ansible-collections/community.general/issues/8436, https://github.com/ansible-collections/community.general/pull/8452).
|
||||
- keycloak_realm - add normalizations for ``attributes`` and ``protocol_mappers`` (https://github.com/ansible-collections/community.general/pull/8496).
|
||||
- launched - correctly report changed status in check mode (https://github.com/ansible-collections/community.general/pull/8406).
|
||||
- opennebula inventory plugin - fix invalid reference to IP when inventory runs against NICs with no IPv4 address (https://github.com/ansible-collections/community.general/pull/8489).
|
||||
- opentelemetry callback - do not save the JSON response when using the ``ansible.builtin.uri`` module (https://github.com/ansible-collections/community.general/pull/8430).
|
||||
- opentelemetry callback - do not save the content response when using the ``ansible.builtin.slurp`` module (https://github.com/ansible-collections/community.general/pull/8430).
|
||||
- paman - do not fail if an empty list of packages has been provided and there is nothing to do (https://github.com/ansible-collections/community.general/pull/8514).
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
- homectl - the module does not work under Python 3.13 or newer, since it relies on the removed ``crypt`` standard library module (https://github.com/ansible-collections/community.general/issues/4691, https://github.com/ansible-collections/community.general/pull/8497).
|
||||
- udm_user - the module does not work under Python 3.13 or newer, since it relies on the removed ``crypt`` standard library module (https://github.com/ansible-collections/community.general/issues/4690, https://github.com/ansible-collections/community.general/pull/8497).
|
||||
|
||||
New Plugins
|
||||
-----------
|
||||
|
||||
Filter
|
||||
~~~~~~
|
||||
|
||||
- community.general.keep_keys - Keep specific keys from dictionaries in a list.
|
||||
- community.general.remove_keys - Remove specific keys from dictionaries in a list.
|
||||
- community.general.replace_keys - Replace specific keys in a list of dictionaries.
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- community.general.consul_agent_check - Add, modify, and delete checks within a consul cluster.
|
||||
- community.general.consul_agent_service - Add, modify and delete services within a consul cluster.
|
||||
- community.general.django_check - Wrapper for C(django-admin check).
|
||||
- community.general.django_createcachetable - Wrapper for C(django-admin createcachetable).
|
||||
|
||||
v9.0.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release for inclusion in Ansible 10.0.0rc1.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- ansible_galaxy_install - minor refactor in the module (https://github.com/ansible-collections/community.general/pull/8413).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- cpanm - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- django module utils - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- gconftool2_info - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- homebrew - do not fail when brew prints warnings (https://github.com/ansible-collections/community.general/pull/8406, https://github.com/ansible-collections/community.general/issues/7044).
|
||||
- hponcfg - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- kernel_blacklist - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- keycloak_client - fix TypeError when sanitizing the ``saml.signing.private.key`` attribute in the module's diff or state output. The ``sanitize_cr`` function expected a dict where in some cases a list might occur (https://github.com/ansible-collections/community.general/pull/8403).
|
||||
- locale_gen - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- mksysb - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- pipx_info - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- snap - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- snap_alias - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410, https://github.com/ansible-collections/community.general/pull/8411).
|
||||
|
||||
v9.0.0
|
||||
======
|
||||
|
||||
|
||||
@@ -445,7 +445,8 @@ releases:
|
||||
in ``category=Raw``) without a specific action info (https://github.com/ansible-collections/community.general/pull/7746).
|
||||
- xfconf - use ``ModuleHelper`` with ``VarDict`` (https://github.com/ansible-collections/community.general/pull/8226).
|
||||
- xfconf_info - use ``ModuleHelper`` with ``VarDict`` (https://github.com/ansible-collections/community.general/pull/8226).
|
||||
release_summary: This is release 9.0.0 of ``community.general``, released on 2024-05-20.
|
||||
release_summary: This is release 9.0.0 of ``community.general``, released on
|
||||
2024-05-20.
|
||||
removed_features:
|
||||
- The deprecated redirects for internal module names have been removed. These
|
||||
internal redirects were extra-long FQCNs like ``community.general.packaging.os.apt_rpm``
|
||||
@@ -755,3 +756,149 @@ releases:
|
||||
name: fqdn_valid
|
||||
namespace: null
|
||||
release_date: '2024-05-20'
|
||||
9.0.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- cpanm - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- django module utils - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- gconftool2_info - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- homebrew - do not fail when brew prints warnings (https://github.com/ansible-collections/community.general/pull/8406,
|
||||
https://github.com/ansible-collections/community.general/issues/7044).
|
||||
- hponcfg - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- kernel_blacklist - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- keycloak_client - fix TypeError when sanitizing the ``saml.signing.private.key``
|
||||
attribute in the module's diff or state output. The ``sanitize_cr`` function
|
||||
expected a dict where in some cases a list might occur (https://github.com/ansible-collections/community.general/pull/8403).
|
||||
- locale_gen - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- mksysb - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- pipx_info - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- snap - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
- snap_alias - use new ``VarDict`` to prevent deprecation warning (https://github.com/ansible-collections/community.general/issues/8410,
|
||||
https://github.com/ansible-collections/community.general/pull/8411).
|
||||
minor_changes:
|
||||
- ansible_galaxy_install - minor refactor in the module (https://github.com/ansible-collections/community.general/pull/8413).
|
||||
release_summary: Bugfix release for inclusion in Ansible 10.0.0rc1.
|
||||
fragments:
|
||||
- 8403-fix-typeerror-in-keycloak-client.yaml
|
||||
- 8406-fix-homebrew-cask-warning.yaml
|
||||
- 8411-locale-gen-vardict.yml
|
||||
- 8413-galaxy-refactor.yml
|
||||
- 9.0.1.yml
|
||||
release_date: '2024-05-27'
|
||||
9.1.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- git_config - fix behavior of ``state=absent`` if ``value`` is present (https://github.com/ansible-collections/community.general/issues/8436,
|
||||
https://github.com/ansible-collections/community.general/pull/8452).
|
||||
- keycloak_realm - add normalizations for ``attributes`` and ``protocol_mappers``
|
||||
(https://github.com/ansible-collections/community.general/pull/8496).
|
||||
- launched - correctly report changed status in check mode (https://github.com/ansible-collections/community.general/pull/8406).
|
||||
- opennebula inventory plugin - fix invalid reference to IP when inventory runs
|
||||
against NICs with no IPv4 address (https://github.com/ansible-collections/community.general/pull/8489).
|
||||
- opentelemetry callback - do not save the JSON response when using the ``ansible.builtin.uri``
|
||||
module (https://github.com/ansible-collections/community.general/pull/8430).
|
||||
- opentelemetry callback - do not save the content response when using the ``ansible.builtin.slurp``
|
||||
module (https://github.com/ansible-collections/community.general/pull/8430).
|
||||
- paman - do not fail if an empty list of packages has been provided and there
|
||||
is nothing to do (https://github.com/ansible-collections/community.general/pull/8514).
|
||||
deprecated_features:
|
||||
- CmdRunner module util - setting the value of the ``ignore_none`` parameter
|
||||
within a ``CmdRunner`` context is deprecated and that feature should be removed
|
||||
in community.general 12.0.0 (https://github.com/ansible-collections/community.general/pull/8479).
|
||||
- git_config - the ``list_all`` option has been deprecated and will be removed
|
||||
in community.general 11.0.0. Use the ``community.general.git_config_info``
|
||||
module instead (https://github.com/ansible-collections/community.general/pull/8453).
|
||||
- git_config - using ``state=present`` without providing ``value`` is deprecated
|
||||
and will be disallowed in community.general 11.0.0. Use the ``community.general.git_config_info``
|
||||
module instead to read a value (https://github.com/ansible-collections/community.general/pull/8453).
|
||||
known_issues:
|
||||
- homectl - the module does not work under Python 3.13 or newer, since it relies
|
||||
on the removed ``crypt`` standard library module (https://github.com/ansible-collections/community.general/issues/4691,
|
||||
https://github.com/ansible-collections/community.general/pull/8497).
|
||||
- udm_user - the module does not work under Python 3.13 or newer, since it relies
|
||||
on the removed ``crypt`` standard library module (https://github.com/ansible-collections/community.general/issues/4690,
|
||||
https://github.com/ansible-collections/community.general/pull/8497).
|
||||
minor_changes:
|
||||
- CmdRunner module util - argument formats can be specified as plain functions
|
||||
without calling ``cmd_runner_fmt.as_func()`` (https://github.com/ansible-collections/community.general/pull/8479).
|
||||
- ansible_galaxy_install - add upgrade feature (https://github.com/ansible-collections/community.general/pull/8431,
|
||||
https://github.com/ansible-collections/community.general/issues/8351).
|
||||
- cargo - add option ``directory``, which allows source directory to be specified
|
||||
(https://github.com/ansible-collections/community.general/pull/8480).
|
||||
- cmd_runner module utils - add decorator ``cmd_runner_fmt.stack`` (https://github.com/ansible-collections/community.general/pull/8415).
|
||||
- cmd_runner_fmt module utils - simplify implementation of ``cmd_runner_fmt.as_bool_not()``
|
||||
(https://github.com/ansible-collections/community.general/pull/8512).
|
||||
- ipa_dnsrecord - adds ``SSHFP`` record type for managing SSH fingerprints in
|
||||
FreeIPA DNS (https://github.com/ansible-collections/community.general/pull/8404).
|
||||
- keycloak_client - assign auth flow by name (https://github.com/ansible-collections/community.general/pull/8428).
|
||||
- openbsd_pkg - adds diff support to show changes in installed package list.
|
||||
This does not yet work for check mode (https://github.com/ansible-collections/community.general/pull/8402).
|
||||
- proxmox - allow specification of the API port when using proxmox_* (https://github.com/ansible-collections/community.general/issues/8440,
|
||||
https://github.com/ansible-collections/community.general/pull/8441).
|
||||
- proxmox_vm_info - add ``network`` option to retrieve current network information
|
||||
(https://github.com/ansible-collections/community.general/pull/8471).
|
||||
- redfish_command - add ``wait`` and ``wait_timeout`` options to allow a user
|
||||
to block a command until a service is accessible after performing the requested
|
||||
command (https://github.com/ansible-collections/community.general/issues/8051,
|
||||
https://github.com/ansible-collections/community.general/pull/8434).
|
||||
- redfish_info - add command ``CheckAvailability`` to check if a service is
|
||||
accessible (https://github.com/ansible-collections/community.general/issues/8051,
|
||||
https://github.com/ansible-collections/community.general/pull/8434).
|
||||
- redis_info - adds support for getting cluster info (https://github.com/ansible-collections/community.general/pull/8464).
|
||||
release_summary: Regular feature and bugfix release.
|
||||
fragments:
|
||||
- 8051-Redfish-Wait-For-Service.yml
|
||||
- 8402-add-diif-mode-openbsd-pkg.yml
|
||||
- 8404-ipa_dnsrecord_sshfp.yml
|
||||
- 8415-cmd-runner-stack.yml
|
||||
- 8428-assign-auth-flow-by-name-keycloak-client.yaml
|
||||
- 8430-fix-opentelemetry-when-using-logs-with-uri-or-slurp-tasks.yaml
|
||||
- 8431-galaxy-upgrade.yml
|
||||
- 8440-allow-api-port-specification.yaml
|
||||
- 8452-git_config-absent.yml
|
||||
- 8453-git_config-deprecate-read.yml
|
||||
- 8464-redis-add-cluster-info.yml
|
||||
- 8471-proxmox-vm-info-network.yml
|
||||
- 8476-launchd-check-mode-changed.yaml
|
||||
- 8479-cmdrunner-improvements.yml
|
||||
- 8480-directory-feature-cargo.yml
|
||||
- 8489-fix-opennebula-inventory-crash-when-nic-has-no-ip.yml
|
||||
- 8496-keycloak_clientscope-add-normalizations.yaml
|
||||
- 8497-crypt.yml
|
||||
- 8512-as-bool-not.yml
|
||||
- 8514-pacman-empty.yml
|
||||
- 9.1.0.yml
|
||||
modules:
|
||||
- description: Add, modify, and delete checks within a consul cluster.
|
||||
name: consul_agent_check
|
||||
namespace: ''
|
||||
- description: Add, modify and delete services within a consul cluster.
|
||||
name: consul_agent_service
|
||||
namespace: ''
|
||||
- description: Wrapper for C(django-admin check).
|
||||
name: django_check
|
||||
namespace: ''
|
||||
- description: Wrapper for C(django-admin createcachetable).
|
||||
name: django_createcachetable
|
||||
namespace: ''
|
||||
plugins:
|
||||
filter:
|
||||
- description: Keep specific keys from dictionaries in a list.
|
||||
name: keep_keys
|
||||
namespace: null
|
||||
- description: Remove specific keys from dictionaries in a list.
|
||||
name: remove_keys
|
||||
namespace: null
|
||||
- description: Replace specific keys in a list of dictionaries.
|
||||
name: replace_keys
|
||||
namespace: null
|
||||
release_date: '2024-06-17'
|
||||
|
||||
@@ -14,3 +14,7 @@ sections:
|
||||
- guide_online
|
||||
- guide_packet
|
||||
- guide_scaleway
|
||||
- title: Developer Guides
|
||||
toctree:
|
||||
- guide_deps
|
||||
- guide_vardict
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
# 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
|
||||
|
||||
list1:
|
||||
- name: foo
|
||||
extra: true
|
||||
- name: bar
|
||||
extra: false
|
||||
- name: meh
|
||||
extra: true
|
||||
- {name: foo, extra: true}
|
||||
- {name: bar, extra: false}
|
||||
- {name: meh, extra: true}
|
||||
|
||||
list2:
|
||||
- name: foo
|
||||
path: /foo
|
||||
- name: baz
|
||||
path: /baz
|
||||
- {name: foo, path: /foo}
|
||||
- {name: baz, path: /baz}
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
# 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
|
||||
|
||||
list1:
|
||||
- name: myname01
|
||||
param01:
|
||||
x: default_value
|
||||
y: default_value
|
||||
list:
|
||||
- default_value
|
||||
list: [default_value]
|
||||
- name: myname02
|
||||
param01: [1, 1, 2, 3]
|
||||
|
||||
@@ -18,7 +16,6 @@ list2:
|
||||
param01:
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
list:
|
||||
- patch_value
|
||||
list: [patch_value]
|
||||
- name: myname02
|
||||
param01: [3, 4, 4, {key: value}]
|
||||
param01: [3, 4, 4]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-001_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-001.out
|
||||
|
||||
@@ -2,6 +2,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
|
||||
|
||||
list3: "{{ list1|
|
||||
list3: "{{ list1 |
|
||||
community.general.lists_mergeby(list2, 'name') }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-002_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-002.out
|
||||
|
||||
@@ -2,6 +2,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
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name') }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-003_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-003.out
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# 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
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true) }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-004_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-004.out
|
||||
|
||||
@@ -2,8 +2,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
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='keep') }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-005_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-005.out
|
||||
|
||||
@@ -2,8 +2,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
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='append') }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-006_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-006.out
|
||||
|
||||
@@ -2,8 +2,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
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='prepend') }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-007_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug|d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-007.out
|
||||
|
||||
@@ -2,8 +2,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
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='append_rp') }}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
dir: example-008_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug|d(false)|bool
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-008.out
|
||||
|
||||
@@ -2,8 +2,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
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='prepend_rp') }}"
|
||||
|
||||
14
docs/docsite/helper/lists_mergeby/example-009.yml
Normal file
14
docs/docsite/helper/lists_mergeby/example-009.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
# 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: 9. Merge single list by common attribute 'name'
|
||||
include_vars:
|
||||
dir: example-009_vars
|
||||
- debug:
|
||||
var: list3
|
||||
when: debug | d(false) | bool
|
||||
- template:
|
||||
src: list3.out.j2
|
||||
dest: example-009.out
|
||||
@@ -0,0 +1 @@
|
||||
../default-common.yml
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
# 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
|
||||
list3: "{{ [list1 + list2, []] |
|
||||
community.general.lists_mergeby('name') }}"
|
||||
@@ -4,51 +4,75 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
examples:
|
||||
- label: 'In the example below the lists are merged by the attribute ``name``:'
|
||||
- title: Two lists
|
||||
description: 'In the example below the lists are merged by the attribute ``name``:'
|
||||
file: example-001_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces:'
|
||||
- title:
|
||||
description: 'This produces:'
|
||||
file: example-001.out
|
||||
lang: 'yaml'
|
||||
- label: 'It is possible to use a list of lists as an input of the filter:'
|
||||
- title: List of two lists
|
||||
description: 'It is possible to use a list of lists as an input of the filter:'
|
||||
file: example-002_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces the same result as in the previous example:'
|
||||
- title:
|
||||
description: 'This produces the same result as in the previous example:'
|
||||
file: example-002.out
|
||||
lang: 'yaml'
|
||||
- label: 'Example ``list_merge=replace`` (default):'
|
||||
- title: Single list
|
||||
description: 'It is possible to merge single list:'
|
||||
file: example-009_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- title:
|
||||
description: 'This produces the same result as in the previous example:'
|
||||
file: example-009.out
|
||||
lang: 'yaml'
|
||||
- title: list_merge=replace (default)
|
||||
description: 'Example :ansopt:`community.general.lists_mergeby#filter:list_merge=replace` (default):'
|
||||
file: example-003_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces:'
|
||||
- title:
|
||||
description: 'This produces:'
|
||||
file: example-003.out
|
||||
lang: 'yaml'
|
||||
- label: 'Example ``list_merge=keep``:'
|
||||
- title: list_merge=keep
|
||||
description: 'Example :ansopt:`community.general.lists_mergeby#filter:list_merge=keep`:'
|
||||
file: example-004_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces:'
|
||||
- title:
|
||||
description: 'This produces:'
|
||||
file: example-004.out
|
||||
lang: 'yaml'
|
||||
- label: 'Example ``list_merge=append``:'
|
||||
- title: list_merge=append
|
||||
description: 'Example :ansopt:`community.general.lists_mergeby#filter:list_merge=append`:'
|
||||
file: example-005_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces:'
|
||||
- title:
|
||||
description: 'This produces:'
|
||||
file: example-005.out
|
||||
lang: 'yaml'
|
||||
- label: 'Example ``list_merge=prepend``:'
|
||||
- title: list_merge=prepend
|
||||
description: 'Example :ansopt:`community.general.lists_mergeby#filter:list_merge=prepend`:'
|
||||
file: example-006_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces:'
|
||||
- title:
|
||||
description: 'This produces:'
|
||||
file: example-006.out
|
||||
lang: 'yaml'
|
||||
- label: 'Example ``list_merge=append_rp``:'
|
||||
- title: list_merge=append_rp
|
||||
description: 'Example :ansopt:`community.general.lists_mergeby#filter:list_merge=append_rp`:'
|
||||
file: example-007_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces:'
|
||||
- title:
|
||||
description: 'This produces:'
|
||||
file: example-007.out
|
||||
lang: 'yaml'
|
||||
- label: 'Example ``list_merge=prepend_rp``:'
|
||||
- title: list_merge=prepend_rp
|
||||
description: 'Example :ansopt:`community.general.lists_mergeby#filter:list_merge=prepend_rp`:'
|
||||
file: example-008_vars/list3.yml
|
||||
lang: 'yaml+jinja'
|
||||
- label: 'This produces:'
|
||||
- title:
|
||||
description: 'This produces:'
|
||||
file: example-008.out
|
||||
lang: 'yaml'
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
{% for i in examples %}
|
||||
{{ i.label }}
|
||||
{{ i.description }}
|
||||
|
||||
.. code-block:: {{ i.lang }}
|
||||
|
||||
{{ lookup('file', i.file)|indent(2) }}
|
||||
{{ lookup('file', i.file) | split('\n') | reject('match', '^(#|---)') | join ('\n') | indent(2) }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
7
docs/docsite/helper/lists_mergeby/extra-vars.yml
Normal file
7
docs/docsite/helper/lists_mergeby/extra-vars.yml
Normal file
@@ -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
|
||||
examples_one: true
|
||||
examples_all: true
|
||||
merging_lists_of_dictionaries: true
|
||||
@@ -6,57 +6,69 @@
|
||||
Merging lists of dictionaries
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have two or more lists of dictionaries and want to combine them into a list of merged dictionaries, where the dictionaries are merged by an attribute, you can use the ``lists_mergeby`` filter.
|
||||
If you have two or more lists of dictionaries and want to combine them into a list of merged dictionaries, where the dictionaries are merged by an attribute, you can use the :ansplugin:`community.general.lists_mergeby <community.general.lists_mergeby#filter>` filter.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See :ref:`the documentation for the community.general.yaml callback plugin <ansible_collections.community.general.yaml_callback>`.
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See the documentation for the :ansplugin:`community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
Let us use the lists below in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
{{ lookup('file', 'default-common.yml')|indent(2) }}
|
||||
{{ lookup('file', 'default-common.yml') | split('\n') | reject('match', '^(#|---)') | join ('\n') | indent(2) }}
|
||||
|
||||
{% for i in examples[0:2] %}
|
||||
{{ i.label }}
|
||||
{% if i.title | d('', true) | length > 0 %}
|
||||
{{ i.title }}
|
||||
{{ "%s" % ('"' * i.title|length) }}
|
||||
{% endif %}
|
||||
{{ i.description }}
|
||||
|
||||
.. code-block:: {{ i.lang }}
|
||||
|
||||
{{ lookup('file', i.file)|indent(2) }}
|
||||
{{ lookup('file', i.file) | split('\n') | reject('match', '^(#|---)') | join ('\n') | indent(2) }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
|
||||
{% for i in examples[2:4] %}
|
||||
{{ i.label }}
|
||||
{% for i in examples[2:6] %}
|
||||
{% if i.title | d('', true) | length > 0 %}
|
||||
{{ i.title }}
|
||||
{{ "%s" % ('"' * i.title|length) }}
|
||||
{% endif %}
|
||||
{{ i.description }}
|
||||
|
||||
.. code-block:: {{ i.lang }}
|
||||
|
||||
{{ lookup('file', i.file)|indent(2) }}
|
||||
{{ lookup('file', i.file) | split('\n') | reject('match', '^(#|---)') | join ('\n') | indent(2) }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
The filter also accepts two optional parameters: ``recursive`` and ``list_merge``. These parameters are only supported when used with ansible-base 2.10 or ansible-core, but not with Ansible 2.9. This is available since community.general 4.4.0.
|
||||
The filter also accepts two optional parameters: :ansopt:`community.general.lists_mergeby#filter:recursive` and :ansopt:`community.general.lists_mergeby#filter:list_merge`. This is available since community.general 4.4.0.
|
||||
|
||||
**recursive**
|
||||
Is a boolean, default to ``False``. Should the ``community.general.lists_mergeby`` recursively merge nested hashes. Note: It does not depend on the value of the ``hash_behaviour`` setting in ``ansible.cfg``.
|
||||
Is a boolean, default to ``false``. Should the :ansplugin:`community.general.lists_mergeby#filter` filter recursively merge nested hashes. Note: It does not depend on the value of the ``hash_behaviour`` setting in ``ansible.cfg``.
|
||||
|
||||
**list_merge**
|
||||
Is a string, its possible values are ``replace`` (default), ``keep``, ``append``, ``prepend``, ``append_rp`` or ``prepend_rp``. It modifies the behaviour of ``community.general.lists_mergeby`` when the hashes to merge contain arrays/lists.
|
||||
Is a string, its possible values are :ansval:`replace` (default), :ansval:`keep`, :ansval:`append`, :ansval:`prepend`, :ansval:`append_rp` or :ansval:`prepend_rp`. It modifies the behaviour of :ansplugin:`community.general.lists_mergeby#filter` when the hashes to merge contain arrays/lists.
|
||||
|
||||
The examples below set ``recursive=true`` and display the differences among all six options of ``list_merge``. Functionality of the parameters is exactly the same as in the filter ``combine``. See :ref:`Combining hashes/dictionaries <combine_filter>` to learn details about these options.
|
||||
The examples below set :ansopt:`community.general.lists_mergeby#filter:recursive=true` and display the differences among all six options of :ansopt:`community.general.lists_mergeby#filter:list_merge`. Functionality of the parameters is exactly the same as in the filter :ansplugin:`ansible.builtin.combine#filter`. See :ref:`Combining hashes/dictionaries <combine_filter>` to learn details about these options.
|
||||
|
||||
Let us use the lists below in the following examples
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
{{ lookup('file', 'default-recursive-true.yml')|indent(2) }}
|
||||
{{ lookup('file', 'default-recursive-true.yml') | split('\n') | reject('match', '^(#|---)') | join ('\n') |indent(2) }}
|
||||
|
||||
{% for i in examples[4:16] %}
|
||||
{{ i.label }}
|
||||
{% for i in examples[6:] %}
|
||||
{% if i.title | d('', true) | length > 0 %}
|
||||
{{ i.title }}
|
||||
{{ "%s" % ('"' * i.title|length) }}
|
||||
{% endif %}
|
||||
{{ i.description }}
|
||||
|
||||
.. code-block:: {{ i.lang }}
|
||||
|
||||
{{ lookup('file', i.file)|indent(2) }}
|
||||
{{ lookup('file', i.file) | split('\n') | reject('match', '^(#|---)') | join ('\n') |indent(2) }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
@@ -4,4 +4,4 @@ GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://w
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#}
|
||||
list3:
|
||||
{{ list3|to_nice_yaml(indent=0) }}
|
||||
{{ list3 | to_yaml(indent=2, sort_keys=false) | indent(2) }}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# 1) Run all examples and create example-XXX.out
|
||||
# shell> ansible-playbook playbook.yml -e examples=true
|
||||
# shell> ansible-playbook playbook.yml -e examples_one=true
|
||||
#
|
||||
# 2) Optionally, for testing, create examples_all.rst
|
||||
# shell> ansible-playbook playbook.yml -e examples_all=true
|
||||
@@ -45,18 +45,20 @@
|
||||
tags: t007
|
||||
- import_tasks: example-008.yml
|
||||
tags: t008
|
||||
when: examples|d(false)|bool
|
||||
- import_tasks: example-009.yml
|
||||
tags: t009
|
||||
when: examples_one | d(false) | bool
|
||||
|
||||
- block:
|
||||
- include_vars: examples.yml
|
||||
- template:
|
||||
src: examples_all.rst.j2
|
||||
dest: examples_all.rst
|
||||
when: examples_all|d(false)|bool
|
||||
when: examples_all | d(false) | bool
|
||||
|
||||
- block:
|
||||
- include_vars: examples.yml
|
||||
- template:
|
||||
src: filter_guide_abstract_informations_merging_lists_of_dictionaries.rst.j2
|
||||
dest: filter_guide_abstract_informations_merging_lists_of_dictionaries.rst
|
||||
when: merging_lists_of_dictionaries|d(false)|bool
|
||||
when: merging_lists_of_dictionaries | d(false) | bool
|
||||
|
||||
@@ -6,33 +6,30 @@
|
||||
Merging lists of dictionaries
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have two or more lists of dictionaries and want to combine them into a list of merged dictionaries, where the dictionaries are merged by an attribute, you can use the :ansplugin:`community.general.lists_mergeby filter <community.general.lists_mergeby#filter>`.
|
||||
If you have two or more lists of dictionaries and want to combine them into a list of merged dictionaries, where the dictionaries are merged by an attribute, you can use the :ansplugin:`community.general.lists_mergeby <community.general.lists_mergeby#filter>` filter.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See :ref:`the documentation for the community.general.yaml callback plugin <ansible_collections.community.general.yaml_callback>`.
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See the documentation for the :ansplugin:`community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
Let us use the lists below in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
list1:
|
||||
- name: foo
|
||||
extra: true
|
||||
- name: bar
|
||||
extra: false
|
||||
- name: meh
|
||||
extra: true
|
||||
- {name: foo, extra: true}
|
||||
- {name: bar, extra: false}
|
||||
- {name: meh, extra: true}
|
||||
|
||||
list2:
|
||||
- name: foo
|
||||
path: /foo
|
||||
- name: baz
|
||||
path: /baz
|
||||
- {name: foo, path: /foo}
|
||||
- {name: baz, path: /baz}
|
||||
|
||||
Two lists
|
||||
"""""""""
|
||||
In the example below the lists are merged by the attribute ``name``:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ list1|
|
||||
list3: "{{ list1 |
|
||||
community.general.lists_mergeby(list2, 'name') }}"
|
||||
|
||||
This produces:
|
||||
@@ -40,24 +37,21 @@ This produces:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- extra: false
|
||||
name: bar
|
||||
- name: baz
|
||||
path: /baz
|
||||
- extra: true
|
||||
name: foo
|
||||
path: /foo
|
||||
- extra: true
|
||||
name: meh
|
||||
- {name: bar, extra: false}
|
||||
- {name: baz, path: /baz}
|
||||
- {name: foo, extra: true, path: /foo}
|
||||
- {name: meh, extra: true}
|
||||
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
|
||||
List of two lists
|
||||
"""""""""""""""""
|
||||
It is possible to use a list of lists as an input of the filter:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name') }}"
|
||||
|
||||
This produces the same result as in the previous example:
|
||||
@@ -65,15 +59,29 @@ This produces the same result as in the previous example:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- extra: false
|
||||
name: bar
|
||||
- name: baz
|
||||
path: /baz
|
||||
- extra: true
|
||||
name: foo
|
||||
path: /foo
|
||||
- extra: true
|
||||
name: meh
|
||||
- {name: bar, extra: false}
|
||||
- {name: baz, path: /baz}
|
||||
- {name: foo, extra: true, path: /foo}
|
||||
- {name: meh, extra: true}
|
||||
|
||||
Single list
|
||||
"""""""""""
|
||||
It is possible to merge single list:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1 + list2, []] |
|
||||
community.general.lists_mergeby('name') }}"
|
||||
|
||||
This produces the same result as in the previous example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- {name: bar, extra: false}
|
||||
- {name: baz, path: /baz}
|
||||
- {name: foo, extra: true, path: /foo}
|
||||
- {name: meh, extra: true}
|
||||
|
||||
|
||||
The filter also accepts two optional parameters: :ansopt:`community.general.lists_mergeby#filter:recursive` and :ansopt:`community.general.lists_mergeby#filter:list_merge`. This is available since community.general 4.4.0.
|
||||
@@ -95,8 +103,7 @@ Let us use the lists below in the following examples
|
||||
param01:
|
||||
x: default_value
|
||||
y: default_value
|
||||
list:
|
||||
- default_value
|
||||
list: [default_value]
|
||||
- name: myname02
|
||||
param01: [1, 1, 2, 3]
|
||||
|
||||
@@ -105,16 +112,17 @@ Let us use the lists below in the following examples
|
||||
param01:
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
list:
|
||||
- patch_value
|
||||
list: [patch_value]
|
||||
- name: myname02
|
||||
param01: [3, 4, 4, {key: value}]
|
||||
param01: [3, 4, 4]
|
||||
|
||||
list_merge=replace (default)
|
||||
""""""""""""""""""""""""""""
|
||||
Example :ansopt:`community.general.lists_mergeby#filter:list_merge=replace` (default):
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true) }}"
|
||||
|
||||
@@ -123,25 +131,22 @@ This produces:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- name: myname01
|
||||
param01:
|
||||
list:
|
||||
- patch_value
|
||||
x: default_value
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01:
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- key: value
|
||||
- name: myname01
|
||||
param01:
|
||||
x: default_value
|
||||
y: patch_value
|
||||
list: [patch_value]
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01: [3, 4, 4]
|
||||
|
||||
list_merge=keep
|
||||
"""""""""""""""
|
||||
Example :ansopt:`community.general.lists_mergeby#filter:list_merge=keep`:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='keep') }}"
|
||||
@@ -151,25 +156,22 @@ This produces:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- name: myname01
|
||||
param01:
|
||||
list:
|
||||
- default_value
|
||||
x: default_value
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01:
|
||||
- 1
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- name: myname01
|
||||
param01:
|
||||
x: default_value
|
||||
y: patch_value
|
||||
list: [default_value]
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01: [1, 1, 2, 3]
|
||||
|
||||
list_merge=append
|
||||
"""""""""""""""""
|
||||
Example :ansopt:`community.general.lists_mergeby#filter:list_merge=append`:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='append') }}"
|
||||
@@ -179,30 +181,22 @@ This produces:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- name: myname01
|
||||
param01:
|
||||
list:
|
||||
- default_value
|
||||
- patch_value
|
||||
x: default_value
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01:
|
||||
- 1
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- key: value
|
||||
- name: myname01
|
||||
param01:
|
||||
x: default_value
|
||||
y: patch_value
|
||||
list: [default_value, patch_value]
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01: [1, 1, 2, 3, 3, 4, 4]
|
||||
|
||||
list_merge=prepend
|
||||
""""""""""""""""""
|
||||
Example :ansopt:`community.general.lists_mergeby#filter:list_merge=prepend`:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='prepend') }}"
|
||||
@@ -212,30 +206,22 @@ This produces:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- name: myname01
|
||||
param01:
|
||||
list:
|
||||
- patch_value
|
||||
- default_value
|
||||
x: default_value
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01:
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- key: value
|
||||
- 1
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- name: myname01
|
||||
param01:
|
||||
x: default_value
|
||||
y: patch_value
|
||||
list: [patch_value, default_value]
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01: [3, 4, 4, 1, 1, 2, 3]
|
||||
|
||||
list_merge=append_rp
|
||||
""""""""""""""""""""
|
||||
Example :ansopt:`community.general.lists_mergeby#filter:list_merge=append_rp`:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='append_rp') }}"
|
||||
@@ -245,29 +231,22 @@ This produces:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- name: myname01
|
||||
param01:
|
||||
list:
|
||||
- default_value
|
||||
- patch_value
|
||||
x: default_value
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01:
|
||||
- 1
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- key: value
|
||||
- name: myname01
|
||||
param01:
|
||||
x: default_value
|
||||
y: patch_value
|
||||
list: [default_value, patch_value]
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01: [1, 1, 2, 3, 4, 4]
|
||||
|
||||
list_merge=prepend_rp
|
||||
"""""""""""""""""""""
|
||||
Example :ansopt:`community.general.lists_mergeby#filter:list_merge=prepend_rp`:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
list3: "{{ [list1, list2]|
|
||||
list3: "{{ [list1, list2] |
|
||||
community.general.lists_mergeby('name',
|
||||
recursive=true,
|
||||
list_merge='prepend_rp') }}"
|
||||
@@ -277,21 +256,12 @@ This produces:
|
||||
.. code-block:: yaml
|
||||
|
||||
list3:
|
||||
- name: myname01
|
||||
param01:
|
||||
list:
|
||||
- patch_value
|
||||
- default_value
|
||||
x: default_value
|
||||
y: patch_value
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01:
|
||||
- 3
|
||||
- 4
|
||||
- 4
|
||||
- key: value
|
||||
- 1
|
||||
- 1
|
||||
- 2
|
||||
- name: myname01
|
||||
param01:
|
||||
x: default_value
|
||||
y: patch_value
|
||||
list: [patch_value, default_value]
|
||||
z: patch_value
|
||||
- name: myname02
|
||||
param01: [3, 4, 4, 1, 1, 2]
|
||||
|
||||
|
||||
74
docs/docsite/rst/guide_deps.rst
Normal file
74
docs/docsite/rst/guide_deps.rst
Normal file
@@ -0,0 +1,74 @@
|
||||
..
|
||||
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
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_deps:
|
||||
|
||||
``deps`` Guide
|
||||
==============
|
||||
|
||||
|
||||
Using ``deps``
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The ``ansible_collections.community.general.plugins.module_utils.deps`` module util simplifies
|
||||
the importing of code as described in :ref:`Importing and using shared code <shared_code>`.
|
||||
Please notice that ``deps`` is meant to be used specifically with Ansible modules, and not other types of plugins.
|
||||
|
||||
The same example from the Developer Guide would become:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils import deps
|
||||
|
||||
with deps.declare("foo"):
|
||||
import foo
|
||||
|
||||
Then in ``main()``, just after the argspec (or anywhere in the code, for that matter), do
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
deps.validate(module) # assuming module is a valid AnsibleModule instance
|
||||
|
||||
By default, ``deps`` will rely on ``ansible.module_utils.basic.missing_required_lib`` to generate
|
||||
a message about a failing import. That function accepts parameters ``reason`` and ``url``, and
|
||||
and so does ``deps```:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with deps.declare("foo", reason="foo is needed to properly bar", url="https://foo.bar.io"):
|
||||
import foo
|
||||
|
||||
If you would rather write a custom message instead of using ``missing_required_lib`` then do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with deps.declare("foo", msg="Custom msg explaining why foo is needed"):
|
||||
import foo
|
||||
|
||||
``deps`` allows for multiple dependencies to be declared:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with deps.declare("foo"):
|
||||
import foo
|
||||
|
||||
with deps.declare("bar"):
|
||||
import bar
|
||||
|
||||
with deps.declare("doe"):
|
||||
import doe
|
||||
|
||||
By default, ``deps.validate()`` will check on all the declared dependencies, but if so desired,
|
||||
they can be validated selectively by doing:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
deps.validate(module, "foo") # only validates the "foo" dependency
|
||||
|
||||
deps.validate(module, "doe:bar") # only validates the "doe" and "bar" dependencies
|
||||
|
||||
deps.validate(module, "-doe:bar") # validates all dependencies except "doe" and "bar"
|
||||
|
||||
.. versionadded:: 6.1.0
|
||||
176
docs/docsite/rst/guide_vardict.rst
Normal file
176
docs/docsite/rst/guide_vardict.rst
Normal file
@@ -0,0 +1,176 @@
|
||||
..
|
||||
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
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_vardict:
|
||||
|
||||
VarDict Guide
|
||||
=============
|
||||
|
||||
Introduction
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The ``ansible_collections.community.general.plugins.module_utils.vardict`` module util provides the
|
||||
``VarDict`` class to help manage the module variables. That class is a container for module variables,
|
||||
especially the ones for which the module must keep track of state changes, and the ones that should
|
||||
be published as return values.
|
||||
|
||||
Each variable has extra behaviors controlled by associated metadata, simplifying the generation of
|
||||
output values from the module.
|
||||
|
||||
Quickstart
|
||||
""""""""""
|
||||
|
||||
The simplest way of using ``VarDict`` is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.vardict import VarDict
|
||||
|
||||
Then in ``main()``, or any other function called from there:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars = VarDict()
|
||||
|
||||
# Next 3 statements are equivalent
|
||||
vars.abc = 123
|
||||
vars["abc"] = 123
|
||||
vars.set("abc", 123)
|
||||
|
||||
vars.xyz = "bananas"
|
||||
vars.ghi = False
|
||||
|
||||
And by the time the module is about to exit:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
results = vars.output()
|
||||
module.exit_json(**results)
|
||||
|
||||
That makes the return value of the module:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"abc": 123,
|
||||
"xyz": "bananas",
|
||||
"ghi": false
|
||||
}
|
||||
|
||||
Metadata
|
||||
""""""""
|
||||
|
||||
The metadata values associated with each variable are:
|
||||
|
||||
- ``output: bool`` - marks the variable for module output as a module return value.
|
||||
- ``fact: bool`` - marks the variable for module output as an Ansible fact.
|
||||
- ``verbosity: int`` - sets the minimum level of verbosity for which the variable will be included in the output.
|
||||
- ``change: bool`` - controls the detection of changes in the variable value.
|
||||
- ``initial_value: any`` - when using ``change`` and need to forcefully set an intial value to the variable.
|
||||
- ``diff: bool`` - used along with ``change``, this generates an Ansible-style diff ``dict``.
|
||||
|
||||
See the sections below for more details on how to use the metadata.
|
||||
|
||||
|
||||
Using VarDict
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Basic Usage
|
||||
"""""""""""
|
||||
|
||||
As shown above, variables can be accessed using the ``[]`` operator, as in a ``dict`` object,
|
||||
and also as an object attribute, such as ``vars.abc``. The form using the ``set()``
|
||||
method is special in the sense that you can use it to set metadata values:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", 123, output=False)
|
||||
vars.set("abc", 123, output=True, change=True)
|
||||
|
||||
Another way to set metadata after the variables have been created is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set_meta("abc", output=False)
|
||||
vars.set_meta("abc", output=True, change=True, diff=True)
|
||||
|
||||
You can use either operator and attribute forms to access the value of the variable. Other ways to
|
||||
access its value and its metadata are:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print("abc value = {0}".format(vars.var("abc")["value"])) # get the value
|
||||
print("abc output? {0}".format(vars.get_meta("abc")["output"])) # get the metadata like this
|
||||
|
||||
The names of methods, such as ``set``, ``get_meta``, ``output`` amongst others, are reserved and
|
||||
cannot be used as variable names. If you try to use a reserved name a ``ValueError`` exception
|
||||
is raised with the message "Name <var> is reserved".
|
||||
|
||||
Generating output
|
||||
"""""""""""""""""
|
||||
|
||||
By default, every variable create will be enable for output with minimum verbosity set to zero, in
|
||||
other words, they will always be in the output by default.
|
||||
|
||||
You can control that when creating the variable for the first time or later in the code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("internal", x + 4, output=False)
|
||||
vars.set_meta("internal", output=False)
|
||||
|
||||
You can also set the verbosity of some variable, like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", x + 4)
|
||||
vars.set("debug_x", x, verbosity=3)
|
||||
|
||||
results = vars.output(module._verbosity)
|
||||
module.exit_json(**results)
|
||||
|
||||
If the module was invoked with verbosity lower than 3, then the output will only contain
|
||||
the variable ``abc``. If running at higher verbosity, as in ``ansible-playbook -vvv``,
|
||||
then the output will also contain ``debug_x``.
|
||||
|
||||
Generating facts is very similar to regular output, but variables are not marked as facts by default.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("modulefact", x + 4, fact=True)
|
||||
vars.set("debugfact", x, fact=True, verbosity=3)
|
||||
|
||||
results = vars.output(module._verbosity)
|
||||
results["ansible_facts"] = {"module_name": vars.facts(module._verbosity)}
|
||||
module.exit_json(**results)
|
||||
|
||||
Handling change
|
||||
"""""""""""""""
|
||||
|
||||
You can use ``VarDict`` to determine whether variables have had their values changed.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", 42, change=True)
|
||||
vars.abc = 90
|
||||
|
||||
results = vars.output()
|
||||
results["changed"] = vars.has_changed
|
||||
module.exit_json(**results)
|
||||
|
||||
If tracking changes in variables, you may want to present the difference between the initial and the final
|
||||
values of it. For that, you want to use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", 42, change=True, diff=True)
|
||||
vars.abc = 90
|
||||
|
||||
results = vars.output()
|
||||
results["changed"] = vars.has_changed
|
||||
results["diff"] = vars.diff()
|
||||
module.exit_json(**results)
|
||||
|
||||
.. versionadded:: 7.1.0
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 9.0.0
|
||||
version: 9.1.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
requires_ansible: '>=2.13.0'
|
||||
action_groups:
|
||||
consul:
|
||||
- consul_agent_check
|
||||
- consul_agent_service
|
||||
- consul_auth_method
|
||||
- consul_binding_rule
|
||||
- consul_policy
|
||||
|
||||
@@ -556,11 +556,19 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
self.otel_exporter_otlp_traces_protocol = self.get_option('otel_exporter_otlp_traces_protocol')
|
||||
|
||||
def dump_results(self, result):
|
||||
def dump_results(self, task, result):
|
||||
""" dump the results if disable_logs is not enabled """
|
||||
if self.disable_logs:
|
||||
return ""
|
||||
return self._dump_results(result._result)
|
||||
# ansible.builtin.uri contains the response in the json field
|
||||
save = dict(result._result)
|
||||
|
||||
if "json" in save and task.action in ("ansible.builtin.uri", "ansible.legacy.uri", "uri"):
|
||||
save.pop("json")
|
||||
# ansible.builtin.slurp contains the response in the content field
|
||||
if "content" in save and task.action in ("ansible.builtin.slurp", "ansible.legacy.slurp", "slurp"):
|
||||
save.pop("content")
|
||||
return self._dump_results(save)
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
self.ansible_playbook = basename(playbook._file_name)
|
||||
@@ -611,7 +619,7 @@ class CallbackModule(CallbackBase):
|
||||
self.tasks_data,
|
||||
status,
|
||||
result,
|
||||
self.dump_results(result)
|
||||
self.dump_results(self.tasks_data[result._task._uuid], result)
|
||||
)
|
||||
|
||||
def v2_runner_on_ok(self, result):
|
||||
@@ -619,7 +627,7 @@ class CallbackModule(CallbackBase):
|
||||
self.tasks_data,
|
||||
'ok',
|
||||
result,
|
||||
self.dump_results(result)
|
||||
self.dump_results(self.tasks_data[result._task._uuid], result)
|
||||
)
|
||||
|
||||
def v2_runner_on_skipped(self, result):
|
||||
@@ -627,7 +635,7 @@ class CallbackModule(CallbackBase):
|
||||
self.tasks_data,
|
||||
'skipped',
|
||||
result,
|
||||
self.dump_results(result)
|
||||
self.dump_results(self.tasks_data[result._task._uuid], result)
|
||||
)
|
||||
|
||||
def v2_playbook_on_include(self, included_file):
|
||||
|
||||
@@ -19,6 +19,16 @@ DOCUMENTATION = '''
|
||||
- default_callback
|
||||
requirements:
|
||||
- set as stdout in configuration
|
||||
seealso:
|
||||
- plugin: ansible.builtin.default
|
||||
plugin_type: callback
|
||||
description: >
|
||||
There is a parameter O(ansible.builtin.default#callback:result_format) in P(ansible.builtin.default#callback)
|
||||
that allows you to change the output format to YAML.
|
||||
notes:
|
||||
- >
|
||||
With ansible-core 2.13 or newer, you can instead specify V(yaml) for the parameter O(ansible.builtin.default#callback:result_format)
|
||||
in P(ansible.builtin.default#callback).
|
||||
'''
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -51,3 +51,12 @@ seealso:
|
||||
Please make sure that you select the right version of Django in the version selector on that page.
|
||||
link: https://docs.djangoproject.com/en/5.0/ref/django-admin/
|
||||
'''
|
||||
|
||||
DATABASE = r'''
|
||||
options:
|
||||
database:
|
||||
description:
|
||||
- Specify the database to be used.
|
||||
type: str
|
||||
default: default
|
||||
'''
|
||||
|
||||
@@ -16,6 +16,13 @@ options:
|
||||
- Specify the target host of the Proxmox VE cluster.
|
||||
type: str
|
||||
required: true
|
||||
api_port:
|
||||
description:
|
||||
- Specify the target port of the Proxmox VE cluster.
|
||||
- Uses the E(PROXMOX_PORT) environment variable if not specified.
|
||||
type: int
|
||||
required: false
|
||||
version_added: 9.1.0
|
||||
api_user:
|
||||
description:
|
||||
- Specify the user to authenticate with.
|
||||
|
||||
138
plugins/filter/keep_keys.py
Normal file
138
plugins/filter/keep_keys.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2024 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
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: keep_keys
|
||||
short_description: Keep specific keys from dictionaries in a list
|
||||
version_added: "9.1.0"
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
- Felix Fontein (@felixfontein)
|
||||
description: This filter keeps only specified keys from a provided list of dictionaries.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- A list of dictionaries.
|
||||
- Top level keys must be strings.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
target:
|
||||
description:
|
||||
- A single key or key pattern to keep, or a list of keys or keys patterns to keep.
|
||||
- If O(matching_parameter=regex) there must be exactly one pattern provided.
|
||||
type: raw
|
||||
required: true
|
||||
matching_parameter:
|
||||
description: Specify the matching option of target keys.
|
||||
type: str
|
||||
default: equal
|
||||
choices:
|
||||
equal: Matches keys of exactly one of the O(target) items.
|
||||
starts_with: Matches keys that start with one of the O(target) items.
|
||||
ends_with: Matches keys that end with one of the O(target) items.
|
||||
regex:
|
||||
- Matches keys that match the regular expresion provided in O(target).
|
||||
- In this case, O(target) must be a regex string or a list with single regex string.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
l:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 1) By default match keys that equal any of the items in the target.
|
||||
t: [k0_x0, k1_x1]
|
||||
r: "{{ l | community.general.keep_keys(target=t) }}"
|
||||
|
||||
# 2) Match keys that start with any of the items in the target.
|
||||
t: [k0, k1]
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 3) Match keys that end with any of the items in target.
|
||||
t: [x0, x1]
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 4) Match keys by the regex.
|
||||
t: ['^.*[01]_x.*$']
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# 5) Match keys by the regex.
|
||||
t: '^.*[01]_x.*$'
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 1-5 are all the same.
|
||||
r:
|
||||
- {k0_x0: A0, k1_x1: B0}
|
||||
- {k0_x0: A1, k1_x1: B1}
|
||||
|
||||
# 6) By default match keys that equal the target.
|
||||
t: k0_x0
|
||||
r: "{{ l | community.general.keep_keys(target=t) }}"
|
||||
|
||||
# 7) Match keys that start with the target.
|
||||
t: k0
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 8) Match keys that end with the target.
|
||||
t: x0
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 9) Match keys by the regex.
|
||||
t: '^.*0_x.*$'
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 6-9 are all the same.
|
||||
r:
|
||||
- {k0_x0: A0}
|
||||
- {k0_x0: A1}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The list of dictionaries with selected keys.
|
||||
type: list
|
||||
elements: dictionary
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.keys_filter import (
|
||||
_keys_filter_params,
|
||||
_keys_filter_target_str)
|
||||
|
||||
|
||||
def keep_keys(data, target=None, matching_parameter='equal'):
|
||||
"""keep specific keys from dictionaries in a list"""
|
||||
|
||||
# test parameters
|
||||
_keys_filter_params(data, matching_parameter)
|
||||
# test and transform target
|
||||
tt = _keys_filter_target_str(target, matching_parameter)
|
||||
|
||||
if matching_parameter == 'equal':
|
||||
def keep_key(key):
|
||||
return key in tt
|
||||
elif matching_parameter == 'starts_with':
|
||||
def keep_key(key):
|
||||
return key.startswith(tt)
|
||||
elif matching_parameter == 'ends_with':
|
||||
def keep_key(key):
|
||||
return key.endswith(tt)
|
||||
elif matching_parameter == 'regex':
|
||||
def keep_key(key):
|
||||
return tt.match(key) is not None
|
||||
|
||||
return [dict((k, v) for k, v in d.items() if keep_key(k)) for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'keep_keys': keep_keys,
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020-2022, Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2020-2024, Vladimir Botka <vbotka@gmail.com>
|
||||
# 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
|
||||
|
||||
@@ -12,22 +12,32 @@ DOCUMENTATION = '''
|
||||
version_added: 2.0.0
|
||||
author: Vladimir Botka (@vbotka)
|
||||
description:
|
||||
- Merge two or more lists by attribute O(index). Optional parameters O(recursive) and O(list_merge)
|
||||
control the merging of the lists in values. The function merge_hash from ansible.utils.vars
|
||||
is used. To learn details on how to use the parameters O(recursive) and O(list_merge) see
|
||||
Ansible User's Guide chapter "Using filters to manipulate data" section "Combining
|
||||
hashes/dictionaries".
|
||||
- Merge two or more lists by attribute O(index). Optional
|
||||
parameters O(recursive) and O(list_merge) control the merging of
|
||||
the nested dictionaries and lists.
|
||||
- The function C(merge_hash) from C(ansible.utils.vars) is used.
|
||||
- To learn details on how to use the parameters O(recursive) and
|
||||
O(list_merge) see Ansible User's Guide chapter "Using filters to
|
||||
manipulate data" section R(Combining hashes/dictionaries, combine_filter) or the
|
||||
filter P(ansible.builtin.combine#filter).
|
||||
|
||||
positional: another_list, index
|
||||
options:
|
||||
_input:
|
||||
description: A list of dictionaries.
|
||||
description:
|
||||
- A list of dictionaries, or a list of lists of dictionaries.
|
||||
- The required type of the C(elements) is set to C(raw)
|
||||
because all elements of O(_input) can be either dictionaries
|
||||
or lists.
|
||||
type: list
|
||||
elements: dictionary
|
||||
elements: raw
|
||||
required: true
|
||||
another_list:
|
||||
description: Another list of dictionaries. This parameter can be specified multiple times.
|
||||
description:
|
||||
- Another list of dictionaries, or a list of lists of dictionaries.
|
||||
- This parameter can be specified multiple times.
|
||||
type: list
|
||||
elements: dictionary
|
||||
elements: raw
|
||||
index:
|
||||
description:
|
||||
- The dictionary key that must be present in every dictionary in every list that is used to
|
||||
@@ -55,40 +65,134 @@ DOCUMENTATION = '''
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Merge two lists
|
||||
# Some results below are manually formatted for better readability. The
|
||||
# dictionaries' keys will be sorted alphabetically in real output.
|
||||
|
||||
- name: Example 1. Merge two lists. The results r1 and r2 are the same.
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ list1 | community.general.lists_mergeby(
|
||||
list2,
|
||||
'index',
|
||||
recursive=True,
|
||||
list_merge='append'
|
||||
) }}"
|
||||
msg: |
|
||||
r1: {{ r1 }}
|
||||
r2: {{ r2 }}
|
||||
vars:
|
||||
list1:
|
||||
- index: a
|
||||
value: 123
|
||||
- index: b
|
||||
value: 42
|
||||
- {index: a, value: 123}
|
||||
- {index: b, value: 4}
|
||||
list2:
|
||||
- index: a
|
||||
foo: bar
|
||||
- index: c
|
||||
foo: baz
|
||||
# Produces the following list of dictionaries:
|
||||
# {
|
||||
# "index": "a",
|
||||
# "foo": "bar",
|
||||
# "value": 123
|
||||
# },
|
||||
# {
|
||||
# "index": "b",
|
||||
# "value": 42
|
||||
# },
|
||||
# {
|
||||
# "index": "c",
|
||||
# "foo": "baz"
|
||||
# }
|
||||
- {index: a, foo: bar}
|
||||
- {index: c, foo: baz}
|
||||
r1: "{{ list1 | community.general.lists_mergeby(list2, 'index') }}"
|
||||
r2: "{{ [list1, list2] | community.general.lists_mergeby('index') }}"
|
||||
|
||||
# r1:
|
||||
# - {index: a, foo: bar, value: 123}
|
||||
# - {index: b, value: 4}
|
||||
# - {index: c, foo: baz}
|
||||
# r2:
|
||||
# - {index: a, foo: bar, value: 123}
|
||||
# - {index: b, value: 4}
|
||||
# - {index: c, foo: baz}
|
||||
|
||||
- name: Example 2. Merge three lists
|
||||
ansible.builtin.debug:
|
||||
var: r
|
||||
vars:
|
||||
list1:
|
||||
- {index: a, value: 123}
|
||||
- {index: b, value: 4}
|
||||
list2:
|
||||
- {index: a, foo: bar}
|
||||
- {index: c, foo: baz}
|
||||
list3:
|
||||
- {index: d, foo: qux}
|
||||
r: "{{ [list1, list2, list3] | community.general.lists_mergeby('index') }}"
|
||||
|
||||
# r:
|
||||
# - {index: a, foo: bar, value: 123}
|
||||
# - {index: b, value: 4}
|
||||
# - {index: c, foo: baz}
|
||||
# - {index: d, foo: qux}
|
||||
|
||||
- name: Example 3. Merge single list. The result is the same as 2.
|
||||
ansible.builtin.debug:
|
||||
var: r
|
||||
vars:
|
||||
list1:
|
||||
- {index: a, value: 123}
|
||||
- {index: b, value: 4}
|
||||
- {index: a, foo: bar}
|
||||
- {index: c, foo: baz}
|
||||
- {index: d, foo: qux}
|
||||
r: "{{ [list1, []] | community.general.lists_mergeby('index') }}"
|
||||
|
||||
# r:
|
||||
# - {index: a, foo: bar, value: 123}
|
||||
# - {index: b, value: 4}
|
||||
# - {index: c, foo: baz}
|
||||
# - {index: d, foo: qux}
|
||||
|
||||
- name: Example 4. Merge two lists. By default, replace nested lists.
|
||||
ansible.builtin.debug:
|
||||
var: r
|
||||
vars:
|
||||
list1:
|
||||
- {index: a, foo: [X1, X2]}
|
||||
- {index: b, foo: [X1, X2]}
|
||||
list2:
|
||||
- {index: a, foo: [Y1, Y2]}
|
||||
- {index: b, foo: [Y1, Y2]}
|
||||
r: "{{ [list1, list2] | community.general.lists_mergeby('index') }}"
|
||||
|
||||
# r:
|
||||
# - {index: a, foo: [Y1, Y2]}
|
||||
# - {index: b, foo: [Y1, Y2]}
|
||||
|
||||
- name: Example 5. Merge two lists. Append nested lists.
|
||||
ansible.builtin.debug:
|
||||
var: r
|
||||
vars:
|
||||
list1:
|
||||
- {index: a, foo: [X1, X2]}
|
||||
- {index: b, foo: [X1, X2]}
|
||||
list2:
|
||||
- {index: a, foo: [Y1, Y2]}
|
||||
- {index: b, foo: [Y1, Y2]}
|
||||
r: "{{ [list1, list2] | community.general.lists_mergeby('index', list_merge='append') }}"
|
||||
|
||||
# r:
|
||||
# - {index: a, foo: [X1, X2, Y1, Y2]}
|
||||
# - {index: b, foo: [X1, X2, Y1, Y2]}
|
||||
|
||||
- name: Example 6. Merge two lists. By default, do not merge nested dictionaries.
|
||||
ansible.builtin.debug:
|
||||
var: r
|
||||
vars:
|
||||
list1:
|
||||
- {index: a, foo: {x: 1, y: 2}}
|
||||
- {index: b, foo: [X1, X2]}
|
||||
list2:
|
||||
- {index: a, foo: {y: 3, z: 4}}
|
||||
- {index: b, foo: [Y1, Y2]}
|
||||
r: "{{ [list1, list2] | community.general.lists_mergeby('index') }}"
|
||||
|
||||
# r:
|
||||
# - {index: a, foo: {y: 3, z: 4}}
|
||||
# - {index: b, foo: [Y1, Y2]}
|
||||
|
||||
- name: Example 7. Merge two lists. Merge nested dictionaries too.
|
||||
ansible.builtin.debug:
|
||||
var: r
|
||||
vars:
|
||||
list1:
|
||||
- {index: a, foo: {x: 1, y: 2}}
|
||||
- {index: b, foo: [X1, X2]}
|
||||
list2:
|
||||
- {index: a, foo: {y: 3, z: 4}}
|
||||
- {index: b, foo: [Y1, Y2]}
|
||||
r: "{{ [list1, list2] | community.general.lists_mergeby('index', recursive=true) }}"
|
||||
|
||||
# r:
|
||||
# - {index: a, foo: {x:1, y: 3, z: 4}}
|
||||
# - {index: b, foo: [Y1, Y2]}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -108,13 +212,14 @@ from operator import itemgetter
|
||||
|
||||
|
||||
def list_mergeby(x, y, index, recursive=False, list_merge='replace'):
|
||||
''' Merge 2 lists by attribute 'index'. The function merge_hash from ansible.utils.vars is used.
|
||||
This function is used by the function lists_mergeby.
|
||||
'''Merge 2 lists by attribute 'index'. The function 'merge_hash'
|
||||
from ansible.utils.vars is used. This function is used by the
|
||||
function lists_mergeby.
|
||||
'''
|
||||
|
||||
d = defaultdict(dict)
|
||||
for l in (x, y):
|
||||
for elem in l:
|
||||
for lst in (x, y):
|
||||
for elem in lst:
|
||||
if not isinstance(elem, Mapping):
|
||||
msg = "Elements of list arguments for lists_mergeby must be dictionaries. %s is %s"
|
||||
raise AnsibleFilterError(msg % (elem, type(elem)))
|
||||
@@ -124,20 +229,9 @@ def list_mergeby(x, y, index, recursive=False, list_merge='replace'):
|
||||
|
||||
|
||||
def lists_mergeby(*terms, **kwargs):
|
||||
''' Merge 2 or more lists by attribute 'index'. Optional parameters 'recursive' and 'list_merge'
|
||||
control the merging of the lists in values. The function merge_hash from ansible.utils.vars
|
||||
is used. To learn details on how to use the parameters 'recursive' and 'list_merge' see
|
||||
Ansible User's Guide chapter "Using filters to manipulate data" section "Combining
|
||||
hashes/dictionaries".
|
||||
|
||||
Example:
|
||||
- debug:
|
||||
msg: "{{ list1|
|
||||
community.general.lists_mergeby(list2,
|
||||
'index',
|
||||
recursive=True,
|
||||
list_merge='append')|
|
||||
list }}"
|
||||
'''Merge 2 or more lists by attribute 'index'. To learn details
|
||||
on how to use the parameters 'recursive' and 'list_merge' see
|
||||
the filter ansible.builtin.combine.
|
||||
'''
|
||||
|
||||
recursive = kwargs.pop('recursive', False)
|
||||
@@ -155,7 +249,7 @@ def lists_mergeby(*terms, **kwargs):
|
||||
"must be lists. %s is %s")
|
||||
raise AnsibleFilterError(msg % (sublist, type(sublist)))
|
||||
if len(sublist) > 0:
|
||||
if all(isinstance(l, Sequence) for l in sublist):
|
||||
if all(isinstance(lst, Sequence) for lst in sublist):
|
||||
for item in sublist:
|
||||
flat_list.append(item)
|
||||
else:
|
||||
|
||||
138
plugins/filter/remove_keys.py
Normal file
138
plugins/filter/remove_keys.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2024 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
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: remove_keys
|
||||
short_description: Remove specific keys from dictionaries in a list
|
||||
version_added: "9.1.0"
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
- Felix Fontein (@felixfontein)
|
||||
description: This filter removes only specified keys from a provided list of dictionaries.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- A list of dictionaries.
|
||||
- Top level keys must be strings.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
target:
|
||||
description:
|
||||
- A single key or key pattern to remove, or a list of keys or keys patterns to remove.
|
||||
- If O(matching_parameter=regex) there must be exactly one pattern provided.
|
||||
type: raw
|
||||
required: true
|
||||
matching_parameter:
|
||||
description: Specify the matching option of target keys.
|
||||
type: str
|
||||
default: equal
|
||||
choices:
|
||||
equal: Matches keys of exactly one of the O(target) items.
|
||||
starts_with: Matches keys that start with one of the O(target) items.
|
||||
ends_with: Matches keys that end with one of the O(target) items.
|
||||
regex:
|
||||
- Matches keys that match the regular expresion provided in O(target).
|
||||
- In this case, O(target) must be a regex string or a list with single regex string.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
l:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 1) By default match keys that equal any of the items in the target.
|
||||
t: [k0_x0, k1_x1]
|
||||
r: "{{ l | community.general.remove_keys(target=t) }}"
|
||||
|
||||
# 2) Match keys that start with any of the items in the target.
|
||||
t: [k0, k1]
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 3) Match keys that end with any of the items in target.
|
||||
t: [x0, x1]
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 4) Match keys by the regex.
|
||||
t: ['^.*[01]_x.*$']
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# 5) Match keys by the regex.
|
||||
t: '^.*[01]_x.*$'
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 1-5 are all the same.
|
||||
r:
|
||||
- {k2_x2: [C0], k3_x3: foo}
|
||||
- {k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 6) By default match keys that equal the target.
|
||||
t: k0_x0
|
||||
r: "{{ l | community.general.remove_keys(target=t) }}"
|
||||
|
||||
# 7) Match keys that start with the target.
|
||||
t: k0
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 8) Match keys that end with the target.
|
||||
t: x0
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 9) Match keys by the regex.
|
||||
t: '^.*0_x.*$'
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 6-9 are all the same.
|
||||
r:
|
||||
- {k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The list of dictionaries with selected keys removed.
|
||||
type: list
|
||||
elements: dictionary
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.keys_filter import (
|
||||
_keys_filter_params,
|
||||
_keys_filter_target_str)
|
||||
|
||||
|
||||
def remove_keys(data, target=None, matching_parameter='equal'):
|
||||
"""remove specific keys from dictionaries in a list"""
|
||||
|
||||
# test parameters
|
||||
_keys_filter_params(data, matching_parameter)
|
||||
# test and transform target
|
||||
tt = _keys_filter_target_str(target, matching_parameter)
|
||||
|
||||
if matching_parameter == 'equal':
|
||||
def keep_key(key):
|
||||
return key not in tt
|
||||
elif matching_parameter == 'starts_with':
|
||||
def keep_key(key):
|
||||
return not key.startswith(tt)
|
||||
elif matching_parameter == 'ends_with':
|
||||
def keep_key(key):
|
||||
return not key.endswith(tt)
|
||||
elif matching_parameter == 'regex':
|
||||
def keep_key(key):
|
||||
return tt.match(key) is None
|
||||
|
||||
return [dict((k, v) for k, v in d.items() if keep_key(k)) for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'remove_keys': remove_keys,
|
||||
}
|
||||
180
plugins/filter/replace_keys.py
Normal file
180
plugins/filter/replace_keys.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2024 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
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: replace_keys
|
||||
short_description: Replace specific keys in a list of dictionaries
|
||||
version_added: "9.1.0"
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
- Felix Fontein (@felixfontein)
|
||||
description: This filter replaces specified keys in a provided list of dictionaries.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- A list of dictionaries.
|
||||
- Top level keys must be strings.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
target:
|
||||
description:
|
||||
- A list of dictionaries with attributes C(before) and C(after).
|
||||
- The value of O(target[].after) replaces key matching O(target[].before).
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
suboptions:
|
||||
before:
|
||||
description:
|
||||
- A key or key pattern to change.
|
||||
- The interpretation of O(target[].before) depends on O(matching_parameter).
|
||||
- For a key that matches multiple O(target[].before)s, the B(first) matching O(target[].after) will be used.
|
||||
type: str
|
||||
after:
|
||||
description: A matching key change to.
|
||||
type: str
|
||||
matching_parameter:
|
||||
description: Specify the matching option of target keys.
|
||||
type: str
|
||||
default: equal
|
||||
choices:
|
||||
equal: Matches keys of exactly one of the O(target[].before) items.
|
||||
starts_with: Matches keys that start with one of the O(target[].before) items.
|
||||
ends_with: Matches keys that end with one of the O(target[].before) items.
|
||||
regex: Matches keys that match one of the regular expressions provided in O(target[].before).
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
l:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 1) By default, replace keys that are equal any of the attributes before.
|
||||
t:
|
||||
- {before: k0_x0, after: a0}
|
||||
- {before: k1_x1, after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t) }}"
|
||||
|
||||
# 2) Replace keys that starts with any of the attributes before.
|
||||
t:
|
||||
- {before: k0, after: a0}
|
||||
- {before: k1, after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 3) Replace keys that ends with any of the attributes before.
|
||||
t:
|
||||
- {before: x0, after: a0}
|
||||
- {before: x1, after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 4) Replace keys that match any regex of the attributes before.
|
||||
t:
|
||||
- {before: "^.*0_x.*$", after: a0}
|
||||
- {before: "^.*1_x.*$", after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 1-4 are all the same.
|
||||
r:
|
||||
- {a0: A0, a1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {a0: A1, a1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 5) If more keys match the same attribute before the last one will be used.
|
||||
t:
|
||||
- {before: "^.*_x.*$", after: X}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# gives
|
||||
|
||||
r:
|
||||
- X: foo
|
||||
- X: bar
|
||||
|
||||
# 6) If there are items with equal attribute before the first one will be used.
|
||||
t:
|
||||
- {before: "^.*_x.*$", after: X}
|
||||
- {before: "^.*_x.*$", after: Y}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# gives
|
||||
|
||||
r:
|
||||
- X: foo
|
||||
- X: bar
|
||||
|
||||
# 7) If there are more matches for a key the first one will be used.
|
||||
l:
|
||||
- {aaa1: A, bbb1: B, ccc1: C}
|
||||
- {aaa2: D, bbb2: E, ccc2: F}
|
||||
t:
|
||||
- {before: a, after: X}
|
||||
- {before: aa, after: Y}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# gives
|
||||
|
||||
r:
|
||||
- {X: A, bbb1: B, ccc1: C}
|
||||
- {X: D, bbb2: E, ccc2: F}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The list of dictionaries with replaced keys.
|
||||
type: list
|
||||
elements: dictionary
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.keys_filter import (
|
||||
_keys_filter_params,
|
||||
_keys_filter_target_dict)
|
||||
|
||||
|
||||
def replace_keys(data, target=None, matching_parameter='equal'):
|
||||
"""replace specific keys in a list of dictionaries"""
|
||||
|
||||
# test parameters
|
||||
_keys_filter_params(data, matching_parameter)
|
||||
# test and transform target
|
||||
tz = _keys_filter_target_dict(target, matching_parameter)
|
||||
|
||||
if matching_parameter == 'equal':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if key == b:
|
||||
return a
|
||||
return key
|
||||
elif matching_parameter == 'starts_with':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if key.startswith(b):
|
||||
return a
|
||||
return key
|
||||
elif matching_parameter == 'ends_with':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if key.endswith(b):
|
||||
return a
|
||||
return key
|
||||
elif matching_parameter == 'regex':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if b.match(key):
|
||||
return a
|
||||
return key
|
||||
|
||||
return [dict((replace_key(k), v) for k, v in d.items()) for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'replace_keys': replace_keys,
|
||||
}
|
||||
@@ -143,7 +143,8 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
nic = [nic]
|
||||
|
||||
for net in nic:
|
||||
return net['IP']
|
||||
if net.get('IP'):
|
||||
return net['IP']
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -89,18 +89,31 @@ class FormatError(CmdRunnerException):
|
||||
|
||||
|
||||
class _ArgFormat(object):
|
||||
# DEPRECATION: set default value for ignore_none to True in community.general 12.0.0
|
||||
def __init__(self, func, ignore_none=None, ignore_missing_value=False):
|
||||
self.func = func
|
||||
self.ignore_none = ignore_none
|
||||
self.ignore_missing_value = ignore_missing_value
|
||||
|
||||
def __call__(self, value, ctx_ignore_none):
|
||||
# DEPRECATION: remove parameter ctx_ignore_none in community.general 12.0.0
|
||||
def __call__(self, value, ctx_ignore_none=True):
|
||||
# DEPRECATION: replace ctx_ignore_none with True in community.general 12.0.0
|
||||
ignore_none = self.ignore_none if self.ignore_none is not None else ctx_ignore_none
|
||||
if value is None and ignore_none:
|
||||
return []
|
||||
f = self.func
|
||||
return [str(x) for x in f(value)]
|
||||
|
||||
def __str__(self):
|
||||
return "<ArgFormat: func={0}, ignore_none={1}, ignore_missing_value={2}>".format(
|
||||
self.func,
|
||||
self.ignore_none,
|
||||
self.ignore_missing_value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class _Format(object):
|
||||
@staticmethod
|
||||
@@ -114,7 +127,7 @@ class _Format(object):
|
||||
|
||||
@staticmethod
|
||||
def as_bool_not(args):
|
||||
return _ArgFormat(lambda value: [] if value else _ensure_list(args), ignore_none=False)
|
||||
return _Format.as_bool([], args, ignore_none=False)
|
||||
|
||||
@staticmethod
|
||||
def as_optval(arg, ignore_none=None):
|
||||
@@ -184,6 +197,19 @@ class _Format(object):
|
||||
return func(**v)
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def stack(fmt):
|
||||
@wraps(fmt)
|
||||
def wrapper(*args, **kwargs):
|
||||
new_func = fmt(ignore_none=True, *args, **kwargs)
|
||||
|
||||
def stacking(value):
|
||||
stack = [new_func(v) for v in value if v]
|
||||
stack = [x for args in stack for x in args]
|
||||
return stack
|
||||
return _ArgFormat(stacking, ignore_none=True)
|
||||
return wrapper
|
||||
|
||||
|
||||
class CmdRunner(object):
|
||||
"""
|
||||
@@ -204,7 +230,11 @@ class CmdRunner(object):
|
||||
self.default_args_order = self._prepare_args_order(default_args_order)
|
||||
if arg_formats is None:
|
||||
arg_formats = {}
|
||||
self.arg_formats = dict(arg_formats)
|
||||
self.arg_formats = {}
|
||||
for fmt_name, fmt in arg_formats.items():
|
||||
if not isinstance(fmt, _ArgFormat):
|
||||
fmt = _Format.as_func(func=fmt, ignore_none=True)
|
||||
self.arg_formats[fmt_name] = fmt
|
||||
self.check_rc = check_rc
|
||||
self.force_lang = force_lang
|
||||
self.path_prefix = path_prefix
|
||||
@@ -223,7 +253,16 @@ class CmdRunner(object):
|
||||
def binary(self):
|
||||
return self.command[0]
|
||||
|
||||
def __call__(self, args_order=None, output_process=None, ignore_value_none=True, check_mode_skip=False, check_mode_return=None, **kwargs):
|
||||
# remove parameter ignore_value_none in community.general 12.0.0
|
||||
def __call__(self, args_order=None, output_process=None, ignore_value_none=None, check_mode_skip=False, check_mode_return=None, **kwargs):
|
||||
if ignore_value_none is None:
|
||||
ignore_value_none = True
|
||||
else:
|
||||
self.module.deprecate(
|
||||
"Using ignore_value_none when creating the runner context is now deprecated, "
|
||||
"and the parameter will be removed in community.general 12.0.0. ",
|
||||
version="12.0.0", collection_name="community.general"
|
||||
)
|
||||
if output_process is None:
|
||||
output_process = _process_as_is
|
||||
if args_order is None:
|
||||
@@ -235,7 +274,7 @@ class CmdRunner(object):
|
||||
return _CmdRunnerContext(runner=self,
|
||||
args_order=args_order,
|
||||
output_process=output_process,
|
||||
ignore_value_none=ignore_value_none,
|
||||
ignore_value_none=ignore_value_none, # DEPRECATION: remove in community.general 12.0.0
|
||||
check_mode_skip=check_mode_skip,
|
||||
check_mode_return=check_mode_return, **kwargs)
|
||||
|
||||
@@ -251,6 +290,7 @@ class _CmdRunnerContext(object):
|
||||
self.runner = runner
|
||||
self.args_order = tuple(args_order)
|
||||
self.output_process = output_process
|
||||
# DEPRECATION: parameter ignore_value_none at the context level is deprecated and will be removed in community.general 12.0.0
|
||||
self.ignore_value_none = ignore_value_none
|
||||
self.check_mode_skip = check_mode_skip
|
||||
self.check_mode_return = check_mode_return
|
||||
@@ -290,6 +330,7 @@ class _CmdRunnerContext(object):
|
||||
value = named_args[arg_name]
|
||||
elif not runner.arg_formats[arg_name].ignore_missing_value:
|
||||
raise MissingArgumentValue(self.args_order, arg_name)
|
||||
# DEPRECATION: remove parameter ctx_ignore_none in 12.0.0
|
||||
self.cmd.extend(runner.arg_formats[arg_name](value, ctx_ignore_none=self.ignore_value_none))
|
||||
except MissingArgumentValue:
|
||||
raise
|
||||
@@ -306,7 +347,7 @@ class _CmdRunnerContext(object):
|
||||
@property
|
||||
def run_info(self):
|
||||
return dict(
|
||||
ignore_value_none=self.ignore_value_none,
|
||||
ignore_value_none=self.ignore_value_none, # DEPRECATION: remove in community.general 12.0.0
|
||||
check_rc=self.check_rc,
|
||||
environ_update=self.environ_update,
|
||||
args_order=self.args_order,
|
||||
|
||||
@@ -10,6 +10,7 @@ __metaclass__ = type
|
||||
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
|
||||
from ansible.module_utils.six.moves.urllib import error as urllib_error
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
@@ -68,6 +69,25 @@ def camel_case_key(key):
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def validate_check(check):
|
||||
validate_duration_keys = ['Interval', 'Ttl', 'Timeout']
|
||||
validate_tcp_regex = r"(?P<host>.*):(?P<port>(?:[0-9]+))$"
|
||||
if check.get('Tcp') is not None:
|
||||
match = re.match(validate_tcp_regex, check['Tcp'])
|
||||
if not match:
|
||||
raise Exception('tcp check must be in host:port format')
|
||||
for duration in validate_duration_keys:
|
||||
if duration in check and check[duration] is not None:
|
||||
check[duration] = validate_duration(check[duration])
|
||||
|
||||
|
||||
def validate_duration(duration):
|
||||
if duration:
|
||||
if not re.search(r"\d+(?:ns|us|ms|s|m|h)", duration):
|
||||
duration = "{0}s".format(duration)
|
||||
return duration
|
||||
|
||||
|
||||
STATE_PARAMETER = "state"
|
||||
STATE_PRESENT = "present"
|
||||
STATE_ABSENT = "absent"
|
||||
@@ -81,7 +101,7 @@ OPERATION_DELETE = "remove"
|
||||
def _normalize_params(params, arg_spec):
|
||||
final_params = {}
|
||||
for k, v in params.items():
|
||||
if k not in arg_spec: # Alias
|
||||
if k not in arg_spec or v is None: # Alias
|
||||
continue
|
||||
spec = arg_spec[k]
|
||||
if (
|
||||
@@ -105,9 +125,10 @@ class _ConsulModule:
|
||||
"""
|
||||
|
||||
api_endpoint = None # type: str
|
||||
unique_identifier = None # type: str
|
||||
unique_identifiers = None # type: list
|
||||
result_key = None # type: str
|
||||
create_only_fields = set()
|
||||
operational_attributes = set()
|
||||
params = {}
|
||||
|
||||
def __init__(self, module):
|
||||
@@ -119,6 +140,8 @@ class _ConsulModule:
|
||||
if k not in STATE_PARAMETER and k not in AUTH_ARGUMENTS_SPEC
|
||||
}
|
||||
|
||||
self.operational_attributes.update({"CreateIndex", "CreateTime", "Hash", "ModifyIndex"})
|
||||
|
||||
def execute(self):
|
||||
obj = self.read_object()
|
||||
|
||||
@@ -203,14 +226,24 @@ class _ConsulModule:
|
||||
return False
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
operational_attributes = {"CreateIndex", "CreateTime", "Hash", "ModifyIndex"}
|
||||
existing = {
|
||||
k: v for k, v in existing.items() if k not in operational_attributes
|
||||
k: v for k, v in existing.items() if k not in self.operational_attributes
|
||||
}
|
||||
for k, v in obj.items():
|
||||
existing[k] = v
|
||||
return existing
|
||||
|
||||
def id_from_obj(self, obj, camel_case=False):
|
||||
def key_func(key):
|
||||
return camel_case_key(key) if camel_case else key
|
||||
|
||||
if self.unique_identifiers:
|
||||
for identifier in self.unique_identifiers:
|
||||
identifier = key_func(identifier)
|
||||
if identifier in obj:
|
||||
return obj[identifier]
|
||||
return None
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_CREATE:
|
||||
return self.api_endpoint
|
||||
@@ -219,7 +252,8 @@ class _ConsulModule:
|
||||
raise RuntimeError("invalid arguments passed")
|
||||
|
||||
def read_object(self):
|
||||
url = self.endpoint_url(OPERATION_READ, self.params.get(self.unique_identifier))
|
||||
identifier = self.id_from_obj(self.params)
|
||||
url = self.endpoint_url(OPERATION_READ, identifier)
|
||||
try:
|
||||
return self.get(url)
|
||||
except RequestError as e:
|
||||
@@ -233,25 +267,28 @@ class _ConsulModule:
|
||||
if self._module.check_mode:
|
||||
return obj
|
||||
else:
|
||||
return self.put(self.api_endpoint, data=self.prepare_object({}, obj))
|
||||
url = self.endpoint_url(OPERATION_CREATE)
|
||||
created_obj = self.put(url, data=self.prepare_object({}, obj))
|
||||
if created_obj is None:
|
||||
created_obj = self.read_object()
|
||||
return created_obj
|
||||
|
||||
def update_object(self, existing, obj):
|
||||
url = self.endpoint_url(
|
||||
OPERATION_UPDATE, existing.get(camel_case_key(self.unique_identifier))
|
||||
)
|
||||
merged_object = self.prepare_object(existing, obj)
|
||||
if self._module.check_mode:
|
||||
return merged_object
|
||||
else:
|
||||
return self.put(url, data=merged_object)
|
||||
url = self.endpoint_url(OPERATION_UPDATE, self.id_from_obj(existing, camel_case=True))
|
||||
updated_obj = self.put(url, data=merged_object)
|
||||
if updated_obj is None:
|
||||
updated_obj = self.read_object()
|
||||
return updated_obj
|
||||
|
||||
def delete_object(self, obj):
|
||||
if self._module.check_mode:
|
||||
return {}
|
||||
else:
|
||||
url = self.endpoint_url(
|
||||
OPERATION_DELETE, obj.get(camel_case_key(self.unique_identifier))
|
||||
)
|
||||
url = self.endpoint_url(OPERATION_DELETE, self.id_from_obj(obj, camel_case=True))
|
||||
return self.delete(url)
|
||||
|
||||
def _request(self, method, url_parts, data=None, params=None):
|
||||
@@ -309,7 +346,9 @@ class _ConsulModule:
|
||||
if 400 <= status < 600:
|
||||
raise RequestError(status, response_data)
|
||||
|
||||
return json.loads(response_data)
|
||||
if response_data:
|
||||
return json.loads(response_data)
|
||||
return None
|
||||
|
||||
def get(self, url_parts, **kwargs):
|
||||
return self._request("GET", url_parts, **kwargs)
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import cmd_runner_fmt
|
||||
from ansible_collections.community.general.plugins.module_utils.python_runner import PythonRunner
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
@@ -33,6 +34,18 @@ _django_std_arg_fmts = dict(
|
||||
skip_checks=cmd_runner_fmt.as_bool("--skip-checks"),
|
||||
)
|
||||
|
||||
_django_database_args = dict(
|
||||
database=dict(type="str", default="default"),
|
||||
)
|
||||
|
||||
_args_menu = dict(
|
||||
std=(django_std_args, _django_std_arg_fmts),
|
||||
database=(_django_database_args, {"database": cmd_runner_fmt.as_opt_eq_val("--database")}),
|
||||
noinput=({}, {"noinput": cmd_runner_fmt.as_fixed("--noinput")}),
|
||||
dry_run=({}, {"dry_run": cmd_runner_fmt.as_bool("--dry-run")}),
|
||||
check=({}, {"check": cmd_runner_fmt.as_bool("--check")}),
|
||||
)
|
||||
|
||||
|
||||
class _DjangoRunner(PythonRunner):
|
||||
def __init__(self, module, arg_formats=None, **kwargs):
|
||||
@@ -54,15 +67,31 @@ class DjangoModuleHelper(ModuleHelper):
|
||||
django_admin_cmd = None
|
||||
arg_formats = {}
|
||||
django_admin_arg_order = ()
|
||||
use_old_vardict = False
|
||||
_django_args = []
|
||||
_check_mode_arg = ""
|
||||
|
||||
def __init__(self):
|
||||
argument_spec = dict(django_std_args)
|
||||
argument_spec.update(self.module.get("argument_spec", {}))
|
||||
self.module["argument_spec"] = argument_spec
|
||||
self.module["argument_spec"], self.arg_formats = self._build_args(self.module.get("argument_spec", {}),
|
||||
self.arg_formats,
|
||||
*(["std"] + self._django_args))
|
||||
super(DjangoModuleHelper, self).__init__(self.module)
|
||||
if self.django_admin_cmd is not None:
|
||||
self.vars.command = self.django_admin_cmd
|
||||
|
||||
@staticmethod
|
||||
def _build_args(arg_spec, arg_format, *names):
|
||||
res_arg_spec = {}
|
||||
res_arg_fmts = {}
|
||||
for name in names:
|
||||
args, fmts = _args_menu[name]
|
||||
res_arg_spec = dict_merge(res_arg_spec, args)
|
||||
res_arg_fmts = dict_merge(res_arg_fmts, fmts)
|
||||
res_arg_spec = dict_merge(res_arg_spec, arg_spec)
|
||||
res_arg_fmts = dict_merge(res_arg_fmts, arg_format)
|
||||
|
||||
return res_arg_spec, res_arg_fmts
|
||||
|
||||
def __run__(self):
|
||||
runner = _DjangoRunner(self.module,
|
||||
default_args_order=self.django_admin_arg_order,
|
||||
@@ -70,7 +99,10 @@ class DjangoModuleHelper(ModuleHelper):
|
||||
venv=self.vars.venv,
|
||||
check_rc=True)
|
||||
with runner() as ctx:
|
||||
results = ctx.run()
|
||||
run_params = self.vars.as_dict()
|
||||
if self._check_mode_arg:
|
||||
run_params.update({self._check_mode_arg: self.check_mode})
|
||||
results = ctx.run(**run_params)
|
||||
self.vars.stdout = ctx.results_out
|
||||
self.vars.stderr = ctx.results_err
|
||||
self.vars.cmd = ctx.cmd
|
||||
|
||||
@@ -29,6 +29,9 @@ def proxmox_auth_argument_spec():
|
||||
required=True,
|
||||
fallback=(env_fallback, ['PROXMOX_HOST'])
|
||||
),
|
||||
api_port=dict(type='int',
|
||||
fallback=(env_fallback, ['PROXMOX_PORT'])
|
||||
),
|
||||
api_user=dict(type='str',
|
||||
required=True,
|
||||
fallback=(env_fallback, ['PROXMOX_USER'])
|
||||
@@ -82,6 +85,7 @@ class ProxmoxAnsible(object):
|
||||
|
||||
def _connect(self):
|
||||
api_host = self.module.params['api_host']
|
||||
api_port = self.module.params['api_port']
|
||||
api_user = self.module.params['api_user']
|
||||
api_password = self.module.params['api_password']
|
||||
api_token_id = self.module.params['api_token_id']
|
||||
@@ -89,6 +93,10 @@ class ProxmoxAnsible(object):
|
||||
validate_certs = self.module.params['validate_certs']
|
||||
|
||||
auth_args = {'user': api_user}
|
||||
|
||||
if api_port:
|
||||
auth_args['port'] = api_port
|
||||
|
||||
if api_password:
|
||||
auth_args['password'] = api_password
|
||||
else:
|
||||
|
||||
@@ -11,6 +11,7 @@ import os
|
||||
import random
|
||||
import string
|
||||
import gzip
|
||||
import time
|
||||
from io import BytesIO
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
@@ -132,11 +133,13 @@ class RedfishUtils(object):
|
||||
return resp
|
||||
|
||||
# The following functions are to send GET/POST/PATCH/DELETE requests
|
||||
def get_request(self, uri, override_headers=None, allow_no_resp=False):
|
||||
def get_request(self, uri, override_headers=None, allow_no_resp=False, timeout=None):
|
||||
req_headers = dict(GET_HEADERS)
|
||||
if override_headers:
|
||||
req_headers.update(override_headers)
|
||||
username, password, basic_auth = self._auth_params(req_headers)
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
try:
|
||||
# Service root is an unauthenticated resource; remove credentials
|
||||
# in case the caller will be using sessions later.
|
||||
@@ -146,7 +149,7 @@ class RedfishUtils(object):
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
use_proxy=True, timeout=timeout)
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
try:
|
||||
if headers.get('content-encoding') == 'gzip' and LooseVersion(ansible_version) < LooseVersion('2.14'):
|
||||
@@ -624,6 +627,24 @@ class RedfishUtils(object):
|
||||
allowable_values = default_values
|
||||
return allowable_values
|
||||
|
||||
def check_service_availability(self):
|
||||
"""
|
||||
Checks if the service is accessible.
|
||||
|
||||
:return: dict containing the status of the service
|
||||
"""
|
||||
|
||||
# Get the service root
|
||||
# Override the timeout since the service root is expected to be readily
|
||||
# available.
|
||||
service_root = self.get_request(self.root_uri + self.service_root, timeout=10)
|
||||
if service_root['ret'] is False:
|
||||
# Failed, either due to a timeout or HTTP error; not available
|
||||
return {'ret': True, 'available': False}
|
||||
|
||||
# Successfully accessed the service root; available
|
||||
return {'ret': True, 'available': True}
|
||||
|
||||
def get_logs(self):
|
||||
log_svcs_uri_list = []
|
||||
list_of_logs = []
|
||||
@@ -1083,11 +1104,12 @@ class RedfishUtils(object):
|
||||
return self.manage_power(command, self.systems_uri,
|
||||
'#ComputerSystem.Reset')
|
||||
|
||||
def manage_manager_power(self, command):
|
||||
def manage_manager_power(self, command, wait=False, wait_timeout=120):
|
||||
return self.manage_power(command, self.manager_uri,
|
||||
'#Manager.Reset')
|
||||
'#Manager.Reset', wait, wait_timeout)
|
||||
|
||||
def manage_power(self, command, resource_uri, action_name):
|
||||
def manage_power(self, command, resource_uri, action_name, wait=False,
|
||||
wait_timeout=120):
|
||||
key = "Actions"
|
||||
reset_type_values = ['On', 'ForceOff', 'GracefulShutdown',
|
||||
'GracefulRestart', 'ForceRestart', 'Nmi',
|
||||
@@ -1147,6 +1169,30 @@ class RedfishUtils(object):
|
||||
response = self.post_request(self.root_uri + action_uri, payload)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
|
||||
# If requested to wait for the service to be available again, block
|
||||
# until it's ready
|
||||
if wait:
|
||||
elapsed_time = 0
|
||||
start_time = time.time()
|
||||
# Start with a large enough sleep. Some services will process new
|
||||
# requests while in the middle of shutting down, thus breaking out
|
||||
# early.
|
||||
time.sleep(30)
|
||||
|
||||
# Periodically check for the service's availability.
|
||||
while elapsed_time <= wait_timeout:
|
||||
status = self.check_service_availability()
|
||||
if status['available']:
|
||||
# It's available; we're done
|
||||
break
|
||||
time.sleep(5)
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
if elapsed_time > wait_timeout:
|
||||
# Exhausted the wait timer; error
|
||||
return {'ret': False, 'changed': True,
|
||||
'msg': 'The service did not become available after %d seconds' % wait_timeout}
|
||||
return {'ret': True, 'changed': True}
|
||||
|
||||
def manager_reset_to_defaults(self, command):
|
||||
|
||||
@@ -100,7 +100,7 @@ class _Variable(object):
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
return "<_Variable: value={0!r}, initial={1!r}, diff={2}, output={3}, change={4}, verbosity={5}>".format(
|
||||
return "<Variable: value={0!r}, initial={1!r}, diff={2}, output={3}, change={4}, verbosity={5}>".format(
|
||||
self.value, self.initial_value, self.diff, self.output, self.change, self.verbosity
|
||||
)
|
||||
|
||||
|
||||
@@ -32,6 +32,19 @@ attributes:
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- >
|
||||
If O(state=present) then the collection or role will be installed.
|
||||
Note that the collections and roles are not updated with this option.
|
||||
- >
|
||||
Currently the O(state=latest) is ignored unless O(type=collection), and it will
|
||||
ensure the collection is installed and updated to the latest available version.
|
||||
- Please note that O(force=true) can be used to perform upgrade regardless of O(type).
|
||||
type: str
|
||||
choices: [ present, latest ]
|
||||
default: present
|
||||
version_added: 9.1.0
|
||||
type:
|
||||
description:
|
||||
- The type of installation performed by C(ansible-galaxy).
|
||||
@@ -69,7 +82,8 @@ options:
|
||||
default: false
|
||||
force:
|
||||
description:
|
||||
- Force overwriting an existing role or collection.
|
||||
- Force overwriting existing roles and/or collections.
|
||||
- It can be used for upgrading, but the module output will always report C(changed=true).
|
||||
- Using O(force=true) is mandatory when downgrading.
|
||||
type: bool
|
||||
default: false
|
||||
@@ -171,7 +185,7 @@ RETURN = """
|
||||
|
||||
import re
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt as fmt
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper, ModuleHelperException
|
||||
|
||||
|
||||
@@ -180,12 +194,15 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
_RE_LIST_PATH = re.compile(r'^# (?P<path>.*)$')
|
||||
_RE_LIST_COLL = re.compile(r'^(?P<elem>\w+\.\w+)\s+(?P<version>[\d\.]+)\s*$')
|
||||
_RE_LIST_ROLE = re.compile(r'^- (?P<elem>\w+\.\w+),\s+(?P<version>[\d\.]+)\s*$')
|
||||
_RE_INSTALL_OUTPUT = None # Set after determining ansible version, see __init_module__()
|
||||
_RE_INSTALL_OUTPUT = re.compile(
|
||||
r'^(?:(?P<collection>\w+\.\w+):(?P<cversion>[\d\.]+)|- (?P<role>\w+\.\w+) \((?P<rversion>[\d\.]+)\)) was installed successfully$'
|
||||
)
|
||||
ansible_version = None
|
||||
|
||||
output_params = ('type', 'name', 'dest', 'requirements_file', 'force', 'no_deps')
|
||||
module = dict(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', choices=['present', 'latest'], default='present'),
|
||||
type=dict(type='str', choices=('collection', 'role', 'both'), required=True),
|
||||
name=dict(type='str'),
|
||||
requirements_file=dict(type='path'),
|
||||
@@ -198,17 +215,19 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
required_if=[('type', 'both', ['requirements_file'])],
|
||||
supports_check_mode=False,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
command = 'ansible-galaxy'
|
||||
command_args_formats = dict(
|
||||
type=fmt.as_func(lambda v: [] if v == 'both' else [v]),
|
||||
galaxy_cmd=fmt.as_list(),
|
||||
requirements_file=fmt.as_opt_val('-r'),
|
||||
dest=fmt.as_opt_val('-p'),
|
||||
force=fmt.as_bool("--force"),
|
||||
no_deps=fmt.as_bool("--no-deps"),
|
||||
version=fmt.as_bool("--version"),
|
||||
name=fmt.as_list(),
|
||||
type=cmd_runner_fmt.as_func(lambda v: [] if v == 'both' else [v]),
|
||||
galaxy_cmd=cmd_runner_fmt.as_list(),
|
||||
upgrade=cmd_runner_fmt.as_bool("--upgrade"),
|
||||
requirements_file=cmd_runner_fmt.as_opt_val('-r'),
|
||||
dest=cmd_runner_fmt.as_opt_val('-p'),
|
||||
force=cmd_runner_fmt.as_bool("--force"),
|
||||
no_deps=cmd_runner_fmt.as_bool("--no-deps"),
|
||||
version=cmd_runner_fmt.as_fixed("--version"),
|
||||
name=cmd_runner_fmt.as_list(),
|
||||
)
|
||||
|
||||
def _make_runner(self, lang):
|
||||
@@ -232,25 +251,16 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
try:
|
||||
runner = self._make_runner("C.UTF-8")
|
||||
with runner("version", check_rc=False, output_process=process) as ctx:
|
||||
return runner, ctx.run(version=True)
|
||||
except UnsupportedLocale as e:
|
||||
return runner, ctx.run()
|
||||
except UnsupportedLocale:
|
||||
runner = self._make_runner("en_US.UTF-8")
|
||||
with runner("version", check_rc=True, output_process=process) as ctx:
|
||||
return runner, ctx.run(version=True)
|
||||
return runner, ctx.run()
|
||||
|
||||
def __init_module__(self):
|
||||
# self.runner = CmdRunner(self.module, command=self.command, arg_formats=self.command_args_formats, force_lang=self.force_lang)
|
||||
self.runner, self.ansible_version = self._get_ansible_galaxy_version()
|
||||
if self.ansible_version < (2, 11):
|
||||
self.module.fail_json(
|
||||
msg="Support for Ansible 2.9 and ansible-base 2.10 has ben removed."
|
||||
)
|
||||
# Collection install output changed:
|
||||
# ansible-base 2.10: "coll.name (x.y.z)"
|
||||
# ansible-core 2.11+: "coll.name:x.y.z"
|
||||
self._RE_INSTALL_OUTPUT = re.compile(r'^(?:(?P<collection>\w+\.\w+)(?: \(|:)(?P<cversion>[\d\.]+)\)?'
|
||||
r'|- (?P<role>\w+\.\w+) \((?P<rversion>[\d\.]+)\))'
|
||||
r' was installed successfully$')
|
||||
self.module.fail_json(msg="Support for Ansible 2.9 and ansible-base 2.10 has been removed.")
|
||||
self.vars.set("new_collections", {}, change=True)
|
||||
self.vars.set("new_roles", {}, change=True)
|
||||
if self.vars.type != "collection":
|
||||
@@ -303,8 +313,9 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
elif match.group("role"):
|
||||
self.vars.new_roles[match.group("role")] = match.group("rversion")
|
||||
|
||||
with self.runner("type galaxy_cmd force no_deps dest requirements_file name", output_process=process) as ctx:
|
||||
ctx.run(galaxy_cmd="install")
|
||||
upgrade = (self.vars.type == "collection" and self.vars.state == "latest")
|
||||
with self.runner("type galaxy_cmd upgrade force no_deps dest requirements_file name", output_process=process) as ctx:
|
||||
ctx.run(galaxy_cmd="install", upgrade=upgrade)
|
||||
if self.verbosity > 2:
|
||||
self.vars.set("run_info", ctx.run_info)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021 Radek Sprta <mail@radeksprta.eu>
|
||||
# Copyright (c) 2024 Colin Nolan <cn580@alumni.york.ac.uk>
|
||||
# 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
|
||||
|
||||
@@ -65,6 +66,13 @@ options:
|
||||
type: str
|
||||
default: present
|
||||
choices: [ "present", "absent", "latest" ]
|
||||
directory:
|
||||
description:
|
||||
- Path to the source directory to install the Rust package from.
|
||||
- This is only used when installing packages.
|
||||
type: path
|
||||
required: false
|
||||
version_added: 9.1.0
|
||||
requirements:
|
||||
- cargo installed
|
||||
"""
|
||||
@@ -98,8 +106,14 @@ EXAMPLES = r"""
|
||||
community.general.cargo:
|
||||
name: ludusavi
|
||||
state: latest
|
||||
|
||||
- name: Install "ludusavi" Rust package from source directory
|
||||
community.general.cargo:
|
||||
name: ludusavi
|
||||
directory: /path/to/ludusavi/source
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
@@ -115,6 +129,7 @@ class Cargo(object):
|
||||
self.state = kwargs["state"]
|
||||
self.version = kwargs["version"]
|
||||
self.locked = kwargs["locked"]
|
||||
self.directory = kwargs["directory"]
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
@@ -143,7 +158,7 @@ class Cargo(object):
|
||||
|
||||
data, dummy = self._exec(cmd, True, False, False)
|
||||
|
||||
package_regex = re.compile(r"^([\w\-]+) v(.+):$")
|
||||
package_regex = re.compile(r"^([\w\-]+) v(\S+).*:$")
|
||||
installed = {}
|
||||
for line in data.splitlines():
|
||||
package_info = package_regex.match(line)
|
||||
@@ -163,19 +178,53 @@ class Cargo(object):
|
||||
if self.version:
|
||||
cmd.append("--version")
|
||||
cmd.append(self.version)
|
||||
if self.directory:
|
||||
cmd.append("--path")
|
||||
cmd.append(self.directory)
|
||||
return self._exec(cmd)
|
||||
|
||||
def is_outdated(self, name):
|
||||
installed_version = self.get_installed().get(name)
|
||||
latest_version = (
|
||||
self.get_latest_published_version(name)
|
||||
if not self.directory
|
||||
else self.get_source_directory_version(name)
|
||||
)
|
||||
return installed_version != latest_version
|
||||
|
||||
def get_latest_published_version(self, name):
|
||||
cmd = ["search", name, "--limit", "1"]
|
||||
data, dummy = self._exec(cmd, True, False, False)
|
||||
|
||||
match = re.search(r'"(.+)"', data)
|
||||
if match:
|
||||
latest_version = match.group(1)
|
||||
if not match:
|
||||
self.module.fail_json(
|
||||
msg="No published version for package %s found" % name
|
||||
)
|
||||
return match.group(1)
|
||||
|
||||
return installed_version != latest_version
|
||||
def get_source_directory_version(self, name):
|
||||
cmd = [
|
||||
"metadata",
|
||||
"--format-version",
|
||||
"1",
|
||||
"--no-deps",
|
||||
"--manifest-path",
|
||||
os.path.join(self.directory, "Cargo.toml"),
|
||||
]
|
||||
data, dummy = self._exec(cmd, True, False, False)
|
||||
manifest = json.loads(data)
|
||||
|
||||
package = next(
|
||||
(package for package in manifest["packages"] if package["name"] == name),
|
||||
None,
|
||||
)
|
||||
if not package:
|
||||
self.module.fail_json(
|
||||
msg="Package %s not defined in source, found: %s"
|
||||
% (name, [x["name"] for x in manifest["packages"]])
|
||||
)
|
||||
return package["version"]
|
||||
|
||||
def uninstall(self, packages=None):
|
||||
cmd = ["uninstall"]
|
||||
@@ -191,16 +240,21 @@ def main():
|
||||
state=dict(default="present", choices=["present", "absent", "latest"]),
|
||||
version=dict(default=None, type="str"),
|
||||
locked=dict(default=False, type="bool"),
|
||||
directory=dict(default=None, type="path"),
|
||||
)
|
||||
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
|
||||
|
||||
name = module.params["name"]
|
||||
state = module.params["state"]
|
||||
version = module.params["version"]
|
||||
directory = module.params["directory"]
|
||||
|
||||
if not name:
|
||||
module.fail_json(msg="Package name must be specified")
|
||||
|
||||
if directory is not None and not os.path.isdir(directory):
|
||||
module.fail_json(msg="Source directory does not exist")
|
||||
|
||||
# Set LANG env since we parse stdout
|
||||
module.run_command_environ_update = dict(
|
||||
LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C"
|
||||
|
||||
254
plugins/modules/consul_agent_check.py
Normal file
254
plugins/modules/consul_agent_check.py
Normal file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Michael Ilg
|
||||
# GNU General Public License v3.0+ (see COPYING 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: consul_agent_check
|
||||
short_description: Add, modify, and delete checks within a consul cluster
|
||||
version_added: 9.1.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of checks in a consul
|
||||
cluster via the agent. For more details on using and configuring Checks,
|
||||
see U(https://developer.hashicorp.com/consul/api-docs/agent/check).
|
||||
- Currently, there is no complete way to retrieve the script, interval or TTL
|
||||
metadata for a registered check. Without this metadata it is not possible to
|
||||
tell if the data supplied with ansible represents a change to a check. As a
|
||||
result this does not attempt to determine changes and will always report a
|
||||
changed occurred. An API method is planned to supply this metadata so at that
|
||||
stage change management will be added.
|
||||
author:
|
||||
- Michael Ilg (@Ilgmi)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
details:
|
||||
- The result is the object as it is defined in the module options and not the object structure of the consul API.
|
||||
For a better overview of what the object structure looks like,
|
||||
take a look at U(https://developer.hashicorp.com/consul/api-docs/agent/check#list-checks).
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will show the object as it is defined in the module options and not the object structure of the consul API.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the check should be present or absent.
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Required name for the service check.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- Specifies a unique ID for this check on the node. This defaults to the O(name) parameter, but it may be necessary to provide
|
||||
an ID for uniqueness. This value will return in the response as "CheckId".
|
||||
type: str
|
||||
interval:
|
||||
description:
|
||||
- The interval at which the service check will be run.
|
||||
This is a number with a V(s) or V(m) suffix to signify the units of seconds or minutes, for example V(15s) or V(1m).
|
||||
If no suffix is supplied V(s) will be used by default, for example V(10) will be V(10s).
|
||||
- Required if one of the parameters O(args), O(http), or O(tcp) is specified.
|
||||
type: str
|
||||
notes:
|
||||
description:
|
||||
- Notes to attach to check when registering it.
|
||||
type: str
|
||||
args:
|
||||
description:
|
||||
- Specifies command arguments to run to update the status of the check.
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(ttl), O(tcp) and O(http).
|
||||
type: list
|
||||
elements: str
|
||||
ttl:
|
||||
description:
|
||||
- Checks can be registered with a TTL instead of a O(args) and O(interval)
|
||||
this means that the service will check in with the agent before the
|
||||
TTL expires. If it doesn't the check will be considered failed.
|
||||
Required if registering a check and the script an interval are missing
|
||||
Similar to the interval this is a number with a V(s) or V(m) suffix to
|
||||
signify the units of seconds or minutes, for example V(15s) or V(1m).
|
||||
If no suffix is supplied V(s) will be used by default, for example V(10) will be V(10s).
|
||||
- Mutually exclusive with O(args), O(tcp) and O(http).
|
||||
type: str
|
||||
tcp:
|
||||
description:
|
||||
- Checks can be registered with a TCP port. This means that consul
|
||||
will check if the connection attempt to that port is successful (that is, the port is currently accepting connections).
|
||||
The format is V(host:port), for example V(localhost:80).
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(args), O(ttl) and O(http).
|
||||
type: str
|
||||
version_added: '1.3.0'
|
||||
http:
|
||||
description:
|
||||
- Checks can be registered with an HTTP endpoint. This means that consul
|
||||
will check that the http endpoint returns a successful HTTP status.
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(args), O(ttl) and O(tcp).
|
||||
type: str
|
||||
timeout:
|
||||
description:
|
||||
- A custom HTTP check timeout. The consul default is 10 seconds.
|
||||
Similar to the interval this is a number with a V(s) or V(m) suffix to
|
||||
signify the units of seconds or minutes, for example V(15s) or V(1m).
|
||||
If no suffix is supplied V(s) will be used by default, for example V(10) will be V(10s).
|
||||
type: str
|
||||
service_id:
|
||||
description:
|
||||
- The ID for the service, must be unique per node. If O(state=absent),
|
||||
defaults to the service name if supplied.
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Register tcp check for service 'nginx'
|
||||
community.general.consul_agent_check:
|
||||
name: nginx_tcp_check
|
||||
service_id: nginx
|
||||
interval: 60s
|
||||
tcp: localhost:80
|
||||
notes: "Nginx Check"
|
||||
|
||||
- name: Register http check for service 'nginx'
|
||||
community.general.consul_agent_check:
|
||||
name: nginx_http_check
|
||||
service_id: nginx
|
||||
interval: 60s
|
||||
http: http://localhost:80/status
|
||||
notes: "Nginx Check"
|
||||
|
||||
- name: Remove check for service 'nginx'
|
||||
community.general.consul_agent_check:
|
||||
state: absent
|
||||
id: nginx_http_check
|
||||
service_id: "{{ nginx_service.ID }}"
|
||||
'''
|
||||
|
||||
RETURN = """
|
||||
check:
|
||||
description: The check as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
CheckID: nginx_check
|
||||
ServiceID: nginx
|
||||
Interval: 30s
|
||||
Type: http
|
||||
Notes: Nginx Check
|
||||
operation:
|
||||
description: The operation performed.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
OPERATION_CREATE,
|
||||
OPERATION_UPDATE,
|
||||
OPERATION_DELETE,
|
||||
OPERATION_READ,
|
||||
_ConsulModule,
|
||||
validate_check,
|
||||
)
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
"name": dict(type='str'),
|
||||
"id": dict(type='str'),
|
||||
"interval": dict(type='str'),
|
||||
"notes": dict(type='str'),
|
||||
"args": dict(type='list', elements='str'),
|
||||
"http": dict(type='str'),
|
||||
"tcp": dict(type='str'),
|
||||
"ttl": dict(type='str'),
|
||||
"timeout": dict(type='str'),
|
||||
"service_id": dict(type='str'),
|
||||
}
|
||||
|
||||
_MUTUALLY_EXCLUSIVE = [
|
||||
('args', 'ttl', 'tcp', 'http'),
|
||||
]
|
||||
|
||||
_REQUIRED_IF = [
|
||||
('state', 'present', ['name']),
|
||||
('state', 'absent', ('id', 'name'), True),
|
||||
]
|
||||
|
||||
_REQUIRED_BY = {
|
||||
'args': 'interval',
|
||||
'http': 'interval',
|
||||
'tcp': 'interval',
|
||||
}
|
||||
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
class ConsulAgentCheckModule(_ConsulModule):
|
||||
api_endpoint = "agent/check"
|
||||
result_key = "check"
|
||||
unique_identifiers = ["id", "name"]
|
||||
operational_attributes = {"Node", "CheckID", "Output", "ServiceName", "ServiceTags",
|
||||
"Status", "Type", "ExposedPort", "Definition"}
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
return "agent/checks"
|
||||
if operation in [OPERATION_CREATE, OPERATION_UPDATE]:
|
||||
return "/".join([self.api_endpoint, "register"])
|
||||
if operation == OPERATION_DELETE:
|
||||
return "/".join([self.api_endpoint, "deregister", identifier])
|
||||
|
||||
return super(ConsulAgentCheckModule, self).endpoint_url(operation, identifier)
|
||||
|
||||
def read_object(self):
|
||||
url = self.endpoint_url(OPERATION_READ)
|
||||
checks = self.get(url)
|
||||
identifier = self.id_from_obj(self.params)
|
||||
if identifier in checks:
|
||||
return checks[identifier]
|
||||
return None
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
existing = super(ConsulAgentCheckModule, self).prepare_object(existing, obj)
|
||||
validate_check(existing)
|
||||
return existing
|
||||
|
||||
def delete_object(self, obj):
|
||||
if not self._module.check_mode:
|
||||
self.put(self.endpoint_url(OPERATION_DELETE, obj.get("CheckID")))
|
||||
return {}
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
mutually_exclusive=_MUTUALLY_EXCLUSIVE,
|
||||
required_if=_REQUIRED_IF,
|
||||
required_by=_REQUIRED_BY,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
consul_module = ConsulAgentCheckModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
289
plugins/modules/consul_agent_service.py
Normal file
289
plugins/modules/consul_agent_service.py
Normal file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Michael Ilg
|
||||
# GNU General Public License v3.0+ (see COPYING 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: consul_agent_service
|
||||
short_description: Add, modify and delete services within a consul cluster
|
||||
version_added: 9.1.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of services in a consul
|
||||
cluster via the agent.
|
||||
- There are currently no plans to create services and checks in one.
|
||||
This is because the Consul API does not provide checks for a service and
|
||||
the checks themselves do not match the module parameters.
|
||||
Therefore, only a service without checks can be created in this module.
|
||||
author:
|
||||
- Michael Ilg (@Ilgmi)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the service should be present or absent.
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Unique name for the service on a node, must be unique per node,
|
||||
required if registering a service.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- Specifies a unique ID for this service. This must be unique per agent. This defaults to the O(name) parameter if not provided.
|
||||
If O(state=absent), defaults to the service name if supplied.
|
||||
type: str
|
||||
tags:
|
||||
description:
|
||||
- Tags that will be attached to the service registration.
|
||||
type: list
|
||||
elements: str
|
||||
address:
|
||||
description:
|
||||
- The address to advertise that the service will be listening on.
|
||||
This value will be passed as the C(address) parameter to Consul's
|
||||
C(/v1/agent/service/register) API method, so refer to the Consul API
|
||||
documentation for further details.
|
||||
type: str
|
||||
meta:
|
||||
description:
|
||||
- Optional meta data used for filtering.
|
||||
For keys, the characters C(A-Z), C(a-z), C(0-9), C(_), C(-) are allowed.
|
||||
Not allowed characters are replaced with underscores.
|
||||
type: dict
|
||||
service_port:
|
||||
description:
|
||||
- The port on which the service is listening. Can optionally be supplied for
|
||||
registration of a service, that is if O(name) or O(id) is set.
|
||||
type: int
|
||||
enable_tag_override:
|
||||
description:
|
||||
- Specifies to disable the anti-entropy feature for this service's tags.
|
||||
If EnableTagOverride is set to true then external agents can update this service in the catalog and modify the tags.
|
||||
type: bool
|
||||
default: False
|
||||
weights:
|
||||
description:
|
||||
- Specifies weights for the service
|
||||
type: dict
|
||||
suboptions:
|
||||
passing:
|
||||
description:
|
||||
- Weights for passing.
|
||||
type: int
|
||||
default: 1
|
||||
warning:
|
||||
description:
|
||||
- Weights for warning.
|
||||
type: int
|
||||
default: 1
|
||||
default: {"passing": 1, "warning": 1}
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Register nginx service with the local consul agent
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
|
||||
- name: Register nginx with a tcp check
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
|
||||
- name: Register nginx with an http check
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
|
||||
- name: Register external service nginx available at 10.1.5.23
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
address: 10.1.5.23
|
||||
|
||||
- name: Register nginx with some service tags
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
tags:
|
||||
- prod
|
||||
- webservers
|
||||
|
||||
- name: Register nginx with some service meta
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
meta:
|
||||
nginx_version: 1.25.3
|
||||
|
||||
- name: Remove nginx service
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
service_id: nginx
|
||||
state: absent
|
||||
|
||||
- name: Register celery worker service
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: celery-worker
|
||||
tags:
|
||||
- prod
|
||||
- worker
|
||||
'''
|
||||
|
||||
RETURN = """
|
||||
service:
|
||||
description: The service as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
ID: nginx
|
||||
Service: nginx
|
||||
Address: localhost
|
||||
Port: 80
|
||||
Tags:
|
||||
- http
|
||||
Meta:
|
||||
- nginx_version: 1.23.3
|
||||
Datacenter: dc1
|
||||
Weights:
|
||||
Passing: 1
|
||||
Warning: 1
|
||||
ContentHash: 61a245cd985261ac
|
||||
EnableTagOverride: false
|
||||
operation:
|
||||
description: The operation performed.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
OPERATION_CREATE,
|
||||
OPERATION_UPDATE,
|
||||
OPERATION_DELETE,
|
||||
_ConsulModule
|
||||
)
|
||||
|
||||
_CHECK_MUTUALLY_EXCLUSIVE = [('args', 'ttl', 'tcp', 'http')]
|
||||
_CHECK_REQUIRED_BY = {
|
||||
'args': 'interval',
|
||||
'http': 'interval',
|
||||
'tcp': 'interval',
|
||||
}
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
"name": dict(type='str'),
|
||||
"id": dict(type='str'),
|
||||
"tags": dict(type='list', elements='str'),
|
||||
"address": dict(type='str'),
|
||||
"meta": dict(type='dict'),
|
||||
"service_port": dict(type='int'),
|
||||
"enable_tag_override": dict(type='bool', default=False),
|
||||
"weights": dict(type='dict', options=dict(
|
||||
passing=dict(type='int', default=1, no_log=False),
|
||||
warning=dict(type='int', default=1)
|
||||
), default={"passing": 1, "warning": 1})
|
||||
}
|
||||
|
||||
_REQUIRED_IF = [
|
||||
('state', 'present', ['name']),
|
||||
('state', 'absent', ('id', 'name'), True),
|
||||
]
|
||||
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
class ConsulAgentServiceModule(_ConsulModule):
|
||||
api_endpoint = "agent/service"
|
||||
result_key = "service"
|
||||
unique_identifiers = ["id", "name"]
|
||||
operational_attributes = {"Service", "ContentHash", "Datacenter"}
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation in [OPERATION_CREATE, OPERATION_UPDATE]:
|
||||
return "/".join([self.api_endpoint, "register"])
|
||||
if operation == OPERATION_DELETE:
|
||||
return "/".join([self.api_endpoint, "deregister", identifier])
|
||||
|
||||
return super(ConsulAgentServiceModule, self).endpoint_url(operation, identifier)
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
existing = super(ConsulAgentServiceModule, self).prepare_object(existing, obj)
|
||||
if "ServicePort" in existing:
|
||||
existing["Port"] = existing.pop("ServicePort")
|
||||
|
||||
if "ID" not in existing:
|
||||
existing["ID"] = existing["Name"]
|
||||
|
||||
return existing
|
||||
|
||||
def needs_update(self, api_obj, module_obj):
|
||||
obj = {}
|
||||
if "Service" in api_obj:
|
||||
obj["Service"] = api_obj["Service"]
|
||||
api_obj = self.prepare_object(api_obj, obj)
|
||||
|
||||
if "Name" in module_obj:
|
||||
module_obj["Service"] = module_obj.pop("Name")
|
||||
if "ServicePort" in module_obj:
|
||||
module_obj["Port"] = module_obj.pop("ServicePort")
|
||||
|
||||
return super(ConsulAgentServiceModule, self).needs_update(api_obj, module_obj)
|
||||
|
||||
def delete_object(self, obj):
|
||||
if not self._module.check_mode:
|
||||
url = self.endpoint_url(OPERATION_DELETE, self.id_from_obj(obj, camel_case=True))
|
||||
self.put(url)
|
||||
return {}
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
required_if=_REQUIRED_IF,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
consul_module = ConsulAgentServiceModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -168,7 +168,7 @@ def normalize_ttl(ttl):
|
||||
class ConsulAuthMethodModule(_ConsulModule):
|
||||
api_endpoint = "acl/auth-method"
|
||||
result_key = "auth_method"
|
||||
unique_identifier = "name"
|
||||
unique_identifiers = ["name"]
|
||||
|
||||
def map_param(self, k, v, is_update):
|
||||
if k == "config" and v:
|
||||
|
||||
@@ -124,7 +124,7 @@ from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
class ConsulBindingRuleModule(_ConsulModule):
|
||||
api_endpoint = "acl/binding-rule"
|
||||
result_key = "binding_rule"
|
||||
unique_identifier = "id"
|
||||
unique_identifiers = ["id"]
|
||||
|
||||
def read_object(self):
|
||||
url = "acl/binding-rules?authmethod={0}".format(self.params["auth_method"])
|
||||
|
||||
@@ -145,7 +145,7 @@ _ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
class ConsulPolicyModule(_ConsulModule):
|
||||
api_endpoint = "acl/policy"
|
||||
result_key = "policy"
|
||||
unique_identifier = "id"
|
||||
unique_identifiers = ["id"]
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
|
||||
@@ -212,7 +212,7 @@ from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
class ConsulRoleModule(_ConsulModule):
|
||||
api_endpoint = "acl/role"
|
||||
result_key = "role"
|
||||
unique_identifier = "id"
|
||||
unique_identifiers = ["id"]
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
|
||||
@@ -235,13 +235,13 @@ def normalize_link_obj(api_obj, module_obj, key):
|
||||
class ConsulTokenModule(_ConsulModule):
|
||||
api_endpoint = "acl/token"
|
||||
result_key = "token"
|
||||
unique_identifier = "accessor_id"
|
||||
unique_identifiers = ["accessor_id"]
|
||||
|
||||
create_only_fields = {"expiration_ttl"}
|
||||
|
||||
def read_object(self):
|
||||
# if `accessor_id` is not supplied we can only create objects and are not idempotent
|
||||
if not self.params.get(self.unique_identifier):
|
||||
if not self.id_from_obj(self.params):
|
||||
return None
|
||||
return super(ConsulTokenModule, self).read_object()
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@ class CPANMinus(ModuleHelper):
|
||||
installdeps=cmd_runner_fmt.as_bool("--installdeps"),
|
||||
pkg_spec=cmd_runner_fmt.as_list(),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
v = self.vars
|
||||
|
||||
113
plugins/modules/django_check.py
Normal file
113
plugins/modules/django_check.py
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024, Alexei Znamensky <russoz@gmail.com>
|
||||
# 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: django_check
|
||||
author:
|
||||
- Alexei Znamensky (@russoz)
|
||||
short_description: Wrapper for C(django-admin check)
|
||||
version_added: 9.1.0
|
||||
description:
|
||||
- This module is a wrapper for the execution of C(django-admin check).
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.django
|
||||
options:
|
||||
database:
|
||||
description:
|
||||
- Specify databases to run checks against.
|
||||
- If not specified, Django will not run database tests.
|
||||
type: list
|
||||
elements: str
|
||||
deploy:
|
||||
description:
|
||||
- Include additional checks relevant in a deployment setting.
|
||||
type: bool
|
||||
default: false
|
||||
fail_level:
|
||||
description:
|
||||
- Message level that will trigger failure.
|
||||
- Default is the Django default value. Check the documentation for the version being used.
|
||||
type: str
|
||||
choices: [CRITICAL, ERROR, WARNING, INFO, DEBUG]
|
||||
tags:
|
||||
description:
|
||||
- Restrict checks to specific tags.
|
||||
type: list
|
||||
elements: str
|
||||
apps:
|
||||
description:
|
||||
- Restrict checks to specific applications.
|
||||
- Default is to check all applications.
|
||||
type: list
|
||||
elements: str
|
||||
notes:
|
||||
- The outcome of the module is found in the common return values RV(ignore:stdout), RV(ignore:stderr), RV(ignore:rc).
|
||||
- The module will fail if RV(ignore:rc) is not zero.
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Check the entire project
|
||||
community.general.django_check:
|
||||
settings: myproject.settings
|
||||
|
||||
- name: Create the project using specific databases
|
||||
community.general.django_check:
|
||||
database:
|
||||
- somedb
|
||||
- myotherdb
|
||||
settings: fancysite.settings
|
||||
pythonpath: /home/joedoe/project/fancysite
|
||||
venv: /home/joedoe/project/fancysite/venv
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
run_info:
|
||||
description: Command-line execution information.
|
||||
type: dict
|
||||
returned: success and C(verbosity) >= 3
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.django import DjangoModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import cmd_runner_fmt
|
||||
|
||||
|
||||
class DjangoCheck(DjangoModuleHelper):
|
||||
module = dict(
|
||||
argument_spec=dict(
|
||||
database=dict(type="list", elements="str"),
|
||||
deploy=dict(type="bool", default=False),
|
||||
fail_level=dict(type="str", choices=["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]),
|
||||
tags=dict(type="list", elements="str"),
|
||||
apps=dict(type="list", elements="str"),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
arg_formats = dict(
|
||||
database=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("--database"),
|
||||
deploy=cmd_runner_fmt.as_bool("--deploy"),
|
||||
fail_level=cmd_runner_fmt.as_opt_val("--fail-level"),
|
||||
tags=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("--tag"),
|
||||
apps=cmd_runner_fmt.as_list(),
|
||||
)
|
||||
django_admin_cmd = "check"
|
||||
django_admin_arg_order = "database deploy fail_level tags apps"
|
||||
|
||||
|
||||
def main():
|
||||
DjangoCheck.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
67
plugins/modules/django_createcachetable.py
Normal file
67
plugins/modules/django_createcachetable.py
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024, Alexei Znamensky <russoz@gmail.com>
|
||||
# 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: django_createcachetable
|
||||
author:
|
||||
- Alexei Znamensky (@russoz)
|
||||
short_description: Wrapper for C(django-admin createcachetable)
|
||||
version_added: 9.1.0
|
||||
description:
|
||||
- This module is a wrapper for the execution of C(django-admin createcachetable).
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.django
|
||||
- community.general.django.database
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create cache table in the default database
|
||||
community.general.django_createcachetable:
|
||||
settings: myproject.settings
|
||||
|
||||
- name: Create cache table in the other database
|
||||
community.general.django_createcachetable:
|
||||
database: myotherdb
|
||||
settings: fancysite.settings
|
||||
pythonpath: /home/joedoe/project/fancysite
|
||||
venv: /home/joedoe/project/fancysite/venv
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
run_info:
|
||||
description: Command-line execution information.
|
||||
type: dict
|
||||
returned: success and O(verbosity) >= 3
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.django import DjangoModuleHelper
|
||||
|
||||
|
||||
class DjangoCreateCacheTable(DjangoModuleHelper):
|
||||
module = dict(
|
||||
supports_check_mode=True,
|
||||
)
|
||||
django_admin_cmd = "createcachetable"
|
||||
django_admin_arg_order = "noinput database dry_run"
|
||||
_django_args = ["noinput", "database", "dry_run"]
|
||||
_check_mode_arg = "dry_run"
|
||||
|
||||
|
||||
def main():
|
||||
DjangoCreateCacheTable.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -60,6 +60,7 @@ class GConftoolInfo(ModuleHelper):
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = gconftool2_runner(self.module, check_rc=True)
|
||||
|
||||
@@ -18,7 +18,7 @@ author:
|
||||
- Matthew Gamble (@djmattyg007)
|
||||
- Marius Gedminas (@mgedmin)
|
||||
requirements: ['git']
|
||||
short_description: Read and write git configuration
|
||||
short_description: Update git configuration
|
||||
description:
|
||||
- The M(community.general.git_config) module changes git configuration by invoking C(git config).
|
||||
This is needed if you do not want to use M(ansible.builtin.template) for the entire git
|
||||
@@ -36,6 +36,8 @@ options:
|
||||
list_all:
|
||||
description:
|
||||
- List all settings (optionally limited to a given O(scope)).
|
||||
- This option is B(deprecated) and will be removed from community.general 11.0.0.
|
||||
Please use M(community.general.git_config_info) instead.
|
||||
type: bool
|
||||
default: false
|
||||
name:
|
||||
@@ -74,6 +76,8 @@ options:
|
||||
description:
|
||||
- When specifying the name of a single setting, supply a value to
|
||||
set that setting to the given value.
|
||||
- From community.general 11.0.0 on, O(value) will be required if O(state=present).
|
||||
To read values, use the M(community.general.git_config_info) module instead.
|
||||
type: str
|
||||
add_mode:
|
||||
description:
|
||||
@@ -143,29 +147,6 @@ EXAMPLES = '''
|
||||
repo: /etc
|
||||
scope: local
|
||||
value: 'root@{{ ansible_fqdn }}'
|
||||
|
||||
- name: Read individual values from git config
|
||||
community.general.git_config:
|
||||
name: alias.ci
|
||||
scope: global
|
||||
|
||||
- name: Scope system is also assumed when reading values, unless list_all=true
|
||||
community.general.git_config:
|
||||
name: alias.diffc
|
||||
|
||||
- name: Read all values from git config
|
||||
community.general.git_config:
|
||||
list_all: true
|
||||
scope: global
|
||||
|
||||
- name: When list_all is yes and no scope is specified, you get configuration from all scopes
|
||||
community.general.git_config:
|
||||
list_all: true
|
||||
|
||||
- name: Specify a repository to include local settings
|
||||
community.general.git_config:
|
||||
list_all: true
|
||||
repo: /path/to/repo.git
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -193,7 +174,7 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
list_all=dict(required=False, type='bool', default=False),
|
||||
list_all=dict(required=False, type='bool', default=False, removed_in_version='11.0.0', removed_from_collection='community.general'),
|
||||
name=dict(type='str'),
|
||||
repo=dict(type='path'),
|
||||
file=dict(type='path'),
|
||||
@@ -222,6 +203,14 @@ def main():
|
||||
new_value = params['value'] or ''
|
||||
add_mode = params['add_mode']
|
||||
|
||||
if not unset and not new_value and not params['list_all']:
|
||||
module.deprecate(
|
||||
'If state=present, a value must be specified from community.general 11.0.0 on.'
|
||||
' To read a config value, use the community.general.git_config_info module instead.',
|
||||
version='11.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
scope = determine_scope(params)
|
||||
cwd = determine_cwd(scope, params)
|
||||
|
||||
@@ -263,7 +252,7 @@ def main():
|
||||
module.exit_json(changed=False, msg='', config_value=old_values[0] if old_values else '')
|
||||
elif unset and not out:
|
||||
module.exit_json(changed=False, msg='no setting to unset')
|
||||
elif new_value in old_values and (len(old_values) == 1 or add_mode == "add"):
|
||||
elif new_value in old_values and (len(old_values) == 1 or add_mode == "add") and not unset:
|
||||
module.exit_json(changed=False, msg="")
|
||||
|
||||
# Until this point, the git config was just read and in case no change is needed, the module has already exited.
|
||||
|
||||
@@ -415,9 +415,9 @@ class Homebrew(object):
|
||||
if self.force_formula:
|
||||
cmd.append("--formula")
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if err:
|
||||
if rc != 0:
|
||||
self.failed = True
|
||||
self.message = err.strip()
|
||||
self.message = err.strip() or ("Unknown failure with exit code %d" % rc)
|
||||
raise HomebrewException(self.message)
|
||||
data = json.loads(out)
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ short_description: Manage user accounts with systemd-homed
|
||||
version_added: 4.4.0
|
||||
description:
|
||||
- Manages a user's home directory managed by systemd-homed.
|
||||
notes:
|
||||
- This module does B(not) work with Python 3.13 or newer. It uses the deprecated L(crypt Python module,
|
||||
https://docs.python.org/3.12/library/crypt.html) from the Python standard library, which was removed
|
||||
from Python 3.13.
|
||||
requirements:
|
||||
- Python 3.12 or earlier
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
@@ -263,12 +269,21 @@ data:
|
||||
}
|
||||
'''
|
||||
|
||||
import crypt
|
||||
import json
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import traceback
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.basic import jsonify
|
||||
from ansible.module_utils.common.text.formatters import human_to_bytes
|
||||
|
||||
try:
|
||||
import crypt
|
||||
except ImportError:
|
||||
HAS_CRYPT = False
|
||||
CRYPT_IMPORT_ERROR = traceback.format_exc()
|
||||
else:
|
||||
HAS_CRYPT = True
|
||||
CRYPT_IMPORT_ERROR = None
|
||||
|
||||
|
||||
class Homectl(object):
|
||||
'''#TODO DOC STRINGS'''
|
||||
@@ -591,6 +606,12 @@ def main():
|
||||
]
|
||||
)
|
||||
|
||||
if not HAS_CRYPT:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib('crypt (part of Python 3.13 standard library)'),
|
||||
exception=CRYPT_IMPORT_ERROR,
|
||||
)
|
||||
|
||||
homectl = Homectl(module)
|
||||
homectl.result['state'] = homectl.state
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ class HPOnCfg(ModuleHelper):
|
||||
verbose=cmd_runner_fmt.as_bool("-v"),
|
||||
minfw=cmd_runner_fmt.as_opt_val("-m"),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __run__(self):
|
||||
runner = CmdRunner(
|
||||
|
||||
@@ -35,13 +35,14 @@ options:
|
||||
record_type:
|
||||
description:
|
||||
- The type of DNS record name.
|
||||
- Currently, 'A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'NS', 'PTR', 'TXT', 'SRV' and 'MX' are supported.
|
||||
- Currently, 'A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'NS', 'PTR', 'TXT', 'SRV', 'MX' and 'SSHFP' are supported.
|
||||
- "'A6', 'CNAME', 'DNAME' and 'TXT' are added in version 2.5."
|
||||
- "'SRV' and 'MX' are added in version 2.8."
|
||||
- "'NS' are added in comunity.general 8.2.0."
|
||||
- "'SSHFP' are added in community.general 9.1.0."
|
||||
required: false
|
||||
default: 'A'
|
||||
choices: ['A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'MX', 'NS', 'PTR', 'SRV', 'TXT']
|
||||
choices: ['A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'MX', 'NS', 'PTR', 'SRV', 'TXT', 'SSHFP']
|
||||
type: str
|
||||
record_value:
|
||||
description:
|
||||
@@ -57,6 +58,7 @@ options:
|
||||
- In the case of 'TXT' record type, this will be a text.
|
||||
- In the case of 'SRV' record type, this will be a service record.
|
||||
- In the case of 'MX' record type, this will be a mail exchanger record.
|
||||
- In the case of 'SSHFP' record type, this will be an SSH fingerprint record.
|
||||
type: str
|
||||
record_values:
|
||||
description:
|
||||
@@ -71,6 +73,7 @@ options:
|
||||
- In the case of 'TXT' record type, this will be a text.
|
||||
- In the case of 'SRV' record type, this will be a service record.
|
||||
- In the case of 'MX' record type, this will be a mail exchanger record.
|
||||
- In the case of 'SSHFP' record type, this will be an SSH fingerprint record.
|
||||
type: list
|
||||
elements: str
|
||||
record_ttl:
|
||||
@@ -175,6 +178,20 @@ EXAMPLES = r'''
|
||||
ipa_host: ipa.example.com
|
||||
ipa_user: admin
|
||||
ipa_pass: ChangeMe!
|
||||
|
||||
- name: Retrieve the current sshfp fingerprints
|
||||
ansible.builtin.command: ssh-keyscan -D localhost
|
||||
register: ssh_hostkeys
|
||||
|
||||
- name: Update the SSHFP records in DNS
|
||||
community.general.ipa_dnsrecord:
|
||||
name: "{{ inventory_hostname}}"
|
||||
zone_name: example.com
|
||||
record_type: 'SSHFP'
|
||||
record_values: "{{ ssh_hostkeys.stdout.split('\n') | map('split', 'SSHFP ') | map('last') | list }}"
|
||||
ipa_host: ipa.example.com
|
||||
ipa_user: admin
|
||||
ipa_pass: ChangeMe!
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
@@ -228,6 +245,8 @@ class DNSRecordIPAClient(IPAClient):
|
||||
item.update(srvrecord=value)
|
||||
elif details['record_type'] == 'MX':
|
||||
item.update(mxrecord=value)
|
||||
elif details['record_type'] == 'SSHFP':
|
||||
item.update(sshfprecord=value)
|
||||
|
||||
self._post_json(method='dnsrecord_add', name=zone_name, item=item)
|
||||
|
||||
@@ -266,6 +285,8 @@ def get_dnsrecord_dict(details=None):
|
||||
module_dnsrecord.update(srvrecord=details['record_values'])
|
||||
elif details['record_type'] == 'MX' and details['record_values']:
|
||||
module_dnsrecord.update(mxrecord=details['record_values'])
|
||||
elif details['record_type'] == 'SSHFP' and details['record_values']:
|
||||
module_dnsrecord.update(sshfprecord=details['record_values'])
|
||||
|
||||
if details.get('record_ttl'):
|
||||
module_dnsrecord.update(dnsttl=details['record_ttl'])
|
||||
@@ -328,7 +349,7 @@ def ensure(module, client):
|
||||
|
||||
|
||||
def main():
|
||||
record_types = ['A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'NS', 'PTR', 'TXT', 'SRV', 'MX']
|
||||
record_types = ['A', 'AAAA', 'A6', 'CNAME', 'DNAME', 'NS', 'PTR', 'TXT', 'SRV', 'MX', 'SSHFP']
|
||||
argument_spec = ipa_argument_spec()
|
||||
argument_spec.update(
|
||||
zone_name=dict(type='str', required=True),
|
||||
|
||||
@@ -67,7 +67,7 @@ class Blacklist(StateModuleHelper):
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
mute_vardict_deprecation = True
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.pattern = re.compile(r'^blacklist\s+{0}$'.format(re.escape(self.vars.name)))
|
||||
|
||||
@@ -340,6 +340,42 @@ options:
|
||||
description:
|
||||
- Override realm authentication flow bindings.
|
||||
type: dict
|
||||
suboptions:
|
||||
browser:
|
||||
description:
|
||||
- Flow ID of the browser authentication flow.
|
||||
- O(authentication_flow_binding_overrides.browser)
|
||||
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
|
||||
type: str
|
||||
|
||||
browser_name:
|
||||
description:
|
||||
- Flow name of the browser authentication flow.
|
||||
- O(authentication_flow_binding_overrides.browser)
|
||||
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
|
||||
aliases:
|
||||
- browserName
|
||||
type: str
|
||||
version_added: 9.1.0
|
||||
|
||||
direct_grant:
|
||||
description:
|
||||
- Flow ID of the direct grant authentication flow.
|
||||
- O(authentication_flow_binding_overrides.direct_grant)
|
||||
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
|
||||
aliases:
|
||||
- directGrant
|
||||
type: str
|
||||
|
||||
direct_grant_name:
|
||||
description:
|
||||
- Flow name of the direct grant authentication flow.
|
||||
- O(authentication_flow_binding_overrides.direct_grant)
|
||||
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
|
||||
aliases:
|
||||
- directGrantName
|
||||
type: str
|
||||
version_added: 9.1.0
|
||||
aliases:
|
||||
- authenticationFlowBindingOverrides
|
||||
version_added: 3.4.0
|
||||
@@ -775,11 +811,70 @@ def sanitize_cr(clientrep):
|
||||
if 'secret' in result:
|
||||
result['secret'] = 'no_log'
|
||||
if 'attributes' in result:
|
||||
if 'saml.signing.private.key' in result['attributes']:
|
||||
result['attributes']['saml.signing.private.key'] = 'no_log'
|
||||
attributes = result['attributes']
|
||||
if isinstance(attributes, dict) and 'saml.signing.private.key' in attributes:
|
||||
attributes['saml.signing.private.key'] = 'no_log'
|
||||
return normalise_cr(result)
|
||||
|
||||
|
||||
def get_authentication_flow_id(flow_name, realm, kc):
|
||||
""" Get the authentication flow ID based on the flow name, realm, and Keycloak client.
|
||||
|
||||
Args:
|
||||
flow_name (str): The name of the authentication flow.
|
||||
realm (str): The name of the realm.
|
||||
kc (KeycloakClient): The Keycloak client instance.
|
||||
|
||||
Returns:
|
||||
str: The ID of the authentication flow.
|
||||
|
||||
Raises:
|
||||
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
|
||||
"""
|
||||
flow = kc.get_authentication_flow_by_alias(flow_name, realm)
|
||||
if flow:
|
||||
return flow["id"]
|
||||
kc.module.fail_json(msg='Authentification flow %s not found in realm %s' % (flow_name, realm))
|
||||
|
||||
|
||||
def flow_binding_from_dict_to_model(newClientFlowBinding, realm, kc):
|
||||
""" Convert a dictionary representing client flow bindings to a model representation.
|
||||
|
||||
Args:
|
||||
newClientFlowBinding (dict): A dictionary containing client flow bindings.
|
||||
realm (str): The name of the realm.
|
||||
kc (KeycloakClient): An instance of the KeycloakClient class.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary representing the model flow bindings. The dictionary has two keys:
|
||||
- "browser" (str or None): The ID of the browser authentication flow binding, or None if not provided.
|
||||
- "direct_grant" (str or None): The ID of the direct grant authentication flow binding, or None if not provided.
|
||||
|
||||
Raises:
|
||||
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
|
||||
|
||||
"""
|
||||
|
||||
modelFlow = {
|
||||
"browser": None,
|
||||
"direct_grant": None
|
||||
}
|
||||
|
||||
for k, v in newClientFlowBinding.items():
|
||||
if not v:
|
||||
continue
|
||||
if k == "browser":
|
||||
modelFlow["browser"] = v
|
||||
elif k == "browser_name":
|
||||
modelFlow["browser"] = get_authentication_flow_id(v, realm, kc)
|
||||
elif k == "direct_grant":
|
||||
modelFlow["direct_grant"] = v
|
||||
elif k == "direct_grant_name":
|
||||
modelFlow["direct_grant"] = get_authentication_flow_id(v, realm, kc)
|
||||
|
||||
return modelFlow
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Module execution
|
||||
@@ -798,6 +893,13 @@ def main():
|
||||
config=dict(type='dict'),
|
||||
)
|
||||
|
||||
authentication_flow_spec = dict(
|
||||
browser=dict(type='str'),
|
||||
browser_name=dict(type='str', aliases=['browserName']),
|
||||
direct_grant=dict(type='str', aliases=['directGrant']),
|
||||
direct_grant_name=dict(type='str', aliases=['directGrantName']),
|
||||
)
|
||||
|
||||
meta_args = dict(
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
realm=dict(type='str', default='master'),
|
||||
@@ -837,7 +939,13 @@ def main():
|
||||
use_template_scope=dict(type='bool', aliases=['useTemplateScope']),
|
||||
use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']),
|
||||
always_display_in_console=dict(type='bool', aliases=['alwaysDisplayInConsole']),
|
||||
authentication_flow_binding_overrides=dict(type='dict', aliases=['authenticationFlowBindingOverrides']),
|
||||
authentication_flow_binding_overrides=dict(
|
||||
type='dict',
|
||||
aliases=['authenticationFlowBindingOverrides'],
|
||||
options=authentication_flow_spec,
|
||||
required_one_of=[['browser', 'direct_grant', 'browser_name', 'direct_grant_name']],
|
||||
mutually_exclusive=[['browser', 'browser_name'], ['direct_grant', 'direct_grant_name']],
|
||||
),
|
||||
protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']),
|
||||
authorization_settings=dict(type='dict', aliases=['authorizationSettings']),
|
||||
default_client_scopes=dict(type='list', elements='str', aliases=['defaultClientScopes']),
|
||||
@@ -899,6 +1007,8 @@ def main():
|
||||
# they are not specified
|
||||
if client_param == 'protocol_mappers':
|
||||
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
||||
elif client_param == 'authentication_flow_binding_overrides':
|
||||
new_param_value = flow_binding_from_dict_to_model(new_param_value, realm, kc)
|
||||
|
||||
changeset[camel(client_param)] = new_param_value
|
||||
|
||||
|
||||
@@ -301,10 +301,37 @@ end_state:
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
||||
keycloak_argument_spec, get_token, KeycloakError
|
||||
keycloak_argument_spec, get_token, KeycloakError, is_struct_included
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def normalise_cr(clientscoperep, remove_ids=False):
|
||||
""" Re-sorts any properties where the order so that diff's is minimised, and adds default values where appropriate so that the
|
||||
the change detection is more effective.
|
||||
|
||||
:param clientscoperep: the clientscoperep dict to be sanitized
|
||||
:param remove_ids: If set to true, then the unique ID's of objects is removed to make the diff and checks for changed
|
||||
not alert when the ID's of objects are not usually known, (e.g. for protocol_mappers)
|
||||
:return: normalised clientscoperep dict
|
||||
"""
|
||||
# Avoid the dict passed in to be modified
|
||||
clientscoperep = clientscoperep.copy()
|
||||
|
||||
if 'attributes' in clientscoperep:
|
||||
clientscoperep['attributes'] = list(sorted(clientscoperep['attributes']))
|
||||
|
||||
if 'protocolMappers' in clientscoperep:
|
||||
clientscoperep['protocolMappers'] = sorted(clientscoperep['protocolMappers'], key=lambda x: (x.get('name'), x.get('protocol'), x.get('protocolMapper')))
|
||||
for mapper in clientscoperep['protocolMappers']:
|
||||
if remove_ids:
|
||||
mapper.pop('id', None)
|
||||
|
||||
# Set to a default value.
|
||||
mapper['consentRequired'] = mapper.get('consentRequired', False)
|
||||
|
||||
return clientscoperep
|
||||
|
||||
|
||||
def sanitize_cr(clientscoperep):
|
||||
""" Removes probably sensitive details from a clientscoperep representation.
|
||||
|
||||
@@ -317,7 +344,7 @@ def sanitize_cr(clientscoperep):
|
||||
if 'attributes' in result:
|
||||
if 'saml.signing.private.key' in result['attributes']:
|
||||
result['attributes']['saml.signing.private.key'] = 'no_log'
|
||||
return result
|
||||
return normalise_cr(result)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -458,6 +485,13 @@ def main():
|
||||
result['diff'] = dict(before=sanitize_cr(before_clientscope), after=sanitize_cr(desired_clientscope))
|
||||
|
||||
if module.check_mode:
|
||||
# We can only compare the current clientscope with the proposed updates we have
|
||||
before_norm = normalise_cr(before_clientscope, remove_ids=True)
|
||||
desired_norm = normalise_cr(desired_clientscope, remove_ids=True)
|
||||
if module._diff:
|
||||
result['diff'] = dict(before=sanitize_cr(before_norm),
|
||||
after=sanitize_cr(desired_norm))
|
||||
result['changed'] = not is_struct_included(desired_norm, before_norm)
|
||||
module.exit_json(**result)
|
||||
|
||||
# do the update
|
||||
|
||||
@@ -514,7 +514,8 @@ def main():
|
||||
result['status']['current_pid'] != result['status']['previous_pid']):
|
||||
result['changed'] = True
|
||||
if module.check_mode:
|
||||
result['changed'] = True
|
||||
if result['status']['current_state'] != action:
|
||||
result['changed'] = True
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ class LocaleGen(StateModuleHelper):
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.vars.set("ubuntu_mode", False)
|
||||
|
||||
@@ -400,7 +400,7 @@ EXAMPLES = '''
|
||||
protocol: simplestreams
|
||||
type: image
|
||||
mode: pull
|
||||
server: https://images.linuxcontainers.org
|
||||
server: [...] # URL to the image server
|
||||
alias: debian/11
|
||||
timeout: 600
|
||||
'''
|
||||
|
||||
@@ -138,6 +138,7 @@ class MkSysB(ModuleHelper):
|
||||
backup_dmapi_fs=cmd_runner_fmt.as_bool("-A"),
|
||||
combined_path=cmd_runner_fmt.as_func(cmd_runner_fmt.unpack_args(lambda p, n: ["%s/%s" % (p, n)])),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
if not os.path.isdir(self.vars.storage_path):
|
||||
|
||||
@@ -24,7 +24,10 @@ attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
support: partial
|
||||
version_added: 9.1.0
|
||||
details:
|
||||
- Only works when check mode is not enabled.
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
@@ -159,6 +162,20 @@ def execute_command(cmd, module):
|
||||
return module.run_command(cmd_args, environ_update={'TERM': 'dumb'})
|
||||
|
||||
|
||||
def get_all_installed(module):
|
||||
"""
|
||||
Get all installed packaged. Used to support diff mode
|
||||
"""
|
||||
command = 'pkg_info -Iq'
|
||||
|
||||
rc, stdout, stderr = execute_command(command, module)
|
||||
|
||||
if stderr:
|
||||
module.fail_json(msg="failed in get_all_installed(): %s" % stderr)
|
||||
|
||||
return stdout
|
||||
|
||||
|
||||
# Function used to find out if a package is currently installed.
|
||||
def get_package_state(names, pkg_spec, module):
|
||||
info_cmd = 'pkg_info -Iq'
|
||||
@@ -573,10 +590,13 @@ def main():
|
||||
result['name'] = name
|
||||
result['state'] = state
|
||||
result['build'] = build
|
||||
result['diff'] = {}
|
||||
|
||||
# The data structure used to keep track of package information.
|
||||
pkg_spec = {}
|
||||
|
||||
new_package_list = original_package_list = get_all_installed(module)
|
||||
|
||||
if build is True:
|
||||
if not os.path.isdir(ports_dir):
|
||||
module.fail_json(msg="the ports source directory %s does not exist" % (ports_dir))
|
||||
@@ -661,6 +681,10 @@ def main():
|
||||
|
||||
result['changed'] = combined_changed
|
||||
|
||||
if result['changed'] and not module.check_mode:
|
||||
new_package_list = get_all_installed(module)
|
||||
result['diff'] = dict(before=original_package_list, after=new_package_list)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
|
||||
@@ -367,8 +367,9 @@ class Pacman(object):
|
||||
self.install_packages(pkgs)
|
||||
self.success()
|
||||
|
||||
# This shouldn't happen...
|
||||
self.fail("This is a bug")
|
||||
# This happens if an empty list has been provided for name
|
||||
self.add_exit_infos(msg='Nothing to do')
|
||||
self.success()
|
||||
|
||||
def install_packages(self, pkgs):
|
||||
pkgs_to_install = []
|
||||
|
||||
@@ -150,6 +150,7 @@ class PipXInfo(ModuleHelper):
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
if self.vars.executable:
|
||||
|
||||
@@ -174,6 +174,7 @@ options:
|
||||
- Allow to force stop VM.
|
||||
- Can be used with states V(stopped), V(restarted), and V(absent).
|
||||
- This option has no default unless O(proxmox_default_behavior) is set to V(compatibility); then the default is V(false).
|
||||
- Requires parameter O(archive).
|
||||
type: bool
|
||||
format:
|
||||
description:
|
||||
|
||||
@@ -57,6 +57,13 @@ options:
|
||||
- pending
|
||||
default: none
|
||||
version_added: 8.1.0
|
||||
network:
|
||||
description:
|
||||
- Whether to retrieve the current network status.
|
||||
- Requires enabled/running qemu-guest-agent on qemu VMs.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 9.1.0
|
||||
extends_documentation_fragment:
|
||||
- community.general.proxmox.actiongroup_proxmox
|
||||
- community.general.proxmox.documentation
|
||||
@@ -172,7 +179,7 @@ class ProxmoxVmInfoAnsible(ProxmoxAnsible):
|
||||
msg="Failed to retrieve VMs information from cluster resources: %s" % e
|
||||
)
|
||||
|
||||
def get_vms_from_nodes(self, cluster_machines, type, vmid=None, name=None, node=None, config=None):
|
||||
def get_vms_from_nodes(self, cluster_machines, type, vmid=None, name=None, node=None, config=None, network=False):
|
||||
# Leave in dict only machines that user wants to know about
|
||||
filtered_vms = {
|
||||
vm: info for vm, info in cluster_machines.items() if not (
|
||||
@@ -201,17 +208,23 @@ class ProxmoxVmInfoAnsible(ProxmoxAnsible):
|
||||
config_type = 0 if config == "pending" else 1
|
||||
# GET /nodes/{node}/qemu/{vmid}/config current=[0/1]
|
||||
desired_vm["config"] = call_vm_getter(this_vm_id).config().get(current=config_type)
|
||||
if network:
|
||||
if type == "qemu":
|
||||
desired_vm["network"] = call_vm_getter(this_vm_id).agent("network-get-interfaces").get()['result']
|
||||
elif type == "lxc":
|
||||
desired_vm["network"] = call_vm_getter(this_vm_id).interfaces.get()
|
||||
|
||||
return filtered_vms
|
||||
|
||||
def get_qemu_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None):
|
||||
def get_qemu_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None, network=False):
|
||||
try:
|
||||
return self.get_vms_from_nodes(cluster_machines, "qemu", vmid, name, node, config)
|
||||
return self.get_vms_from_nodes(cluster_machines, "qemu", vmid, name, node, config, network)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to retrieve QEMU VMs information: %s" % e)
|
||||
|
||||
def get_lxc_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None):
|
||||
def get_lxc_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None, network=False):
|
||||
try:
|
||||
return self.get_vms_from_nodes(cluster_machines, "lxc", vmid, name, node, config)
|
||||
return self.get_vms_from_nodes(cluster_machines, "lxc", vmid, name, node, config, network)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to retrieve LXC VMs information: %s" % e)
|
||||
|
||||
@@ -229,6 +242,7 @@ def main():
|
||||
type="str", choices=["none", "current", "pending"],
|
||||
default="none", required=False
|
||||
),
|
||||
network=dict(type="bool", default=False, required=False),
|
||||
)
|
||||
module_args.update(vm_info_args)
|
||||
|
||||
@@ -245,6 +259,7 @@ def main():
|
||||
vmid = module.params["vmid"]
|
||||
name = module.params["name"]
|
||||
config = module.params["config"]
|
||||
network = module.params["network"]
|
||||
|
||||
result = dict(changed=False)
|
||||
|
||||
@@ -256,12 +271,12 @@ def main():
|
||||
vms = {}
|
||||
|
||||
if type == "lxc":
|
||||
vms = proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config)
|
||||
vms = proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config, network)
|
||||
elif type == "qemu":
|
||||
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config)
|
||||
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config, network)
|
||||
else:
|
||||
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config)
|
||||
vms.update(proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config))
|
||||
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config, network)
|
||||
vms.update(proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config, network))
|
||||
|
||||
result["proxmox_vms"] = [info for vm, info in sorted(vms.items())]
|
||||
module.exit_json(**result)
|
||||
|
||||
@@ -288,6 +288,20 @@ options:
|
||||
type: str
|
||||
choices: [ ResetAll, PreserveNetworkAndUsers, PreserveNetwork ]
|
||||
version_added: 8.6.0
|
||||
wait:
|
||||
required: false
|
||||
description:
|
||||
- Block until the service is ready again.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 9.1.0
|
||||
wait_timeout:
|
||||
required: false
|
||||
description:
|
||||
- How long to block until the service is ready again before giving up.
|
||||
type: int
|
||||
default: 120
|
||||
version_added: 9.1.0
|
||||
|
||||
author:
|
||||
- "Jose Delarosa (@jose-delarosa)"
|
||||
@@ -685,6 +699,16 @@ EXAMPLES = '''
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
|
||||
- name: Restart manager power gracefully and wait for it to be available
|
||||
community.general.redfish_command:
|
||||
category: Manager
|
||||
command: GracefulRestart
|
||||
resource_id: BMC
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
wait: True
|
||||
|
||||
- name: Restart manager power gracefully
|
||||
community.general.redfish_command:
|
||||
category: Manager
|
||||
@@ -841,7 +865,9 @@ def main():
|
||||
),
|
||||
strip_etag_quotes=dict(type='bool', default=False),
|
||||
reset_to_defaults_mode=dict(choices=['ResetAll', 'PreserveNetworkAndUsers', 'PreserveNetwork']),
|
||||
bios_attributes=dict(type="dict")
|
||||
bios_attributes=dict(type="dict"),
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='int', default=120),
|
||||
),
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
@@ -1016,7 +1042,7 @@ def main():
|
||||
command = 'PowerGracefulRestart'
|
||||
|
||||
if command.startswith('Power'):
|
||||
result = rf_utils.manage_manager_power(command)
|
||||
result = rf_utils.manage_manager_power(command, module.params['wait'], module.params['wait_timeout'])
|
||||
elif command == 'ClearLogs':
|
||||
result = rf_utils.clear_logs()
|
||||
elif command == 'VirtualMediaInsert':
|
||||
|
||||
@@ -359,6 +359,16 @@ EXAMPLES = '''
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
|
||||
- name: Check the availability of the service with a timeout of 5 seconds
|
||||
community.general.redfish_info:
|
||||
category: Service
|
||||
command: CheckAvailability
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
timeout: 5
|
||||
register: result
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -385,6 +395,7 @@ CATEGORY_COMMANDS_ALL = {
|
||||
"GetUpdateStatus"],
|
||||
"Manager": ["GetManagerNicInventory", "GetVirtualMedia", "GetLogs", "GetNetworkProtocols",
|
||||
"GetHealthReport", "GetHostInterfaces", "GetManagerInventory", "GetServiceIdentification"],
|
||||
"Service": ["CheckAvailability"],
|
||||
}
|
||||
|
||||
CATEGORY_COMMANDS_DEFAULT = {
|
||||
@@ -393,7 +404,8 @@ CATEGORY_COMMANDS_DEFAULT = {
|
||||
"Accounts": "ListUsers",
|
||||
"Update": "GetFirmwareInventory",
|
||||
"Sessions": "GetSessions",
|
||||
"Manager": "GetManagerNicInventory"
|
||||
"Manager": "GetManagerNicInventory",
|
||||
"Service": "CheckAvailability",
|
||||
}
|
||||
|
||||
|
||||
@@ -473,7 +485,13 @@ def main():
|
||||
module.fail_json(msg="Invalid Category: %s" % category)
|
||||
|
||||
# Organize by Categories / Commands
|
||||
if category == "Systems":
|
||||
if category == "Service":
|
||||
# service-level commands are always available
|
||||
for command in command_list:
|
||||
if command == "CheckAvailability":
|
||||
result["service"] = rf_utils.check_service_availability()
|
||||
|
||||
elif category == "Systems":
|
||||
# execute only if we find a Systems resource
|
||||
resource = rf_utils._find_systems_resource()
|
||||
if resource['ret'] is False:
|
||||
|
||||
@@ -30,6 +30,11 @@ options:
|
||||
version_added: 7.5.0
|
||||
ca_certs:
|
||||
version_added: 7.5.0
|
||||
cluster:
|
||||
default: false
|
||||
description: Get informations about cluster status as RV(cluster).
|
||||
type: bool
|
||||
version_added: 9.1.0
|
||||
seealso:
|
||||
- module: community.general.redis
|
||||
author: "Pavlo Bashynskyi (@levonet)"
|
||||
@@ -43,6 +48,15 @@ EXAMPLES = r'''
|
||||
- name: Print server information
|
||||
ansible.builtin.debug:
|
||||
var: result.info
|
||||
|
||||
- name: Get server cluster information
|
||||
community.general.redis_info:
|
||||
cluster: true
|
||||
register: result
|
||||
|
||||
- name: Print server cluster information
|
||||
ansible.builtin.debug:
|
||||
var: result.cluster_info
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
@@ -178,6 +192,25 @@ info:
|
||||
"used_memory_scripts_human": "0B",
|
||||
"used_memory_startup": 791264
|
||||
}
|
||||
cluster:
|
||||
description: The default set of cluster information sections U(https://redis.io/commands/cluster-info).
|
||||
returned: success if O(cluster=true)
|
||||
version_added: 9.1.0
|
||||
type: dict
|
||||
sample: {
|
||||
"cluster_state": ok,
|
||||
"cluster_slots_assigned": 16384,
|
||||
"cluster_slots_ok": 16384,
|
||||
"cluster_slots_pfail": 0,
|
||||
"cluster_slots_fail": 0,
|
||||
"cluster_known_nodes": 6,
|
||||
"cluster_size": 3,
|
||||
"cluster_current_epoch": 6,
|
||||
"cluster_my_epoch": 2,
|
||||
"cluster_stats_messages_sent": 1483972,
|
||||
"cluster_stats_messages_received": 1483968,
|
||||
"total_cluster_links_buffer_limit_exceeded": 0
|
||||
}
|
||||
'''
|
||||
|
||||
import traceback
|
||||
@@ -202,14 +235,19 @@ def redis_client(**client_params):
|
||||
|
||||
# Module execution.
|
||||
def main():
|
||||
module_args = dict(
|
||||
cluster=dict(type='bool', default=False),
|
||||
)
|
||||
module_args.update(redis_auth_argument_spec(tls_default=False))
|
||||
module = AnsibleModule(
|
||||
argument_spec=redis_auth_argument_spec(tls_default=False),
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
fail_imports(module, module.params['tls'])
|
||||
|
||||
redis_params = redis_auth_params(module)
|
||||
cluster = module.params['cluster']
|
||||
|
||||
# Connect and check
|
||||
client = redis_client(**redis_params)
|
||||
@@ -219,7 +257,13 @@ def main():
|
||||
module.fail_json(msg="unable to connect to database: %s" % to_native(e), exception=traceback.format_exc())
|
||||
|
||||
info = client.info()
|
||||
module.exit_json(changed=False, info=info)
|
||||
|
||||
result = dict(changed=False, info=info)
|
||||
|
||||
if cluster:
|
||||
result['cluster_info'] = client.execute_command('CLUSTER INFO')
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -194,6 +194,7 @@ class Snap(StateModuleHelper):
|
||||
},
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
@staticmethod
|
||||
def _first_non_zero(a):
|
||||
@@ -405,8 +406,8 @@ class Snap(StateModuleHelper):
|
||||
|
||||
def state_present(self):
|
||||
|
||||
self.vars.meta('classic').set(output=True)
|
||||
self.vars.meta('channel').set(output=True)
|
||||
self.vars.set_meta('classic', output=True)
|
||||
self.vars.set_meta('channel', output=True)
|
||||
|
||||
actionable_refresh = [snap for snap in self.vars.name if self.vars.snap_status_map[snap] == Snap.CHANNEL_MISMATCH]
|
||||
if actionable_refresh:
|
||||
|
||||
@@ -105,6 +105,7 @@ class SnapAlias(StateModuleHelper):
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def _aliases(self):
|
||||
n = self.vars.name
|
||||
|
||||
@@ -20,6 +20,12 @@ description:
|
||||
- "This module allows to manage posix users on a univention corporate
|
||||
server (UCS).
|
||||
It uses the python API of the UCS to create a new object or edit it."
|
||||
notes:
|
||||
- This module does B(not) work with Python 3.13 or newer. It uses the deprecated L(crypt Python module,
|
||||
https://docs.python.org/3.12/library/crypt.html) from the Python standard library, which was removed
|
||||
from Python 3.13.
|
||||
requirements:
|
||||
- Python 3.12 or earlier
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
@@ -324,10 +330,10 @@ EXAMPLES = '''
|
||||
|
||||
RETURN = '''# '''
|
||||
|
||||
import crypt
|
||||
from datetime import date, timedelta
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible_collections.community.general.plugins.module_utils.univention_umc import (
|
||||
umc_module_for_add,
|
||||
umc_module_for_edit,
|
||||
@@ -335,6 +341,15 @@ from ansible_collections.community.general.plugins.module_utils.univention_umc i
|
||||
base_dn,
|
||||
)
|
||||
|
||||
try:
|
||||
import crypt
|
||||
except ImportError:
|
||||
HAS_CRYPT = False
|
||||
CRYPT_IMPORT_ERROR = traceback.format_exc()
|
||||
else:
|
||||
HAS_CRYPT = True
|
||||
CRYPT_IMPORT_ERROR = None
|
||||
|
||||
|
||||
def main():
|
||||
expiry = date.strftime(date.today() + timedelta(days=365), "%Y-%m-%d")
|
||||
@@ -451,6 +466,13 @@ def main():
|
||||
('state', 'present', ['firstname', 'lastname', 'password'])
|
||||
])
|
||||
)
|
||||
|
||||
if not HAS_CRYPT:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib('crypt (part of Python 3.13 standard library)'),
|
||||
exception=CRYPT_IMPORT_ERROR,
|
||||
)
|
||||
|
||||
username = module.params['username']
|
||||
position = module.params['position']
|
||||
ou = module.params['ou']
|
||||
|
||||
141
plugins/plugin_utils/keys_filter.py
Normal file
141
plugins/plugin_utils/keys_filter.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2024 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.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common._collections_compat import Mapping, Sequence
|
||||
|
||||
|
||||
def _keys_filter_params(data, matching_parameter):
|
||||
"""test parameters:
|
||||
* data must be a list of dictionaries. All keys must be strings.
|
||||
* matching_parameter is member of a list.
|
||||
"""
|
||||
|
||||
mp = matching_parameter
|
||||
ml = ['equal', 'starts_with', 'ends_with', 'regex']
|
||||
|
||||
if not isinstance(data, Sequence):
|
||||
msg = "First argument must be a list. %s is %s"
|
||||
raise AnsibleFilterError(msg % (data, type(data)))
|
||||
|
||||
for elem in data:
|
||||
if not isinstance(elem, Mapping):
|
||||
msg = "The data items must be dictionaries. %s is %s"
|
||||
raise AnsibleFilterError(msg % (elem, type(elem)))
|
||||
|
||||
for elem in data:
|
||||
if not all(isinstance(item, string_types) for item in elem.keys()):
|
||||
msg = "Top level keys must be strings. keys: %s"
|
||||
raise AnsibleFilterError(msg % elem.keys())
|
||||
|
||||
if mp not in ml:
|
||||
msg = "The matching_parameter must be one of %s. matching_parameter=%s"
|
||||
raise AnsibleFilterError(msg % (ml, mp))
|
||||
|
||||
return
|
||||
|
||||
|
||||
def _keys_filter_target_str(target, matching_parameter):
|
||||
"""
|
||||
Test:
|
||||
* target is a non-empty string or list.
|
||||
* If target is list all items are strings.
|
||||
* target is a string or list with single string if matching_parameter=regex.
|
||||
Convert target and return:
|
||||
* tuple of unique target items, or
|
||||
* tuple with single item, or
|
||||
* compiled regex if matching_parameter=regex.
|
||||
"""
|
||||
|
||||
if not isinstance(target, Sequence):
|
||||
msg = "The target must be a string or a list. target is %s."
|
||||
raise AnsibleFilterError(msg % type(target))
|
||||
|
||||
if len(target) == 0:
|
||||
msg = "The target can't be empty."
|
||||
raise AnsibleFilterError(msg)
|
||||
|
||||
if isinstance(target, list):
|
||||
for elem in target:
|
||||
if not isinstance(elem, string_types):
|
||||
msg = "The target items must be strings. %s is %s"
|
||||
raise AnsibleFilterError(msg % (elem, type(elem)))
|
||||
|
||||
if matching_parameter == 'regex':
|
||||
if isinstance(target, string_types):
|
||||
r = target
|
||||
else:
|
||||
if len(target) > 1:
|
||||
msg = "Single item is required in the target list if matching_parameter=regex."
|
||||
raise AnsibleFilterError(msg)
|
||||
else:
|
||||
r = target[0]
|
||||
try:
|
||||
tt = re.compile(r)
|
||||
except re.error:
|
||||
msg = "The target must be a valid regex if matching_parameter=regex. target is %s"
|
||||
raise AnsibleFilterError(msg % r)
|
||||
elif isinstance(target, string_types):
|
||||
tt = (target, )
|
||||
else:
|
||||
tt = tuple(set(target))
|
||||
|
||||
return tt
|
||||
|
||||
|
||||
def _keys_filter_target_dict(target, matching_parameter):
|
||||
"""
|
||||
Test:
|
||||
* target is a list of dictionaries with attributes 'after' and 'before'.
|
||||
* Attributes 'before' must be valid regex if matching_parameter=regex.
|
||||
* Otherwise, the attributes 'before' must be strings.
|
||||
Convert target and return:
|
||||
* iterator that aggregates attributes 'before' and 'after', or
|
||||
* iterator that aggregates compiled regex of attributes 'before' and 'after' if matching_parameter=regex.
|
||||
"""
|
||||
|
||||
if not isinstance(target, list):
|
||||
msg = "The target must be a list. target is %s."
|
||||
raise AnsibleFilterError(msg % (target, type(target)))
|
||||
|
||||
if len(target) == 0:
|
||||
msg = "The target can't be empty."
|
||||
raise AnsibleFilterError(msg)
|
||||
|
||||
for elem in target:
|
||||
if not isinstance(elem, Mapping):
|
||||
msg = "The target items must be dictionaries. %s is %s"
|
||||
raise AnsibleFilterError(msg % (elem, type(elem)))
|
||||
if not all(k in elem for k in ('before', 'after')):
|
||||
msg = "All dictionaries in target must include attributes: after, before."
|
||||
raise AnsibleFilterError(msg)
|
||||
if not isinstance(elem['before'], string_types):
|
||||
msg = "The attributes before must be strings. %s is %s"
|
||||
raise AnsibleFilterError(msg % (elem['before'], type(elem['before'])))
|
||||
if not isinstance(elem['after'], string_types):
|
||||
msg = "The attributes after must be strings. %s is %s"
|
||||
raise AnsibleFilterError(msg % (elem['after'], type(elem['after'])))
|
||||
|
||||
before = [d['before'] for d in target]
|
||||
after = [d['after'] for d in target]
|
||||
|
||||
if matching_parameter == 'regex':
|
||||
try:
|
||||
tr = map(re.compile, before)
|
||||
tz = list(zip(tr, after))
|
||||
except re.error:
|
||||
msg = ("The attributes before must be valid regex if matching_parameter=regex."
|
||||
" Not all items are valid regex in: %s")
|
||||
raise AnsibleFilterError(msg % before)
|
||||
else:
|
||||
tz = list(zip(before, after))
|
||||
|
||||
return tz
|
||||
@@ -4,10 +4,16 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
###################################################
|
||||
- name: Make directory install_c
|
||||
ansible.builtin.file:
|
||||
path: "{{ remote_tmp_dir }}/install_c"
|
||||
state: directory
|
||||
|
||||
- name: Install collection netbox.netbox
|
||||
community.general.ansible_galaxy_install:
|
||||
type: collection
|
||||
name: netbox.netbox
|
||||
dest: "{{ remote_tmp_dir }}/install_c"
|
||||
register: install_c0
|
||||
|
||||
- name: Assert collection netbox.netbox was installed
|
||||
@@ -20,6 +26,7 @@
|
||||
community.general.ansible_galaxy_install:
|
||||
type: collection
|
||||
name: netbox.netbox
|
||||
dest: "{{ remote_tmp_dir }}/install_c"
|
||||
register: install_c1
|
||||
|
||||
- name: Assert collection was not installed
|
||||
@@ -28,10 +35,16 @@
|
||||
- install_c1 is not changed
|
||||
|
||||
###################################################
|
||||
- name: Make directory install_r
|
||||
ansible.builtin.file:
|
||||
path: "{{ remote_tmp_dir }}/install_r"
|
||||
state: directory
|
||||
|
||||
- name: Install role ansistrano.deploy
|
||||
community.general.ansible_galaxy_install:
|
||||
type: role
|
||||
name: ansistrano.deploy
|
||||
dest: "{{ remote_tmp_dir }}/install_r"
|
||||
register: install_r0
|
||||
|
||||
- name: Assert collection ansistrano.deploy was installed
|
||||
@@ -44,6 +57,7 @@
|
||||
community.general.ansible_galaxy_install:
|
||||
type: role
|
||||
name: ansistrano.deploy
|
||||
dest: "{{ remote_tmp_dir }}/install_r"
|
||||
register: install_r1
|
||||
|
||||
- name: Assert role was not installed
|
||||
@@ -86,3 +100,44 @@
|
||||
assert:
|
||||
that:
|
||||
- install_rq1 is not changed
|
||||
|
||||
###################################################
|
||||
- name: Make directory upgrade_c
|
||||
ansible.builtin.file:
|
||||
path: "{{ remote_tmp_dir }}/upgrade_c"
|
||||
state: directory
|
||||
|
||||
- name: Install collection netbox.netbox 3.17.0
|
||||
community.general.ansible_galaxy_install:
|
||||
type: collection
|
||||
name: netbox.netbox:3.17.0
|
||||
dest: "{{ remote_tmp_dir }}/upgrade_c"
|
||||
register: upgrade_c0
|
||||
|
||||
- name: Assert collection netbox.netbox was installed
|
||||
assert:
|
||||
that:
|
||||
- upgrade_c0 is changed
|
||||
- '"netbox.netbox" in upgrade_c0.new_collections'
|
||||
|
||||
- name: Upgrade collection netbox.netbox
|
||||
community.general.ansible_galaxy_install:
|
||||
state: latest
|
||||
type: collection
|
||||
name: netbox.netbox
|
||||
dest: "{{ remote_tmp_dir }}/upgrade_c"
|
||||
register: upgrade_c1
|
||||
|
||||
- name: Upgrade collection netbox.netbox (again)
|
||||
community.general.ansible_galaxy_install:
|
||||
state: latest
|
||||
type: collection
|
||||
name: netbox.netbox
|
||||
dest: "{{ remote_tmp_dir }}/upgrade_c"
|
||||
register: upgrade_c2
|
||||
|
||||
- name: Assert collection was not installed
|
||||
assert:
|
||||
that:
|
||||
- upgrade_c1 is changed
|
||||
- upgrade_c2 is not changed
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
- block:
|
||||
- import_tasks: test_general.yml
|
||||
- import_tasks: test_version.yml
|
||||
- import_tasks: test_directory.yml
|
||||
environment: "{{ cargo_environment }}"
|
||||
when: has_cargo | default(false)
|
||||
- import_tasks: test_rustup_cargo.yml
|
||||
|
||||
122
tests/integration/targets/cargo/tasks/test_directory.yml
Normal file
122
tests/integration/targets/cargo/tasks/test_directory.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
# Copyright (c) 2024 Colin Nolan <cn580@alumni.york.ac.uk>
|
||||
# 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: Create temp directory
|
||||
tempfile:
|
||||
state: directory
|
||||
register: temp_directory
|
||||
|
||||
- name: Test block
|
||||
vars:
|
||||
manifest_path: "{{ temp_directory.path }}/Cargo.toml"
|
||||
package_name: hello-world-directory-test
|
||||
block:
|
||||
- name: Initialize package
|
||||
ansible.builtin.command:
|
||||
cmd: "cargo init --name {{ package_name }}"
|
||||
args:
|
||||
chdir: "{{ temp_directory.path }}"
|
||||
|
||||
- name: Set package version (1.0.0)
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ manifest_path }}"
|
||||
regexp: '^version = ".*"$'
|
||||
line: 'version = "1.0.0"'
|
||||
|
||||
- name: Ensure package is uninstalled
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
state: absent
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: uninstall_absent
|
||||
|
||||
- name: Install package
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: install_absent
|
||||
|
||||
- name: Change package version (1.0.1)
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ manifest_path }}"
|
||||
regexp: '^version = ".*"$'
|
||||
line: 'version = "1.0.1"'
|
||||
|
||||
- name: Install package again (present)
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
state: present
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: install_present_state
|
||||
|
||||
- name: Install package again (latest)
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
state: latest
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: install_latest_state
|
||||
|
||||
- name: Change package version (2.0.0)
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ manifest_path }}"
|
||||
regexp: '^version = ".*"$'
|
||||
line: 'version = "2.0.0"'
|
||||
|
||||
- name: Install package with given version (matched)
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
version: "2.0.0"
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: install_given_version_matched
|
||||
|
||||
- name: Install package with given version (unmatched)
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
version: "2.0.1"
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: install_given_version_unmatched
|
||||
ignore_errors: true
|
||||
|
||||
- name: Uninstall package
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
state: absent
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: uninstall_present
|
||||
|
||||
- name: Install non-existant package
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}-non-existant"
|
||||
state: present
|
||||
directory: "{{ temp_directory.path }}"
|
||||
register: install_non_existant
|
||||
ignore_errors: true
|
||||
|
||||
- name: Install non-existant source directory
|
||||
community.general.cargo:
|
||||
name: "{{ package_name }}"
|
||||
state: present
|
||||
directory: "{{ temp_directory.path }}/non-existant"
|
||||
register: install_non_existant_source
|
||||
ignore_errors: true
|
||||
|
||||
always:
|
||||
- name: Remove temp directory
|
||||
file:
|
||||
path: "{{ temp_directory.path }}"
|
||||
state: absent
|
||||
|
||||
- name: Check assertions
|
||||
assert:
|
||||
that:
|
||||
- uninstall_absent is not changed
|
||||
- install_absent is changed
|
||||
- install_present_state is not changed
|
||||
- install_latest_state is changed
|
||||
- install_given_version_matched is changed
|
||||
- install_given_version_unmatched is failed
|
||||
- uninstall_present is changed
|
||||
- install_non_existant is failed
|
||||
- install_non_existant_source is failed
|
||||
114
tests/integration/targets/consul/tasks/consul_agent_check.yml
Normal file
114
tests/integration/targets/consul/tasks/consul_agent_check.yml
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
# Copyright (c) 2024, Michael Ilg (@Ilgmi)
|
||||
# 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: Create a service
|
||||
community.general.consul_agent_service:
|
||||
name: nginx
|
||||
service_port: 80
|
||||
address: localhost
|
||||
tags:
|
||||
- http
|
||||
meta:
|
||||
nginx_version: 1.25.3
|
||||
register: result
|
||||
|
||||
- set_fact:
|
||||
nginx_service: "{{result.service}}"
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.service.ID is defined
|
||||
|
||||
- name: Add a check for service
|
||||
community.general.consul_agent_check:
|
||||
name: nginx_check
|
||||
id: nginx_check
|
||||
interval: 30s
|
||||
http: http://localhost:80/morestatus
|
||||
notes: "Nginx Check"
|
||||
service_id: "{{ nginx_service.ID }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.check is defined
|
||||
- result.check.CheckID == 'nginx_check'
|
||||
- result.check.ServiceID == 'nginx'
|
||||
- result.check.Interval == '30s'
|
||||
- result.check.Type == 'http'
|
||||
- result.check.Notes == 'Nginx Check'
|
||||
|
||||
- set_fact:
|
||||
nginx_service_check: "{{ result.check }}"
|
||||
|
||||
- name: Update check for service
|
||||
community.general.consul_agent_check:
|
||||
name: "{{ nginx_service_check.Name }}"
|
||||
id: "{{ nginx_service_check.CheckID }}"
|
||||
interval: 60s
|
||||
http: http://localhost:80/morestatus
|
||||
notes: "New Nginx Check"
|
||||
service_id: "{{ nginx_service.ID }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.check is defined
|
||||
- result.check.CheckID == 'nginx_check'
|
||||
- result.check.ServiceID == 'nginx'
|
||||
- result.check.Interval == '1m0s'
|
||||
- result.check.Type == 'http'
|
||||
- result.check.Notes == 'New Nginx Check'
|
||||
|
||||
- name: Remove check
|
||||
community.general.consul_agent_check:
|
||||
id: "{{ nginx_service_check.Name }}"
|
||||
state: absent
|
||||
service_id: "{{ nginx_service.ID }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result is not failed
|
||||
- result.operation == 'remove'
|
||||
|
||||
- name: Add a check
|
||||
community.general.consul_agent_check:
|
||||
name: check
|
||||
id: check
|
||||
interval: 30s
|
||||
tcp: localhost:80
|
||||
notes: "check"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.check is defined
|
||||
|
||||
- name: Update a check
|
||||
community.general.consul_agent_check:
|
||||
name: check
|
||||
id: check
|
||||
interval: 60s
|
||||
tcp: localhost:80
|
||||
notes: "check"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.check is defined
|
||||
- result.check.Interval == '1m0s'
|
||||
|
||||
- name: Remove check
|
||||
community.general.consul_agent_check:
|
||||
id: check
|
||||
state: absent
|
||||
register: result
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
# Copyright (c) 2024, Michael Ilg (@Ilgmi)
|
||||
# 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: Create a service
|
||||
community.general.consul_agent_service:
|
||||
name: nginx
|
||||
service_port: 80
|
||||
address: localhost
|
||||
tags:
|
||||
- http
|
||||
meta:
|
||||
nginx_version: 1.25.3
|
||||
register: result
|
||||
|
||||
- set_fact:
|
||||
nginx_service: "{{result.service}}"
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.service.ID is defined
|
||||
- result.service.Service == 'nginx'
|
||||
- result.service.Address == 'localhost'
|
||||
- result.service.Port == 80
|
||||
- result.service.Tags[0] == 'http'
|
||||
- result.service.Meta.nginx_version is defined
|
||||
- result.service.Meta.nginx_version == '1.25.3'
|
||||
- result.service.ContentHash is defined
|
||||
|
||||
- name: Update service
|
||||
community.general.consul_agent_service:
|
||||
id: "{{ nginx_service.ID }}"
|
||||
name: "{{ nginx_service.Service }}"
|
||||
service_port: 8080
|
||||
address: 127.0.0.1
|
||||
tags:
|
||||
- http
|
||||
- new_tag
|
||||
meta:
|
||||
nginx_version: 1.0.0
|
||||
nginx: 1.25.3
|
||||
register: result
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.service.ID is defined
|
||||
- result.service.Service == 'nginx'
|
||||
- result.service.Address == '127.0.0.1'
|
||||
- result.service.Port == 8080
|
||||
- result.service.Tags[0] == 'http'
|
||||
- result.service.Tags[1] == 'new_tag'
|
||||
- result.service.Meta.nginx_version is defined
|
||||
- result.service.Meta.nginx_version == '1.0.0'
|
||||
- result.service.Meta.nginx is defined
|
||||
- result.service.Meta.nginx == '1.25.3'
|
||||
- result.service.ContentHash is defined
|
||||
|
||||
- name: Update service not changed when updating again without changes
|
||||
community.general.consul_agent_service:
|
||||
id: "{{ nginx_service.ID }}"
|
||||
name: "{{ nginx_service.Service }}"
|
||||
service_port: 8080
|
||||
address: 127.0.0.1
|
||||
tags:
|
||||
- http
|
||||
- new_tag
|
||||
meta:
|
||||
nginx_version: 1.0.0
|
||||
nginx: 1.25.3
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.operation is not defined
|
||||
|
||||
- name: Remove service
|
||||
community.general.consul_agent_service:
|
||||
id: "{{ nginx_service.ID }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result is not failed
|
||||
- result.operation == 'remove'
|
||||
@@ -97,6 +97,8 @@
|
||||
- import_tasks: consul_token.yml
|
||||
- import_tasks: consul_auth_method.yml
|
||||
- import_tasks: consul_binding_rule.yml
|
||||
- import_tasks: consul_agent_service.yml
|
||||
- import_tasks: consul_agent_check.yml
|
||||
module_defaults:
|
||||
group/community.general.consul:
|
||||
token: "{{ consul_management_token }}"
|
||||
|
||||
5
tests/integration/targets/filter_keep_keys/aliases
Normal file
5
tests/integration/targets/filter_keep_keys/aliases
Normal file
@@ -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
|
||||
|
||||
azp/posix/2
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
# 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: Debug ansible_version
|
||||
ansible.builtin.debug:
|
||||
var: ansible_version
|
||||
when: not quite_test | d(true) | bool
|
||||
tags: ansible_version
|
||||
|
||||
- name: Test keep keys equal (default)
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (rr | difference(result1) | length) == 0
|
||||
success_msg: |
|
||||
[OK] result:
|
||||
{{ rr | to_yaml }}
|
||||
fail_msg: |
|
||||
[ERR] result:
|
||||
{{ rr | to_yaml }}
|
||||
quiet: "{{ quiet_test | d(true) | bool }}"
|
||||
vars:
|
||||
rr: "{{ list1 | community.general.keep_keys(target=tt) }}"
|
||||
tt: [k0_x0, k1_x1]
|
||||
tags: equal_default
|
||||
|
||||
- name: Test keep keys regex string
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (rr | difference(result1) | length) == 0
|
||||
success_msg: |
|
||||
[OK] result:
|
||||
{{ rr | to_yaml }}
|
||||
fail_msg: |
|
||||
[ERR] result:
|
||||
{{ rr | to_yaml }}
|
||||
quiet: "{{ quiet_test | d(true) | bool }}"
|
||||
vars:
|
||||
rr: "{{ list1 | community.general.keep_keys(target=tt, matching_parameter=mp) }}"
|
||||
mp: regex
|
||||
tt: '^.*[01]_x.*$'
|
||||
tags: regex_string
|
||||
|
||||
- name: Test keep keys targets1
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (rr | difference(result1) | length) == 0
|
||||
success_msg: |
|
||||
[OK] result:
|
||||
{{ rr | to_yaml }}
|
||||
fail_msg: |
|
||||
[ERR] result:
|
||||
{{ rr | to_yaml }}
|
||||
quiet: "{{ quiet_test | d(true) | bool }}"
|
||||
loop: "{{ targets1 }}"
|
||||
loop_control:
|
||||
label: "{{ item.mp }}: {{ item.tt }}"
|
||||
vars:
|
||||
rr: "{{ list1 | community.general.keep_keys(target=item.tt, matching_parameter=item.mp) }}"
|
||||
tags: targets1
|
||||
|
||||
- name: Test keep keys targets2
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (rr | difference(result2) | length) == 0
|
||||
success_msg: |
|
||||
[OK] result:
|
||||
{{ rr | to_yaml }}
|
||||
fail_msg: |
|
||||
[ERR] result:
|
||||
{{ rr | to_yaml }}
|
||||
quiet: "{{ quiet_test | d(true) | bool }}"
|
||||
loop: "{{ targets2 }}"
|
||||
loop_control:
|
||||
label: "{{ item.mp }}: {{ item.tt }}"
|
||||
vars:
|
||||
rr: "{{ list2 | community.general.keep_keys(target=item.tt, matching_parameter=item.mp) }}"
|
||||
tags: targets2
|
||||
@@ -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
|
||||
|
||||
- name: Test keep_keys
|
||||
import_tasks: keep_keys.yml
|
||||
33
tests/integration/targets/filter_keep_keys/vars/main.yml
Normal file
33
tests/integration/targets/filter_keep_keys/vars/main.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
targets1:
|
||||
- {mp: equal, tt: [k0_x0, k1_x1]}
|
||||
- {mp: starts_with, tt: [k0, k1]}
|
||||
- {mp: ends_with, tt: [x0, x1]}
|
||||
- {mp: regex, tt: ['^.*[01]_x.*$']}
|
||||
- {mp: regex, tt: '^.*[01]_x.*$'}
|
||||
|
||||
list1:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
result1:
|
||||
- {k0_x0: A0, k1_x1: B0}
|
||||
- {k0_x0: A1, k1_x1: B1}
|
||||
|
||||
targets2:
|
||||
- {mp: equal, tt: k0_x0}
|
||||
- {mp: starts_with, tt: k0}
|
||||
- {mp: ends_with, tt: x0}
|
||||
- {mp: regex, tt: '^.*0_x.*$'}
|
||||
|
||||
list2:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
result2:
|
||||
- {k0_x0: A0}
|
||||
- {k0_x0: A1}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user