mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 18:36:28 +00:00
Compare commits
361 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c24a12462 | ||
|
|
990fffb563 | ||
|
|
1d9bfc206b | ||
|
|
3b74e9b646 | ||
|
|
37308c929b | ||
|
|
2945509a55 | ||
|
|
57be1e8be4 | ||
|
|
086b4e4fb8 | ||
|
|
92dcf1e0b2 | ||
|
|
78a02b84f3 | ||
|
|
7846cddab8 | ||
|
|
b0af1e9c75 | ||
|
|
f6349578c6 | ||
|
|
e4287becb1 | ||
|
|
005e8b151a | ||
|
|
cd2e55e2ab | ||
|
|
1ff276ec34 | ||
|
|
c16c4a32d1 | ||
|
|
3a01ceb355 | ||
|
|
8ca91ab283 | ||
|
|
c88a40d3e3 | ||
|
|
0dfd02c1ca | ||
|
|
5d9072030e | ||
|
|
49765f103b | ||
|
|
1782efb09e | ||
|
|
d8807e9b51 | ||
|
|
d823d71442 | ||
|
|
46d2cddbde | ||
|
|
069b785cb2 | ||
|
|
090d3f3709 | ||
|
|
68a9b66966 | ||
|
|
f96c6476fe | ||
|
|
fc0f677535 | ||
|
|
9a986473bd | ||
|
|
e9f0e49283 | ||
|
|
5eff31e760 | ||
|
|
39c58d5469 | ||
|
|
20d7be4f38 | ||
|
|
2d26fba0b9 | ||
|
|
d6168a196b | ||
|
|
02de81c39e | ||
|
|
4096b9fa5a | ||
|
|
fe3a3a7638 | ||
|
|
7cac741e77 | ||
|
|
f84ebed63f | ||
|
|
f905a1bc94 | ||
|
|
b0470f2e59 | ||
|
|
42175e38b2 | ||
|
|
8e79844b75 | ||
|
|
1338db358a | ||
|
|
06c4439a1c | ||
|
|
16d5d5fc57 | ||
|
|
71af3226f3 | ||
|
|
b3037a46be | ||
|
|
f7df19adbd | ||
|
|
3bca21aa1b | ||
|
|
1bb3d41e15 | ||
|
|
f214f206c3 | ||
|
|
9b8011d692 | ||
|
|
f227038f38 | ||
|
|
86a2996814 | ||
|
|
eb154003cf | ||
|
|
212871fcaf | ||
|
|
1795a67b8e | ||
|
|
a71c0af9cc | ||
|
|
569cde6c3e | ||
|
|
f0db1d1f6b | ||
|
|
5a36e84b86 | ||
|
|
a74c6db77f | ||
|
|
9a14980ca7 | ||
|
|
8c9effce1f | ||
|
|
51ec3594dd | ||
|
|
802f8ea224 | ||
|
|
23af148021 | ||
|
|
1a2c2d0a64 | ||
|
|
80243f8180 | ||
|
|
13b5c4092a | ||
|
|
9b0c983860 | ||
|
|
704a5acc63 | ||
|
|
861f55eb04 | ||
|
|
bebe162a22 | ||
|
|
f82e7a7b83 | ||
|
|
03240ad7dc | ||
|
|
d87b9fe0dc | ||
|
|
5f481939d4 | ||
|
|
70c78c1d71 | ||
|
|
0350a631de | ||
|
|
da8b133a73 | ||
|
|
a409f8fc2f | ||
|
|
bb73f28bf5 | ||
|
|
cd01a928ab | ||
|
|
1ac94b5f44 | ||
|
|
6889e0478d | ||
|
|
fabf6263f1 | ||
|
|
7dd7cbdba8 | ||
|
|
7f4f066e86 | ||
|
|
4f4075a542 | ||
|
|
7aa118b957 | ||
|
|
b774435d8d | ||
|
|
a71e19130d | ||
|
|
d347bf5fa0 | ||
|
|
3b7f13c58e | ||
|
|
136419c5c0 | ||
|
|
bc7ad0f0ea | ||
|
|
cb985b31f9 | ||
|
|
feb443d260 | ||
|
|
bc609d74a0 | ||
|
|
4bd68ac153 | ||
|
|
d75dee3230 | ||
|
|
3eeafecd1f | ||
|
|
ea719649bb | ||
|
|
70adba8991 | ||
|
|
b48293ca31 | ||
|
|
85f9d89510 | ||
|
|
7051fe3449 | ||
|
|
fc2024d837 | ||
|
|
45c2e0f8d0 | ||
|
|
62138b288a | ||
|
|
be3b66c8b5 | ||
|
|
17e11d7d7e | ||
|
|
211688ef1b | ||
|
|
af1c5dd785 | ||
|
|
a5697da29c | ||
|
|
0735656319 | ||
|
|
8f98ba9119 | ||
|
|
a05a5982a6 | ||
|
|
be11d0d409 | ||
|
|
9d66a1dc1e | ||
|
|
f55342d8af | ||
|
|
486c26b224 | ||
|
|
be4d5b7dc4 | ||
|
|
865de5baa0 | ||
|
|
7fd37ea247 | ||
|
|
524d5883b8 | ||
|
|
1b8e6bc95b | ||
|
|
1bbef58844 | ||
|
|
a5b2b5ce8c | ||
|
|
12b76ead29 | ||
|
|
da29ea151d | ||
|
|
bafad8ecd4 | ||
|
|
6c8f949ba9 | ||
|
|
9307b76e74 | ||
|
|
8491bf7b49 | ||
|
|
39ef949f27 | ||
|
|
b674f94f64 | ||
|
|
bc2ff24f74 | ||
|
|
610ecf9bf5 | ||
|
|
13d0310e91 | ||
|
|
e4e091acca | ||
|
|
48b5a7a80a | ||
|
|
b444e8739c | ||
|
|
b463571902 | ||
|
|
b2b8fc30bf | ||
|
|
4f758bfb84 | ||
|
|
90c9f20ef8 | ||
|
|
609f28f791 | ||
|
|
d62fe154d2 | ||
|
|
b389f8637f | ||
|
|
795a855d0e | ||
|
|
a4b32d7b9c | ||
|
|
f5fa16c881 | ||
|
|
9f5193e40b | ||
|
|
23396e62dc | ||
|
|
4363f8764b | ||
|
|
4947786d36 | ||
|
|
fb67df3051 | ||
|
|
da048aa12e | ||
|
|
47b4cf766e | ||
|
|
69ab5eb110 | ||
|
|
6298ad4faa | ||
|
|
73b6b98ed9 | ||
|
|
1c4197aa23 | ||
|
|
23fbc5e241 | ||
|
|
09cded05e7 | ||
|
|
67736d796a | ||
|
|
226207522e | ||
|
|
17e275bc0b | ||
|
|
6fab46710a | ||
|
|
79d87552ef | ||
|
|
c13bede0c5 | ||
|
|
0ded1109fe | ||
|
|
fa30b02294 | ||
|
|
98df344017 | ||
|
|
a50329d0d5 | ||
|
|
74c15c1241 | ||
|
|
248e2ff321 | ||
|
|
05bf5ee1df | ||
|
|
adb367a6af | ||
|
|
2140485148 | ||
|
|
1b0d55fe31 | ||
|
|
787fa46217 | ||
|
|
f6d0b35bb7 | ||
|
|
6cafd3bed7 | ||
|
|
e0dbe9c98d | ||
|
|
638a7fc199 | ||
|
|
b5c3361be4 | ||
|
|
dd7c3ad10d | ||
|
|
102a0857db | ||
|
|
9510988abc | ||
|
|
beacd54b7b | ||
|
|
dd25ddfbe8 | ||
|
|
49bd9cbd3c | ||
|
|
2a8da76907 | ||
|
|
ffa3d15881 | ||
|
|
551b0b9eea | ||
|
|
1dd697bdc2 | ||
|
|
001292c780 | ||
|
|
8ea58618db | ||
|
|
6088e2dc0f | ||
|
|
0a35eb2dda | ||
|
|
980fa36fac | ||
|
|
bc383b8f7b | ||
|
|
eded6ebf64 | ||
|
|
5af921e8d9 | ||
|
|
c7a2e28daa | ||
|
|
549a73bd78 | ||
|
|
fa1f2af460 | ||
|
|
ab6a61237a | ||
|
|
82e74e35d9 | ||
|
|
a5cd4ebea2 | ||
|
|
0dc891bf37 | ||
|
|
997e6345b5 | ||
|
|
2580da9796 | ||
|
|
f8465c692b | ||
|
|
84147081d4 | ||
|
|
afd1988810 | ||
|
|
be3bfd6fa5 | ||
|
|
29f9865497 | ||
|
|
5c72ab34bf | ||
|
|
4298f2dd92 | ||
|
|
2d3f99ec3a | ||
|
|
13e3161f2a | ||
|
|
5a51929aa3 | ||
|
|
44028060c3 | ||
|
|
44679e71a2 | ||
|
|
cd77d67efb | ||
|
|
92f8bf7b6f | ||
|
|
069b485b7e | ||
|
|
002208f425 | ||
|
|
31de16cee3 | ||
|
|
32ec751996 | ||
|
|
c0dea8b164 | ||
|
|
431a37fa5b | ||
|
|
76fde43fca | ||
|
|
8891f559ef | ||
|
|
878664778e | ||
|
|
9946f758af | ||
|
|
ee8b15708f | ||
|
|
f0dd018d47 | ||
|
|
0bfebde5c9 | ||
|
|
acddb190ba | ||
|
|
08ece2e0fa | ||
|
|
6afe35d263 | ||
|
|
4f92f39720 | ||
|
|
3318034403 | ||
|
|
8d307cb190 | ||
|
|
acc3173030 | ||
|
|
1a3c93f80c | ||
|
|
e99b5086a8 | ||
|
|
98181fb8cb | ||
|
|
f7bc6964be | ||
|
|
dfb9b1b9fb | ||
|
|
56a18a029a | ||
|
|
e9f7f7e2de | ||
|
|
fd0d05d6f2 | ||
|
|
ec12422fae | ||
|
|
f79940c415 | ||
|
|
6d74e0c640 | ||
|
|
ec6dfe2fcd | ||
|
|
702dd9bbda | ||
|
|
671b7ab149 | ||
|
|
4a1006ac34 | ||
|
|
825bec7053 | ||
|
|
1fdbb50abb | ||
|
|
1389bba459 | ||
|
|
916f6f7c87 | ||
|
|
0b0a302855 | ||
|
|
98b2d04348 | ||
|
|
30c155e250 | ||
|
|
097f08608f | ||
|
|
6c1eb77f18 | ||
|
|
5e5e1963c3 | ||
|
|
838e4e3f02 | ||
|
|
0c7b9e50b5 | ||
|
|
aea238e5d1 | ||
|
|
2b64ef2a62 | ||
|
|
e2f6d7b523 | ||
|
|
68051774d8 | ||
|
|
a599afa384 | ||
|
|
3d0da92784 | ||
|
|
88d2a3a1fb | ||
|
|
e724bc5f51 | ||
|
|
32558558c0 | ||
|
|
07bac1777f | ||
|
|
b4a2e9da50 | ||
|
|
ecea4a2f38 | ||
|
|
30edafabe7 | ||
|
|
f4a87fdbcb | ||
|
|
58cce27d45 | ||
|
|
241cc02fa8 | ||
|
|
096d36adc5 | ||
|
|
0589c84176 | ||
|
|
e3a3950e3d | ||
|
|
cf7a58f627 | ||
|
|
af01b462d5 | ||
|
|
1b9d437be8 | ||
|
|
512b2c7389 | ||
|
|
d716bd4648 | ||
|
|
42e55e4f86 | ||
|
|
dbba0d1956 | ||
|
|
3b779ecade | ||
|
|
d9f3e7a2ec | ||
|
|
e0346d400f | ||
|
|
5adb7ab948 | ||
|
|
f496256d18 | ||
|
|
d05932fb2c | ||
|
|
938aec492e | ||
|
|
12395732e8 | ||
|
|
af5da7d412 | ||
|
|
b2dea631d1 | ||
|
|
19984ce4df | ||
|
|
fce91ebbd4 | ||
|
|
58705d5ac3 | ||
|
|
f87777b9f5 | ||
|
|
09b9ea466f | ||
|
|
d530470d30 | ||
|
|
0c4d2a6e5e | ||
|
|
a88f6f56c7 | ||
|
|
2a5e7c33df | ||
|
|
5147c49498 | ||
|
|
9b16392648 | ||
|
|
404782c9d7 | ||
|
|
21cd65fccf | ||
|
|
3c12c6f482 | ||
|
|
b8ecb1671b | ||
|
|
b3c661a9f6 | ||
|
|
89f12c87eb | ||
|
|
f8652571f7 | ||
|
|
32fa588f47 | ||
|
|
8d886b42ec | ||
|
|
d0870a022e | ||
|
|
df66885fa4 | ||
|
|
f8d8f691bc | ||
|
|
e1503fc306 | ||
|
|
4a74f46e56 | ||
|
|
6b00b76f32 | ||
|
|
65f58afbd9 | ||
|
|
c2f08c57e0 | ||
|
|
2583c60487 | ||
|
|
c2e578cb14 | ||
|
|
6a514b6843 | ||
|
|
48e860be20 | ||
|
|
0304989392 | ||
|
|
ab0b85d7d2 | ||
|
|
07a47c047b | ||
|
|
567c7d1839 | ||
|
|
74e941e432 | ||
|
|
57e36d7dc2 | ||
|
|
a366318ac6 | ||
|
|
6d0bcec1cb | ||
|
|
e9a3b69fd9 |
@@ -59,14 +59,14 @@ pool: Standard
|
||||
|
||||
stages:
|
||||
### Sanity
|
||||
- stage: Sanity_2_18
|
||||
displayName: Sanity 2.18
|
||||
- stage: Sanity_devel
|
||||
displayName: Sanity devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.18/sanity/{0}
|
||||
testFormat: devel/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
@@ -99,15 +99,28 @@ 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_2_18
|
||||
displayName: Units 2.18
|
||||
- stage: Units_devel
|
||||
displayName: Units devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.18/units/{0}/1
|
||||
testFormat: devel/units/{0}/1
|
||||
targets:
|
||||
- test: 3.8
|
||||
- test: 3.9
|
||||
@@ -138,15 +151,26 @@ stages:
|
||||
- test: 2.7
|
||||
- test: 3.6
|
||||
- test: "3.11"
|
||||
|
||||
## Remote
|
||||
- stage: Remote_2_18_extra_vms
|
||||
displayName: Remote 2.18 extra VMs
|
||||
- stage: Units_2_15
|
||||
displayName: Units 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/{0}
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.15/units/{0}/1
|
||||
targets:
|
||||
- test: 3.5
|
||||
- test: "3.10"
|
||||
|
||||
## Remote
|
||||
- stage: Remote_devel_extra_vms
|
||||
displayName: Remote devel extra VMs
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: Alpine 3.20
|
||||
test: alpine/3.20
|
||||
@@ -158,13 +182,13 @@ stages:
|
||||
test: ubuntu/24.04
|
||||
groups:
|
||||
- vm
|
||||
- stage: Remote_2_18
|
||||
displayName: Remote 2.18
|
||||
- stage: Remote_devel
|
||||
displayName: Remote devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/{0}
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: macOS 14.3
|
||||
test: macos/14.3
|
||||
@@ -208,23 +232,43 @@ stages:
|
||||
test: rhel/9.2
|
||||
- name: RHEL 8.8
|
||||
test: rhel/8.8
|
||||
- name: RHEL 7.9
|
||||
test: rhel/7.9
|
||||
# - name: FreeBSD 13.2
|
||||
# test: freebsd/13.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Docker
|
||||
- stage: Docker_2_18
|
||||
displayName: Docker 2.18
|
||||
- stage: Remote_2_15
|
||||
displayName: Remote 2.15
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/linux/{0}
|
||||
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
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Docker
|
||||
- stage: Docker_devel
|
||||
displayName: Docker devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 40
|
||||
test: fedora40
|
||||
@@ -270,6 +314,20 @@ 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:
|
||||
@@ -278,13 +336,13 @@ stages:
|
||||
- 3
|
||||
|
||||
### Community Docker
|
||||
- stage: Docker_community_2_18
|
||||
displayName: Docker (community images) 2.18
|
||||
- stage: Docker_community_devel
|
||||
displayName: Docker (community images) devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/linux-community/{0}
|
||||
testFormat: devel/linux-community/{0}
|
||||
targets:
|
||||
- name: Debian Bullseye
|
||||
test: debian-bullseye/3.9
|
||||
@@ -298,63 +356,77 @@ stages:
|
||||
- 3
|
||||
|
||||
### Generic
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - 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.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_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'
|
||||
|
||||
- stage: Summary
|
||||
condition: succeededOrFailed()
|
||||
dependsOn:
|
||||
- Sanity_2_18
|
||||
- Sanity_devel
|
||||
- Sanity_2_17
|
||||
- Sanity_2_16
|
||||
- Units_2_18
|
||||
- Sanity_2_15
|
||||
- Units_devel
|
||||
- Units_2_17
|
||||
- Units_2_16
|
||||
- Remote_2_18_extra_vms
|
||||
- Remote_2_18
|
||||
- Units_2_15
|
||||
- Remote_devel_extra_vms
|
||||
- Remote_devel
|
||||
- Remote_2_17
|
||||
- Remote_2_16
|
||||
- Docker_2_18
|
||||
- Remote_2_15
|
||||
- Docker_devel
|
||||
- Docker_2_17
|
||||
- Docker_2_16
|
||||
- Docker_community_2_18
|
||||
- Docker_2_15
|
||||
- Docker_community_devel
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - Generic_2_18
|
||||
# - Generic_devel
|
||||
# - Generic_2_17
|
||||
# - Generic_2_16
|
||||
# - Generic_2_15
|
||||
jobs:
|
||||
- template: templates/coverage.yml
|
||||
|
||||
94
.github/BOTMETA.yml
vendored
94
.github/BOTMETA.yml
vendored
@@ -33,6 +33,8 @@ files:
|
||||
maintainers: $team_ansible_core
|
||||
$becomes/pmrun.py:
|
||||
maintainers: $team_ansible_core
|
||||
$becomes/run0.py:
|
||||
maintainers: konstruktoid
|
||||
$becomes/sesu.py:
|
||||
maintainers: nekonyuu
|
||||
$becomes/sudosu.py:
|
||||
@@ -89,6 +91,8 @@ files:
|
||||
maintainers: ryancurrah
|
||||
$callbacks/syslog_json.py:
|
||||
maintainers: imjoseangel
|
||||
$callbacks/timestamp.py:
|
||||
maintainers: kurokobo
|
||||
$callbacks/unixy.py:
|
||||
labels: unixy
|
||||
maintainers: akatch
|
||||
@@ -117,6 +121,8 @@ files:
|
||||
maintainers: $team_ansible_core
|
||||
$doc_fragments/:
|
||||
labels: docs_fragments
|
||||
$doc_fragments/django.py:
|
||||
maintainers: russoz
|
||||
$doc_fragments/hpe3par.py:
|
||||
labels: hpe3par
|
||||
maintainers: farhan7500 gautamphegde
|
||||
@@ -151,6 +157,8 @@ files:
|
||||
$filters/jc.py:
|
||||
maintainers: kellyjonbrazil
|
||||
$filters/json_query.py: {}
|
||||
$filters/keep_keys.py:
|
||||
maintainers: vbotka
|
||||
$filters/lists.py:
|
||||
maintainers: cfiehe
|
||||
$filters/lists_difference.yml:
|
||||
@@ -164,6 +172,12 @@ files:
|
||||
$filters/lists_union.yml:
|
||||
maintainers: cfiehe
|
||||
$filters/random_mac.py: {}
|
||||
$filters/remove_keys.py:
|
||||
maintainers: vbotka
|
||||
$filters/replace_keys.py:
|
||||
maintainers: vbotka
|
||||
$filters/reveal_ansible_type.py:
|
||||
maintainers: vbotka
|
||||
$filters/time.py:
|
||||
maintainers: resmo
|
||||
$filters/to_days.yml:
|
||||
@@ -294,8 +308,12 @@ files:
|
||||
labels: module_utils
|
||||
$module_utils/btrfs.py:
|
||||
maintainers: gnfzdz
|
||||
$module_utils/cmd_runner.py:
|
||||
maintainers: russoz
|
||||
$module_utils/deps.py:
|
||||
maintainers: russoz
|
||||
$module_utils/django.py:
|
||||
maintainers: russoz
|
||||
$module_utils/gconftool2.py:
|
||||
labels: gconftool2
|
||||
maintainers: russoz
|
||||
@@ -339,6 +357,8 @@ files:
|
||||
$module_utils/pipx.py:
|
||||
labels: pipx
|
||||
maintainers: russoz
|
||||
$module_utils/python_runner.py:
|
||||
maintainers: russoz
|
||||
$module_utils/puppet.py:
|
||||
labels: puppet
|
||||
maintainers: russoz
|
||||
@@ -490,6 +510,12 @@ files:
|
||||
maintainers: tintoy
|
||||
$modules/discord.py:
|
||||
maintainers: cwollinger
|
||||
$modules/django_check.py:
|
||||
maintainers: russoz
|
||||
$modules/django_command.py:
|
||||
maintainers: russoz
|
||||
$modules/django_createcachetable.py:
|
||||
maintainers: russoz
|
||||
$modules/django_manage.py:
|
||||
ignore: scottanderson42 tastychutney
|
||||
labels: django_manage
|
||||
@@ -532,8 +558,6 @@ files:
|
||||
maintainers: $team_flatpak
|
||||
$modules/flatpak_remote.py:
|
||||
maintainers: $team_flatpak
|
||||
$modules/flowdock.py:
|
||||
ignore: mcodd
|
||||
$modules/gandi_livedns.py:
|
||||
maintainers: gthiemonge
|
||||
$modules/gconftool2.py:
|
||||
@@ -1096,46 +1120,6 @@ files:
|
||||
$modules/python_requirements_info.py:
|
||||
ignore: ryansb
|
||||
maintainers: willthames
|
||||
$modules/rax:
|
||||
ignore: ryansb sivel
|
||||
$modules/rax.py:
|
||||
maintainers: omgjlk sivel
|
||||
$modules/rax_cbs.py:
|
||||
maintainers: claco
|
||||
$modules/rax_cbs_attachments.py:
|
||||
maintainers: claco
|
||||
$modules/rax_cdb.py:
|
||||
maintainers: jails
|
||||
$modules/rax_cdb_database.py:
|
||||
maintainers: jails
|
||||
$modules/rax_cdb_user.py:
|
||||
maintainers: jails
|
||||
$modules/rax_clb.py:
|
||||
maintainers: claco
|
||||
$modules/rax_clb_nodes.py:
|
||||
maintainers: neuroid
|
||||
$modules/rax_clb_ssl.py:
|
||||
maintainers: smashwilson
|
||||
$modules/rax_files.py:
|
||||
maintainers: angstwad
|
||||
$modules/rax_files_objects.py:
|
||||
maintainers: angstwad
|
||||
$modules/rax_identity.py:
|
||||
maintainers: claco
|
||||
$modules/rax_mon_alarm.py:
|
||||
maintainers: smashwilson
|
||||
$modules/rax_mon_check.py:
|
||||
maintainers: smashwilson
|
||||
$modules/rax_mon_entity.py:
|
||||
maintainers: smashwilson
|
||||
$modules/rax_mon_notification.py:
|
||||
maintainers: smashwilson
|
||||
$modules/rax_mon_notification_plan.py:
|
||||
maintainers: smashwilson
|
||||
$modules/rax_network.py:
|
||||
maintainers: claco omgjlk
|
||||
$modules/rax_queue.py:
|
||||
maintainers: claco
|
||||
$modules/read_csv.py:
|
||||
maintainers: dagwieers
|
||||
$modules/redfish_:
|
||||
@@ -1300,8 +1284,6 @@ files:
|
||||
maintainers: farhan7500 gautamphegde
|
||||
$modules/ssh_config.py:
|
||||
maintainers: gaqzi Akasurde
|
||||
$modules/stackdriver.py:
|
||||
maintainers: bwhaley
|
||||
$modules/stacki_host.py:
|
||||
labels: stacki_host
|
||||
maintainers: bsanders bbyhuy
|
||||
@@ -1394,8 +1376,6 @@ files:
|
||||
maintainers: $team_wdc
|
||||
$modules/wdc_redfish_info.py:
|
||||
maintainers: $team_wdc
|
||||
$modules/webfaction_:
|
||||
maintainers: quentinsf
|
||||
$modules/xattr.py:
|
||||
labels: xattr
|
||||
maintainers: bcoca
|
||||
@@ -1447,10 +1427,16 @@ files:
|
||||
ignore: matze
|
||||
labels: zypper
|
||||
maintainers: $team_suse
|
||||
$plugin_utils/ansible_type.py:
|
||||
maintainers: vbotka
|
||||
$plugin_utils/keys_filter.py:
|
||||
maintainers: vbotka
|
||||
$plugin_utils/unsafe.py:
|
||||
maintainers: felixfontein
|
||||
$tests/a_module.py:
|
||||
maintainers: felixfontein
|
||||
$tests/ansible_type.py:
|
||||
maintainers: vbotka
|
||||
$tests/fqdn_valid.py:
|
||||
maintainers: vbotka
|
||||
#########################
|
||||
@@ -1464,6 +1450,14 @@ files:
|
||||
maintainers: felixfontein
|
||||
docs/docsite/rst/filter_guide_abstract_informations_lists_helper.rst:
|
||||
maintainers: cfiehe
|
||||
docs/docsite/rst/filter_guide-abstract_informations-lists_of_dictionaries-keep_keys.rst:
|
||||
maintainers: vbotka
|
||||
docs/docsite/rst/filter_guide-abstract_informations-lists_of_dictionaries-remove_keys.rst:
|
||||
maintainers: vbotka
|
||||
docs/docsite/rst/filter_guide-abstract_informations-lists_of_dictionaries-replace_keys.rst:
|
||||
maintainers: vbotka
|
||||
docs/docsite/rst/filter_guide-abstract_informations-lists_of_dictionaries.rst:
|
||||
maintainers: vbotka
|
||||
docs/docsite/rst/filter_guide_abstract_informations_merging_lists_of_dictionaries.rst:
|
||||
maintainers: vbotka
|
||||
docs/docsite/rst/filter_guide_conversions.rst:
|
||||
@@ -1480,12 +1474,18 @@ files:
|
||||
maintainers: ericzolf
|
||||
docs/docsite/rst/guide_alicloud.rst:
|
||||
maintainers: xiaozhu36
|
||||
docs/docsite/rst/guide_cmdrunner.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/guide_deps.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/guide_online.rst:
|
||||
maintainers: remyleone
|
||||
docs/docsite/rst/guide_packet.rst:
|
||||
maintainers: baldwinSPC nurfet-becirevic t0mk teebes
|
||||
docs/docsite/rst/guide_scaleway.rst:
|
||||
maintainers: $team_scaleway
|
||||
docs/docsite/rst/guide_vardict.rst:
|
||||
maintainers: russoz
|
||||
docs/docsite/rst/test_guide.rst:
|
||||
maintainers: felixfontein
|
||||
#########################
|
||||
@@ -1517,7 +1517,7 @@ macros:
|
||||
team_ansible_core:
|
||||
team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross
|
||||
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
|
||||
team_consul: sgargan apollo13
|
||||
team_consul: sgargan apollo13 Ilgmi
|
||||
team_cyberark_conjur: jvanderhoof ryanprior
|
||||
team_e_spirit: MatrixCrawler getjack
|
||||
team_flatpak: JayKayy oolongbrothers
|
||||
|
||||
23
.github/workflows/ansible-test.yml
vendored
23
.github/workflows/ansible-test.yml
vendored
@@ -31,7 +31,6 @@ 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
|
||||
@@ -77,10 +76,6 @@ jobs:
|
||||
python: '3.8'
|
||||
- ansible: '2.14'
|
||||
python: '3.9'
|
||||
- ansible: '2.15'
|
||||
python: '3.5'
|
||||
- ansible: '2.15'
|
||||
python: '3.10'
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
@@ -171,32 +166,16 @@ 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: >-
|
||||
|
||||
10
.github/workflows/reuse.yml
vendored
10
.github/workflows/reuse.yml
vendored
@@ -7,14 +7,10 @@ name: Verify REUSE
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
branches: [main]
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
branches: [main]
|
||||
# Run CI once per day (at 07:30 UTC)
|
||||
schedule:
|
||||
- cron: '30 7 * * *'
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -512,3 +512,7 @@ $RECYCLE.BIN/
|
||||
|
||||
# Integration tests cloud configs
|
||||
tests/integration/cloud-config-*.ini
|
||||
|
||||
|
||||
# VSCode specific extensions
|
||||
.vscode/settings.json
|
||||
|
||||
1211
CHANGELOG.md
1211
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
1091
CHANGELOG.rst
1091
CHANGELOG.rst
File diff suppressed because it is too large
Load Diff
@@ -56,8 +56,6 @@ cd ~/dev/ansible_collections/community/general
|
||||
|
||||
Then you can run `ansible-test` (which is a part of [ansible-core](https://pypi.org/project/ansible-core/)) inside the checkout. The following example commands expect that you have installed Docker or Podman. Note that Podman has only been supported by more recent ansible-core releases. If you are using Docker, the following will work with Ansible 2.9+.
|
||||
|
||||
### Sanity tests
|
||||
|
||||
The following commands show how to run sanity tests:
|
||||
|
||||
```.bash
|
||||
@@ -68,8 +66,6 @@ ansible-test sanity --docker -v
|
||||
ansible-test sanity --docker -v plugins/modules/system/pids.py tests/integration/targets/pids/
|
||||
```
|
||||
|
||||
### Unit tests
|
||||
|
||||
The following commands show how to run unit tests:
|
||||
|
||||
```.bash
|
||||
@@ -83,32 +79,13 @@ ansible-test units --docker -v --python 3.8
|
||||
ansible-test units --docker -v --python 3.8 tests/unit/plugins/modules/net_tools/test_nmcli.py
|
||||
```
|
||||
|
||||
### Integration tests
|
||||
|
||||
The following commands show how to run integration tests:
|
||||
|
||||
#### In Docker
|
||||
|
||||
Integration tests on Docker have the following parameters:
|
||||
- `image_name` (required): The name of the Docker image. To get the list of supported Docker images, run
|
||||
`ansible-test integration --help` and look for _target docker images_.
|
||||
- `test_name` (optional): The name of the integration test.
|
||||
For modules, this equals the short name of the module; for example, `pacman` in case of `community.general.pacman`.
|
||||
For plugins, the plugin type is added before the plugin's short name, for example `callback_yaml` for the `community.general.yaml` callback.
|
||||
```.bash
|
||||
# Test all plugins/modules on fedora40
|
||||
ansible-test integration -v --docker fedora40
|
||||
# Run integration tests for the interfaces_files module in a Docker container using the
|
||||
# fedora35 operating system image (the supported images depend on your ansible-core version):
|
||||
ansible-test integration --docker fedora35 -v interfaces_file
|
||||
|
||||
# Template
|
||||
ansible-test integration -v --docker image_name test_name
|
||||
|
||||
# Example community.general.ini_file module on fedora40 Docker image:
|
||||
ansible-test integration -v --docker fedora40 ini_file
|
||||
```
|
||||
|
||||
#### Without isolation
|
||||
|
||||
```.bash
|
||||
# Run integration tests for the flattened lookup **without any isolation**:
|
||||
ansible-test integration -v lookup_flattened
|
||||
```
|
||||
|
||||
34
README.md
34
README.md
@@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Community General Collection
|
||||
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://github.com/ansible-collections/community.general/actions)
|
||||
[](https://codecov.io/gh/ansible-collections/community.general)
|
||||
[](https://api.reuse.software/info/github.com/ansible-collections/community.general)
|
||||
@@ -23,21 +23,9 @@ We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/comm
|
||||
|
||||
If you encounter abusive behavior violating the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html), please refer to the [policy violations](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html#policy-violations) section of the Code of Conduct for information on how to raise a complaint.
|
||||
|
||||
## 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. 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.
|
||||
|
||||
* The Ansible [Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn): used to announce releases and important changes.
|
||||
|
||||
For more information about communication, see the [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html).
|
||||
|
||||
## 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, and ansible-core 2.18 releases of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, ansible-core 2.17 releases and the current development version of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
|
||||
## External requirements
|
||||
|
||||
@@ -110,13 +98,25 @@ It is necessary for maintainers of this collection to be subscribed to:
|
||||
|
||||
They also should be subscribed to Ansible's [The Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn).
|
||||
|
||||
## Communication
|
||||
|
||||
We announce important development changes and releases through Ansible's [The Bullhorn newsletter](https://eepurl.com/gZmiEP). If you are a collection developer, be sure you are subscribed.
|
||||
|
||||
Join us in the `#ansible` (general use questions and support), `#ansible-community` (community and collection development questions), and other [IRC channels](https://docs.ansible.com/ansible/devel/community/communication.html#irc-channels) on [Libera.chat](https://libera.chat).
|
||||
|
||||
We take part in the global quarterly [Ansible Contributor Summit](https://github.com/ansible/community/wiki/Contributor-Summit) virtually or in-person. Track [The Bullhorn newsletter](https://eepurl.com/gZmiEP) and join us.
|
||||
|
||||
For more information about communities, meetings and agendas see [Community Wiki](https://github.com/ansible/community/wiki/Community).
|
||||
|
||||
For more information about communication, refer to Ansible's the [Communication guide](https://docs.ansible.com/ansible/devel/community/communication.html).
|
||||
|
||||
## Publishing New Version
|
||||
|
||||
See the [Releasing guidelines](https://github.com/ansible/community-docs/blob/main/releasing_collections.rst) to learn how to release this collection.
|
||||
|
||||
## Release notes
|
||||
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-8/CHANGELOG.md).
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-9/CHANGELOG.md).
|
||||
|
||||
## Roadmap
|
||||
|
||||
@@ -135,8 +135,8 @@ See [this issue](https://github.com/ansible-collections/community.general/issues
|
||||
|
||||
This collection is primarily licensed and distributed as a whole under the GNU General Public License v3.0 or later.
|
||||
|
||||
See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/community.general/blob/main/COPYING) for the full text.
|
||||
See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/community.general/blob/stable-9/COPYING) for the full text.
|
||||
|
||||
Parts of the collection are licensed under the [BSD 2-Clause license](https://github.com/ansible-collections/community.general/blob/main/LICENSES/BSD-2-Clause.txt), the [MIT license](https://github.com/ansible-collections/community.general/blob/main/LICENSES/MIT.txt), and the [PSF 2.0 license](https://github.com/ansible-collections/community.general/blob/main/LICENSES/PSF-2.0.txt).
|
||||
Parts of the collection are licensed under the [BSD 2-Clause license](https://github.com/ansible-collections/community.general/blob/stable-9/LICENSES/BSD-2-Clause.txt), the [MIT license](https://github.com/ansible-collections/community.general/blob/stable-9/LICENSES/MIT.txt), and the [PSF 2.0 license](https://github.com/ansible-collections/community.general/blob/stable-9/LICENSES/PSF-2.0.txt).
|
||||
|
||||
All files have a machine readable `SDPX-License-Identifier:` comment denoting its respective license(s) or an equivalent entry in an accompanying `.license` file. Only changelog fragments (which will not be part of a release) are covered by a blanket statement in `.reuse/dep5`. This conforms to the [REUSE specification](https://reuse.software/spec/).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,3 +14,8 @@ sections:
|
||||
- guide_online
|
||||
- guide_packet
|
||||
- guide_scaleway
|
||||
- title: Developer Guides
|
||||
toctree:
|
||||
- guide_deps
|
||||
- guide_vardict
|
||||
- guide_cmdrunner
|
||||
|
||||
61
docs/docsite/helper/keep_keys/README.md
Normal file
61
docs/docsite/helper/keep_keys/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
|
||||
# Docs helper. Create RST file.
|
||||
|
||||
The playbook `playbook.yml` writes a RST file that can be used in
|
||||
docs/docsite/rst. The usage of this helper is recommended but not
|
||||
mandatory. You can stop reading here and update the RST file manually
|
||||
if you don't want to use this helper.
|
||||
|
||||
## Run the playbook
|
||||
|
||||
If you want to generate the RST file by this helper fit the variables
|
||||
in the playbook and the template to your needs. Then, run the play
|
||||
|
||||
```sh
|
||||
shell> ansible-playbook playbook.yml
|
||||
```
|
||||
|
||||
## Copy RST to docs/docsite/rst
|
||||
|
||||
Copy the RST file to `docs/docsite/rst` and remove it from this
|
||||
directory.
|
||||
|
||||
## Update the checksums
|
||||
|
||||
Substitute the variables and run the below commands
|
||||
|
||||
```sh
|
||||
shell> sha1sum {{ target_vars }} > {{ target_sha1 }}
|
||||
shell> sha1sum {{ file_rst }} > {{ file_sha1 }}
|
||||
```
|
||||
|
||||
## Playbook explained
|
||||
|
||||
The playbook includes the variable *tests* from the integration tests
|
||||
and creates the RST file from the template. The playbook will
|
||||
terminate if:
|
||||
|
||||
* The file with the variable *tests* was changed
|
||||
* The RST file was changed
|
||||
|
||||
This means that this helper is probably not up to date.
|
||||
|
||||
### The file with the variable *tests* was changed
|
||||
|
||||
This means that somebody updated the integration tests. Review the
|
||||
changes and update the template if needed. Update the checksum to pass
|
||||
the integrity test. The playbook message provides you with the
|
||||
command.
|
||||
|
||||
### The RST file was changed
|
||||
|
||||
This means that somebody updated the RST file manually. Review the
|
||||
changes and update the template. Update the checksum to pass the
|
||||
integrity test. The playbook message provides you with the
|
||||
command. Make sure that the updated template will create identical RST
|
||||
file. Only then apply your changes.
|
||||
@@ -0,0 +1,80 @@
|
||||
..
|
||||
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
|
||||
|
||||
keep_keys
|
||||
"""""""""
|
||||
|
||||
Use the filter :ansplugin:`community.general.keep_keys#filter` if you have a list of dictionaries and want to keep certain keys only.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See :ansplugin:`the documentation for the community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
|
||||
Let us use the below list in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
input:
|
||||
{{ tests.0.input | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[0:1]|subelements('group') %}
|
||||
* {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1
|
||||
|
||||
target: {{ i.1.tt }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.0.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
.. versionadded:: 9.1.0
|
||||
|
||||
* The results of the below examples 1-5 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.1.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[1:2]|subelements('group') %}
|
||||
{{ loop.index }}. {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: {{ i.1.mp }}
|
||||
target: {{ i.1.tt }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
|
||||
* The results of the below examples 6-9 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.2.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[2:3]|subelements('group') %}
|
||||
{{ loop.index + 5 }}. {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: {{ i.1.mp }}
|
||||
target: {{ i.1.tt }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
1
docs/docsite/helper/keep_keys/keep_keys.rst.sha1
Normal file
1
docs/docsite/helper/keep_keys/keep_keys.rst.sha1
Normal file
@@ -0,0 +1 @@
|
||||
8690afce792abc95693c2f61f743ee27388b1592 ../../rst/filter_guide-abstract_informations-lists_of_dictionaries-keep_keys.rst
|
||||
3
docs/docsite/helper/keep_keys/keep_keys.rst.sha1.license
Normal file
3
docs/docsite/helper/keep_keys/keep_keys.rst.sha1.license
Normal file
@@ -0,0 +1,3 @@
|
||||
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
|
||||
79
docs/docsite/helper/keep_keys/playbook.yml
Normal file
79
docs/docsite/helper/keep_keys/playbook.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Create docs REST files
|
||||
# shell> ansible-playbook playbook.yml
|
||||
#
|
||||
# Proofread and copy created *.rst file into the directory
|
||||
# docs/docsite/rst. Do not add *.rst in this directory to the version
|
||||
# control.
|
||||
#
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# community.general/docs/docsite/helper/keep_keys/playbook.yml
|
||||
|
||||
- name: Create RST file for docs/docsite/rst
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
|
||||
plugin: keep_keys
|
||||
plugin_type: filter
|
||||
docs_path:
|
||||
- filter_guide
|
||||
- abstract_informations
|
||||
- lists_of_dictionaries
|
||||
|
||||
file_base: "{{ (docs_path + [plugin]) | join('-') }}"
|
||||
file_rst: ../../rst/{{ file_base }}.rst
|
||||
file_sha1: "{{ plugin }}.rst.sha1"
|
||||
|
||||
target: "../../../../tests/integration/targets/{{ plugin_type }}_{{ plugin }}"
|
||||
target_vars: "{{ target }}/vars/main/tests.yml"
|
||||
target_sha1: tests.yml.sha1
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Test integrity tests.yml
|
||||
when:
|
||||
- integrity | d(true) | bool
|
||||
- lookup('file', target_sha1) != lookup('pipe', 'sha1sum ' ~ target_vars)
|
||||
block:
|
||||
|
||||
- name: Changed tests.yml
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Changed {{ target_vars }}
|
||||
Review the changes and update {{ target_sha1 }}
|
||||
shell> sha1sum {{ target_vars }} > {{ target_sha1 }}
|
||||
|
||||
- name: Changed tests.yml end host
|
||||
ansible.builtin.meta: end_play
|
||||
|
||||
- name: Test integrity RST file
|
||||
when:
|
||||
- integrity | d(true) | bool
|
||||
- lookup('file', file_sha1) != lookup('pipe', 'sha1sum ' ~ file_rst)
|
||||
block:
|
||||
|
||||
- name: Changed RST file
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Changed {{ file_rst }}
|
||||
Review the changes and update {{ file_sha1 }}
|
||||
shell> sha1sum {{ file_rst }} > {{ file_sha1 }}
|
||||
|
||||
- name: Changed RST file end host
|
||||
ansible.builtin.meta: end_play
|
||||
|
||||
- name: Include target vars
|
||||
include_vars:
|
||||
file: "{{ target_vars }}"
|
||||
|
||||
- name: Create RST file
|
||||
ansible.builtin.template:
|
||||
src: "{{ file_base }}.rst.j2"
|
||||
dest: "{{ file_base }}.rst"
|
||||
1
docs/docsite/helper/keep_keys/tests.yml.sha1
Normal file
1
docs/docsite/helper/keep_keys/tests.yml.sha1
Normal file
@@ -0,0 +1 @@
|
||||
c6fc4ee2017d9222675bcd13cc4f88ba8d14f38d ../../../../tests/integration/targets/filter_keep_keys/vars/main/tests.yml
|
||||
3
docs/docsite/helper/keep_keys/tests.yml.sha1.license
Normal file
3
docs/docsite/helper/keep_keys/tests.yml.sha1.license
Normal file
@@ -0,0 +1,3 @@
|
||||
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
|
||||
61
docs/docsite/helper/remove_keys/README.md
Normal file
61
docs/docsite/helper/remove_keys/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
|
||||
# Docs helper. Create RST file.
|
||||
|
||||
The playbook `playbook.yml` writes a RST file that can be used in
|
||||
docs/docsite/rst. The usage of this helper is recommended but not
|
||||
mandatory. You can stop reading here and update the RST file manually
|
||||
if you don't want to use this helper.
|
||||
|
||||
## Run the playbook
|
||||
|
||||
If you want to generate the RST file by this helper fit the variables
|
||||
in the playbook and the template to your needs. Then, run the play
|
||||
|
||||
```sh
|
||||
shell> ansible-playbook playbook.yml
|
||||
```
|
||||
|
||||
## Copy RST to docs/docsite/rst
|
||||
|
||||
Copy the RST file to `docs/docsite/rst` and remove it from this
|
||||
directory.
|
||||
|
||||
## Update the checksums
|
||||
|
||||
Substitute the variables and run the below commands
|
||||
|
||||
```sh
|
||||
shell> sha1sum {{ target_vars }} > {{ target_sha1 }}
|
||||
shell> sha1sum {{ file_rst }} > {{ file_sha1 }}
|
||||
```
|
||||
|
||||
## Playbook explained
|
||||
|
||||
The playbook includes the variable *tests* from the integration tests
|
||||
and creates the RST file from the template. The playbook will
|
||||
terminate if:
|
||||
|
||||
* The file with the variable *tests* was changed
|
||||
* The RST file was changed
|
||||
|
||||
This means that this helper is probably not up to date.
|
||||
|
||||
### The file with the variable *tests* was changed
|
||||
|
||||
This means that somebody updated the integration tests. Review the
|
||||
changes and update the template if needed. Update the checksum to pass
|
||||
the integrity test. The playbook message provides you with the
|
||||
command.
|
||||
|
||||
### The RST file was changed
|
||||
|
||||
This means that somebody updated the RST file manually. Review the
|
||||
changes and update the template. Update the checksum to pass the
|
||||
integrity test. The playbook message provides you with the
|
||||
command. Make sure that the updated template will create identical RST
|
||||
file. Only then apply your changes.
|
||||
@@ -0,0 +1,80 @@
|
||||
..
|
||||
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
|
||||
|
||||
remove_keys
|
||||
"""""""""""
|
||||
|
||||
Use the filter :ansplugin:`community.general.remove_keys#filter` if you have a list of dictionaries and want to remove certain keys.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See See :ansplugin:`the documentation for the community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
|
||||
Let us use the below list in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
input:
|
||||
{{ tests.0.input | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[0:1]|subelements('group') %}
|
||||
* {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1
|
||||
|
||||
target: {{ i.1.tt }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.0.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
.. versionadded:: 9.1.0
|
||||
|
||||
* The results of the below examples 1-5 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.1.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[1:2]|subelements('group') %}
|
||||
{{ loop.index }}. {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: {{ i.1.mp }}
|
||||
target: {{ i.1.tt }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
|
||||
* The results of the below examples 6-9 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.2.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[2:3]|subelements('group') %}
|
||||
{{ loop.index + 5 }}. {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: {{ i.1.mp }}
|
||||
target: {{ i.1.tt }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
79
docs/docsite/helper/remove_keys/playbook.yml
Normal file
79
docs/docsite/helper/remove_keys/playbook.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Create docs REST files
|
||||
# shell> ansible-playbook playbook.yml
|
||||
#
|
||||
# Proofread and copy created *.rst file into the directory
|
||||
# docs/docsite/rst. Do not add *.rst in this directory to the version
|
||||
# control.
|
||||
#
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# community.general/docs/docsite/helper/remove_keys/playbook.yml
|
||||
|
||||
- name: Create RST file for docs/docsite/rst
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
|
||||
plugin: remove_keys
|
||||
plugin_type: filter
|
||||
docs_path:
|
||||
- filter_guide
|
||||
- abstract_informations
|
||||
- lists_of_dictionaries
|
||||
|
||||
file_base: "{{ (docs_path + [plugin]) | join('-') }}"
|
||||
file_rst: ../../rst/{{ file_base }}.rst
|
||||
file_sha1: "{{ plugin }}.rst.sha1"
|
||||
|
||||
target: "../../../../tests/integration/targets/{{ plugin_type }}_{{ plugin }}"
|
||||
target_vars: "{{ target }}/vars/main/tests.yml"
|
||||
target_sha1: tests.yml.sha1
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Test integrity tests.yml
|
||||
when:
|
||||
- integrity | d(true) | bool
|
||||
- lookup('file', target_sha1) != lookup('pipe', 'sha1sum ' ~ target_vars)
|
||||
block:
|
||||
|
||||
- name: Changed tests.yml
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Changed {{ target_vars }}
|
||||
Review the changes and update {{ target_sha1 }}
|
||||
shell> sha1sum {{ target_vars }} > {{ target_sha1 }}
|
||||
|
||||
- name: Changed tests.yml end host
|
||||
ansible.builtin.meta: end_play
|
||||
|
||||
- name: Test integrity RST file
|
||||
when:
|
||||
- integrity | d(true) | bool
|
||||
- lookup('file', file_sha1) != lookup('pipe', 'sha1sum ' ~ file_rst)
|
||||
block:
|
||||
|
||||
- name: Changed RST file
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Changed {{ file_rst }}
|
||||
Review the changes and update {{ file_sha1 }}
|
||||
shell> sha1sum {{ file_rst }} > {{ file_sha1 }}
|
||||
|
||||
- name: Changed RST file end host
|
||||
ansible.builtin.meta: end_play
|
||||
|
||||
- name: Include target vars
|
||||
include_vars:
|
||||
file: "{{ target_vars }}"
|
||||
|
||||
- name: Create RST file
|
||||
ansible.builtin.template:
|
||||
src: "{{ file_base }}.rst.j2"
|
||||
dest: "{{ file_base }}.rst"
|
||||
1
docs/docsite/helper/remove_keys/remove_keys.rst.sha1
Normal file
1
docs/docsite/helper/remove_keys/remove_keys.rst.sha1
Normal file
@@ -0,0 +1 @@
|
||||
3cc606b42e3d450cf6323f25930f7c5a591fa086 ../../rst/filter_guide-abstract_informations-lists_of_dictionaries-remove_keys.rst
|
||||
@@ -0,0 +1,3 @@
|
||||
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
|
||||
1
docs/docsite/helper/remove_keys/tests.yml.sha1
Normal file
1
docs/docsite/helper/remove_keys/tests.yml.sha1
Normal file
@@ -0,0 +1 @@
|
||||
0554335045f02d8c37b824355b0cf86864cee9a5 ../../../../tests/integration/targets/filter_remove_keys/vars/main/tests.yml
|
||||
3
docs/docsite/helper/remove_keys/tests.yml.sha1.license
Normal file
3
docs/docsite/helper/remove_keys/tests.yml.sha1.license
Normal file
@@ -0,0 +1,3 @@
|
||||
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
|
||||
61
docs/docsite/helper/replace_keys/README.md
Normal file
61
docs/docsite/helper/replace_keys/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
|
||||
# Docs helper. Create RST file.
|
||||
|
||||
The playbook `playbook.yml` writes a RST file that can be used in
|
||||
docs/docsite/rst. The usage of this helper is recommended but not
|
||||
mandatory. You can stop reading here and update the RST file manually
|
||||
if you don't want to use this helper.
|
||||
|
||||
## Run the playbook
|
||||
|
||||
If you want to generate the RST file by this helper fit the variables
|
||||
in the playbook and the template to your needs. Then, run the play
|
||||
|
||||
```sh
|
||||
shell> ansible-playbook playbook.yml
|
||||
```
|
||||
|
||||
## Copy RST to docs/docsite/rst
|
||||
|
||||
Copy the RST file to `docs/docsite/rst` and remove it from this
|
||||
directory.
|
||||
|
||||
## Update the checksums
|
||||
|
||||
Substitute the variables and run the below commands
|
||||
|
||||
```sh
|
||||
shell> sha1sum {{ target_vars }} > {{ target_sha1 }}
|
||||
shell> sha1sum {{ file_rst }} > {{ file_sha1 }}
|
||||
```
|
||||
|
||||
## Playbook explained
|
||||
|
||||
The playbook includes the variable *tests* from the integration tests
|
||||
and creates the RST file from the template. The playbook will
|
||||
terminate if:
|
||||
|
||||
* The file with the variable *tests* was changed
|
||||
* The RST file was changed
|
||||
|
||||
This means that this helper is probably not up to date.
|
||||
|
||||
### The file with the variable *tests* was changed
|
||||
|
||||
This means that somebody updated the integration tests. Review the
|
||||
changes and update the template if needed. Update the checksum to pass
|
||||
the integrity test. The playbook message provides you with the
|
||||
command.
|
||||
|
||||
### The RST file was changed
|
||||
|
||||
This means that somebody updated the RST file manually. Review the
|
||||
changes and update the template. Update the checksum to pass the
|
||||
integrity test. The playbook message provides you with the
|
||||
command. Make sure that the updated template will create identical RST
|
||||
file. Only then apply your changes.
|
||||
@@ -0,0 +1,110 @@
|
||||
..
|
||||
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
|
||||
|
||||
replace_keys
|
||||
""""""""""""
|
||||
|
||||
Use the filter :ansplugin:`community.general.replace_keys#filter` if you have a list of dictionaries and want to replace certain keys.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See :ansplugin:`the documentation for the community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
|
||||
Let us use the below list in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
input:
|
||||
{{ tests.0.input | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[0:1]|subelements('group') %}
|
||||
* {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-3
|
||||
|
||||
target:
|
||||
{{ i.1.tt | to_yaml(indent=2) | indent(5) }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.0.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
.. versionadded:: 9.1.0
|
||||
|
||||
* The results of the below examples 1-3 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.1.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[1:2]|subelements('group') %}
|
||||
{{ loop.index }}. {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-4
|
||||
|
||||
mp: {{ i.1.mp }}
|
||||
target:
|
||||
{{ i.1.tt | to_yaml(indent=2) | indent(5) }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
|
||||
* The results of the below examples 4-5 are the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ tests.2.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% for i in tests[2:3]|subelements('group') %}
|
||||
{{ loop.index + 3 }}. {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-3
|
||||
|
||||
mp: {{ i.1.mp }}
|
||||
target:
|
||||
{{ i.1.tt | to_yaml(indent=2) | indent(5) }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% for i in tests[3:4]|subelements('group') %}
|
||||
{{ loop.index + 5 }}. {{ i.1.d }}
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
input:
|
||||
{{ i.0.input | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-4
|
||||
|
||||
mp: {{ i.1.mp }}
|
||||
target:
|
||||
{{ i.1.tt | to_yaml(indent=2) | indent(5) }}
|
||||
result: "{{ lookup('file', target ~ '/templates/' ~ i.0.template) }}"
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
{{ i.0.result | to_yaml(indent=2) | indent(5) }}
|
||||
|
||||
{% endfor %}
|
||||
79
docs/docsite/helper/replace_keys/playbook.yml
Normal file
79
docs/docsite/helper/replace_keys/playbook.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# Create docs REST files
|
||||
# shell> ansible-playbook playbook.yml
|
||||
#
|
||||
# Proofread and copy created *.rst file into the directory
|
||||
# docs/docsite/rst. Do not add *.rst in this directory to the version
|
||||
# control.
|
||||
#
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# community.general/docs/docsite/helper/replace_keys/playbook.yml
|
||||
|
||||
- name: Create RST file for docs/docsite/rst
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
|
||||
plugin: replace_keys
|
||||
plugin_type: filter
|
||||
docs_path:
|
||||
- filter_guide
|
||||
- abstract_informations
|
||||
- lists_of_dictionaries
|
||||
|
||||
file_base: "{{ (docs_path + [plugin]) | join('-') }}"
|
||||
file_rst: ../../rst/{{ file_base }}.rst
|
||||
file_sha1: "{{ plugin }}.rst.sha1"
|
||||
|
||||
target: "../../../../tests/integration/targets/{{ plugin_type }}_{{ plugin }}"
|
||||
target_vars: "{{ target }}/vars/main/tests.yml"
|
||||
target_sha1: tests.yml.sha1
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Test integrity tests.yml
|
||||
when:
|
||||
- integrity | d(true) | bool
|
||||
- lookup('file', target_sha1) != lookup('pipe', 'sha1sum ' ~ target_vars)
|
||||
block:
|
||||
|
||||
- name: Changed tests.yml
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Changed {{ target_vars }}
|
||||
Review the changes and update {{ target_sha1 }}
|
||||
shell> sha1sum {{ target_vars }} > {{ target_sha1 }}
|
||||
|
||||
- name: Changed tests.yml end host
|
||||
ansible.builtin.meta: end_play
|
||||
|
||||
- name: Test integrity RST file
|
||||
when:
|
||||
- integrity | d(true) | bool
|
||||
- lookup('file', file_sha1) != lookup('pipe', 'sha1sum ' ~ file_rst)
|
||||
block:
|
||||
|
||||
- name: Changed RST file
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Changed {{ file_rst }}
|
||||
Review the changes and update {{ file_sha1 }}
|
||||
shell> sha1sum {{ file_rst }} > {{ file_sha1 }}
|
||||
|
||||
- name: Changed RST file end host
|
||||
ansible.builtin.meta: end_play
|
||||
|
||||
- name: Include target vars
|
||||
include_vars:
|
||||
file: "{{ target_vars }}"
|
||||
|
||||
- name: Create RST file
|
||||
ansible.builtin.template:
|
||||
src: "{{ file_base }}.rst.j2"
|
||||
dest: "{{ file_base }}.rst"
|
||||
1
docs/docsite/helper/replace_keys/replace_keys.rst.sha1
Normal file
1
docs/docsite/helper/replace_keys/replace_keys.rst.sha1
Normal file
@@ -0,0 +1 @@
|
||||
403f23c02ac02b1c3b611cb14f9b3ba59dc3f587 ../../rst/filter_guide-abstract_informations-lists_of_dictionaries-replace_keys.rst
|
||||
@@ -0,0 +1,3 @@
|
||||
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
|
||||
1
docs/docsite/helper/replace_keys/tests.yml.sha1
Normal file
1
docs/docsite/helper/replace_keys/tests.yml.sha1
Normal file
@@ -0,0 +1 @@
|
||||
2e54f3528c95cca746d5748f1ed7ada56ad0890e ../../../../tests/integration/targets/filter_replace_keys/vars/main/tests.yml
|
||||
3
docs/docsite/helper/replace_keys/tests.yml.sha1.license
Normal file
3
docs/docsite/helper/replace_keys/tests.yml.sha1.license
Normal file
@@ -0,0 +1,3 @@
|
||||
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
|
||||
@@ -9,8 +9,6 @@ edit_on_github:
|
||||
path_prefix: ''
|
||||
|
||||
extra_links:
|
||||
- description: Ask for help
|
||||
url: https://forum.ansible.com/c/help/6/none
|
||||
- description: Submit a bug report
|
||||
url: https://github.com/ansible-collections/community.general/issues/new?assignees=&labels=&template=bug_report.yml
|
||||
- description: Request a feature
|
||||
@@ -24,10 +22,10 @@ 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"
|
||||
- topic: Ansible Forum
|
||||
# The following URL directly points to the "Get Help" section
|
||||
url: https://forum.ansible.com/c/help/6/none
|
||||
- topic: "Ansible Forum: Discussions about the collection itself, not for specific modules or plugins"
|
||||
# The following URL directly points to the "community-general" tag
|
||||
url: https://forum.ansible.com/tag/community-general
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
..
|
||||
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
|
||||
|
||||
keep_keys
|
||||
"""""""""
|
||||
|
||||
Use the filter :ansplugin:`community.general.keep_keys#filter` if you have a list of dictionaries and want to keep certain keys only.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See :ansplugin:`the documentation for the community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
|
||||
Let us use the below list in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
input:
|
||||
- k0_x0: A0
|
||||
k1_x1: B0
|
||||
k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- k0_x0: A1
|
||||
k1_x1: B1
|
||||
k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
* By default, match keys that equal any of the items in the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1
|
||||
|
||||
target: ['k0_x0', 'k1_x1']
|
||||
result: "{{ input | community.general.keep_keys(target=target) }}"
|
||||
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- {k0_x0: A0, k1_x1: B0}
|
||||
- {k0_x0: A1, k1_x1: B1}
|
||||
|
||||
|
||||
.. versionadded:: 9.1.0
|
||||
|
||||
* The results of the below examples 1-5 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- {k0_x0: A0, k1_x1: B0}
|
||||
- {k0_x0: A1, k1_x1: B1}
|
||||
|
||||
|
||||
1. Match keys that equal any of the items in the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: equal
|
||||
target: ['k0_x0', 'k1_x1']
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
2. Match keys that start with any of the items in the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: starts_with
|
||||
target: ['k0', 'k1']
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
3. Match keys that end with any of the items in target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: ends_with
|
||||
target: ['x0', 'x1']
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
4. Match keys by the regex.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: regex
|
||||
target: ['^.*[01]_x.*$']
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
5. Match keys by the regex.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: regex
|
||||
target: ^.*[01]_x.*$
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
|
||||
* The results of the below examples 6-9 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- {k0_x0: A0}
|
||||
- {k0_x0: A1}
|
||||
|
||||
|
||||
6. Match keys that equal the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: equal
|
||||
target: k0_x0
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
7. Match keys that start with the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: starts_with
|
||||
target: k0
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
8. Match keys that end with the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: ends_with
|
||||
target: x0
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
9. Match keys by the regex.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: regex
|
||||
target: ^.*0_x.*$
|
||||
result: "{{ input | community.general.keep_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
..
|
||||
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
|
||||
|
||||
remove_keys
|
||||
"""""""""""
|
||||
|
||||
Use the filter :ansplugin:`community.general.remove_keys#filter` if you have a list of dictionaries and want to remove certain keys.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See See :ansplugin:`the documentation for the community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
|
||||
Let us use the below list in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
input:
|
||||
- k0_x0: A0
|
||||
k1_x1: B0
|
||||
k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- k0_x0: A1
|
||||
k1_x1: B1
|
||||
k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
* By default, match keys that equal any of the items in the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1
|
||||
|
||||
target: ['k0_x0', 'k1_x1']
|
||||
result: "{{ input | community.general.remove_keys(target=target) }}"
|
||||
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
.. versionadded:: 9.1.0
|
||||
|
||||
* The results of the below examples 1-5 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
1. Match keys that equal any of the items in the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: equal
|
||||
target: ['k0_x0', 'k1_x1']
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
2. Match keys that start with any of the items in the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: starts_with
|
||||
target: ['k0', 'k1']
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
3. Match keys that end with any of the items in target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: ends_with
|
||||
target: ['x0', 'x1']
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
4. Match keys by the regex.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: regex
|
||||
target: ['^.*[01]_x.*$']
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
5. Match keys by the regex.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: regex
|
||||
target: ^.*[01]_x.*$
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
|
||||
* The results of the below examples 6-9 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- k1_x1: B0
|
||||
k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- k1_x1: B1
|
||||
k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
6. Match keys that equal the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: equal
|
||||
target: k0_x0
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
7. Match keys that start with the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: starts_with
|
||||
target: k0
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
8. Match keys that end with the target.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: ends_with
|
||||
target: x0
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
9. Match keys by the regex.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1,2
|
||||
|
||||
mp: regex
|
||||
target: ^.*0_x.*$
|
||||
result: "{{ input | community.general.remove_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
..
|
||||
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
|
||||
|
||||
replace_keys
|
||||
""""""""""""
|
||||
|
||||
Use the filter :ansplugin:`community.general.replace_keys#filter` if you have a list of dictionaries and want to replace certain keys.
|
||||
|
||||
.. note:: The output of the examples in this section use the YAML callback plugin. Quoting: "Ansible output that can be quite a bit easier to read than the default JSON formatting." See :ansplugin:`the documentation for the community.general.yaml callback plugin <community.general.yaml#callback>`.
|
||||
|
||||
|
||||
Let us use the below list in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
input:
|
||||
- k0_x0: A0
|
||||
k1_x1: B0
|
||||
k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- k0_x0: A1
|
||||
k1_x1: B1
|
||||
k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
* By default, match keys that equal any of the attributes before.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-3
|
||||
|
||||
target:
|
||||
- {after: a0, before: k0_x0}
|
||||
- {after: a1, before: k1_x1}
|
||||
|
||||
result: "{{ input | community.general.replace_keys(target=target) }}"
|
||||
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- a0: A0
|
||||
a1: B0
|
||||
k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- a0: A1
|
||||
a1: B1
|
||||
k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
.. versionadded:: 9.1.0
|
||||
|
||||
* The results of the below examples 1-3 are all the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- a0: A0
|
||||
a1: B0
|
||||
k2_x2: [C0]
|
||||
k3_x3: foo
|
||||
- a0: A1
|
||||
a1: B1
|
||||
k2_x2: [C1]
|
||||
k3_x3: bar
|
||||
|
||||
|
||||
1. Replace keys that starts with any of the attributes before.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-4
|
||||
|
||||
mp: starts_with
|
||||
target:
|
||||
- {after: a0, before: k0}
|
||||
- {after: a1, before: k1}
|
||||
|
||||
result: "{{ input | community.general.replace_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
2. Replace keys that ends with any of the attributes before.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-4
|
||||
|
||||
mp: ends_with
|
||||
target:
|
||||
- {after: a0, before: x0}
|
||||
- {after: a1, before: x1}
|
||||
|
||||
result: "{{ input | community.general.replace_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
3. Replace keys that match any regex of the attributes before.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-4
|
||||
|
||||
mp: regex
|
||||
target:
|
||||
- {after: a0, before: ^.*0_x.*$}
|
||||
- {after: a1, before: ^.*1_x.*$}
|
||||
|
||||
result: "{{ input | community.general.replace_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
|
||||
* The results of the below examples 4-5 are the same:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- {X: foo}
|
||||
- {X: bar}
|
||||
|
||||
|
||||
4. If more keys match the same attribute before the last one will be used.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-3
|
||||
|
||||
mp: regex
|
||||
target:
|
||||
- {after: X, before: ^.*_x.*$}
|
||||
|
||||
result: "{{ input | community.general.replace_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
5. If there are items with equal attribute before the first one will be used.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-3
|
||||
|
||||
mp: regex
|
||||
target:
|
||||
- {after: X, before: ^.*_x.*$}
|
||||
- {after: Y, before: ^.*_x.*$}
|
||||
|
||||
result: "{{ input | community.general.replace_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
|
||||
6. If there are more matches for a key the first one will be used.
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
input:
|
||||
- {aaa1: A, bbb1: B, ccc1: C}
|
||||
- {aaa2: D, bbb2: E, ccc2: F}
|
||||
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
:emphasize-lines: 1-4
|
||||
|
||||
mp: starts_with
|
||||
target:
|
||||
- {after: X, before: a}
|
||||
- {after: Y, before: aa}
|
||||
|
||||
result: "{{ input | community.general.replace_keys(target=target, matching_parameter=mp) }}"
|
||||
|
||||
gives
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 1-
|
||||
|
||||
result:
|
||||
- {X: A, bbb1: B, ccc1: C}
|
||||
- {X: D, bbb2: E, ccc2: F}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
..
|
||||
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.filter_guide.filter_guide_abstract_informations.lists_of_dicts:
|
||||
|
||||
Lists of dictionaries
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Filters to manage keys in a list of dictionaries:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
filter_guide-abstract_informations-lists_of_dictionaries-keep_keys
|
||||
filter_guide-abstract_informations-lists_of_dictionaries-remove_keys
|
||||
filter_guide-abstract_informations-lists_of_dictionaries-replace_keys
|
||||
@@ -11,6 +11,7 @@ Abstract transformations
|
||||
|
||||
filter_guide_abstract_informations_dictionaries
|
||||
filter_guide_abstract_informations_grouping
|
||||
filter_guide-abstract_informations-lists_of_dictionaries
|
||||
filter_guide_abstract_informations_merging_lists_of_dictionaries
|
||||
filter_guide_abstract_informations_lists_helper
|
||||
filter_guide_abstract_informations_counting_elements_in_sequence
|
||||
|
||||
463
docs/docsite/rst/guide_cmdrunner.rst
Normal file
463
docs/docsite/rst/guide_cmdrunner.rst
Normal file
@@ -0,0 +1,463 @@
|
||||
..
|
||||
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_cmdrunner:
|
||||
|
||||
|
||||
Command Runner guide
|
||||
====================
|
||||
|
||||
|
||||
Introduction
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The ``ansible_collections.community.general.plugins.module_utils.cmd_runner`` module util provides the
|
||||
``CmdRunner`` class to help execute external commands. The class is a wrapper around
|
||||
the standard ``AnsibleModule.run_command()`` method, handling command arguments, localization setting,
|
||||
output processing output, check mode, and other features.
|
||||
|
||||
It is even more useful when one command is used in multiple modules, so that you can define all options
|
||||
in a module util file, and each module uses the same runner with different arguments.
|
||||
|
||||
For the sake of clarity, throughout this guide, unless otherwise specified, we use the term *option* when referring to
|
||||
Ansible module options, and the term *argument* when referring to the command line arguments for the external command.
|
||||
|
||||
|
||||
Quickstart
|
||||
""""""""""
|
||||
|
||||
``CmdRunner`` defines a command and a set of coded instructions on how to format
|
||||
the command-line arguments, in which specific order, for a particular execution.
|
||||
It relies on ``ansible.module_utils.basic.AnsibleModule.run_command()`` to actually execute the command.
|
||||
There are other features, see more details throughout this document.
|
||||
|
||||
To use ``CmdRunner`` you must start by creating an object. The example below is a simplified
|
||||
version of the actual code in :ansplugin:`community.general.ansible_galaxy_install#module`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
|
||||
runner = CmdRunner(
|
||||
module,
|
||||
command="ansible-galaxy",
|
||||
arg_formats=dict(
|
||||
type=cmd_runner_fmt.as_func(lambda v: [] if v == 'both' else [v]),
|
||||
galaxy_cmd=cmd_runner_fmt.as_list(),
|
||||
upgrade=cmd_runner_fmt.as_bool("--upgrade"),
|
||||
requirements_file=cmd_runner_fmt.as_opt_val('-r'),
|
||||
dest=cmd_runner_fmt.as_opt_val('-p'),
|
||||
force=cmd_runner_fmt.as_bool("--force"),
|
||||
no_deps=cmd_runner_fmt.as_bool("--no-deps"),
|
||||
version=cmd_runner_fmt.as_fixed("--version"),
|
||||
name=cmd_runner_fmt.as_list(),
|
||||
)
|
||||
)
|
||||
|
||||
This is meant to be done once, then every time you need to execute the command you create a context and pass values as needed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Run the command with these arguments, when values exist for them
|
||||
with runner("type galaxy_cmd upgrade force no_deps dest requirements_file name", output_process=process) as ctx:
|
||||
ctx.run(galaxy_cmd="install", upgrade=upgrade)
|
||||
|
||||
# version is fixed, requires no value
|
||||
with runner("version") 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.
|
||||
|
||||
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.
|
||||
|
||||
That generates a resulting command line similar to (example taken from the
|
||||
output of an integration test):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
"<venv>/bin/ansible-galaxy",
|
||||
"collection",
|
||||
"install",
|
||||
"--upgrade",
|
||||
"-p",
|
||||
"<collection-install-path>",
|
||||
"netbox.netbox",
|
||||
]
|
||||
|
||||
|
||||
Argument formats
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
As seen in the example, ``CmdRunner`` expects a parameter named ``arg_formats``
|
||||
defining how to format each CLI named argument.
|
||||
An "argument format" is nothing but a function to transform the value of a variable
|
||||
into something formatted for the command line.
|
||||
|
||||
|
||||
Argument format function
|
||||
""""""""""""""""""""""""
|
||||
|
||||
An ``arg_format`` function should be of the form:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def func(value):
|
||||
return ["--some-param-name", value]
|
||||
|
||||
The parameter ``value`` can be of any type - although there are convenience
|
||||
mechanisms to help handling sequence and mapping objects.
|
||||
|
||||
The result is expected to be of the type ``Sequence[str]`` type (most commonly
|
||||
``list[str]`` or ``tuple[str]``), otherwise it is considered to be a ``str``,
|
||||
and it is coerced into ``list[str]``.
|
||||
This resulting sequence of strings is added to the command line when that
|
||||
argument is actually used.
|
||||
|
||||
For example, if ``func`` returns:
|
||||
|
||||
- ``["nee", 2, "shruberries"]``, the command line adds arguments ``"nee" "2" "shruberries"``.
|
||||
- ``2 == 2``, the command line adds argument ``True``.
|
||||
- ``None``, the command line adds argument ``None``.
|
||||
- ``[]``, the command line adds no command line argument for that particular argument.
|
||||
|
||||
|
||||
Convenience format methods
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
In the same module as ``CmdRunner`` there is a class ``cmd_runner_fmt`` which
|
||||
provides a set of convenience methods that return format functions for common cases.
|
||||
In the first block of code in the `Quickstart`_ section you can see the importing of
|
||||
that class:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
|
||||
The same example shows how to make use of some of them in the instantiation of the ``CmdRunner`` object.
|
||||
A description of each one of the convenience methods available and examples of how to use them is found below.
|
||||
In these descriptions ``value`` refers to the single parameter passed to the formatting function.
|
||||
|
||||
- ``cmd_runner_fmt.as_list()``
|
||||
This method does not receive any parameter, function returns ``value`` as-is.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_list()``
|
||||
- Example:
|
||||
+----------------------+---------------------+
|
||||
| Value | Outcome |
|
||||
+======================+=====================+
|
||||
| ``["foo", "bar"]`` | ``["foo", "bar"]`` |
|
||||
+----------------------+---------------------+
|
||||
| ``"foobar"`` | ``["foobar"]`` |
|
||||
+----------------------+---------------------+
|
||||
|
||||
- ``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.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_bool("--force")``
|
||||
- Example:
|
||||
+------------+--------------------+
|
||||
| Value | Outcome |
|
||||
+============+====================+
|
||||
| ``True`` | ``["--force"]`` |
|
||||
+------------+--------------------+
|
||||
| ``False`` | ``[]`` |
|
||||
+------------+--------------------+
|
||||
|
||||
- ``cmd_runner_fmt.as_bool_not()``
|
||||
This method receives one parameter, which is returned by the function when the boolean evaluation
|
||||
of ``value`` is ``False``.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_bool_not("--no-deps")``
|
||||
- Example:
|
||||
+-------------+---------------------+
|
||||
| Value | Outcome |
|
||||
+=============+=====================+
|
||||
| ``True`` | ``[]`` |
|
||||
+-------------+---------------------+
|
||||
| ``False`` | ``["--no-deps"]`` |
|
||||
+-------------+---------------------+
|
||||
|
||||
- ``cmd_runner_fmt.as_optval()``
|
||||
This method receives one parameter ``arg``, the function returns the string concatenation
|
||||
of ``arg`` and ``value``.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_optval("-i")``
|
||||
- Example:
|
||||
+---------------+---------------------+
|
||||
| Value | Outcome |
|
||||
+===============+=====================+
|
||||
| ``3`` | ``["-i3"]`` |
|
||||
+---------------+---------------------+
|
||||
| ``foobar`` | ``["-ifoobar"]`` |
|
||||
+---------------+---------------------+
|
||||
|
||||
- ``cmd_runner_fmt.as_opt_val()``
|
||||
This method receives one parameter ``arg``, the function returns ``[arg, value]``.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_opt_val("--name")``
|
||||
- Example:
|
||||
+--------------+--------------------------+
|
||||
| Value | Outcome |
|
||||
+==============+==========================+
|
||||
| ``abc`` | ``["--name", "abc"]`` |
|
||||
+--------------+--------------------------+
|
||||
|
||||
- ``cmd_runner_fmt.as_opt_eq_val()``
|
||||
This method receives one parameter ``arg``, the function returns the string of the form
|
||||
``{arg}={value}``.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_opt_eq_val("--num-cpus")``
|
||||
- Example:
|
||||
+------------+-------------------------+
|
||||
| Value | Outcome |
|
||||
+============+=========================+
|
||||
| ``10`` | ``["--num-cpus=10"]`` |
|
||||
+------------+-------------------------+
|
||||
|
||||
- ``cmd_runner_fmt.as_fixed()``
|
||||
This method receives one parameter ``arg``, the function expects no ``value`` - if one
|
||||
is provided then it is ignored.
|
||||
The function returns ``arg`` as-is.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_fixed("--version")``
|
||||
- Example:
|
||||
+---------+-----------------------+
|
||||
| Value | Outcome |
|
||||
+=========+=======================+
|
||||
| | ``["--version"]`` |
|
||||
+---------+-----------------------+
|
||||
| 57 | ``["--version"]`` |
|
||||
+---------+-----------------------+
|
||||
|
||||
- Note:
|
||||
This is the only special case in which a value can be missing for the formatting function.
|
||||
The example also comes from the code in `Quickstart`_.
|
||||
In that case, the module has code to determine the command's version so that it can assert compatibility.
|
||||
There is no *value* to be passed for that CLI argument.
|
||||
|
||||
- ``cmd_runner_fmt.as_map()``
|
||||
This method receives one parameter ``arg`` which must be a dictionary, and an optional parameter ``default``.
|
||||
The function returns the evaluation of ``arg[value]``.
|
||||
If ``value not in arg``, then it returns ``default`` if defined, otherwise ``[]``.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_map(dict(a=1, b=2, c=3), default=42)``
|
||||
- Example:
|
||||
+---------------------+---------------+
|
||||
| Value | Outcome |
|
||||
+=====================+===============+
|
||||
| ``"b"`` | ``["2"]`` |
|
||||
+---------------------+---------------+
|
||||
| ``"yabadabadoo"`` | ``["42"]`` |
|
||||
+---------------------+---------------+
|
||||
|
||||
- Note:
|
||||
If ``default`` is not specified, invalid values return an empty list, meaning they are silently ignored.
|
||||
|
||||
- ``cmd_runner_fmt.as_func()``
|
||||
This method receives one parameter ``arg`` which is itself is a format function and it must abide by the rules described above.
|
||||
|
||||
- Creation:
|
||||
``cmd_runner_fmt.as_func(lambda v: [] if v == 'stable' else ['--channel', '{0}'.format(v)])``
|
||||
- Note:
|
||||
The outcome for that depends entirely on the function provided by the developer.
|
||||
|
||||
|
||||
Other features for argument formatting
|
||||
""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Some additional features are available as decorators:
|
||||
|
||||
- ``cmd_runner_fmt.unpack args()``
|
||||
This decorator unpacks the incoming ``value`` as a list of elements.
|
||||
|
||||
For example, in ``ansible_collections.community.general.plugins.module_utils.puppet``, it is used as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@cmd_runner_fmt.unpack_args
|
||||
def execute_func(execute, manifest):
|
||||
if execute:
|
||||
return ["--execute", execute]
|
||||
else:
|
||||
return [manifest]
|
||||
|
||||
runner = CmdRunner(
|
||||
module,
|
||||
command=_prepare_base_cmd(),
|
||||
path_prefix=_PUPPET_PATH_PREFIX,
|
||||
arg_formats=dict(
|
||||
# ...
|
||||
_execute=cmd_runner_fmt.as_func(execute_func),
|
||||
# ...
|
||||
),
|
||||
)
|
||||
|
||||
Then, in :ansplugin:`community.general.puppet#module` it is put to use with:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with runner(args_order) as ctx:
|
||||
rc, stdout, stderr = ctx.run(_execute=[p['execute'], p['manifest']])
|
||||
|
||||
- ``cmd_runner_fmt.unpack_kwargs()``
|
||||
Conversely, this decorator unpacks the incoming ``value`` as a ``dict``-like object.
|
||||
|
||||
- ``cmd_runner_fmt.stack()``
|
||||
This decorator assumes ``value`` is a sequence and concatenates the output
|
||||
of the wrapped function applied to each element of the sequence.
|
||||
|
||||
For example, in :ansplugin:`community.general.django_check#module`, the argument format for ``database``
|
||||
is defined as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
arg_formats = dict(
|
||||
# ...
|
||||
database=cmd_runner_fmt.stack(cmd_runner_fmt.as_opt_val)("--database"),
|
||||
# ...
|
||||
)
|
||||
|
||||
When receiving a list ``["abc", "def"]``, the output is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
["--database", "abc", "--database", "def"]
|
||||
|
||||
|
||||
Command Runner
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Settings that can be passed to the ``CmdRunner`` constructor are:
|
||||
|
||||
- ``module: AnsibleModule``
|
||||
Module instance. Mandatory parameter.
|
||||
- ``command: str | list[str]``
|
||||
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.
|
||||
- ``arg_formats: dict``
|
||||
Mapping of argument names to formatting functions.
|
||||
- ``default_args_order: str``
|
||||
As the name suggests, a default ordering for the arguments. When
|
||||
this is passed, the context can be created without specifying ``args_order``. Defaults to ``()``.
|
||||
- ``check_rc: bool``
|
||||
When ``True``, if the return code from the command is not zero, the module exits
|
||||
with an error. Defaults to ``False``.
|
||||
- ``path_prefix: list[str]``
|
||||
If the command being executed is installed in a non-standard directory path,
|
||||
additional paths might be provided to search for the executable. Defaults to ``None``.
|
||||
- ``environ_update: dict``
|
||||
Pass additional environment variables to be set during the command execution.
|
||||
Defaults to ``None``.
|
||||
- ``force_lang: str``
|
||||
It is usually important to force the locale to one specific value, so that responses are consistent and, therefore, parseable.
|
||||
Please note that using this option (which is enabled by default) overwrites the environment variables ``LANGUAGE`` and ``LC_ALL``.
|
||||
To disable this mechanism, set this parameter to ``None``.
|
||||
In community.general 9.1.0 a special value ``auto`` was introduced for this parameter, with the effect
|
||||
that ``CmdRunner`` then tries to determine the best parseable locale for the runtime.
|
||||
It should become the default value in the future, but for the time being the default value is ``C``.
|
||||
|
||||
When creating a context, the additional settings that can be passed to the call are:
|
||||
|
||||
- ``args_order: str``
|
||||
Establishes the order in which the arguments are rendered in the command line.
|
||||
This parameter is mandatory unless ``default_args_order`` was provided to the runner instance.
|
||||
- ``output_process: func``
|
||||
Function to transform the output of the executable into different values or formats.
|
||||
See examples in section below.
|
||||
- ``check_mode_skip: bool``
|
||||
Whether to skip the actual execution of the command when the module is in check mode.
|
||||
Defaults to ``False``.
|
||||
- ``check_mode_return: any``
|
||||
If ``check_mode_skip=True``, then return this value instead.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Processing results
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As mentioned, ``CmdRunner`` uses ``AnsibleModule.run_command()`` to execute the external command,
|
||||
and it passes the return value from that method back to caller. That means that,
|
||||
by default, the result is going to be a tuple ``(rc, stdout, stderr)``.
|
||||
|
||||
If you need to transform or process that output, you can pass a function to the context,
|
||||
as the ``output_process`` parameter. It must be a function like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def process(rc, stdout, stderr):
|
||||
# do some magic
|
||||
return processed_value # whatever that is
|
||||
|
||||
In that case, the return of ``run()`` is the ``processed_value`` returned by the function.
|
||||
|
||||
|
||||
PythonRunner
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The ``PythonRunner`` class is a specialized version of ``CmdRunner``, geared towards the execution of
|
||||
Python scripts. It features two extra and mutually exclusive parameters ``python`` and ``venv`` in its constructor:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.python_runner import PythonRunner
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import cmd_runner_fmt
|
||||
|
||||
runner = PythonRunner(
|
||||
module,
|
||||
command=["-m", "django"],
|
||||
arg_formats=dict(...),
|
||||
python="python",
|
||||
venv="/path/to/some/venv",
|
||||
)
|
||||
|
||||
The default value for ``python`` is the string ``python``, and the for ``venv`` it is ``None``.
|
||||
|
||||
The command line produced by such a command with ``python="python3.12"`` is something like:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
/usr/bin/python3.12 -m django <arg1> <arg2> ...
|
||||
|
||||
And the command line for ``venv="/work/venv"`` is like:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
/work/venv/bin/python -m django <arg1> <arg2> ...
|
||||
|
||||
You may provide the value of the ``command`` argument as a string (in that case the string is used as a script name)
|
||||
or as a list, in which case the elements of the list must be valid arguments for the Python interpreter, as in the example above.
|
||||
See `Command line and environment <https://docs.python.org/3/using/cmdline.html>`_ for more details.
|
||||
|
||||
If the parameter ``python`` is an absolute path, or contains directory separators, such as ``/``, then it is used
|
||||
as-is, otherwise the runtime ``PATH`` is searched for that command name.
|
||||
|
||||
Other than that, everything else works as in ``CmdRunner``.
|
||||
|
||||
.. versionadded:: 4.8.0
|
||||
74
docs/docsite/rst/guide_deps.rst
Normal file
74
docs/docsite/rst/guide_deps.rst
Normal file
@@ -0,0 +1,74 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_deps:
|
||||
|
||||
``deps`` Guide
|
||||
==============
|
||||
|
||||
|
||||
Using ``deps``
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The ``ansible_collections.community.general.plugins.module_utils.deps`` module util simplifies
|
||||
the importing of code as described in :ref:`Importing and using shared code <shared_code>`.
|
||||
Please notice that ``deps`` is meant to be used specifically with Ansible modules, and not other types of plugins.
|
||||
|
||||
The same example from the Developer Guide would become:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils import deps
|
||||
|
||||
with deps.declare("foo"):
|
||||
import foo
|
||||
|
||||
Then in ``main()``, just after the argspec (or anywhere in the code, for that matter), do
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
deps.validate(module) # assuming module is a valid AnsibleModule instance
|
||||
|
||||
By default, ``deps`` will rely on ``ansible.module_utils.basic.missing_required_lib`` to generate
|
||||
a message about a failing import. That function accepts parameters ``reason`` and ``url``, and
|
||||
and so does ``deps```:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with deps.declare("foo", reason="foo is needed to properly bar", url="https://foo.bar.io"):
|
||||
import foo
|
||||
|
||||
If you would rather write a custom message instead of using ``missing_required_lib`` then do:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with deps.declare("foo", msg="Custom msg explaining why foo is needed"):
|
||||
import foo
|
||||
|
||||
``deps`` allows for multiple dependencies to be declared:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with deps.declare("foo"):
|
||||
import foo
|
||||
|
||||
with deps.declare("bar"):
|
||||
import bar
|
||||
|
||||
with deps.declare("doe"):
|
||||
import doe
|
||||
|
||||
By default, ``deps.validate()`` will check on all the declared dependencies, but if so desired,
|
||||
they can be validated selectively by doing:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
deps.validate(module, "foo") # only validates the "foo" dependency
|
||||
|
||||
deps.validate(module, "doe:bar") # only validates the "doe" and "bar" dependencies
|
||||
|
||||
deps.validate(module, "-doe:bar") # validates all dependencies except "doe" and "bar"
|
||||
|
||||
.. versionadded:: 6.1.0
|
||||
176
docs/docsite/rst/guide_vardict.rst
Normal file
176
docs/docsite/rst/guide_vardict.rst
Normal file
@@ -0,0 +1,176 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_vardict:
|
||||
|
||||
VarDict Guide
|
||||
=============
|
||||
|
||||
Introduction
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The ``ansible_collections.community.general.plugins.module_utils.vardict`` module util provides the
|
||||
``VarDict`` class to help manage the module variables. That class is a container for module variables,
|
||||
especially the ones for which the module must keep track of state changes, and the ones that should
|
||||
be published as return values.
|
||||
|
||||
Each variable has extra behaviors controlled by associated metadata, simplifying the generation of
|
||||
output values from the module.
|
||||
|
||||
Quickstart
|
||||
""""""""""
|
||||
|
||||
The simplest way of using ``VarDict`` is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.vardict import VarDict
|
||||
|
||||
Then in ``main()``, or any other function called from there:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars = VarDict()
|
||||
|
||||
# Next 3 statements are equivalent
|
||||
vars.abc = 123
|
||||
vars["abc"] = 123
|
||||
vars.set("abc", 123)
|
||||
|
||||
vars.xyz = "bananas"
|
||||
vars.ghi = False
|
||||
|
||||
And by the time the module is about to exit:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
results = vars.output()
|
||||
module.exit_json(**results)
|
||||
|
||||
That makes the return value of the module:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"abc": 123,
|
||||
"xyz": "bananas",
|
||||
"ghi": false
|
||||
}
|
||||
|
||||
Metadata
|
||||
""""""""
|
||||
|
||||
The metadata values associated with each variable are:
|
||||
|
||||
- ``output: bool`` - marks the variable for module output as a module return value.
|
||||
- ``fact: bool`` - marks the variable for module output as an Ansible fact.
|
||||
- ``verbosity: int`` - sets the minimum level of verbosity for which the variable will be included in the output.
|
||||
- ``change: bool`` - controls the detection of changes in the variable value.
|
||||
- ``initial_value: any`` - when using ``change`` and need to forcefully set an intial value to the variable.
|
||||
- ``diff: bool`` - used along with ``change``, this generates an Ansible-style diff ``dict``.
|
||||
|
||||
See the sections below for more details on how to use the metadata.
|
||||
|
||||
|
||||
Using VarDict
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Basic Usage
|
||||
"""""""""""
|
||||
|
||||
As shown above, variables can be accessed using the ``[]`` operator, as in a ``dict`` object,
|
||||
and also as an object attribute, such as ``vars.abc``. The form using the ``set()``
|
||||
method is special in the sense that you can use it to set metadata values:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", 123, output=False)
|
||||
vars.set("abc", 123, output=True, change=True)
|
||||
|
||||
Another way to set metadata after the variables have been created is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set_meta("abc", output=False)
|
||||
vars.set_meta("abc", output=True, change=True, diff=True)
|
||||
|
||||
You can use either operator and attribute forms to access the value of the variable. Other ways to
|
||||
access its value and its metadata are:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print("abc value = {0}".format(vars.var("abc")["value"])) # get the value
|
||||
print("abc output? {0}".format(vars.get_meta("abc")["output"])) # get the metadata like this
|
||||
|
||||
The names of methods, such as ``set``, ``get_meta``, ``output`` amongst others, are reserved and
|
||||
cannot be used as variable names. If you try to use a reserved name a ``ValueError`` exception
|
||||
is raised with the message "Name <var> is reserved".
|
||||
|
||||
Generating output
|
||||
"""""""""""""""""
|
||||
|
||||
By default, every variable create will be enable for output with minimum verbosity set to zero, in
|
||||
other words, they will always be in the output by default.
|
||||
|
||||
You can control that when creating the variable for the first time or later in the code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("internal", x + 4, output=False)
|
||||
vars.set_meta("internal", output=False)
|
||||
|
||||
You can also set the verbosity of some variable, like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", x + 4)
|
||||
vars.set("debug_x", x, verbosity=3)
|
||||
|
||||
results = vars.output(module._verbosity)
|
||||
module.exit_json(**results)
|
||||
|
||||
If the module was invoked with verbosity lower than 3, then the output will only contain
|
||||
the variable ``abc``. If running at higher verbosity, as in ``ansible-playbook -vvv``,
|
||||
then the output will also contain ``debug_x``.
|
||||
|
||||
Generating facts is very similar to regular output, but variables are not marked as facts by default.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("modulefact", x + 4, fact=True)
|
||||
vars.set("debugfact", x, fact=True, verbosity=3)
|
||||
|
||||
results = vars.output(module._verbosity)
|
||||
results["ansible_facts"] = {"module_name": vars.facts(module._verbosity)}
|
||||
module.exit_json(**results)
|
||||
|
||||
Handling change
|
||||
"""""""""""""""
|
||||
|
||||
You can use ``VarDict`` to determine whether variables have had their values changed.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", 42, change=True)
|
||||
vars.abc = 90
|
||||
|
||||
results = vars.output()
|
||||
results["changed"] = vars.has_changed
|
||||
module.exit_json(**results)
|
||||
|
||||
If tracking changes in variables, you may want to present the difference between the initial and the final
|
||||
values of it. For that, you want to use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vars.set("abc", 42, change=True, diff=True)
|
||||
vars.abc = 90
|
||||
|
||||
results = vars.output()
|
||||
results["changed"] = vars.has_changed
|
||||
results["diff"] = vars.diff()
|
||||
module.exit_json(**results)
|
||||
|
||||
.. versionadded:: 7.1.0
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 8.6.7
|
||||
version: 9.2.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
4101
meta/runtime.yml
4101
meta/runtime.yml
File diff suppressed because it is too large
Load Diff
@@ -88,10 +88,6 @@ class ActionModule(ActionBase):
|
||||
max_timeout = self._connection._play_context.timeout
|
||||
module_args = self._task.args
|
||||
|
||||
async_status_args = {}
|
||||
starter_cmd = None
|
||||
confirm_cmd = None
|
||||
|
||||
if module_args.get('state', None) == 'restored':
|
||||
if not wrap_async:
|
||||
if not check_mode:
|
||||
|
||||
@@ -78,12 +78,13 @@ DOCUMENTATION = '''
|
||||
EXAMPLES = r'''
|
||||
# A polkit rule needed to use the module with a non-root user.
|
||||
# See the Notes section for details.
|
||||
60-machinectl-fast-user-auth.rules: |
|
||||
polkit.addRule(function(action, subject) {
|
||||
if(action.id == "org.freedesktop.machine1.host-shell" && subject.isInGroup("wheel")) {
|
||||
return polkit.Result.AUTH_SELF_KEEP;
|
||||
}
|
||||
});
|
||||
/etc/polkit-1/rules.d/60-machinectl-fast-user-auth.rules: |
|
||||
polkit.addRule(function(action, subject) {
|
||||
if(action.id == "org.freedesktop.machine1.host-shell" &&
|
||||
subject.isInGroup("wheel")) {
|
||||
return polkit.Result.AUTH_SELF_KEEP;
|
||||
}
|
||||
});
|
||||
'''
|
||||
|
||||
from re import compile as re_compile
|
||||
|
||||
128
plugins/become/run0.py
Normal file
128
plugins/become/run0.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024, 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 = """
|
||||
name: run0
|
||||
short_description: Systemd's run0
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the C(run0) utility.
|
||||
author:
|
||||
- Thomas Sjögren (@konstruktoid)
|
||||
version_added: '9.0.0'
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: run0_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_run0_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_RUN0_USER
|
||||
type: string
|
||||
become_exe:
|
||||
description: The C(run0) executable.
|
||||
default: run0
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: run0_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_run0_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_RUN0_EXE
|
||||
type: string
|
||||
become_flags:
|
||||
description: Options to pass to run0.
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: run0_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_run0_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_RUN0_FLAGS
|
||||
type: string
|
||||
notes:
|
||||
- This plugin will only work when a polkit rule is in place.
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# An example polkit rule that allows the user 'ansible' in the 'wheel' group
|
||||
# to execute commands using run0 without authentication.
|
||||
/etc/polkit-1/rules.d/60-run0-fast-user-auth.rules: |
|
||||
polkit.addRule(function(action, subject) {
|
||||
if(action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||
subject.isInGroup("wheel") &&
|
||||
subject.user == "ansible") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
"""
|
||||
|
||||
from re import compile as re_compile
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
from ansible.module_utils._text import to_bytes
|
||||
|
||||
ansi_color_codes = re_compile(to_bytes(r"\x1B\[[0-9;]+m"))
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = "community.general.run0"
|
||||
|
||||
prompt = "Password: "
|
||||
fail = ("==== AUTHENTICATION FAILED ====",)
|
||||
success = ("==== AUTHENTICATION COMPLETE ====",)
|
||||
require_tty = (
|
||||
True # see https://github.com/ansible-collections/community.general/issues/6932
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def remove_ansi_codes(line):
|
||||
return ansi_color_codes.sub(b"", line)
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
super().build_become_command(cmd, shell)
|
||||
|
||||
if not cmd:
|
||||
return cmd
|
||||
|
||||
become = self.get_option("become_exe")
|
||||
flags = self.get_option("become_flags")
|
||||
user = self.get_option("become_user")
|
||||
|
||||
return (
|
||||
f"{become} --user={user} {flags} {self._build_success_command(cmd, shell)}"
|
||||
)
|
||||
|
||||
def check_success(self, b_output):
|
||||
b_output = self.remove_ansi_codes(b_output)
|
||||
return super().check_success(b_output)
|
||||
|
||||
def check_incorrect_password(self, b_output):
|
||||
b_output = self.remove_ansi_codes(b_output)
|
||||
return super().check_incorrect_password(b_output)
|
||||
|
||||
def check_missing_password(self, b_output):
|
||||
b_output = self.remove_ansi_codes(b_output)
|
||||
return super().check_missing_password(b_output)
|
||||
@@ -55,6 +55,21 @@ DOCUMENTATION = """
|
||||
ini:
|
||||
- section: sudo_become_plugin
|
||||
key: password
|
||||
alt_method:
|
||||
description:
|
||||
- Whether to use an alternative method to call C(su). Instead of running C(su -l user /path/to/shell -c command),
|
||||
it runs C(su -l user -c command).
|
||||
- Use this when the default one is not working on your system.
|
||||
required: false
|
||||
type: boolean
|
||||
ini:
|
||||
- section: community.general.sudosu
|
||||
key: alternative_method
|
||||
vars:
|
||||
- name: ansible_sudosu_alt_method
|
||||
env:
|
||||
- name: ANSIBLE_SUDOSU_ALT_METHOD
|
||||
version_added: 9.2.0
|
||||
"""
|
||||
|
||||
|
||||
@@ -89,4 +104,7 @@ class BecomeModule(BecomeBase):
|
||||
if user:
|
||||
user = '%s' % (user)
|
||||
|
||||
return ' '.join([becomecmd, flags, prompt, 'su -l', user, self._build_success_command(cmd, shell)])
|
||||
if self.get_option('alt_method'):
|
||||
return ' '.join([becomecmd, flags, prompt, "su -l", user, "-c", self._build_success_command(cmd, shell, True)])
|
||||
else:
|
||||
return ' '.join([becomecmd, flags, prompt, 'su -l', user, self._build_success_command(cmd, shell)])
|
||||
|
||||
@@ -84,6 +84,33 @@ DOCUMENTATION = '''
|
||||
- section: callback_opentelemetry
|
||||
key: disable_attributes_in_logs
|
||||
version_added: 7.1.0
|
||||
store_spans_in_file:
|
||||
default: None
|
||||
type: str
|
||||
description:
|
||||
- It stores the exported spans in the given file
|
||||
env:
|
||||
- name: ANSIBLE_OPENTELEMETRY_STORE_SPANS_IN_FILE
|
||||
ini:
|
||||
- section: callback_opentelemetry
|
||||
key: store_spans_in_file
|
||||
version_added: 9.0.0
|
||||
otel_exporter_otlp_traces_protocol:
|
||||
type: str
|
||||
description:
|
||||
- E(OTEL_EXPORTER_OTLP_TRACES_PROTOCOL) represents the the transport protocol for spans.
|
||||
- See
|
||||
U(https://opentelemetry-python.readthedocs.io/en/latest/sdk/environment_variables.html#envvar-OTEL_EXPORTER_OTLP_TRACES_PROTOCOL).
|
||||
default: grpc
|
||||
choices:
|
||||
- grpc
|
||||
- http/protobuf
|
||||
env:
|
||||
- name: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
|
||||
ini:
|
||||
- section: callback_opentelemetry
|
||||
key: otel_exporter_otlp_traces_protocol
|
||||
version_added: 9.0.0
|
||||
requirements:
|
||||
- opentelemetry-api (Python library)
|
||||
- opentelemetry-exporter-otlp (Python library)
|
||||
@@ -107,6 +134,7 @@ examples: |
|
||||
'''
|
||||
|
||||
import getpass
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
@@ -124,15 +152,19 @@ from ansible.plugins.callback import CallbackBase
|
||||
try:
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.trace import SpanKind
|
||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GRPCOTLPSpanExporter
|
||||
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPOTLPSpanExporter
|
||||
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
|
||||
from opentelemetry.trace.status import Status, StatusCode
|
||||
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import (
|
||||
BatchSpanProcessor
|
||||
BatchSpanProcessor,
|
||||
SimpleSpanProcessor
|
||||
)
|
||||
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
|
||||
InMemorySpanExporter
|
||||
)
|
||||
|
||||
# Support for opentelemetry-api <= 1.12
|
||||
try:
|
||||
from opentelemetry.util._time import _time_ns
|
||||
@@ -255,7 +287,16 @@ class OpenTelemetrySource(object):
|
||||
task.dump = dump
|
||||
task.add_host(HostData(host_uuid, host_name, status, result))
|
||||
|
||||
def generate_distributed_traces(self, otel_service_name, ansible_playbook, tasks_data, status, traceparent, disable_logs, disable_attributes_in_logs):
|
||||
def generate_distributed_traces(self,
|
||||
otel_service_name,
|
||||
ansible_playbook,
|
||||
tasks_data,
|
||||
status,
|
||||
traceparent,
|
||||
disable_logs,
|
||||
disable_attributes_in_logs,
|
||||
otel_exporter_otlp_traces_protocol,
|
||||
store_spans_in_file):
|
||||
""" generate distributed traces from the collected TaskData and HostData """
|
||||
|
||||
tasks = []
|
||||
@@ -271,7 +312,16 @@ class OpenTelemetrySource(object):
|
||||
)
|
||||
)
|
||||
|
||||
processor = BatchSpanProcessor(OTLPSpanExporter())
|
||||
otel_exporter = None
|
||||
if store_spans_in_file:
|
||||
otel_exporter = InMemorySpanExporter()
|
||||
processor = SimpleSpanProcessor(otel_exporter)
|
||||
else:
|
||||
if otel_exporter_otlp_traces_protocol == 'grpc':
|
||||
otel_exporter = GRPCOTLPSpanExporter()
|
||||
else:
|
||||
otel_exporter = HTTPOTLPSpanExporter()
|
||||
processor = BatchSpanProcessor(otel_exporter)
|
||||
|
||||
trace.get_tracer_provider().add_span_processor(processor)
|
||||
|
||||
@@ -293,6 +343,8 @@ class OpenTelemetrySource(object):
|
||||
with tracer.start_as_current_span(task.name, start_time=task.start, end_on_exit=False) as span:
|
||||
self.update_span_data(task, host_data, span, disable_logs, disable_attributes_in_logs)
|
||||
|
||||
return otel_exporter
|
||||
|
||||
def update_span_data(self, task_data, host_data, span, disable_logs, disable_attributes_in_logs):
|
||||
""" update the span with the given TaskData and HostData """
|
||||
|
||||
@@ -304,7 +356,6 @@ class OpenTelemetrySource(object):
|
||||
status = Status(status_code=StatusCode.OK)
|
||||
if host_data.status != 'included':
|
||||
# Support loops
|
||||
enriched_error_message = None
|
||||
if 'results' in host_data.result._result:
|
||||
if host_data.status == 'failed':
|
||||
message = self.get_error_message_from_results(host_data.result._result['results'], task_data.action)
|
||||
@@ -464,6 +515,8 @@ class CallbackModule(CallbackBase):
|
||||
self.errors = 0
|
||||
self.disabled = False
|
||||
self.traceparent = False
|
||||
self.store_spans_in_file = False
|
||||
self.otel_exporter_otlp_traces_protocol = None
|
||||
|
||||
if OTEL_LIBRARY_IMPORT_ERROR:
|
||||
raise_from(
|
||||
@@ -491,6 +544,8 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
self.disable_logs = self.get_option('disable_logs')
|
||||
|
||||
self.store_spans_in_file = self.get_option('store_spans_in_file')
|
||||
|
||||
self.otel_service_name = self.get_option('otel_service_name')
|
||||
|
||||
if not self.otel_service_name:
|
||||
@@ -499,6 +554,8 @@ class CallbackModule(CallbackBase):
|
||||
# See https://github.com/open-telemetry/opentelemetry-specification/issues/740
|
||||
self.traceparent = self.get_option('traceparent')
|
||||
|
||||
self.otel_exporter_otlp_traces_protocol = self.get_option('otel_exporter_otlp_traces_protocol')
|
||||
|
||||
def dump_results(self, task, result):
|
||||
""" dump the results if disable_logs is not enabled """
|
||||
if self.disable_logs:
|
||||
@@ -594,15 +651,22 @@ class CallbackModule(CallbackBase):
|
||||
status = Status(status_code=StatusCode.OK)
|
||||
else:
|
||||
status = Status(status_code=StatusCode.ERROR)
|
||||
self.opentelemetry.generate_distributed_traces(
|
||||
otel_exporter = self.opentelemetry.generate_distributed_traces(
|
||||
self.otel_service_name,
|
||||
self.ansible_playbook,
|
||||
self.tasks_data,
|
||||
status,
|
||||
self.traceparent,
|
||||
self.disable_logs,
|
||||
self.disable_attributes_in_logs
|
||||
self.disable_attributes_in_logs,
|
||||
self.otel_exporter_otlp_traces_protocol,
|
||||
self.store_spans_in_file
|
||||
)
|
||||
|
||||
if self.store_spans_in_file:
|
||||
spans = [json.loads(span.to_json()) for span in otel_exporter.get_finished_spans()]
|
||||
with open(self.store_spans_in_file, "w", encoding="utf-8") as output:
|
||||
json.dump({"spans": spans}, output, indent=4)
|
||||
|
||||
def v2_runner_on_async_failed(self, result, **kwargs):
|
||||
self.errors += 1
|
||||
|
||||
127
plugins/callback/timestamp.py
Normal file
127
plugins/callback/timestamp.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, kurokobo <kurokobo@protonmail.com>
|
||||
# Copyright (c) 2014, Michael DeHaan <michael.dehaan@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"""
|
||||
name: timestamp
|
||||
type: stdout
|
||||
short_description: Adds simple timestamp for each header
|
||||
version_added: 9.0.0
|
||||
description:
|
||||
- This callback adds simple timestamp for each header.
|
||||
author: kurokobo (@kurokobo)
|
||||
options:
|
||||
timezone:
|
||||
description:
|
||||
- Timezone to use for the timestamp in IANA time zone format.
|
||||
- For example C(America/New_York), C(Asia/Tokyo)). Ignored on Python < 3.9.
|
||||
ini:
|
||||
- section: callback_timestamp
|
||||
key: timezone
|
||||
env:
|
||||
- name: ANSIBLE_CALLBACK_TIMESTAMP_TIMEZONE
|
||||
type: string
|
||||
format_string:
|
||||
description:
|
||||
- Format of the timestamp shown to user in 1989 C standard format.
|
||||
- >
|
||||
Refer to L(the Python documentation,https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
|
||||
for the available format codes.
|
||||
ini:
|
||||
- section: callback_timestamp
|
||||
key: format_string
|
||||
env:
|
||||
- name: ANSIBLE_CALLBACK_TIMESTAMP_FORMAT_STRING
|
||||
default: "%H:%M:%S"
|
||||
type: string
|
||||
seealso:
|
||||
- plugin: ansible.posix.profile_tasks
|
||||
plugin_type: callback
|
||||
description: >
|
||||
You can use P(ansible.posix.profile_tasks#callback) callback plugin to time individual tasks and overall execution time
|
||||
with detailed timestamps.
|
||||
extends_documentation_fragment:
|
||||
- ansible.builtin.default_callback
|
||||
- ansible.builtin.result_format_callback
|
||||
"""
|
||||
|
||||
|
||||
from ansible.plugins.callback.default import CallbackModule as Default
|
||||
from ansible.utils.display import get_text_width
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from datetime import datetime
|
||||
import types
|
||||
import sys
|
||||
|
||||
# Store whether the zoneinfo module is available
|
||||
_ZONEINFO_AVAILABLE = sys.version_info >= (3, 9)
|
||||
|
||||
|
||||
def get_datetime_now(tz):
|
||||
"""
|
||||
Returns the current timestamp with the specified timezone
|
||||
"""
|
||||
return datetime.now(tz=tz)
|
||||
|
||||
|
||||
def banner(self, msg, color=None, cows=True):
|
||||
"""
|
||||
Prints a header-looking line with cowsay or stars with length depending on terminal width (3 minimum) with trailing timestamp
|
||||
|
||||
Based on the banner method of Display class from ansible.utils.display
|
||||
|
||||
https://github.com/ansible/ansible/blob/4403519afe89138042108e237aef317fd5f09c33/lib/ansible/utils/display.py#L511
|
||||
"""
|
||||
timestamp = get_datetime_now(self.timestamp_tzinfo).strftime(self.timestamp_format_string)
|
||||
timestamp_len = get_text_width(timestamp) + 1 # +1 for leading space
|
||||
|
||||
msg = to_text(msg)
|
||||
if self.b_cowsay and cows:
|
||||
try:
|
||||
self.banner_cowsay("%s @ %s" % (msg, timestamp))
|
||||
return
|
||||
except OSError:
|
||||
self.warning("somebody cleverly deleted cowsay or something during the PB run. heh.")
|
||||
|
||||
msg = msg.strip()
|
||||
try:
|
||||
star_len = self.columns - get_text_width(msg) - timestamp_len
|
||||
except EnvironmentError:
|
||||
star_len = self.columns - len(msg) - timestamp_len
|
||||
if star_len <= 3:
|
||||
star_len = 3
|
||||
stars = "*" * star_len
|
||||
self.display("\n%s %s %s" % (msg, stars, timestamp), color=color)
|
||||
|
||||
|
||||
class CallbackModule(Default):
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = "stdout"
|
||||
CALLBACK_NAME = "community.general.timestamp"
|
||||
|
||||
def __init__(self):
|
||||
super(CallbackModule, self).__init__()
|
||||
|
||||
# Replace the banner method of the display object with the custom one
|
||||
self._display.banner = types.MethodType(banner, self._display)
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
|
||||
# Store zoneinfo for specified timezone if available
|
||||
tzinfo = None
|
||||
if _ZONEINFO_AVAILABLE and self.get_option("timezone"):
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
tzinfo = ZoneInfo(self.get_option("timezone"))
|
||||
|
||||
# Inject options into the display object
|
||||
setattr(self._display, "timestamp_tzinfo", tzinfo)
|
||||
setattr(self._display, "timestamp_format_string", self.get_option("format_string"))
|
||||
@@ -56,5 +56,4 @@ attributes:
|
||||
support: full
|
||||
membership:
|
||||
- community.general.consul
|
||||
version_added: 8.3.0
|
||||
"""
|
||||
|
||||
62
plugins/doc_fragments/django.py
Normal file
62
plugins/doc_fragments/django.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- 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:
|
||||
venv:
|
||||
description:
|
||||
- Use the the Python interpreter from this virtual environment.
|
||||
- Pass the path to the root of the virtualenv, not the C(bin/) directory nor the C(python) executable.
|
||||
type: path
|
||||
settings:
|
||||
description:
|
||||
- Specifies the settings module to use.
|
||||
- The value will be passed as is to the C(--settings) argument in C(django-admin).
|
||||
type: str
|
||||
required: true
|
||||
pythonpath:
|
||||
description:
|
||||
- Adds the given filesystem path to the Python import search path.
|
||||
- The value will be passed as is to the C(--pythonpath) argument in C(django-admin).
|
||||
type: path
|
||||
traceback:
|
||||
description:
|
||||
- Provides a full stack trace in the output when a C(CommandError) is raised.
|
||||
type: bool
|
||||
verbosity:
|
||||
description:
|
||||
- Specifies the amount of notification and debug information in the output of C(django-admin).
|
||||
type: int
|
||||
choices: [0, 1, 2, 3]
|
||||
skip_checks:
|
||||
description:
|
||||
- Skips running system checks prior to running the command.
|
||||
type: bool
|
||||
|
||||
|
||||
notes:
|
||||
- The C(django-admin) command is always executed using the C(C) locale, and the option C(--no-color) is always passed.
|
||||
|
||||
seealso:
|
||||
- name: django-admin and manage.py in official Django documentation
|
||||
description: >-
|
||||
Refer to this documentation for the builtin commands and options of C(django-admin).
|
||||
Please make sure that you select the right version of Django in the version selector on that page.
|
||||
link: https://docs.djangoproject.com/en/5.0/ref/django-admin/
|
||||
'''
|
||||
|
||||
DATABASE = r'''
|
||||
options:
|
||||
database:
|
||||
description:
|
||||
- Specify the database to be used.
|
||||
type: str
|
||||
default: default
|
||||
'''
|
||||
@@ -16,6 +16,13 @@ options:
|
||||
- Specify the target host of the Proxmox VE cluster.
|
||||
type: str
|
||||
required: true
|
||||
api_port:
|
||||
description:
|
||||
- Specify the target port of the Proxmox VE cluster.
|
||||
- Uses the E(PROXMOX_PORT) environment variable if not specified.
|
||||
type: int
|
||||
required: false
|
||||
version_added: 9.1.0
|
||||
api_user:
|
||||
description:
|
||||
- Specify the user to authenticate with.
|
||||
@@ -65,3 +72,13 @@ options:
|
||||
- Add the new VM to the specified pool.
|
||||
type: str
|
||||
'''
|
||||
|
||||
ACTIONGROUP_PROXMOX = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
action_group:
|
||||
description: Use C(group/community.general.proxmox) in C(module_defaults) to set defaults for this module.
|
||||
support: full
|
||||
membership:
|
||||
- community.general.proxmox
|
||||
"""
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014, Matt Martz <matt@sivel.net>
|
||||
# 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):
|
||||
|
||||
# Standard Rackspace only documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
api_key:
|
||||
description:
|
||||
- Rackspace API key, overrides O(credentials).
|
||||
type: str
|
||||
aliases: [ password ]
|
||||
credentials:
|
||||
description:
|
||||
- File to find the Rackspace credentials in. Ignored if O(api_key) and
|
||||
O(username) are provided.
|
||||
type: path
|
||||
aliases: [ creds_file ]
|
||||
env:
|
||||
description:
|
||||
- Environment as configured in C(~/.pyrax.cfg),
|
||||
see U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration).
|
||||
type: str
|
||||
region:
|
||||
description:
|
||||
- Region to create an instance in.
|
||||
type: str
|
||||
username:
|
||||
description:
|
||||
- Rackspace username, overrides O(credentials).
|
||||
type: str
|
||||
validate_certs:
|
||||
description:
|
||||
- Whether or not to require SSL validation of API endpoints.
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
requirements:
|
||||
- pyrax
|
||||
notes:
|
||||
- The following environment variables can be used, E(RAX_USERNAME),
|
||||
E(RAX_API_KEY), E(RAX_CREDS_FILE), E(RAX_CREDENTIALS), E(RAX_REGION).
|
||||
- E(RAX_CREDENTIALS) and E(RAX_CREDS_FILE) point to a credentials file
|
||||
appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating).
|
||||
- E(RAX_USERNAME) and E(RAX_API_KEY) obviate the use of a credentials file.
|
||||
- E(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...).
|
||||
'''
|
||||
|
||||
# Documentation fragment including attributes to enable communication
|
||||
# of other OpenStack clouds. Not all rax modules support this.
|
||||
OPENSTACK = r'''
|
||||
options:
|
||||
api_key:
|
||||
type: str
|
||||
description:
|
||||
- Rackspace API key, overrides O(credentials).
|
||||
aliases: [ password ]
|
||||
auth_endpoint:
|
||||
type: str
|
||||
description:
|
||||
- The URI of the authentication service.
|
||||
- If not specified will be set to U(https://identity.api.rackspacecloud.com/v2.0/).
|
||||
credentials:
|
||||
type: path
|
||||
description:
|
||||
- File to find the Rackspace credentials in. Ignored if O(api_key) and
|
||||
O(username) are provided.
|
||||
aliases: [ creds_file ]
|
||||
env:
|
||||
type: str
|
||||
description:
|
||||
- Environment as configured in C(~/.pyrax.cfg),
|
||||
see U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration).
|
||||
identity_type:
|
||||
type: str
|
||||
description:
|
||||
- Authentication mechanism to use, such as rackspace or keystone.
|
||||
default: rackspace
|
||||
region:
|
||||
type: str
|
||||
description:
|
||||
- Region to create an instance in.
|
||||
tenant_id:
|
||||
type: str
|
||||
description:
|
||||
- The tenant ID used for authentication.
|
||||
tenant_name:
|
||||
type: str
|
||||
description:
|
||||
- The tenant name used for authentication.
|
||||
username:
|
||||
type: str
|
||||
description:
|
||||
- Rackspace username, overrides O(credentials).
|
||||
validate_certs:
|
||||
description:
|
||||
- Whether or not to require SSL validation of API endpoints.
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: This module relies on the deprecated package pyrax.
|
||||
alternative: Use the Openstack modules instead.
|
||||
requirements:
|
||||
- pyrax
|
||||
notes:
|
||||
- The following environment variables can be used, E(RAX_USERNAME),
|
||||
E(RAX_API_KEY), E(RAX_CREDS_FILE), E(RAX_CREDENTIALS), E(RAX_REGION).
|
||||
- E(RAX_CREDENTIALS) and E(RAX_CREDS_FILE) points to a credentials file
|
||||
appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating).
|
||||
- E(RAX_USERNAME) and E(RAX_API_KEY) obviate the use of a credentials file.
|
||||
- E(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...).
|
||||
'''
|
||||
@@ -13,8 +13,6 @@ 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:
|
||||
|
||||
138
plugins/filter/keep_keys.py
Normal file
138
plugins/filter/keep_keys.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2024 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: keep_keys
|
||||
short_description: Keep specific keys from dictionaries in a list
|
||||
version_added: "9.1.0"
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
- Felix Fontein (@felixfontein)
|
||||
description: This filter keeps only specified keys from a provided list of dictionaries.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- A list of dictionaries.
|
||||
- Top level keys must be strings.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
target:
|
||||
description:
|
||||
- A single key or key pattern to keep, or a list of keys or keys patterns to keep.
|
||||
- If O(matching_parameter=regex) there must be exactly one pattern provided.
|
||||
type: raw
|
||||
required: true
|
||||
matching_parameter:
|
||||
description: Specify the matching option of target keys.
|
||||
type: str
|
||||
default: equal
|
||||
choices:
|
||||
equal: Matches keys of exactly one of the O(target) items.
|
||||
starts_with: Matches keys that start with one of the O(target) items.
|
||||
ends_with: Matches keys that end with one of the O(target) items.
|
||||
regex:
|
||||
- Matches keys that match the regular expresion provided in O(target).
|
||||
- In this case, O(target) must be a regex string or a list with single regex string.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
l:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 1) By default match keys that equal any of the items in the target.
|
||||
t: [k0_x0, k1_x1]
|
||||
r: "{{ l | community.general.keep_keys(target=t) }}"
|
||||
|
||||
# 2) Match keys that start with any of the items in the target.
|
||||
t: [k0, k1]
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 3) Match keys that end with any of the items in target.
|
||||
t: [x0, x1]
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 4) Match keys by the regex.
|
||||
t: ['^.*[01]_x.*$']
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# 5) Match keys by the regex.
|
||||
t: '^.*[01]_x.*$'
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 1-5 are all the same.
|
||||
r:
|
||||
- {k0_x0: A0, k1_x1: B0}
|
||||
- {k0_x0: A1, k1_x1: B1}
|
||||
|
||||
# 6) By default match keys that equal the target.
|
||||
t: k0_x0
|
||||
r: "{{ l | community.general.keep_keys(target=t) }}"
|
||||
|
||||
# 7) Match keys that start with the target.
|
||||
t: k0
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 8) Match keys that end with the target.
|
||||
t: x0
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 9) Match keys by the regex.
|
||||
t: '^.*0_x.*$'
|
||||
r: "{{ l | community.general.keep_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 6-9 are all the same.
|
||||
r:
|
||||
- {k0_x0: A0}
|
||||
- {k0_x0: A1}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The list of dictionaries with selected keys.
|
||||
type: list
|
||||
elements: dictionary
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.keys_filter import (
|
||||
_keys_filter_params,
|
||||
_keys_filter_target_str)
|
||||
|
||||
|
||||
def keep_keys(data, target=None, matching_parameter='equal'):
|
||||
"""keep specific keys from dictionaries in a list"""
|
||||
|
||||
# test parameters
|
||||
_keys_filter_params(data, matching_parameter)
|
||||
# test and transform target
|
||||
tt = _keys_filter_target_str(target, matching_parameter)
|
||||
|
||||
if matching_parameter == 'equal':
|
||||
def keep_key(key):
|
||||
return key in tt
|
||||
elif matching_parameter == 'starts_with':
|
||||
def keep_key(key):
|
||||
return key.startswith(tt)
|
||||
elif matching_parameter == 'ends_with':
|
||||
def keep_key(key):
|
||||
return key.endswith(tt)
|
||||
elif matching_parameter == 'regex':
|
||||
def keep_key(key):
|
||||
return tt.match(key) is not None
|
||||
|
||||
return [dict((k, v) for k, v in d.items() if keep_key(k)) for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'keep_keys': keep_keys,
|
||||
}
|
||||
138
plugins/filter/remove_keys.py
Normal file
138
plugins/filter/remove_keys.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2024 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: remove_keys
|
||||
short_description: Remove specific keys from dictionaries in a list
|
||||
version_added: "9.1.0"
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
- Felix Fontein (@felixfontein)
|
||||
description: This filter removes only specified keys from a provided list of dictionaries.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- A list of dictionaries.
|
||||
- Top level keys must be strings.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
target:
|
||||
description:
|
||||
- A single key or key pattern to remove, or a list of keys or keys patterns to remove.
|
||||
- If O(matching_parameter=regex) there must be exactly one pattern provided.
|
||||
type: raw
|
||||
required: true
|
||||
matching_parameter:
|
||||
description: Specify the matching option of target keys.
|
||||
type: str
|
||||
default: equal
|
||||
choices:
|
||||
equal: Matches keys of exactly one of the O(target) items.
|
||||
starts_with: Matches keys that start with one of the O(target) items.
|
||||
ends_with: Matches keys that end with one of the O(target) items.
|
||||
regex:
|
||||
- Matches keys that match the regular expresion provided in O(target).
|
||||
- In this case, O(target) must be a regex string or a list with single regex string.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
l:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 1) By default match keys that equal any of the items in the target.
|
||||
t: [k0_x0, k1_x1]
|
||||
r: "{{ l | community.general.remove_keys(target=t) }}"
|
||||
|
||||
# 2) Match keys that start with any of the items in the target.
|
||||
t: [k0, k1]
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 3) Match keys that end with any of the items in target.
|
||||
t: [x0, x1]
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 4) Match keys by the regex.
|
||||
t: ['^.*[01]_x.*$']
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# 5) Match keys by the regex.
|
||||
t: '^.*[01]_x.*$'
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 1-5 are all the same.
|
||||
r:
|
||||
- {k2_x2: [C0], k3_x3: foo}
|
||||
- {k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 6) By default match keys that equal the target.
|
||||
t: k0_x0
|
||||
r: "{{ l | community.general.remove_keys(target=t) }}"
|
||||
|
||||
# 7) Match keys that start with the target.
|
||||
t: k0
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 8) Match keys that end with the target.
|
||||
t: x0
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 9) Match keys by the regex.
|
||||
t: '^.*0_x.*$'
|
||||
r: "{{ l | community.general.remove_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 6-9 are all the same.
|
||||
r:
|
||||
- {k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The list of dictionaries with selected keys removed.
|
||||
type: list
|
||||
elements: dictionary
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.keys_filter import (
|
||||
_keys_filter_params,
|
||||
_keys_filter_target_str)
|
||||
|
||||
|
||||
def remove_keys(data, target=None, matching_parameter='equal'):
|
||||
"""remove specific keys from dictionaries in a list"""
|
||||
|
||||
# test parameters
|
||||
_keys_filter_params(data, matching_parameter)
|
||||
# test and transform target
|
||||
tt = _keys_filter_target_str(target, matching_parameter)
|
||||
|
||||
if matching_parameter == 'equal':
|
||||
def keep_key(key):
|
||||
return key not in tt
|
||||
elif matching_parameter == 'starts_with':
|
||||
def keep_key(key):
|
||||
return not key.startswith(tt)
|
||||
elif matching_parameter == 'ends_with':
|
||||
def keep_key(key):
|
||||
return not key.endswith(tt)
|
||||
elif matching_parameter == 'regex':
|
||||
def keep_key(key):
|
||||
return tt.match(key) is None
|
||||
|
||||
return [dict((k, v) for k, v in d.items() if keep_key(k)) for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'remove_keys': remove_keys,
|
||||
}
|
||||
180
plugins/filter/replace_keys.py
Normal file
180
plugins/filter/replace_keys.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# Copyright (c) 2024 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: replace_keys
|
||||
short_description: Replace specific keys in a list of dictionaries
|
||||
version_added: "9.1.0"
|
||||
author:
|
||||
- Vladimir Botka (@vbotka)
|
||||
- Felix Fontein (@felixfontein)
|
||||
description: This filter replaces specified keys in a provided list of dictionaries.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- A list of dictionaries.
|
||||
- Top level keys must be strings.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
target:
|
||||
description:
|
||||
- A list of dictionaries with attributes C(before) and C(after).
|
||||
- The value of O(target[].after) replaces key matching O(target[].before).
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
suboptions:
|
||||
before:
|
||||
description:
|
||||
- A key or key pattern to change.
|
||||
- The interpretation of O(target[].before) depends on O(matching_parameter).
|
||||
- For a key that matches multiple O(target[].before)s, the B(first) matching O(target[].after) will be used.
|
||||
type: str
|
||||
after:
|
||||
description: A matching key change to.
|
||||
type: str
|
||||
matching_parameter:
|
||||
description: Specify the matching option of target keys.
|
||||
type: str
|
||||
default: equal
|
||||
choices:
|
||||
equal: Matches keys of exactly one of the O(target[].before) items.
|
||||
starts_with: Matches keys that start with one of the O(target[].before) items.
|
||||
ends_with: Matches keys that end with one of the O(target[].before) items.
|
||||
regex: Matches keys that match one of the regular expressions provided in O(target[].before).
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
l:
|
||||
- {k0_x0: A0, k1_x1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {k0_x0: A1, k1_x1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 1) By default, replace keys that are equal any of the attributes before.
|
||||
t:
|
||||
- {before: k0_x0, after: a0}
|
||||
- {before: k1_x1, after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t) }}"
|
||||
|
||||
# 2) Replace keys that starts with any of the attributes before.
|
||||
t:
|
||||
- {before: k0, after: a0}
|
||||
- {before: k1, after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# 3) Replace keys that ends with any of the attributes before.
|
||||
t:
|
||||
- {before: x0, after: a0}
|
||||
- {before: x1, after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='ends_with') }}"
|
||||
|
||||
# 4) Replace keys that match any regex of the attributes before.
|
||||
t:
|
||||
- {before: "^.*0_x.*$", after: a0}
|
||||
- {before: "^.*1_x.*$", after: a1}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# The results of above examples 1-4 are all the same.
|
||||
r:
|
||||
- {a0: A0, a1: B0, k2_x2: [C0], k3_x3: foo}
|
||||
- {a0: A1, a1: B1, k2_x2: [C1], k3_x3: bar}
|
||||
|
||||
# 5) If more keys match the same attribute before the last one will be used.
|
||||
t:
|
||||
- {before: "^.*_x.*$", after: X}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# gives
|
||||
|
||||
r:
|
||||
- X: foo
|
||||
- X: bar
|
||||
|
||||
# 6) If there are items with equal attribute before the first one will be used.
|
||||
t:
|
||||
- {before: "^.*_x.*$", after: X}
|
||||
- {before: "^.*_x.*$", after: Y}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='regex') }}"
|
||||
|
||||
# gives
|
||||
|
||||
r:
|
||||
- X: foo
|
||||
- X: bar
|
||||
|
||||
# 7) If there are more matches for a key the first one will be used.
|
||||
l:
|
||||
- {aaa1: A, bbb1: B, ccc1: C}
|
||||
- {aaa2: D, bbb2: E, ccc2: F}
|
||||
t:
|
||||
- {before: a, after: X}
|
||||
- {before: aa, after: Y}
|
||||
r: "{{ l | community.general.replace_keys(target=t, matching_parameter='starts_with') }}"
|
||||
|
||||
# gives
|
||||
|
||||
r:
|
||||
- {X: A, bbb1: B, ccc1: C}
|
||||
- {X: D, bbb2: E, ccc2: F}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The list of dictionaries with replaced keys.
|
||||
type: list
|
||||
elements: dictionary
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.keys_filter import (
|
||||
_keys_filter_params,
|
||||
_keys_filter_target_dict)
|
||||
|
||||
|
||||
def replace_keys(data, target=None, matching_parameter='equal'):
|
||||
"""replace specific keys in a list of dictionaries"""
|
||||
|
||||
# test parameters
|
||||
_keys_filter_params(data, matching_parameter)
|
||||
# test and transform target
|
||||
tz = _keys_filter_target_dict(target, matching_parameter)
|
||||
|
||||
if matching_parameter == 'equal':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if key == b:
|
||||
return a
|
||||
return key
|
||||
elif matching_parameter == 'starts_with':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if key.startswith(b):
|
||||
return a
|
||||
return key
|
||||
elif matching_parameter == 'ends_with':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if key.endswith(b):
|
||||
return a
|
||||
return key
|
||||
elif matching_parameter == 'regex':
|
||||
def replace_key(key):
|
||||
for b, a in tz:
|
||||
if b.match(key):
|
||||
return a
|
||||
return key
|
||||
|
||||
return [dict((replace_key(k), v) for k, v in d.items()) for d in data]
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'replace_keys': replace_keys,
|
||||
}
|
||||
134
plugins/filter/reveal_ansible_type.py
Normal file
134
plugins/filter/reveal_ansible_type.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2024 Vladimir Botka <vbotka@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: reveal_ansible_type
|
||||
short_description: Return input type
|
||||
version_added: "9.2.0"
|
||||
author: Vladimir Botka (@vbotka)
|
||||
description: This filter returns input type.
|
||||
options:
|
||||
_input:
|
||||
description: Input data.
|
||||
type: raw
|
||||
required: true
|
||||
alias:
|
||||
description: Data type aliases.
|
||||
default: {}
|
||||
type: dictionary
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Substitution converts str to AnsibleUnicode
|
||||
# -------------------------------------------
|
||||
|
||||
# String. AnsibleUnicode.
|
||||
data: "abc"
|
||||
result: '{{ data | community.general.reveal_ansible_type }}'
|
||||
# result => AnsibleUnicode
|
||||
|
||||
# String. AnsibleUnicode alias str.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
data: "abc"
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => str
|
||||
|
||||
# List. All items are AnsibleUnicode.
|
||||
data: ["a", "b", "c"]
|
||||
result: '{{ data | community.general.reveal_ansible_type }}'
|
||||
# result => list[AnsibleUnicode]
|
||||
|
||||
# Dictionary. All keys are AnsibleUnicode. All values are AnsibleUnicode.
|
||||
data: {"a": "foo", "b": "bar", "c": "baz"}
|
||||
result: '{{ data | community.general.reveal_ansible_type }}'
|
||||
# result => dict[AnsibleUnicode, AnsibleUnicode]
|
||||
|
||||
# No substitution and no alias. Type of strings is str
|
||||
# ----------------------------------------------------
|
||||
|
||||
# String
|
||||
result: '{{ "abc" | community.general.reveal_ansible_type }}'
|
||||
# result => str
|
||||
|
||||
# Integer
|
||||
result: '{{ 123 | community.general.reveal_ansible_type }}'
|
||||
# result => int
|
||||
|
||||
# Float
|
||||
result: '{{ 123.45 | community.general.reveal_ansible_type }}'
|
||||
# result => float
|
||||
|
||||
# Boolean
|
||||
result: '{{ true | community.general.reveal_ansible_type }}'
|
||||
# result => bool
|
||||
|
||||
# List. All items are strings.
|
||||
result: '{{ ["a", "b", "c"] | community.general.reveal_ansible_type }}'
|
||||
# result => list[str]
|
||||
|
||||
# List of dictionaries.
|
||||
result: '{{ [{"a": 1}, {"b": 2}] | community.general.reveal_ansible_type }}'
|
||||
# result => list[dict]
|
||||
|
||||
# Dictionary. All keys are strings. All values are integers.
|
||||
result: '{{ {"a": 1} | community.general.reveal_ansible_type }}'
|
||||
# result => dict[str, int]
|
||||
|
||||
# Dictionary. All keys are strings. All values are integers.
|
||||
result: '{{ {"a": 1, "b": 2} | community.general.reveal_ansible_type }}'
|
||||
# result => dict[str, int]
|
||||
|
||||
# Type of strings is AnsibleUnicode or str
|
||||
# ----------------------------------------
|
||||
|
||||
# Dictionary. The keys are integers or strings. All values are strings.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
data: {1: 'a', 'b': 'b'}
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => dict[int|str, str]
|
||||
|
||||
# Dictionary. All keys are integers. All values are keys.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
data: {1: 'a', 2: 'b'}
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => dict[int, str]
|
||||
|
||||
# Dictionary. All keys are strings. Multiple types values.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
data: {'a': 1, 'b': 1.1, 'c': 'abc', 'd': True, 'e': ['x', 'y', 'z'], 'f': {'x': 1, 'y': 2}}
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => dict[str, bool|dict|float|int|list|str]
|
||||
|
||||
# List. Multiple types items.
|
||||
alias: {"AnsibleUnicode": "str"}
|
||||
data: [1, 2, 1.1, 'abc', True, ['x', 'y', 'z'], {'x': 1, 'y': 2}]
|
||||
result: '{{ data | community.general.reveal_ansible_type(alias) }}'
|
||||
# result => list[bool|dict|float|int|list|str]
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: Type of the data.
|
||||
type: str
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.ansible_type import _ansible_type
|
||||
|
||||
|
||||
def reveal_ansible_type(data, alias=None):
|
||||
"""Returns data type"""
|
||||
|
||||
return _ansible_type(data, alias)
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'reveal_ansible_type': reveal_ansible_type
|
||||
}
|
||||
@@ -329,9 +329,8 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
data = json['data']
|
||||
break
|
||||
else:
|
||||
if json['data']:
|
||||
# /hosts 's 'results' is a list of all hosts, returned is paginated
|
||||
data = data + 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
|
||||
|
||||
@@ -14,6 +14,8 @@ DOCUMENTATION = '''
|
||||
- Get inventory hosts from the local virtualbox installation.
|
||||
- Uses a YAML configuration file that ends with virtualbox.(yml|yaml) or vbox.(yml|yaml).
|
||||
- The inventory_hostname is always the 'Name' of the virtualbox instance.
|
||||
- Groups can be assigned to the VMs using C(VBoxManage). Multiple groups can be assigned by using V(/) as a delimeter.
|
||||
- A separate parameter, O(enable_advanced_group_parsing) is exposed to change grouping behaviour. See the parameter documentation for details.
|
||||
extends_documentation_fragment:
|
||||
- constructed
|
||||
- inventory_cache
|
||||
@@ -35,6 +37,19 @@ DOCUMENTATION = '''
|
||||
description: create vars from virtualbox properties
|
||||
type: dictionary
|
||||
default: {}
|
||||
enable_advanced_group_parsing:
|
||||
description:
|
||||
- The default group parsing rule (when this setting is set to V(false)) is to split the VirtualBox VM's group based on the V(/) character and
|
||||
assign the resulting list elements as an Ansible Group.
|
||||
- Setting O(enable_advanced_group_parsing=true) changes this behaviour to match VirtualBox's interpretation of groups according to
|
||||
U(https://www.virtualbox.org/manual/UserManual.html#gui-vmgroups).
|
||||
Groups are now split using the V(,) character, and the V(/) character indicates nested groups.
|
||||
- When enabled, a VM that's been configured using V(VBoxManage modifyvm "vm01" --groups "/TestGroup/TestGroup2,/TestGroup3") will result in
|
||||
the group C(TestGroup2) being a child group of C(TestGroup); and
|
||||
the VM being a part of C(TestGroup2) and C(TestGroup3).
|
||||
default: false
|
||||
type: bool
|
||||
version_added: 9.2.0
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
@@ -177,14 +192,10 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
# found groups
|
||||
elif k == 'Groups':
|
||||
for group in v.split('/'):
|
||||
if group:
|
||||
group = make_unsafe(group)
|
||||
group = self.inventory.add_group(group)
|
||||
self.inventory.add_child(group, current_host)
|
||||
if group not in cacheable_results:
|
||||
cacheable_results[group] = {'hosts': []}
|
||||
cacheable_results[group]['hosts'].append(current_host)
|
||||
if self.get_option('enable_advanced_group_parsing'):
|
||||
self._handle_vboxmanage_group_string(v, current_host, cacheable_results)
|
||||
else:
|
||||
self._handle_group_string(v, current_host, cacheable_results)
|
||||
continue
|
||||
|
||||
else:
|
||||
@@ -227,6 +238,64 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
return all(find_host(host, inventory))
|
||||
|
||||
def _handle_group_string(self, vboxmanage_group, current_host, cacheable_results):
|
||||
'''Handles parsing the VM's Group assignment from VBoxManage according to this inventory's initial implementation.'''
|
||||
# The original implementation of this inventory plugin treated `/` as
|
||||
# a delimeter to split and use as Ansible Groups.
|
||||
for group in vboxmanage_group.split('/'):
|
||||
if group:
|
||||
group = make_unsafe(group)
|
||||
group = self.inventory.add_group(group)
|
||||
self.inventory.add_child(group, current_host)
|
||||
if group not in cacheable_results:
|
||||
cacheable_results[group] = {'hosts': []}
|
||||
cacheable_results[group]['hosts'].append(current_host)
|
||||
|
||||
def _handle_vboxmanage_group_string(self, vboxmanage_group, current_host, cacheable_results):
|
||||
'''Handles parsing the VM's Group assignment from VBoxManage according to VirtualBox documentation.'''
|
||||
# Per the VirtualBox documentation, a VM can be part of many groups,
|
||||
# and it's possible to have nested groups.
|
||||
# Many groups are separated by commas ",", and nested groups use
|
||||
# slash "/".
|
||||
# https://www.virtualbox.org/manual/UserManual.html#gui-vmgroups
|
||||
# Multi groups: VBoxManage modifyvm "vm01" --groups "/TestGroup,/TestGroup2"
|
||||
# Nested groups: VBoxManage modifyvm "vm01" --groups "/TestGroup/TestGroup2"
|
||||
|
||||
for group in vboxmanage_group.split(','):
|
||||
if not group:
|
||||
# We could get an empty element due how to split works, and
|
||||
# possible assignments from VirtualBox. e.g. ,/Group1
|
||||
continue
|
||||
|
||||
if group == "/":
|
||||
# This is the "root" group. We get here if the VM was not
|
||||
# assigned to a particular group. Consider the host to be
|
||||
# unassigned to a group.
|
||||
continue
|
||||
|
||||
parent_group = None
|
||||
for subgroup in group.split('/'):
|
||||
if not subgroup:
|
||||
# Similarly to above, we could get an empty element.
|
||||
# e.g //Group1
|
||||
continue
|
||||
|
||||
if subgroup == '/':
|
||||
# "root" group.
|
||||
# Consider the host to be unassigned
|
||||
continue
|
||||
|
||||
subgroup = make_unsafe(subgroup)
|
||||
subgroup = self.inventory.add_group(subgroup)
|
||||
if parent_group is not None:
|
||||
self.inventory.add_child(parent_group, subgroup)
|
||||
self.inventory.add_child(subgroup, current_host)
|
||||
if subgroup not in cacheable_results:
|
||||
cacheable_results[subgroup] = {'hosts': []}
|
||||
cacheable_results[subgroup]['hosts'].append(current_host)
|
||||
|
||||
parent_group = subgroup
|
||||
|
||||
def verify_file(self, path):
|
||||
|
||||
valid = False
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -330,7 +330,6 @@ 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')
|
||||
@@ -346,6 +345,7 @@ 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,6 +358,7 @@ 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:
|
||||
@@ -396,9 +397,6 @@ class LookupModule(LookupBase):
|
||||
|
||||
# print "--- domain = {0} qtype={1} rdclass={2}".format(domain, qtype, rdclass)
|
||||
|
||||
if len(nameservers) > 0:
|
||||
myres.nameservers = nameservers
|
||||
|
||||
if qtype.upper() == 'PTR':
|
||||
reversed_domains = []
|
||||
for domain in domains:
|
||||
|
||||
@@ -468,8 +468,7 @@ class LookupModule(LookupBase):
|
||||
def opt_lock(self, type):
|
||||
if self.get_option('lock') == type:
|
||||
tmpdir = os.environ.get('TMPDIR', '/tmp')
|
||||
user = os.environ.get('USER')
|
||||
lockfile = os.path.join(tmpdir, '.{0}.passwordstore.lock'.format(user))
|
||||
lockfile = os.path.join(tmpdir, '.passwordstore.lock')
|
||||
with FileLock().lock_file(lockfile, tmpdir, self.lock_timeout):
|
||||
self.locked = type
|
||||
yield
|
||||
|
||||
@@ -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
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
var: query('community.general.random_string', upper=false, numbers=false, special=false)
|
||||
# Example result: ['exolxzyz']
|
||||
|
||||
- name: Generate random hexadecimal string
|
||||
ansible.builtin.debug:
|
||||
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
|
||||
ansible.builtin.debug:
|
||||
debug:
|
||||
var: query('community.general.random_string', override_all=hex_chars)
|
||||
vars:
|
||||
hex_chars: '0123456789ABCDEF'
|
||||
|
||||
@@ -11,6 +11,7 @@ from functools import wraps
|
||||
|
||||
from ansible.module_utils.common.collections import is_sequence
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils.common.locale import get_best_parsable_locale
|
||||
|
||||
|
||||
def _ensure_list(value):
|
||||
@@ -89,18 +90,31 @@ class FormatError(CmdRunnerException):
|
||||
|
||||
|
||||
class _ArgFormat(object):
|
||||
# DEPRECATION: set default value for ignore_none to True in community.general 12.0.0
|
||||
def __init__(self, func, ignore_none=None, ignore_missing_value=False):
|
||||
self.func = func
|
||||
self.ignore_none = ignore_none
|
||||
self.ignore_missing_value = ignore_missing_value
|
||||
|
||||
def __call__(self, value, ctx_ignore_none):
|
||||
# DEPRECATION: remove parameter ctx_ignore_none in community.general 12.0.0
|
||||
def __call__(self, value, ctx_ignore_none=True):
|
||||
# DEPRECATION: replace ctx_ignore_none with True in community.general 12.0.0
|
||||
ignore_none = self.ignore_none if self.ignore_none is not None else ctx_ignore_none
|
||||
if value is None and ignore_none:
|
||||
return []
|
||||
f = self.func
|
||||
return [str(x) for x in f(value)]
|
||||
|
||||
def __str__(self):
|
||||
return "<ArgFormat: func={0}, ignore_none={1}, ignore_missing_value={2}>".format(
|
||||
self.func,
|
||||
self.ignore_none,
|
||||
self.ignore_missing_value,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class _Format(object):
|
||||
@staticmethod
|
||||
@@ -114,7 +128,7 @@ class _Format(object):
|
||||
|
||||
@staticmethod
|
||||
def as_bool_not(args):
|
||||
return _ArgFormat(lambda value: [] if value else _ensure_list(args), ignore_none=False)
|
||||
return _Format.as_bool([], args, ignore_none=False)
|
||||
|
||||
@staticmethod
|
||||
def as_optval(arg, ignore_none=None):
|
||||
@@ -129,8 +143,15 @@ class _Format(object):
|
||||
return _ArgFormat(lambda value: ["{0}={1}".format(arg, value)], ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_list(ignore_none=None):
|
||||
return _ArgFormat(_ensure_list, ignore_none=ignore_none)
|
||||
def as_list(ignore_none=None, min_len=0, max_len=None):
|
||||
def func(value):
|
||||
value = _ensure_list(value)
|
||||
if len(value) < min_len:
|
||||
raise ValueError("Parameter must have at least {0} element(s)".format(min_len))
|
||||
if max_len is not None and len(value) > max_len:
|
||||
raise ValueError("Parameter must have at most {0} element(s)".format(max_len))
|
||||
return value
|
||||
return _ArgFormat(func, ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_fixed(args):
|
||||
@@ -177,6 +198,19 @@ class _Format(object):
|
||||
return func(**v)
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def stack(fmt):
|
||||
@wraps(fmt)
|
||||
def wrapper(*args, **kwargs):
|
||||
new_func = fmt(ignore_none=True, *args, **kwargs)
|
||||
|
||||
def stacking(value):
|
||||
stack = [new_func(v) for v in value if v]
|
||||
stack = [x for args in stack for x in args]
|
||||
return stack
|
||||
return _ArgFormat(stacking, ignore_none=True)
|
||||
return wrapper
|
||||
|
||||
|
||||
class CmdRunner(object):
|
||||
"""
|
||||
@@ -197,9 +231,19 @@ class CmdRunner(object):
|
||||
self.default_args_order = self._prepare_args_order(default_args_order)
|
||||
if arg_formats is None:
|
||||
arg_formats = {}
|
||||
self.arg_formats = dict(arg_formats)
|
||||
self.arg_formats = {}
|
||||
for fmt_name, fmt in arg_formats.items():
|
||||
if not isinstance(fmt, _ArgFormat):
|
||||
fmt = _Format.as_func(func=fmt, ignore_none=True)
|
||||
self.arg_formats[fmt_name] = fmt
|
||||
self.check_rc = check_rc
|
||||
self.force_lang = force_lang
|
||||
if force_lang == "auto":
|
||||
try:
|
||||
self.force_lang = get_best_parsable_locale()
|
||||
except RuntimeWarning:
|
||||
self.force_lang = "C"
|
||||
else:
|
||||
self.force_lang = force_lang
|
||||
self.path_prefix = path_prefix
|
||||
if environ_update is None:
|
||||
environ_update = {}
|
||||
@@ -216,7 +260,16 @@ class CmdRunner(object):
|
||||
def binary(self):
|
||||
return self.command[0]
|
||||
|
||||
def __call__(self, args_order=None, output_process=None, ignore_value_none=True, check_mode_skip=False, check_mode_return=None, **kwargs):
|
||||
# remove parameter ignore_value_none in community.general 12.0.0
|
||||
def __call__(self, args_order=None, output_process=None, ignore_value_none=None, check_mode_skip=False, check_mode_return=None, **kwargs):
|
||||
if ignore_value_none is None:
|
||||
ignore_value_none = True
|
||||
else:
|
||||
self.module.deprecate(
|
||||
"Using ignore_value_none when creating the runner context is now deprecated, "
|
||||
"and the parameter will be removed in community.general 12.0.0. ",
|
||||
version="12.0.0", collection_name="community.general"
|
||||
)
|
||||
if output_process is None:
|
||||
output_process = _process_as_is
|
||||
if args_order is None:
|
||||
@@ -228,7 +281,7 @@ class CmdRunner(object):
|
||||
return _CmdRunnerContext(runner=self,
|
||||
args_order=args_order,
|
||||
output_process=output_process,
|
||||
ignore_value_none=ignore_value_none,
|
||||
ignore_value_none=ignore_value_none, # DEPRECATION: remove in community.general 12.0.0
|
||||
check_mode_skip=check_mode_skip,
|
||||
check_mode_return=check_mode_return, **kwargs)
|
||||
|
||||
@@ -244,6 +297,7 @@ class _CmdRunnerContext(object):
|
||||
self.runner = runner
|
||||
self.args_order = tuple(args_order)
|
||||
self.output_process = output_process
|
||||
# DEPRECATION: parameter ignore_value_none at the context level is deprecated and will be removed in community.general 12.0.0
|
||||
self.ignore_value_none = ignore_value_none
|
||||
self.check_mode_skip = check_mode_skip
|
||||
self.check_mode_return = check_mode_return
|
||||
@@ -283,6 +337,7 @@ class _CmdRunnerContext(object):
|
||||
value = named_args[arg_name]
|
||||
elif not runner.arg_formats[arg_name].ignore_missing_value:
|
||||
raise MissingArgumentValue(self.args_order, arg_name)
|
||||
# DEPRECATION: remove parameter ctx_ignore_none in 12.0.0
|
||||
self.cmd.extend(runner.arg_formats[arg_name](value, ctx_ignore_none=self.ignore_value_none))
|
||||
except MissingArgumentValue:
|
||||
raise
|
||||
@@ -299,7 +354,7 @@ class _CmdRunnerContext(object):
|
||||
@property
|
||||
def run_info(self):
|
||||
return dict(
|
||||
ignore_value_none=self.ignore_value_none,
|
||||
ignore_value_none=self.ignore_value_none, # DEPRECATION: remove in community.general 12.0.0
|
||||
check_rc=self.check_rc,
|
||||
environ_update=self.environ_update,
|
||||
args_order=self.args_order,
|
||||
|
||||
@@ -10,6 +10,7 @@ __metaclass__ = type
|
||||
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
|
||||
from ansible.module_utils.six.moves.urllib import error as urllib_error
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
@@ -68,6 +69,25 @@ def camel_case_key(key):
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def validate_check(check):
|
||||
validate_duration_keys = ['Interval', 'Ttl', 'Timeout']
|
||||
validate_tcp_regex = r"(?P<host>.*):(?P<port>(?:[0-9]+))$"
|
||||
if check.get('Tcp') is not None:
|
||||
match = re.match(validate_tcp_regex, check['Tcp'])
|
||||
if not match:
|
||||
raise Exception('tcp check must be in host:port format')
|
||||
for duration in validate_duration_keys:
|
||||
if duration in check and check[duration] is not None:
|
||||
check[duration] = validate_duration(check[duration])
|
||||
|
||||
|
||||
def validate_duration(duration):
|
||||
if duration:
|
||||
if not re.search(r"\d+(?:ns|us|ms|s|m|h)", duration):
|
||||
duration = "{0}s".format(duration)
|
||||
return duration
|
||||
|
||||
|
||||
STATE_PARAMETER = "state"
|
||||
STATE_PRESENT = "present"
|
||||
STATE_ABSENT = "absent"
|
||||
@@ -81,7 +101,7 @@ OPERATION_DELETE = "remove"
|
||||
def _normalize_params(params, arg_spec):
|
||||
final_params = {}
|
||||
for k, v in params.items():
|
||||
if k not in arg_spec: # Alias
|
||||
if k not in arg_spec or v is None: # Alias
|
||||
continue
|
||||
spec = arg_spec[k]
|
||||
if (
|
||||
@@ -105,9 +125,10 @@ class _ConsulModule:
|
||||
"""
|
||||
|
||||
api_endpoint = None # type: str
|
||||
unique_identifier = None # type: str
|
||||
unique_identifiers = None # type: list
|
||||
result_key = None # type: str
|
||||
create_only_fields = set()
|
||||
operational_attributes = set()
|
||||
params = {}
|
||||
|
||||
def __init__(self, module):
|
||||
@@ -119,6 +140,8 @@ class _ConsulModule:
|
||||
if k not in STATE_PARAMETER and k not in AUTH_ARGUMENTS_SPEC
|
||||
}
|
||||
|
||||
self.operational_attributes.update({"CreateIndex", "CreateTime", "Hash", "ModifyIndex"})
|
||||
|
||||
def execute(self):
|
||||
obj = self.read_object()
|
||||
|
||||
@@ -203,14 +226,24 @@ class _ConsulModule:
|
||||
return False
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
operational_attributes = {"CreateIndex", "CreateTime", "Hash", "ModifyIndex"}
|
||||
existing = {
|
||||
k: v for k, v in existing.items() if k not in operational_attributes
|
||||
k: v for k, v in existing.items() if k not in self.operational_attributes
|
||||
}
|
||||
for k, v in obj.items():
|
||||
existing[k] = v
|
||||
return existing
|
||||
|
||||
def id_from_obj(self, obj, camel_case=False):
|
||||
def key_func(key):
|
||||
return camel_case_key(key) if camel_case else key
|
||||
|
||||
if self.unique_identifiers:
|
||||
for identifier in self.unique_identifiers:
|
||||
identifier = key_func(identifier)
|
||||
if identifier in obj:
|
||||
return obj[identifier]
|
||||
return None
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_CREATE:
|
||||
return self.api_endpoint
|
||||
@@ -219,7 +252,8 @@ class _ConsulModule:
|
||||
raise RuntimeError("invalid arguments passed")
|
||||
|
||||
def read_object(self):
|
||||
url = self.endpoint_url(OPERATION_READ, self.params.get(self.unique_identifier))
|
||||
identifier = self.id_from_obj(self.params)
|
||||
url = self.endpoint_url(OPERATION_READ, identifier)
|
||||
try:
|
||||
return self.get(url)
|
||||
except RequestError as e:
|
||||
@@ -233,25 +267,28 @@ class _ConsulModule:
|
||||
if self._module.check_mode:
|
||||
return obj
|
||||
else:
|
||||
return self.put(self.api_endpoint, data=self.prepare_object({}, obj))
|
||||
url = self.endpoint_url(OPERATION_CREATE)
|
||||
created_obj = self.put(url, data=self.prepare_object({}, obj))
|
||||
if created_obj is None:
|
||||
created_obj = self.read_object()
|
||||
return created_obj
|
||||
|
||||
def update_object(self, existing, obj):
|
||||
url = self.endpoint_url(
|
||||
OPERATION_UPDATE, existing.get(camel_case_key(self.unique_identifier))
|
||||
)
|
||||
merged_object = self.prepare_object(existing, obj)
|
||||
if self._module.check_mode:
|
||||
return merged_object
|
||||
else:
|
||||
return self.put(url, data=merged_object)
|
||||
url = self.endpoint_url(OPERATION_UPDATE, self.id_from_obj(existing, camel_case=True))
|
||||
updated_obj = self.put(url, data=merged_object)
|
||||
if updated_obj is None:
|
||||
updated_obj = self.read_object()
|
||||
return updated_obj
|
||||
|
||||
def delete_object(self, obj):
|
||||
if self._module.check_mode:
|
||||
return {}
|
||||
else:
|
||||
url = self.endpoint_url(
|
||||
OPERATION_DELETE, obj.get(camel_case_key(self.unique_identifier))
|
||||
)
|
||||
url = self.endpoint_url(OPERATION_DELETE, self.id_from_obj(obj, camel_case=True))
|
||||
return self.delete(url)
|
||||
|
||||
def _request(self, method, url_parts, data=None, params=None):
|
||||
@@ -309,7 +346,9 @@ class _ConsulModule:
|
||||
if 400 <= status < 600:
|
||||
raise RequestError(status, response_data)
|
||||
|
||||
return json.loads(response_data)
|
||||
if response_data:
|
||||
return json.loads(response_data)
|
||||
return None
|
||||
|
||||
def get(self, url_parts, **kwargs):
|
||||
return self._request("GET", url_parts, **kwargs)
|
||||
|
||||
116
plugins/module_utils/django.py
Normal file
116
plugins/module_utils/django.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import cmd_runner_fmt
|
||||
from ansible_collections.community.general.plugins.module_utils.python_runner import PythonRunner
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
|
||||
|
||||
django_std_args = dict(
|
||||
# environmental options
|
||||
venv=dict(type="path"),
|
||||
# default options of django-admin
|
||||
settings=dict(type="str", required=True),
|
||||
pythonpath=dict(type="path"),
|
||||
traceback=dict(type="bool"),
|
||||
verbosity=dict(type="int", choices=[0, 1, 2, 3]),
|
||||
skip_checks=dict(type="bool"),
|
||||
)
|
||||
|
||||
_django_std_arg_fmts = dict(
|
||||
command=cmd_runner_fmt.as_list(),
|
||||
settings=cmd_runner_fmt.as_opt_eq_val("--settings"),
|
||||
pythonpath=cmd_runner_fmt.as_opt_eq_val("--pythonpath"),
|
||||
traceback=cmd_runner_fmt.as_bool("--traceback"),
|
||||
verbosity=cmd_runner_fmt.as_opt_val("--verbosity"),
|
||||
no_color=cmd_runner_fmt.as_fixed("--no-color"),
|
||||
skip_checks=cmd_runner_fmt.as_bool("--skip-checks"),
|
||||
)
|
||||
|
||||
_django_database_args = dict(
|
||||
database=dict(type="str", default="default"),
|
||||
)
|
||||
|
||||
_args_menu = dict(
|
||||
std=(django_std_args, _django_std_arg_fmts),
|
||||
database=(_django_database_args, {"database": cmd_runner_fmt.as_opt_eq_val("--database")}),
|
||||
noinput=({}, {"noinput": cmd_runner_fmt.as_fixed("--noinput")}),
|
||||
dry_run=({}, {"dry_run": cmd_runner_fmt.as_bool("--dry-run")}),
|
||||
check=({}, {"check": cmd_runner_fmt.as_bool("--check")}),
|
||||
)
|
||||
|
||||
|
||||
class _DjangoRunner(PythonRunner):
|
||||
def __init__(self, module, arg_formats=None, **kwargs):
|
||||
arg_fmts = dict(arg_formats) if arg_formats else {}
|
||||
arg_fmts.update(_django_std_arg_fmts)
|
||||
|
||||
super(_DjangoRunner, self).__init__(module, ["-m", "django"], arg_formats=arg_fmts, **kwargs)
|
||||
|
||||
def __call__(self, output_process=None, ignore_value_none=True, check_mode_skip=False, check_mode_return=None, **kwargs):
|
||||
args_order = (
|
||||
("command", "no_color", "settings", "pythonpath", "traceback", "verbosity", "skip_checks") + self._prepare_args_order(self.default_args_order)
|
||||
)
|
||||
return super(_DjangoRunner, self).__call__(args_order, output_process, ignore_value_none, check_mode_skip, check_mode_return, **kwargs)
|
||||
|
||||
|
||||
class DjangoModuleHelper(ModuleHelper):
|
||||
module = {}
|
||||
use_old_vardict = False
|
||||
django_admin_cmd = None
|
||||
arg_formats = {}
|
||||
django_admin_arg_order = ()
|
||||
use_old_vardict = False
|
||||
_django_args = []
|
||||
_check_mode_arg = ""
|
||||
|
||||
def __init__(self):
|
||||
self.module["argument_spec"], self.arg_formats = self._build_args(self.module.get("argument_spec", {}),
|
||||
self.arg_formats,
|
||||
*(["std"] + self._django_args))
|
||||
super(DjangoModuleHelper, self).__init__(self.module)
|
||||
if self.django_admin_cmd is not None:
|
||||
self.vars.command = self.django_admin_cmd
|
||||
|
||||
@staticmethod
|
||||
def _build_args(arg_spec, arg_format, *names):
|
||||
res_arg_spec = {}
|
||||
res_arg_fmts = {}
|
||||
for name in names:
|
||||
args, fmts = _args_menu[name]
|
||||
res_arg_spec = dict_merge(res_arg_spec, args)
|
||||
res_arg_fmts = dict_merge(res_arg_fmts, fmts)
|
||||
res_arg_spec = dict_merge(res_arg_spec, arg_spec)
|
||||
res_arg_fmts = dict_merge(res_arg_fmts, arg_format)
|
||||
|
||||
return res_arg_spec, res_arg_fmts
|
||||
|
||||
def __run__(self):
|
||||
runner = _DjangoRunner(self.module,
|
||||
default_args_order=self.django_admin_arg_order,
|
||||
arg_formats=self.arg_formats,
|
||||
venv=self.vars.venv,
|
||||
check_rc=True)
|
||||
with runner() as ctx:
|
||||
run_params = self.vars.as_dict()
|
||||
if self._check_mode_arg:
|
||||
run_params.update({self._check_mode_arg: self.check_mode})
|
||||
results = ctx.run(**run_params)
|
||||
self.vars.stdout = ctx.results_out
|
||||
self.vars.stderr = ctx.results_err
|
||||
self.vars.cmd = ctx.cmd
|
||||
if self.verbosity >= 3:
|
||||
self.vars.run_info = ctx.run_info
|
||||
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def execute(cls):
|
||||
cls().run()
|
||||
@@ -33,6 +33,7 @@ class GandiLiveDNSAPI(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.api_key = module.params['api_key']
|
||||
self.personal_access_token = module.params['personal_access_token']
|
||||
|
||||
def _build_error_message(self, module, info):
|
||||
s = ''
|
||||
@@ -50,7 +51,12 @@ class GandiLiveDNSAPI(object):
|
||||
return s
|
||||
|
||||
def _gandi_api_call(self, api_call, method='GET', payload=None, error_on_404=True):
|
||||
headers = {'Authorization': 'Apikey {0}'.format(self.api_key),
|
||||
authorization_header = (
|
||||
'Bearer {0}'.format(self.personal_access_token)
|
||||
if self.personal_access_token
|
||||
else 'Apikey {0}'.format(self.api_key)
|
||||
)
|
||||
headers = {'Authorization': authorization_header,
|
||||
'Content-Type': 'application/json'}
|
||||
data = None
|
||||
if payload:
|
||||
|
||||
@@ -115,6 +115,11 @@ def gitlab_authentication(module, min_version=None):
|
||||
# Changelog : https://github.com/python-gitlab/python-gitlab/releases/tag/v1.13.0
|
||||
# This condition allow to still support older version of the python-gitlab library
|
||||
if LooseVersion(gitlab.__version__) < LooseVersion("1.13.0"):
|
||||
module.deprecate(
|
||||
"GitLab basic auth is deprecated and will be removed in next major version, "
|
||||
"using another auth method (API token or OAuth) is strongly recommended.",
|
||||
version='10.0.0',
|
||||
collection_name='community.general')
|
||||
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=verify, email=gitlab_user, password=gitlab_password,
|
||||
private_token=gitlab_token, api_version=4)
|
||||
else:
|
||||
|
||||
115
plugins/module_utils/homebrew.py
Normal file
115
plugins/module_utils/homebrew.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) Ansible project
|
||||
# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import re
|
||||
from ansible.module_utils.six import string_types
|
||||
|
||||
|
||||
def _create_regex_group_complement(s):
|
||||
lines = (line.strip() for line in s.split("\n") if line.strip())
|
||||
chars = filter(None, (line.split("#")[0].strip() for line in lines))
|
||||
group = r"[^" + r"".join(chars) + r"]"
|
||||
return re.compile(group)
|
||||
|
||||
|
||||
class HomebrewValidate(object):
|
||||
# class regexes ------------------------------------------------ {{{
|
||||
VALID_PATH_CHARS = r"""
|
||||
\w # alphanumeric characters (i.e., [a-zA-Z0-9_])
|
||||
\s # spaces
|
||||
: # colons
|
||||
{sep} # the OS-specific path separator
|
||||
. # dots
|
||||
\- # dashes
|
||||
""".format(
|
||||
sep=os.path.sep
|
||||
)
|
||||
|
||||
VALID_BREW_PATH_CHARS = r"""
|
||||
\w # alphanumeric characters (i.e., [a-zA-Z0-9_])
|
||||
\s # spaces
|
||||
{sep} # the OS-specific path separator
|
||||
. # dots
|
||||
\- # dashes
|
||||
""".format(
|
||||
sep=os.path.sep
|
||||
)
|
||||
|
||||
VALID_PACKAGE_CHARS = r"""
|
||||
\w # alphanumeric characters (i.e., [a-zA-Z0-9_])
|
||||
. # dots
|
||||
/ # slash (for taps)
|
||||
\+ # plusses
|
||||
\- # dashes
|
||||
: # colons (for URLs)
|
||||
@ # at-sign
|
||||
"""
|
||||
|
||||
INVALID_PATH_REGEX = _create_regex_group_complement(VALID_PATH_CHARS)
|
||||
INVALID_BREW_PATH_REGEX = _create_regex_group_complement(VALID_BREW_PATH_CHARS)
|
||||
INVALID_PACKAGE_REGEX = _create_regex_group_complement(VALID_PACKAGE_CHARS)
|
||||
# /class regexes ----------------------------------------------- }}}
|
||||
|
||||
# class validations -------------------------------------------- {{{
|
||||
@classmethod
|
||||
def valid_path(cls, path):
|
||||
"""
|
||||
`path` must be one of:
|
||||
- list of paths
|
||||
- a string containing only:
|
||||
- alphanumeric characters
|
||||
- dashes
|
||||
- dots
|
||||
- spaces
|
||||
- colons
|
||||
- os.path.sep
|
||||
"""
|
||||
|
||||
if isinstance(path, string_types):
|
||||
return not cls.INVALID_PATH_REGEX.search(path)
|
||||
|
||||
try:
|
||||
iter(path)
|
||||
except TypeError:
|
||||
return False
|
||||
else:
|
||||
paths = path
|
||||
return all(cls.valid_brew_path(path_) for path_ in paths)
|
||||
|
||||
@classmethod
|
||||
def valid_brew_path(cls, brew_path):
|
||||
"""
|
||||
`brew_path` must be one of:
|
||||
- None
|
||||
- a string containing only:
|
||||
- alphanumeric characters
|
||||
- dashes
|
||||
- dots
|
||||
- spaces
|
||||
- os.path.sep
|
||||
"""
|
||||
|
||||
if brew_path is None:
|
||||
return True
|
||||
|
||||
return isinstance(
|
||||
brew_path, string_types
|
||||
) and not cls.INVALID_BREW_PATH_REGEX.search(brew_path)
|
||||
|
||||
@classmethod
|
||||
def valid_package(cls, package):
|
||||
"""A valid package is either None or alphanumeric."""
|
||||
|
||||
if package is None:
|
||||
return True
|
||||
|
||||
return isinstance(
|
||||
package, string_types
|
||||
) and not cls.INVALID_PACKAGE_REGEX.search(package)
|
||||
@@ -29,7 +29,6 @@ class iLORedfishUtils(RedfishUtils):
|
||||
result['ret'] = True
|
||||
data = response['data']
|
||||
|
||||
current_session = None
|
||||
if 'Oem' in data:
|
||||
if data["Oem"]["Hpe"]["Links"]["MySession"]["@odata.id"]:
|
||||
current_session = data["Oem"]["Hpe"]["Links"]["MySession"]["@odata.id"]
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import ModuleHelperBase
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.deco import module_fails_on_exception
|
||||
|
||||
|
||||
class DependencyCtxMgr(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use plugins/module_utils/deps.py instead.
|
||||
"""
|
||||
def __init__(self, name, msg=None):
|
||||
self.name = name
|
||||
self.msg = msg
|
||||
@@ -35,39 +36,3 @@ class DependencyCtxMgr(object):
|
||||
@property
|
||||
def text(self):
|
||||
return self.msg or str(self.exc_val)
|
||||
|
||||
|
||||
class DependencyMixin(ModuleHelperBase):
|
||||
"""
|
||||
THIS CLASS IS BEING DEPRECATED.
|
||||
See the deprecation notice in ``DependencyMixin.fail_on_missing_deps()`` below.
|
||||
|
||||
Mixin for mapping module options to running a CLI command with its arguments.
|
||||
"""
|
||||
_dependencies = []
|
||||
|
||||
@classmethod
|
||||
def dependency(cls, name, msg):
|
||||
cls._dependencies.append(DependencyCtxMgr(name, msg))
|
||||
return cls._dependencies[-1]
|
||||
|
||||
def fail_on_missing_deps(self):
|
||||
if not self._dependencies:
|
||||
return
|
||||
self.module.deprecate(
|
||||
'The DependencyMixin is being deprecated. '
|
||||
'Modules should use community.general.plugins.module_utils.deps instead.',
|
||||
version='9.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
for d in self._dependencies:
|
||||
if not d.has_it:
|
||||
self.module.fail_json(changed=False,
|
||||
exception="\n".join(traceback.format_exception(d.exc_type, d.exc_val, d.exc_tb)),
|
||||
msg=d.text,
|
||||
**self.output)
|
||||
|
||||
@module_fails_on_exception
|
||||
def run(self):
|
||||
self.fail_on_missing_deps()
|
||||
super(DependencyMixin, self).run()
|
||||
|
||||
@@ -14,7 +14,7 @@ class VarMeta(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 10.0.0
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use the VarDict from plugins/module_utils/vardict.py instead.
|
||||
"""
|
||||
|
||||
@@ -70,7 +70,7 @@ class VarDict(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 10.0.0
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use the VarDict from plugins/module_utils/vardict.py instead.
|
||||
"""
|
||||
def __init__(self):
|
||||
@@ -139,7 +139,7 @@ class VarsMixin(object):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 10.0.0
|
||||
This class is deprecated and will be removed in community.general 11.0.0
|
||||
Modules should use the VarDict from plugins/module_utils/vardict.py instead.
|
||||
"""
|
||||
def __init__(self, module=None):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright (c) 2020, Ansible Project
|
||||
# (c) 2020-2024, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright (c) 2020-2024, Ansible Project
|
||||
# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
@@ -10,23 +10,40 @@ __metaclass__ = type
|
||||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.vardict import VarDict as _NewVarDict # remove "as NewVarDict" in 11.0.0
|
||||
# (TODO: remove AnsibleModule!) pylint: disable-next=unused-import
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import ModuleHelperBase, AnsibleModule # noqa: F401
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import AnsibleModule # noqa: F401 DEPRECATED, remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import ModuleHelperBase
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.state import StateMixin
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deps import DependencyMixin
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarsMixin
|
||||
# (TODO: remove mh.mixins.vars!) pylint: disable-next=unused-import
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarsMixin, VarDict as _OldVarDict # noqa: F401 remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deprecate_attrs import DeprecateAttrsMixin
|
||||
|
||||
|
||||
class ModuleHelper(DeprecateAttrsMixin, VarsMixin, DependencyMixin, ModuleHelperBase):
|
||||
class ModuleHelper(DeprecateAttrsMixin, ModuleHelperBase):
|
||||
facts_name = None
|
||||
output_params = ()
|
||||
diff_params = ()
|
||||
change_params = ()
|
||||
facts_params = ()
|
||||
use_old_vardict = True # remove in 11.0.0
|
||||
mute_vardict_deprecation = False
|
||||
|
||||
def __init__(self, module=None):
|
||||
super(ModuleHelper, self).__init__(module)
|
||||
if self.use_old_vardict: # remove first half of the if in 11.0.0
|
||||
self.vars = _OldVarDict()
|
||||
super(ModuleHelper, self).__init__(module)
|
||||
if not self.mute_vardict_deprecation:
|
||||
self.module.deprecate(
|
||||
"This class is using the old VarDict from ModuleHelper, which is deprecated. "
|
||||
"Set the class variable use_old_vardict to False and make the necessary adjustments."
|
||||
"The old VarDict class will be removed in community.general 11.0.0",
|
||||
version="11.0.0", collection_name="community.general"
|
||||
)
|
||||
else:
|
||||
self.vars = _NewVarDict()
|
||||
super(ModuleHelper, self).__init__(module)
|
||||
|
||||
for name, value in self.module.params.items():
|
||||
self.vars.set(
|
||||
name, value,
|
||||
@@ -36,6 +53,12 @@ class ModuleHelper(DeprecateAttrsMixin, VarsMixin, DependencyMixin, ModuleHelper
|
||||
fact=name in self.facts_params,
|
||||
)
|
||||
|
||||
def update_vars(self, meta=None, **kwargs):
|
||||
if meta is None:
|
||||
meta = {}
|
||||
for k, v in kwargs.items():
|
||||
self.vars.set(k, v, **meta)
|
||||
|
||||
def update_output(self, **kwargs):
|
||||
self.update_vars(meta={"output": True}, **kwargs)
|
||||
|
||||
@@ -43,7 +66,10 @@ class ModuleHelper(DeprecateAttrsMixin, VarsMixin, DependencyMixin, ModuleHelper
|
||||
self.update_vars(meta={"fact": True}, **kwargs)
|
||||
|
||||
def _vars_changed(self):
|
||||
return any(self.vars.has_changed(v) for v in self.vars.change_vars())
|
||||
if self.use_old_vardict:
|
||||
return any(self.vars.has_changed(v) for v in self.vars.change_vars())
|
||||
|
||||
return self.vars.has_changed
|
||||
|
||||
def has_changed(self):
|
||||
return self.changed or self._vars_changed()
|
||||
|
||||
@@ -9,14 +9,14 @@ __metaclass__ = type
|
||||
|
||||
# pylint: disable=unused-import
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.module_helper import (
|
||||
ModuleHelper, StateModuleHelper, AnsibleModule
|
||||
ModuleHelper, StateModuleHelper,
|
||||
AnsibleModule # remove in 11.0.0
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.state import StateMixin # noqa: F401
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deps import DependencyCtxMgr, DependencyMixin # noqa: F401
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.state import StateMixin # noqa: F401 remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deps import DependencyCtxMgr # noqa: F401 remove in 11.0.0
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ModuleHelperException # noqa: F401
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.deco import (
|
||||
cause_changes, module_fails_on_exception, check_mode_skip, check_mode_skip_returns,
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarMeta, VarDict, VarsMixin # noqa: F401
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarMeta, VarDict, VarsMixin # noqa: F401 remove in 11.0.0
|
||||
|
||||
@@ -29,6 +29,9 @@ def proxmox_auth_argument_spec():
|
||||
required=True,
|
||||
fallback=(env_fallback, ['PROXMOX_HOST'])
|
||||
),
|
||||
api_port=dict(type='int',
|
||||
fallback=(env_fallback, ['PROXMOX_PORT'])
|
||||
),
|
||||
api_user=dict(type='str',
|
||||
required=True,
|
||||
fallback=(env_fallback, ['PROXMOX_USER'])
|
||||
@@ -82,6 +85,7 @@ class ProxmoxAnsible(object):
|
||||
|
||||
def _connect(self):
|
||||
api_host = self.module.params['api_host']
|
||||
api_port = self.module.params['api_port']
|
||||
api_user = self.module.params['api_user']
|
||||
api_password = self.module.params['api_password']
|
||||
api_token_id = self.module.params['api_token_id']
|
||||
@@ -89,6 +93,10 @@ class ProxmoxAnsible(object):
|
||||
validate_certs = self.module.params['validate_certs']
|
||||
|
||||
auth_args = {'user': api_user}
|
||||
|
||||
if api_port:
|
||||
auth_args['port'] = api_port
|
||||
|
||||
if api_password:
|
||||
auth_args['password'] = api_password
|
||||
else:
|
||||
|
||||
@@ -103,6 +103,7 @@ def puppet_runner(module):
|
||||
modulepath=cmd_runner_fmt.as_opt_eq_val("--modulepath"),
|
||||
_execute=cmd_runner_fmt.as_func(execute_func),
|
||||
summarize=cmd_runner_fmt.as_bool("--summarize"),
|
||||
waitforlock=cmd_runner_fmt.as_opt_val("--waitforlock"),
|
||||
debug=cmd_runner_fmt.as_bool("--debug"),
|
||||
verbose=cmd_runner_fmt.as_bool("--verbose"),
|
||||
),
|
||||
|
||||
34
plugins/module_utils/python_runner.py
Normal file
34
plugins/module_utils/python_runner.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# -*- 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
|
||||
|
||||
import os
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, _ensure_list
|
||||
|
||||
|
||||
class PythonRunner(CmdRunner):
|
||||
def __init__(self, module, command, arg_formats=None, default_args_order=(),
|
||||
check_rc=False, force_lang="C", path_prefix=None, environ_update=None,
|
||||
python="python", venv=None):
|
||||
self.python = python
|
||||
self.venv = venv
|
||||
self.has_venv = venv is not None
|
||||
|
||||
if (os.path.isabs(python) or '/' in python):
|
||||
self.python = python
|
||||
elif self.has_venv:
|
||||
path_prefix = os.path.join(venv, "bin")
|
||||
if environ_update is None:
|
||||
environ_update = {}
|
||||
environ_update["PATH"] = "%s:%s" % (path_prefix, os.environ["PATH"])
|
||||
environ_update["VIRTUAL_ENV"] = venv
|
||||
|
||||
python_cmd = [self.python] + _ensure_list(command)
|
||||
|
||||
super(PythonRunner, self).__init__(module, python_cmd, arg_formats, default_args_order,
|
||||
check_rc, force_lang, path_prefix, environ_update)
|
||||
@@ -1,334 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by
|
||||
# Ansible still belong to the author of the module, and may assign their own
|
||||
# license to the complete work.
|
||||
#
|
||||
# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
|
||||
#
|
||||
# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
from uuid import UUID
|
||||
|
||||
from ansible.module_utils.six import text_type, binary_type
|
||||
|
||||
FINAL_STATUSES = ('ACTIVE', 'ERROR')
|
||||
VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use',
|
||||
'error', 'error_deleting')
|
||||
|
||||
CLB_ALGORITHMS = ['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN',
|
||||
'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN']
|
||||
CLB_PROTOCOLS = ['DNS_TCP', 'DNS_UDP', 'FTP', 'HTTP', 'HTTPS', 'IMAPS',
|
||||
'IMAPv4', 'LDAP', 'LDAPS', 'MYSQL', 'POP3', 'POP3S', 'SMTP',
|
||||
'TCP', 'TCP_CLIENT_FIRST', 'UDP', 'UDP_STREAM', 'SFTP']
|
||||
|
||||
NON_CALLABLES = (text_type, binary_type, bool, dict, int, list, type(None))
|
||||
PUBLIC_NET_ID = "00000000-0000-0000-0000-000000000000"
|
||||
SERVICE_NET_ID = "11111111-1111-1111-1111-111111111111"
|
||||
|
||||
|
||||
def rax_slugify(value):
|
||||
"""Prepend a key with rax_ and normalize the key name"""
|
||||
return 'rax_%s' % (re.sub(r'[^\w-]', '_', value).lower().lstrip('_'))
|
||||
|
||||
|
||||
def rax_clb_node_to_dict(obj):
|
||||
"""Function to convert a CLB Node object to a dict"""
|
||||
if not obj:
|
||||
return {}
|
||||
node = obj.to_dict()
|
||||
node['id'] = obj.id
|
||||
node['weight'] = obj.weight
|
||||
return node
|
||||
|
||||
|
||||
def rax_to_dict(obj, obj_type='standard'):
|
||||
"""Generic function to convert a pyrax object to a dict
|
||||
|
||||
obj_type values:
|
||||
standard
|
||||
clb
|
||||
server
|
||||
|
||||
"""
|
||||
instance = {}
|
||||
for key in dir(obj):
|
||||
value = getattr(obj, key)
|
||||
if obj_type == 'clb' and key == 'nodes':
|
||||
instance[key] = []
|
||||
for node in value:
|
||||
instance[key].append(rax_clb_node_to_dict(node))
|
||||
elif (isinstance(value, list) and len(value) > 0 and
|
||||
not isinstance(value[0], NON_CALLABLES)):
|
||||
instance[key] = []
|
||||
for item in value:
|
||||
instance[key].append(rax_to_dict(item))
|
||||
elif (isinstance(value, NON_CALLABLES) and not key.startswith('_')):
|
||||
if obj_type == 'server':
|
||||
if key == 'image':
|
||||
if not value:
|
||||
instance['rax_boot_source'] = 'volume'
|
||||
else:
|
||||
instance['rax_boot_source'] = 'local'
|
||||
key = rax_slugify(key)
|
||||
instance[key] = value
|
||||
|
||||
if obj_type == 'server':
|
||||
for attr in ['id', 'accessIPv4', 'name', 'status']:
|
||||
instance[attr] = instance.get(rax_slugify(attr))
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
def rax_find_bootable_volume(module, rax_module, server, exit=True):
|
||||
"""Find a servers bootable volume"""
|
||||
cs = rax_module.cloudservers
|
||||
cbs = rax_module.cloud_blockstorage
|
||||
server_id = rax_module.utils.get_id(server)
|
||||
volumes = cs.volumes.get_server_volumes(server_id)
|
||||
bootable_volumes = []
|
||||
for volume in volumes:
|
||||
vol = cbs.get(volume)
|
||||
if module.boolean(vol.bootable):
|
||||
bootable_volumes.append(vol)
|
||||
if not bootable_volumes:
|
||||
if exit:
|
||||
module.fail_json(msg='No bootable volumes could be found for '
|
||||
'server %s' % server_id)
|
||||
else:
|
||||
return False
|
||||
elif len(bootable_volumes) > 1:
|
||||
if exit:
|
||||
module.fail_json(msg='Multiple bootable volumes found for server '
|
||||
'%s' % server_id)
|
||||
else:
|
||||
return False
|
||||
|
||||
return bootable_volumes[0]
|
||||
|
||||
|
||||
def rax_find_image(module, rax_module, image, exit=True):
|
||||
"""Find a server image by ID or Name"""
|
||||
cs = rax_module.cloudservers
|
||||
try:
|
||||
UUID(image)
|
||||
except ValueError:
|
||||
try:
|
||||
image = cs.images.find(human_id=image)
|
||||
except (cs.exceptions.NotFound, cs.exceptions.NoUniqueMatch):
|
||||
try:
|
||||
image = cs.images.find(name=image)
|
||||
except (cs.exceptions.NotFound,
|
||||
cs.exceptions.NoUniqueMatch):
|
||||
if exit:
|
||||
module.fail_json(msg='No matching image found (%s)' %
|
||||
image)
|
||||
else:
|
||||
return False
|
||||
|
||||
return rax_module.utils.get_id(image)
|
||||
|
||||
|
||||
def rax_find_volume(module, rax_module, name):
|
||||
"""Find a Block storage volume by ID or name"""
|
||||
cbs = rax_module.cloud_blockstorage
|
||||
try:
|
||||
UUID(name)
|
||||
volume = cbs.get(name)
|
||||
except ValueError:
|
||||
try:
|
||||
volume = cbs.find(name=name)
|
||||
except rax_module.exc.NotFound:
|
||||
volume = None
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e)
|
||||
return volume
|
||||
|
||||
|
||||
def rax_find_network(module, rax_module, network):
|
||||
"""Find a cloud network by ID or name"""
|
||||
cnw = rax_module.cloud_networks
|
||||
try:
|
||||
UUID(network)
|
||||
except ValueError:
|
||||
if network.lower() == 'public':
|
||||
return cnw.get_server_networks(PUBLIC_NET_ID)
|
||||
elif network.lower() == 'private':
|
||||
return cnw.get_server_networks(SERVICE_NET_ID)
|
||||
else:
|
||||
try:
|
||||
network_obj = cnw.find_network_by_label(network)
|
||||
except (rax_module.exceptions.NetworkNotFound,
|
||||
rax_module.exceptions.NetworkLabelNotUnique):
|
||||
module.fail_json(msg='No matching network found (%s)' %
|
||||
network)
|
||||
else:
|
||||
return cnw.get_server_networks(network_obj)
|
||||
else:
|
||||
return cnw.get_server_networks(network)
|
||||
|
||||
|
||||
def rax_find_server(module, rax_module, server):
|
||||
"""Find a Cloud Server by ID or name"""
|
||||
cs = rax_module.cloudservers
|
||||
try:
|
||||
UUID(server)
|
||||
server = cs.servers.get(server)
|
||||
except ValueError:
|
||||
servers = cs.servers.list(search_opts=dict(name='^%s$' % server))
|
||||
if not servers:
|
||||
module.fail_json(msg='No Server was matched by name, '
|
||||
'try using the Server ID instead')
|
||||
if len(servers) > 1:
|
||||
module.fail_json(msg='Multiple servers matched by name, '
|
||||
'try using the Server ID instead')
|
||||
|
||||
# We made it this far, grab the first and hopefully only server
|
||||
# in the list
|
||||
server = servers[0]
|
||||
return server
|
||||
|
||||
|
||||
def rax_find_loadbalancer(module, rax_module, loadbalancer):
|
||||
"""Find a Cloud Load Balancer by ID or name"""
|
||||
clb = rax_module.cloud_loadbalancers
|
||||
try:
|
||||
found = clb.get(loadbalancer)
|
||||
except Exception:
|
||||
found = []
|
||||
for lb in clb.list():
|
||||
if loadbalancer == lb.name:
|
||||
found.append(lb)
|
||||
|
||||
if not found:
|
||||
module.fail_json(msg='No loadbalancer was matched')
|
||||
|
||||
if len(found) > 1:
|
||||
module.fail_json(msg='Multiple loadbalancers matched')
|
||||
|
||||
# We made it this far, grab the first and hopefully only item
|
||||
# in the list
|
||||
found = found[0]
|
||||
|
||||
return found
|
||||
|
||||
|
||||
def rax_argument_spec():
|
||||
"""Return standard base dictionary used for the argument_spec
|
||||
argument in AnsibleModule
|
||||
|
||||
"""
|
||||
return dict(
|
||||
api_key=dict(type='str', aliases=['password'], no_log=True),
|
||||
auth_endpoint=dict(type='str'),
|
||||
credentials=dict(type='path', aliases=['creds_file']),
|
||||
env=dict(type='str'),
|
||||
identity_type=dict(type='str', default='rackspace'),
|
||||
region=dict(type='str'),
|
||||
tenant_id=dict(type='str'),
|
||||
tenant_name=dict(type='str'),
|
||||
username=dict(type='str'),
|
||||
validate_certs=dict(type='bool', aliases=['verify_ssl']),
|
||||
)
|
||||
|
||||
|
||||
def rax_required_together():
|
||||
"""Return the default list used for the required_together argument to
|
||||
AnsibleModule"""
|
||||
return [['api_key', 'username']]
|
||||
|
||||
|
||||
def setup_rax_module(module, rax_module, region_required=True):
|
||||
"""Set up pyrax in a standard way for all modules"""
|
||||
rax_module.USER_AGENT = 'ansible/%s %s' % (module.ansible_version,
|
||||
rax_module.USER_AGENT)
|
||||
|
||||
api_key = module.params.get('api_key')
|
||||
auth_endpoint = module.params.get('auth_endpoint')
|
||||
credentials = module.params.get('credentials')
|
||||
env = module.params.get('env')
|
||||
identity_type = module.params.get('identity_type')
|
||||
region = module.params.get('region')
|
||||
tenant_id = module.params.get('tenant_id')
|
||||
tenant_name = module.params.get('tenant_name')
|
||||
username = module.params.get('username')
|
||||
verify_ssl = module.params.get('validate_certs')
|
||||
|
||||
if env is not None:
|
||||
rax_module.set_environment(env)
|
||||
|
||||
rax_module.set_setting('identity_type', identity_type)
|
||||
if verify_ssl is not None:
|
||||
rax_module.set_setting('verify_ssl', verify_ssl)
|
||||
if auth_endpoint is not None:
|
||||
rax_module.set_setting('auth_endpoint', auth_endpoint)
|
||||
if tenant_id is not None:
|
||||
rax_module.set_setting('tenant_id', tenant_id)
|
||||
if tenant_name is not None:
|
||||
rax_module.set_setting('tenant_name', tenant_name)
|
||||
|
||||
try:
|
||||
username = username or os.environ.get('RAX_USERNAME')
|
||||
if not username:
|
||||
username = rax_module.get_setting('keyring_username')
|
||||
if username:
|
||||
api_key = 'USE_KEYRING'
|
||||
if not api_key:
|
||||
api_key = os.environ.get('RAX_API_KEY')
|
||||
credentials = (credentials or os.environ.get('RAX_CREDENTIALS') or
|
||||
os.environ.get('RAX_CREDS_FILE'))
|
||||
region = (region or os.environ.get('RAX_REGION') or
|
||||
rax_module.get_setting('region'))
|
||||
except KeyError as e:
|
||||
module.fail_json(msg='Unable to load %s' % e.message)
|
||||
|
||||
try:
|
||||
if api_key and username:
|
||||
if api_key == 'USE_KEYRING':
|
||||
rax_module.keyring_auth(username, region=region)
|
||||
else:
|
||||
rax_module.set_credentials(username, api_key=api_key,
|
||||
region=region)
|
||||
elif credentials:
|
||||
credentials = os.path.expanduser(credentials)
|
||||
rax_module.set_credential_file(credentials, region=region)
|
||||
else:
|
||||
raise Exception('No credentials supplied!')
|
||||
except Exception as e:
|
||||
if e.message:
|
||||
msg = str(e.message)
|
||||
else:
|
||||
msg = repr(e)
|
||||
module.fail_json(msg=msg)
|
||||
|
||||
if region_required and region not in rax_module.regions:
|
||||
module.fail_json(msg='%s is not a valid region, must be one of: %s' %
|
||||
(region, ','.join(rax_module.regions)))
|
||||
|
||||
return rax_module
|
||||
|
||||
|
||||
def rax_scaling_group_personality_file(module, files):
|
||||
if not files:
|
||||
return []
|
||||
|
||||
results = []
|
||||
for rpath, lpath in files.items():
|
||||
lpath = os.path.expanduser(lpath)
|
||||
try:
|
||||
with open(lpath, 'r') as f:
|
||||
results.append({
|
||||
'path': rpath,
|
||||
'contents': f.read(),
|
||||
})
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Failed to load %s: %s' % (lpath, str(e)))
|
||||
return results
|
||||
@@ -11,6 +11,7 @@ import os
|
||||
import random
|
||||
import string
|
||||
import gzip
|
||||
import time
|
||||
from io import BytesIO
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
@@ -41,7 +42,7 @@ FAIL_MSG = 'Issuing a data modification command without specifying the '\
|
||||
class RedfishUtils(object):
|
||||
|
||||
def __init__(self, creds, root_uri, timeout, module, resource_id=None,
|
||||
data_modification=False, strip_etag_quotes=False):
|
||||
data_modification=False, strip_etag_quotes=False, ciphers=None):
|
||||
self.root_uri = root_uri
|
||||
self.creds = creds
|
||||
self.timeout = timeout
|
||||
@@ -52,6 +53,7 @@ class RedfishUtils(object):
|
||||
self.resource_id = resource_id
|
||||
self.data_modification = data_modification
|
||||
self.strip_etag_quotes = strip_etag_quotes
|
||||
self.ciphers = ciphers
|
||||
self._vendor = None
|
||||
self._init_session()
|
||||
|
||||
@@ -132,11 +134,13 @@ class RedfishUtils(object):
|
||||
return resp
|
||||
|
||||
# The following functions are to send GET/POST/PATCH/DELETE requests
|
||||
def get_request(self, uri, override_headers=None, allow_no_resp=False):
|
||||
def get_request(self, uri, override_headers=None, allow_no_resp=False, timeout=None):
|
||||
req_headers = dict(GET_HEADERS)
|
||||
if override_headers:
|
||||
req_headers.update(override_headers)
|
||||
username, password, basic_auth = self._auth_params(req_headers)
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
try:
|
||||
# Service root is an unauthenticated resource; remove credentials
|
||||
# in case the caller will be using sessions later.
|
||||
@@ -146,7 +150,7 @@ class RedfishUtils(object):
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
use_proxy=True, timeout=timeout, ciphers=self.ciphers)
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
try:
|
||||
if headers.get('content-encoding') == 'gzip' and LooseVersion(ansible_version) < LooseVersion('2.14'):
|
||||
@@ -196,7 +200,7 @@ class RedfishUtils(object):
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
try:
|
||||
data = json.loads(to_native(resp.read()))
|
||||
except Exception as e:
|
||||
@@ -250,7 +254,7 @@ class RedfishUtils(object):
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
return {'ret': False, 'changed': False,
|
||||
@@ -285,7 +289,7 @@ class RedfishUtils(object):
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
@@ -311,7 +315,7 @@ class RedfishUtils(object):
|
||||
url_username=username, url_password=password,
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
@@ -624,6 +628,24 @@ class RedfishUtils(object):
|
||||
allowable_values = default_values
|
||||
return allowable_values
|
||||
|
||||
def check_service_availability(self):
|
||||
"""
|
||||
Checks if the service is accessible.
|
||||
|
||||
:return: dict containing the status of the service
|
||||
"""
|
||||
|
||||
# Get the service root
|
||||
# Override the timeout since the service root is expected to be readily
|
||||
# available.
|
||||
service_root = self.get_request(self.root_uri + self.service_root, timeout=10)
|
||||
if service_root['ret'] is False:
|
||||
# Failed, either due to a timeout or HTTP error; not available
|
||||
return {'ret': True, 'available': False}
|
||||
|
||||
# Successfully accessed the service root; available
|
||||
return {'ret': True, 'available': True}
|
||||
|
||||
def get_logs(self):
|
||||
log_svcs_uri_list = []
|
||||
list_of_logs = []
|
||||
@@ -1083,11 +1105,12 @@ class RedfishUtils(object):
|
||||
return self.manage_power(command, self.systems_uri,
|
||||
'#ComputerSystem.Reset')
|
||||
|
||||
def manage_manager_power(self, command):
|
||||
def manage_manager_power(self, command, wait=False, wait_timeout=120):
|
||||
return self.manage_power(command, self.manager_uri,
|
||||
'#Manager.Reset')
|
||||
'#Manager.Reset', wait, wait_timeout)
|
||||
|
||||
def manage_power(self, command, resource_uri, action_name):
|
||||
def manage_power(self, command, resource_uri, action_name, wait=False,
|
||||
wait_timeout=120):
|
||||
key = "Actions"
|
||||
reset_type_values = ['On', 'ForceOff', 'GracefulShutdown',
|
||||
'GracefulRestart', 'ForceRestart', 'Nmi',
|
||||
@@ -1147,6 +1170,30 @@ class RedfishUtils(object):
|
||||
response = self.post_request(self.root_uri + action_uri, payload)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
|
||||
# If requested to wait for the service to be available again, block
|
||||
# until it's ready
|
||||
if wait:
|
||||
elapsed_time = 0
|
||||
start_time = time.time()
|
||||
# Start with a large enough sleep. Some services will process new
|
||||
# requests while in the middle of shutting down, thus breaking out
|
||||
# early.
|
||||
time.sleep(30)
|
||||
|
||||
# Periodically check for the service's availability.
|
||||
while elapsed_time <= wait_timeout:
|
||||
status = self.check_service_availability()
|
||||
if status['available']:
|
||||
# It's available; we're done
|
||||
break
|
||||
time.sleep(5)
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
if elapsed_time > wait_timeout:
|
||||
# Exhausted the wait timer; error
|
||||
return {'ret': False, 'changed': True,
|
||||
'msg': 'The service did not become available after %d seconds' % wait_timeout}
|
||||
return {'ret': True, 'changed': True}
|
||||
|
||||
def manager_reset_to_defaults(self, command):
|
||||
@@ -3679,8 +3726,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']
|
||||
allowed_parameters = ['CapacityBytes', 'DisplayName', 'InitializeMethod', 'MediaSpanCount',
|
||||
required_parameters = ['RAIDType', 'Drives', 'CapacityBytes']
|
||||
allowed_parameters = ['DisplayName', 'InitializeMethod', 'MediaSpanCount',
|
||||
'Name', 'ReadCachePolicy', 'StripSizeBytes', 'VolumeUsage', 'WriteCachePolicy']
|
||||
|
||||
for parameter in required_parameters:
|
||||
|
||||
@@ -15,10 +15,8 @@ __metaclass__ = type
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import types
|
||||
|
||||
from ansible.module_utils.six.moves import configparser
|
||||
|
||||
@@ -76,241 +74,3 @@ class RegistrationBase(object):
|
||||
|
||||
def subscribe(self, **kwargs):
|
||||
raise NotImplementedError("Must be implemented by a sub-class")
|
||||
|
||||
|
||||
class Rhsm(RegistrationBase):
|
||||
"""
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 9.0.0.
|
||||
There is no replacement for it; please contact the community.general
|
||||
maintainers in case you are using it.
|
||||
"""
|
||||
|
||||
def __init__(self, module, username=None, password=None):
|
||||
RegistrationBase.__init__(self, module, username, password)
|
||||
self.config = self._read_config()
|
||||
self.module = module
|
||||
self.module.deprecate(
|
||||
'The Rhsm class is deprecated with no replacement.',
|
||||
version='9.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
def _read_config(self, rhsm_conf='/etc/rhsm/rhsm.conf'):
|
||||
'''
|
||||
Load RHSM configuration from /etc/rhsm/rhsm.conf.
|
||||
Returns:
|
||||
* ConfigParser object
|
||||
'''
|
||||
|
||||
# Read RHSM defaults ...
|
||||
cp = configparser.ConfigParser()
|
||||
cp.read(rhsm_conf)
|
||||
|
||||
# Add support for specifying a default value w/o having to standup some configuration
|
||||
# Yeah, I know this should be subclassed ... but, oh well
|
||||
def get_option_default(self, key, default=''):
|
||||
sect, opt = key.split('.', 1)
|
||||
if self.has_section(sect) and self.has_option(sect, opt):
|
||||
return self.get(sect, opt)
|
||||
else:
|
||||
return default
|
||||
|
||||
cp.get_option = types.MethodType(get_option_default, cp, configparser.ConfigParser)
|
||||
|
||||
return cp
|
||||
|
||||
def enable(self):
|
||||
'''
|
||||
Enable the system to receive updates from subscription-manager.
|
||||
This involves updating affected yum plugins and removing any
|
||||
conflicting yum repositories.
|
||||
'''
|
||||
RegistrationBase.enable(self)
|
||||
self.update_plugin_conf('rhnplugin', False)
|
||||
self.update_plugin_conf('subscription-manager', True)
|
||||
|
||||
def configure(self, **kwargs):
|
||||
'''
|
||||
Configure the system as directed for registration with RHN
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'config']
|
||||
|
||||
# Pass supplied **kwargs as parameters to subscription-manager. Ignore
|
||||
# non-configuration parameters and replace '_' with '.'. For example,
|
||||
# 'server_hostname' becomes '--system.hostname'.
|
||||
for k, v in kwargs.items():
|
||||
if re.search(r'^(system|rhsm)_', k):
|
||||
args.append('--%s=%s' % (k.replace('_', '.'), v))
|
||||
|
||||
self.module.run_command(args, check_rc=True)
|
||||
|
||||
@property
|
||||
def is_registered(self):
|
||||
'''
|
||||
Determine whether the current system
|
||||
Returns:
|
||||
* Boolean - whether the current system is currently registered to
|
||||
RHN.
|
||||
'''
|
||||
args = ['subscription-manager', 'identity']
|
||||
rc, stdout, stderr = self.module.run_command(args, check_rc=False)
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def register(self, username, password, autosubscribe, activationkey):
|
||||
'''
|
||||
Register the current system to the provided RHN server
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'register']
|
||||
|
||||
# Generate command arguments
|
||||
if activationkey:
|
||||
args.append('--activationkey "%s"' % activationkey)
|
||||
else:
|
||||
if autosubscribe:
|
||||
args.append('--autosubscribe')
|
||||
if username:
|
||||
args.extend(['--username', username])
|
||||
if password:
|
||||
args.extend(['--password', password])
|
||||
|
||||
# Do the needful...
|
||||
rc, stderr, stdout = self.module.run_command(args, check_rc=True)
|
||||
|
||||
def unsubscribe(self):
|
||||
'''
|
||||
Unsubscribe a system from all subscribed channels
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'unsubscribe', '--all']
|
||||
rc, stderr, stdout = self.module.run_command(args, check_rc=True)
|
||||
|
||||
def unregister(self):
|
||||
'''
|
||||
Unregister a currently registered system
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
args = ['subscription-manager', 'unregister']
|
||||
rc, stderr, stdout = self.module.run_command(args, check_rc=True)
|
||||
self.update_plugin_conf('rhnplugin', False)
|
||||
self.update_plugin_conf('subscription-manager', False)
|
||||
|
||||
def subscribe(self, regexp):
|
||||
'''
|
||||
Subscribe current system to available pools matching the specified
|
||||
regular expression
|
||||
Raises:
|
||||
* Exception - if error occurs while running command
|
||||
'''
|
||||
|
||||
# Available pools ready for subscription
|
||||
available_pools = RhsmPools(self.module)
|
||||
|
||||
for pool in available_pools.filter(regexp):
|
||||
pool.subscribe()
|
||||
|
||||
|
||||
class RhsmPool(object):
|
||||
"""
|
||||
Convenience class for housing subscription information
|
||||
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 9.0.0.
|
||||
There is no replacement for it; please contact the community.general
|
||||
maintainers in case you are using it.
|
||||
"""
|
||||
|
||||
def __init__(self, module, **kwargs):
|
||||
self.module = module
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
self.module.deprecate(
|
||||
'The RhsmPool class is deprecated with no replacement.',
|
||||
version='9.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__getattribute__('_name'))
|
||||
|
||||
def subscribe(self):
|
||||
args = "subscription-manager subscribe --pool %s" % self.PoolId
|
||||
rc, stdout, stderr = self.module.run_command(args, check_rc=True)
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class RhsmPools(object):
|
||||
"""
|
||||
This class is used for manipulating pools subscriptions with RHSM
|
||||
|
||||
DEPRECATION WARNING
|
||||
|
||||
This class is deprecated and will be removed in community.general 9.0.0.
|
||||
There is no replacement for it; please contact the community.general
|
||||
maintainers in case you are using it.
|
||||
"""
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.products = self._load_product_list()
|
||||
self.module.deprecate(
|
||||
'The RhsmPools class is deprecated with no replacement.',
|
||||
version='9.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return self.products.__iter__()
|
||||
|
||||
def _load_product_list(self):
|
||||
"""
|
||||
Loads list of all available pools for system in data structure
|
||||
"""
|
||||
args = "subscription-manager list --available"
|
||||
rc, stdout, stderr = self.module.run_command(args, check_rc=True)
|
||||
|
||||
products = []
|
||||
for line in stdout.split('\n'):
|
||||
# Remove leading+trailing whitespace
|
||||
line = line.strip()
|
||||
# An empty line implies the end of an output group
|
||||
if len(line) == 0:
|
||||
continue
|
||||
# If a colon ':' is found, parse
|
||||
elif ':' in line:
|
||||
(key, value) = line.split(':', 1)
|
||||
key = key.strip().replace(" ", "") # To unify
|
||||
value = value.strip()
|
||||
if key in ['ProductName', 'SubscriptionName']:
|
||||
# Remember the name for later processing
|
||||
products.append(RhsmPool(self.module, _name=value, key=value))
|
||||
elif products:
|
||||
# Associate value with most recently recorded product
|
||||
products[-1].__setattr__(key, value)
|
||||
# FIXME - log some warning?
|
||||
# else:
|
||||
# warnings.warn("Unhandled subscription key/value: %s/%s" % (key,value))
|
||||
return products
|
||||
|
||||
def filter(self, regexp='^$'):
|
||||
'''
|
||||
Return a list of RhsmPools whose name matches the provided regular expression
|
||||
'''
|
||||
r = re.compile(regexp)
|
||||
for product in self.products:
|
||||
if r.search(product._name):
|
||||
yield product
|
||||
|
||||
@@ -28,7 +28,7 @@ def api_argument_spec():
|
||||
return api_argument_spec
|
||||
|
||||
|
||||
def api_request(module, endpoint, data=None, method="GET"):
|
||||
def api_request(module, endpoint, data=None, method="GET", content_type="application/json"):
|
||||
"""Manages Rundeck API requests via HTTP(S)
|
||||
|
||||
:arg module: The AnsibleModule (used to get url, api_version, api_token, etc).
|
||||
@@ -63,7 +63,7 @@ def api_request(module, endpoint, data=None, method="GET"):
|
||||
data=json.dumps(data),
|
||||
method=method,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Content-Type": content_type,
|
||||
"Accept": "application/json",
|
||||
"X-Rundeck-Auth-Token": module.params["api_token"]
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class _Variable(object):
|
||||
return
|
||||
|
||||
def __str__(self):
|
||||
return "<_Variable: value={0!r}, initial={1!r}, diff={2}, output={3}, change={4}, verbosity={5}>".format(
|
||||
return "<Variable: value={0!r}, initial={1!r}, diff={2}, output={3}, change={4}, verbosity={5}>".format(
|
||||
self.value, self.initial_value, self.diff, self.output, self.change, self.verbosity
|
||||
)
|
||||
|
||||
|
||||
@@ -192,7 +192,6 @@ def main():
|
||||
rmitab = module.get_bin_path('rmitab')
|
||||
chitab = module.get_bin_path('chitab')
|
||||
rc = 0
|
||||
err = None
|
||||
|
||||
# check if the new entry exists
|
||||
current_entry = check_current_entry(module)
|
||||
|
||||
@@ -240,8 +240,6 @@ def main():
|
||||
state = module.params['state']
|
||||
pvs = module.params['pvs']
|
||||
|
||||
pv_list = ' '.join(pvs)
|
||||
|
||||
if policy == 'maximum':
|
||||
lv_policy = 'x'
|
||||
else:
|
||||
@@ -249,16 +247,16 @@ def main():
|
||||
|
||||
# Add echo command when running in check-mode
|
||||
if module.check_mode:
|
||||
test_opt = 'echo '
|
||||
test_opt = [module.get_bin_path("echo", required=True)]
|
||||
else:
|
||||
test_opt = ''
|
||||
test_opt = []
|
||||
|
||||
# check if system commands are available
|
||||
lsvg_cmd = module.get_bin_path("lsvg", required=True)
|
||||
lslv_cmd = module.get_bin_path("lslv", required=True)
|
||||
|
||||
# Get information on volume group requested
|
||||
rc, vg_info, err = module.run_command("%s %s" % (lsvg_cmd, vg))
|
||||
rc, vg_info, err = module.run_command([lsvg_cmd, vg])
|
||||
|
||||
if rc != 0:
|
||||
if state == 'absent':
|
||||
@@ -273,8 +271,7 @@ def main():
|
||||
lv_size = round_ppsize(convert_size(module, size), base=this_vg['pp_size'])
|
||||
|
||||
# Get information on logical volume requested
|
||||
rc, lv_info, err = module.run_command(
|
||||
"%s %s" % (lslv_cmd, lv))
|
||||
rc, lv_info, err = module.run_command([lslv_cmd, lv])
|
||||
|
||||
if rc != 0:
|
||||
if state == 'absent':
|
||||
@@ -296,7 +293,7 @@ def main():
|
||||
# create LV
|
||||
mklv_cmd = module.get_bin_path("mklv", required=True)
|
||||
|
||||
cmd = "%s %s -t %s -y %s -c %s -e %s %s %s %sM %s" % (test_opt, mklv_cmd, lv_type, lv, copies, lv_policy, opts, vg, lv_size, pv_list)
|
||||
cmd = test_opt + [mklv_cmd, "-t", lv_type, "-y", lv, "-c", copies, "-e", lv_policy, opts, vg, "%sM" % (lv_size, )] + pvs
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
module.exit_json(changed=True, msg="Logical volume %s created." % lv)
|
||||
@@ -306,7 +303,7 @@ def main():
|
||||
if state == 'absent':
|
||||
# remove LV
|
||||
rmlv_cmd = module.get_bin_path("rmlv", required=True)
|
||||
rc, out, err = module.run_command("%s %s -f %s" % (test_opt, rmlv_cmd, this_lv['name']))
|
||||
rc, out, err = module.run_command(test_opt + [rmlv_cmd, "-f", this_lv['name']])
|
||||
if rc == 0:
|
||||
module.exit_json(changed=True, msg="Logical volume %s deleted." % lv)
|
||||
else:
|
||||
@@ -315,7 +312,7 @@ def main():
|
||||
if this_lv['policy'] != policy:
|
||||
# change lv allocation policy
|
||||
chlv_cmd = module.get_bin_path("chlv", required=True)
|
||||
rc, out, err = module.run_command("%s %s -e %s %s" % (test_opt, chlv_cmd, lv_policy, this_lv['name']))
|
||||
rc, out, err = module.run_command(test_opt + [chlv_cmd, "-e", lv_policy, this_lv['name']])
|
||||
if rc == 0:
|
||||
module.exit_json(changed=True, msg="Logical volume %s policy changed: %s." % (lv, policy))
|
||||
else:
|
||||
@@ -331,7 +328,7 @@ def main():
|
||||
# resize LV based on absolute values
|
||||
if int(lv_size) > this_lv['size']:
|
||||
extendlv_cmd = module.get_bin_path("extendlv", required=True)
|
||||
cmd = "%s %s %s %sM" % (test_opt, extendlv_cmd, lv, lv_size - this_lv['size'])
|
||||
cmd = test_opt + [extendlv_cmd, lv, "%sM" % (lv_size - this_lv['size'], )]
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc == 0:
|
||||
module.exit_json(changed=True, msg="Logical volume %s size extended to %sMB." % (lv, lv_size))
|
||||
|
||||
@@ -32,6 +32,19 @@ attributes:
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- >
|
||||
If O(state=present) then the collection or role will be installed.
|
||||
Note that the collections and roles are not updated with this option.
|
||||
- >
|
||||
Currently the O(state=latest) is ignored unless O(type=collection), and it will
|
||||
ensure the collection is installed and updated to the latest available version.
|
||||
- Please note that O(force=true) can be used to perform upgrade regardless of O(type).
|
||||
type: str
|
||||
choices: [ present, latest ]
|
||||
default: present
|
||||
version_added: 9.1.0
|
||||
type:
|
||||
description:
|
||||
- The type of installation performed by C(ansible-galaxy).
|
||||
@@ -69,20 +82,11 @@ options:
|
||||
default: false
|
||||
force:
|
||||
description:
|
||||
- Force overwriting an existing role or collection.
|
||||
- Force overwriting existing roles and/or collections.
|
||||
- It can be used for upgrading, but the module output will always report C(changed=true).
|
||||
- Using O(force=true) is mandatory when downgrading.
|
||||
type: bool
|
||||
default: false
|
||||
ack_ansible29:
|
||||
description:
|
||||
- This option has no longer any effect and will be removed in community.general 9.0.0.
|
||||
type: bool
|
||||
default: false
|
||||
ack_min_ansiblecore211:
|
||||
description:
|
||||
- This option has no longer any effect and will be removed in community.general 9.0.0.
|
||||
type: bool
|
||||
default: false
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
@@ -181,7 +185,7 @@ RETURN = """
|
||||
|
||||
import re
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt as fmt
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper, ModuleHelperException
|
||||
|
||||
|
||||
@@ -190,47 +194,40 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
_RE_LIST_PATH = re.compile(r'^# (?P<path>.*)$')
|
||||
_RE_LIST_COLL = re.compile(r'^(?P<elem>\w+\.\w+)\s+(?P<version>[\d\.]+)\s*$')
|
||||
_RE_LIST_ROLE = re.compile(r'^- (?P<elem>\w+\.\w+),\s+(?P<version>[\d\.]+)\s*$')
|
||||
_RE_INSTALL_OUTPUT = None # Set after determining ansible version, see __init_module__()
|
||||
_RE_INSTALL_OUTPUT = re.compile(
|
||||
r'^(?:(?P<collection>\w+\.\w+):(?P<cversion>[\d\.]+)|- (?P<role>\w+\.\w+) \((?P<rversion>[\d\.]+)\)) was installed successfully$'
|
||||
)
|
||||
ansible_version = None
|
||||
|
||||
output_params = ('type', 'name', 'dest', 'requirements_file', 'force', 'no_deps')
|
||||
module = dict(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', choices=['present', 'latest'], default='present'),
|
||||
type=dict(type='str', choices=('collection', 'role', 'both'), required=True),
|
||||
name=dict(type='str'),
|
||||
requirements_file=dict(type='path'),
|
||||
dest=dict(type='path'),
|
||||
force=dict(type='bool', default=False),
|
||||
no_deps=dict(type='bool', default=False),
|
||||
ack_ansible29=dict(
|
||||
type='bool',
|
||||
default=False,
|
||||
removed_in_version='9.0.0',
|
||||
removed_from_collection='community.general',
|
||||
),
|
||||
ack_min_ansiblecore211=dict(
|
||||
type='bool',
|
||||
default=False,
|
||||
removed_in_version='9.0.0',
|
||||
removed_from_collection='community.general',
|
||||
),
|
||||
),
|
||||
mutually_exclusive=[('name', 'requirements_file')],
|
||||
required_one_of=[('name', 'requirements_file')],
|
||||
required_if=[('type', 'both', ['requirements_file'])],
|
||||
supports_check_mode=False,
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
command = 'ansible-galaxy'
|
||||
command_args_formats = dict(
|
||||
type=fmt.as_func(lambda v: [] if v == 'both' else [v]),
|
||||
galaxy_cmd=fmt.as_list(),
|
||||
requirements_file=fmt.as_opt_val('-r'),
|
||||
dest=fmt.as_opt_val('-p'),
|
||||
force=fmt.as_bool("--force"),
|
||||
no_deps=fmt.as_bool("--no-deps"),
|
||||
version=fmt.as_bool("--version"),
|
||||
name=fmt.as_list(),
|
||||
type=cmd_runner_fmt.as_func(lambda v: [] if v == 'both' else [v]),
|
||||
galaxy_cmd=cmd_runner_fmt.as_list(),
|
||||
upgrade=cmd_runner_fmt.as_bool("--upgrade"),
|
||||
requirements_file=cmd_runner_fmt.as_opt_val('-r'),
|
||||
dest=cmd_runner_fmt.as_opt_val('-p'),
|
||||
force=cmd_runner_fmt.as_bool("--force"),
|
||||
no_deps=cmd_runner_fmt.as_bool("--no-deps"),
|
||||
version=cmd_runner_fmt.as_fixed("--version"),
|
||||
name=cmd_runner_fmt.as_list(),
|
||||
)
|
||||
|
||||
def _make_runner(self, lang):
|
||||
@@ -254,25 +251,16 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
try:
|
||||
runner = self._make_runner("C.UTF-8")
|
||||
with runner("version", check_rc=False, output_process=process) as ctx:
|
||||
return runner, ctx.run(version=True)
|
||||
except UnsupportedLocale as e:
|
||||
return runner, ctx.run()
|
||||
except UnsupportedLocale:
|
||||
runner = self._make_runner("en_US.UTF-8")
|
||||
with runner("version", check_rc=True, output_process=process) as ctx:
|
||||
return runner, ctx.run(version=True)
|
||||
return runner, ctx.run()
|
||||
|
||||
def __init_module__(self):
|
||||
# self.runner = CmdRunner(self.module, command=self.command, arg_formats=self.command_args_formats, force_lang=self.force_lang)
|
||||
self.runner, self.ansible_version = self._get_ansible_galaxy_version()
|
||||
if self.ansible_version < (2, 11):
|
||||
self.module.fail_json(
|
||||
msg="Support for Ansible 2.9 and ansible-base 2.10 has ben removed."
|
||||
)
|
||||
# Collection install output changed:
|
||||
# ansible-base 2.10: "coll.name (x.y.z)"
|
||||
# ansible-core 2.11+: "coll.name:x.y.z"
|
||||
self._RE_INSTALL_OUTPUT = re.compile(r'^(?:(?P<collection>\w+\.\w+)(?: \(|:)(?P<cversion>[\d\.]+)\)?'
|
||||
r'|- (?P<role>\w+\.\w+) \((?P<rversion>[\d\.]+)\))'
|
||||
r' was installed successfully$')
|
||||
self.module.fail_json(msg="Support for Ansible 2.9 and ansible-base 2.10 has been removed.")
|
||||
self.vars.set("new_collections", {}, change=True)
|
||||
self.vars.set("new_roles", {}, change=True)
|
||||
if self.vars.type != "collection":
|
||||
@@ -325,8 +313,9 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
elif match.group("role"):
|
||||
self.vars.new_roles[match.group("role")] = match.group("rversion")
|
||||
|
||||
with self.runner("type galaxy_cmd force no_deps dest requirements_file name", output_process=process) as ctx:
|
||||
ctx.run(galaxy_cmd="install")
|
||||
upgrade = (self.vars.type == "collection" and self.vars.state == "latest")
|
||||
with self.runner("type galaxy_cmd upgrade force no_deps dest requirements_file name", output_process=process) as ctx:
|
||||
ctx.run(galaxy_cmd="install", upgrade=upgrade)
|
||||
if self.verbosity > 2:
|
||||
self.vars.set("run_info", ctx.run_info)
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ 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
|
||||
|
||||
@@ -170,7 +170,7 @@ def local_rpm_package_name(path):
|
||||
def query_package(module, name):
|
||||
# rpm -q returns 0 if the package is installed,
|
||||
# 1 if it is not installed
|
||||
rc, out, err = module.run_command("%s -q %s" % (RPM_PATH, name))
|
||||
rc, out, err = module.run_command([RPM_PATH, "-q", name])
|
||||
if rc == 0:
|
||||
return True
|
||||
else:
|
||||
@@ -203,7 +203,7 @@ def query_package_provides(module, name, allow_upgrade=False):
|
||||
|
||||
name = local_rpm_package_name(name)
|
||||
|
||||
rc, out, err = module.run_command("%s -q --provides %s" % (RPM_PATH, name))
|
||||
rc, out, err = module.run_command([RPM_PATH, "-q", "--provides", name])
|
||||
if rc == 0:
|
||||
if not allow_upgrade:
|
||||
return True
|
||||
@@ -253,7 +253,7 @@ def remove_packages(module, packages):
|
||||
if not query_package(module, package):
|
||||
continue
|
||||
|
||||
rc, out, err = module.run_command("%s -y remove %s" % (APT_PATH, package), environ_update={"LANG": "C"})
|
||||
rc, out, err = module.run_command([APT_PATH, "-y", "remove", package], environ_update={"LANG": "C"})
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(msg="failed to remove %s: %s" % (package, err))
|
||||
@@ -271,14 +271,14 @@ def install_packages(module, pkgspec, allow_upgrade=False):
|
||||
if pkgspec is None:
|
||||
return (False, "Empty package list")
|
||||
|
||||
packages = ""
|
||||
packages = []
|
||||
for package in pkgspec:
|
||||
if not query_package_provides(module, package, allow_upgrade=allow_upgrade):
|
||||
packages += "'%s' " % package
|
||||
packages.append(package)
|
||||
|
||||
if len(packages) != 0:
|
||||
|
||||
rc, out, err = module.run_command("%s -y install %s" % (APT_PATH, packages), environ_update={"LANG": "C"})
|
||||
if packages:
|
||||
command = [APT_PATH, "-y", "install"] + packages
|
||||
rc, out, err = module.run_command(command, environ_update={"LANG": "C"})
|
||||
|
||||
installed = True
|
||||
for package in pkgspec:
|
||||
@@ -287,7 +287,7 @@ def install_packages(module, pkgspec, allow_upgrade=False):
|
||||
|
||||
# apt-rpm always have 0 for exit code if --force is used
|
||||
if rc or not installed:
|
||||
module.fail_json(msg="'apt-get -y install %s' failed: %s" % (packages, err))
|
||||
module.fail_json(msg="'%s' failed: %s" % (" ".join(command), err))
|
||||
else:
|
||||
return (True, "%s present(s)" % packages)
|
||||
else:
|
||||
@@ -310,6 +310,18 @@ def main():
|
||||
module.fail_json(msg="cannot find /usr/bin/apt-get and/or /usr/bin/rpm")
|
||||
|
||||
p = module.params
|
||||
if p['state'] in ['installed', 'present']:
|
||||
module.deprecate(
|
||||
'state=%s currently behaves unexpectedly by always upgrading to the latest version if'
|
||||
' the package is already installed. This behavior is deprecated and will change in'
|
||||
' community.general 11.0.0. You can use state=latest to explicitly request this behavior'
|
||||
' or state=present_not_latest to explicitly request the behavior that state=%s will have'
|
||||
' in community.general 11.0.0, namely that the package will not be upgraded if it is'
|
||||
' already installed.' % (p['state'], p['state']),
|
||||
version='11.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
modified = False
|
||||
output = ""
|
||||
|
||||
|
||||
@@ -102,40 +102,40 @@ EXAMPLES = r'''
|
||||
- name: Create a @home subvolume under the root subvolume
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@home
|
||||
filesystem_device: /dev/vda2
|
||||
device: /dev/vda2
|
||||
|
||||
- name: Remove the @home subvolume if it exists
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@home
|
||||
state: absent
|
||||
filesystem_device: /dev/vda2
|
||||
device: /dev/vda2
|
||||
|
||||
- name: Create a snapshot of the root subvolume named @
|
||||
community.general.btrfs_subvolume:
|
||||
name: /@
|
||||
snapshot_source: /
|
||||
filesystem_device: /dev/vda2
|
||||
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
|
||||
filesystem_device: /dev/vda2
|
||||
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
|
||||
filesystem_device: /dev/vda2
|
||||
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
|
||||
filesystem_device: /dev/vda2
|
||||
device: /dev/vda2
|
||||
|
||||
'''
|
||||
|
||||
@@ -572,10 +572,7 @@ class BtrfsSubvolumeModule(object):
|
||||
self.__temporary_mounts[cache_key] = mountpoint
|
||||
|
||||
mount = self.module.get_bin_path("mount", required=True)
|
||||
command = "%s -o noatime,subvolid=%d %s %s " % (mount,
|
||||
subvolid,
|
||||
device,
|
||||
mountpoint)
|
||||
command = [mount, "-o", "noatime,subvolid=%d" % subvolid, device, mountpoint]
|
||||
result = self.module.run_command(command, check_rc=True)
|
||||
|
||||
return mountpoint
|
||||
@@ -586,10 +583,10 @@ class BtrfsSubvolumeModule(object):
|
||||
|
||||
def __cleanup_mount(self, mountpoint):
|
||||
umount = self.module.get_bin_path("umount", required=True)
|
||||
result = self.module.run_command("%s %s" % (umount, mountpoint))
|
||||
result = self.module.run_command([umount, mountpoint])
|
||||
if result[0] == 0:
|
||||
rmdir = self.module.get_bin_path("rmdir", required=True)
|
||||
self.module.run_command("%s %s" % (rmdir, mountpoint))
|
||||
self.module.run_command([rmdir, mountpoint])
|
||||
|
||||
# Format and return results
|
||||
def get_results(self):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021 Radek Sprta <mail@radeksprta.eu>
|
||||
# Copyright (c) 2024 Colin Nolan <cn580@alumni.york.ac.uk>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -65,6 +66,13 @@ options:
|
||||
type: str
|
||||
default: present
|
||||
choices: [ "present", "absent", "latest" ]
|
||||
directory:
|
||||
description:
|
||||
- Path to the source directory to install the Rust package from.
|
||||
- This is only used when installing packages.
|
||||
type: path
|
||||
required: false
|
||||
version_added: 9.1.0
|
||||
requirements:
|
||||
- cargo installed
|
||||
"""
|
||||
@@ -98,8 +106,14 @@ EXAMPLES = r"""
|
||||
community.general.cargo:
|
||||
name: ludusavi
|
||||
state: latest
|
||||
|
||||
- name: Install "ludusavi" Rust package from source directory
|
||||
community.general.cargo:
|
||||
name: ludusavi
|
||||
directory: /path/to/ludusavi/source
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
@@ -115,6 +129,7 @@ class Cargo(object):
|
||||
self.state = kwargs["state"]
|
||||
self.version = kwargs["version"]
|
||||
self.locked = kwargs["locked"]
|
||||
self.directory = kwargs["directory"]
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
@@ -143,7 +158,7 @@ class Cargo(object):
|
||||
|
||||
data, dummy = self._exec(cmd, True, False, False)
|
||||
|
||||
package_regex = re.compile(r"^([\w\-]+) v(.+):$")
|
||||
package_regex = re.compile(r"^([\w\-]+) v(\S+).*:$")
|
||||
installed = {}
|
||||
for line in data.splitlines():
|
||||
package_info = package_regex.match(line)
|
||||
@@ -163,19 +178,53 @@ class Cargo(object):
|
||||
if self.version:
|
||||
cmd.append("--version")
|
||||
cmd.append(self.version)
|
||||
if self.directory:
|
||||
cmd.append("--path")
|
||||
cmd.append(self.directory)
|
||||
return self._exec(cmd)
|
||||
|
||||
def is_outdated(self, name):
|
||||
installed_version = self.get_installed().get(name)
|
||||
latest_version = (
|
||||
self.get_latest_published_version(name)
|
||||
if not self.directory
|
||||
else self.get_source_directory_version(name)
|
||||
)
|
||||
return installed_version != latest_version
|
||||
|
||||
def get_latest_published_version(self, name):
|
||||
cmd = ["search", name, "--limit", "1"]
|
||||
data, dummy = self._exec(cmd, True, False, False)
|
||||
|
||||
match = re.search(r'"(.+)"', data)
|
||||
if match:
|
||||
latest_version = match.group(1)
|
||||
if not match:
|
||||
self.module.fail_json(
|
||||
msg="No published version for package %s found" % name
|
||||
)
|
||||
return match.group(1)
|
||||
|
||||
return installed_version != latest_version
|
||||
def get_source_directory_version(self, name):
|
||||
cmd = [
|
||||
"metadata",
|
||||
"--format-version",
|
||||
"1",
|
||||
"--no-deps",
|
||||
"--manifest-path",
|
||||
os.path.join(self.directory, "Cargo.toml"),
|
||||
]
|
||||
data, dummy = self._exec(cmd, True, False, False)
|
||||
manifest = json.loads(data)
|
||||
|
||||
package = next(
|
||||
(package for package in manifest["packages"] if package["name"] == name),
|
||||
None,
|
||||
)
|
||||
if not package:
|
||||
self.module.fail_json(
|
||||
msg="Package %s not defined in source, found: %s"
|
||||
% (name, [x["name"] for x in manifest["packages"]])
|
||||
)
|
||||
return package["version"]
|
||||
|
||||
def uninstall(self, packages=None):
|
||||
cmd = ["uninstall"]
|
||||
@@ -191,16 +240,21 @@ def main():
|
||||
state=dict(default="present", choices=["present", "absent", "latest"]),
|
||||
version=dict(default=None, type="str"),
|
||||
locked=dict(default=False, type="bool"),
|
||||
directory=dict(default=None, type="path"),
|
||||
)
|
||||
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
|
||||
|
||||
name = module.params["name"]
|
||||
state = module.params["state"]
|
||||
version = module.params["version"]
|
||||
directory = module.params["directory"]
|
||||
|
||||
if not name:
|
||||
module.fail_json(msg="Package name must be specified")
|
||||
|
||||
if directory is not None and not os.path.isdir(directory):
|
||||
module.fail_json(msg="Source directory does not exist")
|
||||
|
||||
# Set LANG env since we parse stdout
|
||||
module.run_command_environ_update = dict(
|
||||
LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C"
|
||||
|
||||
@@ -148,9 +148,9 @@ options:
|
||||
type:
|
||||
description:
|
||||
- The type of DNS record to create. Required if O(state=present).
|
||||
- Note that V(SPF) is no longer supported by CloudFlare. Support for it will be removed from community.general 9.0.0.
|
||||
- Support for V(SPF) has been removed from community.general 9.0.0 since that record type is no longer supported by CloudFlare.
|
||||
type: str
|
||||
choices: [ A, AAAA, CNAME, DS, MX, NS, SPF, SRV, SSHFP, TLSA, CAA, TXT ]
|
||||
choices: [ A, AAAA, CNAME, DS, MX, NS, SRV, SSHFP, TLSA, CAA, TXT ]
|
||||
value:
|
||||
description:
|
||||
- The record value.
|
||||
@@ -674,7 +674,7 @@ class CloudflareAPI(object):
|
||||
if (params['type'] is None) or (params['record'] is None):
|
||||
self.module.fail_json(msg="You must provide a type and a record to create a new record")
|
||||
|
||||
if (params['type'] in ['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'NS', 'SPF']):
|
||||
if (params['type'] in ['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'NS']):
|
||||
if not params['value']:
|
||||
self.module.fail_json(msg="You must provide a non-empty value to create this record type")
|
||||
|
||||
@@ -716,14 +716,12 @@ 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'],
|
||||
"name": params['service'] + '.' + params['proto'] + '.' + params['record'],
|
||||
"ttl": params['ttl'],
|
||||
'data': srv_data,
|
||||
}
|
||||
new_record = {"type": params['type'], "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']
|
||||
|
||||
@@ -871,7 +869,7 @@ def main():
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
timeout=dict(type='int', default=30),
|
||||
ttl=dict(type='int', default=1),
|
||||
type=dict(type='str', choices=['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'SPF', 'SRV', 'SSHFP', 'TLSA', 'CAA', 'TXT']),
|
||||
type=dict(type='str', choices=['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'SRV', 'SSHFP', 'TLSA', 'CAA', 'TXT']),
|
||||
value=dict(type='str', aliases=['content']),
|
||||
weight=dict(type='int', default=1),
|
||||
zone=dict(type='str', required=True, aliases=['domain']),
|
||||
|
||||
254
plugins/modules/consul_agent_check.py
Normal file
254
plugins/modules/consul_agent_check.py
Normal file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Michael Ilg
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: consul_agent_check
|
||||
short_description: Add, modify, and delete checks within a consul cluster
|
||||
version_added: 9.1.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of checks in a consul
|
||||
cluster via the agent. For more details on using and configuring Checks,
|
||||
see U(https://developer.hashicorp.com/consul/api-docs/agent/check).
|
||||
- Currently, there is no complete way to retrieve the script, interval or TTL
|
||||
metadata for a registered check. Without this metadata it is not possible to
|
||||
tell if the data supplied with ansible represents a change to a check. As a
|
||||
result this does not attempt to determine changes and will always report a
|
||||
changed occurred. An API method is planned to supply this metadata so at that
|
||||
stage change management will be added.
|
||||
author:
|
||||
- Michael Ilg (@Ilgmi)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
details:
|
||||
- The result is the object as it is defined in the module options and not the object structure of the consul API.
|
||||
For a better overview of what the object structure looks like,
|
||||
take a look at U(https://developer.hashicorp.com/consul/api-docs/agent/check#list-checks).
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will show the object as it is defined in the module options and not the object structure of the consul API.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the check should be present or absent.
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Required name for the service check.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- Specifies a unique ID for this check on the node. This defaults to the O(name) parameter, but it may be necessary to provide
|
||||
an ID for uniqueness. This value will return in the response as "CheckId".
|
||||
type: str
|
||||
interval:
|
||||
description:
|
||||
- The interval at which the service check will be run.
|
||||
This is a number with a V(s) or V(m) suffix to signify the units of seconds or minutes, for example V(15s) or V(1m).
|
||||
If no suffix is supplied V(s) will be used by default, for example V(10) will be V(10s).
|
||||
- Required if one of the parameters O(args), O(http), or O(tcp) is specified.
|
||||
type: str
|
||||
notes:
|
||||
description:
|
||||
- Notes to attach to check when registering it.
|
||||
type: str
|
||||
args:
|
||||
description:
|
||||
- Specifies command arguments to run to update the status of the check.
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(ttl), O(tcp) and O(http).
|
||||
type: list
|
||||
elements: str
|
||||
ttl:
|
||||
description:
|
||||
- Checks can be registered with a TTL instead of a O(args) and O(interval)
|
||||
this means that the service will check in with the agent before the
|
||||
TTL expires. If it doesn't the check will be considered failed.
|
||||
Required if registering a check and the script an interval are missing
|
||||
Similar to the interval this is a number with a V(s) or V(m) suffix to
|
||||
signify the units of seconds or minutes, for example V(15s) or V(1m).
|
||||
If no suffix is supplied V(s) will be used by default, for example V(10) will be V(10s).
|
||||
- Mutually exclusive with O(args), O(tcp) and O(http).
|
||||
type: str
|
||||
tcp:
|
||||
description:
|
||||
- Checks can be registered with a TCP port. This means that consul
|
||||
will check if the connection attempt to that port is successful (that is, the port is currently accepting connections).
|
||||
The format is V(host:port), for example V(localhost:80).
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(args), O(ttl) and O(http).
|
||||
type: str
|
||||
version_added: '1.3.0'
|
||||
http:
|
||||
description:
|
||||
- Checks can be registered with an HTTP endpoint. This means that consul
|
||||
will check that the http endpoint returns a successful HTTP status.
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(args), O(ttl) and O(tcp).
|
||||
type: str
|
||||
timeout:
|
||||
description:
|
||||
- A custom HTTP check timeout. The consul default is 10 seconds.
|
||||
Similar to the interval this is a number with a V(s) or V(m) suffix to
|
||||
signify the units of seconds or minutes, for example V(15s) or V(1m).
|
||||
If no suffix is supplied V(s) will be used by default, for example V(10) will be V(10s).
|
||||
type: str
|
||||
service_id:
|
||||
description:
|
||||
- The ID for the service, must be unique per node. If O(state=absent),
|
||||
defaults to the service name if supplied.
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Register tcp check for service 'nginx'
|
||||
community.general.consul_agent_check:
|
||||
name: nginx_tcp_check
|
||||
service_id: nginx
|
||||
interval: 60s
|
||||
tcp: localhost:80
|
||||
notes: "Nginx Check"
|
||||
|
||||
- name: Register http check for service 'nginx'
|
||||
community.general.consul_agent_check:
|
||||
name: nginx_http_check
|
||||
service_id: nginx
|
||||
interval: 60s
|
||||
http: http://localhost:80/status
|
||||
notes: "Nginx Check"
|
||||
|
||||
- name: Remove check for service 'nginx'
|
||||
community.general.consul_agent_check:
|
||||
state: absent
|
||||
id: nginx_http_check
|
||||
service_id: "{{ nginx_service.ID }}"
|
||||
'''
|
||||
|
||||
RETURN = """
|
||||
check:
|
||||
description: The check as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
CheckID: nginx_check
|
||||
ServiceID: nginx
|
||||
Interval: 30s
|
||||
Type: http
|
||||
Notes: Nginx Check
|
||||
operation:
|
||||
description: The operation performed.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
OPERATION_CREATE,
|
||||
OPERATION_UPDATE,
|
||||
OPERATION_DELETE,
|
||||
OPERATION_READ,
|
||||
_ConsulModule,
|
||||
validate_check,
|
||||
)
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
"name": dict(type='str'),
|
||||
"id": dict(type='str'),
|
||||
"interval": dict(type='str'),
|
||||
"notes": dict(type='str'),
|
||||
"args": dict(type='list', elements='str'),
|
||||
"http": dict(type='str'),
|
||||
"tcp": dict(type='str'),
|
||||
"ttl": dict(type='str'),
|
||||
"timeout": dict(type='str'),
|
||||
"service_id": dict(type='str'),
|
||||
}
|
||||
|
||||
_MUTUALLY_EXCLUSIVE = [
|
||||
('args', 'ttl', 'tcp', 'http'),
|
||||
]
|
||||
|
||||
_REQUIRED_IF = [
|
||||
('state', 'present', ['name']),
|
||||
('state', 'absent', ('id', 'name'), True),
|
||||
]
|
||||
|
||||
_REQUIRED_BY = {
|
||||
'args': 'interval',
|
||||
'http': 'interval',
|
||||
'tcp': 'interval',
|
||||
}
|
||||
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
class ConsulAgentCheckModule(_ConsulModule):
|
||||
api_endpoint = "agent/check"
|
||||
result_key = "check"
|
||||
unique_identifiers = ["id", "name"]
|
||||
operational_attributes = {"Node", "CheckID", "Output", "ServiceName", "ServiceTags",
|
||||
"Status", "Type", "ExposedPort", "Definition"}
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
return "agent/checks"
|
||||
if operation in [OPERATION_CREATE, OPERATION_UPDATE]:
|
||||
return "/".join([self.api_endpoint, "register"])
|
||||
if operation == OPERATION_DELETE:
|
||||
return "/".join([self.api_endpoint, "deregister", identifier])
|
||||
|
||||
return super(ConsulAgentCheckModule, self).endpoint_url(operation, identifier)
|
||||
|
||||
def read_object(self):
|
||||
url = self.endpoint_url(OPERATION_READ)
|
||||
checks = self.get(url)
|
||||
identifier = self.id_from_obj(self.params)
|
||||
if identifier in checks:
|
||||
return checks[identifier]
|
||||
return None
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
existing = super(ConsulAgentCheckModule, self).prepare_object(existing, obj)
|
||||
validate_check(existing)
|
||||
return existing
|
||||
|
||||
def delete_object(self, obj):
|
||||
if not self._module.check_mode:
|
||||
self.put(self.endpoint_url(OPERATION_DELETE, obj.get("CheckID")))
|
||||
return {}
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
mutually_exclusive=_MUTUALLY_EXCLUSIVE,
|
||||
required_if=_REQUIRED_IF,
|
||||
required_by=_REQUIRED_BY,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
consul_module = ConsulAgentCheckModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
289
plugins/modules/consul_agent_service.py
Normal file
289
plugins/modules/consul_agent_service.py
Normal file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Michael Ilg
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: consul_agent_service
|
||||
short_description: Add, modify and delete services within a consul cluster
|
||||
version_added: 9.1.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of services in a consul
|
||||
cluster via the agent.
|
||||
- There are currently no plans to create services and checks in one.
|
||||
This is because the Consul API does not provide checks for a service and
|
||||
the checks themselves do not match the module parameters.
|
||||
Therefore, only a service without checks can be created in this module.
|
||||
author:
|
||||
- Michael Ilg (@Ilgmi)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the service should be present or absent.
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Unique name for the service on a node, must be unique per node,
|
||||
required if registering a service.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- Specifies a unique ID for this service. This must be unique per agent. This defaults to the O(name) parameter if not provided.
|
||||
If O(state=absent), defaults to the service name if supplied.
|
||||
type: str
|
||||
tags:
|
||||
description:
|
||||
- Tags that will be attached to the service registration.
|
||||
type: list
|
||||
elements: str
|
||||
address:
|
||||
description:
|
||||
- The address to advertise that the service will be listening on.
|
||||
This value will be passed as the C(address) parameter to Consul's
|
||||
C(/v1/agent/service/register) API method, so refer to the Consul API
|
||||
documentation for further details.
|
||||
type: str
|
||||
meta:
|
||||
description:
|
||||
- Optional meta data used for filtering.
|
||||
For keys, the characters C(A-Z), C(a-z), C(0-9), C(_), C(-) are allowed.
|
||||
Not allowed characters are replaced with underscores.
|
||||
type: dict
|
||||
service_port:
|
||||
description:
|
||||
- The port on which the service is listening. Can optionally be supplied for
|
||||
registration of a service, that is if O(name) or O(id) is set.
|
||||
type: int
|
||||
enable_tag_override:
|
||||
description:
|
||||
- Specifies to disable the anti-entropy feature for this service's tags.
|
||||
If EnableTagOverride is set to true then external agents can update this service in the catalog and modify the tags.
|
||||
type: bool
|
||||
default: False
|
||||
weights:
|
||||
description:
|
||||
- Specifies weights for the service
|
||||
type: dict
|
||||
suboptions:
|
||||
passing:
|
||||
description:
|
||||
- Weights for passing.
|
||||
type: int
|
||||
default: 1
|
||||
warning:
|
||||
description:
|
||||
- Weights for warning.
|
||||
type: int
|
||||
default: 1
|
||||
default: {"passing": 1, "warning": 1}
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Register nginx service with the local consul agent
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
|
||||
- name: Register nginx with a tcp check
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
|
||||
- name: Register nginx with an http check
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
|
||||
- name: Register external service nginx available at 10.1.5.23
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
address: 10.1.5.23
|
||||
|
||||
- name: Register nginx with some service tags
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
tags:
|
||||
- prod
|
||||
- webservers
|
||||
|
||||
- name: Register nginx with some service meta
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: nginx
|
||||
service_port: 80
|
||||
meta:
|
||||
nginx_version: 1.25.3
|
||||
|
||||
- name: Remove nginx service
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
service_id: nginx
|
||||
state: absent
|
||||
|
||||
- name: Register celery worker service
|
||||
community.general.consul_agent_service:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: celery-worker
|
||||
tags:
|
||||
- prod
|
||||
- worker
|
||||
'''
|
||||
|
||||
RETURN = """
|
||||
service:
|
||||
description: The service as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
ID: nginx
|
||||
Service: nginx
|
||||
Address: localhost
|
||||
Port: 80
|
||||
Tags:
|
||||
- http
|
||||
Meta:
|
||||
- nginx_version: 1.23.3
|
||||
Datacenter: dc1
|
||||
Weights:
|
||||
Passing: 1
|
||||
Warning: 1
|
||||
ContentHash: 61a245cd985261ac
|
||||
EnableTagOverride: false
|
||||
operation:
|
||||
description: The operation performed.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
OPERATION_CREATE,
|
||||
OPERATION_UPDATE,
|
||||
OPERATION_DELETE,
|
||||
_ConsulModule
|
||||
)
|
||||
|
||||
_CHECK_MUTUALLY_EXCLUSIVE = [('args', 'ttl', 'tcp', 'http')]
|
||||
_CHECK_REQUIRED_BY = {
|
||||
'args': 'interval',
|
||||
'http': 'interval',
|
||||
'tcp': 'interval',
|
||||
}
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
"name": dict(type='str'),
|
||||
"id": dict(type='str'),
|
||||
"tags": dict(type='list', elements='str'),
|
||||
"address": dict(type='str'),
|
||||
"meta": dict(type='dict'),
|
||||
"service_port": dict(type='int'),
|
||||
"enable_tag_override": dict(type='bool', default=False),
|
||||
"weights": dict(type='dict', options=dict(
|
||||
passing=dict(type='int', default=1, no_log=False),
|
||||
warning=dict(type='int', default=1)
|
||||
), default={"passing": 1, "warning": 1})
|
||||
}
|
||||
|
||||
_REQUIRED_IF = [
|
||||
('state', 'present', ['name']),
|
||||
('state', 'absent', ('id', 'name'), True),
|
||||
]
|
||||
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
class ConsulAgentServiceModule(_ConsulModule):
|
||||
api_endpoint = "agent/service"
|
||||
result_key = "service"
|
||||
unique_identifiers = ["id", "name"]
|
||||
operational_attributes = {"Service", "ContentHash", "Datacenter"}
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation in [OPERATION_CREATE, OPERATION_UPDATE]:
|
||||
return "/".join([self.api_endpoint, "register"])
|
||||
if operation == OPERATION_DELETE:
|
||||
return "/".join([self.api_endpoint, "deregister", identifier])
|
||||
|
||||
return super(ConsulAgentServiceModule, self).endpoint_url(operation, identifier)
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
existing = super(ConsulAgentServiceModule, self).prepare_object(existing, obj)
|
||||
if "ServicePort" in existing:
|
||||
existing["Port"] = existing.pop("ServicePort")
|
||||
|
||||
if "ID" not in existing:
|
||||
existing["ID"] = existing["Name"]
|
||||
|
||||
return existing
|
||||
|
||||
def needs_update(self, api_obj, module_obj):
|
||||
obj = {}
|
||||
if "Service" in api_obj:
|
||||
obj["Service"] = api_obj["Service"]
|
||||
api_obj = self.prepare_object(api_obj, obj)
|
||||
|
||||
if "Name" in module_obj:
|
||||
module_obj["Service"] = module_obj.pop("Name")
|
||||
if "ServicePort" in module_obj:
|
||||
module_obj["Port"] = module_obj.pop("ServicePort")
|
||||
|
||||
return super(ConsulAgentServiceModule, self).needs_update(api_obj, module_obj)
|
||||
|
||||
def delete_object(self, obj):
|
||||
if not self._module.check_mode:
|
||||
url = self.endpoint_url(OPERATION_DELETE, self.id_from_obj(obj, camel_case=True))
|
||||
self.put(url)
|
||||
return {}
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
required_if=_REQUIRED_IF,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
consul_module = ConsulAgentServiceModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -168,7 +168,7 @@ def normalize_ttl(ttl):
|
||||
class ConsulAuthMethodModule(_ConsulModule):
|
||||
api_endpoint = "acl/auth-method"
|
||||
result_key = "auth_method"
|
||||
unique_identifier = "name"
|
||||
unique_identifiers = ["name"]
|
||||
|
||||
def map_param(self, k, v, is_update):
|
||||
if k == "config" and v:
|
||||
|
||||
@@ -124,7 +124,7 @@ from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
class ConsulBindingRuleModule(_ConsulModule):
|
||||
api_endpoint = "acl/binding-rule"
|
||||
result_key = "binding_rule"
|
||||
unique_identifier = "id"
|
||||
unique_identifiers = ["id"]
|
||||
|
||||
def read_object(self):
|
||||
url = "acl/binding-rules?authmethod={0}".format(self.params["auth_method"])
|
||||
|
||||
@@ -33,6 +33,8 @@ attributes:
|
||||
version_added: 8.3.0
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
action_group:
|
||||
version_added: 8.3.0
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
@@ -143,7 +145,7 @@ _ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
class ConsulPolicyModule(_ConsulModule):
|
||||
api_endpoint = "acl/policy"
|
||||
result_key = "policy"
|
||||
unique_identifier = "id"
|
||||
unique_identifiers = ["id"]
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
|
||||
@@ -32,6 +32,8 @@ attributes:
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
version_added: 8.3.0
|
||||
action_group:
|
||||
version_added: 8.3.0
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
@@ -210,7 +212,7 @@ from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
class ConsulRoleModule(_ConsulModule):
|
||||
api_endpoint = "acl/role"
|
||||
result_key = "role"
|
||||
unique_identifier = "id"
|
||||
unique_identifiers = ["id"]
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
|
||||
@@ -29,6 +29,8 @@ attributes:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
action_group:
|
||||
version_added: 8.3.0
|
||||
options:
|
||||
id:
|
||||
description:
|
||||
|
||||
@@ -31,6 +31,8 @@ attributes:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
action_group:
|
||||
version_added: 8.3.0
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
@@ -233,13 +235,13 @@ def normalize_link_obj(api_obj, module_obj, key):
|
||||
class ConsulTokenModule(_ConsulModule):
|
||||
api_endpoint = "acl/token"
|
||||
result_key = "token"
|
||||
unique_identifier = "accessor_id"
|
||||
unique_identifiers = ["accessor_id"]
|
||||
|
||||
create_only_fields = {"expiration_ttl"}
|
||||
|
||||
def read_object(self):
|
||||
# if `accessor_id` is not supplied we can only create objects and are not idempotent
|
||||
if not self.params.get(self.unique_identifier):
|
||||
if not self.id_from_obj(self.params):
|
||||
return None
|
||||
return super(ConsulTokenModule, self).read_object()
|
||||
|
||||
|
||||
@@ -68,9 +68,10 @@ options:
|
||||
mode:
|
||||
description:
|
||||
- Controls the module behavior. See notes below for more details.
|
||||
- Default is V(compatibility) but that behavior is deprecated and will be changed to V(new) in community.general 9.0.0.
|
||||
- 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:
|
||||
@@ -80,12 +81,16 @@ options:
|
||||
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 is the default mode.
|
||||
- >
|
||||
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."
|
||||
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)"
|
||||
@@ -150,7 +155,7 @@ class CPANMinus(ModuleHelper):
|
||||
mirror_only=dict(type='bool', default=False),
|
||||
installdeps=dict(type='bool', default=False),
|
||||
executable=dict(type='path'),
|
||||
mode=dict(type='str', choices=['compatibility', 'new']),
|
||||
mode=dict(type='str', default='new', choices=['compatibility', 'new']),
|
||||
name_check=dict(type='str')
|
||||
),
|
||||
required_one_of=[('name', 'from_path')],
|
||||
@@ -165,17 +170,10 @@ class CPANMinus(ModuleHelper):
|
||||
installdeps=cmd_runner_fmt.as_bool("--installdeps"),
|
||||
pkg_spec=cmd_runner_fmt.as_list(),
|
||||
)
|
||||
use_old_vardict = False
|
||||
|
||||
def __init_module__(self):
|
||||
v = self.vars
|
||||
if v.mode is None:
|
||||
self.deprecate(
|
||||
"The default value 'compatibility' for parameter 'mode' is being deprecated "
|
||||
"and it will be replaced by 'new'",
|
||||
version="9.0.0",
|
||||
collection_name="community.general"
|
||||
)
|
||||
v.mode = "compatibility"
|
||||
if v.mode == "compatibility":
|
||||
if v.name_check:
|
||||
self.do_raise("Parameter name_check can only be used with mode=new")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user