mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 18:36:28 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
205e28d2fe | ||
|
|
27629b6497 | ||
|
|
5735c5a045 | ||
|
|
ceb051851e | ||
|
|
a17083ea84 | ||
|
|
d03fdc8093 | ||
|
|
469209a17f | ||
|
|
72ea96cc74 | ||
|
|
b2c34d1afe | ||
|
|
f4fca86f82 | ||
|
|
78c8fa0d49 | ||
|
|
d3650f27b0 | ||
|
|
7b901f9caa | ||
|
|
35d6ab10bb | ||
|
|
d811807e1f | ||
|
|
dedd625700 | ||
|
|
10e41862cb | ||
|
|
3d418d9ede | ||
|
|
ebb150c3f9 | ||
|
|
df28c80946 | ||
|
|
403152d91a | ||
|
|
75e35bfa6c | ||
|
|
fa846e9677 | ||
|
|
db62a36d6e | ||
|
|
e3dae0b646 | ||
|
|
9aaf8e4825 | ||
|
|
cf0a233d7b | ||
|
|
cebd5bb3c8 | ||
|
|
d452e903f8 | ||
|
|
c76ef6ba99 | ||
|
|
52bd7cdb2d | ||
|
|
da3ba1e7be | ||
|
|
cb46453b78 | ||
|
|
a784e66a2c | ||
|
|
52cc1881d8 | ||
|
|
5c7076e0bc |
@@ -43,8 +43,6 @@ variables:
|
||||
value: ansible_collections/community/general
|
||||
- name: coverageBranches
|
||||
value: main
|
||||
- name: pipelinesCoverage
|
||||
value: coverage
|
||||
- name: entryPoint
|
||||
value: tests/utils/shippable/shippable.sh
|
||||
- name: fetchDepth
|
||||
|
||||
@@ -28,16 +28,6 @@ jobs:
|
||||
- bash: .azure-pipelines/scripts/report-coverage.sh
|
||||
displayName: Generate Coverage Report
|
||||
condition: gt(variables.coverageFileCount, 0)
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
# Azure Pipelines only accepts a single coverage data file.
|
||||
# That means only Python or PowerShell coverage can be uploaded, but not both.
|
||||
# Set the "pipelinesCoverage" variable to determine which type is uploaded.
|
||||
# Use "coverage" for Python and "coverage-powershell" for PowerShell.
|
||||
summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml"
|
||||
displayName: Publish to Azure Pipelines
|
||||
condition: gt(variables.coverageFileCount, 0)
|
||||
- bash: .azure-pipelines/scripts/publish-codecov.py "$(outputPath)"
|
||||
displayName: Publish to codecov.io
|
||||
condition: gt(variables.coverageFileCount, 0)
|
||||
|
||||
4
.github/BOTMETA.yml
vendored
4
.github/BOTMETA.yml
vendored
@@ -1364,6 +1364,8 @@ files:
|
||||
maintainers: konstruktoid
|
||||
$modules/systemd_creds_encrypt.py:
|
||||
maintainers: konstruktoid
|
||||
$modules/systemd_info.py:
|
||||
maintainers: NomakCooper
|
||||
$modules/sysupgrade.py:
|
||||
maintainers: precurse
|
||||
$modules/taiga_issue.py:
|
||||
@@ -1543,6 +1545,8 @@ files:
|
||||
maintainers: baldwinSPC nurfet-becirevic t0mk teebes
|
||||
docs/docsite/rst/guide_scaleway.rst:
|
||||
maintainers: $team_scaleway
|
||||
docs/docsite/rst/guide_uthelper.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/guide_vardict.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/test_guide.rst:
|
||||
|
||||
162
CHANGELOG.md
162
CHANGELOG.md
@@ -2,71 +2,135 @@
|
||||
|
||||
**Topics**
|
||||
|
||||
- <a href="#v10-3-1">v10\.3\.1</a>
|
||||
- <a href="#v10-4-0">v10\.4\.0</a>
|
||||
- <a href="#release-summary">Release Summary</a>
|
||||
- <a href="#minor-changes">Minor Changes</a>
|
||||
- <a href="#deprecated-features">Deprecated Features</a>
|
||||
- <a href="#bugfixes">Bugfixes</a>
|
||||
- <a href="#v10-3-0">v10\.3\.0</a>
|
||||
- <a href="#new-modules">New Modules</a>
|
||||
- <a href="#v10-3-1">v10\.3\.1</a>
|
||||
- <a href="#release-summary-1">Release Summary</a>
|
||||
- <a href="#minor-changes-1">Minor Changes</a>
|
||||
- <a href="#deprecated-features">Deprecated Features</a>
|
||||
- <a href="#security-fixes">Security Fixes</a>
|
||||
- <a href="#bugfixes-1">Bugfixes</a>
|
||||
- <a href="#v10-3-0">v10\.3\.0</a>
|
||||
- <a href="#release-summary-2">Release Summary</a>
|
||||
- <a href="#minor-changes-2">Minor Changes</a>
|
||||
- <a href="#deprecated-features-1">Deprecated Features</a>
|
||||
- <a href="#security-fixes">Security Fixes</a>
|
||||
- <a href="#bugfixes-2">Bugfixes</a>
|
||||
- <a href="#new-plugins">New Plugins</a>
|
||||
- <a href="#connection">Connection</a>
|
||||
- <a href="#filter">Filter</a>
|
||||
- <a href="#lookup">Lookup</a>
|
||||
- <a href="#new-modules">New Modules</a>
|
||||
- <a href="#v10-2-0">v10\.2\.0</a>
|
||||
- <a href="#release-summary-2">Release Summary</a>
|
||||
- <a href="#minor-changes-2">Minor Changes</a>
|
||||
- <a href="#deprecated-features-1">Deprecated Features</a>
|
||||
- <a href="#security-fixes-1">Security Fixes</a>
|
||||
- <a href="#bugfixes-2">Bugfixes</a>
|
||||
- <a href="#new-plugins-1">New Plugins</a>
|
||||
- <a href="#inventory">Inventory</a>
|
||||
- <a href="#new-modules-1">New Modules</a>
|
||||
- <a href="#v10-1-0">v10\.1\.0</a>
|
||||
- <a href="#v10-2-0">v10\.2\.0</a>
|
||||
- <a href="#release-summary-3">Release Summary</a>
|
||||
- <a href="#minor-changes-3">Minor Changes</a>
|
||||
- <a href="#deprecated-features-2">Deprecated Features</a>
|
||||
- <a href="#security-fixes-1">Security Fixes</a>
|
||||
- <a href="#bugfixes-3">Bugfixes</a>
|
||||
- <a href="#new-plugins-1">New Plugins</a>
|
||||
- <a href="#inventory">Inventory</a>
|
||||
- <a href="#new-modules-2">New Modules</a>
|
||||
- <a href="#v10-1-0">v10\.1\.0</a>
|
||||
- <a href="#release-summary-4">Release Summary</a>
|
||||
- <a href="#minor-changes-4">Minor Changes</a>
|
||||
- <a href="#deprecated-features-3">Deprecated Features</a>
|
||||
- <a href="#bugfixes-4">Bugfixes</a>
|
||||
- <a href="#new-plugins-2">New Plugins</a>
|
||||
- <a href="#filter-1">Filter</a>
|
||||
- <a href="#new-modules-2">New Modules</a>
|
||||
- <a href="#new-modules-3">New Modules</a>
|
||||
- <a href="#v10-0-1">v10\.0\.1</a>
|
||||
- <a href="#release-summary-4">Release Summary</a>
|
||||
- <a href="#bugfixes-4">Bugfixes</a>
|
||||
- <a href="#v10-0-0">v10\.0\.0</a>
|
||||
- <a href="#release-summary-5">Release Summary</a>
|
||||
- <a href="#minor-changes-4">Minor Changes</a>
|
||||
- <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
|
||||
- <a href="#deprecated-features-3">Deprecated Features</a>
|
||||
- <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a>
|
||||
- <a href="#bugfixes-5">Bugfixes</a>
|
||||
- <a href="#v10-0-0">v10\.0\.0</a>
|
||||
- <a href="#release-summary-6">Release Summary</a>
|
||||
- <a href="#minor-changes-5">Minor Changes</a>
|
||||
- <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
|
||||
- <a href="#deprecated-features-4">Deprecated Features</a>
|
||||
- <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a>
|
||||
- <a href="#bugfixes-6">Bugfixes</a>
|
||||
- <a href="#known-issues">Known Issues</a>
|
||||
- <a href="#new-plugins-3">New Plugins</a>
|
||||
- <a href="#filter-2">Filter</a>
|
||||
- <a href="#test">Test</a>
|
||||
- <a href="#new-modules-3">New Modules</a>
|
||||
- <a href="#new-modules-4">New Modules</a>
|
||||
This changelog describes changes after version 9\.0\.0\.
|
||||
|
||||
<a id="v10-3-1"></a>
|
||||
## v10\.3\.1
|
||||
<a id="v10-4-0"></a>
|
||||
## v10\.4\.0
|
||||
|
||||
<a id="release-summary"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix release\.
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes"></a>
|
||||
### Minor Changes
|
||||
|
||||
* onepassword\_ssh\_key \- refactor to move code to lookup class \([https\://github\.com/ansible\-collections/community\.general/pull/9633](https\://github\.com/ansible\-collections/community\.general/pull/9633)\)\.
|
||||
* bitwarden lookup plugin \- add new option <code>collection\_name</code> to filter results by collection name\, and new option <code>result\_count</code> to validate number of results \([https\://github\.com/ansible\-collections/community\.general/pull/9728](https\://github\.com/ansible\-collections/community\.general/pull/9728)\)\.
|
||||
* incus connection plugin \- adds <code>remote\_user</code> and <code>incus\_become\_method</code> parameters for allowing a non\-root user to connect to an Incus instance \([https\://github\.com/ansible\-collections/community\.general/pull/9743](https\://github\.com/ansible\-collections/community\.general/pull/9743)\)\.
|
||||
* iocage inventory plugin \- the new parameter <code>hooks\_results</code> of the plugin is a list of files inside a jail that provide configuration parameters for the inventory\. The inventory plugin reads the files from the jails and put the contents into the items of created variable <code>iocage\_hooks</code> \([https\://github\.com/ansible\-collections/community\.general/issues/9650](https\://github\.com/ansible\-collections/community\.general/issues/9650)\, [https\://github\.com/ansible\-collections/community\.general/pull/9651](https\://github\.com/ansible\-collections/community\.general/pull/9651)\)\.
|
||||
* jira \- adds <code>client\_cert</code> and <code>client\_key</code> parameters for supporting client certificate authentification when connecting to Jira \([https\://github\.com/ansible\-collections/community\.general/pull/9753](https\://github\.com/ansible\-collections/community\.general/pull/9753)\)\.
|
||||
* lldp \- adds <code>multivalues</code> parameter to control behavior when lldpctl outputs an attribute multiple times \([https\://github\.com/ansible\-collections/community\.general/pull/9657](https\://github\.com/ansible\-collections/community\.general/pull/9657)\)\.
|
||||
* lvg \- add <code>remove\_extra\_pvs</code> parameter to control if ansible should remove physical volumes which are not in the <code>pvs</code> parameter \([https\://github\.com/ansible\-collections/community\.general/pull/9698](https\://github\.com/ansible\-collections/community\.general/pull/9698)\)\.
|
||||
* lxd connection plugin \- adds <code>remote\_user</code> and <code>lxd\_become\_method</code> parameters for allowing a non\-root user to connect to an LXD instance \([https\://github\.com/ansible\-collections/community\.general/pull/9659](https\://github\.com/ansible\-collections/community\.general/pull/9659)\)\.
|
||||
* nmcli \- adds VRF support with new <code>type</code> value <code>vrf</code> and new <code>slave\_type</code> value <code>vrf</code> as well as new <code>table</code> parameter \([https\://github\.com/ansible\-collections/community\.general/pull/9658](https\://github\.com/ansible\-collections/community\.general/pull/9658)\, [https\://github\.com/ansible\-collections/community\.general/issues/8014](https\://github\.com/ansible\-collections/community\.general/issues/8014)\)\.
|
||||
* proxmox\_kvm \- allow hibernation and suspending of VMs \([https\://github\.com/ansible\-collections/community\.general/issues/9620](https\://github\.com/ansible\-collections/community\.general/issues/9620)\, [https\://github\.com/ansible\-collections/community\.general/pull/9653](https\://github\.com/ansible\-collections/community\.general/pull/9653)\)\.
|
||||
* redfish\_command \- add <code>PowerFullPowerCycle</code> to power command options \([https\://github\.com/ansible\-collections/community\.general/pull/9729](https\://github\.com/ansible\-collections/community\.general/pull/9729)\)\.
|
||||
* ssh\_config \- add <code>other\_options</code> option \([https\://github\.com/ansible\-collections/community\.general/issues/8053](https\://github\.com/ansible\-collections/community\.general/issues/8053)\, [https\://github\.com/ansible\-collections/community\.general/pull/9684](https\://github\.com/ansible\-collections/community\.general/pull/9684)\)\.
|
||||
* xen\_orchestra inventory plugin \- add <code>use\_vm\_uuid</code> and <code>use\_host\_uuid</code> boolean options to allow switching over to using VM/Xen name labels instead of UUIDs as item names \([https\://github\.com/ansible\-collections/community\.general/pull/9787](https\://github\.com/ansible\-collections/community\.general/pull/9787)\)\.
|
||||
|
||||
<a id="deprecated-features"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* profitbricks \- module is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9733](https\://github\.com/ansible\-collections/community\.general/pull/9733)\)\.
|
||||
* profitbricks\_datacenter \- module is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9733](https\://github\.com/ansible\-collections/community\.general/pull/9733)\)\.
|
||||
* profitbricks\_nic \- module is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9733](https\://github\.com/ansible\-collections/community\.general/pull/9733)\)\.
|
||||
* profitbricks\_volume \- module is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9733](https\://github\.com/ansible\-collections/community\.general/pull/9733)\)\.
|
||||
* profitbricks\_volume\_attachments \- module is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9733](https\://github\.com/ansible\-collections/community\.general/pull/9733)\)\.
|
||||
|
||||
<a id="bugfixes"></a>
|
||||
### Bugfixes
|
||||
|
||||
* apache2\_mod\_proxy \- make compatible with Python 3 \([https\://github\.com/ansible\-collections/community\.general/pull/9762](https\://github\.com/ansible\-collections/community\.general/pull/9762)\)\.
|
||||
* apache2\_mod\_proxy \- passing the cluster\'s page as referer for the member\'s pages\. This makes the module actually work again for halfway modern Apache versions\. According to some comments founds on the net the referer was required since at least 2019 for some versions of Apache 2 \([https\://github\.com/ansible\-collections/community\.general/pull/9762](https\://github\.com/ansible\-collections/community\.general/pull/9762)\)\.
|
||||
* elasticsearch\_plugin \- fix <code>ERROR\: D is not a recognized option</code> issue when configuring proxy settings \([https\://github\.com/ansible\-collections/community\.general/pull/9774](https\://github\.com/ansible\-collections/community\.general/pull/9774)\, [https\://github\.com/ansible\-collections/community\.general/issues/9773](https\://github\.com/ansible\-collections/community\.general/issues/9773)\)\.
|
||||
* ipa\_host \- module revoked existing host certificates even if <code>user\_certificate</code> was not given \([https\://github\.com/ansible\-collections/community\.general/pull/9694](https\://github\.com/ansible\-collections/community\.general/pull/9694)\)\.
|
||||
* keycloak\_client \- in check mode\, detect whether the lists in before client \(for example redirect URI list\) contain items that the lists in the desired client do not contain \([https\://github\.com/ansible\-collections/community\.general/pull/9739](https\://github\.com/ansible\-collections/community\.general/pull/9739)\)\.
|
||||
* lldp \- fix crash caused by certain lldpctl output where an attribute is defined as branch and leaf \([https\://github\.com/ansible\-collections/community\.general/pull/9657](https\://github\.com/ansible\-collections/community\.general/pull/9657)\)\.
|
||||
* onepassword\_doc lookup plugin \- ensure that 1Password Connect support also works for this plugin \([https\://github\.com/ansible\-collections/community\.general/pull/9625](https\://github\.com/ansible\-collections/community\.general/pull/9625)\)\.
|
||||
* passwordstore lookup plugin \- fix subkey creation even when <code>create\=false</code> \([https\://github\.com/ansible\-collections/community\.general/issues/9105](https\://github\.com/ansible\-collections/community\.general/issues/9105)\, [https\://github\.com/ansible\-collections/community\.general/pull/9106](https\://github\.com/ansible\-collections/community\.general/pull/9106)\)\.
|
||||
* proxmox inventory plugin \- plugin did not update cache correctly after <code>meta\: refresh\_inventory</code> \([https\://github\.com/ansible\-collections/community\.general/issues/9710](https\://github\.com/ansible\-collections/community\.general/issues/9710)\, [https\://github\.com/ansible\-collections/community\.general/pull/9760](https\://github\.com/ansible\-collections/community\.general/pull/9760)\)\.
|
||||
* redhat\_subscription \- use the \"enable\_content\" option \(when available\) when
|
||||
registering using D\-Bus\, to ensure that subscription\-manager enables the
|
||||
content on registration\; this is particular important on EL 10\+ and Fedora
|
||||
41\+
|
||||
\([https\://github\.com/ansible\-collections/community\.general/pull/9778](https\://github\.com/ansible\-collections/community\.general/pull/9778)\)\.
|
||||
* zfs \- fix handling of multi\-line values of user\-defined ZFS properties \([https\://github\.com/ansible\-collections/community\.general/pull/6264](https\://github\.com/ansible\-collections/community\.general/pull/6264)\)\.
|
||||
* zfs\_facts \- parameter <code>type</code> now accepts multple values as documented \([https\://github\.com/ansible\-collections/community\.general/issues/5909](https\://github\.com/ansible\-collections/community\.general/issues/5909)\, [https\://github\.com/ansible\-collections/community\.general/pull/9697](https\://github\.com/ansible\-collections/community\.general/pull/9697)\)\.
|
||||
|
||||
<a id="new-modules"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.systemd\_info \- Gather C\(systemd\) unit info\.
|
||||
|
||||
<a id="v10-3-1"></a>
|
||||
## v10\.3\.1
|
||||
|
||||
<a id="release-summary-1"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix release\.
|
||||
|
||||
<a id="minor-changes-1"></a>
|
||||
### Minor Changes
|
||||
|
||||
* onepassword\_ssh\_key \- refactor to move code to lookup class \([https\://github\.com/ansible\-collections/community\.general/pull/9633](https\://github\.com/ansible\-collections/community\.general/pull/9633)\)\.
|
||||
|
||||
<a id="bugfixes-1"></a>
|
||||
### Bugfixes
|
||||
|
||||
* cloudflare\_dns \- fix crash when deleting a DNS record or when updating a record with <code>solo\=true</code> \([https\://github\.com/ansible\-collections/community\.general/issues/9652](https\://github\.com/ansible\-collections/community\.general/issues/9652)\, [https\://github\.com/ansible\-collections/community\.general/pull/9649](https\://github\.com/ansible\-collections/community\.general/pull/9649)\)\.
|
||||
* homebrew \- make package name parsing more resilient \([https\://github\.com/ansible\-collections/community\.general/pull/9665](https\://github\.com/ansible\-collections/community\.general/pull/9665)\, [https\://github\.com/ansible\-collections/community\.general/issues/9641](https\://github\.com/ansible\-collections/community\.general/issues/9641)\)\.
|
||||
* keycloak module utils \- replaces missing return in get\_role\_composites method which caused it to return None instead of composite roles \([https\://github\.com/ansible\-collections/community\.general/issues/9678](https\://github\.com/ansible\-collections/community\.general/issues/9678)\, [https\://github\.com/ansible\-collections/community\.general/pull/9691](https\://github\.com/ansible\-collections/community\.general/pull/9691)\)\.
|
||||
@@ -78,12 +142,12 @@ Bugfix release\.
|
||||
<a id="v10-3-0"></a>
|
||||
## v10\.3\.0
|
||||
|
||||
<a id="release-summary-1"></a>
|
||||
<a id="release-summary-2"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-1"></a>
|
||||
<a id="minor-changes-2"></a>
|
||||
### Minor Changes
|
||||
|
||||
* MH module utils \- delegate <code>debug</code> to the underlying <code>AnsibleModule</code> instance or issues a warning if an attribute already exists with that name \([https\://github\.com/ansible\-collections/community\.general/pull/9577](https\://github\.com/ansible\-collections/community\.general/pull/9577)\)\.
|
||||
@@ -206,7 +270,7 @@ Regular bugfix and feature release\.
|
||||
* yaml callback plugin \- adjust standard preamble for Python 3 \([https\://github\.com/ansible\-collections/community\.general/pull/9583](https\://github\.com/ansible\-collections/community\.general/pull/9583)\)\.
|
||||
* zone connection plugin \- adjust standard preamble for Python 3 \([https\://github\.com/ansible\-collections/community\.general/pull/9584](https\://github\.com/ansible\-collections/community\.general/pull/9584)\)\.
|
||||
|
||||
<a id="deprecated-features"></a>
|
||||
<a id="deprecated-features-1"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* MH module utils \- attribute <code>debug</code> definition in subclasses of MH is now deprecated\, as that name will become a delegation to <code>AnsibleModule</code> in community\.general 12\.0\.0\, and any such attribute will be overridden by that delegation in that version \([https\://github\.com/ansible\-collections/community\.general/pull/9577](https\://github\.com/ansible\-collections/community\.general/pull/9577)\)\.
|
||||
@@ -217,7 +281,7 @@ Regular bugfix and feature release\.
|
||||
|
||||
* keycloak\_client \- Sanitize <code>saml\.encryption\.private\.key</code> so it does not show in the logs \([https\://github\.com/ansible\-collections/community\.general/pull/9621](https\://github\.com/ansible\-collections/community\.general/pull/9621)\)\.
|
||||
|
||||
<a id="bugfixes-1"></a>
|
||||
<a id="bugfixes-2"></a>
|
||||
### Bugfixes
|
||||
|
||||
* homebrew \- fix incorrect handling of homebrew modules when a tap is requested \([https\://github\.com/ansible\-collections/community\.general/pull/9546](https\://github\.com/ansible\-collections/community\.general/pull/9546)\, [https\://github\.com/ansible\-collections/community\.general/issues/9533](https\://github\.com/ansible\-collections/community\.general/issues/9533)\)\.
|
||||
@@ -253,7 +317,7 @@ Regular bugfix and feature release\.
|
||||
|
||||
* community\.general\.onepassword\_ssh\_key \- Fetch SSH keys stored in 1Password\.
|
||||
|
||||
<a id="new-modules"></a>
|
||||
<a id="new-modules-1"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.proxmox\_backup\_info \- Retrieve information on Proxmox scheduled backups\.
|
||||
@@ -261,12 +325,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v10-2-0"></a>
|
||||
## v10\.2\.0
|
||||
|
||||
<a id="release-summary-2"></a>
|
||||
<a id="release-summary-3"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-2"></a>
|
||||
<a id="minor-changes-3"></a>
|
||||
### Minor Changes
|
||||
|
||||
* bitwarden lookup plugin \- use f\-strings instead of interpolations or <code>format</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9324](https\://github\.com/ansible\-collections/community\.general/pull/9324)\)\.
|
||||
@@ -397,7 +461,7 @@ Regular bugfix and feature release\.
|
||||
* zypper \- add <code>quiet</code> option \([https\://github\.com/ansible\-collections/community\.general/pull/9270](https\://github\.com/ansible\-collections/community\.general/pull/9270)\)\.
|
||||
* zypper \- add <code>simple\_errors</code> option \([https\://github\.com/ansible\-collections/community\.general/pull/9270](https\://github\.com/ansible\-collections/community\.general/pull/9270)\)\.
|
||||
|
||||
<a id="deprecated-features-1"></a>
|
||||
<a id="deprecated-features-2"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* atomic\_container \- module is deprecated and will be removed in community\.general 13\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9487](https\://github\.com/ansible\-collections/community\.general/pull/9487)\)\.
|
||||
@@ -420,7 +484,7 @@ Regular bugfix and feature release\.
|
||||
|
||||
* keycloak\_authentication \- API calls did not properly set the <code>priority</code> during update resulting in incorrectly sorted authentication flows\. This apparently only affects Keycloak 25 or newer \([https\://github\.com/ansible\-collections/community\.general/pull/9263](https\://github\.com/ansible\-collections/community\.general/pull/9263)\)\.
|
||||
|
||||
<a id="bugfixes-2"></a>
|
||||
<a id="bugfixes-3"></a>
|
||||
### Bugfixes
|
||||
|
||||
* dig lookup plugin \- correctly handle <code>NoNameserver</code> exception \([https\://github\.com/ansible\-collections/community\.general/pull/9363](https\://github\.com/ansible\-collections/community\.general/pull/9363)\, [https\://github\.com/ansible\-collections/community\.general/issues/9362](https\://github\.com/ansible\-collections/community\.general/issues/9362)\)\.
|
||||
@@ -440,7 +504,7 @@ Regular bugfix and feature release\.
|
||||
|
||||
* community\.general\.iocage \- iocage inventory source\.
|
||||
|
||||
<a id="new-modules-1"></a>
|
||||
<a id="new-modules-2"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.android\_sdk \- Manages Android SDK packages\.
|
||||
@@ -451,12 +515,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v10-1-0"></a>
|
||||
## v10\.1\.0
|
||||
|
||||
<a id="release-summary-3"></a>
|
||||
<a id="release-summary-4"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-3"></a>
|
||||
<a id="minor-changes-4"></a>
|
||||
### Minor Changes
|
||||
|
||||
* alternatives \- add <code>family</code> parameter that allows to utilize the <code>\-\-family</code> option available in RedHat version of update\-alternatives \([https\://github\.com/ansible\-collections/community\.general/issues/5060](https\://github\.com/ansible\-collections/community\.general/issues/5060)\, [https\://github\.com/ansible\-collections/community\.general/pull/9096](https\://github\.com/ansible\-collections/community\.general/pull/9096)\)\.
|
||||
@@ -477,13 +541,13 @@ Regular bugfix and feature release\.
|
||||
* scaleway\_lb \- minor simplification in the code \([https\://github\.com/ansible\-collections/community\.general/pull/9189](https\://github\.com/ansible\-collections/community\.general/pull/9189)\)\.
|
||||
* ssh\_config \- add <code>dynamicforward</code> option \([https\://github\.com/ansible\-collections/community\.general/pull/9192](https\://github\.com/ansible\-collections/community\.general/pull/9192)\)\.
|
||||
|
||||
<a id="deprecated-features-2"></a>
|
||||
<a id="deprecated-features-3"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* opkg \- deprecate value <code>\"\"</code> for parameter <code>force</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9172](https\://github\.com/ansible\-collections/community\.general/pull/9172)\)\.
|
||||
* redfish\_utils module utils \- deprecate method <code>RedfishUtils\.\_init\_session\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9190](https\://github\.com/ansible\-collections/community\.general/pull/9190)\)\.
|
||||
|
||||
<a id="bugfixes-3"></a>
|
||||
<a id="bugfixes-4"></a>
|
||||
### Bugfixes
|
||||
|
||||
* dnf\_config\_manager \- fix hanging when prompting to import GPG keys \([https\://github\.com/ansible\-collections/community\.general/pull/9124](https\://github\.com/ansible\-collections/community\.general/pull/9124)\, [https\://github\.com/ansible\-collections/community\.general/issues/8830](https\://github\.com/ansible\-collections/community\.general/issues/8830)\)\.
|
||||
@@ -503,7 +567,7 @@ Regular bugfix and feature release\.
|
||||
|
||||
* community\.general\.accumulate \- Produce a list of accumulated sums of the input list contents\.
|
||||
|
||||
<a id="new-modules-2"></a>
|
||||
<a id="new-modules-3"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.decompress \- Decompresses compressed files\.
|
||||
@@ -512,12 +576,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v10-0-1"></a>
|
||||
## v10\.0\.1
|
||||
|
||||
<a id="release-summary-4"></a>
|
||||
<a id="release-summary-5"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix release for inclusion in Ansible 11\.0\.0rc1\.
|
||||
|
||||
<a id="bugfixes-4"></a>
|
||||
<a id="bugfixes-5"></a>
|
||||
### Bugfixes
|
||||
|
||||
* keycloak\_client \- fix diff by removing code that turns the attributes dict which contains additional settings into a list \([https\://github\.com/ansible\-collections/community\.general/pull/9077](https\://github\.com/ansible\-collections/community\.general/pull/9077)\)\.
|
||||
@@ -527,12 +591,12 @@ Bugfix release for inclusion in Ansible 11\.0\.0rc1\.
|
||||
<a id="v10-0-0"></a>
|
||||
## v10\.0\.0
|
||||
|
||||
<a id="release-summary-5"></a>
|
||||
<a id="release-summary-6"></a>
|
||||
### Release Summary
|
||||
|
||||
This is release 10\.0\.0 of <code>community\.general</code>\, released on 2024\-11\-04\.
|
||||
|
||||
<a id="minor-changes-4"></a>
|
||||
<a id="minor-changes-5"></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)\)\.
|
||||
@@ -737,7 +801,7 @@ This is release 10\.0\.0 of <code>community\.general</code>\, released on 2024\-
|
||||
* irc \- the defaults of <code>use\_tls</code> and <code>validate\_certs</code> changed from <code>false</code> to <code>true</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8918](https\://github\.com/ansible\-collections/community\.general/pull/8918)\)\.
|
||||
* rhsm\_repository \- the states <code>present</code> and <code>absent</code> have been removed\. Use <code>enabled</code> and <code>disabled</code> instead \([https\://github\.com/ansible\-collections/community\.general/pull/8918](https\://github\.com/ansible\-collections/community\.general/pull/8918)\)\.
|
||||
|
||||
<a id="deprecated-features-3"></a>
|
||||
<a id="deprecated-features-4"></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)\)\.
|
||||
@@ -762,7 +826,7 @@ This is release 10\.0\.0 of <code>community\.general</code>\, released on 2024\-
|
||||
* proxmox\_kvm \- removed the <code>proxmox\_default\_behavior</code> option\. Explicitly specify the old default values if you were using <code>proxmox\_default\_behavior\=compatibility</code>\, otherwise simply remove it \([https\://github\.com/ansible\-collections/community\.general/pull/8918](https\://github\.com/ansible\-collections/community\.general/pull/8918)\)\.
|
||||
* redhat\_subscriptions \- removed the <code>pool</code> option\. Use <code>pool\_ids</code> instead \([https\://github\.com/ansible\-collections/community\.general/pull/8918](https\://github\.com/ansible\-collections/community\.general/pull/8918)\)\.
|
||||
|
||||
<a id="bugfixes-5"></a>
|
||||
<a id="bugfixes-6"></a>
|
||||
### Bugfixes
|
||||
|
||||
* bitwarden lookup plugin \- fix <code>KeyError</code> in <code>search\_field</code> \([https\://github\.com/ansible\-collections/community\.general/issues/8549](https\://github\.com/ansible\-collections/community\.general/issues/8549)\, [https\://github\.com/ansible\-collections/community\.general/pull/8557](https\://github\.com/ansible\-collections/community\.general/pull/8557)\)\.
|
||||
@@ -863,7 +927,7 @@ This is release 10\.0\.0 of <code>community\.general</code>\, released on 2024\-
|
||||
|
||||
* community\.general\.ansible\_type \- Validate input type\.
|
||||
|
||||
<a id="new-modules-3"></a>
|
||||
<a id="new-modules-4"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.bootc\_manage \- Bootc Switch and Upgrade\.
|
||||
|
||||
@@ -6,6 +6,64 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 9.0.0.
|
||||
|
||||
v10.4.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- bitwarden lookup plugin - add new option ``collection_name`` to filter results by collection name, and new option ``result_count`` to validate number of results (https://github.com/ansible-collections/community.general/pull/9728).
|
||||
- incus connection plugin - adds ``remote_user`` and ``incus_become_method`` parameters for allowing a non-root user to connect to an Incus instance (https://github.com/ansible-collections/community.general/pull/9743).
|
||||
- iocage inventory plugin - the new parameter ``hooks_results`` of the plugin is a list of files inside a jail that provide configuration parameters for the inventory. The inventory plugin reads the files from the jails and put the contents into the items of created variable ``iocage_hooks`` (https://github.com/ansible-collections/community.general/issues/9650, https://github.com/ansible-collections/community.general/pull/9651).
|
||||
- jira - adds ``client_cert`` and ``client_key`` parameters for supporting client certificate authentification when connecting to Jira (https://github.com/ansible-collections/community.general/pull/9753).
|
||||
- lldp - adds ``multivalues`` parameter to control behavior when lldpctl outputs an attribute multiple times (https://github.com/ansible-collections/community.general/pull/9657).
|
||||
- lvg - add ``remove_extra_pvs`` parameter to control if ansible should remove physical volumes which are not in the ``pvs`` parameter (https://github.com/ansible-collections/community.general/pull/9698).
|
||||
- lxd connection plugin - adds ``remote_user`` and ``lxd_become_method`` parameters for allowing a non-root user to connect to an LXD instance (https://github.com/ansible-collections/community.general/pull/9659).
|
||||
- nmcli - adds VRF support with new ``type`` value ``vrf`` and new ``slave_type`` value ``vrf`` as well as new ``table`` parameter (https://github.com/ansible-collections/community.general/pull/9658, https://github.com/ansible-collections/community.general/issues/8014).
|
||||
- proxmox_kvm - allow hibernation and suspending of VMs (https://github.com/ansible-collections/community.general/issues/9620, https://github.com/ansible-collections/community.general/pull/9653).
|
||||
- redfish_command - add ``PowerFullPowerCycle`` to power command options (https://github.com/ansible-collections/community.general/pull/9729).
|
||||
- ssh_config - add ``other_options`` option (https://github.com/ansible-collections/community.general/issues/8053, https://github.com/ansible-collections/community.general/pull/9684).
|
||||
- xen_orchestra inventory plugin - add ``use_vm_uuid`` and ``use_host_uuid`` boolean options to allow switching over to using VM/Xen name labels instead of UUIDs as item names (https://github.com/ansible-collections/community.general/pull/9787).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- profitbricks - module is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_datacenter - module is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_nic - module is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_volume - module is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_volume_attachments - module is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- apache2_mod_proxy - make compatible with Python 3 (https://github.com/ansible-collections/community.general/pull/9762).
|
||||
- apache2_mod_proxy - passing the cluster's page as referer for the member's pages. This makes the module actually work again for halfway modern Apache versions. According to some comments founds on the net the referer was required since at least 2019 for some versions of Apache 2 (https://github.com/ansible-collections/community.general/pull/9762).
|
||||
- elasticsearch_plugin - fix ``ERROR: D is not a recognized option`` issue when configuring proxy settings (https://github.com/ansible-collections/community.general/pull/9774, https://github.com/ansible-collections/community.general/issues/9773).
|
||||
- ipa_host - module revoked existing host certificates even if ``user_certificate`` was not given (https://github.com/ansible-collections/community.general/pull/9694).
|
||||
- keycloak_client - in check mode, detect whether the lists in before client (for example redirect URI list) contain items that the lists in the desired client do not contain (https://github.com/ansible-collections/community.general/pull/9739).
|
||||
- lldp - fix crash caused by certain lldpctl output where an attribute is defined as branch and leaf (https://github.com/ansible-collections/community.general/pull/9657).
|
||||
- onepassword_doc lookup plugin - ensure that 1Password Connect support also works for this plugin (https://github.com/ansible-collections/community.general/pull/9625).
|
||||
- passwordstore lookup plugin - fix subkey creation even when ``create=false`` (https://github.com/ansible-collections/community.general/issues/9105, https://github.com/ansible-collections/community.general/pull/9106).
|
||||
- proxmox inventory plugin - plugin did not update cache correctly after ``meta: refresh_inventory`` (https://github.com/ansible-collections/community.general/issues/9710, https://github.com/ansible-collections/community.general/pull/9760).
|
||||
- redhat_subscription - use the "enable_content" option (when available) when
|
||||
registering using D-Bus, to ensure that subscription-manager enables the
|
||||
content on registration; this is particular important on EL 10+ and Fedora
|
||||
41+
|
||||
(https://github.com/ansible-collections/community.general/pull/9778).
|
||||
- zfs - fix handling of multi-line values of user-defined ZFS properties (https://github.com/ansible-collections/community.general/pull/6264).
|
||||
- zfs_facts - parameter ``type`` now accepts multple values as documented (https://github.com/ansible-collections/community.general/issues/5909, https://github.com/ansible-collections/community.general/pull/9697).
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- community.general.systemd_info - Gather C(systemd) unit info.
|
||||
|
||||
v10.3.1
|
||||
=======
|
||||
|
||||
|
||||
@@ -1468,3 +1468,116 @@ releases:
|
||||
- 9691-keycloak-module-utils-replace-missing-return-in-get_role_composites.yml
|
||||
- 9695-xml-close-file.yml
|
||||
release_date: '2025-02-10'
|
||||
10.4.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- apache2_mod_proxy - make compatible with Python 3 (https://github.com/ansible-collections/community.general/pull/9762).
|
||||
- apache2_mod_proxy - passing the cluster's page as referer for the member's
|
||||
pages. This makes the module actually work again for halfway modern Apache
|
||||
versions. According to some comments founds on the net the referer was required
|
||||
since at least 2019 for some versions of Apache 2 (https://github.com/ansible-collections/community.general/pull/9762).
|
||||
- 'elasticsearch_plugin - fix ``ERROR: D is not a recognized option`` issue
|
||||
when configuring proxy settings (https://github.com/ansible-collections/community.general/pull/9774,
|
||||
https://github.com/ansible-collections/community.general/issues/9773).'
|
||||
- ipa_host - module revoked existing host certificates even if ``user_certificate``
|
||||
was not given (https://github.com/ansible-collections/community.general/pull/9694).
|
||||
- keycloak_client - in check mode, detect whether the lists in before client
|
||||
(for example redirect URI list) contain items that the lists in the desired
|
||||
client do not contain (https://github.com/ansible-collections/community.general/pull/9739).
|
||||
- lldp - fix crash caused by certain lldpctl output where an attribute is
|
||||
defined as branch and leaf (https://github.com/ansible-collections/community.general/pull/9657).
|
||||
- onepassword_doc lookup plugin - ensure that 1Password Connect support also
|
||||
works for this plugin (https://github.com/ansible-collections/community.general/pull/9625).
|
||||
- passwordstore lookup plugin - fix subkey creation even when ``create=false``
|
||||
(https://github.com/ansible-collections/community.general/issues/9105, https://github.com/ansible-collections/community.general/pull/9106).
|
||||
- 'proxmox inventory plugin - plugin did not update cache correctly after
|
||||
``meta: refresh_inventory`` (https://github.com/ansible-collections/community.general/issues/9710,
|
||||
https://github.com/ansible-collections/community.general/pull/9760).'
|
||||
- 'redhat_subscription - use the "enable_content" option (when available)
|
||||
when
|
||||
|
||||
registering using D-Bus, to ensure that subscription-manager enables the
|
||||
|
||||
content on registration; this is particular important on EL 10+ and Fedora
|
||||
|
||||
41+
|
||||
|
||||
(https://github.com/ansible-collections/community.general/pull/9778).
|
||||
|
||||
'
|
||||
- zfs - fix handling of multi-line values of user-defined ZFS properties (https://github.com/ansible-collections/community.general/pull/6264).
|
||||
- zfs_facts - parameter ``type`` now accepts multple values as documented
|
||||
(https://github.com/ansible-collections/community.general/issues/5909, https://github.com/ansible-collections/community.general/pull/9697).
|
||||
deprecated_features:
|
||||
- profitbricks - module is deprecated and will be removed in community.general
|
||||
11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_datacenter - module is deprecated and will be removed in community.general
|
||||
11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_nic - module is deprecated and will be removed in community.general
|
||||
11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_volume - module is deprecated and will be removed in community.general
|
||||
11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
- profitbricks_volume_attachments - module is deprecated and will be removed
|
||||
in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/9733).
|
||||
minor_changes:
|
||||
- bitwarden lookup plugin - add new option ``collection_name`` to filter results
|
||||
by collection name, and new option ``result_count`` to validate number of
|
||||
results (https://github.com/ansible-collections/community.general/pull/9728).
|
||||
- incus connection plugin - adds ``remote_user`` and ``incus_become_method``
|
||||
parameters for allowing a non-root user to connect to an Incus instance
|
||||
(https://github.com/ansible-collections/community.general/pull/9743).
|
||||
- iocage inventory plugin - the new parameter ``hooks_results`` of the plugin
|
||||
is a list of files inside a jail that provide configuration parameters for
|
||||
the inventory. The inventory plugin reads the files from the jails and put
|
||||
the contents into the items of created variable ``iocage_hooks`` (https://github.com/ansible-collections/community.general/issues/9650,
|
||||
https://github.com/ansible-collections/community.general/pull/9651).
|
||||
- jira - adds ``client_cert`` and ``client_key`` parameters for supporting
|
||||
client certificate authentification when connecting to Jira (https://github.com/ansible-collections/community.general/pull/9753).
|
||||
- lldp - adds ``multivalues`` parameter to control behavior when lldpctl outputs
|
||||
an attribute multiple times (https://github.com/ansible-collections/community.general/pull/9657).
|
||||
- lvg - add ``remove_extra_pvs`` parameter to control if ansible should remove
|
||||
physical volumes which are not in the ``pvs`` parameter (https://github.com/ansible-collections/community.general/pull/9698).
|
||||
- lxd connection plugin - adds ``remote_user`` and ``lxd_become_method`` parameters
|
||||
for allowing a non-root user to connect to an LXD instance (https://github.com/ansible-collections/community.general/pull/9659).
|
||||
- nmcli - adds VRF support with new ``type`` value ``vrf`` and new ``slave_type``
|
||||
value ``vrf`` as well as new ``table`` parameter (https://github.com/ansible-collections/community.general/pull/9658,
|
||||
https://github.com/ansible-collections/community.general/issues/8014).
|
||||
- proxmox_kvm - allow hibernation and suspending of VMs (https://github.com/ansible-collections/community.general/issues/9620,
|
||||
https://github.com/ansible-collections/community.general/pull/9653).
|
||||
- redfish_command - add ``PowerFullPowerCycle`` to power command options (https://github.com/ansible-collections/community.general/pull/9729).
|
||||
- ssh_config - add ``other_options`` option (https://github.com/ansible-collections/community.general/issues/8053,
|
||||
https://github.com/ansible-collections/community.general/pull/9684).
|
||||
- xen_orchestra inventory plugin - add ``use_vm_uuid`` and ``use_host_uuid``
|
||||
boolean options to allow switching over to using VM/Xen name labels instead
|
||||
of UUIDs as item names (https://github.com/ansible-collections/community.general/pull/9787).
|
||||
release_summary: Regular bugfix and feature release.
|
||||
fragments:
|
||||
- 10.4.0.yaml
|
||||
- 6264-zfs-multiline-property-value.yml
|
||||
- 9106-passwordstore-fix-subkey-creation-even-when-create-==-false.yml
|
||||
- 9625-onepassword_doc.yml
|
||||
- 9651-iocage-inventory-hooks.yml
|
||||
- 9653-proxmox-kvm-allow-vm-hibernation.yml
|
||||
- 9657-lldp-handling-attributes-defined-multiple-times.yml
|
||||
- 9658-add-vrf-commands-to-nmcli-module.yml
|
||||
- 9659-lxd_connection-nonroot-user.yml
|
||||
- 9694-ipa-host-certificate-revoked.yml
|
||||
- 9697-zfs-facts-type.yml
|
||||
- 9698-lvg-remove-extra-pvs-parameter.yml
|
||||
- 9728-bitwarden-collection-name-filter.yml
|
||||
- 9729-redfish-fullpowercycle-command.yml
|
||||
- 9733-profitbrick-deprecation.yml
|
||||
- 9739-keycloak_client-compare-before-desired-directly.yml
|
||||
- 9743-incus_connection-nonroot-user.yml
|
||||
- 9753-jira-add-client-certificate-auth.yml
|
||||
- 9760-proxmox-inventory.yml
|
||||
- 9762-apache2_mod_proxy.yml
|
||||
- 9774-fix-elasticsearch_plugin-proxy-settings.yml
|
||||
- 9778-redhat_subscription-ensure-to-enable-content.yml
|
||||
- 9787-xoa_allow_using_names_in_inventory.yml
|
||||
- ssh_config_add_other_options.yml
|
||||
modules:
|
||||
- description: Gather C(systemd) unit info.
|
||||
name: systemd_info
|
||||
namespace: ''
|
||||
release_date: '2025-02-24'
|
||||
|
||||
@@ -20,3 +20,4 @@ sections:
|
||||
- guide_vardict
|
||||
- guide_cmdrunner
|
||||
- guide_modulehelper
|
||||
- guide_uthelper
|
||||
|
||||
@@ -124,7 +124,7 @@ To get a hash map with all ports and names of a cluster:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
|
||||
vars:
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster2'].{name: name, port: port}"
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster1'].{name: name, port: port}"
|
||||
|
||||
To extract ports from all clusters with name starting with 'server1':
|
||||
|
||||
|
||||
394
docs/docsite/rst/guide_uthelper.rst
Normal file
394
docs/docsite/rst/guide_uthelper.rst
Normal file
@@ -0,0 +1,394 @@
|
||||
..
|
||||
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_uthelper:
|
||||
|
||||
UTHelper Guide
|
||||
==============
|
||||
|
||||
Introduction
|
||||
^^^^^^^^^^^^
|
||||
|
||||
``UTHelper`` was written to reduce the boilerplate code used in unit tests for modules.
|
||||
It was originally written to handle tests of modules that run external commands using ``AnsibleModule.run_command()``.
|
||||
At the time of writing (Feb 2025) that remains the only type of tests you can use
|
||||
``UTHelper`` for, but it aims to provide support for other types of interactions.
|
||||
|
||||
Until now, there are many different ways to implement unit tests that validate a module based on the execution of external commands. See some examples:
|
||||
|
||||
* `test_apk.py <https://github.com/ansible-collections/community.general/blob/10.3.0/tests/unit/plugins/modules/test_apk.py>`_ - A very simple one
|
||||
* `test_bootc_manage.py <https://github.com/ansible-collections/community.general/blob/10.3.0/tests/unit/plugins/modules/test_bootc_manage.py>`_ -
|
||||
This one has more test cases, but do notice how the code is repeated amongst them.
|
||||
* `test_modprobe.py <https://github.com/ansible-collections/community.general/blob/10.3.0/tests/unit/plugins/modules/test_modprobe.py>`_ -
|
||||
This one has 15 tests in it, but to achieve that it declares 8 classes repeating quite a lot of code.
|
||||
|
||||
As you can notice, there is no consistency in the way these tests are executed -
|
||||
they all do the same thing eventually, but each one is written in a very distinct way.
|
||||
|
||||
``UTHelper`` aims to:
|
||||
|
||||
* provide a consistent idiom to define unit tests
|
||||
* reduce the code to a bare minimal, and
|
||||
* define tests as data instead
|
||||
* allow the test cases definition to be expressed not only as a Python data structure but also as YAML content
|
||||
|
||||
Quickstart
|
||||
""""""""""
|
||||
|
||||
To use UTHelper, your test module will need only a bare minimal of code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# tests/unit/plugin/modules/test_ansible_module.py
|
||||
from ansible_collections.community.general.plugins.modules import ansible_module
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
UTHelper.from_module(ansible_module, __name__, mocks=[RunCommandMock])
|
||||
|
||||
Then, in the test specification file, you have:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# tests/unit/plugin/modules/test_ansible_module.yaml
|
||||
test_cases:
|
||||
- id: test_ansible_module
|
||||
flags:
|
||||
diff: true
|
||||
input:
|
||||
state: present
|
||||
name: Roger the Shrubber
|
||||
output:
|
||||
shrubbery:
|
||||
looks: nice
|
||||
price: not too expensive
|
||||
changed: true
|
||||
diff:
|
||||
before:
|
||||
shrubbery: null
|
||||
after:
|
||||
shrubbery:
|
||||
looks: nice
|
||||
price: not too expensive
|
||||
mocks:
|
||||
run_command:
|
||||
- command: [/testbin/shrubber, --version]
|
||||
rc: 0
|
||||
out: "2.80.0\n"
|
||||
err: ''
|
||||
- command: [/testbin/shrubber, --make-shrubbery]
|
||||
rc: 0
|
||||
out: 'Shrubbery created'
|
||||
err: ''
|
||||
|
||||
.. note::
|
||||
|
||||
If you prefer to pick a different YAML file for the test cases, or if you prefer to define them in plain Python,
|
||||
you can use the convenience methods ``UTHelper.from_file()`` and ``UTHelper.from_spec()``, respectively.
|
||||
See more details below.
|
||||
|
||||
|
||||
Using ``UTHelper``
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Test Module
|
||||
"""""""""""
|
||||
|
||||
``UTHelper`` is **strictly for unit tests**. To use it, you import the ``.uthelper.UTHelper`` class.
|
||||
As mentioned in different parts of this guide, there are three different mechanisms to load the test cases.
|
||||
|
||||
.. seealso::
|
||||
|
||||
See the UTHelper class reference below for API details on the three different mechanisms.
|
||||
|
||||
|
||||
The easies and most recommended way of using ``UTHelper`` is literally the example shown.
|
||||
See a real world example at
|
||||
`test_gconftool2.py <https://github.com/ansible-collections/community.general/blob/10.3.0/tests/unit/plugins/modules/test_gconftool2.py>`_.
|
||||
|
||||
The ``from_module()`` method will pick the filename of the test module up (in the example above, ``tests/unit/plugins/modules/test_gconftool2.py``)
|
||||
and it will search for ``tests/unit/plugins/modules/test_gconftool2.yaml`` (or ``.yml`` if that is not found).
|
||||
In that file it will expect to find the test specification expressed in YAML format, conforming to the structure described below LINK LINK LINK.
|
||||
|
||||
If you prefer to read the test specifications a different file path, use ``from_file()`` passing the file handle for the YAML file.
|
||||
|
||||
And, if for any reason you prefer or need to pass the data structure rather than dealing with YAML files, use the ``from_spec()`` method.
|
||||
A real world example for that can be found at
|
||||
`test_snap.py <https://github.com/ansible-collections/community.general/blob/main/tests/unit/plugins/modules/test_snap.py>`_.
|
||||
|
||||
|
||||
Test Specification
|
||||
""""""""""""""""""
|
||||
|
||||
The structure of the test specification data is described below.
|
||||
|
||||
Top level
|
||||
---------
|
||||
|
||||
At the top level there are two accepted keys:
|
||||
|
||||
- ``anchors: dict``
|
||||
Optional. Placeholder for you to define YAML anchors that can be repeated in the test cases.
|
||||
Its contents are never accessed directly by test Helper.
|
||||
- ``test_cases: list``
|
||||
Mandatory. List of test cases, see below for definition.
|
||||
|
||||
Test cases
|
||||
----------
|
||||
|
||||
You write the test cases with five elements:
|
||||
|
||||
- ``id: str``
|
||||
Mandatory. Used to identify the test case.
|
||||
|
||||
- ``flags: dict``
|
||||
Optional. Flags controling the behavior of the test case. All flags are optional. Accepted flags:
|
||||
|
||||
* ``check: bool``: set to ``true`` if the module is to be executed in **check mode**.
|
||||
* ``diff: bool``: set to ``true`` if the module is to be executed in **diff mode**.
|
||||
* ``skip: str``: set the test case to be skipped, providing the message for ``pytest.skip()``.
|
||||
* ``xfail: str``: set the test case to expect failure, providing the message for ``pytest.xfail()``.
|
||||
|
||||
- ``input: dict``
|
||||
Optional. Parameters for the Ansible module, it can be empty.
|
||||
|
||||
- ``output: dict``
|
||||
Optional. Expected return values from the Ansible module.
|
||||
All RV names are used here are expected to be found in the module output, but not all RVs in the output must be here.
|
||||
It can include special RVs such as ``changed`` and ``diff``.
|
||||
It can be empty.
|
||||
|
||||
- ``mocks: dict``
|
||||
Optional. Mocked interactions, ``run_command`` being the only one supported for now.
|
||||
Each key in this dictionary refers to one subclass of ``TestCaseMock`` and its
|
||||
structure is dictated by the ``TestCaseMock`` subclass implementation.
|
||||
All keys are expected to be named using snake case, as in ``run_command``.
|
||||
The ``TestCaseMock`` subclass is responsible for defining the name used in the test specification.
|
||||
The structure for that specification is dependent on the implementing class.
|
||||
See more details below for the implementation of ``RunCommandMock``
|
||||
|
||||
Example using YAML
|
||||
------------------
|
||||
|
||||
We recommend you use ``UTHelper`` reading the test specifications from a YAML file.
|
||||
See an example below of how one actually looks like (excerpt from ``test_opkg.yaml``):
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
anchors:
|
||||
environ: &env-def {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
|
||||
test_cases:
|
||||
- id: install_zlibdev
|
||||
input:
|
||||
name: zlib-dev
|
||||
state: present
|
||||
output:
|
||||
msg: installed 1 package(s)
|
||||
mocks:
|
||||
run_command:
|
||||
- command: [/testbin/opkg, --version]
|
||||
environ: *env-def
|
||||
rc: 0
|
||||
out: ''
|
||||
err: ''
|
||||
- command: [/testbin/opkg, list-installed, zlib-dev]
|
||||
environ: *env-def
|
||||
rc: 0
|
||||
out: ''
|
||||
err: ''
|
||||
- command: [/testbin/opkg, install, zlib-dev]
|
||||
environ: *env-def
|
||||
rc: 0
|
||||
out: |
|
||||
Installing zlib-dev (1.2.11-6) to root...
|
||||
Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib-dev_1.2.11-6_mips_24kc.ipk
|
||||
Installing zlib (1.2.11-6) to root...
|
||||
Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib_1.2.11-6_mips_24kc.ipk
|
||||
Configuring zlib.
|
||||
Configuring zlib-dev.
|
||||
err: ''
|
||||
- command: [/testbin/opkg, list-installed, zlib-dev]
|
||||
environ: *env-def
|
||||
rc: 0
|
||||
out: |
|
||||
zlib-dev - 1.2.11-6
|
||||
err: ''
|
||||
- id: install_zlibdev_present
|
||||
input:
|
||||
name: zlib-dev
|
||||
state: present
|
||||
output:
|
||||
msg: package(s) already present
|
||||
mocks:
|
||||
run_command:
|
||||
- command: [/testbin/opkg, --version]
|
||||
environ: *env-def
|
||||
rc: 0
|
||||
out: ''
|
||||
err: ''
|
||||
- command: [/testbin/opkg, list-installed, zlib-dev]
|
||||
environ: *env-def
|
||||
rc: 0
|
||||
out: |
|
||||
zlib-dev - 1.2.11-6
|
||||
err: ''
|
||||
|
||||
TestCaseMocks Specifications
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``TestCaseMock`` subclass is free to define the expected data structure.
|
||||
|
||||
RunCommandMock Specification
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
``RunCommandMock`` mocks can be specified with the key ``run_command`` and it expects a ``list`` in which elements follow the structure:
|
||||
|
||||
- ``command: Union[list, str]``
|
||||
Mandatory. The command that is expected to be executed by the module. It corresponds to the parameter ``args`` of the ``AnsibleModule.run_command()`` call.
|
||||
It can be either a list or a string, though the list form is generally recommended.
|
||||
- ``environ: dict``
|
||||
Mandatory. All other parameters passed to the ``AnsibleModule.run_command()`` call.
|
||||
Most commonly used are ``environ_update`` and ``check_rc``.
|
||||
Must include all parameters the Ansible module uses in the ``AnsibleModule.run_command()`` call, otherwise the test will fail.
|
||||
- ``rc: int``
|
||||
Mandatory. The return code for the command execution.
|
||||
As per usual in bash scripting, a value of ``0`` means success, whereas any other number is an error code.
|
||||
- ``out: str``
|
||||
Mandatory. The *stdout* result of the command execution, as one single string containing zero or more lines.
|
||||
- ``err: str``
|
||||
Mandatory. The *stderr* result of the command execution, as one single string containing zero or more lines.
|
||||
|
||||
|
||||
``UTHelper`` Reference
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. py:module:: .uthelper
|
||||
|
||||
.. py:class:: UTHelper
|
||||
|
||||
A class to encapsulate unit tests.
|
||||
|
||||
.. py:staticmethod:: from_spec(ansible_module, test_module, test_spec, mocks=None)
|
||||
|
||||
Creates an ``UTHelper`` instance from a given test specification.
|
||||
|
||||
:param ansible_module: The Ansible module to be tested.
|
||||
:type ansible_module: module
|
||||
:param test_module: The test module.
|
||||
:type test_module: module
|
||||
:param test_spec: The test specification.
|
||||
:type test_spec: dict
|
||||
:param mocks: List of ``TestCaseMocks`` to be used during testing. Currently only ``RunCommandMock`` exists.
|
||||
:type mocks: list or None
|
||||
:return: An ``UTHelper`` instance.
|
||||
:rtype: UTHelper
|
||||
|
||||
Example usage of ``from_spec()``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import ansible_module
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
TEST_SPEC = dict(
|
||||
test_cases=[
|
||||
...
|
||||
]
|
||||
)
|
||||
|
||||
helper = UTHelper.from_spec(ansible_module, sys.modules[__name__], TEST_SPEC, mocks=[RunCommandMock])
|
||||
|
||||
.. py:staticmethod:: from_file(ansible_module, test_module, test_spec_filehandle, mocks=None)
|
||||
|
||||
Creates an ``UTHelper`` instance from a test specification file.
|
||||
|
||||
:param ansible_module: The Ansible module to be tested.
|
||||
:type ansible_module: module
|
||||
:param test_module: The test module.
|
||||
:type test_module: module
|
||||
:param test_spec_filehandle: A file handle to an file stream handle providing the test specification in YAML format.
|
||||
:type test_spec_filehandle: file
|
||||
:param mocks: List of ``TestCaseMocks`` to be used during testing. Currently only ``RunCommandMock`` exists.
|
||||
:type mocks: list or None
|
||||
:return: An ``UTHelper`` instance.
|
||||
:rtype: UTHelper
|
||||
|
||||
Example usage of ``from_file()``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import ansible_module
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
with open("test_spec.yaml", "r") as test_spec_filehandle:
|
||||
helper = UTHelper.from_file(ansible_module, sys.modules[__name__], test_spec_filehandle, mocks=[RunCommandMock])
|
||||
|
||||
.. py:staticmethod:: from_module(ansible_module, test_module_name, mocks=None)
|
||||
|
||||
Creates an ``UTHelper`` instance from a given Ansible module and test module.
|
||||
|
||||
:param ansible_module: The Ansible module to be tested.
|
||||
:type ansible_module: module
|
||||
:param test_module_name: The name of the test module. It works if passed ``__name__``.
|
||||
:type test_module_name: str
|
||||
:param mocks: List of ``TestCaseMocks`` to be used during testing. Currently only ``RunCommandMock`` exists.
|
||||
:type mocks: list or None
|
||||
:return: An ``UTHelper`` instance.
|
||||
:rtype: UTHelper
|
||||
|
||||
Example usage of ``from_module()``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import ansible_module
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
# Example usage
|
||||
helper = UTHelper.from_module(ansible_module, __name__, mocks=[RunCommandMock])
|
||||
|
||||
|
||||
Creating TestCaseMocks
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To create a new ``TestCaseMock`` you must extend that class and implement the relevant parts:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ShrubberyMock(TestCaseMock):
|
||||
# this name is mandatory, it is the name used in the test specification
|
||||
name = "shrubbery"
|
||||
|
||||
def setup(self, mocker):
|
||||
# perform setup, commonly using mocker to patch some other piece of code
|
||||
...
|
||||
|
||||
def check(self, test_case, results):
|
||||
# verify the tst execution met the expectations of the test case
|
||||
# for example the function was called as many times as it should
|
||||
...
|
||||
|
||||
def fixtures(self):
|
||||
# returns a dict mapping names to pytest fixtures that should be used for the test case
|
||||
# for example, in RunCommandMock it creates a fixture that patches AnsibleModule.get_bin_path
|
||||
...
|
||||
|
||||
Caveats
|
||||
^^^^^^^
|
||||
|
||||
Known issues/opportunities for improvement:
|
||||
|
||||
* Only one ``UTHelper`` per test module: UTHelper injects a test function with a fixed name into the module's namespace,
|
||||
so placing a second ``UTHelper`` instance is going to overwrite the function created by the first one.
|
||||
* Order of elements in module's namespace is not consistent across executions in Python 3.5, so if adding more tests to the test module
|
||||
might make Test Helper add its function before or after the other test functions.
|
||||
In the community.general collection the CI processes uses ``pytest-xdist`` to paralellize and distribute the tests,
|
||||
and it requires the order of the tests to be consistent.
|
||||
|
||||
.. versionadded:: 7.5.0
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 10.3.1
|
||||
version: 10.4.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
@@ -17,7 +17,7 @@ action_groups:
|
||||
proxmox:
|
||||
- proxmox
|
||||
- proxmox_backup
|
||||
- proxmox_backup_info
|
||||
- proxmox_backup_info
|
||||
- proxmox_disk
|
||||
- proxmox_domain_info
|
||||
- proxmox_group_info
|
||||
@@ -112,17 +112,53 @@ plugin_routing:
|
||||
atomic_container:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Poject Atomic was sunset by the end of 2019.
|
||||
warning_text: Project Atomic was sunset by the end of 2019.
|
||||
atomic_host:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Poject Atomic was sunset by the end of 2019.
|
||||
warning_text: Project Atomic was sunset by the end of 2019.
|
||||
atomic_image:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Poject Atomic was sunset by the end of 2019.
|
||||
warning_text: Project Atomic was sunset by the end of 2019.
|
||||
cisco_spark:
|
||||
redirect: community.general.cisco_webex
|
||||
clc_alert_policy:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_blueprint_package:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_firewall_policy:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_group:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_loadbalancer:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_modify_server:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_publicip:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_server:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
clc_server_snapshot:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: CenturyLink Cloud services went EOL in September 2023.
|
||||
consul_acl:
|
||||
tombstone:
|
||||
removal_version: 10.0.0
|
||||
@@ -603,6 +639,26 @@ plugin_routing:
|
||||
redirect: community.postgresql.postgresql_user
|
||||
postgresql_user_obj_stat_info:
|
||||
redirect: community.postgresql.postgresql_user_obj_stat_info
|
||||
profitbricks:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_datacenter:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_nic:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_volume:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
profitbricks_volume_attachments:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Supporting library is unsupported since 2021.
|
||||
purefa_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
|
||||
@@ -32,6 +32,15 @@ options:
|
||||
vars:
|
||||
- name: ansible_executable
|
||||
- name: ansible_incus_executable
|
||||
incus_become_method:
|
||||
description:
|
||||
- Become command used to switch to a non-root user.
|
||||
- Is only used when O(remote_user) is not V(root).
|
||||
type: str
|
||||
default: /bin/su
|
||||
vars:
|
||||
- name: incus_become_method
|
||||
version_added: 10.4.0
|
||||
remote:
|
||||
description:
|
||||
- The name of the Incus remote to use (per C(incus remote list)).
|
||||
@@ -40,6 +49,22 @@ options:
|
||||
default: local
|
||||
vars:
|
||||
- name: ansible_incus_remote
|
||||
remote_user:
|
||||
description:
|
||||
- User to login/authenticate as.
|
||||
- Can be set from the CLI via the C(--user) or C(-u) options.
|
||||
type: string
|
||||
default: root
|
||||
vars:
|
||||
- name: ansible_user
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_USER
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_user
|
||||
keyword:
|
||||
- name: remote_user
|
||||
version_added: 10.4.0
|
||||
project:
|
||||
description:
|
||||
- The name of the Incus project to use (per C(incus project list)).
|
||||
@@ -64,7 +89,6 @@ class Connection(ConnectionBase):
|
||||
|
||||
transport = "incus"
|
||||
has_pipelining = True
|
||||
default_user = 'root'
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
@@ -79,10 +103,34 @@ class Connection(ConnectionBase):
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self._connected:
|
||||
self._display.vvv("ESTABLISH Incus CONNECTION FOR USER: root",
|
||||
self._display.vvv(f"ESTABLISH Incus CONNECTION FOR USER: {self.get_option('remote_user')}",
|
||||
host=self._instance())
|
||||
self._connected = True
|
||||
|
||||
def _build_command(self, cmd) -> str:
|
||||
"""build the command to execute on the incus host"""
|
||||
|
||||
exec_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"exec",
|
||||
f"{self.get_option('remote')}:{self._instance()}",
|
||||
"--"]
|
||||
|
||||
if self.get_option("remote_user") != "root":
|
||||
self._display.vvv(
|
||||
f"INFO: Running as non-root user: {self.get_option('remote_user')}, \
|
||||
trying to run 'incus exec' with become method: {self.get_option('incus_become_method')}",
|
||||
host=self._instance(),
|
||||
)
|
||||
exec_cmd.extend(
|
||||
[self.get_option("incus_become_method"), self.get_option("remote_user"), "-c"]
|
||||
)
|
||||
|
||||
exec_cmd.extend([self.get_option("executable"), "-c", cmd])
|
||||
|
||||
return exec_cmd
|
||||
|
||||
def _instance(self):
|
||||
# Return only the leading part of the FQDN as the instance name
|
||||
# as Incus instance names cannot be a FQDN.
|
||||
@@ -95,13 +143,8 @@ class Connection(ConnectionBase):
|
||||
self._display.vvv(f"EXEC {cmd}",
|
||||
host=self._instance())
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"exec",
|
||||
f"{self.get_option('remote')}:{self._instance()}",
|
||||
"--",
|
||||
self._play_context.executable, "-c", cmd]
|
||||
local_cmd = self._build_command(cmd)
|
||||
self._display.vvvvv(f"EXEC {local_cmd}", host=self._instance())
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
||||
@@ -120,6 +163,25 @@ class Connection(ConnectionBase):
|
||||
|
||||
return process.returncode, stdout, stderr
|
||||
|
||||
def _get_remote_uid_gid(self) -> tuple[int, int]:
|
||||
"""Get the user and group ID of 'remote_user' from the instance."""
|
||||
|
||||
rc, uid_out, err = self.exec_command("/bin/id -u")
|
||||
if rc != 0:
|
||||
raise AnsibleError(
|
||||
f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}"
|
||||
)
|
||||
uid = uid_out.strip()
|
||||
|
||||
rc, gid_out, err = self.exec_command("/bin/id -g")
|
||||
if rc != 0:
|
||||
raise AnsibleError(
|
||||
f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}"
|
||||
)
|
||||
gid = gid_out.strip()
|
||||
|
||||
return int(uid), int(gid)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" put a file from local to Incus """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
@@ -130,12 +192,35 @@ class Connection(ConnectionBase):
|
||||
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound(f"input path is not a file: {in_path}")
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"file", "push", "--quiet",
|
||||
in_path,
|
||||
f"{self.get_option('remote')}:{self._instance()}/{out_path}"]
|
||||
if self.get_option("remote_user") != "root":
|
||||
uid, gid = self._get_remote_uid_gid()
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project",
|
||||
self.get_option("project"),
|
||||
"file",
|
||||
"push",
|
||||
"--uid",
|
||||
str(uid),
|
||||
"--gid",
|
||||
str(gid),
|
||||
"--quiet",
|
||||
in_path,
|
||||
f"{self.get_option('remote')}:{self._instance()}/{out_path}",
|
||||
]
|
||||
else:
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project",
|
||||
self.get_option("project"),
|
||||
"file",
|
||||
"push",
|
||||
"--quiet",
|
||||
in_path,
|
||||
f"{self.get_option('remote')}:{self._instance()}/{out_path}",
|
||||
]
|
||||
|
||||
self._display.vvvvv(f"PUT {local_cmd}", host=self._instance())
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
|
||||
@@ -32,6 +32,15 @@ options:
|
||||
vars:
|
||||
- name: ansible_executable
|
||||
- name: ansible_lxd_executable
|
||||
lxd_become_method:
|
||||
description:
|
||||
- Become command used to switch to a non-root user.
|
||||
- Is only used when O(remote_user) is not V(root).
|
||||
type: str
|
||||
default: /bin/su
|
||||
vars:
|
||||
- name: lxd_become_method
|
||||
version_added: 10.4.0
|
||||
remote:
|
||||
description:
|
||||
- Name of the LXD remote to use.
|
||||
@@ -40,6 +49,22 @@ options:
|
||||
vars:
|
||||
- name: ansible_lxd_remote
|
||||
version_added: 2.0.0
|
||||
remote_user:
|
||||
description:
|
||||
- User to login/authenticate as.
|
||||
- Can be set from the CLI via the C(--user) or C(-u) options.
|
||||
type: string
|
||||
default: root
|
||||
vars:
|
||||
- name: ansible_user
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_USER
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_user
|
||||
keyword:
|
||||
- name: remote_user
|
||||
version_added: 10.4.0
|
||||
project:
|
||||
description:
|
||||
- Name of the LXD project to use.
|
||||
@@ -63,7 +88,6 @@ class Connection(ConnectionBase):
|
||||
|
||||
transport = 'community.general.lxd'
|
||||
has_pipelining = True
|
||||
default_user = 'root'
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
@@ -73,9 +97,6 @@ class Connection(ConnectionBase):
|
||||
except ValueError:
|
||||
raise AnsibleError("lxc command not found in PATH")
|
||||
|
||||
if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
|
||||
self._display.warning('lxd does not support remote_user, using default: root')
|
||||
|
||||
def _host(self):
|
||||
""" translate remote_addr to lxd (short) hostname """
|
||||
return self.get_option("remote_addr").split(".", 1)[0]
|
||||
@@ -85,25 +106,40 @@ class Connection(ConnectionBase):
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self._connected:
|
||||
self._display.vvv("ESTABLISH LXD CONNECTION FOR USER: root", host=self._host())
|
||||
self._display.vvv(f"ESTABLISH LXD CONNECTION FOR USER: {self.get_option('remote_user')}", host=self._host())
|
||||
self._connected = True
|
||||
|
||||
def _build_command(self, cmd) -> str:
|
||||
"""build the command to execute on the lxd host"""
|
||||
|
||||
exec_cmd = [self._lxc_cmd]
|
||||
|
||||
if self.get_option("project"):
|
||||
exec_cmd.extend(["--project", self.get_option("project")])
|
||||
|
||||
exec_cmd.extend(["exec", f"{self.get_option('remote')}:{self._host()}", "--"])
|
||||
|
||||
if self.get_option("remote_user") != "root":
|
||||
self._display.vvv(
|
||||
f"INFO: Running as non-root user: {self.get_option('remote_user')}, \
|
||||
trying to run 'lxc exec' with become method: {self.get_option('lxd_become_method')}",
|
||||
host=self._host(),
|
||||
)
|
||||
exec_cmd.extend(
|
||||
[self.get_option("lxd_become_method"), self.get_option("remote_user"), "-c"]
|
||||
)
|
||||
|
||||
exec_cmd.extend([self.get_option("executable"), "-c", cmd])
|
||||
|
||||
return exec_cmd
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
""" execute a command on the lxd host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
self._display.vvv(f"EXEC {cmd}", host=self._host())
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"exec",
|
||||
f"{self.get_option('remote')}:{self._host()}",
|
||||
"--",
|
||||
self.get_option("executable"), "-c", cmd
|
||||
])
|
||||
|
||||
local_cmd = self._build_command(cmd)
|
||||
self._display.vvvvv(f"EXEC {local_cmd}", host=self._host())
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
@@ -125,6 +161,25 @@ class Connection(ConnectionBase):
|
||||
|
||||
return process.returncode, stdout, stderr
|
||||
|
||||
def _get_remote_uid_gid(self) -> tuple[int, int]:
|
||||
"""Get the user and group ID of 'remote_user' from the instance."""
|
||||
|
||||
rc, uid_out, err = self.exec_command("/bin/id -u")
|
||||
if rc != 0:
|
||||
raise AnsibleError(
|
||||
f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}"
|
||||
)
|
||||
uid = uid_out.strip()
|
||||
|
||||
rc, gid_out, err = self.exec_command("/bin/id -g")
|
||||
if rc != 0:
|
||||
raise AnsibleError(
|
||||
f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}"
|
||||
)
|
||||
gid = gid_out.strip()
|
||||
|
||||
return int(uid), int(gid)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" put a file from local to lxd """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
@@ -137,11 +192,32 @@ class Connection(ConnectionBase):
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"file", "push",
|
||||
in_path,
|
||||
f"{self.get_option('remote')}:{self._host()}/{out_path}"
|
||||
])
|
||||
|
||||
if self.get_option("remote_user") != "root":
|
||||
uid, gid = self._get_remote_uid_gid()
|
||||
local_cmd.extend(
|
||||
[
|
||||
"file",
|
||||
"push",
|
||||
"--uid",
|
||||
str(uid),
|
||||
"--gid",
|
||||
str(gid),
|
||||
in_path,
|
||||
f"{self.get_option('remote')}:{self._host()}/{out_path}",
|
||||
]
|
||||
)
|
||||
else:
|
||||
local_cmd.extend(
|
||||
[
|
||||
"file",
|
||||
"push",
|
||||
in_path,
|
||||
f"{self.get_option('remote')}:{self._host()}/{out_path}",
|
||||
]
|
||||
)
|
||||
|
||||
self._display.vvvvv(f"PUT {local_cmd}", host=self._host())
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
|
||||
@@ -6,85 +6,99 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: iocage
|
||||
short_description: iocage inventory source
|
||||
version_added: 10.2.0
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
requirements:
|
||||
- iocage >= 1.8
|
||||
DOCUMENTATION = r'''
|
||||
name: iocage
|
||||
short_description: iocage inventory source
|
||||
version_added: 10.2.0
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
requirements:
|
||||
- iocage >= 1.8
|
||||
description:
|
||||
- Get inventory hosts from the iocage jail manager running on O(host).
|
||||
- By default, O(host) is V(localhost). If O(host) is not V(localhost) it
|
||||
is expected that the user running Ansible on the controller can
|
||||
connect to the O(host) account O(user) with SSH non-interactively and
|
||||
execute the command C(iocage list).
|
||||
- Uses a configuration file as an inventory source, it must end
|
||||
in C(.iocage.yml) or C(.iocage.yaml).
|
||||
extends_documentation_fragment:
|
||||
- ansible.builtin.constructed
|
||||
- ansible.builtin.inventory_cache
|
||||
options:
|
||||
plugin:
|
||||
description:
|
||||
- Get inventory hosts from the iocage jail manager running on O(host).
|
||||
- By default, O(host) is V(localhost). If O(host) is not V(localhost) it
|
||||
is expected that the user running Ansible on the controller can
|
||||
connect to the O(host) account O(user) with SSH non-interactively and
|
||||
execute the command C(iocage list).
|
||||
- Uses a configuration file as an inventory source, it must end
|
||||
in C(.iocage.yml) or C(.iocage.yaml).
|
||||
extends_documentation_fragment:
|
||||
- ansible.builtin.constructed
|
||||
- ansible.builtin.inventory_cache
|
||||
options:
|
||||
plugin:
|
||||
description:
|
||||
- The name of this plugin, it should always be set to
|
||||
V(community.general.iocage) for this plugin to recognize
|
||||
it as its own.
|
||||
required: true
|
||||
choices: ['community.general.iocage']
|
||||
type: str
|
||||
host:
|
||||
description: The IP/hostname of the C(iocage) host.
|
||||
type: str
|
||||
default: localhost
|
||||
user:
|
||||
description:
|
||||
- C(iocage) user.
|
||||
It is expected that the O(user) is able to connect to the
|
||||
O(host) with SSH and execute the command C(iocage list).
|
||||
This option is not required if O(host) is V(localhost).
|
||||
type: str
|
||||
sudo:
|
||||
description:
|
||||
- Enable execution as root.
|
||||
- This requires passwordless sudo of the command C(iocage list*).
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 10.3.0
|
||||
sudo_preserve_env:
|
||||
description:
|
||||
- Preserve environment if O(sudo) is enabled.
|
||||
- This requires C(SETENV) sudoers tag.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 10.3.0
|
||||
get_properties:
|
||||
description:
|
||||
- Get jails' properties.
|
||||
Creates dictionary C(iocage_properties) for each added host.
|
||||
type: bool
|
||||
default: false
|
||||
env:
|
||||
description:
|
||||
- O(user)'s environment on O(host).
|
||||
- Enable O(sudo_preserve_env) if O(sudo) is enabled.
|
||||
type: dict
|
||||
default: {}
|
||||
notes:
|
||||
- You might want to test the command C(ssh user@host iocage list -l) on
|
||||
the controller before using this inventory plugin with O(user) specified
|
||||
and with O(host) other than V(localhost).
|
||||
- If you run this inventory plugin on V(localhost) C(ssh) is not used.
|
||||
In this case, test the command C(iocage list -l).
|
||||
- This inventory plugin creates variables C(iocage_*) for each added host.
|
||||
- The values of these variables are collected from the output of the
|
||||
command C(iocage list -l).
|
||||
- The names of these variables correspond to the output columns.
|
||||
- The column C(NAME) is used to name the added host.
|
||||
- The name of this plugin, it should always be set to
|
||||
V(community.general.iocage) for this plugin to recognize
|
||||
it as its own.
|
||||
required: true
|
||||
choices: ['community.general.iocage']
|
||||
type: str
|
||||
host:
|
||||
description: The IP/hostname of the C(iocage) host.
|
||||
type: str
|
||||
default: localhost
|
||||
user:
|
||||
description:
|
||||
- C(iocage) user.
|
||||
It is expected that the O(user) is able to connect to the
|
||||
O(host) with SSH and execute the command C(iocage list).
|
||||
This option is not required if O(host) is V(localhost).
|
||||
type: str
|
||||
sudo:
|
||||
description:
|
||||
- Enable execution as root.
|
||||
- This requires passwordless sudo of the command C(iocage list*).
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 10.3.0
|
||||
sudo_preserve_env:
|
||||
description:
|
||||
- Preserve environment if O(sudo) is enabled.
|
||||
- This requires C(SETENV) sudoers tag.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 10.3.0
|
||||
get_properties:
|
||||
description:
|
||||
- Get jails' properties.
|
||||
Creates dictionary C(iocage_properties) for each added host.
|
||||
type: bool
|
||||
default: false
|
||||
env:
|
||||
description:
|
||||
- O(user)'s environment on O(host).
|
||||
- Enable O(sudo_preserve_env) if O(sudo) is enabled.
|
||||
type: dict
|
||||
default: {}
|
||||
hooks_results:
|
||||
description:
|
||||
- List of paths to the files in a jail.
|
||||
- Content of the files is stored in the items of the list C(iocage_hooks).
|
||||
- If a file is not available the item keeps the dash character C(-).
|
||||
- The variable C(iocage_hooks) is not created if O(hooks_results) is empty.
|
||||
type: list
|
||||
elements: path
|
||||
version_added: 10.4.0
|
||||
notes:
|
||||
- You might want to test the command C(ssh user@host iocage list -l) on
|
||||
the controller before using this inventory plugin with O(user) specified
|
||||
and with O(host) other than V(localhost).
|
||||
- If you run this inventory plugin on V(localhost) C(ssh) is not used.
|
||||
In this case, test the command C(iocage list -l).
|
||||
- This inventory plugin creates variables C(iocage_*) for each added host.
|
||||
- The values of these variables are collected from the output of the
|
||||
command C(iocage list -l).
|
||||
- The names of these variables correspond to the output columns.
|
||||
- The column C(NAME) is used to name the added host.
|
||||
- The option O(hooks_results) expects the C(poolname) of a jail is mounted to
|
||||
C(/poolname). For example, if you activate the pool C(iocage) this plugin
|
||||
expects to find the O(hooks_results) items in the path
|
||||
C(/iocage/iocage/jails/<name>/root). If you mount the C(poolname) to a
|
||||
different path the easiest remedy is to create a symlink.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
---
|
||||
# file name must end with iocage.yaml or iocage.yml
|
||||
plugin: community.general.iocage
|
||||
@@ -142,6 +156,18 @@ keyed_groups:
|
||||
key: iocage_release
|
||||
- prefix: state
|
||||
key: iocage_state
|
||||
|
||||
---
|
||||
# Read the file /var/db/dhclient-hook.address.epair0b in the jails and use it as ansible_host
|
||||
plugin: community.general.iocage
|
||||
host: 10.1.0.73
|
||||
user: admin
|
||||
hooks_results:
|
||||
- /var/db/dhclient-hook.address.epair0b
|
||||
compose:
|
||||
ansible_host: iocage_hooks.0
|
||||
groups:
|
||||
test: inventory_hostname.startswith('test')
|
||||
'''
|
||||
|
||||
import re
|
||||
@@ -226,6 +252,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
sudo_preserve_env = self.get_option('sudo_preserve_env')
|
||||
env = self.get_option('env')
|
||||
get_properties = self.get_option('get_properties')
|
||||
hooks_results = self.get_option('hooks_results')
|
||||
|
||||
cmd = []
|
||||
my_env = os.environ.copy()
|
||||
@@ -286,6 +313,50 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
self.get_properties(t_stdout, results, hostname)
|
||||
|
||||
if hooks_results:
|
||||
cmd_get_pool = cmd.copy()
|
||||
cmd_get_pool.append(self.IOCAGE)
|
||||
cmd_get_pool.append('get')
|
||||
cmd_get_pool.append('--pool')
|
||||
try:
|
||||
p = Popen(cmd_get_pool, stdout=PIPE, stderr=PIPE, env=my_env)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError(
|
||||
f'Failed to run cmd={cmd_get_pool}, rc={p.returncode}, stderr={to_native(stderr)}')
|
||||
try:
|
||||
iocage_pool = to_text(stdout, errors='surrogate_or_strict').strip()
|
||||
except UnicodeError as e:
|
||||
raise AnsibleError(f'Invalid (non unicode) input returned: {e}') from e
|
||||
except Exception as e:
|
||||
raise AnsibleError(f'Failed to get pool: {e}') from e
|
||||
|
||||
for hostname, host_vars in results['_meta']['hostvars'].items():
|
||||
iocage_hooks = []
|
||||
for hook in hooks_results:
|
||||
path = "/" + iocage_pool + "/iocage/jails/" + hostname + "/root" + hook
|
||||
cmd_cat_hook = cmd.copy()
|
||||
cmd_cat_hook.append('cat')
|
||||
cmd_cat_hook.append(path)
|
||||
try:
|
||||
p = Popen(cmd_cat_hook, stdout=PIPE, stderr=PIPE, env=my_env)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
iocage_hooks.append('-')
|
||||
continue
|
||||
|
||||
try:
|
||||
iocage_hook = to_text(stdout, errors='surrogate_or_strict').strip()
|
||||
except UnicodeError as e:
|
||||
raise AnsibleError(f'Invalid (non unicode) input returned: {e}') from e
|
||||
|
||||
except Exception:
|
||||
iocage_hooks.append('-')
|
||||
else:
|
||||
iocage_hooks.append(iocage_hook)
|
||||
|
||||
results['_meta']['hostvars'][hostname]['iocage_hooks'] = iocage_hooks
|
||||
|
||||
return results
|
||||
|
||||
def get_jails(self, t_stdout, results):
|
||||
|
||||
@@ -308,12 +308,17 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
def _get_json(self, url, ignore_errors=None):
|
||||
|
||||
if not self.use_cache or url not in self._cache.get(self.cache_key, {}):
|
||||
data = []
|
||||
has_data = False
|
||||
|
||||
if self.cache_key not in self._cache:
|
||||
self._cache[self.cache_key] = {'url': ''}
|
||||
if self.use_cache:
|
||||
try:
|
||||
data = self._cache[self.cache_key][url]
|
||||
has_data = True
|
||||
except KeyError:
|
||||
self.update_cache = True
|
||||
|
||||
data = []
|
||||
if not has_data:
|
||||
s = self._get_session()
|
||||
while True:
|
||||
ret = s.get(url, headers=self.headers)
|
||||
@@ -339,9 +344,8 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
data = data + json['data']
|
||||
break
|
||||
|
||||
self._cache[self.cache_key][url] = data
|
||||
|
||||
return make_unsafe(self._cache[self.cache_key][url])
|
||||
self._results[url] = data
|
||||
return make_unsafe(data)
|
||||
|
||||
def _get_nodes(self):
|
||||
return self._get_json(f"{self.proxmox_url}/api2/json/nodes")
|
||||
@@ -680,10 +684,14 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
self.exclude_nodes = self.get_option('exclude_nodes')
|
||||
self.cache_key = self.get_cache_key(path)
|
||||
self.use_cache = cache and self.get_option('cache')
|
||||
self.update_cache = not cache and self.get_option('cache')
|
||||
self.host_filters = self.get_option('filters')
|
||||
self.group_prefix = self.get_option('group_prefix')
|
||||
self.facts_prefix = self.get_option('facts_prefix')
|
||||
self.strict = self.get_option('strict')
|
||||
|
||||
# actually populate inventory
|
||||
self._results = {}
|
||||
self._populate()
|
||||
if self.update_cache:
|
||||
self._cache[self.cache_key] = self._results
|
||||
|
||||
@@ -57,6 +57,20 @@ DOCUMENTATION = '''
|
||||
description: Use wss when connecting to the Xen Orchestra API
|
||||
type: boolean
|
||||
default: true
|
||||
use_vm_uuid:
|
||||
description:
|
||||
- Import Xen VMs to inventory using their UUID as the VM entry name.
|
||||
- If set to V(false) use VM name labels instead of UUIDs.
|
||||
type: boolean
|
||||
default: true
|
||||
version_added: 10.4.0
|
||||
use_host_uuid:
|
||||
description:
|
||||
- Import Xen Hosts to inventory using their UUID as the Host entry name.
|
||||
- If set to V(false) use Host name labels instead of UUIDs.
|
||||
type: boolean
|
||||
default: true
|
||||
version_added: 10.4.0
|
||||
'''
|
||||
|
||||
|
||||
@@ -72,6 +86,8 @@ groups:
|
||||
kube_nodes: "'kube_node' in tags"
|
||||
compose:
|
||||
ansible_port: 2222
|
||||
use_vm_uuid: false
|
||||
use_host_uuid: true
|
||||
|
||||
'''
|
||||
|
||||
@@ -196,10 +212,20 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
self._set_composite_vars(self.get_option('compose'), variables, name, strict=strict)
|
||||
|
||||
def _add_vms(self, vms, hosts, pools):
|
||||
vm_name_list = []
|
||||
for uuid, vm in vms.items():
|
||||
if self.vm_entry_name_type == 'name_label':
|
||||
if vm['name_label'] not in vm_name_list:
|
||||
entry_name = vm['name_label']
|
||||
vm_name_list.append(vm['name_label'])
|
||||
else:
|
||||
vm_duplicate_count = vm_name_list.count(vm['name_label'])
|
||||
entry_name = vm['name_label'] + "_" + str(vm_duplicate_count)
|
||||
vm_name_list.append(vm['name_label'])
|
||||
else:
|
||||
entry_name = uuid
|
||||
group = 'with_ip'
|
||||
ip = vm.get('mainIpAddress')
|
||||
entry_name = uuid
|
||||
power_state = vm['power_state'].lower()
|
||||
pool_name = self._pool_group_name_for_uuid(pools, vm['$poolId'])
|
||||
host_name = self._host_group_name_for_uuid(hosts, vm['$container'])
|
||||
@@ -246,8 +272,19 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
self._apply_constructable(entry_name, self.inventory.get_host(entry_name).get_vars())
|
||||
|
||||
def _add_hosts(self, hosts, pools):
|
||||
host_name_list = []
|
||||
for host in hosts.values():
|
||||
entry_name = host['uuid']
|
||||
if self.host_entry_name_type == 'name_label':
|
||||
if host['name_label'] not in host_name_list:
|
||||
entry_name = host['name_label']
|
||||
host_name_list.append(host['name_label'])
|
||||
else:
|
||||
host_duplicate_count = host_name_list.count(host['name_label'])
|
||||
entry_name = host['name_label'] + "_" + str(host_duplicate_count)
|
||||
host_name_list.append(host['name_label'])
|
||||
else:
|
||||
entry_name = host['uuid']
|
||||
|
||||
group_name = f"xo_host_{clean_group_name(host['name_label'])}"
|
||||
pool_name = self._pool_group_name_for_uuid(pools, host['$poolId'])
|
||||
|
||||
@@ -337,5 +374,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
if not self.get_option('use_ssl'):
|
||||
self.protocol = 'ws'
|
||||
|
||||
self.vm_entry_name_type = 'uuid'
|
||||
if not self.get_option('use_vm_uuid'):
|
||||
self.vm_entry_name_type = 'name_label'
|
||||
|
||||
self.host_entry_name_type = 'uuid'
|
||||
if not self.get_option('use_host_uuid'):
|
||||
self.host_entry_name_type = 'name_label'
|
||||
|
||||
objects = self._get_objects()
|
||||
self._populate(make_unsafe(objects))
|
||||
|
||||
@@ -37,9 +37,17 @@ DOCUMENTATION = """
|
||||
description: Field to fetch. Leave unset to fetch whole response.
|
||||
type: str
|
||||
collection_id:
|
||||
description: Collection ID to filter results by collection. Leave unset to skip filtering.
|
||||
description:
|
||||
- Collection ID to filter results by collection. Leave unset to skip filtering.
|
||||
- O(collection_id) and O(collection_name) are mutually exclusive.
|
||||
type: str
|
||||
version_added: 6.3.0
|
||||
collection_name:
|
||||
description:
|
||||
- Collection name to filter results by collection. Leave unset to skip filtering.
|
||||
- O(collection_id) and O(collection_name) are mutually exclusive.
|
||||
type: str
|
||||
version_added: 10.4.0
|
||||
organization_id:
|
||||
description: Organization ID to filter results by organization. Leave unset to skip filtering.
|
||||
type: str
|
||||
@@ -48,6 +56,12 @@ DOCUMENTATION = """
|
||||
description: Pass session key instead of reading from env.
|
||||
type: str
|
||||
version_added: 8.4.0
|
||||
result_count:
|
||||
description:
|
||||
- Number of results expected for the lookup query. Task will fail if O(result_count)
|
||||
is set but does not match the number of query results. Leave empty to skip this check.
|
||||
type: int
|
||||
version_added: 10.4.0
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
@@ -85,6 +99,16 @@ EXAMPLES = """
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ lookup('community.general.bitwarden', None, collection_id='bafba515-af11-47e6-abe3-af1200cd18b2') }}
|
||||
|
||||
- name: "Get all Bitwarden records from collection"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ lookup('community.general.bitwarden', None, collection_name='my_collections/test_collection') }}
|
||||
|
||||
- name: "Get Bitwarden record named 'a_test', ensure there is exactly one match"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ lookup('community.general.bitwarden', 'a_test', result_count=1) }}
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -99,7 +123,7 @@ RETURN = """
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.parsing.ajson import AnsibleJSONDecoder
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
@@ -211,6 +235,24 @@ class Bitwarden(object):
|
||||
|
||||
return field_matches
|
||||
|
||||
def get_collection_ids(self, collection_name: str, organization_id=None) -> list[str]:
|
||||
"""Return matching IDs of collections whose name is equal to collection_name."""
|
||||
|
||||
# Prepare set of params for Bitwarden CLI
|
||||
params = ['list', 'collections', '--search', collection_name]
|
||||
|
||||
if organization_id:
|
||||
params.extend(['--organizationid', organization_id])
|
||||
|
||||
out, err = self._run(params)
|
||||
|
||||
# This includes things that matched in different fields.
|
||||
initial_matches = AnsibleJSONDecoder().raw_decode(out)[0]
|
||||
|
||||
# Filter to only return the ID of a collections with exactly matching name
|
||||
return [item['id'] for item in initial_matches
|
||||
if str(item.get('name')).lower() == collection_name.lower()]
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
||||
@@ -219,7 +261,9 @@ class LookupModule(LookupBase):
|
||||
field = self.get_option('field')
|
||||
search_field = self.get_option('search')
|
||||
collection_id = self.get_option('collection_id')
|
||||
collection_name = self.get_option('collection_name')
|
||||
organization_id = self.get_option('organization_id')
|
||||
result_count = self.get_option('result_count')
|
||||
_bitwarden.session = self.get_option('bw_session')
|
||||
|
||||
if not _bitwarden.unlocked:
|
||||
@@ -228,7 +272,27 @@ class LookupModule(LookupBase):
|
||||
if not terms:
|
||||
terms = [None]
|
||||
|
||||
return [_bitwarden.get_field(field, term, search_field, collection_id, organization_id) for term in terms]
|
||||
if collection_name and collection_id:
|
||||
raise AnsibleOptionsError("'collection_name' and 'collection_id' are mutually exclusive!")
|
||||
elif collection_name:
|
||||
collection_ids = _bitwarden.get_collection_ids(collection_name, organization_id)
|
||||
if not collection_ids:
|
||||
raise BitwardenException("No matching collections found!")
|
||||
else:
|
||||
collection_ids = [collection_id]
|
||||
|
||||
results = [
|
||||
_bitwarden.get_field(field, term, search_field, collection_id, organization_id)
|
||||
for collection_id in collection_ids
|
||||
for term in terms
|
||||
]
|
||||
|
||||
for result in results:
|
||||
if result_count is not None and len(result) != result_count:
|
||||
raise BitwardenException(
|
||||
f"Number of results doesn't match result_count! ({len(result)} != {result_count})")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
_bitwarden = Bitwarden()
|
||||
|
||||
@@ -553,9 +553,7 @@ class OnePassCLIv2(OnePassCLIBase):
|
||||
environment_update = {"OP_SECRET_KEY": self.secret_key}
|
||||
return self._run(args, command_input=to_bytes(self.master_password), environment_update=environment_update)
|
||||
|
||||
def get_raw(self, item_id, vault=None, token=None):
|
||||
args = ["item", "get", item_id, "--format", "json"]
|
||||
|
||||
def _add_parameters_and_run(self, args, vault=None, token=None):
|
||||
if self.account_id:
|
||||
args.extend(["--account", self.account_id])
|
||||
|
||||
@@ -582,6 +580,10 @@ class OnePassCLIv2(OnePassCLIBase):
|
||||
|
||||
return self._run(args)
|
||||
|
||||
def get_raw(self, item_id, vault=None, token=None):
|
||||
args = ["item", "get", item_id, "--format", "json"]
|
||||
return self._add_parameters_and_run(args, vault=vault, token=token)
|
||||
|
||||
def signin(self):
|
||||
self._check_required_params(['master_password'])
|
||||
|
||||
|
||||
@@ -46,28 +46,13 @@ RETURN = """
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword import OnePass, OnePassCLIv2
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
class OnePassCLIv2Doc(OnePassCLIv2):
|
||||
def get_raw(self, item_id, vault=None, token=None):
|
||||
args = ["document", "get", item_id]
|
||||
if vault is not None:
|
||||
args = [*args, f"--vault={vault}"]
|
||||
|
||||
if self.service_account_token:
|
||||
if vault is None:
|
||||
raise AnsibleLookupError("'vault' is required with 'service_account_token'")
|
||||
|
||||
environment_update = {"OP_SERVICE_ACCOUNT_TOKEN": self.service_account_token}
|
||||
return self._run(args, environment_update=environment_update)
|
||||
|
||||
if token is not None:
|
||||
args = [*args, to_bytes("--session=") + token]
|
||||
|
||||
return self._run(args)
|
||||
return self._add_parameters_and_run(args, vault=vault, token=token)
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
||||
@@ -572,16 +572,20 @@ class LookupModule(LookupBase):
|
||||
for term in terms:
|
||||
self.parse_params(term) # parse the input into paramvals
|
||||
with self.opt_lock('readwrite'):
|
||||
if self.check_pass(): # password exists
|
||||
if self.paramvals['overwrite']:
|
||||
if self.check_pass(): # password file exists
|
||||
if self.paramvals['overwrite']: # if "overwrite", always update password
|
||||
with self.opt_lock('write'):
|
||||
result.append(self.update_password())
|
||||
elif self.paramvals["subkey"] != "password" and not self.passdict.get(self.paramvals['subkey']): # password exists but not the subkey
|
||||
elif (
|
||||
self.paramvals["subkey"] != "password"
|
||||
and not self.passdict.get(self.paramvals["subkey"])
|
||||
and self.paramvals["missing"] == "create"
|
||||
): # target is a subkey, this subkey is not in passdict BUT missing == create
|
||||
with self.opt_lock('write'):
|
||||
result.append(self.update_password())
|
||||
else:
|
||||
result.append(self.get_passresult())
|
||||
else: # password does not exist
|
||||
else: # password does not exist
|
||||
if self.paramvals['missing'] == 'create':
|
||||
with self.opt_lock('write'):
|
||||
if self.locked == 'write' and self.check_pass(): # lookup password again if under write lock
|
||||
|
||||
@@ -1119,7 +1119,8 @@ class RedfishUtils(object):
|
||||
key = "Actions"
|
||||
reset_type_values = ['On', 'ForceOff', 'GracefulShutdown',
|
||||
'GracefulRestart', 'ForceRestart', 'Nmi',
|
||||
'ForceOn', 'PushPowerButton', 'PowerCycle']
|
||||
'ForceOn', 'PushPowerButton', 'PowerCycle',
|
||||
'FullPowerCycle']
|
||||
|
||||
# command should be PowerOn, PowerForceOff, etc.
|
||||
if not command.startswith('Power'):
|
||||
|
||||
@@ -19,7 +19,7 @@ description:
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
requirements:
|
||||
- Python package C(BeautifulSoup).
|
||||
- Python package C(BeautifulSoup) on Python 2, C(beautifulsoup4) on Python 3.
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
@@ -207,16 +207,27 @@ import re
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils import deps
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.six import iteritems, PY2
|
||||
|
||||
with deps.declare("BeautifulSoup"):
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
if PY2:
|
||||
with deps.declare("BeautifulSoup"):
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
else:
|
||||
with deps.declare("beautifulsoup4"):
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# balancer member attributes extraction regexp:
|
||||
EXPRESSION = re.compile(r"(b=([\w\.\-]+)&w=(https?|ajp|wss?|ftp|[sf]cgi)://([\w\.\-]+):?(\d*)([/\w\.\-]*)&?[\w\-\=]*)")
|
||||
EXPRESSION = re.compile(to_text(r"(b=([\w\.\-]+)&w=(https?|ajp|wss?|ftp|[sf]cgi)://([\w\.\-]+):?(\d*)([/\w\.\-]*)&?[\w\-\=]*)"))
|
||||
# Apache2 server version extraction regexp:
|
||||
APACHE_VERSION_EXPRESSION = re.compile(r"SERVER VERSION: APACHE/([\d.]+)")
|
||||
APACHE_VERSION_EXPRESSION = re.compile(to_text(r"SERVER VERSION: APACHE/([\d.]+)"))
|
||||
|
||||
|
||||
def find_all(where, what):
|
||||
if PY2:
|
||||
return where.findAll(what)
|
||||
return where.find_all(what)
|
||||
|
||||
|
||||
def regexp_extraction(string, _regexp, groups=1):
|
||||
@@ -256,7 +267,7 @@ class BalancerMember(object):
|
||||
def get_member_attributes(self):
|
||||
""" Returns a dictionary of a balancer member's attributes."""
|
||||
|
||||
resp, info = fetch_url(self.module, self.management_url)
|
||||
resp, info = fetch_url(self.module, self.management_url, headers={'Referer': self.management_url})
|
||||
|
||||
if info['status'] != 200:
|
||||
self.module.fail_json(msg="Could not get balancer_member_page, check for connectivity! {0}".format(info))
|
||||
@@ -266,11 +277,11 @@ class BalancerMember(object):
|
||||
except TypeError as exc:
|
||||
self.module.fail_json(msg="Cannot parse balancer_member_page HTML! {0}".format(exc))
|
||||
else:
|
||||
subsoup = soup.findAll('table')[1].findAll('tr')
|
||||
keys = subsoup[0].findAll('th')
|
||||
subsoup = find_all(find_all(soup, 'table')[1], 'tr')
|
||||
keys = find_all(subsoup[0], 'th')
|
||||
for valuesset in subsoup[1::1]:
|
||||
if re.search(pattern=self.host, string=str(valuesset)):
|
||||
values = valuesset.findAll('td')
|
||||
values = find_all(valuesset, 'td')
|
||||
return {keys[x].string: values[x].string for x in range(0, len(keys))}
|
||||
|
||||
def get_member_status(self):
|
||||
@@ -294,9 +305,9 @@ class BalancerMember(object):
|
||||
values_url = "".join("{0}={1}".format(url_param, 1 if values[mode] else 0) for mode, url_param in values_mapping.items())
|
||||
request_body = "{0}{1}".format(request_body, values_url)
|
||||
|
||||
response, info = fetch_url(self.module, self.management_url, data=request_body)
|
||||
response, info = fetch_url(self.module, self.management_url, data=request_body, headers={'Referer': self.management_url})
|
||||
if info['status'] != 200:
|
||||
self.module.fail_json(msg="Could not set the member status! " + self.host + " " + info['status'])
|
||||
self.module.fail_json(msg="Could not set the member status! {host} {status}".format(host=self.host, status=info['status']))
|
||||
|
||||
attributes = property(get_member_attributes)
|
||||
status = property(get_member_status, set_member_status)
|
||||
@@ -333,11 +344,15 @@ class Balancer(object):
|
||||
if info['status'] != 200:
|
||||
self.module.fail_json(msg="Could not get balancer page! HTTP status response: {0}".format(info['status']))
|
||||
else:
|
||||
content = resp.read()
|
||||
content = to_text(resp.read())
|
||||
apache_version = regexp_extraction(content.upper(), APACHE_VERSION_EXPRESSION, 1)
|
||||
if apache_version:
|
||||
if not re.search(pattern=r"2\.4\.[\d]*", string=apache_version):
|
||||
self.module.fail_json(msg="This module only acts on an Apache2 2.4+ instance, current Apache2 version: " + str(apache_version))
|
||||
self.module.fail_json(
|
||||
msg="This module only acts on an Apache2 2.4+ instance, current Apache2 version: {version}".format(
|
||||
version=apache_version
|
||||
)
|
||||
)
|
||||
return content
|
||||
|
||||
self.module.fail_json(msg="Could not get the Apache server version from the balancer-manager")
|
||||
@@ -349,7 +364,8 @@ class Balancer(object):
|
||||
except TypeError:
|
||||
self.module.fail_json(msg="Cannot parse balancer page HTML! {0}".format(self.page))
|
||||
else:
|
||||
for element in soup.findAll('a')[1::1]:
|
||||
elements = find_all(soup, 'a')
|
||||
for element in elements[1::1]:
|
||||
balancer_member_suffix = str(element.get('href'))
|
||||
if not balancer_member_suffix:
|
||||
self.module.fail_json(msg="Argument 'balancer_member_suffix' is empty!")
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_alert_policy
|
||||
short_description: Create or Delete Alert Policies at CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create or Delete Alert Policies at CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_blueprint_package
|
||||
short_description: Deploys a blue print package on a set of servers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to deploy blue print package on a set of servers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_firewall_policy
|
||||
short_description: Create/delete/update firewall policies
|
||||
description:
|
||||
- Create or delete or update firewall policies on Centurylink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_group
|
||||
short_description: Create/delete Server Groups at Centurylink Cloud
|
||||
description:
|
||||
- Create or delete Server Groups at Centurylink Centurylink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_loadbalancer
|
||||
short_description: Create, Delete shared loadbalancers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create, Delete shared loadbalancers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_modify_server
|
||||
short_description: Modify servers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to modify servers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_publicip
|
||||
short_description: Add and Delete public IPs on servers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to add or delete public IP addresses on an existing server or servers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_server
|
||||
short_description: Create, Delete, Start and Stop servers in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create, Delete, Start and Stop servers in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -14,6 +14,12 @@ module: clc_server_snapshot
|
||||
short_description: Create, Delete and Restore server snapshots in CenturyLink Cloud
|
||||
description:
|
||||
- An Ansible module to Create, Delete and Restore server snapshots in CenturyLink Cloud.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: >
|
||||
Lumen Public Cloud (formerly known as CenturyLink Cloud) has gone End-of-Life in September 2023.
|
||||
See more at U(https://www.ctl.io/knowledge-base/release-notes/2023/lumen-public-cloud-platform-end-of-life-notice/?).
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.clc
|
||||
|
||||
@@ -163,33 +163,38 @@ def parse_error(string):
|
||||
|
||||
|
||||
def install_plugin(module, plugin_bin, plugin_name, version, src, url, proxy_host, proxy_port, timeout, force):
|
||||
cmd_args = [plugin_bin, PACKAGE_STATE_MAP["present"]]
|
||||
cmd = [plugin_bin, PACKAGE_STATE_MAP["present"]]
|
||||
is_old_command = (os.path.basename(plugin_bin) == 'plugin')
|
||||
|
||||
# Timeout and version are only valid for plugin, not elasticsearch-plugin
|
||||
if is_old_command:
|
||||
if timeout:
|
||||
cmd_args.append("--timeout %s" % timeout)
|
||||
cmd.append("--timeout")
|
||||
cmd.append(timeout)
|
||||
|
||||
if version:
|
||||
plugin_name = plugin_name + '/' + version
|
||||
cmd_args[2] = plugin_name
|
||||
cmd[2] = plugin_name
|
||||
|
||||
if proxy_host and proxy_port:
|
||||
cmd_args.append("-DproxyHost=%s -DproxyPort=%s" % (proxy_host, proxy_port))
|
||||
java_opts = ["-Dhttp.proxyHost=%s" % proxy_host,
|
||||
"-Dhttp.proxyPort=%s" % proxy_port,
|
||||
"-Dhttps.proxyHost=%s" % proxy_host,
|
||||
"-Dhttps.proxyPort=%s" % proxy_port]
|
||||
module.run_command_environ_update = dict(CLI_JAVA_OPTS=" ".join(java_opts), # Elasticsearch 8.x
|
||||
ES_JAVA_OPTS=" ".join(java_opts)) # Older Elasticsearch versions
|
||||
|
||||
# Legacy ES 1.x
|
||||
if url:
|
||||
cmd_args.append("--url %s" % url)
|
||||
cmd.append("--url")
|
||||
cmd.append(url)
|
||||
|
||||
if force:
|
||||
cmd_args.append("--batch")
|
||||
cmd.append("--batch")
|
||||
if src:
|
||||
cmd_args.append(src)
|
||||
cmd.append(src)
|
||||
else:
|
||||
cmd_args.append(plugin_name)
|
||||
|
||||
cmd = " ".join(cmd_args)
|
||||
cmd.append(plugin_name)
|
||||
|
||||
if module.check_mode:
|
||||
rc, out, err = 0, "check mode", ""
|
||||
@@ -204,9 +209,7 @@ def install_plugin(module, plugin_bin, plugin_name, version, src, url, proxy_hos
|
||||
|
||||
|
||||
def remove_plugin(module, plugin_bin, plugin_name):
|
||||
cmd_args = [plugin_bin, PACKAGE_STATE_MAP["absent"], parse_plugin_repo(plugin_name)]
|
||||
|
||||
cmd = " ".join(cmd_args)
|
||||
cmd = [plugin_bin, PACKAGE_STATE_MAP["absent"], parse_plugin_repo(plugin_name)]
|
||||
|
||||
if module.check_mode:
|
||||
rc, out, err = 0, "check mode", ""
|
||||
|
||||
@@ -270,6 +270,10 @@ def ensure(module, client):
|
||||
data = {}
|
||||
for key in diff:
|
||||
data[key] = module_host.get(key)
|
||||
if "usercertificate" not in data:
|
||||
data["usercertificate"] = [
|
||||
cert['__base64__'] for cert in ipa_host.get("usercertificate", [])
|
||||
]
|
||||
ipa_host_show = client.host_show(name=name)
|
||||
if ipa_host_show.get('has_keytab', True) and (state == 'disabled' or module.params.get('random_password')):
|
||||
client.host_disable(name=name)
|
||||
|
||||
@@ -59,6 +59,18 @@ options:
|
||||
- The personal access token to log-in with.
|
||||
- Mutually exclusive with O(username) and O(password).
|
||||
version_added: 4.2.0
|
||||
client_cert:
|
||||
type: path
|
||||
description:
|
||||
- Client certificate if required.
|
||||
- In addition to O(username) and O(password) or O(token). Not mutually exclusive.
|
||||
version_added: 10.4.0
|
||||
client_key:
|
||||
type: path
|
||||
description:
|
||||
- Client certificate key if required.
|
||||
- In addition to O(username) and O(password) or O(token). Not mutually exclusive.
|
||||
version_added: 10.4.0
|
||||
|
||||
project:
|
||||
type: str
|
||||
@@ -446,6 +458,23 @@ EXAMPLES = r"""
|
||||
operation: attach
|
||||
attachment:
|
||||
filename: topsecretreport.xlsx
|
||||
|
||||
# Use username, password and client certificate authentification
|
||||
- name: Create an issue
|
||||
community.general.jira:
|
||||
uri: '{{ server }}'
|
||||
username: '{{ user }}'
|
||||
password: '{{ pass }}'
|
||||
client_cert: '{{ path/to/client-cert }}'
|
||||
client_key: '{{ path/to/client-key }}'
|
||||
|
||||
# Use token and client certificate authentification
|
||||
- name: Create an issue
|
||||
community.general.jira:
|
||||
uri: '{{ server }}'
|
||||
token: '{{ token }}'
|
||||
client_cert: '{{ path/to/client-cert }}'
|
||||
client_key: '{{ path/to/client-key }}'
|
||||
"""
|
||||
|
||||
import base64
|
||||
@@ -480,6 +509,8 @@ class JIRA(StateModuleHelper):
|
||||
username=dict(type='str'),
|
||||
password=dict(type='str', no_log=True),
|
||||
token=dict(type='str', no_log=True),
|
||||
client_cert=dict(type='path'),
|
||||
client_key=dict(type='path'),
|
||||
project=dict(type='str', ),
|
||||
summary=dict(type='str', ),
|
||||
description=dict(type='str', ),
|
||||
@@ -511,6 +542,7 @@ class JIRA(StateModuleHelper):
|
||||
],
|
||||
required_together=[
|
||||
['username', 'password'],
|
||||
['client_cert', 'client_key']
|
||||
],
|
||||
required_one_of=[
|
||||
['username', 'token'],
|
||||
|
||||
@@ -720,7 +720,7 @@ end_state:
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
||||
keycloak_argument_spec, get_token, KeycloakError, is_struct_included
|
||||
keycloak_argument_spec, get_token, KeycloakError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import copy
|
||||
|
||||
@@ -771,6 +771,7 @@ def normalise_cr(clientrep, remove_ids=False):
|
||||
for key, value in clientrep['attributes'].items():
|
||||
if isinstance(value, bool):
|
||||
clientrep['attributes'][key] = str(value).lower()
|
||||
clientrep['attributes'].pop('client.secret.creation.time', None)
|
||||
return clientrep
|
||||
|
||||
|
||||
@@ -965,6 +966,11 @@ def main():
|
||||
else:
|
||||
before_client = kc.get_client_by_id(cid, realm=realm)
|
||||
|
||||
# kc drops the variable 'authorizationServicesEnabled' if set to false
|
||||
# to minimize diff/changes we set it to false if not set by kc
|
||||
if before_client and 'authorizationServicesEnabled' not in before_client:
|
||||
before_client['authorizationServicesEnabled'] = False
|
||||
|
||||
if before_client is None:
|
||||
before_client = {}
|
||||
|
||||
@@ -1036,7 +1042,7 @@ def main():
|
||||
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, CLIENT_META_DATA)
|
||||
result['changed'] = desired_norm != before_norm
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
@@ -22,7 +22,12 @@ attributes:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options: {}
|
||||
options:
|
||||
multivalues:
|
||||
description: If lldpctl outputs an attribute multiple time represent all values as a list.
|
||||
required: false
|
||||
type: bool
|
||||
default: false
|
||||
author: "Andy Hill (@andyhky)"
|
||||
notes:
|
||||
- Requires C(lldpd) running and LLDP enabled on switches.
|
||||
@@ -53,26 +58,49 @@ def gather_lldp(module):
|
||||
if output:
|
||||
output_dict = {}
|
||||
current_dict = {}
|
||||
lldp_entries = output.split("\n")
|
||||
lldp_entries = output.strip().split("\n")
|
||||
|
||||
final = ""
|
||||
for entry in lldp_entries:
|
||||
if entry.startswith('lldp'):
|
||||
path, value = entry.strip().split("=", 1)
|
||||
path = path.split(".")
|
||||
path_components, final = path[:-1], path[-1]
|
||||
elif final in current_dict and isinstance(current_dict[final], str):
|
||||
current_dict[final] += '\n' + entry
|
||||
continue
|
||||
elif final in current_dict and isinstance(current_dict[final], list):
|
||||
current_dict[final][-1] += '\n' + entry
|
||||
continue
|
||||
else:
|
||||
value = current_dict[final] + '\n' + entry
|
||||
continue
|
||||
|
||||
current_dict = output_dict
|
||||
for path_component in path_components:
|
||||
current_dict[path_component] = current_dict.get(path_component, {})
|
||||
if not isinstance(current_dict[path_component], dict):
|
||||
current_dict[path_component] = {'value': current_dict[path_component]}
|
||||
current_dict = current_dict[path_component]
|
||||
current_dict[final] = value
|
||||
|
||||
if final in current_dict and isinstance(current_dict[final], dict) and module.params['multivalues']:
|
||||
current_dict = current_dict[final]
|
||||
final = 'value'
|
||||
|
||||
if final not in current_dict or not module.params['multivalues']:
|
||||
current_dict[final] = value
|
||||
elif isinstance(current_dict[final], str):
|
||||
current_dict[final] = [current_dict[final], value]
|
||||
elif isinstance(current_dict[final], list):
|
||||
current_dict[final].append(value)
|
||||
|
||||
return output_dict
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule({})
|
||||
module_args = dict(
|
||||
multivalues=dict(type='bool', required=False, default=False)
|
||||
)
|
||||
module = AnsibleModule(module_args)
|
||||
|
||||
lldp_output = gather_lldp(module)
|
||||
try:
|
||||
|
||||
@@ -34,6 +34,7 @@ options:
|
||||
- List of comma-separated devices to use as physical devices in this volume group.
|
||||
- Required when creating or resizing volume group.
|
||||
- The module will take care of running pvcreate if needed.
|
||||
- O(remove_extra_pvs) controls whether or not unspecified physical devices are removed from the volume group.
|
||||
type: list
|
||||
elements: str
|
||||
pesize:
|
||||
@@ -88,6 +89,12 @@ options:
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 7.1.0
|
||||
remove_extra_pvs:
|
||||
description:
|
||||
- Remove physical volumes from the volume group which are not in O(pvs).
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 10.4.0
|
||||
seealso:
|
||||
- module: community.general.filesystem
|
||||
- module: community.general.lvol
|
||||
@@ -383,6 +390,7 @@ def main():
|
||||
force=dict(type='bool', default=False),
|
||||
reset_vg_uuid=dict(type='bool', default=False),
|
||||
reset_pv_uuid=dict(type='bool', default=False),
|
||||
remove_extra_pvs=dict(type="bool", default=True),
|
||||
),
|
||||
required_if=[
|
||||
['reset_pv_uuid', True, ['pvs']],
|
||||
@@ -399,6 +407,7 @@ def main():
|
||||
vgoptions = module.params['vg_options'].split()
|
||||
reset_vg_uuid = module.boolean(module.params['reset_vg_uuid'])
|
||||
reset_pv_uuid = module.boolean(module.params['reset_pv_uuid'])
|
||||
remove_extra_pvs = module.boolean(module.params["remove_extra_pvs"])
|
||||
|
||||
this_vg = find_vg(module=module, vg=vg)
|
||||
present_state = state in ['present', 'active', 'inactive']
|
||||
@@ -494,6 +503,9 @@ def main():
|
||||
devs_to_remove = list(set(current_devs) - set(dev_list))
|
||||
devs_to_add = list(set(dev_list) - set(current_devs))
|
||||
|
||||
if not remove_extra_pvs:
|
||||
devs_to_remove = []
|
||||
|
||||
if current_devs:
|
||||
if present_state:
|
||||
for device in current_devs:
|
||||
|
||||
@@ -79,13 +79,14 @@ options:
|
||||
- Type V(ovs-port) is added in community.general 8.6.0.
|
||||
- Type V(wireguard) is added in community.general 4.3.0.
|
||||
- Type V(vpn) is added in community.general 5.1.0.
|
||||
- Type V(vrf) is added in community.general 10.4.0.
|
||||
- Using V(bond-slave), V(bridge-slave), or V(team-slave) implies V(ethernet) connection type with corresponding O(slave_type)
|
||||
option.
|
||||
- If you want to control non-ethernet connection attached to V(bond), V(bridge), or V(team) consider using O(slave_type)
|
||||
option.
|
||||
type: str
|
||||
choices: [bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, macvlan, sit, team,
|
||||
team-slave, vlan, vxlan, wifi, gsm, wireguard, ovs-bridge, ovs-port, ovs-interface, vpn, loopback]
|
||||
team-slave, vlan, vxlan, wifi, gsm, wireguard, ovs-bridge, ovs-port, ovs-interface, vpn, vrf, loopback]
|
||||
mode:
|
||||
description:
|
||||
- This is the type of device or network connection that you wish to create for a bond or bridge.
|
||||
@@ -103,7 +104,7 @@ options:
|
||||
- Type of the device of this slave's master connection (for example V(bond)).
|
||||
- Type V(ovs-port) is added in community.general 8.6.0.
|
||||
type: str
|
||||
choices: ['bond', 'bridge', 'team', 'ovs-port']
|
||||
choices: ['bond', 'bridge', 'team', 'ovs-port', 'vrf']
|
||||
version_added: 7.0.0
|
||||
master:
|
||||
description:
|
||||
@@ -521,6 +522,11 @@ options:
|
||||
- Only used when O(type=gre).
|
||||
type: str
|
||||
version_added: 3.6.0
|
||||
table:
|
||||
description:
|
||||
- This is only used with VRF - VRF table number.
|
||||
type: int
|
||||
version_added: 10.4.0
|
||||
zone:
|
||||
description:
|
||||
- The trust level of the connection.
|
||||
@@ -1569,6 +1575,29 @@ EXAMPLES = r"""
|
||||
vlanid: 5
|
||||
state: present
|
||||
|
||||
## Creating VRF and adding VLAN interface to it
|
||||
- name: Create VRF
|
||||
community.general.nmcli:
|
||||
type: vrf
|
||||
ifname: vrf10
|
||||
table: 10
|
||||
state: present
|
||||
conn_name: vrf10
|
||||
method4: disabled
|
||||
method6: disabled
|
||||
|
||||
- name: Create VLAN interface inside VRF
|
||||
community.general.nmcli:
|
||||
conn_name: "eth0.124"
|
||||
type: vlan
|
||||
vlanid: "124"
|
||||
vlandev: "eth0"
|
||||
master: "vrf10"
|
||||
slave_type: vrf
|
||||
state: "present"
|
||||
ip4: '192.168.124.50'
|
||||
gw4: '192.168.124.1'
|
||||
|
||||
## Defining ip rules while setting a static IP
|
||||
## table 'production' is set with id 200 in this example.
|
||||
- name: Set Static ips for interface with ip rules and routes
|
||||
@@ -1755,6 +1784,9 @@ class Nmcli(object):
|
||||
else:
|
||||
self.ipv6_method = None
|
||||
|
||||
if self.type == "vrf":
|
||||
self.table = module.params['table']
|
||||
|
||||
self.edit_commands = []
|
||||
|
||||
self.extra_options_validation()
|
||||
@@ -1787,7 +1819,8 @@ class Nmcli(object):
|
||||
|
||||
# IP address options.
|
||||
# The ovs-interface type can be both ip_conn_type and have a master
|
||||
if (self.ip_conn_type and not self.master) or self.type == "ovs-interface":
|
||||
# An interface that has a master but is of slave type vrf can have an IP address
|
||||
if (self.ip_conn_type and (not self.master or self.slave_type == "vrf")) or self.type == "ovs-interface":
|
||||
options.update({
|
||||
'ipv4.addresses': self.enforce_ipv4_cidr_notation(self.ip4),
|
||||
'ipv4.dhcp-client-id': self.dhcp_client_id,
|
||||
@@ -2001,6 +2034,10 @@ class Nmcli(object):
|
||||
options.update({
|
||||
'infiniband.transport-mode': self.transport_mode,
|
||||
})
|
||||
elif self.type == 'vrf':
|
||||
options.update({
|
||||
'table': self.table,
|
||||
})
|
||||
|
||||
if self.type == 'ethernet':
|
||||
if self.sriov:
|
||||
@@ -2057,6 +2094,7 @@ class Nmcli(object):
|
||||
'vpn',
|
||||
'loopback',
|
||||
'ovs-interface',
|
||||
'vrf'
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -2528,7 +2566,7 @@ def main():
|
||||
conn_name=dict(type='str', required=True),
|
||||
conn_reload=dict(type='bool', default=False),
|
||||
master=dict(type='str'),
|
||||
slave_type=dict(type='str', choices=['bond', 'bridge', 'team', 'ovs-port']),
|
||||
slave_type=dict(type='str', choices=['bond', 'bridge', 'team', 'ovs-port', 'vrf']),
|
||||
ifname=dict(type='str'),
|
||||
type=dict(type='str',
|
||||
choices=[
|
||||
@@ -2556,6 +2594,7 @@ def main():
|
||||
'ovs-interface',
|
||||
'ovs-bridge',
|
||||
'ovs-port',
|
||||
'vrf',
|
||||
]),
|
||||
ip4=dict(type='list', elements='str'),
|
||||
gw4=dict(type='str'),
|
||||
@@ -2669,6 +2708,7 @@ def main():
|
||||
vpn=dict(type='dict'),
|
||||
transport_mode=dict(type='str', choices=['datagram', 'connected']),
|
||||
sriov=dict(type='dict'),
|
||||
table=dict(type='int'),
|
||||
),
|
||||
mutually_exclusive=[['never_default4', 'gw4'],
|
||||
['routes4_extended', 'routes4'],
|
||||
|
||||
@@ -14,6 +14,13 @@ short_description: Create, destroy, start, stop, and reboot a ProfitBricks virtu
|
||||
description:
|
||||
- Create, destroy, update, start, stop, and reboot a ProfitBricks virtual machine. When the virtual machine is created it
|
||||
can optionally wait for it to be 'running' before returning. This module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
@@ -14,6 +14,14 @@ short_description: Create or destroy a ProfitBricks Virtual Datacenter
|
||||
description:
|
||||
- This is a simple module that supports creating or removing vDCs. A vDC is required before you can create servers. This
|
||||
module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
@@ -13,6 +13,13 @@ module: profitbricks_nic
|
||||
short_description: Create or Remove a NIC
|
||||
description:
|
||||
- This module allows you to create or restore a volume snapshot. This module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
@@ -14,6 +14,13 @@ short_description: Create or destroy a volume
|
||||
description:
|
||||
- Allows you to create or remove a volume from a ProfitBricks datacenter. This module has a dependency on profitbricks >=
|
||||
1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
@@ -13,6 +13,13 @@ module: profitbricks_volume_attachments
|
||||
short_description: Attach or detach a volume
|
||||
description:
|
||||
- Allows you to attach or detach a volume from a ProfitBricks server. This module has a dependency on profitbricks >= 1.0.0.
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Module relies on library unsupported since 2021.
|
||||
alternative: >
|
||||
Profitbricks has rebranded as Ionos Cloud and they provide a collection named ionoscloudsdk.ionoscloud.
|
||||
Whilst it is likely it will provide the features of this module, that has not been verified.
|
||||
Please refer to that collection's documentation for more details.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
@@ -455,8 +455,9 @@ options:
|
||||
- Indicates desired state of the instance.
|
||||
- If V(current), the current state of the VM will be fetched. You can access it with C(results.status).
|
||||
- V(template) was added in community.general 8.1.0.
|
||||
- V(paused) and V(hibernated) were added in community.general 10.4.0.
|
||||
type: str
|
||||
choices: ['present', 'started', 'absent', 'stopped', 'restarted', 'current', 'template']
|
||||
choices: ['present', 'started', 'absent', 'stopped', 'restarted', 'current', 'template', 'paused', 'hibernated']
|
||||
default: present
|
||||
storage:
|
||||
description:
|
||||
@@ -1208,6 +1209,16 @@ class ProxmoxKvmAnsible(ProxmoxAnsible):
|
||||
return False
|
||||
return True
|
||||
|
||||
def suspend_vm(self, vm, timeout, todisk):
|
||||
vmid = vm['vmid']
|
||||
proxmox_node = self.proxmox_api.nodes(vm['node'])
|
||||
taskid = proxmox_node.qemu(vmid).status.suspend.post(todisk=(1 if todisk else 0), timeout=timeout)
|
||||
if not self.wait_for_task(vm['node'], taskid):
|
||||
self.module.fail_json(msg='Reached timeout while waiting for suspending VM. Last line in task before timeout: %s' %
|
||||
proxmox_node.tasks(taskid).log.get()[:1])
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
module_args = proxmox_auth_argument_spec()
|
||||
@@ -1287,7 +1298,7 @@ def main():
|
||||
sshkeys=dict(type='str', no_log=False),
|
||||
startdate=dict(type='str'),
|
||||
startup=dict(),
|
||||
state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted', 'current', 'template']),
|
||||
state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted', 'current', 'template', 'paused', 'hibernated']),
|
||||
storage=dict(type='str'),
|
||||
tablet=dict(type='bool'),
|
||||
tags=dict(type='list', elements='str'),
|
||||
@@ -1611,6 +1622,23 @@ def main():
|
||||
if status:
|
||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s with vmid = %s is %s" % (name, vmid, current), **status)
|
||||
|
||||
elif state in ['paused', 'hibernated']:
|
||||
if not vmid:
|
||||
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
||||
|
||||
status = {}
|
||||
try:
|
||||
vm = proxmox.get_vm(vmid)
|
||||
current = proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).status.current.get()['status']
|
||||
status['status'] = current
|
||||
if current != 'running':
|
||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is not running" % vmid, **status)
|
||||
|
||||
proxmox.suspend_vm(vm, force=module.params['force'], timeout=module.params['timeout'], todisk=(state == 'hibernated'))
|
||||
module.exit_json(changed=True, vmid=vmid, msg="VM %s is suspending" % vmid, **status)
|
||||
except Exception as e:
|
||||
module.fail_json(vmid=vmid, msg="suspending of VM %s failed with exception: %s" % (vmid, e), **status)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -853,8 +853,10 @@ from ansible.module_utils.common.text.converters import to_native
|
||||
# More will be added as module features are expanded
|
||||
CATEGORY_COMMANDS_ALL = {
|
||||
"Systems": ["PowerOn", "PowerForceOff", "PowerForceRestart", "PowerGracefulRestart",
|
||||
"PowerGracefulShutdown", "PowerReboot", "PowerCycle", "SetOneTimeBoot", "EnableContinuousBootOverride", "DisableBootOverride",
|
||||
"IndicatorLedOn", "IndicatorLedOff", "IndicatorLedBlink", "VirtualMediaInsert", "VirtualMediaEject", "VerifyBiosAttributes"],
|
||||
"PowerGracefulShutdown", "PowerReboot", "PowerCycle", "PowerFullPowerCycle",
|
||||
"SetOneTimeBoot", "EnableContinuousBootOverride", "DisableBootOverride",
|
||||
"IndicatorLedOn", "IndicatorLedOff", "IndicatorLedBlink", "VirtualMediaInsert",
|
||||
"VirtualMediaEject", "VerifyBiosAttributes"],
|
||||
"Chassis": ["IndicatorLedOn", "IndicatorLedOff", "IndicatorLedBlink"],
|
||||
"Accounts": ["AddUser", "EnableUser", "DeleteUser", "DisableUser",
|
||||
"UpdateUserRole", "UpdateUserPassword", "UpdateUserName",
|
||||
|
||||
@@ -543,6 +543,45 @@ class Rhsm(object):
|
||||
(distro_version[0] == 9 and distro_version[1] >= 2) or
|
||||
distro_version[0] > 9)):
|
||||
dbus_force_option_works = True
|
||||
# We need to use the 'enable_content' D-Bus option to ensure that
|
||||
# content is enabled; sadly the option is available depending on the
|
||||
# version of the distro, and also depending on which API/method is used
|
||||
# for registration.
|
||||
dbus_has_enable_content_option = False
|
||||
if activationkey:
|
||||
def supports_enable_content_for_activation_keys():
|
||||
# subscription-manager in Fedora >= 41 has the new option.
|
||||
if distro_id == 'fedora' and distro_version[0] >= 41:
|
||||
return True
|
||||
# Assume EL distros here.
|
||||
if distro_version[0] >= 10:
|
||||
return True
|
||||
return False
|
||||
dbus_has_enable_content_option = supports_enable_content_for_activation_keys()
|
||||
else:
|
||||
def supports_enable_content_for_credentials():
|
||||
# subscription-manager in any supported Fedora version
|
||||
# has the new option.
|
||||
if distro_id == 'fedora':
|
||||
return True
|
||||
# Check for RHEL 8 >= 8.6, or RHEL >= 9.
|
||||
if distro_id == 'rhel' and \
|
||||
((distro_version[0] == 8 and distro_version[1] >= 6) or
|
||||
distro_version[0] >= 9):
|
||||
return True
|
||||
# CentOS: similar checks as for RHEL, with one extra bit:
|
||||
# if the 2nd part of the version is empty, it means it is
|
||||
# CentOS Stream, and thus we can assume it has the latest
|
||||
# version of subscription-manager.
|
||||
if distro_id == 'centos' and \
|
||||
((distro_version[0] == 8 and
|
||||
(distro_version[1] >= 6 or distro_version_parts[1] == '')) or
|
||||
distro_version[0] >= 9):
|
||||
return True
|
||||
# Unknown or old distro: assume it does not support
|
||||
# the new option.
|
||||
return False
|
||||
dbus_has_enable_content_option = supports_enable_content_for_credentials()
|
||||
|
||||
if force_register and not dbus_force_option_works and was_registered:
|
||||
self.unregister()
|
||||
@@ -615,6 +654,8 @@ class Rhsm(object):
|
||||
register_opts[environment_key] = environment
|
||||
if force_register and dbus_force_option_works and was_registered:
|
||||
register_opts['force'] = True
|
||||
if dbus_has_enable_content_option:
|
||||
register_opts['enable_content'] = "1"
|
||||
# Wrap it as proper D-Bus dict
|
||||
register_opts = dbus.Dictionary(register_opts, signature='sv', variant_level=1)
|
||||
|
||||
|
||||
@@ -139,6 +139,13 @@ options:
|
||||
- Sets the C(DynamicForward) option.
|
||||
type: str
|
||||
version_added: 10.1.0
|
||||
other_options:
|
||||
description:
|
||||
- Provides the option to specify arbitrary SSH config entry options via a dictionary.
|
||||
- The key names must be lower case. Keys with upper case values are rejected.
|
||||
- The values must be strings. Other values are rejected.
|
||||
type: dict
|
||||
version_added: 10.4.0
|
||||
requirements:
|
||||
- paramiko
|
||||
"""
|
||||
@@ -152,6 +159,8 @@ EXAMPLES = r"""
|
||||
identity_file: "/home/akasurde/.ssh/id_rsa"
|
||||
port: '2223'
|
||||
state: present
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
|
||||
- name: Delete a host from the configuration
|
||||
community.general.ssh_config:
|
||||
@@ -204,6 +213,7 @@ from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible_collections.community.general.plugins.module_utils._stormssh import ConfigParser, HAS_PARAMIKO, PARAMIKO_IMPORT_ERROR
|
||||
from ansible_collections.community.general.plugins.module_utils.ssh import determine_config_file
|
||||
|
||||
@@ -274,6 +284,17 @@ class SSHConfig(object):
|
||||
controlpersist=fix_bool_str(self.params.get('controlpersist')),
|
||||
dynamicforward=self.params.get('dynamicforward'),
|
||||
)
|
||||
if self.params.get('other_options'):
|
||||
for key, value in self.params.get('other_options').items():
|
||||
if key.lower() != key:
|
||||
self.module.fail_json(msg="The other_options key {key!r} must be lower case".format(key=key))
|
||||
if key not in args:
|
||||
if not isinstance(value, string_types):
|
||||
self.module.fail_json(msg="The other_options value provided for key {key!r} must be a string, got {type}".format(key=key,
|
||||
type=type(value)))
|
||||
args[key] = value
|
||||
else:
|
||||
self.module.fail_json(msg="Multiple values provided for key {key!r}".format(key=key))
|
||||
|
||||
config_changed = False
|
||||
hosts_changed = []
|
||||
@@ -361,6 +382,7 @@ def main():
|
||||
host_key_algorithms=dict(type='str', no_log=False),
|
||||
identity_file=dict(type='path'),
|
||||
identities_only=dict(type='bool'),
|
||||
other_options=dict(type='dict'),
|
||||
port=dict(type='str'),
|
||||
proxycommand=dict(type='str', default=None),
|
||||
proxyjump=dict(type='str', default=None),
|
||||
|
||||
361
plugins/modules/systemd_info.py
Normal file
361
plugins/modules/systemd_info.py
Normal file
@@ -0,0 +1,361 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2025, Marco Noce <nce.marco@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 = r'''
|
||||
---
|
||||
module: systemd_info
|
||||
short_description: Gather C(systemd) unit info
|
||||
description:
|
||||
- This module gathers info about systemd units (services, targets, sockets, mount).
|
||||
- It runs C(systemctl list-units) (or processes selected units) and collects properties
|
||||
for each unit using C(systemctl show).
|
||||
- Even if a unit has a RV(units.loadstate) of V(not-found) or V(masked), it is returned,
|
||||
but only with the minimal properties (RV(units.name), RV(units.loadstate), RV(units.activestate), RV(units.substate)).
|
||||
- When O(unitname) and O(extra_properties) are used, the module first checks if the unit exists,
|
||||
then check if properties exist. If not, the module fails.
|
||||
version_added: "10.4.0"
|
||||
options:
|
||||
unitname:
|
||||
description:
|
||||
- List of unit names to process.
|
||||
- It supports C(.service), C(.target), C(.socket), and C(.mount) units type.
|
||||
- Each name must correspond to the full name of the C(systemd) unit.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
extra_properties:
|
||||
description:
|
||||
- Additional properties to retrieve (appended to the default ones).
|
||||
- Note that all property names are converted to lower-case.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
author:
|
||||
- Marco Noce (@NomakCooper)
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
---
|
||||
# Gather info for all systemd services, targets, sockets and mount
|
||||
- name: Gather all systemd unit info
|
||||
community.general.systemd_info:
|
||||
register: results
|
||||
|
||||
# Gather info for selected units with extra properties.
|
||||
- name: Gather info for selected unit(s)
|
||||
community.general.systemd_info:
|
||||
unitname:
|
||||
- systemd-journald.service
|
||||
- systemd-journald.socket
|
||||
- sshd-keygen.target
|
||||
- -.mount
|
||||
extra_properties:
|
||||
- Description
|
||||
register: results
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
units:
|
||||
description:
|
||||
- Dictionary of systemd unit info keyed by unit name.
|
||||
- Additional fields will be returned depending on the value of O(extra_properties).
|
||||
returned: success
|
||||
type: dict
|
||||
elements: dict
|
||||
contains:
|
||||
name:
|
||||
description: Unit full name.
|
||||
returned: always
|
||||
type: str
|
||||
sample: systemd-journald.service
|
||||
loadstate:
|
||||
description:
|
||||
- The state of the unit's configuration load.
|
||||
- The most common values are V(loaded), V(not-found), and V(masked), but other values are possible as well.
|
||||
returned: always
|
||||
type: str
|
||||
sample: loaded
|
||||
activestate:
|
||||
description:
|
||||
- The current active state of the unit.
|
||||
- The most common values are V(active), V(inactive), and V(failed), but other values are possible as well.
|
||||
returned: always
|
||||
type: str
|
||||
sample: active
|
||||
substate:
|
||||
description:
|
||||
- The detailed sub state of the unit.
|
||||
- The most common values are V(running), V(dead), V(exited), V(failed), V(listening), V(active), and V(mounted), but other values are possible as well.
|
||||
returned: always
|
||||
type: str
|
||||
sample: running
|
||||
fragmentpath:
|
||||
description: Path to the unit's fragment file.
|
||||
returned: always except for C(.mount) units.
|
||||
type: str
|
||||
sample: /usr/lib/systemd/system/systemd-journald.service
|
||||
unitfilepreset:
|
||||
description:
|
||||
- The preset configuration state for the unit file.
|
||||
- The most common values are V(enabled), V(disabled), and V(static), but other values are possible as well.
|
||||
returned: always except for C(.mount) units.
|
||||
type: str
|
||||
sample: disabled
|
||||
unitfilestate:
|
||||
description:
|
||||
- The actual configuration state for the unit file.
|
||||
- The most common values are V(enabled), V(disabled), and V(static), but other values are possible as well.
|
||||
returned: always except for C(.mount) units.
|
||||
type: str
|
||||
sample: enabled
|
||||
mainpid:
|
||||
description: PID of the main process of the unit.
|
||||
returned: only for C(.service) units.
|
||||
type: str
|
||||
sample: 798
|
||||
execmainpid:
|
||||
description: PID of the ExecStart process of the unit.
|
||||
returned: only for C(.service) units.
|
||||
type: str
|
||||
sample: 799
|
||||
options:
|
||||
description: The mount options.
|
||||
returned: only for C(.mount) units.
|
||||
type: str
|
||||
sample: rw,relatime,noquota
|
||||
type:
|
||||
description: The filesystem type of the mounted device.
|
||||
returned: only for C(.mount) units.
|
||||
type: str
|
||||
sample: ext4
|
||||
what:
|
||||
description: The device that is mounted.
|
||||
returned: only for C(.mount) units.
|
||||
type: str
|
||||
sample: /dev/sda1
|
||||
where:
|
||||
description: The mount point where the device is mounted.
|
||||
returned: only for C(.mount) units.
|
||||
type: str
|
||||
sample: /
|
||||
sample: {
|
||||
"-.mount": {
|
||||
"activestate": "active",
|
||||
"description": "Root Mount",
|
||||
"loadstate": "loaded",
|
||||
"name": "-.mount",
|
||||
"options": "rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota",
|
||||
"substate": "mounted",
|
||||
"type": "xfs",
|
||||
"what": "/dev/mapper/cs-root",
|
||||
"where": "/"
|
||||
},
|
||||
"sshd-keygen.target": {
|
||||
"activestate": "active",
|
||||
"description": "sshd-keygen.target",
|
||||
"fragmentpath": "/usr/lib/systemd/system/sshd-keygen.target",
|
||||
"loadstate": "loaded",
|
||||
"name": "sshd-keygen.target",
|
||||
"substate": "active",
|
||||
"unitfilepreset": "disabled",
|
||||
"unitfilestate": "static"
|
||||
},
|
||||
"systemd-journald.service": {
|
||||
"activestate": "active",
|
||||
"description": "Journal Service",
|
||||
"execmainpid": "613",
|
||||
"fragmentpath": "/usr/lib/systemd/system/systemd-journald.service",
|
||||
"loadstate": "loaded",
|
||||
"mainpid": "613",
|
||||
"name": "systemd-journald.service",
|
||||
"substate": "running",
|
||||
"unitfilepreset": "disabled",
|
||||
"unitfilestate": "static"
|
||||
},
|
||||
"systemd-journald.socket": {
|
||||
"activestate": "active",
|
||||
"description": "Journal Socket",
|
||||
"fragmentpath": "/usr/lib/systemd/system/systemd-journald.socket",
|
||||
"loadstate": "loaded",
|
||||
"name": "systemd-journald.socket",
|
||||
"substate": "running",
|
||||
"unitfilepreset": "disabled",
|
||||
"unitfilestate": "static"
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def run_command(module, cmd):
|
||||
rc, stdout, stderr = module.run_command(cmd, check_rc=True)
|
||||
return stdout.strip()
|
||||
|
||||
|
||||
def parse_show_output(output):
|
||||
result = {}
|
||||
for line in output.splitlines():
|
||||
if "=" in line:
|
||||
key, val = line.split("=", 1)
|
||||
key = key.lower()
|
||||
if key not in result:
|
||||
result[key] = val
|
||||
return result
|
||||
|
||||
|
||||
def get_unit_properties(module, systemctl_bin, unit, prop_list):
|
||||
cmd = [systemctl_bin, "show", "-p", ",".join(prop_list), "--", unit]
|
||||
output = run_command(module, cmd)
|
||||
return parse_show_output(output)
|
||||
|
||||
|
||||
def determine_category(unit):
|
||||
if unit.endswith('.service'):
|
||||
return 'service'
|
||||
elif unit.endswith('.target'):
|
||||
return 'target'
|
||||
elif unit.endswith('.socket'):
|
||||
return 'socket'
|
||||
elif unit.endswith('.mount'):
|
||||
return 'mount'
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def extract_unit_properties(unit_data, prop_list):
|
||||
lowerprop = [x.lower() for x in prop_list]
|
||||
extracted = {
|
||||
prop: unit_data[prop] for prop in lowerprop if prop in unit_data
|
||||
}
|
||||
return extracted
|
||||
|
||||
|
||||
def unit_exists(module, systemctl_bin, unit):
|
||||
cmd = [systemctl_bin, "show", "-p", "LoadState", "--", unit]
|
||||
rc, stdout, stderr = module.run_command(cmd)
|
||||
return (rc == 0)
|
||||
|
||||
|
||||
def validate_unit_and_properties(module, systemctl_bin, unit, extra_properties):
|
||||
cmd = [systemctl_bin, "show", "-p", "LoadState", "--", unit]
|
||||
|
||||
output = run_command(module, cmd)
|
||||
if "loadstate=not-found" in output.lower():
|
||||
module.fail_json(msg="Unit '{0}' does not exist or is inaccessible.".format(unit))
|
||||
|
||||
if extra_properties:
|
||||
unit_data = get_unit_properties(module, systemctl_bin, unit, extra_properties)
|
||||
missing_props = [prop for prop in extra_properties if prop.lower() not in unit_data]
|
||||
if missing_props:
|
||||
module.fail_json(msg="The following properties do not exist for unit '{0}': {1}".format(unit, ", ".join(missing_props)))
|
||||
|
||||
|
||||
def main():
|
||||
module_args = dict(
|
||||
unitname=dict(type='list', elements='str', default=[]),
|
||||
extra_properties=dict(type='list', elements='str', default=[])
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
systemctl_bin = module.get_bin_path('systemctl', required=True)
|
||||
|
||||
run_command(module, [systemctl_bin, '--version'])
|
||||
|
||||
base_properties = {
|
||||
'service': ['FragmentPath', 'UnitFileState', 'UnitFilePreset', 'MainPID', 'ExecMainPID'],
|
||||
'target': ['FragmentPath', 'UnitFileState', 'UnitFilePreset'],
|
||||
'socket': ['FragmentPath', 'UnitFileState', 'UnitFilePreset'],
|
||||
'mount': ['Where', 'What', 'Options', 'Type']
|
||||
}
|
||||
state_props = ['LoadState', 'ActiveState', 'SubState']
|
||||
|
||||
results = {}
|
||||
|
||||
if not module.params['unitname']:
|
||||
list_cmd = [
|
||||
systemctl_bin, "list-units",
|
||||
"--no-pager",
|
||||
"--type", "service,target,socket,mount",
|
||||
"--all",
|
||||
"--plain",
|
||||
"--no-legend"
|
||||
]
|
||||
list_output = run_command(module, list_cmd)
|
||||
for line in list_output.splitlines():
|
||||
tokens = line.split()
|
||||
if len(tokens) < 4:
|
||||
continue
|
||||
|
||||
unit_name = tokens[0]
|
||||
loadstate = tokens[1]
|
||||
activestate = tokens[2]
|
||||
substate = tokens[3]
|
||||
|
||||
fact = {
|
||||
"name": unit_name,
|
||||
"loadstate": loadstate,
|
||||
"activestate": activestate,
|
||||
"substate": substate
|
||||
}
|
||||
|
||||
if loadstate in ("not-found", "masked"):
|
||||
results[unit_name] = fact
|
||||
continue
|
||||
|
||||
category = determine_category(unit_name)
|
||||
if not category:
|
||||
results[unit_name] = fact
|
||||
continue
|
||||
|
||||
props = base_properties.get(category, [])
|
||||
full_props = set(props + state_props)
|
||||
unit_data = get_unit_properties(module, systemctl_bin, unit_name, full_props)
|
||||
|
||||
fact.update(extract_unit_properties(unit_data, full_props))
|
||||
results[unit_name] = fact
|
||||
|
||||
else:
|
||||
selected_units = module.params['unitname']
|
||||
extra_properties = module.params['extra_properties']
|
||||
|
||||
for unit in selected_units:
|
||||
validate_unit_and_properties(module, systemctl_bin, unit, extra_properties)
|
||||
category = determine_category(unit)
|
||||
|
||||
if not category:
|
||||
module.fail_json(msg="Could not determine the category for unit '{0}'.".format(unit))
|
||||
|
||||
props = base_properties.get(category, [])
|
||||
full_props = set(props + state_props + extra_properties)
|
||||
unit_data = get_unit_properties(module, systemctl_bin, unit, full_props)
|
||||
fact = {"name": unit}
|
||||
minimal_keys = ["LoadState", "ActiveState", "SubState"]
|
||||
|
||||
fact.update(extract_unit_properties(unit_data, minimal_keys))
|
||||
|
||||
ls = unit_data.get("loadstate", "").lower()
|
||||
if ls not in ("not-found", "masked"):
|
||||
fact.update(extract_unit_properties(unit_data, full_props))
|
||||
|
||||
results[unit] = fact
|
||||
|
||||
module.exit_json(changed=False, units=results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -98,10 +98,10 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
class Zfs(object):
|
||||
|
||||
def __init__(self, module, name, properties):
|
||||
def __init__(self, module, name, extra_zfs_properties):
|
||||
self.module = module
|
||||
self.name = name
|
||||
self.properties = properties
|
||||
self.extra_zfs_properties = extra_zfs_properties
|
||||
self.changed = False
|
||||
self.zfs_cmd = module.get_bin_path('zfs', True)
|
||||
self.zpool_cmd = module.get_bin_path('zpool', True)
|
||||
@@ -142,7 +142,7 @@ class Zfs(object):
|
||||
if self.module.check_mode:
|
||||
self.changed = True
|
||||
return
|
||||
properties = self.properties
|
||||
extra_zfs_properties = self.extra_zfs_properties
|
||||
origin = self.module.params.get('origin')
|
||||
cmd = [self.zfs_cmd]
|
||||
|
||||
@@ -158,8 +158,8 @@ class Zfs(object):
|
||||
if action in ['create', 'clone']:
|
||||
cmd += ['-p']
|
||||
|
||||
if properties:
|
||||
for prop, value in properties.items():
|
||||
if extra_zfs_properties:
|
||||
for prop, value in extra_zfs_properties.items():
|
||||
if prop == 'volsize':
|
||||
cmd += ['-V', value]
|
||||
elif prop == 'volblocksize':
|
||||
@@ -189,45 +189,62 @@ class Zfs(object):
|
||||
|
||||
def set_properties_if_changed(self):
|
||||
diff = {'before': {'extra_zfs_properties': {}}, 'after': {'extra_zfs_properties': {}}}
|
||||
current_properties = self.get_current_properties()
|
||||
for prop, value in self.properties.items():
|
||||
current_value = current_properties.get(prop, None)
|
||||
current_properties = self.list_properties()
|
||||
for prop, value in self.extra_zfs_properties.items():
|
||||
current_value = self.get_property(prop, current_properties)
|
||||
if current_value != value:
|
||||
self.set_property(prop, value)
|
||||
diff['before']['extra_zfs_properties'][prop] = current_value
|
||||
diff['after']['extra_zfs_properties'][prop] = value
|
||||
if self.module.check_mode:
|
||||
return diff
|
||||
updated_properties = self.get_current_properties()
|
||||
for prop in self.properties:
|
||||
value = updated_properties.get(prop, None)
|
||||
updated_properties = self.list_properties()
|
||||
for prop in self.extra_zfs_properties:
|
||||
value = self.get_property(prop, updated_properties)
|
||||
if value is None:
|
||||
self.module.fail_json(msg="zfsprop was not present after being successfully set: %s" % prop)
|
||||
if current_properties.get(prop, None) != value:
|
||||
if self.get_property(prop, current_properties) != value:
|
||||
self.changed = True
|
||||
if prop in diff['after']['extra_zfs_properties']:
|
||||
diff['after']['extra_zfs_properties'][prop] = value
|
||||
return diff
|
||||
|
||||
def get_current_properties(self):
|
||||
cmd = [self.zfs_cmd, 'get', '-H', '-p', '-o', "property,value,source"]
|
||||
def list_properties(self):
|
||||
cmd = [self.zfs_cmd, 'get', '-H', '-p', '-o', "property,source"]
|
||||
if self.enhanced_sharing:
|
||||
cmd += ['-e']
|
||||
cmd += ['all', self.name]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
properties = dict()
|
||||
properties = []
|
||||
for line in out.splitlines():
|
||||
prop, value, source = line.split('\t')
|
||||
prop, source = line.split('\t')
|
||||
# include source '-' so that creation-only properties are not removed
|
||||
# to avoids errors when the dataset already exists and the property is not changed
|
||||
# this scenario is most likely when the same playbook is run more than once
|
||||
if source in ('local', 'received', '-'):
|
||||
properties[prop] = value
|
||||
properties.append(prop)
|
||||
return properties
|
||||
|
||||
def get_property(self, name, list_of_properties):
|
||||
# Add alias for enhanced sharing properties
|
||||
if self.enhanced_sharing:
|
||||
properties['sharenfs'] = properties.get('share.nfs', None)
|
||||
properties['sharesmb'] = properties.get('share.smb', None)
|
||||
return properties
|
||||
if name == 'sharenfs':
|
||||
name = 'share.nfs'
|
||||
elif name == 'sharesmb':
|
||||
name = 'share.smb'
|
||||
if name not in list_of_properties:
|
||||
return None
|
||||
cmd = [self.zfs_cmd, 'get', '-H', '-p', '-o', "value"]
|
||||
if self.enhanced_sharing:
|
||||
cmd += ['-e']
|
||||
cmd += [name, self.name]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
return None
|
||||
#
|
||||
# Strip last newline
|
||||
#
|
||||
return out[:-1]
|
||||
|
||||
|
||||
def main():
|
||||
@@ -282,7 +299,7 @@ def main():
|
||||
result['diff']['before_header'] = name
|
||||
result['diff']['after_header'] = name
|
||||
|
||||
result.update(zfs.properties)
|
||||
result.update(zfs.extra_zfs_properties)
|
||||
result['changed'] = zfs.changed
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
@@ -44,10 +44,12 @@ options:
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- Specifies which datasets types to display. Multiple values have to be provided in comma-separated form.
|
||||
- Specifies which datasets types to display. Multiple values have to be provided as a list or in comma-separated form.
|
||||
- Value V(all) cannot be used together with other values.
|
||||
choices: ['all', 'filesystem', 'volume', 'snapshot', 'bookmark']
|
||||
default: all
|
||||
type: str
|
||||
default: [all]
|
||||
type: list
|
||||
elements: str
|
||||
depth:
|
||||
description:
|
||||
- Specifies recursion depth.
|
||||
@@ -106,7 +108,6 @@ zfs_datasets:
|
||||
from collections import defaultdict
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
|
||||
SUPPORTED_TYPES = ['all', 'filesystem', 'volume', 'snapshot', 'bookmark']
|
||||
@@ -132,10 +133,7 @@ class ZFSFacts(object):
|
||||
|
||||
(rc, out, err) = self.module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return rc == 0
|
||||
|
||||
def get_facts(self):
|
||||
cmd = [self.module.get_bin_path('zfs'), 'get', '-H']
|
||||
@@ -148,41 +146,44 @@ class ZFSFacts(object):
|
||||
cmd.append('%s' % self.depth)
|
||||
if self.type:
|
||||
cmd.append('-t')
|
||||
cmd.append(self.type)
|
||||
cmd.append(','.join(self.type))
|
||||
cmd.extend(['-o', 'name,property,value', self.properties, self.name])
|
||||
|
||||
(rc, out, err) = self.module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
for line in out.splitlines():
|
||||
dataset, property, value = line.split('\t')
|
||||
|
||||
self._datasets[dataset].update({property: value})
|
||||
|
||||
for k, v in iteritems(self._datasets):
|
||||
v.update({'name': k})
|
||||
self.facts.append(v)
|
||||
|
||||
return {'ansible_zfs_datasets': self.facts}
|
||||
else:
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg='Error while trying to get facts about ZFS dataset: %s' % self.name,
|
||||
stderr=err,
|
||||
rc=rc)
|
||||
|
||||
for line in out.splitlines():
|
||||
dataset, property, value = line.split('\t')
|
||||
|
||||
self._datasets[dataset].update({property: value})
|
||||
|
||||
for k, v in self._datasets.items():
|
||||
v.update({'name': k})
|
||||
self.facts.append(v)
|
||||
|
||||
return {'ansible_zfs_datasets': self.facts}
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(required=True, aliases=['ds', 'dataset'], type='str'),
|
||||
recurse=dict(required=False, default=False, type='bool'),
|
||||
parsable=dict(required=False, default=False, type='bool'),
|
||||
properties=dict(required=False, default='all', type='str'),
|
||||
type=dict(required=False, default='all', type='str', choices=SUPPORTED_TYPES),
|
||||
depth=dict(required=False, default=0, type='int')
|
||||
recurse=dict(default=False, type='bool'),
|
||||
parsable=dict(default=False, type='bool'),
|
||||
properties=dict(default='all', type='str'),
|
||||
type=dict(default='all', type='list', elements='str', choices=SUPPORTED_TYPES),
|
||||
depth=dict(default=0, type='int')
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if 'all' in module.params['type'] and len(module.params['type']) > 1:
|
||||
module.fail_json(msg="Value 'all' for parameter 'type' is mutually exclusive with other values")
|
||||
|
||||
zfs_facts = ZFSFacts(module)
|
||||
|
||||
result = {}
|
||||
@@ -195,11 +196,11 @@ def main():
|
||||
if zfs_facts.recurse:
|
||||
result['recurse'] = zfs_facts.recurse
|
||||
|
||||
if zfs_facts.dataset_exists():
|
||||
result['ansible_facts'] = zfs_facts.get_facts()
|
||||
else:
|
||||
if not zfs_facts.dataset_exists():
|
||||
module.fail_json(msg='ZFS dataset %s does not exist!' % zfs_facts.name)
|
||||
|
||||
result['ansible_facts'] = zfs_facts.get_facts()
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
|
||||
7
tests/integration/targets/apache2_mod_proxy/aliases
Normal file
7
tests/integration/targets/apache2_mod_proxy/aliases
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
|
||||
|
||||
azp/posix/3
|
||||
destructive
|
||||
skip/aix
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
dependencies:
|
||||
- setup_remote_constraints
|
||||
- setup_apache2
|
||||
253
tests/integration/targets/apache2_mod_proxy/tasks/main.yml
Normal file
253
tests/integration/targets/apache2_mod_proxy/tasks/main.yml
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# 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
|
||||
|
||||
- meta: end_play
|
||||
when: ansible_os_family not in ['Debian', 'Suse']
|
||||
|
||||
- name: Enable mod_proxy
|
||||
community.general.apache2_module:
|
||||
state: present
|
||||
name: "{{ item }}"
|
||||
loop:
|
||||
- status
|
||||
- proxy
|
||||
- proxy_http
|
||||
- proxy_balancer
|
||||
- lbmethod_byrequests
|
||||
|
||||
- name: Add port 81
|
||||
lineinfile:
|
||||
path: "/etc/apache2/{{ 'ports.conf' if ansible_os_family == 'Debian' else 'listen.conf' }}"
|
||||
line: Listen 81
|
||||
|
||||
- name: Set up virtual host
|
||||
copy:
|
||||
dest: "/etc/apache2/{{ 'sites-available' if ansible_os_family == 'Debian' else 'vhosts.d' }}/000-apache2_mod_proxy-test.conf"
|
||||
content: |
|
||||
<VirtualHost *:81>
|
||||
<Proxy balancer://mycluster>
|
||||
BalancerMember http://127.0.0.1:8080
|
||||
BalancerMember http://127.0.0.1:8081
|
||||
</Proxy>
|
||||
|
||||
<IfModule mod_evasive20.c>
|
||||
DOSBlockingPeriod 0
|
||||
DOSWhiteList 127.0.0.1
|
||||
DOSWhiteList ::1
|
||||
</IfModule>
|
||||
|
||||
<Location "/app/">
|
||||
ProxyPreserveHost On
|
||||
ProxyPass balancer://mycluster/
|
||||
ProxyPassReverse balancer://mycluster/
|
||||
</Location>
|
||||
|
||||
<Location "/balancer-manager">
|
||||
SetHandler balancer-manager
|
||||
Require all granted
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
|
||||
- name: Enable virtual host
|
||||
file:
|
||||
src: /etc/apache2/sites-available/000-apache2_mod_proxy-test.conf
|
||||
dest: /etc/apache2/sites-enabled/000-apache2_mod_proxy-test.conf
|
||||
owner: root
|
||||
group: root
|
||||
state: link
|
||||
when: ansible_os_family not in ['Suse']
|
||||
|
||||
- name: Restart Apache
|
||||
service:
|
||||
name: apache2
|
||||
state: restarted
|
||||
|
||||
- name: Install BeautifulSoup
|
||||
pip:
|
||||
name: "{{ 'BeautifulSoup' if ansible_python_version is version('3', '<') else 'BeautifulSoup4' }}"
|
||||
extra_args: "-c {{ remote_constraints }}"
|
||||
|
||||
- name: Get all current balancer pool members attributes
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.members | length == 2
|
||||
- result.members[0].port in ["8080", "8081"]
|
||||
- result.members[0].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[0].host == "127.0.0.1"
|
||||
- result.members[0].path is none
|
||||
- result.members[0].protocol == "http"
|
||||
- result.members[1].port in ["8080", "8081"]
|
||||
- result.members[1].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[1].host == "127.0.0.1"
|
||||
- result.members[1].path is none
|
||||
- result.members[1].protocol == "http"
|
||||
|
||||
- name: Enable member
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
member_host: 127.0.0.1
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Get all current balancer pool members attributes
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.members | length == 2
|
||||
- result.members[0].port in ["8080", "8081"]
|
||||
- result.members[0].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[0].host == "127.0.0.1"
|
||||
- result.members[0].path is none
|
||||
- result.members[0].protocol == "http"
|
||||
- result.members[0].status.disabled == false
|
||||
- result.members[0].status.drained == false
|
||||
- result.members[0].status.hot_standby == false
|
||||
- result.members[0].status.ignore_errors == false
|
||||
- result.members[1].port in ["8080", "8081"]
|
||||
- result.members[1].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[1].host == "127.0.0.1"
|
||||
- result.members[1].path is none
|
||||
- result.members[1].protocol == "http"
|
||||
- result.members[1].status.disabled == false
|
||||
- result.members[1].status.drained == false
|
||||
- result.members[1].status.hot_standby == false
|
||||
- result.members[1].status.ignore_errors == false
|
||||
|
||||
- name: Drain member
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
member_host: 127.0.0.1
|
||||
state: drained
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
# Note that since both members are on the same host, this always affects **both** members!
|
||||
|
||||
- name: Get all current balancer pool members attributes
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.members | length == 2
|
||||
- result.members[0].port in ["8080", "8081"]
|
||||
- result.members[0].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[0].host == "127.0.0.1"
|
||||
- result.members[0].path is none
|
||||
- result.members[0].protocol == "http"
|
||||
- result.members[0].status.disabled == false
|
||||
- result.members[0].status.drained == true
|
||||
- result.members[0].status.hot_standby == false
|
||||
- result.members[0].status.ignore_errors == false
|
||||
- result.members[1].port in ["8080", "8081"]
|
||||
- result.members[1].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[1].host == "127.0.0.1"
|
||||
- result.members[1].path is none
|
||||
- result.members[1].protocol == "http"
|
||||
- result.members[1].status.disabled == false
|
||||
- result.members[1].status.drained == true
|
||||
- result.members[1].status.hot_standby == false
|
||||
- result.members[1].status.ignore_errors == false
|
||||
|
||||
- name: Disable member
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
member_host: 127.0.0.1
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Get all current balancer pool members attributes
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.members | length == 2
|
||||
- result.members[0].port in ["8080", "8081"]
|
||||
- result.members[0].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[0].host == "127.0.0.1"
|
||||
- result.members[0].path is none
|
||||
- result.members[0].protocol == "http"
|
||||
- result.members[0].status.disabled == true
|
||||
- result.members[0].status.drained == false
|
||||
- result.members[0].status.hot_standby == false
|
||||
- result.members[0].status.ignore_errors == false
|
||||
- result.members[1].port in ["8080", "8081"]
|
||||
- result.members[1].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[1].host == "127.0.0.1"
|
||||
- result.members[1].path is none
|
||||
- result.members[1].protocol == "http"
|
||||
- result.members[1].status.disabled == true
|
||||
- result.members[1].status.drained == false
|
||||
- result.members[1].status.hot_standby == false
|
||||
- result.members[1].status.ignore_errors == false
|
||||
|
||||
- name: Enable member
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
member_host: 127.0.0.1
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Get all current balancer pool members attributes
|
||||
community.general.apache2_mod_proxy:
|
||||
balancer_vhost: localhost:81
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.members | length == 2
|
||||
- result.members[0].port in ["8080", "8081"]
|
||||
- result.members[0].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[0].host == "127.0.0.1"
|
||||
- result.members[0].path is none
|
||||
- result.members[0].protocol == "http"
|
||||
- result.members[0].status.disabled == false
|
||||
- result.members[0].status.drained == false
|
||||
- result.members[0].status.hot_standby == false
|
||||
- result.members[0].status.ignore_errors == false
|
||||
- result.members[1].port in ["8080", "8081"]
|
||||
- result.members[1].balancer_url == "http://localhost:81/balancer-manager/"
|
||||
- result.members[1].host == "127.0.0.1"
|
||||
- result.members[1].path is none
|
||||
- result.members[1].protocol == "http"
|
||||
- result.members[1].status.disabled == false
|
||||
- result.members[1].status.drained == false
|
||||
- result.members[1].status.hot_standby == false
|
||||
- result.members[1].status.ignore_errors == false
|
||||
7
tests/integration/targets/apache2_module/meta/main.yml
Normal file
7
tests/integration/targets/apache2_module/meta/main.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
|
||||
|
||||
dependencies:
|
||||
- setup_apache2
|
||||
@@ -8,21 +8,6 @@
|
||||
# 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: install apache via apt
|
||||
apt:
|
||||
name: "{{item}}"
|
||||
state: present
|
||||
when: "ansible_os_family == 'Debian'"
|
||||
with_items:
|
||||
- apache2
|
||||
- libapache2-mod-evasive
|
||||
|
||||
- name: install apache via zypper
|
||||
community.general.zypper:
|
||||
name: apache2
|
||||
state: present
|
||||
when: "ansible_os_family == 'Suse'"
|
||||
|
||||
- name: test apache2_module
|
||||
block:
|
||||
- name: get list of enabled modules
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
assert:
|
||||
that:
|
||||
- fetch_by_client_id_result.clientsecret_info.type == "secret"
|
||||
- "{{ fetch_by_client_id_result.clientsecret_info.value | length }} >= 32"
|
||||
- fetch_by_client_id_result.clientsecret_info.value | length >= 32
|
||||
|
||||
- name: Keycloak Client fetch clientsecret by id
|
||||
community.general.keycloak_clientsecret_info: "{{ auth_args | combine(call_args) }}"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
assert:
|
||||
that:
|
||||
- regenerate_by_client_id.end_state.type == "secret"
|
||||
- "{{ regenerate_by_client_id.end_state.value | length }} >= 32"
|
||||
- regenerate_by_client_id.end_state.value | length >= 32
|
||||
|
||||
- name: Keycloak Client regenerate clientsecret by id
|
||||
community.general.keycloak_clientsecret_regenerate: "{{ auth_args | combine(call_args) }}"
|
||||
@@ -45,5 +45,5 @@
|
||||
- name: Assert that client secret was regenerated
|
||||
assert:
|
||||
that:
|
||||
- "{{ regenerate_by_id.end_state.value | length }} >= 32"
|
||||
- regenerate_by_id.end_state.value | length >= 32
|
||||
- regenerate_by_id.end_state.value != regenerate_by_client_id.end_state.value
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing == {}
|
||||
- result.end_state.name == "{{ role }}"
|
||||
- result.end_state.containerId == "{{ realm }}"
|
||||
- result.end_state.name == role
|
||||
- result.end_state.containerId == realm
|
||||
|
||||
- name: Create existing realm role
|
||||
community.general.keycloak_role:
|
||||
@@ -89,8 +89,8 @@
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing.description == "{{ description_1 }}"
|
||||
- result.end_state.description == "{{ description_2 }}"
|
||||
- result.existing.description == description_1
|
||||
- result.end_state.description == description_2
|
||||
|
||||
- name: Delete existing realm role
|
||||
community.general.keycloak_role:
|
||||
@@ -156,8 +156,8 @@
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing == {}
|
||||
- result.end_state.name == "{{ role }}"
|
||||
- result.end_state.containerId == "{{ client.end_state.id }}"
|
||||
- result.end_state.name == role
|
||||
- result.end_state.containerId == client.end_state.id
|
||||
|
||||
- name: Create existing client role
|
||||
community.general.keycloak_role:
|
||||
@@ -202,8 +202,8 @@
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing.description == "{{ description_1 }}"
|
||||
- result.end_state.description == "{{ description_2 }}"
|
||||
- result.existing.description == description_1
|
||||
- result.end_state.description == description_2
|
||||
|
||||
- name: Delete existing client role
|
||||
community.general.keycloak_role:
|
||||
@@ -480,4 +480,4 @@
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state == {}
|
||||
- result.end_state == {}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing == {}
|
||||
- result.end_state.name == "{{ federation }}"
|
||||
- result.end_state.name == federation
|
||||
|
||||
- name: Create new user federation in admin realm
|
||||
community.general.keycloak_user_federation:
|
||||
@@ -117,7 +117,7 @@
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing == {}
|
||||
- result.end_state.name == "{{ federation }}"
|
||||
- result.end_state.name == federation
|
||||
|
||||
- name: Update existing user federation (no change)
|
||||
community.general.keycloak_user_federation:
|
||||
@@ -170,9 +170,9 @@
|
||||
that:
|
||||
- result is not changed
|
||||
- result.existing != {}
|
||||
- result.existing.name == "{{ federation }}"
|
||||
- result.existing.name == federation
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "{{ federation }}"
|
||||
- result.end_state.name == federation
|
||||
|
||||
- name: Update existing user federation (no change, admin realm)
|
||||
community.general.keycloak_user_federation:
|
||||
@@ -225,9 +225,9 @@
|
||||
that:
|
||||
- result is not changed
|
||||
- result.existing != {}
|
||||
- result.existing.name == "{{ federation }}"
|
||||
- result.existing.name == federation
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "{{ federation }}"
|
||||
- result.end_state.name == federation
|
||||
|
||||
- name: Update existing user federation (with change)
|
||||
community.general.keycloak_user_federation:
|
||||
@@ -296,9 +296,9 @@
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing != {}
|
||||
- result.existing.name == "{{ federation }}"
|
||||
- result.existing.name == federation
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "{{ federation }}"
|
||||
- result.end_state.name == federation
|
||||
|
||||
- name: Delete existing user federation
|
||||
community.general.keycloak_user_federation:
|
||||
@@ -411,7 +411,7 @@
|
||||
that:
|
||||
- result is changed
|
||||
- result.existing == {}
|
||||
- result.end_state.name == "{{ federation }}"
|
||||
- result.end_state.name == federation
|
||||
|
||||
## no point in retesting this, just doing it to clean up introduced server changes
|
||||
- name: Delete absent user federation
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state | selectattr("clientRole", "eq", false) | selectattr("name", "eq", "{{role}}") | list | count > 0
|
||||
- result.end_state | selectattr("clientRole", "eq", false) | selectattr("name", "eq", role) | list | count > 0
|
||||
|
||||
- name: Unmap a realm role from client service account
|
||||
vars:
|
||||
@@ -74,8 +74,8 @@
|
||||
that:
|
||||
- result is changed
|
||||
- (result.end_state | length) == (result.existing | length) - 1
|
||||
- result.existing | selectattr("clientRole", "eq", false) | selectattr("name", "eq", "{{role}}") | list | count > 0
|
||||
- result.end_state | selectattr("clientRole", "eq", false) | selectattr("name", "eq", "{{role}}") | list | count == 0
|
||||
- result.existing | selectattr("clientRole", "eq", false) | selectattr("name", "eq", role) | list | count > 0
|
||||
- result.end_state | selectattr("clientRole", "eq", false) | selectattr("name", "eq", role) | list | count == 0
|
||||
|
||||
- name: Delete existing realm role
|
||||
community.general.keycloak_role:
|
||||
@@ -118,7 +118,7 @@
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state | selectattr("clientRole", "eq", true) | selectattr("name", "eq", "{{role}}") | list | count > 0
|
||||
- result.end_state | selectattr("clientRole", "eq", true) | selectattr("name", "eq", role) | list | count > 0
|
||||
|
||||
- name: Unmap a client role from client service account
|
||||
vars:
|
||||
@@ -140,4 +140,4 @@
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == []
|
||||
- result.existing | selectattr("clientRole", "eq", true) | selectattr("name", "eq", "{{role}}") | list | count > 0
|
||||
- result.existing | selectattr("clientRole", "eq", true) | selectattr("name", "eq", role) | list | count > 0
|
||||
|
||||
@@ -6,5 +6,7 @@ azp/posix/3
|
||||
destructive
|
||||
needs/root
|
||||
skip/aix
|
||||
skip/fedora
|
||||
skip/freebsd
|
||||
skip/macos
|
||||
skip/rhel
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
- name: Bail out if not supported
|
||||
ansible.builtin.meta: end_play
|
||||
when: ansible_distribution not in ('Ubuntu', 'Debian')
|
||||
when: ansible_distribution not in ('Ubuntu', 'Debian', 'Archlinux')
|
||||
|
||||
- name: Run tests auto-detecting mechanism
|
||||
ansible.builtin.include_tasks: basic.yml
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
- import_tasks: test_grow_reduce.yml
|
||||
|
||||
- import_tasks: test_remove_extra_pvs.yml
|
||||
|
||||
- import_tasks: test_pvresize.yml
|
||||
|
||||
- import_tasks: test_active_change.yml
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
# test_grow_reduce already checks the base case with default parameters (remove additional PVs)
|
||||
|
||||
- name: "Create volume group on first disk"
|
||||
lvg:
|
||||
vg: testvg
|
||||
pvs: "{{ loop_device1 }}"
|
||||
|
||||
- name: "get lvm facts"
|
||||
setup:
|
||||
|
||||
- debug: var=ansible_lvm
|
||||
|
||||
- name: "Assert the testvg span only on first disk"
|
||||
assert:
|
||||
that:
|
||||
- ansible_lvm.pvs[loop_device1].vg == "testvg"
|
||||
- 'loop_device2 not in ansible_lvm.pvs or
|
||||
ansible_lvm.pvs[loop_device2].vg == ""'
|
||||
|
||||
- name: "Extend to second disk AND keep first disk"
|
||||
lvg:
|
||||
vg: testvg
|
||||
pvs: "{{ loop_device2 }}"
|
||||
remove_extra_pvs: false
|
||||
|
||||
- name: "get lvm facts"
|
||||
setup:
|
||||
|
||||
- debug: var=ansible_lvm
|
||||
|
||||
- name: "Assert the testvg spans on both disks"
|
||||
assert:
|
||||
that:
|
||||
- ansible_lvm.pvs[loop_device1].vg == "testvg"
|
||||
- ansible_lvm.pvs[loop_device2].vg == "testvg"
|
||||
30
tests/integration/targets/setup_apache2/tasks/main.yml
Normal file
30
tests/integration/targets/setup_apache2/tasks/main.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# 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: Install apache via apt
|
||||
apt:
|
||||
name: "{{item}}"
|
||||
state: present
|
||||
when: "ansible_os_family == 'Debian'"
|
||||
with_items:
|
||||
- apache2
|
||||
- libapache2-mod-evasive
|
||||
|
||||
- name: Install apache via zypper
|
||||
community.general.zypper:
|
||||
name: apache2
|
||||
state: present
|
||||
when: "ansible_os_family == 'Suse'"
|
||||
|
||||
- name: Enable mod_slotmem_shm on SuSE
|
||||
apache2_module:
|
||||
name: slotmem_shm
|
||||
state: present
|
||||
when: "ansible_os_family == 'Suse'"
|
||||
@@ -22,6 +22,8 @@
|
||||
controlpath: "~/.ssh/sockets/%r@%h-%p"
|
||||
controlpersist: yes
|
||||
dynamicforward: '10080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_add
|
||||
check_mode: true
|
||||
@@ -57,6 +59,8 @@
|
||||
controlpath: "~/.ssh/sockets/%r@%h-%p"
|
||||
controlpersist: yes
|
||||
dynamicforward: '10080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_add
|
||||
|
||||
@@ -81,6 +85,8 @@
|
||||
controlpath: "~/.ssh/sockets/%r@%h-%p"
|
||||
controlpersist: yes
|
||||
dynamicforward: '10080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_add_again
|
||||
|
||||
@@ -109,6 +115,7 @@
|
||||
- "'controlpath ~/.ssh/sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist yes' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 10080' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
- name: Options - Update host
|
||||
community.general.ssh_config:
|
||||
@@ -123,6 +130,8 @@
|
||||
controlpath: "~/.ssh/new-sockets/%r@%h-%p"
|
||||
controlpersist: "600"
|
||||
dynamicforward: '11080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_update
|
||||
|
||||
@@ -149,6 +158,8 @@
|
||||
controlpath: "~/.ssh/new-sockets/%r@%h-%p"
|
||||
controlpersist: "600"
|
||||
dynamicforward: '11080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_update
|
||||
|
||||
@@ -178,6 +189,7 @@
|
||||
- "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist 600' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 11080' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
- name: Options - Ensure no update in case option exist in ssh_config file but wasn't defined in playbook
|
||||
community.general.ssh_config:
|
||||
@@ -212,6 +224,7 @@
|
||||
- "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist 600' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 11080' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
- name: Debug
|
||||
debug:
|
||||
@@ -264,6 +277,7 @@
|
||||
- "'controlpath ~/.ssh/sockets/%r@%h-%p' not in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist yes' not in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 10080' not in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' not in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
# Proxycommand and ProxyJump are mutually exclusive.
|
||||
# Reset ssh_config before testing options with proxyjump
|
||||
@@ -286,6 +300,8 @@
|
||||
controlpath: "~/.ssh/sockets/%r@%h-%p"
|
||||
controlpersist: yes
|
||||
dynamicforward: '10080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_add
|
||||
check_mode: true
|
||||
@@ -321,6 +337,8 @@
|
||||
controlpath: "~/.ssh/sockets/%r@%h-%p"
|
||||
controlpersist: yes
|
||||
dynamicforward: '10080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_add
|
||||
|
||||
@@ -345,6 +363,8 @@
|
||||
controlpath: "~/.ssh/sockets/%r@%h-%p"
|
||||
controlpersist: yes
|
||||
dynamicforward: '10080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_add_again
|
||||
|
||||
@@ -373,6 +393,7 @@
|
||||
- "'controlpath ~/.ssh/sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist yes' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 10080' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
- name: Options - Update host
|
||||
community.general.ssh_config:
|
||||
@@ -387,6 +408,8 @@
|
||||
controlpath: "~/.ssh/new-sockets/%r@%h-%p"
|
||||
controlpersist: "600"
|
||||
dynamicforward: '11080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_update
|
||||
|
||||
@@ -413,6 +436,8 @@
|
||||
controlpath: "~/.ssh/new-sockets/%r@%h-%p"
|
||||
controlpersist: "600"
|
||||
dynamicforward: '11080'
|
||||
other_options:
|
||||
serveraliveinterval: '30'
|
||||
state: present
|
||||
register: options_update
|
||||
|
||||
@@ -442,6 +467,7 @@
|
||||
- "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist 600' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 11080' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
- name: Options - Ensure no update in case option exist in ssh_config file but wasn't defined in playbook
|
||||
community.general.ssh_config:
|
||||
@@ -476,6 +502,7 @@
|
||||
- "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist 600' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 11080' in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
- name: Debug
|
||||
debug:
|
||||
@@ -528,3 +555,4 @@
|
||||
- "'controlpath ~/.ssh/sockets/%r@%h-%p' not in slurp_ssh_config['content'] | b64decode"
|
||||
- "'controlpersist yes' not in slurp_ssh_config['content'] | b64decode"
|
||||
- "'dynamicforward 10080' not in slurp_ssh_config['content'] | b64decode"
|
||||
- "'serveraliveinterval 30' not in slurp_ssh_config['content'] | b64decode"
|
||||
|
||||
10
tests/integration/targets/systemd_info/aliases
Normal file
10
tests/integration/targets/systemd_info/aliases
Normal file
@@ -0,0 +1,10 @@
|
||||
# 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
|
||||
|
||||
needs/root
|
||||
azp/posix/1
|
||||
skip/aix
|
||||
skip/freebsd
|
||||
skip/osx
|
||||
skip/macos
|
||||
26
tests/integration/targets/systemd_info/tasks/main.yml
Normal file
26
tests/integration/targets/systemd_info/tasks/main.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: skip Alpine
|
||||
meta: end_host
|
||||
when: ansible_distribution == 'Alpine'
|
||||
|
||||
- name: check ansible_service_mgr
|
||||
ansible.builtin.assert:
|
||||
that: ansible_service_mgr == 'systemd'
|
||||
|
||||
- name: Test systemd_facts
|
||||
block:
|
||||
|
||||
- name: Run tests
|
||||
import_tasks: tests.yml
|
||||
|
||||
when: >
|
||||
(ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux'] and ansible_distribution_major_version is version('7', '>=')) or
|
||||
ansible_distribution == 'Fedora' or
|
||||
(ansible_distribution == 'Ubuntu' and ansible_distribution_version is version('15.04', '>=')) or
|
||||
(ansible_distribution == 'Debian' and ansible_distribution_version is version('8', '>=')) or
|
||||
ansible_os_family == 'Suse' or
|
||||
ansible_distribution == 'Archlinux'
|
||||
107
tests/integration/targets/systemd_info/tasks/tests.yml
Normal file
107
tests/integration/targets/systemd_info/tasks/tests.yml
Normal file
@@ -0,0 +1,107 @@
|
||||
# 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: Gather all units from shell
|
||||
ansible.builtin.command: systemctl list-units --no-pager --type service,target,socket,mount --all --plain --no-legend
|
||||
register: all_units
|
||||
|
||||
- name: Assert command run successfully
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- all_units.rc == 0
|
||||
|
||||
- name: Gather all units
|
||||
community.general.systemd_info:
|
||||
register: units_all
|
||||
|
||||
- name: Check all units exists
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- units_all is defined
|
||||
- units_all.units | length == all_units.stdout_lines | length
|
||||
success_msg: "Success: All units collected."
|
||||
|
||||
- name: Build all units list
|
||||
set_fact:
|
||||
shell_units: "{{ all_units.stdout_lines | map('split') | list }}"
|
||||
|
||||
- name: Check all units properties
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- units_all.units[item[0]].name == item[0]
|
||||
- units_all.units[item[0]].loadstate == item[1]
|
||||
- units_all.units[item[0]].activestate == item[2]
|
||||
- units_all.units[item[0]].substate == item[3]
|
||||
loop: "{{ shell_units }}"
|
||||
loop_control:
|
||||
label: "{{ item[0] }}"
|
||||
|
||||
- name: Gather systemd-journald.service properties from shell
|
||||
ansible.builtin.command: systemctl show systemd-journald.service -p Id,LoadState,ActiveState,SubState,FragmentPath,MainPID,ExecMainPID,UnitFileState,UnitFilePreset,Description,Restart
|
||||
register: journald_prop
|
||||
|
||||
- name: Assert command run successfully
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- journald_prop.rc == 0
|
||||
|
||||
- name: Gather systemd-journald.service
|
||||
community.general.systemd_info:
|
||||
unitname:
|
||||
- systemd-journald.service
|
||||
register: journal_unit
|
||||
|
||||
- name: Check unit facts and all properties
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- journal_unit.units is defined
|
||||
- journal_unit.units['systemd-journald.service'] is defined
|
||||
- journal_unit.units['systemd-journald.service'].name is defined
|
||||
- journal_unit.units['systemd-journald.service'].loadstate is defined
|
||||
- journal_unit.units['systemd-journald.service'].activestate is defined
|
||||
- journal_unit.units['systemd-journald.service'].substate is defined
|
||||
- journal_unit.units['systemd-journald.service'].fragmentpath is defined
|
||||
- journal_unit.units['systemd-journald.service'].mainpid is defined
|
||||
- journal_unit.units['systemd-journald.service'].execmainpid is defined
|
||||
- journal_unit.units['systemd-journald.service'].unitfilestate is defined
|
||||
- journal_unit.units['systemd-journald.service'].unitfilepreset is defined
|
||||
success_msg: "Success: All properties collected."
|
||||
|
||||
- name: Create dict of properties from shell
|
||||
ansible.builtin.set_fact:
|
||||
journald_shell: "{{ dict(journald_prop.stdout_lines | map('split', '=', 1) | list) }}"
|
||||
|
||||
- name: Check properties content
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- journal_unit.units['systemd-journald.service'].name == journald_shell.Id
|
||||
- journal_unit.units['systemd-journald.service'].loadstate == journald_shell.LoadState
|
||||
- journal_unit.units['systemd-journald.service'].activestate == journald_shell.ActiveState
|
||||
- journal_unit.units['systemd-journald.service'].substate == journald_shell.SubState
|
||||
- journal_unit.units['systemd-journald.service'].fragmentpath == journald_shell.FragmentPath
|
||||
- journal_unit.units['systemd-journald.service'].mainpid == journald_shell.MainPID
|
||||
- journal_unit.units['systemd-journald.service'].execmainpid == journald_shell.ExecMainPID
|
||||
- journal_unit.units['systemd-journald.service'].unitfilestate == journald_shell.UnitFileState
|
||||
- journal_unit.units['systemd-journald.service'].unitfilepreset == journald_shell.UnitFilePreset
|
||||
success_msg: "Success: Property values are correct."
|
||||
|
||||
- name: Gather systemd-journald.service extra properties
|
||||
community.general.systemd_info:
|
||||
unitname:
|
||||
- systemd-journald.service
|
||||
extra_properties:
|
||||
- Description
|
||||
- Restart
|
||||
register: journal_extra
|
||||
|
||||
- name: Check new properties
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- journal_extra.units is defined
|
||||
- journal_extra.units['systemd-journald.service'] is defined
|
||||
- journal_extra.units['systemd-journald.service'].description is defined
|
||||
- journal_extra.units['systemd-journald.service'].restart is defined
|
||||
- journal_extra.units['systemd-journald.service'].description == journald_shell.Description
|
||||
- journal_extra.units['systemd-journald.service'].restart == journald_shell.Restart
|
||||
success_msg: "Success: Extra property values are correct."
|
||||
@@ -10,5 +10,5 @@ plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt
|
||||
plugins/modules/udm_user.py import-3.12 # Uses deprecated stdlib library 'crypt'
|
||||
plugins/modules/xfconf.py validate-modules:return-syntax-error
|
||||
plugins/module_utils/univention_umc.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/helper.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/uthelper.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes
|
||||
|
||||
@@ -10,5 +10,5 @@ plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt
|
||||
plugins/modules/udm_user.py import-3.12 # Uses deprecated stdlib library 'crypt'
|
||||
plugins/modules/xfconf.py validate-modules:return-syntax-error
|
||||
plugins/module_utils/univention_umc.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/helper.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/uthelper.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes
|
||||
|
||||
@@ -10,5 +10,5 @@ plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt
|
||||
plugins/modules/udm_user.py import-3.12 # Uses deprecated stdlib library 'crypt'
|
||||
plugins/modules/xfconf.py validate-modules:return-syntax-error
|
||||
plugins/module_utils/univention_umc.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/helper.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/uthelper.py pylint:use-yield-from # suggested construct does not work with Python 2
|
||||
tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes
|
||||
|
||||
@@ -158,6 +158,8 @@ def test_verify_file_bad_config(inventory):
|
||||
|
||||
|
||||
def test_populate(inventory, mocker):
|
||||
inventory.host_entry_name_type = 'uuid'
|
||||
inventory.vm_entry_name_type = 'uuid'
|
||||
inventory.get_option = mocker.MagicMock(side_effect=get_option)
|
||||
inventory._populate(objects)
|
||||
actual = sorted(inventory.inventory.hosts.keys())
|
||||
|
||||
@@ -13,7 +13,7 @@ from ansible_collections.community.general.tests.unit.compat.mock import patch
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils import six
|
||||
from ansible.plugins.loader import lookup_loader
|
||||
from ansible_collections.community.general.plugins.lookup.bitwarden import Bitwarden
|
||||
from ansible_collections.community.general.plugins.lookup.bitwarden import Bitwarden, BitwardenException
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder
|
||||
|
||||
MOCK_COLLECTION_ID = "3b12a9da-7c49-40b8-ad33-aede017a7ead"
|
||||
@@ -131,7 +131,21 @@ MOCK_RECORDS = [
|
||||
"reprompt": 0,
|
||||
"revisionDate": "2024-14-15T11:30:00.000Z",
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"object": "collection",
|
||||
"id": MOCK_COLLECTION_ID,
|
||||
"organizationId": MOCK_ORGANIZATION_ID,
|
||||
"name": "MOCK_COLLECTION",
|
||||
"externalId": None
|
||||
},
|
||||
{
|
||||
"object": "collection",
|
||||
"id": "3b12a9da-7c49-40b8-ad33-aede017a8ead",
|
||||
"organizationId": "3b12a9da-7c49-40b8-ad33-aede017a9ead",
|
||||
"name": "some/other/collection",
|
||||
"externalId": None
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -164,6 +178,9 @@ class MockBitwarden(Bitwarden):
|
||||
|
||||
items = []
|
||||
for item in MOCK_RECORDS:
|
||||
if item.get('object') != 'item':
|
||||
continue
|
||||
|
||||
if search_value and not re.search(search_value, item.get('name')):
|
||||
continue
|
||||
if collection_to_filter and collection_to_filter not in item.get('collectionIds', []):
|
||||
@@ -172,6 +189,35 @@ class MockBitwarden(Bitwarden):
|
||||
continue
|
||||
items.append(item)
|
||||
return AnsibleJSONEncoder().encode(items), ''
|
||||
elif args[1] == 'collections':
|
||||
try:
|
||||
search_value = args[args.index('--search') + 1]
|
||||
except ValueError:
|
||||
search_value = None
|
||||
|
||||
try:
|
||||
collection_to_filter = args[args.index('--collectionid') + 1]
|
||||
except ValueError:
|
||||
collection_to_filter = None
|
||||
|
||||
try:
|
||||
organization_to_filter = args[args.index('--organizationid') + 1]
|
||||
except ValueError:
|
||||
organization_to_filter = None
|
||||
|
||||
collections = []
|
||||
for item in MOCK_RECORDS:
|
||||
if item.get('object') != 'collection':
|
||||
continue
|
||||
|
||||
if search_value and not re.search(search_value, item.get('name')):
|
||||
continue
|
||||
if collection_to_filter and collection_to_filter not in item.get('collectionIds', []):
|
||||
continue
|
||||
if organization_to_filter and item.get('organizationId') != organization_to_filter:
|
||||
continue
|
||||
collections.append(item)
|
||||
return AnsibleJSONEncoder().encode(collections), ''
|
||||
|
||||
return '[]', ''
|
||||
|
||||
@@ -261,3 +307,26 @@ class TestLookupModule(unittest.TestCase):
|
||||
def test_bitwarden_plugin_full_collection_organization(self):
|
||||
self.assertEqual([MOCK_RECORDS[0], MOCK_RECORDS[2]], self.lookup.run(None,
|
||||
collection_id=MOCK_COLLECTION_ID, organization_id=MOCK_ORGANIZATION_ID)[0])
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden', new=MockBitwarden())
|
||||
def test_bitwarden_plugin_collection_name_filter(self):
|
||||
# all passwords from MOCK_COLLECTION
|
||||
self.assertEqual([MOCK_RECORDS[0], MOCK_RECORDS[2]], self.lookup.run(None,
|
||||
collection_name="MOCK_COLLECTION")[0])
|
||||
# Existing collection, no results
|
||||
self.assertEqual([], self.lookup.run(None, collection_name="some/other/collection")[0])
|
||||
|
||||
# Non-Existent collection
|
||||
with self.assertRaises(BitwardenException):
|
||||
self.lookup.run(None, collection_name="nonexistent")
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden', new=MockBitwarden())
|
||||
def test_bitwarden_plugin_result_count_check(self):
|
||||
self.lookup.run(None, collection_id=MOCK_COLLECTION_ID, organization_id=MOCK_ORGANIZATION_ID, result_count=2)
|
||||
with self.assertRaises(BitwardenException):
|
||||
self.lookup.run(None, collection_id=MOCK_COLLECTION_ID, organization_id=MOCK_ORGANIZATION_ID,
|
||||
result_count=1)
|
||||
|
||||
self.lookup.run(None, organization_id=MOCK_ORGANIZATION_ID, result_count=3)
|
||||
with self.assertRaises(BitwardenException):
|
||||
self.lookup.run(None, organization_id=MOCK_ORGANIZATION_ID, result_count=0)
|
||||
|
||||
@@ -14,7 +14,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import cpanm
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(cpanm, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(cpanm, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -7,7 +7,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import django_check
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(django_check, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(django_check, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -7,7 +7,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import django_command
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(django_command, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(django_command, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -7,7 +7,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import django_createcachetable
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(django_createcachetable, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(django_createcachetable, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -8,7 +8,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import facter_facts
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(facter_facts, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(facter_facts, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -8,7 +8,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import gconftool2
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(gconftool2, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(gconftool2, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -8,7 +8,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import gconftool2_info
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(gconftool2_info, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(gconftool2_info, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -8,7 +8,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import gio_mime
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(gio_mime, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(gio_mime, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -8,7 +8,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import krb_ticket
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(krb_ticket, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(krb_ticket, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -1570,6 +1570,37 @@ macvlan.promiscuous: yes
|
||||
macvlan.tap: no
|
||||
"""
|
||||
|
||||
TESTCASE_VRF = [
|
||||
{
|
||||
'type': 'vrf',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'ifname': 'vrf_not_exists',
|
||||
'ip4': '10.10.10.10/24',
|
||||
'gw4': '10.10.10.1',
|
||||
'table': 10,
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
}
|
||||
]
|
||||
|
||||
TESTCASE_VRF_SHOW_OUTPUT = """\
|
||||
connection.id: non_existent_nw_device
|
||||
connection.interface-name: vrf_not_exists
|
||||
connection.autoconnect: yes
|
||||
ipv4.method: manual
|
||||
ipv4.addresses: 10.10.10.10/24
|
||||
ipv4.gateway: 10.10.10.1
|
||||
ipv4.ignore-auto-dns: no
|
||||
ipv4.ignore-auto-routes: no
|
||||
ipv4.never-default: no
|
||||
ipv4.may-fail: yes
|
||||
ipv6.method: auto
|
||||
ipv6.ignore-auto-dns: no
|
||||
ipv6.ignore-auto-routes: no
|
||||
table: 10
|
||||
802-3-ethernet.mtu: auto
|
||||
"""
|
||||
|
||||
|
||||
def mocker_set(mocker,
|
||||
connection_exists=False,
|
||||
@@ -2035,6 +2066,13 @@ def mocked_loopback_connection_modify(mocker):
|
||||
))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mocked_vrf_connection_unchanged(mocker):
|
||||
mocker_set(mocker,
|
||||
connection_exists=True,
|
||||
execute_return=(0, TESTCASE_VRF_SHOW_OUTPUT, ""))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_BOND, indirect=['patch_ansible_module'])
|
||||
def test_bond_connection_create(mocked_generic_connection_create, capfd):
|
||||
"""
|
||||
@@ -4911,3 +4949,76 @@ def test_add_second_ip4_address_to_loopback_connection(mocked_loopback_connectio
|
||||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert results['changed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_VRF, indirect=['patch_ansible_module'])
|
||||
def test_create_vrf_con(mocked_generic_connection_create, capfd):
|
||||
"""
|
||||
Test if VRF created
|
||||
"""
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
nmcli.main()
|
||||
|
||||
assert nmcli.Nmcli.execute_command.call_count == 1
|
||||
arg_list = nmcli.Nmcli.execute_command.call_args_list
|
||||
args, kwargs = arg_list[0]
|
||||
|
||||
assert args[0][0] == '/usr/bin/nmcli'
|
||||
assert args[0][1] == 'con'
|
||||
assert args[0][2] == 'add'
|
||||
assert args[0][3] == 'type'
|
||||
assert args[0][4] == 'vrf'
|
||||
assert args[0][5] == 'con-name'
|
||||
assert args[0][6] == 'non_existent_nw_device'
|
||||
|
||||
args_text = list(map(to_text, args[0]))
|
||||
for param in ['ipv4.addresses', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'table', '10']:
|
||||
assert param in args_text
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert results['changed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_VRF, indirect=['patch_ansible_module'])
|
||||
def test_mod_vrf_conn(mocked_generic_connection_modify, capfd):
|
||||
"""
|
||||
Test if VRF modified
|
||||
"""
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
nmcli.main()
|
||||
|
||||
assert nmcli.Nmcli.execute_command.call_count == 1
|
||||
arg_list = nmcli.Nmcli.execute_command.call_args_list
|
||||
args, kwargs = arg_list[0]
|
||||
|
||||
assert args[0][0] == '/usr/bin/nmcli'
|
||||
assert args[0][1] == 'con'
|
||||
assert args[0][2] == 'modify'
|
||||
assert args[0][3] == 'non_existent_nw_device'
|
||||
|
||||
args_text = list(map(to_text, args[0]))
|
||||
for param in ['ipv4.addresses', '10.10.10.10/24', 'ipv4.gateway', '10.10.10.1', 'table', '10']:
|
||||
assert param in args_text
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert results['changed']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_VRF, indirect=['patch_ansible_module'])
|
||||
def test_vrf_connection_unchanged(mocked_vrf_connection_unchanged, capfd):
|
||||
"""
|
||||
Test : VRF connection unchanged
|
||||
"""
|
||||
with pytest.raises(SystemExit):
|
||||
nmcli.main()
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert not results.get('failed')
|
||||
assert not results['changed']
|
||||
|
||||
@@ -8,7 +8,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import opkg
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(opkg, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(opkg, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -14,7 +14,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import puppet
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(puppet, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(puppet, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -9,7 +9,7 @@ __metaclass__ = type
|
||||
import sys
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import snap
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
issue_6803_status_out = """Name Version Rev Tracking Publisher Notes
|
||||
@@ -501,4 +501,4 @@ TEST_SPEC = dict(
|
||||
]
|
||||
)
|
||||
|
||||
Helper.from_spec(snap, sys.modules[__name__], TEST_SPEC, mocks=[RunCommandMock])
|
||||
UTHelper.from_spec(snap, sys.modules[__name__], TEST_SPEC, mocks=[RunCommandMock])
|
||||
|
||||
@@ -14,7 +14,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import xfconf
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(xfconf, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(xfconf, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -7,7 +7,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.modules import xfconf_info
|
||||
from .helper import Helper, RunCommandMock
|
||||
from .uthelper import UTHelper, RunCommandMock
|
||||
|
||||
|
||||
Helper.from_module(xfconf_info, __name__, mocks=[RunCommandMock])
|
||||
UTHelper.from_module(xfconf_info, __name__, mocks=[RunCommandMock])
|
||||
|
||||
@@ -14,18 +14,18 @@ import yaml
|
||||
import pytest
|
||||
|
||||
|
||||
class Helper(object):
|
||||
class UTHelper(object):
|
||||
TEST_SPEC_VALID_SECTIONS = ["anchors", "test_cases"]
|
||||
|
||||
@staticmethod
|
||||
def from_spec(ansible_module, test_module, test_spec, mocks=None):
|
||||
helper = Helper(ansible_module, test_module, test_spec=test_spec, mocks=mocks)
|
||||
helper = UTHelper(ansible_module, test_module, test_spec=test_spec, mocks=mocks)
|
||||
return helper
|
||||
|
||||
@staticmethod
|
||||
def from_file(ansible_module, test_module, test_spec_filehandle, mocks=None):
|
||||
test_spec = yaml.safe_load(test_spec_filehandle)
|
||||
return Helper.from_spec(ansible_module, test_module, test_spec, mocks)
|
||||
return UTHelper.from_spec(ansible_module, test_module, test_spec, mocks)
|
||||
|
||||
# @TODO: calculate the test_module_name automatically, remove one more parameter
|
||||
@staticmethod
|
||||
@@ -36,7 +36,7 @@ class Helper(object):
|
||||
test_spec_filename = test_module.__file__.replace('.py', ext)
|
||||
if os.path.exists(test_spec_filename):
|
||||
with open(test_spec_filename, "r") as test_spec_filehandle:
|
||||
return Helper.from_file(ansible_module, test_module, test_spec_filehandle, mocks=mocks)
|
||||
return UTHelper.from_file(ansible_module, test_module, test_spec_filehandle, mocks=mocks)
|
||||
|
||||
raise Exception("Cannot find test case file for {0} with one of the extensions: {1}".format(test_module.__file__, extensions))
|
||||
|
||||
Reference in New Issue
Block a user