mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-28 09:26:44 +00:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8505bd8d9 | ||
|
|
aa0df4d81d | ||
|
|
c349861190 | ||
|
|
a853e561f1 | ||
|
|
1d9367ebea | ||
|
|
f1cf0c1949 | ||
|
|
5b26e09319 | ||
|
|
3cc5a5bc8d | ||
|
|
cd0818e488 | ||
|
|
ad197a303a | ||
|
|
cdbf70d781 | ||
|
|
c77661c184 | ||
|
|
d99a7974ad | ||
|
|
0fcd23a3f1 | ||
|
|
49fc40b275 | ||
|
|
c6c65ee554 | ||
|
|
d385caa73f | ||
|
|
2b52822043 | ||
|
|
9ebb5b270a | ||
|
|
7b9ba8d2e2 | ||
|
|
d33467611e | ||
|
|
9e0eeb0b94 | ||
|
|
5fd97399b0 | ||
|
|
eb14bd572d | ||
|
|
beff4063b3 | ||
|
|
b6cd89c677 | ||
|
|
9c535cb17d | ||
|
|
772b5b1ad3 | ||
|
|
e5ab4be82d | ||
|
|
65dd204dab | ||
|
|
5112994fd4 | ||
|
|
0d4d3f6b73 | ||
|
|
4a7b93df64 | ||
|
|
ab20c90929 | ||
|
|
721ea50420 | ||
|
|
23b646d5c2 | ||
|
|
1e78ff58d5 | ||
|
|
280a5b0e61 | ||
|
|
21a840eab7 | ||
|
|
6f98adf602 | ||
|
|
8c45cba53c | ||
|
|
9fe31235d8 | ||
|
|
fc7609628e | ||
|
|
a1d4051a12 | ||
|
|
acdf19c9e6 | ||
|
|
e2513b318e | ||
|
|
cc2794ad05 | ||
|
|
154b5f86fd | ||
|
|
e302058e2d | ||
|
|
1affd48260 | ||
|
|
dc5a89b040 | ||
|
|
71d8109275 | ||
|
|
73362a1e43 | ||
|
|
bc4fda8b14 | ||
|
|
2766898ea8 | ||
|
|
02f123877a | ||
|
|
d4c29e19c0 | ||
|
|
200ab045fa | ||
|
|
9b4decd831 | ||
|
|
566ec0a002 | ||
|
|
2e72051b6c | ||
|
|
89d33bbd7b | ||
|
|
76b6c8e184 | ||
|
|
fdc279def9 | ||
|
|
a471fa88b8 | ||
|
|
bf4e5dc3c0 | ||
|
|
185bdaaa39 | ||
|
|
55d44975dd | ||
|
|
43772cfbbb | ||
|
|
e93b6231ec | ||
|
|
605a557a8d | ||
|
|
d97f1a31ba | ||
|
|
5949b29a12 | ||
|
|
993d580adc | ||
|
|
1ac7783c5c | ||
|
|
f0724c0975 | ||
|
|
f3aca8a575 | ||
|
|
ad1cf82a34 | ||
|
|
42d0a55984 | ||
|
|
4f4d962f7c | ||
|
|
5343880fa5 | ||
|
|
5ea44edc64 | ||
|
|
8152cb3e1f | ||
|
|
eae0c4f92b | ||
|
|
57277e0661 | ||
|
|
53a941cee7 | ||
|
|
e6edf9cdea | ||
|
|
5550ba1946 | ||
|
|
374378beeb | ||
|
|
5222df306b | ||
|
|
788c722b3e | ||
|
|
2ddbda2aa7 | ||
|
|
cb939cbb75 | ||
|
|
7db1613730 | ||
|
|
38a16b421d | ||
|
|
bd8b7e3737 | ||
|
|
057f7196c7 | ||
|
|
8d8fc3d3ba | ||
|
|
5f7a3ac896 | ||
|
|
23ac1b62c3 | ||
|
|
186d410f63 | ||
|
|
1978100d25 | ||
|
|
6fec5a7005 | ||
|
|
aca2afc6f8 | ||
|
|
adda8d3113 | ||
|
|
21ed66a097 | ||
|
|
73a1ac2f1c | ||
|
|
7c3b441246 | ||
|
|
19613ce111 | ||
|
|
594ca4f983 | ||
|
|
af08ea33b1 | ||
|
|
7f729d99a2 | ||
|
|
5b62e0edd6 | ||
|
|
b935dcedd8 | ||
|
|
c58181bdc9 | ||
|
|
f71af21287 | ||
|
|
1a48ebd699 | ||
|
|
5e1be68b01 | ||
|
|
812431e7fe | ||
|
|
04f36f0bac | ||
|
|
2f3d3aaf76 | ||
|
|
e7024c3f97 | ||
|
|
0c2642aa2e | ||
|
|
2f62b6bbc6 | ||
|
|
5c10ed5f5c | ||
|
|
a4bac47520 | ||
|
|
b396ceb6c3 |
@@ -29,14 +29,14 @@ schedules:
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-10
|
||||
- stable-9
|
||||
- stable-8
|
||||
- cron: 0 11 * * 0
|
||||
displayName: Weekly (old stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-7
|
||||
- stable-8
|
||||
|
||||
variables:
|
||||
- name: checkoutPath
|
||||
@@ -73,6 +73,19 @@ stages:
|
||||
- test: 3
|
||||
- test: 4
|
||||
- test: extra
|
||||
- stage: Sanity_2_18
|
||||
displayName: Sanity 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.18/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- stage: Sanity_2_17
|
||||
displayName: Sanity 2.17
|
||||
dependsOn: []
|
||||
@@ -99,19 +112,6 @@ stages:
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- stage: Sanity_2_15
|
||||
displayName: Sanity 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.15/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
### Units
|
||||
- stage: Units_devel
|
||||
displayName: Units devel
|
||||
@@ -128,6 +128,17 @@ stages:
|
||||
- test: '3.11'
|
||||
- test: '3.12'
|
||||
- test: '3.13'
|
||||
- stage: Units_2_18
|
||||
displayName: Units 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.18/units/{0}/1
|
||||
targets:
|
||||
- test: 3.8
|
||||
- test: "3.13"
|
||||
- stage: Units_2_17
|
||||
displayName: Units 2.17
|
||||
dependsOn: []
|
||||
@@ -151,17 +162,6 @@ stages:
|
||||
- test: 2.7
|
||||
- test: 3.6
|
||||
- test: "3.11"
|
||||
- stage: Units_2_15
|
||||
displayName: Units 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.15/units/{0}/1
|
||||
targets:
|
||||
- test: 3.5
|
||||
- test: "3.10"
|
||||
|
||||
## Remote
|
||||
- stage: Remote_devel_extra_vms
|
||||
@@ -196,6 +196,22 @@ stages:
|
||||
test: rhel/9.4
|
||||
- name: FreeBSD 14.1
|
||||
test: freebsd/14.1
|
||||
- name: FreeBSD 13.4
|
||||
test: freebsd/13.4
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Remote_2_18
|
||||
displayName: Remote 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/{0}
|
||||
targets:
|
||||
- name: RHEL 9.4
|
||||
test: rhel/9.4
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -232,30 +248,10 @@ stages:
|
||||
test: rhel/9.2
|
||||
- name: RHEL 8.8
|
||||
test: rhel/8.8
|
||||
# - name: FreeBSD 13.2
|
||||
# test: freebsd/13.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Remote_2_15
|
||||
displayName: Remote 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.15/{0}
|
||||
targets:
|
||||
- name: RHEL 9.1
|
||||
test: rhel/9.1
|
||||
- name: RHEL 8.7
|
||||
test: rhel/8.7
|
||||
- name: RHEL 7.9
|
||||
test: rhel/7.9
|
||||
# - name: FreeBSD 13.1
|
||||
# test: freebsd/13.1
|
||||
# - name: FreeBSD 12.4
|
||||
# test: freebsd/12.4
|
||||
# - name: FreeBSD 13.2
|
||||
# test: freebsd/13.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -282,6 +278,20 @@ stages:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_18
|
||||
displayName: Docker 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/linux/{0}
|
||||
targets:
|
||||
- name: Ubuntu 24.04
|
||||
test: ubuntu2404
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_17
|
||||
displayName: Docker 2.17
|
||||
dependsOn: []
|
||||
@@ -314,20 +324,6 @@ stages:
|
||||
test: opensuse15
|
||||
- name: Alpine 3
|
||||
test: alpine3
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_15
|
||||
displayName: Docker 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.15/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 37
|
||||
test: fedora37
|
||||
- name: CentOS 7
|
||||
test: centos7
|
||||
groups:
|
||||
@@ -356,77 +352,79 @@ stages:
|
||||
- 3
|
||||
|
||||
### Generic
|
||||
- stage: Generic_devel
|
||||
displayName: Generic devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: devel/generic/{0}/1
|
||||
targets:
|
||||
- test: '3.8'
|
||||
- test: '3.11'
|
||||
- test: '3.13'
|
||||
- stage: Generic_2_17
|
||||
displayName: Generic 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.17/generic/{0}/1
|
||||
targets:
|
||||
- test: '3.7'
|
||||
- test: '3.12'
|
||||
- stage: Generic_2_16
|
||||
displayName: Generic 2.16
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.16/generic/{0}/1
|
||||
targets:
|
||||
- test: '2.7'
|
||||
- test: '3.6'
|
||||
- test: '3.11'
|
||||
- stage: Generic_2_15
|
||||
displayName: Generic 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.15/generic/{0}/1
|
||||
targets:
|
||||
- test: '3.9'
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - stage: Generic_devel
|
||||
# displayName: Generic devel
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: devel/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '3.8'
|
||||
# - test: '3.11'
|
||||
# - test: '3.13'
|
||||
# - stage: Generic_2_18
|
||||
# displayName: Generic 2.18
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: 2.18/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '3.8'
|
||||
# - test: '3.13'
|
||||
# - stage: Generic_2_17
|
||||
# displayName: Generic 2.17
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: 2.17/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '3.7'
|
||||
# - test: '3.12'
|
||||
# - stage: Generic_2_16
|
||||
# displayName: Generic 2.16
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: 2.16/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '2.7'
|
||||
# - test: '3.6'
|
||||
# - test: '3.11'
|
||||
|
||||
- stage: Summary
|
||||
condition: succeededOrFailed()
|
||||
dependsOn:
|
||||
- Sanity_devel
|
||||
- Sanity_2_18
|
||||
- Sanity_2_17
|
||||
- Sanity_2_16
|
||||
- Sanity_2_15
|
||||
- Units_devel
|
||||
- Units_2_18
|
||||
- Units_2_17
|
||||
- Units_2_16
|
||||
- Units_2_15
|
||||
- Remote_devel_extra_vms
|
||||
- Remote_devel
|
||||
- Remote_2_18
|
||||
- Remote_2_17
|
||||
- Remote_2_16
|
||||
- Remote_2_15
|
||||
- Docker_devel
|
||||
- Docker_2_18
|
||||
- Docker_2_17
|
||||
- Docker_2_16
|
||||
- Docker_2_15
|
||||
- Docker_community_devel
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - Generic_devel
|
||||
# - Generic_2_18
|
||||
# - Generic_2_17
|
||||
# - Generic_2_16
|
||||
# - Generic_2_15
|
||||
jobs:
|
||||
- template: templates/coverage.yml
|
||||
|
||||
10
.github/BOTMETA.yml
vendored
10
.github/BOTMETA.yml
vendored
@@ -131,6 +131,8 @@ files:
|
||||
maintainers: $team_huawei
|
||||
$doc_fragments/nomad.py:
|
||||
maintainers: chris93111 apecnascimento
|
||||
$doc_fragments/pipx.py:
|
||||
maintainers: russoz
|
||||
$doc_fragments/xenserver.py:
|
||||
labels: xenserver
|
||||
maintainers: bvitnik
|
||||
@@ -712,6 +714,8 @@ files:
|
||||
$modules/ipa_:
|
||||
maintainers: $team_ipa
|
||||
ignore: fxfitz
|
||||
$modules/ipa_getkeytab.py:
|
||||
maintainers: abakanovskii
|
||||
$modules/ipa_dnsrecord.py:
|
||||
maintainers: $team_ipa jwbernin
|
||||
$modules/ipbase_info.py:
|
||||
@@ -809,6 +813,8 @@ files:
|
||||
maintainers: elfelip
|
||||
$modules/keycloak_user_federation.py:
|
||||
maintainers: laurpaum
|
||||
$modules/keycloak_userprofile.py:
|
||||
maintainers: yeoldegrove
|
||||
$modules/keycloak_component_info.py:
|
||||
maintainers: desand01
|
||||
$modules/keycloak_client_rolescope.py:
|
||||
@@ -974,6 +980,8 @@ files:
|
||||
maintainers: $team_opennebula
|
||||
$modules/one_host.py:
|
||||
maintainers: rvalle
|
||||
$modules/one_vnet.py:
|
||||
maintainers: abakanovskii
|
||||
$modules/oneandone_:
|
||||
maintainers: aajdinov edevenport
|
||||
$modules/onepassword_info.py:
|
||||
@@ -1485,6 +1493,8 @@ files:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/guide_deps.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/guide_modulehelper.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/guide_online.rst:
|
||||
maintainers: remyleone
|
||||
docs/docsite/rst/guide_packet.rst:
|
||||
|
||||
23
.github/workflows/ansible-test.yml
vendored
23
.github/workflows/ansible-test.yml
vendored
@@ -31,6 +31,7 @@ jobs:
|
||||
ansible:
|
||||
- '2.13'
|
||||
- '2.14'
|
||||
- '2.15'
|
||||
# Ansible-test on various stable branches does not yet work well with cgroups v2.
|
||||
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
|
||||
# image for these stable branches. The list of branches where this is necessary will
|
||||
@@ -76,6 +77,10 @@ jobs:
|
||||
python: '3.8'
|
||||
- ansible: '2.14'
|
||||
python: '3.9'
|
||||
- ansible: '2.15'
|
||||
python: '3.5'
|
||||
- ansible: '2.15'
|
||||
python: '3.10'
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
@@ -166,16 +171,32 @@ jobs:
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
# 2.15
|
||||
- ansible: '2.15'
|
||||
docker: fedora37
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.15'
|
||||
docker: fedora37
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.15'
|
||||
docker: fedora37
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - ansible: '2.13'
|
||||
# docker: default
|
||||
# python: '3.9'
|
||||
# target: azp/generic/1/
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - ansible: '2.14'
|
||||
# docker: default
|
||||
# python: '3.10'
|
||||
# target: azp/generic/1/
|
||||
# - ansible: '2.15'
|
||||
# docker: default
|
||||
# python: '3.9'
|
||||
# target: azp/generic/1/
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
|
||||
12
.github/workflows/reuse.yml
vendored
12
.github/workflows/reuse.yml
vendored
@@ -7,10 +7,14 @@ name: Verify REUSE
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [main]
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
# Run CI once per day (at 07:30 UTC)
|
||||
schedule:
|
||||
- cron: '30 7 * * *'
|
||||
@@ -27,4 +31,4 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@v4
|
||||
uses: fsfe/reuse-action@v5
|
||||
|
||||
362
CHANGELOG.md
362
CHANGELOG.md
@@ -2,39 +2,59 @@
|
||||
|
||||
**Topics**
|
||||
|
||||
- <a href="#v9-3-0">v9\.3\.0</a>
|
||||
- <a href="#v9-5-2">v9\.5\.2</a>
|
||||
- <a href="#release-summary">Release Summary</a>
|
||||
- <a href="#minor-changes">Minor Changes</a>
|
||||
- <a href="#bugfixes">Bugfixes</a>
|
||||
- <a href="#new-modules">New Modules</a>
|
||||
- <a href="#v9-2-0">v9\.2\.0</a>
|
||||
- <a href="#v9-5-1">v9\.5\.1</a>
|
||||
- <a href="#release-summary-1">Release Summary</a>
|
||||
- <a href="#minor-changes-1">Minor Changes</a>
|
||||
- <a href="#bugfixes-1">Bugfixes</a>
|
||||
- <a href="#new-plugins">New Plugins</a>
|
||||
- <a href="#filter">Filter</a>
|
||||
- <a href="#test">Test</a>
|
||||
- <a href="#v9-1-0">v9\.1\.0</a>
|
||||
- <a href="#v9-5-0">v9\.5\.0</a>
|
||||
- <a href="#release-summary-2">Release Summary</a>
|
||||
- <a href="#minor-changes-2">Minor Changes</a>
|
||||
- <a href="#deprecated-features">Deprecated Features</a>
|
||||
- <a href="#bugfixes-2">Bugfixes</a>
|
||||
- <a href="#new-modules">New Modules</a>
|
||||
- <a href="#v9-4-0">v9\.4\.0</a>
|
||||
- <a href="#release-summary-3">Release Summary</a>
|
||||
- <a href="#minor-changes-3">Minor Changes</a>
|
||||
- <a href="#deprecated-features-1">Deprecated Features</a>
|
||||
- <a href="#bugfixes-3">Bugfixes</a>
|
||||
- <a href="#new-modules-1">New Modules</a>
|
||||
- <a href="#v9-3-0">v9\.3\.0</a>
|
||||
- <a href="#release-summary-4">Release Summary</a>
|
||||
- <a href="#minor-changes-4">Minor Changes</a>
|
||||
- <a href="#bugfixes-4">Bugfixes</a>
|
||||
- <a href="#new-modules-2">New Modules</a>
|
||||
- <a href="#v9-2-0">v9\.2\.0</a>
|
||||
- <a href="#release-summary-5">Release Summary</a>
|
||||
- <a href="#minor-changes-5">Minor Changes</a>
|
||||
- <a href="#bugfixes-5">Bugfixes</a>
|
||||
- <a href="#new-plugins">New Plugins</a>
|
||||
- <a href="#filter">Filter</a>
|
||||
- <a href="#test">Test</a>
|
||||
- <a href="#v9-1-0">v9\.1\.0</a>
|
||||
- <a href="#release-summary-6">Release Summary</a>
|
||||
- <a href="#minor-changes-6">Minor Changes</a>
|
||||
- <a href="#deprecated-features-2">Deprecated Features</a>
|
||||
- <a href="#bugfixes-6">Bugfixes</a>
|
||||
- <a href="#known-issues">Known Issues</a>
|
||||
- <a href="#new-plugins-1">New Plugins</a>
|
||||
- <a href="#filter-1">Filter</a>
|
||||
- <a href="#new-modules-1">New Modules</a>
|
||||
- <a href="#new-modules-3">New Modules</a>
|
||||
- <a href="#v9-0-1">v9\.0\.1</a>
|
||||
- <a href="#release-summary-3">Release Summary</a>
|
||||
- <a href="#minor-changes-3">Minor Changes</a>
|
||||
- <a href="#bugfixes-3">Bugfixes</a>
|
||||
- <a href="#release-summary-7">Release Summary</a>
|
||||
- <a href="#minor-changes-7">Minor Changes</a>
|
||||
- <a href="#bugfixes-7">Bugfixes</a>
|
||||
- <a href="#v9-0-0">v9\.0\.0</a>
|
||||
- <a href="#release-summary-4">Release Summary</a>
|
||||
- <a href="#minor-changes-4">Minor Changes</a>
|
||||
- <a href="#release-summary-8">Release Summary</a>
|
||||
- <a href="#minor-changes-8">Minor Changes</a>
|
||||
- <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
|
||||
- <a href="#deprecated-features-1">Deprecated Features</a>
|
||||
- <a href="#deprecated-features-3">Deprecated Features</a>
|
||||
- <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a>
|
||||
- <a href="#security-fixes">Security Fixes</a>
|
||||
- <a href="#bugfixes-4">Bugfixes</a>
|
||||
- <a href="#bugfixes-8">Bugfixes</a>
|
||||
- <a href="#new-plugins-2">New Plugins</a>
|
||||
- <a href="#become">Become</a>
|
||||
- <a href="#callback">Callback</a>
|
||||
@@ -42,20 +62,288 @@
|
||||
- <a href="#filter-2">Filter</a>
|
||||
- <a href="#lookup">Lookup</a>
|
||||
- <a href="#test-1">Test</a>
|
||||
- <a href="#new-modules-2">New Modules</a>
|
||||
- <a href="#new-modules-4">New Modules</a>
|
||||
This changelog describes changes after version 8\.0\.0\.
|
||||
|
||||
<a id="v9-3-0"></a>
|
||||
## v9\.3\.0
|
||||
<a id="v9-5-2"></a>
|
||||
## v9\.5\.2
|
||||
|
||||
<a id="release-summary"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
Regular bugfix release\.
|
||||
|
||||
<a id="minor-changes"></a>
|
||||
### Minor Changes
|
||||
|
||||
* proxmox inventory plugin \- fix urllib3 <code>InsecureRequestWarnings</code> not being suppressed when a token is used \([https\://github\.com/ansible\-collections/community\.general/pull/9099](https\://github\.com/ansible\-collections/community\.general/pull/9099)\)\.
|
||||
|
||||
<a id="bugfixes"></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)\)\.
|
||||
* dnf\_config\_manager \- forces locale to <code>C</code> before module starts\. If the locale was set to non\-English\, the output of the <code>dnf config\-manager</code> could not be parsed \([https\://github\.com/ansible\-collections/community\.general/pull/9157](https\://github\.com/ansible\-collections/community\.general/pull/9157)\, [https\://github\.com/ansible\-collections/community\.general/issues/9046](https\://github\.com/ansible\-collections/community\.general/issues/9046)\)\.
|
||||
* flatpak \- force the locale language to <code>C</code> when running the flatpak command \([https\://github\.com/ansible\-collections/community\.general/pull/9187](https\://github\.com/ansible\-collections/community\.general/pull/9187)\, [https\://github\.com/ansible\-collections/community\.general/issues/8883](https\://github\.com/ansible\-collections/community\.general/issues/8883)\)\.
|
||||
* github\_key \- in check mode\, a faulty call to <code>\`datetime\.strftime\(\.\.\.\)\`</code> was being made which generated an exception \([https\://github\.com/ansible\-collections/community\.general/issues/9185](https\://github\.com/ansible\-collections/community\.general/issues/9185)\)\.
|
||||
* homebrew\_cask \- allow <code>\+</code> symbol in Homebrew cask name validation regex \([https\://github\.com/ansible\-collections/community\.general/pull/9128](https\://github\.com/ansible\-collections/community\.general/pull/9128)\)\.
|
||||
* 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)\)\.
|
||||
* keycloak\_clientscope \- fix diff and <code>end\_state</code> by removing the code that turns the attributes dict\, which contains additional config items\, into a list \([https\://github\.com/ansible\-collections/community\.general/pull/9082](https\://github\.com/ansible\-collections/community\.general/pull/9082)\)\.
|
||||
* 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)\)\.
|
||||
* redfish\_utils module utils \- remove undocumented default applytime \([https\://github\.com/ansible\-collections/community\.general/pull/9114](https\://github\.com/ansible\-collections/community\.general/pull/9114)\)\.
|
||||
* 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="v9-5-1"></a>
|
||||
## v9\.5\.1
|
||||
|
||||
<a id="release-summary-1"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix release\.
|
||||
|
||||
<a id="minor-changes-1"></a>
|
||||
### Minor Changes
|
||||
|
||||
* redfish\_utils module utils \- schedule a BIOS configuration job at next reboot when the BIOS config is changed \([https\://github\.com/ansible\-collections/community\.general/pull/9012](https\://github\.com/ansible\-collections/community\.general/pull/9012)\)\.
|
||||
|
||||
<a id="bugfixes-1"></a>
|
||||
### Bugfixes
|
||||
|
||||
* bitwarden lookup plugin \- support BWS v0\.3\.0 syntax breaking change \([https\://github\.com/ansible\-collections/community\.general/pull/9028](https\://github\.com/ansible\-collections/community\.general/pull/9028)\)\.
|
||||
* collection\_version lookup plugin \- use <code>importlib</code> directly instead of the deprecated and in ansible\-core 2\.19 removed <code>ansible\.module\_utils\.compat\.importlib</code> \([https\://github\.com/ansible\-collections/community\.general/pull/9084](https\://github\.com/ansible\-collections/community\.general/pull/9084)\)\.
|
||||
* gitlab\_label \- update label\'s color \([https\://github\.com/ansible\-collections/community\.general/pull/9010](https\://github\.com/ansible\-collections/community\.general/pull/9010)\)\.
|
||||
* keycloak\_clientscope\_type \- fix detect changes in check mode \([https\://github\.com/ansible\-collections/community\.general/issues/9092](https\://github\.com/ansible\-collections/community\.general/issues/9092)\, [https\://github\.com/ansible\-collections/community\.general/pull/9093](https\://github\.com/ansible\-collections/community\.general/pull/9093)\)\.
|
||||
* keycloak\_group \- fix crash caused in subgroup creation\. The crash was caused by a missing or empty <code>subGroups</code> property in Keycloak ≥23 \([https\://github\.com/ansible\-collections/community\.general/issues/8788](https\://github\.com/ansible\-collections/community\.general/issues/8788)\, [https\://github\.com/ansible\-collections/community\.general/pull/8979](https\://github\.com/ansible\-collections/community\.general/pull/8979)\)\.
|
||||
* modprobe \- fix check mode not being honored for <code>persistent</code> option \([https\://github\.com/ansible\-collections/community\.general/issues/9051](https\://github\.com/ansible\-collections/community\.general/issues/9051)\, [https\://github\.com/ansible\-collections/community\.general/pull/9052](https\://github\.com/ansible\-collections/community\.general/pull/9052)\)\.
|
||||
* one\_host \- fix if statements for cases when <code>ID\=0</code> \([https\://github\.com/ansible\-collections/community\.general/issues/1199](https\://github\.com/ansible\-collections/community\.general/issues/1199)\, [https\://github\.com/ansible\-collections/community\.general/pull/8907](https\://github\.com/ansible\-collections/community\.general/pull/8907)\)\.
|
||||
* one\_image \- fix module failing due to a class method typo \([https\://github\.com/ansible\-collections/community\.general/pull/9056](https\://github\.com/ansible\-collections/community\.general/pull/9056)\)\.
|
||||
* one\_image\_info \- fix module failing due to a class method typo \([https\://github\.com/ansible\-collections/community\.general/pull/9056](https\://github\.com/ansible\-collections/community\.general/pull/9056)\)\.
|
||||
* one\_vnet \- fix module failing due to a variable typo \([https\://github\.com/ansible\-collections/community\.general/pull/9019](https\://github\.com/ansible\-collections/community\.general/pull/9019)\)\.
|
||||
* redfish\_utils module utils \- fix issue with URI parsing to gracefully handling trailing slashes when extracting member identifiers \([https\://github\.com/ansible\-collections/community\.general/issues/9047](https\://github\.com/ansible\-collections/community\.general/issues/9047)\, [https\://github\.com/ansible\-collections/community\.general/pull/9057](https\://github\.com/ansible\-collections/community\.general/pull/9057)\)\.
|
||||
|
||||
<a id="v9-5-0"></a>
|
||||
## v9\.5\.0
|
||||
|
||||
<a id="release-summary-2"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
Please note that this is the last feature release for community\.general 9\.x\.y\.
|
||||
From now on\, new features will only go into community\.general 10\.x\.y\.
|
||||
|
||||
<a id="minor-changes-2"></a>
|
||||
### Minor Changes
|
||||
|
||||
* dig lookup plugin \- add <code>port</code> option to specify DNS server port \([https\://github\.com/ansible\-collections/community\.general/pull/8966](https\://github\.com/ansible\-collections/community\.general/pull/8966)\)\.
|
||||
* flatpak \- improve the parsing of Flatpak application IDs based on official guidelines \([https\://github\.com/ansible\-collections/community\.general/pull/8909](https\://github\.com/ansible\-collections/community\.general/pull/8909)\)\.
|
||||
* gio\_mime \- adjust code ahead of the old <code>VardDict</code> deprecation \([https\://github\.com/ansible\-collections/community\.general/pull/8855](https\://github\.com/ansible\-collections/community\.general/pull/8855)\)\.
|
||||
* gitlab\_deploy\_key \- better construct when using <code>dict\.items\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* gitlab\_group \- add many new parameters \([https\://github\.com/ansible\-collections/community\.general/pull/8908](https\://github\.com/ansible\-collections/community\.general/pull/8908)\)\.
|
||||
* gitlab\_group \- better construct when using <code>dict\.items\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* gitlab\_issue \- better construct when using <code>dict\.items\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* gitlab\_merge\_request \- better construct when using <code>dict\.items\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* gitlab\_runner \- better construct when using <code>dict\.items\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* icinga2\_host \- replace loop with dict comprehension \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* jira \- adjust code ahead of the old <code>VardDict</code> deprecation \([https\://github\.com/ansible\-collections/community\.general/pull/8856](https\://github\.com/ansible\-collections/community\.general/pull/8856)\)\.
|
||||
* keycloak\_client \- add <code>client\-x509</code> choice to <code>client\_authenticator\_type</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8973](https\://github\.com/ansible\-collections/community\.general/pull/8973)\)\.
|
||||
* keycloak\_user\_federation \- add the user federation config parameter <code>referral</code> to the module arguments \([https\://github\.com/ansible\-collections/community\.general/pull/8954](https\://github\.com/ansible\-collections/community\.general/pull/8954)\)\.
|
||||
* memset\_dns\_reload \- replace loop with <code>dict\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* memset\_memstore\_info \- replace loop with <code>dict\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* memset\_server\_info \- replace loop with <code>dict\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* memset\_zone \- replace loop with <code>dict\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* memset\_zone\_domain \- replace loop with <code>dict\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* memset\_zone\_record \- replace loop with <code>dict\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* nmcli \- add <code>conn\_enable</code> param to reload connection \([https\://github\.com/ansible\-collections/community\.general/issues/3752](https\://github\.com/ansible\-collections/community\.general/issues/3752)\, [https\://github\.com/ansible\-collections/community\.general/issues/8704](https\://github\.com/ansible\-collections/community\.general/issues/8704)\, [https\://github\.com/ansible\-collections/community\.general/pull/8897](https\://github\.com/ansible\-collections/community\.general/pull/8897)\)\.
|
||||
* nmcli \- add <code>state\=up</code> and <code>state\=down</code> to enable/disable connections \([https\://github\.com/ansible\-collections/community\.general/issues/3752](https\://github\.com/ansible\-collections/community\.general/issues/3752)\, [https\://github\.com/ansible\-collections/community\.general/issues/8704](https\://github\.com/ansible\-collections/community\.general/issues/8704)\, [https\://github\.com/ansible\-collections/community\.general/issues/7152](https\://github\.com/ansible\-collections/community\.general/issues/7152)\, [https\://github\.com/ansible\-collections/community\.general/pull/8897](https\://github\.com/ansible\-collections/community\.general/pull/8897)\)\.
|
||||
* nmcli \- better construct when using <code>dict\.items\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* npm \- add <code>force</code> parameter to allow <code>\-\-force</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8885](https\://github\.com/ansible\-collections/community\.general/pull/8885)\)\.
|
||||
* one\_image \- add option <code>persistent</code> to manage image persistence \([https\://github\.com/ansible\-collections/community\.general/issues/3578](https\://github\.com/ansible\-collections/community\.general/issues/3578)\, [https\://github\.com/ansible\-collections/community\.general/pull/8889](https\://github\.com/ansible\-collections/community\.general/pull/8889)\)\.
|
||||
* one\_image \- extend xsd scheme to make it return a lot more info about image \([https\://github\.com/ansible\-collections/community\.general/pull/8889](https\://github\.com/ansible\-collections/community\.general/pull/8889)\)\.
|
||||
* one\_image \- refactor code to make it more similar to <code>one\_template</code> and <code>one\_vnet</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8889](https\://github\.com/ansible\-collections/community\.general/pull/8889)\)\.
|
||||
* one\_image\_info \- extend xsd scheme to make it return a lot more info about image \([https\://github\.com/ansible\-collections/community\.general/pull/8889](https\://github\.com/ansible\-collections/community\.general/pull/8889)\)\.
|
||||
* one\_image\_info \- refactor code to make it more similar to <code>one\_template</code> and <code>one\_vnet</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8889](https\://github\.com/ansible\-collections/community\.general/pull/8889)\)\.
|
||||
* open\_iscsi \- allow login to a portal with multiple targets without specifying any of them \([https\://github\.com/ansible\-collections/community\.general/pull/8719](https\://github\.com/ansible\-collections/community\.general/pull/8719)\)\.
|
||||
* opennebula\.py \- add VM <code>id</code> and VM <code>host</code> to inventory host data \([https\://github\.com/ansible\-collections/community\.general/pull/8532](https\://github\.com/ansible\-collections/community\.general/pull/8532)\)\.
|
||||
* passwordstore lookup plugin \- add subkey creation/update support \([https\://github\.com/ansible\-collections/community\.general/pull/8952](https\://github\.com/ansible\-collections/community\.general/pull/8952)\)\.
|
||||
* proxmox inventory plugin \- clean up authentication code \([https\://github\.com/ansible\-collections/community\.general/pull/8917](https\://github\.com/ansible\-collections/community\.general/pull/8917)\)\.
|
||||
* redfish\_command \- add handling of the <code>PasswordChangeRequired</code> message from services in the <code>UpdateUserPassword</code> command to directly modify the user\'s password if the requested user is the one invoking the operation \([https\://github\.com/ansible\-collections/community\.general/issues/8652](https\://github\.com/ansible\-collections/community\.general/issues/8652)\, [https\://github\.com/ansible\-collections/community\.general/pull/8653](https\://github\.com/ansible\-collections/community\.general/pull/8653)\)\.
|
||||
* redfish\_confg \- remove <code>CapacityBytes</code> from required paramaters of the <code>CreateVolume</code> command \([https\://github\.com/ansible\-collections/community\.general/pull/8956](https\://github\.com/ansible\-collections/community\.general/pull/8956)\)\.
|
||||
* redfish\_config \- add parameter <code>storage\_none\_volume\_deletion</code> to <code>CreateVolume</code> command in order to control the automatic deletion of non\-RAID volumes \([https\://github\.com/ansible\-collections/community\.general/pull/8990](https\://github\.com/ansible\-collections/community\.general/pull/8990)\)\.
|
||||
* redfish\_info \- adds <code>RedfishURI</code> and <code>StorageId</code> to Disk inventory \([https\://github\.com/ansible\-collections/community\.general/pull/8937](https\://github\.com/ansible\-collections/community\.general/pull/8937)\)\.
|
||||
* scaleway\_container \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_container\_info \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_container\_namespace \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_container\_namespace\_info \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_container\_registry \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_container\_registry\_info \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_function \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_function\_info \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_function\_namespace \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_function\_namespace\_info \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8858](https\://github\.com/ansible\-collections/community\.general/pull/8858)\)\.
|
||||
* scaleway\_user\_data \- better construct when using <code>dict\.items\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
* udm\_dns\_record \- replace loop with <code>dict\.update\(\)</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8876](https\://github\.com/ansible\-collections/community\.general/pull/8876)\)\.
|
||||
|
||||
<a id="deprecated-features"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* hipchat \- the hipchat service has been discontinued and the self\-hosted variant has been End of Life since 2020\. The module is therefore deprecated and will be removed from community\.general 11\.0\.0 if nobody provides compelling reasons to still keep it \([https\://github\.com/ansible\-collections/community\.general/pull/8919](https\://github\.com/ansible\-collections/community\.general/pull/8919)\)\.
|
||||
|
||||
<a id="bugfixes-2"></a>
|
||||
### Bugfixes
|
||||
|
||||
* cloudflare\_dns \- fix changing Cloudflare SRV records \([https\://github\.com/ansible\-collections/community\.general/issues/8679](https\://github\.com/ansible\-collections/community\.general/issues/8679)\, [https\://github\.com/ansible\-collections/community\.general/pull/8948](https\://github\.com/ansible\-collections/community\.general/pull/8948)\)\.
|
||||
* cmd\_runner module utils \- call to <code>get\_best\_parsable\_locales\(\)</code> was missing parameter \([https\://github\.com/ansible\-collections/community\.general/pull/8929](https\://github\.com/ansible\-collections/community\.general/pull/8929)\)\.
|
||||
* dig lookup plugin \- fix using only the last nameserver specified \([https\://github\.com/ansible\-collections/community\.general/pull/8970](https\://github\.com/ansible\-collections/community\.general/pull/8970)\)\.
|
||||
* django\_command \- option <code>command</code> is now split lexically before passed to underlying PythonRunner \([https\://github\.com/ansible\-collections/community\.general/pull/8944](https\://github\.com/ansible\-collections/community\.general/pull/8944)\)\.
|
||||
* homectl \- the module now tries to use <code>legacycrypt</code> on Python 3\.13\+ \([https\://github\.com/ansible\-collections/community\.general/issues/4691](https\://github\.com/ansible\-collections/community\.general/issues/4691)\, [https\://github\.com/ansible\-collections/community\.general/pull/8987](https\://github\.com/ansible\-collections/community\.general/pull/8987)\)\.
|
||||
* ini\_file \- pass absolute paths to <code>module\.atomic\_move\(\)</code> \([https\://github\.com/ansible/ansible/issues/83950](https\://github\.com/ansible/ansible/issues/83950)\, [https\://github\.com/ansible\-collections/community\.general/pull/8925](https\://github\.com/ansible\-collections/community\.general/pull/8925)\)\.
|
||||
* ipa\_host \- add <code>force\_create</code>\, fix <code>enabled</code> and <code>disabled</code> states \([https\://github\.com/ansible\-collections/community\.general/issues/1094](https\://github\.com/ansible\-collections/community\.general/issues/1094)\, [https\://github\.com/ansible\-collections/community\.general/pull/8920](https\://github\.com/ansible\-collections/community\.general/pull/8920)\)\.
|
||||
* ipa\_hostgroup \- fix <code>enabled \`\` and \`\`disabled</code> states \([https\://github\.com/ansible\-collections/community\.general/issues/8408](https\://github\.com/ansible\-collections/community\.general/issues/8408)\, [https\://github\.com/ansible\-collections/community\.general/pull/8900](https\://github\.com/ansible\-collections/community\.general/pull/8900)\)\.
|
||||
* java\_keystore \- pass absolute paths to <code>module\.atomic\_move\(\)</code> \([https\://github\.com/ansible/ansible/issues/83950](https\://github\.com/ansible/ansible/issues/83950)\, [https\://github\.com/ansible\-collections/community\.general/pull/8925](https\://github\.com/ansible\-collections/community\.general/pull/8925)\)\.
|
||||
* jenkins\_plugin \- pass absolute paths to <code>module\.atomic\_move\(\)</code> \([https\://github\.com/ansible/ansible/issues/83950](https\://github\.com/ansible/ansible/issues/83950)\, [https\://github\.com/ansible\-collections/community\.general/pull/8925](https\://github\.com/ansible\-collections/community\.general/pull/8925)\)\.
|
||||
* kdeconfig \- pass absolute paths to <code>module\.atomic\_move\(\)</code> \([https\://github\.com/ansible/ansible/issues/83950](https\://github\.com/ansible/ansible/issues/83950)\, [https\://github\.com/ansible\-collections/community\.general/pull/8925](https\://github\.com/ansible\-collections/community\.general/pull/8925)\)\.
|
||||
* keycloak\_realm \- fix change detection in check mode by sorting the lists in the realms beforehand \([https\://github\.com/ansible\-collections/community\.general/pull/8877](https\://github\.com/ansible\-collections/community\.general/pull/8877)\)\.
|
||||
* keycloak\_user\_federation \- add module argument allowing users to configure the update mode for the parameter <code>bindCredential</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8898](https\://github\.com/ansible\-collections/community\.general/pull/8898)\)\.
|
||||
* keycloak\_user\_federation \- minimize change detection by setting <code>krbPrincipalAttribute</code> to <code>\'\'</code> in Keycloak responses if missing \([https\://github\.com/ansible\-collections/community\.general/pull/8785](https\://github\.com/ansible\-collections/community\.general/pull/8785)\)\.
|
||||
* keycloak\_user\_federation \- remove <code>lastSync</code> parameter from Keycloak responses to minimize diff/changes \([https\://github\.com/ansible\-collections/community\.general/pull/8812](https\://github\.com/ansible\-collections/community\.general/pull/8812)\)\.
|
||||
* keycloak\_userprofile \- fix empty response when fetching userprofile component by removing <code>parent\=parent\_id</code> filter \([https\://github\.com/ansible\-collections/community\.general/pull/8923](https\://github\.com/ansible\-collections/community\.general/pull/8923)\)\.
|
||||
* keycloak\_userprofile \- improve diff by deserializing the fetched <code>kc\.user\.profile\.config</code> and serialize it only when sending back \([https\://github\.com/ansible\-collections/community\.general/pull/8940](https\://github\.com/ansible\-collections/community\.general/pull/8940)\)\.
|
||||
* lxd\_container \- fix bug introduced in previous commit \([https\://github\.com/ansible\-collections/community\.general/pull/8895](https\://github\.com/ansible\-collections/community\.general/pull/8895)\, [https\://github\.com/ansible\-collections/community\.general/issues/8888](https\://github\.com/ansible\-collections/community\.general/issues/8888)\)\.
|
||||
* one\_service \- fix service creation after it was deleted with <code>unique</code> parameter \([https\://github\.com/ansible\-collections/community\.general/issues/3137](https\://github\.com/ansible\-collections/community\.general/issues/3137)\, [https\://github\.com/ansible\-collections/community\.general/pull/8887](https\://github\.com/ansible\-collections/community\.general/pull/8887)\)\.
|
||||
* pam\_limits \- pass absolute paths to <code>module\.atomic\_move\(\)</code> \([https\://github\.com/ansible/ansible/issues/83950](https\://github\.com/ansible/ansible/issues/83950)\, [https\://github\.com/ansible\-collections/community\.general/pull/8925](https\://github\.com/ansible\-collections/community\.general/pull/8925)\)\.
|
||||
* python\_runner module utils \- parameter <code>path\_prefix</code> was being handled as string when it should be a list \([https\://github\.com/ansible\-collections/community\.general/pull/8944](https\://github\.com/ansible\-collections/community\.general/pull/8944)\)\.
|
||||
* 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="new-modules"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.ipa\_getkeytab \- Manage keytab file in FreeIPA\.
|
||||
|
||||
<a id="v9-4-0"></a>
|
||||
## v9\.4\.0
|
||||
|
||||
<a id="release-summary-3"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-3"></a>
|
||||
### Minor Changes
|
||||
|
||||
* MH module utils \- add parameter <code>when</code> to <code>cause\_changes</code> decorator \([https\://github\.com/ansible\-collections/community\.general/pull/8766](https\://github\.com/ansible\-collections/community\.general/pull/8766)\)\.
|
||||
* MH module utils \- minor refactor in decorators \([https\://github\.com/ansible\-collections/community\.general/pull/8766](https\://github\.com/ansible\-collections/community\.general/pull/8766)\)\.
|
||||
* alternatives \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* apache2\_mod\_proxy \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* apache2\_mod\_proxy \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* consul\_acl \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* copr \- Added <code>includepkgs</code> and <code>excludepkgs</code> parameters to limit the list of packages fetched or excluded from the repository\([https\://github\.com/ansible\-collections/community\.general/pull/8779](https\://github\.com/ansible\-collections/community\.general/pull/8779)\)\.
|
||||
* credstash lookup plugin \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* csv module utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* deco MH module utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* etcd3 \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* gio\_mime \- mute the old <code>VarDict</code> deprecation \([https\://github\.com/ansible\-collections/community\.general/pull/8776](https\://github\.com/ansible\-collections/community\.general/pull/8776)\)\.
|
||||
* gitlab\_group \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* gitlab\_project \- add option <code>issues\_access\_level</code> to enable/disable project issues \([https\://github\.com/ansible\-collections/community\.general/pull/8760](https\://github\.com/ansible\-collections/community\.general/pull/8760)\)\.
|
||||
* gitlab\_project \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* gitlab\_project \- sorted parameters in order to avoid future merge conflicts \([https\://github\.com/ansible\-collections/community\.general/pull/8759](https\://github\.com/ansible\-collections/community\.general/pull/8759)\)\.
|
||||
* hashids filter plugin \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* hwc\_ecs\_instance \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* hwc\_evs\_disk \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* hwc\_vpc\_eip \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* hwc\_vpc\_peering\_connect \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* hwc\_vpc\_port \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* hwc\_vpc\_subnet \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* imc\_rest \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* ipa\_otptoken \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* jira \- mute the old <code>VarDict</code> deprecation \([https\://github\.com/ansible\-collections/community\.general/pull/8776](https\://github\.com/ansible\-collections/community\.general/pull/8776)\)\.
|
||||
* jira \- replace deprecated params when using decorator <code>cause\_changes</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8791](https\://github\.com/ansible\-collections/community\.general/pull/8791)\)\.
|
||||
* keep\_keys filter plugin \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* keycloak module utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* keycloak\_client \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* keycloak\_clientscope \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* keycloak\_identity\_provider \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* keycloak\_user\_federation \- add module argument allowing users to optout of the removal of unspecified mappers\, for example to keep the keycloak default mappers \([https\://github\.com/ansible\-collections/community\.general/pull/8764](https\://github\.com/ansible\-collections/community\.general/pull/8764)\)\.
|
||||
* keycloak\_user\_federation \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* keycloak\_user\_federation \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* keycloak\_user\_federation \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* linode \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* lxc\_container \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* lxd\_container \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* manageiq\_provider \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* ocapi\_utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* one\_service \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* one\_vm \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* onepassword lookup plugin \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* pids \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* pipx \- added new states <code>install\_all</code>\, <code>uninject</code>\, <code>upgrade\_shared</code>\, <code>pin</code>\, and <code>unpin</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8809](https\://github\.com/ansible\-collections/community\.general/pull/8809)\)\.
|
||||
* pipx \- added parameter <code>global</code> to module \([https\://github\.com/ansible\-collections/community\.general/pull/8793](https\://github\.com/ansible\-collections/community\.general/pull/8793)\)\.
|
||||
* pipx \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* pipx\_info \- added parameter <code>global</code> to module \([https\://github\.com/ansible\-collections/community\.general/pull/8793](https\://github\.com/ansible\-collections/community\.general/pull/8793)\)\.
|
||||
* pipx\_info \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* pkg5\_publisher \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* proxmox \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* proxmox\_disk \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* proxmox\_kvm \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* proxmox\_kvm \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* redfish\_utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* redfish\_utils module utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* redis cache plugin \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* remove\_keys filter plugin \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* replace\_keys filter plugin \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* scaleway \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* scaleway module utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* scaleway\_compute \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* scaleway\_ip \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* scaleway\_lb \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* scaleway\_security\_group \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* scaleway\_security\_group \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* scaleway\_user\_data \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* sensu\_silence \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* snmp\_facts \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* sorcery \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8833](https\://github\.com/ansible\-collections/community\.general/pull/8833)\)\.
|
||||
* ufw \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
* unsafe plugin utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* vardict module utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* vars MH module utils \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8814](https\://github\.com/ansible\-collections/community\.general/pull/8814)\)\.
|
||||
* vmadm \- replace Python 2\.6 construct with dict comprehensions \([https\://github\.com/ansible\-collections/community\.general/pull/8822](https\://github\.com/ansible\-collections/community\.general/pull/8822)\)\.
|
||||
|
||||
<a id="deprecated-features-1"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* MH decorator cause\_changes module utils \- deprecate parameters <code>on\_success</code> and <code>on\_failure</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8791](https\://github\.com/ansible\-collections/community\.general/pull/8791)\)\.
|
||||
* pipx \- support for versions of the command line tool <code>pipx</code> older than <code>1\.7\.0</code> is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/8793](https\://github\.com/ansible\-collections/community\.general/pull/8793)\)\.
|
||||
* pipx\_info \- support for versions of the command line tool <code>pipx</code> older than <code>1\.7\.0</code> is deprecated and will be removed in community\.general 11\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/8793](https\://github\.com/ansible\-collections/community\.general/pull/8793)\)\.
|
||||
|
||||
<a id="bugfixes-3"></a>
|
||||
### Bugfixes
|
||||
|
||||
* gitlab\_group\_access\_token \- fix crash in check mode caused by attempted access to a newly created access token \([https\://github\.com/ansible\-collections/community\.general/pull/8796](https\://github\.com/ansible\-collections/community\.general/pull/8796)\)\.
|
||||
* gitlab\_project \- fix <code>container\_expiration\_policy</code> not being applied when creating a new project \([https\://github\.com/ansible\-collections/community\.general/pull/8790](https\://github\.com/ansible\-collections/community\.general/pull/8790)\)\.
|
||||
* gitlab\_project \- fix crash caused by old Gitlab projects not having a <code>container\_expiration\_policy</code> attribute \([https\://github\.com/ansible\-collections/community\.general/pull/8790](https\://github\.com/ansible\-collections/community\.general/pull/8790)\)\.
|
||||
* gitlab\_project\_access\_token \- fix crash in check mode caused by attempted access to a newly created access token \([https\://github\.com/ansible\-collections/community\.general/pull/8796](https\://github\.com/ansible\-collections/community\.general/pull/8796)\)\.
|
||||
* keycloak\_realm\_key \- fix invalid usage of <code>parent\_id</code> \([https\://github\.com/ansible\-collections/community\.general/issues/7850](https\://github\.com/ansible\-collections/community\.general/issues/7850)\, [https\://github\.com/ansible\-collections/community\.general/pull/8823](https\://github\.com/ansible\-collections/community\.general/pull/8823)\)\.
|
||||
* keycloak\_user\_federation \- fix key error when removing mappers during an update and new mappers are specified in the module args \([https\://github\.com/ansible\-collections/community\.general/pull/8762](https\://github\.com/ansible\-collections/community\.general/pull/8762)\)\.
|
||||
* keycloak\_user\_federation \- fix the <code>UnboundLocalError</code> that occurs when an ID is provided for a user federation mapper \([https\://github\.com/ansible\-collections/community\.general/pull/8831](https\://github\.com/ansible\-collections/community\.general/pull/8831)\)\.
|
||||
* keycloak\_user\_federation \- sort desired and after mapper list by name \(analog to before mapper list\) to minimize diff and make change detection more accurate \([https\://github\.com/ansible\-collections/community\.general/pull/8761](https\://github\.com/ansible\-collections/community\.general/pull/8761)\)\.
|
||||
* proxmox inventory plugin \- fixed a possible error on concatenating responses from proxmox\. In case an API call unexpectedly returned an empty result\, the inventory failed with a fatal error\. Added check for empty response \([https\://github\.com/ansible\-collections/community\.general/issues/8798](https\://github\.com/ansible\-collections/community\.general/issues/8798)\, [https\://github\.com/ansible\-collections/community\.general/pull/8794](https\://github\.com/ansible\-collections/community\.general/pull/8794)\)\.
|
||||
|
||||
<a id="new-modules-1"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.keycloak\_userprofile \- Allows managing Keycloak User Profiles\.
|
||||
* community\.general\.one\_vnet \- Manages OpenNebula virtual networks\.
|
||||
|
||||
<a id="v9-3-0"></a>
|
||||
## v9\.3\.0
|
||||
|
||||
<a id="release-summary-4"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular bugfix and feature release\.
|
||||
|
||||
<a id="minor-changes-4"></a>
|
||||
### Minor Changes
|
||||
|
||||
* cgroup\_memory\_recap\, hipchat\, jabber\, log\_plays\, loganalytics\, logentries\, logstash\, slack\, splunk\, sumologic\, syslog\_json callback plugins \- make sure that all options are typed \([https\://github\.com/ansible\-collections/community\.general/pull/8628](https\://github\.com/ansible\-collections/community\.general/pull/8628)\)\.
|
||||
* chef\_databag\, consul\_kv\, cyberarkpassword\, dsv\, etcd\, filetree\, hiera\, onepassword\, onepassword\_doc\, onepassword\_raw\, passwordstore\, redis\, shelvefile\, tss lookup plugins \- make sure that all options are typed \([https\://github\.com/ansible\-collections/community\.general/pull/8626](https\://github\.com/ansible\-collections/community\.general/pull/8626)\)\.
|
||||
* chroot\, funcd\, incus\, iocage\, jail\, lxc\, lxd\, qubes\, zone connection plugins \- make sure that all options are typed \([https\://github\.com/ansible\-collections/community\.general/pull/8627](https\://github\.com/ansible\-collections/community\.general/pull/8627)\)\.
|
||||
@@ -76,7 +364,7 @@ Regular bugfix and feature release\.
|
||||
* proxmox inventory plugin \- add new fact for LXC interface details \([https\://github\.com/ansible\-collections/community\.general/pull/8713](https\://github\.com/ansible\-collections/community\.general/pull/8713)\)\.
|
||||
* redis\, redis\_info \- add <code>client\_cert</code> and <code>client\_key</code> options to specify path to certificate for Redis authentication \([https\://github\.com/ansible\-collections/community\.general/pull/8654](https\://github\.com/ansible\-collections/community\.general/pull/8654)\)\.
|
||||
|
||||
<a id="bugfixes"></a>
|
||||
<a id="bugfixes-4"></a>
|
||||
### Bugfixes
|
||||
|
||||
* gitlab\_runner \- fix <code>paused</code> parameter being ignored \([https\://github\.com/ansible\-collections/community\.general/pull/8648](https\://github\.com/ansible\-collections/community\.general/pull/8648)\)\.
|
||||
@@ -87,7 +375,7 @@ Regular bugfix and feature release\.
|
||||
* proxmox \- fixed an issue where volume strings where overwritten instead of appended to in the new <code>build\_volume\(\)</code> method \([https\://github\.com/ansible\-collections/community\.general/pull/8646](https\://github\.com/ansible\-collections/community\.general/pull/8646)\)\.
|
||||
* proxmox \- removed the forced conversion of non\-string values to strings to be consistent with the module documentation \([https\://github\.com/ansible\-collections/community\.general/pull/8646](https\://github\.com/ansible\-collections/community\.general/pull/8646)\)\.
|
||||
|
||||
<a id="new-modules"></a>
|
||||
<a id="new-modules-2"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.bootc\_manage \- Bootc Switch and Upgrade\.
|
||||
@@ -97,12 +385,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v9-2-0"></a>
|
||||
## v9\.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
|
||||
|
||||
* CmdRunner module utils \- the parameter <code>force\_lang</code> now supports the special value <code>auto</code> which will automatically try and determine the best parsable locale in the system \([https\://github\.com/ansible\-collections/community\.general/pull/8517](https\://github\.com/ansible\-collections/community\.general/pull/8517)\)\.
|
||||
@@ -114,7 +402,7 @@ Regular bugfix and feature release\.
|
||||
* virtualbox inventory plugin \- expose a new parameter <code>enable\_advanced\_group\_parsing</code> to change how the VirtualBox dynamic inventory parses VM groups \([https\://github\.com/ansible\-collections/community\.general/issues/8508](https\://github\.com/ansible\-collections/community\.general/issues/8508)\, [https\://github\.com/ansible\-collections/community\.general/pull/8510](https\://github\.com/ansible\-collections/community\.general/pull/8510)\)\.
|
||||
* wdc\_redfish\_command \- minor change to handle upgrade file for Redfish WD platforms \([https\://github\.com/ansible\-collections/community\.general/pull/8444](https\://github\.com/ansible\-collections/community\.general/pull/8444)\)\.
|
||||
|
||||
<a id="bugfixes-1"></a>
|
||||
<a id="bugfixes-5"></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)\)\.
|
||||
@@ -139,12 +427,12 @@ Regular bugfix and feature release\.
|
||||
<a id="v9-1-0"></a>
|
||||
## v9\.1\.0
|
||||
|
||||
<a id="release-summary-2"></a>
|
||||
<a id="release-summary-6"></a>
|
||||
### Release Summary
|
||||
|
||||
Regular feature and bugfix release\.
|
||||
|
||||
<a id="minor-changes-2"></a>
|
||||
<a id="minor-changes-6"></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)\)\.
|
||||
@@ -161,14 +449,14 @@ Regular feature and bugfix release\.
|
||||
* redfish\_info \- add command <code>CheckAvailability</code> to check if a service is accessible \([https\://github\.com/ansible\-collections/community\.general/issues/8051](https\://github\.com/ansible\-collections/community\.general/issues/8051)\, [https\://github\.com/ansible\-collections/community\.general/pull/8434](https\://github\.com/ansible\-collections/community\.general/pull/8434)\)\.
|
||||
* redis\_info \- adds support for getting cluster info \([https\://github\.com/ansible\-collections/community\.general/pull/8464](https\://github\.com/ansible\-collections/community\.general/pull/8464)\)\.
|
||||
|
||||
<a id="deprecated-features"></a>
|
||||
<a id="deprecated-features-2"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* CmdRunner module util \- setting the value of the <code>ignore\_none</code> parameter within a <code>CmdRunner</code> context is deprecated and that feature should be removed in community\.general 12\.0\.0 \([https\://github\.com/ansible\-collections/community\.general/pull/8479](https\://github\.com/ansible\-collections/community\.general/pull/8479)\)\.
|
||||
* git\_config \- the <code>list\_all</code> option has been deprecated and will be removed in community\.general 11\.0\.0\. Use the <code>community\.general\.git\_config\_info</code> module instead \([https\://github\.com/ansible\-collections/community\.general/pull/8453](https\://github\.com/ansible\-collections/community\.general/pull/8453)\)\.
|
||||
* git\_config \- using <code>state\=present</code> without providing <code>value</code> is deprecated and will be disallowed in community\.general 11\.0\.0\. Use the <code>community\.general\.git\_config\_info</code> module instead to read a value \([https\://github\.com/ansible\-collections/community\.general/pull/8453](https\://github\.com/ansible\-collections/community\.general/pull/8453)\)\.
|
||||
|
||||
<a id="bugfixes-2"></a>
|
||||
<a id="bugfixes-6"></a>
|
||||
### Bugfixes
|
||||
|
||||
* git\_config \- fix behavior of <code>state\=absent</code> if <code>value</code> is present \([https\://github\.com/ansible\-collections/community\.general/issues/8436](https\://github\.com/ansible\-collections/community\.general/issues/8436)\, [https\://github\.com/ansible\-collections/community\.general/pull/8452](https\://github\.com/ansible\-collections/community\.general/pull/8452)\)\.
|
||||
@@ -195,7 +483,7 @@ Regular feature and bugfix release\.
|
||||
* community\.general\.remove\_keys \- Remove specific keys from dictionaries in a list\.
|
||||
* community\.general\.replace\_keys \- Replace specific keys in a list of dictionaries\.
|
||||
|
||||
<a id="new-modules-1"></a>
|
||||
<a id="new-modules-3"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.consul\_agent\_check \- Add\, modify\, and delete checks within a consul cluster\.
|
||||
@@ -206,17 +494,17 @@ Regular feature and bugfix release\.
|
||||
<a id="v9-0-1"></a>
|
||||
## v9\.0\.1
|
||||
|
||||
<a id="release-summary-3"></a>
|
||||
<a id="release-summary-7"></a>
|
||||
### Release Summary
|
||||
|
||||
Bugfix release for inclusion in Ansible 10\.0\.0rc1\.
|
||||
|
||||
<a id="minor-changes-3"></a>
|
||||
<a id="minor-changes-7"></a>
|
||||
### Minor Changes
|
||||
|
||||
* ansible\_galaxy\_install \- minor refactor in the module \([https\://github\.com/ansible\-collections/community\.general/pull/8413](https\://github\.com/ansible\-collections/community\.general/pull/8413)\)\.
|
||||
|
||||
<a id="bugfixes-3"></a>
|
||||
<a id="bugfixes-7"></a>
|
||||
### Bugfixes
|
||||
|
||||
* cpanm \- use new <code>VarDict</code> to prevent deprecation warning \([https\://github\.com/ansible\-collections/community\.general/issues/8410](https\://github\.com/ansible\-collections/community\.general/issues/8410)\, [https\://github\.com/ansible\-collections/community\.general/pull/8411](https\://github\.com/ansible\-collections/community\.general/pull/8411)\)\.
|
||||
@@ -235,12 +523,12 @@ Bugfix release for inclusion in Ansible 10\.0\.0rc1\.
|
||||
<a id="v9-0-0"></a>
|
||||
## v9\.0\.0
|
||||
|
||||
<a id="release-summary-4"></a>
|
||||
<a id="release-summary-8"></a>
|
||||
### Release Summary
|
||||
|
||||
This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-05\-20\.
|
||||
|
||||
<a id="minor-changes-4"></a>
|
||||
<a id="minor-changes-8"></a>
|
||||
### Minor Changes
|
||||
|
||||
* PythonRunner module utils \- specialisation of <code>CmdRunner</code> to execute Python scripts \([https\://github\.com/ansible\-collections/community\.general/pull/8289](https\://github\.com/ansible\-collections/community\.general/pull/8289)\)\.
|
||||
@@ -369,7 +657,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
* django\_manage \- the module will now fail if <code>virtualenv</code> is specified but no virtual environment exists at that location \([https\://github\.com/ansible\-collections/community\.general/pull/8198](https\://github\.com/ansible\-collections/community\.general/pull/8198)\)\.
|
||||
* redfish\_command\, redfish\_config\, redfish\_info \- change the default for <code>timeout</code> from 10 to 60 \([https\://github\.com/ansible\-collections/community\.general/pull/8198](https\://github\.com/ansible\-collections/community\.general/pull/8198)\)\.
|
||||
|
||||
<a id="deprecated-features-1"></a>
|
||||
<a id="deprecated-features-3"></a>
|
||||
### Deprecated Features
|
||||
|
||||
* MH DependencyCtxMgr module\_utils \- deprecate <code>module\_utils\.mh\.mixin\.deps\.DependencyCtxMgr</code> in favour of <code>module\_utils\.deps</code> \([https\://github\.com/ansible\-collections/community\.general/pull/8280](https\://github\.com/ansible\-collections/community\.general/pull/8280)\)\.
|
||||
@@ -410,7 +698,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
* cobbler\, gitlab\_runners\, icinga2\, linode\, lxd\, nmap\, online\, opennebula\, proxmox\, scaleway\, stackpath\_compute\, virtualbox\, and xen\_orchestra inventory plugin \- make sure all data received from the remote servers is marked as unsafe\, so remote code execution by obtaining texts that can be evaluated as templates is not possible \([https\://www\.die\-welt\.net/2024/03/remote\-code\-execution\-in\-ansible\-dynamic\-inventory\-plugins/](https\://www\.die\-welt\.net/2024/03/remote\-code\-execution\-in\-ansible\-dynamic\-inventory\-plugins/)\, [https\://github\.com/ansible\-collections/community\.general/pull/8098](https\://github\.com/ansible\-collections/community\.general/pull/8098)\)\.
|
||||
* keycloak\_identity\_provider \- the client secret was not correctly sanitized by the module\. The return values <code>proposed</code>\, <code>existing</code>\, and <code>end\_state</code>\, as well as the diff\, did contain the client secret unmasked \([https\://github\.com/ansible\-collections/community\.general/pull/8355](https\://github\.com/ansible\-collections/community\.general/pull/8355)\)\.
|
||||
|
||||
<a id="bugfixes-4"></a>
|
||||
<a id="bugfixes-8"></a>
|
||||
### Bugfixes
|
||||
|
||||
* aix\_filesystem \- fix <code>\_validate\_vg</code> not passing VG name to <code>lsvg\_cmd</code> \([https\://github\.com/ansible\-collections/community\.general/issues/8151](https\://github\.com/ansible\-collections/community\.general/issues/8151)\)\.
|
||||
@@ -525,7 +813,7 @@ This is release 9\.0\.0 of <code>community\.general</code>\, released on 2024\-0
|
||||
|
||||
* community\.general\.fqdn\_valid \- Validates fully\-qualified domain names against RFC 1123\.
|
||||
|
||||
<a id="new-modules-2"></a>
|
||||
<a id="new-modules-4"></a>
|
||||
### New Modules
|
||||
|
||||
* community\.general\.consul\_acl\_bootstrap \- Bootstrap ACLs in Consul\.
|
||||
|
||||
268
CHANGELOG.rst
268
CHANGELOG.rst
@@ -6,6 +6,274 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 8.0.0.
|
||||
|
||||
v9.5.2
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- proxmox inventory plugin - fix urllib3 ``InsecureRequestWarnings`` not being suppressed when a token is used (https://github.com/ansible-collections/community.general/pull/9099).
|
||||
|
||||
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/issues/8830).
|
||||
- dnf_config_manager - forces locale to ``C`` before module starts. If the locale was set to non-English, the output of the ``dnf config-manager`` could not be parsed (https://github.com/ansible-collections/community.general/pull/9157, https://github.com/ansible-collections/community.general/issues/9046).
|
||||
- flatpak - force the locale language to ``C`` when running the flatpak command (https://github.com/ansible-collections/community.general/pull/9187, https://github.com/ansible-collections/community.general/issues/8883).
|
||||
- github_key - in check mode, a faulty call to ```datetime.strftime(...)``` was being made which generated an exception (https://github.com/ansible-collections/community.general/issues/9185).
|
||||
- homebrew_cask - allow ``+`` symbol in Homebrew cask name validation regex (https://github.com/ansible-collections/community.general/pull/9128).
|
||||
- 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).
|
||||
- keycloak_clientscope - fix diff and ``end_state`` by removing the code that turns the attributes dict, which contains additional config items, into a list (https://github.com/ansible-collections/community.general/pull/9082).
|
||||
- keycloak_clientscope_type - sort the default and optional clientscope lists to improve the diff (https://github.com/ansible-collections/community.general/pull/9202).
|
||||
- redfish_utils module utils - remove undocumented default applytime (https://github.com/ansible-collections/community.general/pull/9114).
|
||||
- slack - fail if Slack API response is not OK with error message (https://github.com/ansible-collections/community.general/pull/9198).
|
||||
|
||||
v9.5.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- redfish_utils module utils - schedule a BIOS configuration job at next reboot when the BIOS config is changed (https://github.com/ansible-collections/community.general/pull/9012).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- bitwarden lookup plugin - support BWS v0.3.0 syntax breaking change (https://github.com/ansible-collections/community.general/pull/9028).
|
||||
- collection_version lookup plugin - use ``importlib`` directly instead of the deprecated and in ansible-core 2.19 removed ``ansible.module_utils.compat.importlib`` (https://github.com/ansible-collections/community.general/pull/9084).
|
||||
- gitlab_label - update label's color (https://github.com/ansible-collections/community.general/pull/9010).
|
||||
- keycloak_clientscope_type - fix detect changes in check mode (https://github.com/ansible-collections/community.general/issues/9092, https://github.com/ansible-collections/community.general/pull/9093).
|
||||
- keycloak_group - fix crash caused in subgroup creation. The crash was caused by a missing or empty ``subGroups`` property in Keycloak ≥23 (https://github.com/ansible-collections/community.general/issues/8788, https://github.com/ansible-collections/community.general/pull/8979).
|
||||
- modprobe - fix check mode not being honored for ``persistent`` option (https://github.com/ansible-collections/community.general/issues/9051, https://github.com/ansible-collections/community.general/pull/9052).
|
||||
- one_host - fix if statements for cases when ``ID=0`` (https://github.com/ansible-collections/community.general/issues/1199, https://github.com/ansible-collections/community.general/pull/8907).
|
||||
- one_image - fix module failing due to a class method typo (https://github.com/ansible-collections/community.general/pull/9056).
|
||||
- one_image_info - fix module failing due to a class method typo (https://github.com/ansible-collections/community.general/pull/9056).
|
||||
- one_vnet - fix module failing due to a variable typo (https://github.com/ansible-collections/community.general/pull/9019).
|
||||
- redfish_utils module utils - fix issue with URI parsing to gracefully handling trailing slashes when extracting member identifiers (https://github.com/ansible-collections/community.general/issues/9047, https://github.com/ansible-collections/community.general/pull/9057).
|
||||
|
||||
v9.5.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Please note that this is the last feature release for community.general 9.x.y.
|
||||
From now on, new features will only go into community.general 10.x.y.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- dig lookup plugin - add ``port`` option to specify DNS server port (https://github.com/ansible-collections/community.general/pull/8966).
|
||||
- flatpak - improve the parsing of Flatpak application IDs based on official guidelines (https://github.com/ansible-collections/community.general/pull/8909).
|
||||
- gio_mime - adjust code ahead of the old ``VardDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8855).
|
||||
- gitlab_deploy_key - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_group - add many new parameters (https://github.com/ansible-collections/community.general/pull/8908).
|
||||
- gitlab_group - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_issue - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_merge_request - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_runner - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- icinga2_host - replace loop with dict comprehension (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- jira - adjust code ahead of the old ``VardDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8856).
|
||||
- keycloak_client - add ``client-x509`` choice to ``client_authenticator_type`` (https://github.com/ansible-collections/community.general/pull/8973).
|
||||
- keycloak_user_federation - add the user federation config parameter ``referral`` to the module arguments (https://github.com/ansible-collections/community.general/pull/8954).
|
||||
- memset_dns_reload - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_memstore_info - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_server_info - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_zone - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_zone_domain - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_zone_record - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- nmcli - add ``conn_enable`` param to reload connection (https://github.com/ansible-collections/community.general/issues/3752, https://github.com/ansible-collections/community.general/issues/8704, https://github.com/ansible-collections/community.general/pull/8897).
|
||||
- nmcli - add ``state=up`` and ``state=down`` to enable/disable connections (https://github.com/ansible-collections/community.general/issues/3752, https://github.com/ansible-collections/community.general/issues/8704, https://github.com/ansible-collections/community.general/issues/7152, https://github.com/ansible-collections/community.general/pull/8897).
|
||||
- nmcli - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- npm - add ``force`` parameter to allow ``--force`` (https://github.com/ansible-collections/community.general/pull/8885).
|
||||
- one_image - add option ``persistent`` to manage image persistence (https://github.com/ansible-collections/community.general/issues/3578, https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image - extend xsd scheme to make it return a lot more info about image (https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image - refactor code to make it more similar to ``one_template`` and ``one_vnet`` (https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image_info - extend xsd scheme to make it return a lot more info about image (https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image_info - refactor code to make it more similar to ``one_template`` and ``one_vnet`` (https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- open_iscsi - allow login to a portal with multiple targets without specifying any of them (https://github.com/ansible-collections/community.general/pull/8719).
|
||||
- opennebula.py - add VM ``id`` and VM ``host`` to inventory host data (https://github.com/ansible-collections/community.general/pull/8532).
|
||||
- passwordstore lookup plugin - add subkey creation/update support (https://github.com/ansible-collections/community.general/pull/8952).
|
||||
- proxmox inventory plugin - clean up authentication code (https://github.com/ansible-collections/community.general/pull/8917).
|
||||
- redfish_command - add handling of the ``PasswordChangeRequired`` message from services in the ``UpdateUserPassword`` command to directly modify the user's password if the requested user is the one invoking the operation (https://github.com/ansible-collections/community.general/issues/8652, https://github.com/ansible-collections/community.general/pull/8653).
|
||||
- redfish_confg - remove ``CapacityBytes`` from required paramaters of the ``CreateVolume`` command (https://github.com/ansible-collections/community.general/pull/8956).
|
||||
- redfish_config - add parameter ``storage_none_volume_deletion`` to ``CreateVolume`` command in order to control the automatic deletion of non-RAID volumes (https://github.com/ansible-collections/community.general/pull/8990).
|
||||
- redfish_info - adds ``RedfishURI`` and ``StorageId`` to Disk inventory (https://github.com/ansible-collections/community.general/pull/8937).
|
||||
- scaleway_container - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_info - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_namespace - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_namespace_info - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_registry - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_registry_info - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function_info - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function_namespace - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function_namespace_info - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_user_data - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- udm_dns_record - replace loop with ``dict.update()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- hipchat - the hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020. The module is therefore deprecated and will be removed from community.general 11.0.0 if nobody provides compelling reasons to still keep it (https://github.com/ansible-collections/community.general/pull/8919).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- cloudflare_dns - fix changing Cloudflare SRV records (https://github.com/ansible-collections/community.general/issues/8679, https://github.com/ansible-collections/community.general/pull/8948).
|
||||
- cmd_runner module utils - call to ``get_best_parsable_locales()`` was missing parameter (https://github.com/ansible-collections/community.general/pull/8929).
|
||||
- dig lookup plugin - fix using only the last nameserver specified (https://github.com/ansible-collections/community.general/pull/8970).
|
||||
- django_command - option ``command`` is now split lexically before passed to underlying PythonRunner (https://github.com/ansible-collections/community.general/pull/8944).
|
||||
- homectl - the module now tries to use ``legacycrypt`` on Python 3.13+ (https://github.com/ansible-collections/community.general/issues/4691, https://github.com/ansible-collections/community.general/pull/8987).
|
||||
- ini_file - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950, https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- ipa_host - add ``force_create``, fix ``enabled`` and ``disabled`` states (https://github.com/ansible-collections/community.general/issues/1094, https://github.com/ansible-collections/community.general/pull/8920).
|
||||
- ipa_hostgroup - fix ``enabled `` and ``disabled`` states (https://github.com/ansible-collections/community.general/issues/8408, https://github.com/ansible-collections/community.general/pull/8900).
|
||||
- java_keystore - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950, https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- jenkins_plugin - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950, https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- kdeconfig - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950, https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- keycloak_realm - fix change detection in check mode by sorting the lists in the realms beforehand (https://github.com/ansible-collections/community.general/pull/8877).
|
||||
- keycloak_user_federation - add module argument allowing users to configure the update mode for the parameter ``bindCredential`` (https://github.com/ansible-collections/community.general/pull/8898).
|
||||
- keycloak_user_federation - minimize change detection by setting ``krbPrincipalAttribute`` to ``''`` in Keycloak responses if missing (https://github.com/ansible-collections/community.general/pull/8785).
|
||||
- keycloak_user_federation - remove ``lastSync`` parameter from Keycloak responses to minimize diff/changes (https://github.com/ansible-collections/community.general/pull/8812).
|
||||
- keycloak_userprofile - fix empty response when fetching userprofile component by removing ``parent=parent_id`` filter (https://github.com/ansible-collections/community.general/pull/8923).
|
||||
- keycloak_userprofile - improve diff by deserializing the fetched ``kc.user.profile.config`` and serialize it only when sending back (https://github.com/ansible-collections/community.general/pull/8940).
|
||||
- lxd_container - fix bug introduced in previous commit (https://github.com/ansible-collections/community.general/pull/8895, https://github.com/ansible-collections/community.general/issues/8888).
|
||||
- one_service - fix service creation after it was deleted with ``unique`` parameter (https://github.com/ansible-collections/community.general/issues/3137, https://github.com/ansible-collections/community.general/pull/8887).
|
||||
- pam_limits - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950, https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- python_runner module utils - parameter ``path_prefix`` was being handled as string when it should be a list (https://github.com/ansible-collections/community.general/pull/8944).
|
||||
- udm_user - the module now tries to use ``legacycrypt`` on Python 3.13+ (https://github.com/ansible-collections/community.general/issues/4690, https://github.com/ansible-collections/community.general/pull/8987).
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- community.general.ipa_getkeytab - Manage keytab file in FreeIPA.
|
||||
|
||||
v9.4.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix and feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- MH module utils - add parameter ``when`` to ``cause_changes`` decorator (https://github.com/ansible-collections/community.general/pull/8766).
|
||||
- MH module utils - minor refactor in decorators (https://github.com/ansible-collections/community.general/pull/8766).
|
||||
- alternatives - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- apache2_mod_proxy - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- apache2_mod_proxy - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- consul_acl - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- copr - Added ``includepkgs`` and ``excludepkgs`` parameters to limit the list of packages fetched or excluded from the repository(https://github.com/ansible-collections/community.general/pull/8779).
|
||||
- credstash lookup plugin - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- csv module utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- deco MH module utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- etcd3 - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- gio_mime - mute the old ``VarDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8776).
|
||||
- gitlab_group - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- gitlab_project - add option ``issues_access_level`` to enable/disable project issues (https://github.com/ansible-collections/community.general/pull/8760).
|
||||
- gitlab_project - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- gitlab_project - sorted parameters in order to avoid future merge conflicts (https://github.com/ansible-collections/community.general/pull/8759).
|
||||
- hashids filter plugin - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- hwc_ecs_instance - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_evs_disk - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_eip - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_peering_connect - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_port - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_subnet - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- imc_rest - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- ipa_otptoken - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- jira - mute the old ``VarDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8776).
|
||||
- jira - replace deprecated params when using decorator ``cause_changes`` (https://github.com/ansible-collections/community.general/pull/8791).
|
||||
- keep_keys filter plugin - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak module utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- keycloak_client - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_clientscope - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_identity_provider - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_user_federation - add module argument allowing users to optout of the removal of unspecified mappers, for example to keep the keycloak default mappers (https://github.com/ansible-collections/community.general/pull/8764).
|
||||
- keycloak_user_federation - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_user_federation - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- keycloak_user_federation - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- linode - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- lxc_container - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- lxd_container - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- manageiq_provider - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- ocapi_utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- one_service - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- one_vm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- onepassword lookup plugin - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pids - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pipx - added new states ``install_all``, ``uninject``, ``upgrade_shared``, ``pin``, and ``unpin`` (https://github.com/ansible-collections/community.general/pull/8809).
|
||||
- pipx - added parameter ``global`` to module (https://github.com/ansible-collections/community.general/pull/8793).
|
||||
- pipx - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pipx_info - added parameter ``global`` to module (https://github.com/ansible-collections/community.general/pull/8793).
|
||||
- pipx_info - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pkg5_publisher - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- proxmox - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- proxmox_disk - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- proxmox_kvm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- proxmox_kvm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- redfish_utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- redfish_utils module utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- redis cache plugin - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- remove_keys filter plugin - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- replace_keys filter plugin - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- scaleway - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway module utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- scaleway_compute - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_ip - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_lb - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_security_group - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- scaleway_security_group - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_user_data - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- sensu_silence - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- snmp_facts - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- sorcery - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- ufw - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- unsafe plugin utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- vardict module utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- vars MH module utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- vmadm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- MH decorator cause_changes module utils - deprecate parameters ``on_success`` and ``on_failure`` (https://github.com/ansible-collections/community.general/pull/8791).
|
||||
- pipx - support for versions of the command line tool ``pipx`` older than ``1.7.0`` is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/8793).
|
||||
- pipx_info - support for versions of the command line tool ``pipx`` older than ``1.7.0`` is deprecated and will be removed in community.general 11.0.0 (https://github.com/ansible-collections/community.general/pull/8793).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- gitlab_group_access_token - fix crash in check mode caused by attempted access to a newly created access token (https://github.com/ansible-collections/community.general/pull/8796).
|
||||
- gitlab_project - fix ``container_expiration_policy`` not being applied when creating a new project (https://github.com/ansible-collections/community.general/pull/8790).
|
||||
- gitlab_project - fix crash caused by old Gitlab projects not having a ``container_expiration_policy`` attribute (https://github.com/ansible-collections/community.general/pull/8790).
|
||||
- gitlab_project_access_token - fix crash in check mode caused by attempted access to a newly created access token (https://github.com/ansible-collections/community.general/pull/8796).
|
||||
- keycloak_realm_key - fix invalid usage of ``parent_id`` (https://github.com/ansible-collections/community.general/issues/7850, https://github.com/ansible-collections/community.general/pull/8823).
|
||||
- keycloak_user_federation - fix key error when removing mappers during an update and new mappers are specified in the module args (https://github.com/ansible-collections/community.general/pull/8762).
|
||||
- keycloak_user_federation - fix the ``UnboundLocalError`` that occurs when an ID is provided for a user federation mapper (https://github.com/ansible-collections/community.general/pull/8831).
|
||||
- keycloak_user_federation - sort desired and after mapper list by name (analog to before mapper list) to minimize diff and make change detection more accurate (https://github.com/ansible-collections/community.general/pull/8761).
|
||||
- proxmox inventory plugin - fixed a possible error on concatenating responses from proxmox. In case an API call unexpectedly returned an empty result, the inventory failed with a fatal error. Added check for empty response (https://github.com/ansible-collections/community.general/issues/8798, https://github.com/ansible-collections/community.general/pull/8794).
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- community.general.keycloak_userprofile - Allows managing Keycloak User Profiles.
|
||||
- community.general.one_vnet - Manages OpenNebula virtual networks.
|
||||
|
||||
v9.3.0
|
||||
======
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ If you encounter abusive behavior violating the [Ansible Code of Conduct](https:
|
||||
## Communication
|
||||
|
||||
* Join the Ansible forum:
|
||||
* [Get Help](https://forum.ansible.com/c/help/6): get help or help others. This is for questions about modules or plugins in the collection.
|
||||
* [Get Help](https://forum.ansible.com/c/help/6): get help or help others. This is for questions about modules or plugins in the collection. Please add appropriate tags if you start new discussions.
|
||||
* [Tag `community-general`](https://forum.ansible.com/tag/community-general): discuss the *collection itself*, instead of specific modules or plugins.
|
||||
* [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts.
|
||||
* [News & Announcements](https://forum.ansible.com/c/news/5): track project-wide announcements including social events.
|
||||
@@ -37,7 +37,7 @@ For more information about communication, see the [Ansible communication guide](
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, ansible-core 2.17 releases and the current development version of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, ansible-core 2.17, ansible-core 2.18 releases and the current development version of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
|
||||
## External requirements
|
||||
|
||||
|
||||
@@ -1066,3 +1066,446 @@ releases:
|
||||
name: keycloak_realm_keys_metadata_info
|
||||
namespace: ''
|
||||
release_date: '2024-08-12'
|
||||
9.4.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- gitlab_group_access_token - fix crash in check mode caused by attempted
|
||||
access to a newly created access token (https://github.com/ansible-collections/community.general/pull/8796).
|
||||
- gitlab_project - fix ``container_expiration_policy`` not being applied when
|
||||
creating a new project (https://github.com/ansible-collections/community.general/pull/8790).
|
||||
- gitlab_project - fix crash caused by old Gitlab projects not having a ``container_expiration_policy``
|
||||
attribute (https://github.com/ansible-collections/community.general/pull/8790).
|
||||
- gitlab_project_access_token - fix crash in check mode caused by attempted
|
||||
access to a newly created access token (https://github.com/ansible-collections/community.general/pull/8796).
|
||||
- keycloak_realm_key - fix invalid usage of ``parent_id`` (https://github.com/ansible-collections/community.general/issues/7850,
|
||||
https://github.com/ansible-collections/community.general/pull/8823).
|
||||
- keycloak_user_federation - fix key error when removing mappers during an
|
||||
update and new mappers are specified in the module args (https://github.com/ansible-collections/community.general/pull/8762).
|
||||
- keycloak_user_federation - fix the ``UnboundLocalError`` that occurs when
|
||||
an ID is provided for a user federation mapper (https://github.com/ansible-collections/community.general/pull/8831).
|
||||
- keycloak_user_federation - sort desired and after mapper list by name (analog
|
||||
to before mapper list) to minimize diff and make change detection more accurate
|
||||
(https://github.com/ansible-collections/community.general/pull/8761).
|
||||
- proxmox inventory plugin - fixed a possible error on concatenating responses
|
||||
from proxmox. In case an API call unexpectedly returned an empty result,
|
||||
the inventory failed with a fatal error. Added check for empty response
|
||||
(https://github.com/ansible-collections/community.general/issues/8798, https://github.com/ansible-collections/community.general/pull/8794).
|
||||
deprecated_features:
|
||||
- MH decorator cause_changes module utils - deprecate parameters ``on_success``
|
||||
and ``on_failure`` (https://github.com/ansible-collections/community.general/pull/8791).
|
||||
- 'pipx - support for versions of the command line tool ``pipx`` older than
|
||||
``1.7.0`` is deprecated and will be removed in community.general 11.0.0
|
||||
(https://github.com/ansible-collections/community.general/pull/8793).
|
||||
|
||||
'
|
||||
- 'pipx_info - support for versions of the command line tool ``pipx`` older
|
||||
than ``1.7.0`` is deprecated and will be removed in community.general 11.0.0
|
||||
(https://github.com/ansible-collections/community.general/pull/8793).
|
||||
|
||||
'
|
||||
minor_changes:
|
||||
- MH module utils - add parameter ``when`` to ``cause_changes`` decorator
|
||||
(https://github.com/ansible-collections/community.general/pull/8766).
|
||||
- MH module utils - minor refactor in decorators (https://github.com/ansible-collections/community.general/pull/8766).
|
||||
- alternatives - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- apache2_mod_proxy - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- apache2_mod_proxy - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- consul_acl - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- copr - Added ``includepkgs`` and ``excludepkgs`` parameters to limit the
|
||||
list of packages fetched or excluded from the repository(https://github.com/ansible-collections/community.general/pull/8779).
|
||||
- credstash lookup plugin - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- csv module utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- deco MH module utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- etcd3 - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- gio_mime - mute the old ``VarDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8776).
|
||||
- gitlab_group - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- gitlab_project - add option ``issues_access_level`` to enable/disable project
|
||||
issues (https://github.com/ansible-collections/community.general/pull/8760).
|
||||
- gitlab_project - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- gitlab_project - sorted parameters in order to avoid future merge conflicts
|
||||
(https://github.com/ansible-collections/community.general/pull/8759).
|
||||
- hashids filter plugin - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- hwc_ecs_instance - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_evs_disk - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_eip - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_peering_connect - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_port - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- hwc_vpc_subnet - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- imc_rest - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- ipa_otptoken - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- jira - mute the old ``VarDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8776).
|
||||
- jira - replace deprecated params when using decorator ``cause_changes``
|
||||
(https://github.com/ansible-collections/community.general/pull/8791).
|
||||
- keep_keys filter plugin - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak module utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- keycloak_client - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_clientscope - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_identity_provider - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_user_federation - add module argument allowing users to optout
|
||||
of the removal of unspecified mappers, for example to keep the keycloak
|
||||
default mappers (https://github.com/ansible-collections/community.general/pull/8764).
|
||||
- keycloak_user_federation - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- keycloak_user_federation - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- keycloak_user_federation - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- linode - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- lxc_container - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- lxd_container - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- manageiq_provider - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- ocapi_utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- one_service - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- one_vm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- onepassword lookup plugin - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pids - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pipx - added new states ``install_all``, ``uninject``, ``upgrade_shared``,
|
||||
``pin``, and ``unpin`` (https://github.com/ansible-collections/community.general/pull/8809).
|
||||
- pipx - added parameter ``global`` to module (https://github.com/ansible-collections/community.general/pull/8793).
|
||||
- pipx - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pipx_info - added parameter ``global`` to module (https://github.com/ansible-collections/community.general/pull/8793).
|
||||
- pipx_info - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- pkg5_publisher - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- proxmox - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- proxmox_disk - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- proxmox_kvm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- proxmox_kvm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- redfish_utils - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- redfish_utils module utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- redis cache plugin - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- remove_keys filter plugin - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- replace_keys filter plugin - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- scaleway - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway module utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- scaleway_compute - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_ip - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_lb - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_security_group - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- scaleway_security_group - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- scaleway_user_data - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- sensu_silence - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- snmp_facts - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- sorcery - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8833).
|
||||
- ufw - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
- unsafe plugin utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- vardict module utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- vars MH module utils - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8814).
|
||||
- vmadm - replace Python 2.6 construct with dict comprehensions (https://github.com/ansible-collections/community.general/pull/8822).
|
||||
release_summary: Bugfix and feature release.
|
||||
fragments:
|
||||
- 8738-limit-packages-for-copr.yml
|
||||
- 8759-gitlab_project-sort-params.yml
|
||||
- 8760-gitlab_project-add-issues-access-level.yml
|
||||
- 8761-keycloak_user_federation-sort-desired-and-after-mappers-by-name.yml
|
||||
- 8762-keycloac_user_federation-fix-key-error-when-updating.yml
|
||||
- 8764-keycloak_user_federation-make-mapper-removal-optout.yml
|
||||
- 8766-mh-deco-improve.yml
|
||||
- 8776-mute-vardict-deprecation.yml
|
||||
- 8790-gitlab_project-fix-cleanup-policy-on-project-create.yml
|
||||
- 8791-mh-cause-changes-param-depr.yml
|
||||
- 8793-pipx-global.yml
|
||||
- 8794-Fixing-possible-concatination-error.yaml
|
||||
- 8796-gitlab-access-token-check-mode.yml
|
||||
- 8809-pipx-new-params.yml
|
||||
- 8814-dict-comprehension.yml
|
||||
- 8822-dict-comprehension.yml
|
||||
- 8823-keycloak-realm-key.yml
|
||||
- 8831-fix-error-when-mapper-id-is-provided.yml
|
||||
- 8833-dict-comprehension.yml
|
||||
- 9.4.0.yml
|
||||
modules:
|
||||
- description: Allows managing Keycloak User Profiles.
|
||||
name: keycloak_userprofile
|
||||
namespace: ''
|
||||
- description: Manages OpenNebula virtual networks.
|
||||
name: one_vnet
|
||||
namespace: ''
|
||||
release_date: '2024-09-09'
|
||||
9.5.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- cloudflare_dns - fix changing Cloudflare SRV records (https://github.com/ansible-collections/community.general/issues/8679,
|
||||
https://github.com/ansible-collections/community.general/pull/8948).
|
||||
- cmd_runner module utils - call to ``get_best_parsable_locales()`` was missing
|
||||
parameter (https://github.com/ansible-collections/community.general/pull/8929).
|
||||
- dig lookup plugin - fix using only the last nameserver specified (https://github.com/ansible-collections/community.general/pull/8970).
|
||||
- django_command - option ``command`` is now split lexically before passed
|
||||
to underlying PythonRunner (https://github.com/ansible-collections/community.general/pull/8944).
|
||||
- homectl - the module now tries to use ``legacycrypt`` on Python 3.13+ (https://github.com/ansible-collections/community.general/issues/4691,
|
||||
https://github.com/ansible-collections/community.general/pull/8987).
|
||||
- ini_file - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950,
|
||||
https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- ipa_host - add ``force_create``, fix ``enabled`` and ``disabled`` states
|
||||
(https://github.com/ansible-collections/community.general/issues/1094, https://github.com/ansible-collections/community.general/pull/8920).
|
||||
- ipa_hostgroup - fix ``enabled `` and ``disabled`` states (https://github.com/ansible-collections/community.general/issues/8408,
|
||||
https://github.com/ansible-collections/community.general/pull/8900).
|
||||
- java_keystore - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950,
|
||||
https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- jenkins_plugin - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950,
|
||||
https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- kdeconfig - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950,
|
||||
https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- keycloak_realm - fix change detection in check mode by sorting the lists
|
||||
in the realms beforehand (https://github.com/ansible-collections/community.general/pull/8877).
|
||||
- keycloak_user_federation - add module argument allowing users to configure
|
||||
the update mode for the parameter ``bindCredential`` (https://github.com/ansible-collections/community.general/pull/8898).
|
||||
- keycloak_user_federation - minimize change detection by setting ``krbPrincipalAttribute``
|
||||
to ``''`` in Keycloak responses if missing (https://github.com/ansible-collections/community.general/pull/8785).
|
||||
- keycloak_user_federation - remove ``lastSync`` parameter from Keycloak responses
|
||||
to minimize diff/changes (https://github.com/ansible-collections/community.general/pull/8812).
|
||||
- keycloak_userprofile - fix empty response when fetching userprofile component
|
||||
by removing ``parent=parent_id`` filter (https://github.com/ansible-collections/community.general/pull/8923).
|
||||
- keycloak_userprofile - improve diff by deserializing the fetched ``kc.user.profile.config``
|
||||
and serialize it only when sending back (https://github.com/ansible-collections/community.general/pull/8940).
|
||||
- lxd_container - fix bug introduced in previous commit (https://github.com/ansible-collections/community.general/pull/8895,
|
||||
https://github.com/ansible-collections/community.general/issues/8888).
|
||||
- one_service - fix service creation after it was deleted with ``unique``
|
||||
parameter (https://github.com/ansible-collections/community.general/issues/3137,
|
||||
https://github.com/ansible-collections/community.general/pull/8887).
|
||||
- pam_limits - pass absolute paths to ``module.atomic_move()`` (https://github.com/ansible/ansible/issues/83950,
|
||||
https://github.com/ansible-collections/community.general/pull/8925).
|
||||
- python_runner module utils - parameter ``path_prefix`` was being handled
|
||||
as string when it should be a list (https://github.com/ansible-collections/community.general/pull/8944).
|
||||
- udm_user - the module now tries to use ``legacycrypt`` on Python 3.13+ (https://github.com/ansible-collections/community.general/issues/4690,
|
||||
https://github.com/ansible-collections/community.general/pull/8987).
|
||||
deprecated_features:
|
||||
- hipchat - the hipchat service has been discontinued and the self-hosted
|
||||
variant has been End of Life since 2020. The module is therefore deprecated
|
||||
and will be removed from community.general 11.0.0 if nobody provides compelling
|
||||
reasons to still keep it (https://github.com/ansible-collections/community.general/pull/8919).
|
||||
minor_changes:
|
||||
- dig lookup plugin - add ``port`` option to specify DNS server port (https://github.com/ansible-collections/community.general/pull/8966).
|
||||
- flatpak - improve the parsing of Flatpak application IDs based on official
|
||||
guidelines (https://github.com/ansible-collections/community.general/pull/8909).
|
||||
- gio_mime - adjust code ahead of the old ``VardDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8855).
|
||||
- gitlab_deploy_key - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_group - add many new parameters (https://github.com/ansible-collections/community.general/pull/8908).
|
||||
- gitlab_group - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_issue - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_merge_request - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- gitlab_runner - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- icinga2_host - replace loop with dict comprehension (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- jira - adjust code ahead of the old ``VardDict`` deprecation (https://github.com/ansible-collections/community.general/pull/8856).
|
||||
- keycloak_client - add ``client-x509`` choice to ``client_authenticator_type``
|
||||
(https://github.com/ansible-collections/community.general/pull/8973).
|
||||
- keycloak_user_federation - add the user federation config parameter ``referral``
|
||||
to the module arguments (https://github.com/ansible-collections/community.general/pull/8954).
|
||||
- memset_dns_reload - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_memstore_info - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_server_info - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_zone - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_zone_domain - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- memset_zone_record - replace loop with ``dict()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- nmcli - add ``conn_enable`` param to reload connection (https://github.com/ansible-collections/community.general/issues/3752,
|
||||
https://github.com/ansible-collections/community.general/issues/8704, https://github.com/ansible-collections/community.general/pull/8897).
|
||||
- nmcli - add ``state=up`` and ``state=down`` to enable/disable connections
|
||||
(https://github.com/ansible-collections/community.general/issues/3752, https://github.com/ansible-collections/community.general/issues/8704,
|
||||
https://github.com/ansible-collections/community.general/issues/7152, https://github.com/ansible-collections/community.general/pull/8897).
|
||||
- nmcli - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- npm - add ``force`` parameter to allow ``--force`` (https://github.com/ansible-collections/community.general/pull/8885).
|
||||
- one_image - add option ``persistent`` to manage image persistence (https://github.com/ansible-collections/community.general/issues/3578,
|
||||
https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image - extend xsd scheme to make it return a lot more info about image
|
||||
(https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image - refactor code to make it more similar to ``one_template`` and
|
||||
``one_vnet`` (https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image_info - extend xsd scheme to make it return a lot more info about
|
||||
image (https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- one_image_info - refactor code to make it more similar to ``one_template``
|
||||
and ``one_vnet`` (https://github.com/ansible-collections/community.general/pull/8889).
|
||||
- open_iscsi - allow login to a portal with multiple targets without specifying
|
||||
any of them (https://github.com/ansible-collections/community.general/pull/8719).
|
||||
- opennebula.py - add VM ``id`` and VM ``host`` to inventory host data (https://github.com/ansible-collections/community.general/pull/8532).
|
||||
- passwordstore lookup plugin - add subkey creation/update support (https://github.com/ansible-collections/community.general/pull/8952).
|
||||
- proxmox inventory plugin - clean up authentication code (https://github.com/ansible-collections/community.general/pull/8917).
|
||||
- redfish_command - add handling of the ``PasswordChangeRequired`` message
|
||||
from services in the ``UpdateUserPassword`` command to directly modify the
|
||||
user's password if the requested user is the one invoking the operation
|
||||
(https://github.com/ansible-collections/community.general/issues/8652, https://github.com/ansible-collections/community.general/pull/8653).
|
||||
- redfish_confg - remove ``CapacityBytes`` from required paramaters of the
|
||||
``CreateVolume`` command (https://github.com/ansible-collections/community.general/pull/8956).
|
||||
- redfish_config - add parameter ``storage_none_volume_deletion`` to ``CreateVolume``
|
||||
command in order to control the automatic deletion of non-RAID volumes (https://github.com/ansible-collections/community.general/pull/8990).
|
||||
- redfish_info - adds ``RedfishURI`` and ``StorageId`` to Disk inventory (https://github.com/ansible-collections/community.general/pull/8937).
|
||||
- scaleway_container - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_info - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_namespace - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_namespace_info - replace Python 2.6 construct with dict
|
||||
comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_registry - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_container_registry_info - replace Python 2.6 construct with dict
|
||||
comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function_info - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function_namespace - replace Python 2.6 construct with dict comprehensions
|
||||
(https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_function_namespace_info - replace Python 2.6 construct with dict
|
||||
comprehensions (https://github.com/ansible-collections/community.general/pull/8858).
|
||||
- scaleway_user_data - better construct when using ``dict.items()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
- udm_dns_record - replace loop with ``dict.update()`` (https://github.com/ansible-collections/community.general/pull/8876).
|
||||
release_summary: 'Regular bugfix and feature release.
|
||||
|
||||
|
||||
Please note that this is the last feature release for community.general 9.x.y.
|
||||
|
||||
From now on, new features will only go into community.general 10.x.y.'
|
||||
fragments:
|
||||
- 8532-expand-opennuebula-inventory-data.yml
|
||||
- 8652-Redfish-Password-Change-Required.yml
|
||||
- 8679-fix-cloudflare-srv.yml
|
||||
- 8719-openiscsi-add-multiple-targets.yaml
|
||||
- 8785-keycloak_user_federation-set-krbPrincipalAttribute-to-empty-string-if-missing.yaml
|
||||
- 8812-keycloak-user-federation-remove-lastSync-param-from-kc-responses.yml
|
||||
- 8855-gio_mime_vardict.yml
|
||||
- 8856-jira_vardict.yml
|
||||
- 8858-dict-comprehension.yml
|
||||
- 8876-dict-items-loop.yml
|
||||
- 8877-keycloak_realm-sort-lists-before-change-detection.yaml
|
||||
- 8885-add-force-flag-for-nmp.yml
|
||||
- 8887-fix-one_service-unique.yml
|
||||
- 8889-refactor-one-image-modules.yml
|
||||
- 8895-fix-comprehension.yaml
|
||||
- 8897-nmcli-add-reload-and-up-down.yml
|
||||
- 8898-add-arg-to-exclude-bind-credential-from-change-check.yaml
|
||||
- 8900-ipa-hostgroup-fix-states.yml
|
||||
- 8908-add-gitlab-group-params.yml
|
||||
- 8909-flatpak-improve-name-parsing.yaml
|
||||
- 8917-proxmox-clean-auth.yml
|
||||
- 8920-ipa-host-fix-state.yml
|
||||
- 8923-keycloak_userprofile-fix-empty-response-when-fetching-userprofile.yml
|
||||
- 8925-atomic.yml
|
||||
- 8929-cmd_runner-bugfix.yml
|
||||
- 8937-add-StorageId-RedfishURI-to-disk-facts.yml
|
||||
- 8940-keycloak_userprofile-improve-diff.yml
|
||||
- 8944-django-command-fix.yml
|
||||
- 8952-password-store-lookup-create-subkey-support.yml
|
||||
- 8954-keycloak-user-federation-add-referral-parameter.yml
|
||||
- 8956-remove-capacitybytes-from-the-required-parameters_list.yml
|
||||
- 8966-dig-add-port-option.yml
|
||||
- 8970-fix-dig-multi-nameservers.yml
|
||||
- 8973-keycloak_client-add-x509-auth.yml
|
||||
- 8987-legacycrypt.yml
|
||||
- 8990.yml
|
||||
- 9.5.0.yml
|
||||
- deprecate-hipchat.yml
|
||||
modules:
|
||||
- description: Manage keytab file in FreeIPA.
|
||||
name: ipa_getkeytab
|
||||
namespace: ''
|
||||
release_date: '2024-10-07'
|
||||
9.5.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- bitwarden lookup plugin - support BWS v0.3.0 syntax breaking change (https://github.com/ansible-collections/community.general/pull/9028).
|
||||
- collection_version lookup plugin - use ``importlib`` directly instead of
|
||||
the deprecated and in ansible-core 2.19 removed ``ansible.module_utils.compat.importlib``
|
||||
(https://github.com/ansible-collections/community.general/pull/9084).
|
||||
- gitlab_label - update label's color (https://github.com/ansible-collections/community.general/pull/9010).
|
||||
- keycloak_clientscope_type - fix detect changes in check mode (https://github.com/ansible-collections/community.general/issues/9092,
|
||||
https://github.com/ansible-collections/community.general/pull/9093).
|
||||
- "keycloak_group - fix crash caused in subgroup creation. The crash was caused\
|
||||
\ by a missing or empty ``subGroups`` property in Keycloak \u226523 (https://github.com/ansible-collections/community.general/issues/8788,\
|
||||
\ https://github.com/ansible-collections/community.general/pull/8979)."
|
||||
- modprobe - fix check mode not being honored for ``persistent`` option (https://github.com/ansible-collections/community.general/issues/9051,
|
||||
https://github.com/ansible-collections/community.general/pull/9052).
|
||||
- one_host - fix if statements for cases when ``ID=0`` (https://github.com/ansible-collections/community.general/issues/1199,
|
||||
https://github.com/ansible-collections/community.general/pull/8907).
|
||||
- one_image - fix module failing due to a class method typo (https://github.com/ansible-collections/community.general/pull/9056).
|
||||
- one_image_info - fix module failing due to a class method typo (https://github.com/ansible-collections/community.general/pull/9056).
|
||||
- one_vnet - fix module failing due to a variable typo (https://github.com/ansible-collections/community.general/pull/9019).
|
||||
- redfish_utils module utils - fix issue with URI parsing to gracefully handling
|
||||
trailing slashes when extracting member identifiers (https://github.com/ansible-collections/community.general/issues/9047,
|
||||
https://github.com/ansible-collections/community.general/pull/9057).
|
||||
minor_changes:
|
||||
- redfish_utils module utils - schedule a BIOS configuration job at next reboot
|
||||
when the BIOS config is changed (https://github.com/ansible-collections/community.general/pull/9012).
|
||||
release_summary: Regular bugfix release.
|
||||
fragments:
|
||||
- 8907-fix-one-host-id.yml
|
||||
- 8979-keycloak_group-fix-subgroups.yml
|
||||
- 9.5.1.yml
|
||||
- 9010-edit-gitlab-label-color.yaml
|
||||
- 9012-dell-pwrbutton-requires-a-job-initiated-at-reboot.yml
|
||||
- 9019-onevnet-bugfix.yml
|
||||
- 9028-bitwarden-secrets-manager-syntax-fix.yml
|
||||
- 9047-redfish-uri-parsing.yml
|
||||
- 9052-modprobe-bugfix.yml
|
||||
- 9056-fix-one_image-modules.yml
|
||||
- 9084-collection_version-importlib.yml
|
||||
- 9092-keycloak-clientscope-type-fix-check-mode.yml
|
||||
release_date: '2024-11-03'
|
||||
9.5.2:
|
||||
changes:
|
||||
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/issues/8830).
|
||||
- dnf_config_manager - forces locale to ``C`` before module starts. If the
|
||||
locale was set to non-English, the output of the ``dnf config-manager``
|
||||
could not be parsed (https://github.com/ansible-collections/community.general/pull/9157,
|
||||
https://github.com/ansible-collections/community.general/issues/9046).
|
||||
- flatpak - force the locale language to ``C`` when running the flatpak command
|
||||
(https://github.com/ansible-collections/community.general/pull/9187, https://github.com/ansible-collections/community.general/issues/8883).
|
||||
- github_key - in check mode, a faulty call to ```datetime.strftime(...)```
|
||||
was being made which generated an exception (https://github.com/ansible-collections/community.general/issues/9185).
|
||||
- homebrew_cask - allow ``+`` symbol in Homebrew cask name validation regex
|
||||
(https://github.com/ansible-collections/community.general/pull/9128).
|
||||
- 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).
|
||||
- keycloak_clientscope - fix diff and ``end_state`` by removing the code that
|
||||
turns the attributes dict, which contains additional config items, into
|
||||
a list (https://github.com/ansible-collections/community.general/pull/9082).
|
||||
- keycloak_clientscope_type - sort the default and optional clientscope lists
|
||||
to improve the diff (https://github.com/ansible-collections/community.general/pull/9202).
|
||||
- redfish_utils module utils - remove undocumented default applytime (https://github.com/ansible-collections/community.general/pull/9114).
|
||||
- slack - fail if Slack API response is not OK with error message (https://github.com/ansible-collections/community.general/pull/9198).
|
||||
minor_changes:
|
||||
- proxmox inventory plugin - fix urllib3 ``InsecureRequestWarnings`` not being
|
||||
suppressed when a token is used (https://github.com/ansible-collections/community.general/pull/9099).
|
||||
release_summary: Regular bugfix release.
|
||||
fragments:
|
||||
- 9.5.2.yml
|
||||
- 9077-keycloak_client-fix-attributes-dict-turned-into-list.yml
|
||||
- 9082-keycloak_clientscope-fix-attributes-dict-turned-into-list.yml
|
||||
- 9099-proxmox-fix-insecure.yml
|
||||
- 9114-redfish-utils-update-remove-default-applytime.yml
|
||||
- 9124-dnf_config_manager.yml
|
||||
- 9128-homebrew_cask-name-regex-fix.yml
|
||||
- 9157-fix-dnf_config_manager-locale.yml
|
||||
- 9186-fix-broken-check-mode-in-github-key.yml
|
||||
- 9187-flatpak-lang.yml
|
||||
- 9198-fail-if-slack-api-response-is-not-ok-with-error-message.yml
|
||||
- 9202-keycloak_clientscope_type-sort-lists.yml
|
||||
release_date: '2024-12-02'
|
||||
|
||||
@@ -19,3 +19,4 @@ sections:
|
||||
- guide_deps
|
||||
- guide_vardict
|
||||
- guide_cmdrunner
|
||||
- guide_modulehelper
|
||||
|
||||
@@ -24,9 +24,6 @@ communication:
|
||||
- topic: General usage and support questions
|
||||
network: Libera
|
||||
channel: '#ansible'
|
||||
mailing_lists:
|
||||
- topic: Ansible Project List
|
||||
url: https://groups.google.com/g/ansible-project
|
||||
forums:
|
||||
- topic: "Ansible Forum: General usage and support questions"
|
||||
# The following URL directly points to the "Get Help" section
|
||||
|
||||
@@ -68,20 +68,27 @@ This is meant to be done once, then every time you need to execute the command y
|
||||
with runner("version") as ctx:
|
||||
dummy, stdout, dummy = ctx.run()
|
||||
|
||||
# passes arg 'data' to AnsibleModule.run_command()
|
||||
with runner("type name", data=stdin_data) as ctx:
|
||||
dummy, stdout, dummy = ctx.run()
|
||||
|
||||
# Another way of expressing it
|
||||
dummy, stdout, dummy = runner("version").run()
|
||||
|
||||
Note that you can pass values for the arguments when calling ``run()``,
|
||||
otherwise ``CmdRunner`` uses the module options with the exact same names to
|
||||
provide values for the runner arguments. If no value is passed and no module option
|
||||
is found for the name specified, then an exception is raised, unless the
|
||||
argument is using ``cmd_runner_fmt.as_fixed`` as format function like the
|
||||
``version`` in the example above. See more about it below.
|
||||
Note that you can pass values for the arguments when calling ``run()``, otherwise ``CmdRunner``
|
||||
uses the module options with the exact same names to provide values for the runner arguments.
|
||||
If no value is passed and no module option is found for the name specified, then an exception is raised, unless
|
||||
the argument is using ``cmd_runner_fmt.as_fixed`` as format function like the ``version`` in the example above.
|
||||
See more about it below.
|
||||
|
||||
In the first example, values of ``type``, ``force``, ``no_deps`` and others
|
||||
are taken straight from the module, whilst ``galaxy_cmd`` and ``upgrade`` are
|
||||
passed explicitly.
|
||||
|
||||
.. note::
|
||||
|
||||
It is not possible to automatically retrieve values of suboptions.
|
||||
|
||||
That generates a resulting command line similar to (example taken from the
|
||||
output of an integration test):
|
||||
|
||||
@@ -110,7 +117,7 @@ into something formatted for the command line.
|
||||
Argument format function
|
||||
""""""""""""""""""""""""
|
||||
|
||||
An ``arg_format`` function should be of the form:
|
||||
An ``arg_format`` function is defined in the form similar to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -155,7 +162,7 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_list()``
|
||||
- Example:
|
||||
- Examples:
|
||||
+----------------------+---------------------+
|
||||
| Value | Outcome |
|
||||
+======================+=====================+
|
||||
@@ -167,12 +174,11 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
- ``cmd_runner_fmt.as_bool()``
|
||||
This method receives two different parameters: ``args_true`` and ``args_false``, latter being optional.
|
||||
If the boolean evaluation of ``value`` is ``True``, the format function returns ``args_true``.
|
||||
If the boolean evaluation is ``False``, then the function returns ``args_false``
|
||||
if it was provided, or ``[]`` otherwise.
|
||||
If the boolean evaluation is ``False``, then the function returns ``args_false`` if it was provided, or ``[]`` otherwise.
|
||||
|
||||
- Creation:
|
||||
- Creation (one arg):
|
||||
``cmd_runner_fmt.as_bool("--force")``
|
||||
- Example:
|
||||
- Examples:
|
||||
+------------+--------------------+
|
||||
| Value | Outcome |
|
||||
+============+====================+
|
||||
@@ -180,6 +186,30 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
+------------+--------------------+
|
||||
| ``False`` | ``[]`` |
|
||||
+------------+--------------------+
|
||||
- Creation (two args, ``None`` treated as ``False``):
|
||||
``cmd_runner_fmt.as_bool("--relax", "--dont-do-it")``
|
||||
- Examples:
|
||||
+------------+----------------------+
|
||||
| Value | Outcome |
|
||||
+============+======================+
|
||||
| ``True`` | ``["--relax"]`` |
|
||||
+------------+----------------------+
|
||||
| ``False`` | ``["--dont-do-it"]`` |
|
||||
+------------+----------------------+
|
||||
| | ``["--dont-do-it"]`` |
|
||||
+------------+----------------------+
|
||||
- Creation (two args, ``None`` is ignored):
|
||||
``cmd_runner_fmt.as_bool("--relax", "--dont-do-it", ignore_none=True)``
|
||||
- Examples:
|
||||
+------------+----------------------+
|
||||
| Value | Outcome |
|
||||
+============+======================+
|
||||
| ``True`` | ``["--relax"]`` |
|
||||
+------------+----------------------+
|
||||
| ``False`` | ``["--dont-do-it"]`` |
|
||||
+------------+----------------------+
|
||||
| | ``[]`` |
|
||||
+------------+----------------------+
|
||||
|
||||
- ``cmd_runner_fmt.as_bool_not()``
|
||||
This method receives one parameter, which is returned by the function when the boolean evaluation
|
||||
@@ -187,7 +217,7 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_bool_not("--no-deps")``
|
||||
- Example:
|
||||
- Examples:
|
||||
+-------------+---------------------+
|
||||
| Value | Outcome |
|
||||
+=============+=====================+
|
||||
@@ -202,7 +232,7 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_optval("-i")``
|
||||
- Example:
|
||||
- Examples:
|
||||
+---------------+---------------------+
|
||||
| Value | Outcome |
|
||||
+===============+=====================+
|
||||
@@ -216,7 +246,7 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_opt_val("--name")``
|
||||
- Example:
|
||||
- Examples:
|
||||
+--------------+--------------------------+
|
||||
| Value | Outcome |
|
||||
+==============+==========================+
|
||||
@@ -229,7 +259,7 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_opt_eq_val("--num-cpus")``
|
||||
- Example:
|
||||
- Examples:
|
||||
+------------+-------------------------+
|
||||
| Value | Outcome |
|
||||
+============+=========================+
|
||||
@@ -243,7 +273,7 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_fixed("--version")``
|
||||
- Example:
|
||||
- Examples:
|
||||
+---------+-----------------------+
|
||||
| Value | Outcome |
|
||||
+=========+=======================+
|
||||
@@ -265,7 +295,7 @@ In these descriptions ``value`` refers to the single parameter passed to the for
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_map(dict(a=1, b=2, c=3), default=42)``
|
||||
- Example:
|
||||
- Examples:
|
||||
+---------------------+---------------+
|
||||
| Value | Outcome |
|
||||
+=====================+===============+
|
||||
@@ -359,6 +389,8 @@ Settings that can be passed to the ``CmdRunner`` constructor are:
|
||||
Command to be executed. It can be a single string, the executable name, or a list
|
||||
of strings containing the executable name as the first element and, optionally, fixed parameters.
|
||||
Those parameters are used in all executions of the runner.
|
||||
The *executable* pointed by this parameter (whether itself when ``str`` or its first element when ``list``) is
|
||||
processed using ``AnsibleModule.get_bin_path()`` *unless* it is an absolute path or contains the character ``/``.
|
||||
- ``arg_formats: dict``
|
||||
Mapping of argument names to formatting functions.
|
||||
- ``default_args_order: str``
|
||||
@@ -394,6 +426,10 @@ When creating a context, the additional settings that can be passed to the call
|
||||
Defaults to ``False``.
|
||||
- ``check_mode_return: any``
|
||||
If ``check_mode_skip=True``, then return this value instead.
|
||||
- valid named arguments to ``AnsibleModule.run_command()``
|
||||
Other than ``args``, any valid argument to ``run_command()`` can be passed when setting up the run context.
|
||||
For example, ``data`` can be used to send information to the command's standard input.
|
||||
Or ``cwd`` can be used to run the command inside a specific working directory.
|
||||
|
||||
Additionally, any other valid parameters for ``AnsibleModule.run_command()`` may be passed, but unexpected behavior
|
||||
might occur if redefining options already present in the runner or its context creation. Use with caution.
|
||||
|
||||
540
docs/docsite/rst/guide_modulehelper.rst
Normal file
540
docs/docsite/rst/guide_modulehelper.rst
Normal file
@@ -0,0 +1,540 @@
|
||||
..
|
||||
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_modulehelper:
|
||||
|
||||
Module Helper guide
|
||||
===================
|
||||
|
||||
|
||||
Introduction
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Writing a module for Ansible is largely described in existing documentation.
|
||||
However, a good part of that is boilerplate code that needs to be repeated every single time.
|
||||
That is where ``ModuleHelper`` comes to assistance: a lot of that boilerplate code is done.
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_modulehelper.quickstart:
|
||||
|
||||
Quickstart
|
||||
""""""""""
|
||||
|
||||
See the `example from Ansible documentation <https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#creating-a-module>`_
|
||||
written with ``ModuleHelper``.
|
||||
But bear in mind that it does not showcase all of MH's features:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
module = dict(
|
||||
argument_spec=dict(
|
||||
name=dict(type='str', required=True),
|
||||
new=dict(type='bool', required=False, default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __run__(self):
|
||||
self.vars.original_message = ''
|
||||
self.vars.message = ''
|
||||
if self.check_mode:
|
||||
return
|
||||
self.vars.original_message = self.vars.name
|
||||
self.vars.message = 'goodbye'
|
||||
self.changed = self.vars['new']
|
||||
if self.vars.name == "fail me":
|
||||
self.do_raise("You requested this to fail")
|
||||
|
||||
|
||||
def main():
|
||||
MyTest.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
Module Helper
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Introduction
|
||||
""""""""""""
|
||||
|
||||
``ModuleHelper`` is a wrapper around the standard ``AnsibleModule``, providing extra features and conveniences.
|
||||
The basic structure of a module using ``ModuleHelper`` is as shown in the
|
||||
:ref:`ansible_collections.community.general.docsite.guide_modulehelper.quickstart`
|
||||
section above, but there are more elements that will take part in it.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
output_params = ()
|
||||
change_params = ()
|
||||
diff_params = ()
|
||||
facts_name = None
|
||||
facts_params = ()
|
||||
use_old_vardict = True
|
||||
mute_vardict_deprecation = False
|
||||
module = dict(
|
||||
argument_spec=dict(...),
|
||||
# ...
|
||||
)
|
||||
|
||||
After importing the ``ModuleHelper`` class, you need to declare your own class extending it.
|
||||
|
||||
.. seealso::
|
||||
|
||||
There is a variation called ``StateModuleHelper``, which builds on top of the features provided by MH.
|
||||
See :ref:`ansible_collections.community.general.docsite.guide_modulehelper.statemh` below for more details.
|
||||
|
||||
The easiest way of specifying the module is to create the class variable ``module`` with a dictionary
|
||||
containing the exact arguments that would be passed as parameters to ``AnsibleModule``.
|
||||
If you prefer to create the ``AnsibleModule`` object yourself, just assign it to the ``module`` class variable.
|
||||
MH also accepts a parameter ``module`` in its constructor, if that parameter is used used,
|
||||
then it will override the class variable. The parameter can either be ``dict`` or ``AnsibleModule`` as well.
|
||||
|
||||
Beyond the definition of the module, there are other variables that can be used to control aspects
|
||||
of MH's behavior. These variables should be set at the very beginning of the class, and their semantics are
|
||||
explained through this document.
|
||||
|
||||
The main logic of MH happens in the ``ModuleHelper.run()`` method, which looks like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@module_fails_on_exception
|
||||
def run(self):
|
||||
self.__init_module__()
|
||||
self.__run__()
|
||||
self.__quit_module__()
|
||||
output = self.output
|
||||
if 'failed' not in output:
|
||||
output['failed'] = False
|
||||
self.module.exit_json(changed=self.has_changed(), **output)
|
||||
|
||||
The method ``ModuleHelper.__run__()`` must be implemented by the module and most
|
||||
modules will be able to perform their actions implementing only that MH method.
|
||||
However, in some cases, you might want to execute actions before or after the main tasks, in which cases
|
||||
you should implement ``ModuleHelper.__init_module__()`` and ``ModuleHelper.__quit_module__()`` respectively.
|
||||
|
||||
Note that the output comes from ``self.output``, which is a ``@property`` method.
|
||||
By default, that property will collect all the variables that are marked for output and return them in a dictionary with their values.
|
||||
Moreover, the default ``self.output`` will also handle Ansible ``facts`` and *diff mode*.
|
||||
Also note the changed status comes from ``self.has_changed()``, which is usually calculated from variables that are marked
|
||||
to track changes in their content.
|
||||
|
||||
.. seealso::
|
||||
|
||||
More details in sections
|
||||
:ref:`ansible_collections.community.general.docsite.guide_modulehelper.paramvaroutput` and
|
||||
:ref:`ansible_collections.community.general.docsite.guide_modulehelper.changes` below.
|
||||
|
||||
.. seealso::
|
||||
|
||||
See more about the decorator
|
||||
:ref:`ansible_collections.community.general.docsite.guide_modulehelper.modulefailsdeco` below.
|
||||
|
||||
|
||||
Another way to write the example from the
|
||||
:ref:`ansible_collections.community.general.docsite.guide_modulehelper.quickstart`
|
||||
would be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def __init_module__(self):
|
||||
self.vars.original_message = ''
|
||||
self.vars.message = ''
|
||||
|
||||
def __run__(self):
|
||||
if self.check_mode:
|
||||
return
|
||||
self.vars.original_message = self.vars.name
|
||||
self.vars.message = 'goodbye'
|
||||
self.changed = self.vars['new']
|
||||
|
||||
def __quit_module__(self):
|
||||
if self.vars.name == "fail me":
|
||||
self.do_raise("You requested this to fail")
|
||||
|
||||
Notice that there are no calls to ``module.exit_json()`` nor ``module.fail_json()``: if the module fails, raise an exception.
|
||||
You can use the convenience method ``self.do_raise()`` or raise the exception as usual in Python to do that.
|
||||
If no exception is raised, then the module succeeds.
|
||||
|
||||
.. seealso::
|
||||
|
||||
See more about exceptions in section
|
||||
:ref:`ansible_collections.community.general.docsite.guide_modulehelper.exceptions` below.
|
||||
|
||||
Ansible modules must have a ``main()`` function and the usual test for ``'__main__'``. When using MH that should look like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def main():
|
||||
MyTest.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
The class method ``execute()`` is nothing more than a convenience shorcut for:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
m = MyTest()
|
||||
m.run()
|
||||
|
||||
Optionally, an ``AnsibleModule`` may be passed as parameter to ``execute()``.
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_modulehelper.paramvaroutput:
|
||||
|
||||
Parameters, variables, and output
|
||||
"""""""""""""""""""""""""""""""""
|
||||
|
||||
All the parameters automatically become variables in the ``self.vars`` attribute, which is of the ``VarDict`` type.
|
||||
By using ``self.vars``, you get a central mechanism to access the parameters but also to expose variables as return values of the module.
|
||||
As described in :ref:`ansible_collections.community.general.docsite.guide_vardict`, variables in ``VarDict`` have metadata associated to them.
|
||||
One of the attributes in that metadata marks the variable for output, and MH makes use of that to generate the module's return values.
|
||||
|
||||
.. important::
|
||||
|
||||
The ``VarDict`` feature described was introduced in community.general 7.1.0, but there was a first
|
||||
implementation of it embedded within ``ModuleHelper``.
|
||||
That older implementation is now deprecated and will be removed in community.general 11.0.0.
|
||||
After community.general 7.1.0, MH modules generate a deprecation message about *using the old VarDict*.
|
||||
There are two ways to prevent that from happening:
|
||||
|
||||
#. Set ``mute_vardict_deprecation = True`` and the deprecation will be silenced. If the module still uses the old ``VarDict``,
|
||||
it will not be able to update to community.general 11.0.0 (Spring 2026) upon its release.
|
||||
#. Set ``use_old_vardict = False`` to make the MH module use the new ``VarDict`` immediatelly.
|
||||
The new ``VarDict`` and its use is documented and this is the recommended way to handle this.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
use_old_vardict = False
|
||||
mute_vardict_deprecation = True
|
||||
...
|
||||
|
||||
These two settings are mutually exclusive, but that is not enforced and the behavior when setting both is not specified.
|
||||
|
||||
Contrary to new variables created in ``VarDict``, module parameters are not set for output by default.
|
||||
If you want to include some module parameters in the output, list them in the ``output_params`` class variable.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
output_params = ('state', 'name')
|
||||
...
|
||||
|
||||
Another neat feature provided by MH by using ``VarDict`` is the automatic tracking of changes when setting the metadata ``change=True``.
|
||||
Again, to enable this feature for module parameters, you must list them in the ``change_params`` class variable.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
# example from community.general.xfconf
|
||||
change_params = ('value', )
|
||||
...
|
||||
|
||||
.. seealso::
|
||||
|
||||
See more about this in
|
||||
:ref:`ansible_collections.community.general.docsite.guide_modulehelper.changes` below.
|
||||
|
||||
Similarly, if you want to use Ansible's diff mode, you can set the metadata ``diff=True`` and ``diff_params`` for module parameters.
|
||||
With that, MH will automatically generate the diff output for variables that have changed.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
diff_params = ('value', )
|
||||
|
||||
def __run__(self):
|
||||
# example from community.general.gio_mime
|
||||
self.vars.set_meta("handler", initial_value=gio_mime_get(self.runner, self.vars.mime_type), diff=True, change=True)
|
||||
|
||||
Moreover, if a module is set to return *facts* instead of return values, then again use the metadata ``fact=True`` and ``fact_params`` for module parameters.
|
||||
Additionally, you must specify ``facts_name``, as in:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class VolumeFacts(ModuleHelper):
|
||||
facts_name = 'volume_facts'
|
||||
|
||||
def __init_module__(self):
|
||||
self.vars.set("volume", 123, fact=True)
|
||||
|
||||
That generates an Ansible fact like:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Obtain volume facts
|
||||
some.collection.volume_facts:
|
||||
# parameters
|
||||
|
||||
- name: Print volume facts
|
||||
debug:
|
||||
msg: Volume fact is {{ ansible_facts.volume_facts.volume }}
|
||||
|
||||
.. important::
|
||||
|
||||
If ``facts_name`` is not set, the module does not generate any facts.
|
||||
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_modulehelper.changes:
|
||||
|
||||
Handling changes
|
||||
""""""""""""""""
|
||||
|
||||
In MH there are many ways to indicate change in the module execution. Here they are:
|
||||
|
||||
Tracking changes in variables
|
||||
-----------------------------
|
||||
|
||||
As explained above, you can enable change tracking in any number of variables in ``self.vars``.
|
||||
By the end of the module execution, if any of those variables has a value different then the first value assigned to them,
|
||||
then that will be picked up by MH and signalled as changed at the module output.
|
||||
See the example below to learn how you can enabled change tracking in variables:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# using __init_module__() as example, it works the same in __run__() and __quit_module__()
|
||||
def __init_module__(self):
|
||||
# example from community.general.ansible_galaxy_install
|
||||
self.vars.set("new_roles", {}, change=True)
|
||||
|
||||
# example of "hidden" variable used only to track change in a value from community.general.gconftool2
|
||||
self.vars.set('_value', self.vars.previous_value, output=False, change=True)
|
||||
|
||||
# enable change-tracking without assigning value
|
||||
self.vars.set_meta("new_roles", change=True)
|
||||
|
||||
# if you must forcibly set an initial value to the variable
|
||||
self.vars.set_meta("new_roles", initial_value=[])
|
||||
...
|
||||
|
||||
If the end value of any variable marked ``change`` is different from its initial value, then MH will return ``changed=True``.
|
||||
|
||||
Indicating changes with ``changed``
|
||||
-----------------------------------
|
||||
|
||||
If you want to indicate change directly in the code, then use the ``self.changed`` property in MH.
|
||||
Beware that this is a ``@property`` method in MH, with both a *getter* and a *setter*.
|
||||
By default, that hidden field is set to ``False``.
|
||||
|
||||
Effective change
|
||||
----------------
|
||||
|
||||
The effective outcome for the module is determined in the ``self.has_changed()`` method, and it consists of the logical *OR* operation
|
||||
between ``self.changed`` and the change calculated from ``self.vars``.
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_modulehelper.exceptions:
|
||||
|
||||
Exceptions
|
||||
""""""""""
|
||||
|
||||
In MH, instead of calling ``module.fail_json()`` you can just raise an exception.
|
||||
The output variables are collected the same way they would be for a successful execution.
|
||||
However, you can set output variables specifically for that exception, if you so choose.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def __init_module__(self):
|
||||
if not complex_validation():
|
||||
self.do_raise("Validation failed!")
|
||||
|
||||
# Or passing output variables
|
||||
awesomeness = calculate_awesomeness()
|
||||
if awesomeness > 1000:
|
||||
self.do_raise("Over awesome, I cannot handle it!", update_output={"awesomeness": awesomeness})
|
||||
|
||||
All exceptions derived from ``Exception`` are captured and translated into a ``fail_json()`` call.
|
||||
However, if you do want to call ``self.module.fail_json()`` yourself it will work,
|
||||
just keep in mind that there will be no automatic handling of output variables in that case.
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_modulehelper.statemh:
|
||||
|
||||
StateModuleHelper
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Many modules use a parameter ``state`` that effectively controls the exact action performed by the module, such as
|
||||
``state=present`` or ``state=absent`` for installing or removing packages.
|
||||
By using ``StateModuleHelper`` you can make your code like the excerpt from the ``gconftool2`` below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
|
||||
|
||||
class GConftool(StateModuleHelper):
|
||||
...
|
||||
module = dict(
|
||||
...
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = gconftool2_runner(self.module, check_rc=True)
|
||||
...
|
||||
|
||||
self.vars.set('previous_value', self._get(), fact=True)
|
||||
self.vars.set('value_type', self.vars.value_type)
|
||||
self.vars.set('_value', self.vars.previous_value, output=False, change=True)
|
||||
self.vars.set_meta('value', initial_value=self.vars.previous_value)
|
||||
self.vars.set('playbook_value', self.vars.value, fact=True)
|
||||
|
||||
...
|
||||
|
||||
def state_absent(self):
|
||||
with self.runner("state key", output_process=self._make_process(False)) as ctx:
|
||||
ctx.run()
|
||||
self.vars.set('run_info', ctx.run_info, verbosity=4)
|
||||
self.vars.set('new_value', None, fact=True)
|
||||
self.vars._value = None
|
||||
|
||||
def state_present(self):
|
||||
with self.runner("direct config_source value_type state key value", output_process=self._make_process(True)) as ctx:
|
||||
ctx.run()
|
||||
self.vars.set('run_info', ctx.run_info, verbosity=4)
|
||||
self.vars.set('new_value', self._get(), fact=True)
|
||||
self.vars._value = self.vars.new_value
|
||||
|
||||
Note that the method ``__run__()`` is implemented in ``StateModuleHelper``, all you need to implement are the methods ``state_<state_value>``.
|
||||
In the example above, :ansplugin:`community.general.gconftool2#module` only has two states, ``present`` and ``absent``, thus, ``state_present()`` and ``state_absent()``.
|
||||
|
||||
If the controlling parameter is not called ``state``, like in :ansplugin:`community.general.jira#module` module, just let SMH know about it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class JIRA(StateModuleHelper):
|
||||
state_param = 'operation'
|
||||
|
||||
def operation_create(self):
|
||||
...
|
||||
|
||||
def operation_search(self):
|
||||
...
|
||||
|
||||
Lastly, if the module is called with ``state=somevalue`` and the method ``state_somevalue``
|
||||
is not implemented, SMH will resort to call a method called ``__state_fallback__()``.
|
||||
By default, this method will raise a ``ValueError`` indicating the method was not found.
|
||||
Naturally, you can override that method to write a default implementation, as in :ansplugin:`community.general.locale_gen#module`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def __state_fallback__(self):
|
||||
if self.vars.state_tracking == self.vars.state:
|
||||
return
|
||||
if self.vars.ubuntu_mode:
|
||||
self.apply_change_ubuntu(self.vars.state, self.vars.name)
|
||||
else:
|
||||
self.apply_change(self.vars.state, self.vars.name)
|
||||
|
||||
That module has only the states ``present`` and ``absent`` and the code for both is the one in the fallback method.
|
||||
|
||||
.. note::
|
||||
|
||||
The name of the fallback method **does not change** if you set a different value of ``state_param``.
|
||||
|
||||
|
||||
Other Conveniences
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Delegations to AnsibleModule
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
The MH properties and methods below are delegated as-is to the underlying ``AnsibleModule`` instance in ``self.module``:
|
||||
|
||||
- ``check_mode``
|
||||
- ``get_bin_path()``
|
||||
- ``warn()``
|
||||
- ``deprecate()``
|
||||
|
||||
Additionally, MH will also delegate:
|
||||
|
||||
- ``diff_mode`` to ``self.module._diff``
|
||||
- ``verbosity`` to ``self.module._verbosity``
|
||||
|
||||
Decorators
|
||||
""""""""""
|
||||
|
||||
The following decorators should only be used within ``ModuleHelper`` class.
|
||||
|
||||
@cause_changes
|
||||
--------------
|
||||
|
||||
This decorator will control whether the outcome of the method will cause the module to signal change in its output.
|
||||
If the method completes without raising an exception it is considered to have succeeded, otherwise, it will have failed.
|
||||
|
||||
The decorator has a parameter ``when`` that accepts three different values: ``success``, ``failure``, and ``always``.
|
||||
There are also two legacy parameters, ``on_success`` and ``on_failure``, that will be deprecated, so do not use them.
|
||||
The value of ``changed`` in the module output will be set to ``True``:
|
||||
|
||||
- ``when="success"`` and the method completes without raising an exception.
|
||||
- ``when="failure"`` and the method raises an exception.
|
||||
- ``when="always"``, regardless of the method raising an exception or not.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import cause_changes
|
||||
|
||||
# adapted excerpt from the community.general.jira module
|
||||
class JIRA(StateModuleHelper):
|
||||
@cause_changes(when="success")
|
||||
def operation_create(self):
|
||||
...
|
||||
|
||||
If ``when`` has a different value or no parameters are specificied, the decorator will have no effect whatsoever.
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_modulehelper.modulefailsdeco:
|
||||
|
||||
@module_fails_on_exception
|
||||
--------------------------
|
||||
|
||||
In a method using this decorator, if an exception is raised, the text message of that exception will be captured
|
||||
by the decorator and used to call ``self.module.fail_json()``.
|
||||
In most of the cases there will be no need to use this decorator, because ``ModuleHelper.run()`` already uses it.
|
||||
|
||||
@check_mode_skip
|
||||
----------------
|
||||
|
||||
If the module is running in check mode, this decorator will prevent the method from executing.
|
||||
The return value in that case is ``None``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import check_mode_skip
|
||||
|
||||
# adapted excerpt from the community.general.locale_gen module
|
||||
class LocaleGen(StateModuleHelper):
|
||||
@check_mode_skip
|
||||
def __state_fallback__(self):
|
||||
...
|
||||
|
||||
|
||||
@check_mode_skip_returns
|
||||
------------------------
|
||||
|
||||
This decorator is similar to the previous one, but the developer can control the return value for the method when running in check mode.
|
||||
It is used with one of two parameters. One is ``callable`` and the return value in check mode will be ``callable(self, *args, **kwargs)``,
|
||||
where ``self`` is the ``ModuleHelper`` instance and the union of ``args`` and ``kwargs`` will contain all the parameters passed to the method.
|
||||
|
||||
The other option is to use the parameter ``value``, in which case the method will return ``value`` when in check mode.
|
||||
|
||||
|
||||
References
|
||||
^^^^^^^^^^
|
||||
|
||||
- `Ansible Developer Guide <https://docs.ansible.com/ansible/latest/dev_guide/index.html>`_
|
||||
- `Creating a module <https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#creating-a-module>`_
|
||||
- `Returning ansible facts <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#ansible-facts>`_
|
||||
- :ref:`ansible_collections.community.general.docsite.guide_vardict`
|
||||
|
||||
|
||||
.. versionadded:: 3.1.0
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 9.3.0
|
||||
version: 9.5.2
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
@@ -75,6 +75,10 @@ plugin_routing:
|
||||
deprecation:
|
||||
removal_version: 10.0.0
|
||||
warning_text: Use community.general.consul_token and/or community.general.consul_policy instead.
|
||||
hipchat:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
rax_cbs_attachments:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
|
||||
2
plugins/cache/redis.py
vendored
2
plugins/cache/redis.py
vendored
@@ -227,7 +227,7 @@ class CacheModule(BaseCacheModule):
|
||||
|
||||
def copy(self):
|
||||
# TODO: there is probably a better way to do this in redis
|
||||
ret = dict([(k, self.get(k)) for k in self.keys()])
|
||||
ret = {k: self.get(k) for k in self.keys()}
|
||||
return ret
|
||||
|
||||
def __getstate__(self):
|
||||
|
||||
42
plugins/doc_fragments/pipx.py
Normal file
42
plugins/doc_fragments/pipx.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Alexei Znamensky <russoz@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
global:
|
||||
description:
|
||||
- The module will pass the C(--global) argument to C(pipx), to execute actions in global scope.
|
||||
- The C(--global) is only available in C(pipx>=1.6.0), so make sure to have a compatible version when using this option.
|
||||
Moreover, a nasty bug with C(--global) was fixed in C(pipx==1.7.0), so it is strongly recommended you used that version or newer.
|
||||
type: bool
|
||||
default: false
|
||||
executable:
|
||||
description:
|
||||
- Path to the C(pipx) installed in the system.
|
||||
- >
|
||||
If not specified, the module will use C(python -m pipx) to run the tool,
|
||||
using the same Python interpreter as ansible itself.
|
||||
type: path
|
||||
notes:
|
||||
- This module requires C(pipx) version 0.16.2.1 or above. From community.general 11.0.0 onwards, the module will require C(pipx>=1.7.0).
|
||||
- Please note that C(pipx) requires Python 3.6 or above.
|
||||
- This module does not install the C(pipx) python package, however that can be easily done with the module M(ansible.builtin.pip).
|
||||
- This module does not require C(pipx) to be in the shell C(PATH), but it must be loadable by Python as a module.
|
||||
- >
|
||||
This module will honor C(pipx) environment variables such as but not limited to E(PIPX_HOME) and E(PIPX_BIN_DIR)
|
||||
passed using the R(environment Ansible keyword, playbooks_environment).
|
||||
|
||||
seealso:
|
||||
- name: C(pipx) command manual page
|
||||
description: Manual page for the command.
|
||||
link: https://pipx.pypa.io/latest/docs/
|
||||
|
||||
'''
|
||||
@@ -57,8 +57,8 @@ EXAMPLES = '''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The dictionary having the provided key-value pairs.
|
||||
type: boolean
|
||||
description: A dictionary with the provided key-value pairs.
|
||||
type: dictionary
|
||||
'''
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ DOCUMENTATION = '''
|
||||
author: Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Transform a sequence of dictionaries to a dictionary where the dictionaries are indexed by an attribute.
|
||||
- This filter is similar to the Jinja2 C(groupby) filter. Use the Jinja2 C(groupby) filter if you have multiple entries with the same value,
|
||||
or when you need a dictionary with list values, or when you need to use deeply nested attributes.
|
||||
positional: attribute
|
||||
options:
|
||||
_input:
|
||||
|
||||
@@ -27,7 +27,7 @@ def initialize_hashids(**kwargs):
|
||||
if not HAS_HASHIDS:
|
||||
raise AnsibleError("The hashids library must be installed in order to use this plugin")
|
||||
|
||||
params = dict((k, v) for k, v in kwargs.items() if v)
|
||||
params = {k: v for k, v in kwargs.items() if v}
|
||||
|
||||
try:
|
||||
return Hashids(**params)
|
||||
|
||||
@@ -127,7 +127,7 @@ def keep_keys(data, target=None, matching_parameter='equal'):
|
||||
def keep_key(key):
|
||||
return tt.match(key) is not None
|
||||
|
||||
return [dict((k, v) for k, v in d.items() if keep_key(k)) for d in data]
|
||||
return [{k: v for k, v in d.items() if keep_key(k)} for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
@@ -127,7 +127,7 @@ def remove_keys(data, target=None, matching_parameter='equal'):
|
||||
def keep_key(key):
|
||||
return tt.match(key) is None
|
||||
|
||||
return [dict((k, v) for k, v in d.items() if keep_key(k)) for d in data]
|
||||
return [{k: v for k, v in d.items() if keep_key(k)} for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
@@ -169,7 +169,7 @@ def replace_keys(data, target=None, matching_parameter='equal'):
|
||||
return a
|
||||
return key
|
||||
|
||||
return [dict((replace_key(k), v) for k, v in d.items()) for d in data]
|
||||
return [{replace_key(k): v for k, v in d.items()} for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
@@ -199,6 +199,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
continue
|
||||
|
||||
server['name'] = vm.NAME
|
||||
server['id'] = vm.ID
|
||||
if hasattr(vm.HISTORY_RECORDS, 'HISTORY') and vm.HISTORY_RECORDS.HISTORY:
|
||||
server['host'] = vm.HISTORY_RECORDS.HISTORY[-1].HOSTNAME
|
||||
server['LABELS'] = labels
|
||||
server['v4_first_ip'] = self._get_vm_ipv4(vm)
|
||||
server['v6_first_ip'] = self._get_vm_ipv6(vm)
|
||||
|
||||
@@ -275,18 +275,19 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
return self.session
|
||||
|
||||
def _get_auth(self):
|
||||
credentials = urlencode({'username': self.proxmox_user, 'password': self.proxmox_password, })
|
||||
|
||||
validate_certs = self.get_option('validate_certs')
|
||||
|
||||
if validate_certs is False:
|
||||
from requests.packages.urllib3 import disable_warnings
|
||||
disable_warnings()
|
||||
|
||||
if self.proxmox_password:
|
||||
|
||||
credentials = urlencode({'username': self.proxmox_user, 'password': self.proxmox_password, })
|
||||
credentials = urlencode({'username': self.proxmox_user, 'password': self.proxmox_password})
|
||||
|
||||
a = self._get_session()
|
||||
|
||||
if a.verify is False:
|
||||
from requests.packages.urllib3 import disable_warnings
|
||||
disable_warnings()
|
||||
|
||||
ret = a.post('%s/api2/json/access/ticket' % self.proxmox_url, data=credentials)
|
||||
|
||||
json = ret.json()
|
||||
@@ -329,8 +330,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
data = json['data']
|
||||
break
|
||||
else:
|
||||
# /hosts 's 'results' is a list of all hosts, returned is paginated
|
||||
data = data + json['data']
|
||||
if json['data']:
|
||||
# /hosts 's 'results' is a list of all hosts, returned is paginated
|
||||
data = data + json['data']
|
||||
break
|
||||
|
||||
self._cache[self.cache_key][url] = data
|
||||
|
||||
@@ -77,6 +77,8 @@ from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.parsing.ajson import AnsibleJSONDecoder
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
|
||||
class BitwardenSecretsManagerException(AnsibleLookupError):
|
||||
pass
|
||||
@@ -114,6 +116,15 @@ class BitwardenSecretsManager(object):
|
||||
rc = p.wait()
|
||||
return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict'), rc
|
||||
|
||||
def get_bws_version(self):
|
||||
"""Get the version of the Bitwarden Secrets Manager CLI.
|
||||
"""
|
||||
out, err, rc = self._run(['--version'])
|
||||
if rc != 0:
|
||||
raise BitwardenSecretsManagerException(to_text(err))
|
||||
# strip the prefix and grab the last segment, the version number
|
||||
return out.split()[-1]
|
||||
|
||||
def get_secret(self, secret_id, bws_access_token):
|
||||
"""Get and return the secret with the given secret_id.
|
||||
"""
|
||||
@@ -122,10 +133,18 @@ class BitwardenSecretsManager(object):
|
||||
# Color output was not always disabled correctly with the default 'auto' setting so explicitly disable it.
|
||||
params = [
|
||||
'--color', 'no',
|
||||
'--access-token', bws_access_token,
|
||||
'get', 'secret', secret_id
|
||||
'--access-token', bws_access_token
|
||||
]
|
||||
|
||||
# bws version 0.3.0 introduced a breaking change in the command line syntax:
|
||||
# pre-0.3.0: verb noun
|
||||
# 0.3.0 and later: noun verb
|
||||
bws_version = self.get_bws_version()
|
||||
if LooseVersion(bws_version) < LooseVersion('0.3.0'):
|
||||
params.extend(['get', 'secret', secret_id])
|
||||
else:
|
||||
params.extend(['secret', 'get', secret_id])
|
||||
|
||||
out, err, rc = self._run_with_retry(params)
|
||||
if rc != 0:
|
||||
raise BitwardenSecretsManagerException(to_text(err))
|
||||
|
||||
@@ -63,11 +63,11 @@ RETURN = """
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from importlib import import_module
|
||||
|
||||
import yaml
|
||||
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.compat.importlib import import_module
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
|
||||
@@ -120,10 +120,10 @@ class LookupModule(LookupBase):
|
||||
aws_secret_access_key = self.get_option('aws_secret_access_key')
|
||||
aws_session_token = self.get_option('aws_session_token')
|
||||
|
||||
context = dict(
|
||||
(k, v) for k, v in kwargs.items()
|
||||
context = {
|
||||
k: v for k, v in kwargs.items()
|
||||
if k not in ('version', 'region', 'table', 'profile_name', 'aws_access_key_id', 'aws_secret_access_key', 'aws_session_token')
|
||||
)
|
||||
}
|
||||
|
||||
kwargs_pass = {
|
||||
'profile_name': profile_name,
|
||||
|
||||
@@ -75,6 +75,11 @@ DOCUMENTATION = '''
|
||||
default: false
|
||||
type: bool
|
||||
version_added: 7.5.0
|
||||
port:
|
||||
description: Use port as target port when looking up DNS records.
|
||||
default: 53
|
||||
type: int
|
||||
version_added: 9.5.0
|
||||
notes:
|
||||
- ALL is not a record per-se, merely the listed fields are available for any record results you retrieve in the form of a dictionary.
|
||||
- While the 'dig' lookup plugin supports anything which dnspython supports out of the box, only a subset can be converted into a dictionary.
|
||||
@@ -330,11 +335,13 @@ class LookupModule(LookupBase):
|
||||
myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size)
|
||||
|
||||
domains = []
|
||||
nameservers = []
|
||||
qtype = self.get_option('qtype')
|
||||
flat = self.get_option('flat')
|
||||
fail_on_error = self.get_option('fail_on_error')
|
||||
real_empty = self.get_option('real_empty')
|
||||
tcp = self.get_option('tcp')
|
||||
port = self.get_option('port')
|
||||
try:
|
||||
rdclass = dns.rdataclass.from_text(self.get_option('class'))
|
||||
except Exception as e:
|
||||
@@ -345,7 +352,6 @@ class LookupModule(LookupBase):
|
||||
if t.startswith('@'): # e.g. "@10.0.1.2,192.0.2.1" is ok.
|
||||
nsset = t[1:].split(',')
|
||||
for ns in nsset:
|
||||
nameservers = []
|
||||
# Check if we have a valid IP address. If so, use that, otherwise
|
||||
# try to resolve name to address using system's resolver. If that
|
||||
# fails we bail out.
|
||||
@@ -358,7 +364,6 @@ class LookupModule(LookupBase):
|
||||
nameservers.append(nsaddr)
|
||||
except Exception as e:
|
||||
raise AnsibleError("dns lookup NS: %s" % to_native(e))
|
||||
myres.nameservers = nameservers
|
||||
continue
|
||||
if '=' in t:
|
||||
try:
|
||||
@@ -397,6 +402,11 @@ class LookupModule(LookupBase):
|
||||
|
||||
# print "--- domain = {0} qtype={1} rdclass={2}".format(domain, qtype, rdclass)
|
||||
|
||||
if port:
|
||||
myres.port = port
|
||||
if len(nameservers) > 0:
|
||||
myres.nameservers = nameservers
|
||||
|
||||
if qtype.upper() == 'PTR':
|
||||
reversed_domains = []
|
||||
for domain in domains:
|
||||
|
||||
@@ -49,8 +49,8 @@ EXAMPLES = '''
|
||||
dest: /srv/checkout
|
||||
vars:
|
||||
github_token: >-
|
||||
lookup('community.general.github_app_access_token', key_path='/home/to_your/key',
|
||||
app_id='123456', installation_id='64209')
|
||||
{{ lookup('community.general.github_app_access_token', key_path='/home/to_your/key',
|
||||
app_id='123456', installation_id='64209') }}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
||||
@@ -135,7 +135,7 @@ class OnePassCLIBase(with_metaclass(abc.ABCMeta, object)):
|
||||
self._version = None
|
||||
|
||||
def _check_required_params(self, required_params):
|
||||
non_empty_attrs = dict((param, getattr(self, param, None)) for param in required_params if getattr(self, param, None))
|
||||
non_empty_attrs = {param: getattr(self, param) for param in required_params if getattr(self, param, None)}
|
||||
missing = set(required_params).difference(non_empty_attrs)
|
||||
if missing:
|
||||
prefix = "Unable to sign in to 1Password. Missing required parameter"
|
||||
|
||||
@@ -14,7 +14,7 @@ DOCUMENTATION = '''
|
||||
short_description: manage passwords with passwordstore.org's pass utility
|
||||
description:
|
||||
- Enables Ansible to retrieve, create or update passwords from the passwordstore.org pass utility.
|
||||
It also retrieves YAML style keys stored as multilines in the passwordfile.
|
||||
It can also retrieve, create or update YAML style keys stored as multilines in the passwordfile.
|
||||
- To avoid problems when accessing multiple secrets at once, add C(auto-expand-secmem) to
|
||||
C(~/.gnupg/gpg-agent.conf). Where this is not possible, consider using O(lock=readwrite) instead.
|
||||
options:
|
||||
@@ -33,11 +33,11 @@ DOCUMENTATION = '''
|
||||
env:
|
||||
- name: PASSWORD_STORE_DIR
|
||||
create:
|
||||
description: Create the password if it does not already exist. Takes precedence over O(missing).
|
||||
description: Create the password or the subkey if it does not already exist. Takes precedence over O(missing).
|
||||
type: bool
|
||||
default: false
|
||||
overwrite:
|
||||
description: Overwrite the password if it does already exist.
|
||||
description: Overwrite the password or the subkey if it does already exist.
|
||||
type: bool
|
||||
default: false
|
||||
umask:
|
||||
@@ -53,7 +53,9 @@ DOCUMENTATION = '''
|
||||
type: bool
|
||||
default: false
|
||||
subkey:
|
||||
description: Return a specific subkey of the password. When set to V(password), always returns the first line.
|
||||
description:
|
||||
- By default return a specific subkey of the password. When set to V(password), always returns the first line.
|
||||
- With O(overwrite=true), it will create the subkey and return it.
|
||||
type: str
|
||||
default: password
|
||||
userpass:
|
||||
@@ -64,7 +66,7 @@ DOCUMENTATION = '''
|
||||
type: integer
|
||||
default: 16
|
||||
backup:
|
||||
description: Used with O(overwrite=true). Backup the previous password in a subkey.
|
||||
description: Used with O(overwrite=true). Backup the previous password or subkey in a subkey.
|
||||
type: bool
|
||||
default: false
|
||||
nosymbols:
|
||||
@@ -189,6 +191,17 @@ tasks.yml: |
|
||||
vars:
|
||||
mypassword: "{{ lookup('community.general.passwordstore', 'example/test', missing='create')}}"
|
||||
|
||||
- name: >-
|
||||
Create a random 16 character password in a subkey. If the password file already exists, just add the subkey in it.
|
||||
If the subkey exists, returns it
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.passwordstore', 'example/test', create=true, subkey='foo') }}"
|
||||
|
||||
- name: >-
|
||||
Create a random 16 character password in a subkey. Overwrite if it already exists and backup the old one.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.passwordstore', 'example/test', create=true, subkey='user', overwrite=true, backup=true) }}"
|
||||
|
||||
- name: Prints 'abc' if example/test does not exist, just give the password otherwise
|
||||
ansible.builtin.debug:
|
||||
var: mypassword
|
||||
@@ -411,15 +424,48 @@ class LookupModule(LookupBase):
|
||||
|
||||
def update_password(self):
|
||||
# generate new password, insert old lines from current result and return new password
|
||||
# if the target is a subkey, only modify the subkey
|
||||
newpass = self.get_newpass()
|
||||
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
|
||||
msg = newpass
|
||||
if self.paramvals['preserve'] or self.paramvals['timestamp']:
|
||||
msg += '\n'
|
||||
if self.paramvals['preserve'] and self.passoutput[1:]:
|
||||
msg += '\n'.join(self.passoutput[1:]) + '\n'
|
||||
if self.paramvals['timestamp'] and self.paramvals['backup']:
|
||||
msg += "lookup_pass: old password was {0} (Updated on {1})\n".format(self.password, datetime)
|
||||
subkey = self.paramvals["subkey"]
|
||||
|
||||
if subkey != "password":
|
||||
|
||||
msg_lines = []
|
||||
subkey_exists = False
|
||||
subkey_line = "{0}: {1}".format(subkey, newpass)
|
||||
oldpass = None
|
||||
|
||||
for line in self.passoutput:
|
||||
if line.startswith("{0}: ".format(subkey)):
|
||||
oldpass = self.passdict[subkey]
|
||||
line = subkey_line
|
||||
subkey_exists = True
|
||||
|
||||
msg_lines.append(line)
|
||||
|
||||
if not subkey_exists:
|
||||
msg_lines.insert(2, subkey_line)
|
||||
|
||||
if self.paramvals["timestamp"] and self.paramvals["backup"] and oldpass and oldpass != newpass:
|
||||
msg_lines.append(
|
||||
"lookup_pass: old subkey '{0}' password was {1} (Updated on {2})\n".format(
|
||||
subkey, oldpass, datetime
|
||||
)
|
||||
)
|
||||
|
||||
msg = os.linesep.join(msg_lines)
|
||||
|
||||
else:
|
||||
msg = newpass
|
||||
|
||||
if self.paramvals['preserve'] or self.paramvals['timestamp']:
|
||||
msg += '\n'
|
||||
if self.paramvals['preserve'] and self.passoutput[1:]:
|
||||
msg += '\n'.join(self.passoutput[1:]) + '\n'
|
||||
if self.paramvals['timestamp'] and self.paramvals['backup']:
|
||||
msg += "lookup_pass: old password was {0} (Updated on {1})\n".format(self.password, datetime)
|
||||
|
||||
try:
|
||||
check_output2([self.pass_cmd, 'insert', '-f', '-m', self.passname], input=msg, env=self.env)
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
@@ -431,13 +477,21 @@ class LookupModule(LookupBase):
|
||||
# use pwgen to generate the password and insert values with pass -m
|
||||
newpass = self.get_newpass()
|
||||
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
|
||||
msg = newpass
|
||||
subkey = self.paramvals["subkey"]
|
||||
|
||||
if subkey != "password":
|
||||
msg = "\n\n{0}: {1}".format(subkey, newpass)
|
||||
else:
|
||||
msg = newpass
|
||||
|
||||
if self.paramvals['timestamp']:
|
||||
msg += '\n' + "lookup_pass: First generated by ansible on {0}\n".format(datetime)
|
||||
|
||||
try:
|
||||
check_output2([self.pass_cmd, 'insert', '-f', '-m', self.passname], input=msg, env=self.env)
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
raise AnsibleError('exit code {0} while running {1}. Error output: {2}'.format(e.returncode, e.cmd, e.output))
|
||||
|
||||
return newpass
|
||||
|
||||
def get_passresult(self):
|
||||
@@ -525,7 +579,10 @@ class LookupModule(LookupBase):
|
||||
self.parse_params(term) # parse the input into paramvals
|
||||
with self.opt_lock('readwrite'):
|
||||
if self.check_pass(): # password exists
|
||||
if self.paramvals['overwrite'] and self.paramvals['subkey'] == 'password':
|
||||
if self.paramvals['overwrite']:
|
||||
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
|
||||
with self.opt_lock('write'):
|
||||
result.append(self.update_password())
|
||||
else:
|
||||
|
||||
@@ -104,37 +104,37 @@ EXAMPLES = r"""
|
||||
- name: Generate random string
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string')
|
||||
# Example result: ['DeadBeeF']
|
||||
# Example result: 'DeadBeeF'
|
||||
|
||||
- name: Generate random string with length 12
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string', length=12)
|
||||
# Example result: ['Uan0hUiX5kVG']
|
||||
# Example result: 'Uan0hUiX5kVG'
|
||||
|
||||
- name: Generate base64 encoded random string
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string', base64=True)
|
||||
# Example result: ['NHZ6eWN5Qk0=']
|
||||
# Example result: 'NHZ6eWN5Qk0='
|
||||
|
||||
- name: Generate a random string with 1 lower, 1 upper, 1 number and 1 special char (at least)
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string', min_lower=1, min_upper=1, min_special=1, min_numeric=1)
|
||||
# Example result: ['&Qw2|E[-']
|
||||
# Example result: '&Qw2|E[-'
|
||||
|
||||
- name: Generate a random string with all lower case characters
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: query('community.general.random_string', upper=false, numbers=false, special=false)
|
||||
# Example result: ['exolxzyz']
|
||||
|
||||
- name: Generate random hexadecimal string
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: query('community.general.random_string', upper=false, lower=false, override_special=hex_chars, numbers=false)
|
||||
vars:
|
||||
hex_chars: '0123456789ABCDEF'
|
||||
# Example result: ['D2A40737']
|
||||
|
||||
- name: Generate random hexadecimal string with override_all
|
||||
debug:
|
||||
ansible.builtin.debug:
|
||||
var: query('community.general.random_string', override_all=hex_chars)
|
||||
vars:
|
||||
hex_chars: '0123456789ABCDEF'
|
||||
|
||||
@@ -239,7 +239,7 @@ class CmdRunner(object):
|
||||
self.check_rc = check_rc
|
||||
if force_lang == "auto":
|
||||
try:
|
||||
self.force_lang = get_best_parsable_locale()
|
||||
self.force_lang = get_best_parsable_locale(module)
|
||||
except RuntimeWarning:
|
||||
self.force_lang = "C"
|
||||
else:
|
||||
|
||||
@@ -43,7 +43,7 @@ def initialize_dialect(dialect, **kwargs):
|
||||
raise DialectNotAvailableError("Dialect '%s' is not supported by your version of python." % dialect)
|
||||
|
||||
# Create a dictionary from only set options
|
||||
dialect_params = dict((k, v) for k, v in kwargs.items() if v is not None)
|
||||
dialect_params = {k: v for k, v in kwargs.items() if v is not None}
|
||||
if dialect_params:
|
||||
try:
|
||||
csv.register_dialect('custom', dialect, **dialect_params)
|
||||
|
||||
@@ -185,8 +185,7 @@ def get_token(module_params):
|
||||
'password': auth_password,
|
||||
}
|
||||
# Remove empty items, for instance missing client_secret
|
||||
payload = dict(
|
||||
(k, v) for k, v in temp_payload.items() if v is not None)
|
||||
payload = {k: v for k, v in temp_payload.items() if v is not None}
|
||||
try:
|
||||
r = json.loads(to_native(open_url(auth_url, method='POST',
|
||||
validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout,
|
||||
@@ -1500,6 +1499,23 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg="Could not fetch group %s in realm %s: %s"
|
||||
% (gid, realm, str(e)))
|
||||
|
||||
def get_subgroups(self, parent, realm="master"):
|
||||
if 'subGroupCount' in parent:
|
||||
# Since version 23, when GETting a group Keycloak does not
|
||||
# return subGroups but only a subGroupCount.
|
||||
# Children must be fetched in a second request.
|
||||
if parent['subGroupCount'] == 0:
|
||||
group_children = []
|
||||
else:
|
||||
group_children_url = URL_GROUP_CHILDREN.format(url=self.baseurl, realm=realm, groupid=parent['id'])
|
||||
group_children = json.loads(to_native(open_url(group_children_url, method="GET", http_agent=self.http_agent, headers=self.restheaders,
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
subgroups = group_children
|
||||
else:
|
||||
subgroups = parent['subGroups']
|
||||
return subgroups
|
||||
|
||||
def get_group_by_name(self, name, realm="master", parents=None):
|
||||
""" Fetch a keycloak group within a realm based on its name.
|
||||
|
||||
@@ -1520,7 +1536,7 @@ class KeycloakAPI(object):
|
||||
if not parent:
|
||||
return None
|
||||
|
||||
all_groups = parent['subGroups']
|
||||
all_groups = self.get_subgroups(parent, realm)
|
||||
else:
|
||||
all_groups = self.get_groups(realm=realm)
|
||||
|
||||
@@ -1569,7 +1585,7 @@ class KeycloakAPI(object):
|
||||
return None
|
||||
|
||||
for p in name_chain[1:]:
|
||||
for sg in tmp['subGroups']:
|
||||
for sg in self.get_subgroups(tmp):
|
||||
pv, is_id = self._get_normed_group_parent(p)
|
||||
|
||||
if is_id:
|
||||
|
||||
@@ -13,23 +13,27 @@ from functools import wraps
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ModuleHelperException
|
||||
|
||||
|
||||
def cause_changes(on_success=None, on_failure=None):
|
||||
def cause_changes(on_success=None, on_failure=None, when=None):
|
||||
# Parameters on_success and on_failure are deprecated and should be removed in community.general 12.0.0
|
||||
|
||||
def deco(func):
|
||||
if on_success is None and on_failure is None:
|
||||
return func
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
self = args[0]
|
||||
func(*args, **kwargs)
|
||||
func(self, *args, **kwargs)
|
||||
if on_success is not None:
|
||||
self.changed = on_success
|
||||
elif when == "success":
|
||||
self.changed = True
|
||||
except Exception:
|
||||
if on_failure is not None:
|
||||
self.changed = on_failure
|
||||
elif when == "failure":
|
||||
self.changed = True
|
||||
raise
|
||||
finally:
|
||||
if when == "always":
|
||||
self.changed = True
|
||||
|
||||
return wrapper
|
||||
|
||||
@@ -41,17 +45,15 @@ def module_fails_on_exception(func):
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
def fix_key(k):
|
||||
return k if k not in conflict_list else "_" + k
|
||||
|
||||
def fix_var_conflicts(output):
|
||||
result = dict([
|
||||
(k if k not in conflict_list else "_" + k, v)
|
||||
for k, v in output.items()
|
||||
])
|
||||
result = {fix_key(k): v for k, v in output.items()}
|
||||
return result
|
||||
|
||||
try:
|
||||
func(self, *args, **kwargs)
|
||||
except SystemExit:
|
||||
raise
|
||||
except ModuleHelperException as e:
|
||||
if e.update_output:
|
||||
self.update_output(e.update_output)
|
||||
@@ -73,6 +75,7 @@ def check_mode_skip(func):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if not self.module.check_mode:
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -87,7 +90,7 @@ def check_mode_skip_returns(callable=None, value=None):
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper_callable
|
||||
|
||||
if value is not None:
|
||||
else:
|
||||
@wraps(func)
|
||||
def wrapper_value(self, *args, **kwargs):
|
||||
if self.module.check_mode:
|
||||
@@ -95,7 +98,4 @@ def check_mode_skip_returns(callable=None, value=None):
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper_value
|
||||
|
||||
if callable is None and value is None:
|
||||
return check_mode_skip
|
||||
|
||||
return deco
|
||||
|
||||
@@ -113,7 +113,7 @@ class VarDict(object):
|
||||
self._meta[name] = meta
|
||||
|
||||
def output(self):
|
||||
return dict((k, v) for k, v in self._data.items() if self.meta(k).output)
|
||||
return {k: v for k, v in self._data.items() if self.meta(k).output}
|
||||
|
||||
def diff(self):
|
||||
diff_results = [(k, self.meta(k).diff_result) for k in self._data]
|
||||
@@ -125,7 +125,7 @@ class VarDict(object):
|
||||
return None
|
||||
|
||||
def facts(self):
|
||||
facts_result = dict((k, v) for k, v in self._data.items() if self._meta[k].fact)
|
||||
facts_result = {k: v for k, v in self._data.items() if self._meta[k].fact}
|
||||
return facts_result if facts_result else None
|
||||
|
||||
def change_vars(self):
|
||||
|
||||
@@ -56,7 +56,7 @@ class OcapiUtils(object):
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
data = json.loads(to_native(resp.read()))
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
except HTTPError as e:
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on GET request to '%s'"
|
||||
@@ -86,7 +86,7 @@ class OcapiUtils(object):
|
||||
data = json.loads(to_native(resp.read()))
|
||||
else:
|
||||
data = ""
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
except HTTPError as e:
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on DELETE request to '%s'"
|
||||
@@ -113,7 +113,7 @@ class OcapiUtils(object):
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
except HTTPError as e:
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on PUT request to '%s'"
|
||||
@@ -144,7 +144,7 @@ class OcapiUtils(object):
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout if timeout is None else timeout)
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
except HTTPError as e:
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on POST request to '%s'"
|
||||
|
||||
@@ -16,6 +16,7 @@ from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS', 'LOCKED_USED', 'LOCKED_USED_PERS']
|
||||
HAS_PYONE = True
|
||||
|
||||
try:
|
||||
@@ -347,3 +348,90 @@ class OpenNebulaModule:
|
||||
result: the Ansible result
|
||||
"""
|
||||
raise NotImplementedError("Method requires implementation")
|
||||
|
||||
def get_image_list_id(self, image, element):
|
||||
"""
|
||||
This is a helper function for get_image_info to iterate over a simple list of objects
|
||||
"""
|
||||
list_of_id = []
|
||||
|
||||
if element == 'VMS':
|
||||
image_list = image.VMS
|
||||
if element == 'CLONES':
|
||||
image_list = image.CLONES
|
||||
if element == 'APP_CLONES':
|
||||
image_list = image.APP_CLONES
|
||||
|
||||
for iter in image_list.ID:
|
||||
list_of_id.append(
|
||||
# These are optional so firstly check for presence
|
||||
getattr(iter, 'ID', 'Null'),
|
||||
)
|
||||
return list_of_id
|
||||
|
||||
def get_image_snapshots_list(self, image):
|
||||
"""
|
||||
This is a helper function for get_image_info to iterate over a dictionary
|
||||
"""
|
||||
list_of_snapshots = []
|
||||
|
||||
for iter in image.SNAPSHOTS.SNAPSHOT:
|
||||
list_of_snapshots.append({
|
||||
'date': iter['DATE'],
|
||||
'parent': iter['PARENT'],
|
||||
'size': iter['SIZE'],
|
||||
# These are optional so firstly check for presence
|
||||
'allow_orhans': getattr(image.SNAPSHOTS, 'ALLOW_ORPHANS', 'Null'),
|
||||
'children': getattr(iter, 'CHILDREN', 'Null'),
|
||||
'active': getattr(iter, 'ACTIVE', 'Null'),
|
||||
'name': getattr(iter, 'NAME', 'Null'),
|
||||
})
|
||||
return list_of_snapshots
|
||||
|
||||
def get_image_info(self, image):
|
||||
"""
|
||||
This method is used by one_image and one_image_info modules to retrieve
|
||||
information from XSD scheme of an image
|
||||
Returns: a copy of the parameters that includes the resolved parameters.
|
||||
"""
|
||||
info = {
|
||||
'id': image.ID,
|
||||
'name': image.NAME,
|
||||
'state': IMAGE_STATES[image.STATE],
|
||||
'running_vms': image.RUNNING_VMS,
|
||||
'used': bool(image.RUNNING_VMS),
|
||||
'user_name': image.UNAME,
|
||||
'user_id': image.UID,
|
||||
'group_name': image.GNAME,
|
||||
'group_id': image.GID,
|
||||
'permissions': {
|
||||
'owner_u': image.PERMISSIONS.OWNER_U,
|
||||
'owner_m': image.PERMISSIONS.OWNER_M,
|
||||
'owner_a': image.PERMISSIONS.OWNER_A,
|
||||
'group_u': image.PERMISSIONS.GROUP_U,
|
||||
'group_m': image.PERMISSIONS.GROUP_M,
|
||||
'group_a': image.PERMISSIONS.GROUP_A,
|
||||
'other_u': image.PERMISSIONS.OTHER_U,
|
||||
'other_m': image.PERMISSIONS.OTHER_M,
|
||||
'other_a': image.PERMISSIONS.OTHER_A
|
||||
},
|
||||
'type': image.TYPE,
|
||||
'disk_type': image.DISK_TYPE,
|
||||
'persistent': image.PERSISTENT,
|
||||
'regtime': image.REGTIME,
|
||||
'source': image.SOURCE,
|
||||
'path': image.PATH,
|
||||
'fstype': getattr(image, 'FSTYPE', 'Null'),
|
||||
'size': image.SIZE,
|
||||
'cloning_ops': image.CLONING_OPS,
|
||||
'cloning_id': image.CLONING_ID,
|
||||
'target_snapshot': image.TARGET_SNAPSHOT,
|
||||
'datastore_id': image.DATASTORE_ID,
|
||||
'datastore': image.DATASTORE,
|
||||
'vms': self.get_image_list_id(image, 'VMS'),
|
||||
'clones': self.get_image_list_id(image, 'CLONES'),
|
||||
'app_clones': self.get_image_list_id(image, 'APP_CLONES'),
|
||||
'snapshots': self.get_image_snapshots_list(image),
|
||||
'template': image.TEMPLATE,
|
||||
}
|
||||
return info
|
||||
|
||||
@@ -9,41 +9,55 @@ __metaclass__ = type
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt as fmt
|
||||
|
||||
|
||||
pipx_common_argspec = {
|
||||
"global": dict(type='bool', default=False),
|
||||
"executable": dict(type='path'),
|
||||
}
|
||||
|
||||
|
||||
_state_map = dict(
|
||||
install='install',
|
||||
install_all='install-all',
|
||||
present='install',
|
||||
uninstall='uninstall',
|
||||
absent='uninstall',
|
||||
uninstall_all='uninstall-all',
|
||||
inject='inject',
|
||||
uninject='uninject',
|
||||
upgrade='upgrade',
|
||||
upgrade_shared='upgrade-shared',
|
||||
upgrade_all='upgrade-all',
|
||||
reinstall='reinstall',
|
||||
reinstall_all='reinstall-all',
|
||||
pin='pin',
|
||||
unpin='unpin',
|
||||
)
|
||||
|
||||
|
||||
def pipx_runner(module, command, **kwargs):
|
||||
arg_formats = dict(
|
||||
state=fmt.as_map(_state_map),
|
||||
name=fmt.as_list(),
|
||||
name_source=fmt.as_func(fmt.unpack_args(lambda n, s: [s] if s else [n])),
|
||||
install_apps=fmt.as_bool("--include-apps"),
|
||||
install_deps=fmt.as_bool("--include-deps"),
|
||||
inject_packages=fmt.as_list(),
|
||||
force=fmt.as_bool("--force"),
|
||||
include_injected=fmt.as_bool("--include-injected"),
|
||||
index_url=fmt.as_opt_val('--index-url'),
|
||||
python=fmt.as_opt_val('--python'),
|
||||
system_site_packages=fmt.as_bool("--system-site-packages"),
|
||||
_list=fmt.as_fixed(['list', '--include-injected', '--json']),
|
||||
editable=fmt.as_bool("--editable"),
|
||||
pip_args=fmt.as_opt_eq_val('--pip-args'),
|
||||
suffix=fmt.as_opt_val('--suffix'),
|
||||
)
|
||||
arg_formats["global"] = fmt.as_bool("--global")
|
||||
|
||||
runner = CmdRunner(
|
||||
module,
|
||||
command=command,
|
||||
arg_formats=dict(
|
||||
state=fmt.as_map(_state_map),
|
||||
name=fmt.as_list(),
|
||||
name_source=fmt.as_func(fmt.unpack_args(lambda n, s: [s] if s else [n])),
|
||||
install_apps=fmt.as_bool("--include-apps"),
|
||||
install_deps=fmt.as_bool("--include-deps"),
|
||||
inject_packages=fmt.as_list(),
|
||||
force=fmt.as_bool("--force"),
|
||||
include_injected=fmt.as_bool("--include-injected"),
|
||||
index_url=fmt.as_opt_val('--index-url'),
|
||||
python=fmt.as_opt_val('--python'),
|
||||
system_site_packages=fmt.as_bool("--system-site-packages"),
|
||||
_list=fmt.as_fixed(['list', '--include-injected', '--json']),
|
||||
editable=fmt.as_bool("--editable"),
|
||||
pip_args=fmt.as_opt_eq_val('--pip-args'),
|
||||
suffix=fmt.as_opt_val('--suffix'),
|
||||
),
|
||||
arg_formats=arg_formats,
|
||||
environ_update={'USE_EMOJI': '0'},
|
||||
check_rc=True,
|
||||
**kwargs
|
||||
|
||||
@@ -22,10 +22,12 @@ class PythonRunner(CmdRunner):
|
||||
if (os.path.isabs(python) or '/' in python):
|
||||
self.python = python
|
||||
elif self.has_venv:
|
||||
path_prefix = os.path.join(venv, "bin")
|
||||
if path_prefix is None:
|
||||
path_prefix = []
|
||||
path_prefix.append(os.path.join(venv, "bin"))
|
||||
if environ_update is None:
|
||||
environ_update = {}
|
||||
environ_update["PATH"] = "%s:%s" % (path_prefix, os.environ["PATH"])
|
||||
environ_update["PATH"] = "%s:%s" % (":".join(path_prefix), os.environ["PATH"])
|
||||
environ_update["VIRTUAL_ENV"] = venv
|
||||
|
||||
python_cmd = [self.python] + _ensure_list(command)
|
||||
|
||||
@@ -151,7 +151,7 @@ class RedfishUtils(object):
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=timeout, ciphers=self.ciphers)
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
try:
|
||||
if headers.get('content-encoding') == 'gzip' and LooseVersion(ansible_version) < LooseVersion('2.14'):
|
||||
# Older versions of Ansible do not automatically decompress the data
|
||||
@@ -165,11 +165,11 @@ class RedfishUtils(object):
|
||||
if not allow_no_resp:
|
||||
raise
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on GET request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on GET request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
@@ -206,13 +206,13 @@ class RedfishUtils(object):
|
||||
except Exception as e:
|
||||
# No response data; this is okay in many cases
|
||||
data = None
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on POST request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on POST request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
@@ -256,11 +256,11 @@ class RedfishUtils(object):
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False, 'changed': False,
|
||||
'msg': "HTTP Error %s on PATCH request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'changed': False,
|
||||
'msg': "URL Error on PATCH request to '%s': '%s'" % (uri, e.reason)}
|
||||
@@ -291,11 +291,11 @@ class RedfishUtils(object):
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on PUT request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on PUT request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
@@ -317,11 +317,11 @@ class RedfishUtils(object):
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on DELETE request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on DELETE request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
@@ -391,8 +391,10 @@ class RedfishUtils(object):
|
||||
:param error: an HTTPError exception
|
||||
:type error: HTTPError
|
||||
:return: the ExtendedInfo message if present, else standard HTTP error
|
||||
:return: the JSON data of the response if present
|
||||
"""
|
||||
msg = http_client.responses.get(error.code, '')
|
||||
data = None
|
||||
if error.code >= 400:
|
||||
try:
|
||||
body = error.read().decode('utf-8')
|
||||
@@ -406,7 +408,7 @@ class RedfishUtils(object):
|
||||
msg = str(data['error']['@Message.ExtendedInfo'])
|
||||
except Exception:
|
||||
pass
|
||||
return msg
|
||||
return msg, data
|
||||
|
||||
def _init_session(self):
|
||||
pass
|
||||
@@ -610,12 +612,13 @@ class RedfishUtils(object):
|
||||
data = response['data']
|
||||
if 'Parameters' in data:
|
||||
params = data['Parameters']
|
||||
ai = dict((p['Name'], p)
|
||||
for p in params if 'Name' in p)
|
||||
ai = {p['Name']: p for p in params if 'Name' in p}
|
||||
if not ai:
|
||||
ai = dict((k[:-24],
|
||||
{'AllowableValues': v}) for k, v in action.items()
|
||||
if k.endswith('@Redfish.AllowableValues'))
|
||||
ai = {
|
||||
k[:-24]: {'AllowableValues': v}
|
||||
for k, v in action.items()
|
||||
if k.endswith('@Redfish.AllowableValues')
|
||||
}
|
||||
return ai
|
||||
|
||||
def _get_allowable_values(self, action, name, default_values=None):
|
||||
@@ -692,7 +695,7 @@ class RedfishUtils(object):
|
||||
entry[prop] = logEntry.get(prop)
|
||||
if entry:
|
||||
list_of_log_entries.append(entry)
|
||||
log_name = log_svcs_uri.split('/')[-1]
|
||||
log_name = log_svcs_uri.rstrip('/').split('/')[-1]
|
||||
logs[log_name] = list_of_log_entries
|
||||
list_of_logs.append(logs)
|
||||
|
||||
@@ -863,6 +866,7 @@ class RedfishUtils(object):
|
||||
return response
|
||||
data = response['data']
|
||||
controller_name = 'Controller 1'
|
||||
storage_id = data['Id']
|
||||
if 'Controllers' in data:
|
||||
controllers_uri = data['Controllers'][u'@odata.id']
|
||||
|
||||
@@ -897,6 +901,7 @@ class RedfishUtils(object):
|
||||
data = response['data']
|
||||
|
||||
drive_result = {}
|
||||
drive_result['RedfishURI'] = data['@odata.id']
|
||||
for property in properties:
|
||||
if property in data:
|
||||
if data[property] is not None:
|
||||
@@ -908,6 +913,7 @@ class RedfishUtils(object):
|
||||
drive_result[property] = data[property]
|
||||
drive_results.append(drive_result)
|
||||
drives = {'Controller': controller_name,
|
||||
'StorageId': storage_id,
|
||||
'Drives': drive_results}
|
||||
result["entries"].append(drives)
|
||||
|
||||
@@ -1046,7 +1052,7 @@ class RedfishUtils(object):
|
||||
if 'Drives' in data[u'Links']:
|
||||
for link in data[u'Links'][u'Drives']:
|
||||
drive_id_link = link[u'@odata.id']
|
||||
drive_id = drive_id_link.split("/")[-1]
|
||||
drive_id = drive_id_link.rstrip('/').split('/')[-1]
|
||||
drive_id_list.append({'Id': drive_id})
|
||||
volume_result['Linked_drives'] = drive_id_list
|
||||
volume_results.append(volume_result)
|
||||
@@ -1244,32 +1250,49 @@ class RedfishUtils(object):
|
||||
return response
|
||||
return {'ret': True, 'changed': True}
|
||||
|
||||
def _find_account_uri(self, username=None, acct_id=None):
|
||||
def _find_account_uri(self, username=None, acct_id=None, password_change_uri=None):
|
||||
if not any((username, acct_id)):
|
||||
return {'ret': False, 'msg':
|
||||
'Must provide either account_id or account_username'}
|
||||
|
||||
response = self.get_request(self.root_uri + self.accounts_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
|
||||
uris = [a.get('@odata.id') for a in data.get('Members', []) if
|
||||
a.get('@odata.id')]
|
||||
for uri in uris:
|
||||
response = self.get_request(self.root_uri + uri)
|
||||
if password_change_uri:
|
||||
# Password change required; go directly to the specified URI
|
||||
response = self.get_request(self.root_uri + password_change_uri)
|
||||
if response['ret'] is False:
|
||||
continue
|
||||
return response
|
||||
data = response['data']
|
||||
headers = response['headers']
|
||||
if username:
|
||||
if username == data.get('UserName'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
'headers': headers, 'uri': password_change_uri}
|
||||
if acct_id:
|
||||
if acct_id == data.get('Id'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
'headers': headers, 'uri': password_change_uri}
|
||||
else:
|
||||
# Walk the accounts collection to find the desired user
|
||||
response = self.get_request(self.root_uri + self.accounts_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
|
||||
uris = [a.get('@odata.id') for a in data.get('Members', []) if
|
||||
a.get('@odata.id')]
|
||||
for uri in uris:
|
||||
response = self.get_request(self.root_uri + uri)
|
||||
if response['ret'] is False:
|
||||
continue
|
||||
data = response['data']
|
||||
headers = response['headers']
|
||||
if username:
|
||||
if username == data.get('UserName'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
if acct_id:
|
||||
if acct_id == data.get('Id'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
|
||||
return {'ret': False, 'no_match': True, 'msg':
|
||||
'No account with the given account_id or account_username found'}
|
||||
@@ -1490,7 +1513,8 @@ class RedfishUtils(object):
|
||||
'Must provide account_password for UpdateUserPassword command'}
|
||||
|
||||
response = self._find_account_uri(username=user.get('account_username'),
|
||||
acct_id=user.get('account_id'))
|
||||
acct_id=user.get('account_id'),
|
||||
password_change_uri=user.get('account_passwordchangerequired'))
|
||||
if not response['ret']:
|
||||
return response
|
||||
|
||||
@@ -1533,6 +1557,31 @@ class RedfishUtils(object):
|
||||
resp['msg'] = 'Modified account service'
|
||||
return resp
|
||||
|
||||
def check_password_change_required(self, return_data):
|
||||
"""
|
||||
Checks a response if a user needs to change their password
|
||||
|
||||
:param return_data: The return data for a failed request
|
||||
:return: None or the URI of the account to update
|
||||
"""
|
||||
uri = None
|
||||
if 'data' in return_data:
|
||||
# Find the extended messages in the response payload
|
||||
extended_messages = return_data['data'].get('error', {}).get('@Message.ExtendedInfo', [])
|
||||
if len(extended_messages) == 0:
|
||||
extended_messages = return_data['data'].get('@Message.ExtendedInfo', [])
|
||||
# Go through each message and look for Base.1.X.PasswordChangeRequired
|
||||
for message in extended_messages:
|
||||
message_id = message.get('MessageId')
|
||||
if message_id is None:
|
||||
# While this is invalid, treat the lack of a MessageId as "no message"
|
||||
continue
|
||||
if message_id.startswith('Base.1.') and message_id.endswith('.PasswordChangeRequired'):
|
||||
# Password change required; get the URI of the user account
|
||||
uri = message['MessageArgs'][0]
|
||||
break
|
||||
return uri
|
||||
|
||||
def get_sessions(self):
|
||||
result = {}
|
||||
# listing all users has always been slower than other operations, why?
|
||||
@@ -1888,7 +1937,7 @@ class RedfishUtils(object):
|
||||
update_uri = data['MultipartHttpPushUri']
|
||||
|
||||
# Assemble the JSON payload portion of the request
|
||||
payload = {"@Redfish.OperationApplyTime": "Immediate"}
|
||||
payload = {}
|
||||
if targets:
|
||||
payload["Targets"] = targets
|
||||
if apply_time:
|
||||
@@ -2242,7 +2291,7 @@ class RedfishUtils(object):
|
||||
continue
|
||||
|
||||
# If already set to requested value, remove it from PATCH payload
|
||||
if data[u'Attributes'][attr_name] == attributes[attr_name]:
|
||||
if data[u'Attributes'][attr_name] == attr_value:
|
||||
del attrs_to_patch[attr_name]
|
||||
|
||||
warning = ""
|
||||
@@ -2262,11 +2311,19 @@ class RedfishUtils(object):
|
||||
|
||||
# Construct payload and issue PATCH command
|
||||
payload = {"Attributes": attrs_to_patch}
|
||||
|
||||
# WORKAROUND
|
||||
# Dell systems require manually setting the apply time to "OnReset"
|
||||
# to spawn a proprietary job to apply the BIOS settings
|
||||
vendor = self._get_vendor()['Vendor']
|
||||
if vendor == 'Dell':
|
||||
payload.update({"@Redfish.SettingsApplyTime": {"ApplyTime": "OnReset"}})
|
||||
|
||||
response = self.patch_request(self.root_uri + set_bios_attr_uri, payload)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
return {'ret': True, 'changed': True,
|
||||
'msg': "Modified BIOS attributes %s" % (attrs_to_patch),
|
||||
'msg': "Modified BIOS attributes %s. A reboot is required" % (attrs_to_patch),
|
||||
'warning': warning}
|
||||
|
||||
def set_boot_order(self, boot_list):
|
||||
@@ -2780,9 +2837,11 @@ class RedfishUtils(object):
|
||||
|
||||
def virtual_media_insert_via_patch(self, options, param_map, uri, data, image_only=False):
|
||||
# get AllowableValues
|
||||
ai = dict((k[:-24],
|
||||
{'AllowableValues': v}) for k, v in data.items()
|
||||
if k.endswith('@Redfish.AllowableValues'))
|
||||
ai = {
|
||||
k[:-24]: {'AllowableValues': v}
|
||||
for k, v in data.items()
|
||||
if k.endswith('@Redfish.AllowableValues')
|
||||
}
|
||||
# construct payload
|
||||
payload = self._insert_virt_media_payload(options, param_map, data, ai)
|
||||
if 'Inserted' not in payload and not image_only:
|
||||
@@ -3373,7 +3432,7 @@ class RedfishUtils(object):
|
||||
|
||||
# Capture list of URIs that match a specified HostInterface resource Id
|
||||
if hostinterface_id:
|
||||
matching_hostinterface_uris = [uri for uri in uris if hostinterface_id in uri.split('/')[-1]]
|
||||
matching_hostinterface_uris = [uri for uri in uris if hostinterface_id in uri.rstrip('/').split('/')[-1]]
|
||||
if hostinterface_id and matching_hostinterface_uris:
|
||||
hostinterface_uri = list.pop(matching_hostinterface_uris)
|
||||
elif hostinterface_id and not matching_hostinterface_uris:
|
||||
@@ -3492,12 +3551,12 @@ class RedfishUtils(object):
|
||||
result = {}
|
||||
if manager is None:
|
||||
if len(self.manager_uris) == 1:
|
||||
manager = self.manager_uris[0].split('/')[-1]
|
||||
manager = self.manager_uris[0].rstrip('/').split('/')[-1]
|
||||
elif len(self.manager_uris) > 1:
|
||||
entries = self.get_multi_manager_inventory()['entries']
|
||||
managers = [m[0]['manager_uri'] for m in entries if m[1].get('ServiceIdentification')]
|
||||
if len(managers) == 1:
|
||||
manager = managers[0].split('/')[-1]
|
||||
manager = managers[0].rstrip('/').split('/')[-1]
|
||||
else:
|
||||
self.module.fail_json(msg=[
|
||||
"Multiple managers with ServiceIdentification were found: %s" % str(managers),
|
||||
@@ -3655,7 +3714,7 @@ class RedfishUtils(object):
|
||||
# Matching Storage Subsystem ID with user input
|
||||
self.storage_subsystem_uri = ""
|
||||
for storage_subsystem_uri in self.storage_subsystems_uris:
|
||||
if storage_subsystem_uri.split("/")[-2] == storage_subsystem_id:
|
||||
if storage_subsystem_uri.rstrip('/').split('/')[-1] == storage_subsystem_id:
|
||||
self.storage_subsystem_uri = storage_subsystem_uri
|
||||
|
||||
if not self.storage_subsystem_uri:
|
||||
@@ -3683,7 +3742,7 @@ class RedfishUtils(object):
|
||||
|
||||
# Delete each volume
|
||||
for volume in self.volume_uris:
|
||||
if volume.split("/")[-1] in volume_ids:
|
||||
if volume.rstrip('/').split('/')[-1] in volume_ids:
|
||||
response = self.delete_request(self.root_uri + volume)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
@@ -3691,7 +3750,7 @@ class RedfishUtils(object):
|
||||
return {'ret': True, 'changed': True,
|
||||
'msg': "The following volumes were deleted: %s" % str(volume_ids)}
|
||||
|
||||
def create_volume(self, volume_details, storage_subsystem_id):
|
||||
def create_volume(self, volume_details, storage_subsystem_id, storage_none_volume_deletion=False):
|
||||
# Find the Storage resource from the requested ComputerSystem resource
|
||||
response = self.get_request(self.root_uri + self.systems_uri)
|
||||
if response['ret'] is False:
|
||||
@@ -3717,7 +3776,7 @@ class RedfishUtils(object):
|
||||
# Matching Storage Subsystem ID with user input
|
||||
self.storage_subsystem_uri = ""
|
||||
for storage_subsystem_uri in self.storage_subsystems_uris:
|
||||
if storage_subsystem_uri.split("/")[-2] == storage_subsystem_id:
|
||||
if storage_subsystem_uri.rstrip('/').split('/')[-1] == storage_subsystem_id:
|
||||
self.storage_subsystem_uri = storage_subsystem_uri
|
||||
|
||||
if not self.storage_subsystem_uri:
|
||||
@@ -3726,8 +3785,8 @@ class RedfishUtils(object):
|
||||
'msg': "Provided Storage Subsystem ID %s does not exist on the server" % storage_subsystem_id}
|
||||
|
||||
# Validate input parameters
|
||||
required_parameters = ['RAIDType', 'Drives', 'CapacityBytes']
|
||||
allowed_parameters = ['DisplayName', 'InitializeMethod', 'MediaSpanCount',
|
||||
required_parameters = ['RAIDType', 'Drives']
|
||||
allowed_parameters = ['CapacityBytes', 'DisplayName', 'InitializeMethod', 'MediaSpanCount',
|
||||
'Name', 'ReadCachePolicy', 'StripSizeBytes', 'VolumeUsage', 'WriteCachePolicy']
|
||||
|
||||
for parameter in required_parameters:
|
||||
@@ -3743,22 +3802,23 @@ class RedfishUtils(object):
|
||||
data = response['data']
|
||||
|
||||
# Deleting any volumes of RAIDType None present on the Storage Subsystem
|
||||
response = self.get_request(self.root_uri + data['Volumes']['@odata.id'])
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
volume_data = response['data']
|
||||
if storage_none_volume_deletion:
|
||||
response = self.get_request(self.root_uri + data['Volumes']['@odata.id'])
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
volume_data = response['data']
|
||||
|
||||
if "Members" in volume_data:
|
||||
for member in volume_data["Members"]:
|
||||
response = self.get_request(self.root_uri + member['@odata.id'])
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
member_data = response['data']
|
||||
|
||||
if member_data["RAIDType"] == "None":
|
||||
response = self.delete_request(self.root_uri + member['@odata.id'])
|
||||
if "Members" in volume_data:
|
||||
for member in volume_data["Members"]:
|
||||
response = self.get_request(self.root_uri + member['@odata.id'])
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
member_data = response['data']
|
||||
|
||||
if member_data["RAIDType"] == "None":
|
||||
response = self.delete_request(self.root_uri + member['@odata.id'])
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
|
||||
# Construct payload and issue POST command to create volume
|
||||
volume_details["Links"] = {}
|
||||
|
||||
@@ -51,11 +51,11 @@ def scaleway_waitable_resource_argument_spec():
|
||||
|
||||
|
||||
def payload_from_object(scw_object):
|
||||
return dict(
|
||||
(k, v)
|
||||
return {
|
||||
k: v
|
||||
for k, v in scw_object.items()
|
||||
if k != 'id' and v is not None
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class ScalewayException(Exception):
|
||||
@@ -117,10 +117,7 @@ class SecretVariables(object):
|
||||
@staticmethod
|
||||
def list_to_dict(source_list, hashed=False):
|
||||
key_value = 'hashed_value' if hashed else 'value'
|
||||
return dict(
|
||||
(var['key'], var[key_value])
|
||||
for var in source_list
|
||||
)
|
||||
return {var['key']: var[key_value] for var in source_list}
|
||||
|
||||
@classmethod
|
||||
def decode(cls, secrets_list, values_list):
|
||||
@@ -143,7 +140,7 @@ def resource_attributes_should_be_changed(target, wished, verifiable_mutable_att
|
||||
diff[attr] = wished[attr]
|
||||
|
||||
if diff:
|
||||
return dict((attr, wished[attr]) for attr in mutable_attributes)
|
||||
return {attr: wished[attr] for attr in mutable_attributes}
|
||||
else:
|
||||
return diff
|
||||
|
||||
|
||||
@@ -175,18 +175,18 @@ class VarDict(object):
|
||||
self.__vars__[name] = var
|
||||
|
||||
def output(self, verbosity=0):
|
||||
return dict((n, v.value) for n, v in self.__vars__.items() if v.output and v.is_visible(verbosity))
|
||||
return {n: v.value for n, v in self.__vars__.items() if v.output and v.is_visible(verbosity)}
|
||||
|
||||
def diff(self, verbosity=0):
|
||||
diff_results = [(n, v.diff_result) for n, v in self.__vars__.items() if v.diff_result and v.is_visible(verbosity)]
|
||||
if diff_results:
|
||||
before = dict((n, dr['before']) for n, dr in diff_results)
|
||||
after = dict((n, dr['after']) for n, dr in diff_results)
|
||||
before = {n: dr['before'] for n, dr in diff_results}
|
||||
after = {n: dr['after'] for n, dr in diff_results}
|
||||
return {'before': before, 'after': after}
|
||||
return None
|
||||
|
||||
def facts(self, verbosity=0):
|
||||
facts_result = dict((n, v.value) for n, v in self.__vars__.items() if v.fact and v.is_visible(verbosity))
|
||||
facts_result = {n: v.value for n, v in self.__vars__.items() if v.fact and v.is_visible(verbosity)}
|
||||
return facts_result if facts_result else None
|
||||
|
||||
@property
|
||||
@@ -194,4 +194,4 @@ class VarDict(object):
|
||||
return any(var.has_changed for var in self.__vars__.values())
|
||||
|
||||
def as_dict(self):
|
||||
return dict((name, var.value) for name, var in self.__vars__.items())
|
||||
return {name: var.value for name, var in self.__vars__.items()}
|
||||
|
||||
@@ -344,7 +344,7 @@ class AlternativesModule(object):
|
||||
|
||||
subcmd_path_map = dict(subcmd_path_link_regex.findall(display_output))
|
||||
if not subcmd_path_map and self.subcommands:
|
||||
subcmd_path_map = dict((s['name'], s['link']) for s in self.subcommands)
|
||||
subcmd_path_map = {s['name']: s['link'] for s in self.subcommands}
|
||||
|
||||
for path, prio, subcmd in alternative_regex.findall(display_output):
|
||||
self.current_alternatives[path] = dict(
|
||||
|
||||
@@ -9,23 +9,29 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: ansible_galaxy_install
|
||||
author:
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
short_description: Install Ansible roles or collections using ansible-galaxy
|
||||
version_added: 3.5.0
|
||||
description:
|
||||
- This module allows the installation of Ansible collections or roles using C(ansible-galaxy).
|
||||
- This module allows the installation of Ansible collections or roles using C(ansible-galaxy).
|
||||
notes:
|
||||
- Support for B(Ansible 2.9/2.10) was removed in community.general 8.0.0.
|
||||
- >
|
||||
The module will try and run using the C(C.UTF-8) locale.
|
||||
If that fails, it will try C(en_US.UTF-8).
|
||||
If that one also fails, the module will fail.
|
||||
- Support for B(Ansible 2.9/2.10) was removed in community.general 8.0.0.
|
||||
- >
|
||||
The module will try and run using the C(C.UTF-8) locale.
|
||||
If that fails, it will try C(en_US.UTF-8).
|
||||
If that one also fails, the module will fail.
|
||||
seealso:
|
||||
- name: C(ansible-galaxy) command manual page
|
||||
description: Manual page for the command.
|
||||
link: https://docs.ansible.com/ansible/latest/cli/ansible-galaxy.html
|
||||
|
||||
requirements:
|
||||
- ansible-core 2.11 or newer
|
||||
- ansible-core 2.11 or newer
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
@@ -34,62 +40,63 @@ attributes:
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- >
|
||||
If O(state=present) then the collection or role will be installed.
|
||||
Note that the collections and roles are not updated with this option.
|
||||
- >
|
||||
Currently the O(state=latest) is ignored unless O(type=collection), and it will
|
||||
ensure the collection is installed and updated to the latest available version.
|
||||
- Please note that O(force=true) can be used to perform upgrade regardless of O(type).
|
||||
- >
|
||||
If O(state=present) then the collection or role will be installed.
|
||||
Note that the collections and roles are not updated with this option.
|
||||
- >
|
||||
Currently the O(state=latest) is ignored unless O(type=collection), and it will
|
||||
ensure the collection is installed and updated to the latest available version.
|
||||
- Please note that O(force=true) can be used to perform upgrade regardless of O(type).
|
||||
type: str
|
||||
choices: [ present, latest ]
|
||||
choices: [present, latest]
|
||||
default: present
|
||||
version_added: 9.1.0
|
||||
type:
|
||||
description:
|
||||
- The type of installation performed by C(ansible-galaxy).
|
||||
- If O(type=both), then O(requirements_file) must be passed and it may contain both roles and collections.
|
||||
- "Note however that the opposite is not true: if using a O(requirements_file), then O(type) can be any of the three choices."
|
||||
- The type of installation performed by C(ansible-galaxy).
|
||||
- If O(type=both), then O(requirements_file) must be passed and it may contain both roles and collections.
|
||||
- "Note however that the opposite is not true: if using a O(requirements_file), then O(type) can be any of the three choices."
|
||||
type: str
|
||||
choices: [collection, role, both]
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- Name of the collection or role being installed.
|
||||
- >
|
||||
Versions can be specified with C(ansible-galaxy) usual formats.
|
||||
For example, the collection V(community.docker:1.6.1) or the role V(ansistrano.deploy,3.8.0).
|
||||
- O(name) and O(requirements_file) are mutually exclusive.
|
||||
- Name of the collection or role being installed.
|
||||
- >
|
||||
Versions can be specified with C(ansible-galaxy) usual formats.
|
||||
For example, the collection V(community.docker:1.6.1) or the role V(ansistrano.deploy,3.8.0).
|
||||
- O(name) and O(requirements_file) are mutually exclusive.
|
||||
type: str
|
||||
requirements_file:
|
||||
description:
|
||||
- Path to a file containing a list of requirements to be installed.
|
||||
- It works for O(type) equals to V(collection) and V(role).
|
||||
- O(name) and O(requirements_file) are mutually exclusive.
|
||||
- Path to a file containing a list of requirements to be installed.
|
||||
- It works for O(type) equals to V(collection) and V(role).
|
||||
- O(name) and O(requirements_file) are mutually exclusive.
|
||||
type: path
|
||||
dest:
|
||||
description:
|
||||
- The path to the directory containing your collections or roles, according to the value of O(type).
|
||||
- >
|
||||
Please notice that C(ansible-galaxy) will not install collections with O(type=both), when O(requirements_file)
|
||||
contains both roles and collections and O(dest) is specified.
|
||||
- The path to the directory containing your collections or roles, according to the value of O(type).
|
||||
- >
|
||||
Please notice that C(ansible-galaxy) will not install collections with O(type=both), when O(requirements_file)
|
||||
contains both roles and collections and O(dest) is specified.
|
||||
type: path
|
||||
no_deps:
|
||||
description:
|
||||
- Refrain from installing dependencies.
|
||||
- Refrain from installing dependencies.
|
||||
version_added: 4.5.0
|
||||
type: bool
|
||||
default: false
|
||||
force:
|
||||
description:
|
||||
- Force overwriting existing roles and/or collections.
|
||||
- It can be used for upgrading, but the module output will always report C(changed=true).
|
||||
- Using O(force=true) is mandatory when downgrading.
|
||||
- Force overwriting existing roles and/or collections.
|
||||
- It can be used for upgrading, but the module output will always report C(changed=true).
|
||||
- Using O(force=true) is mandatory when downgrading.
|
||||
type: bool
|
||||
default: false
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
- name: Install collection community.network
|
||||
community.general.ansible_galaxy_install:
|
||||
type: collection
|
||||
@@ -111,76 +118,76 @@ EXAMPLES = """
|
||||
type: collection
|
||||
name: community.network:3.0.2
|
||||
force: true
|
||||
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
type:
|
||||
description: The value of the O(type) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
name:
|
||||
description: The value of the O(name) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
dest:
|
||||
description: The value of the O(dest) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
requirements_file:
|
||||
description: The value of the O(requirements_file) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
force:
|
||||
description: The value of the O(force) parameter.
|
||||
type: bool
|
||||
returned: always
|
||||
installed_roles:
|
||||
description:
|
||||
- If O(requirements_file) is specified instead, returns dictionary with all the roles installed per path.
|
||||
- If O(name) is specified, returns that role name and the version installed per path.
|
||||
type: dict
|
||||
returned: always when installing roles
|
||||
contains:
|
||||
"<path>":
|
||||
description: Roles and versions for that path.
|
||||
type: dict
|
||||
sample:
|
||||
/home/user42/.ansible/roles:
|
||||
ansistrano.deploy: 3.9.0
|
||||
baztian.xfce: v0.0.3
|
||||
/custom/ansible/roles:
|
||||
ansistrano.deploy: 3.8.0
|
||||
installed_collections:
|
||||
description:
|
||||
- If O(requirements_file) is specified instead, returns dictionary with all the collections installed per path.
|
||||
- If O(name) is specified, returns that collection name and the version installed per path.
|
||||
type: dict
|
||||
returned: always when installing collections
|
||||
contains:
|
||||
"<path>":
|
||||
description: Collections and versions for that path
|
||||
type: dict
|
||||
sample:
|
||||
/home/az/.ansible/collections/ansible_collections:
|
||||
community.docker: 1.6.0
|
||||
community.general: 3.0.2
|
||||
/custom/ansible/ansible_collections:
|
||||
community.general: 3.1.0
|
||||
new_collections:
|
||||
description: New collections installed by this module.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
community.general: 3.1.0
|
||||
community.docker: 1.6.1
|
||||
new_roles:
|
||||
description: New roles installed by this module.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
ansistrano.deploy: 3.8.0
|
||||
---
|
||||
type:
|
||||
description: The value of the O(type) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
name:
|
||||
description: The value of the O(name) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
dest:
|
||||
description: The value of the O(dest) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
requirements_file:
|
||||
description: The value of the O(requirements_file) parameter.
|
||||
type: str
|
||||
returned: always
|
||||
force:
|
||||
description: The value of the O(force) parameter.
|
||||
type: bool
|
||||
returned: always
|
||||
installed_roles:
|
||||
description:
|
||||
- If O(requirements_file) is specified instead, returns dictionary with all the roles installed per path.
|
||||
- If O(name) is specified, returns that role name and the version installed per path.
|
||||
type: dict
|
||||
returned: always when installing roles
|
||||
contains:
|
||||
"<path>":
|
||||
description: Roles and versions for that path.
|
||||
type: dict
|
||||
sample:
|
||||
/home/user42/.ansible/roles:
|
||||
ansistrano.deploy: 3.9.0
|
||||
baztian.xfce: v0.0.3
|
||||
/custom/ansible/roles:
|
||||
ansistrano.deploy: 3.8.0
|
||||
installed_collections:
|
||||
description:
|
||||
- If O(requirements_file) is specified instead, returns dictionary with all the collections installed per path.
|
||||
- If O(name) is specified, returns that collection name and the version installed per path.
|
||||
type: dict
|
||||
returned: always when installing collections
|
||||
contains:
|
||||
"<path>":
|
||||
description: Collections and versions for that path
|
||||
type: dict
|
||||
sample:
|
||||
/home/az/.ansible/collections/ansible_collections:
|
||||
community.docker: 1.6.0
|
||||
community.general: 3.0.2
|
||||
/custom/ansible/ansible_collections:
|
||||
community.general: 3.1.0
|
||||
new_collections:
|
||||
description: New collections installed by this module.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
community.general: 3.1.0
|
||||
community.docker: 1.6.1
|
||||
new_roles:
|
||||
description: New roles installed by this module.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
ansistrano.deploy: 3.8.0
|
||||
baztian.xfce: v0.0.3
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
@@ -277,7 +277,7 @@ class BalancerMember(object):
|
||||
for valuesset in subsoup[1::1]:
|
||||
if re.search(pattern=self.host, string=str(valuesset)):
|
||||
values = valuesset.findAll('td')
|
||||
return dict((keys[x].string, values[x].string) for x in range(0, len(keys)))
|
||||
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."""
|
||||
@@ -286,7 +286,7 @@ class BalancerMember(object):
|
||||
'hot_standby': 'Stby',
|
||||
'ignore_errors': 'Ign'}
|
||||
actual_status = str(self.attributes['Status'])
|
||||
status = dict((mode, patt in actual_status) for mode, patt in iteritems(status_mapping))
|
||||
status = {mode: patt in actual_status for mode, patt in iteritems(status_mapping)}
|
||||
return status
|
||||
|
||||
def set_member_status(self, values):
|
||||
|
||||
@@ -74,6 +74,7 @@ options:
|
||||
world:
|
||||
description:
|
||||
- Use a custom world file when checking for explicitly installed packages.
|
||||
The file is used only when a value is provided for O(name), and O(state) is set to V(present) or V(latest).
|
||||
type: str
|
||||
default: /etc/apk/world
|
||||
version_added: 5.4.0
|
||||
|
||||
@@ -102,40 +102,40 @@ EXAMPLES = r'''
|
||||
- name: Create a @home subvolume under the root subvolume
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@home
|
||||
device: /dev/vda2
|
||||
filesystem_device: /dev/vda2
|
||||
|
||||
- name: Remove the @home subvolume if it exists
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@home
|
||||
state: absent
|
||||
device: /dev/vda2
|
||||
filesystem_device: /dev/vda2
|
||||
|
||||
- name: Create a snapshot of the root subvolume named @
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@
|
||||
snapshot_source: /
|
||||
device: /dev/vda2
|
||||
filesystem_device: /dev/vda2
|
||||
|
||||
- name: Create a snapshot of the root subvolume and make it the new default subvolume
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@
|
||||
snapshot_source: /
|
||||
default: Yes
|
||||
device: /dev/vda2
|
||||
filesystem_device: /dev/vda2
|
||||
|
||||
- name: Create a snapshot of the /@ subvolume and recursively creating intermediate subvolumes as required
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@snapshots/@2022_06_09
|
||||
snapshot_source: /@
|
||||
recursive: True
|
||||
device: /dev/vda2
|
||||
filesystem_device: /dev/vda2
|
||||
|
||||
- name: Remove the /@ subvolume and recursively delete child subvolumes as required
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@snapshots/@2022_06_09
|
||||
snapshot_source: /@
|
||||
recursive: True
|
||||
device: /dev/vda2
|
||||
filesystem_device: /dev/vda2
|
||||
|
||||
'''
|
||||
|
||||
|
||||
@@ -716,12 +716,14 @@ class CloudflareAPI(object):
|
||||
"port": params['port'],
|
||||
"weight": params['weight'],
|
||||
"priority": params['priority'],
|
||||
"name": params['record'],
|
||||
"proto": params['proto'],
|
||||
"service": params['service']
|
||||
}
|
||||
|
||||
new_record = {"type": params['type'], "ttl": params['ttl'], 'data': srv_data}
|
||||
new_record = {
|
||||
"type": params['type'],
|
||||
"name": params['service'] + '.' + params['proto'] + '.' + params['record'],
|
||||
"ttl": params['ttl'],
|
||||
'data': srv_data,
|
||||
}
|
||||
search_value = str(params['weight']) + '\t' + str(params['port']) + '\t' + params['value']
|
||||
search_record = params['service'] + '.' + params['proto'] + '.' + params['record']
|
||||
|
||||
|
||||
@@ -273,8 +273,8 @@ def set_acl(consul_client, configuration):
|
||||
:return: the output of setting the ACL
|
||||
"""
|
||||
acls_as_json = decode_acls_as_json(consul_client.acl.list())
|
||||
existing_acls_mapped_by_name = dict((acl.name, acl) for acl in acls_as_json if acl.name is not None)
|
||||
existing_acls_mapped_by_token = dict((acl.token, acl) for acl in acls_as_json)
|
||||
existing_acls_mapped_by_name = {acl.name: acl for acl in acls_as_json if acl.name is not None}
|
||||
existing_acls_mapped_by_token = {acl.token: acl for acl in acls_as_json}
|
||||
if None in existing_acls_mapped_by_token:
|
||||
raise AssertionError("expecting ACL list to be associated to a token: %s" %
|
||||
existing_acls_mapped_by_token[None])
|
||||
|
||||
@@ -52,6 +52,18 @@ options:
|
||||
for example V(epel-7-x86_64). Default chroot is determined by the operating system,
|
||||
version of the operating system, and architecture on which the module is run.
|
||||
type: str
|
||||
includepkgs:
|
||||
description: List of packages to include.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 9.4.0
|
||||
excludepkgs:
|
||||
description: List of packages to exclude.
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 9.4.0
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
@@ -255,6 +267,12 @@ class CoprModule(object):
|
||||
"""
|
||||
if not repo_content:
|
||||
repo_content = self._download_repo_info()
|
||||
if self.ansible_module.params["includepkgs"]:
|
||||
includepkgs_value = ','.join(self.ansible_module.params['includepkgs'])
|
||||
repo_content = repo_content.rstrip('\n') + '\nincludepkgs={0}\n'.format(includepkgs_value)
|
||||
if self.ansible_module.params["excludepkgs"]:
|
||||
excludepkgs_value = ','.join(self.ansible_module.params['excludepkgs'])
|
||||
repo_content = repo_content.rstrip('\n') + '\nexcludepkgs={0}\n'.format(excludepkgs_value)
|
||||
if self._compare_repo_content(repo_filename_path, repo_content):
|
||||
return False
|
||||
if not self.check_mode:
|
||||
@@ -470,6 +488,8 @@ def run_module():
|
||||
name=dict(type="str", required=True),
|
||||
state=dict(type="str", choices=["enabled", "disabled", "absent"], default="enabled"),
|
||||
chroot=dict(type="str"),
|
||||
includepkgs=dict(type='list', elements="str", required=False),
|
||||
excludepkgs=dict(type='list', elements="str", required=False),
|
||||
)
|
||||
module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
|
||||
params = module.params
|
||||
|
||||
@@ -10,14 +10,14 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: cpanm
|
||||
short_description: Manages Perl library dependencies
|
||||
description:
|
||||
- Manage Perl library dependencies using cpanminus.
|
||||
- Manage Perl library dependencies using cpanminus.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
@@ -27,76 +27,82 @@ options:
|
||||
name:
|
||||
type: str
|
||||
description:
|
||||
- The Perl library to install. Valid values change according to the O(mode), see notes for more details.
|
||||
- Note that for installing from a local path the parameter O(from_path) should be used.
|
||||
- The Perl library to install. Valid values change according to the O(mode), see notes for more details.
|
||||
- Note that for installing from a local path the parameter O(from_path) should be used.
|
||||
aliases: [pkg]
|
||||
from_path:
|
||||
type: path
|
||||
description:
|
||||
- The local directory or C(tar.gz) file to install from.
|
||||
- The local directory or C(tar.gz) file to install from.
|
||||
notest:
|
||||
description:
|
||||
- Do not run unit tests.
|
||||
- Do not run unit tests.
|
||||
type: bool
|
||||
default: false
|
||||
locallib:
|
||||
description:
|
||||
- Specify the install base to install modules.
|
||||
- Specify the install base to install modules.
|
||||
type: path
|
||||
mirror:
|
||||
description:
|
||||
- Specifies the base URL for the CPAN mirror to use.
|
||||
- Specifies the base URL for the CPAN mirror to use.
|
||||
type: str
|
||||
mirror_only:
|
||||
description:
|
||||
- Use the mirror's index file instead of the CPAN Meta DB.
|
||||
- Use the mirror's index file instead of the CPAN Meta DB.
|
||||
type: bool
|
||||
default: false
|
||||
installdeps:
|
||||
description:
|
||||
- Only install dependencies.
|
||||
- Only install dependencies.
|
||||
type: bool
|
||||
default: false
|
||||
version:
|
||||
description:
|
||||
- Version specification for the perl module. When O(mode) is V(new), C(cpanm) version operators are accepted.
|
||||
- Version specification for the perl module. When O(mode) is V(new), C(cpanm) version operators are accepted.
|
||||
type: str
|
||||
executable:
|
||||
description:
|
||||
- Override the path to the cpanm executable.
|
||||
- Override the path to the cpanm executable.
|
||||
type: path
|
||||
mode:
|
||||
description:
|
||||
- Controls the module behavior. See notes below for more details.
|
||||
- The default changed from V(compatibility) to V(new) in community.general 9.0.0.
|
||||
- Controls the module behavior. See notes below for more details.
|
||||
- The default changed from V(compatibility) to V(new) in community.general 9.0.0.
|
||||
type: str
|
||||
choices: [compatibility, new]
|
||||
default: new
|
||||
version_added: 3.0.0
|
||||
name_check:
|
||||
description:
|
||||
- When O(mode=new), this parameter can be used to check if there is a module O(name) installed (at O(version), when specified).
|
||||
- When O(mode=new), this parameter can be used to check if there is a module O(name) installed (at O(version), when specified).
|
||||
type: str
|
||||
version_added: 3.0.0
|
||||
notes:
|
||||
- Please note that U(http://search.cpan.org/dist/App-cpanminus/bin/cpanm, cpanm) must be installed on the remote host.
|
||||
- "This module now comes with a choice of execution O(mode): V(compatibility) or V(new)."
|
||||
- >
|
||||
O(mode=compatibility): When using V(compatibility) mode, the module will keep backward compatibility.
|
||||
This was the default mode before community.general 9.0.0.
|
||||
O(name) must be either a module name or a distribution file. If the perl module given by O(name) is installed (at the exact O(version)
|
||||
when specified), then nothing happens. Otherwise, it will be installed using the C(cpanm) executable. O(name) cannot be an URL, or a git URL.
|
||||
C(cpanm) version specifiers do not work in this mode.
|
||||
- >
|
||||
O(mode=new): When using V(new) mode, the module will behave differently. The O(name) parameter may refer to a module name, a distribution file,
|
||||
a HTTP URL or a git repository URL as described in C(cpanminus) documentation. C(cpanm) version specifiers are recognized.
|
||||
This is the default mode from community.general 9.0.0 onwards.
|
||||
author:
|
||||
- "Franck Cuny (@fcuny)"
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
'''
|
||||
- Please note that U(http://search.cpan.org/dist/App-cpanminus/bin/cpanm, cpanm) must be installed on the remote host.
|
||||
- "This module now comes with a choice of execution O(mode): V(compatibility) or V(new)."
|
||||
- >
|
||||
O(mode=compatibility): When using V(compatibility) mode, the module will keep backward compatibility.
|
||||
This was the default mode before community.general 9.0.0.
|
||||
O(name) must be either a module name or a distribution file. If the perl module given by O(name) is installed (at the exact O(version)
|
||||
when specified), then nothing happens. Otherwise, it will be installed using the C(cpanm) executable. O(name) cannot be an URL, or a git URL.
|
||||
C(cpanm) version specifiers do not work in this mode.
|
||||
- >
|
||||
O(mode=new): When using V(new) mode, the module will behave differently. The O(name) parameter may refer to a module name, a distribution file,
|
||||
a HTTP URL or a git repository URL as described in C(cpanminus) documentation. C(cpanm) version specifiers are recognized.
|
||||
This is the default mode from community.general 9.0.0 onwards.
|
||||
|
||||
EXAMPLES = '''
|
||||
seealso:
|
||||
- name: C(cpanm) command manual page
|
||||
description: Manual page for the command.
|
||||
link: https://metacpan.org/dist/App-cpanminus/view/bin/cpanm
|
||||
author:
|
||||
- "Franck Cuny (@fcuny)"
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
- name: Install Dancer perl package
|
||||
community.general.cpanm:
|
||||
name: Dancer
|
||||
@@ -134,7 +140,7 @@ EXAMPLES = '''
|
||||
community.general.cpanm:
|
||||
name: Dancer
|
||||
version: '1.0'
|
||||
'''
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ run_info:
|
||||
returned: success and O(verbosity) >= 3
|
||||
"""
|
||||
|
||||
import shlex
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.django import DjangoModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import cmd_runner_fmt
|
||||
|
||||
@@ -74,6 +76,9 @@ class DjangoCommand(DjangoModuleHelper):
|
||||
)
|
||||
django_admin_arg_order = "extra_args"
|
||||
|
||||
def __init_module__(self):
|
||||
self.vars.command = shlex.split(self.vars.command)
|
||||
|
||||
|
||||
def main():
|
||||
DjangoCommand.execute()
|
||||
|
||||
@@ -153,7 +153,7 @@ def get_repo_states(module):
|
||||
|
||||
|
||||
def set_repo_states(module, repo_ids, state):
|
||||
module.run_command([DNF_BIN, 'config-manager', '--set-{0}'.format(state)] + repo_ids, check_rc=True)
|
||||
module.run_command([DNF_BIN, 'config-manager', '--assumeyes', '--set-{0}'.format(state)] + repo_ids, check_rc=True)
|
||||
|
||||
|
||||
def pack_repo_states_for_return(states):
|
||||
@@ -186,6 +186,7 @@ def main():
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=True
|
||||
)
|
||||
module.run_command_environ_update = dict(LANGUAGE='C', LC_ALL='C')
|
||||
|
||||
if not os.path.exists(DNF_BIN):
|
||||
module.fail_json(msg="%s was not found" % DNF_BIN)
|
||||
|
||||
@@ -193,13 +193,8 @@ def run_module():
|
||||
|
||||
allowed_keys = ['host', 'port', 'ca_cert', 'cert_cert', 'cert_key',
|
||||
'timeout', 'user', 'password']
|
||||
# TODO(evrardjp): Move this back to a dict comprehension when python 2.7 is
|
||||
# the minimum supported version
|
||||
# client_params = {key: value for key, value in module.params.items() if key in allowed_keys}
|
||||
client_params = dict()
|
||||
for key, value in module.params.items():
|
||||
if key in allowed_keys:
|
||||
client_params[key] = value
|
||||
|
||||
client_params = {key: value for key, value in module.params.items() if key in allowed_keys}
|
||||
try:
|
||||
etcd = etcd3.client(**client_params)
|
||||
except Exception as exp:
|
||||
|
||||
@@ -329,13 +329,39 @@ def _match_flat_using_flatpak_column_feature(module, binary, parsed_name, method
|
||||
return row.split()[0]
|
||||
|
||||
|
||||
def _is_flatpak_id(part):
|
||||
# For guidelines on application IDs, refer to the following resources:
|
||||
# Flatpak:
|
||||
# https://docs.flatpak.org/en/latest/conventions.html#application-ids
|
||||
# Flathub:
|
||||
# https://docs.flathub.org/docs/for-app-authors/requirements#application-id
|
||||
if '.' not in part:
|
||||
return False
|
||||
sections = part.split('.')
|
||||
if len(sections) < 2:
|
||||
return False
|
||||
domain = sections[0]
|
||||
if not domain.islower():
|
||||
return False
|
||||
for section in sections[1:]:
|
||||
if not section.isalnum():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _parse_flatpak_name(name):
|
||||
if name.startswith('http://') or name.startswith('https://'):
|
||||
file_name = urlparse(name).path.split('/')[-1]
|
||||
file_name_without_extension = file_name.split('.')[0:-1]
|
||||
common_name = ".".join(file_name_without_extension)
|
||||
else:
|
||||
common_name = name
|
||||
parts = name.split('/')
|
||||
for part in parts:
|
||||
if _is_flatpak_id(part):
|
||||
common_name = part
|
||||
break
|
||||
else:
|
||||
common_name = name
|
||||
return common_name
|
||||
|
||||
|
||||
@@ -393,6 +419,8 @@ def main():
|
||||
if not binary:
|
||||
module.fail_json(msg="Executable '%s' was not found on the system." % executable, **result)
|
||||
|
||||
module.run_command_environ_update = dict(LANGUAGE='C', LC_ALL='C')
|
||||
|
||||
installed, not_installed = flatpak_exists(module, binary, name, method)
|
||||
if state == 'absent' and installed:
|
||||
uninstall_flat(module, binary, installed, method)
|
||||
|
||||
@@ -9,16 +9,21 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: gconftool2
|
||||
author:
|
||||
- Kenneth D. Evensen (@kevensen)
|
||||
- Kenneth D. Evensen (@kevensen)
|
||||
short_description: Edit GNOME Configurations
|
||||
description:
|
||||
- This module allows for the manipulation of GNOME 2 Configuration via
|
||||
gconftool-2. Please see the gconftool-2(1) man pages for more details.
|
||||
- This module allows for the manipulation of GNOME 2 Configuration via gconftool-2. Please see the gconftool-2(1) man pages for more details.
|
||||
seealso:
|
||||
- name: C(gconftool-2) command manual page
|
||||
description: Manual page for the command.
|
||||
link: https://help.gnome.org/admin//system-admin-guide/2.32/gconf-6.html.en
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
@@ -28,42 +33,36 @@ options:
|
||||
key:
|
||||
type: str
|
||||
description:
|
||||
- A GConf preference key is an element in the GConf repository
|
||||
that corresponds to an application preference. See man gconftool-2(1).
|
||||
- A GConf preference key is an element in the GConf repository that corresponds to an application preference.
|
||||
required: true
|
||||
value:
|
||||
type: str
|
||||
description:
|
||||
- Preference keys typically have simple values such as strings,
|
||||
integers, or lists of strings and integers.
|
||||
This is ignored unless O(state=present). See man gconftool-2(1).
|
||||
- Preference keys typically have simple values such as strings, integers, or lists of strings and integers. This is ignored unless O(state=present).
|
||||
value_type:
|
||||
type: str
|
||||
description:
|
||||
- The type of value being set.
|
||||
This is ignored unless O(state=present). See man gconftool-2(1).
|
||||
choices: [ bool, float, int, string ]
|
||||
- The type of value being set. This is ignored unless O(state=present).
|
||||
choices: [bool, float, int, string]
|
||||
state:
|
||||
type: str
|
||||
description:
|
||||
- The action to take upon the key/value.
|
||||
required: true
|
||||
choices: [ absent, present ]
|
||||
choices: [absent, present]
|
||||
config_source:
|
||||
type: str
|
||||
description:
|
||||
- Specify a configuration source to use rather than the default path.
|
||||
See man gconftool-2(1).
|
||||
direct:
|
||||
description:
|
||||
- Access the config database directly, bypassing server. If O(direct) is
|
||||
specified then the O(config_source) must be specified as well.
|
||||
See man gconftool-2(1).
|
||||
- Access the config database directly, bypassing server. If O(direct) is specified then the O(config_source) must be specified as well.
|
||||
type: bool
|
||||
default: false
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
- name: Change the widget font to "Serif 12"
|
||||
community.general.gconftool2:
|
||||
key: "/desktop/gnome/interface/font_name"
|
||||
@@ -71,33 +70,33 @@ EXAMPLES = """
|
||||
value: "Serif 12"
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
key:
|
||||
description: The key specified in the module parameters.
|
||||
returned: success
|
||||
type: str
|
||||
sample: /desktop/gnome/interface/font_name
|
||||
value_type:
|
||||
description: The type of the value that was changed.
|
||||
returned: success
|
||||
type: str
|
||||
sample: string
|
||||
value:
|
||||
description:
|
||||
- The value of the preference key after executing the module or V(null) if key is removed.
|
||||
- From community.general 7.0.0 onwards it returns V(null) for a non-existent O(key), and returned V("") before that.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "Serif 12"
|
||||
previous_value:
|
||||
description:
|
||||
- The value of the preference key before executing the module.
|
||||
- From community.general 7.0.0 onwards it returns V(null) for a non-existent O(key), and returned V("") before that.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "Serif 12"
|
||||
...
|
||||
'''
|
||||
RETURN = """
|
||||
---
|
||||
key:
|
||||
description: The key specified in the module parameters.
|
||||
returned: success
|
||||
type: str
|
||||
sample: /desktop/gnome/interface/font_name
|
||||
value_type:
|
||||
description: The type of the value that was changed.
|
||||
returned: success
|
||||
type: str
|
||||
sample: string
|
||||
value:
|
||||
description:
|
||||
- The value of the preference key after executing the module or V(null) if key is removed.
|
||||
- From community.general 7.0.0 onwards it returns V(null) for a non-existent O(key), and returned V("") before that.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "Serif 12"
|
||||
previous_value:
|
||||
description:
|
||||
- The value of the preference key before executing the module.
|
||||
- From community.general 7.0.0 onwards it returns V(null) for a non-existent O(key), and returned V("") before that.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "Serif 12"
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.gconftool2 import gconftool2_runner
|
||||
|
||||
@@ -7,46 +7,50 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: gconftool2_info
|
||||
author:
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
short_description: Retrieve GConf configurations
|
||||
version_added: 5.1.0
|
||||
description:
|
||||
- This module allows retrieving application preferences from the GConf database, with the help of C(gconftool-2).
|
||||
- This module allows retrieving application preferences from the GConf database, with the help of C(gconftool-2).
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
options:
|
||||
key:
|
||||
description:
|
||||
- The key name for an element in the GConf database.
|
||||
type: str
|
||||
required: true
|
||||
notes:
|
||||
- See man gconftool-2(1) for more details.
|
||||
seealso:
|
||||
- name: gconf repository (archived)
|
||||
description: Git repository for the project. It is an archived project, so the repository is read-only.
|
||||
link: https://gitlab.gnome.org/Archive/gconf
|
||||
'''
|
||||
- name: C(gconftool-2) command manual page
|
||||
description: Manual page for the command.
|
||||
link: https://help.gnome.org/admin//system-admin-guide/2.32/gconf-6.html.en
|
||||
- name: gconf repository (archived)
|
||||
description: Git repository for the project. It is an archived project, so the repository is read-only.
|
||||
link: https://gitlab.gnome.org/Archive/gconf
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
- name: Get value for a certain key in the database.
|
||||
community.general.gconftool2_info:
|
||||
key: /desktop/gnome/background/picture_filename
|
||||
register: result
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
value:
|
||||
description:
|
||||
- The value of the property.
|
||||
returned: success
|
||||
type: str
|
||||
sample: Monospace 10
|
||||
'''
|
||||
RETURN = """
|
||||
---
|
||||
value:
|
||||
description:
|
||||
- The value of the property.
|
||||
returned: success
|
||||
type: str
|
||||
sample: Monospace 10
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.gconftool2 import gconftool2_runner
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: gio_mime
|
||||
author:
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
short_description: Set default handler for MIME type, for applications using Gnome GIO
|
||||
version_added: 7.5.0
|
||||
description:
|
||||
- This module allows configuring the default handler for a specific MIME type, to be used by applications built with th Gnome GIO API.
|
||||
- This module allows configuring the default handler for a specific MIME type, to be used by applications built with th Gnome GIO API.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
@@ -25,24 +26,28 @@ attributes:
|
||||
options:
|
||||
mime_type:
|
||||
description:
|
||||
- MIME type for which a default handler will be set.
|
||||
- MIME type for which a default handler will be set.
|
||||
type: str
|
||||
required: true
|
||||
handler:
|
||||
description:
|
||||
- Default handler will be set for the MIME type.
|
||||
- Default handler will be set for the MIME type.
|
||||
type: str
|
||||
required: true
|
||||
notes:
|
||||
- This module is a thin wrapper around the C(gio mime) command (and subcommand).
|
||||
- See man gio(1) for more details.
|
||||
- This module is a thin wrapper around the C(gio mime) command (and subcommand).
|
||||
- See man gio(1) for more details.
|
||||
seealso:
|
||||
- name: GIO Documentation
|
||||
description: Reference documentation for the GIO API..
|
||||
link: https://docs.gtk.org/gio/
|
||||
'''
|
||||
- name: C(gio) command manual page
|
||||
description: Manual page for the command.
|
||||
link: https://man.archlinux.org/man/gio.1
|
||||
- name: GIO Documentation
|
||||
description: Reference documentation for the GIO API..
|
||||
link: https://docs.gtk.org/gio/
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
- name: Set chrome as the default handler for https
|
||||
community.general.gio_mime:
|
||||
mime_type: x-scheme-handler/https
|
||||
@@ -50,26 +55,27 @@ EXAMPLES = """
|
||||
register: result
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
handler:
|
||||
description:
|
||||
- The handler set as default.
|
||||
returned: success
|
||||
type: str
|
||||
sample: google-chrome.desktop
|
||||
stdout:
|
||||
description:
|
||||
- The output of the C(gio) command.
|
||||
returned: success
|
||||
type: str
|
||||
sample: Set google-chrome.desktop as the default for x-scheme-handler/https
|
||||
stderr:
|
||||
description:
|
||||
- The error output of the C(gio) command.
|
||||
returned: failure
|
||||
type: str
|
||||
sample: 'gio: Failed to load info for handler "never-existed.desktop"'
|
||||
'''
|
||||
RETURN = """
|
||||
---
|
||||
handler:
|
||||
description:
|
||||
- The handler set as default.
|
||||
returned: success
|
||||
type: str
|
||||
sample: google-chrome.desktop
|
||||
stdout:
|
||||
description:
|
||||
- The output of the C(gio) command.
|
||||
returned: success
|
||||
type: str
|
||||
sample: Set google-chrome.desktop as the default for x-scheme-handler/https
|
||||
stderr:
|
||||
description:
|
||||
- The error output of the C(gio) command.
|
||||
returned: failure
|
||||
type: str
|
||||
sample: 'gio: Failed to load info for handler "never-existed.desktop"'
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.gio_mime import gio_mime_runner, gio_mime_get
|
||||
@@ -84,6 +90,7 @@ class GioMime(ModuleHelper):
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = gio_mime_runner(self.module, check_rc=True)
|
||||
@@ -91,7 +98,7 @@ class GioMime(ModuleHelper):
|
||||
|
||||
def __run__(self):
|
||||
check_mode_return = (0, 'Module executed in check mode', '')
|
||||
if self.vars.has_changed("handler"):
|
||||
if self.vars.has_changed:
|
||||
with self.runner.context(args_order=["mime_type", "handler"], check_mode_skip=True, check_mode_return=check_mode_return) as ctx:
|
||||
rc, out, err = ctx.run()
|
||||
self.vars.stdout = out
|
||||
|
||||
@@ -162,7 +162,7 @@ def create_key(session, name, pubkey, check_mode):
|
||||
'key': pubkey,
|
||||
'title': name,
|
||||
'url': 'http://example.com/CHECK_MODE_GITHUB_KEY',
|
||||
'created_at': datetime.strftime(now_t, '%Y-%m-%dT%H:%M:%SZ'),
|
||||
'created_at': datetime.datetime.strftime(now_t, '%Y-%m-%dT%H:%M:%SZ'),
|
||||
'read_only': False,
|
||||
'verified': False
|
||||
}
|
||||
|
||||
@@ -196,9 +196,9 @@ class GitLabDeployKey(object):
|
||||
changed = False
|
||||
|
||||
for arg_key, arg_value in arguments.items():
|
||||
if arguments[arg_key] is not None:
|
||||
if getattr(deploy_key, arg_key) != arguments[arg_key]:
|
||||
setattr(deploy_key, arg_key, arguments[arg_key])
|
||||
if arg_value is not None:
|
||||
if getattr(deploy_key, arg_key) != arg_value:
|
||||
setattr(deploy_key, arg_key, arg_value)
|
||||
changed = True
|
||||
|
||||
return (changed, deploy_key)
|
||||
|
||||
@@ -33,66 +33,35 @@ attributes:
|
||||
support: none
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the group you want to create.
|
||||
required: true
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- The path of the group you want to create, this will be api_url/group_path
|
||||
- If not supplied, the group_name will be used.
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- A description for the group.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- create or delete group.
|
||||
- Possible values are present and absent.
|
||||
default: present
|
||||
type: str
|
||||
choices: ["present", "absent"]
|
||||
parent:
|
||||
description:
|
||||
- Allow to create subgroups
|
||||
- Id or Full path of parent group in the form of group/name
|
||||
type: str
|
||||
visibility:
|
||||
description:
|
||||
- Default visibility of the group
|
||||
choices: ["private", "internal", "public"]
|
||||
default: private
|
||||
type: str
|
||||
project_creation_level:
|
||||
description:
|
||||
- Determine if developers can create projects in the group.
|
||||
choices: ["developer", "maintainer", "noone"]
|
||||
type: str
|
||||
version_added: 3.7.0
|
||||
auto_devops_enabled:
|
||||
description:
|
||||
- Default to Auto DevOps pipeline for all projects within this group.
|
||||
type: bool
|
||||
version_added: 3.7.0
|
||||
subgroup_creation_level:
|
||||
description:
|
||||
- Allowed to create subgroups.
|
||||
choices: ["maintainer", "owner"]
|
||||
type: str
|
||||
version_added: 3.7.0
|
||||
require_two_factor_authentication:
|
||||
description:
|
||||
- Require all users in this group to setup two-factor authentication.
|
||||
type: bool
|
||||
version_added: 3.7.0
|
||||
avatar_path:
|
||||
description:
|
||||
- Absolute path image to configure avatar. File size should not exceed 200 kb.
|
||||
- This option is only used on creation, not for updates.
|
||||
type: path
|
||||
version_added: 4.2.0
|
||||
default_branch:
|
||||
description:
|
||||
- All merge requests and commits are made against this branch unless you specify a different one.
|
||||
type: str
|
||||
version_added: 9.5.0
|
||||
description:
|
||||
description:
|
||||
- A description for the group.
|
||||
type: str
|
||||
enabled_git_access_protocol:
|
||||
description:
|
||||
- V(all) means SSH and HTTP(S) is enabled.
|
||||
- V(ssh) means only SSH is enabled.
|
||||
- V(http) means only HTTP(S) is enabled.
|
||||
- Only available for top level groups.
|
||||
choices: ["all", "ssh", "http"]
|
||||
type: str
|
||||
version_added: 9.5.0
|
||||
force_delete:
|
||||
description:
|
||||
- Force delete group even if projects in it.
|
||||
@@ -100,6 +69,113 @@ options:
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 7.5.0
|
||||
lfs_enabled:
|
||||
description:
|
||||
- Projects in this group can use Git LFS.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
lock_duo_features_enabled:
|
||||
description:
|
||||
- Enforce GitLab Duo features for all subgroups.
|
||||
- Only available for top level groups.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
membership_lock:
|
||||
description:
|
||||
- Users cannot be added to projects in this group.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
mentions_disabled:
|
||||
description:
|
||||
- Group mentions are disabled.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
name:
|
||||
description:
|
||||
- Name of the group you want to create.
|
||||
required: true
|
||||
type: str
|
||||
parent:
|
||||
description:
|
||||
- Allow to create subgroups
|
||||
- Id or Full path of parent group in the form of group/name
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- The path of the group you want to create, this will be api_url/group_path
|
||||
- If not supplied, the group_name will be used.
|
||||
type: str
|
||||
prevent_forking_outside_group:
|
||||
description:
|
||||
- Prevent forking outside of the group.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
prevent_sharing_groups_outside_hierarchy:
|
||||
description:
|
||||
- Members cannot invite groups outside of this group and its subgroups.
|
||||
- Only available for top level groups.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
project_creation_level:
|
||||
description:
|
||||
- Determine if developers can create projects in the group.
|
||||
choices: ["developer", "maintainer", "noone"]
|
||||
type: str
|
||||
version_added: 3.7.0
|
||||
request_access_enabled:
|
||||
description:
|
||||
- Users can request access (if visibility is public or internal).
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
service_access_tokens_expiration_enforced:
|
||||
description:
|
||||
- Service account token expiration.
|
||||
- Changes will not affect existing token expiration dates.
|
||||
- Only available for top level groups.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
share_with_group_lock:
|
||||
description:
|
||||
- Projects cannot be shared with other groups.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
require_two_factor_authentication:
|
||||
description:
|
||||
- Require all users in this group to setup two-factor authentication.
|
||||
type: bool
|
||||
version_added: 3.7.0
|
||||
state:
|
||||
description:
|
||||
- create or delete group.
|
||||
- Possible values are present and absent.
|
||||
default: present
|
||||
type: str
|
||||
choices: ["present", "absent"]
|
||||
subgroup_creation_level:
|
||||
description:
|
||||
- Allowed to create subgroups.
|
||||
choices: ["maintainer", "owner"]
|
||||
type: str
|
||||
version_added: 3.7.0
|
||||
two_factor_grace_period:
|
||||
description:
|
||||
- Delay 2FA enforcement (hours).
|
||||
type: str
|
||||
version_added: 9.5.0
|
||||
visibility:
|
||||
description:
|
||||
- Default visibility of the group
|
||||
choices: ["private", "internal", "public"]
|
||||
default: private
|
||||
type: str
|
||||
wiki_access_level:
|
||||
description:
|
||||
- V(enabled) means everyone can access the wiki.
|
||||
- V(private) means only members of this group can access the wiki.
|
||||
- V(disabled) means group-level wiki is disabled.
|
||||
choices: ["enabled", "private", "disabled"]
|
||||
type: str
|
||||
version_added: 9.5.0
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
@@ -202,23 +278,38 @@ class GitLabGroup(object):
|
||||
def create_or_update_group(self, name, parent, options):
|
||||
changed = False
|
||||
|
||||
payload = {
|
||||
'auto_devops_enabled': options['auto_devops_enabled'],
|
||||
'default_branch': options['default_branch'],
|
||||
'description': options['description'],
|
||||
'lfs_enabled': options['lfs_enabled'],
|
||||
'membership_lock': options['membership_lock'],
|
||||
'mentions_disabled': options['mentions_disabled'],
|
||||
'name': name,
|
||||
'path': options['path'],
|
||||
'prevent_forking_outside_group': options['prevent_forking_outside_group'],
|
||||
'project_creation_level': options['project_creation_level'],
|
||||
'request_access_enabled': options['request_access_enabled'],
|
||||
'require_two_factor_authentication': options['require_two_factor_authentication'],
|
||||
'share_with_group_lock': options['share_with_group_lock'],
|
||||
'subgroup_creation_level': options['subgroup_creation_level'],
|
||||
'visibility': options['visibility'],
|
||||
'wiki_access_level': options['wiki_access_level'],
|
||||
}
|
||||
if options.get('enabled_git_access_protocol') and parent is None:
|
||||
payload['enabled_git_access_protocol'] = options['enabled_git_access_protocol']
|
||||
if options.get('lock_duo_features_enabled') and parent is None:
|
||||
payload['lock_duo_features_enabled'] = options['lock_duo_features_enabled']
|
||||
if options.get('prevent_sharing_groups_outside_hierarchy') and parent is None:
|
||||
payload['prevent_sharing_groups_outside_hierarchy'] = options['prevent_sharing_groups_outside_hierarchy']
|
||||
if options.get('service_access_tokens_expiration_enforced') and parent is None:
|
||||
payload['service_access_tokens_expiration_enforced'] = options['service_access_tokens_expiration_enforced']
|
||||
if options.get('two_factor_grace_period'):
|
||||
payload['two_factor_grace_period'] = int(options['two_factor_grace_period'])
|
||||
|
||||
# Because we have already call userExists in main()
|
||||
if self.group_object is None:
|
||||
parent_id = self.get_group_id(parent)
|
||||
|
||||
payload = {
|
||||
'name': name,
|
||||
'path': options['path'],
|
||||
'parent_id': parent_id,
|
||||
'visibility': options['visibility'],
|
||||
'project_creation_level': options['project_creation_level'],
|
||||
'auto_devops_enabled': options['auto_devops_enabled'],
|
||||
'subgroup_creation_level': options['subgroup_creation_level'],
|
||||
}
|
||||
if options.get('description'):
|
||||
payload['description'] = options['description']
|
||||
if options.get('require_two_factor_authentication'):
|
||||
payload['require_two_factor_authentication'] = options['require_two_factor_authentication']
|
||||
payload['parent_id'] = self.get_group_id(parent)
|
||||
group = self.create_group(payload)
|
||||
|
||||
# add avatar to group
|
||||
@@ -229,15 +320,7 @@ class GitLabGroup(object):
|
||||
self._module.fail_json(msg='Cannot open {0}: {1}'.format(options['avatar_path'], e))
|
||||
changed = True
|
||||
else:
|
||||
changed, group = self.update_group(self.group_object, {
|
||||
'name': name,
|
||||
'description': options['description'],
|
||||
'visibility': options['visibility'],
|
||||
'project_creation_level': options['project_creation_level'],
|
||||
'auto_devops_enabled': options['auto_devops_enabled'],
|
||||
'subgroup_creation_level': options['subgroup_creation_level'],
|
||||
'require_two_factor_authentication': options['require_two_factor_authentication'],
|
||||
})
|
||||
changed, group = self.update_group(self.group_object, payload)
|
||||
|
||||
self.group_object = group
|
||||
if changed:
|
||||
@@ -261,7 +344,7 @@ class GitLabGroup(object):
|
||||
|
||||
try:
|
||||
# Filter out None values
|
||||
filtered = dict((arg_key, arg_value) for arg_key, arg_value in arguments.items() if arg_value is not None)
|
||||
filtered = {arg_key: arg_value for arg_key, arg_value in arguments.items() if arg_value is not None}
|
||||
|
||||
group = self._gitlab.groups.create(filtered)
|
||||
except (gitlab.exceptions.GitlabCreateError) as e:
|
||||
@@ -277,9 +360,9 @@ class GitLabGroup(object):
|
||||
changed = False
|
||||
|
||||
for arg_key, arg_value in arguments.items():
|
||||
if arguments[arg_key] is not None:
|
||||
if getattr(group, arg_key) != arguments[arg_key]:
|
||||
setattr(group, arg_key, arguments[arg_key])
|
||||
if arg_value is not None:
|
||||
if getattr(group, arg_key) != arg_value:
|
||||
setattr(group, arg_key, arg_value)
|
||||
changed = True
|
||||
|
||||
return (changed, group)
|
||||
@@ -322,28 +405,41 @@ def main():
|
||||
argument_spec = basic_auth_argument_spec()
|
||||
argument_spec.update(auth_argument_spec())
|
||||
argument_spec.update(dict(
|
||||
name=dict(type='str', required=True),
|
||||
path=dict(type='str'),
|
||||
description=dict(type='str'),
|
||||
state=dict(type='str', default="present", choices=["absent", "present"]),
|
||||
parent=dict(type='str'),
|
||||
visibility=dict(type='str', default="private", choices=["internal", "private", "public"]),
|
||||
project_creation_level=dict(type='str', choices=['developer', 'maintainer', 'noone']),
|
||||
auto_devops_enabled=dict(type='bool'),
|
||||
subgroup_creation_level=dict(type='str', choices=['maintainer', 'owner']),
|
||||
require_two_factor_authentication=dict(type='bool'),
|
||||
avatar_path=dict(type='path'),
|
||||
default_branch=dict(type='str'),
|
||||
description=dict(type='str'),
|
||||
enabled_git_access_protocol=dict(type='str', choices=['all', 'ssh', 'http']),
|
||||
force_delete=dict(type='bool', default=False),
|
||||
lfs_enabled=dict(type='bool'),
|
||||
lock_duo_features_enabled=dict(type='bool'),
|
||||
membership_lock=dict(type='bool'),
|
||||
mentions_disabled=dict(type='bool'),
|
||||
name=dict(type='str', required=True),
|
||||
parent=dict(type='str'),
|
||||
path=dict(type='str'),
|
||||
prevent_forking_outside_group=dict(type='bool'),
|
||||
prevent_sharing_groups_outside_hierarchy=dict(type='bool'),
|
||||
project_creation_level=dict(type='str', choices=['developer', 'maintainer', 'noone']),
|
||||
request_access_enabled=dict(type='bool'),
|
||||
require_two_factor_authentication=dict(type='bool'),
|
||||
service_access_tokens_expiration_enforced=dict(type='bool'),
|
||||
share_with_group_lock=dict(type='bool'),
|
||||
state=dict(type='str', default="present", choices=["absent", "present"]),
|
||||
subgroup_creation_level=dict(type='str', choices=['maintainer', 'owner']),
|
||||
two_factor_grace_period=dict(type='str'),
|
||||
visibility=dict(type='str', default="private", choices=["internal", "private", "public"]),
|
||||
wiki_access_level=dict(type='str', choices=['enabled', 'private', 'disabled']),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
mutually_exclusive=[
|
||||
['api_username', 'api_token'],
|
||||
['api_username', 'api_oauth_token'],
|
||||
['api_username', 'api_job_token'],
|
||||
['api_token', 'api_oauth_token'],
|
||||
['api_token', 'api_job_token'],
|
||||
['api_token', 'api_oauth_token'],
|
||||
['api_username', 'api_job_token'],
|
||||
['api_username', 'api_oauth_token'],
|
||||
['api_username', 'api_token'],
|
||||
],
|
||||
required_together=[
|
||||
['api_username', 'api_password'],
|
||||
@@ -357,18 +453,31 @@ def main():
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
auto_devops_enabled = module.params['auto_devops_enabled']
|
||||
avatar_path = module.params['avatar_path']
|
||||
default_branch = module.params['default_branch']
|
||||
description = module.params['description']
|
||||
enabled_git_access_protocol = module.params['enabled_git_access_protocol']
|
||||
force_delete = module.params['force_delete']
|
||||
group_name = module.params['name']
|
||||
group_path = module.params['path']
|
||||
description = module.params['description']
|
||||
state = module.params['state']
|
||||
parent_identifier = module.params['parent']
|
||||
group_visibility = module.params['visibility']
|
||||
lfs_enabled = module.params['lfs_enabled']
|
||||
lock_duo_features_enabled = module.params['lock_duo_features_enabled']
|
||||
membership_lock = module.params['membership_lock']
|
||||
mentions_disabled = module.params['mentions_disabled']
|
||||
parent_identifier = module.params['parent']
|
||||
prevent_forking_outside_group = module.params['prevent_forking_outside_group']
|
||||
prevent_sharing_groups_outside_hierarchy = module.params['prevent_sharing_groups_outside_hierarchy']
|
||||
project_creation_level = module.params['project_creation_level']
|
||||
auto_devops_enabled = module.params['auto_devops_enabled']
|
||||
subgroup_creation_level = module.params['subgroup_creation_level']
|
||||
request_access_enabled = module.params['request_access_enabled']
|
||||
require_two_factor_authentication = module.params['require_two_factor_authentication']
|
||||
avatar_path = module.params['avatar_path']
|
||||
force_delete = module.params['force_delete']
|
||||
service_access_tokens_expiration_enforced = module.params['service_access_tokens_expiration_enforced']
|
||||
share_with_group_lock = module.params['share_with_group_lock']
|
||||
state = module.params['state']
|
||||
subgroup_creation_level = module.params['subgroup_creation_level']
|
||||
two_factor_grace_period = module.params['two_factor_grace_period']
|
||||
wiki_access_level = module.params['wiki_access_level']
|
||||
|
||||
# Define default group_path based on group_name
|
||||
if group_path is None:
|
||||
@@ -380,7 +489,7 @@ def main():
|
||||
if parent_identifier:
|
||||
parent_group = find_group(gitlab_instance, parent_identifier)
|
||||
if not parent_group:
|
||||
module.fail_json(msg="Failed create GitLab group: Parent group doesn't exists")
|
||||
module.fail_json(msg="Failed to create GitLab group: Parent group doesn't exist")
|
||||
|
||||
group_exists = gitlab_group.exists_group(parent_group.full_path + '/' + group_path)
|
||||
else:
|
||||
@@ -391,18 +500,31 @@ def main():
|
||||
gitlab_group.delete_group(force=force_delete)
|
||||
module.exit_json(changed=True, msg="Successfully deleted group %s" % group_name)
|
||||
else:
|
||||
module.exit_json(changed=False, msg="Group deleted or does not exists")
|
||||
module.exit_json(changed=False, msg="Group deleted or does not exist")
|
||||
|
||||
if state == 'present':
|
||||
if gitlab_group.create_or_update_group(group_name, parent_group, {
|
||||
"path": group_path,
|
||||
"description": description,
|
||||
"visibility": group_visibility,
|
||||
"project_creation_level": project_creation_level,
|
||||
"auto_devops_enabled": auto_devops_enabled,
|
||||
"subgroup_creation_level": subgroup_creation_level,
|
||||
"require_two_factor_authentication": require_two_factor_authentication,
|
||||
"avatar_path": avatar_path,
|
||||
"default_branch": default_branch,
|
||||
"description": description,
|
||||
"enabled_git_access_protocol": enabled_git_access_protocol,
|
||||
"lfs_enabled": lfs_enabled,
|
||||
"lock_duo_features_enabled": lock_duo_features_enabled,
|
||||
"membership_lock": membership_lock,
|
||||
"mentions_disabled": mentions_disabled,
|
||||
"path": group_path,
|
||||
"prevent_forking_outside_group": prevent_forking_outside_group,
|
||||
"prevent_sharing_groups_outside_hierarchy": prevent_sharing_groups_outside_hierarchy,
|
||||
"project_creation_level": project_creation_level,
|
||||
"request_access_enabled": request_access_enabled,
|
||||
"require_two_factor_authentication": require_two_factor_authentication,
|
||||
"service_access_tokens_expiration_enforced": service_access_tokens_expiration_enforced,
|
||||
"share_with_group_lock": share_with_group_lock,
|
||||
"subgroup_creation_level": subgroup_creation_level,
|
||||
"two_factor_grace_period": two_factor_grace_period,
|
||||
"visibility": group_visibility,
|
||||
"wiki_access_level": wiki_access_level,
|
||||
}):
|
||||
module.exit_json(changed=True, msg="Successfully created or updated the group %s" % group_name, group=gitlab_group.group_object._attrs)
|
||||
else:
|
||||
|
||||
@@ -313,7 +313,10 @@ def main():
|
||||
module.exit_json(changed=True, msg="Successfully recreated access token", access_token=gitlab_access_token.access_token_object._attrs)
|
||||
else:
|
||||
gitlab_access_token.create_access_token(group, {'name': name, 'scopes': scopes, 'access_level': access_level, 'expires_at': expires_at})
|
||||
module.exit_json(changed=True, msg="Successfully created access token", access_token=gitlab_access_token.access_token_object._attrs)
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, msg="Successfully created access token", access_token={})
|
||||
else:
|
||||
module.exit_json(changed=True, msg="Successfully created access token", access_token=gitlab_access_token.access_token_object._attrs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -264,14 +264,14 @@ class GitlabIssue(object):
|
||||
|
||||
if key == 'milestone_id':
|
||||
old_milestone = getattr(issue, 'milestone')['id'] if getattr(issue, 'milestone') else ""
|
||||
if options[key] != old_milestone:
|
||||
if value != old_milestone:
|
||||
return True
|
||||
elif key == 'assignee_ids':
|
||||
if options[key] != sorted([user["id"] for user in getattr(issue, 'assignees')]):
|
||||
if value != sorted([user["id"] for user in getattr(issue, 'assignees')]):
|
||||
return True
|
||||
|
||||
elif key == 'labels':
|
||||
if options[key] != sorted(getattr(issue, key)):
|
||||
if value != sorted(getattr(issue, key)):
|
||||
return True
|
||||
|
||||
elif getattr(issue, key) != value:
|
||||
|
||||
@@ -275,6 +275,8 @@ class GitlabLabels(object):
|
||||
_label.description = var_obj.get('description')
|
||||
if var_obj.get('priority') is not None:
|
||||
_label.priority = var_obj.get('priority')
|
||||
if var_obj.get('color') is not None:
|
||||
_label.color = var_obj.get('color')
|
||||
|
||||
# save returns None
|
||||
_label.save()
|
||||
|
||||
@@ -263,15 +263,15 @@ class GitlabMergeRequest(object):
|
||||
key = 'force_remove_source_branch'
|
||||
|
||||
if key == 'assignee_ids':
|
||||
if options[key] != sorted([user["id"] for user in getattr(mr, 'assignees')]):
|
||||
if value != sorted([user["id"] for user in getattr(mr, 'assignees')]):
|
||||
return True
|
||||
|
||||
elif key == 'reviewer_ids':
|
||||
if options[key] != sorted([user["id"] for user in getattr(mr, 'reviewers')]):
|
||||
if value != sorted([user["id"] for user in getattr(mr, 'reviewers')]):
|
||||
return True
|
||||
|
||||
elif key == 'labels':
|
||||
if options[key] != sorted(getattr(mr, key)):
|
||||
if value != sorted(getattr(mr, key)):
|
||||
return True
|
||||
|
||||
elif getattr(mr, key) != value:
|
||||
|
||||
@@ -15,7 +15,7 @@ module: gitlab_project
|
||||
short_description: Creates/updates/deletes GitLab Projects
|
||||
description:
|
||||
- When the project does not exist in GitLab, it will be created.
|
||||
- When the project does exists and O(state=absent), the project will be deleted.
|
||||
- When the project does exist and O(state=absent), the project will be deleted.
|
||||
- When changes are made to the project, the project will be updated.
|
||||
author:
|
||||
- Werner Dijkerman (@dj-wasabi)
|
||||
@@ -34,160 +34,17 @@ attributes:
|
||||
support: none
|
||||
|
||||
options:
|
||||
group:
|
||||
description:
|
||||
- Id or the full path of the group of which this projects belongs to.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- The name of the project.
|
||||
required: true
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- The path of the project you want to create, this will be server_url/<group>/path.
|
||||
- If not supplied, name will be used.
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- An description for the project.
|
||||
type: str
|
||||
initialize_with_readme:
|
||||
description:
|
||||
- Will initialize the project with a default C(README.md).
|
||||
- Is only used when the project is created, and ignored otherwise.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: "4.0.0"
|
||||
issues_enabled:
|
||||
description:
|
||||
- Whether you want to create issues or not.
|
||||
- Possible values are true and false.
|
||||
type: bool
|
||||
default: true
|
||||
merge_requests_enabled:
|
||||
description:
|
||||
- If merge requests can be made or not.
|
||||
- Possible values are true and false.
|
||||
type: bool
|
||||
default: true
|
||||
wiki_enabled:
|
||||
description:
|
||||
- If an wiki for this project should be available or not.
|
||||
type: bool
|
||||
default: true
|
||||
snippets_enabled:
|
||||
description:
|
||||
- If creating snippets should be available or not.
|
||||
type: bool
|
||||
default: true
|
||||
visibility:
|
||||
description:
|
||||
- V(private) Project access must be granted explicitly for each user.
|
||||
- V(internal) The project can be cloned by any logged in user.
|
||||
- V(public) The project can be cloned without any authentication.
|
||||
default: private
|
||||
type: str
|
||||
choices: ["private", "internal", "public"]
|
||||
aliases:
|
||||
- visibility_level
|
||||
import_url:
|
||||
description:
|
||||
- Git repository which will be imported into gitlab.
|
||||
- GitLab server needs read access to this git repository.
|
||||
required: false
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Create or delete project.
|
||||
- Possible values are present and absent.
|
||||
default: present
|
||||
type: str
|
||||
choices: ["present", "absent"]
|
||||
merge_method:
|
||||
description:
|
||||
- What requirements are placed upon merges.
|
||||
- Possible values are V(merge), V(rebase_merge) merge commit with semi-linear history, V(ff) fast-forward merges only.
|
||||
type: str
|
||||
choices: ["ff", "merge", "rebase_merge"]
|
||||
default: merge
|
||||
version_added: "1.0.0"
|
||||
lfs_enabled:
|
||||
description:
|
||||
- Enable Git large file systems to manages large files such
|
||||
as audio, video, and graphics files.
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
version_added: "2.0.0"
|
||||
username:
|
||||
description:
|
||||
- Used to create a personal project under a user's name.
|
||||
type: str
|
||||
version_added: "3.3.0"
|
||||
allow_merge_on_skipped_pipeline:
|
||||
description:
|
||||
- Allow merge when skipped pipelines exist.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
only_allow_merge_if_all_discussions_are_resolved:
|
||||
description:
|
||||
- All discussions on a merge request (MR) have to be resolved.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
only_allow_merge_if_pipeline_succeeds:
|
||||
description:
|
||||
- Only allow merges if pipeline succeeded.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
packages_enabled:
|
||||
description:
|
||||
- Enable GitLab package repository.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
remove_source_branch_after_merge:
|
||||
description:
|
||||
- Remove the source branch after merge.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
squash_option:
|
||||
description:
|
||||
- Squash commits when merging.
|
||||
type: str
|
||||
choices: ["never", "always", "default_off", "default_on"]
|
||||
version_added: "3.4.0"
|
||||
ci_config_path:
|
||||
description:
|
||||
- Custom path to the CI configuration file for this project.
|
||||
type: str
|
||||
version_added: "3.7.0"
|
||||
shared_runners_enabled:
|
||||
description:
|
||||
- Enable shared runners for this project.
|
||||
type: bool
|
||||
version_added: "3.7.0"
|
||||
avatar_path:
|
||||
description:
|
||||
- Absolute path image to configure avatar. File size should not exceed 200 kb.
|
||||
- This option is only used on creation, not for updates.
|
||||
type: path
|
||||
version_added: "4.2.0"
|
||||
default_branch:
|
||||
description:
|
||||
- The default branch name for this project.
|
||||
- For project creation, this option requires O(initialize_with_readme=true).
|
||||
- For project update, the branch must exist.
|
||||
- Supports project's default branch update since community.general 8.0.0.
|
||||
type: str
|
||||
version_added: "4.2.0"
|
||||
repository_access_level:
|
||||
description:
|
||||
- V(private) means that accessing repository is allowed only to project members.
|
||||
- V(disabled) means that accessing repository is disabled.
|
||||
- V(enabled) means that accessing repository is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "9.3.0"
|
||||
builds_access_level:
|
||||
description:
|
||||
- V(private) means that repository CI/CD is allowed only to project members.
|
||||
@@ -196,77 +53,11 @@ options:
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.2.0"
|
||||
forking_access_level:
|
||||
ci_config_path:
|
||||
description:
|
||||
- V(private) means that repository forks is allowed only to project members.
|
||||
- V(disabled) means that repository forks are disabled.
|
||||
- V(enabled) means that repository forks are enabled.
|
||||
- Custom path to the CI configuration file for this project.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.2.0"
|
||||
container_registry_access_level:
|
||||
description:
|
||||
- V(private) means that container registry is allowed only to project members.
|
||||
- V(disabled) means that container registry is disabled.
|
||||
- V(enabled) means that container registry is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.2.0"
|
||||
releases_access_level:
|
||||
description:
|
||||
- V(private) means that accessing release is allowed only to project members.
|
||||
- V(disabled) means that accessing release is disabled.
|
||||
- V(enabled) means that accessing release is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
environments_access_level:
|
||||
description:
|
||||
- V(private) means that deployment to environment is allowed only to project members.
|
||||
- V(disabled) means that deployment to environment is disabled.
|
||||
- V(enabled) means that deployment to environment is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
feature_flags_access_level:
|
||||
description:
|
||||
- V(private) means that feature rollout is allowed only to project members.
|
||||
- V(disabled) means that feature rollout is disabled.
|
||||
- V(enabled) means that feature rollout is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
infrastructure_access_level:
|
||||
description:
|
||||
- V(private) means that configuring infrastructure is allowed only to project members.
|
||||
- V(disabled) means that configuring infrastructure is disabled.
|
||||
- V(enabled) means that configuring infrastructure is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
monitor_access_level:
|
||||
description:
|
||||
- V(private) means that monitoring health is allowed only to project members.
|
||||
- V(disabled) means that monitoring health is disabled.
|
||||
- V(enabled) means that monitoring health is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
security_and_compliance_access_level:
|
||||
description:
|
||||
- V(private) means that accessing security and complicance tab is allowed only to project members.
|
||||
- V(disabled) means that accessing security and complicance tab is disabled.
|
||||
- V(enabled) means that accessing security and complicance tab is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
topics:
|
||||
description:
|
||||
- A topic or list of topics to be assigned to a project.
|
||||
- It is compatible with old GitLab server releases (versions before 14, correspond to C(tag_list)).
|
||||
type: list
|
||||
elements: str
|
||||
version_added: "6.6.0"
|
||||
version_added: "3.7.0"
|
||||
container_expiration_policy:
|
||||
description:
|
||||
- Project cleanup policy for its container registry.
|
||||
@@ -302,19 +93,111 @@ options:
|
||||
- Keep tags matching this regular expression.
|
||||
type: str
|
||||
version_added: "9.3.0"
|
||||
pages_access_level:
|
||||
container_registry_access_level:
|
||||
description:
|
||||
- V(private) means that accessing pages tab is allowed only to project members.
|
||||
- V(disabled) means that accessing pages tab is disabled.
|
||||
- V(enabled) means that accessing pages tab is enabled.
|
||||
- V(private) means that container registry is allowed only to project members.
|
||||
- V(disabled) means that container registry is disabled.
|
||||
- V(enabled) means that container registry is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "9.3.0"
|
||||
service_desk_enabled:
|
||||
version_added: "6.2.0"
|
||||
default_branch:
|
||||
description:
|
||||
- Enable Service Desk.
|
||||
- The default branch name for this project.
|
||||
- For project creation, this option requires O(initialize_with_readme=true).
|
||||
- For project update, the branch must exist.
|
||||
- Supports project's default branch update since community.general 8.0.0.
|
||||
type: str
|
||||
version_added: "4.2.0"
|
||||
description:
|
||||
description:
|
||||
- An description for the project.
|
||||
type: str
|
||||
environments_access_level:
|
||||
description:
|
||||
- V(private) means that deployment to environment is allowed only to project members.
|
||||
- V(disabled) means that deployment to environment is disabled.
|
||||
- V(enabled) means that deployment to environment is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
feature_flags_access_level:
|
||||
description:
|
||||
- V(private) means that feature rollout is allowed only to project members.
|
||||
- V(disabled) means that feature rollout is disabled.
|
||||
- V(enabled) means that feature rollout is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
forking_access_level:
|
||||
description:
|
||||
- V(private) means that repository forks is allowed only to project members.
|
||||
- V(disabled) means that repository forks are disabled.
|
||||
- V(enabled) means that repository forks are enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.2.0"
|
||||
group:
|
||||
description:
|
||||
- Id or the full path of the group of which this projects belongs to.
|
||||
type: str
|
||||
import_url:
|
||||
description:
|
||||
- Git repository which will be imported into gitlab.
|
||||
- GitLab server needs read access to this git repository.
|
||||
required: false
|
||||
type: str
|
||||
infrastructure_access_level:
|
||||
description:
|
||||
- V(private) means that configuring infrastructure is allowed only to project members.
|
||||
- V(disabled) means that configuring infrastructure is disabled.
|
||||
- V(enabled) means that configuring infrastructure is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
initialize_with_readme:
|
||||
description:
|
||||
- Will initialize the project with a default C(README.md).
|
||||
- Is only used when the project is created, and ignored otherwise.
|
||||
type: bool
|
||||
version_added: "9.3.0"
|
||||
default: false
|
||||
version_added: "4.0.0"
|
||||
issues_access_level:
|
||||
description:
|
||||
- V(private) means that accessing issues tab is allowed only to project members.
|
||||
- V(disabled) means that accessing issues tab is disabled.
|
||||
- V(enabled) means that accessing issues tab is enabled.
|
||||
- O(issues_access_level) and O(issues_enabled) are mutually exclusive.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "9.4.0"
|
||||
issues_enabled:
|
||||
description:
|
||||
- Whether you want to create issues or not.
|
||||
- O(issues_access_level) and O(issues_enabled) are mutually exclusive.
|
||||
type: bool
|
||||
default: true
|
||||
lfs_enabled:
|
||||
description:
|
||||
- Enable Git large file systems to manages large files such
|
||||
as audio, video, and graphics files.
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
version_added: "2.0.0"
|
||||
merge_method:
|
||||
description:
|
||||
- What requirements are placed upon merges.
|
||||
- Possible values are V(merge), V(rebase_merge) merge commit with semi-linear history, V(ff) fast-forward merges only.
|
||||
type: str
|
||||
choices: ["ff", "merge", "rebase_merge"]
|
||||
default: merge
|
||||
version_added: "1.0.0"
|
||||
merge_requests_enabled:
|
||||
description:
|
||||
- If merge requests can be made or not.
|
||||
type: bool
|
||||
default: true
|
||||
model_registry_access_level:
|
||||
description:
|
||||
- V(private) means that accessing model registry tab is allowed only to project members.
|
||||
@@ -323,6 +206,131 @@ options:
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "9.3.0"
|
||||
monitor_access_level:
|
||||
description:
|
||||
- V(private) means that monitoring health is allowed only to project members.
|
||||
- V(disabled) means that monitoring health is disabled.
|
||||
- V(enabled) means that monitoring health is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
name:
|
||||
description:
|
||||
- The name of the project.
|
||||
required: true
|
||||
type: str
|
||||
only_allow_merge_if_all_discussions_are_resolved:
|
||||
description:
|
||||
- All discussions on a merge request (MR) have to be resolved.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
only_allow_merge_if_pipeline_succeeds:
|
||||
description:
|
||||
- Only allow merges if pipeline succeeded.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
packages_enabled:
|
||||
description:
|
||||
- Enable GitLab package repository.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
pages_access_level:
|
||||
description:
|
||||
- V(private) means that accessing pages tab is allowed only to project members.
|
||||
- V(disabled) means that accessing pages tab is disabled.
|
||||
- V(enabled) means that accessing pages tab is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "9.3.0"
|
||||
path:
|
||||
description:
|
||||
- The path of the project you want to create, this will be server_url/<group>/path.
|
||||
- If not supplied, name will be used.
|
||||
type: str
|
||||
releases_access_level:
|
||||
description:
|
||||
- V(private) means that accessing release is allowed only to project members.
|
||||
- V(disabled) means that accessing release is disabled.
|
||||
- V(enabled) means that accessing release is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
remove_source_branch_after_merge:
|
||||
description:
|
||||
- Remove the source branch after merge.
|
||||
type: bool
|
||||
version_added: "3.4.0"
|
||||
repository_access_level:
|
||||
description:
|
||||
- V(private) means that accessing repository is allowed only to project members.
|
||||
- V(disabled) means that accessing repository is disabled.
|
||||
- V(enabled) means that accessing repository is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "9.3.0"
|
||||
security_and_compliance_access_level:
|
||||
description:
|
||||
- V(private) means that accessing security and complicance tab is allowed only to project members.
|
||||
- V(disabled) means that accessing security and complicance tab is disabled.
|
||||
- V(enabled) means that accessing security and complicance tab is enabled.
|
||||
type: str
|
||||
choices: ["private", "disabled", "enabled"]
|
||||
version_added: "6.4.0"
|
||||
service_desk_enabled:
|
||||
description:
|
||||
- Enable Service Desk.
|
||||
type: bool
|
||||
version_added: "9.3.0"
|
||||
shared_runners_enabled:
|
||||
description:
|
||||
- Enable shared runners for this project.
|
||||
type: bool
|
||||
version_added: "3.7.0"
|
||||
snippets_enabled:
|
||||
description:
|
||||
- If creating snippets should be available or not.
|
||||
type: bool
|
||||
default: true
|
||||
squash_option:
|
||||
description:
|
||||
- Squash commits when merging.
|
||||
type: str
|
||||
choices: ["never", "always", "default_off", "default_on"]
|
||||
version_added: "3.4.0"
|
||||
state:
|
||||
description:
|
||||
- Create or delete project.
|
||||
- Possible values are present and absent.
|
||||
default: present
|
||||
type: str
|
||||
choices: ["present", "absent"]
|
||||
topics:
|
||||
description:
|
||||
- A topic or list of topics to be assigned to a project.
|
||||
- It is compatible with old GitLab server releases (versions before 14, correspond to C(tag_list)).
|
||||
type: list
|
||||
elements: str
|
||||
version_added: "6.6.0"
|
||||
username:
|
||||
description:
|
||||
- Used to create a personal project under a user's name.
|
||||
type: str
|
||||
version_added: "3.3.0"
|
||||
visibility:
|
||||
description:
|
||||
- V(private) Project access must be granted explicitly for each user.
|
||||
- V(internal) The project can be cloned by any logged in user.
|
||||
- V(public) The project can be cloned without any authentication.
|
||||
default: private
|
||||
type: str
|
||||
choices: ["private", "internal", "public"]
|
||||
aliases:
|
||||
- visibility_level
|
||||
wiki_enabled:
|
||||
description:
|
||||
- If an wiki for this project should be available or not.
|
||||
type: bool
|
||||
default: true
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
@@ -422,37 +430,38 @@ class GitLabProject(object):
|
||||
def create_or_update_project(self, module, project_name, namespace, options):
|
||||
changed = False
|
||||
project_options = {
|
||||
'name': project_name,
|
||||
'description': options['description'],
|
||||
'issues_enabled': options['issues_enabled'],
|
||||
'merge_requests_enabled': options['merge_requests_enabled'],
|
||||
'merge_method': options['merge_method'],
|
||||
'wiki_enabled': options['wiki_enabled'],
|
||||
'snippets_enabled': options['snippets_enabled'],
|
||||
'visibility': options['visibility'],
|
||||
'lfs_enabled': options['lfs_enabled'],
|
||||
'allow_merge_on_skipped_pipeline': options['allow_merge_on_skipped_pipeline'],
|
||||
'builds_access_level': options['builds_access_level'],
|
||||
'ci_config_path': options['ci_config_path'],
|
||||
'container_expiration_policy': options['container_expiration_policy'],
|
||||
'container_registry_access_level': options['container_registry_access_level'],
|
||||
'description': options['description'],
|
||||
'environments_access_level': options['environments_access_level'],
|
||||
'feature_flags_access_level': options['feature_flags_access_level'],
|
||||
'forking_access_level': options['forking_access_level'],
|
||||
'infrastructure_access_level': options['infrastructure_access_level'],
|
||||
'issues_access_level': options['issues_access_level'],
|
||||
'issues_enabled': options['issues_enabled'],
|
||||
'lfs_enabled': options['lfs_enabled'],
|
||||
'merge_method': options['merge_method'],
|
||||
'merge_requests_enabled': options['merge_requests_enabled'],
|
||||
'model_registry_access_level': options['model_registry_access_level'],
|
||||
'monitor_access_level': options['monitor_access_level'],
|
||||
'name': project_name,
|
||||
'only_allow_merge_if_all_discussions_are_resolved': options['only_allow_merge_if_all_discussions_are_resolved'],
|
||||
'only_allow_merge_if_pipeline_succeeds': options['only_allow_merge_if_pipeline_succeeds'],
|
||||
'packages_enabled': options['packages_enabled'],
|
||||
'remove_source_branch_after_merge': options['remove_source_branch_after_merge'],
|
||||
'squash_option': options['squash_option'],
|
||||
'ci_config_path': options['ci_config_path'],
|
||||
'shared_runners_enabled': options['shared_runners_enabled'],
|
||||
'repository_access_level': options['repository_access_level'],
|
||||
'builds_access_level': options['builds_access_level'],
|
||||
'forking_access_level': options['forking_access_level'],
|
||||
'container_registry_access_level': options['container_registry_access_level'],
|
||||
'releases_access_level': options['releases_access_level'],
|
||||
'environments_access_level': options['environments_access_level'],
|
||||
'feature_flags_access_level': options['feature_flags_access_level'],
|
||||
'infrastructure_access_level': options['infrastructure_access_level'],
|
||||
'monitor_access_level': options['monitor_access_level'],
|
||||
'security_and_compliance_access_level': options['security_and_compliance_access_level'],
|
||||
'container_expiration_policy': options['container_expiration_policy'],
|
||||
'pages_access_level': options['pages_access_level'],
|
||||
'releases_access_level': options['releases_access_level'],
|
||||
'remove_source_branch_after_merge': options['remove_source_branch_after_merge'],
|
||||
'repository_access_level': options['repository_access_level'],
|
||||
'security_and_compliance_access_level': options['security_and_compliance_access_level'],
|
||||
'service_desk_enabled': options['service_desk_enabled'],
|
||||
'model_registry_access_level': options['model_registry_access_level'],
|
||||
'shared_runners_enabled': options['shared_runners_enabled'],
|
||||
'snippets_enabled': options['snippets_enabled'],
|
||||
'squash_option': options['squash_option'],
|
||||
'visibility': options['visibility'],
|
||||
'wiki_enabled': options['wiki_enabled'],
|
||||
}
|
||||
|
||||
# topics was introduced on gitlab >=14 and replace tag_list. We get current gitlab version
|
||||
@@ -465,7 +474,7 @@ class GitLabProject(object):
|
||||
# Because we have already call userExists in main()
|
||||
if self.project_object is None:
|
||||
if options['default_branch'] and not options['initialize_with_readme']:
|
||||
module.fail_json(msg="Param default_branch need param initialize_with_readme set to true")
|
||||
module.fail_json(msg="Param default_branch needs param initialize_with_readme set to true")
|
||||
project_options.update({
|
||||
'path': options['path'],
|
||||
'import_url': options['import_url'],
|
||||
@@ -499,7 +508,7 @@ class GitLabProject(object):
|
||||
try:
|
||||
project.save()
|
||||
except Exception as e:
|
||||
self._module.fail_json(msg="Failed update project: %s " % e)
|
||||
self._module.fail_json(msg="Failed to update project: %s " % e)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -512,6 +521,8 @@ class GitLabProject(object):
|
||||
return True
|
||||
|
||||
arguments['namespace_id'] = namespace.id
|
||||
if 'container_expiration_policy' in arguments:
|
||||
arguments['container_expiration_policy_attributes'] = arguments['container_expiration_policy']
|
||||
try:
|
||||
project = self._gitlab.projects.create(arguments)
|
||||
except (gitlab.exceptions.GitlabCreateError) as e:
|
||||
@@ -523,11 +534,7 @@ class GitLabProject(object):
|
||||
@param arguments Attributes of the project
|
||||
'''
|
||||
def get_options_with_value(self, arguments):
|
||||
ret_arguments = dict()
|
||||
for arg_key, arg_value in arguments.items():
|
||||
if arguments[arg_key] is not None:
|
||||
ret_arguments[arg_key] = arg_value
|
||||
|
||||
ret_arguments = {k: v for k, v in arguments.items() if v is not None}
|
||||
return ret_arguments
|
||||
|
||||
'''
|
||||
@@ -538,10 +545,10 @@ class GitLabProject(object):
|
||||
changed = False
|
||||
|
||||
for arg_key, arg_value in arguments.items():
|
||||
if arguments[arg_key] is not None:
|
||||
if getattr(project, arg_key) != arguments[arg_key]:
|
||||
if arg_value is not None:
|
||||
if getattr(project, arg_key, None) != arg_value:
|
||||
if arg_key == 'container_expiration_policy':
|
||||
old_val = getattr(project, arg_key)
|
||||
old_val = getattr(project, arg_key, {})
|
||||
final_val = {key: value for key, value in arg_value.items() if value is not None}
|
||||
|
||||
if final_val.get('older_than') == '0d':
|
||||
@@ -583,42 +590,10 @@ def main():
|
||||
argument_spec = basic_auth_argument_spec()
|
||||
argument_spec.update(auth_argument_spec())
|
||||
argument_spec.update(dict(
|
||||
group=dict(type='str'),
|
||||
name=dict(type='str', required=True),
|
||||
path=dict(type='str'),
|
||||
description=dict(type='str'),
|
||||
initialize_with_readme=dict(type='bool', default=False),
|
||||
default_branch=dict(type='str'),
|
||||
issues_enabled=dict(type='bool', default=True),
|
||||
merge_requests_enabled=dict(type='bool', default=True),
|
||||
merge_method=dict(type='str', default='merge', choices=["merge", "rebase_merge", "ff"]),
|
||||
wiki_enabled=dict(type='bool', default=True),
|
||||
snippets_enabled=dict(default=True, type='bool'),
|
||||
visibility=dict(type='str', default="private", choices=["internal", "private", "public"], aliases=["visibility_level"]),
|
||||
import_url=dict(type='str'),
|
||||
state=dict(type='str', default="present", choices=["absent", "present"]),
|
||||
lfs_enabled=dict(default=False, type='bool'),
|
||||
username=dict(type='str'),
|
||||
allow_merge_on_skipped_pipeline=dict(type='bool'),
|
||||
only_allow_merge_if_all_discussions_are_resolved=dict(type='bool'),
|
||||
only_allow_merge_if_pipeline_succeeds=dict(type='bool'),
|
||||
packages_enabled=dict(type='bool'),
|
||||
remove_source_branch_after_merge=dict(type='bool'),
|
||||
squash_option=dict(type='str', choices=['never', 'always', 'default_off', 'default_on']),
|
||||
ci_config_path=dict(type='str'),
|
||||
shared_runners_enabled=dict(type='bool'),
|
||||
avatar_path=dict(type='path'),
|
||||
repository_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
builds_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
forking_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
container_registry_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
releases_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
environments_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
feature_flags_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
infrastructure_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
monitor_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
security_and_compliance_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
topics=dict(type='list', elements='str'),
|
||||
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"]),
|
||||
enabled=dict(type='bool'),
|
||||
@@ -627,9 +602,42 @@ def main():
|
||||
name_regex=dict(type='str'),
|
||||
name_regex_keep=dict(type='str'),
|
||||
)),
|
||||
pages_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
service_desk_enabled=dict(type='bool'),
|
||||
container_registry_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
default_branch=dict(type='str'),
|
||||
description=dict(type='str'),
|
||||
environments_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
feature_flags_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
forking_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
group=dict(type='str'),
|
||||
import_url=dict(type='str'),
|
||||
infrastructure_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
initialize_with_readme=dict(type='bool', default=False),
|
||||
issues_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
issues_enabled=dict(type='bool', default=True),
|
||||
lfs_enabled=dict(default=False, type='bool'),
|
||||
merge_method=dict(type='str', default='merge', choices=["merge", "rebase_merge", "ff"]),
|
||||
merge_requests_enabled=dict(type='bool', default=True),
|
||||
model_registry_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
monitor_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
name=dict(type='str', required=True),
|
||||
only_allow_merge_if_all_discussions_are_resolved=dict(type='bool'),
|
||||
only_allow_merge_if_pipeline_succeeds=dict(type='bool'),
|
||||
packages_enabled=dict(type='bool'),
|
||||
pages_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
path=dict(type='str'),
|
||||
releases_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
remove_source_branch_after_merge=dict(type='bool'),
|
||||
repository_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
security_and_compliance_access_level=dict(type='str', choices=['private', 'disabled', 'enabled']),
|
||||
service_desk_enabled=dict(type='bool'),
|
||||
shared_runners_enabled=dict(type='bool'),
|
||||
snippets_enabled=dict(default=True, type='bool'),
|
||||
squash_option=dict(type='str', choices=['never', 'always', 'default_off', 'default_on']),
|
||||
state=dict(type='str', default="present", choices=["absent", "present"]),
|
||||
topics=dict(type='list', elements='str'),
|
||||
username=dict(type='str'),
|
||||
visibility=dict(type='str', default="private", choices=["internal", "private", "public"], aliases=["visibility_level"]),
|
||||
wiki_enabled=dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
@@ -641,6 +649,7 @@ def main():
|
||||
['api_token', 'api_oauth_token'],
|
||||
['api_token', 'api_job_token'],
|
||||
['group', 'username'],
|
||||
['issues_access_level', 'issues_enabled'],
|
||||
],
|
||||
required_together=[
|
||||
['api_username', 'api_password'],
|
||||
@@ -654,46 +663,47 @@ def main():
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
group_identifier = module.params['group']
|
||||
project_name = module.params['name']
|
||||
project_path = module.params['path']
|
||||
project_description = module.params['description']
|
||||
initialize_with_readme = module.params['initialize_with_readme']
|
||||
issues_enabled = module.params['issues_enabled']
|
||||
merge_requests_enabled = module.params['merge_requests_enabled']
|
||||
merge_method = module.params['merge_method']
|
||||
wiki_enabled = module.params['wiki_enabled']
|
||||
snippets_enabled = module.params['snippets_enabled']
|
||||
visibility = module.params['visibility']
|
||||
import_url = module.params['import_url']
|
||||
state = module.params['state']
|
||||
lfs_enabled = module.params['lfs_enabled']
|
||||
username = module.params['username']
|
||||
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']
|
||||
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']
|
||||
default_branch = module.params['default_branch']
|
||||
environments_access_level = module.params['environments_access_level']
|
||||
feature_flags_access_level = module.params['feature_flags_access_level']
|
||||
forking_access_level = module.params['forking_access_level']
|
||||
group_identifier = module.params['group']
|
||||
import_url = module.params['import_url']
|
||||
infrastructure_access_level = module.params['infrastructure_access_level']
|
||||
initialize_with_readme = module.params['initialize_with_readme']
|
||||
issues_access_level = module.params['issues_access_level']
|
||||
issues_enabled = module.params['issues_enabled']
|
||||
lfs_enabled = module.params['lfs_enabled']
|
||||
merge_method = module.params['merge_method']
|
||||
merge_requests_enabled = module.params['merge_requests_enabled']
|
||||
model_registry_access_level = module.params['model_registry_access_level']
|
||||
monitor_access_level = module.params['monitor_access_level']
|
||||
only_allow_merge_if_all_discussions_are_resolved = module.params['only_allow_merge_if_all_discussions_are_resolved']
|
||||
only_allow_merge_if_pipeline_succeeds = module.params['only_allow_merge_if_pipeline_succeeds']
|
||||
packages_enabled = module.params['packages_enabled']
|
||||
remove_source_branch_after_merge = module.params['remove_source_branch_after_merge']
|
||||
squash_option = module.params['squash_option']
|
||||
ci_config_path = module.params['ci_config_path']
|
||||
shared_runners_enabled = module.params['shared_runners_enabled']
|
||||
avatar_path = module.params['avatar_path']
|
||||
default_branch = module.params['default_branch']
|
||||
repository_access_level = module.params['repository_access_level']
|
||||
builds_access_level = module.params['builds_access_level']
|
||||
forking_access_level = module.params['forking_access_level']
|
||||
container_registry_access_level = module.params['container_registry_access_level']
|
||||
releases_access_level = module.params['releases_access_level']
|
||||
environments_access_level = module.params['environments_access_level']
|
||||
feature_flags_access_level = module.params['feature_flags_access_level']
|
||||
infrastructure_access_level = module.params['infrastructure_access_level']
|
||||
monitor_access_level = module.params['monitor_access_level']
|
||||
security_and_compliance_access_level = module.params['security_and_compliance_access_level']
|
||||
topics = module.params['topics']
|
||||
container_expiration_policy = module.params['container_expiration_policy']
|
||||
pages_access_level = module.params['pages_access_level']
|
||||
project_description = module.params['description']
|
||||
project_name = module.params['name']
|
||||
project_path = module.params['path']
|
||||
releases_access_level = module.params['releases_access_level']
|
||||
remove_source_branch_after_merge = module.params['remove_source_branch_after_merge']
|
||||
repository_access_level = module.params['repository_access_level']
|
||||
security_and_compliance_access_level = module.params['security_and_compliance_access_level']
|
||||
service_desk_enabled = module.params['service_desk_enabled']
|
||||
model_registry_access_level = module.params['model_registry_access_level']
|
||||
shared_runners_enabled = module.params['shared_runners_enabled']
|
||||
snippets_enabled = module.params['snippets_enabled']
|
||||
squash_option = module.params['squash_option']
|
||||
state = module.params['state']
|
||||
topics = module.params['topics']
|
||||
username = module.params['username']
|
||||
visibility = module.params['visibility']
|
||||
wiki_enabled = module.params['wiki_enabled']
|
||||
|
||||
# Set project_path to project_name if it is empty.
|
||||
if project_path is None:
|
||||
@@ -706,7 +716,7 @@ def main():
|
||||
if group_identifier:
|
||||
group = find_group(gitlab_instance, group_identifier)
|
||||
if group is None:
|
||||
module.fail_json(msg="Failed to create project: group %s doesn't exists" % group_identifier)
|
||||
module.fail_json(msg="Failed to create project: group %s doesn't exist" % group_identifier)
|
||||
|
||||
namespace_id = group.id
|
||||
else:
|
||||
@@ -737,42 +747,43 @@ def main():
|
||||
if state == 'present':
|
||||
|
||||
if gitlab_project.create_or_update_project(module, project_name, namespace, {
|
||||
"path": project_path,
|
||||
"description": project_description,
|
||||
"initialize_with_readme": initialize_with_readme,
|
||||
"default_branch": default_branch,
|
||||
"issues_enabled": issues_enabled,
|
||||
"merge_requests_enabled": merge_requests_enabled,
|
||||
"merge_method": merge_method,
|
||||
"wiki_enabled": wiki_enabled,
|
||||
"snippets_enabled": snippets_enabled,
|
||||
"visibility": visibility,
|
||||
"import_url": import_url,
|
||||
"lfs_enabled": lfs_enabled,
|
||||
"allow_merge_on_skipped_pipeline": allow_merge_on_skipped_pipeline,
|
||||
"avatar_path": avatar_path,
|
||||
"builds_access_level": builds_access_level,
|
||||
"ci_config_path": ci_config_path,
|
||||
"container_expiration_policy": container_expiration_policy,
|
||||
"container_registry_access_level": container_registry_access_level,
|
||||
"default_branch": default_branch,
|
||||
"description": project_description,
|
||||
"environments_access_level": environments_access_level,
|
||||
"feature_flags_access_level": feature_flags_access_level,
|
||||
"forking_access_level": forking_access_level,
|
||||
"import_url": import_url,
|
||||
"infrastructure_access_level": infrastructure_access_level,
|
||||
"initialize_with_readme": initialize_with_readme,
|
||||
"issues_access_level": issues_access_level,
|
||||
"issues_enabled": issues_enabled,
|
||||
"lfs_enabled": lfs_enabled,
|
||||
"merge_method": merge_method,
|
||||
"merge_requests_enabled": merge_requests_enabled,
|
||||
"model_registry_access_level": model_registry_access_level,
|
||||
"monitor_access_level": monitor_access_level,
|
||||
"only_allow_merge_if_all_discussions_are_resolved": only_allow_merge_if_all_discussions_are_resolved,
|
||||
"only_allow_merge_if_pipeline_succeeds": only_allow_merge_if_pipeline_succeeds,
|
||||
"packages_enabled": packages_enabled,
|
||||
"remove_source_branch_after_merge": remove_source_branch_after_merge,
|
||||
"squash_option": squash_option,
|
||||
"ci_config_path": ci_config_path,
|
||||
"shared_runners_enabled": shared_runners_enabled,
|
||||
"avatar_path": avatar_path,
|
||||
"repository_access_level": repository_access_level,
|
||||
"builds_access_level": builds_access_level,
|
||||
"forking_access_level": forking_access_level,
|
||||
"container_registry_access_level": container_registry_access_level,
|
||||
"releases_access_level": releases_access_level,
|
||||
"environments_access_level": environments_access_level,
|
||||
"feature_flags_access_level": feature_flags_access_level,
|
||||
"infrastructure_access_level": infrastructure_access_level,
|
||||
"monitor_access_level": monitor_access_level,
|
||||
"security_and_compliance_access_level": security_and_compliance_access_level,
|
||||
"topics": topics,
|
||||
"container_expiration_policy": container_expiration_policy,
|
||||
"pages_access_level": pages_access_level,
|
||||
"path": project_path,
|
||||
"releases_access_level": releases_access_level,
|
||||
"remove_source_branch_after_merge": remove_source_branch_after_merge,
|
||||
"repository_access_level": repository_access_level,
|
||||
"security_and_compliance_access_level": security_and_compliance_access_level,
|
||||
"service_desk_enabled": service_desk_enabled,
|
||||
"model_registry_access_level": model_registry_access_level,
|
||||
"shared_runners_enabled": shared_runners_enabled,
|
||||
"snippets_enabled": snippets_enabled,
|
||||
"squash_option": squash_option,
|
||||
"topics": topics,
|
||||
"visibility": visibility,
|
||||
"wiki_enabled": wiki_enabled,
|
||||
}):
|
||||
|
||||
module.exit_json(changed=True, msg="Successfully created or updated the project %s" % project_name, project=gitlab_project.project_object._attrs)
|
||||
|
||||
@@ -311,7 +311,10 @@ def main():
|
||||
module.exit_json(changed=True, msg="Successfully recreated access token", access_token=gitlab_access_token.access_token_object._attrs)
|
||||
else:
|
||||
gitlab_access_token.create_access_token(project, {'name': name, 'scopes': scopes, 'access_level': access_level, 'expires_at': expires_at})
|
||||
module.exit_json(changed=True, msg="Successfully created access token", access_token=gitlab_access_token.access_token_object._attrs)
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, msg="Successfully created access token", access_token={})
|
||||
else:
|
||||
module.exit_json(changed=True, msg="Successfully created access token", access_token=gitlab_access_token.access_token_object._attrs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -33,7 +33,10 @@ author:
|
||||
- Samy Coenen (@SamyCoenen)
|
||||
- Guillaume Martinez (@Lunik)
|
||||
requirements:
|
||||
- python-gitlab >= 1.5.0
|
||||
- python-gitlab >= 1.5.0 for legacy runner registration workflow
|
||||
(runner registration token - U(https://docs.gitlab.com/runner/register/#register-with-a-runner-registration-token-deprecated))
|
||||
- python-gitlab >= 4.0.0 for new runner registration workflow
|
||||
(runner authentication token - U(https://docs.gitlab.com/runner/register/#register-with-a-runner-authentication-token))
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
- community.general.gitlab
|
||||
@@ -365,18 +368,18 @@ class GitLabRunner(object):
|
||||
changed = False
|
||||
|
||||
for arg_key, arg_value in arguments.items():
|
||||
if arguments[arg_key] is not None:
|
||||
if isinstance(arguments[arg_key], list):
|
||||
if arg_value is not None:
|
||||
if isinstance(arg_value, list):
|
||||
list1 = getattr(runner, arg_key)
|
||||
list1.sort()
|
||||
list2 = arguments[arg_key]
|
||||
list2 = arg_value
|
||||
list2.sort()
|
||||
if list1 != list2:
|
||||
setattr(runner, arg_key, arguments[arg_key])
|
||||
setattr(runner, arg_key, arg_value)
|
||||
changed = True
|
||||
else:
|
||||
if getattr(runner, arg_key) != arguments[arg_key]:
|
||||
setattr(runner, arg_key, arguments[arg_key])
|
||||
if getattr(runner, arg_key) != arg_value:
|
||||
setattr(runner, arg_key, arg_value)
|
||||
changed = True
|
||||
|
||||
return (changed, runner)
|
||||
|
||||
@@ -17,6 +17,10 @@ description:
|
||||
- Send a message to a Hipchat room, with options to control the formatting.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
deprecated:
|
||||
removed_in: 11.0.0
|
||||
why: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
alternative: There is none.
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
|
||||
@@ -190,6 +190,7 @@ class HomebrewCask(object):
|
||||
/ # slash (for taps)
|
||||
\- # dashes
|
||||
@ # at symbol
|
||||
\+ # plus symbol
|
||||
'''
|
||||
|
||||
INVALID_CASK_REGEX = _create_regex_group_complement(VALID_CASK_CHARS)
|
||||
|
||||
@@ -61,17 +61,17 @@ EXAMPLES = """
|
||||
state: present
|
||||
|
||||
- name: Start the foo service (equivalent to `brew services start foo`)
|
||||
community.general.homebrew_service:
|
||||
community.general.homebrew_services:
|
||||
name: foo
|
||||
state: present
|
||||
|
||||
- name: Restart the foo service (equivalent to `brew services restart foo`)
|
||||
community.general.homebrew_service:
|
||||
community.general.homebrew_services:
|
||||
name: foo
|
||||
state: restarted
|
||||
|
||||
- name: Remove the foo service (equivalent to `brew services stop foo`)
|
||||
community.general.homebrew_service:
|
||||
community.general.homebrew_services:
|
||||
name: foo
|
||||
service_state: absent
|
||||
"""
|
||||
|
||||
@@ -18,11 +18,11 @@ version_added: 4.4.0
|
||||
description:
|
||||
- Manages a user's home directory managed by systemd-homed.
|
||||
notes:
|
||||
- This module does B(not) work with Python 3.13 or newer. It uses the deprecated L(crypt Python module,
|
||||
https://docs.python.org/3.12/library/crypt.html) from the Python standard library, which was removed
|
||||
from Python 3.13.
|
||||
- This module requires the deprecated L(crypt Python module,
|
||||
https://docs.python.org/3.12/library/crypt.html) library which was removed from Python 3.13.
|
||||
For Python 3.13 or newer, you need to install L(legacycrypt, https://pypi.org/project/legacycrypt/).
|
||||
requirements:
|
||||
- Python 3.12 or earlier
|
||||
- legacycrypt (on Python 3.13 or newer)
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
@@ -284,6 +284,17 @@ else:
|
||||
HAS_CRYPT = True
|
||||
CRYPT_IMPORT_ERROR = None
|
||||
|
||||
try:
|
||||
import legacycrypt
|
||||
if not HAS_CRYPT:
|
||||
crypt = legacycrypt
|
||||
except ImportError:
|
||||
HAS_LEGACYCRYPT = False
|
||||
LEGACYCRYPT_IMPORT_ERROR = traceback.format_exc()
|
||||
else:
|
||||
HAS_LEGACYCRYPT = True
|
||||
LEGACYCRYPT_IMPORT_ERROR = None
|
||||
|
||||
|
||||
class Homectl(object):
|
||||
'''#TODO DOC STRINGS'''
|
||||
@@ -606,9 +617,9 @@ def main():
|
||||
]
|
||||
)
|
||||
|
||||
if not HAS_CRYPT:
|
||||
if not HAS_CRYPT and not HAS_LEGACYCRYPT:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib('crypt (part of Python 3.13 standard library)'),
|
||||
msg=missing_required_lib('crypt (part of standard library up to Python 3.12) or legacycrypt (PyPI)'),
|
||||
exception=CRYPT_IMPORT_ERROR,
|
||||
)
|
||||
|
||||
|
||||
@@ -1163,8 +1163,7 @@ def send_delete_volume_request(module, params, client, info):
|
||||
path_parameters = {
|
||||
"volume_id": ["volume_id"],
|
||||
}
|
||||
data = dict((key, navigate_value(info, path))
|
||||
for key, path in path_parameters.items())
|
||||
data = {key: navigate_value(info, path) for key, path in path_parameters.items()}
|
||||
|
||||
url = build_path(module, "cloudservers/{id}/detachvolume/{volume_id}", data)
|
||||
|
||||
|
||||
@@ -771,8 +771,7 @@ def async_wait(config, result, client, timeout):
|
||||
path_parameters = {
|
||||
"job_id": ["job_id"],
|
||||
}
|
||||
data = dict((key, navigate_value(result, path))
|
||||
for key, path in path_parameters.items())
|
||||
data = {key: navigate_value(result, path) for key, path in path_parameters.items()}
|
||||
|
||||
url = build_path(module, "jobs/{job_id}", data)
|
||||
|
||||
|
||||
@@ -547,8 +547,7 @@ def async_wait_create(config, result, client, timeout):
|
||||
path_parameters = {
|
||||
"publicip_id": ["publicip", "id"],
|
||||
}
|
||||
data = dict((key, navigate_value(result, path))
|
||||
for key, path in path_parameters.items())
|
||||
data = {key: navigate_value(result, path) for key, path in path_parameters.items()}
|
||||
|
||||
url = build_path(module, "publicips/{publicip_id}", data)
|
||||
|
||||
|
||||
@@ -407,8 +407,7 @@ def async_wait_create(config, result, client, timeout):
|
||||
path_parameters = {
|
||||
"peering_id": ["peering", "id"],
|
||||
}
|
||||
data = dict((key, navigate_value(result, path))
|
||||
for key, path in path_parameters.items())
|
||||
data = {key: navigate_value(result, path) for key, path in path_parameters.items()}
|
||||
|
||||
url = build_path(module, "v2.0/vpc/peerings/{peering_id}", data)
|
||||
|
||||
|
||||
@@ -560,8 +560,7 @@ def async_wait_create(config, result, client, timeout):
|
||||
path_parameters = {
|
||||
"port_id": ["port", "id"],
|
||||
}
|
||||
data = dict((key, navigate_value(result, path))
|
||||
for key, path in path_parameters.items())
|
||||
data = {key: navigate_value(result, path) for key, path in path_parameters.items()}
|
||||
|
||||
url = build_path(module, "ports/{port_id}", data)
|
||||
|
||||
|
||||
@@ -440,8 +440,7 @@ def async_wait_create(config, result, client, timeout):
|
||||
path_parameters = {
|
||||
"subnet_id": ["subnet", "id"],
|
||||
}
|
||||
data = dict((key, navigate_value(result, path))
|
||||
for key, path in path_parameters.items())
|
||||
data = {key: navigate_value(result, path) for key, path in path_parameters.items()}
|
||||
|
||||
url = build_path(module, "subnets/{subnet_id}", data)
|
||||
|
||||
@@ -538,8 +537,7 @@ def async_wait_update(config, result, client, timeout):
|
||||
path_parameters = {
|
||||
"subnet_id": ["subnet", "id"],
|
||||
}
|
||||
data = dict((key, navigate_value(result, path))
|
||||
for key, path in path_parameters.items())
|
||||
data = {key: navigate_value(result, path) for key, path in path_parameters.items()}
|
||||
|
||||
url = build_path(module, "subnets/{subnet_id}", data)
|
||||
|
||||
|
||||
@@ -282,9 +282,7 @@ def main():
|
||||
'vars.made_by': "ansible"
|
||||
}
|
||||
}
|
||||
|
||||
for key, value in variables.items():
|
||||
data['attrs']['vars.' + key] = value
|
||||
data['attrs'].update({'vars.' + key: value for key, value in variables.items()})
|
||||
|
||||
changed = False
|
||||
if icinga.exists(name):
|
||||
|
||||
@@ -323,8 +323,7 @@ def merge(one, two):
|
||||
''' Merge two complex nested datastructures into one'''
|
||||
if isinstance(one, dict) and isinstance(two, dict):
|
||||
copy = dict(one)
|
||||
# copy.update({key: merge(one.get(key, None), two[key]) for key in two})
|
||||
copy.update(dict((key, merge(one.get(key, None), two[key])) for key in two))
|
||||
copy.update({key: merge(one.get(key, None), two[key]) for key in two})
|
||||
return copy
|
||||
|
||||
elif isinstance(one, list) and isinstance(two, list):
|
||||
|
||||
@@ -569,7 +569,7 @@ def do_ini(module, filename, section=None, section_has_values=None, option=None,
|
||||
module.fail_json(msg="Unable to create temporary file %s", traceback=traceback.format_exc())
|
||||
|
||||
try:
|
||||
module.atomic_move(tmpfile, target_filename)
|
||||
module.atomic_move(tmpfile, os.path.abspath(target_filename))
|
||||
except IOError:
|
||||
module.ansible.fail_json(msg='Unable to move temporary \
|
||||
file %s to %s, IOError' % (tmpfile, target_filename), traceback=traceback.format_exc())
|
||||
|
||||
247
plugins/modules/ipa_getkeytab.py
Normal file
247
plugins/modules/ipa_getkeytab.py
Normal file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Alexander Bakanovskii <skottttt228@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ipa_getkeytab
|
||||
short_description: Manage keytab file in FreeIPA
|
||||
version_added: 9.5.0
|
||||
description:
|
||||
- Manage keytab file with C(ipa-getkeytab) utility.
|
||||
- See U(https://manpages.ubuntu.com/manpages/jammy/man1/ipa-getkeytab.1.html) for reference.
|
||||
author: "Alexander Bakanovskii (@abakanovskii)"
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- The base path where to put generated keytab file.
|
||||
type: path
|
||||
aliases: ["keytab"]
|
||||
required: true
|
||||
principal:
|
||||
description:
|
||||
- The non-realm part of the full principal name.
|
||||
type: str
|
||||
required: true
|
||||
ipa_host:
|
||||
description:
|
||||
- The IPA server to retrieve the keytab from (FQDN).
|
||||
type: str
|
||||
ldap_uri:
|
||||
description:
|
||||
- LDAP URI. If V(ldap://) is specified, STARTTLS is initiated by default.
|
||||
- Can not be used with the O(ipa_host) option.
|
||||
type: str
|
||||
bind_dn:
|
||||
description:
|
||||
- The LDAP DN to bind as when retrieving a keytab without Kerberos credentials.
|
||||
- Generally used with the O(bind_pw) option.
|
||||
type: str
|
||||
bind_pw:
|
||||
description:
|
||||
- The LDAP password to use when not binding with Kerberos.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Use this password for the key instead of one randomly generated.
|
||||
type: str
|
||||
ca_cert:
|
||||
description:
|
||||
- The path to the IPA CA certificate used to validate LDAPS/STARTTLS connections.
|
||||
type: path
|
||||
sasl_mech:
|
||||
description:
|
||||
- SASL mechanism to use if O(bind_dn) and O(bind_pw) are not specified.
|
||||
choices: ["GSSAPI", "EXTERNAL"]
|
||||
type: str
|
||||
retrieve_mode:
|
||||
description:
|
||||
- Retrieve an existing key from the server instead of generating a new one.
|
||||
- This is incompatible with the O(password), and will work only against a IPA server more recent than version 3.3.
|
||||
- The user requesting the keytab must have access to the keys for this operation to succeed.
|
||||
- Be aware that if set V(true), a new keytab will be generated.
|
||||
- This invalidates all previously retrieved keytabs for this service principal.
|
||||
type: bool
|
||||
encryption_types:
|
||||
description:
|
||||
- The list of encryption types to use to generate keys.
|
||||
- It will use local client defaults if not provided.
|
||||
- Valid values depend on the Kerberos library version and configuration.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- The state of the keytab file.
|
||||
- V(present) only check for existence of a file, if you want to recreate keytab with other parameters you should set O(force=true).
|
||||
type: str
|
||||
default: present
|
||||
choices: ["present", "absent"]
|
||||
force:
|
||||
description:
|
||||
- Force recreation if exists already.
|
||||
type: bool
|
||||
requirements:
|
||||
- freeipa-client
|
||||
- Managed host is FreeIPA client
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get kerberos ticket
|
||||
ansible.builtin.shell: kinit admin
|
||||
args:
|
||||
stdin: "{{ aldpro_admin_password }}"
|
||||
changed_when: true
|
||||
|
||||
- name: Create keytab
|
||||
community.general.ipa_getkeytab:
|
||||
path: /etc/ipa/test.keytab
|
||||
principal: HTTP/freeipa-dc02.ipa.test
|
||||
ipa_host: freeipa-dc01.ipa.test
|
||||
|
||||
- name: Retrieve already existing keytab
|
||||
community.general.ipa_getkeytab:
|
||||
path: /etc/ipa/test.keytab
|
||||
principal: HTTP/freeipa-dc02.ipa.test
|
||||
ipa_host: freeipa-dc01.ipa.test
|
||||
retrieve_mode: true
|
||||
|
||||
- name: Force keytab recreation
|
||||
community.general.ipa_getkeytab:
|
||||
path: /etc/ipa/test.keytab
|
||||
principal: HTTP/freeipa-dc02.ipa.test
|
||||
ipa_host: freeipa-dc01.ipa.test
|
||||
force: true
|
||||
'''
|
||||
|
||||
import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
|
||||
|
||||
class IPAKeytab(object):
|
||||
def __init__(self, module, **kwargs):
|
||||
self.module = module
|
||||
self.path = kwargs['path']
|
||||
self.state = kwargs['state']
|
||||
self.principal = kwargs['principal']
|
||||
self.ipa_host = kwargs['ipa_host']
|
||||
self.ldap_uri = kwargs['ldap_uri']
|
||||
self.bind_dn = kwargs['bind_dn']
|
||||
self.bind_pw = kwargs['bind_pw']
|
||||
self.password = kwargs['password']
|
||||
self.ca_cert = kwargs['ca_cert']
|
||||
self.sasl_mech = kwargs['sasl_mech']
|
||||
self.retrieve_mode = kwargs['retrieve_mode']
|
||||
self.encryption_types = kwargs['encryption_types']
|
||||
|
||||
self.runner = CmdRunner(
|
||||
module,
|
||||
command='ipa-getkeytab',
|
||||
arg_formats=dict(
|
||||
retrieve_mode=cmd_runner_fmt.as_bool('--retrieve'),
|
||||
path=cmd_runner_fmt.as_opt_val('--keytab'),
|
||||
ipa_host=cmd_runner_fmt.as_opt_val('--server'),
|
||||
principal=cmd_runner_fmt.as_opt_val('--principal'),
|
||||
ldap_uri=cmd_runner_fmt.as_opt_val('--ldapuri'),
|
||||
bind_dn=cmd_runner_fmt.as_opt_val('--binddn'),
|
||||
bind_pw=cmd_runner_fmt.as_opt_val('--bindpw'),
|
||||
password=cmd_runner_fmt.as_opt_val('--password'),
|
||||
ca_cert=cmd_runner_fmt.as_opt_val('--cacert'),
|
||||
sasl_mech=cmd_runner_fmt.as_opt_val('--mech'),
|
||||
encryption_types=cmd_runner_fmt.as_opt_val('--enctypes'),
|
||||
)
|
||||
)
|
||||
|
||||
def _exec(self, check_rc=True):
|
||||
with self.runner(
|
||||
"retrieve_mode path ipa_host principal ldap_uri bind_dn bind_pw password ca_cert sasl_mech encryption_types",
|
||||
check_rc=check_rc
|
||||
) as ctx:
|
||||
rc, out, err = ctx.run()
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
arg_spec = dict(
|
||||
path=dict(type='path', required=True, aliases=["keytab"]),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
principal=dict(type='str', required=True),
|
||||
ipa_host=dict(type='str'),
|
||||
ldap_uri=dict(type='str'),
|
||||
bind_dn=dict(type='str'),
|
||||
bind_pw=dict(type='str'),
|
||||
password=dict(type='str', no_log=True),
|
||||
ca_cert=dict(type='path'),
|
||||
sasl_mech=dict(type='str', choices=["GSSAPI", "EXTERNAL"]),
|
||||
retrieve_mode=dict(type='bool'),
|
||||
encryption_types=dict(type='str'),
|
||||
force=dict(type='bool'),
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
mutually_exclusive=[('ipa_host', 'ldap_uri'), ('retrieve_mode', 'password')],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
path = module.params['path']
|
||||
state = module.params['state']
|
||||
force = module.params['force']
|
||||
|
||||
keytab = IPAKeytab(module,
|
||||
path=path,
|
||||
state=state,
|
||||
principal=module.params['principal'],
|
||||
ipa_host=module.params['ipa_host'],
|
||||
ldap_uri=module.params['ldap_uri'],
|
||||
bind_dn=module.params['bind_dn'],
|
||||
bind_pw=module.params['bind_pw'],
|
||||
password=module.params['password'],
|
||||
ca_cert=module.params['ca_cert'],
|
||||
sasl_mech=module.params['sasl_mech'],
|
||||
retrieve_mode=module.params['retrieve_mode'],
|
||||
encryption_types=module.params['encryption_types'],
|
||||
)
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
if os.path.exists(path):
|
||||
if force and not module.check_mode:
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError as e:
|
||||
module.fail_json(msg="Error deleting: %s - %s." % (e.filename, e.strerror))
|
||||
keytab._exec()
|
||||
changed = True
|
||||
if force and module.check_mode:
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
keytab._exec()
|
||||
|
||||
if state == 'absent':
|
||||
if os.path.exists(path):
|
||||
changed = True
|
||||
if not module.check_mode:
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError as e:
|
||||
module.fail_json(msg="Error deleting: %s - %s." % (e.filename, e.strerror))
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -74,10 +74,17 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
state:
|
||||
description: State to ensure.
|
||||
description:
|
||||
- State to ensure.
|
||||
default: present
|
||||
choices: ["absent", "disabled", "enabled", "present"]
|
||||
type: str
|
||||
force_creation:
|
||||
description:
|
||||
- Create host if O(state=disabled) or O(state=enabled) but not present.
|
||||
default: true
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
update_dns:
|
||||
description:
|
||||
- If set V(true) with O(state=absent), then removes DNS records of the host managed by FreeIPA DNS.
|
||||
@@ -233,26 +240,31 @@ def get_host_diff(client, ipa_host, module_host):
|
||||
def ensure(module, client):
|
||||
name = module.params['fqdn']
|
||||
state = module.params['state']
|
||||
force_creation = module.params['force_creation']
|
||||
|
||||
ipa_host = client.host_find(name=name)
|
||||
module_host = get_host_dict(description=module.params['description'],
|
||||
force=module.params['force'], ip_address=module.params['ip_address'],
|
||||
force=module.params['force'],
|
||||
ip_address=module.params['ip_address'],
|
||||
ns_host_location=module.params['ns_host_location'],
|
||||
ns_hardware_platform=module.params['ns_hardware_platform'],
|
||||
ns_os_version=module.params['ns_os_version'],
|
||||
user_certificate=module.params['user_certificate'],
|
||||
mac_address=module.params['mac_address'],
|
||||
random_password=module.params.get('random_password'),
|
||||
random_password=module.params['random_password'],
|
||||
)
|
||||
changed = False
|
||||
if state in ['present', 'enabled', 'disabled']:
|
||||
if not ipa_host:
|
||||
if not ipa_host and (force_creation or state == 'present'):
|
||||
changed = True
|
||||
if not module.check_mode:
|
||||
# OTP password generated by FreeIPA is visible only for host_add command
|
||||
# so, return directly from here.
|
||||
return changed, client.host_add(name=name, host=module_host)
|
||||
else:
|
||||
if state in ['disabled', 'enabled']:
|
||||
module.fail_json(msg="No host with name " + ipa_host + " found")
|
||||
|
||||
diff = get_host_diff(client, ipa_host, module_host)
|
||||
if len(diff) > 0:
|
||||
changed = True
|
||||
@@ -261,11 +273,10 @@ def ensure(module, client):
|
||||
for key in diff:
|
||||
data[key] = module_host.get(key)
|
||||
ipa_host_show = client.host_show(name=name)
|
||||
if ipa_host_show.get('has_keytab', False) and module.params.get('random_password'):
|
||||
if ipa_host_show.get('has_keytab', True) and (state == 'disabled' or module.params.get('random_password')):
|
||||
client.host_disable(name=name)
|
||||
return changed, client.host_mod(name=name, host=data)
|
||||
|
||||
else:
|
||||
elif state == 'absent':
|
||||
if ipa_host:
|
||||
changed = True
|
||||
update_dns = module.params.get('update_dns', False)
|
||||
@@ -288,7 +299,8 @@ def main():
|
||||
mac_address=dict(type='list', aliases=['macaddress'], elements='str'),
|
||||
update_dns=dict(type='bool'),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']),
|
||||
random_password=dict(type='bool', no_log=False),)
|
||||
random_password=dict(type='bool', no_log=False),
|
||||
force_creation=dict(type='bool', default=True),)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
@@ -57,13 +57,14 @@ options:
|
||||
state:
|
||||
description:
|
||||
- State to ensure.
|
||||
- V("absent") and V("disabled") give the same results.
|
||||
- V("present") and V("enabled") give the same results.
|
||||
default: "present"
|
||||
choices: ["absent", "disabled", "enabled", "present"]
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.general.ipa.documentation
|
||||
- community.general.attributes
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
@@ -160,7 +161,7 @@ def ensure(module, client):
|
||||
module_hostgroup = get_hostgroup_dict(description=module.params['description'])
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
if state in ['present', 'enabled']:
|
||||
if not ipa_hostgroup:
|
||||
changed = True
|
||||
if not module.check_mode:
|
||||
|
||||
@@ -392,9 +392,7 @@ def ensure(module, client):
|
||||
'counter': 'ipatokenhotpcounter'}
|
||||
|
||||
# Create inverse dictionary for mapping return values
|
||||
ipa_to_ansible = {}
|
||||
for (k, v) in ansible_to_ipa.items():
|
||||
ipa_to_ansible[v] = k
|
||||
ipa_to_ansible = {v: k for k, v in ansible_to_ipa.items()}
|
||||
|
||||
unmodifiable_after_creation = ['otptype', 'secretkey', 'algorithm',
|
||||
'digits', 'offset', 'interval', 'counter']
|
||||
|
||||
@@ -150,13 +150,11 @@ EXAMPLES = '''
|
||||
name: example
|
||||
certificate: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
h19dUZ2co2fI/ibYiwxWk4aeNE6KWvCaTQOMQ8t6Uo2XKhpL/xnjoAgh1uCQN/69
|
||||
MG+34+RhUWzCfdZH7T8/qDxJw2kEPKluaYh7KnMsba+5jHjmtzix5QIDAQABo4IB
|
||||
h19dUZ2co2f...
|
||||
-----END CERTIFICATE-----
|
||||
private_key: |
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
DBVFTEVDVFJJQ0lURSBERSBGUkFOQ0UxFzAVBgNVBAsMDjAwMDIgNTUyMDgxMzE3
|
||||
GLlDNMw/uHyME7gHFsqJA7O11VY6O5WQ4IDP3m/s5ZV6s+Nn6Lerz17VZ99
|
||||
DBVFTEVDVFJ...
|
||||
-----END RSA PRIVATE KEY-----
|
||||
password: changeit
|
||||
dest: /etc/security/keystore.jks
|
||||
@@ -472,7 +470,7 @@ class JavaKeystore:
|
||||
|
||||
if self.keystore_type == 'pkcs12':
|
||||
# Preserve properties of the destination file, if any.
|
||||
self.module.atomic_move(keystore_p12_path, self.keystore_path)
|
||||
self.module.atomic_move(os.path.abspath(keystore_p12_path), os.path.abspath(self.keystore_path))
|
||||
self.update_permissions()
|
||||
self.result['changed'] = True
|
||||
return self.result
|
||||
|
||||
@@ -685,7 +685,7 @@ class JenkinsPlugin(object):
|
||||
|
||||
# Move the updates file to the right place if we could read it
|
||||
if tmp_updates_file != updates_file:
|
||||
self.module.atomic_move(tmp_updates_file, updates_file)
|
||||
self.module.atomic_move(os.path.abspath(tmp_updates_file), os.path.abspath(updates_file))
|
||||
|
||||
# Check if we have the plugin data available
|
||||
if not data.get('plugins', {}).get(self.params['name']):
|
||||
@@ -718,7 +718,7 @@ class JenkinsPlugin(object):
|
||||
details=to_native(e))
|
||||
|
||||
# Move the file onto the right place
|
||||
self.module.atomic_move(tmp_f, f)
|
||||
self.module.atomic_move(os.path.abspath(tmp_f), os.path.abspath(f))
|
||||
|
||||
def uninstall(self):
|
||||
changed = False
|
||||
|
||||
@@ -531,7 +531,7 @@ class JIRA(StateModuleHelper):
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
use_old_vardict = False
|
||||
state_param = 'operation'
|
||||
|
||||
def __init_module__(self):
|
||||
@@ -544,7 +544,7 @@ class JIRA(StateModuleHelper):
|
||||
self.vars.uri = self.vars.uri.strip('/')
|
||||
self.vars.set('restbase', self.vars.uri + '/rest/api/2')
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_create(self):
|
||||
createfields = {
|
||||
'project': {'key': self.vars.project},
|
||||
@@ -562,7 +562,7 @@ class JIRA(StateModuleHelper):
|
||||
url = self.vars.restbase + '/issue/'
|
||||
self.vars.meta = self.post(url, data)
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_comment(self):
|
||||
data = {
|
||||
'body': self.vars.comment
|
||||
@@ -578,7 +578,7 @@ class JIRA(StateModuleHelper):
|
||||
url = self.vars.restbase + '/issue/' + self.vars.issue + '/comment'
|
||||
self.vars.meta = self.post(url, data)
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_worklog(self):
|
||||
data = {
|
||||
'comment': self.vars.comment
|
||||
@@ -594,7 +594,7 @@ class JIRA(StateModuleHelper):
|
||||
url = self.vars.restbase + '/issue/' + self.vars.issue + '/worklog'
|
||||
self.vars.meta = self.post(url, data)
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_edit(self):
|
||||
data = {
|
||||
'fields': self.vars.fields
|
||||
@@ -602,7 +602,7 @@ class JIRA(StateModuleHelper):
|
||||
url = self.vars.restbase + '/issue/' + self.vars.issue
|
||||
self.vars.meta = self.put(url, data)
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_update(self):
|
||||
data = {
|
||||
"update": self.vars.fields,
|
||||
@@ -624,7 +624,7 @@ class JIRA(StateModuleHelper):
|
||||
|
||||
self.vars.meta = self.get(url)
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_transition(self):
|
||||
# Find the transition id
|
||||
turl = self.vars.restbase + '/issue/' + self.vars.issue + "/transitions"
|
||||
@@ -657,7 +657,7 @@ class JIRA(StateModuleHelper):
|
||||
url = self.vars.restbase + '/issue/' + self.vars.issue + "/transitions"
|
||||
self.vars.meta = self.post(url, data)
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_link(self):
|
||||
data = {
|
||||
'type': {'name': self.vars.linktype},
|
||||
@@ -667,7 +667,7 @@ class JIRA(StateModuleHelper):
|
||||
url = self.vars.restbase + '/issueLink/'
|
||||
self.vars.meta = self.post(url, data)
|
||||
|
||||
@cause_changes(on_success=True)
|
||||
@cause_changes(when="success")
|
||||
def operation_attach(self):
|
||||
v = self.vars
|
||||
filename = v.attachment.get('filename')
|
||||
|
||||
@@ -214,7 +214,7 @@ def run_module(module, tmpdir, kwriteconfig):
|
||||
if module.params['backup'] and os.path.exists(b_path):
|
||||
result['backup_file'] = module.backup_local(result['path'])
|
||||
try:
|
||||
module.atomic_move(b_tmpfile, b_path)
|
||||
module.atomic_move(b_tmpfile, os.path.abspath(b_path))
|
||||
except IOError:
|
||||
module.ansible.fail_json(msg='Unable to move temporary file %s to %s, IOError' % (tmpfile, result['path']), traceback=traceback.format_exc())
|
||||
|
||||
|
||||
@@ -108,13 +108,14 @@ options:
|
||||
|
||||
client_authenticator_type:
|
||||
description:
|
||||
- How do clients authenticate with the auth server? Either V(client-secret) or
|
||||
V(client-jwt) can be chosen. When using V(client-secret), the module parameter
|
||||
O(secret) can set it, while for V(client-jwt), you can use the keys C(use.jwks.url),
|
||||
- How do clients authenticate with the auth server? Either V(client-secret),
|
||||
V(client-jwt), or V(client-x509) can be chosen. When using V(client-secret), the module parameter
|
||||
O(secret) can set it, for V(client-jwt), you can use the keys C(use.jwks.url),
|
||||
C(jwks.url), and C(jwt.credential.certificate) in the O(attributes) module parameter
|
||||
to configure its behavior.
|
||||
to configure its behavior. For V(client-x509) you can use the keys C(x509.allow.regex.pattern.comparison)
|
||||
and C(x509.subjectdn) in the O(attributes) module parameter to configure which certificate(s) to accept.
|
||||
- This is 'clientAuthenticatorType' in the Keycloak REST API.
|
||||
choices: ['client-secret', 'client-jwt']
|
||||
choices: ['client-secret', 'client-jwt', 'client-x509']
|
||||
aliases:
|
||||
- clientAuthenticatorType
|
||||
type: str
|
||||
@@ -533,7 +534,6 @@ options:
|
||||
description:
|
||||
- SAML Redirect Binding URL for the client's assertion consumer service (login responses).
|
||||
|
||||
|
||||
saml_force_name_id_format:
|
||||
description:
|
||||
- For SAML clients, Boolean specifying whether to ignore requested NameID subject format and using the configured one instead.
|
||||
@@ -581,6 +581,18 @@ options:
|
||||
- For OpenID-Connect clients, client certificate for validating JWT issued by
|
||||
client and signed by its key, base64-encoded.
|
||||
|
||||
x509.subjectdn:
|
||||
description:
|
||||
- For OpenID-Connect clients, subject which will be used to authenticate the client.
|
||||
type: str
|
||||
version_added: 9.5.0
|
||||
|
||||
x509.allow.regex.pattern.comparison:
|
||||
description:
|
||||
- For OpenID-Connect clients, boolean specifying whether to allow C(x509.subjectdn) as regular expression.
|
||||
type: bool
|
||||
version_added: 9.5.0
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.keycloak
|
||||
- community.general.attributes
|
||||
@@ -624,6 +636,22 @@ EXAMPLES = '''
|
||||
delegate_to: localhost
|
||||
|
||||
|
||||
- name: Create or update a Keycloak client (minimal example), with x509 authentication
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
realm: master
|
||||
state: present
|
||||
client_id: test
|
||||
client_authenticator_type: client-x509
|
||||
attributes:
|
||||
x509.subjectdn: "CN=client"
|
||||
x509.allow.regex.pattern.comparison: false
|
||||
|
||||
|
||||
- name: Create or update a Keycloak client (with all the bells and whistles)
|
||||
community.general.keycloak_client:
|
||||
auth_client_id: admin-cli
|
||||
@@ -777,9 +805,6 @@ def normalise_cr(clientrep, remove_ids=False):
|
||||
# Avoid the dict passed in to be modified
|
||||
clientrep = clientrep.copy()
|
||||
|
||||
if 'attributes' in clientrep:
|
||||
clientrep['attributes'] = list(sorted(clientrep['attributes']))
|
||||
|
||||
if 'defaultClientScopes' in clientrep:
|
||||
clientrep['defaultClientScopes'] = list(sorted(clientrep['defaultClientScopes']))
|
||||
|
||||
@@ -913,7 +938,7 @@ def main():
|
||||
base_url=dict(type='str', aliases=['baseUrl']),
|
||||
surrogate_auth_required=dict(type='bool', aliases=['surrogateAuthRequired']),
|
||||
enabled=dict(type='bool'),
|
||||
client_authenticator_type=dict(type='str', choices=['client-secret', 'client-jwt'], aliases=['clientAuthenticatorType']),
|
||||
client_authenticator_type=dict(type='str', choices=['client-secret', 'client-jwt', 'client-x509'], aliases=['clientAuthenticatorType']),
|
||||
secret=dict(type='str', no_log=True),
|
||||
registration_access_token=dict(type='str', aliases=['registrationAccessToken'], no_log=True),
|
||||
default_roles=dict(type='list', elements='str', aliases=['defaultRoles']),
|
||||
@@ -996,17 +1021,10 @@ def main():
|
||||
for client_param in client_params:
|
||||
new_param_value = module.params.get(client_param)
|
||||
|
||||
# some lists in the Keycloak API are sorted, some are not.
|
||||
if isinstance(new_param_value, list):
|
||||
if client_param in ['attributes']:
|
||||
try:
|
||||
new_param_value = sorted(new_param_value)
|
||||
except TypeError:
|
||||
pass
|
||||
# Unfortunately, the ansible argument spec checker introduces variables with null values when
|
||||
# they are not specified
|
||||
if client_param == 'protocol_mappers':
|
||||
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
||||
new_param_value = [{k: v for k, v in x.items() if v is not None} for x in new_param_value]
|
||||
elif client_param == 'authentication_flow_binding_overrides':
|
||||
new_param_value = flow_binding_from_dict_to_model(new_param_value, realm, kc)
|
||||
|
||||
|
||||
@@ -317,9 +317,6 @@ def normalise_cr(clientscoperep, remove_ids=False):
|
||||
# Avoid the dict passed in to be modified
|
||||
clientscoperep = clientscoperep.copy()
|
||||
|
||||
if 'attributes' in clientscoperep:
|
||||
clientscoperep['attributes'] = list(sorted(clientscoperep['attributes']))
|
||||
|
||||
if 'protocolMappers' in clientscoperep:
|
||||
clientscoperep['protocolMappers'] = sorted(clientscoperep['protocolMappers'], key=lambda x: (x.get('name'), x.get('protocol'), x.get('protocolMapper')))
|
||||
for mapper in clientscoperep['protocolMappers']:
|
||||
@@ -418,17 +415,10 @@ def main():
|
||||
for clientscope_param in clientscope_params:
|
||||
new_param_value = module.params.get(clientscope_param)
|
||||
|
||||
# some lists in the Keycloak API are sorted, some are not.
|
||||
if isinstance(new_param_value, list):
|
||||
if clientscope_param in ['attributes']:
|
||||
try:
|
||||
new_param_value = sorted(new_param_value)
|
||||
except TypeError:
|
||||
pass
|
||||
# Unfortunately, the ansible argument spec checker introduces variables with null values when
|
||||
# they are not specified
|
||||
if clientscope_param == 'protocol_mappers':
|
||||
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
||||
new_param_value = [{k: v for k, v in x.items() if v is not None} for x in new_param_value]
|
||||
changeset[camel(clientscope_param)] = new_param_value
|
||||
|
||||
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
||||
|
||||
@@ -190,6 +190,15 @@ def extract_field(dictionary, field='name'):
|
||||
return [cs[field] for cs in dictionary]
|
||||
|
||||
|
||||
def normalize_scopes(scopes):
|
||||
scopes_copy = scopes.copy()
|
||||
if isinstance(scopes_copy.get('default_clientscopes'), list):
|
||||
scopes_copy['default_clientscopes'] = sorted(scopes_copy['default_clientscopes'])
|
||||
if isinstance(scopes_copy.get('optional_clientscopes'), list):
|
||||
scopes_copy['optional_clientscopes'] = sorted(scopes_copy['optional_clientscopes'])
|
||||
return scopes_copy
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Module keycloak_clientscope_type
|
||||
@@ -244,10 +253,7 @@ def main():
|
||||
})
|
||||
|
||||
if module._diff:
|
||||
result['diff'] = dict(before=result['existing'], after=result['proposed'])
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
result['diff'] = dict(before=normalize_scopes(result['existing']), after=normalize_scopes(result['proposed']))
|
||||
|
||||
default_clientscopes_add = clientscopes_to_add(default_clientscopes_existing, default_clientscopes_real)
|
||||
optional_clientscopes_add = clientscopes_to_add(optional_clientscopes_existing, optional_clientscopes_real)
|
||||
@@ -255,6 +261,13 @@ def main():
|
||||
default_clientscopes_delete = clientscopes_to_delete(default_clientscopes_existing, default_clientscopes_real)
|
||||
optional_clientscopes_delete = clientscopes_to_delete(optional_clientscopes_existing, optional_clientscopes_real)
|
||||
|
||||
result["changed"] = any(len(x) > 0 for x in [
|
||||
default_clientscopes_add, optional_clientscopes_add, default_clientscopes_delete, optional_clientscopes_delete
|
||||
])
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
# first delete so clientscopes can change type
|
||||
for clientscope in default_clientscopes_delete:
|
||||
kc.delete_default_clientscope(clientscope['id'], realm, client_id)
|
||||
@@ -266,13 +279,6 @@ def main():
|
||||
for clientscope in optional_clientscopes_add:
|
||||
kc.add_optional_clientscope(clientscope['id'], realm, client_id)
|
||||
|
||||
result["changed"] = (
|
||||
len(default_clientscopes_add) > 0
|
||||
or len(optional_clientscopes_add) > 0
|
||||
or len(default_clientscopes_delete) > 0
|
||||
or len(optional_clientscopes_delete) > 0
|
||||
)
|
||||
|
||||
result['end_state'].update({
|
||||
'default_clientscopes': extract_field(kc.get_default_clientscopes(realm, client_id)),
|
||||
'optional_clientscopes': extract_field(kc.get_optional_clientscopes(realm, client_id))
|
||||
|
||||
@@ -534,7 +534,7 @@ def main():
|
||||
# special handling of mappers list to allow change detection
|
||||
if module.params.get('mappers') is not None:
|
||||
for change in module.params['mappers']:
|
||||
change = dict((k, v) for k, v in change.items() if change[k] is not None)
|
||||
change = {k: v for k, v in change.items() if v is not None}
|
||||
if change.get('id') is None and change.get('name') is None:
|
||||
module.fail_json(msg='Either `name` or `id` has to be specified on each mapper.')
|
||||
if before_idp == dict():
|
||||
|
||||
@@ -803,7 +803,7 @@ def main():
|
||||
if module._diff:
|
||||
result['diff'] = dict(before=sanitize_cr(before_norm),
|
||||
after=sanitize_cr(desired_norm))
|
||||
result['changed'] = (before_realm != desired_realm)
|
||||
result['changed'] = (before_norm != desired_norm)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ options:
|
||||
type: bool
|
||||
parent_id:
|
||||
description:
|
||||
- The parent_id of the realm key. In practice the ID (name) of the realm.
|
||||
- The parent_id of the realm key. In practice the name of the realm.
|
||||
type: str
|
||||
required: true
|
||||
provider_id:
|
||||
@@ -300,7 +300,7 @@ def main():
|
||||
|
||||
kc = KeycloakAPI(module, connection_header)
|
||||
|
||||
params_to_ignore = list(keycloak_argument_spec().keys()) + ["state", "force"]
|
||||
params_to_ignore = list(keycloak_argument_spec().keys()) + ["state", "force", "parent_id"]
|
||||
|
||||
# Filter and map the parameters names that apply to the role
|
||||
component_params = [x for x in module.params
|
||||
@@ -371,7 +371,7 @@ def main():
|
||||
parent_id = module.params.get('parent_id')
|
||||
|
||||
# Get a list of all Keycloak components that are of keyprovider type.
|
||||
realm_keys = kc.get_components(urlencode(dict(type=provider_type, parent=parent_id)), parent_id)
|
||||
realm_keys = kc.get_components(urlencode(dict(type=provider_type)), parent_id)
|
||||
|
||||
# If this component is present get its key ID. Confusingly the key ID is
|
||||
# also known as the Provider ID.
|
||||
|
||||
@@ -85,6 +85,32 @@ options:
|
||||
- parentId
|
||||
type: str
|
||||
|
||||
remove_unspecified_mappers:
|
||||
description:
|
||||
- Remove mappers that are not specified in the configuration for this federation.
|
||||
- Set to V(false) to keep mappers that are not listed in O(mappers).
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 9.4.0
|
||||
|
||||
bind_credential_update_mode:
|
||||
description:
|
||||
- The value of the config parameter O(config.bindCredential) is redacted in the Keycloak responses.
|
||||
Comparing the redacted value with the desired value always evaluates to not equal. This means
|
||||
the before and desired states are never equal if the parameter is set.
|
||||
- Set to V(always) to include O(config.bindCredential) in the comparison of before and desired state.
|
||||
Because of the redacted value returned by Keycloak the module will always detect a change
|
||||
and make an update if a O(config.bindCredential) value is set.
|
||||
- Set to V(only_indirect) to exclude O(config.bindCredential) when comparing the before state with the
|
||||
desired state. The value of O(config.bindCredential) will only be updated if there are other changes
|
||||
to the user federation that require an update.
|
||||
type: str
|
||||
default: always
|
||||
choices:
|
||||
- always
|
||||
- only_indirect
|
||||
version_added: 9.5.0
|
||||
|
||||
config:
|
||||
description:
|
||||
- Dict specifying the configuration options for the provider; the contents differ depending on
|
||||
@@ -434,6 +460,17 @@ options:
|
||||
- Max lifespan of cache entry in milliseconds.
|
||||
type: int
|
||||
|
||||
referral:
|
||||
description:
|
||||
- Specifies if LDAP referrals should be followed or ignored. Please note that enabling
|
||||
referrals can slow down authentication as it allows the LDAP server to decide which other
|
||||
LDAP servers to use. This could potentially include untrusted servers.
|
||||
type: str
|
||||
choices:
|
||||
- ignore
|
||||
- follow
|
||||
version_added: 9.5.0
|
||||
|
||||
mappers:
|
||||
description:
|
||||
- A list of dicts defining mappers associated with this Identity Provider.
|
||||
@@ -713,19 +750,27 @@ from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
def normalize_kc_comp(comp):
|
||||
if 'config' in comp:
|
||||
# kc completely removes the parameter `krbPrincipalAttribute` if it is set to `''`; the unset kc parameter is equivalent to `''`;
|
||||
# to make change detection and diff more accurate we set it again in the kc responses
|
||||
if 'krbPrincipalAttribute' not in comp['config']:
|
||||
comp['config']['krbPrincipalAttribute'] = ['']
|
||||
|
||||
# kc stores a timestamp of the last sync in `lastSync` to time the periodic sync, it is removed to minimize diff/changes
|
||||
comp['config'].pop('lastSync', None)
|
||||
|
||||
|
||||
def sanitize(comp):
|
||||
compcopy = deepcopy(comp)
|
||||
if 'config' in compcopy:
|
||||
compcopy['config'] = dict((k, v[0]) for k, v in compcopy['config'].items())
|
||||
compcopy['config'] = {k: v[0] for k, v in compcopy['config'].items()}
|
||||
if 'bindCredential' in compcopy['config']:
|
||||
compcopy['config']['bindCredential'] = '**********'
|
||||
# an empty string is valid for krbPrincipalAttribute but is filtered out in diff
|
||||
if 'krbPrincipalAttribute' not in compcopy['config']:
|
||||
compcopy['config']['krbPrincipalAttribute'] = ''
|
||||
if 'mappers' in compcopy:
|
||||
for mapper in compcopy['mappers']:
|
||||
if 'config' in mapper:
|
||||
mapper['config'] = dict((k, v[0]) for k, v in mapper['config'].items())
|
||||
mapper['config'] = {k: v[0] for k, v in mapper['config'].items()}
|
||||
return compcopy
|
||||
|
||||
|
||||
@@ -772,6 +817,7 @@ def main():
|
||||
priority=dict(type='int', default=0),
|
||||
rdnLDAPAttribute=dict(type='str'),
|
||||
readTimeout=dict(type='int'),
|
||||
referral=dict(type='str', choices=['ignore', 'follow']),
|
||||
searchScope=dict(type='str', choices=['1', '2'], default='1'),
|
||||
serverPrincipal=dict(type='str'),
|
||||
krbPrincipalAttribute=dict(type='str'),
|
||||
@@ -808,6 +854,8 @@ def main():
|
||||
provider_id=dict(type='str', aliases=['providerId']),
|
||||
provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'),
|
||||
parent_id=dict(type='str', aliases=['parentId']),
|
||||
remove_unspecified_mappers=dict(type='bool', default=True),
|
||||
bind_credential_update_mode=dict(type='str', default='always', choices=['always', 'only_indirect']),
|
||||
mappers=dict(type='list', elements='dict', options=mapper_spec),
|
||||
)
|
||||
|
||||
@@ -838,19 +886,26 @@ def main():
|
||||
|
||||
# Keycloak API expects config parameters to be arrays containing a single string element
|
||||
if config is not None:
|
||||
module.params['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v])
|
||||
for k, v in config.items() if config[k] is not None)
|
||||
module.params['config'] = {
|
||||
k: [str(v).lower() if not isinstance(v, str) else v]
|
||||
for k, v in config.items()
|
||||
if config[k] is not None
|
||||
}
|
||||
|
||||
if mappers is not None:
|
||||
for mapper in mappers:
|
||||
if mapper.get('config') is not None:
|
||||
mapper['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v])
|
||||
for k, v in mapper['config'].items() if mapper['config'][k] is not None)
|
||||
mapper['config'] = {
|
||||
k: [str(v).lower() if not isinstance(v, str) else v]
|
||||
for k, v in mapper['config'].items()
|
||||
if mapper['config'][k] is not None
|
||||
}
|
||||
|
||||
# Filter and map the parameters names that apply
|
||||
comp_params = [x for x in module.params
|
||||
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'mappers'] and
|
||||
module.params.get(x) is not None]
|
||||
if x not in list(keycloak_argument_spec().keys())
|
||||
+ ['state', 'realm', 'mappers', 'remove_unspecified_mappers', 'bind_credential_update_mode']
|
||||
and module.params.get(x) is not None]
|
||||
|
||||
# See if it already exists in Keycloak
|
||||
if cid is None:
|
||||
@@ -868,7 +923,9 @@ def main():
|
||||
|
||||
# if user federation exists, get associated mappers
|
||||
if cid is not None and before_comp:
|
||||
before_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name'))
|
||||
before_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name') or '')
|
||||
|
||||
normalize_kc_comp(before_comp)
|
||||
|
||||
# Build a proposed changeset from parameters given to this module
|
||||
changeset = {}
|
||||
@@ -877,7 +934,7 @@ def main():
|
||||
new_param_value = module.params.get(param)
|
||||
old_value = before_comp[camel(param)] if camel(param) in before_comp else None
|
||||
if param == 'mappers':
|
||||
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
||||
new_param_value = [{k: v for k, v in x.items() if v is not None} for x in new_param_value]
|
||||
if new_param_value != old_value:
|
||||
changeset[camel(param)] = new_param_value
|
||||
|
||||
@@ -886,13 +943,13 @@ def main():
|
||||
if module.params['provider_id'] in ['kerberos', 'sssd']:
|
||||
module.fail_json(msg='Cannot configure mappers for {type} provider.'.format(type=module.params['provider_id']))
|
||||
for change in module.params['mappers']:
|
||||
change = dict((k, v) for k, v in change.items() if change[k] is not None)
|
||||
change = {k: v for k, v in change.items() if v is not None}
|
||||
if change.get('id') is None and change.get('name') is None:
|
||||
module.fail_json(msg='Either `name` or `id` has to be specified on each mapper.')
|
||||
if cid is None:
|
||||
old_mapper = {}
|
||||
elif change.get('id') is not None:
|
||||
old_mapper = next((before_mapper for before_mapper in before_mapper.get('mappers', []) if before_mapper["id"] == change['id']), None)
|
||||
old_mapper = next((before_mapper for before_mapper in before_comp.get('mappers', []) if before_mapper["id"] == change['id']), None)
|
||||
if old_mapper is None:
|
||||
old_mapper = {}
|
||||
else:
|
||||
@@ -909,6 +966,12 @@ def main():
|
||||
if changeset.get('mappers') is None:
|
||||
changeset['mappers'] = list()
|
||||
changeset['mappers'].append(new_mapper)
|
||||
changeset['mappers'] = sorted(changeset['mappers'], key=lambda x: x.get('name') or '')
|
||||
|
||||
# to keep unspecified existing mappers we add them to the desired mappers list, unless they're already present
|
||||
if not module.params['remove_unspecified_mappers'] and 'mappers' in before_comp:
|
||||
changeset_mapper_ids = [mapper['id'] for mapper in changeset['mappers'] if 'id' in mapper]
|
||||
changeset['mappers'].extend([mapper for mapper in before_comp['mappers'] if mapper['id'] not in changeset_mapper_ids])
|
||||
|
||||
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
||||
desired_comp = before_comp.copy()
|
||||
@@ -965,13 +1028,15 @@ def main():
|
||||
new_mapper['parentId'] = cid
|
||||
updated_mappers.append(kc.create_component(new_mapper, realm))
|
||||
|
||||
# we remove all unwanted default mappers
|
||||
# we use ids so we dont accidently remove one of the previously updated default mapper
|
||||
for default_mapper in default_mappers:
|
||||
if not default_mapper['id'] in [x['id'] for x in updated_mappers]:
|
||||
kc.delete_component(default_mapper['id'], realm)
|
||||
if module.params['remove_unspecified_mappers']:
|
||||
# we remove all unwanted default mappers
|
||||
# we use ids so we dont accidently remove one of the previously updated default mapper
|
||||
for default_mapper in default_mappers:
|
||||
if not default_mapper['id'] in [x['id'] for x in updated_mappers]:
|
||||
kc.delete_component(default_mapper['id'], realm)
|
||||
|
||||
after_comp['mappers'] = kc.get_components(urlencode(dict(parent=cid)), realm)
|
||||
normalize_kc_comp(after_comp)
|
||||
if module._diff:
|
||||
result['diff'] = dict(before='', after=sanitize(after_comp))
|
||||
result['end_state'] = sanitize(after_comp)
|
||||
@@ -982,8 +1047,15 @@ def main():
|
||||
if state == 'present':
|
||||
# Process an update
|
||||
|
||||
desired_copy = deepcopy(desired_comp)
|
||||
before_copy = deepcopy(before_comp)
|
||||
# exclude bindCredential when checking wether an update is required, therefore
|
||||
# updating it only if there are other changes
|
||||
if module.params['bind_credential_update_mode'] == 'only_indirect':
|
||||
desired_copy.get('config', []).pop('bindCredential', None)
|
||||
before_copy.get('config', []).pop('bindCredential', None)
|
||||
# no changes
|
||||
if desired_comp == before_comp:
|
||||
if desired_copy == before_copy:
|
||||
result['changed'] = False
|
||||
result['end_state'] = sanitize(desired_comp)
|
||||
result['msg'] = "No changes required to user federation {id}.".format(id=cid)
|
||||
@@ -1004,7 +1076,7 @@ def main():
|
||||
|
||||
for before_mapper in before_comp.get('mappers', []):
|
||||
# remove unwanted existing mappers that will not be updated
|
||||
if not before_mapper['id'] in [x['id'] for x in desired_mappers]:
|
||||
if not before_mapper['id'] in [x['id'] for x in desired_mappers if 'id' in x]:
|
||||
kc.delete_component(before_mapper['id'], realm)
|
||||
|
||||
for mapper in desired_mappers:
|
||||
@@ -1018,7 +1090,8 @@ def main():
|
||||
kc.create_component(mapper, realm)
|
||||
|
||||
after_comp = kc.get_component(cid, realm)
|
||||
after_comp['mappers'] = kc.get_components(urlencode(dict(parent=cid)), realm)
|
||||
after_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name') or '')
|
||||
normalize_kc_comp(after_comp)
|
||||
after_comp_sanitized = sanitize(after_comp)
|
||||
before_comp_sanitized = sanitize(before_comp)
|
||||
result['end_state'] = after_comp_sanitized
|
||||
|
||||
738
plugins/modules/keycloak_userprofile.py
Normal file
738
plugins/modules/keycloak_userprofile.py
Normal file
@@ -0,0 +1,738 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) Ansible project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: keycloak_userprofile
|
||||
|
||||
short_description: Allows managing Keycloak User Profiles
|
||||
|
||||
description:
|
||||
- This module allows you to create, update, or delete Keycloak User Profiles via Keycloak API. You can also customize the "Unmanaged Attributes" with it.
|
||||
|
||||
- The names of module options are snake_cased versions of the camelCase ones found in the
|
||||
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/24.0.5/rest-api/index.html).
|
||||
For compatibility reasons, the module also accepts the camelCase versions of the options.
|
||||
|
||||
version_added: "9.4.0"
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: full
|
||||
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- State of the User Profile provider.
|
||||
- On V(present), the User Profile provider will be created if it does not yet exist, or updated with
|
||||
the parameters you provide.
|
||||
- On V(absent), the User Profile provider will be removed if it exists.
|
||||
default: 'present'
|
||||
type: str
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
|
||||
parent_id:
|
||||
description:
|
||||
- The parent ID of the realm key. In practice the ID (name) of the realm.
|
||||
aliases:
|
||||
- parentId
|
||||
- realm
|
||||
type: str
|
||||
required: true
|
||||
|
||||
provider_id:
|
||||
description:
|
||||
- The name of the provider ID for the key (supported value is V(declarative-user-profile)).
|
||||
aliases:
|
||||
- providerId
|
||||
choices: ['declarative-user-profile']
|
||||
default: 'declarative-user-profile'
|
||||
type: str
|
||||
|
||||
provider_type:
|
||||
description:
|
||||
- Component type for User Profile (only supported value is V(org.keycloak.userprofile.UserProfileProvider)).
|
||||
aliases:
|
||||
- providerType
|
||||
choices: ['org.keycloak.userprofile.UserProfileProvider']
|
||||
default: org.keycloak.userprofile.UserProfileProvider
|
||||
type: str
|
||||
|
||||
config:
|
||||
description:
|
||||
- The configuration of the User Profile Provider.
|
||||
type: dict
|
||||
required: false
|
||||
suboptions:
|
||||
kc_user_profile_config:
|
||||
description:
|
||||
- Define a declarative User Profile. See EXAMPLES for more context.
|
||||
aliases:
|
||||
- kcUserProfileConfig
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
attributes:
|
||||
description:
|
||||
- A list of attributes to be included in the User Profile.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the attribute.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
display_name:
|
||||
description:
|
||||
- The display name of the attribute.
|
||||
aliases:
|
||||
- displayName
|
||||
type: str
|
||||
required: true
|
||||
|
||||
validations:
|
||||
description:
|
||||
- The validations to be applied to the attribute.
|
||||
type: dict
|
||||
suboptions:
|
||||
length:
|
||||
description:
|
||||
- The length validation for the attribute.
|
||||
type: dict
|
||||
suboptions:
|
||||
min:
|
||||
description:
|
||||
- The minimum length of the attribute.
|
||||
type: int
|
||||
max:
|
||||
description:
|
||||
- The maximum length of the attribute.
|
||||
type: int
|
||||
required: true
|
||||
|
||||
email:
|
||||
description:
|
||||
- The email validation for the attribute.
|
||||
type: dict
|
||||
|
||||
username_prohibited_characters:
|
||||
description:
|
||||
- The prohibited characters validation for the username attribute.
|
||||
type: dict
|
||||
aliases:
|
||||
- usernameProhibitedCharacters
|
||||
|
||||
up_username_not_idn_homograph:
|
||||
description:
|
||||
- The validation to prevent IDN homograph attacks in usernames.
|
||||
type: dict
|
||||
aliases:
|
||||
- upUsernameNotIdnHomograph
|
||||
|
||||
person_name_prohibited_characters:
|
||||
description:
|
||||
- The prohibited characters validation for person name attributes.
|
||||
type: dict
|
||||
aliases:
|
||||
- personNameProhibitedCharacters
|
||||
|
||||
uri:
|
||||
description:
|
||||
- The URI validation for the attribute.
|
||||
type: dict
|
||||
|
||||
pattern:
|
||||
description:
|
||||
- The pattern validation for the attribute using regular expressions.
|
||||
type: dict
|
||||
|
||||
options:
|
||||
description:
|
||||
- Validation to ensure the attribute matches one of the provided options.
|
||||
type: dict
|
||||
|
||||
annotations:
|
||||
description:
|
||||
- Annotations for the attribute.
|
||||
type: dict
|
||||
|
||||
group:
|
||||
description:
|
||||
- Specifies the User Profile group where this attribute will be added.
|
||||
type: str
|
||||
|
||||
permissions:
|
||||
description:
|
||||
- The permissions for viewing and editing the attribute.
|
||||
type: dict
|
||||
suboptions:
|
||||
view:
|
||||
description:
|
||||
- The roles that can view the attribute.
|
||||
- Supported values are V(admin) and V(user).
|
||||
type: list
|
||||
elements: str
|
||||
default:
|
||||
- admin
|
||||
- user
|
||||
|
||||
edit:
|
||||
description:
|
||||
- The roles that can edit the attribute.
|
||||
- Supported values are V(admin) and V(user).
|
||||
type: list
|
||||
elements: str
|
||||
default:
|
||||
- admin
|
||||
- user
|
||||
|
||||
multivalued:
|
||||
description:
|
||||
- Whether the attribute can have multiple values.
|
||||
type: bool
|
||||
default: false
|
||||
|
||||
required:
|
||||
description:
|
||||
- The roles that require this attribute.
|
||||
type: dict
|
||||
suboptions:
|
||||
roles:
|
||||
description:
|
||||
- The roles for which this attribute is required.
|
||||
- Supported values are V(admin) and V(user).
|
||||
type: list
|
||||
elements: str
|
||||
default:
|
||||
- user
|
||||
|
||||
groups:
|
||||
description:
|
||||
- A list of attribute groups to be included in the User Profile.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the group.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
display_header:
|
||||
description:
|
||||
- The display header for the group.
|
||||
aliases:
|
||||
- displayHeader
|
||||
type: str
|
||||
required: true
|
||||
|
||||
display_description:
|
||||
description:
|
||||
- The display description for the group.
|
||||
aliases:
|
||||
- displayDescription
|
||||
type: str
|
||||
required: false
|
||||
|
||||
annotations:
|
||||
description:
|
||||
- The annotations included in the group.
|
||||
type: dict
|
||||
required: false
|
||||
|
||||
unmanaged_attribute_policy:
|
||||
description:
|
||||
- Policy for unmanaged attributes.
|
||||
aliases:
|
||||
- unmanagedAttributePolicy
|
||||
type: str
|
||||
choices:
|
||||
- ENABLED
|
||||
- ADMIN_EDIT
|
||||
- ADMIN_VIEW
|
||||
|
||||
notes:
|
||||
- Currently, only a single V(declarative-user-profile) entry is supported for O(provider_id) (design of the Keyckoak API).
|
||||
However, there can be multiple O(config.kc_user_profile_config[].attributes[]) entries.
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.keycloak
|
||||
- community.general.attributes
|
||||
|
||||
author:
|
||||
- Eike Waldt (@yeoldegrove)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a Declarative User Profile with default settings
|
||||
community.general.keycloak_userprofile:
|
||||
state: present
|
||||
parent_id: master
|
||||
config:
|
||||
kc_user_profile_config:
|
||||
- attributes:
|
||||
- name: username
|
||||
displayName: ${username}
|
||||
validations:
|
||||
length:
|
||||
min: 3
|
||||
max: 255
|
||||
username_prohibited_characters: {}
|
||||
up_username_not_idn_homograph: {}
|
||||
annotations: {}
|
||||
permissions:
|
||||
view:
|
||||
- admin
|
||||
- user
|
||||
edit: []
|
||||
multivalued: false
|
||||
- name: email
|
||||
displayName: ${email}
|
||||
validations:
|
||||
email: {}
|
||||
length:
|
||||
max: 255
|
||||
annotations: {}
|
||||
required:
|
||||
roles:
|
||||
- user
|
||||
permissions:
|
||||
view:
|
||||
- admin
|
||||
- user
|
||||
edit: []
|
||||
multivalued: false
|
||||
- name: firstName
|
||||
displayName: ${firstName}
|
||||
validations:
|
||||
length:
|
||||
max: 255
|
||||
person_name_prohibited_characters: {}
|
||||
annotations: {}
|
||||
required:
|
||||
roles:
|
||||
- user
|
||||
permissions:
|
||||
view:
|
||||
- admin
|
||||
- user
|
||||
edit: []
|
||||
multivalued: false
|
||||
- name: lastName
|
||||
displayName: ${lastName}
|
||||
validations:
|
||||
length:
|
||||
max: 255
|
||||
person_name_prohibited_characters: {}
|
||||
annotations: {}
|
||||
required:
|
||||
roles:
|
||||
- user
|
||||
permissions:
|
||||
view:
|
||||
- admin
|
||||
- user
|
||||
edit: []
|
||||
multivalued: false
|
||||
groups:
|
||||
- name: user-metadata
|
||||
displayHeader: User metadata
|
||||
displayDescription: Attributes, which refer to user metadata
|
||||
annotations: {}
|
||||
|
||||
- name: Delete a Keycloak User Profile Provider
|
||||
keycloak_userprofile:
|
||||
state: absent
|
||||
parent_id: master
|
||||
|
||||
# Unmanaged attributes are user attributes not explicitly defined in the User Profile
|
||||
# configuration. By default, unmanaged attributes are "Disabled" and are not
|
||||
# available from any context such as registration, account, and the
|
||||
# administration console. By setting "Enabled", unmanaged attributes are fully
|
||||
# recognized by the server and accessible through all contexts, useful if you are
|
||||
# starting migrating an existing realm to the declarative User Profile
|
||||
# and you don't have yet all user attributes defined in the User Profile configuration.
|
||||
- name: Enable Unmanaged Attributes
|
||||
community.general.keycloak_userprofile:
|
||||
state: present
|
||||
parent_id: master
|
||||
config:
|
||||
kc_user_profile_config:
|
||||
- unmanagedAttributePolicy: ENABLED
|
||||
|
||||
# By setting "Only administrators can write", unmanaged attributes can be managed
|
||||
# only through the administration console and API, useful if you have already
|
||||
# defined any custom attribute that can be managed by users but you are unsure
|
||||
# about adding other attributes that should only be managed by administrators.
|
||||
- name: Enable ADMIN_EDIT on Unmanaged Attributes
|
||||
community.general.keycloak_userprofile:
|
||||
state: present
|
||||
parent_id: master
|
||||
config:
|
||||
kc_user_profile_config:
|
||||
- unmanagedAttributePolicy: ADMIN_EDIT
|
||||
|
||||
# By setting `Only administrators can view`, unmanaged attributes are read-only
|
||||
# and only available through the administration console and API.
|
||||
- name: Enable ADMIN_VIEW on Unmanaged Attributes
|
||||
community.general.keycloak_userprofile:
|
||||
state: present
|
||||
parent_id: master
|
||||
config:
|
||||
kc_user_profile_config:
|
||||
- unmanagedAttributePolicy: ADMIN_VIEW
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
msg:
|
||||
description: The output message generated by the module.
|
||||
returned: always
|
||||
type: str
|
||||
sample: UserProfileProvider created successfully
|
||||
data:
|
||||
description: The data returned by the Keycloak API.
|
||||
returned: when state is present
|
||||
type: dict
|
||||
sample: {...}
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
||||
keycloak_argument_spec, get_token, KeycloakError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from copy import deepcopy
|
||||
import json
|
||||
|
||||
|
||||
def remove_null_values(data):
|
||||
if isinstance(data, dict):
|
||||
# Recursively remove null values from dictionaries
|
||||
return {k: remove_null_values(v) for k, v in data.items() if v is not None}
|
||||
elif isinstance(data, list):
|
||||
# Recursively remove null values from lists
|
||||
return [remove_null_values(item) for item in data if item is not None]
|
||||
else:
|
||||
# Return the data if it's neither a dictionary nor a list
|
||||
return data
|
||||
|
||||
|
||||
def camel_recursive(data):
|
||||
if isinstance(data, dict):
|
||||
# Convert keys to camelCase and apply recursively
|
||||
return {camel(k): camel_recursive(v) for k, v in data.items()}
|
||||
elif isinstance(data, list):
|
||||
# Apply camelCase conversion to each item in the list
|
||||
return [camel_recursive(item) for item in data]
|
||||
else:
|
||||
# Return the data as is if it's not a dict or list
|
||||
return data
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = keycloak_argument_spec()
|
||||
|
||||
meta_args = dict(
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
parent_id=dict(type='str', aliases=['parentId', 'realm'], required=True),
|
||||
provider_id=dict(type='str', aliases=['providerId'], default='declarative-user-profile', choices=['declarative-user-profile']),
|
||||
provider_type=dict(
|
||||
type='str',
|
||||
aliases=['providerType'],
|
||||
default='org.keycloak.userprofile.UserProfileProvider',
|
||||
choices=['org.keycloak.userprofile.UserProfileProvider']
|
||||
),
|
||||
config=dict(
|
||||
type='dict',
|
||||
required=False,
|
||||
options={
|
||||
'kc_user_profile_config': dict(
|
||||
type='list',
|
||||
aliases=['kcUserProfileConfig'],
|
||||
elements='dict',
|
||||
options={
|
||||
'attributes': dict(
|
||||
type='list',
|
||||
elements='dict',
|
||||
required=False,
|
||||
options={
|
||||
'name': dict(type='str', required=True),
|
||||
'display_name': dict(type='str', aliases=['displayName'], required=True),
|
||||
'validations': dict(
|
||||
type='dict',
|
||||
options={
|
||||
'length': dict(
|
||||
type='dict',
|
||||
options={
|
||||
'min': dict(type='int', required=False),
|
||||
'max': dict(type='int', required=True)
|
||||
}
|
||||
),
|
||||
'email': dict(type='dict', required=False),
|
||||
'username_prohibited_characters': dict(type='dict', aliases=['usernameProhibitedCharacters'], required=False),
|
||||
'up_username_not_idn_homograph': dict(type='dict', aliases=['upUsernameNotIdnHomograph'], required=False),
|
||||
'person_name_prohibited_characters': dict(type='dict', aliases=['personNameProhibitedCharacters'], required=False),
|
||||
'uri': dict(type='dict', required=False),
|
||||
'pattern': dict(type='dict', required=False),
|
||||
'options': dict(type='dict', required=False)
|
||||
}
|
||||
),
|
||||
'annotations': dict(type='dict'),
|
||||
'group': dict(type='str'),
|
||||
'permissions': dict(
|
||||
type='dict',
|
||||
options={
|
||||
'view': dict(type='list', elements='str', default=['admin', 'user']),
|
||||
'edit': dict(type='list', elements='str', default=['admin', 'user'])
|
||||
}
|
||||
),
|
||||
'multivalued': dict(type='bool', default=False),
|
||||
'required': dict(
|
||||
type='dict',
|
||||
options={
|
||||
'roles': dict(type='list', elements='str', default=['user'])
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
'groups': dict(
|
||||
type='list',
|
||||
elements='dict',
|
||||
options={
|
||||
'name': dict(type='str', required=True),
|
||||
'display_header': dict(type='str', aliases=['displayHeader'], required=True),
|
||||
'display_description': dict(type='str', aliases=['displayDescription'], required=False),
|
||||
'annotations': dict(type='dict', required=False)
|
||||
}
|
||||
),
|
||||
'unmanaged_attribute_policy': dict(
|
||||
type='str',
|
||||
aliases=['unmanagedAttributePolicy'],
|
||||
choices=['ENABLED', 'ADMIN_EDIT', 'ADMIN_VIEW'],
|
||||
required=False
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
argument_spec.update(meta_args)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||
|
||||
# Initialize the result object. Only "changed" seems to have special
|
||||
# meaning for Ansible.
|
||||
result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={}))
|
||||
|
||||
# This will include the current state of the realm userprofile if it is already
|
||||
# present. This is only used for diff-mode.
|
||||
before_realm_userprofile = {}
|
||||
before_realm_userprofile['config'] = {}
|
||||
|
||||
# Obtain access token, initialize API
|
||||
try:
|
||||
connection_header = get_token(module.params)
|
||||
except KeycloakError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
kc = KeycloakAPI(module, connection_header)
|
||||
|
||||
params_to_ignore = list(keycloak_argument_spec().keys()) + ["state"]
|
||||
|
||||
# Filter and map the parameters names that apply to the role
|
||||
component_params = [
|
||||
x
|
||||
for x in module.params
|
||||
if x not in params_to_ignore and module.params.get(x) is not None
|
||||
]
|
||||
|
||||
# Build a proposed changeset from parameters given to this module
|
||||
changeset = {}
|
||||
|
||||
# Build the changeset with proper JSON serialization for kc_user_profile_config
|
||||
config = module.params.get('config')
|
||||
changeset['config'] = {}
|
||||
|
||||
# Generate a JSON payload for Keycloak Admin API from the module
|
||||
# parameters. Parameters that do not belong to the JSON payload (e.g.
|
||||
# "state" or "auth_keycloal_url") have been filtered away earlier (see
|
||||
# above).
|
||||
#
|
||||
# This loop converts Ansible module parameters (snake-case) into
|
||||
# Keycloak-compatible format (camel-case). For example proider_id
|
||||
# becomes providerId. It also handles some special cases, e.g. aliases.
|
||||
for component_param in component_params:
|
||||
# realm/parent_id parameter
|
||||
if component_param == 'realm' or component_param == 'parent_id':
|
||||
changeset['parent_id'] = module.params.get(component_param)
|
||||
changeset.pop(component_param, None)
|
||||
# complex parameters in config suboptions
|
||||
elif component_param == 'config':
|
||||
for config_param in config:
|
||||
# special parameter kc_user_profile_config
|
||||
if config_param in ('kcUserProfileConfig', 'kc_user_profile_config'):
|
||||
config_param_org = config_param
|
||||
# rename parameter to be accepted by Keycloak API
|
||||
config_param = 'kc.user.profile.config'
|
||||
# make sure no null values are passed to Keycloak API
|
||||
kc_user_profile_config = remove_null_values(config[config_param_org])
|
||||
changeset[camel(component_param)][config_param] = []
|
||||
if len(kc_user_profile_config) > 0:
|
||||
# convert aliases to camelCase
|
||||
kc_user_profile_config = camel_recursive(kc_user_profile_config)
|
||||
# rename validations to be accepted by Keycloak API
|
||||
if 'attributes' in kc_user_profile_config[0]:
|
||||
for attribute in kc_user_profile_config[0]['attributes']:
|
||||
if 'validations' in attribute:
|
||||
if 'usernameProhibitedCharacters' in attribute['validations']:
|
||||
attribute['validations']['username-prohibited-characters'] = (
|
||||
attribute['validations'].pop('usernameProhibitedCharacters')
|
||||
)
|
||||
if 'upUsernameNotIdnHomograph' in attribute['validations']:
|
||||
attribute['validations']['up-username-not-idn-homograph'] = (
|
||||
attribute['validations'].pop('upUsernameNotIdnHomograph')
|
||||
)
|
||||
if 'personNameProhibitedCharacters' in attribute['validations']:
|
||||
attribute['validations']['person-name-prohibited-characters'] = (
|
||||
attribute['validations'].pop('personNameProhibitedCharacters')
|
||||
)
|
||||
changeset[camel(component_param)][config_param].append(kc_user_profile_config[0])
|
||||
# usual camelCase parameters
|
||||
else:
|
||||
changeset[camel(component_param)][camel(config_param)] = []
|
||||
raw_value = module.params.get(component_param)[config_param]
|
||||
if isinstance(raw_value, bool):
|
||||
value = str(raw_value).lower()
|
||||
else:
|
||||
value = raw_value # Directly use the raw value
|
||||
changeset[camel(component_param)][camel(config_param)].append(value)
|
||||
# usual parameters
|
||||
else:
|
||||
new_param_value = module.params.get(component_param)
|
||||
changeset[camel(component_param)] = new_param_value
|
||||
|
||||
# Make it easier to refer to current module parameters
|
||||
state = module.params.get('state')
|
||||
enabled = module.params.get('enabled')
|
||||
parent_id = module.params.get('parent_id')
|
||||
provider_type = module.params.get('provider_type')
|
||||
provider_id = module.params.get('provider_id')
|
||||
|
||||
# Make a deep copy of the changeset. This is use when determining
|
||||
# changes to the current state.
|
||||
changeset_copy = deepcopy(changeset)
|
||||
|
||||
# Get a list of all Keycloak components that are of userprofile provider type.
|
||||
realm_userprofiles = kc.get_components(urlencode(dict(type=provider_type)), parent_id)
|
||||
|
||||
# If this component is present get its userprofile ID. Confusingly the userprofile ID is
|
||||
# also known as the Provider ID.
|
||||
userprofile_id = None
|
||||
|
||||
# Track individual parameter changes
|
||||
changes = ""
|
||||
|
||||
# This tells Ansible whether the userprofile was changed (added, removed, modified)
|
||||
result['changed'] = False
|
||||
|
||||
# Loop through the list of components. If we encounter a component whose
|
||||
# name matches the value of the name parameter then assume the userprofile is
|
||||
# already present.
|
||||
for userprofile in realm_userprofiles:
|
||||
if provider_id == "declarative-user-profile":
|
||||
userprofile_id = userprofile['id']
|
||||
changeset['id'] = userprofile_id
|
||||
changeset_copy['id'] = userprofile_id
|
||||
|
||||
# keycloak returns kc.user.profile.config as a single JSON formatted string, so we have to deserialize it
|
||||
if 'config' in userprofile and 'kc.user.profile.config' in userprofile['config']:
|
||||
userprofile['config']['kc.user.profile.config'][0] = json.loads(userprofile['config']['kc.user.profile.config'][0])
|
||||
|
||||
# Compare top-level parameters
|
||||
for param, value in changeset.items():
|
||||
before_realm_userprofile[param] = userprofile[param]
|
||||
|
||||
if changeset_copy[param] != userprofile[param] and param != 'config':
|
||||
changes += "%s: %s -> %s, " % (param, userprofile[param], changeset_copy[param])
|
||||
result['changed'] = True
|
||||
|
||||
# Compare parameters under the "config" userprofile
|
||||
for p, v in changeset_copy['config'].items():
|
||||
before_realm_userprofile['config'][p] = userprofile['config'][p]
|
||||
if changeset_copy['config'][p] != userprofile['config'][p]:
|
||||
changes += "config.%s: %s -> %s, " % (p, userprofile['config'][p], changeset_copy['config'][p])
|
||||
result['changed'] = True
|
||||
|
||||
# Check all the possible states of the resource and do what is needed to
|
||||
# converge current state with desired state (create, update or delete
|
||||
# the userprofile).
|
||||
|
||||
# keycloak expects kc.user.profile.config as a single JSON formatted string, so we have to serialize it
|
||||
if 'config' in changeset and 'kc.user.profile.config' in changeset['config']:
|
||||
changeset['config']['kc.user.profile.config'][0] = json.dumps(changeset['config']['kc.user.profile.config'][0])
|
||||
if userprofile_id and state == 'present':
|
||||
if result['changed']:
|
||||
if module._diff:
|
||||
result['diff'] = dict(before=before_realm_userprofile, after=changeset_copy)
|
||||
|
||||
if module.check_mode:
|
||||
result['msg'] = "Userprofile %s would be changed: %s" % (provider_id, changes.strip(", "))
|
||||
else:
|
||||
kc.update_component(changeset, parent_id)
|
||||
result['msg'] = "Userprofile %s changed: %s" % (provider_id, changes.strip(", "))
|
||||
else:
|
||||
result['msg'] = "Userprofile %s was in sync" % (provider_id)
|
||||
|
||||
result['end_state'] = changeset_copy
|
||||
elif userprofile_id and state == 'absent':
|
||||
if module._diff:
|
||||
result['diff'] = dict(before=before_realm_userprofile, after={})
|
||||
|
||||
if module.check_mode:
|
||||
result['changed'] = True
|
||||
result['msg'] = "Userprofile %s would be deleted" % (provider_id)
|
||||
else:
|
||||
kc.delete_component(userprofile_id, parent_id)
|
||||
result['changed'] = True
|
||||
result['msg'] = "Userprofile %s deleted" % (provider_id)
|
||||
|
||||
result['end_state'] = {}
|
||||
elif not userprofile_id and state == 'present':
|
||||
if module._diff:
|
||||
result['diff'] = dict(before={}, after=changeset_copy)
|
||||
|
||||
if module.check_mode:
|
||||
result['changed'] = True
|
||||
result['msg'] = "Userprofile %s would be created" % (provider_id)
|
||||
else:
|
||||
kc.create_component(changeset, parent_id)
|
||||
result['changed'] = True
|
||||
result['msg'] = "Userprofile %s created" % (provider_id)
|
||||
|
||||
result['end_state'] = changeset_copy
|
||||
elif not userprofile_id and state == 'absent':
|
||||
result['changed'] = False
|
||||
result['msg'] = "Userprofile %s not present" % (provider_id)
|
||||
result['end_state'] = {}
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -670,7 +670,7 @@ def main():
|
||||
backupwindow=backupwindow,
|
||||
)
|
||||
|
||||
kwargs = dict((k, v) for k, v in check_items.items() if v is not None)
|
||||
kwargs = {k: v for k, v in check_items.items() if v is not None}
|
||||
|
||||
# setup the auth
|
||||
try:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user