mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 02:16:50 +00:00
Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68c3c9b7ba | ||
|
|
7bb291864e | ||
|
|
0090af8cfb | ||
|
|
42aeeb975b | ||
|
|
9dd7be05dc | ||
|
|
11a847a7b5 | ||
|
|
ebcceafdb7 | ||
|
|
79b3521547 | ||
|
|
7aae8a94f2 | ||
|
|
acde075b5f | ||
|
|
0310c7875d | ||
|
|
8bce7601bc | ||
|
|
ab5c4b186b | ||
|
|
ee2779e6c1 | ||
|
|
91ac9f84b8 | ||
|
|
1c1d58482c | ||
|
|
138740127a | ||
|
|
1d7aad9b46 | ||
|
|
f3a12a9e78 | ||
|
|
0e818c4812 | ||
|
|
8751f0feea | ||
|
|
5dd4cc5148 | ||
|
|
39f27d7d43 | ||
|
|
4dec46778c | ||
|
|
d5e0d36e48 | ||
|
|
fb45b908dd | ||
|
|
6e16c6c649 | ||
|
|
033d5f23f8 | ||
|
|
6367bb853d | ||
|
|
8e6941ed5d | ||
|
|
2d730da8a7 | ||
|
|
88f5100657 | ||
|
|
0293f84b3e | ||
|
|
8333c881d3 | ||
|
|
4a394088b3 | ||
|
|
d72d9b3e45 | ||
|
|
e44bbbdcba | ||
|
|
eca7c1a00b | ||
|
|
b1ff713c41 | ||
|
|
c95a8b6540 | ||
|
|
57dcd31c82 | ||
|
|
7ba3d84004 | ||
|
|
837c1289d0 | ||
|
|
3c7c946297 | ||
|
|
f0f5035ba2 | ||
|
|
3955a6be0f | ||
|
|
bc5b4bdef3 | ||
|
|
8f8b1ee4ce | ||
|
|
6534db4942 | ||
|
|
7a55295798 | ||
|
|
0fd7cfd2d6 | ||
|
|
68ca28b69a | ||
|
|
c91e7b4c03 | ||
|
|
2583152512 | ||
|
|
cee6c98d2a | ||
|
|
8859379bed | ||
|
|
e899631137 | ||
|
|
e99dcaa729 | ||
|
|
c12dd2f9c7 | ||
|
|
8253fb171d | ||
|
|
5f1f76b8f4 | ||
|
|
e3793e09e8 | ||
|
|
a800a6dbad | ||
|
|
7d45b678e4 | ||
|
|
cd0ca389ed | ||
|
|
9ebf72d560 | ||
|
|
d6a4fab8ea | ||
|
|
4e43b124cd | ||
|
|
9bcf61d153 | ||
|
|
7f5305fb80 | ||
|
|
aea60a8dd6 | ||
|
|
195ac4d7e6 | ||
|
|
91d515bd1e | ||
|
|
7d8f5559e2 | ||
|
|
9e68816db9 | ||
|
|
fc1ba5152c | ||
|
|
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 | ||
|
|
fc752f3143 | ||
|
|
4637c265fa | ||
|
|
33e980039b | ||
|
|
e49775765d | ||
|
|
c2590cfcd8 | ||
|
|
4282b6ed16 | ||
|
|
3c77c8ec3c | ||
|
|
07e4e4a782 | ||
|
|
d881a59ed7 | ||
|
|
338328341e | ||
|
|
6e48528b22 | ||
|
|
f545c300d9 | ||
|
|
df496e37c0 | ||
|
|
c35f13084d | ||
|
|
4d4e626f95 | ||
|
|
faa913d566 | ||
|
|
7e4a39964e | ||
|
|
c6aecd18f4 | ||
|
|
c5bdb1501e | ||
|
|
163bfd0f37 | ||
|
|
0331798f84 | ||
|
|
2c7940c5de | ||
|
|
4ef5c3c11a |
@@ -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
|
||||
@@ -72,7 +70,6 @@ stages:
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- test: extra
|
||||
- stage: Sanity_2_18
|
||||
displayName: Sanity 2.18
|
||||
dependsOn: []
|
||||
@@ -190,14 +187,14 @@ stages:
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: macOS 14.3
|
||||
test: macos/14.3
|
||||
- name: macOS 15.3
|
||||
test: macos/15.3
|
||||
- name: RHEL 9.5
|
||||
test: rhel/9.5
|
||||
- name: FreeBSD 14.2
|
||||
test: freebsd/14.2
|
||||
- name: FreeBSD 13.4
|
||||
test: freebsd/13.4
|
||||
- name: FreeBSD 13.5
|
||||
test: freebsd/13.5
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -210,6 +207,8 @@ stages:
|
||||
parameters:
|
||||
testFormat: 2.18/{0}
|
||||
targets:
|
||||
- name: macOS 14.3
|
||||
test: macos/14.3
|
||||
- name: RHEL 9.4
|
||||
test: rhel/9.4
|
||||
- name: FreeBSD 14.1
|
||||
|
||||
@@ -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)
|
||||
|
||||
12
.github/BOTMETA.yml
vendored
12
.github/BOTMETA.yml
vendored
@@ -119,6 +119,8 @@ files:
|
||||
$connections/saltstack.py:
|
||||
labels: saltstack
|
||||
maintainers: mscherer
|
||||
$connections/wsl.py:
|
||||
maintainers: rgl
|
||||
$connections/zone.py:
|
||||
maintainers: $team_ansible_core
|
||||
$doc_fragments/:
|
||||
@@ -378,6 +380,8 @@ files:
|
||||
$module_utils/oracle/oci_utils.py:
|
||||
labels: cloud
|
||||
maintainers: $team_oracle
|
||||
$module_utils/pacemaker.py:
|
||||
maintainers: munchtoast
|
||||
$module_utils/pipx.py:
|
||||
labels: pipx
|
||||
maintainers: russoz
|
||||
@@ -402,6 +406,8 @@ files:
|
||||
maintainers: russoz
|
||||
$module_utils/ssh.py:
|
||||
maintainers: russoz
|
||||
$module_utils/systemd.py:
|
||||
maintainers: NomakCooper
|
||||
$module_utils/storage/hpe3par/hpe3par.py:
|
||||
maintainers: farhan7500 gautamphegde
|
||||
$module_utils/utm_utils.py:
|
||||
@@ -1054,6 +1060,8 @@ files:
|
||||
maintainers: fraff
|
||||
$modules/pacemaker_cluster.py:
|
||||
maintainers: matbu
|
||||
$modules/pacemaker_resource.py:
|
||||
maintainers: munchtoast
|
||||
$modules/packet_:
|
||||
maintainers: nurfet-becirevic t0mk
|
||||
$modules/packet_device.py:
|
||||
@@ -1364,6 +1372,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 +1553,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:
|
||||
|
||||
4
.github/workflows/ansible-test.yml
vendored
4
.github/workflows/ansible-test.yml
vendored
@@ -45,6 +45,8 @@ jobs:
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pull-request-change-detection: 'true'
|
||||
testing-type: sanity
|
||||
pre-test-cmd: >-
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git ../../community/internal_test_tools
|
||||
|
||||
units:
|
||||
# Ansible-test on various stable branches does not yet work well with cgroups v2.
|
||||
@@ -171,6 +173,8 @@ jobs:
|
||||
;
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.crypto.git ../../community/crypto
|
||||
;
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.docker.git ../../community/docker
|
||||
;
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git ../../community/internal_test_tools
|
||||
pull-request-change-detection: 'true'
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
20
.github/workflows/import-galaxy.yml
vendored
20
.github/workflows/import-galaxy.yml
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
# 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: import-galaxy
|
||||
'on':
|
||||
# Run CI against all pushes (direct commits, also merged PRs) to main, and all Pull Requests
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
import-galaxy:
|
||||
permissions:
|
||||
contents: read
|
||||
name: Test to import built collection artifact with Galaxy importer
|
||||
uses: ansible-community/github-action-test-galaxy-import/.github/workflows/test-galaxy-import.yml@main
|
||||
28
.github/workflows/nox.yml
vendored
Normal file
28
.github/workflows/nox.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
# 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: nox
|
||||
'on':
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
# Run CI once per day (at 08:00 UTC)
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
nox:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Run extra sanity tests"
|
||||
steps:
|
||||
- name: Check out collection
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run nox
|
||||
uses: ansible-community/antsibull-nox@main
|
||||
35
.github/workflows/reuse.yml
vendored
35
.github/workflows/reuse.yml
vendored
@@ -1,35 +0,0 @@
|
||||
---
|
||||
# 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: Verify REUSE
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
# Run CI once per day (at 07:30 UTC)
|
||||
schedule:
|
||||
- cron: '30 7 * * *'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@v5
|
||||
@@ -1,5 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
|
||||
Files: changelogs/fragments/*
|
||||
Copyright: Ansible Project
|
||||
License: GPL-3.0-or-later
|
||||
337
CHANGELOG.md
337
CHANGELOG.md
@@ -2,68 +2,286 @@
|
||||
|
||||
**Topics**
|
||||
|
||||
- <a href="#v10-3-0">v10\.3\.0</a>
|
||||
- <a href="#v10-6-0">v10\.6\.0</a>
|
||||
- <a href="#release-summary">Release Summary</a>
|
||||
- <a href="#major-changes">Major Changes</a>
|
||||
- <a href="#minor-changes">Minor Changes</a>
|
||||
- <a href="#deprecated-features">Deprecated Features</a>
|
||||
- <a href="#security-fixes">Security Fixes</a>
|
||||
- <a href="#bugfixes">Bugfixes</a>
|
||||
- <a href="#known-issues">Known Issues</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="#v10-5-0">v10\.5\.0</a>
|
||||
- <a href="#release-summary-1">Release Summary</a>
|
||||
- <a href="#minor-changes-1">Minor Changes</a>
|
||||
- <a href="#deprecated-features-1">Deprecated Features</a>
|
||||
- <a href="#security-fixes-1">Security Fixes</a>
|
||||
- <a href="#bugfixes-1">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="#new-modules">New Modules</a>
|
||||
- <a href="#v10-4-0">v10\.4\.0</a>
|
||||
- <a href="#release-summary-2">Release Summary</a>
|
||||
- <a href="#minor-changes-2">Minor Changes</a>
|
||||
- <a href="#deprecated-features-2">Deprecated Features</a>
|
||||
- <a href="#deprecated-features-1">Deprecated Features</a>
|
||||
- <a href="#bugfixes-2">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="#v10-0-1">v10\.0\.1</a>
|
||||
- <a href="#new-modules-1">New Modules</a>
|
||||
- <a href="#v10-3-1">v10\.3\.1</a>
|
||||
- <a href="#release-summary-3">Release Summary</a>
|
||||
- <a href="#bugfixes-3">Bugfixes</a>
|
||||
- <a href="#v10-0-0">v10\.0\.0</a>
|
||||
- <a href="#release-summary-4">Release Summary</a>
|
||||
- <a href="#minor-changes-3">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-3">Bugfixes</a>
|
||||
- <a href="#v10-3-0">v10\.3\.0</a>
|
||||
- <a href="#release-summary-4">Release Summary</a>
|
||||
- <a href="#minor-changes-4">Minor Changes</a>
|
||||
- <a href="#deprecated-features-2">Deprecated Features</a>
|
||||
- <a href="#security-fixes">Security Fixes</a>
|
||||
- <a href="#bugfixes-4">Bugfixes</a>
|
||||
- <a href="#known-issues">Known Issues</a>
|
||||
- <a href="#new-plugins-1">New Plugins</a>
|
||||
- <a href="#connection-1">Connection</a>
|
||||
- <a href="#filter">Filter</a>
|
||||
- <a href="#lookup">Lookup</a>
|
||||
- <a href="#new-modules-2">New Modules</a>
|
||||
- <a href="#v10-2-0">v10\.2\.0</a>
|
||||
- <a href="#release-summary-5">Release Summary</a>
|
||||
- <a href="#minor-changes-5">Minor Changes</a>
|
||||
- <a href="#deprecated-features-3">Deprecated Features</a>
|
||||
- <a href="#security-fixes-1">Security Fixes</a>
|
||||
- <a href="#bugfixes-5">Bugfixes</a>
|
||||
- <a href="#new-plugins-2">New Plugins</a>
|
||||
- <a href="#inventory">Inventory</a>
|
||||
- <a href="#new-modules-3">New Modules</a>
|
||||
- <a href="#v10-1-0">v10\.1\.0</a>
|
||||
- <a href="#release-summary-6">Release Summary</a>
|
||||
- <a href="#minor-changes-6">Minor Changes</a>
|
||||
- <a href="#deprecated-features-4">Deprecated Features</a>
|
||||
- <a href="#bugfixes-6">Bugfixes</a>
|
||||
- <a href="#new-plugins-3">New Plugins</a>
|
||||
- <a href="#filter-1">Filter</a>
|
||||
- <a href="#new-modules-4">New Modules</a>
|
||||
- <a href="#v10-0-1">v10\.0\.1</a>
|
||||
- <a href="#release-summary-7">Release Summary</a>
|
||||
- <a href="#bugfixes-7">Bugfixes</a>
|
||||
- <a href="#v10-0-0">v10\.0\.0</a>
|
||||
- <a href="#release-summary-8">Release Summary</a>
|
||||
- <a href="#minor-changes-7">Minor Changes</a>
|
||||
- <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
|
||||
- <a href="#deprecated-features-5">Deprecated Features</a>
|
||||
- <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a>
|
||||
- <a href="#bugfixes-8">Bugfixes</a>
|
||||
- <a href="#known-issues-1">Known Issues</a>
|
||||
- <a href="#new-plugins-4">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-5">New Modules</a>
|
||||
This changelog describes changes after version 9\.0\.0\.
|
||||
|
||||
<a id="v10-3-0"></a>
|
||||
## v10\.3\.0
|
||||
<a id="v10-6-0"></a>
|
||||
## v10\.6\.0
|
||||
|
||||
<a id="release-summary"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="major-changes"></a>
|
||||
### Major Changes
|
||||
|
||||
* keycloak\_\* modules \- <code>refresh\_token</code> parameter added\. When multiple authentication parameters are provided \(<code>token</code>\, <code>refresh\_token</code>\, and <code>auth\_username</code>/<code>auth\_password</code>\)\, modules will now automatically retry requests upon authentication errors \(401\)\, using in order the token\, refresh token\, and username/password \([https\://github\.com/ansible\-collections/community\.general/pull/9494](https\://github\.com/ansible\-collections/community\.general/pull/9494)\)\.
|
||||
|
||||
<a id="minor-changes"></a>
|
||||
### Minor Changes
|
||||
|
||||
* apache2\_module \- added workaround for new PHP module name\, from <code>php7\_module</code> to <code>php\_module</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9951](https\://github\.com/ansible\-collections/community\.general/pull/9951)\)\.
|
||||
* gitlab\_project \- add option <code>build\_timeout</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9960](https\://github\.com/ansible\-collections/community\.general/pull/9960)\)\.
|
||||
* gitlab\_project\_members \- extend choices parameter <code>access\_level</code> by missing upstream valid value <code>owner</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9953](https\://github\.com/ansible\-collections/community\.general/pull/9953)\)\.
|
||||
* hpilo\_boot \- add option to get an idempotent behavior while powering on server\, resulting in success instead of failure when using <code>state\: boot\_once</code> option \([https\://github\.com/ansible\-collections/community\.general/pull/9646](https\://github\.com/ansible\-collections/community\.general/pull/9646)\)\.
|
||||
* idrac\_redfish\_command\, idrac\_redfish\_config\, idrac\_redfish\_info \- add <code>validate\_certs</code>\, <code>ca\_path</code>\, and <code>ciphers</code> options to configure TLS/SSL \([https\://github\.com/ansible\-collections/community\.general/issues/3686](https\://github\.com/ansible\-collections/community\.general/issues/3686)\, [https\://github\.com/ansible\-collections/community\.general/pull/9964](https\://github\.com/ansible\-collections/community\.general/pull/9964)\)\.
|
||||
* ilo\_redfish\_command\, ilo\_redfish\_config\, ilo\_redfish\_info \- add <code>validate\_certs</code>\, <code>ca\_path</code>\, and <code>ciphers</code> options to configure TLS/SSL \([https\://github\.com/ansible\-collections/community\.general/issues/3686](https\://github\.com/ansible\-collections/community\.general/issues/3686)\, [https\://github\.com/ansible\-collections/community\.general/pull/9964](https\://github\.com/ansible\-collections/community\.general/pull/9964)\)\.
|
||||
* keycloak module\_utils \- user groups can now be referenced by their name\, like <code>staff</code>\, or their path\, like <code>/staff/engineering</code>\. The path syntax allows users to reference subgroups\, which is not possible otherwise \([https\://github\.com/ansible\-collections/community\.general/pull/9898](https\://github\.com/ansible\-collections/community\.general/pull/9898)\)\.
|
||||
* keycloak\_user module \- user groups can now be referenced by their name\, like <code>staff</code>\, or their path\, like <code>/staff/engineering</code>\. The path syntax allows users to reference subgroups\, which is not possible otherwise \([https\://github\.com/ansible\-collections/community\.general/pull/9898](https\://github\.com/ansible\-collections/community\.general/pull/9898)\)\.
|
||||
* nmcli \- add support for Infiniband MAC setting when <code>type</code> is <code>infiniband</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9962](https\://github\.com/ansible\-collections/community\.general/pull/9962)\)\.
|
||||
* one\_vm \- update allowed values for <code>updateconf</code> to include new parameters as per the latest OpenNebula API documentation\.
|
||||
Added parameters\:
|
||||
|
||||
- <code>OS</code>\: <code>FIRMWARE</code>\;
|
||||
- <code>CPU\_MODEL</code>\: <code>MODEL</code>\, <code>FEATURES</code>\;
|
||||
- <code>FEATURES</code>\: <code>VIRTIO\_BLK\_QUEUES</code>\, <code>VIRTIO\_SCSI\_QUEUES</code>\, <code>IOTHREADS</code>\;
|
||||
- <code>GRAPHICS</code>\: <code>PORT</code>\, <code>COMMAND</code>\;
|
||||
- <code>VIDEO</code>\: <code>ATS</code>\, <code>IOMMU</code>\, <code>RESOLUTION</code>\, <code>TYPE</code>\, <code>VRAM</code>\;
|
||||
- <code>RAW</code>\: <code>VALIDATE</code>\;
|
||||
- <code>BACKUP\_CONFIG</code>\: <code>FS\_FREEZE</code>\, <code>KEEP\_LAST</code>\, <code>BACKUP\_VOLATILE</code>\, <code>MODE</code>\, <code>INCREMENT\_MODE</code>\.
|
||||
|
||||
\([https\://github\.com/ansible\-collections/community\.general/pull/9959](https\://github\.com/ansible\-collections/community\.general/pull/9959)\)\.
|
||||
* proxmox and proxmox\_kvm modules \- allow uppercase characters in VM/container tags \([https\://github\.com/ansible\-collections/community\.general/issues/9895](https\://github\.com/ansible\-collections/community\.general/issues/9895)\, [https\://github\.com/ansible\-collections/community\.general/pull/10024](https\://github\.com/ansible\-collections/community\.general/pull/10024)\)\.
|
||||
* puppet \- improve parameter formatting\, no impact to user \([https\://github\.com/ansible\-collections/community\.general/pull/10014](https\://github\.com/ansible\-collections/community\.general/pull/10014)\)\.
|
||||
* redfish module utils \- add <code>REDFISH\_COMMON\_ARGUMENT\_SPEC</code>\, a corresponding <code>redfish</code> docs fragment\, and support for its <code>validate\_certs</code>\, <code>ca\_path</code>\, and <code>ciphers</code> options \([https\://github\.com/ansible\-collections/community\.general/issues/3686](https\://github\.com/ansible\-collections/community\.general/issues/3686)\, [https\://github\.com/ansible\-collections/community\.general/pull/9964](https\://github\.com/ansible\-collections/community\.general/pull/9964)\)\.
|
||||
* redfish\_command\, redfish\_config\, redfish\_info \- add <code>validate\_certs</code> and <code>ca\_path</code> options to configure TLS/SSL \([https\://github\.com/ansible\-collections/community\.general/issues/3686](https\://github\.com/ansible\-collections/community\.general/issues/3686)\, [https\://github\.com/ansible\-collections/community\.general/pull/9964](https\://github\.com/ansible\-collections/community\.general/pull/9964)\)\.
|
||||
* rocketchat \- fix duplicate JSON conversion for Rocket\.Chat \< 7\.4\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9965](https\://github\.com/ansible\-collections/community\.general/pull/9965)\)\.
|
||||
* wdc\_redfish\_command\, wdc\_redfish\_info \- add <code>validate\_certs</code>\, <code>ca\_path</code>\, and <code>ciphers</code> options to configure TLS/SSL \([https\://github\.com/ansible\-collections/community\.general/issues/3686](https\://github\.com/ansible\-collections/community\.general/issues/3686)\, [https\://github\.com/ansible\-collections/community\.general/pull/9964](https\://github\.com/ansible\-collections/community\.general/pull/9964)\)\.
|
||||
* xcc\_redfish\_command \- add <code>validate\_certs</code>\, <code>ca\_path</code>\, and <code>ciphers</code> options to configure TLS/SSL \([https\://github\.com/ansible\-collections/community\.general/issues/3686](https\://github\.com/ansible\-collections/community\.general/issues/3686)\, [https\://github\.com/ansible\-collections/community\.general/pull/9964](https\://github\.com/ansible\-collections/community\.general/pull/9964)\)\.
|
||||
* zypper \- adds <code>skip\_post\_errors</code> that allows to skip RPM post\-install errors \(Zypper return code 107\) \([https\://github\.com/ansible\-collections/community\.general/issues/9972](https\://github\.com/ansible\-collections/community\.general/issues/9972)\)\.
|
||||
|
||||
<a id="deprecated-features"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* manifold lookup plugin \- plugin is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/10028](https\://github\.com/ansible\-collections/community\.general/pull/10028)\)\.
|
||||
* stackpath\_compute inventory plugin \- plugin is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/10026](https\://github\.com/ansible\-collections/community\.general/pull/10026)\)\.
|
||||
|
||||
<a id="bugfixes"></a>
|
||||
### Bugfixes
|
||||
|
||||
* dependent look plugin \- make compatible with ansible\-core\'s Data Tagging feature \([https\://github\.com/ansible\-collections/community\.general/pull/9833](https\://github\.com/ansible\-collections/community\.general/pull/9833)\)\.
|
||||
* diy callback plugin \- make compatible with ansible\-core\'s Data Tagging feature \([https\://github\.com/ansible\-collections/community\.general/pull/9833](https\://github\.com/ansible\-collections/community\.general/pull/9833)\)\.
|
||||
* github\_deploy\_key \- check that key really exists on 422 to avoid masking other errors \([https\://github\.com/ansible\-collections/community\.general/issues/6718](https\://github\.com/ansible\-collections/community\.general/issues/6718)\, [https\://github\.com/ansible\-collections/community\.general/pull/10011](https\://github\.com/ansible\-collections/community\.general/pull/10011)\)\.
|
||||
* hashids and unicode\_normalize filter plugins \- avoid deprecated <code>AnsibleFilterTypeError</code> on ansible\-core 2\.19 \([https\://github\.com/ansible\-collections/community\.general/pull/9992](https\://github\.com/ansible\-collections/community\.general/pull/9992)\)\.
|
||||
* homebrew \- emit a useful error message if <code>brew info</code> reports a package tap is <code>null</code> \([https\://github\.com/ansible\-collections/community\.general/pull/10013](https\://github\.com/ansible\-collections/community\.general/pull/10013)\, [https\://github\.com/ansible\-collections/community\.general/issues/10012](https\://github\.com/ansible\-collections/community\.general/issues/10012)\)\.
|
||||
* java\_cert \- the module no longer fails if the optional parameters <code>pkcs12\_alias</code> and <code>cert\_alias</code> are not provided \([https\://github\.com/ansible\-collections/community\.general/pull/9970](https\://github\.com/ansible\-collections/community\.general/pull/9970)\)\.
|
||||
* keycloak\_authentication \- fix authentification config duplication for Keycloak \< 26\.2\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/9987](https\://github\.com/ansible\-collections/community\.general/pull/9987)\)\.
|
||||
* keycloak\_client \- fix the idempotency regression by normalizing the Keycloak response for <code>after\_client</code> \([https\://github\.com/ansible\-collections/community\.general/issues/9905](https\://github\.com/ansible\-collections/community\.general/issues/9905)\, [https\://github\.com/ansible\-collections/community\.general/pull/9976](https\://github\.com/ansible\-collections/community\.general/pull/9976)\)\.
|
||||
* proxmox inventory plugin \- fix <code>ansible\_host</code> staying empty for certain Proxmox nodes \([https\://github\.com/ansible\-collections/community\.general/issues/5906](https\://github\.com/ansible\-collections/community\.general/issues/5906)\, [https\://github\.com/ansible\-collections/community\.general/pull/9952](https\://github\.com/ansible\-collections/community\.general/pull/9952)\)\.
|
||||
* proxmox\_disk \- fail gracefully if <code>storage</code> is required but not provided by the user \([https\://github\.com/ansible\-collections/community\.general/issues/9941](https\://github\.com/ansible\-collections/community\.general/issues/9941)\, [https\://github\.com/ansible\-collections/community\.general/pull/9963](https\://github\.com/ansible\-collections/community\.general/pull/9963)\)\.
|
||||
* reveal\_ansible\_type filter plugin and ansible\_type test plugin \- make compatible with ansible\-core\'s Data Tagging feature \([https\://github\.com/ansible\-collections/community\.general/pull/9833](https\://github\.com/ansible\-collections/community\.general/pull/9833)\)\.
|
||||
* sysrc \- no longer always reporting <code>changed\=true</code> when <code>state\=absent</code>\. This fixes the method <code>exists\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/issues/10004](https\://github\.com/ansible\-collections/community\.general/issues/10004)\, [https\://github\.com/ansible\-collections/community\.general/pull/10005](https\://github\.com/ansible\-collections/community\.general/pull/10005)\)\.
|
||||
* yaml callback plugin \- use ansible\-core internals to avoid breakage with Data Tagging \([https\://github\.com/ansible\-collections/community\.general/pull/9833](https\://github\.com/ansible\-collections/community\.general/pull/9833)\)\.
|
||||
|
||||
<a id="known-issues"></a>
|
||||
### Known Issues
|
||||
|
||||
* reveal\_ansible\_type filter plugin and ansible\_type test plugin \- note that ansible\-core\'s Data Tagging feature implements new aliases\, such as <code>\_AnsibleTaggedStr</code> for <code>str</code>\, <code>\_AnsibleTaggedInt</code> for <code>int</code>\, and <code>\_AnsibleTaggedFloat</code> for <code>float</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9833](https\://github\.com/ansible\-collections/community\.general/pull/9833)\)\.
|
||||
|
||||
<a id="new-plugins"></a>
|
||||
### New Plugins
|
||||
|
||||
<a id="connection"></a>
|
||||
#### Connection
|
||||
|
||||
* community\.general\.wsl \- Run tasks in WSL distribution using wsl\.exe CLI via SSH\.
|
||||
|
||||
<a id="v10-5-0"></a>
|
||||
## v10\.5\.0
|
||||
|
||||
<a id="release-summary-1"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-1"></a>
|
||||
### Minor Changes
|
||||
|
||||
* CmdRunner module utils \- the convenience method <code>cmd\_runner\_fmt\.as\_fixed\(\)</code> now accepts multiple arguments as a list \([https\://github\.com/ansible\-collections/community\.general/pull/9893](https\://github\.com/ansible\-collections/community\.general/pull/9893)\)\.
|
||||
* apache2\_mod\_proxy \- code simplification\, no change in functionality \([https\://github\.com/ansible\-collections/community\.general/pull/9457](https\://github\.com/ansible\-collections/community\.general/pull/9457)\)\.
|
||||
* consul\_token \- fix idempotency when <code>policies</code> or <code>roles</code> are supplied by name \([https\://github\.com/ansible\-collections/community\.general/issues/9841](https\://github\.com/ansible\-collections/community\.general/issues/9841)\, [https\://github\.com/ansible\-collections/community\.general/pull/9845](https\://github\.com/ansible\-collections/community\.general/pull/9845)\)\.
|
||||
* keycloak\_realm \- remove ID requirement when creating a realm to allow Keycloak generating its own realm ID \([https\://github\.com/ansible\-collections/community\.general/pull/9768](https\://github\.com/ansible\-collections/community\.general/pull/9768)\)\.
|
||||
* nmap inventory plugin \- adds <code>dns\_servers</code> option for specifying DNS servers for name resolution\. Accepts hostnames or IP addresses in the same format as the <code>exclude</code> option \([https\://github\.com/ansible\-collections/community\.general/pull/9849](https\://github\.com/ansible\-collections/community\.general/pull/9849)\)\.
|
||||
* proxmox\_kvm \- add missing audio hardware device handling \([https\://github\.com/ansible\-collections/community\.general/issues/5192](https\://github\.com/ansible\-collections/community\.general/issues/5192)\, [https\://github\.com/ansible\-collections/community\.general/pull/9847](https\://github\.com/ansible\-collections/community\.general/pull/9847)\)\.
|
||||
* redfish\_config \- add command <code>SetPowerRestorePolicy</code> to set the desired power state of the system when power is restored \([https\://github\.com/ansible\-collections/community\.general/pull/9837](https\://github\.com/ansible\-collections/community\.general/pull/9837)\)\.
|
||||
* redfish\_info \- add command <code>GetPowerRestorePolicy</code> to get the desired power state of the system when power is restored \([https\://github\.com/ansible\-collections/community\.general/pull/9824](https\://github\.com/ansible\-collections/community\.general/pull/9824)\)\.
|
||||
* rocketchat \- option <code>is\_pre740</code> has been added to control the format of the payload\. For Rocket\.Chat 7\.4\.0 or newer\, it must be set to <code>false</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9882](https\://github\.com/ansible\-collections/community\.general/pull/9882)\)\.
|
||||
* slack callback plugin \- add <code>http\_agent</code> option to enable the user to set a custom user agent for slack callback plugin \([https\://github\.com/ansible\-collections/community\.general/issues/9813](https\://github\.com/ansible\-collections/community\.general/issues/9813)\, [https\://github\.com/ansible\-collections/community\.general/pull/9836](https\://github\.com/ansible\-collections/community\.general/pull/9836)\)\.
|
||||
* systemd\_info \- add wildcard expression support in <code>unitname</code> option \([https\://github\.com/ansible\-collections/community\.general/pull/9821](https\://github\.com/ansible\-collections/community\.general/pull/9821)\)\.
|
||||
* systemd\_info \- extend support to timer units \([https\://github\.com/ansible\-collections/community\.general/pull/9891](https\://github\.com/ansible\-collections/community\.general/pull/9891)\)\.
|
||||
* vmadm \- add new options <code>flexible\_disk\_size</code> and <code>owner\_uuid</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9892](https\://github\.com/ansible\-collections/community\.general/pull/9892)\)\.
|
||||
|
||||
<a id="bugfixes-1"></a>
|
||||
### Bugfixes
|
||||
|
||||
* cloudlare\_dns \- handle exhausted response stream in case of HTTP errors to show nice error message to the user \([https\://github\.com/ansible\-collections/community\.general/issues/9782](https\://github\.com/ansible\-collections/community\.general/issues/9782)\, [https\://github\.com/ansible\-collections/community\.general/pull/9818](https\://github\.com/ansible\-collections/community\.general/pull/9818)\)\.
|
||||
* dnf\_versionlock \- add support for dnf5 \([https\://github\.com/ansible\-collections/community\.general/issues/9556](https\://github\.com/ansible\-collections/community\.general/issues/9556)\)\.
|
||||
* homebrew \- fix crash when package names include tap \([https\://github\.com/ansible\-collections/community\.general/issues/9777](https\://github\.com/ansible\-collections/community\.general/issues/9777)\, [https\://github\.com/ansible\-collections/community\.general/pull/9803](https\://github\.com/ansible\-collections/community\.general/pull/9803)\)\.
|
||||
* homebrew\_cask \- handle unusual brew version strings \([https\://github\.com/ansible\-collections/community\.general/issues/8432](https\://github\.com/ansible\-collections/community\.general/issues/8432)\, [https\://github\.com/ansible\-collections/community\.general/pull/9881](https\://github\.com/ansible\-collections/community\.general/pull/9881)\)\.
|
||||
* nmcli \- enable changing only the order of DNS servers or search suffixes \([https\://github\.com/ansible\-collections/community\.general/issues/8724](https\://github\.com/ansible\-collections/community\.general/issues/8724)\, [https\://github\.com/ansible\-collections/community\.general/pull/9880](https\://github\.com/ansible\-collections/community\.general/pull/9880)\)\.
|
||||
* proxmox \- add missing key selection of <code>\'status\'</code> key to <code>get\_lxc\_status</code> \([https\://github\.com/ansible\-collections/community\.general/issues/9696](https\://github\.com/ansible\-collections/community\.general/issues/9696)\, [https\://github\.com/ansible\-collections/community\.general/pull/9809](https\://github\.com/ansible\-collections/community\.general/pull/9809)\)\.
|
||||
* proxmox\_vm\_info \- the module no longer expects that the key <code>template</code> exists in a dictionary returned by Proxmox \([https\://github\.com/ansible\-collections/community\.general/issues/9875](https\://github\.com/ansible\-collections/community\.general/issues/9875)\, [https\://github\.com/ansible\-collections/community\.general/pull/9910](https\://github\.com/ansible\-collections/community\.general/pull/9910)\)\.
|
||||
* sudoers \- display stdout and stderr raised while failed validation \([https\://github\.com/ansible\-collections/community\.general/issues/9674](https\://github\.com/ansible\-collections/community\.general/issues/9674)\, [https\://github\.com/ansible\-collections/community\.general/pull/9871](https\://github\.com/ansible\-collections/community\.general/pull/9871)\)\.
|
||||
|
||||
<a id="new-modules"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.pacemaker\_resource \- Manage pacemaker resources\.
|
||||
|
||||
<a id="v10-4-0"></a>
|
||||
## v10\.4\.0
|
||||
|
||||
<a id="release-summary-2"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-2"></a>
|
||||
### Minor Changes
|
||||
|
||||
* 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-1"></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-2"></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-1"></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-3"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix release\.
|
||||
|
||||
<a id="minor-changes-3"></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-3"></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)\)\.
|
||||
* keycloak\_client \- fix and improve existing tests\. The module showed a diff without actual changes\, solved by improving the <code>normalise\_cr\(\)</code> function \([https\://github\.com/ansible\-collections/community\.general/pull/9644](https\://github\.com/ansible\-collections/community\.general/pull/9644)\)\.
|
||||
* proxmox \- adds the <code>pubkey</code> parameter \(back to\) the <code>update</code> state \([https\://github\.com/ansible\-collections/community\.general/issues/9642](https\://github\.com/ansible\-collections/community\.general/issues/9642)\, [https\://github\.com/ansible\-collections/community\.general/pull/9645](https\://github\.com/ansible\-collections/community\.general/pull/9645)\)\.
|
||||
* proxmox \- fixes a typo in the translation of the <code>pubkey</code> parameter to proxmox\' <code>ssh\-public\-keys</code> \([https\://github\.com/ansible\-collections/community\.general/issues/9642](https\://github\.com/ansible\-collections/community\.general/issues/9642)\, [https\://github\.com/ansible\-collections/community\.general/pull/9645](https\://github\.com/ansible\-collections/community\.general/pull/9645)\)\.
|
||||
* xml \- ensure file descriptor is closed \([https\://github\.com/ansible\-collections/community\.general/pull/9695](https\://github\.com/ansible\-collections/community\.general/pull/9695)\)\.
|
||||
|
||||
<a id="v10-3-0"></a>
|
||||
## v10\.3\.0
|
||||
|
||||
<a id="release-summary-4"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-4"></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)\)\.
|
||||
* apache2\_mod\_proxy \- better handling regexp extraction \([https\://github\.com/ansible\-collections/community\.general/pull/9609](https\://github\.com/ansible\-collections/community\.general/pull/9609)\)\.
|
||||
* apache2\_mod\_proxy \- change type of <code>state</code> to a list of strings\. No change for the users \([https\://github\.com/ansible\-collections/community\.general/pull/9600](https\://github\.com/ansible\-collections/community\.general/pull/9600)\)\.
|
||||
@@ -110,6 +328,7 @@ Regular bugfix and feature release\.
|
||||
* jira \- transition operation now has <code>status\_id</code> to directly reference wanted transition \([https\://github\.com/ansible\-collections/community\.general/pull/9602](https\://github\.com/ansible\-collections/community\.general/pull/9602)\)\.
|
||||
* json\_query filter plugin \- adjust standard preamble for Python 3 \([https\://github\.com/ansible\-collections/community\.general/pull/9585](https\://github\.com/ansible\-collections/community\.general/pull/9585)\)\.
|
||||
* keep\_keys filter plugin \- adjust standard preamble for Python 3 \([https\://github\.com/ansible\-collections/community\.general/pull/9585](https\://github\.com/ansible\-collections/community\.general/pull/9585)\)\.
|
||||
* keycloak\_\* modules \- <code>refresh\_token</code> parameter added\. When multiple authentication parameters are provided \(<code>token</code>\, <code>refresh\_token</code>\, and <code>auth\_username</code>/<code>auth\_password</code>\)\, modules will now automatically retry requests upon authentication errors \(401\)\, using in order the token\, refresh token\, and username/password \([https\://github\.com/ansible\-collections/community\.general/pull/9494](https\://github\.com/ansible\-collections/community\.general/pull/9494)\)\.
|
||||
* known\_hosts \- open file using <code>open\(\)</code> as a context manager \([https\://github\.com/ansible\-collections/community\.general/pull/9579](https\://github\.com/ansible\-collections/community\.general/pull/9579)\)\.
|
||||
* ksu become 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)\)\.
|
||||
* linode inventory 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)\)\.
|
||||
@@ -183,7 +402,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-2"></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)\)\.
|
||||
@@ -194,7 +413,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"></a>
|
||||
<a id="bugfixes-4"></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)\)\.
|
||||
@@ -210,10 +429,10 @@ Regular bugfix and feature release\.
|
||||
thus unsubscribing will fail
|
||||
\([https\://github\.com/ansible\-collections/community\.general/pull/9578](https\://github\.com/ansible\-collections/community\.general/pull/9578)\)\.
|
||||
|
||||
<a id="new-plugins"></a>
|
||||
<a id="new-plugins-1"></a>
|
||||
### New Plugins
|
||||
|
||||
<a id="connection"></a>
|
||||
<a id="connection-1"></a>
|
||||
#### Connection
|
||||
|
||||
* community\.general\.proxmox\_pct\_remote \- Run tasks in Proxmox LXC container instances using pct CLI via SSH\.
|
||||
@@ -230,7 +449,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-2"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.proxmox\_backup\_info \- Retrieve information on Proxmox scheduled backups\.
|
||||
@@ -238,12 +457,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v10-2-0"></a>
|
||||
## v10\.2\.0
|
||||
|
||||
<a id="release-summary-1"></a>
|
||||
<a id="release-summary-5"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-1"></a>
|
||||
<a id="minor-changes-5"></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)\)\.
|
||||
@@ -374,7 +593,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-3"></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)\)\.
|
||||
@@ -397,7 +616,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-1"></a>
|
||||
<a id="bugfixes-5"></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)\)\.
|
||||
@@ -409,7 +628,7 @@ Regular bugfix and feature release\.
|
||||
* qubes connection plugin \- fix the printing of debug information \([https\://github\.com/ansible\-collections/community\.general/pull/9334](https\://github\.com/ansible\-collections/community\.general/pull/9334)\)\.
|
||||
* redfish\_utils module utils \- Fix <code>VerifyBiosAttributes</code> command on multi system resource nodes \([https\://github\.com/ansible\-collections/community\.general/pull/9234](https\://github\.com/ansible\-collections/community\.general/pull/9234)\)\.
|
||||
|
||||
<a id="new-plugins-1"></a>
|
||||
<a id="new-plugins-2"></a>
|
||||
### New Plugins
|
||||
|
||||
<a id="inventory"></a>
|
||||
@@ -417,7 +636,7 @@ Regular bugfix and feature release\.
|
||||
|
||||
* community\.general\.iocage \- iocage inventory source\.
|
||||
|
||||
<a id="new-modules-1"></a>
|
||||
<a id="new-modules-3"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.android\_sdk \- Manages Android SDK packages\.
|
||||
@@ -428,12 +647,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v10-1-0"></a>
|
||||
## v10\.1\.0
|
||||
|
||||
<a id="release-summary-2"></a>
|
||||
<a id="release-summary-6"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-2"></a>
|
||||
<a id="minor-changes-6"></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)\)\.
|
||||
@@ -454,13 +673,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-4"></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-2"></a>
|
||||
<a id="bugfixes-6"></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)\)\.
|
||||
@@ -472,7 +691,7 @@ Regular bugfix and feature release\.
|
||||
* keycloak\_clientscope\_type \- sort the default and optional clientscope lists to improve the diff \([https\://github\.com/ansible\-collections/community\.general/pull/9202](https\://github\.com/ansible\-collections/community\.general/pull/9202)\)\.
|
||||
* slack \- fail if Slack API response is not OK with error message \([https\://github\.com/ansible\-collections/community\.general/pull/9198](https\://github\.com/ansible\-collections/community\.general/pull/9198)\)\.
|
||||
|
||||
<a id="new-plugins-2"></a>
|
||||
<a id="new-plugins-3"></a>
|
||||
### New Plugins
|
||||
|
||||
<a id="filter-1"></a>
|
||||
@@ -480,7 +699,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-4"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.decompress \- Decompresses compressed files\.
|
||||
@@ -489,12 +708,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v10-0-1"></a>
|
||||
## v10\.0\.1
|
||||
|
||||
<a id="release-summary-3"></a>
|
||||
<a id="release-summary-7"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix release for inclusion in Ansible 11\.0\.0rc1\.
|
||||
|
||||
<a id="bugfixes-3"></a>
|
||||
<a id="bugfixes-7"></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)\)\.
|
||||
@@ -504,12 +723,12 @@ Bugfix release for inclusion in Ansible 11\.0\.0rc1\.
|
||||
<a id="v10-0-0"></a>
|
||||
## v10\.0\.0
|
||||
|
||||
<a id="release-summary-4"></a>
|
||||
<a id="release-summary-8"></a>
|
||||
### Release Summary
|
||||
|
||||
This is release 10\.0\.0 of <code>community\.general</code>\, released on 2024\-11\-04\.
|
||||
|
||||
<a id="minor-changes-3"></a>
|
||||
<a id="minor-changes-7"></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)\)\.
|
||||
@@ -714,7 +933,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-5"></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)\)\.
|
||||
@@ -739,7 +958,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-4"></a>
|
||||
<a id="bugfixes-8"></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)\)\.
|
||||
@@ -819,12 +1038,12 @@ This is release 10\.0\.0 of <code>community\.general</code>\, released on 2024\-
|
||||
* snap\_alias \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
* udm\_user \- the module now tries to use <code>legacycrypt</code> on Python 3\.13\+ \([https\://github\.com/ansible\-collections/community\.general/issues/4690](https\://github\.com/ansible\-collections/community\.general/issues/4690)\, [https\://github\.com/ansible\-collections/community\.general/pull/8987](https\://github\.com/ansible\-collections/community\.general/pull/8987)\)\.
|
||||
|
||||
<a id="known-issues"></a>
|
||||
<a id="known-issues-1"></a>
|
||||
### Known Issues
|
||||
|
||||
* jenkins\_node \- the module is not able to update offline message when node is already offline due to internally using toggleOffline API \([https\://github\.com/ansible\-collections/community\.general/pull/9084](https\://github\.com/ansible\-collections/community\.general/pull/9084)\)\.
|
||||
|
||||
<a id="new-plugins-3"></a>
|
||||
<a id="new-plugins-4"></a>
|
||||
### New Plugins
|
||||
|
||||
<a id="filter-2"></a>
|
||||
@@ -840,7 +1059,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-5"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.bootc\_manage \- Bootc Switch and Upgrade\.
|
||||
|
||||
203
CHANGELOG.rst
203
CHANGELOG.rst
@@ -6,7 +6,7 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 9.0.0.
|
||||
|
||||
v10.3.0
|
||||
v10.6.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
@@ -14,10 +14,206 @@ Release Summary
|
||||
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Major Changes
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- keycloak_* modules - ``refresh_token`` parameter added. When multiple authentication parameters are provided (``token``, ``refresh_token``, and ``auth_username``/``auth_password``), modules will now automatically retry requests upon authentication errors (401), using in order the token, refresh token, and username/password (https://github.com/ansible-collections/community.general/pull/9494).
|
||||
- apache2_module - added workaround for new PHP module name, from ``php7_module`` to ``php_module`` (https://github.com/ansible-collections/community.general/pull/9951).
|
||||
- gitlab_project - add option ``build_timeout`` (https://github.com/ansible-collections/community.general/pull/9960).
|
||||
- gitlab_project_members - extend choices parameter ``access_level`` by missing upstream valid value ``owner`` (https://github.com/ansible-collections/community.general/pull/9953).
|
||||
- hpilo_boot - add option to get an idempotent behavior while powering on server, resulting in success instead of failure when using ``state: boot_once`` option (https://github.com/ansible-collections/community.general/pull/9646).
|
||||
- idrac_redfish_command, idrac_redfish_config, idrac_redfish_info - add ``validate_certs``, ``ca_path``, and ``ciphers`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686, https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- ilo_redfish_command, ilo_redfish_config, ilo_redfish_info - add ``validate_certs``, ``ca_path``, and ``ciphers`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686, https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- keycloak module_utils - user groups can now be referenced by their name, like ``staff``, or their path, like ``/staff/engineering``. The path syntax allows users to reference subgroups, which is not possible otherwise (https://github.com/ansible-collections/community.general/pull/9898).
|
||||
- keycloak_user module - user groups can now be referenced by their name, like ``staff``, or their path, like ``/staff/engineering``. The path syntax allows users to reference subgroups, which is not possible otherwise (https://github.com/ansible-collections/community.general/pull/9898).
|
||||
- nmcli - add support for Infiniband MAC setting when ``type`` is ``infiniband`` (https://github.com/ansible-collections/community.general/pull/9962).
|
||||
- one_vm - update allowed values for ``updateconf`` to include new parameters as per the latest OpenNebula API documentation.
|
||||
Added parameters:
|
||||
|
||||
* ``OS``: ``FIRMWARE``;
|
||||
* ``CPU_MODEL``: ``MODEL``, ``FEATURES``;
|
||||
* ``FEATURES``: ``VIRTIO_BLK_QUEUES``, ``VIRTIO_SCSI_QUEUES``, ``IOTHREADS``;
|
||||
* ``GRAPHICS``: ``PORT``, ``COMMAND``;
|
||||
* ``VIDEO``: ``ATS``, ``IOMMU``, ``RESOLUTION``, ``TYPE``, ``VRAM``;
|
||||
* ``RAW``: ``VALIDATE``;
|
||||
* ``BACKUP_CONFIG``: ``FS_FREEZE``, ``KEEP_LAST``, ``BACKUP_VOLATILE``, ``MODE``, ``INCREMENT_MODE``.
|
||||
|
||||
(https://github.com/ansible-collections/community.general/pull/9959).
|
||||
- proxmox and proxmox_kvm modules - allow uppercase characters in VM/container tags (https://github.com/ansible-collections/community.general/issues/9895, https://github.com/ansible-collections/community.general/pull/10024).
|
||||
- puppet - improve parameter formatting, no impact to user (https://github.com/ansible-collections/community.general/pull/10014).
|
||||
- redfish module utils - add ``REDFISH_COMMON_ARGUMENT_SPEC``, a corresponding ``redfish`` docs fragment, and support for its ``validate_certs``, ``ca_path``, and ``ciphers`` options (https://github.com/ansible-collections/community.general/issues/3686, https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- redfish_command, redfish_config, redfish_info - add ``validate_certs`` and ``ca_path`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686, https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- rocketchat - fix duplicate JSON conversion for Rocket.Chat < 7.4.0 (https://github.com/ansible-collections/community.general/pull/9965).
|
||||
- wdc_redfish_command, wdc_redfish_info - add ``validate_certs``, ``ca_path``, and ``ciphers`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686, https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- xcc_redfish_command - add ``validate_certs``, ``ca_path``, and ``ciphers`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686, https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- zypper - adds ``skip_post_errors`` that allows to skip RPM post-install errors (Zypper return code 107) (https://github.com/ansible-collections/community.general/issues/9972).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- manifold lookup plugin - plugin is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/10028).
|
||||
- stackpath_compute inventory plugin - plugin is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/10026).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- dependent look plugin - make compatible with ansible-core's Data Tagging feature (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
- diy callback plugin - make compatible with ansible-core's Data Tagging feature (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
- github_deploy_key - check that key really exists on 422 to avoid masking other errors (https://github.com/ansible-collections/community.general/issues/6718, https://github.com/ansible-collections/community.general/pull/10011).
|
||||
- hashids and unicode_normalize filter plugins - avoid deprecated ``AnsibleFilterTypeError`` on ansible-core 2.19 (https://github.com/ansible-collections/community.general/pull/9992).
|
||||
- homebrew - emit a useful error message if ``brew info`` reports a package tap is ``null`` (https://github.com/ansible-collections/community.general/pull/10013, https://github.com/ansible-collections/community.general/issues/10012).
|
||||
- java_cert - the module no longer fails if the optional parameters ``pkcs12_alias`` and ``cert_alias`` are not provided (https://github.com/ansible-collections/community.general/pull/9970).
|
||||
- keycloak_authentication - fix authentification config duplication for Keycloak < 26.2.0 (https://github.com/ansible-collections/community.general/pull/9987).
|
||||
- keycloak_client - fix the idempotency regression by normalizing the Keycloak response for ``after_client`` (https://github.com/ansible-collections/community.general/issues/9905, https://github.com/ansible-collections/community.general/pull/9976).
|
||||
- proxmox inventory plugin - fix ``ansible_host`` staying empty for certain Proxmox nodes (https://github.com/ansible-collections/community.general/issues/5906, https://github.com/ansible-collections/community.general/pull/9952).
|
||||
- proxmox_disk - fail gracefully if ``storage`` is required but not provided by the user (https://github.com/ansible-collections/community.general/issues/9941, https://github.com/ansible-collections/community.general/pull/9963).
|
||||
- reveal_ansible_type filter plugin and ansible_type test plugin - make compatible with ansible-core's Data Tagging feature (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
- sysrc - no longer always reporting ``changed=true`` when ``state=absent``. This fixes the method ``exists()`` (https://github.com/ansible-collections/community.general/issues/10004, https://github.com/ansible-collections/community.general/pull/10005).
|
||||
- yaml callback plugin - use ansible-core internals to avoid breakage with Data Tagging (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
- reveal_ansible_type filter plugin and ansible_type test plugin - note that ansible-core's Data Tagging feature implements new aliases, such as ``_AnsibleTaggedStr`` for ``str``, ``_AnsibleTaggedInt`` for ``int``, and ``_AnsibleTaggedFloat`` for ``float`` (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
|
||||
New Plugins
|
||||
-----------
|
||||
|
||||
Connection
|
||||
~~~~~~~~~~
|
||||
|
||||
- community.general.wsl - Run tasks in WSL distribution using wsl.exe CLI via SSH.
|
||||
|
||||
v10.5.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- CmdRunner module utils - the convenience method ``cmd_runner_fmt.as_fixed()`` now accepts multiple arguments as a list (https://github.com/ansible-collections/community.general/pull/9893).
|
||||
- apache2_mod_proxy - code simplification, no change in functionality (https://github.com/ansible-collections/community.general/pull/9457).
|
||||
- consul_token - fix idempotency when ``policies`` or ``roles`` are supplied by name (https://github.com/ansible-collections/community.general/issues/9841, https://github.com/ansible-collections/community.general/pull/9845).
|
||||
- keycloak_realm - remove ID requirement when creating a realm to allow Keycloak generating its own realm ID (https://github.com/ansible-collections/community.general/pull/9768).
|
||||
- nmap inventory plugin - adds ``dns_servers`` option for specifying DNS servers for name resolution. Accepts hostnames or IP addresses in the same format as the ``exclude`` option (https://github.com/ansible-collections/community.general/pull/9849).
|
||||
- proxmox_kvm - add missing audio hardware device handling (https://github.com/ansible-collections/community.general/issues/5192, https://github.com/ansible-collections/community.general/pull/9847).
|
||||
- redfish_config - add command ``SetPowerRestorePolicy`` to set the desired power state of the system when power is restored (https://github.com/ansible-collections/community.general/pull/9837).
|
||||
- redfish_info - add command ``GetPowerRestorePolicy`` to get the desired power state of the system when power is restored (https://github.com/ansible-collections/community.general/pull/9824).
|
||||
- rocketchat - option ``is_pre740`` has been added to control the format of the payload. For Rocket.Chat 7.4.0 or newer, it must be set to ``false`` (https://github.com/ansible-collections/community.general/pull/9882).
|
||||
- slack callback plugin - add ``http_agent`` option to enable the user to set a custom user agent for slack callback plugin (https://github.com/ansible-collections/community.general/issues/9813, https://github.com/ansible-collections/community.general/pull/9836).
|
||||
- systemd_info - add wildcard expression support in ``unitname`` option (https://github.com/ansible-collections/community.general/pull/9821).
|
||||
- systemd_info - extend support to timer units (https://github.com/ansible-collections/community.general/pull/9891).
|
||||
- vmadm - add new options ``flexible_disk_size`` and ``owner_uuid`` (https://github.com/ansible-collections/community.general/pull/9892).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- cloudlare_dns - handle exhausted response stream in case of HTTP errors to show nice error message to the user (https://github.com/ansible-collections/community.general/issues/9782, https://github.com/ansible-collections/community.general/pull/9818).
|
||||
- dnf_versionlock - add support for dnf5 (https://github.com/ansible-collections/community.general/issues/9556).
|
||||
- homebrew - fix crash when package names include tap (https://github.com/ansible-collections/community.general/issues/9777, https://github.com/ansible-collections/community.general/pull/9803).
|
||||
- homebrew_cask - handle unusual brew version strings (https://github.com/ansible-collections/community.general/issues/8432, https://github.com/ansible-collections/community.general/pull/9881).
|
||||
- nmcli - enable changing only the order of DNS servers or search suffixes (https://github.com/ansible-collections/community.general/issues/8724, https://github.com/ansible-collections/community.general/pull/9880).
|
||||
- proxmox - add missing key selection of ``'status'`` key to ``get_lxc_status`` (https://github.com/ansible-collections/community.general/issues/9696, https://github.com/ansible-collections/community.general/pull/9809).
|
||||
- proxmox_vm_info - the module no longer expects that the key ``template`` exists in a dictionary returned by Proxmox (https://github.com/ansible-collections/community.general/issues/9875, https://github.com/ansible-collections/community.general/pull/9910).
|
||||
- sudoers - display stdout and stderr raised while failed validation (https://github.com/ansible-collections/community.general/issues/9674, https://github.com/ansible-collections/community.general/pull/9871).
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- community.general.pacemaker_resource - Manage pacemaker resources.
|
||||
|
||||
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
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- onepassword_ssh_key - refactor to move code to lookup class (https://github.com/ansible-collections/community.general/pull/9633).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- cloudflare_dns - fix crash when deleting a DNS record or when updating a record with ``solo=true`` (https://github.com/ansible-collections/community.general/issues/9652, 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/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/pull/9691).
|
||||
- keycloak_client - fix and improve existing tests. The module showed a diff without actual changes, solved by improving the ``normalise_cr()`` function (https://github.com/ansible-collections/community.general/pull/9644).
|
||||
- proxmox - adds the ``pubkey`` parameter (back to) the ``update`` state (https://github.com/ansible-collections/community.general/issues/9642, https://github.com/ansible-collections/community.general/pull/9645).
|
||||
- proxmox - fixes a typo in the translation of the ``pubkey`` parameter to proxmox' ``ssh-public-keys`` (https://github.com/ansible-collections/community.general/issues/9642, https://github.com/ansible-collections/community.general/pull/9645).
|
||||
- xml - ensure file descriptor is closed (https://github.com/ansible-collections/community.general/pull/9695).
|
||||
|
||||
v10.3.0
|
||||
=======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
@@ -68,6 +264,7 @@ Minor Changes
|
||||
- jira - transition operation now has ``status_id`` to directly reference wanted transition (https://github.com/ansible-collections/community.general/pull/9602).
|
||||
- json_query filter plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9585).
|
||||
- keep_keys filter plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9585).
|
||||
- keycloak_* modules - ``refresh_token`` parameter added. When multiple authentication parameters are provided (``token``, ``refresh_token``, and ``auth_username``/``auth_password``), modules will now automatically retry requests upon authentication errors (401), using in order the token, refresh token, and username/password (https://github.com/ansible-collections/community.general/pull/9494).
|
||||
- known_hosts - open file using ``open()`` as a context manager (https://github.com/ansible-collections/community.general/pull/9579).
|
||||
- ksu become plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9583).
|
||||
- linode inventory plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9584).
|
||||
|
||||
@@ -9,6 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
[](https://docs.ansible.com/ansible/latest/collections/community/general/)
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://github.com/ansible-collections/community.general/actions)
|
||||
[](https://github.com/ansible-collections/community.general/actions)
|
||||
[](https://codecov.io/gh/ansible-collections/community.general)
|
||||
[](https://api.reuse.software/info/github.com/ansible-collections/community.general)
|
||||
|
||||
|
||||
11
REUSE.toml
Normal file
11
REUSE.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
version = 1
|
||||
|
||||
[[annotations]]
|
||||
path = "changelogs/fragments/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "Ansible Project"
|
||||
SPDX-License-Identifier = "GPL-3.0-or-later"
|
||||
48
antsibull-nox.toml
Normal file
48
antsibull-nox.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: 2025 Felix Fontein <felix@fontein.de>
|
||||
|
||||
[collection_sources]
|
||||
"ansible.posix" = "git+https://github.com/ansible-collections/ansible.posix.git,main"
|
||||
"community.crypto" = "git+https://github.com/ansible-collections/community.crypto.git,main"
|
||||
"community.docker" = "git+https://github.com/ansible-collections/community.docker.git,main"
|
||||
"community.internal_test_tools" = "git+https://github.com/ansible-collections/community.internal_test_tools.git,main"
|
||||
|
||||
[sessions]
|
||||
|
||||
[sessions.docs_check]
|
||||
validate_collection_refs="all"
|
||||
|
||||
[sessions.license_check]
|
||||
|
||||
[sessions.extra_checks]
|
||||
run_no_unwanted_files = true
|
||||
no_unwanted_files_module_extensions = [".py"]
|
||||
no_unwanted_files_yaml_extensions = [".yml"]
|
||||
run_action_groups = true
|
||||
|
||||
[[sessions.extra_checks.action_groups_config]]
|
||||
name = "consul"
|
||||
pattern = "^consul_.*$"
|
||||
exclusions = [
|
||||
"consul_acl_bootstrap",
|
||||
"consul_kv",
|
||||
]
|
||||
doc_fragment = "community.general.consul.actiongroup_consul"
|
||||
|
||||
[[sessions.extra_checks.action_groups_config]]
|
||||
name = "keycloak"
|
||||
pattern = "^keycloak_.*$"
|
||||
exclusions = [
|
||||
"keycloak_realm_info",
|
||||
]
|
||||
doc_fragment = "community.general.keycloak.actiongroup_keycloak"
|
||||
|
||||
[[sessions.extra_checks.action_groups_config]]
|
||||
name = "proxmox"
|
||||
pattern = "^proxmox(_.*)?$"
|
||||
exclusions = []
|
||||
doc_fragment = "community.general.proxmox.actiongroup_proxmox"
|
||||
|
||||
[sessions.build_import_check]
|
||||
run_galaxy_importer = true
|
||||
@@ -1228,11 +1228,6 @@ releases:
|
||||
'
|
||||
- proxmox - removes default value ``false`` of ``update`` parameter. This
|
||||
will be changed to a default of ``true`` in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/9225).
|
||||
major_changes:
|
||||
- keycloak_* modules - ``refresh_token`` parameter added. When multiple authentication
|
||||
parameters are provided (``token``, ``refresh_token``, and ``auth_username``/``auth_password``),
|
||||
modules will now automatically retry requests upon authentication errors
|
||||
(401), using in order the token, refresh token, and username/password (https://github.com/ansible-collections/community.general/pull/9494).
|
||||
minor_changes:
|
||||
- MH module utils - delegate ``debug`` to the underlying ``AnsibleModule``
|
||||
instance or issues a warning if an attribute already exists with that name
|
||||
@@ -1295,6 +1290,10 @@ releases:
|
||||
wanted transition (https://github.com/ansible-collections/community.general/pull/9602).
|
||||
- json_query filter plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9585).
|
||||
- keep_keys filter plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9585).
|
||||
- keycloak_* modules - ``refresh_token`` parameter added. When multiple authentication
|
||||
parameters are provided (``token``, ``refresh_token``, and ``auth_username``/``auth_password``),
|
||||
modules will now automatically retry requests upon authentication errors
|
||||
(401), using in order the token, refresh token, and username/password (https://github.com/ansible-collections/community.general/pull/9494).
|
||||
- known_hosts - open file using ``open()`` as a context manager (https://github.com/ansible-collections/community.general/pull/9579).
|
||||
- ksu become plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9583).
|
||||
- linode inventory plugin - adjust standard preamble for Python 3 (https://github.com/ansible-collections/community.general/pull/9584).
|
||||
@@ -1436,3 +1435,369 @@ releases:
|
||||
name: onepassword_ssh_key
|
||||
namespace: null
|
||||
release_date: '2025-01-27'
|
||||
10.3.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- cloudflare_dns - fix crash when deleting a DNS record or when updating a
|
||||
record with ``solo=true`` (https://github.com/ansible-collections/community.general/issues/9652,
|
||||
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/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/pull/9691).
|
||||
- keycloak_client - fix and improve existing tests. The module showed a diff
|
||||
without actual changes, solved by improving the ``normalise_cr()`` function
|
||||
(https://github.com/ansible-collections/community.general/pull/9644).
|
||||
- proxmox - adds the ``pubkey`` parameter (back to) the ``update`` state (https://github.com/ansible-collections/community.general/issues/9642,
|
||||
https://github.com/ansible-collections/community.general/pull/9645).
|
||||
- proxmox - fixes a typo in the translation of the ``pubkey`` parameter to
|
||||
proxmox' ``ssh-public-keys`` (https://github.com/ansible-collections/community.general/issues/9642,
|
||||
https://github.com/ansible-collections/community.general/pull/9645).
|
||||
- xml - ensure file descriptor is closed (https://github.com/ansible-collections/community.general/pull/9695).
|
||||
minor_changes:
|
||||
- onepassword_ssh_key - refactor to move code to lookup class (https://github.com/ansible-collections/community.general/pull/9633).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 10.3.1.yml
|
||||
- 9633-onepassword_ssh_key.yml
|
||||
- 9644-kc_client-test-improvement-and-fix.yaml
|
||||
- 9645-proxmox-fix-pubkey.yml
|
||||
- 9649-cloudflare_dns-fix-crash-when-deleting-record.yml
|
||||
- 9665-more-resilient-handling-of-homebrew-packages-names.yml
|
||||
- 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'
|
||||
10.5.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- cloudlare_dns - handle exhausted response stream in case of HTTP errors
|
||||
to show nice error message to the user (https://github.com/ansible-collections/community.general/issues/9782,
|
||||
https://github.com/ansible-collections/community.general/pull/9818).
|
||||
- dnf_versionlock - add support for dnf5 (https://github.com/ansible-collections/community.general/issues/9556).
|
||||
- homebrew - fix crash when package names include tap (https://github.com/ansible-collections/community.general/issues/9777,
|
||||
https://github.com/ansible-collections/community.general/pull/9803).
|
||||
- homebrew_cask - handle unusual brew version strings (https://github.com/ansible-collections/community.general/issues/8432,
|
||||
https://github.com/ansible-collections/community.general/pull/9881).
|
||||
- nmcli - enable changing only the order of DNS servers or search suffixes
|
||||
(https://github.com/ansible-collections/community.general/issues/8724, https://github.com/ansible-collections/community.general/pull/9880).
|
||||
- proxmox - add missing key selection of ``'status'`` key to ``get_lxc_status``
|
||||
(https://github.com/ansible-collections/community.general/issues/9696, https://github.com/ansible-collections/community.general/pull/9809).
|
||||
- proxmox_vm_info - the module no longer expects that the key ``template``
|
||||
exists in a dictionary returned by Proxmox (https://github.com/ansible-collections/community.general/issues/9875,
|
||||
https://github.com/ansible-collections/community.general/pull/9910).
|
||||
- sudoers - display stdout and stderr raised while failed validation (https://github.com/ansible-collections/community.general/issues/9674,
|
||||
https://github.com/ansible-collections/community.general/pull/9871).
|
||||
minor_changes:
|
||||
- CmdRunner module utils - the convenience method ``cmd_runner_fmt.as_fixed()``
|
||||
now accepts multiple arguments as a list (https://github.com/ansible-collections/community.general/pull/9893).
|
||||
- apache2_mod_proxy - code simplification, no change in functionality (https://github.com/ansible-collections/community.general/pull/9457).
|
||||
- consul_token - fix idempotency when ``policies`` or ``roles`` are supplied
|
||||
by name (https://github.com/ansible-collections/community.general/issues/9841,
|
||||
https://github.com/ansible-collections/community.general/pull/9845).
|
||||
- keycloak_realm - remove ID requirement when creating a realm to allow Keycloak
|
||||
generating its own realm ID (https://github.com/ansible-collections/community.general/pull/9768).
|
||||
- nmap inventory plugin - adds ``dns_servers`` option for specifying DNS servers
|
||||
for name resolution. Accepts hostnames or IP addresses in the same format
|
||||
as the ``exclude`` option (https://github.com/ansible-collections/community.general/pull/9849).
|
||||
- proxmox_kvm - add missing audio hardware device handling (https://github.com/ansible-collections/community.general/issues/5192,
|
||||
https://github.com/ansible-collections/community.general/pull/9847).
|
||||
- redfish_config - add command ``SetPowerRestorePolicy`` to set the desired
|
||||
power state of the system when power is restored (https://github.com/ansible-collections/community.general/pull/9837).
|
||||
- redfish_info - add command ``GetPowerRestorePolicy`` to get the desired
|
||||
power state of the system when power is restored (https://github.com/ansible-collections/community.general/pull/9824).
|
||||
- rocketchat - option ``is_pre740`` has been added to control the format of
|
||||
the payload. For Rocket.Chat 7.4.0 or newer, it must be set to ``false``
|
||||
(https://github.com/ansible-collections/community.general/pull/9882).
|
||||
- slack callback plugin - add ``http_agent`` option to enable the user to
|
||||
set a custom user agent for slack callback plugin (https://github.com/ansible-collections/community.general/issues/9813,
|
||||
https://github.com/ansible-collections/community.general/pull/9836).
|
||||
- systemd_info - add wildcard expression support in ``unitname`` option (https://github.com/ansible-collections/community.general/pull/9821).
|
||||
- systemd_info - extend support to timer units (https://github.com/ansible-collections/community.general/pull/9891).
|
||||
- vmadm - add new options ``flexible_disk_size`` and ``owner_uuid`` (https://github.com/ansible-collections/community.general/pull/9892).
|
||||
release_summary: Regular bugfix and feature release.
|
||||
fragments:
|
||||
- 10.5.0.yml
|
||||
- 9457-apache2-mod-proxy-revamp.yml
|
||||
- 9768-keycloak_realm-remove-id-requirement.yaml
|
||||
- 9777-homebrew-fix-crash-when-packages-include-tap.yml
|
||||
- 9809-proxmox-fix-status-getter.yml
|
||||
- 9818-cloudflare-dns-exhausted-response.yml
|
||||
- 9821-systemd_info-add-wildcards.yml
|
||||
- 9824-redfish-implement-obtaining-powerrestorepolicy.yml
|
||||
- 9836-option-for-http-agent-for-user-to-callback-slack.yml
|
||||
- 9837-redfish-implement-setting-powerrestorepolicy.yml
|
||||
- 9845-consul_token_idempotency.yml
|
||||
- 9847-Adding_audio_device-support_to_proxmox_kvm.yml
|
||||
- 9849-nmap_dns_servers.yml
|
||||
- 9875-proxmox-dont-expect-key-template-to-exist.yml
|
||||
- 9880-nmcli-fix-reorder-same-dns-nameservers-search-suffixes.yml
|
||||
- 9882-fix-payload-to-match-rocketchat-740-requirement.yml
|
||||
- 9891-systemd_info-add_timer.yml
|
||||
- 9892-vmadm-add-new-options.yml
|
||||
- 9893-cmdrunner-as-fixed-args.yml
|
||||
- dnf_versionlock.yml
|
||||
- homebrew_cask.yml
|
||||
- sudoers.yml
|
||||
modules:
|
||||
- description: Manage pacemaker resources.
|
||||
name: pacemaker_resource
|
||||
namespace: ''
|
||||
release_date: '2025-03-24'
|
||||
10.6.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- dependent look plugin - make compatible with ansible-core's Data Tagging
|
||||
feature (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
- diy callback plugin - make compatible with ansible-core's Data Tagging feature
|
||||
(https://github.com/ansible-collections/community.general/pull/9833).
|
||||
- "github_deploy_key - check that key really exists on 422\_to avoid masking\
|
||||
\ other errors (https://github.com/ansible-collections/community.general/issues/6718,\
|
||||
\ https://github.com/ansible-collections/community.general/pull/10011)."
|
||||
- hashids and unicode_normalize filter plugins - avoid deprecated ``AnsibleFilterTypeError``
|
||||
on ansible-core 2.19 (https://github.com/ansible-collections/community.general/pull/9992).
|
||||
- homebrew - emit a useful error message if ``brew info`` reports a package
|
||||
tap is ``null`` (https://github.com/ansible-collections/community.general/pull/10013,
|
||||
https://github.com/ansible-collections/community.general/issues/10012).
|
||||
- java_cert - the module no longer fails if the optional parameters ``pkcs12_alias``
|
||||
and ``cert_alias`` are not provided (https://github.com/ansible-collections/community.general/pull/9970).
|
||||
- keycloak_authentication - fix authentification config duplication for Keycloak
|
||||
< 26.2.0 (https://github.com/ansible-collections/community.general/pull/9987).
|
||||
- keycloak_client - fix the idempotency regression by normalizing the Keycloak
|
||||
response for ``after_client`` (https://github.com/ansible-collections/community.general/issues/9905,
|
||||
https://github.com/ansible-collections/community.general/pull/9976).
|
||||
- proxmox inventory plugin - fix ``ansible_host`` staying empty for certain
|
||||
Proxmox nodes (https://github.com/ansible-collections/community.general/issues/5906,
|
||||
https://github.com/ansible-collections/community.general/pull/9952).
|
||||
- proxmox_disk - fail gracefully if ``storage`` is required but not provided
|
||||
by the user (https://github.com/ansible-collections/community.general/issues/9941,
|
||||
https://github.com/ansible-collections/community.general/pull/9963).
|
||||
- reveal_ansible_type filter plugin and ansible_type test plugin - make compatible
|
||||
with ansible-core's Data Tagging feature (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
- sysrc - no longer always reporting ``changed=true`` when ``state=absent``.
|
||||
This fixes the method ``exists()`` (https://github.com/ansible-collections/community.general/issues/10004,
|
||||
https://github.com/ansible-collections/community.general/pull/10005).
|
||||
- yaml callback plugin - use ansible-core internals to avoid breakage with
|
||||
Data Tagging (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
deprecated_features:
|
||||
- manifold lookup plugin - plugin is deprecated and will be removed in community.general
|
||||
11.0.0 (https://github.com/ansible-collections/community.general/pull/10028).
|
||||
- stackpath_compute inventory plugin - plugin is deprecated and will be removed
|
||||
in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/10026).
|
||||
known_issues:
|
||||
- reveal_ansible_type filter plugin and ansible_type test plugin - note that
|
||||
ansible-core's Data Tagging feature implements new aliases, such as ``_AnsibleTaggedStr``
|
||||
for ``str``, ``_AnsibleTaggedInt`` for ``int``, and ``_AnsibleTaggedFloat``
|
||||
for ``float`` (https://github.com/ansible-collections/community.general/pull/9833).
|
||||
minor_changes:
|
||||
- apache2_module - added workaround for new PHP module name, from ``php7_module``
|
||||
to ``php_module`` (https://github.com/ansible-collections/community.general/pull/9951).
|
||||
- gitlab_project - add option ``build_timeout`` (https://github.com/ansible-collections/community.general/pull/9960).
|
||||
- gitlab_project_members - extend choices parameter ``access_level`` by missing
|
||||
upstream valid value ``owner`` (https://github.com/ansible-collections/community.general/pull/9953).
|
||||
- 'hpilo_boot - add option to get an idempotent behavior while powering on
|
||||
server, resulting in success instead of failure when using ``state: boot_once``
|
||||
option (https://github.com/ansible-collections/community.general/pull/9646).'
|
||||
- idrac_redfish_command, idrac_redfish_config, idrac_redfish_info - add ``validate_certs``,
|
||||
``ca_path``, and ``ciphers`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686,
|
||||
https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- ilo_redfish_command, ilo_redfish_config, ilo_redfish_info - add ``validate_certs``,
|
||||
``ca_path``, and ``ciphers`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686,
|
||||
https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- keycloak module_utils - user groups can now be referenced by their name,
|
||||
like ``staff``, or their path, like ``/staff/engineering``. The path syntax
|
||||
allows users to reference subgroups, which is not possible otherwise (https://github.com/ansible-collections/community.general/pull/9898).
|
||||
- keycloak_user module - user groups can now be referenced by their name,
|
||||
like ``staff``, or their path, like ``/staff/engineering``. The path syntax
|
||||
allows users to reference subgroups, which is not possible otherwise (https://github.com/ansible-collections/community.general/pull/9898).
|
||||
- nmcli - add support for Infiniband MAC setting when ``type`` is ``infiniband``
|
||||
(https://github.com/ansible-collections/community.general/pull/9962).
|
||||
- 'one_vm - update allowed values for ``updateconf`` to include new parameters
|
||||
as per the latest OpenNebula API documentation.
|
||||
|
||||
Added parameters:
|
||||
|
||||
|
||||
* ``OS``: ``FIRMWARE``;
|
||||
|
||||
* ``CPU_MODEL``: ``MODEL``, ``FEATURES``;
|
||||
|
||||
* ``FEATURES``: ``VIRTIO_BLK_QUEUES``, ``VIRTIO_SCSI_QUEUES``, ``IOTHREADS``;
|
||||
|
||||
* ``GRAPHICS``: ``PORT``, ``COMMAND``;
|
||||
|
||||
* ``VIDEO``: ``ATS``, ``IOMMU``, ``RESOLUTION``, ``TYPE``, ``VRAM``;
|
||||
|
||||
* ``RAW``: ``VALIDATE``;
|
||||
|
||||
* ``BACKUP_CONFIG``: ``FS_FREEZE``, ``KEEP_LAST``, ``BACKUP_VOLATILE``,
|
||||
``MODE``, ``INCREMENT_MODE``.
|
||||
|
||||
|
||||
(https://github.com/ansible-collections/community.general/pull/9959).'
|
||||
- proxmox and proxmox_kvm modules - allow uppercase characters in VM/container
|
||||
tags (https://github.com/ansible-collections/community.general/issues/9895,
|
||||
https://github.com/ansible-collections/community.general/pull/10024).
|
||||
- puppet - improve parameter formatting, no impact to user (https://github.com/ansible-collections/community.general/pull/10014).
|
||||
- redfish module utils - add ``REDFISH_COMMON_ARGUMENT_SPEC``, a corresponding
|
||||
``redfish`` docs fragment, and support for its ``validate_certs``, ``ca_path``,
|
||||
and ``ciphers`` options (https://github.com/ansible-collections/community.general/issues/3686,
|
||||
https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- redfish_command, redfish_config, redfish_info - add ``validate_certs`` and
|
||||
``ca_path`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686,
|
||||
https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- rocketchat - fix duplicate JSON conversion for Rocket.Chat < 7.4.0 (https://github.com/ansible-collections/community.general/pull/9965).
|
||||
- wdc_redfish_command, wdc_redfish_info - add ``validate_certs``, ``ca_path``,
|
||||
and ``ciphers`` options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686,
|
||||
https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- xcc_redfish_command - add ``validate_certs``, ``ca_path``, and ``ciphers``
|
||||
options to configure TLS/SSL (https://github.com/ansible-collections/community.general/issues/3686,
|
||||
https://github.com/ansible-collections/community.general/pull/9964).
|
||||
- zypper - adds ``skip_post_errors`` that allows to skip RPM post-install
|
||||
errors (Zypper return code 107) (https://github.com/ansible-collections/community.general/issues/9972).
|
||||
release_summary: Regular bugfix and feature release.
|
||||
fragments:
|
||||
- 10.6.0.yml
|
||||
- 10005-fix-method-exists-in-sysrc.yml
|
||||
- 10011-github_deploy_key-check-key-present.yml
|
||||
- 10012-improve-error-handling-homebrew-missing-tap.yml
|
||||
- 10014-puppet-improve-param.yml
|
||||
- 10026-stackpath-compute-deprecation.yml
|
||||
- 10028-manifold-deprecation.yml
|
||||
- 9646-hpilo-fix-idempotency.yml
|
||||
- 9833-data-tagging.yml
|
||||
- 9895-proxmox_tags_with_uppercase_chars.yml
|
||||
- 9898-keycloak_user-supports-subgroups.yaml
|
||||
- 9951-mod-php-identifier.yml
|
||||
- 9952-proxmox-inventory-plugin-improve-ansible_host.yml
|
||||
- 9953-gitlab-project-members-support-owner-level.yml
|
||||
- 9959-update-opennebula-onevm-updateconf-params.yml
|
||||
- 9960-gitlab_project-add-build_timeout-option.yml
|
||||
- 9962-nmcli-add-infiniband-mac-support.yml
|
||||
- 9963-proxmox_disk-storage.yml
|
||||
- 9964-redfish-tls.yml
|
||||
- 9965-fix-duplicate-jsonify-payload-for-rocketchat-pre740.yml
|
||||
- 9970-pkcs12_alias_cert_alias_optional.yml
|
||||
- 9972-zypper-skip-post-errors.yml
|
||||
- 9976-keycloak_client-fix-idempotency-regression.yml
|
||||
- 9987-keycloak-auth-flow-fix-config.yaml
|
||||
- 9992-filtertypeerror.yml
|
||||
plugins:
|
||||
connection:
|
||||
- description: Run tasks in WSL distribution using wsl.exe CLI via SSH.
|
||||
name: wsl
|
||||
namespace: null
|
||||
release_date: '2025-04-21'
|
||||
|
||||
@@ -20,3 +20,4 @@ sections:
|
||||
- guide_vardict
|
||||
- guide_cmdrunner
|
||||
- guide_modulehelper
|
||||
- guide_uthelper
|
||||
|
||||
@@ -65,7 +65,7 @@ All three statements are equivalent and give:
|
||||
|
||||
.. note:: Be aware that in most cases, filter calls without any argument require ``flatten=true``, otherwise the input is returned as result. The reason for this is, that the input is considered as a variable argument and is wrapped by an additional outer list. ``flatten=true`` ensures that this list is removed before the input is processed by the filter logic.
|
||||
|
||||
The filters ansplugin:`community.general.lists_difference#filter` or :ansplugin:`community.general.lists_symmetric_difference#filter` can be used in the same way as the filters in the examples above. They calculate the difference or the symmetric difference between two or more lists and preserve the item order.
|
||||
The filters :ansplugin:`community.general.lists_difference#filter` or :ansplugin:`community.general.lists_symmetric_difference#filter` can be used in the same way as the filters in the examples above. They calculate the difference or the symmetric difference between two or more lists and preserve the item order.
|
||||
|
||||
For example, the symmetric difference of ``A``, ``B`` and ``C`` may be written as:
|
||||
|
||||
|
||||
@@ -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':
|
||||
|
||||
|
||||
@@ -267,24 +267,54 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
+------------+-------------------------+
|
||||
|
||||
- ``cmd_runner_fmt.as_fixed()``
|
||||
This method receives one parameter ``arg``, the function expects no ``value`` - if one
|
||||
is provided then it is ignored.
|
||||
The function returns ``arg`` as-is.
|
||||
This method defines one or more fixed arguments that are returned by the generated function
|
||||
regardless whether ``value`` is passed to it or not.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_fixed("--version")``
|
||||
This method accepts these arguments in one of three forms:
|
||||
|
||||
* one scalar parameter ``arg``, which will be returned as ``[arg]`` by the function, or
|
||||
* one sequence parameter, such as a list, ``arg``, which will be returned by the function as ``arg[0]``, or
|
||||
* multiple parameters ``args``, which will be returned as ``args`` directly by the function.
|
||||
|
||||
See the examples below for each one of those forms. And, stressing that the generated function expects no ``value`` - if one
|
||||
is provided then it is ignored.
|
||||
|
||||
- Creation (one scalar argument):
|
||||
* ``cmd_runner_fmt.as_fixed("--version")``
|
||||
- Examples:
|
||||
+---------+-----------------------+
|
||||
| Value | Outcome |
|
||||
+=========+=======================+
|
||||
| | ``["--version"]`` |
|
||||
+---------+-----------------------+
|
||||
| 57 | ``["--version"]`` |
|
||||
+---------+-----------------------+
|
||||
+---------+--------------------------------------+
|
||||
| Value | Outcome |
|
||||
+=========+======================================+
|
||||
| | * ``["--version"]`` |
|
||||
+---------+--------------------------------------+
|
||||
| 57 | * ``["--version"]`` |
|
||||
+---------+--------------------------------------+
|
||||
|
||||
- Creation (one sequence argument):
|
||||
* ``cmd_runner_fmt.as_fixed(["--list", "--json"])``
|
||||
- Examples:
|
||||
+---------+--------------------------------------+
|
||||
| Value | Outcome |
|
||||
+=========+======================================+
|
||||
| | * ``["--list", "--json"]`` |
|
||||
+---------+--------------------------------------+
|
||||
| True | * ``["--list", "--json"]`` |
|
||||
+---------+--------------------------------------+
|
||||
|
||||
- Creation (multiple arguments):
|
||||
* ``cmd_runner_fmt.as_fixed("--one", "--two", "--three")``
|
||||
- Examples:
|
||||
+---------+--------------------------------------+
|
||||
| Value | Outcome |
|
||||
+=========+======================================+
|
||||
| | * ``["--one", "--two", "--three"]`` |
|
||||
+---------+--------------------------------------+
|
||||
| False | * ``["--one", "--two", "--three"]`` |
|
||||
+---------+--------------------------------------+
|
||||
|
||||
- Note:
|
||||
This is the only special case in which a value can be missing for the formatting function.
|
||||
The example also comes from the code in `Quickstart`_.
|
||||
The first example here comes from the code in `Quickstart`_.
|
||||
In that case, the module has code to determine the command's version so that it can assert compatibility.
|
||||
There is no *value* to be passed for that CLI argument.
|
||||
|
||||
|
||||
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.0
|
||||
version: 10.6.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
|
||||
@@ -98,6 +98,10 @@ plugin_routing:
|
||||
redirect: community.google.gcp_storage_file
|
||||
hashi_vault:
|
||||
redirect: community.hashi_vault.hashi_vault
|
||||
manifold:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: Company was acquired in 2021 and service was ceased afterwards.
|
||||
nios:
|
||||
redirect: infoblox.nios_modules.nios_lookup
|
||||
nios_next_ip:
|
||||
@@ -112,17 +116,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 +643,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
|
||||
@@ -908,6 +968,10 @@ plugin_routing:
|
||||
redirect: community.docker.docker_swarm
|
||||
kubevirt:
|
||||
redirect: community.kubevirt.kubevirt
|
||||
stackpath_compute:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: The company and the service were sunset in June 2024.
|
||||
filter:
|
||||
path_join:
|
||||
# The ansible.builtin.path_join filter has been added in ansible-base 2.10.
|
||||
|
||||
38
noxfile.py
Normal file
38
noxfile.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: 2025 Felix Fontein <felix@fontein.de>
|
||||
|
||||
# /// script
|
||||
# dependencies = ["nox>=2025.02.09", "antsibull-nox"]
|
||||
# ///
|
||||
|
||||
import sys
|
||||
|
||||
import nox
|
||||
|
||||
|
||||
try:
|
||||
import antsibull_nox
|
||||
except ImportError:
|
||||
print("You need to install antsibull-nox in the same Python environment as nox.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
antsibull_nox.load_antsibull_nox_toml()
|
||||
|
||||
|
||||
@nox.session(name="aliases", python=False, default=True)
|
||||
def aliases(session: nox.Session) -> None:
|
||||
session.run("python", "tests/sanity/extra/aliases.py")
|
||||
|
||||
|
||||
@nox.session(name="botmeta", default=True)
|
||||
def botmeta(session: nox.Session) -> None:
|
||||
session.install("PyYAML", "voluptuous")
|
||||
session.run("python", "tests/sanity/extra/botmeta.py")
|
||||
|
||||
|
||||
# Allow to run the noxfile with `python noxfile.py`, `pipx run noxfile.py`, or similar.
|
||||
# Requires nox >= 2025.02.09
|
||||
if __name__ == "__main__":
|
||||
nox.main()
|
||||
@@ -785,6 +785,12 @@ from ansible.vars.manager import VariableManager
|
||||
from ansible.plugins.callback.default import CallbackModule as Default
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
|
||||
try:
|
||||
from ansible.template import trust_as_template # noqa: F401, pylint: disable=unused-import
|
||||
SUPPORTS_DATA_TAGGING = True
|
||||
except ImportError:
|
||||
SUPPORTS_DATA_TAGGING = False
|
||||
|
||||
|
||||
class DummyStdout(object):
|
||||
def flush(self):
|
||||
@@ -838,7 +844,10 @@ class CallbackModule(Default):
|
||||
return _ret
|
||||
|
||||
def _using_diy(self, spec):
|
||||
return (spec['msg'] is not None) and (spec['msg'] != spec['vars']['omit'])
|
||||
sentinel = object()
|
||||
omit = spec['vars'].get('omit', sentinel)
|
||||
# With Data Tagging, omit is sentinel
|
||||
return (spec['msg'] is not None) and (spec['msg'] != omit or omit is sentinel)
|
||||
|
||||
def _parent_has_callback(self):
|
||||
return hasattr(super(CallbackModule, self), sys._getframe(1).f_code.co_name)
|
||||
@@ -894,7 +903,7 @@ class CallbackModule(Default):
|
||||
)
|
||||
_ret.update(_all)
|
||||
|
||||
_ret.update(_ret.get(self.DIY_NS, {self.DIY_NS: CallbackDIYDict()}))
|
||||
_ret.update(_ret.get(self.DIY_NS, {self.DIY_NS: {} if SUPPORTS_DATA_TAGGING else CallbackDIYDict()}))
|
||||
|
||||
_ret[self.DIY_NS].update({'playbook': {}})
|
||||
_playbook_attributes = ['entries', 'file_name', 'basedir']
|
||||
|
||||
@@ -18,6 +18,11 @@ short_description: Sends play events to a Slack channel
|
||||
description:
|
||||
- This is an ansible callback plugin that sends status updates to a Slack channel during playbook execution.
|
||||
options:
|
||||
http_agent:
|
||||
description:
|
||||
- HTTP user agent to use for requests to Slack.
|
||||
type: string
|
||||
version_added: "10.5.0"
|
||||
webhook_url:
|
||||
required: true
|
||||
description: Slack Webhook URL.
|
||||
@@ -106,7 +111,7 @@ class CallbackModule(CallbackBase):
|
||||
self.username = self.get_option('username')
|
||||
self.show_invocation = (self._display.verbosity > 1)
|
||||
self.validate_certs = self.get_option('validate_certs')
|
||||
|
||||
self.http_agent = self.get_option('http_agent')
|
||||
if self.webhook_url is None:
|
||||
self.disabled = True
|
||||
self._display.warning('Slack Webhook URL was not provided. The '
|
||||
@@ -132,8 +137,13 @@ class CallbackModule(CallbackBase):
|
||||
self._display.debug(data)
|
||||
self._display.debug(self.webhook_url)
|
||||
try:
|
||||
response = open_url(self.webhook_url, data=data, validate_certs=self.validate_certs,
|
||||
headers=headers)
|
||||
response = open_url(
|
||||
self.webhook_url,
|
||||
data=data,
|
||||
validate_certs=self.validate_certs,
|
||||
headers=headers,
|
||||
http_agent=self.http_agent,
|
||||
)
|
||||
return response.read()
|
||||
except Exception as e:
|
||||
self._display.warning(f'Could not submit message to Slack: {e}')
|
||||
|
||||
@@ -53,29 +53,77 @@ def should_use_block(value):
|
||||
return False
|
||||
|
||||
|
||||
class MyDumper(AnsibleDumper):
|
||||
def represent_scalar(self, tag, value, style=None):
|
||||
"""Uses block style for multi-line strings"""
|
||||
if style is None:
|
||||
if should_use_block(value):
|
||||
style = '|'
|
||||
# we care more about readable than accuracy, so...
|
||||
# ...no trailing space
|
||||
value = value.rstrip()
|
||||
# ...and non-printable characters
|
||||
value = ''.join(x for x in value if x in string.printable or ord(x) >= 0xA0)
|
||||
# ...tabs prevent blocks from expanding
|
||||
value = value.expandtabs()
|
||||
# ...and odd bits of whitespace
|
||||
value = re.sub(r'[\x0b\x0c\r]', '', value)
|
||||
# ...as does trailing space
|
||||
value = re.sub(r' +\n', '\n', value)
|
||||
else:
|
||||
style = self.default_style
|
||||
node = yaml.representer.ScalarNode(tag, value, style=style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
return node
|
||||
try:
|
||||
class MyDumper(AnsibleDumper): # pylint: disable=inherit-non-class
|
||||
def represent_scalar(self, tag, value, style=None):
|
||||
"""Uses block style for multi-line strings"""
|
||||
if style is None:
|
||||
if should_use_block(value):
|
||||
style = '|'
|
||||
# we care more about readable than accuracy, so...
|
||||
# ...no trailing space
|
||||
value = value.rstrip()
|
||||
# ...and non-printable characters
|
||||
value = ''.join(x for x in value if x in string.printable or ord(x) >= 0xA0)
|
||||
# ...tabs prevent blocks from expanding
|
||||
value = value.expandtabs()
|
||||
# ...and odd bits of whitespace
|
||||
value = re.sub(r'[\x0b\x0c\r]', '', value)
|
||||
# ...as does trailing space
|
||||
value = re.sub(r' +\n', '\n', value)
|
||||
else:
|
||||
style = self.default_style
|
||||
node = yaml.representer.ScalarNode(tag, value, style=style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
return node
|
||||
except: # noqa: E722, pylint: disable=bare-except
|
||||
# This happens with Data Tagging, see https://github.com/ansible/ansible/issues/84781
|
||||
# Until there is a better solution we'll resort to using ansible-core internals.
|
||||
from ansible._internal._yaml import _dumper
|
||||
import typing as t
|
||||
|
||||
class MyDumper(_dumper._BaseDumper):
|
||||
# This code is mostly taken from ansible._internal._yaml._dumper
|
||||
@classmethod
|
||||
def _register_representers(cls) -> None:
|
||||
cls.add_multi_representer(_dumper.AnsibleTaggedObject, cls.represent_ansible_tagged_object)
|
||||
cls.add_multi_representer(_dumper.Tripwire, cls.represent_tripwire)
|
||||
cls.add_multi_representer(_dumper.c.Mapping, _dumper.SafeRepresenter.represent_dict)
|
||||
cls.add_multi_representer(_dumper.c.Sequence, _dumper.SafeRepresenter.represent_list)
|
||||
|
||||
def represent_ansible_tagged_object(self, data):
|
||||
if ciphertext := _dumper.VaultHelper.get_ciphertext(data, with_tags=False):
|
||||
return self.represent_scalar('!vault', ciphertext, style='|')
|
||||
|
||||
return self.represent_data(_dumper.AnsibleTagHelper.as_native_type(data)) # automatically decrypts encrypted strings
|
||||
|
||||
def represent_tripwire(self, data: _dumper.Tripwire) -> t.NoReturn:
|
||||
data.trip()
|
||||
|
||||
# The following function is the same as in the try/except
|
||||
def represent_scalar(self, tag, value, style=None):
|
||||
"""Uses block style for multi-line strings"""
|
||||
if style is None:
|
||||
if should_use_block(value):
|
||||
style = '|'
|
||||
# we care more about readable than accuracy, so...
|
||||
# ...no trailing space
|
||||
value = value.rstrip()
|
||||
# ...and non-printable characters
|
||||
value = ''.join(x for x in value if x in string.printable or ord(x) >= 0xA0)
|
||||
# ...tabs prevent blocks from expanding
|
||||
value = value.expandtabs()
|
||||
# ...and odd bits of whitespace
|
||||
value = re.sub(r'[\x0b\x0c\r]', '', value)
|
||||
# ...as does trailing space
|
||||
value = re.sub(r' +\n', '\n', value)
|
||||
else:
|
||||
style = self.default_style
|
||||
node = yaml.representer.ScalarNode(tag, value, style=style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
return node
|
||||
|
||||
|
||||
class CallbackModule(Default):
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
786
plugins/connection/wsl.py
Normal file
786
plugins/connection/wsl.py
Normal file
@@ -0,0 +1,786 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Derived from ansible/plugins/connection/proxmox_pct_remote.py (c) 2024 Nils Stein (@mietzen) <github.nstein@mailbox.org>
|
||||
# Derived from ansible/plugins/connection/paramiko_ssh.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Copyright (c) 2025 Rui Lopes (@rgl) <ruilopes.com>
|
||||
# Copyright (c) 2025 Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Rui Lopes (@rgl) <ruilopes.com>
|
||||
name: wsl
|
||||
short_description: Run tasks in WSL distribution using wsl.exe CLI via SSH
|
||||
requirements:
|
||||
- paramiko
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing WSL distribution using wsl.exe CLI via SSH.
|
||||
- Uses the Python SSH implementation (Paramiko) to connect to the WSL host.
|
||||
version_added: "10.6.0"
|
||||
options:
|
||||
remote_addr:
|
||||
description:
|
||||
- Address of the remote target.
|
||||
default: inventory_hostname
|
||||
type: string
|
||||
vars:
|
||||
- name: inventory_hostname
|
||||
- name: ansible_host
|
||||
- name: ansible_ssh_host
|
||||
- name: ansible_paramiko_host
|
||||
port:
|
||||
description: Remote port to connect to.
|
||||
type: int
|
||||
default: 22
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_port
|
||||
- section: paramiko_connection
|
||||
key: remote_port
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_PORT
|
||||
- name: ANSIBLE_REMOTE_PARAMIKO_PORT
|
||||
vars:
|
||||
- name: ansible_port
|
||||
- name: ansible_ssh_port
|
||||
- name: ansible_paramiko_port
|
||||
keyword:
|
||||
- name: port
|
||||
remote_user:
|
||||
description:
|
||||
- User to login/authenticate as.
|
||||
- Can be set from the CLI via the C(--user) or C(-u) options.
|
||||
type: string
|
||||
vars:
|
||||
- name: ansible_user
|
||||
- name: ansible_ssh_user
|
||||
- name: ansible_paramiko_user
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_USER
|
||||
- name: ANSIBLE_PARAMIKO_REMOTE_USER
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_user
|
||||
- section: paramiko_connection
|
||||
key: remote_user
|
||||
keyword:
|
||||
- name: remote_user
|
||||
password:
|
||||
description:
|
||||
- Secret used to either login the SSH server or as a passphrase for SSH keys that require it.
|
||||
- Can be set from the CLI via the C(--ask-pass) option.
|
||||
type: string
|
||||
vars:
|
||||
- name: ansible_password
|
||||
- name: ansible_ssh_pass
|
||||
- name: ansible_ssh_password
|
||||
- name: ansible_paramiko_pass
|
||||
- name: ansible_paramiko_password
|
||||
use_rsa_sha2_algorithms:
|
||||
description:
|
||||
- Whether or not to enable RSA SHA2 algorithms for pubkeys and hostkeys.
|
||||
- On paramiko versions older than 2.9, this only affects hostkeys.
|
||||
- For behavior matching paramiko<2.9 set this to V(false).
|
||||
vars:
|
||||
- name: ansible_paramiko_use_rsa_sha2_algorithms
|
||||
ini:
|
||||
- {key: use_rsa_sha2_algorithms, section: paramiko_connection}
|
||||
env:
|
||||
- {name: ANSIBLE_PARAMIKO_USE_RSA_SHA2_ALGORITHMS}
|
||||
default: true
|
||||
type: boolean
|
||||
host_key_auto_add:
|
||||
description: "Automatically add host keys to C(~/.ssh/known_hosts)."
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_HOST_KEY_AUTO_ADD
|
||||
ini:
|
||||
- key: host_key_auto_add
|
||||
section: paramiko_connection
|
||||
type: boolean
|
||||
look_for_keys:
|
||||
default: True
|
||||
description: "Set to V(false) to disable searching for private key files in C(~/.ssh/)."
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS
|
||||
ini:
|
||||
- {key: look_for_keys, section: paramiko_connection}
|
||||
type: boolean
|
||||
proxy_command:
|
||||
default: ""
|
||||
description:
|
||||
- Proxy information for running the connection via a jumphost.
|
||||
- This option is supported by paramiko version 1.9.0 or newer.
|
||||
type: string
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_PROXY_COMMAND
|
||||
ini:
|
||||
- {key: proxy_command, section: paramiko_connection}
|
||||
vars:
|
||||
- name: ansible_paramiko_proxy_command
|
||||
record_host_keys:
|
||||
default: True
|
||||
description: "Save the host keys to a file."
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_RECORD_HOST_KEYS
|
||||
ini:
|
||||
- section: paramiko_connection
|
||||
key: record_host_keys
|
||||
type: boolean
|
||||
host_key_checking:
|
||||
description: "Set this to V(false) if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host."
|
||||
type: boolean
|
||||
default: true
|
||||
env:
|
||||
- name: ANSIBLE_HOST_KEY_CHECKING
|
||||
- name: ANSIBLE_SSH_HOST_KEY_CHECKING
|
||||
- name: ANSIBLE_PARAMIKO_HOST_KEY_CHECKING
|
||||
ini:
|
||||
- section: defaults
|
||||
key: host_key_checking
|
||||
- section: paramiko_connection
|
||||
key: host_key_checking
|
||||
vars:
|
||||
- name: ansible_host_key_checking
|
||||
- name: ansible_ssh_host_key_checking
|
||||
- name: ansible_paramiko_host_key_checking
|
||||
use_persistent_connections:
|
||||
description: "Toggles the use of persistence for connections."
|
||||
type: boolean
|
||||
default: False
|
||||
env:
|
||||
- name: ANSIBLE_USE_PERSISTENT_CONNECTIONS
|
||||
ini:
|
||||
- section: defaults
|
||||
key: use_persistent_connections
|
||||
banner_timeout:
|
||||
type: float
|
||||
default: 30
|
||||
description:
|
||||
- Configures, in seconds, the amount of time to wait for the SSH
|
||||
banner to be presented.
|
||||
- This option is supported by paramiko version 1.15.0 or newer.
|
||||
ini:
|
||||
- section: paramiko_connection
|
||||
key: banner_timeout
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_BANNER_TIMEOUT
|
||||
timeout:
|
||||
type: int
|
||||
default: 10
|
||||
description:
|
||||
- Number of seconds until the plugin gives up on failing to establish a TCP connection.
|
||||
- This option is supported by paramiko version 2.2.0 or newer.
|
||||
ini:
|
||||
- section: defaults
|
||||
key: timeout
|
||||
- section: ssh_connection
|
||||
key: timeout
|
||||
- section: paramiko_connection
|
||||
key: timeout
|
||||
env:
|
||||
- name: ANSIBLE_TIMEOUT
|
||||
- name: ANSIBLE_SSH_TIMEOUT
|
||||
- name: ANSIBLE_PARAMIKO_TIMEOUT
|
||||
vars:
|
||||
- name: ansible_ssh_timeout
|
||||
- name: ansible_paramiko_timeout
|
||||
cli:
|
||||
- name: timeout
|
||||
lock_file_timeout:
|
||||
type: int
|
||||
default: 60
|
||||
description: Number of seconds until the plugin gives up on trying to write a lock file when writing SSH known host keys.
|
||||
vars:
|
||||
- name: ansible_lock_file_timeout
|
||||
env:
|
||||
- name: ANSIBLE_LOCK_FILE_TIMEOUT
|
||||
private_key_file:
|
||||
description:
|
||||
- Path to private key file to use for authentication.
|
||||
type: path
|
||||
ini:
|
||||
- section: defaults
|
||||
key: private_key_file
|
||||
- section: paramiko_connection
|
||||
key: private_key_file
|
||||
env:
|
||||
- name: ANSIBLE_PRIVATE_KEY_FILE
|
||||
- name: ANSIBLE_PARAMIKO_PRIVATE_KEY_FILE
|
||||
vars:
|
||||
- name: ansible_private_key_file
|
||||
- name: ansible_ssh_private_key_file
|
||||
- name: ansible_paramiko_private_key_file
|
||||
cli:
|
||||
- name: private_key_file
|
||||
option: "--private-key"
|
||||
user_known_hosts_file:
|
||||
description:
|
||||
- Path to the user known hosts file.
|
||||
- Used to verify the ssh hosts keys.
|
||||
type: path
|
||||
default: ~/.ssh/known_hosts
|
||||
ini:
|
||||
- section: paramiko_connection
|
||||
key: user_known_hosts_file
|
||||
vars:
|
||||
- name: ansible_paramiko_user_known_hosts_file
|
||||
wsl_distribution:
|
||||
description:
|
||||
- WSL distribution name
|
||||
type: string
|
||||
required: true
|
||||
vars:
|
||||
- name: wsl_distribution
|
||||
wsl_user:
|
||||
description:
|
||||
- WSL distribution user
|
||||
type: string
|
||||
vars:
|
||||
- name: wsl_user
|
||||
become_user:
|
||||
description:
|
||||
- WSL distribution user
|
||||
type: string
|
||||
default: root
|
||||
vars:
|
||||
- name: become_user
|
||||
- name: ansible_become_user
|
||||
become:
|
||||
description:
|
||||
- whether to use the user defined by ansible_become_user.
|
||||
type: bool
|
||||
default: false
|
||||
vars:
|
||||
- name: become
|
||||
- name: ansible_become
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# ------------------------
|
||||
# Inventory: inventory.yml
|
||||
# ------------------------
|
||||
---
|
||||
all:
|
||||
children:
|
||||
wsl:
|
||||
hosts:
|
||||
example-wsl-ubuntu:
|
||||
ansible_host: 10.0.0.10
|
||||
wsl_distribution: ubuntu
|
||||
wsl_user: ubuntu
|
||||
vars:
|
||||
ansible_connection: community.general.wsl
|
||||
ansible_user: vagrant
|
||||
# ----------------------
|
||||
# Playbook: playbook.yml
|
||||
# ----------------------
|
||||
---
|
||||
- name: WSL Example
|
||||
hosts: wsl
|
||||
gather_facts: true
|
||||
become: true
|
||||
tasks:
|
||||
- name: Ping
|
||||
ansible.builtin.ping:
|
||||
- name: Id (with become false)
|
||||
become: false
|
||||
changed_when: false
|
||||
args:
|
||||
executable: /bin/bash
|
||||
ansible.builtin.shell: |
|
||||
exec 2>&1
|
||||
set -x
|
||||
echo "$0"
|
||||
pwd
|
||||
id
|
||||
- name: Id (with become true)
|
||||
changed_when: false
|
||||
args:
|
||||
executable: /bin/bash
|
||||
ansible.builtin.shell: |
|
||||
exec 2>&1
|
||||
set -x
|
||||
echo "$0"
|
||||
pwd
|
||||
id
|
||||
- name: Reboot
|
||||
ansible.builtin.reboot:
|
||||
boot_time_command: systemctl show -p ActiveEnterTimestamp init.scope
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import pathlib
|
||||
import shlex
|
||||
import socket
|
||||
import tempfile
|
||||
import typing as t
|
||||
|
||||
from ansible.errors import (
|
||||
AnsibleAuthenticationFailure,
|
||||
AnsibleConnectionFailure,
|
||||
AnsibleError,
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils._filelock import FileLock, LockTimeout
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible.module_utils.compat.paramiko import PARAMIKO_IMPORT_ERR, paramiko
|
||||
from ansible.module_utils.compat.version import LooseVersion
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.path import makedirs_safe
|
||||
from binascii import hexlify
|
||||
from subprocess import list2cmdline
|
||||
|
||||
|
||||
if t.TYPE_CHECKING and paramiko:
|
||||
from paramiko import MissingHostKeyPolicy
|
||||
from paramiko.client import SSHClient
|
||||
from paramiko.pkey import PKey
|
||||
else:
|
||||
MissingHostKeyPolicy: type = object
|
||||
SSHClient: type = object
|
||||
PKey: type = object
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
def authenticity_msg(hostname: str, ktype: str, fingerprint: str) -> str:
|
||||
msg = f"""
|
||||
paramiko: The authenticity of host '{hostname}' can't be established.
|
||||
The {ktype} key fingerprint is {fingerprint}.
|
||||
Are you sure you want to continue connecting (yes/no)?
|
||||
"""
|
||||
return msg
|
||||
|
||||
|
||||
class MyAddPolicy(MissingHostKeyPolicy):
|
||||
"""
|
||||
Based on AutoAddPolicy in paramiko so we can determine when keys are added
|
||||
|
||||
and also prompt for input.
|
||||
|
||||
Policy for automatically adding the hostname and new host key to the
|
||||
local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
|
||||
"""
|
||||
|
||||
def __init__(self, connection: Connection) -> None:
|
||||
self.connection = connection
|
||||
self._options = connection._options
|
||||
|
||||
def missing_host_key(self, client: SSHClient, hostname: str, key: PKey) -> None:
|
||||
|
||||
if all((self.connection.get_option('host_key_checking'), not self.connection.get_option('host_key_auto_add'))):
|
||||
|
||||
fingerprint = hexlify(key.get_fingerprint())
|
||||
ktype = key.get_name()
|
||||
|
||||
if self.connection.get_option('use_persistent_connections') or self.connection.force_persistence:
|
||||
# don't print the prompt string since the user cannot respond
|
||||
# to the question anyway
|
||||
raise AnsibleError(authenticity_msg(hostname, ktype, fingerprint)[1:92])
|
||||
|
||||
inp = to_text(
|
||||
display.prompt_until(authenticity_msg(hostname, ktype, fingerprint), private=False),
|
||||
errors='surrogate_or_strict'
|
||||
)
|
||||
|
||||
if inp.lower() not in ['yes', 'y', '']:
|
||||
raise AnsibleError('host connection rejected by user')
|
||||
|
||||
key._added_by_ansible_this_time = True
|
||||
|
||||
# existing implementation below:
|
||||
client._host_keys.add(hostname, key.get_name(), key)
|
||||
|
||||
# host keys are actually saved in close() function below
|
||||
# in order to control ordering.
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" SSH based connections (paramiko) to WSL """
|
||||
|
||||
transport = 'community.general.wsl'
|
||||
_log_channel: str | None = None
|
||||
|
||||
def __init__(self, play_context: PlayContext, new_stdin: io.TextIOWrapper | None = None, *args: t.Any, **kwargs: t.Any):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
||||
def _set_log_channel(self, name: str) -> None:
|
||||
""" Mimic paramiko.SSHClient.set_log_channel """
|
||||
self._log_channel = name
|
||||
|
||||
def _parse_proxy_command(self, port: int = 22) -> dict[str, t.Any]:
|
||||
proxy_command = self.get_option('proxy_command') or None
|
||||
|
||||
sock_kwarg = {}
|
||||
if proxy_command:
|
||||
replacers: t.Dict[str, str] = {
|
||||
'%h': self.get_option('remote_addr'),
|
||||
'%p': str(port),
|
||||
'%r': self.get_option('remote_user')
|
||||
}
|
||||
for find, replace in replacers.items():
|
||||
proxy_command = proxy_command.replace(find, replace)
|
||||
try:
|
||||
sock_kwarg = {'sock': paramiko.ProxyCommand(proxy_command)}
|
||||
display.vvv(f'CONFIGURE PROXY COMMAND FOR CONNECTION: {proxy_command}', host=self.get_option('remote_addr'))
|
||||
except AttributeError:
|
||||
display.warning('Paramiko ProxyCommand support unavailable. '
|
||||
'Please upgrade to Paramiko 1.9.0 or newer. '
|
||||
'Not using configured ProxyCommand')
|
||||
|
||||
return sock_kwarg
|
||||
|
||||
def _connect(self) -> Connection:
|
||||
""" activates the connection object """
|
||||
|
||||
if paramiko is None:
|
||||
raise AnsibleError(f'paramiko is not installed: {to_native(PARAMIKO_IMPORT_ERR)}')
|
||||
|
||||
port = self.get_option('port')
|
||||
display.vvv(f'ESTABLISH PARAMIKO SSH CONNECTION FOR USER: {self.get_option("remote_user")} on PORT {to_text(port)} TO {self.get_option("remote_addr")}',
|
||||
host=self.get_option('remote_addr'))
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
|
||||
# Set pubkey and hostkey algorithms to disable, the only manipulation allowed currently
|
||||
# is keeping or omitting rsa-sha2 algorithms
|
||||
# default_keys: t.Tuple[str] = ()
|
||||
paramiko_preferred_pubkeys = getattr(paramiko.Transport, '_preferred_pubkeys', ())
|
||||
paramiko_preferred_hostkeys = getattr(paramiko.Transport, '_preferred_keys', ())
|
||||
use_rsa_sha2_algorithms = self.get_option('use_rsa_sha2_algorithms')
|
||||
disabled_algorithms: t.Dict[str, t.Iterable[str]] = {}
|
||||
if not use_rsa_sha2_algorithms:
|
||||
if paramiko_preferred_pubkeys:
|
||||
disabled_algorithms['pubkeys'] = tuple(a for a in paramiko_preferred_pubkeys if 'rsa-sha2' in a)
|
||||
if paramiko_preferred_hostkeys:
|
||||
disabled_algorithms['keys'] = tuple(a for a in paramiko_preferred_hostkeys if 'rsa-sha2' in a)
|
||||
|
||||
# override paramiko's default logger name
|
||||
if self._log_channel is not None:
|
||||
ssh.set_log_channel(self._log_channel)
|
||||
|
||||
self.keyfile = os.path.expanduser(self.get_option('user_known_hosts_file'))
|
||||
|
||||
if self.get_option('host_key_checking'):
|
||||
for ssh_known_hosts in ('/etc/ssh/ssh_known_hosts', '/etc/openssh/ssh_known_hosts', self.keyfile):
|
||||
try:
|
||||
ssh.load_system_host_keys(ssh_known_hosts)
|
||||
break
|
||||
except IOError:
|
||||
pass # file was not found, but not required to function
|
||||
except paramiko.hostkeys.InvalidHostKey as e:
|
||||
raise AnsibleConnectionFailure(f'Invalid host key: {to_text(e.line)}')
|
||||
try:
|
||||
ssh.load_system_host_keys()
|
||||
except paramiko.hostkeys.InvalidHostKey as e:
|
||||
raise AnsibleConnectionFailure(f'Invalid host key: {to_text(e.line)}')
|
||||
|
||||
ssh_connect_kwargs = self._parse_proxy_command(port)
|
||||
ssh.set_missing_host_key_policy(MyAddPolicy(self))
|
||||
conn_password = self.get_option('password')
|
||||
allow_agent = True
|
||||
|
||||
if conn_password is not None:
|
||||
allow_agent = False
|
||||
|
||||
try:
|
||||
key_filename = None
|
||||
if self.get_option('private_key_file'):
|
||||
key_filename = os.path.expanduser(self.get_option('private_key_file'))
|
||||
|
||||
# paramiko 2.2 introduced auth_timeout parameter
|
||||
if LooseVersion(paramiko.__version__) >= LooseVersion('2.2.0'):
|
||||
ssh_connect_kwargs['auth_timeout'] = self.get_option('timeout')
|
||||
|
||||
# paramiko 1.15 introduced banner timeout parameter
|
||||
if LooseVersion(paramiko.__version__) >= LooseVersion('1.15.0'):
|
||||
ssh_connect_kwargs['banner_timeout'] = self.get_option('banner_timeout')
|
||||
|
||||
ssh.connect(
|
||||
self.get_option('remote_addr').lower(),
|
||||
username=self.get_option('remote_user'),
|
||||
allow_agent=allow_agent,
|
||||
look_for_keys=self.get_option('look_for_keys'),
|
||||
key_filename=key_filename,
|
||||
password=conn_password,
|
||||
timeout=self.get_option('timeout'),
|
||||
port=port,
|
||||
disabled_algorithms=disabled_algorithms,
|
||||
**ssh_connect_kwargs,
|
||||
)
|
||||
except paramiko.ssh_exception.BadHostKeyException as e:
|
||||
raise AnsibleConnectionFailure(f'host key mismatch for {to_text(e.hostname)}')
|
||||
except paramiko.ssh_exception.AuthenticationException as e:
|
||||
msg = f'Failed to authenticate: {e}'
|
||||
raise AnsibleAuthenticationFailure(msg)
|
||||
except Exception as e:
|
||||
msg = to_text(e)
|
||||
if u'PID check failed' in msg:
|
||||
raise AnsibleError('paramiko version issue, please upgrade paramiko on the machine running ansible')
|
||||
elif u'Private key file is encrypted' in msg:
|
||||
msg = f'ssh {self.get_option("remote_user")}@{self.get_options("remote_addr")}:{port} : ' + \
|
||||
f'{msg}\nTo connect as a different user, use -u <username>.'
|
||||
raise AnsibleConnectionFailure(msg)
|
||||
else:
|
||||
raise AnsibleConnectionFailure(msg)
|
||||
self.ssh = ssh
|
||||
self._connected = True
|
||||
return self
|
||||
|
||||
def _any_keys_added(self) -> bool:
|
||||
for hostname, keys in self.ssh._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
|
||||
if added_this_time:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _save_ssh_host_keys(self, filename: str) -> None:
|
||||
"""
|
||||
not using the paramiko save_ssh_host_keys function as we want to add new SSH keys at the bottom so folks
|
||||
don't complain about it :)
|
||||
"""
|
||||
|
||||
if not self._any_keys_added():
|
||||
return
|
||||
|
||||
path = os.path.expanduser('~/.ssh')
|
||||
makedirs_safe(path)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
for hostname, keys in self.ssh._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
# was f.write
|
||||
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
|
||||
if not added_this_time:
|
||||
f.write(f'{hostname} {keytype} {key.get_base64()}\n')
|
||||
|
||||
for hostname, keys in self.ssh._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
|
||||
if added_this_time:
|
||||
f.write(f'{hostname} {keytype} {key.get_base64()}\n')
|
||||
|
||||
def _build_wsl_command(self, cmd: str) -> str:
|
||||
wsl_distribution = self.get_option('wsl_distribution')
|
||||
become = self.get_option('become')
|
||||
become_user = self.get_option('become_user')
|
||||
if become and become_user:
|
||||
wsl_user = become_user
|
||||
else:
|
||||
wsl_user = self.get_option('wsl_user')
|
||||
args = ['wsl.exe', '--distribution', wsl_distribution]
|
||||
if wsl_user:
|
||||
args.extend(['--user', wsl_user])
|
||||
args.extend(['--'])
|
||||
args.extend(shlex.split(cmd))
|
||||
if os.getenv('_ANSIBLE_TEST_WSL_CONNECTION_PLUGIN_Waeri5tepheeSha2fae8'):
|
||||
return shlex.join(args)
|
||||
return list2cmdline(args) # see https://github.com/python/cpython/blob/3.11/Lib/subprocess.py#L576
|
||||
|
||||
def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
|
||||
""" run a command on inside a WSL distribution """
|
||||
|
||||
cmd = self._build_wsl_command(cmd)
|
||||
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
bufsize = 4096
|
||||
|
||||
try:
|
||||
self.ssh.get_transport().set_keepalive(5)
|
||||
chan = self.ssh.get_transport().open_session()
|
||||
except Exception as e:
|
||||
text_e = to_text(e)
|
||||
msg = 'Failed to open session'
|
||||
if text_e:
|
||||
msg += f': {text_e}'
|
||||
raise AnsibleConnectionFailure(to_native(msg))
|
||||
|
||||
display.vvv(f'EXEC {cmd}', host=self.get_option('remote_addr'))
|
||||
|
||||
cmd = to_bytes(cmd, errors='surrogate_or_strict')
|
||||
|
||||
no_prompt_out = b''
|
||||
no_prompt_err = b''
|
||||
become_output = b''
|
||||
|
||||
try:
|
||||
chan.exec_command(cmd)
|
||||
if self.become and self.become.expect_prompt():
|
||||
password_prompt = False
|
||||
become_success = False
|
||||
while not (become_success or password_prompt):
|
||||
display.debug('Waiting for Privilege Escalation input')
|
||||
|
||||
chunk = chan.recv(bufsize)
|
||||
display.debug(f'chunk is: {to_text(chunk)}')
|
||||
if not chunk:
|
||||
if b'unknown user' in become_output:
|
||||
n_become_user = to_native(self.become.get_option('become_user'))
|
||||
raise AnsibleError(f'user {n_become_user} does not exist')
|
||||
else:
|
||||
break
|
||||
# raise AnsibleError('ssh connection closed waiting for password prompt')
|
||||
become_output += chunk
|
||||
|
||||
# need to check every line because we might get lectured
|
||||
# and we might get the middle of a line in a chunk
|
||||
for line in become_output.splitlines(True):
|
||||
if self.become.check_success(line):
|
||||
become_success = True
|
||||
break
|
||||
elif self.become.check_password_prompt(line):
|
||||
password_prompt = True
|
||||
break
|
||||
|
||||
if password_prompt:
|
||||
if self.become:
|
||||
become_pass = self.become.get_option('become_pass')
|
||||
chan.sendall(to_bytes(become_pass + '\n', errors='surrogate_or_strict'))
|
||||
else:
|
||||
raise AnsibleError('A password is required but none was supplied')
|
||||
else:
|
||||
no_prompt_out += become_output
|
||||
no_prompt_err += become_output
|
||||
|
||||
if in_data:
|
||||
for i in range(0, len(in_data), bufsize):
|
||||
chan.send(in_data[i:i + bufsize])
|
||||
chan.shutdown_write()
|
||||
elif in_data == b'':
|
||||
chan.shutdown_write()
|
||||
|
||||
except socket.timeout:
|
||||
raise AnsibleError('ssh timed out waiting for privilege escalation.\n' + to_text(become_output))
|
||||
|
||||
stdout = b''.join(chan.makefile('rb', bufsize))
|
||||
stderr = b''.join(chan.makefile_stderr('rb', bufsize))
|
||||
returncode = chan.recv_exit_status()
|
||||
|
||||
# NB the full english error message is:
|
||||
# 'wsl.exe' is not recognized as an internal or external command,
|
||||
# operable program or batch file.
|
||||
if "'wsl.exe' is not recognized" in stderr.decode('utf-8'):
|
||||
raise AnsibleError(
|
||||
f'wsl.exe not found in path of host: {to_text(self.get_option("remote_addr"))}')
|
||||
|
||||
return (returncode, no_prompt_out + stdout, no_prompt_out + stderr)
|
||||
|
||||
def put_file(self, in_path: str, out_path: str) -> None:
|
||||
""" transfer a file from local to remote """
|
||||
|
||||
display.vvv(f'PUT {in_path} TO {out_path}', host=self.get_option('remote_addr'))
|
||||
try:
|
||||
with open(in_path, 'rb') as f:
|
||||
data = f.read()
|
||||
returncode, stdout, stderr = self.exec_command(
|
||||
' '.join([
|
||||
self._shell.executable, '-c',
|
||||
self._shell.quote(f'cat > {out_path}')]),
|
||||
in_data=data,
|
||||
sudoable=False)
|
||||
if returncode != 0:
|
||||
if 'cat: not found' in stderr.decode('utf-8'):
|
||||
raise AnsibleError(
|
||||
f'cat not found in path of WSL distribution: {to_text(self.get_option("wsl_distribution"))}')
|
||||
raise AnsibleError(
|
||||
f'{to_text(stdout)}\n{to_text(stderr)}')
|
||||
except Exception as e:
|
||||
raise AnsibleError(
|
||||
f'error occurred while putting file from {in_path} to {out_path}!\n{to_text(e)}')
|
||||
|
||||
def fetch_file(self, in_path: str, out_path: str) -> None:
|
||||
""" save a remote file to the specified path """
|
||||
|
||||
display.vvv(f'FETCH {in_path} TO {out_path}', host=self.get_option('remote_addr'))
|
||||
try:
|
||||
returncode, stdout, stderr = self.exec_command(
|
||||
' '.join([
|
||||
self._shell.executable, '-c',
|
||||
self._shell.quote(f'cat {in_path}')]),
|
||||
sudoable=False)
|
||||
if returncode != 0:
|
||||
if 'cat: not found' in stderr.decode('utf-8'):
|
||||
raise AnsibleError(
|
||||
f'cat not found in path of WSL distribution: {to_text(self.get_option("wsl_distribution"))}')
|
||||
raise AnsibleError(
|
||||
f'{to_text(stdout)}\n{to_text(stderr)}')
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(stdout)
|
||||
except Exception as e:
|
||||
raise AnsibleError(
|
||||
f'error occurred while fetching file from {in_path} to {out_path}!\n{to_text(e)}')
|
||||
|
||||
def reset(self) -> None:
|
||||
""" reset the connection """
|
||||
|
||||
if not self._connected:
|
||||
return
|
||||
self.close()
|
||||
self._connect()
|
||||
|
||||
def close(self) -> None:
|
||||
""" terminate the connection """
|
||||
|
||||
if self.get_option('host_key_checking') and self.get_option('record_host_keys') and self._any_keys_added():
|
||||
# add any new SSH host keys -- warning -- this could be slow
|
||||
# (This doesn't acquire the connection lock because it needs
|
||||
# to exclude only other known_hosts writers, not connections
|
||||
# that are starting up.)
|
||||
lockfile = os.path.basename(self.keyfile)
|
||||
dirname = os.path.dirname(self.keyfile)
|
||||
makedirs_safe(dirname)
|
||||
tmp_keyfile_name = None
|
||||
try:
|
||||
with FileLock().lock_file(lockfile, dirname, self.get_option('lock_file_timeout')):
|
||||
# just in case any were added recently
|
||||
|
||||
self.ssh.load_system_host_keys()
|
||||
self.ssh._host_keys.update(self.ssh._system_host_keys)
|
||||
|
||||
# gather information about the current key file, so
|
||||
# we can ensure the new file has the correct mode/owner
|
||||
|
||||
key_dir = os.path.dirname(self.keyfile)
|
||||
if os.path.exists(self.keyfile):
|
||||
key_stat = os.stat(self.keyfile)
|
||||
mode = key_stat.st_mode & 0o777
|
||||
uid = key_stat.st_uid
|
||||
gid = key_stat.st_gid
|
||||
else:
|
||||
mode = 0o644
|
||||
uid = os.getuid()
|
||||
gid = os.getgid()
|
||||
|
||||
# Save the new keys to a temporary file and move it into place
|
||||
# rather than rewriting the file. We set delete=False because
|
||||
# the file will be moved into place rather than cleaned up.
|
||||
|
||||
with tempfile.NamedTemporaryFile(dir=key_dir, delete=False) as tmp_keyfile:
|
||||
tmp_keyfile_name = tmp_keyfile.name
|
||||
os.chmod(tmp_keyfile_name, mode)
|
||||
os.chown(tmp_keyfile_name, uid, gid)
|
||||
self._save_ssh_host_keys(tmp_keyfile_name)
|
||||
|
||||
os.rename(tmp_keyfile_name, self.keyfile)
|
||||
except LockTimeout:
|
||||
raise AnsibleError(
|
||||
f'writing lock file for {self.keyfile} ran in to the timeout of {self.get_option("lock_file_timeout")}s')
|
||||
except paramiko.hostkeys.InvalidHostKey as e:
|
||||
raise AnsibleConnectionFailure(f'Invalid host key: {e.line}')
|
||||
except Exception as e:
|
||||
# unable to save keys, including scenario when key was invalid
|
||||
# and caught earlier
|
||||
raise AnsibleError(
|
||||
f'error occurred while writing SSH host keys!\n{to_text(e)}')
|
||||
finally:
|
||||
if tmp_keyfile_name is not None:
|
||||
pathlib.Path(tmp_keyfile_name).unlink(missing_ok=True)
|
||||
|
||||
self.ssh.close()
|
||||
self._connected = False
|
||||
37
plugins/doc_fragments/redfish.py
Normal file
37
plugins/doc_fragments/redfish.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2025 Ansible community
|
||||
# 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
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Use together with the community.general.redfish module utils' REDFISH_COMMON_ARGUMENT_SPEC
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
validate_certs:
|
||||
description:
|
||||
- If V(false), TLS/SSL certificates will not be validated.
|
||||
- Set this to V(true) to enable certificate checking. Should be used together with O(ca_path).
|
||||
type: bool
|
||||
default: false
|
||||
ca_path:
|
||||
description:
|
||||
- PEM formatted file that contains a CA certificate to be used for validation.
|
||||
- Only used if O(validate_certs=true).
|
||||
type: path
|
||||
ciphers:
|
||||
required: false
|
||||
description:
|
||||
- TLS/SSL Ciphers to use for the request.
|
||||
- When a list is provided, all ciphers are joined in order with V(:).
|
||||
- See the L(OpenSSL Cipher List Format,https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT)
|
||||
for more details.
|
||||
- The available ciphers is dependent on the Python and OpenSSL/LibreSSL versions.
|
||||
type: list
|
||||
elements: str
|
||||
"""
|
||||
@@ -9,12 +9,16 @@ from __future__ import annotations
|
||||
from ansible.errors import (
|
||||
AnsibleError,
|
||||
AnsibleFilterError,
|
||||
AnsibleFilterTypeError,
|
||||
)
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.common.collections import is_sequence
|
||||
|
||||
try:
|
||||
from ansible.errors import AnsibleTypeError
|
||||
except ImportError:
|
||||
from ansible.errors import AnsibleFilterTypeError as AnsibleTypeError
|
||||
|
||||
try:
|
||||
from hashids import Hashids
|
||||
HAS_HASHIDS = True
|
||||
@@ -63,7 +67,7 @@ def hashids_encode(nums, salt=None, alphabet=None, min_length=None):
|
||||
try:
|
||||
hashid = hashids.encode(*nums)
|
||||
except TypeError as e:
|
||||
raise AnsibleFilterTypeError(
|
||||
raise AnsibleTypeError(
|
||||
"Data to encode must by a tuple or list of ints: %s" % to_native(e)
|
||||
)
|
||||
|
||||
|
||||
@@ -23,29 +23,29 @@ options:
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Substitution converts str to AnsibleUnicode
|
||||
# -------------------------------------------
|
||||
# Substitution converts str to AnsibleUnicode or _AnsibleTaggedStr
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# String. AnsibleUnicode.
|
||||
# String. AnsibleUnicode or _AnsibleTaggedStr.
|
||||
- data: "abc"
|
||||
result: '{{ data | community.general.reveal_ansible_type }}'
|
||||
# result => AnsibleUnicode
|
||||
# result => AnsibleUnicode (or _AnsibleTaggedStr)
|
||||
|
||||
# String. AnsibleUnicode alias str.
|
||||
- alias: {"AnsibleUnicode": "str"}
|
||||
# String. AnsibleUnicode/_AnsibleTaggedStr alias str.
|
||||
- alias: {"AnsibleUnicode": "str", "_AnsibleTaggedStr": "str"}
|
||||
data: "abc"
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => str
|
||||
|
||||
# List. All items are AnsibleUnicode.
|
||||
# List. All items are AnsibleUnicode/_AnsibleTaggedStr.
|
||||
- data: ["a", "b", "c"]
|
||||
result: '{{ data | community.general.reveal_ansible_type }}'
|
||||
# result => list[AnsibleUnicode]
|
||||
# result => list[AnsibleUnicode] or list[_AnsibleTaggedStr]
|
||||
|
||||
# Dictionary. All keys are AnsibleUnicode. All values are AnsibleUnicode.
|
||||
# Dictionary. All keys and values are AnsibleUnicode/_AnsibleTaggedStr.
|
||||
- data: {"a": "foo", "b": "bar", "c": "baz"}
|
||||
result: '{{ data | community.general.reveal_ansible_type }}'
|
||||
# result => dict[AnsibleUnicode, AnsibleUnicode]
|
||||
# result => dict[AnsibleUnicode, AnsibleUnicode] or dict[_AnsibleTaggedStr, _AnsibleTaggedStr]
|
||||
|
||||
# No substitution and no alias. Type of strings is str
|
||||
# ----------------------------------------------------
|
||||
@@ -82,29 +82,43 @@ EXAMPLES = r"""
|
||||
- result: '{{ {"a": 1, "b": 2} | community.general.reveal_ansible_type }}'
|
||||
# result => dict[str, int]
|
||||
|
||||
# Type of strings is AnsibleUnicode or str
|
||||
# ----------------------------------------
|
||||
# Type of strings is AnsibleUnicode, _AnsibleTaggedStr, or str
|
||||
# ------------------------------------------------------------
|
||||
|
||||
# Dictionary. The keys are integers or strings. All values are strings.
|
||||
- alias: {"AnsibleUnicode": "str"}
|
||||
- alias:
|
||||
AnsibleUnicode: str
|
||||
_AnsibleTaggedStr: str
|
||||
_AnsibleTaggedInt: int
|
||||
data: {1: 'a', 'b': 'b'}
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => dict[int|str, str]
|
||||
|
||||
# Dictionary. All keys are integers. All values are keys.
|
||||
- alias: {"AnsibleUnicode": "str"}
|
||||
- alias:
|
||||
AnsibleUnicode: str
|
||||
_AnsibleTaggedStr: str
|
||||
_AnsibleTaggedInt: int
|
||||
data: {1: 'a', 2: 'b'}
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => dict[int, str]
|
||||
|
||||
# Dictionary. All keys are strings. Multiple types values.
|
||||
- alias: {"AnsibleUnicode": "str"}
|
||||
- alias:
|
||||
AnsibleUnicode: str
|
||||
_AnsibleTaggedStr: str
|
||||
_AnsibleTaggedInt: int
|
||||
_AnsibleTaggedFloat: float
|
||||
data: {'a': 1, 'b': 1.1, 'c': 'abc', 'd': true, 'e': ['x', 'y', 'z'], 'f': {'x': 1, 'y': 2}}
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => dict[str, bool|dict|float|int|list|str]
|
||||
|
||||
# List. Multiple types items.
|
||||
- alias: {"AnsibleUnicode": "str"}
|
||||
- alias:
|
||||
AnsibleUnicode: str
|
||||
_AnsibleTaggedStr: str
|
||||
_AnsibleTaggedInt: int
|
||||
_AnsibleTaggedFloat: float
|
||||
data: [1, 2, 1.1, 'abc', true, ['x', 'y', 'z'], {'x': 1, 'y': 2}]
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => list[bool|dict|float|int|list|str]
|
||||
@@ -122,6 +136,7 @@ from ansible_collections.community.general.plugins.plugin_utils.ansible_type imp
|
||||
def reveal_ansible_type(data, alias=None):
|
||||
"""Returns data type"""
|
||||
|
||||
# TODO: expose use_native_type parameter
|
||||
return _ansible_type(data, alias)
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_days
|
||||
short_description: Converte a duration string to days
|
||||
short_description: Converts a duration string to days
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to days.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_hours
|
||||
short_description: Converte a duration string to hours
|
||||
short_description: Converts a duration string to hours
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to hours.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_milliseconds
|
||||
short_description: Converte a duration string to milliseconds
|
||||
short_description: Converts a duration string to milliseconds
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to milliseconds.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_minutes
|
||||
short_description: Converte a duration string to minutes
|
||||
short_description: Converts a duration string to minutes
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to minutes.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_months
|
||||
short_description: Converte a duration string to months
|
||||
short_description: Convert a duration string to months
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to months.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_seconds
|
||||
short_description: Converte a duration string to seconds
|
||||
short_description: Converts a duration string to seconds
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to seconds.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_time_unit
|
||||
short_description: Converte a duration string to the given time unit
|
||||
short_description: Converts a duration string to the given time unit
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to the given time unit.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_weeks
|
||||
short_description: Converte a duration string to weeks
|
||||
short_description: Converts a duration string to weeks
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to weeks.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
DOCUMENTATION:
|
||||
name: to_years
|
||||
short_description: Converte a duration string to years
|
||||
short_description: Converts a duration string to years
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to years.
|
||||
|
||||
@@ -48,9 +48,14 @@ _value:
|
||||
|
||||
from unicodedata import normalize
|
||||
|
||||
from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import text_type
|
||||
|
||||
try:
|
||||
from ansible.errors import AnsibleTypeError
|
||||
except ImportError:
|
||||
from ansible.errors import AnsibleFilterTypeError as AnsibleTypeError
|
||||
|
||||
|
||||
def unicode_normalize(data, form='NFC'):
|
||||
"""Applies normalization to 'unicode' strings.
|
||||
@@ -65,7 +70,7 @@ def unicode_normalize(data, form='NFC'):
|
||||
"""
|
||||
|
||||
if not isinstance(data, text_type):
|
||||
raise AnsibleFilterTypeError("%s is not a valid input type" % type(data))
|
||||
raise AnsibleTypeError("%s is not a valid input type" % type(data))
|
||||
|
||||
if form not in ('NFC', 'NFD', 'NFKC', 'NFKD'):
|
||||
raise AnsibleFilterError("%s is not a valid form" % form)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -86,6 +86,11 @@ DOCUMENTATION = '''
|
||||
type: boolean
|
||||
default: false
|
||||
version_added: 6.1.0
|
||||
dns_servers:
|
||||
description: Specify which DNS servers to use for name resolution.
|
||||
type: list
|
||||
elements: string
|
||||
version_added: 10.5.0
|
||||
use_arp_ping:
|
||||
description: Whether to always (V(true)) use the quick ARP ping or (V(false)) a slower but more reliable method.
|
||||
type: boolean
|
||||
@@ -231,6 +236,10 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
if self.get_option('dns_resolve'):
|
||||
cmd.append('-n')
|
||||
|
||||
if self.get_option('dns_servers'):
|
||||
cmd.append('--dns-servers')
|
||||
cmd.append(','.join(self.get_option('dns_servers')))
|
||||
|
||||
if self.get_option('udp_scan'):
|
||||
cmd.append('-sU')
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -362,11 +366,26 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
def _get_node_ip(self, node):
|
||||
ret = self._get_json(f"{self.proxmox_url}/api2/json/nodes/{node}/network")
|
||||
|
||||
# sort interface by iface name to make selection as stable as possible
|
||||
ret.sort(key=lambda x: x['iface'])
|
||||
|
||||
for iface in ret:
|
||||
try:
|
||||
# only process interfaces adhering to these rules
|
||||
if 'active' not in iface:
|
||||
self.display.vvv(f"Interface {iface['iface']} on node {node} does not have an active state")
|
||||
continue
|
||||
if 'address' not in iface:
|
||||
self.display.vvv(f"Interface {iface['iface']} on node {node} does not have an address")
|
||||
continue
|
||||
if 'gateway' not in iface:
|
||||
self.display.vvv(f"Interface {iface['iface']} on node {node} does not have a gateway")
|
||||
continue
|
||||
self.display.vv(f"Using interface {iface['iface']} on node {node} with address {iface['address']} as node ip for ansible_host")
|
||||
return iface['address']
|
||||
except Exception:
|
||||
return None
|
||||
continue
|
||||
return None
|
||||
|
||||
def _get_lxc_interfaces(self, properties, node, vmid):
|
||||
status_key = self._fact('status')
|
||||
@@ -680,10 +699,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
|
||||
|
||||
@@ -6,60 +6,60 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: stackpath_compute
|
||||
short_description: StackPath Edge Computing inventory source
|
||||
version_added: 1.2.0
|
||||
author:
|
||||
- UNKNOWN (@shayrybak)
|
||||
extends_documentation_fragment:
|
||||
- inventory_cache
|
||||
- constructed
|
||||
DOCUMENTATION = r"""
|
||||
name: stackpath_compute
|
||||
short_description: StackPath Edge Computing inventory source
|
||||
version_added: 1.2.0
|
||||
author:
|
||||
- UNKNOWN (@shayrybak)
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Stackpath (the company) ceased its operations in June 2024. The API URL this plugin relies on is not found in DNS.
|
||||
alternative: There is none.
|
||||
extends_documentation_fragment:
|
||||
- inventory_cache
|
||||
- constructed
|
||||
description:
|
||||
- Get inventory hosts from StackPath Edge Computing.
|
||||
- Uses a YAML configuration file that ends with stackpath_compute.(yml|yaml).
|
||||
options:
|
||||
plugin:
|
||||
description:
|
||||
- Get inventory hosts from StackPath Edge Computing.
|
||||
- Uses a YAML configuration file that ends with stackpath_compute.(yml|yaml).
|
||||
options:
|
||||
plugin:
|
||||
description:
|
||||
- A token that ensures this is a source file for the plugin.
|
||||
required: true
|
||||
type: string
|
||||
choices: ['community.general.stackpath_compute']
|
||||
client_id:
|
||||
description:
|
||||
- An OAuth client ID generated from the API Management section of the StackPath customer portal
|
||||
U(https://control.stackpath.net/api-management).
|
||||
required: true
|
||||
type: str
|
||||
client_secret:
|
||||
description:
|
||||
- An OAuth client secret generated from the API Management section of the StackPath customer portal
|
||||
U(https://control.stackpath.net/api-management).
|
||||
required: true
|
||||
type: str
|
||||
stack_slugs:
|
||||
description:
|
||||
- A list of Stack slugs to query instances in. If no entry then get instances in all stacks on the account.
|
||||
type: list
|
||||
elements: str
|
||||
use_internal_ip:
|
||||
description:
|
||||
- Whether or not to use internal IP addresses, If false, uses external IP addresses, internal otherwise.
|
||||
- If an instance doesn't have an external IP it will not be returned when this option is set to false.
|
||||
type: bool
|
||||
'''
|
||||
- A token that ensures this is a source file for the plugin.
|
||||
required: true
|
||||
type: string
|
||||
choices: ['community.general.stackpath_compute']
|
||||
client_id:
|
||||
description:
|
||||
- An OAuth client ID generated from the API Management section of the StackPath customer portal U(https://control.stackpath.net/api-management).
|
||||
required: true
|
||||
type: str
|
||||
client_secret:
|
||||
description:
|
||||
- An OAuth client secret generated from the API Management section of the StackPath customer portal U(https://control.stackpath.net/api-management).
|
||||
required: true
|
||||
type: str
|
||||
stack_slugs:
|
||||
description:
|
||||
- A list of Stack slugs to query instances in. If no entry then get instances in all stacks on the account.
|
||||
type: list
|
||||
elements: str
|
||||
use_internal_ip:
|
||||
description:
|
||||
- Whether or not to use internal IP addresses, If false, uses external IP addresses, internal otherwise.
|
||||
- If an instance doesn't have an external IP it will not be returned when this option is set to false.
|
||||
type: bool
|
||||
"""
|
||||
|
||||
EXAMPLES = '''
|
||||
# Example using credentials to fetch all workload instances in a stack.
|
||||
---
|
||||
EXAMPLES = r"""
|
||||
plugin: community.general.stackpath_compute
|
||||
client_id: my_client_id
|
||||
client_secret: my_client_secret
|
||||
stack_slugs:
|
||||
- my_first_stack_slug
|
||||
- my_other_stack_slug
|
||||
- my_first_stack_slug
|
||||
- my_other_stack_slug
|
||||
use_internal_ip: false
|
||||
'''
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import json
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -130,12 +130,24 @@ from ansible.template import Templar
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
try:
|
||||
from ansible.template import trust_as_template as _trust_as_template
|
||||
HAS_DATATAGGING = True
|
||||
except ImportError:
|
||||
HAS_DATATAGGING = False
|
||||
|
||||
|
||||
# Whether Templar has a cache, which can be controlled by Templar.template()'s cache option.
|
||||
# The cache was removed for ansible-core 2.14 (https://github.com/ansible/ansible/pull/78419)
|
||||
_TEMPLAR_HAS_TEMPLATE_CACHE = LooseVersion(ansible_version) < LooseVersion('2.14.0')
|
||||
|
||||
|
||||
def _make_safe(value):
|
||||
if HAS_DATATAGGING and isinstance(value, str):
|
||||
return _trust_as_template(value)
|
||||
return value
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def __evaluate(self, expression, templar, variables):
|
||||
"""Evaluate expression with templar.
|
||||
@@ -144,10 +156,13 @@ class LookupModule(LookupBase):
|
||||
``variables`` are the variables to use.
|
||||
"""
|
||||
templar.available_variables = variables or {}
|
||||
expression = "{0}{1}{2}".format("{{", expression, "}}")
|
||||
quoted_expression = "{0}{1}{2}".format("{{", expression, "}}")
|
||||
if _TEMPLAR_HAS_TEMPLATE_CACHE:
|
||||
return templar.template(expression, cache=False)
|
||||
return templar.template(expression)
|
||||
return templar.template(quoted_expression, cache=False)
|
||||
if hasattr(templar, 'evaluate_expression'):
|
||||
# This is available since the Data Tagging PR has been merged
|
||||
return templar.evaluate_expression(_make_safe(expression))
|
||||
return templar.template(quoted_expression)
|
||||
|
||||
def __process(self, result, terms, index, current, templar, variables):
|
||||
"""Fills ``result`` list with evaluated items.
|
||||
|
||||
@@ -13,6 +13,10 @@ DOCUMENTATION = '''
|
||||
short_description: get credentials from Manifold.co
|
||||
description:
|
||||
- Retrieves resources' credentials from Manifold.co
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: Manifold (the company) has been acquired in 2021 and the services used by this plugin are no longer operational.
|
||||
alternative: There is none.
|
||||
options:
|
||||
_terms:
|
||||
description:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -58,11 +58,8 @@ from ansible.errors import AnsibleLookupError
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
class OnePassCLIv2SSHKey(OnePassCLIv2):
|
||||
|
||||
def get_ssh_key(self, item_id, vault=None, token=None, ssh_format=False):
|
||||
rc, out, err = self.get_raw(item_id, vault=vault, token=token)
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def get_ssh_key(self, out, item_id, ssh_format=False):
|
||||
data = json.loads(out)
|
||||
|
||||
if data.get("category") != "SSH_KEY":
|
||||
@@ -87,8 +84,6 @@ class OnePassCLIv2SSHKey(OnePassCLIv2):
|
||||
)
|
||||
return private_key_field.get("value", "")
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
|
||||
@@ -114,11 +109,11 @@ class LookupModule(LookupBase):
|
||||
account_id=account_id,
|
||||
connect_host=connect_host,
|
||||
connect_token=connect_token,
|
||||
cli_class=OnePassCLIv2SSHKey,
|
||||
cli_class=OnePassCLIv2,
|
||||
)
|
||||
op.assert_logged_in()
|
||||
|
||||
return [
|
||||
op._cli.get_ssh_key(term, vault, token=op.token, ssh_format=ssh_format)
|
||||
self.get_ssh_key(op.get_raw(term, vault), term, ssh_format=ssh_format)
|
||||
for term in terms
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,7 +78,9 @@ def as_list(ignore_none=None, min_len=0, max_len=None):
|
||||
return _ArgFormat(func, ignore_none=ignore_none)
|
||||
|
||||
|
||||
def as_fixed(args):
|
||||
def as_fixed(*args):
|
||||
if len(args) == 1 and is_sequence(args[0]):
|
||||
args = args[0]
|
||||
return _ArgFormat(lambda value: _ensure_list(args), ignore_none=False, ignore_missing_value=True)
|
||||
|
||||
|
||||
|
||||
@@ -456,6 +456,8 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
# The Keycloak API expects the realm name (like `master`) not the ID when fetching the realm data.
|
||||
# See the Keycloak API docs: https://www.keycloak.org/docs-api/latest/rest-api/#_realms_admin
|
||||
def get_realm_by_id(self, realm='master'):
|
||||
""" Obtain realm representation by id
|
||||
|
||||
@@ -1856,7 +1858,7 @@ class KeycloakAPI(object):
|
||||
else:
|
||||
composite_url = URL_REALM_ROLE_COMPOSITES.format(url=self.baseurl, realm=realm, name=quote(rolerep["name"], safe=''))
|
||||
# Get existing composites
|
||||
self._request_and_deserialize(composite_url, method='GET')
|
||||
return self._request_and_deserialize(composite_url, method='GET')
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg='Could not get role %s composites in realm %s: %s'
|
||||
% (rolerep['name'], realm, str(e)))
|
||||
@@ -2222,6 +2224,23 @@ class KeycloakAPI(object):
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e)))
|
||||
|
||||
def delete_authentication_config(self, configId, realm='master'):
|
||||
""" Delete authenticator config
|
||||
|
||||
:param configId: id of authentication config
|
||||
:param realm: realm of authentication config to be deleted
|
||||
"""
|
||||
try:
|
||||
# Send a DELETE request to remove the specified authentication config from the Keycloak server.
|
||||
self._request(
|
||||
URL_AUTHENTICATION_CONFIG.format(
|
||||
url=self.baseurl,
|
||||
realm=realm,
|
||||
id=configId),
|
||||
method='DELETE')
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg="Unable to delete authentication config %s: %s" % (configId, str(e)))
|
||||
|
||||
def create_subflow(self, subflowName, flowAlias, realm='master', flowType='basic-flow'):
|
||||
""" Create new sublow on the flow
|
||||
|
||||
@@ -2808,29 +2827,33 @@ class KeycloakAPI(object):
|
||||
|
||||
def get_user_groups(self, user_id, realm='master'):
|
||||
"""
|
||||
Get groups for a user.
|
||||
Get the group names for a user.
|
||||
:param user_id: User ID
|
||||
:param realm: Realm
|
||||
:return: Representation of the client groups.
|
||||
:return: The client group names as a list of strings.
|
||||
"""
|
||||
user_groups = self.get_user_group_details(user_id, realm)
|
||||
return [user_group['name'] for user_group in user_groups if 'name' in user_group]
|
||||
|
||||
def get_user_group_details(self, user_id, realm='master'):
|
||||
"""
|
||||
Get the group details for a user.
|
||||
:param user_id: User ID
|
||||
:param realm: Realm
|
||||
:return: The client group details as a list of dictionaries.
|
||||
"""
|
||||
try:
|
||||
groups = []
|
||||
user_groups_url = URL_USER_GROUPS.format(
|
||||
url=self.baseurl,
|
||||
realm=realm,
|
||||
id=user_id)
|
||||
user_groups = json.load(
|
||||
self._request(
|
||||
user_groups_url,
|
||||
method='GET'))
|
||||
for user_group in user_groups:
|
||||
groups.append(user_group["name"])
|
||||
return groups
|
||||
user_groups_url = URL_USER_GROUPS.format(url=self.baseurl, realm=realm, id=user_id)
|
||||
return self._request_and_deserialize(user_groups_url, method='GET')
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg='Could not get groups for user %s in realm %s: %s'
|
||||
% (user_id, realm, str(e)))
|
||||
|
||||
def add_user_in_group(self, user_id, group_id, realm='master'):
|
||||
"""DEPRECATED: Call add_user_to_group(...) instead. This method is scheduled for removal in community.general 13.0.0."""
|
||||
return self.add_user_to_group(user_id, group_id, realm)
|
||||
|
||||
def add_user_to_group(self, user_id, group_id, realm='master'):
|
||||
"""
|
||||
Add a user to a group.
|
||||
:param user_id: User ID
|
||||
@@ -2848,7 +2871,7 @@ class KeycloakAPI(object):
|
||||
user_group_url,
|
||||
method='PUT')
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg='Could not add user %s in group %s in realm %s: %s'
|
||||
self.fail_request(e, msg='Could not add user %s to group %s in realm %s: %s'
|
||||
% (user_id, group_id, realm, str(e)))
|
||||
|
||||
def remove_user_from_group(self, user_id, group_id, realm='master'):
|
||||
@@ -2879,49 +2902,72 @@ class KeycloakAPI(object):
|
||||
:param realm: Realm
|
||||
:return: True if group membership has been changed. False Otherwise.
|
||||
"""
|
||||
changed = False
|
||||
try:
|
||||
user_existing_groups = self.get_user_groups(
|
||||
user_id=userrep['id'],
|
||||
realm=realm)
|
||||
groups_to_add_and_remove = self.extract_groups_to_add_to_and_remove_from_user(groups)
|
||||
# If group membership need to be changed
|
||||
if not is_struct_included(groups_to_add_and_remove['add'], user_existing_groups):
|
||||
# Get available groups in the realm
|
||||
realm_groups = self.get_groups(realm=realm)
|
||||
for realm_group in realm_groups:
|
||||
if "name" in realm_group and realm_group["name"] in groups_to_add_and_remove['add']:
|
||||
self.add_user_in_group(
|
||||
user_id=userrep["id"],
|
||||
group_id=realm_group["id"],
|
||||
realm=realm)
|
||||
changed = True
|
||||
elif "name" in realm_group and realm_group['name'] in groups_to_add_and_remove['remove']:
|
||||
self.remove_user_from_group(
|
||||
user_id=userrep['id'],
|
||||
group_id=realm_group['id'],
|
||||
realm=realm)
|
||||
changed = True
|
||||
return changed
|
||||
groups_to_add, groups_to_remove = self.extract_groups_to_add_to_and_remove_from_user(groups)
|
||||
if not groups_to_add and not groups_to_remove:
|
||||
return False
|
||||
|
||||
user_groups = self.get_user_group_details(user_id=userrep['id'], realm=realm)
|
||||
user_group_names = [user_group['name'] for user_group in user_groups if 'name' in user_group]
|
||||
user_group_paths = [user_group['path'] for user_group in user_groups if 'path' in user_group]
|
||||
|
||||
groups_to_add = [group_to_add for group_to_add in groups_to_add
|
||||
if group_to_add not in user_group_names and group_to_add not in user_group_paths]
|
||||
groups_to_remove = [group_to_remove for group_to_remove in groups_to_remove
|
||||
if group_to_remove in user_group_names or group_to_remove in user_group_paths]
|
||||
if not groups_to_add and not groups_to_remove:
|
||||
return False
|
||||
|
||||
for group_to_add in groups_to_add:
|
||||
realm_group = self.find_group_by_path(group_to_add, realm=realm)
|
||||
if realm_group:
|
||||
self.add_user_to_group(user_id=userrep['id'], group_id=realm_group['id'], realm=realm)
|
||||
|
||||
for group_to_remove in groups_to_remove:
|
||||
realm_group = self.find_group_by_path(group_to_remove, realm=realm)
|
||||
if realm_group:
|
||||
self.remove_user_from_group(user_id=userrep['id'], group_id=realm_group['id'], realm=realm)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not update group membership for user %s in realm %s: %s'
|
||||
% (userrep['id]'], realm, str(e)))
|
||||
% (userrep['username'], realm, e))
|
||||
|
||||
def extract_groups_to_add_to_and_remove_from_user(self, groups):
|
||||
groups_extract = {}
|
||||
groups_to_add = []
|
||||
groups_to_remove = []
|
||||
if isinstance(groups, list) and len(groups) > 0:
|
||||
if isinstance(groups, list):
|
||||
for group in groups:
|
||||
group_name = group['name'] if isinstance(group, dict) and 'name' in group else group
|
||||
if isinstance(group, dict) and ('state' not in group or group['state'] == 'present'):
|
||||
groups_to_add.append(group_name)
|
||||
else:
|
||||
groups_to_remove.append(group_name)
|
||||
groups_extract['add'] = groups_to_add
|
||||
groups_extract['remove'] = groups_to_remove
|
||||
if isinstance(group, dict):
|
||||
if 'state' not in group or group['state'] == 'present':
|
||||
groups_to_add.append(group_name)
|
||||
else:
|
||||
groups_to_remove.append(group_name)
|
||||
return groups_to_add, groups_to_remove
|
||||
|
||||
return groups_extract
|
||||
def find_group_by_path(self, target, realm='master'):
|
||||
"""
|
||||
Finds a realm group by path, e.g. '/my/group'.
|
||||
The path is formed by prepending a '/' character to `target` unless it's already present.
|
||||
This adds support for finding top level groups by name and subgroups by path.
|
||||
"""
|
||||
groups = self.get_groups(realm=realm)
|
||||
path = target if target.startswith('/') else '/' + target
|
||||
for segment in path.split('/'):
|
||||
if not segment:
|
||||
continue
|
||||
abort = True
|
||||
for group in groups:
|
||||
if group['path'] == path:
|
||||
return self.get_group_by_groupid(group['id'], realm=realm)
|
||||
if group['name'] == segment:
|
||||
groups = self.get_subgroups(group, realm=realm)
|
||||
abort = False
|
||||
break
|
||||
if abort:
|
||||
break
|
||||
return None
|
||||
|
||||
def convert_user_group_list_of_str_to_list_of_dict(self, groups):
|
||||
list_of_groups = []
|
||||
|
||||
56
plugins/module_utils/pacemaker.py
Normal file
56
plugins/module_utils/pacemaker.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2025, Dexter Le <dextersydney2001@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
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
|
||||
|
||||
_state_map = {
|
||||
"present": "create",
|
||||
"absent": "remove",
|
||||
"status": "status",
|
||||
"enabled": "enable",
|
||||
"disabled": "disable"
|
||||
}
|
||||
|
||||
|
||||
def fmt_resource_type(value):
|
||||
return [value[k] for k in ['resource_standard', 'resource_provider', 'resource_name'] if value.get(k) is not None]
|
||||
|
||||
|
||||
def fmt_resource_operation(value):
|
||||
cmd = []
|
||||
for op in value:
|
||||
cmd.append("op")
|
||||
cmd.append(op.get('operation_action'))
|
||||
for operation_option in op.get('operation_option'):
|
||||
cmd.append(operation_option)
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def fmt_resource_argument(value):
|
||||
return ['--group' if value['argument_action'] == 'group' else value['argument_action']] + value['argument_option']
|
||||
|
||||
|
||||
def pacemaker_runner(module, cli_action, **kwargs):
|
||||
runner = CmdRunner(
|
||||
module,
|
||||
command=['pcs', cli_action],
|
||||
arg_formats=dict(
|
||||
state=cmd_runner_fmt.as_map(_state_map),
|
||||
name=cmd_runner_fmt.as_list(),
|
||||
resource_type=cmd_runner_fmt.as_func(fmt_resource_type),
|
||||
resource_option=cmd_runner_fmt.as_list(),
|
||||
resource_operation=cmd_runner_fmt.as_func(fmt_resource_operation),
|
||||
resource_meta=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("meta"),
|
||||
resource_argument=cmd_runner_fmt.as_func(fmt_resource_argument),
|
||||
wait=cmd_runner_fmt.as_opt_eq_val("--wait"),
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
return runner
|
||||
@@ -95,10 +95,7 @@ def puppet_runner(module):
|
||||
skip_tags=cmd_runner_fmt.as_func(lambda v: ["--skip_tags", ",".join(v)]),
|
||||
certname=cmd_runner_fmt.as_opt_eq_val("--certname"),
|
||||
noop=cmd_runner_fmt.as_func(noop_func),
|
||||
use_srv_records=cmd_runner_fmt.as_map({
|
||||
True: "--usr_srv_records",
|
||||
False: "--no-usr_srv_records",
|
||||
}),
|
||||
use_srv_records=cmd_runner_fmt.as_bool("--usr_srv_records", "--no-usr_srv_records", ignore_none=True),
|
||||
logdest=cmd_runner_fmt.as_map(_logdest_map, default=[]),
|
||||
modulepath=cmd_runner_fmt.as_opt_eq_val("--modulepath"),
|
||||
_execute=cmd_runner_fmt.as_func(execute_func),
|
||||
|
||||
@@ -38,6 +38,21 @@ FAIL_MSG = 'Issuing a data modification command without specifying the '\
|
||||
'than one %(resource)s is no longer allowed. Use the `resource_id` '\
|
||||
'option to specify the target %(resource)s ID.'
|
||||
|
||||
# Use together with the community.general.redfish docs fragment
|
||||
REDFISH_COMMON_ARGUMENT_SPEC = {
|
||||
"validate_certs": {
|
||||
"type": "bool",
|
||||
"default": False,
|
||||
},
|
||||
"ca_path": {
|
||||
"type": "path",
|
||||
},
|
||||
"ciphers": {
|
||||
"type": "list",
|
||||
"elements": "str",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class RedfishUtils(object):
|
||||
|
||||
@@ -53,8 +68,10 @@ class RedfishUtils(object):
|
||||
self.resource_id = resource_id
|
||||
self.data_modification = data_modification
|
||||
self.strip_etag_quotes = strip_etag_quotes
|
||||
self.ciphers = ciphers
|
||||
self.ciphers = ciphers if ciphers is not None else module.params.get("ciphers")
|
||||
self._vendor = None
|
||||
self.validate_certs = module.params.get("validate_certs", False)
|
||||
self.ca_path = module.params.get("ca_path")
|
||||
|
||||
def _auth_params(self, headers):
|
||||
"""
|
||||
@@ -132,6 +149,17 @@ class RedfishUtils(object):
|
||||
resp['msg'] = 'Properties in %s are already set' % uri
|
||||
return resp
|
||||
|
||||
def _request(self, uri, **kwargs):
|
||||
kwargs.setdefault("validate_certs", self.validate_certs)
|
||||
kwargs.setdefault("follow_redirects", "all")
|
||||
kwargs.setdefault("use_proxy", True)
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault("ciphers", self.ciphers)
|
||||
kwargs.setdefault("ca_path", self.ca_path)
|
||||
resp = open_url(uri, **kwargs)
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
return resp, headers
|
||||
|
||||
# The following functions are to send GET/POST/PATCH/DELETE requests
|
||||
def get_request(self, uri, override_headers=None, allow_no_resp=False, timeout=None):
|
||||
req_headers = dict(GET_HEADERS)
|
||||
@@ -145,12 +173,15 @@ class RedfishUtils(object):
|
||||
# in case the caller will be using sessions later.
|
||||
if uri == (self.root_uri + self.service_root):
|
||||
basic_auth = False
|
||||
resp = open_url(uri, method="GET", headers=req_headers,
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=timeout, ciphers=self.ciphers)
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
resp, headers = self._request(
|
||||
uri,
|
||||
method="GET",
|
||||
headers=req_headers,
|
||||
url_username=username,
|
||||
url_password=password,
|
||||
force_basic_auth=basic_auth,
|
||||
timeout=timeout,
|
||||
)
|
||||
try:
|
||||
if headers.get('content-encoding') == 'gzip' and LooseVersion(ansible_version) < LooseVersion('2.14'):
|
||||
# Older versions of Ansible do not automatically decompress the data
|
||||
@@ -194,18 +225,20 @@ class RedfishUtils(object):
|
||||
req_headers['content-type'] = multipart_encoder[1]
|
||||
else:
|
||||
data = json.dumps(pyld)
|
||||
resp = open_url(uri, data=data,
|
||||
headers=req_headers, method="POST",
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
resp, headers = self._request(
|
||||
uri,
|
||||
data=data,
|
||||
headers=req_headers,
|
||||
method="POST",
|
||||
url_username=username,
|
||||
url_password=password,
|
||||
force_basic_auth=basic_auth,
|
||||
)
|
||||
try:
|
||||
data = json.loads(to_native(resp.read()))
|
||||
except Exception as e:
|
||||
# No response data; this is okay in many cases
|
||||
data = None
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
except HTTPError as e:
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
@@ -248,12 +281,15 @@ class RedfishUtils(object):
|
||||
|
||||
username, password, basic_auth = self._auth_params(req_headers)
|
||||
try:
|
||||
resp = open_url(uri, data=json.dumps(pyld),
|
||||
headers=req_headers, method="PATCH",
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
resp, dummy = self._request(
|
||||
uri,
|
||||
data=json.dumps(pyld),
|
||||
headers=req_headers,
|
||||
method="PATCH",
|
||||
url_username=username,
|
||||
url_password=password,
|
||||
force_basic_auth=basic_auth,
|
||||
)
|
||||
except HTTPError as e:
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False, 'changed': False,
|
||||
@@ -283,12 +319,15 @@ class RedfishUtils(object):
|
||||
req_headers['If-Match'] = etag
|
||||
username, password, basic_auth = self._auth_params(req_headers)
|
||||
try:
|
||||
resp = open_url(uri, data=json.dumps(pyld),
|
||||
headers=req_headers, method="PUT",
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
resp, dummy = self._request(
|
||||
uri,
|
||||
data=json.dumps(pyld),
|
||||
headers=req_headers,
|
||||
method="PUT",
|
||||
url_username=username,
|
||||
url_password=password,
|
||||
force_basic_auth=basic_auth,
|
||||
)
|
||||
except HTTPError as e:
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
@@ -309,12 +348,15 @@ class RedfishUtils(object):
|
||||
username, password, basic_auth = self._auth_params(req_headers)
|
||||
try:
|
||||
data = json.dumps(pyld) if pyld else None
|
||||
resp = open_url(uri, data=data,
|
||||
headers=req_headers, method="DELETE",
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
resp, dummy = self._request(
|
||||
uri,
|
||||
data=data,
|
||||
headers=req_headers,
|
||||
method="DELETE",
|
||||
url_username=username,
|
||||
url_password=password,
|
||||
force_basic_auth=basic_auth,
|
||||
)
|
||||
except HTTPError as e:
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
@@ -1119,7 +1161,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'):
|
||||
@@ -3969,3 +4012,20 @@ class RedfishUtils(object):
|
||||
'ret': True,
|
||||
'entries': response['data']
|
||||
}
|
||||
|
||||
def get_power_restore_policy(self, systems_uri):
|
||||
# Retrieve System resource
|
||||
response = self.get_request(self.root_uri + systems_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
return {
|
||||
'ret': True,
|
||||
'entries': response['data']['PowerRestorePolicy']
|
||||
}
|
||||
|
||||
def get_multi_power_restore_policy(self):
|
||||
return self.aggregate_systems(self.get_power_restore_policy)
|
||||
|
||||
def set_power_restore_policy(self, policy):
|
||||
body = {'PowerRestorePolicy': policy}
|
||||
return self.patch_request(self.root_uri + self.systems_uri, body, check_pyld=True)
|
||||
|
||||
34
plugins/module_utils/systemd.py
Normal file
34
plugins/module_utils/systemd.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
|
||||
|
||||
def systemd_runner(module, command, **kwargs):
|
||||
arg_formats = dict(
|
||||
version=cmd_runner_fmt.as_fixed("--version"),
|
||||
list_units=cmd_runner_fmt.as_fixed(["list-units", "--no-pager"]),
|
||||
types=cmd_runner_fmt.as_func(lambda v: [] if not v else ["--type", ",".join(v)]),
|
||||
all=cmd_runner_fmt.as_fixed("--all"),
|
||||
plain=cmd_runner_fmt.as_fixed("--plain"),
|
||||
no_legend=cmd_runner_fmt.as_fixed("--no-legend"),
|
||||
show=cmd_runner_fmt.as_fixed("show"),
|
||||
props=cmd_runner_fmt.as_func(lambda v: [] if not v else ["-p", ",".join(v)]),
|
||||
dashdash=cmd_runner_fmt.as_fixed("--"),
|
||||
unit=cmd_runner_fmt.as_list(),
|
||||
)
|
||||
|
||||
runner = CmdRunner(
|
||||
module,
|
||||
command=command,
|
||||
arg_formats=arg_formats,
|
||||
check_rc=True,
|
||||
**kwargs
|
||||
)
|
||||
return runner
|
||||
@@ -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
|
||||
@@ -206,17 +206,29 @@ members:
|
||||
import re
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils import deps
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper, ModuleHelperException
|
||||
|
||||
with deps.declare("BeautifulSoup"):
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils.six import raise_from, PY2
|
||||
|
||||
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,22 +268,22 @@ 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))
|
||||
else:
|
||||
try:
|
||||
soup = BeautifulSoup(resp)
|
||||
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')
|
||||
for valuesset in subsoup[1::1]:
|
||||
if re.search(pattern=self.host, string=str(valuesset)):
|
||||
values = valuesset.findAll('td')
|
||||
return {keys[x].string: values[x].string for x in range(0, len(keys))}
|
||||
raise ModuleHelperException("Could not get balancer_member_page, check for connectivity! {0}".format(info))
|
||||
|
||||
try:
|
||||
soup = BeautifulSoup(resp)
|
||||
except TypeError as exc:
|
||||
raise_from(ModuleHelperException("Cannot parse balancer_member_page HTML! {0}".format(exc)), exc)
|
||||
|
||||
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 = find_all(valuesset, 'td')
|
||||
return {keys[x].string: values[x].string for x in range(0, len(keys))}
|
||||
|
||||
def get_member_status(self):
|
||||
""" Returns a dictionary of a balancer member's status attributes."""
|
||||
@@ -280,7 +292,7 @@ class BalancerMember(object):
|
||||
'hot_standby': 'Stby',
|
||||
'ignore_errors': 'Ign'}
|
||||
actual_status = self.attributes['Status']
|
||||
status = {mode: patt in actual_status for mode, patt in iteritems(status_mapping)}
|
||||
status = {mode: patt in actual_status for mode, patt in status_mapping.items()}
|
||||
return status
|
||||
|
||||
def set_member_status(self, values):
|
||||
@@ -294,9 +306,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'])
|
||||
raise ModuleHelperException("Could not set the member status! {0} {1}".format(self.host, info['status']))
|
||||
|
||||
attributes = property(get_member_attributes)
|
||||
status = property(get_member_status, set_member_status)
|
||||
@@ -317,13 +329,10 @@ class BalancerMember(object):
|
||||
class Balancer(object):
|
||||
""" Apache httpd 2.4 mod_proxy balancer object"""
|
||||
|
||||
def __init__(self, host, suffix, module, tls=False):
|
||||
if tls:
|
||||
self.base_url = 'https://{0}'.format(host)
|
||||
self.url = 'https://{0}{1}'.format(host, suffix)
|
||||
else:
|
||||
self.base_url = 'http://{0}'.format(host)
|
||||
self.url = 'http://{0}{1}'.format(host, suffix)
|
||||
def __init__(self, module, host, suffix, tls=False):
|
||||
proto = "https" if tls else "http"
|
||||
self.base_url = '{0}://{1}'.format(proto, host)
|
||||
self.url = '{0}://{1}{2}'.format(proto, host, suffix)
|
||||
self.module = module
|
||||
self.page = self.fetch_balancer_page()
|
||||
|
||||
@@ -331,37 +340,38 @@ class Balancer(object):
|
||||
""" Returns the balancer management html page as a string for later parsing."""
|
||||
resp, info = fetch_url(self.module, self.url)
|
||||
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()
|
||||
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))
|
||||
return content
|
||||
raise ModuleHelperException("Could not get balancer page! HTTP status response: {0}".format(info['status']))
|
||||
|
||||
self.module.fail_json(msg="Could not get the Apache server version from the balancer-manager")
|
||||
content = to_text(resp.read())
|
||||
apache_version = regexp_extraction(content.upper(), APACHE_VERSION_EXPRESSION, 1)
|
||||
if not apache_version:
|
||||
raise ModuleHelperException("Could not get the Apache server version from the balancer-manager")
|
||||
|
||||
if not re.search(pattern=r"2\.4\.[\d]*", string=apache_version):
|
||||
raise ModuleHelperException("This module only acts on an Apache2 2.4+ instance, current Apache2 version: {0}".format(apache_version))
|
||||
return content
|
||||
|
||||
def get_balancer_members(self):
|
||||
""" Returns members of the balancer as a generator object for later iteration."""
|
||||
try:
|
||||
soup = BeautifulSoup(self.page)
|
||||
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]:
|
||||
balancer_member_suffix = str(element.get('href'))
|
||||
if not balancer_member_suffix:
|
||||
self.module.fail_json(msg="Argument 'balancer_member_suffix' is empty!")
|
||||
else:
|
||||
yield BalancerMember(self.base_url + balancer_member_suffix, self.url, self.module)
|
||||
except TypeError as e:
|
||||
raise_from(ModuleHelperException("Cannot parse balancer page HTML! {0}".format(self.page)), e)
|
||||
|
||||
elements = find_all(soup, 'a')
|
||||
for element in elements[1::1]:
|
||||
balancer_member_suffix = element.get('href')
|
||||
if not balancer_member_suffix:
|
||||
raise ModuleHelperException("Argument 'balancer_member_suffix' is empty!")
|
||||
|
||||
yield BalancerMember(self.base_url + balancer_member_suffix, self.url, self.module)
|
||||
|
||||
members = property(get_balancer_members)
|
||||
|
||||
|
||||
def main():
|
||||
class ApacheModProxy(ModuleHelper):
|
||||
""" Initiates module."""
|
||||
module = AnsibleModule(
|
||||
module = dict(
|
||||
argument_spec=dict(
|
||||
balancer_vhost=dict(required=True, type='str'),
|
||||
balancer_url_suffix=dict(default="/balancer-manager/", type='str'),
|
||||
@@ -372,64 +382,47 @@ def main():
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
deps.validate(module)
|
||||
def __init_module__(self):
|
||||
deps.validate(self.module)
|
||||
|
||||
if module.params['state'] is not None:
|
||||
states = module.params['state']
|
||||
if (len(states) > 1) and (("present" in states) or ("enabled" in states)):
|
||||
module.fail_json(msg="state present/enabled is mutually exclusive with other states!")
|
||||
else:
|
||||
states = ['None']
|
||||
if len(self.vars.state or []) > 1 and ("present" in self.vars.state or "enabled" in self.vars.state):
|
||||
self.do_raise(msg="states present/enabled are mutually exclusive with other states!")
|
||||
|
||||
mybalancer = Balancer(module.params['balancer_vhost'],
|
||||
module.params['balancer_url_suffix'],
|
||||
module=module,
|
||||
tls=module.params['tls'])
|
||||
self.mybalancer = Balancer(self.module, self.vars.balancer_vhost, self.vars.balancer_url_suffix, tls=self.vars.tls)
|
||||
|
||||
if module.params['member_host'] is None:
|
||||
json_output_list = []
|
||||
for member in mybalancer.members:
|
||||
json_output_list.append(member.as_dict())
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
members=json_output_list
|
||||
)
|
||||
else:
|
||||
changed = False
|
||||
member_exists = False
|
||||
member_status = {'disabled': False, 'drained': False, 'hot_standby': False, 'ignore_errors': False}
|
||||
for mode in member_status:
|
||||
for state in states:
|
||||
if mode == state:
|
||||
member_status[mode] = True
|
||||
elif mode == 'disabled' and state == 'absent':
|
||||
member_status[mode] = True
|
||||
|
||||
for member in mybalancer.members:
|
||||
if str(member.host) == module.params['member_host']:
|
||||
member_exists = True
|
||||
if module.params['state'] is not None:
|
||||
member_status_before = member.status
|
||||
if not module.check_mode:
|
||||
member_status_after = member.status = member_status
|
||||
else:
|
||||
member_status_after = member_status
|
||||
if member_status_before != member_status_after:
|
||||
changed = True
|
||||
json_output = member.as_dict()
|
||||
if member_exists:
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
member=json_output
|
||||
)
|
||||
def __run__(self):
|
||||
if self.vars.member_host is None:
|
||||
self.vars.members = [member.as_dict() for member in self.mybalancer.members]
|
||||
else:
|
||||
module.fail_json(
|
||||
msg='{member_host} is not a member of the balancer {balancer_vhost}!'.format(
|
||||
member_host=module.params['member_host'],
|
||||
balancer_vhost=module.params['balancer_vhost'],
|
||||
)
|
||||
)
|
||||
member_exists = False
|
||||
member_status = {'disabled': False, 'drained': False, 'hot_standby': False, 'ignore_errors': False}
|
||||
for mode in member_status:
|
||||
for state in self.vars.state or []:
|
||||
if mode == state:
|
||||
member_status[mode] = True
|
||||
elif mode == 'disabled' and state == 'absent':
|
||||
member_status[mode] = True
|
||||
|
||||
for member in self.mybalancer.members:
|
||||
if str(member.host) == self.vars.member_host:
|
||||
member_exists = True
|
||||
if self.vars.state is not None:
|
||||
member_status_before = member.status
|
||||
if not self.check_mode:
|
||||
member_status_after = member.status = member_status
|
||||
else:
|
||||
member_status_after = member_status
|
||||
self.changed |= (member_status_before != member_status_after)
|
||||
self.vars.member = member.as_dict()
|
||||
|
||||
if not member_exists:
|
||||
self.do_raise(msg='{0} is not a member of the balancer {1}!'.format(self.vars.member_host, self.vars.balancer_vhost))
|
||||
|
||||
|
||||
def main():
|
||||
ApacheModProxy.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -194,6 +194,7 @@ def create_apache_identifier(name):
|
||||
|
||||
# re expressions to extract subparts of names
|
||||
re_workarounds = [
|
||||
('php8', re.compile(r'^(php)[\d\.]+')),
|
||||
('php', re.compile(r'^(php\d)\.')),
|
||||
]
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ EXAMPLES = r"""
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@
|
||||
snapshot_source: /
|
||||
default: Yes
|
||||
default: true
|
||||
filesystem_device: /dev/vda2
|
||||
|
||||
- name: Create a snapshot of the /@ subvolume and recursively creating intermediate subvolumes as required
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -551,6 +551,9 @@ class CloudflareAPI(object):
|
||||
try:
|
||||
content = resp.read()
|
||||
except AttributeError:
|
||||
content = None
|
||||
|
||||
if not content:
|
||||
if info['body']:
|
||||
content = info['body']
|
||||
else:
|
||||
@@ -685,6 +688,7 @@ class CloudflareAPI(object):
|
||||
else:
|
||||
search_value = content
|
||||
|
||||
zone_id = self._get_zone_id(params['zone'])
|
||||
records = self.get_dns_records(params['zone'], params['type'], search_record, search_value)
|
||||
|
||||
for rr in records:
|
||||
@@ -692,11 +696,11 @@ class CloudflareAPI(object):
|
||||
if not ((rr['type'] == params['type']) and (rr['name'] == search_record) and (rr['content'] == content)):
|
||||
self.changed = True
|
||||
if not self.module.check_mode:
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records/{1}'.format(rr['zone_id'], rr['id']), 'DELETE')
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records/{1}'.format(zone_id, rr['id']), 'DELETE')
|
||||
else:
|
||||
self.changed = True
|
||||
if not self.module.check_mode:
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records/{1}'.format(rr['zone_id'], rr['id']), 'DELETE')
|
||||
result, info = self._cf_api_call('/zones/{0}/dns_records/{1}'.format(zone_id, rr['id']), 'DELETE')
|
||||
return self.changed
|
||||
|
||||
def ensure_dns_record(self, **kwargs):
|
||||
|
||||
@@ -220,7 +220,7 @@ def normalize_link_obj(api_obj, module_obj, key):
|
||||
|
||||
for obj in module_objs:
|
||||
identifier = obj.get("ID")
|
||||
name = obj.get("Name)")
|
||||
name = obj.get("Name")
|
||||
if identifier and not name and identifier in id_to_name:
|
||||
obj["Name"] = id_to_name[identifier]
|
||||
if not identifier and name and name in name_to_id:
|
||||
|
||||
@@ -77,6 +77,13 @@ EXAMPLES = r"""
|
||||
community.general.copr:
|
||||
state: absent
|
||||
name: '@copr/integration_tests'
|
||||
|
||||
- name: Install Caddy
|
||||
community.general.copr:
|
||||
name: '@caddy/caddy'
|
||||
chroot: fedora-rawhide-{{ ansible_facts.architecture }}
|
||||
includepkgs:
|
||||
- caddy
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
|
||||
@@ -221,6 +221,43 @@ def get_packages(module, patterns, only_installed=False):
|
||||
return packages_available_map_name_evrs
|
||||
|
||||
|
||||
def get_package_mgr():
|
||||
for bin_path in (DNF_BIN,):
|
||||
if os.path.exists(bin_path):
|
||||
return "dnf5" if os.path.realpath(bin_path) == "/usr/bin/dnf5" else "dnf"
|
||||
# fallback to dnf
|
||||
return "dnf"
|
||||
|
||||
|
||||
def get_package_list(module, package_mgr="dnf"):
|
||||
if package_mgr == "dnf":
|
||||
return do_versionlock(module, "list").split()
|
||||
|
||||
package_list = []
|
||||
if package_mgr == "dnf5":
|
||||
stanza_start = False
|
||||
package_name = None
|
||||
for line in do_versionlock(module, "list").splitlines():
|
||||
if line.startswith(("#", " ")):
|
||||
continue
|
||||
if line.startswith("Package name:"):
|
||||
stanza_start = True
|
||||
dummy, name = line.split(":", 1)
|
||||
name = name.strip()
|
||||
pkg_name = get_packages(module, patterns=[name])
|
||||
package_name = "%s-%s.*" % (name, pkg_name[name].pop())
|
||||
if package_name and package_name not in package_list:
|
||||
package_list.append(package_name)
|
||||
if line.startswith("evr"):
|
||||
dummy, package_version = line.split("=", 1)
|
||||
package_version = package_version.strip()
|
||||
if stanza_start:
|
||||
if package_name and package_name not in package_list:
|
||||
package_list.append(package_name)
|
||||
stanza_start = False
|
||||
return package_list
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
@@ -239,9 +276,10 @@ def main():
|
||||
msg = ""
|
||||
|
||||
# Check module pre-requisites.
|
||||
if not os.path.exists(DNF_BIN):
|
||||
module.fail_json(msg="%s was not found" % DNF_BIN)
|
||||
if not os.path.exists(VERSIONLOCK_CONF):
|
||||
global DNF_BIN
|
||||
DNF_BIN = module.get_bin_path('dnf', True)
|
||||
package_mgr = get_package_mgr()
|
||||
if package_mgr == "dnf" and not os.path.exists(VERSIONLOCK_CONF):
|
||||
module.fail_json(msg="plugin versionlock is required")
|
||||
|
||||
# Check incompatible options.
|
||||
@@ -250,7 +288,7 @@ def main():
|
||||
if state != "clean" and not patterns:
|
||||
module.fail_json(msg="name list is required for %s state" % state)
|
||||
|
||||
locklist_pre = do_versionlock(module, "list").split()
|
||||
locklist_pre = get_package_list(module, package_mgr=package_mgr)
|
||||
|
||||
specs_toadd = []
|
||||
specs_todelete = []
|
||||
@@ -329,7 +367,7 @@ def main():
|
||||
"specs_todelete": specs_todelete
|
||||
}
|
||||
if not module.check_mode:
|
||||
response["locklist_post"] = do_versionlock(module, "list").split()
|
||||
response["locklist_post"] = get_package_list(module, package_mgr=package_mgr)
|
||||
else:
|
||||
if state == "clean":
|
||||
response["locklist_post"] = []
|
||||
|
||||
@@ -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", ""
|
||||
|
||||
@@ -259,7 +259,12 @@ class GithubDeployKey(object):
|
||||
key_id = response_body["id"]
|
||||
self.module.exit_json(changed=True, msg="Deploy key successfully added", id=key_id)
|
||||
elif status_code == 422:
|
||||
self.module.exit_json(changed=False, msg="Deploy key already exists")
|
||||
# there might be multiple reasons for a 422
|
||||
# so we must check if the reason is that the key already exists
|
||||
if self.get_existing_key():
|
||||
self.module.exit_json(changed=False, msg="Deploy key already exists")
|
||||
else:
|
||||
self.handle_error(method="POST", info=info)
|
||||
else:
|
||||
self.handle_error(method="POST", info=info)
|
||||
|
||||
|
||||
@@ -44,6 +44,12 @@ options:
|
||||
- This option is only used on creation, not for updates.
|
||||
type: path
|
||||
version_added: "4.2.0"
|
||||
build_timeout:
|
||||
description:
|
||||
- Maximum number of seconds a CI job can run.
|
||||
- If not specified on creation, GitLab will impose a default value.
|
||||
type: int
|
||||
version_added: "10.6.0"
|
||||
builds_access_level:
|
||||
description:
|
||||
- V(private) means that repository CI/CD is allowed only to project members.
|
||||
@@ -430,6 +436,7 @@ class GitLabProject(object):
|
||||
project_options = {
|
||||
'allow_merge_on_skipped_pipeline': options['allow_merge_on_skipped_pipeline'],
|
||||
'builds_access_level': options['builds_access_level'],
|
||||
'build_timeout': options['build_timeout'],
|
||||
'ci_config_path': options['ci_config_path'],
|
||||
'container_expiration_policy': options['container_expiration_policy'],
|
||||
'container_registry_access_level': options['container_registry_access_level'],
|
||||
@@ -591,6 +598,7 @@ def main():
|
||||
allow_merge_on_skipped_pipeline=dict(type='bool'),
|
||||
avatar_path=dict(type='path'),
|
||||
builds_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
build_timeout=dict(type='int'),
|
||||
ci_config_path=dict(type='str'),
|
||||
container_expiration_policy=dict(type='dict', default=None, options=dict(
|
||||
cadence=dict(type='str', choices=["1d", "7d", "14d", "1month", "3month"]),
|
||||
@@ -664,6 +672,7 @@ def main():
|
||||
allow_merge_on_skipped_pipeline = module.params['allow_merge_on_skipped_pipeline']
|
||||
avatar_path = module.params['avatar_path']
|
||||
builds_access_level = module.params['builds_access_level']
|
||||
build_timeout = module.params['build_timeout']
|
||||
ci_config_path = module.params['ci_config_path']
|
||||
container_expiration_policy = module.params['container_expiration_policy']
|
||||
container_registry_access_level = module.params['container_registry_access_level']
|
||||
@@ -748,6 +757,7 @@ def main():
|
||||
"allow_merge_on_skipped_pipeline": allow_merge_on_skipped_pipeline,
|
||||
"avatar_path": avatar_path,
|
||||
"builds_access_level": builds_access_level,
|
||||
"build_timeout": build_timeout,
|
||||
"ci_config_path": ci_config_path,
|
||||
"container_expiration_policy": container_expiration_policy,
|
||||
"container_registry_access_level": container_registry_access_level,
|
||||
|
||||
@@ -48,8 +48,9 @@ options:
|
||||
description:
|
||||
- The access level for the user.
|
||||
- Required if O(state=present), user state is set to present.
|
||||
- V(owner) was added in community.general 10.6.0.
|
||||
type: str
|
||||
choices: ['guest', 'reporter', 'developer', 'maintainer']
|
||||
choices: ['guest', 'reporter', 'developer', 'maintainer', 'owner']
|
||||
gitlab_users_access:
|
||||
description:
|
||||
- Provide a list of user to access level mappings.
|
||||
@@ -67,8 +68,9 @@ options:
|
||||
description:
|
||||
- The access level for the user.
|
||||
- Required if O(state=present), user state is set to present.
|
||||
- V(owner) was added in community.general 10.6.0.
|
||||
type: str
|
||||
choices: ['guest', 'reporter', 'developer', 'maintainer']
|
||||
choices: ['guest', 'reporter', 'developer', 'maintainer', 'owner']
|
||||
required: true
|
||||
version_added: 3.7.0
|
||||
state:
|
||||
@@ -84,9 +86,10 @@ options:
|
||||
- Adds/remove users of the given access_level to match the given O(gitlab_user)/O(gitlab_users_access) list. If omitted
|
||||
do not purge orphaned members.
|
||||
- Is only used when O(state=present).
|
||||
- V(owner) was added in community.general 10.6.0.
|
||||
type: list
|
||||
elements: str
|
||||
choices: ['guest', 'reporter', 'developer', 'maintainer']
|
||||
choices: ['guest', 'reporter', 'developer', 'maintainer', 'owner']
|
||||
version_added: 3.7.0
|
||||
"""
|
||||
|
||||
@@ -239,16 +242,16 @@ def main():
|
||||
project=dict(type='str', required=True),
|
||||
gitlab_user=dict(type='list', elements='str'),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
access_level=dict(type='str', choices=['guest', 'reporter', 'developer', 'maintainer']),
|
||||
access_level=dict(type='str', choices=['guest', 'reporter', 'developer', 'maintainer', 'owner']),
|
||||
purge_users=dict(type='list', elements='str', choices=[
|
||||
'guest', 'reporter', 'developer', 'maintainer']),
|
||||
'guest', 'reporter', 'developer', 'maintainer', 'owner']),
|
||||
gitlab_users_access=dict(
|
||||
type='list',
|
||||
elements='dict',
|
||||
options=dict(
|
||||
name=dict(type='str', required=True),
|
||||
access_level=dict(type='str', choices=[
|
||||
'guest', 'reporter', 'developer', 'maintainer'], required=True),
|
||||
'guest', 'reporter', 'developer', 'maintainer', 'owner'], required=True),
|
||||
)
|
||||
),
|
||||
))
|
||||
@@ -286,6 +289,7 @@ def main():
|
||||
'reporter': gitlab.const.REPORTER_ACCESS,
|
||||
'developer': gitlab.const.DEVELOPER_ACCESS,
|
||||
'maintainer': gitlab.const.MAINTAINER_ACCESS,
|
||||
'owner': gitlab.const.OWNER_ACCESS,
|
||||
}
|
||||
|
||||
gitlab_project = module.params['project']
|
||||
|
||||
@@ -385,12 +385,39 @@ class Homebrew(object):
|
||||
self.outdated_packages.add(package_name)
|
||||
|
||||
def _extract_package_name(self, package_detail, is_cask):
|
||||
canonical_name = package_detail["full_token"] if is_cask else package_detail["full_name"] # For ex: 'sqlite'
|
||||
all_valid_names = set(package_detail.get("aliases", [])) # For ex: {'sqlite3'}
|
||||
all_valid_names.add(canonical_name)
|
||||
# "brew info" can lookup by name, full_name, token, full_token, or aliases
|
||||
# In addition, any name can be prefixed by the tap.
|
||||
# Any of these can be supplied by the user as the package name. In case
|
||||
# of ambiguity, where a given name might match multiple packages,
|
||||
# formulae are preferred over casks. For all other ambiguities, the
|
||||
# results are an error. Note that in the homebrew/core and
|
||||
# homebrew/cask taps, there are no "other" ambiguities.
|
||||
if is_cask: # according to brew info
|
||||
name = package_detail["token"]
|
||||
full_name = package_detail["full_token"]
|
||||
else:
|
||||
name = package_detail["name"]
|
||||
full_name = package_detail["full_name"]
|
||||
|
||||
# Issue https://github.com/ansible-collections/community.general/issues/9803:
|
||||
# name can include the tap as a prefix, in order to disambiguate,
|
||||
# e.g. casks from identically named formulae.
|
||||
#
|
||||
# Issue https://github.com/ansible-collections/community.general/issues/10012:
|
||||
# package_detail["tap"] is None if package is no longer available.
|
||||
tapped_name = [package_detail["tap"] + "/" + name] if package_detail["tap"] else []
|
||||
aliases = package_detail.get("aliases", [])
|
||||
package_names = set([name, full_name] + tapped_name + aliases)
|
||||
|
||||
# Finally, identify which of all those package names was the one supplied by the user.
|
||||
package_names = package_names & set(self.packages)
|
||||
if len(package_names) != 1:
|
||||
self.failed = True
|
||||
self.message = "Package names are missing or ambiguous: " + ", ".join(str(p) for p in package_names)
|
||||
raise HomebrewException(self.message)
|
||||
|
||||
# Then make sure the user provided name resurface.
|
||||
return (all_valid_names & set(self.packages)).pop()
|
||||
return package_names.pop()
|
||||
|
||||
def _get_packages_info(self):
|
||||
cmd = [
|
||||
@@ -831,7 +858,7 @@ def main():
|
||||
p = module.params
|
||||
|
||||
if p['name']:
|
||||
packages = p['name']
|
||||
packages = [package_name.lower() for package_name in p['name']]
|
||||
else:
|
||||
packages = None
|
||||
|
||||
|
||||
@@ -425,10 +425,7 @@ class HomebrewCask(object):
|
||||
cmd = base_opts + [self.current_cask]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return rc == 0
|
||||
|
||||
def _get_brew_version(self):
|
||||
if self.brew_version:
|
||||
@@ -436,11 +433,13 @@ class HomebrewCask(object):
|
||||
|
||||
cmd = [self.brew_path, '--version']
|
||||
|
||||
rc, out, err = self.module.run_command(cmd, check_rc=True)
|
||||
dummy, out, dummy = self.module.run_command(cmd, check_rc=True)
|
||||
|
||||
# get version string from first line of "brew --version" output
|
||||
version = out.split('\n')[0].split(' ')[1]
|
||||
self.brew_version = version
|
||||
pattern = r"Homebrew (.*)(\d+\.\d+\.\d+)(-dirty)?"
|
||||
rematch = re.search(pattern, out)
|
||||
if not rematch:
|
||||
self.module.fail_json(msg="Failed to match regex to get brew version", stdout=out)
|
||||
self.brew_version = rematch.groups()[1]
|
||||
return self.brew_version
|
||||
|
||||
def _brew_cask_command_is_deprecated(self):
|
||||
|
||||
@@ -72,7 +72,7 @@ EXAMPLES = r"""
|
||||
- name: Remove the foo service (equivalent to `brew services stop foo`)
|
||||
community.general.homebrew_services:
|
||||
name: foo
|
||||
service_state: absent
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
|
||||
@@ -76,6 +76,12 @@ options:
|
||||
default: TLSv1
|
||||
type: str
|
||||
choices: ["SSLv3", "SSLv23", "TLSv1", "TLSv1_1", "TLSv1_2"]
|
||||
idempotent_boot_once:
|
||||
description:
|
||||
- "This option makes O(state=boot_once) succeed instead of failing when the server is already powered on."
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 10.6.0
|
||||
requirements:
|
||||
- python-hpilo
|
||||
notes:
|
||||
@@ -138,6 +144,7 @@ def main():
|
||||
image=dict(type='str'),
|
||||
state=dict(type='str', default='boot_once', choices=['boot_always', 'boot_once', 'connect', 'disconnect', 'no_boot', 'poweroff']),
|
||||
force=dict(type='bool', default=False),
|
||||
idempotent_boot_once=dict(type='bool', default=False),
|
||||
ssl_version=dict(type='str', default='TLSv1', choices=['SSLv3', 'SSLv23', 'TLSv1', 'TLSv1_1', 'TLSv1_2']),
|
||||
)
|
||||
)
|
||||
@@ -152,6 +159,7 @@ def main():
|
||||
image = module.params['image']
|
||||
state = module.params['state']
|
||||
force = module.params['force']
|
||||
idempotent_boot_once = module.params['idempotent_boot_once']
|
||||
ssl_version = getattr(hpilo.ssl, 'PROTOCOL_' + module.params.get('ssl_version').upper().replace('V', 'v'))
|
||||
|
||||
ilo = hpilo.Ilo(host, login=login, password=password, ssl_version=ssl_version)
|
||||
@@ -187,13 +195,21 @@ def main():
|
||||
|
||||
power_status = ilo.get_host_power_status()
|
||||
|
||||
if not force and power_status == 'ON':
|
||||
module.fail_json(msg='HP iLO (%s) reports that the server is already powered on !' % host)
|
||||
|
||||
if power_status == 'ON':
|
||||
ilo.warm_boot_server()
|
||||
# ilo.cold_boot_server()
|
||||
changed = True
|
||||
if not force and not idempotent_boot_once:
|
||||
# module.deprecate(
|
||||
# 'The failure of the module when the server is already powered on is being deprecated.'
|
||||
# ' Please set the parameter "idempotent_boot_once=true" to start using the new behavior.',
|
||||
# version='11.0.0',
|
||||
# collection_name='community.general'
|
||||
# )
|
||||
module.fail_json(msg='HP iLO (%s) reports that the server is already powered on !' % host)
|
||||
elif not force and idempotent_boot_once:
|
||||
pass
|
||||
elif force:
|
||||
ilo.warm_boot_server()
|
||||
# ilo.cold_boot_server()
|
||||
changed = True
|
||||
else:
|
||||
ilo.press_pwr_btn()
|
||||
# ilo.reset_server()
|
||||
|
||||
@@ -85,7 +85,7 @@ EXAMPLES = r"""
|
||||
password: '9s36?;fyNp'
|
||||
owner: root
|
||||
group: www-data
|
||||
mode: 0640
|
||||
mode: '0640'
|
||||
|
||||
- name: Remove a user from a password file
|
||||
community.general.htpasswd:
|
||||
|
||||
@@ -16,6 +16,7 @@ description:
|
||||
- For use with Dell iDRAC operations that require Redfish OEM extensions.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.redfish
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
@@ -62,6 +63,12 @@ options:
|
||||
- ID of the System, Manager or Chassis to modify.
|
||||
type: str
|
||||
version_added: '0.2.0'
|
||||
validate_certs:
|
||||
version_added: 10.6.0
|
||||
ca_path:
|
||||
version_added: 10.6.0
|
||||
ciphers:
|
||||
version_added: 10.6.0
|
||||
|
||||
author: "Jose Delarosa (@jose-delarosa)"
|
||||
"""
|
||||
@@ -93,7 +100,7 @@ return_values:
|
||||
|
||||
import re
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils, REDFISH_COMMON_ARGUMENT_SPEC
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
@@ -147,17 +154,19 @@ CATEGORY_COMMANDS_ALL = {
|
||||
def main():
|
||||
result = {}
|
||||
return_values = {}
|
||||
argument_spec = dict(
|
||||
category=dict(required=True),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
timeout=dict(type='int', default=10),
|
||||
resource_id=dict()
|
||||
)
|
||||
argument_spec.update(REDFISH_COMMON_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
category=dict(required=True),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
timeout=dict(type='int', default=10),
|
||||
resource_id=dict()
|
||||
),
|
||||
argument_spec,
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
],
|
||||
|
||||
@@ -16,6 +16,7 @@ description:
|
||||
- Builds Redfish URIs locally and sends them to remote iDRAC controllers to set or update a configuration attribute.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.redfish
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
@@ -71,6 +72,12 @@ options:
|
||||
- ID of the System, Manager or Chassis to modify.
|
||||
type: str
|
||||
version_added: '0.2.0'
|
||||
validate_certs:
|
||||
version_added: 10.6.0
|
||||
ca_path:
|
||||
version_added: 10.6.0
|
||||
ciphers:
|
||||
version_added: 10.6.0
|
||||
|
||||
author: "Jose Delarosa (@jose-delarosa)"
|
||||
"""
|
||||
@@ -154,7 +161,7 @@ from ansible.module_utils.common.validation import (
|
||||
check_mutually_exclusive,
|
||||
check_required_arguments
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils, REDFISH_COMMON_ARGUMENT_SPEC
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
@@ -246,18 +253,20 @@ CATEGORY_COMMANDS_MUTUALLY_EXCLUSIVE = {
|
||||
|
||||
def main():
|
||||
result = {}
|
||||
argument_spec = dict(
|
||||
category=dict(required=True),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
manager_attributes=dict(type='dict', default={}),
|
||||
timeout=dict(type='int', default=10),
|
||||
resource_id=dict()
|
||||
)
|
||||
argument_spec.update(REDFISH_COMMON_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
category=dict(required=True),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
manager_attributes=dict(type='dict', default={}),
|
||||
timeout=dict(type='int', default=10),
|
||||
resource_id=dict()
|
||||
),
|
||||
argument_spec,
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
],
|
||||
|
||||
@@ -17,6 +17,7 @@ description:
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
- community.general.redfish
|
||||
attributes:
|
||||
check_mode:
|
||||
version_added: 3.3.0
|
||||
@@ -57,6 +58,12 @@ options:
|
||||
- Timeout in seconds for HTTP requests to iDRAC.
|
||||
default: 10
|
||||
type: int
|
||||
validate_certs:
|
||||
version_added: 10.6.0
|
||||
ca_path:
|
||||
version_added: 10.6.0
|
||||
ciphers:
|
||||
version_added: 10.6.0
|
||||
|
||||
author: "Jose Delarosa (@jose-delarosa)"
|
||||
"""
|
||||
@@ -124,7 +131,7 @@ msg:
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils, REDFISH_COMMON_ARGUMENT_SPEC
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
@@ -177,16 +184,18 @@ CATEGORY_COMMANDS_ALL = {
|
||||
|
||||
def main():
|
||||
result = {}
|
||||
argument_spec = dict(
|
||||
category=dict(required=True),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
timeout=dict(type='int', default=10)
|
||||
)
|
||||
argument_spec.update(REDFISH_COMMON_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
category=dict(required=True),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
timeout=dict(type='int', default=10)
|
||||
),
|
||||
argument_spec,
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
],
|
||||
|
||||
@@ -19,6 +19,7 @@ attributes:
|
||||
support: none
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.redfish
|
||||
options:
|
||||
category:
|
||||
required: true
|
||||
@@ -58,6 +59,12 @@ options:
|
||||
- Timeout in seconds for HTTP requests to iLO.
|
||||
default: 60
|
||||
type: int
|
||||
validate_certs:
|
||||
version_added: 10.6.0
|
||||
ca_path:
|
||||
version_added: 10.6.0
|
||||
ciphers:
|
||||
version_added: 10.6.0
|
||||
author:
|
||||
- Varni H P (@varini-hp)
|
||||
"""
|
||||
@@ -96,22 +103,25 @@ CATEGORY_COMMANDS_ALL = {
|
||||
}
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.ilo_redfish_utils import iLORedfishUtils
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import REDFISH_COMMON_ARGUMENT_SPEC
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
def main():
|
||||
result = {}
|
||||
argument_spec = dict(
|
||||
category=dict(required=True, choices=list(CATEGORY_COMMANDS_ALL.keys())),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
timeout=dict(type="int", default=60),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True)
|
||||
)
|
||||
argument_spec.update(REDFISH_COMMON_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
category=dict(required=True, choices=list(CATEGORY_COMMANDS_ALL.keys())),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
timeout=dict(type="int", default=60),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True)
|
||||
),
|
||||
argument_spec,
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
],
|
||||
|
||||
@@ -15,6 +15,7 @@ description:
|
||||
- For use with HPE iLO operations that require Redfish OEM extensions.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.redfish
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
@@ -65,6 +66,12 @@ options:
|
||||
description:
|
||||
- Value of the attribute to be configured.
|
||||
type: str
|
||||
validate_certs:
|
||||
version_added: 10.6.0
|
||||
ca_path:
|
||||
version_added: 10.6.0
|
||||
ciphers:
|
||||
version_added: 10.6.0
|
||||
author:
|
||||
- "Bhavya B (@bhavya06)"
|
||||
"""
|
||||
@@ -113,25 +120,28 @@ CATEGORY_COMMANDS_ALL = {
|
||||
}
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.ilo_redfish_utils import iLORedfishUtils
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import REDFISH_COMMON_ARGUMENT_SPEC
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
def main():
|
||||
result = {}
|
||||
argument_spec = dict(
|
||||
category=dict(required=True, choices=list(
|
||||
CATEGORY_COMMANDS_ALL.keys())),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
attribute_name=dict(required=True),
|
||||
attribute_value=dict(type='str'),
|
||||
timeout=dict(type='int', default=10)
|
||||
)
|
||||
argument_spec.update(REDFISH_COMMON_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
category=dict(required=True, choices=list(
|
||||
CATEGORY_COMMANDS_ALL.keys())),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
attribute_name=dict(required=True),
|
||||
attribute_value=dict(type='str'),
|
||||
timeout=dict(type='int', default=10)
|
||||
),
|
||||
argument_spec,
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
],
|
||||
|
||||
@@ -16,6 +16,7 @@ description:
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
- community.general.redfish
|
||||
options:
|
||||
category:
|
||||
required: true
|
||||
@@ -51,6 +52,12 @@ options:
|
||||
- Timeout in seconds for HTTP requests to iLO.
|
||||
default: 10
|
||||
type: int
|
||||
validate_certs:
|
||||
version_added: 10.6.0
|
||||
ca_path:
|
||||
version_added: 10.6.0
|
||||
ciphers:
|
||||
version_added: 10.6.0
|
||||
author:
|
||||
- "Bhavya B (@bhavya06)"
|
||||
"""
|
||||
@@ -108,21 +115,24 @@ CATEGORY_COMMANDS_DEFAULT = {
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.ilo_redfish_utils import iLORedfishUtils
|
||||
from ansible_collections.community.general.plugins.module_utils.redfish_utils import REDFISH_COMMON_ARGUMENT_SPEC
|
||||
|
||||
|
||||
def main():
|
||||
result = {}
|
||||
category_list = []
|
||||
argument_spec = dict(
|
||||
category=dict(required=True, type='list', elements='str'),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
timeout=dict(type='int', default=10)
|
||||
)
|
||||
argument_spec.update(REDFISH_COMMON_ARGUMENT_SPEC)
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
category=dict(required=True, type='list', elements='str'),
|
||||
command=dict(required=True, type='list', elements='str'),
|
||||
baseuri=dict(required=True),
|
||||
username=dict(),
|
||||
password=dict(no_log=True),
|
||||
auth_token=dict(no_log=True),
|
||||
timeout=dict(type='int', default=10)
|
||||
),
|
||||
argument_spec,
|
||||
required_together=[
|
||||
('username', 'password'),
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -124,7 +124,7 @@ EXAMPLES = r"""
|
||||
server: irc.example.net
|
||||
use_tls: true
|
||||
validate_certs: true
|
||||
channel: #t1
|
||||
channel: '#t1'
|
||||
msg: Hello world
|
||||
|
||||
- name: Send a message to an IRC channel
|
||||
@@ -134,7 +134,7 @@ EXAMPLES = r"""
|
||||
server: irc.example.net
|
||||
use_tls: true
|
||||
validate_certs: true
|
||||
channel: #t1
|
||||
channel: '#t1'
|
||||
msg: 'All finished at {{ ansible_date_time.iso8601 }}'
|
||||
color: red
|
||||
nick: ansibleIRC
|
||||
@@ -146,7 +146,7 @@ EXAMPLES = r"""
|
||||
server: irc.example.net
|
||||
use_tls: true
|
||||
validate_certs: true
|
||||
channel: #t1
|
||||
channel: '#t1'
|
||||
nick_to:
|
||||
- nick1
|
||||
- nick2
|
||||
|
||||
@@ -315,12 +315,13 @@ def _export_public_cert_from_pkcs12(module, executable, pkcs_file, alias, passwo
|
||||
"-noprompt",
|
||||
"-keystore",
|
||||
pkcs_file,
|
||||
"-alias",
|
||||
alias,
|
||||
"-storetype",
|
||||
"pkcs12",
|
||||
"-rfc"
|
||||
]
|
||||
# Append optional alias
|
||||
if alias:
|
||||
export_cmd.extend(["-alias", alias])
|
||||
(export_rc, export_stdout, export_err) = module.run_command(export_cmd, data=password, check_rc=False)
|
||||
|
||||
if export_rc != 0:
|
||||
@@ -393,6 +394,10 @@ def import_pkcs12_path(module, executable, pkcs12_path, pkcs12_pass, pkcs12_alia
|
||||
keystore_path, keystore_pass, keystore_alias, keystore_type):
|
||||
''' Import pkcs12 from path into keystore located on
|
||||
keystore_path as alias '''
|
||||
optional_aliases = {
|
||||
"-destalias": keystore_alias,
|
||||
"-srcalias": pkcs12_alias
|
||||
}
|
||||
import_cmd = [
|
||||
executable,
|
||||
"-importkeystore",
|
||||
@@ -401,13 +406,14 @@ def import_pkcs12_path(module, executable, pkcs12_path, pkcs12_pass, pkcs12_alia
|
||||
"pkcs12",
|
||||
"-srckeystore",
|
||||
pkcs12_path,
|
||||
"-srcalias",
|
||||
pkcs12_alias,
|
||||
"-destkeystore",
|
||||
keystore_path,
|
||||
"-destalias",
|
||||
keystore_alias
|
||||
]
|
||||
# Append optional aliases
|
||||
for flag, value in optional_aliases.items():
|
||||
if value:
|
||||
import_cmd.extend([flag, value])
|
||||
|
||||
import_cmd += _get_keystore_type_keytool_parameters(keystore_type)
|
||||
|
||||
secret_data = "%s\n%s" % (keystore_pass, pkcs12_pass)
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -308,6 +308,8 @@ def create_or_update_executions(kc, config, realm='master'):
|
||||
}
|
||||
# add the execution configuration
|
||||
if new_exec["authenticationConfig"] is not None:
|
||||
if "authenticationConfig" in execution and "id" in execution["authenticationConfig"]:
|
||||
kc.delete_authentication_config(execution["authenticationConfig"]["id"], realm=realm)
|
||||
kc.add_authenticationConfig_to_execution(updated_exec["id"], new_exec["authenticationConfig"], realm=realm)
|
||||
for key in new_exec:
|
||||
# remove unwanted key for the next API call
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -758,12 +758,30 @@ def normalise_cr(clientrep, remove_ids=False):
|
||||
if remove_ids:
|
||||
mapper.pop('id', None)
|
||||
|
||||
# Convert bool to string
|
||||
if 'config' in mapper:
|
||||
for key, value in mapper['config'].items():
|
||||
if isinstance(value, bool):
|
||||
mapper['config'][key] = str(value).lower()
|
||||
|
||||
# Set to a default value.
|
||||
mapper['consentRequired'] = mapper.get('consentRequired', False)
|
||||
|
||||
if 'attributes' in clientrep:
|
||||
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
|
||||
|
||||
|
||||
def normalize_kc_resp(clientrep):
|
||||
# kc drops the variable 'authorizationServicesEnabled' if set to false
|
||||
# to minimize diff/changes we set it to false if not set by kc
|
||||
if clientrep and 'authorizationServicesEnabled' not in clientrep:
|
||||
clientrep['authorizationServicesEnabled'] = False
|
||||
|
||||
|
||||
def sanitize_cr(clientrep):
|
||||
""" Removes probably sensitive details from a client representation.
|
||||
|
||||
@@ -955,6 +973,8 @@ def main():
|
||||
else:
|
||||
before_client = kc.get_client_by_id(cid, realm=realm)
|
||||
|
||||
normalize_kc_resp(before_client)
|
||||
|
||||
if before_client is None:
|
||||
before_client = {}
|
||||
|
||||
@@ -1026,7 +1046,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)
|
||||
|
||||
@@ -1034,6 +1054,8 @@ def main():
|
||||
kc.update_client(cid, desired_client, realm=realm)
|
||||
|
||||
after_client = kc.get_client_by_id(cid, realm=realm)
|
||||
normalize_kc_resp(after_client)
|
||||
|
||||
if before_client == after_client:
|
||||
result['changed'] = False
|
||||
if module._diff:
|
||||
|
||||
@@ -528,8 +528,7 @@ EXAMPLES = r"""
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
id: realm
|
||||
realm: realm
|
||||
realm: unique_realm_name
|
||||
state: present
|
||||
|
||||
- name: Delete a Keycloak realm
|
||||
@@ -539,7 +538,7 @@ EXAMPLES = r"""
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
id: test
|
||||
realm: unique_realm_name
|
||||
state: absent
|
||||
"""
|
||||
|
||||
@@ -554,7 +553,7 @@ proposed:
|
||||
description: Representation of proposed realm.
|
||||
returned: always
|
||||
type: dict
|
||||
sample: {id: "test"}
|
||||
sample: {realm: "test"}
|
||||
|
||||
existing:
|
||||
description: Representation of existing realm (sample is truncated).
|
||||
@@ -767,9 +766,6 @@ def main():
|
||||
# Process a creation
|
||||
result['changed'] = True
|
||||
|
||||
if 'id' not in desired_realm:
|
||||
module.fail_json(msg='id needs to be specified when creating a new realm')
|
||||
|
||||
if module._diff:
|
||||
result['diff'] = dict(before='', after=sanitize_cr(desired_realm))
|
||||
|
||||
@@ -778,11 +774,11 @@ def main():
|
||||
|
||||
# create it
|
||||
kc.create_realm(desired_realm)
|
||||
after_realm = kc.get_realm_by_id(desired_realm['id'])
|
||||
after_realm = kc.get_realm_by_id(desired_realm['realm'])
|
||||
|
||||
result['end_state'] = sanitize_cr(after_realm)
|
||||
|
||||
result['msg'] = 'Realm %s has been created.' % desired_realm['id']
|
||||
result['msg'] = 'Realm %s has been created.' % desired_realm['realm']
|
||||
module.exit_json(**result)
|
||||
|
||||
else:
|
||||
@@ -816,7 +812,7 @@ def main():
|
||||
result['diff'] = dict(before=before_realm_sanitized,
|
||||
after=sanitize_cr(after_realm))
|
||||
|
||||
result['msg'] = 'Realm %s has been updated.' % desired_realm['id']
|
||||
result['msg'] = 'Realm %s has been updated.' % desired_realm['realm']
|
||||
module.exit_json(**result)
|
||||
|
||||
else:
|
||||
@@ -835,7 +831,7 @@ def main():
|
||||
result['proposed'] = {}
|
||||
result['end_state'] = {}
|
||||
|
||||
result['msg'] = 'Realm %s has been deleted.' % before_realm['id']
|
||||
result['msg'] = 'Realm %s has been deleted.' % before_realm['realm']
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
@@ -101,6 +101,9 @@ options:
|
||||
groups:
|
||||
description:
|
||||
- List of groups for the user.
|
||||
Groups can be referenced by their name, like V(staff), or their path, like V(/staff/engineering).
|
||||
The path syntax allows you to reference subgroups, which is not possible otherwise.
|
||||
This is possible since community.general 10.6.0.
|
||||
type: list
|
||||
elements: dict
|
||||
default: []
|
||||
|
||||
@@ -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:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user