mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 02:16:50 +00:00
Compare commits
490 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a0794d330 | ||
|
|
2c1172d5d2 | ||
|
|
e49a26b6ab | ||
|
|
a781573977 | ||
|
|
88b1d1e0c1 | ||
|
|
45dd6a59f8 | ||
|
|
71bf1184ef | ||
|
|
855201d9fb | ||
|
|
4a6ed38ee2 | ||
|
|
c4ea44ffb4 | ||
|
|
2109d10740 | ||
|
|
d32a2e0df9 | ||
|
|
4f5a5a5c3d | ||
|
|
372639ba58 | ||
|
|
0e69322924 | ||
|
|
f436839cb8 | ||
|
|
641f7c0890 | ||
|
|
36bd15c804 | ||
|
|
2d226ad94d | ||
|
|
fade5f539c | ||
|
|
2e1ac61ca9 | ||
|
|
f2d078ebf0 | ||
|
|
fee6fcfbdd | ||
|
|
393358bbb1 | ||
|
|
b98cc00fa3 | ||
|
|
9d51e05cfb | ||
|
|
440bdb1119 | ||
|
|
d02f564019 | ||
|
|
adddf455fb | ||
|
|
0e3d9e1497 | ||
|
|
fec312eb54 | ||
|
|
f7b1393752 | ||
|
|
6f8d1849b5 | ||
|
|
6a4bc401c5 | ||
|
|
f5f1a33a7e | ||
|
|
0b0a4b0eaa | ||
|
|
24e5bf3d5b | ||
|
|
4f0b5b88a1 | ||
|
|
be4e7fc374 | ||
|
|
ba9f295b3f | ||
|
|
4b4a2a5977 | ||
|
|
5a1ee4e3ee | ||
|
|
11c0f9642c | ||
|
|
e42353d690 | ||
|
|
5af750d06b | ||
|
|
f745dd369e | ||
|
|
745c7a856e | ||
|
|
4e1e5a92e3 | ||
|
|
12b39987a0 | ||
|
|
32338f14e5 | ||
|
|
2583e3993d | ||
|
|
e4cef5cb24 | ||
|
|
cde2be8100 | ||
|
|
40ce650d08 | ||
|
|
c09b5e2c66 | ||
|
|
a618203c01 | ||
|
|
882ce25ce2 | ||
|
|
25a6603059 | ||
|
|
065287f260 | ||
|
|
b0cf8ce6e7 | ||
|
|
68eb07a900 | ||
|
|
a7d8397449 | ||
|
|
c71274639d | ||
|
|
5ef9476207 | ||
|
|
36edaaa6ea | ||
|
|
5c80ff00ab | ||
|
|
a45cb0ca04 | ||
|
|
838ff55003 | ||
|
|
8927dc777a | ||
|
|
baeae01797 | ||
|
|
c3df54689c | ||
|
|
e043274ced | ||
|
|
1355b8d533 | ||
|
|
5ea46a581d | ||
|
|
a8be2e2a58 | ||
|
|
07fa35791f | ||
|
|
06a3e3708d | ||
|
|
56532388fd | ||
|
|
6d09a3588e | ||
|
|
a73404ae3f | ||
|
|
ddf566a729 | ||
|
|
0c676df7cf | ||
|
|
3dcdd3b59e | ||
|
|
8b2e658fc0 | ||
|
|
9bf146a67a | ||
|
|
567b573971 | ||
|
|
b7b781a2be | ||
|
|
f852ac90a2 | ||
|
|
77f64cc56d | ||
|
|
faa3b7349f | ||
|
|
3fd8c520c6 | ||
|
|
5e5cba985f | ||
|
|
8bfd8bfb09 | ||
|
|
533df42e6f | ||
|
|
ac86d28687 | ||
|
|
6cffa0f84f | ||
|
|
78847004c2 | ||
|
|
84320bacb2 | ||
|
|
351eb7292c | ||
|
|
952ee5c5e8 | ||
|
|
14a0e387ac | ||
|
|
c5ada449e4 | ||
|
|
22d8e99282 | ||
|
|
e67ba12211 | ||
|
|
2f04bd32d0 | ||
|
|
62613427af | ||
|
|
febf5f24ab | ||
|
|
6652261ef4 | ||
|
|
c4da880c46 | ||
|
|
1d87acef96 | ||
|
|
8bc51bb0ec | ||
|
|
504759ce92 | ||
|
|
a3704353c9 | ||
|
|
fef1a495e1 | ||
|
|
871829dd97 | ||
|
|
957ccd463e | ||
|
|
a0c7c10099 | ||
|
|
50e2794658 | ||
|
|
c88610305b | ||
|
|
9f0af5380c | ||
|
|
c512b789cb | ||
|
|
ec23171586 | ||
|
|
1704f947e3 | ||
|
|
80c7fc2d12 | ||
|
|
5b1bb61b9e | ||
|
|
56e00efcba | ||
|
|
ef42314714 | ||
|
|
9a6eb4e028 | ||
|
|
cf4b814c2d | ||
|
|
f47fced4ca | ||
|
|
370fa9814a | ||
|
|
7f60b1f2dd | ||
|
|
bbbc98a751 | ||
|
|
233743f2fe | ||
|
|
8539c534e3 | ||
|
|
eadf1320df | ||
|
|
1242dff77f | ||
|
|
2ce82ce1fa | ||
|
|
f3aab7a5b8 | ||
|
|
b3d79d728e | ||
|
|
ecd6bca049 | ||
|
|
84c883e854 | ||
|
|
91c8d6badc | ||
|
|
3cf6a67f74 | ||
|
|
3593d9c17c | ||
|
|
b0910d6a47 | ||
|
|
5c6053bf79 | ||
|
|
d789351195 | ||
|
|
a4739d8a36 | ||
|
|
986c0ab03a | ||
|
|
67279e7ca1 | ||
|
|
f34cd9ddb9 | ||
|
|
cbab5e887d | ||
|
|
c0cd4827da | ||
|
|
833c21a2bc | ||
|
|
cd333e6575 | ||
|
|
a70de88577 | ||
|
|
3ce83dcf6a | ||
|
|
6448372c04 | ||
|
|
be09373815 | ||
|
|
9a6f7c5c3f | ||
|
|
1f94bd4a17 | ||
|
|
b15a2c52e3 | ||
|
|
f764685c53 | ||
|
|
1d6552e005 | ||
|
|
770ae38aff | ||
|
|
4edae7afd0 | ||
|
|
40f87e25ff | ||
|
|
ebf8d9cde1 | ||
|
|
f0c5dd9cbc | ||
|
|
656a7f7087 | ||
|
|
c2303926af | ||
|
|
aa707d665e | ||
|
|
be2fd43243 | ||
|
|
b2eb0fb8f8 | ||
|
|
57053f8a32 | ||
|
|
583a7a75d3 | ||
|
|
065fdf990d | ||
|
|
2ed7e96372 | ||
|
|
985fbb321b | ||
|
|
037863b834 | ||
|
|
e19fda6cb0 | ||
|
|
4175c4c8fe | ||
|
|
e64d124e18 | ||
|
|
52c0a1565d | ||
|
|
df89012081 | ||
|
|
ee2d7cd21b | ||
|
|
22735bcc73 | ||
|
|
5100d972b3 | ||
|
|
7ce39c38c9 | ||
|
|
6316bd6e4d | ||
|
|
d3b9759ef1 | ||
|
|
2321a12d07 | ||
|
|
e8f9f21be1 | ||
|
|
f1fee975ba | ||
|
|
d4e831f31d | ||
|
|
07d0de5640 | ||
|
|
c1309ceb8b | ||
|
|
00efbe6ea2 | ||
|
|
d18092a128 | ||
|
|
b783d025df | ||
|
|
113764215d | ||
|
|
ef8fb888cd | ||
|
|
8385d2eb39 | ||
|
|
de38d23bdc | ||
|
|
3cb9b0fa91 | ||
|
|
551e5e4bd5 | ||
|
|
c75711167f | ||
|
|
b279694779 | ||
|
|
625d22391f | ||
|
|
1b488b53f5 | ||
|
|
51648d5328 | ||
|
|
87aedc7bd6 | ||
|
|
1a0c9eb5e6 | ||
|
|
b862c0db49 | ||
|
|
adba23c223 | ||
|
|
7fa84e8ec7 | ||
|
|
14a86ed0ad | ||
|
|
dcfd0f47e6 | ||
|
|
481570d0e3 | ||
|
|
9254110b8b | ||
|
|
17c8e274dc | ||
|
|
30289c7a03 | ||
|
|
e8861cafa6 | ||
|
|
c47888a5f9 | ||
|
|
58ba101990 | ||
|
|
bf54291500 | ||
|
|
8f27ef76f5 | ||
|
|
61e82c50e4 | ||
|
|
dfbde55aeb | ||
|
|
24b6441580 | ||
|
|
4381ac1bf3 | ||
|
|
e83bb285b2 | ||
|
|
edd4637b9f | ||
|
|
eefdf5b58e | ||
|
|
39c39e3de1 | ||
|
|
50284d1292 | ||
|
|
1590892a56 | ||
|
|
f6722c142d | ||
|
|
417db583e7 | ||
|
|
aa3b53fb87 | ||
|
|
ffca7eaf52 | ||
|
|
5b9b98340b | ||
|
|
4be9bb1118 | ||
|
|
d50476cdab | ||
|
|
363e8662b0 | ||
|
|
5365dcef3c | ||
|
|
89accbfa2b | ||
|
|
63210f4fc4 | ||
|
|
01864514c2 | ||
|
|
418589e346 | ||
|
|
88fab247ca | ||
|
|
56edbfc539 | ||
|
|
c94fa6132d | ||
|
|
2fa17c32a3 | ||
|
|
926f627128 | ||
|
|
7c6f286df2 | ||
|
|
b6ed6787b5 | ||
|
|
94a350e72b | ||
|
|
46d454eae0 | ||
|
|
adfd73d7ed | ||
|
|
aa2a5d9578 | ||
|
|
0f300bddb9 | ||
|
|
3785b656d6 | ||
|
|
16499072ff | ||
|
|
cad6b30036 | ||
|
|
2df1126d27 | ||
|
|
0d5ec37249 | ||
|
|
7c04aaa48f | ||
|
|
80113063ac | ||
|
|
1b09e8168a | ||
|
|
aadd48461c | ||
|
|
d565a20013 | ||
|
|
c69fb82ee0 | ||
|
|
cffc3dad11 | ||
|
|
a27025946b | ||
|
|
1825feb652 | ||
|
|
0c2d1eda44 | ||
|
|
d617f6919f | ||
|
|
b17cc09b07 | ||
|
|
ee7f44b09b | ||
|
|
a357944fb0 | ||
|
|
5d7d973f6d | ||
|
|
f3a516b79d | ||
|
|
d4eaef2d83 | ||
|
|
235e55fa9f | ||
|
|
c3baaa8cfa | ||
|
|
d68f6fcfff | ||
|
|
70e4ae440c | ||
|
|
8b66bb9a02 | ||
|
|
76fbb50270 | ||
|
|
93971b292a | ||
|
|
724bba79d5 | ||
|
|
e44f43b4d2 | ||
|
|
f82422502b | ||
|
|
5588ce3741 | ||
|
|
719ecc9e85 | ||
|
|
1a801323a8 | ||
|
|
7ebb301930 | ||
|
|
fb5047b605 | ||
|
|
b7977b8fa9 | ||
|
|
bae1440425 | ||
|
|
04f3dd2b56 | ||
|
|
99e3965ece | ||
|
|
14625a214a | ||
|
|
3c067aa2c3 | ||
|
|
01004bd27b | ||
|
|
f8265ecc4e | ||
|
|
2e355bef9f | ||
|
|
e6f65634fe | ||
|
|
61314898ca | ||
|
|
301711e0d3 | ||
|
|
7cf834fb3c | ||
|
|
eda3d160fa | ||
|
|
b71d8813b2 | ||
|
|
a4c0df1ded | ||
|
|
a2b1756bea | ||
|
|
08d89a2f85 | ||
|
|
1dad95370e | ||
|
|
200b858b36 | ||
|
|
342a5a14f9 | ||
|
|
4609907367 | ||
|
|
7d68af57af | ||
|
|
fb3768aada | ||
|
|
93f990a1b9 | ||
|
|
f003833c1a | ||
|
|
8eb94dc36b | ||
|
|
7bf155284f | ||
|
|
0f5f00f41a | ||
|
|
b02ea33f9b | ||
|
|
437d1bbf7a | ||
|
|
a1582aa8cb | ||
|
|
4816157c05 | ||
|
|
67356d287d | ||
|
|
2b76b1f43a | ||
|
|
0f2d5136b8 | ||
|
|
58a4610b61 | ||
|
|
d1a412dafc | ||
|
|
c82362194b | ||
|
|
bb80ff6aee | ||
|
|
15b950f1cf | ||
|
|
7577d5218a | ||
|
|
f317fd924a | ||
|
|
6070dc80d4 | ||
|
|
b3fad4fa87 | ||
|
|
76626eb7e8 | ||
|
|
37ba1d0e5e | ||
|
|
57d1e74f3d | ||
|
|
f6b5b793c8 | ||
|
|
6584348d05 | ||
|
|
a610e27853 | ||
|
|
01220475dc | ||
|
|
0a1b53a10e | ||
|
|
db8f38ea3a | ||
|
|
7c0e4bda35 | ||
|
|
50425a49ec | ||
|
|
ce30e0732b | ||
|
|
c2cbac062e | ||
|
|
ed4bc4c1d2 | ||
|
|
cda63f7221 | ||
|
|
ebaf490653 | ||
|
|
9027c367d4 | ||
|
|
e69ea28662 | ||
|
|
eccc41eadc | ||
|
|
b5d56463a6 | ||
|
|
3c5094d971 | ||
|
|
15cbc9665e | ||
|
|
4259792751 | ||
|
|
fe4099c163 | ||
|
|
b2417accbf | ||
|
|
9b21b0d31c | ||
|
|
330b0304ef | ||
|
|
f8fc18412c | ||
|
|
abd2a85709 | ||
|
|
c1536a3501 | ||
|
|
4fa1f1a6dd | ||
|
|
42cc5280d9 | ||
|
|
1c8fbed36c | ||
|
|
f8d0d07fed | ||
|
|
3ee01ddb7f | ||
|
|
5d5befdf96 | ||
|
|
98cea930f0 | ||
|
|
9036d8edd0 | ||
|
|
72d1af86f3 | ||
|
|
6c718a4f55 | ||
|
|
751e2400e6 | ||
|
|
c2ae3dd026 | ||
|
|
9a97d5e14a | ||
|
|
f794ba17c9 | ||
|
|
f4575816be | ||
|
|
fd3bc75fb3 | ||
|
|
dc898dfdf8 | ||
|
|
28c7a62989 | ||
|
|
f490bc1dba | ||
|
|
5bd671b8bf | ||
|
|
0057908705 | ||
|
|
39d83fefee | ||
|
|
145b4e7433 | ||
|
|
d45b112cc0 | ||
|
|
fc64490f89 | ||
|
|
4a0276261b | ||
|
|
2e0079cb3e | ||
|
|
4209c58ae1 | ||
|
|
e27851e2e3 | ||
|
|
ee4a4f3b49 | ||
|
|
50eb0a95de | ||
|
|
ba559d24cd | ||
|
|
c35d8b560c | ||
|
|
cbb29febd6 | ||
|
|
f24302f301 | ||
|
|
27cf237a86 | ||
|
|
6f518ba18b | ||
|
|
5d29270e23 | ||
|
|
920046beaf | ||
|
|
1592be779a | ||
|
|
e261332acf | ||
|
|
a406fb1e0c | ||
|
|
546eb77fd0 | ||
|
|
5c7b103936 | ||
|
|
91110f4933 | ||
|
|
7d7e099333 | ||
|
|
bee530b6cc | ||
|
|
34c4b1f367 | ||
|
|
f4af31b76b | ||
|
|
f583dbd2d3 | ||
|
|
be0d207f90 | ||
|
|
e968f89125 | ||
|
|
92466e0dbd | ||
|
|
ecf6f585ee | ||
|
|
6789f7939a | ||
|
|
94f23ee647 | ||
|
|
7dcbb1ade4 | ||
|
|
adca0d5d75 | ||
|
|
4699568996 | ||
|
|
41ba810463 | ||
|
|
a8f5926da3 | ||
|
|
3b24363383 | ||
|
|
7b0890c98f | ||
|
|
f986b97c9a | ||
|
|
396b94183d | ||
|
|
88b5e7ec0b | ||
|
|
bd1c1b257f | ||
|
|
211b520017 | ||
|
|
cae94f9d5e | ||
|
|
fc0981f3f1 | ||
|
|
d63658ea79 | ||
|
|
543792a68e | ||
|
|
1f518751a1 | ||
|
|
04162da6c9 | ||
|
|
b5a276dc77 | ||
|
|
ef0665843f | ||
|
|
c55585a0e2 | ||
|
|
c86d34f198 | ||
|
|
1eeff1556f | ||
|
|
75a69de909 | ||
|
|
1a35fb1d77 | ||
|
|
b4275969c1 | ||
|
|
41b5464942 | ||
|
|
7ee0389c98 | ||
|
|
816d4e8f49 | ||
|
|
07f854fff1 | ||
|
|
307a291b57 | ||
|
|
c4ebd482eb | ||
|
|
5cec31586f | ||
|
|
4bdd27de6a | ||
|
|
dd726d28ca | ||
|
|
e55df1c63e | ||
|
|
2a40169da5 | ||
|
|
131bf72d72 | ||
|
|
b49aeab5f5 | ||
|
|
91bfdbd7a0 | ||
|
|
f663fe73c1 | ||
|
|
703519197f | ||
|
|
fce8eac2a8 | ||
|
|
7531e97ddd | ||
|
|
7b83815835 | ||
|
|
70023f98f6 | ||
|
|
caa4e4feb4 | ||
|
|
714b24b01c | ||
|
|
5d5dd734e5 | ||
|
|
45d3708d31 | ||
|
|
5b7c759552 | ||
|
|
443d5a2a5f | ||
|
|
813030a5f2 | ||
|
|
ac398d8b2b | ||
|
|
f21f1cf461 | ||
|
|
b714bed0c1 | ||
|
|
3416a3c22a | ||
|
|
d4aeb322bb | ||
|
|
165da11731 |
@@ -29,14 +29,14 @@ schedules:
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-9
|
||||
- stable-8
|
||||
- stable-7
|
||||
- cron: 0 11 * * 0
|
||||
displayName: Weekly (old stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-6
|
||||
- stable-7
|
||||
|
||||
variables:
|
||||
- name: checkoutPath
|
||||
@@ -53,20 +53,20 @@ variables:
|
||||
resources:
|
||||
containers:
|
||||
- container: default
|
||||
image: quay.io/ansible/azure-pipelines-test-container:4.0.1
|
||||
image: quay.io/ansible/azure-pipelines-test-container:6.0.0
|
||||
|
||||
pool: Standard
|
||||
|
||||
stages:
|
||||
### Sanity
|
||||
- stage: Sanity_devel
|
||||
displayName: Sanity devel
|
||||
- stage: Sanity_2_17
|
||||
displayName: Sanity 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: devel/sanity/{0}
|
||||
testFormat: 2.17/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
@@ -99,28 +99,15 @@ stages:
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- stage: Sanity_2_14
|
||||
displayName: Sanity 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.14/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
### Units
|
||||
- stage: Units_devel
|
||||
displayName: Units devel
|
||||
- stage: Units_2_17
|
||||
displayName: Units 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: devel/units/{0}/1
|
||||
testFormat: 2.17/units/{0}/1
|
||||
targets:
|
||||
- test: 3.7
|
||||
- test: 3.8
|
||||
@@ -151,48 +138,40 @@ stages:
|
||||
targets:
|
||||
- test: 3.5
|
||||
- test: "3.10"
|
||||
- stage: Units_2_14
|
||||
displayName: Units 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.14/units/{0}/1
|
||||
targets:
|
||||
- test: 3.9
|
||||
|
||||
## Remote
|
||||
- stage: Remote_devel_extra_vms
|
||||
displayName: Remote devel extra VMs
|
||||
- stage: Remote_2_17_extra_vms
|
||||
displayName: Remote 2.17 extra VMs
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
testFormat: 2.17/{0}
|
||||
targets:
|
||||
- name: Alpine 3.18
|
||||
test: alpine/3.18
|
||||
- name: Alpine 3.19
|
||||
test: alpine/3.19
|
||||
# - name: Fedora 39
|
||||
# test: fedora/39
|
||||
- name: Ubuntu 22.04
|
||||
test: ubuntu/22.04
|
||||
groups:
|
||||
- vm
|
||||
- stage: Remote_devel
|
||||
displayName: Remote devel
|
||||
- stage: Remote_2_17
|
||||
displayName: Remote 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
testFormat: 2.17/{0}
|
||||
targets:
|
||||
- name: macOS 13.2
|
||||
test: macos/13.2
|
||||
- name: macOS 14.3
|
||||
test: macos/14.3
|
||||
- name: RHEL 9.3
|
||||
test: rhel/9.3
|
||||
- name: FreeBSD 13.2
|
||||
test: freebsd/13.2
|
||||
- name: FreeBSD 13.3
|
||||
test: freebsd/13.3
|
||||
- name: FreeBSD 14.0
|
||||
test: freebsd/14.0
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -205,14 +184,14 @@ stages:
|
||||
parameters:
|
||||
testFormat: 2.16/{0}
|
||||
targets:
|
||||
#- name: macOS 13.2
|
||||
# test: macos/13.2
|
||||
- name: macOS 13.2
|
||||
test: macos/13.2
|
||||
- name: RHEL 9.2
|
||||
test: rhel/9.2
|
||||
- name: RHEL 8.8
|
||||
test: rhel/8.8
|
||||
#- name: FreeBSD 13.2
|
||||
# test: freebsd/13.2
|
||||
# - name: FreeBSD 13.2
|
||||
# test: freebsd/13.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -239,33 +218,15 @@ stages:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Remote_2_14
|
||||
displayName: Remote 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.14/{0}
|
||||
targets:
|
||||
#- name: macOS 12.0
|
||||
# test: macos/12.0
|
||||
- name: RHEL 9.0
|
||||
test: rhel/9.0
|
||||
#- name: FreeBSD 12.4
|
||||
# test: freebsd/12.4
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Docker
|
||||
- stage: Docker_devel
|
||||
displayName: Docker devel
|
||||
- stage: Docker_2_17
|
||||
displayName: Docker 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/linux/{0}
|
||||
testFormat: 2.17/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 39
|
||||
test: fedora39
|
||||
@@ -273,8 +234,8 @@ stages:
|
||||
test: ubuntu2004
|
||||
- name: Ubuntu 22.04
|
||||
test: ubuntu2204
|
||||
- name: Alpine 3
|
||||
test: alpine3
|
||||
- name: Alpine 3.19
|
||||
test: alpine319
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -291,6 +252,8 @@ stages:
|
||||
test: fedora38
|
||||
- name: openSUSE 15
|
||||
test: opensuse15
|
||||
- name: Alpine 3
|
||||
test: alpine3
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -311,50 +274,36 @@ stages:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_14
|
||||
displayName: Docker 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.14/linux/{0}
|
||||
targets:
|
||||
- name: Alpine 3
|
||||
test: alpine3
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Community Docker
|
||||
- stage: Docker_community_devel
|
||||
displayName: Docker (community images) devel
|
||||
- stage: Docker_community_2_17
|
||||
displayName: Docker (community images) 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/linux-community/{0}
|
||||
testFormat: 2.17/linux-community/{0}
|
||||
targets:
|
||||
- name: Debian Bullseye
|
||||
test: debian-bullseye/3.9
|
||||
- name: Debian Bookworm
|
||||
test: debian-bookworm/3.11
|
||||
- name: ArchLinux
|
||||
test: archlinux/3.11
|
||||
test: archlinux/3.12
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Generic
|
||||
- stage: Generic_devel
|
||||
displayName: Generic devel
|
||||
- stage: Generic_2_17
|
||||
displayName: Generic 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: devel/generic/{0}/1
|
||||
testFormat: 2.17/generic/{0}/1
|
||||
targets:
|
||||
- test: '3.7'
|
||||
- test: '3.12'
|
||||
@@ -380,42 +329,27 @@ stages:
|
||||
testFormat: 2.15/generic/{0}/1
|
||||
targets:
|
||||
- test: '3.9'
|
||||
- stage: Generic_2_14
|
||||
displayName: Generic 2.14
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.14/generic/{0}/1
|
||||
targets:
|
||||
- test: '3.10'
|
||||
|
||||
- stage: Summary
|
||||
condition: succeededOrFailed()
|
||||
dependsOn:
|
||||
- Sanity_devel
|
||||
- Sanity_2_17
|
||||
- Sanity_2_16
|
||||
- Sanity_2_15
|
||||
- Sanity_2_14
|
||||
- Units_devel
|
||||
- Units_2_17
|
||||
- Units_2_16
|
||||
- Units_2_15
|
||||
- Units_2_14
|
||||
- Remote_devel_extra_vms
|
||||
- Remote_devel
|
||||
- Remote_2_17_extra_vms
|
||||
- Remote_2_17
|
||||
- Remote_2_16
|
||||
- Remote_2_15
|
||||
- Remote_2_14
|
||||
- Docker_devel
|
||||
- Docker_2_17
|
||||
- Docker_2_16
|
||||
- Docker_2_15
|
||||
- Docker_2_14
|
||||
- Docker_community_devel
|
||||
- Docker_community_2_17
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - Generic_devel
|
||||
# - Generic_2_17
|
||||
# - Generic_2_16
|
||||
# - Generic_2_15
|
||||
# - Generic_2_14
|
||||
jobs:
|
||||
- template: templates/coverage.yml
|
||||
|
||||
72
.github/BOTMETA.yml
vendored
72
.github/BOTMETA.yml
vendored
@@ -97,9 +97,6 @@ files:
|
||||
$connections/funcd.py:
|
||||
maintainers: mscherer
|
||||
$connections/iocage.py: {}
|
||||
$connections/incus.py:
|
||||
labels: incus
|
||||
maintainers: stgraber
|
||||
$connections/jail.py:
|
||||
maintainers: $team_ansible_core
|
||||
$connections/lxc.py: {}
|
||||
@@ -122,7 +119,7 @@ files:
|
||||
labels: hwc
|
||||
maintainers: $team_huawei
|
||||
$doc_fragments/nomad.py:
|
||||
maintainers: chris93111 apecnascimento
|
||||
maintainers: chris93111
|
||||
$doc_fragments/xenserver.py:
|
||||
labels: xenserver
|
||||
maintainers: bvitnik
|
||||
@@ -136,8 +133,6 @@ files:
|
||||
maintainers: giner
|
||||
$filters/from_csv.py:
|
||||
maintainers: Ajpantuso
|
||||
$filters/from_ini.py:
|
||||
maintainers: sscheib
|
||||
$filters/groupby_as_dict.py:
|
||||
maintainers: felixfontein
|
||||
$filters/hashids.py:
|
||||
@@ -158,8 +153,6 @@ files:
|
||||
maintainers: resmo
|
||||
$filters/to_hours.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_ini.py:
|
||||
maintainers: sscheib
|
||||
$filters/to_milliseconds.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_minutes.yml:
|
||||
@@ -241,8 +234,6 @@ files:
|
||||
$lookups/filetree.py:
|
||||
maintainers: dagwieers
|
||||
$lookups/flattened.py: {}
|
||||
$lookups/github_app_access_token.py:
|
||||
maintainers: weisheng-p
|
||||
$lookups/hiera.py:
|
||||
maintainers: jparrill
|
||||
$lookups/keyring.py: {}
|
||||
@@ -484,8 +475,6 @@ files:
|
||||
maintainers: russoz
|
||||
$modules/dnf_versionlock.py:
|
||||
maintainers: moreda
|
||||
$modules/dnf_config_manager.py:
|
||||
maintainers: ahyattdev
|
||||
$modules/dnsimple.py:
|
||||
maintainers: drcapulet
|
||||
$modules/dnsimple_info.py:
|
||||
@@ -508,9 +497,6 @@ files:
|
||||
$modules/facter.py:
|
||||
labels: facter
|
||||
maintainers: $team_ansible_core gamethis
|
||||
$modules/facter_facts.py:
|
||||
labels: facter
|
||||
maintainers: russoz $team_ansible_core gamethis
|
||||
$modules/filesize.py:
|
||||
maintainers: quidame
|
||||
$modules/filesystem.py:
|
||||
@@ -537,8 +523,6 @@ files:
|
||||
maintainers: russoz
|
||||
$modules/git_config.py:
|
||||
maintainers: djmattyg007 mgedmin
|
||||
$modules/git_config_info.py:
|
||||
maintainers: guenhter
|
||||
$modules/github_:
|
||||
maintainers: stpierre
|
||||
$modules/github_deploy_key.py:
|
||||
@@ -560,8 +544,6 @@ files:
|
||||
ignore: dj-wasabi
|
||||
$modules/gitlab_branch.py:
|
||||
maintainers: paytroff
|
||||
$modules/gitlab_issue.py:
|
||||
maintainers: zvaraondrej
|
||||
$modules/gitlab_merge_request.py:
|
||||
maintainers: zvaraondrej
|
||||
$modules/gitlab_project_variable.py:
|
||||
@@ -661,8 +643,6 @@ files:
|
||||
$modules/ipa_:
|
||||
maintainers: $team_ipa
|
||||
ignore: fxfitz
|
||||
$modules/ipa_dnsrecord.py:
|
||||
maintainers: $team_ipa jwbernin
|
||||
$modules/ipbase_info.py:
|
||||
maintainers: dominikkukacka
|
||||
$modules/ipa_pwpolicy.py:
|
||||
@@ -758,12 +738,8 @@ files:
|
||||
maintainers: elfelip
|
||||
$modules/keycloak_user_federation.py:
|
||||
maintainers: laurpaum
|
||||
$modules/keycloak_component_info.py:
|
||||
maintainers: desand01
|
||||
$modules/keycloak_user_rolemapping.py:
|
||||
maintainers: bratwurzt
|
||||
$modules/keycloak_realm_rolemapping.py:
|
||||
maintainers: agross mhuysamen Gaetan2907
|
||||
$modules/keyring.py:
|
||||
maintainers: ahussey-redhat
|
||||
$modules/keyring_info.py:
|
||||
@@ -892,7 +868,7 @@ files:
|
||||
$modules/nmcli.py:
|
||||
maintainers: alcamie101
|
||||
$modules/nomad_:
|
||||
maintainers: chris93111 apecnascimento
|
||||
maintainers: chris93111
|
||||
$modules/nosh.py:
|
||||
maintainers: tacatac
|
||||
$modules/npm.py:
|
||||
@@ -1051,10 +1027,6 @@ files:
|
||||
maintainers: helldorado
|
||||
$modules/proxmox_nic.py:
|
||||
maintainers: Kogelvis
|
||||
$modules/proxmox_node_info.py:
|
||||
maintainers: jwbernin
|
||||
$modules/proxmox_storage_contents_info.py:
|
||||
maintainers: l00ptr
|
||||
$modules/proxmox_tasks_info:
|
||||
maintainers: paginabianca
|
||||
$modules/proxmox_template.py:
|
||||
@@ -1423,43 +1395,10 @@ files:
|
||||
ignore: matze
|
||||
labels: zypper
|
||||
maintainers: $team_suse
|
||||
$plugin_utils/unsafe.py:
|
||||
maintainers: felixfontein
|
||||
$tests/a_module.py:
|
||||
maintainers: felixfontein
|
||||
$tests/fqdn_valid.py:
|
||||
maintainers: vbotka
|
||||
#########################
|
||||
docs/docsite/rst/filter_guide.rst: {}
|
||||
docs/docsite/rst/filter_guide_abstract_informations.rst: {}
|
||||
docs/docsite/rst/filter_guide_abstract_informations_counting_elements_in_sequence.rst:
|
||||
maintainers: keilr
|
||||
docs/docsite/rst/filter_guide_abstract_informations_dictionaries.rst:
|
||||
maintainers: felixfontein giner
|
||||
docs/docsite/rst/filter_guide_abstract_informations_grouping.rst:
|
||||
maintainers: felixfontein
|
||||
docs/docsite/rst/filter_guide_abstract_informations_merging_lists_of_dictionaries.rst:
|
||||
maintainers: vbotka
|
||||
docs/docsite/rst/filter_guide_conversions.rst:
|
||||
maintainers: Ajpantuso kellyjonbrazil
|
||||
docs/docsite/rst/filter_guide_creating_identifiers.rst:
|
||||
maintainers: Ajpantuso
|
||||
docs/docsite/rst/filter_guide_paths.rst: {}
|
||||
docs/docsite/rst/filter_guide_selecting_json_data.rst: {}
|
||||
docs/docsite/rst/filter_guide_working_with_times.rst:
|
||||
maintainers: resmo
|
||||
docs/docsite/rst/filter_guide_working_with_unicode.rst:
|
||||
maintainers: Ajpantuso
|
||||
docs/docsite/rst/filter_guide_working_with_versions.rst:
|
||||
maintainers: ericzolf
|
||||
docs/docsite/rst/guide_alicloud.rst:
|
||||
maintainers: xiaozhu36
|
||||
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/test_guide.rst:
|
||||
maintainers: felixfontein
|
||||
#########################
|
||||
tests/:
|
||||
labels: tests
|
||||
@@ -1477,7 +1416,6 @@ macros:
|
||||
becomes: plugins/become
|
||||
caches: plugins/cache
|
||||
callbacks: plugins/callback
|
||||
cliconfs: plugins/cliconf
|
||||
connections: plugins/connection
|
||||
doc_fragments: plugins/doc_fragments
|
||||
filters: plugins/filter
|
||||
@@ -1485,7 +1423,7 @@ macros:
|
||||
lookups: plugins/lookup
|
||||
module_utils: plugins/module_utils
|
||||
modules: plugins/modules
|
||||
terminals: plugins/terminal
|
||||
plugin_utils: plugins/plugin_utils
|
||||
tests: plugins/test
|
||||
team_ansible_core:
|
||||
team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross
|
||||
|
||||
120
.github/workflows/ansible-test.yml
vendored
120
.github/workflows/ansible-test.yml
vendored
@@ -14,9 +14,9 @@ on:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
# Run EOL CI once per day (at 08:00 UTC)
|
||||
# Run EOL CI once per day (at 10:00 UTC)
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
- cron: '0 10 * * *'
|
||||
|
||||
concurrency:
|
||||
# Make sure there is at most one active run per PR, but do not cancel any non-PR runs
|
||||
@@ -29,18 +29,26 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
ansible:
|
||||
- '2.11'
|
||||
- '2.12'
|
||||
- '2.13'
|
||||
- '2.14'
|
||||
# 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
|
||||
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
|
||||
# for the latest list.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: >-
|
||||
${{ contains(fromJson(
|
||||
'["2.9", "2.10", "2.11"]'
|
||||
), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Perform sanity testing
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pull-request-change-detection: 'true'
|
||||
testing-type: sanity
|
||||
@@ -51,7 +59,10 @@ jobs:
|
||||
# image for these stable branches. The list of branches where this is necessary will
|
||||
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
|
||||
# for the latest list.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: >-
|
||||
${{ contains(fromJson(
|
||||
'["2.9", "2.10", "2.11"]'
|
||||
), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
|
||||
name: EOL Units (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }})
|
||||
strategy:
|
||||
# As soon as the first unit test fails, cancel the others to free up the CI queue
|
||||
@@ -64,14 +75,20 @@ jobs:
|
||||
exclude:
|
||||
- ansible: ''
|
||||
include:
|
||||
- ansible: '2.13'
|
||||
- ansible: '2.11'
|
||||
python: '2.7'
|
||||
- ansible: '2.13'
|
||||
- ansible: '2.11'
|
||||
python: '3.5'
|
||||
- ansible: '2.12'
|
||||
python: '2.6'
|
||||
- ansible: '2.12'
|
||||
python: '3.8'
|
||||
- ansible: '2.13'
|
||||
python: '2.7'
|
||||
- ansible: '2.13'
|
||||
python: '3.8'
|
||||
- ansible: '2.14'
|
||||
python: '3.9'
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
@@ -79,7 +96,9 @@ jobs:
|
||||
Ansible version ${{ matrix.ansible }}
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pre-test-cmd: >-
|
||||
mkdir -p ../../ansible
|
||||
@@ -95,7 +114,10 @@ jobs:
|
||||
# image for these stable branches. The list of branches where this is necessary will
|
||||
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
|
||||
# for the latest list.
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: >-
|
||||
${{ contains(fromJson(
|
||||
'["2.9", "2.10", "2.11"]'
|
||||
), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
|
||||
name: EOL I (Ⓐ${{ matrix.ansible }}+${{ matrix.docker }}+py${{ matrix.python }}:${{ matrix.target }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -111,6 +133,70 @@ jobs:
|
||||
exclude:
|
||||
- ansible: ''
|
||||
include:
|
||||
# 2.11
|
||||
- ansible: '2.11'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.11'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.11'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - ansible: '2.11'
|
||||
# docker: default
|
||||
# python: '2.7'
|
||||
# target: azp/generic/1/
|
||||
# - ansible: '2.11'
|
||||
# docker: default
|
||||
# python: '3.5'
|
||||
# target: azp/generic/1/
|
||||
# 2.12
|
||||
- ansible: '2.12'
|
||||
docker: centos6
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.12'
|
||||
docker: centos6
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.12'
|
||||
docker: centos6
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
- ansible: '2.12'
|
||||
docker: fedora34
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.12'
|
||||
docker: fedora34
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.12'
|
||||
docker: fedora34
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
- ansible: '2.12'
|
||||
docker: ubuntu1804
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.12'
|
||||
docker: ubuntu1804
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.12'
|
||||
docker: ubuntu1804
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - ansible: '2.12'
|
||||
# docker: default
|
||||
# python: '3.8'
|
||||
# target: azp/generic/1/
|
||||
# 2.13
|
||||
- ansible: '2.13'
|
||||
docker: fedora35
|
||||
@@ -153,6 +239,24 @@ jobs:
|
||||
# docker: default
|
||||
# python: '3.9'
|
||||
# target: azp/generic/1/
|
||||
# 2.14
|
||||
- ansible: '2.14'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.14'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.14'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
# 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/
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
@@ -161,7 +265,9 @@ jobs:
|
||||
under Python ${{ matrix.python }}
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
docker-image: ${{ matrix.docker }}
|
||||
integration-continue-on-error: 'false'
|
||||
|
||||
20
.github/workflows/import-galaxy.yml
vendored
Normal file
20
.github/workflows/import-galaxy.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: import-galaxy
|
||||
'on':
|
||||
# Run CI against all pushes (direct commits, also merged PRs) to main, and all Pull Requests
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
import-galaxy:
|
||||
permissions:
|
||||
contents: read
|
||||
name: Test to import built collection artifact with Galaxy importer
|
||||
uses: ansible-community/github-action-test-galaxy-import/.github/workflows/test-galaxy-import.yml@main
|
||||
19
.github/workflows/reuse.yml
vendored
19
.github/workflows/reuse.yml
vendored
@@ -7,10 +7,14 @@ name: Verify REUSE
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [main]
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
# Run CI once per day (at 07:30 UTC)
|
||||
schedule:
|
||||
- cron: '30 7 * * *'
|
||||
@@ -26,10 +30,5 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install reuse
|
||||
|
||||
- name: Check REUSE compliance
|
||||
run: |
|
||||
reuse lint
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@v4
|
||||
|
||||
1012
CHANGELOG.md
Normal file
1012
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
3
CHANGELOG.md.license
Normal file
3
CHANGELOG.md.license
Normal file
@@ -0,0 +1,3 @@
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
SPDX-FileCopyrightText: Ansible Project
|
||||
1163
CHANGELOG.rst
1163
CHANGELOG.rst
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,9 @@ Also, consider taking up a valuable, reviewed, but abandoned pull request which
|
||||
* Try committing your changes with an informative but short commit message.
|
||||
* Do not squash your commits and force-push to your branch if not needed. Reviews of your pull request are much easier with individual commits to comprehend the pull request history. All commits of your pull request branch will be squashed into one commit by GitHub upon merge.
|
||||
* Do not add merge commits to your PR. The bot will complain and you will have to rebase ([instructions for rebasing](https://docs.ansible.com/ansible/latest/dev_guide/developing_rebasing.html)) to remove them before your PR can be merged. To avoid that git automatically does merges during pulls, you can configure it to do rebases instead by running `git config pull.rebase true` inside the repository checkout.
|
||||
* Make sure your PR includes a [changelog fragment](https://docs.ansible.com/ansible/devel/community/development_process.html#creating-changelog-fragments). (You must not include a fragment for new modules or new plugins. Also you shouldn't include one for docs-only changes. If you're not sure, simply don't include one, we'll tell you whether one is needed or not :) )
|
||||
* Make sure your PR includes a [changelog fragment](https://docs.ansible.com/ansible/devel/community/collection_development_process.html#creating-a-changelog-fragment).
|
||||
* You must not include a fragment for new modules or new plugins. Also you shouldn't include one for docs-only changes. (If you're not sure, simply don't include one, we'll tell you whether one is needed or not :) )
|
||||
* Please always include a link to the pull request itself, and if the PR is about an issue, also a link to the issue. Also make sure the fragment ends with a period, and begins with a lower-case letter after `-`. (Again, if you don't do this, we'll add suggestions to fix it, so don't worry too much :) )
|
||||
* Avoid reformatting unrelated parts of the codebase in your PR. These types of changes will likely be requested for reversion, create additional work for reviewers, and may cause approval to be delayed.
|
||||
|
||||
You can also read [our Quick-start development guide](https://github.com/ansible/community-docs/blob/main/create_pr_quick_start_guide.rst).
|
||||
|
||||
21
README.md
21
README.md
@@ -6,9 +6,10 @@ 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)
|
||||
|
||||
This repository contains the `community.general` Ansible Collection. The collection is a part of the Ansible package and includes many modules and plugins supported by Ansible community which are not part of more specialized community collections.
|
||||
|
||||
@@ -24,7 +25,9 @@ If you encounter abusive behavior violating the [Ansible Code of Conduct](https:
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16 releases and the current development version of ansible-core. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
Tested with the current ansible-core 2.11, ansible-core 2.12, ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, and ansible-core 2.17 releases. Ansible-core versions before 2.11.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
|
||||
Parts of this collection will not work with ansible-core 2.11 on Python 3.12+.
|
||||
|
||||
## External requirements
|
||||
|
||||
@@ -71,13 +74,13 @@ We are actively accepting new contributors.
|
||||
|
||||
All types of contributions are very welcome.
|
||||
|
||||
You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.general/blob/main/CONTRIBUTING.md)!
|
||||
You don't know how to start? Refer to our [contribution guide](https://github.com/ansible-collections/community.general/blob/stable-7/CONTRIBUTING.md)!
|
||||
|
||||
The current maintainers are listed in the [commit-rights.md](https://github.com/ansible-collections/community.general/blob/main/commit-rights.md#people) file. If you have questions or need help, feel free to mention them in the proposals.
|
||||
The current maintainers are listed in the [commit-rights.md](https://github.com/ansible-collections/community.general/blob/stable-7/commit-rights.md#people) file. If you have questions or need help, feel free to mention them in the proposals.
|
||||
|
||||
You can find more information in the [developer guide for collections](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections), and in the [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html).
|
||||
|
||||
Also for some notes specific to this collection see [our CONTRIBUTING documentation](https://github.com/ansible-collections/community.general/blob/main/CONTRIBUTING.md).
|
||||
Also for some notes specific to this collection see [our CONTRIBUTING documentation](https://github.com/ansible-collections/community.general/blob/stable-7/CONTRIBUTING.md).
|
||||
|
||||
### Running tests
|
||||
|
||||
@@ -87,7 +90,7 @@ See [here](https://docs.ansible.com/ansible/devel/dev_guide/developing_collectio
|
||||
|
||||
To learn how to maintain / become a maintainer of this collection, refer to:
|
||||
|
||||
* [Committer guidelines](https://github.com/ansible-collections/community.general/blob/main/commit-rights.md).
|
||||
* [Committer guidelines](https://github.com/ansible-collections/community.general/blob/stable-7/commit-rights.md).
|
||||
* [Maintainer guidelines](https://github.com/ansible/community-docs/blob/main/maintaining.rst).
|
||||
|
||||
It is necessary for maintainers of this collection to be subscribed to:
|
||||
@@ -115,7 +118,7 @@ See the [Releasing guidelines](https://github.com/ansible/community-docs/blob/ma
|
||||
|
||||
## Release notes
|
||||
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-8/CHANGELOG.rst).
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-7/CHANGELOG.md).
|
||||
|
||||
## Roadmap
|
||||
|
||||
@@ -134,8 +137,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-7/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-7/LICENSES/BSD-2-Clause.txt), the [MIT license](https://github.com/ansible-collections/community.general/blob/stable-7/LICENSES/MIT.txt), and the [PSF 2.0 license](https://github.com/ansible-collections/community.general/blob/stable-7/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
@@ -12,23 +12,31 @@ mention_ancestor: true
|
||||
flatmap: true
|
||||
new_plugins_after_name: removed_features
|
||||
notesdir: fragments
|
||||
output_formats:
|
||||
- md
|
||||
- rst
|
||||
prelude_section_name: release_summary
|
||||
prelude_section_title: Release Summary
|
||||
sections:
|
||||
- - major_changes
|
||||
- Major Changes
|
||||
- - minor_changes
|
||||
- Minor Changes
|
||||
- - breaking_changes
|
||||
- Breaking Changes / Porting Guide
|
||||
- - deprecated_features
|
||||
- Deprecated Features
|
||||
- - removed_features
|
||||
- Removed Features (previously deprecated)
|
||||
- - security_fixes
|
||||
- Security Fixes
|
||||
- - bugfixes
|
||||
- Bugfixes
|
||||
- - known_issues
|
||||
- Known Issues
|
||||
- - major_changes
|
||||
- Major Changes
|
||||
- - minor_changes
|
||||
- Minor Changes
|
||||
- - breaking_changes
|
||||
- Breaking Changes / Porting Guide
|
||||
- - deprecated_features
|
||||
- Deprecated Features
|
||||
- - removed_features
|
||||
- Removed Features (previously deprecated)
|
||||
- - security_fixes
|
||||
- Security Fixes
|
||||
- - bugfixes
|
||||
- Bugfixes
|
||||
- - known_issues
|
||||
- Known Issues
|
||||
title: Community General
|
||||
trivial_section_name: trivial
|
||||
use_fqcn: true
|
||||
add_plugin_period: true
|
||||
changelog_nice_yaml: true
|
||||
changelog_sort: version
|
||||
|
||||
@@ -8,9 +8,3 @@ sections:
|
||||
toctree:
|
||||
- filter_guide
|
||||
- test_guide
|
||||
- title: Cloud Guides
|
||||
toctree:
|
||||
- guide_alicloud
|
||||
- guide_online
|
||||
- guide_packet
|
||||
- guide_scaleway
|
||||
|
||||
@@ -22,6 +22,7 @@ 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
|
||||
# The following URL directly points to the "Get Help" section
|
||||
url: https://forum.ansible.com/c/help/6/none
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_alicloud:
|
||||
|
||||
Alibaba Cloud Compute Services Guide
|
||||
====================================
|
||||
|
||||
Introduction
|
||||
````````````
|
||||
|
||||
The community.general collection contains several modules for controlling and managing Alibaba Cloud Compute Services (Alicloud). This guide
|
||||
explains how to use the Alicloud Ansible modules together.
|
||||
|
||||
All Alicloud modules require ``footmark`` - install it on your control machine with ``pip install footmark``.
|
||||
|
||||
Cloud modules, including Alicloud modules, are usually executed on your local machine (the control machine) with ``connection: local``, rather than on remote machines defined in your hosts.
|
||||
|
||||
Normally, you'll use the following pattern for plays that provision Alicloud resources:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
vars:
|
||||
- ...
|
||||
tasks:
|
||||
- ...
|
||||
|
||||
Authentication
|
||||
``````````````
|
||||
|
||||
You can specify your Alicloud authentication credentials (access key and secret key) by passing them as
|
||||
environment variables or by storing them in a vars file.
|
||||
|
||||
To pass authentication credentials as environment variables:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
export ALICLOUD_ACCESS_KEY='Alicloud123'
|
||||
export ALICLOUD_SECRET_KEY='AlicloudSecret123'
|
||||
|
||||
To store authentication credentials in a vars file, encrypt them with :ref:`Ansible Vault <vault>` to keep them secure, then list them:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
alicloud_access_key: "--REMOVED--"
|
||||
alicloud_secret_key: "--REMOVED--"
|
||||
|
||||
Note that if you store your credentials in a vars file, you need to refer to them in each Alicloud module. For example:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- community.general.ali_instance:
|
||||
alicloud_access_key: "{{ alicloud_access_key }}"
|
||||
alicloud_secret_key: "{{ alicloud_secret_key }}"
|
||||
image_id: "..."
|
||||
|
||||
Provisioning
|
||||
````````````
|
||||
|
||||
Alicloud modules create Alicloud ECS instances (:ansplugin:`community.general.ali_instance#module`) and retrieve information on these (:ansplugin:`community.general.ali_instance_info#module`).
|
||||
|
||||
You can use the ``count`` parameter to control the number of resources you create or terminate. For example, if you want exactly 5 instances tagged ``NewECS``, set the ``count`` of instances to 5 and the ``count_tag`` to ``NewECS``, as shown in the last task of the example playbook below. If there are no instances with the tag ``NewECS``, the task creates 5 new instances. If there are 2 instances with that tag, the task creates 3 more. If there are 8 instances with that tag, the task terminates 3 of those instances.
|
||||
|
||||
If you do not specify a ``count_tag``, the task creates the number of instances you specify in ``count`` with the ``instance_name`` you provide.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# alicloud_setup.yml
|
||||
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
|
||||
tasks:
|
||||
- name: Create a set of instances
|
||||
community.general.ali_instance:
|
||||
instance_type: ecs.n4.small
|
||||
image_id: "{{ ami_id }}"
|
||||
instance_name: "My-new-instance"
|
||||
instance_tags:
|
||||
Name: NewECS
|
||||
Version: 0.0.1
|
||||
count: 5
|
||||
count_tag:
|
||||
Name: NewECS
|
||||
allocate_public_ip: true
|
||||
max_bandwidth_out: 50
|
||||
register: create_instance
|
||||
|
||||
In the example playbook above, data about the instances created by this playbook is saved in the variable defined by the ``register`` keyword in the task.
|
||||
|
||||
Each Alicloud module offers a variety of parameter options. Not all options are demonstrated in the above example. See each individual module for further details and examples.
|
||||
@@ -1,49 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_online:
|
||||
|
||||
****************
|
||||
Online.net Guide
|
||||
****************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Online is a French hosting company mainly known for providing bare-metal servers named Dedibox.
|
||||
Check it out: `https://www.online.net/en <https://www.online.net/en>`_
|
||||
|
||||
Dynamic inventory for Online resources
|
||||
--------------------------------------
|
||||
|
||||
Ansible has a dynamic inventory plugin that can list your resources.
|
||||
|
||||
1. Create a YAML configuration such as ``online_inventory.yml`` with this content:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
plugin: community.general.online
|
||||
|
||||
2. Set your ``ONLINE_TOKEN`` environment variable with your token.
|
||||
|
||||
You need to open an account and log into it before you can get a token.
|
||||
You can find your token at the following page: `https://console.online.net/en/api/access <https://console.online.net/en/api/access>`_
|
||||
|
||||
3. You can test that your inventory is working by running:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ansible-inventory -v -i online_inventory.yml --list
|
||||
|
||||
|
||||
4. Now you can run your playbook or any other module with this inventory:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
$ ansible all -i online_inventory.yml -m ping
|
||||
sd-96735 | SUCCESS => {
|
||||
"changed": false,
|
||||
"ping": "pong"
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_packet:
|
||||
|
||||
**********************************
|
||||
Packet.net Guide
|
||||
**********************************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
`Packet.net <https://packet.net>`_ is a bare metal infrastructure host that is supported by the community.general collection through six cloud modules. The six modules are:
|
||||
|
||||
- :ansplugin:`community.general.packet_device#module`: manages servers on Packet. You can use this module to create, restart and delete devices.
|
||||
- :ansplugin:`community.general.packet_ip_subnet#module`: assign IP subnet to a bare metal server
|
||||
- :ansplugin:`community.general.packet_project#module`: create/delete a project in Packet host
|
||||
- :ansplugin:`community.general.packet_sshkey#module`: adds a public SSH key from file or value to the Packet infrastructure. Every subsequently-created device will have this public key installed in .ssh/authorized_keys.
|
||||
- :ansplugin:`community.general.packet_volume#module`: create/delete a volume in Packet host
|
||||
- :ansplugin:`community.general.packet_volume_attachment#module`: attach/detach a volume to a device in the Packet host
|
||||
|
||||
Note, this guide assumes you are familiar with Ansible and how it works. If you are not, have a look at their :ref:`docs <ansible_documentation>` before getting started.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
The Packet modules connect to the Packet API using the `packet-python package <https://pypi.org/project/packet-python/>`_. You can install it with pip:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install packet-python
|
||||
|
||||
In order to check the state of devices created by Ansible on Packet, it is a good idea to install one of the `Packet CLI clients <https://www.packet.net/developers/integrations/>`_. Otherwise you can check them through the `Packet portal <https://app.packet.net/portal>`_.
|
||||
|
||||
To use the modules you will need a Packet API token. You can generate an API token through the Packet portal `here <https://app.packet.net/portal#/api-keys>`__. The simplest way to authenticate yourself is to set the Packet API token in an environment variable:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export PACKET_API_TOKEN=Bfse9F24SFtfs423Gsd3ifGsd43sSdfs
|
||||
|
||||
If you are not comfortable exporting your API token, you can pass it as a parameter to the modules.
|
||||
|
||||
On Packet, devices and reserved IP addresses belong to `projects <https://www.packet.com/developers/api/#projects>`_. In order to use the packet_device module, you need to specify the UUID of the project in which you want to create or manage devices. You can find a project's UUID in the Packet portal `here <https://app.packet.net/portal#/projects/list/table/>`_ (it is just under the project table) or through one of the available `CLIs <https://www.packet.net/developers/integrations/>`_.
|
||||
|
||||
|
||||
If you want to use a new SSH key pair in this tutorial, you can generate it to ``./id_rsa`` and ``./id_rsa.pub`` as:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ssh-keygen -t rsa -f ./id_rsa
|
||||
|
||||
If you want to use an existing key pair, just copy the private and public key over to the playbook directory.
|
||||
|
||||
|
||||
Device Creation
|
||||
===============
|
||||
|
||||
The following code block is a simple playbook that creates one `Type 0 <https://www.packet.com/cloud/servers/t1-small/>`_ server (the ``plan`` parameter). You have to supply ``plan`` and ``operating_system``. ``location`` defaults to ``ewr1`` (Parsippany, NJ). You can find all the possible values for the parameters through a `CLI client <https://www.packet.net/developers/integrations/>`_.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_create.yml
|
||||
|
||||
- name: Create Ubuntu device
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_sshkey:
|
||||
key_file: ./id_rsa.pub
|
||||
label: tutorial key
|
||||
|
||||
- community.general.packet_device:
|
||||
project_id: <your_project_id>
|
||||
hostnames: myserver
|
||||
operating_system: ubuntu_16_04
|
||||
plan: baremetal_0
|
||||
facility: sjc1
|
||||
|
||||
After running ``ansible-playbook playbook_create.yml``, you should have a server provisioned on Packet. You can verify through a CLI or in the `Packet portal <https://app.packet.net/portal#/projects/list/table>`__.
|
||||
|
||||
If you get an error with the message "failed to set machine state present, error: Error 404: Not Found", please verify your project UUID.
|
||||
|
||||
|
||||
Updating Devices
|
||||
================
|
||||
|
||||
The two parameters used to uniquely identify Packet devices are: "device_ids" and "hostnames". Both parameters accept either a single string (later converted to a one-element list), or a list of strings.
|
||||
|
||||
The ``device_ids`` and ``hostnames`` parameters are mutually exclusive. The following values are all acceptable:
|
||||
|
||||
- device_ids: ``a27b7a83-fc93-435b-a128-47a5b04f2dcf``
|
||||
|
||||
- hostnames: ``mydev1``
|
||||
|
||||
- device_ids: ``[a27b7a83-fc93-435b-a128-47a5b04f2dcf, 4887130f-0ccd-49a0-99b0-323c1ceb527b]``
|
||||
|
||||
- hostnames: ``[mydev1, mydev2]``
|
||||
|
||||
In addition, hostnames can contain a special ``%d`` formatter along with a ``count`` parameter that lets you easily expand hostnames that follow a simple name and number pattern; in other words, ``hostnames: "mydev%d", count: 2`` will expand to [mydev1, mydev2].
|
||||
|
||||
If your playbook acts on existing Packet devices, you can only pass the ``hostname`` and ``device_ids`` parameters. The following playbook shows how you can reboot a specific Packet device by setting the ``hostname`` parameter:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_reboot.yml
|
||||
|
||||
- name: reboot myserver
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_device:
|
||||
project_id: <your_project_id>
|
||||
hostnames: myserver
|
||||
state: rebooted
|
||||
|
||||
You can also identify specific Packet devices with the ``device_ids`` parameter. The device's UUID can be found in the `Packet Portal <https://app.packet.net/portal>`_ or by using a `CLI <https://www.packet.net/developers/integrations/>`_. The following playbook removes a Packet device using the ``device_ids`` field:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_remove.yml
|
||||
|
||||
- name: remove a device
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_device:
|
||||
project_id: <your_project_id>
|
||||
device_ids: <myserver_device_id>
|
||||
state: absent
|
||||
|
||||
|
||||
More Complex Playbooks
|
||||
======================
|
||||
|
||||
In this example, we will create a CoreOS cluster with `user data <https://packet.com/developers/docs/servers/key-features/user-data/>`_.
|
||||
|
||||
|
||||
The CoreOS cluster will use `etcd <https://etcd.io/>`_ for discovery of other servers in the cluster. Before provisioning your servers, you will need to generate a discovery token for your cluster:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ curl -w "\n" 'https://discovery.etcd.io/new?size=3'
|
||||
|
||||
The following playbook will create an SSH key, 3 Packet servers, and then wait until SSH is ready (or until 5 minutes passed). Make sure to substitute the discovery token URL in ``user_data``, and the ``project_id`` before running ``ansible-playbook``. Also, feel free to change ``plan`` and ``facility``.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_coreos.yml
|
||||
|
||||
- name: Start 3 CoreOS nodes in Packet and wait until SSH is ready
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_sshkey:
|
||||
key_file: ./id_rsa.pub
|
||||
label: new
|
||||
|
||||
- community.general.packet_device:
|
||||
hostnames: [coreos-one, coreos-two, coreos-three]
|
||||
operating_system: coreos_beta
|
||||
plan: baremetal_0
|
||||
facility: ewr1
|
||||
project_id: <your_project_id>
|
||||
wait_for_public_IPv: 4
|
||||
user_data: |
|
||||
#cloud-config
|
||||
coreos:
|
||||
etcd2:
|
||||
discovery: https://discovery.etcd.io/<token>
|
||||
advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001
|
||||
initial-advertise-peer-urls: http://$private_ipv4:2380
|
||||
listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001
|
||||
listen-peer-urls: http://$private_ipv4:2380
|
||||
fleet:
|
||||
public-ip: $private_ipv4
|
||||
units:
|
||||
- name: etcd2.service
|
||||
command: start
|
||||
- name: fleet.service
|
||||
command: start
|
||||
register: newhosts
|
||||
|
||||
- name: wait for ssh
|
||||
ansible.builtin.wait_for:
|
||||
delay: 1
|
||||
host: "{{ item.public_ipv4 }}"
|
||||
port: 22
|
||||
state: started
|
||||
timeout: 500
|
||||
loop: "{{ newhosts.results[0].devices }}"
|
||||
|
||||
|
||||
As with most Ansible modules, the default states of the Packet modules are idempotent, meaning the resources in your project will remain the same after re-runs of a playbook. Thus, we can keep the ``packet_sshkey`` module call in our playbook. If the public key is already in your Packet account, the call will have no effect.
|
||||
|
||||
The second module call provisions 3 Packet Type 0 (specified using the ``plan`` parameter) servers in the project identified by the ``project_id`` parameter. The servers are all provisioned with CoreOS beta (the ``operating_system`` parameter) and are customized with cloud-config user data passed to the ``user_data`` parameter.
|
||||
|
||||
The ``packet_device`` module has a ``wait_for_public_IPv`` that is used to specify the version of the IP address to wait for (valid values are ``4`` or ``6`` for IPv4 or IPv6). If specified, Ansible will wait until the GET API call for a device contains an Internet-routeable IP address of the specified version. When referring to an IP address of a created device in subsequent module calls, it is wise to use the ``wait_for_public_IPv`` parameter, or ``state: active`` in the packet_device module call.
|
||||
|
||||
Run the playbook:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ansible-playbook playbook_coreos.yml
|
||||
|
||||
Once the playbook quits, your new devices should be reachable through SSH. Try to connect to one and check if etcd has started properly:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
tomk@work $ ssh -i id_rsa core@$one_of_the_servers_ip
|
||||
core@coreos-one ~ $ etcdctl cluster-health
|
||||
|
||||
If you have any questions or comments let us know! help@packet.net
|
||||
@@ -1,320 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_scaleway:
|
||||
|
||||
**************
|
||||
Scaleway Guide
|
||||
**************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
`Scaleway <https://scaleway.com>`_ is a cloud provider supported by the community.general collection through a set of plugins and modules.
|
||||
Those modules are:
|
||||
|
||||
- :ansplugin:`community.general.scaleway_compute#module`: manages servers on Scaleway. You can use this module to create, restart and delete servers.
|
||||
- :ansplugin:`community.general.scaleway_compute_private_network#module`
|
||||
- :ansplugin:`community.general.scaleway_container#module`
|
||||
- :ansplugin:`community.general.scaleway_container_info#module`
|
||||
- :ansplugin:`community.general.scaleway_container_namespace_info#module`
|
||||
- :ansplugin:`community.general.scaleway_container_namespace#module`
|
||||
- :ansplugin:`community.general.scaleway_container_registry_info#module`
|
||||
- :ansplugin:`community.general.scaleway_container_registry#module`
|
||||
- :ansplugin:`community.general.scaleway_database_backup#module`
|
||||
- :ansplugin:`community.general.scaleway_function#module`
|
||||
- :ansplugin:`community.general.scaleway_function_info#module`
|
||||
- :ansplugin:`community.general.scaleway_function_namespace_info#module`
|
||||
- :ansplugin:`community.general.scaleway_function_namespace#module`
|
||||
- :ansplugin:`community.general.scaleway_image_info#module`
|
||||
- :ansplugin:`community.general.scaleway_ip#module`
|
||||
- :ansplugin:`community.general.scaleway_ip_info#module`
|
||||
- :ansplugin:`community.general.scaleway_lb#module`
|
||||
- :ansplugin:`community.general.scaleway_organization_info#module`
|
||||
- :ansplugin:`community.general.scaleway_private_network#module`
|
||||
- :ansplugin:`community.general.scaleway_security_group#module`
|
||||
- :ansplugin:`community.general.scaleway_security_group_info#module`
|
||||
- :ansplugin:`community.general.scaleway_security_group_rule#module`
|
||||
- :ansplugin:`community.general.scaleway_server_info#module`
|
||||
- :ansplugin:`community.general.scaleway_snapshot_info#module`
|
||||
- :ansplugin:`community.general.scaleway_sshkey#module`: adds a public SSH key from a file or value to the Packet infrastructure. Every subsequently-created device will have this public key installed in .ssh/authorized_keys.
|
||||
- :ansplugin:`community.general.scaleway_user_data#module`
|
||||
- :ansplugin:`community.general.scaleway_volume#module`: manages volumes on Scaleway.
|
||||
- :ansplugin:`community.general.scaleway_volume_info#module`
|
||||
|
||||
The plugins are:
|
||||
|
||||
- :ansplugin:`community.general.scaleway#inventory`: inventory plugin
|
||||
|
||||
|
||||
.. note::
|
||||
This guide assumes you are familiar with Ansible and how it works.
|
||||
If you are not, have a look at :ref:`ansible_documentation` before getting started.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
The Scaleway modules and inventory script connect to the Scaleway API using `Scaleway REST API <https://developer.scaleway.com>`_.
|
||||
To use the modules and inventory script you will need a Scaleway API token.
|
||||
You can generate an API token through the `Scaleway console's credential page <https://cloud.scaleway.com/#/credentials>`__.
|
||||
The simplest way to authenticate yourself is to set the Scaleway API token in an environment variable:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export SCW_TOKEN=00000000-1111-2222-3333-444444444444
|
||||
|
||||
If you are not comfortable exporting your API token, you can pass it as a parameter to the modules using the ``api_token`` argument.
|
||||
|
||||
If you want to use a new SSH key pair in this tutorial, you can generate it to ``./id_rsa`` and ``./id_rsa.pub`` as:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ssh-keygen -t rsa -f ./id_rsa
|
||||
|
||||
If you want to use an existing key pair, just copy the private and public key over to the playbook directory.
|
||||
|
||||
How to add an SSH key?
|
||||
======================
|
||||
|
||||
Connection to Scaleway Compute nodes use Secure Shell.
|
||||
SSH keys are stored at the account level, which means that you can reuse the same SSH key in multiple nodes.
|
||||
The first step to configure Scaleway compute resources is to have at least one SSH key configured.
|
||||
|
||||
:ansplugin:`community.general.scaleway_sshkey#module` is a module that manages SSH keys on your Scaleway account.
|
||||
You can add an SSH key to your account by including the following task in a playbook:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Add SSH key"
|
||||
community.general.scaleway_sshkey:
|
||||
ssh_pub_key: "ssh-rsa AAAA..."
|
||||
state: "present"
|
||||
|
||||
The ``ssh_pub_key`` parameter contains your ssh public key as a string. Here is an example inside a playbook:
|
||||
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Test SSH key lifecycle on a Scaleway account
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
environment:
|
||||
SCW_API_KEY: ""
|
||||
|
||||
tasks:
|
||||
|
||||
- community.general.scaleway_sshkey:
|
||||
ssh_pub_key: "ssh-rsa AAAAB...424242 developer@example.com"
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- result is success and result is changed
|
||||
|
||||
How to create a compute instance?
|
||||
=================================
|
||||
|
||||
Now that we have an SSH key configured, the next step is to spin up a server!
|
||||
:ansplugin:`community.general.scaleway_compute#module` is a module that can create, update and delete Scaleway compute instances:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Create a server
|
||||
community.general.scaleway_compute:
|
||||
name: foobar
|
||||
state: present
|
||||
image: 00000000-1111-2222-3333-444444444444
|
||||
organization: 00000000-1111-2222-3333-444444444444
|
||||
region: ams1
|
||||
commercial_type: START1-S
|
||||
|
||||
Here are the parameter details for the example shown above:
|
||||
|
||||
- ``name`` is the name of the instance (the one that will show up in your web console).
|
||||
- ``image`` is the UUID of the system image you would like to use.
|
||||
A list of all images is available for each availability zone.
|
||||
- ``organization`` represents the organization that your account is attached to.
|
||||
- ``region`` represents the Availability Zone which your instance is in (for this example, ``par1`` and ``ams1``).
|
||||
- ``commercial_type`` represents the name of the commercial offers.
|
||||
You can check out the Scaleway pricing page to find which instance is right for you.
|
||||
|
||||
Take a look at this short playbook to see a working example using ``scaleway_compute``:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Test compute instance lifecycle on a Scaleway account
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
environment:
|
||||
SCW_API_KEY: ""
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Create a server
|
||||
register: server_creation_task
|
||||
community.general.scaleway_compute:
|
||||
name: foobar
|
||||
state: present
|
||||
image: 00000000-1111-2222-3333-444444444444
|
||||
organization: 00000000-1111-2222-3333-444444444444
|
||||
region: ams1
|
||||
commercial_type: START1-S
|
||||
wait: true
|
||||
|
||||
- ansible.builtin.debug:
|
||||
var: server_creation_task
|
||||
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- server_creation_task is success
|
||||
- server_creation_task is changed
|
||||
|
||||
- name: Run it
|
||||
community.general.scaleway_compute:
|
||||
name: foobar
|
||||
state: running
|
||||
image: 00000000-1111-2222-3333-444444444444
|
||||
organization: 00000000-1111-2222-3333-444444444444
|
||||
region: ams1
|
||||
commercial_type: START1-S
|
||||
wait: true
|
||||
tags:
|
||||
- web_server
|
||||
register: server_run_task
|
||||
|
||||
- ansible.builtin.debug:
|
||||
var: server_run_task
|
||||
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- server_run_task is success
|
||||
- server_run_task is changed
|
||||
|
||||
Dynamic Inventory Plugin
|
||||
========================
|
||||
|
||||
Ansible ships with :ansplugin:`community.general.scaleway#inventory`.
|
||||
You can now get a complete inventory of your Scaleway resources through this plugin and filter it on
|
||||
different parameters (``regions`` and ``tags`` are currently supported).
|
||||
|
||||
Let us create an example!
|
||||
Suppose that we want to get all hosts that got the tag web_server.
|
||||
Create a file named ``scaleway_inventory.yml`` with the following content:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
plugin: community.general.scaleway
|
||||
regions:
|
||||
- ams1
|
||||
- par1
|
||||
tags:
|
||||
- web_server
|
||||
|
||||
This inventory means that we want all hosts that got the tag ``web_server`` on the zones ``ams1`` and ``par1``.
|
||||
Once you have configured this file, you can get the information using the following command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ansible-inventory --list -i scaleway_inventory.yml
|
||||
|
||||
The output will be:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
"dd8e3ae9-0c7c-459e-bc7b-aba8bfa1bb8d": {
|
||||
"ansible_verbosity": 6,
|
||||
"arch": "x86_64",
|
||||
"commercial_type": "START1-S",
|
||||
"hostname": "foobar",
|
||||
"ipv4": "192.0.2.1",
|
||||
"organization": "00000000-1111-2222-3333-444444444444",
|
||||
"state": "running",
|
||||
"tags": [
|
||||
"web_server"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"all": {
|
||||
"children": [
|
||||
"ams1",
|
||||
"par1",
|
||||
"ungrouped",
|
||||
"web_server"
|
||||
]
|
||||
},
|
||||
"ams1": {},
|
||||
"par1": {
|
||||
"hosts": [
|
||||
"dd8e3ae9-0c7c-459e-bc7b-aba8bfa1bb8d"
|
||||
]
|
||||
},
|
||||
"ungrouped": {},
|
||||
"web_server": {
|
||||
"hosts": [
|
||||
"dd8e3ae9-0c7c-459e-bc7b-aba8bfa1bb8d"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
As you can see, we get different groups of hosts.
|
||||
``par1`` and ``ams1`` are groups based on location.
|
||||
``web_server`` is a group based on a tag.
|
||||
|
||||
In case a filter parameter is not defined, the plugin supposes all values possible are wanted.
|
||||
This means that for each tag that exists on your Scaleway compute nodes, a group based on each tag will be created.
|
||||
|
||||
Scaleway S3 object storage
|
||||
==========================
|
||||
|
||||
`Object Storage <https://www.scaleway.com/object-storage>`_ allows you to store any kind of objects (documents, images, videos, and so on).
|
||||
As the Scaleway API is S3 compatible, Ansible supports it natively through the amazon.aws modules: :ansplugin:`amazon.aws.s3_bucket#module`, :ansplugin:`amazon.aws.s3_object#module`.
|
||||
|
||||
You can find many examples in the `scaleway_s3 integration tests <https://github.com/ansible/ansible-legacy-tests/tree/devel/test/legacy/roles/scaleway_s3>`_.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- hosts: myserver
|
||||
vars:
|
||||
scaleway_region: nl-ams
|
||||
s3_url: https://s3.nl-ams.scw.cloud
|
||||
environment:
|
||||
# AWS_ACCESS_KEY matches your scaleway organization id available at https://cloud.scaleway.com/#/account
|
||||
AWS_ACCESS_KEY: 00000000-1111-2222-3333-444444444444
|
||||
# AWS_SECRET_KEY matches a secret token that you can retrieve at https://cloud.scaleway.com/#/credentials
|
||||
AWS_SECRET_KEY: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
|
||||
module_defaults:
|
||||
group/amazon.aws.aws:
|
||||
s3_url: '{{ s3_url }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
tasks:
|
||||
# use a fact instead of a variable, otherwise template is evaluate each time variable is used
|
||||
- ansible.builtin.set_fact:
|
||||
bucket_name: "{{ 99999999 | random | to_uuid }}"
|
||||
|
||||
# "requester_pays:" is mandatory because Scaleway does not implement related API
|
||||
# another way is to use amazon.aws.s3_object and "mode: create" !
|
||||
- amazon.aws.s3_bucket:
|
||||
name: '{{ bucket_name }}'
|
||||
requester_pays:
|
||||
|
||||
- name: Another way to create the bucket
|
||||
amazon.aws.s3_object:
|
||||
bucket: '{{ bucket_name }}'
|
||||
mode: create
|
||||
encrypt: false
|
||||
register: bucket_creation_check
|
||||
|
||||
- name: add something in the bucket
|
||||
amazon.aws.s3_object:
|
||||
mode: put
|
||||
bucket: '{{ bucket_name }}'
|
||||
src: /tmp/test.txt # needs to be created before
|
||||
object: test.txt
|
||||
encrypt: false # server side encryption must be disabled
|
||||
12
galaxy.yml
12
galaxy.yml
@@ -5,17 +5,17 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 8.2.0
|
||||
version: 7.5.9
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
description: >-
|
||||
The community.general collection is a part of the Ansible package and includes many modules and
|
||||
plugins supported by Ansible community which are not part of more specialized community collections.
|
||||
description: null
|
||||
license_file: COPYING
|
||||
tags:
|
||||
- community
|
||||
tags: [community]
|
||||
# NOTE: No dependencies are expected to be added here
|
||||
# dependencies:
|
||||
repository: https://github.com/ansible-collections/community.general
|
||||
documentation: https://docs.ansible.com/ansible/latest/collections/community/general/
|
||||
homepage: https://github.com/ansible-collections/community.general
|
||||
issues: https://github.com/ansible-collections/community.general/issues
|
||||
#type: flatmap
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# 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
|
||||
|
||||
requires_ansible: '>=2.13.0'
|
||||
requires_ansible: '>=2.11.0'
|
||||
plugin_routing:
|
||||
connection:
|
||||
docker:
|
||||
|
||||
@@ -71,16 +71,6 @@ options:
|
||||
ini:
|
||||
- section: callback_mail
|
||||
key: bcc
|
||||
message_id_domain:
|
||||
description:
|
||||
- The domain name to use for the L(Message-ID header, https://en.wikipedia.org/wiki/Message-ID).
|
||||
- The default is the hostname of the control node.
|
||||
type: str
|
||||
ini:
|
||||
- section: callback_mail
|
||||
key: message_id_domain
|
||||
version_added: 8.2.0
|
||||
|
||||
'''
|
||||
|
||||
import json
|
||||
@@ -141,7 +131,7 @@ class CallbackModule(CallbackBase):
|
||||
content += 'To: %s\n' % ', '.join([email.utils.formataddr(pair) for pair in to_addresses])
|
||||
if self.cc:
|
||||
content += 'Cc: %s\n' % ', '.join([email.utils.formataddr(pair) for pair in cc_addresses])
|
||||
content += 'Message-ID: %s\n' % email.utils.make_msgid(domain=self.get_option('message_id_domain'))
|
||||
content += 'Message-ID: %s\n' % email.utils.make_msgid()
|
||||
content += 'Subject: %s\n\n' % subject.strip()
|
||||
content += body
|
||||
|
||||
|
||||
@@ -350,7 +350,8 @@ class OpenTelemetrySource(object):
|
||||
if not disable_logs:
|
||||
# This will avoid populating span attributes to the logs
|
||||
span.add_event(task_data.dump, attributes={} if disable_attributes_in_logs else attributes)
|
||||
span.end(end_time=host_data.finish)
|
||||
# Close span always
|
||||
span.end(end_time=host_data.finish)
|
||||
|
||||
def set_span_attributes(self, span, attributes):
|
||||
""" update the span attributes with the given attributes if not None """
|
||||
@@ -497,6 +498,12 @@ class CallbackModule(CallbackBase):
|
||||
# See https://github.com/open-telemetry/opentelemetry-specification/issues/740
|
||||
self.traceparent = self.get_option('traceparent')
|
||||
|
||||
def dump_results(self, result):
|
||||
""" dump the results if disable_logs is not enabled """
|
||||
if self.disable_logs:
|
||||
return ""
|
||||
return self._dump_results(result._result)
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
self.ansible_playbook = basename(playbook._file_name)
|
||||
|
||||
@@ -546,7 +553,7 @@ class CallbackModule(CallbackBase):
|
||||
self.tasks_data,
|
||||
status,
|
||||
result,
|
||||
self._dump_results(result._result)
|
||||
self.dump_results(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_ok(self, result):
|
||||
@@ -554,7 +561,7 @@ class CallbackModule(CallbackBase):
|
||||
self.tasks_data,
|
||||
'ok',
|
||||
result,
|
||||
self._dump_results(result._result)
|
||||
self.dump_results(result)
|
||||
)
|
||||
|
||||
def v2_runner_on_skipped(self, result):
|
||||
@@ -562,7 +569,7 @@ class CallbackModule(CallbackBase):
|
||||
self.tasks_data,
|
||||
'skipped',
|
||||
result,
|
||||
self._dump_results(result._result)
|
||||
self.dump_results(result)
|
||||
)
|
||||
|
||||
def v2_playbook_on_include(self, included_file):
|
||||
|
||||
@@ -18,6 +18,8 @@ DOCUMENTATION = '''
|
||||
short_description: notify using software speech synthesizer
|
||||
description:
|
||||
- This plugin will use the C(say) or C(espeak) program to "speak" about play events.
|
||||
notes:
|
||||
- In Ansible 2.8, this callback has been renamed from C(osx_say) into M(community.general.say).
|
||||
'''
|
||||
|
||||
import platform
|
||||
|
||||
@@ -44,17 +44,26 @@ from ansible import constants as C
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
|
||||
try:
|
||||
codeCodes = C.COLOR_CODES
|
||||
except AttributeError:
|
||||
# This constant was moved to ansible.constants in
|
||||
# https://github.com/ansible/ansible/commit/1202dd000f10b0e8959019484f1c3b3f9628fc67
|
||||
# (will be included in ansible-core 2.11.0). For older Ansible/ansible-base versions,
|
||||
# we include from the original location.
|
||||
from ansible.utils.color import codeCodes
|
||||
|
||||
|
||||
DONT_COLORIZE = False
|
||||
COLORS = {
|
||||
'normal': '\033[0m',
|
||||
'ok': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_OK]),
|
||||
'ok': '\033[{0}m'.format(codeCodes[C.COLOR_OK]),
|
||||
'bold': '\033[1m',
|
||||
'not_so_bold': '\033[1m\033[34m',
|
||||
'changed': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_CHANGED]),
|
||||
'failed': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_ERROR]),
|
||||
'changed': '\033[{0}m'.format(codeCodes[C.COLOR_CHANGED]),
|
||||
'failed': '\033[{0}m'.format(codeCodes[C.COLOR_ERROR]),
|
||||
'endc': '\033[0m',
|
||||
'skipped': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_SKIP]),
|
||||
'skipped': '\033[{0}m'.format(codeCodes[C.COLOR_SKIP]),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ DOCUMENTATION = '''
|
||||
short_description: Sends play events to a Slack channel
|
||||
description:
|
||||
- This is an ansible callback plugin that sends status updates to a Slack channel during playbook execution.
|
||||
- Before Ansible 2.4 only environment variables were available for configuring this plugin.
|
||||
options:
|
||||
webhook_url:
|
||||
required: true
|
||||
|
||||
@@ -16,6 +16,7 @@ DOCUMENTATION = '''
|
||||
short_description: sends JSON events to syslog
|
||||
description:
|
||||
- This plugin logs ansible-playbook and ansible runs to a syslog server in JSON format.
|
||||
- Before Ansible 2.9 only environment variables were available for configuration.
|
||||
options:
|
||||
server:
|
||||
description: Syslog server that will receive the event.
|
||||
|
||||
@@ -19,6 +19,16 @@ DOCUMENTATION = '''
|
||||
- default_callback
|
||||
requirements:
|
||||
- set as stdout in configuration
|
||||
seealso:
|
||||
- plugin: ansible.builtin.default
|
||||
plugin_type: callback
|
||||
description: >
|
||||
There is a parameter O(ansible.builtin.default#callback:result_format) in P(ansible.builtin.default#callback)
|
||||
that allows you to change the output format to YAML.
|
||||
notes:
|
||||
- >
|
||||
With ansible-core 2.13 or newer, you can instead specify V(yaml) for the parameter O(ansible.builtin.default#callback:result_format)
|
||||
in P(ansible.builtin.default#callback).
|
||||
'''
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Based on lxd.py (c) 2016, Matt Clay <matt@mystile.com>
|
||||
# (c) 2023, Stephane Graber <stgraber@stgraber.org>
|
||||
# Copyright (c) 2023 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 = """
|
||||
author: Stéphane Graber (@stgraber)
|
||||
name: incus
|
||||
short_description: Run tasks in Incus instances via the Incus CLI.
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing Incus instance using Incus CLI.
|
||||
version_added: "8.2.0"
|
||||
options:
|
||||
remote_addr:
|
||||
description:
|
||||
- The instance identifier.
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: ansible_host
|
||||
- name: ansible_incus_host
|
||||
executable:
|
||||
description:
|
||||
- The shell to use for execution inside the instance.
|
||||
default: /bin/sh
|
||||
vars:
|
||||
- name: ansible_executable
|
||||
- name: ansible_incus_executable
|
||||
remote:
|
||||
description:
|
||||
- The name of the Incus remote to use (per C(incus remote list)).
|
||||
- Remotes are used to access multiple servers from a single client.
|
||||
default: local
|
||||
vars:
|
||||
- name: ansible_incus_remote
|
||||
project:
|
||||
description:
|
||||
- The name of the Incus project to use (per C(incus project list)).
|
||||
- Projects are used to divide the instances running on a server.
|
||||
default: default
|
||||
vars:
|
||||
- name: ansible_incus_project
|
||||
"""
|
||||
|
||||
import os
|
||||
from subprocess import call, Popen, PIPE
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Incus based connections """
|
||||
|
||||
transport = "incus"
|
||||
has_pipelining = True
|
||||
default_user = 'root'
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
||||
self._incus_cmd = get_bin_path("incus")
|
||||
|
||||
if not self._incus_cmd:
|
||||
raise AnsibleError("incus command not found in PATH")
|
||||
|
||||
def _connect(self):
|
||||
"""connect to Incus (nothing to do here) """
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self._connected:
|
||||
self._display.vvv(u"ESTABLISH Incus CONNECTION FOR USER: root",
|
||||
host=self._instance())
|
||||
self._connected = True
|
||||
|
||||
def _instance(self):
|
||||
# Return only the leading part of the FQDN as the instance name
|
||||
# as Incus instance names cannot be a FQDN.
|
||||
return self.get_option('remote_addr').split(".")[0]
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
""" execute a command on the Incus host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
self._display.vvv(u"EXEC {0}".format(cmd),
|
||||
host=self._instance())
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"exec",
|
||||
"%s:%s" % (self.get_option("remote"), self._instance()),
|
||||
"--",
|
||||
self._play_context.executable, "-c", cmd]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
||||
|
||||
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = process.communicate(in_data)
|
||||
|
||||
stdout = to_text(stdout)
|
||||
stderr = to_text(stderr)
|
||||
|
||||
if stderr == "Error: Instance is not running.\n":
|
||||
raise AnsibleConnectionFailure("instance not running: %s" %
|
||||
self._instance())
|
||||
|
||||
if stderr == "Error: Instance not found\n":
|
||||
raise AnsibleConnectionFailure("instance not found: %s" %
|
||||
self._instance())
|
||||
|
||||
return process.returncode, stdout, stderr
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" put a file from local to Incus """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path),
|
||||
host=self._instance())
|
||||
|
||||
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound("input path is not a file: %s" % in_path)
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"file", "push", "--quiet",
|
||||
in_path,
|
||||
"%s:%s/%s" % (self.get_option("remote"),
|
||||
self._instance(),
|
||||
out_path)]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
call(local_cmd)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from Incus to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path),
|
||||
host=self._instance())
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"file", "pull", "--quiet",
|
||||
"%s:%s/%s" % (self.get_option("remote"),
|
||||
self._instance(),
|
||||
in_path),
|
||||
out_path]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
call(local_cmd)
|
||||
|
||||
def close(self):
|
||||
""" close the connection (nothing to do here) """
|
||||
super(Connection, self).close()
|
||||
|
||||
self._connected = False
|
||||
@@ -71,11 +71,10 @@ class Connection(ConnectionBase):
|
||||
msg = "lxc python bindings are not installed"
|
||||
raise errors.AnsibleError(msg)
|
||||
|
||||
container_name = self.get_option('remote_addr')
|
||||
if self.container and self.container_name == container_name:
|
||||
if self.container:
|
||||
return
|
||||
|
||||
self.container_name = container_name
|
||||
self.container_name = self.get_option('remote_addr')
|
||||
|
||||
self._display.vvv("THIS IS A LOCAL LXC DIR", host=self.container_name)
|
||||
self.container = _lxc.Container(self.container_name)
|
||||
|
||||
@@ -10,15 +10,13 @@ __metaclass__ = type
|
||||
DOCUMENTATION = '''
|
||||
author: Matt Clay (@mattclay) <matt@mystile.com>
|
||||
name: lxd
|
||||
short_description: Run tasks in lxc containers via lxc CLI
|
||||
short_description: Run tasks in LXD instances via C(lxc) CLI
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing lxc container using lxc CLI
|
||||
- Run commands or put/fetch files to an existing instance using C(lxc) CLI.
|
||||
options:
|
||||
remote_addr:
|
||||
description:
|
||||
- Instance (container/VM) identifier.
|
||||
- Since community.general 8.0.0, a FQDN can be provided; in that case, the first component (the part before C(.))
|
||||
is used as the instance identifier.
|
||||
- Container identifier.
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: inventory_hostname
|
||||
@@ -26,7 +24,7 @@ DOCUMENTATION = '''
|
||||
- name: ansible_lxd_host
|
||||
executable:
|
||||
description:
|
||||
- shell to use for execution inside container
|
||||
- Shell to use for execution inside instance.
|
||||
default: /bin/sh
|
||||
vars:
|
||||
- name: ansible_executable
|
||||
@@ -71,38 +69,32 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("lxc command not found in PATH")
|
||||
|
||||
if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
|
||||
self._display.warning('lxd does not support remote_user, using container default: root')
|
||||
|
||||
def _host(self):
|
||||
""" translate remote_addr to lxd (short) hostname """
|
||||
return self.get_option("remote_addr").split(".", 1)[0]
|
||||
self._display.warning('lxd does not support remote_user, using default: root')
|
||||
|
||||
def _connect(self):
|
||||
"""connect to lxd (nothing to do here) """
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self._connected:
|
||||
self._display.vvv(u"ESTABLISH LXD CONNECTION FOR USER: root", host=self._host())
|
||||
self._display.vvv(u"ESTABLISH LXD CONNECTION FOR USER: root", host=self.get_option('remote_addr'))
|
||||
self._connected = True
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
""" execute a command on the lxd host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
self._display.vvv(u"EXEC {0}".format(cmd), host=self._host())
|
||||
self._display.vvv(u"EXEC {0}".format(cmd), host=self.get_option('remote_addr'))
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"exec",
|
||||
"%s:%s" % (self.get_option("remote"), self._host()),
|
||||
"%s:%s" % (self.get_option("remote"), self.get_option("remote_addr")),
|
||||
"--",
|
||||
self.get_option("executable"), "-c", cmd
|
||||
])
|
||||
|
||||
self._display.vvvvv(u"EXEC {0}".format(local_cmd), host=self._host())
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
||||
|
||||
@@ -112,13 +104,11 @@ class Connection(ConnectionBase):
|
||||
stdout = to_text(stdout)
|
||||
stderr = to_text(stderr)
|
||||
|
||||
self._display.vvvvv(u"EXEC lxc output: {0} {1}".format(stdout, stderr), host=self._host())
|
||||
if stderr == "error: Container is not running.\n":
|
||||
raise AnsibleConnectionFailure("container not running: %s" % self.get_option('remote_addr'))
|
||||
|
||||
if "is not running" in stderr:
|
||||
raise AnsibleConnectionFailure("instance not running: %s" % self._host())
|
||||
|
||||
if stderr.strip() == "Error: Instance not found" or stderr.strip() == "error: not found":
|
||||
raise AnsibleConnectionFailure("instance not found: %s" % self._host())
|
||||
if stderr == "error: not found\n":
|
||||
raise AnsibleConnectionFailure("container not found: %s" % self.get_option('remote_addr'))
|
||||
|
||||
return process.returncode, stdout, stderr
|
||||
|
||||
@@ -126,7 +116,7 @@ class Connection(ConnectionBase):
|
||||
""" put a file from local to lxd """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._host())
|
||||
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self.get_option('remote_addr'))
|
||||
|
||||
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound("input path is not a file: %s" % in_path)
|
||||
@@ -137,7 +127,7 @@ class Connection(ConnectionBase):
|
||||
local_cmd.extend([
|
||||
"file", "push",
|
||||
in_path,
|
||||
"%s:%s/%s" % (self.get_option("remote"), self._host(), out_path)
|
||||
"%s:%s/%s" % (self.get_option("remote"), self.get_option("remote_addr"), out_path)
|
||||
])
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
@@ -149,14 +139,14 @@ class Connection(ConnectionBase):
|
||||
""" fetch a file from lxd to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._host())
|
||||
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self.get_option('remote_addr'))
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"file", "pull",
|
||||
"%s:%s/%s" % (self.get_option("remote"), self._host(), in_path),
|
||||
"%s:%s/%s" % (self.get_option("remote"), self.get_option("remote_addr"), in_path),
|
||||
out_path
|
||||
])
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ options:
|
||||
aliases: ['assume_role']
|
||||
alicloud_assume_role_arn:
|
||||
description:
|
||||
- The Alibaba Cloud C(role_arn). The ARN of the role to assume. If ARN is set to an empty string,
|
||||
- The Alibaba Cloud role_arn. The ARN of the role to assume. If ARN is set to an empty string,
|
||||
it does not perform role switching. It supports environment variable E(ALICLOUD_ASSUME_ROLE_ARN).
|
||||
ansible will execute with provided credentials.
|
||||
aliases: ['assume_role_arn']
|
||||
@@ -61,7 +61,7 @@ options:
|
||||
type: str
|
||||
alicloud_assume_role_session_expiration:
|
||||
description:
|
||||
- The Alibaba Cloud C(session_expiration). The time after which the established session for assuming
|
||||
- The Alibaba Cloud session_expiration. The time after which the established session for assuming
|
||||
role expires. Valid value range 900-3600 seconds. Default to 3600 (in this case Alicloud use own default
|
||||
value). It supports environment variable E(ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION).
|
||||
aliases: ['assume_role_session_expiration']
|
||||
@@ -85,12 +85,12 @@ options:
|
||||
description:
|
||||
- This is the path to the shared credentials file. It can also be sourced from the E(ALICLOUD_SHARED_CREDENTIALS_FILE)
|
||||
environment variable.
|
||||
- If this is not set and a profile is specified, C(~/.aliyun/config.json) will be used.
|
||||
- If this is not set and a profile is specified, ~/.aliyun/config.json will be used.
|
||||
type: str
|
||||
author:
|
||||
- "He Guimin (@xiaozhu36)"
|
||||
requirements:
|
||||
- "Python >= 3.6"
|
||||
- "python >= 3.6"
|
||||
notes:
|
||||
- If parameters are not set within the module, the following
|
||||
environment variables can be used in decreasing order of precedence
|
||||
@@ -103,7 +103,7 @@ notes:
|
||||
E(ALICLOUD_PROFILE),
|
||||
E(ALICLOUD_ASSUME_ROLE_ARN),
|
||||
E(ALICLOUD_ASSUME_ROLE_SESSION_NAME),
|
||||
E(ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION).
|
||||
E(ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION),
|
||||
- E(ALICLOUD_REGION) or E(ALICLOUD_REGION_ID) can be typically be used to specify the
|
||||
Alicloud region, when required, but this can also be configured in the footmark config file
|
||||
ALICLOUD region, when required, but this can also be configured in the footmark config file
|
||||
'''
|
||||
|
||||
@@ -14,19 +14,19 @@ class ModuleDocFragment(object):
|
||||
options:
|
||||
api_url:
|
||||
description:
|
||||
- The resolvable endpoint for the API.
|
||||
- The resolvable endpoint for the API
|
||||
type: str
|
||||
api_username:
|
||||
description:
|
||||
- The username to use for authentication against the API.
|
||||
- The username to use for authentication against the API
|
||||
type: str
|
||||
api_password:
|
||||
description:
|
||||
- The password to use for authentication against the API.
|
||||
- The password to use for authentication against the API
|
||||
type: str
|
||||
validate_certs:
|
||||
description:
|
||||
- Whether or not to validate SSL certs when supplying a HTTPS endpoint.
|
||||
- Whether or not to validate SSL certs when supplying a https endpoint.
|
||||
type: bool
|
||||
default: true
|
||||
'''
|
||||
|
||||
@@ -20,10 +20,10 @@ options:
|
||||
region:
|
||||
description:
|
||||
- The target region.
|
||||
- Regions are defined in Apache libcloud project [libcloud/common/dimensiondata.py].
|
||||
- They are also listed in U(https://libcloud.readthedocs.io/en/latest/compute/drivers/dimensiondata.html).
|
||||
- Note that the default value C(na) stands for "North America".
|
||||
- The module prepends C(dd-) to the region choice.
|
||||
- Regions are defined in Apache libcloud project [libcloud/common/dimensiondata.py]
|
||||
- They are also listed in U(https://libcloud.readthedocs.io/en/latest/compute/drivers/dimensiondata.html)
|
||||
- Note that the default value "na" stands for "North America".
|
||||
- The module prepends 'dd-' to the region choice.
|
||||
type: str
|
||||
default: na
|
||||
mcp_user:
|
||||
|
||||
@@ -34,4 +34,4 @@ options:
|
||||
- Only applicable if O(wait=true).
|
||||
type: int
|
||||
default: 2
|
||||
'''
|
||||
'''
|
||||
|
||||
@@ -39,7 +39,8 @@ options:
|
||||
default: sysadmin
|
||||
requirements:
|
||||
- An EMC VNX Storage device.
|
||||
- storops (0.5.10 or greater). Install using C(pip install storops).
|
||||
- Ansible 2.7.
|
||||
- storops (0.5.10 or greater). Install using 'pip install storops'.
|
||||
notes:
|
||||
- The modules prefixed with C(emc_vnx) are built to support the EMC VNX storage platform.
|
||||
- The modules prefixed with emc_vnx are built to support the EMC VNX storage platform.
|
||||
'''
|
||||
|
||||
@@ -29,9 +29,4 @@ options:
|
||||
- GitLab CI job token for logging in.
|
||||
type: str
|
||||
version_added: 4.2.0
|
||||
ca_path:
|
||||
description:
|
||||
- The CA certificates bundle to use to verify GitLab server certificate.
|
||||
type: str
|
||||
version_added: 8.1.0
|
||||
'''
|
||||
|
||||
@@ -19,8 +19,8 @@ options:
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- The user name to login with.
|
||||
- Currently only user names are supported, and not user IDs.
|
||||
- The user name to login with (currently only user names are
|
||||
supported, and not user IDs).
|
||||
type: str
|
||||
required: true
|
||||
password:
|
||||
@@ -31,13 +31,14 @@ options:
|
||||
domain:
|
||||
description:
|
||||
- The name of the Domain to scope to (Identity v3).
|
||||
- Currently only domain names are supported, and not domain IDs.
|
||||
(currently only domain names are supported, and not domain IDs).
|
||||
type: str
|
||||
required: true
|
||||
project:
|
||||
description:
|
||||
- The name of the Tenant (Identity v2) or Project (Identity v3).
|
||||
- Currently only project names are supported, and not project IDs.
|
||||
(currently only project names are supported, and not
|
||||
project IDs).
|
||||
type: str
|
||||
required: true
|
||||
region:
|
||||
@@ -46,20 +47,20 @@ options:
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- The ID of resource to be managed.
|
||||
- The id of resource to be managed.
|
||||
type: str
|
||||
notes:
|
||||
- For authentication, you can set identity_endpoint using the
|
||||
E(ANSIBLE_HWC_IDENTITY_ENDPOINT) environment variable.
|
||||
E(ANSIBLE_HWC_IDENTITY_ENDPOINT) env variable.
|
||||
- For authentication, you can set user using the
|
||||
E(ANSIBLE_HWC_USER) environment variable.
|
||||
- For authentication, you can set password using the E(ANSIBLE_HWC_PASSWORD) environment
|
||||
E(ANSIBLE_HWC_USER) env variable.
|
||||
- For authentication, you can set password using the E(ANSIBLE_HWC_PASSWORD) env
|
||||
variable.
|
||||
- For authentication, you can set domain using the E(ANSIBLE_HWC_DOMAIN) environment
|
||||
- For authentication, you can set domain using the E(ANSIBLE_HWC_DOMAIN) env
|
||||
variable.
|
||||
- For authentication, you can set project using the E(ANSIBLE_HWC_PROJECT) environment
|
||||
- For authentication, you can set project using the E(ANSIBLE_HWC_PROJECT) env
|
||||
variable.
|
||||
- For authentication, you can set region using the E(ANSIBLE_HWC_REGION) environment variable.
|
||||
- For authentication, you can set region using the E(ANSIBLE_HWC_REGION) env variable.
|
||||
- Environment variables values will only be used if the playbook values are
|
||||
not set.
|
||||
'''
|
||||
|
||||
@@ -31,7 +31,8 @@ options:
|
||||
required: true
|
||||
notes:
|
||||
- This module requires pyxcli python library.
|
||||
Use C(pip install pyxcli) in order to get pyxcli.
|
||||
Use 'pip install pyxcli' in order to get pyxcli.
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- pyxcli
|
||||
'''
|
||||
|
||||
@@ -16,29 +16,32 @@ options:
|
||||
hostname:
|
||||
description:
|
||||
- The hostname or IP address on which InfluxDB server is listening.
|
||||
- Since Ansible 2.5, defaulted to localhost.
|
||||
type: str
|
||||
default: localhost
|
||||
username:
|
||||
description:
|
||||
- Username that will be used to authenticate against InfluxDB server.
|
||||
- Alias O(login_username) added in Ansible 2.5.
|
||||
type: str
|
||||
default: root
|
||||
aliases: [ login_username ]
|
||||
password:
|
||||
description:
|
||||
- Password that will be used to authenticate against InfluxDB server.
|
||||
- Alias O(login_password) added in Ansible 2.5.
|
||||
type: str
|
||||
default: root
|
||||
aliases: [ login_password ]
|
||||
port:
|
||||
description:
|
||||
- The port on which InfluxDB server is listening.
|
||||
- The port on which InfluxDB server is listening
|
||||
type: int
|
||||
default: 8086
|
||||
path:
|
||||
description:
|
||||
- The path on which InfluxDB server is accessible.
|
||||
- Only available when using python-influxdb >= 5.1.0.
|
||||
- The path on which InfluxDB server is accessible
|
||||
- Only available when using python-influxdb >= 5.1.0
|
||||
type: str
|
||||
default: ''
|
||||
version_added: '0.2.0'
|
||||
@@ -61,7 +64,7 @@ options:
|
||||
description:
|
||||
- Number of retries client will try before aborting.
|
||||
- V(0) indicates try until success.
|
||||
- Only available when using python-influxdb >= 4.1.0.
|
||||
- Only available when using python-influxdb >= 4.1.0
|
||||
type: int
|
||||
default: 3
|
||||
use_udp:
|
||||
|
||||
@@ -18,6 +18,7 @@ options:
|
||||
- Port of FreeIPA / IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_PORT) will be used instead.
|
||||
- If both the environment variable E(IPA_PORT) and the value are not specified in the task, then default value is set.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: int
|
||||
default: 443
|
||||
ipa_host:
|
||||
@@ -25,8 +26,9 @@ options:
|
||||
- IP or hostname of IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_HOST) will be used instead.
|
||||
- If both the environment variable E(IPA_HOST) and the value are not specified in the task, then DNS will be used to try to discover the FreeIPA server.
|
||||
- The relevant entry needed in FreeIPA is the C(ipa-ca) entry.
|
||||
- The relevant entry needed in FreeIPA is the 'ipa-ca' entry.
|
||||
- If neither the DNS entry, nor the environment E(IPA_HOST), nor the value are available in the task, then the default value will be used.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
default: ipa.example.com
|
||||
ipa_user:
|
||||
@@ -34,6 +36,7 @@ options:
|
||||
- Administrative account used on IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_USER) will be used instead.
|
||||
- If both the environment variable E(IPA_USER) and the value are not specified in the task, then default value is set.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
default: admin
|
||||
ipa_pass:
|
||||
@@ -44,12 +47,14 @@ options:
|
||||
- If the environment variable E(KRB5CCNAME) is available, the module will use this kerberos credentials cache to authenticate to the FreeIPA server.
|
||||
- If the environment variable E(KRB5_CLIENT_KTNAME) is available, and E(KRB5CCNAME) is not; the module will use this kerberos keytab to authenticate.
|
||||
- If GSSAPI is not available, the usage of O(ipa_pass) is required.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
ipa_prot:
|
||||
description:
|
||||
- Protocol used by IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_PROT) will be used instead.
|
||||
- If both the environment variable E(IPA_PROT) and the value are not specified in the task, then default value is set.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
choices: [ http, https ]
|
||||
default: https
|
||||
|
||||
@@ -69,7 +69,6 @@ options:
|
||||
type: int
|
||||
default: 10
|
||||
version_added: 4.5.0
|
||||
|
||||
http_agent:
|
||||
description:
|
||||
- Configures the HTTP User-Agent header.
|
||||
|
||||
@@ -30,7 +30,7 @@ options:
|
||||
|
||||
auth_url:
|
||||
description:
|
||||
- lxca HTTPS full web address.
|
||||
- lxca https full web address
|
||||
type: str
|
||||
required: true
|
||||
|
||||
@@ -38,6 +38,7 @@ requirements:
|
||||
- pylxca
|
||||
|
||||
notes:
|
||||
- Additional detail about pylxca can be found at U(https://github.com/lenovo/pylxca).
|
||||
- Playbooks using these modules can be found at U(https://github.com/lenovo/ansible.lenovo-lxca).
|
||||
- Additional detail about pylxca can be found at U(https://github.com/lenovo/pylxca)
|
||||
- Playbooks using these modules can be found at U(https://github.com/lenovo/ansible.lenovo-lxca)
|
||||
- Check mode is not supported.
|
||||
'''
|
||||
|
||||
@@ -18,12 +18,6 @@ options:
|
||||
- FQDN of Nomad server.
|
||||
required: true
|
||||
type: str
|
||||
port:
|
||||
description:
|
||||
- Port of Nomad server.
|
||||
type: int
|
||||
default: 4646
|
||||
version_added: 8.0.0
|
||||
use_ssl:
|
||||
description:
|
||||
- Use TLS/SSL connection.
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, 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
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
requirements:
|
||||
- See U(https://support.1password.com/command-line/)
|
||||
options:
|
||||
master_password:
|
||||
description: The password used to unlock the specified vault.
|
||||
aliases: ['vault_password']
|
||||
type: str
|
||||
section:
|
||||
description: Item section containing the field to retrieve (case-insensitive). If absent will return first match from any section.
|
||||
domain:
|
||||
description: Domain of 1Password.
|
||||
default: '1password.com'
|
||||
type: str
|
||||
subdomain:
|
||||
description: The 1Password subdomain to authenticate against.
|
||||
type: str
|
||||
account_id:
|
||||
description: The account ID to target.
|
||||
type: str
|
||||
username:
|
||||
description: The username used to sign in.
|
||||
type: str
|
||||
secret_key:
|
||||
description: The secret key used when performing an initial sign in.
|
||||
type: str
|
||||
service_account_token:
|
||||
description:
|
||||
- The access key for a service account.
|
||||
- Only works with 1Password CLI version 2 or later.
|
||||
type: str
|
||||
vault:
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
|
||||
type: str
|
||||
connect_host:
|
||||
description: The host for 1Password Connect. Must be used in combination with O(connect_token).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_HOST
|
||||
version_added: 8.1.0
|
||||
connect_token:
|
||||
description: The token for 1Password Connect. Must be used in combination with O(connect_host).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_TOKEN
|
||||
version_added: 8.1.0
|
||||
'''
|
||||
|
||||
LOOKUP = r'''
|
||||
options:
|
||||
service_account_token:
|
||||
env:
|
||||
- name: OP_SERVICE_ACCOUNT_TOKEN
|
||||
version_added: 8.2.0
|
||||
notes:
|
||||
- This lookup will use an existing 1Password session if one exists. If not, and you have already
|
||||
performed an initial sign in (meaning C(~/.op/config), C(~/.config/op/config) or C(~/.config/.op/config) exists), then only the
|
||||
O(master_password) is required. You may optionally specify O(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
|
||||
- This lookup can perform an initial login by providing O(subdomain), O(username), O(secret_key), and O(master_password).
|
||||
- Can target a specific account by providing the O(account_id).
|
||||
- Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
|
||||
needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
|
||||
to the 1Password master password.
|
||||
- This lookup stores potentially sensitive data from 1Password as Ansible facts.
|
||||
Facts are subject to caching if enabled, which means this data could be stored in clear text
|
||||
on disk or in a database.
|
||||
- Tested with C(op) version 2.7.2.
|
||||
'''
|
||||
@@ -15,7 +15,7 @@ class ModuleDocFragment(object):
|
||||
options:
|
||||
config:
|
||||
description:
|
||||
- Path to a JSON configuration file containing the OneView client configuration.
|
||||
- Path to a .json configuration file containing the OneView client configuration.
|
||||
The configuration file is optional and when used should be present in the host running the ansible commands.
|
||||
If the file path is not provided, the configuration will be loaded from environment variables.
|
||||
For links to example configuration files or how to use the environment variables verify the notes section.
|
||||
@@ -42,7 +42,7 @@ options:
|
||||
type: str
|
||||
|
||||
requirements:
|
||||
- Python >= 2.7.9
|
||||
- python >= 2.7.9
|
||||
|
||||
notes:
|
||||
- "A sample configuration file for the config parameter can be found at:
|
||||
@@ -70,11 +70,11 @@ options:
|
||||
options:
|
||||
params:
|
||||
description:
|
||||
- List of parameters to delimit, filter and sort the list of resources.
|
||||
- "Parameter keys allowed are:"
|
||||
- "C(start): The first item to return, using 0-based indexing."
|
||||
- "C(count): The number of resources to return."
|
||||
- "C(filter): A general filter/query string to narrow the list of items returned."
|
||||
- "C(sort): The sort order of the returned data set."
|
||||
- List of params to delimit, filter and sort the list of resources.
|
||||
- "params allowed:
|
||||
- C(start): The first item to return, using 0-based indexing.
|
||||
- C(count): The number of resources to return.
|
||||
- C(filter): A general filter/query string to narrow the list of items returned.
|
||||
- C(sort): The sort order of the returned data set."
|
||||
type: dict
|
||||
'''
|
||||
|
||||
@@ -20,7 +20,7 @@ options:
|
||||
aliases: [ oauth_token ]
|
||||
api_url:
|
||||
description:
|
||||
- Online API URL.
|
||||
- Online API URL
|
||||
type: str
|
||||
default: 'https://api.online.net'
|
||||
aliases: [ base_url ]
|
||||
@@ -36,7 +36,7 @@ options:
|
||||
type: bool
|
||||
default: true
|
||||
notes:
|
||||
- Also see the API documentation on U(https://console.online.net/en/api/).
|
||||
- Also see the API documentation on U(https://console.online.net/en/api/)
|
||||
- If O(api_token) is not set within the module, the following
|
||||
environment variables can be used in decreasing order of precedence
|
||||
E(ONLINE_TOKEN), E(ONLINE_API_KEY), E(ONLINE_OAUTH_TOKEN), E(ONLINE_API_TOKEN).
|
||||
|
||||
@@ -64,7 +64,7 @@ options:
|
||||
description:
|
||||
- Configures the transport connection to use when connecting to the
|
||||
remote device. The transport argument supports connectivity to the
|
||||
device over SSH (V(ssh)), CLI (V(cli)), or REST (V(rest)).
|
||||
device over ssh, cli or REST.
|
||||
required: true
|
||||
type: str
|
||||
choices: [ cli, rest, ssh ]
|
||||
|
||||
@@ -10,21 +10,22 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
DOCUMENTATION = """
|
||||
requirements:
|
||||
- Python SDK for Oracle Cloud Infrastructure U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io)
|
||||
- "python >= 2.7"
|
||||
- Python SDK for Oracle Cloud Infrastructure U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io)
|
||||
notes:
|
||||
- For OCI Python SDK configuration, please refer to
|
||||
U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io/en/latest/configuration.html).
|
||||
- For OCI python sdk configuration, please refer to
|
||||
U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io/en/latest/configuration.html)
|
||||
options:
|
||||
config_file_location:
|
||||
description:
|
||||
- Path to configuration file. If not set then the value of the E(OCI_CONFIG_FILE) environment variable,
|
||||
if any, is used. Otherwise, defaults to C(~/.oci/config).
|
||||
if any, is used. Otherwise, defaults to ~/.oci/config.
|
||||
type: str
|
||||
config_profile_name:
|
||||
description:
|
||||
- The profile to load from the config file referenced by O(config_file_location). If not set, then the
|
||||
value of the E(OCI_CONFIG_PROFILE) environment variable, if any, is used. Otherwise, defaults to the
|
||||
C(DEFAULT) profile in O(config_file_location).
|
||||
"DEFAULT" profile in O(config_file_location).
|
||||
default: "DEFAULT"
|
||||
type: str
|
||||
api_user:
|
||||
@@ -69,8 +70,8 @@ class ModuleDocFragment(object):
|
||||
description:
|
||||
- OCID of your tenancy. If not set, then the value of the OCI_TENANCY variable, if any, is
|
||||
used. This option is required if the tenancy OCID is not specified through a configuration file
|
||||
(See O(config_file_location)). To get the tenancy OCID, please refer to
|
||||
U(https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/apisigningkey.htm).
|
||||
(See O(config_file_location)). To get the tenancy OCID, please refer
|
||||
U(https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/apisigningkey.htm)
|
||||
type: str
|
||||
region:
|
||||
description:
|
||||
|
||||
@@ -21,7 +21,7 @@ class ModuleDocFragment(object):
|
||||
wait_until:
|
||||
description: The lifecycle state to wait for the resource to transition into when O(wait=true). By default,
|
||||
when O(wait=true), we wait for the resource to get into ACTIVE/ATTACHED/AVAILABLE/PROVISIONED/
|
||||
RUNNING applicable lifecycle state during create operation and to get into DELETED/DETACHED/
|
||||
RUNNING applicable lifecycle state during create operation & to get into DELETED/DETACHED/
|
||||
TERMINATED lifecycle state during delete operation.
|
||||
type: str
|
||||
"""
|
||||
|
||||
@@ -32,10 +32,11 @@ options:
|
||||
- FlashBlade API token for admin privileged user.
|
||||
type: str
|
||||
notes:
|
||||
- This module requires the C(purity_fb) Python library.
|
||||
- This module requires the C(purity_fb) Python library
|
||||
- You must set E(PUREFB_URL) and E(PUREFB_API) environment variables
|
||||
if O(fb_url) and O(api_token) arguments are not passed to the module directly.
|
||||
if O(fb_url) and O(api_token) arguments are not passed to the module directly
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- purity_fb >= 1.1
|
||||
'''
|
||||
|
||||
@@ -53,9 +54,10 @@ options:
|
||||
type: str
|
||||
required: true
|
||||
notes:
|
||||
- This module requires the C(purestorage) Python library.
|
||||
- This module requires the C(purestorage) Python library
|
||||
- You must set E(PUREFA_URL) and E(PUREFA_API) environment variables
|
||||
if O(fa_url) and O(api_token) arguments are not passed to the module directly.
|
||||
if O(fa_url) and O(api_token) arguments are not passed to the module directly
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- purestorage
|
||||
'''
|
||||
|
||||
@@ -43,14 +43,15 @@ options:
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
- 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, ...).
|
||||
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
|
||||
@@ -66,7 +67,7 @@ options:
|
||||
type: str
|
||||
description:
|
||||
- The URI of the authentication service.
|
||||
- If not specified will be set to U(https://identity.api.rackspacecloud.com/v2.0/).
|
||||
- If not specified will be set to U(https://identity.api.rackspacecloud.com/v2.0/)
|
||||
credentials:
|
||||
type: path
|
||||
description:
|
||||
@@ -109,12 +110,13 @@ deprecated:
|
||||
why: This module relies on the deprecated package pyrax.
|
||||
alternative: Use the Openstack modules instead.
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
- 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, ...).
|
||||
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, ...)
|
||||
'''
|
||||
|
||||
@@ -42,7 +42,7 @@ options:
|
||||
type: bool
|
||||
default: true
|
||||
notes:
|
||||
- Also see the API documentation on U(https://developer.scaleway.com/).
|
||||
- Also see the API documentation on U(https://developer.scaleway.com/)
|
||||
- If O(api_token) is not set within the module, the following
|
||||
environment variables can be used in decreasing order of precedence
|
||||
E(SCW_TOKEN), E(SCW_API_KEY), E(SCW_OAUTH_TOKEN) or E(SCW_API_TOKEN).
|
||||
|
||||
@@ -14,7 +14,7 @@ options:
|
||||
headers:
|
||||
description:
|
||||
- A dictionary of additional headers to be sent to POST and PUT requests.
|
||||
- Is needed for some modules.
|
||||
- Is needed for some modules
|
||||
type: dict
|
||||
required: false
|
||||
default: {}
|
||||
@@ -30,9 +30,8 @@ options:
|
||||
default: 4444
|
||||
utm_token:
|
||||
description:
|
||||
- "The token used to identify at the REST-API. See
|
||||
U(https://www.sophos.com/en-us/medialibrary/PDFs/documentation/UTMonAWS/Sophos-UTM-RESTful-API.pdf?la=en),
|
||||
Chapter 2.4.2."
|
||||
- "The token used to identify at the REST-API. See U(https://www.sophos.com/en-us/medialibrary/\
|
||||
PDFs/documentation/UTMonAWS/Sophos-UTM-RESTful-API.pdf?la=en), Chapter 2.4.2."
|
||||
type: str
|
||||
required: true
|
||||
utm_protocol:
|
||||
@@ -49,8 +48,8 @@ options:
|
||||
state:
|
||||
description:
|
||||
- The desired state of the object.
|
||||
- V(present) will create or update an object.
|
||||
- V(absent) will delete an object if it was present.
|
||||
- V(present) will create or update an object
|
||||
- V(absent) will delete an object if it was present
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
|
||||
@@ -30,13 +30,11 @@ options:
|
||||
user:
|
||||
description:
|
||||
- Vexata API user with administrative privileges.
|
||||
- Uses the E(VEXATA_USER) environment variable as a fallback.
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Vexata API user password.
|
||||
- Uses the E(VEXATA_PASSWORD) environment variable as a fallback.
|
||||
required: false
|
||||
type: str
|
||||
validate_certs:
|
||||
@@ -50,6 +48,7 @@ options:
|
||||
requirements:
|
||||
- Vexata VX100 storage array with VXOS >= v3.5.0 on storage array
|
||||
- vexatapi >= 0.0.1
|
||||
- E(VEXATA_USER) and E(VEXATA_PASSWORD) environment variables must be set if
|
||||
- python >= 2.7
|
||||
- VEXATA_USER and VEXATA_PASSWORD environment variables must be set if
|
||||
user and password arguments are not passed to the module directly.
|
||||
'''
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me>
|
||||
# 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
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: from_ini
|
||||
short_description: Converts INI text input into a dictionary
|
||||
version_added: 8.2.0
|
||||
author: Steffen Scheib (@sscheib)
|
||||
description:
|
||||
- Converts INI text input into a dictionary.
|
||||
options:
|
||||
_input:
|
||||
description: A string containing an INI document.
|
||||
type: string
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Slurp an INI file
|
||||
ansible.builtin.slurp:
|
||||
src: /etc/rhsm/rhsm.conf
|
||||
register: rhsm_conf
|
||||
|
||||
- name: Display the INI file as dictionary
|
||||
ansible.builtin.debug:
|
||||
var: rhsm_conf.content | b64decode | community.general.from_ini
|
||||
|
||||
- name: Set a new dictionary fact with the contents of the INI file
|
||||
ansible.builtin.set_fact:
|
||||
rhsm_dict: >-
|
||||
{{
|
||||
rhsm_conf.content | b64decode | community.general.from_ini
|
||||
}}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: A dictionary representing the INI file.
|
||||
type: dictionary
|
||||
'''
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.six.moves import StringIO
|
||||
from ansible.module_utils.six.moves.configparser import ConfigParser
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
class IniParser(ConfigParser):
|
||||
''' Implements a configparser which is able to return a dict '''
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.optionxform = str
|
||||
|
||||
def as_dict(self):
|
||||
d = dict(self._sections)
|
||||
for k in d:
|
||||
d[k] = dict(self._defaults, **d[k])
|
||||
d[k].pop('__name__', None)
|
||||
|
||||
if self._defaults:
|
||||
d['DEFAULT'] = dict(self._defaults)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def from_ini(obj):
|
||||
''' Read the given string as INI file and return a dict '''
|
||||
|
||||
if not isinstance(obj, string_types):
|
||||
raise AnsibleFilterError(f'from_ini requires a str, got {type(obj)}')
|
||||
|
||||
parser = IniParser()
|
||||
|
||||
try:
|
||||
parser.read_file(StringIO(obj))
|
||||
except Exception as ex:
|
||||
raise AnsibleFilterError(f'from_ini failed to parse given string: '
|
||||
f'{to_native(ex)}', orig_exc=ex)
|
||||
|
||||
return parser.as_dict()
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
''' Query filter '''
|
||||
|
||||
def filters(self):
|
||||
|
||||
return {
|
||||
'from_ini': from_ini
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me>
|
||||
# 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
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: to_ini
|
||||
short_description: Converts a dictionary to the INI file format
|
||||
version_added: 8.2.0
|
||||
author: Steffen Scheib (@sscheib)
|
||||
description:
|
||||
- Converts a dictionary to the INI file format.
|
||||
options:
|
||||
_input:
|
||||
description: The dictionary that should be converted to the INI format.
|
||||
type: dictionary
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Define a dictionary
|
||||
ansible.builtin.set_fact:
|
||||
my_dict:
|
||||
section_name:
|
||||
key_name: 'key value'
|
||||
|
||||
another_section:
|
||||
connection: 'ssh'
|
||||
|
||||
- name: Write dictionary to INI file
|
||||
ansible.builtin.copy:
|
||||
dest: /tmp/test.ini
|
||||
content: '{{ my_dict | community.general.to_ini }}'
|
||||
|
||||
# /tmp/test.ini will look like this:
|
||||
# [section_name]
|
||||
# key_name = key value
|
||||
#
|
||||
# [another_section]
|
||||
# connection = ssh
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
_value:
|
||||
description: A string formatted as INI file.
|
||||
type: string
|
||||
'''
|
||||
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.common._collections_compat import Mapping
|
||||
from ansible.module_utils.six.moves import StringIO
|
||||
from ansible.module_utils.six.moves.configparser import ConfigParser
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
class IniParser(ConfigParser):
|
||||
''' Implements a configparser which sets the correct optionxform '''
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.optionxform = str
|
||||
|
||||
|
||||
def to_ini(obj):
|
||||
''' Read the given dict and return an INI formatted string '''
|
||||
|
||||
if not isinstance(obj, Mapping):
|
||||
raise AnsibleFilterError(f'to_ini requires a dict, got {type(obj)}')
|
||||
|
||||
ini_parser = IniParser()
|
||||
|
||||
try:
|
||||
ini_parser.read_dict(obj)
|
||||
except Exception as ex:
|
||||
raise AnsibleFilterError('to_ini failed to parse given dict:'
|
||||
f'{to_native(ex)}', orig_exc=ex)
|
||||
|
||||
# catching empty dicts
|
||||
if obj == dict():
|
||||
raise AnsibleFilterError('to_ini received an empty dict. '
|
||||
'An empty dict cannot be converted.')
|
||||
|
||||
config = StringIO()
|
||||
ini_parser.write(config)
|
||||
|
||||
# config.getvalue() returns two \n at the end
|
||||
# with the below insanity, we remove the very last character of
|
||||
# the resulting string
|
||||
return ''.join(config.getvalue().rsplit(config.getvalue()[-1], 1))
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
''' Query filter '''
|
||||
|
||||
def filters(self):
|
||||
|
||||
return {
|
||||
'to_ini': to_ini
|
||||
}
|
||||
@@ -118,6 +118,8 @@ from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, to_safe_group_name
|
||||
from ansible.module_utils.six import text_type
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
# xmlrpc
|
||||
try:
|
||||
import xmlrpclib as xmlrpc_client
|
||||
@@ -274,9 +276,9 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
|
||||
for host in self._get_systems():
|
||||
# Get the FQDN for the host and add it to the right groups
|
||||
if self.inventory_hostname == 'system':
|
||||
hostname = host['name'] # None
|
||||
hostname = make_unsafe(host['name']) # None
|
||||
else:
|
||||
hostname = host['hostname'] # None
|
||||
hostname = make_unsafe(host['hostname']) # None
|
||||
interfaces = host['interfaces']
|
||||
|
||||
if set(host['mgmt_classes']) & set(self.include_mgmt_classes):
|
||||
@@ -296,7 +298,7 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
|
||||
if ivalue['management'] or not ivalue['static']:
|
||||
this_dns_name = ivalue.get('dns_name', None)
|
||||
if this_dns_name is not None and this_dns_name != "":
|
||||
hostname = this_dns_name
|
||||
hostname = make_unsafe(this_dns_name)
|
||||
self.display.vvvv('Set hostname to %s from %s\n' % (hostname, iname))
|
||||
|
||||
if hostname == '':
|
||||
@@ -361,18 +363,18 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
|
||||
if ip_address is None and ip_address_first is not None:
|
||||
ip_address = ip_address_first
|
||||
if ip_address is not None:
|
||||
self.inventory.set_variable(hostname, 'cobbler_ipv4_address', ip_address)
|
||||
self.inventory.set_variable(hostname, 'cobbler_ipv4_address', make_unsafe(ip_address))
|
||||
if ipv6_address is None and ipv6_address_first is not None:
|
||||
ipv6_address = ipv6_address_first
|
||||
if ipv6_address is not None:
|
||||
self.inventory.set_variable(hostname, 'cobbler_ipv6_address', ipv6_address)
|
||||
self.inventory.set_variable(hostname, 'cobbler_ipv6_address', make_unsafe(ipv6_address))
|
||||
|
||||
if self.get_option('want_facts'):
|
||||
try:
|
||||
self.inventory.set_variable(hostname, 'cobbler', host)
|
||||
self.inventory.set_variable(hostname, 'cobbler', make_unsafe(host))
|
||||
except ValueError as e:
|
||||
self.display.warning("Could not set host info for %s: %s" % (hostname, to_text(e)))
|
||||
|
||||
if self.get_option('want_ip_addresses'):
|
||||
self.inventory.set_variable(self.group, 'cobbler_ipv4_addresses', ip_addresses)
|
||||
self.inventory.set_variable(self.group, 'cobbler_ipv6_addresses', ipv6_addresses)
|
||||
self.inventory.set_variable(self.group, 'cobbler_ipv4_addresses', make_unsafe(ip_addresses))
|
||||
self.inventory.set_variable(self.group, 'cobbler_ipv6_addresses', make_unsafe(ipv6_addresses))
|
||||
|
||||
@@ -14,6 +14,7 @@ DOCUMENTATION = '''
|
||||
- Stefan Heitmüller (@morph027) <stefan.heitmueller@gmx.com>
|
||||
short_description: Ansible dynamic inventory plugin for GitLab runners.
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab > 1.8.0
|
||||
extends_documentation_fragment:
|
||||
- constructed
|
||||
@@ -84,6 +85,8 @@ from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
try:
|
||||
import gitlab
|
||||
HAS_GITLAB = True
|
||||
@@ -105,11 +108,11 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
else:
|
||||
runners = gl.runners.all()
|
||||
for runner in runners:
|
||||
host = str(runner['id'])
|
||||
host = make_unsafe(str(runner['id']))
|
||||
ip_address = runner['ip_address']
|
||||
host_attrs = vars(gl.runners.get(runner['id']))['_attrs']
|
||||
host_attrs = make_unsafe(vars(gl.runners.get(runner['id']))['_attrs'])
|
||||
self.inventory.add_host(host, group='gitlab_runners')
|
||||
self.inventory.set_variable(host, 'ansible_host', ip_address)
|
||||
self.inventory.set_variable(host, 'ansible_host', make_unsafe(ip_address))
|
||||
if self.get_option('verbose_output', True):
|
||||
self.inventory.set_variable(host, 'gitlab_runner_attributes', host_attrs)
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ url: http://localhost:5665
|
||||
user: ansible
|
||||
password: secure
|
||||
host_filter: \"linux-servers\" in host.groups
|
||||
validate_certs: false # only do this when connecting to localhost!
|
||||
validate_certs: false
|
||||
inventory_attr: name
|
||||
groups:
|
||||
# simple name matching
|
||||
@@ -97,6 +97,8 @@ from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
''' Host inventory parser for ansible using Icinga2 as source. '''
|
||||
@@ -233,15 +235,15 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
"""Convert Icinga2 API data to JSON format for Ansible"""
|
||||
groups_dict = {"_meta": {"hostvars": {}}}
|
||||
for entry in json_data:
|
||||
host_attrs = entry['attrs']
|
||||
host_attrs = make_unsafe(entry['attrs'])
|
||||
if self.inventory_attr == "name":
|
||||
host_name = entry.get('name')
|
||||
host_name = make_unsafe(entry.get('name'))
|
||||
if self.inventory_attr == "address":
|
||||
# When looking for address for inventory, if missing fallback to object name
|
||||
if host_attrs.get('address', '') != '':
|
||||
host_name = host_attrs.get('address')
|
||||
host_name = make_unsafe(host_attrs.get('address'))
|
||||
else:
|
||||
host_name = entry.get('name')
|
||||
host_name = make_unsafe(entry.get('name'))
|
||||
if self.inventory_attr == "display_name":
|
||||
host_name = host_attrs.get('display_name')
|
||||
if host_attrs['state'] == 0:
|
||||
@@ -257,7 +259,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
# If the address attribute is populated, override ansible_host with the value
|
||||
if host_attrs.get('address') != '':
|
||||
self.inventory.set_variable(host_name, 'ansible_host', host_attrs.get('address'))
|
||||
self.inventory.set_variable(host_name, 'hostname', entry.get('name'))
|
||||
self.inventory.set_variable(host_name, 'hostname', make_unsafe(entry.get('name')))
|
||||
self.inventory.set_variable(host_name, 'display_name', host_attrs.get('display_name'))
|
||||
self.inventory.set_variable(host_name, 'state',
|
||||
host_attrs['state'])
|
||||
|
||||
@@ -12,6 +12,7 @@ DOCUMENTATION = r'''
|
||||
- Luke Murphy (@decentral1se)
|
||||
short_description: Ansible dynamic inventory plugin for Linode.
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- linode_api4 >= 2.0.0
|
||||
description:
|
||||
- Reads inventories from the Linode API v4.
|
||||
@@ -123,6 +124,8 @@ compose:
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
|
||||
try:
|
||||
from linode_api4 import LinodeClient
|
||||
@@ -198,20 +201,21 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
def _add_instances_to_groups(self):
|
||||
"""Add instance names to their dynamic inventory groups."""
|
||||
for instance in self.instances:
|
||||
self.inventory.add_host(instance.label, group=instance.group)
|
||||
self.inventory.add_host(make_unsafe(instance.label), group=instance.group)
|
||||
|
||||
def _add_hostvars_for_instances(self):
|
||||
"""Add hostvars for instances in the dynamic inventory."""
|
||||
ip_style = self.get_option('ip_style')
|
||||
for instance in self.instances:
|
||||
hostvars = instance._raw_json
|
||||
hostname = make_unsafe(instance.label)
|
||||
for hostvar_key in hostvars:
|
||||
if ip_style == 'api' and hostvar_key in ['ipv4', 'ipv6']:
|
||||
continue
|
||||
self.inventory.set_variable(
|
||||
instance.label,
|
||||
hostname,
|
||||
hostvar_key,
|
||||
hostvars[hostvar_key]
|
||||
make_unsafe(hostvars[hostvar_key])
|
||||
)
|
||||
if ip_style == 'api':
|
||||
ips = instance.ips.ipv4.public + instance.ips.ipv4.private
|
||||
@@ -220,9 +224,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
for ip_type in set(ip.type for ip in ips):
|
||||
self.inventory.set_variable(
|
||||
instance.label,
|
||||
hostname,
|
||||
ip_type,
|
||||
self._ip_data([ip for ip in ips if ip.type == ip_type])
|
||||
make_unsafe(self._ip_data([ip for ip in ips if ip.type == ip_type]))
|
||||
)
|
||||
|
||||
def _ip_data(self, ip_list):
|
||||
@@ -253,30 +257,44 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
self._add_instances_to_groups()
|
||||
self._add_hostvars_for_instances()
|
||||
for instance in self.instances:
|
||||
variables = self.inventory.get_host(instance.label).get_vars()
|
||||
hostname = make_unsafe(instance.label)
|
||||
variables = self.inventory.get_host(hostname).get_vars()
|
||||
self._add_host_to_composed_groups(
|
||||
self.get_option('groups'),
|
||||
variables,
|
||||
instance.label,
|
||||
hostname,
|
||||
strict=strict)
|
||||
self._add_host_to_keyed_groups(
|
||||
self.get_option('keyed_groups'),
|
||||
variables,
|
||||
instance.label,
|
||||
hostname,
|
||||
strict=strict)
|
||||
self._set_composite_vars(
|
||||
self.get_option('compose'),
|
||||
variables,
|
||||
instance.label,
|
||||
hostname,
|
||||
strict=strict)
|
||||
|
||||
def verify_file(self, path):
|
||||
"""Verify the Linode configuration file."""
|
||||
"""Verify the Linode configuration file.
|
||||
|
||||
Return true/false if the config-file is valid for this plugin
|
||||
|
||||
Args:
|
||||
str(path): path to the config
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
bool(valid): is valid config file"""
|
||||
valid = False
|
||||
if super(InventoryModule, self).verify_file(path):
|
||||
endings = ('linode.yaml', 'linode.yml')
|
||||
if any((path.endswith(ending) for ending in endings)):
|
||||
return True
|
||||
return False
|
||||
if path.endswith(("linode.yaml", "linode.yml")):
|
||||
valid = True
|
||||
else:
|
||||
self.display.vvv('Inventory source not ending in "linode.yaml" or "linode.yml"')
|
||||
return valid
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
"""Dynamically parse Linode the cloud inventory."""
|
||||
|
||||
@@ -41,20 +41,6 @@ DOCUMENTATION = r'''
|
||||
aliases: [ cert_file ]
|
||||
default: $HOME/.config/lxc/client.crt
|
||||
type: path
|
||||
server_cert:
|
||||
description:
|
||||
- The server certificate file path.
|
||||
type: path
|
||||
version_added: 8.0.0
|
||||
server_check_hostname:
|
||||
description:
|
||||
- This option controls if the server's hostname is checked as part of the HTTPS connection verification.
|
||||
This can be useful to disable, if for example, the server certificate provided (see O(server_cert) option)
|
||||
does not cover a name matching the one used to communicate with the server. Such mismatch is common as LXD
|
||||
generates self-signed server certificates by default.
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 8.0.0
|
||||
trust_password:
|
||||
description:
|
||||
- The client trusted password.
|
||||
@@ -175,6 +161,7 @@ from ansible.module_utils.six import raise_from
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
@@ -300,7 +287,7 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
urls = (url for url in url_list if self.validate_url(url))
|
||||
for url in urls:
|
||||
try:
|
||||
socket_connection = LXDClient(url, self.client_key, self.client_cert, self.debug, self.server_cert, self.server_check_hostname)
|
||||
socket_connection = LXDClient(url, self.client_key, self.client_cert, self.debug)
|
||||
return socket_connection
|
||||
except LXDClientException as err:
|
||||
error_storage[url] = err
|
||||
@@ -470,7 +457,7 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
Helper to get the preferred interface provide by neme pattern from 'prefered_instance_network_interface'.
|
||||
|
||||
Args:
|
||||
str(containe_name): name of instance
|
||||
str(instance_name): name of instance
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
@@ -495,7 +482,7 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
Helper to get the VLAN_ID from the instance
|
||||
|
||||
Args:
|
||||
str(containe_name): name of instance
|
||||
str(instance_name): name of instance
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
@@ -670,7 +657,7 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
|
||||
if self._get_data_entry('inventory/{0}/network_interfaces'.format(instance_name)): # instance have network interfaces
|
||||
self.inventory.set_variable(instance_name, 'ansible_connection', 'ssh')
|
||||
self.inventory.set_variable(instance_name, 'ansible_host', interface_selection(instance_name))
|
||||
self.inventory.set_variable(instance_name, 'ansible_host', make_unsafe(interface_selection(instance_name)))
|
||||
else:
|
||||
self.inventory.set_variable(instance_name, 'ansible_connection', 'local')
|
||||
|
||||
@@ -696,31 +683,39 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
if self.filter.lower() != instance_state:
|
||||
continue
|
||||
# add instance
|
||||
instance_name = make_unsafe(instance_name)
|
||||
self.inventory.add_host(instance_name)
|
||||
# add network information
|
||||
self.build_inventory_network(instance_name)
|
||||
# add os
|
||||
v = self._get_data_entry('inventory/{0}/os'.format(instance_name))
|
||||
if v:
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_os', v.lower())
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_os', make_unsafe(v.lower()))
|
||||
# add release
|
||||
v = self._get_data_entry('inventory/{0}/release'.format(instance_name))
|
||||
if v:
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_release', v.lower())
|
||||
self.inventory.set_variable(
|
||||
instance_name, 'ansible_lxd_release', make_unsafe(v.lower()))
|
||||
# add profile
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(instance_name)))
|
||||
self.inventory.set_variable(
|
||||
instance_name, 'ansible_lxd_profile', make_unsafe(self._get_data_entry('inventory/{0}/profile'.format(instance_name))))
|
||||
# add state
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_state', instance_state)
|
||||
self.inventory.set_variable(
|
||||
instance_name, 'ansible_lxd_state', make_unsafe(instance_state))
|
||||
# add type
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_type', self._get_data_entry('inventory/{0}/type'.format(instance_name)))
|
||||
self.inventory.set_variable(
|
||||
instance_name, 'ansible_lxd_type', make_unsafe(self._get_data_entry('inventory/{0}/type'.format(instance_name))))
|
||||
# add location information
|
||||
if self._get_data_entry('inventory/{0}/location'.format(instance_name)) != "none": # wrong type by lxd 'none' != 'None'
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_location', self._get_data_entry('inventory/{0}/location'.format(instance_name)))
|
||||
self.inventory.set_variable(
|
||||
instance_name, 'ansible_lxd_location', make_unsafe(self._get_data_entry('inventory/{0}/location'.format(instance_name))))
|
||||
# add VLAN_ID information
|
||||
if self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)):
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)))
|
||||
self.inventory.set_variable(
|
||||
instance_name, 'ansible_lxd_vlan_ids', make_unsafe(self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name))))
|
||||
# add project
|
||||
self.inventory.set_variable(instance_name, 'ansible_lxd_project', self._get_data_entry('inventory/{0}/project'.format(instance_name)))
|
||||
self.inventory.set_variable(
|
||||
instance_name, 'ansible_lxd_project', make_unsafe(self._get_data_entry('inventory/{0}/project'.format(instance_name))))
|
||||
|
||||
def build_inventory_groups_location(self, group_name):
|
||||
"""create group by attribute: location
|
||||
@@ -993,7 +988,7 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
for group_name in self.groupby:
|
||||
if not group_name.isalnum():
|
||||
raise AnsibleParserError('Invalid character(s) in groupname: {0}'.format(to_native(group_name)))
|
||||
group_type(group_name)
|
||||
group_type(make_unsafe(group_name))
|
||||
|
||||
def build_inventory(self):
|
||||
"""Build dynamic inventory
|
||||
@@ -1092,8 +1087,6 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
try:
|
||||
self.client_key = self.get_option('client_key')
|
||||
self.client_cert = self.get_option('client_cert')
|
||||
self.server_cert = self.get_option('server_cert')
|
||||
self.server_check_hostname = self.get_option('server_check_hostname')
|
||||
self.project = self.get_option('project')
|
||||
self.debug = self.DEBUG
|
||||
self.data = {} # store for inventory-data
|
||||
|
||||
@@ -127,6 +127,8 @@ from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
@@ -143,6 +145,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
strict = self.get_option('strict')
|
||||
|
||||
for host in hosts:
|
||||
host = make_unsafe(host)
|
||||
hostname = host['name']
|
||||
self.inventory.add_host(hostname)
|
||||
for var, value in host.items():
|
||||
|
||||
@@ -69,6 +69,8 @@ from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils.ansible_release import __version__ as ansible_version
|
||||
from ansible.module_utils.six.moves.urllib.parse import urljoin
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
NAME = 'community.general.online'
|
||||
@@ -169,20 +171,20 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
"support"
|
||||
)
|
||||
for attribute in targeted_attributes:
|
||||
self.inventory.set_variable(hostname, attribute, host_infos[attribute])
|
||||
self.inventory.set_variable(hostname, attribute, make_unsafe(host_infos[attribute]))
|
||||
|
||||
if self.extract_public_ipv4(host_infos=host_infos):
|
||||
self.inventory.set_variable(hostname, "public_ipv4", self.extract_public_ipv4(host_infos=host_infos))
|
||||
self.inventory.set_variable(hostname, "ansible_host", self.extract_public_ipv4(host_infos=host_infos))
|
||||
self.inventory.set_variable(hostname, "public_ipv4", make_unsafe(self.extract_public_ipv4(host_infos=host_infos)))
|
||||
self.inventory.set_variable(hostname, "ansible_host", make_unsafe(self.extract_public_ipv4(host_infos=host_infos)))
|
||||
|
||||
if self.extract_private_ipv4(host_infos=host_infos):
|
||||
self.inventory.set_variable(hostname, "public_ipv4", self.extract_private_ipv4(host_infos=host_infos))
|
||||
self.inventory.set_variable(hostname, "public_ipv4", make_unsafe(self.extract_private_ipv4(host_infos=host_infos)))
|
||||
|
||||
if self.extract_os_name(host_infos=host_infos):
|
||||
self.inventory.set_variable(hostname, "os_name", self.extract_os_name(host_infos=host_infos))
|
||||
self.inventory.set_variable(hostname, "os_name", make_unsafe(self.extract_os_name(host_infos=host_infos)))
|
||||
|
||||
if self.extract_os_version(host_infos=host_infos):
|
||||
self.inventory.set_variable(hostname, "os_version", self.extract_os_name(host_infos=host_infos))
|
||||
self.inventory.set_variable(hostname, "os_version", make_unsafe(self.extract_os_name(host_infos=host_infos)))
|
||||
|
||||
def _filter_host(self, host_infos, hostname_preferences):
|
||||
|
||||
@@ -201,6 +203,8 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
if not hostname:
|
||||
return
|
||||
|
||||
hostname = make_unsafe(hostname)
|
||||
|
||||
self.inventory.add_host(host=hostname)
|
||||
self._fill_host_variables(hostname=hostname, host_infos=host_infos)
|
||||
|
||||
@@ -210,6 +214,8 @@ class InventoryModule(BaseInventoryPlugin):
|
||||
if not group:
|
||||
return
|
||||
|
||||
group = make_unsafe(group)
|
||||
|
||||
self.inventory.add_group(group=group)
|
||||
self.inventory.add_host(group=group, host=hostname)
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ from ansible.errors import AnsibleError
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
from collections import namedtuple
|
||||
import os
|
||||
|
||||
@@ -215,6 +217,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
filter_by_label = self.get_option('filter_by_label')
|
||||
servers = self._retrieve_servers(filter_by_label)
|
||||
for server in servers:
|
||||
server = make_unsafe(server)
|
||||
hostname = server['name']
|
||||
# check for labels
|
||||
if group_by_labels and server['LABELS']:
|
||||
|
||||
@@ -116,11 +116,6 @@ DOCUMENTATION = '''
|
||||
- The default of this option changed from V(true) to V(false) in community.general 6.0.0.
|
||||
type: bool
|
||||
default: false
|
||||
exclude_nodes:
|
||||
description: Exclude proxmox nodes and the nodes-group from the inventory output.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 8.1.0
|
||||
filters:
|
||||
version_added: 4.6.0
|
||||
description: A list of Jinja templates that allow filtering hosts.
|
||||
@@ -171,6 +166,7 @@ plugin: community.general.proxmox
|
||||
url: http://pve.domain.com:8006
|
||||
user: ansible@pve
|
||||
password: secure
|
||||
validate_certs: false
|
||||
want_facts: true
|
||||
keyed_groups:
|
||||
# proxmox_tags_parsed is an example of a fact only returned when 'want_facts=true'
|
||||
@@ -191,10 +187,10 @@ want_proxmox_nodes_ansible_host: true
|
||||
# Note: my_inv_var demonstrates how to add a string variable to every host used by the inventory.
|
||||
# my.proxmox.yml
|
||||
plugin: community.general.proxmox
|
||||
url: http://192.168.1.2:8006
|
||||
url: http://pve.domain.com:8006
|
||||
user: ansible@pve
|
||||
password: secure
|
||||
validate_certs: false # only do this when you trust the network!
|
||||
validate_certs: false
|
||||
want_facts: true
|
||||
want_proxmox_nodes_ansible_host: false
|
||||
compose:
|
||||
@@ -228,6 +224,7 @@ from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.utils.display import Display
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
# 3rd party imports
|
||||
try:
|
||||
@@ -334,7 +331,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
self._cache[self.cache_key][url] = data
|
||||
|
||||
return self._cache[self.cache_key][url]
|
||||
return make_unsafe(self._cache[self.cache_key][url])
|
||||
|
||||
def _get_nodes(self):
|
||||
return self._get_json("%s/api2/json/nodes" % self.proxmox_url)
|
||||
@@ -569,9 +566,9 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
for group in default_groups:
|
||||
self.inventory.add_group(self._group('all_%s' % (group)))
|
||||
|
||||
nodes_group = self._group('nodes')
|
||||
if not self.exclude_nodes:
|
||||
self.inventory.add_group(nodes_group)
|
||||
self.inventory.add_group(nodes_group)
|
||||
|
||||
want_proxmox_nodes_ansible_host = self.get_option("want_proxmox_nodes_ansible_host")
|
||||
|
||||
@@ -581,23 +578,22 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
for node in self._get_nodes():
|
||||
if not node.get('node'):
|
||||
continue
|
||||
if not self.exclude_nodes:
|
||||
self.inventory.add_host(node['node'])
|
||||
if node['type'] == 'node' and not self.exclude_nodes:
|
||||
|
||||
self.inventory.add_host(node['node'])
|
||||
if node['type'] == 'node':
|
||||
self.inventory.add_child(nodes_group, node['node'])
|
||||
|
||||
if node['status'] == 'offline':
|
||||
continue
|
||||
|
||||
# get node IP address
|
||||
if want_proxmox_nodes_ansible_host and not self.exclude_nodes:
|
||||
if want_proxmox_nodes_ansible_host:
|
||||
ip = self._get_node_ip(node['node'])
|
||||
self.inventory.set_variable(node['node'], 'ansible_host', ip)
|
||||
|
||||
# Setting composite variables
|
||||
if not self.exclude_nodes:
|
||||
variables = self.inventory.get_host(node['node']).get_vars()
|
||||
self._set_composite_vars(self.get_option('compose'), variables, node['node'], strict=self.strict)
|
||||
variables = self.inventory.get_host(node['node']).get_vars()
|
||||
self._set_composite_vars(self.get_option('compose'), variables, node['node'], strict=self.strict)
|
||||
|
||||
# add LXC/Qemu groups for the node
|
||||
for ittype in ('lxc', 'qemu'):
|
||||
@@ -640,8 +636,8 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
if self.get_option('qemu_extended_statuses') and not self.get_option('want_facts'):
|
||||
raise AnsibleError('You must set want_facts to True if you want to use qemu_extended_statuses.')
|
||||
|
||||
# read rest of options
|
||||
self.exclude_nodes = self.get_option('exclude_nodes')
|
||||
self.cache_key = self.get_cache_key(path)
|
||||
self.use_cache = cache and self.get_option('cache')
|
||||
self.host_filters = self.get_option('filters')
|
||||
|
||||
@@ -121,6 +121,7 @@ else:
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
|
||||
from ansible_collections.community.general.plugins.module_utils.scaleway import SCALEWAY_LOCATION, parse_pagination_link
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
from ansible.module_utils.six import raise_from
|
||||
@@ -279,7 +280,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
zone_info = SCALEWAY_LOCATION[zone]
|
||||
|
||||
url = _build_server_url(zone_info["api_endpoint"])
|
||||
raw_zone_hosts_infos = _fetch_information(url=url, token=token)
|
||||
raw_zone_hosts_infos = make_unsafe(_fetch_information(url=url, token=token))
|
||||
|
||||
for host_infos in raw_zone_hosts_infos:
|
||||
|
||||
@@ -341,4 +342,4 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
hostname_preference = self.get_option("hostnames")
|
||||
|
||||
for zone in self._get_zones(config_zones):
|
||||
self.do_zone_inventory(zone=zone, token=token, tags=tags, hostname_preferences=hostname_preference)
|
||||
self.do_zone_inventory(zone=make_unsafe(zone), token=token, tags=tags, hostname_preferences=hostname_preference)
|
||||
|
||||
@@ -73,6 +73,8 @@ from ansible.plugins.inventory import (
|
||||
)
|
||||
from ansible.utils.display import Display
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
@@ -271,7 +273,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
if not cache or cache_needs_update:
|
||||
results = self._query()
|
||||
|
||||
self._populate(results)
|
||||
self._populate(make_unsafe(results))
|
||||
|
||||
# If the cache has expired/doesn't exist or
|
||||
# if refresh_inventory/flush cache is used
|
||||
|
||||
@@ -63,6 +63,8 @@ from ansible.module_utils.common._collections_compat import MutableMapping
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
''' Host inventory parser for ansible using local virtualbox. '''
|
||||
@@ -116,6 +118,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), hostvars[host], host, strict=strict)
|
||||
|
||||
def _populate_from_cache(self, source_data):
|
||||
source_data = make_unsafe(source_data)
|
||||
hostvars = source_data.pop('_meta', {}).get('hostvars', {})
|
||||
for group in source_data:
|
||||
if group == 'all':
|
||||
@@ -162,7 +165,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
v = v.strip()
|
||||
# found host
|
||||
if k.startswith('Name') and ',' not in v: # some setting strings appear in Name
|
||||
current_host = v
|
||||
current_host = make_unsafe(v)
|
||||
if current_host not in hostvars:
|
||||
hostvars[current_host] = {}
|
||||
self.inventory.add_host(current_host)
|
||||
@@ -170,12 +173,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
# try to get network info
|
||||
netdata = self._query_vbox_data(current_host, netinfo)
|
||||
if netdata:
|
||||
self.inventory.set_variable(current_host, 'ansible_host', netdata)
|
||||
self.inventory.set_variable(current_host, 'ansible_host', make_unsafe(netdata))
|
||||
|
||||
# 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:
|
||||
@@ -185,17 +189,17 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
|
||||
else:
|
||||
# found vars, accumulate in hostvars for clean inventory set
|
||||
pref_k = 'vbox_' + k.strip().replace(' ', '_')
|
||||
pref_k = make_unsafe('vbox_' + k.strip().replace(' ', '_'))
|
||||
leading_spaces = len(k) - len(k.lstrip(' '))
|
||||
if 0 < leading_spaces <= 2:
|
||||
if prevkey not in hostvars[current_host] or not isinstance(hostvars[current_host][prevkey], dict):
|
||||
hostvars[current_host][prevkey] = {}
|
||||
hostvars[current_host][prevkey][pref_k] = v
|
||||
hostvars[current_host][prevkey][pref_k] = make_unsafe(v)
|
||||
elif leading_spaces > 2:
|
||||
continue
|
||||
else:
|
||||
if v != '':
|
||||
hostvars[current_host][pref_k] = v
|
||||
hostvars[current_host][pref_k] = make_unsafe(v)
|
||||
if self._ungrouped_host(current_host, cacheable_results):
|
||||
if 'ungrouped' not in cacheable_results:
|
||||
cacheable_results['ungrouped'] = {'hosts': []}
|
||||
|
||||
@@ -84,6 +84,7 @@ from ansible.errors import AnsibleError
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
from ansible_collections.community.general.plugins.plugin_utils.unsafe import make_unsafe
|
||||
|
||||
# 3rd party imports
|
||||
try:
|
||||
@@ -347,4 +348,4 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
self.protocol = 'ws'
|
||||
|
||||
objects = self._get_objects()
|
||||
self._populate(objects)
|
||||
self._populate(make_unsafe(objects))
|
||||
|
||||
@@ -110,8 +110,6 @@ class Bitwarden(object):
|
||||
out, err = p.communicate(to_bytes(stdin))
|
||||
rc = p.wait()
|
||||
if rc != expected_rc:
|
||||
if len(args) > 2 and args[0] == 'get' and args[1] == 'item' and b'Not found.' in err:
|
||||
return 'null', ''
|
||||
raise BitwardenException(err)
|
||||
return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict')
|
||||
|
||||
@@ -120,10 +118,7 @@ class Bitwarden(object):
|
||||
"""
|
||||
|
||||
# Prepare set of params for Bitwarden CLI
|
||||
if search_field == 'id':
|
||||
params = ['get', 'item', search_value]
|
||||
else:
|
||||
params = ['list', 'items', '--search', search_value]
|
||||
params = ['list', 'items', '--search', search_value]
|
||||
|
||||
if collection_id:
|
||||
params.extend(['--collectionid', collection_id])
|
||||
@@ -132,11 +127,7 @@ class Bitwarden(object):
|
||||
|
||||
# This includes things that matched in different fields.
|
||||
initial_matches = AnsibleJSONDecoder().raw_decode(out)[0]
|
||||
if search_field == 'id':
|
||||
if initial_matches is None:
|
||||
initial_matches = []
|
||||
else:
|
||||
initial_matches = [initial_matches]
|
||||
|
||||
# Filter to only include results from the right field.
|
||||
return [item for item in initial_matches if item[search_field] == search_value]
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ RETURN = """
|
||||
"""
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from time import sleep
|
||||
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
@@ -84,11 +85,29 @@ class BitwardenSecretsManagerException(AnsibleLookupError):
|
||||
class BitwardenSecretsManager(object):
|
||||
def __init__(self, path='bws'):
|
||||
self._cli_path = path
|
||||
self._max_retries = 3
|
||||
self._retry_delay = 1
|
||||
|
||||
@property
|
||||
def cli_path(self):
|
||||
return self._cli_path
|
||||
|
||||
def _run_with_retry(self, args, stdin=None, retries=0):
|
||||
out, err, rc = self._run(args, stdin)
|
||||
|
||||
if rc != 0:
|
||||
if retries >= self._max_retries:
|
||||
raise BitwardenSecretsManagerException("Max retries exceeded. Unable to retrieve secret.")
|
||||
|
||||
if "Too many requests" in err:
|
||||
delay = self._retry_delay * (2 ** retries)
|
||||
sleep(delay)
|
||||
return self._run_with_retry(args, stdin, retries + 1)
|
||||
else:
|
||||
raise BitwardenSecretsManagerException("Command failed with return code {rc}: {err}".format(rc=rc, err=err))
|
||||
|
||||
return out, err, rc
|
||||
|
||||
def _run(self, args, stdin=None):
|
||||
p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
||||
out, err = p.communicate(stdin)
|
||||
@@ -107,7 +126,7 @@ class BitwardenSecretsManager(object):
|
||||
'get', 'secret', secret_id
|
||||
]
|
||||
|
||||
out, err, rc = self._run(params)
|
||||
out, err, rc = self._run_with_retry(params)
|
||||
if rc != 0:
|
||||
raise BitwardenSecretsManagerException(to_text(err))
|
||||
|
||||
|
||||
@@ -98,10 +98,15 @@ def load_collection_meta(collection_pkg, no_version='*'):
|
||||
if os.path.exists(manifest_path):
|
||||
return load_collection_meta_manifest(manifest_path)
|
||||
|
||||
# Try to load galaxy.yml
|
||||
# Try to load galaxy.y(a)ml
|
||||
galaxy_path = os.path.join(path, 'galaxy.yml')
|
||||
if os.path.exists(galaxy_path):
|
||||
return load_collection_meta_galaxy(galaxy_path, no_version=no_version)
|
||||
galaxy_alt_path = os.path.join(path, 'galaxy.yaml')
|
||||
# galaxy.yaml was only supported in ansible-base 2.10 and ansible-core 2.11. Support was removed
|
||||
# in https://github.com/ansible/ansible/commit/595413d11346b6f26bb3d9df2d8e05f2747508a3 for
|
||||
# ansible-core 2.12.
|
||||
for path in (galaxy_path, galaxy_alt_path):
|
||||
if os.path.exists(path):
|
||||
return load_collection_meta_galaxy(path, no_version=no_version)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ EXAMPLES = '''
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.etcd', 'foo', 'bar', 'baz') }}"
|
||||
|
||||
- name: "you can set server options inline"
|
||||
- name: "since Ansible 2.5 you can set server options inline"
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.etcd', 'foo', version='v2', url='http://192.168.0.27:4001') }}"
|
||||
'''
|
||||
@@ -62,7 +62,7 @@ EXAMPLES = '''
|
||||
RETURN = '''
|
||||
_raw:
|
||||
description:
|
||||
- List of values associated with input keys.
|
||||
- list of values associated with input keys
|
||||
type: list
|
||||
elements: string
|
||||
'''
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2023, Poh Wei Sheng <weisheng-p@hotmail.sg>
|
||||
# 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 = '''
|
||||
name: github_app_access_token
|
||||
author:
|
||||
- Poh Wei Sheng (@weisheng-p)
|
||||
short_description: Obtain short-lived Github App Access tokens
|
||||
version_added: '8.2.0'
|
||||
requirements:
|
||||
- jwt (https://github.com/GehirnInc/python-jwt)
|
||||
description:
|
||||
- This generates a Github access token that can be used with a C(git) command, if you use a Github App.
|
||||
options:
|
||||
key_path:
|
||||
description:
|
||||
- Path to your private key.
|
||||
required: true
|
||||
type: path
|
||||
app_id:
|
||||
description:
|
||||
- Your GitHub App ID, you can find this in the Settings page.
|
||||
required: true
|
||||
type: str
|
||||
installation_id:
|
||||
description:
|
||||
- The installation ID that contains the git repository you would like access to.
|
||||
- As of 2023-12-24, this can be found via Settings page > Integrations > Application. The last part of the URL in the
|
||||
configure button is the installation ID.
|
||||
- Alternatively, you can use PyGithub (U(https://github.com/PyGithub/PyGithub)) to get your installation ID.
|
||||
required: true
|
||||
type: str
|
||||
token_expiry:
|
||||
description:
|
||||
- How long the token should last for in seconds.
|
||||
default: 600
|
||||
type: int
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Get access token to be used for git checkout with app_id=123456, installation_id=64209
|
||||
ansible.builtin.git:
|
||||
repo: >-
|
||||
https://x-access-token:{{ github_token }}@github.com/hidden_user/super-secret-repo.git
|
||||
dest: /srv/checkout
|
||||
vars:
|
||||
github_token: >-
|
||||
lookup('github_app_token', key_path='/home/to_your/key',
|
||||
app_id='123456', installation_id='64209')
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_raw:
|
||||
description: A one-element list containing your GitHub access token.
|
||||
type: list
|
||||
elements: str
|
||||
'''
|
||||
|
||||
|
||||
try:
|
||||
from jwt import JWT, jwk_from_pem
|
||||
HAS_JWT = True
|
||||
except ImportError:
|
||||
HAS_JWT = False
|
||||
|
||||
import time
|
||||
import json
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.utils.display import Display
|
||||
|
||||
if HAS_JWT:
|
||||
jwt_instance = JWT()
|
||||
else:
|
||||
jwk_from_pem = None
|
||||
jwt_instance = None
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
def read_key(path):
|
||||
try:
|
||||
with open(path, 'rb') as pem_file:
|
||||
return jwk_from_pem(pem_file.read())
|
||||
except Exception as e:
|
||||
raise AnsibleError("Error while parsing key file: {0}".format(e))
|
||||
|
||||
|
||||
def encode_jwt(app_id, jwk, exp=600):
|
||||
now = int(time.time())
|
||||
payload = {
|
||||
'iat': now,
|
||||
'exp': now + exp,
|
||||
'iss': app_id,
|
||||
}
|
||||
try:
|
||||
return jwt_instance.encode(payload, jwk, alg='RS256')
|
||||
except Exception as e:
|
||||
raise AnsibleError("Error while encoding jwt: {0}".format(e))
|
||||
|
||||
|
||||
def post_request(generated_jwt, installation_id):
|
||||
github_api_url = f'https://api.github.com/app/installations/{installation_id}/access_tokens'
|
||||
headers = {
|
||||
"Authorization": f'Bearer {generated_jwt}',
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
try:
|
||||
response = open_url(github_api_url, headers=headers, method='POST')
|
||||
except HTTPError as e:
|
||||
try:
|
||||
error_body = json.loads(e.read().decode())
|
||||
display.vvv("Error returned: {0}".format(error_body))
|
||||
except Exception:
|
||||
error_body = {}
|
||||
if e.code == 404:
|
||||
raise AnsibleError("Github return error. Please confirm your installationd_id value is valid")
|
||||
elif e.code == 401:
|
||||
raise AnsibleError("Github return error. Please confirm your private key is valid")
|
||||
raise AnsibleError("Unexpected data returned: {0} -- {1}".format(e, error_body))
|
||||
response_body = response.read()
|
||||
try:
|
||||
json_data = json.loads(response_body.decode('utf-8'))
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise AnsibleError("Error while dencoding JSON respone from github: {0}".format(e))
|
||||
return json_data.get('token')
|
||||
|
||||
|
||||
def get_token(key_path, app_id, installation_id, expiry=600):
|
||||
jwk = read_key(key_path)
|
||||
generated_jwt = encode_jwt(app_id, jwk, exp=expiry)
|
||||
return post_request(generated_jwt, installation_id)
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
if not HAS_JWT:
|
||||
raise AnsibleError('Python jwt library is required. '
|
||||
'Please install using "pip install jwt"')
|
||||
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
|
||||
t = get_token(
|
||||
self.get_option('key_path'),
|
||||
self.get_option('app_id'),
|
||||
self.get_option('installation_id'),
|
||||
self.get_option('token_expiry'),
|
||||
)
|
||||
|
||||
return [t]
|
||||
@@ -11,7 +11,7 @@ DOCUMENTATION = """
|
||||
- Roy Lenferink (@rlenferink)
|
||||
- Mark Ettema (@m-a-r-k-e)
|
||||
name: merge_variables
|
||||
short_description: merge variables with a certain suffix
|
||||
short_description: merge variables whose names match a given pattern
|
||||
description:
|
||||
- This lookup returns the merged result of all variables in scope that match the given prefixes, suffixes, or
|
||||
regular expressions, optionally.
|
||||
|
||||
@@ -14,28 +14,59 @@ DOCUMENTATION = '''
|
||||
- Scott Buchanan (@scottsb)
|
||||
- Andrew Zenk (@azenk)
|
||||
- Sam Doran (@samdoran)
|
||||
short_description: Fetch field values from 1Password
|
||||
requirements:
|
||||
- C(op) 1Password command line utility. See U(https://support.1password.com/command-line/)
|
||||
short_description: fetch field values from 1Password
|
||||
description:
|
||||
- P(community.general.onepassword#lookup) wraps the C(op) command line utility to fetch specific field values from 1Password.
|
||||
requirements:
|
||||
- C(op) 1Password command line utility
|
||||
options:
|
||||
_terms:
|
||||
description: Identifier(s) (case-insensitive UUID or name) of item(s) to retrieve.
|
||||
description: identifier(s) (UUID, name, or subdomain; case-insensitive) of item(s) to retrieve.
|
||||
required: true
|
||||
account_id:
|
||||
version_added: 7.5.0
|
||||
domain:
|
||||
version_added: 3.2.0
|
||||
field:
|
||||
description: Field to return from each matching item (case-insensitive).
|
||||
description: field to return from each matching item (case-insensitive).
|
||||
default: 'password'
|
||||
master_password:
|
||||
description: The password used to unlock the specified vault.
|
||||
aliases: ['vault_password']
|
||||
section:
|
||||
description: Item section containing the field to retrieve (case-insensitive). If absent will return first match from any section.
|
||||
domain:
|
||||
description: Domain of 1Password.
|
||||
version_added: 3.2.0
|
||||
default: '1password.com'
|
||||
type: str
|
||||
subdomain:
|
||||
description: The 1Password subdomain to authenticate against.
|
||||
account_id:
|
||||
description: The account ID to target.
|
||||
type: str
|
||||
version_added: 7.5.0
|
||||
username:
|
||||
description: The username used to sign in.
|
||||
secret_key:
|
||||
description: The secret key used when performing an initial sign in.
|
||||
service_account_token:
|
||||
description:
|
||||
- The access key for a service account.
|
||||
- Only works with 1Password CLI version 2 or later.
|
||||
type: str
|
||||
version_added: 7.1.0
|
||||
extends_documentation_fragment:
|
||||
- community.general.onepassword
|
||||
- community.general.onepassword.lookup
|
||||
vault:
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
|
||||
notes:
|
||||
- This lookup will use an existing 1Password session if one exists. If not, and you have already
|
||||
performed an initial sign in (meaning C(~/.op/config), C(~/.config/op/config) or C(~/.config/.op/config) exists), then only the
|
||||
C(master_password) is required. You may optionally specify O(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
|
||||
- This lookup can perform an initial login by providing O(subdomain), O(username), O(secret_key), and O(master_password).
|
||||
- Can target a specific account by providing the O(account_id).
|
||||
- Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
|
||||
needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
|
||||
to the 1Password master password.
|
||||
- This lookup stores potentially sensitive data from 1Password as Ansible facts.
|
||||
Facts are subject to caching if enabled, which means this data could be stored in clear text
|
||||
on disk or in a database.
|
||||
- Tested with C(op) version 2.7.2
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
@@ -77,7 +108,7 @@ EXAMPLES = """
|
||||
|
||||
RETURN = """
|
||||
_raw:
|
||||
description: Field data requested.
|
||||
description: field data requested
|
||||
type: list
|
||||
elements: str
|
||||
"""
|
||||
@@ -88,7 +119,7 @@ import json
|
||||
import subprocess
|
||||
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.errors import AnsibleLookupError, AnsibleOptionsError
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
from ansible.module_utils.six import with_metaclass
|
||||
@@ -116,8 +147,6 @@ class OnePassCLIBase(with_metaclass(abc.ABCMeta, object)):
|
||||
master_password=None,
|
||||
service_account_token=None,
|
||||
account_id=None,
|
||||
connect_host=None,
|
||||
connect_token=None,
|
||||
):
|
||||
self.subdomain = subdomain
|
||||
self.domain = domain
|
||||
@@ -126,8 +155,6 @@ class OnePassCLIBase(with_metaclass(abc.ABCMeta, object)):
|
||||
self.secret_key = secret_key
|
||||
self.service_account_token = service_account_token
|
||||
self.account_id = account_id
|
||||
self.connect_host = connect_host
|
||||
self.connect_token = connect_token
|
||||
|
||||
self._path = None
|
||||
self._version = None
|
||||
@@ -306,10 +333,6 @@ class OnePassCLIv1(OnePassCLIBase):
|
||||
return not bool(rc)
|
||||
|
||||
def full_signin(self):
|
||||
if self.connect_host or self.connect_token:
|
||||
raise AnsibleLookupError(
|
||||
"1Password Connect is not available with 1Password CLI version 1. Please use version 2 or later.")
|
||||
|
||||
if self.service_account_token:
|
||||
raise AnsibleLookupError(
|
||||
"1Password CLI version 1 does not support Service Accounts. Please use version 2 or later.")
|
||||
@@ -489,18 +512,15 @@ class OnePassCLIv2(OnePassCLIBase):
|
||||
current_section_title = section.get("label", section.get("id", "")).lower()
|
||||
if section_title == current_section_title:
|
||||
# In the correct section. Check "label" then "id" for the desired field_name
|
||||
if field.get("label") == field_name:
|
||||
if field.get("label", "").lower() == field_name:
|
||||
return field.get("value", "")
|
||||
|
||||
if field.get("id") == field_name:
|
||||
if field.get("id", "").lower() == field_name:
|
||||
return field.get("value", "")
|
||||
|
||||
return ""
|
||||
|
||||
def assert_logged_in(self):
|
||||
if self.connect_host and self.connect_token:
|
||||
return True
|
||||
|
||||
if self.service_account_token:
|
||||
args = ["whoami"]
|
||||
environment_update = {"OP_SERVICE_ACCOUNT_TOKEN": self.service_account_token}
|
||||
@@ -560,15 +580,6 @@ class OnePassCLIv2(OnePassCLIBase):
|
||||
if vault is not None:
|
||||
args += ["--vault={0}".format(vault)]
|
||||
|
||||
if self.connect_host and self.connect_token:
|
||||
if vault is None:
|
||||
raise AnsibleLookupError("'vault' is required with 1Password Connect")
|
||||
environment_update = {
|
||||
"OP_CONNECT_HOST": self.connect_host,
|
||||
"OP_CONNECT_TOKEN": self.connect_token,
|
||||
}
|
||||
return self._run(args, environment_update=environment_update)
|
||||
|
||||
if self.service_account_token:
|
||||
if vault is None:
|
||||
raise AnsibleLookupError("'vault' is required with 'service_account_token'")
|
||||
@@ -592,7 +603,7 @@ class OnePassCLIv2(OnePassCLIBase):
|
||||
|
||||
class OnePass(object):
|
||||
def __init__(self, subdomain=None, domain="1password.com", username=None, secret_key=None, master_password=None,
|
||||
service_account_token=None, account_id=None, connect_host=None, connect_token=None, cli_class=None):
|
||||
service_account_token=None, account_id=None):
|
||||
self.subdomain = subdomain
|
||||
self.domain = domain
|
||||
self.username = username
|
||||
@@ -600,28 +611,19 @@ class OnePass(object):
|
||||
self.master_password = master_password
|
||||
self.service_account_token = service_account_token
|
||||
self.account_id = account_id
|
||||
self.connect_host = connect_host
|
||||
self.connect_token = connect_token
|
||||
|
||||
self.logged_in = False
|
||||
self.token = None
|
||||
|
||||
self._config = OnePasswordConfig()
|
||||
self._cli = self._get_cli_class(cli_class)
|
||||
|
||||
if (self.connect_host or self.connect_token) and None in (self.connect_host, self.connect_token):
|
||||
raise AnsibleOptionsError("connect_host and connect_token are required together")
|
||||
|
||||
def _get_cli_class(self, cli_class=None):
|
||||
if cli_class is not None:
|
||||
return cli_class(self.subdomain, self.domain, self.username, self.secret_key, self.master_password, self.service_account_token)
|
||||
self._cli = self._get_cli_class()
|
||||
|
||||
def _get_cli_class(self):
|
||||
version = OnePassCLIBase.get_current_version()
|
||||
for cls in OnePassCLIBase.__subclasses__():
|
||||
if cls.supports_version == version.split(".")[0]:
|
||||
try:
|
||||
return cls(self.subdomain, self.domain, self.username, self.secret_key, self.master_password, self.service_account_token,
|
||||
self.account_id, self.connect_host, self.connect_token)
|
||||
return cls(self.subdomain, self.domain, self.username, self.secret_key, self.master_password, self.service_account_token, self.account_id)
|
||||
except TypeError as e:
|
||||
raise AnsibleLookupError(e)
|
||||
|
||||
@@ -686,20 +688,8 @@ class LookupModule(LookupBase):
|
||||
master_password = self.get_option("master_password")
|
||||
service_account_token = self.get_option("service_account_token")
|
||||
account_id = self.get_option("account_id")
|
||||
connect_host = self.get_option("connect_host")
|
||||
connect_token = self.get_option("connect_token")
|
||||
|
||||
op = OnePass(
|
||||
subdomain=subdomain,
|
||||
domain=domain,
|
||||
username=username,
|
||||
secret_key=secret_key,
|
||||
master_password=master_password,
|
||||
service_account_token=service_account_token,
|
||||
account_id=account_id,
|
||||
connect_host=connect_host,
|
||||
connect_token=connect_token,
|
||||
)
|
||||
op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token, account_id)
|
||||
op.assert_logged_in()
|
||||
|
||||
values = []
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2023, 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: onepassword_doc
|
||||
author:
|
||||
- Sam Doran (@samdoran)
|
||||
requirements:
|
||||
- C(op) 1Password command line utility version 2 or later.
|
||||
short_description: Fetch documents stored in 1Password
|
||||
version_added: "8.1.0"
|
||||
description:
|
||||
- P(community.general.onepassword_doc#lookup) wraps C(op) command line utility to fetch one or more documents from 1Password.
|
||||
notes:
|
||||
- The document contents are a string exactly as stored in 1Password.
|
||||
- This plugin requires C(op) version 2 or later.
|
||||
|
||||
options:
|
||||
_terms:
|
||||
description: Identifier(s) (case-insensitive UUID or name) of item(s) to retrieve.
|
||||
required: true
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.onepassword
|
||||
- community.general.onepassword.lookup
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Retrieve a private key from 1Password
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.onepassword_doc', 'Private key')
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
_raw:
|
||||
description: Requested document
|
||||
type: list
|
||||
elements: string
|
||||
"""
|
||||
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword import OnePass, OnePassCLIv2
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
class OnePassCLIv2Doc(OnePassCLIv2):
|
||||
def get_raw(self, item_id, vault=None, token=None):
|
||||
args = ["document", "get", item_id]
|
||||
if vault is not None:
|
||||
args = [*args, "--vault={0}".format(vault)]
|
||||
|
||||
if self.service_account_token:
|
||||
if vault is None:
|
||||
raise AnsibleLookupError("'vault' is required with 'service_account_token'")
|
||||
|
||||
environment_update = {"OP_SERVICE_ACCOUNT_TOKEN": self.service_account_token}
|
||||
return self._run(args, environment_update=environment_update)
|
||||
|
||||
if token is not None:
|
||||
args = [*args, to_bytes("--session=") + token]
|
||||
|
||||
return self._run(args)
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
|
||||
vault = self.get_option("vault")
|
||||
subdomain = self.get_option("subdomain")
|
||||
domain = self.get_option("domain", "1password.com")
|
||||
username = self.get_option("username")
|
||||
secret_key = self.get_option("secret_key")
|
||||
master_password = self.get_option("master_password")
|
||||
service_account_token = self.get_option("service_account_token")
|
||||
account_id = self.get_option("account_id")
|
||||
connect_host = self.get_option("connect_host")
|
||||
connect_token = self.get_option("connect_token")
|
||||
|
||||
op = OnePass(
|
||||
subdomain=subdomain,
|
||||
domain=domain,
|
||||
username=username,
|
||||
secret_key=secret_key,
|
||||
master_password=master_password,
|
||||
service_account_token=service_account_token,
|
||||
account_id=account_id,
|
||||
connect_host=connect_host,
|
||||
connect_token=connect_token,
|
||||
cli_class=OnePassCLIv2Doc,
|
||||
)
|
||||
op.assert_logged_in()
|
||||
|
||||
values = []
|
||||
for term in terms:
|
||||
values.append(op.get_raw(term, vault))
|
||||
|
||||
return values
|
||||
@@ -15,23 +15,55 @@ DOCUMENTATION = '''
|
||||
- Andrew Zenk (@azenk)
|
||||
- Sam Doran (@samdoran)
|
||||
requirements:
|
||||
- C(op) 1Password command line utility
|
||||
short_description: Fetch an entire item from 1Password
|
||||
- C(op) 1Password command line utility. See U(https://support.1password.com/command-line/)
|
||||
short_description: fetch an entire item from 1Password
|
||||
description:
|
||||
- P(community.general.onepassword_raw#lookup) wraps C(op) command line utility to fetch an entire item from 1Password.
|
||||
options:
|
||||
_terms:
|
||||
description: Identifier(s) (case-insensitive UUID or name) of item(s) to retrieve.
|
||||
description: identifier(s) (UUID, name, or domain; case-insensitive) of item(s) to retrieve.
|
||||
required: true
|
||||
account_id:
|
||||
version_added: 7.5.0
|
||||
master_password:
|
||||
description: The password used to unlock the specified vault.
|
||||
aliases: ['vault_password']
|
||||
section:
|
||||
description: Item section containing the field to retrieve (case-insensitive). If absent will return first match from any section.
|
||||
subdomain:
|
||||
description: The 1Password subdomain to authenticate against.
|
||||
domain:
|
||||
description: Domain of 1Password.
|
||||
version_added: 6.0.0
|
||||
default: '1password.com'
|
||||
type: str
|
||||
account_id:
|
||||
description: The account ID to target.
|
||||
type: str
|
||||
version_added: 7.5.0
|
||||
username:
|
||||
description: The username used to sign in.
|
||||
secret_key:
|
||||
description: The secret key used when performing an initial sign in.
|
||||
service_account_token:
|
||||
description:
|
||||
- The access key for a service account.
|
||||
- Only works with 1Password CLI version 2 or later.
|
||||
type: string
|
||||
version_added: 7.1.0
|
||||
extends_documentation_fragment:
|
||||
- community.general.onepassword
|
||||
- community.general.onepassword.lookup
|
||||
vault:
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
|
||||
notes:
|
||||
- This lookup will use an existing 1Password session if one exists. If not, and you have already
|
||||
performed an initial sign in (meaning C(~/.op/config exists)), then only the O(master_password) is required.
|
||||
You may optionally specify O(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
|
||||
- This lookup can perform an initial login by providing O(subdomain), O(username), O(secret_key), and O(master_password).
|
||||
- Can target a specific account by providing the O(account_id).
|
||||
- Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
|
||||
needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
|
||||
to the 1Password master password.
|
||||
- This lookup stores potentially sensitive data from 1Password as Ansible facts.
|
||||
Facts are subject to caching if enabled, which means this data could be stored in clear text
|
||||
on disk or in a database.
|
||||
- Tested with C(op) version 2.7.0
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
@@ -46,7 +78,7 @@ EXAMPLES = """
|
||||
|
||||
RETURN = """
|
||||
_raw:
|
||||
description: Entire item requested.
|
||||
description: field data requested
|
||||
type: list
|
||||
elements: dict
|
||||
"""
|
||||
@@ -70,20 +102,8 @@ class LookupModule(LookupBase):
|
||||
master_password = self.get_option("master_password")
|
||||
service_account_token = self.get_option("service_account_token")
|
||||
account_id = self.get_option("account_id")
|
||||
connect_host = self.get_option("connect_host")
|
||||
connect_token = self.get_option("connect_token")
|
||||
|
||||
op = OnePass(
|
||||
subdomain=subdomain,
|
||||
domain=domain,
|
||||
username=username,
|
||||
secret_key=secret_key,
|
||||
master_password=master_password,
|
||||
service_account_token=service_account_token,
|
||||
account_id=account_id,
|
||||
connect_host=connect_host,
|
||||
connect_token=connect_token,
|
||||
)
|
||||
op = OnePass(subdomain, domain, username, secret_key, master_password, service_account_token, account_id)
|
||||
op.assert_logged_in()
|
||||
|
||||
values = []
|
||||
|
||||
@@ -129,16 +129,6 @@ DOCUMENTATION = '''
|
||||
- pass
|
||||
- gopass
|
||||
version_added: 5.2.0
|
||||
timestamp:
|
||||
description: Add the password generation information to the end of the file.
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 8.1.0
|
||||
preserve:
|
||||
description: Include the old (edited) password inside the pass file.
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 8.1.0
|
||||
notes:
|
||||
- The lookup supports passing all options as lookup parameters since community.general 6.0.0.
|
||||
'''
|
||||
@@ -396,13 +386,11 @@ class LookupModule(LookupBase):
|
||||
# generate new password, insert old lines from current result and return new password
|
||||
newpass = self.get_newpass()
|
||||
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
|
||||
msg = newpass
|
||||
if self.paramvals['preserve'] or self.paramvals['timestamp']:
|
||||
msg += '\n'
|
||||
if self.paramvals['preserve'] and self.passoutput[1:]:
|
||||
msg += '\n'.join(self.passoutput[1:]) + '\n'
|
||||
if self.paramvals['timestamp'] and self.paramvals['backup']:
|
||||
msg += "lookup_pass: old password was {0} (Updated on {1})\n".format(self.password, datetime)
|
||||
msg = newpass + '\n'
|
||||
if self.passoutput[1:]:
|
||||
msg += '\n'.join(self.passoutput[1:]) + '\n'
|
||||
if self.paramvals['backup']:
|
||||
msg += "lookup_pass: old password was {0} (Updated on {1})\n".format(self.password, datetime)
|
||||
try:
|
||||
check_output2([self.pass_cmd, 'insert', '-f', '-m', self.passname], input=msg, env=self.env)
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
@@ -414,9 +402,7 @@ class LookupModule(LookupBase):
|
||||
# use pwgen to generate the password and insert values with pass -m
|
||||
newpass = self.get_newpass()
|
||||
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
|
||||
msg = newpass
|
||||
if self.paramvals['timestamp']:
|
||||
msg += '\n' + "lookup_pass: First generated by ansible on {0}\n".format(datetime)
|
||||
msg = newpass + '\n' + "lookup_pass: First generated by ansible on {0}\n".format(datetime)
|
||||
try:
|
||||
check_output2([self.pass_cmd, 'insert', '-f', '-m', self.passname], input=msg, env=self.env)
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
@@ -479,8 +465,6 @@ class LookupModule(LookupBase):
|
||||
'backup': self.get_option('backup'),
|
||||
'missing': self.get_option('missing'),
|
||||
'umask': self.get_option('umask'),
|
||||
'timestamp': self.get_option('timestamp'),
|
||||
'preserve': self.get_option('preserve'),
|
||||
}
|
||||
|
||||
def run(self, terms, variables, **kwargs):
|
||||
|
||||
@@ -21,20 +21,34 @@ except ImportError:
|
||||
|
||||
import traceback
|
||||
|
||||
|
||||
def _determine_list_all_kwargs(version):
|
||||
gitlab_version = LooseVersion(version)
|
||||
if gitlab_version >= LooseVersion('4.0.0'):
|
||||
# 4.0.0 removed 'as_list'
|
||||
return {'iterator': True, 'per_page': 100}
|
||||
elif gitlab_version >= LooseVersion('3.7.0'):
|
||||
# 3.7.0 added 'get_all'
|
||||
return {'as_list': False, 'get_all': True, 'per_page': 100}
|
||||
else:
|
||||
return {'as_list': False, 'all': True, 'per_page': 100}
|
||||
|
||||
|
||||
GITLAB_IMP_ERR = None
|
||||
try:
|
||||
import gitlab
|
||||
import requests
|
||||
HAS_GITLAB_PACKAGE = True
|
||||
list_all_kwargs = _determine_list_all_kwargs(gitlab.__version__)
|
||||
except Exception:
|
||||
gitlab = None
|
||||
GITLAB_IMP_ERR = traceback.format_exc()
|
||||
HAS_GITLAB_PACKAGE = False
|
||||
list_all_kwargs = {}
|
||||
|
||||
|
||||
def auth_argument_spec(spec=None):
|
||||
arg_spec = (dict(
|
||||
ca_path=dict(type='str'),
|
||||
api_token=dict(type='str', no_log=True),
|
||||
api_oauth_token=dict(type='str', no_log=True),
|
||||
api_job_token=dict(type='str', no_log=True),
|
||||
@@ -75,36 +89,33 @@ def ensure_gitlab_package(module):
|
||||
|
||||
|
||||
def gitlab_authentication(module):
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
gitlab_url = module.params['api_url']
|
||||
validate_certs = module.params['validate_certs']
|
||||
ca_path = module.params['ca_path']
|
||||
gitlab_user = module.params['api_username']
|
||||
gitlab_password = module.params['api_password']
|
||||
gitlab_token = module.params['api_token']
|
||||
gitlab_oauth_token = module.params['api_oauth_token']
|
||||
gitlab_job_token = module.params['api_job_token']
|
||||
|
||||
verify = ca_path if validate_certs and ca_path else validate_certs
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
try:
|
||||
# python-gitlab library remove support for username/password authentication since 1.13.0
|
||||
# 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"):
|
||||
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=verify, email=gitlab_user, password=gitlab_password,
|
||||
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=validate_certs, email=gitlab_user, password=gitlab_password,
|
||||
private_token=gitlab_token, api_version=4)
|
||||
else:
|
||||
# We can create an oauth_token using a username and password
|
||||
# https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow
|
||||
if gitlab_user:
|
||||
data = {'grant_type': 'password', 'username': gitlab_user, 'password': gitlab_password}
|
||||
resp = requests.post(urljoin(gitlab_url, "oauth/token"), data=data, verify=verify)
|
||||
resp = requests.post(urljoin(gitlab_url, "oauth/token"), data=data, verify=validate_certs)
|
||||
resp_data = resp.json()
|
||||
gitlab_oauth_token = resp_data["access_token"]
|
||||
|
||||
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=verify, private_token=gitlab_token,
|
||||
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=validate_certs, private_token=gitlab_token,
|
||||
oauth_token=gitlab_oauth_token, job_token=gitlab_job_token, api_version=4)
|
||||
|
||||
gitlab_instance.auth()
|
||||
|
||||
@@ -78,8 +78,6 @@ URL_CLIENT_USER_ROLEMAPPINGS = "{url}/admin/realms/{realm}/users/{id}/role-mappi
|
||||
URL_CLIENT_USER_ROLEMAPPINGS_AVAILABLE = "{url}/admin/realms/{realm}/users/{id}/role-mappings/clients/{client}/available"
|
||||
URL_CLIENT_USER_ROLEMAPPINGS_COMPOSITE = "{url}/admin/realms/{realm}/users/{id}/role-mappings/clients/{client}/composite"
|
||||
|
||||
URL_REALM_GROUP_ROLEMAPPINGS = "{url}/admin/realms/{realm}/groups/{group}/role-mappings/realm"
|
||||
|
||||
URL_CLIENTSECRET = "{url}/admin/realms/{realm}/clients/{id}/client-secret"
|
||||
|
||||
URL_AUTHENTICATION_FLOWS = "{url}/admin/realms/{realm}/authentication/flows"
|
||||
@@ -294,8 +292,8 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
except ValueError as e:
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
@@ -319,8 +317,8 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
except ValueError as e:
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
@@ -340,8 +338,8 @@ class KeycloakAPI(object):
|
||||
return open_url(realm_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(realmrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
self.module.fail_json(msg='Could not update realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
def create_realm(self, realmrep):
|
||||
""" Create a realm in keycloak
|
||||
@@ -354,8 +352,8 @@ class KeycloakAPI(object):
|
||||
return open_url(realm_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(realmrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create realm %s: %s' % (realmrep['id'], str(e)),
|
||||
exception=traceback.format_exc())
|
||||
self.module.fail_json(msg='Could not create realm %s: %s' % (realmrep['id'], str(e)),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
def delete_realm(self, realm="master"):
|
||||
""" Delete a realm from Keycloak
|
||||
@@ -369,8 +367,8 @@ class KeycloakAPI(object):
|
||||
return open_url(realm_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
self.module.fail_json(msg='Could not delete realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
def get_clients(self, realm='master', filter=None):
|
||||
""" Obtains client representations for clients in a realm
|
||||
@@ -391,7 +389,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of clients for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain list of clients for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain list of clients for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_client_by_clientid(self, client_id, realm='master'):
|
||||
@@ -424,7 +422,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not obtain client %s for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain client %s for realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
except ValueError as e:
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain client %s for realm %s: %s'
|
||||
@@ -459,7 +457,7 @@ class KeycloakAPI(object):
|
||||
return open_url(client_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(clientrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update client %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update client %s in realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
|
||||
def create_client(self, clientrep, realm="master"):
|
||||
@@ -474,7 +472,7 @@ class KeycloakAPI(object):
|
||||
return open_url(client_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(clientrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create client %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create client %s in realm %s: %s'
|
||||
% (clientrep['clientId'], realm, str(e)))
|
||||
|
||||
def delete_client(self, id, realm="master"):
|
||||
@@ -490,7 +488,7 @@ class KeycloakAPI(object):
|
||||
return open_url(client_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete client %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not delete client %s in realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
|
||||
def get_client_roles_by_id(self, cid, realm="master"):
|
||||
@@ -506,7 +504,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch rolemappings for client %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch rolemappings for client %s in realm %s: %s"
|
||||
% (cid, realm, str(e)))
|
||||
|
||||
def get_client_role_id_by_name(self, cid, name, realm="master"):
|
||||
@@ -541,7 +539,7 @@ class KeycloakAPI(object):
|
||||
if rid == role['id']:
|
||||
return role
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch rolemappings for client %s in group %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch rolemappings for client %s in group %s, realm %s: %s"
|
||||
% (cid, gid, realm, str(e)))
|
||||
return None
|
||||
|
||||
@@ -559,7 +557,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s"
|
||||
% (cid, gid, realm, str(e)))
|
||||
|
||||
def get_client_group_composite_rolemappings(self, gid, cid, realm="master"):
|
||||
@@ -576,7 +574,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s"
|
||||
% (cid, gid, realm, str(e)))
|
||||
|
||||
def get_role_by_id(self, rid, realm="master"):
|
||||
@@ -592,7 +590,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch role for id %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch role for id %s in realm %s: %s"
|
||||
% (rid, realm, str(e)))
|
||||
|
||||
def get_client_roles_by_id_composite_rolemappings(self, rid, cid, realm="master"):
|
||||
@@ -609,7 +607,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch role for id %s and cid %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch role for id %s and cid %s in realm %s: %s"
|
||||
% (rid, cid, realm, str(e)))
|
||||
|
||||
def add_client_roles_by_id_composite_rolemapping(self, rid, roles_rep, realm="master"):
|
||||
@@ -625,41 +623,9 @@ class KeycloakAPI(object):
|
||||
open_url(available_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(roles_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not assign roles to composite role %s and realm %s: %s"
|
||||
self.module.fail_json(msg="Could not assign roles to composite role %s and realm %s: %s"
|
||||
% (rid, realm, str(e)))
|
||||
|
||||
def add_group_realm_rolemapping(self, gid, role_rep, realm="master"):
|
||||
""" Add the specified realm role to specified group on the Keycloak server.
|
||||
|
||||
:param gid: ID of the group to add the role mapping.
|
||||
:param role_rep: Representation of the role to assign.
|
||||
:param realm: Realm from which to obtain the rolemappings.
|
||||
:return: None.
|
||||
"""
|
||||
url = URL_REALM_GROUP_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, group=gid)
|
||||
try:
|
||||
open_url(url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could add realm role mappings for group %s, realm %s: %s"
|
||||
% (gid, realm, str(e)))
|
||||
|
||||
def delete_group_realm_rolemapping(self, gid, role_rep, realm="master"):
|
||||
""" Delete the specified realm role from the specified group on the Keycloak server.
|
||||
|
||||
:param gid: ID of the group from which to obtain the rolemappings.
|
||||
:param role_rep: Representation of the role to assign.
|
||||
:param realm: Realm from which to obtain the rolemappings.
|
||||
:return: None.
|
||||
"""
|
||||
url = URL_REALM_GROUP_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, group=gid)
|
||||
try:
|
||||
open_url(url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not delete realm role mappings for group %s, realm %s: %s"
|
||||
% (gid, realm, str(e)))
|
||||
|
||||
def add_group_rolemapping(self, gid, cid, role_rep, realm="master"):
|
||||
""" Fetch the composite role of a client in a specified group on the Keycloak server.
|
||||
|
||||
@@ -674,7 +640,7 @@ class KeycloakAPI(object):
|
||||
open_url(available_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s"
|
||||
% (cid, gid, realm, str(e)))
|
||||
|
||||
def delete_group_rolemapping(self, gid, cid, role_rep, realm="master"):
|
||||
@@ -691,7 +657,7 @@ class KeycloakAPI(object):
|
||||
open_url(available_rolemappings_url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not delete available rolemappings for client %s in group %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not delete available rolemappings for client %s in group %s, realm %s: %s"
|
||||
% (cid, gid, realm, str(e)))
|
||||
|
||||
def get_client_user_rolemapping_by_id(self, uid, cid, rid, realm='master'):
|
||||
@@ -712,7 +678,7 @@ class KeycloakAPI(object):
|
||||
if rid == role['id']:
|
||||
return role
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch rolemappings for client %s and user %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch rolemappings for client %s and user %s, realm %s: %s"
|
||||
% (cid, uid, realm, str(e)))
|
||||
return None
|
||||
|
||||
@@ -730,7 +696,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch effective rolemappings for client %s and user %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch effective rolemappings for client %s and user %s, realm %s: %s"
|
||||
% (cid, uid, realm, str(e)))
|
||||
|
||||
def get_client_user_composite_rolemappings(self, uid, cid, realm="master"):
|
||||
@@ -747,7 +713,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch available rolemappings for user %s of realm %s: %s"
|
||||
% (uid, realm, str(e)))
|
||||
|
||||
def get_realm_user_rolemapping_by_id(self, uid, rid, realm='master'):
|
||||
@@ -767,7 +733,7 @@ class KeycloakAPI(object):
|
||||
if rid == role['id']:
|
||||
return role
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch rolemappings for user %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch rolemappings for user %s, realm %s: %s"
|
||||
% (uid, realm, str(e)))
|
||||
return None
|
||||
|
||||
@@ -784,7 +750,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch available rolemappings for user %s of realm %s: %s"
|
||||
% (uid, realm, str(e)))
|
||||
|
||||
def get_realm_user_composite_rolemappings(self, uid, realm="master"):
|
||||
@@ -800,7 +766,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch effective rolemappings for user %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch effective rolemappings for user %s, realm %s: %s"
|
||||
% (uid, realm, str(e)))
|
||||
|
||||
def get_user_by_username(self, username, realm="master"):
|
||||
@@ -827,7 +793,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain the user for realm %s and username %s: %s'
|
||||
% (realm, username, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain the user for realm %s and username %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain the user for realm %s and username %s: %s'
|
||||
% (realm, username, str(e)))
|
||||
|
||||
def get_service_account_user_by_client_id(self, client_id, realm="master"):
|
||||
@@ -848,7 +814,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain the service-account-user for realm %s and client_id %s: %s'
|
||||
% (realm, client_id, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain the service-account-user for realm %s and client_id %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain the service-account-user for realm %s and client_id %s: %s'
|
||||
% (realm, client_id, str(e)))
|
||||
|
||||
def add_user_rolemapping(self, uid, cid, role_rep, realm="master"):
|
||||
@@ -866,7 +832,7 @@ class KeycloakAPI(object):
|
||||
open_url(user_realm_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not map roles to userId %s for realm %s and roles %s: %s"
|
||||
self.module.fail_json(msg="Could not map roles to userId %s for realm %s and roles %s: %s"
|
||||
% (uid, realm, json.dumps(role_rep), str(e)))
|
||||
else:
|
||||
user_client_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid, client=cid)
|
||||
@@ -874,7 +840,7 @@ class KeycloakAPI(object):
|
||||
open_url(user_client_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not map roles to userId %s for client %s, realm %s and roles %s: %s"
|
||||
self.module.fail_json(msg="Could not map roles to userId %s for client %s, realm %s and roles %s: %s"
|
||||
% (cid, uid, realm, json.dumps(role_rep), str(e)))
|
||||
|
||||
def delete_user_rolemapping(self, uid, cid, role_rep, realm="master"):
|
||||
@@ -892,7 +858,7 @@ class KeycloakAPI(object):
|
||||
open_url(user_realm_rolemappings_url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not remove roles %s from userId %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not remove roles %s from userId %s, realm %s: %s"
|
||||
% (json.dumps(role_rep), uid, realm, str(e)))
|
||||
else:
|
||||
user_client_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid, client=cid)
|
||||
@@ -900,7 +866,7 @@ class KeycloakAPI(object):
|
||||
open_url(user_client_rolemappings_url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep),
|
||||
validate_certs=self.validate_certs, timeout=self.connection_timeout)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not remove roles %s for client %s from userId %s, realm %s: %s"
|
||||
self.module.fail_json(msg="Could not remove roles %s for client %s from userId %s, realm %s: %s"
|
||||
% (json.dumps(role_rep), cid, uid, realm, str(e)))
|
||||
|
||||
def get_client_templates(self, realm='master'):
|
||||
@@ -918,7 +884,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of client templates for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain list of client templates for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain list of client templates for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_client_template_by_id(self, id, realm='master'):
|
||||
@@ -937,7 +903,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain client templates %s for realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain client template %s for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain client template %s for realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
|
||||
def get_client_template_by_name(self, name, realm='master'):
|
||||
@@ -980,7 +946,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(clienttrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update client template %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update client template %s in realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
|
||||
def create_client_template(self, clienttrep, realm="master"):
|
||||
@@ -995,7 +961,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(clienttrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create client template %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create client template %s in realm %s: %s'
|
||||
% (clienttrep['clientId'], realm, str(e)))
|
||||
|
||||
def delete_client_template(self, id, realm="master"):
|
||||
@@ -1011,7 +977,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete client template %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not delete client template %s in realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
|
||||
def get_clientscopes(self, realm="master"):
|
||||
@@ -1029,7 +995,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch list of clientscopes in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch list of clientscopes in realm %s: %s"
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_clientscope_by_clientscopeid(self, cid, realm="master"):
|
||||
@@ -1051,7 +1017,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg="Could not fetch clientscope %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch clientscope %s in realm %s: %s"
|
||||
% (cid, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Could not clientscope group %s in realm %s: %s"
|
||||
@@ -1092,7 +1058,7 @@ class KeycloakAPI(object):
|
||||
return open_url(clientscopes_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(clientscoperep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not create clientscope %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not create clientscope %s in realm %s: %s"
|
||||
% (clientscoperep['name'], realm, str(e)))
|
||||
|
||||
def update_clientscope(self, clientscoperep, realm="master"):
|
||||
@@ -1108,7 +1074,7 @@ class KeycloakAPI(object):
|
||||
data=json.dumps(clientscoperep), validate_certs=self.validate_certs)
|
||||
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update clientscope %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update clientscope %s in realm %s: %s'
|
||||
% (clientscoperep['name'], realm, str(e)))
|
||||
|
||||
def delete_clientscope(self, name=None, cid=None, realm="master"):
|
||||
@@ -1146,7 +1112,7 @@ class KeycloakAPI(object):
|
||||
validate_certs=self.validate_certs)
|
||||
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Unable to delete clientscope %s: %s" % (cid, str(e)))
|
||||
self.module.fail_json(msg="Unable to delete clientscope %s: %s" % (cid, str(e)))
|
||||
|
||||
def get_clientscope_protocolmappers(self, cid, realm="master"):
|
||||
""" Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||
@@ -1164,7 +1130,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch list of protocolmappers in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch list of protocolmappers in realm %s: %s"
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm="master"):
|
||||
@@ -1188,7 +1154,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg="Could not fetch protocolmapper %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s"
|
||||
% (pid, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s"
|
||||
@@ -1231,7 +1197,7 @@ class KeycloakAPI(object):
|
||||
return open_url(protocolmappers_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(mapper_rep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not create protocolmapper %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not create protocolmapper %s in realm %s: %s"
|
||||
% (mapper_rep['name'], realm, str(e)))
|
||||
|
||||
def update_clientscope_protocolmappers(self, cid, mapper_rep, realm="master"):
|
||||
@@ -1248,7 +1214,7 @@ class KeycloakAPI(object):
|
||||
data=json.dumps(mapper_rep), validate_certs=self.validate_certs)
|
||||
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update protocolmappers for clientscope %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update protocolmappers for clientscope %s in realm %s: %s'
|
||||
% (mapper_rep, realm, str(e)))
|
||||
|
||||
def get_default_clientscopes(self, realm, client_id=None):
|
||||
@@ -1295,7 +1261,7 @@ class KeycloakAPI(object):
|
||||
return json.loads(to_native(open_url(clientscopes_url, method="GET", http_agent=self.http_agent, headers=self.restheaders,
|
||||
timeout=self.connection_timeout, validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch list of %s clientscopes in realm %s: %s" % (scope_type, realm, str(e)))
|
||||
self.module.fail_json(msg="Could not fetch list of %s clientscopes in realm %s: %s" % (scope_type, realm, str(e)))
|
||||
else:
|
||||
cid = self.get_client_id(client_id=client_id, realm=realm)
|
||||
clientscopes_url = url_template.format(url=self.baseurl, realm=realm, cid=cid)
|
||||
@@ -1303,7 +1269,7 @@ class KeycloakAPI(object):
|
||||
return json.loads(to_native(open_url(clientscopes_url, method="GET", http_agent=self.http_agent, headers=self.restheaders,
|
||||
timeout=self.connection_timeout, validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch list of %s clientscopes in client %s: %s" % (scope_type, client_id, clientscopes_url))
|
||||
self.module.fail_json(msg="Could not fetch list of %s clientscopes in client %s: %s" % (scope_type, client_id, clientscopes_url))
|
||||
|
||||
def _decide_url_type_clientscope(self, client_id=None, scope_type="default"):
|
||||
"""Decides which url to use.
|
||||
@@ -1374,7 +1340,7 @@ class KeycloakAPI(object):
|
||||
|
||||
except Exception as e:
|
||||
place = 'realm' if client_id is None else 'client ' + client_id
|
||||
self.fail_open_url(e, msg="Unable to %s %s clientscope %s @ %s : %s" % (action, scope_type, id, place, str(e)))
|
||||
self.module.fail_json(msg="Unable to %s %s clientscope %s @ %s : %s" % (action, scope_type, id, place, str(e)))
|
||||
|
||||
def create_clientsecret(self, id, realm="master"):
|
||||
""" Generate a new client secret by id
|
||||
@@ -1394,7 +1360,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not obtain clientsecret of client %s for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain clientsecret of client %s for realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not obtain clientsecret of client %s for realm %s: %s'
|
||||
@@ -1418,7 +1384,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not obtain clientsecret of client %s for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain clientsecret of client %s for realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not obtain clientsecret of client %s for realm %s: %s'
|
||||
@@ -1438,7 +1404,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not fetch list of groups in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch list of groups in realm %s: %s"
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_group_by_groupid(self, gid, realm="master"):
|
||||
@@ -1459,7 +1425,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg="Could not fetch group %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not fetch group %s in realm %s: %s"
|
||||
% (gid, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Could not fetch group %s in realm %s: %s"
|
||||
@@ -1606,7 +1572,7 @@ class KeycloakAPI(object):
|
||||
return open_url(groups_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(grouprep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not create group %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not create group %s in realm %s: %s"
|
||||
% (grouprep['name'], realm, str(e)))
|
||||
|
||||
def create_subgroup(self, parents, grouprep, realm="master"):
|
||||
@@ -1634,7 +1600,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(grouprep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Could not create subgroup %s for parent group %s in realm %s: %s"
|
||||
self.module.fail_json(msg="Could not create subgroup %s for parent group %s in realm %s: %s"
|
||||
% (grouprep['name'], parent_id, realm, str(e)))
|
||||
|
||||
def update_group(self, grouprep, realm="master"):
|
||||
@@ -1649,7 +1615,7 @@ class KeycloakAPI(object):
|
||||
return open_url(group_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(grouprep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update group %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update group %s in realm %s: %s'
|
||||
% (grouprep['name'], realm, str(e)))
|
||||
|
||||
def delete_group(self, name=None, groupid=None, realm="master"):
|
||||
@@ -1686,7 +1652,7 @@ class KeycloakAPI(object):
|
||||
return open_url(group_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Unable to delete group %s: %s" % (groupid, str(e)))
|
||||
self.module.fail_json(msg="Unable to delete group %s: %s" % (groupid, str(e)))
|
||||
|
||||
def get_realm_roles(self, realm='master'):
|
||||
""" Obtains role representations for roles in a realm
|
||||
@@ -1703,7 +1669,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain list of roles for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain list of roles for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_realm_role(self, name, realm='master'):
|
||||
@@ -1721,7 +1687,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not fetch role %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not fetch role %s in realm %s: %s'
|
||||
% (name, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not fetch role %s in realm %s: %s'
|
||||
@@ -1741,7 +1707,7 @@ class KeycloakAPI(object):
|
||||
return open_url(roles_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(rolerep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create role %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create role %s in realm %s: %s'
|
||||
% (rolerep['name'], realm, str(e)))
|
||||
|
||||
def update_realm_role(self, rolerep, realm='master'):
|
||||
@@ -1762,7 +1728,7 @@ class KeycloakAPI(object):
|
||||
self.update_role_composites(rolerep=rolerep, composites=composites, realm=realm)
|
||||
return role_response
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update role %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update role %s in realm %s: %s'
|
||||
% (rolerep['name'], realm, str(e)))
|
||||
|
||||
def get_role_composites(self, rolerep, clientid=None, realm='master'):
|
||||
@@ -1783,7 +1749,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not get role %s composites in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not get role %s composites in realm %s: %s'
|
||||
% (rolerep['name'], realm, str(e)))
|
||||
|
||||
def create_role_composites(self, rolerep, composites, clientid=None, realm='master'):
|
||||
@@ -1800,7 +1766,7 @@ class KeycloakAPI(object):
|
||||
return open_url(composite_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(composites), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create role %s composites in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create role %s composites in realm %s: %s'
|
||||
% (rolerep['name'], realm, str(e)))
|
||||
|
||||
def delete_role_composites(self, rolerep, composites, clientid=None, realm='master'):
|
||||
@@ -1817,7 +1783,7 @@ class KeycloakAPI(object):
|
||||
return open_url(composite_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(composites), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create role %s composites in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create role %s composites in realm %s: %s'
|
||||
% (rolerep['name'], realm, str(e)))
|
||||
|
||||
def update_role_composites(self, rolerep, composites, clientid=None, realm='master'):
|
||||
@@ -1881,7 +1847,7 @@ class KeycloakAPI(object):
|
||||
return open_url(role_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Unable to delete role %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Unable to delete role %s in realm %s: %s'
|
||||
% (name, realm, str(e)))
|
||||
|
||||
def get_client_roles(self, clientid, realm='master'):
|
||||
@@ -1904,7 +1870,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for client %s in realm %s: %s'
|
||||
% (clientid, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain list of roles for client %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain list of roles for client %s in realm %s: %s'
|
||||
% (clientid, realm, str(e)))
|
||||
|
||||
def get_client_role(self, name, clientid, realm='master'):
|
||||
@@ -1928,7 +1894,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not fetch role %s in client %s of realm %s: %s'
|
||||
self.module.fail_json(msg='Could not fetch role %s in client %s of realm %s: %s'
|
||||
% (name, clientid, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not fetch role %s for client %s in realm %s: %s'
|
||||
@@ -1954,7 +1920,7 @@ class KeycloakAPI(object):
|
||||
return open_url(roles_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(rolerep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create role %s for client %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create role %s for client %s in realm %s: %s'
|
||||
% (rolerep['name'], clientid, realm, str(e)))
|
||||
|
||||
def convert_role_composites(self, composites):
|
||||
@@ -1996,7 +1962,7 @@ class KeycloakAPI(object):
|
||||
self.update_role_composites(rolerep=rolerep, clientid=clientid, composites=composites, realm=realm)
|
||||
return update_role_response
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update role %s for client %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update role %s for client %s in realm %s: %s'
|
||||
% (rolerep['name'], clientid, realm, str(e)))
|
||||
|
||||
def delete_client_role(self, name, clientid, realm="master"):
|
||||
@@ -2015,7 +1981,7 @@ class KeycloakAPI(object):
|
||||
return open_url(role_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Unable to delete role %s for client %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Unable to delete role %s for client %s in realm %s: %s'
|
||||
% (name, clientid, realm, str(e)))
|
||||
|
||||
def get_authentication_flow_by_alias(self, alias, realm='master'):
|
||||
@@ -2037,7 +2003,7 @@ class KeycloakAPI(object):
|
||||
break
|
||||
return authentication_flow
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Unable get authentication flow %s: %s" % (alias, str(e)))
|
||||
self.module.fail_json(msg="Unable get authentication flow %s: %s" % (alias, str(e)))
|
||||
|
||||
def delete_authentication_flow_by_id(self, id, realm='master'):
|
||||
"""
|
||||
@@ -2052,8 +2018,8 @@ class KeycloakAPI(object):
|
||||
return open_url(flow_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete authentication flow %s in realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not delete authentication flow %s in realm %s: %s'
|
||||
% (id, realm, str(e)))
|
||||
|
||||
def copy_auth_flow(self, config, realm='master'):
|
||||
"""
|
||||
@@ -2089,8 +2055,8 @@ class KeycloakAPI(object):
|
||||
return flow
|
||||
return None
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not copy authentication flow %s in realm %s: %s'
|
||||
% (config["alias"], realm, str(e)))
|
||||
self.module.fail_json(msg='Could not copy authentication flow %s in realm %s: %s'
|
||||
% (config["alias"], realm, str(e)))
|
||||
|
||||
def create_empty_auth_flow(self, config, realm='master'):
|
||||
"""
|
||||
@@ -2129,8 +2095,8 @@ class KeycloakAPI(object):
|
||||
return flow
|
||||
return None
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create empty authentication flow %s in realm %s: %s'
|
||||
% (config["alias"], realm, str(e)))
|
||||
self.module.fail_json(msg='Could not create empty authentication flow %s in realm %s: %s'
|
||||
% (config["alias"], realm, str(e)))
|
||||
|
||||
def update_authentication_executions(self, flowAlias, updatedExec, realm='master'):
|
||||
""" Update authentication executions
|
||||
@@ -2151,8 +2117,8 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except HTTPError as e:
|
||||
self.fail_open_url(e, msg="Unable to update execution '%s': %s: %s %s"
|
||||
% (flowAlias, repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(updatedExec)))
|
||||
self.module.fail_json(msg="Unable to update execution '%s': %s: %s %s" %
|
||||
(flowAlias, repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(updatedExec)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Unable to update executions %s: %s" % (updatedExec, str(e)))
|
||||
|
||||
@@ -2175,7 +2141,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e)))
|
||||
self.module.fail_json(msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e)))
|
||||
|
||||
def create_subflow(self, subflowName, flowAlias, realm='master', flowType='basic-flow'):
|
||||
""" Create new sublow on the flow
|
||||
@@ -2200,7 +2166,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Unable to create new subflow %s: %s" % (subflowName, str(e)))
|
||||
self.module.fail_json(msg="Unable to create new subflow %s: %s" % (subflowName, str(e)))
|
||||
|
||||
def create_execution(self, execution, flowAlias, realm='master'):
|
||||
""" Create new execution on the flow
|
||||
@@ -2224,8 +2190,8 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except HTTPError as e:
|
||||
self.fail_open_url(e, msg="Unable to create new execution '%s' %s: %s: %s %s"
|
||||
% (flowAlias, execution["providerId"], repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(newExec)))
|
||||
self.module.fail_json(msg="Unable to create new execution '%s' %s: %s: %s %s" %
|
||||
(flowAlias, execution["providerId"], repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(newExec)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Unable to create new execution '%s' %s: %s" % (flowAlias, execution["providerId"], repr(e)))
|
||||
|
||||
@@ -2261,7 +2227,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg="Unable to change execution priority %s: %s" % (executionId, str(e)))
|
||||
self.module.fail_json(msg="Unable to change execution priority %s: %s" % (executionId, str(e)))
|
||||
|
||||
def get_executions_representation(self, config, realm='master'):
|
||||
"""
|
||||
@@ -2298,8 +2264,8 @@ class KeycloakAPI(object):
|
||||
execution["authenticationConfig"] = execConfig
|
||||
return executions
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not get executions for authentication flow %s in realm %s: %s'
|
||||
% (config["alias"], realm, str(e)))
|
||||
self.module.fail_json(msg='Could not get executions for authentication flow %s in realm %s: %s'
|
||||
% (config["alias"], realm, str(e)))
|
||||
|
||||
def get_required_actions(self, realm='master'):
|
||||
"""
|
||||
@@ -2352,8 +2318,7 @@ class KeycloakAPI(object):
|
||||
validate_certs=self.validate_certs
|
||||
)
|
||||
except Exception as e:
|
||||
self.fail_open_url(
|
||||
e,
|
||||
self.module.fail_json(
|
||||
msg='Unable to register required action %s in realm %s: %s'
|
||||
% (rep["name"], realm, str(e))
|
||||
)
|
||||
@@ -2381,8 +2346,7 @@ class KeycloakAPI(object):
|
||||
validate_certs=self.validate_certs
|
||||
)
|
||||
except Exception as e:
|
||||
self.fail_open_url(
|
||||
e,
|
||||
self.module.fail_json(
|
||||
msg='Unable to update required action %s in realm %s: %s'
|
||||
% (alias, realm, str(e))
|
||||
)
|
||||
@@ -2408,8 +2372,7 @@ class KeycloakAPI(object):
|
||||
validate_certs=self.validate_certs
|
||||
)
|
||||
except Exception as e:
|
||||
self.fail_open_url(
|
||||
e,
|
||||
self.module.fail_json(
|
||||
msg='Unable to delete required action %s in realm %s: %s'
|
||||
% (alias, realm, str(e))
|
||||
)
|
||||
@@ -2427,7 +2390,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity providers for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain list of identity providers for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain list of identity providers for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_identity_provider(self, alias, realm='master'):
|
||||
@@ -2444,7 +2407,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not fetch identity provider %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not fetch identity provider %s in realm %s: %s'
|
||||
% (alias, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not fetch identity provider %s in realm %s: %s'
|
||||
@@ -2461,7 +2424,7 @@ class KeycloakAPI(object):
|
||||
return open_url(idps_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(idprep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create identity provider %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create identity provider %s in realm %s: %s'
|
||||
% (idprep['alias'], realm, str(e)))
|
||||
|
||||
def update_identity_provider(self, idprep, realm='master'):
|
||||
@@ -2475,7 +2438,7 @@ class KeycloakAPI(object):
|
||||
return open_url(idp_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(idprep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update identity provider %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update identity provider %s in realm %s: %s'
|
||||
% (idprep['alias'], realm, str(e)))
|
||||
|
||||
def delete_identity_provider(self, alias, realm='master'):
|
||||
@@ -2488,7 +2451,7 @@ class KeycloakAPI(object):
|
||||
return open_url(idp_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Unable to delete identity provider %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Unable to delete identity provider %s in realm %s: %s'
|
||||
% (alias, realm, str(e)))
|
||||
|
||||
def get_identity_provider_mappers(self, alias, realm='master'):
|
||||
@@ -2506,7 +2469,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity provider mappers for idp %s in realm %s: %s'
|
||||
% (alias, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain list of identity provider mappers for idp %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain list of identity provider mappers for idp %s in realm %s: %s'
|
||||
% (alias, realm, str(e)))
|
||||
|
||||
def get_identity_provider_mapper(self, mid, alias, realm='master'):
|
||||
@@ -2525,7 +2488,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not fetch mapper %s for identity provider %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not fetch mapper %s for identity provider %s in realm %s: %s'
|
||||
% (mid, alias, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not fetch mapper %s for identity provider %s in realm %s: %s'
|
||||
@@ -2543,7 +2506,7 @@ class KeycloakAPI(object):
|
||||
return open_url(mappers_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(mapper), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create identity provider mapper %s for idp %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create identity provider mapper %s for idp %s in realm %s: %s'
|
||||
% (mapper['name'], alias, realm, str(e)))
|
||||
|
||||
def update_identity_provider_mapper(self, mapper, alias, realm='master'):
|
||||
@@ -2558,7 +2521,7 @@ class KeycloakAPI(object):
|
||||
return open_url(mapper_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(mapper), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update mapper %s for identity provider %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update mapper %s for identity provider %s in realm %s: %s'
|
||||
% (mapper['id'], alias, realm, str(e)))
|
||||
|
||||
def delete_identity_provider_mapper(self, mid, alias, realm='master'):
|
||||
@@ -2572,7 +2535,7 @@ class KeycloakAPI(object):
|
||||
return open_url(mapper_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Unable to delete mapper %s for identity provider %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Unable to delete mapper %s for identity provider %s in realm %s: %s'
|
||||
% (mid, alias, realm, str(e)))
|
||||
|
||||
def get_components(self, filter=None, realm='master'):
|
||||
@@ -2592,7 +2555,7 @@ class KeycloakAPI(object):
|
||||
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of components for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not obtain list of components for realm %s: %s'
|
||||
self.module.fail_json(msg='Could not obtain list of components for realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
|
||||
def get_component(self, cid, realm='master'):
|
||||
@@ -2609,7 +2572,7 @@ class KeycloakAPI(object):
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
self.fail_open_url(e, msg='Could not fetch component %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not fetch component %s in realm %s: %s'
|
||||
% (cid, realm, str(e)))
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not fetch component %s in realm %s: %s'
|
||||
@@ -2632,7 +2595,7 @@ class KeycloakAPI(object):
|
||||
return json.loads(to_native(open_url(comp_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create component in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create component in realm %s: %s'
|
||||
% (realm, str(e)))
|
||||
|
||||
def update_component(self, comprep, realm='master'):
|
||||
@@ -2649,7 +2612,7 @@ class KeycloakAPI(object):
|
||||
return open_url(comp_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(comprep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update component %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update component %s in realm %s: %s'
|
||||
% (cid, realm, str(e)))
|
||||
|
||||
def delete_component(self, cid, realm='master'):
|
||||
@@ -2662,7 +2625,7 @@ class KeycloakAPI(object):
|
||||
return open_url(comp_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Unable to delete component %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Unable to delete component %s in realm %s: %s'
|
||||
% (cid, realm, str(e)))
|
||||
|
||||
def get_authz_authorization_scope_by_name(self, name, client_id, realm):
|
||||
@@ -2684,7 +2647,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(payload), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create authorization scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not create authorization scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
|
||||
def update_authz_authorization_scope(self, payload, id, client_id, realm):
|
||||
"""Update an authorization scope for a Keycloak client"""
|
||||
@@ -2694,7 +2657,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(payload), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create update scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not create update scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
|
||||
def remove_authz_authorization_scope(self, id, client_id, realm):
|
||||
"""Remove an authorization scope from a Keycloak client"""
|
||||
@@ -2704,7 +2667,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete scope %s for client %s in realm %s: %s' % (id, client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not delete scope %s for client %s in realm %s: %s' % (id, client_id, realm, str(e)))
|
||||
|
||||
def get_user_by_id(self, user_id, realm='master'):
|
||||
"""
|
||||
@@ -2727,7 +2690,7 @@ class KeycloakAPI(object):
|
||||
validate_certs=self.validate_certs))
|
||||
return userrep
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not get user %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not get user %s in realm %s: %s'
|
||||
% (user_id, realm, str(e)))
|
||||
|
||||
def create_user(self, userrep, realm='master'):
|
||||
@@ -2755,7 +2718,7 @@ class KeycloakAPI(object):
|
||||
realm=realm)
|
||||
return created_user
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create user %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not create user %s in realm %s: %s'
|
||||
% (userrep['username'], realm, str(e)))
|
||||
|
||||
def convert_user_attributes_to_keycloak_dict(self, attributes):
|
||||
@@ -2801,7 +2764,7 @@ class KeycloakAPI(object):
|
||||
realm=realm)
|
||||
return updated_user
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not update user %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not update user %s in realm %s: %s'
|
||||
% (userrep['username'], realm, str(e)))
|
||||
|
||||
def delete_user(self, user_id, realm='master'):
|
||||
@@ -2823,7 +2786,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete user %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not delete user %s in realm %s: %s'
|
||||
% (user_id, realm, str(e)))
|
||||
|
||||
def get_user_groups(self, user_id, realm='master'):
|
||||
@@ -2850,7 +2813,7 @@ class KeycloakAPI(object):
|
||||
groups.append(user_group["name"])
|
||||
return groups
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not get groups for user %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not get groups for user %s in realm %s: %s'
|
||||
% (user_id, realm, str(e)))
|
||||
|
||||
def add_user_in_group(self, user_id, group_id, realm='master'):
|
||||
@@ -2874,7 +2837,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not add user %s in group %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not add user %s in group %s in realm %s: %s'
|
||||
% (user_id, group_id, realm, str(e)))
|
||||
|
||||
def remove_user_from_group(self, user_id, group_id, realm='master'):
|
||||
@@ -2898,7 +2861,7 @@ class KeycloakAPI(object):
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not remove user %s from group %s in realm %s: %s'
|
||||
self.module.fail_json(msg='Could not remove user %s from group %s in realm %s: %s'
|
||||
% (user_id, group_id, realm, str(e)))
|
||||
|
||||
def update_user_groups_membership(self, userrep, groups, realm='master'):
|
||||
@@ -2970,7 +2933,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(payload), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
|
||||
def remove_authz_custom_policy(self, policy_id, client_id, realm):
|
||||
"""Remove a custom policy from a Keycloak client"""
|
||||
@@ -2981,7 +2944,7 @@ class KeycloakAPI(object):
|
||||
return open_url(delete_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete custom policy %s for client %s in realm %s: %s' % (id, client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not delete custom policy %s for client %s in realm %s: %s' % (id, client_id, realm, str(e)))
|
||||
|
||||
def get_authz_permission_by_name(self, name, client_id, realm):
|
||||
"""Get authorization permission by name"""
|
||||
@@ -3003,7 +2966,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(payload), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
|
||||
def remove_authz_permission(self, id, client_id, realm):
|
||||
"""Create an authorization permission for a Keycloak client"""
|
||||
@@ -3013,7 +2976,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not delete permission %s for client %s in realm %s: %s' % (id, client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not delete permission %s for client %s in realm %s: %s' % (id, client_id, realm, str(e)))
|
||||
|
||||
def update_authz_permission(self, payload, permission_type, id, client_id, realm):
|
||||
"""Update a permission for a Keycloak client"""
|
||||
@@ -3023,7 +2986,7 @@ class KeycloakAPI(object):
|
||||
return open_url(url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(payload), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.fail_open_url(e, msg='Could not create update permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
self.module.fail_json(msg='Could not create update permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
|
||||
def get_authz_resource_by_name(self, name, client_id, realm):
|
||||
"""Get authorization resource by name"""
|
||||
@@ -3048,11 +3011,3 @@ class KeycloakAPI(object):
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def fail_open_url(self, e, msg, **kwargs):
|
||||
try:
|
||||
if isinstance(e, HTTPError):
|
||||
msg = "%s: %s" % (msg, to_native(e.read()))
|
||||
except Exception as ingore:
|
||||
pass
|
||||
self.module.fail_json(msg, **kwargs)
|
||||
|
||||
@@ -104,7 +104,7 @@ class IPAClient(object):
|
||||
|
||||
def get_ipa_version(self):
|
||||
response = self.ping()['summary']
|
||||
ipa_ver_regex = re.compile(r'IPA server version (\d\.\d\.\d).*')
|
||||
ipa_ver_regex = re.compile(r'IPA server version (\d+\.\d+\.\d+).*')
|
||||
version_match = ipa_ver_regex.match(response)
|
||||
ipa_version = None
|
||||
if version_match:
|
||||
|
||||
@@ -139,5 +139,7 @@ class LdapGeneric(object):
|
||||
|
||||
def _xorder_dn(self):
|
||||
# match X_ORDERed DNs
|
||||
regex = r"\w+=\{\d+\}.+"
|
||||
return re.match(regex, self.module.params['dn']) is not None
|
||||
regex = r".+\{\d+\}.+"
|
||||
explode_dn = ldap.dn.explode_dn(self.module.params['dn'])
|
||||
|
||||
return re.match(regex, explode_dn[0]) is not None
|
||||
|
||||
@@ -41,7 +41,7 @@ class LXDClientException(Exception):
|
||||
|
||||
|
||||
class LXDClient(object):
|
||||
def __init__(self, url, key_file=None, cert_file=None, debug=False, server_cert_file=None, server_check_hostname=True):
|
||||
def __init__(self, url, key_file=None, cert_file=None, debug=False):
|
||||
"""LXD Client.
|
||||
|
||||
:param url: The URL of the LXD server. (e.g. unix:/var/lib/lxd/unix.socket or https://127.0.0.1)
|
||||
@@ -52,10 +52,6 @@ class LXDClient(object):
|
||||
:type cert_file: ``str``
|
||||
:param debug: The debug flag. The request and response are stored in logs when debug is true.
|
||||
:type debug: ``bool``
|
||||
:param server_cert_file: The path of the server certificate file.
|
||||
:type server_cert_file: ``str``
|
||||
:param server_check_hostname: Whether to check the server's hostname as part of TLS verification.
|
||||
:type debug: ``bool``
|
||||
"""
|
||||
self.url = url
|
||||
self.debug = debug
|
||||
@@ -65,10 +61,6 @@ class LXDClient(object):
|
||||
self.key_file = key_file
|
||||
parts = generic_urlparse(urlparse(self.url))
|
||||
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
if server_cert_file:
|
||||
# Check that the received cert is signed by the provided server_cert_file
|
||||
ctx.load_verify_locations(cafile=server_cert_file)
|
||||
ctx.check_hostname = server_check_hostname
|
||||
ctx.load_cert_chain(cert_file, keyfile=key_file)
|
||||
self.connection = HTTPSConnection(parts.get('netloc'), context=ctx)
|
||||
elif url.startswith('unix:'):
|
||||
|
||||
205
plugins/module_utils/mh/mixins/cmd.py
Normal file
205
plugins/module_utils/mh/mixins/cmd.py
Normal file
@@ -0,0 +1,205 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright (c) 2020, 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
|
||||
|
||||
from functools import partial
|
||||
|
||||
|
||||
class ArgFormat(object):
|
||||
"""
|
||||
Argument formatter for use as a command line parameter. Used in CmdMixin.
|
||||
"""
|
||||
BOOLEAN = 0
|
||||
PRINTF = 1
|
||||
FORMAT = 2
|
||||
BOOLEAN_NOT = 3
|
||||
|
||||
@staticmethod
|
||||
def stars_deco(num):
|
||||
if num == 1:
|
||||
def deco(f):
|
||||
return lambda v: f(*v)
|
||||
return deco
|
||||
elif num == 2:
|
||||
def deco(f):
|
||||
return lambda v: f(**v)
|
||||
return deco
|
||||
|
||||
return lambda f: f
|
||||
|
||||
def __init__(self, name, fmt=None, style=FORMAT, stars=0):
|
||||
"""
|
||||
THIS CLASS IS BEING DEPRECATED.
|
||||
It was never meant to be used outside the scope of CmdMixin, and CmdMixin is being deprecated.
|
||||
See the deprecation notice in ``CmdMixin.__init__()`` below.
|
||||
|
||||
Creates a CLI-formatter for one specific argument. The argument may be a module parameter or just a named parameter for
|
||||
the CLI command execution.
|
||||
:param name: Name of the argument to be formatted
|
||||
:param fmt: Either a str to be formatted (using or not printf-style) or a callable that does that
|
||||
:param style: Whether arg_format (as str) should use printf-style formatting.
|
||||
Ignored if arg_format is None or not a str (should be callable).
|
||||
:param stars: A int with 0, 1 or 2 value, indicating to formatting the value as: value, *value or **value
|
||||
"""
|
||||
def printf_fmt(_fmt, v):
|
||||
try:
|
||||
return [_fmt % v]
|
||||
except TypeError as e:
|
||||
if e.args[0] != 'not all arguments converted during string formatting':
|
||||
raise
|
||||
return [_fmt]
|
||||
|
||||
_fmts = {
|
||||
ArgFormat.BOOLEAN: lambda _fmt, v: ([_fmt] if bool(v) else []),
|
||||
ArgFormat.BOOLEAN_NOT: lambda _fmt, v: ([] if bool(v) else [_fmt]),
|
||||
ArgFormat.PRINTF: printf_fmt,
|
||||
ArgFormat.FORMAT: lambda _fmt, v: [_fmt.format(v)],
|
||||
}
|
||||
|
||||
self.name = name
|
||||
self.stars = stars
|
||||
self.style = style
|
||||
|
||||
if fmt is None:
|
||||
fmt = "{0}"
|
||||
style = ArgFormat.FORMAT
|
||||
|
||||
if isinstance(fmt, str):
|
||||
func = _fmts[style]
|
||||
self.arg_format = partial(func, fmt)
|
||||
elif isinstance(fmt, list) or isinstance(fmt, tuple):
|
||||
self.arg_format = lambda v: [_fmts[style](f, v)[0] for f in fmt]
|
||||
elif hasattr(fmt, '__call__'):
|
||||
self.arg_format = fmt
|
||||
else:
|
||||
raise TypeError('Parameter fmt must be either: a string, a list/tuple of '
|
||||
'strings or a function: type={0}, value={1}'.format(type(fmt), fmt))
|
||||
|
||||
if stars:
|
||||
self.arg_format = (self.stars_deco(stars))(self.arg_format)
|
||||
|
||||
def to_text(self, value):
|
||||
if value is None and self.style != ArgFormat.BOOLEAN_NOT:
|
||||
return []
|
||||
func = self.arg_format
|
||||
return [str(p) for p in func(value)]
|
||||
|
||||
|
||||
class CmdMixin(object):
|
||||
"""
|
||||
THIS CLASS IS BEING DEPRECATED.
|
||||
See the deprecation notice in ``CmdMixin.__init__()`` below.
|
||||
|
||||
Mixin for mapping module options to running a CLI command with its arguments.
|
||||
"""
|
||||
command = None
|
||||
command_args_formats = {}
|
||||
run_command_fixed_options = {}
|
||||
check_rc = False
|
||||
force_lang = "C"
|
||||
|
||||
@property
|
||||
def module_formats(self):
|
||||
result = {}
|
||||
for param in self.module.params.keys():
|
||||
result[param] = ArgFormat(param)
|
||||
return result
|
||||
|
||||
@property
|
||||
def custom_formats(self):
|
||||
result = {}
|
||||
for param, fmt_spec in self.command_args_formats.items():
|
||||
result[param] = ArgFormat(param, **fmt_spec)
|
||||
return result
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CmdMixin, self).__init__(*args, **kwargs)
|
||||
self.module.deprecate(
|
||||
'The CmdMixin used in classes CmdModuleHelper and CmdStateModuleHelper is being deprecated. '
|
||||
'Modules should use community.general.plugins.module_utils.cmd_runner.CmdRunner instead.',
|
||||
version='8.0.0',
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
def _calculate_args(self, extra_params=None, params=None):
|
||||
def add_arg_formatted_param(_cmd_args, arg_format, _value):
|
||||
args = list(arg_format.to_text(_value))
|
||||
return _cmd_args + args
|
||||
|
||||
def find_format(_param):
|
||||
return self.custom_formats.get(_param, self.module_formats.get(_param))
|
||||
|
||||
extra_params = extra_params or dict()
|
||||
cmd_args = list([self.command]) if isinstance(self.command, str) else list(self.command)
|
||||
try:
|
||||
cmd_args[0] = self.module.get_bin_path(cmd_args[0], required=True)
|
||||
except ValueError:
|
||||
pass
|
||||
param_list = params if params else self.vars.keys()
|
||||
|
||||
for param in param_list:
|
||||
if isinstance(param, dict):
|
||||
if len(param) != 1:
|
||||
self.do_raise("run_command parameter as a dict must contain only one key: {0}".format(param))
|
||||
_param = list(param.keys())[0]
|
||||
fmt = find_format(_param)
|
||||
value = param[_param]
|
||||
elif isinstance(param, str):
|
||||
if param in self.vars.keys():
|
||||
fmt = find_format(param)
|
||||
value = self.vars[param]
|
||||
elif param in extra_params:
|
||||
fmt = find_format(param)
|
||||
value = extra_params[param]
|
||||
else:
|
||||
self.do_raise('Cannot determine value for parameter: {0}'.format(param))
|
||||
else:
|
||||
self.do_raise("run_command parameter must be either a str or a dict: {0}".format(param))
|
||||
cmd_args = add_arg_formatted_param(cmd_args, fmt, value)
|
||||
|
||||
return cmd_args
|
||||
|
||||
def process_command_output(self, rc, out, err):
|
||||
return rc, out, err
|
||||
|
||||
def run_command(self,
|
||||
extra_params=None,
|
||||
params=None,
|
||||
process_output=None,
|
||||
publish_rc=True,
|
||||
publish_out=True,
|
||||
publish_err=True,
|
||||
publish_cmd=True,
|
||||
*args, **kwargs):
|
||||
cmd_args = self._calculate_args(extra_params, params)
|
||||
options = dict(self.run_command_fixed_options)
|
||||
options['check_rc'] = options.get('check_rc', self.check_rc)
|
||||
options.update(kwargs)
|
||||
env_update = dict(options.get('environ_update', {}))
|
||||
if self.force_lang:
|
||||
env_update.update({
|
||||
'LANGUAGE': self.force_lang,
|
||||
'LC_ALL': self.force_lang,
|
||||
})
|
||||
self.update_output(force_lang=self.force_lang)
|
||||
options['environ_update'] = env_update
|
||||
rc, out, err = self.module.run_command(cmd_args, *args, **options)
|
||||
if publish_rc:
|
||||
self.update_output(rc=rc)
|
||||
if publish_out:
|
||||
self.update_output(stdout=out)
|
||||
if publish_err:
|
||||
self.update_output(stderr=err)
|
||||
if publish_cmd:
|
||||
self.update_output(cmd_args=cmd_args)
|
||||
if process_output is None:
|
||||
_process = self.process_command_output
|
||||
else:
|
||||
_process = process_output
|
||||
|
||||
return _process(rc, out, err)
|
||||
@@ -12,6 +12,7 @@ from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
|
||||
# (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.mixins.cmd import CmdMixin
|
||||
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
|
||||
@@ -65,3 +66,19 @@ class ModuleHelper(DeprecateAttrsMixin, VarsMixin, DependencyMixin, ModuleHelper
|
||||
|
||||
class StateModuleHelper(StateMixin, ModuleHelper):
|
||||
pass
|
||||
|
||||
|
||||
class CmdModuleHelper(CmdMixin, ModuleHelper):
|
||||
"""
|
||||
THIS CLASS IS BEING DEPRECATED.
|
||||
See the deprecation notice in ``CmdMixin.__init__()``.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CmdStateModuleHelper(CmdMixin, StateMixin, ModuleHelper):
|
||||
"""
|
||||
THIS CLASS IS BEING DEPRECATED.
|
||||
See the deprecation notice in ``CmdMixin.__init__()``.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -11,8 +11,9 @@ __metaclass__ = type
|
||||
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.module_helper import (
|
||||
ModuleHelper, StateModuleHelper, AnsibleModule
|
||||
ModuleHelper, StateModuleHelper, CmdModuleHelper, CmdStateModuleHelper, AnsibleModule
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.cmd import CmdMixin, ArgFormat # noqa: F401
|
||||
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.exceptions import ModuleHelperException # noqa: F401
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
# (TODO: remove next line!)
|
||||
import atexit # noqa: F401, pylint: disable=unused-import
|
||||
# (TODO: remove next line!)
|
||||
import time # noqa: F401, pylint: disable=unused-import
|
||||
# (TODO: remove next line!)
|
||||
import re # noqa: F401, pylint: disable=unused-import
|
||||
import traceback
|
||||
|
||||
PROXMOXER_IMP_ERR = None
|
||||
@@ -20,6 +26,8 @@ except ImportError:
|
||||
|
||||
|
||||
from ansible.module_utils.basic import env_fallback, missing_required_lib
|
||||
# (TODO: remove next line!)
|
||||
from ansible.module_utils.common.text.converters import to_native # noqa: F401, pylint: disable=unused-import
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
|
||||
@@ -180,17 +188,3 @@ class ProxmoxAnsible(object):
|
||||
return self.proxmox_api.storage.get(type=type)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Unable to retrieve storages information with type %s: %s" % (type, e))
|
||||
|
||||
def get_storage_content(self, node, storage, content=None, vmid=None):
|
||||
try:
|
||||
return (
|
||||
self.proxmox_api.nodes(node)
|
||||
.storage(storage)
|
||||
.content()
|
||||
.get(content=content, vmid=vmid)
|
||||
)
|
||||
except Exception as e:
|
||||
self.module.fail_json(
|
||||
msg="Unable to list content on %s, %s for %s and %s: %s"
|
||||
% (node, storage, content, vmid, e)
|
||||
)
|
||||
|
||||
@@ -107,5 +107,6 @@ def puppet_runner(module):
|
||||
verbose=cmd_runner_fmt.as_bool("--verbose"),
|
||||
),
|
||||
check_rc=False,
|
||||
force_lang=module.params["environment_lang"],
|
||||
)
|
||||
return runner
|
||||
|
||||
@@ -20,6 +20,8 @@ from ansible.module_utils.six import text_type
|
||||
from ansible.module_utils.six.moves import http_client
|
||||
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
||||
from ansible.module_utils.ansible_release import __version__ as ansible_version
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
GET_HEADERS = {'accept': 'application/json', 'OData-Version': '4.0'}
|
||||
POST_HEADERS = {'content-type': 'application/json', 'accept': 'application/json',
|
||||
@@ -130,7 +132,7 @@ class RedfishUtils(object):
|
||||
return resp
|
||||
|
||||
# The following functions are to send GET/POST/PATCH/DELETE requests
|
||||
def get_request(self, uri, override_headers=None):
|
||||
def get_request(self, uri, override_headers=None, allow_no_resp=False):
|
||||
req_headers = dict(GET_HEADERS)
|
||||
if override_headers:
|
||||
req_headers.update(override_headers)
|
||||
@@ -145,13 +147,19 @@ class RedfishUtils(object):
|
||||
force_basic_auth=basic_auth, validate_certs=False,
|
||||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout)
|
||||
if override_headers:
|
||||
resp = gzip.open(BytesIO(resp.read()), 'rt', encoding='utf-8')
|
||||
data = json.loads(to_native(resp.read()))
|
||||
headers = req_headers
|
||||
else:
|
||||
data = json.loads(to_native(resp.read()))
|
||||
headers = dict((k.lower(), v) for (k, v) in resp.info().items())
|
||||
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'):
|
||||
# Older versions of Ansible do not automatically decompress the data
|
||||
# Starting in 2.14, open_url will decompress the response data by default
|
||||
data = json.loads(to_native(gzip.open(BytesIO(resp.read()), 'rt', encoding='utf-8').read()))
|
||||
else:
|
||||
data = json.loads(to_native(resp.read()))
|
||||
except Exception as e:
|
||||
# No response data; this is okay in certain cases
|
||||
data = None
|
||||
if not allow_no_resp:
|
||||
raise
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
@@ -1813,7 +1821,7 @@ class RedfishUtils(object):
|
||||
return {'ret': False, 'msg': 'Must provide a handle tracking the update.'}
|
||||
|
||||
# Get the task or job tracking the update
|
||||
response = self.get_request(self.root_uri + update_handle)
|
||||
response = self.get_request(self.root_uri + update_handle, allow_no_resp=True)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
|
||||
@@ -2907,8 +2915,7 @@ class RedfishUtils(object):
|
||||
|
||||
# Get a list of all Chassis and build URIs, then get all PowerSupplies
|
||||
# from each Power entry in the Chassis
|
||||
chassis_uri_list = self.chassis_uris
|
||||
for chassis_uri in chassis_uri_list:
|
||||
for chassis_uri in self.chassis_uris:
|
||||
response = self.get_request(self.root_uri + chassis_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
@@ -2955,7 +2962,7 @@ class RedfishUtils(object):
|
||||
result = {}
|
||||
inventory = {}
|
||||
# Get these entries, but does not fail if not found
|
||||
properties = ['Status', 'HostName', 'PowerState', 'BootProgress', 'Model', 'Manufacturer',
|
||||
properties = ['Status', 'HostName', 'PowerState', 'Model', 'Manufacturer',
|
||||
'PartNumber', 'SystemType', 'AssetTag', 'ServiceTag',
|
||||
'SerialNumber', 'SKU', 'BiosVersion', 'MemorySummary',
|
||||
'ProcessorSummary', 'TrustedModules', 'Name', 'Id']
|
||||
@@ -3470,33 +3477,30 @@ class RedfishUtils(object):
|
||||
result = {}
|
||||
key = "Thermal"
|
||||
# Go through list
|
||||
for chassis_uri in self.chassis_uri_list:
|
||||
for chassis_uri in self.chassis_uris:
|
||||
response = self.get_request(self.root_uri + chassis_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
result['ret'] = True
|
||||
data = response['data']
|
||||
oem = data.get['Oem']
|
||||
hpe = oem.get['Hpe']
|
||||
thermal_config = hpe.get('ThermalConfiguration')
|
||||
result["current_thermal_config"] = thermal_config
|
||||
return result
|
||||
val = data.get('Oem', {}).get('Hpe', {}).get('ThermalConfiguration')
|
||||
if val is not None:
|
||||
return {"ret": True, "current_thermal_config": val}
|
||||
return {"ret": False}
|
||||
|
||||
def get_hpe_fan_percent_min(self):
|
||||
result = {}
|
||||
key = "Thermal"
|
||||
# Go through list
|
||||
for chassis_uri in self.chassis_uri_list:
|
||||
for chassis_uri in self.chassis_uris:
|
||||
response = self.get_request(self.root_uri + chassis_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
result['ret'] = True
|
||||
data = response['data']
|
||||
oem = data.get['Oem']
|
||||
hpe = oem.get['Hpe']
|
||||
fan_percent_min_config = hpe.get('FanPercentMinimum')
|
||||
result["fan_percent_min"] = fan_percent_min_config
|
||||
return result
|
||||
val = data.get('Oem', {}).get('Hpe', {}).get('FanPercentMinimum')
|
||||
if val is not None:
|
||||
return {"ret": True, "fan_percent_min": val}
|
||||
return {"ret": False}
|
||||
|
||||
def delete_volumes(self, storage_subsystem_id, volume_ids):
|
||||
# Find the Storage resource from the requested ComputerSystem resource
|
||||
|
||||
@@ -66,19 +66,6 @@ class _Variable(object):
|
||||
if verbosity is not None:
|
||||
self.verbosity = verbosity
|
||||
|
||||
def as_dict(self, meta_only=False):
|
||||
d = {
|
||||
"diff": self.diff,
|
||||
"change": self.change,
|
||||
"output": self.output,
|
||||
"fact": self.fact,
|
||||
"verbosity": self.verbosity,
|
||||
}
|
||||
if not meta_only:
|
||||
d["initial_value"] = copy.deepcopy(self.initial_value)
|
||||
d["value"] = self.value
|
||||
return d
|
||||
|
||||
def set_value(self, value):
|
||||
if not self.init:
|
||||
self.initial_value = copy.deepcopy(value)
|
||||
@@ -106,7 +93,7 @@ class _Variable(object):
|
||||
|
||||
|
||||
class VarDict(object):
|
||||
reserved_names = ('__vars__', '_var', 'var', 'set_meta', 'get_meta', 'set', 'output', 'diff', 'facts', 'has_changed', 'as_dict')
|
||||
reserved_names = ('__vars__', 'var', 'set_meta', 'set', 'output', 'diff', 'facts', 'has_changed')
|
||||
|
||||
def __init__(self):
|
||||
self.__vars__ = dict()
|
||||
@@ -132,9 +119,6 @@ class VarDict(object):
|
||||
def _var(self, name):
|
||||
return self.__vars__[name]
|
||||
|
||||
def var(self, name):
|
||||
return self._var(name).as_dict()
|
||||
|
||||
def set_meta(self, name, **kwargs):
|
||||
"""Set the metadata for the variable
|
||||
|
||||
@@ -149,9 +133,6 @@ class VarDict(object):
|
||||
"""
|
||||
self._var(name).set_meta(**kwargs)
|
||||
|
||||
def get_meta(self, name):
|
||||
return self._var(name).as_dict(meta_only=True)
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
"""Set the value and optionally metadata for a variable. The variable is not required to exist prior to calling `set`.
|
||||
|
||||
@@ -191,7 +172,7 @@ class VarDict(object):
|
||||
|
||||
@property
|
||||
def has_changed(self):
|
||||
return any(var.has_changed for var in self.__vars__.values())
|
||||
return any(True for var in self.__vars__.values() if var.has_changed)
|
||||
|
||||
def as_dict(self):
|
||||
return dict((name, var.value) for name, var in self.__vars__.items())
|
||||
|
||||
@@ -10,4 +10,13 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
from ansible.module_utils.compat.version import LooseVersion # noqa: F401, pylint: disable=unused-import
|
||||
from ansible.module_utils.six import raise_from
|
||||
|
||||
try:
|
||||
from ansible.module_utils.compat.version import LooseVersion # noqa: F401, pylint: disable=unused-import
|
||||
except ImportError:
|
||||
try:
|
||||
from distutils.version import LooseVersion # noqa: F401, pylint: disable=unused-import
|
||||
except ImportError as exc:
|
||||
msg = 'To use this plugin or module with ansible-core 2.11, you need to use Python < 3.12 with distutils.version present'
|
||||
raise_from(ImportError(msg), exc)
|
||||
|
||||
@@ -38,8 +38,8 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
default:
|
||||
- agblksize='4096'
|
||||
- isnapshot='no'
|
||||
- agblksize=4096
|
||||
- isnapshot=no
|
||||
auto_mount:
|
||||
description:
|
||||
- File system is automatically mounted at system restart.
|
||||
@@ -242,7 +242,7 @@ def _validate_vg(module, vg):
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed executing %s command." % lsvg_cmd)
|
||||
|
||||
rc, current_all_vgs, err = module.run_command([lsvg_cmd, "%s"])
|
||||
rc, current_all_vgs, err = module.run_command([lsvg_cmd])
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed executing %s command." % lsvg_cmd)
|
||||
|
||||
@@ -365,7 +365,53 @@ def create_fs(
|
||||
# Creates a LVM file system.
|
||||
crfs_cmd = module.get_bin_path('crfs', True)
|
||||
if not module.check_mode:
|
||||
cmd = [crfs_cmd, "-v", fs_type, "-m", filesystem, vg, device, mount_group, auto_mount, account_subsystem, "-p", permissions, size, "-a", attributes]
|
||||
cmd = [crfs_cmd]
|
||||
|
||||
cmd.append("-v")
|
||||
cmd.append(fs_type)
|
||||
|
||||
if vg:
|
||||
(flag, value) = vg.split()
|
||||
cmd.append(flag)
|
||||
cmd.append(value)
|
||||
|
||||
if device:
|
||||
(flag, value) = device.split()
|
||||
cmd.append(flag)
|
||||
cmd.append(value)
|
||||
|
||||
cmd.append("-m")
|
||||
cmd.append(filesystem)
|
||||
|
||||
if mount_group:
|
||||
(flag, value) = mount_group.split()
|
||||
cmd.append(flag)
|
||||
cmd.append(value)
|
||||
|
||||
if auto_mount:
|
||||
(flag, value) = auto_mount.split()
|
||||
cmd.append(flag)
|
||||
cmd.append(value)
|
||||
|
||||
if account_subsystem:
|
||||
(flag, value) = account_subsystem.split()
|
||||
cmd.append(flag)
|
||||
cmd.append(value)
|
||||
|
||||
cmd.append("-p")
|
||||
cmd.append(permissions)
|
||||
|
||||
if size:
|
||||
(flag, value) = size.split()
|
||||
cmd.append(flag)
|
||||
cmd.append(value)
|
||||
|
||||
if attributes:
|
||||
splitted_attributes = attributes.split()
|
||||
cmd.append("-a")
|
||||
for value in splitted_attributes:
|
||||
cmd.append(value)
|
||||
|
||||
rc, crfs_out, err = module.run_command(cmd)
|
||||
|
||||
if rc == 10:
|
||||
@@ -461,7 +507,7 @@ def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
account_subsystem=dict(type='bool', default=False),
|
||||
attributes=dict(type='list', elements='str', default=["agblksize='4096'", "isnapshot='no'"]),
|
||||
attributes=dict(type='list', elements='str', default=["agblksize=4096", "isnapshot=no"]),
|
||||
auto_mount=dict(type='bool', default=True),
|
||||
device=dict(type='str'),
|
||||
filesystem=dict(type='str', required=True),
|
||||
|
||||
@@ -253,7 +253,7 @@ options:
|
||||
author:
|
||||
- "He Guimin (@xiaozhu36)"
|
||||
requirements:
|
||||
- "Python >= 3.6"
|
||||
- "python >= 3.6"
|
||||
- "footmark >= 1.19.0"
|
||||
extends_documentation_fragment:
|
||||
- community.general.alicloud
|
||||
|
||||
@@ -31,6 +31,7 @@ short_description: Gather information on instances of Alibaba Cloud ECS
|
||||
description:
|
||||
- This module fetches data from the Open API in Alicloud.
|
||||
The module must be called from within the ECS instance itself.
|
||||
- This module was called C(ali_instance_facts) before Ansible 2.9. The usage did not change.
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
@@ -60,7 +61,7 @@ options:
|
||||
author:
|
||||
- "He Guimin (@xiaozhu36)"
|
||||
requirements:
|
||||
- "Python >= 3.6"
|
||||
- "python >= 3.6"
|
||||
- "footmark >= 1.13.0"
|
||||
extends_documentation_fragment:
|
||||
- community.general.alicloud
|
||||
|
||||
@@ -17,13 +17,15 @@ version_added: 3.5.0
|
||||
description:
|
||||
- This module allows the installation of Ansible collections or roles using C(ansible-galaxy).
|
||||
notes:
|
||||
- Support for B(Ansible 2.9/2.10) was removed in community.general 8.0.0.
|
||||
- >
|
||||
B(Ansible 2.9/2.10): The C(ansible-galaxy) command changed significantly between Ansible 2.9 and
|
||||
ansible-base 2.10 (later ansible-core 2.11). See comments in the parameters.
|
||||
- >
|
||||
The module will try and run using the C(C.UTF-8) locale.
|
||||
If that fails, it will try C(en_US.UTF-8).
|
||||
If that one also fails, the module will fail.
|
||||
requirements:
|
||||
- ansible-core 2.11 or newer
|
||||
- Ansible 2.9, ansible-base 2.10, or ansible-core 2.11 or newer
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
@@ -37,6 +39,7 @@ options:
|
||||
- The type of installation performed by C(ansible-galaxy).
|
||||
- If O(type=both), then O(requirements_file) must be passed and it may contain both roles and collections.
|
||||
- "Note however that the opposite is not true: if using a O(requirements_file), then O(type) can be any of the three choices."
|
||||
- "B(Ansible 2.9): The option V(both) will have the same effect as V(role)."
|
||||
type: str
|
||||
choices: [collection, role, both]
|
||||
required: true
|
||||
@@ -53,6 +56,7 @@ options:
|
||||
- Path to a file containing a list of requirements to be installed.
|
||||
- It works for O(type) equals to V(collection) and V(role).
|
||||
- O(name) and O(requirements_file) are mutually exclusive.
|
||||
- "B(Ansible 2.9): It can only be used to install either O(type=role) or O(type=collection), but not both at the same run."
|
||||
type: path
|
||||
dest:
|
||||
description:
|
||||
@@ -71,16 +75,24 @@ options:
|
||||
description:
|
||||
- Force overwriting an existing role or collection.
|
||||
- Using O(force=true) is mandatory when downgrading.
|
||||
- "B(Ansible 2.9 and 2.10): Must be V(true) to upgrade roles and collections."
|
||||
type: bool
|
||||
default: false
|
||||
ack_ansible29:
|
||||
description:
|
||||
- This option has no longer any effect and will be removed in community.general 9.0.0.
|
||||
- Acknowledge using Ansible 2.9 with its limitations, and prevents the module from generating warnings about them.
|
||||
- This option is completely ignored if using a version of Ansible greater than C(2.9.x).
|
||||
- Note that this option will be removed without any further deprecation warning once support
|
||||
for Ansible 2.9 is removed from this module.
|
||||
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.
|
||||
- Acknowledge the module is deprecating support for Ansible 2.9 and ansible-base 2.10.
|
||||
- Support for those versions will be removed in community.general 8.0.0.
|
||||
At the same time, this option will be removed without any deprecation warning!
|
||||
- This option is completely ignored if using a version of ansible-core/ansible-base/Ansible greater than C(2.11).
|
||||
- For the sake of conciseness, setting this parameter to V(true) implies O(ack_ansible29=true).
|
||||
type: bool
|
||||
default: false
|
||||
"""
|
||||
@@ -135,6 +147,7 @@ RETURN = """
|
||||
description:
|
||||
- If O(requirements_file) is specified instead, returns dictionary with all the roles installed per path.
|
||||
- If O(name) is specified, returns that role name and the version installed per path.
|
||||
- "B(Ansible 2.9): Returns empty because C(ansible-galaxy) has no C(list) subcommand."
|
||||
type: dict
|
||||
returned: always when installing roles
|
||||
contains:
|
||||
@@ -151,6 +164,7 @@ RETURN = """
|
||||
description:
|
||||
- If O(requirements_file) is specified instead, returns dictionary with all the collections installed per path.
|
||||
- If O(name) is specified, returns that collection name and the version installed per path.
|
||||
- "B(Ansible 2.9): Returns empty because C(ansible-galaxy) has no C(list) subcommand."
|
||||
type: dict
|
||||
returned: always when installing collections
|
||||
contains:
|
||||
@@ -192,6 +206,7 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
_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__()
|
||||
ansible_version = None
|
||||
is_ansible29 = None
|
||||
|
||||
output_params = ('type', 'name', 'dest', 'requirements_file', 'force', 'no_deps')
|
||||
module = dict(
|
||||
@@ -202,18 +217,8 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
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',
|
||||
),
|
||||
ack_ansible29=dict(type='bool', default=False),
|
||||
ack_min_ansiblecore211=dict(type='bool', default=False),
|
||||
),
|
||||
mutually_exclusive=[('name', 'requirements_file')],
|
||||
required_one_of=[('name', 'requirements_file')],
|
||||
@@ -263,22 +268,26 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
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."
|
||||
if self.ansible_version < (2, 11) and not self.vars.ack_min_ansiblecore211:
|
||||
self.module.deprecate(
|
||||
"Support for Ansible 2.9 and ansible-base 2.10 is being deprecated. "
|
||||
"At the same time support for them is ended, also the ack_ansible29 option will be removed. "
|
||||
"Upgrading is strongly recommended, or set 'ack_min_ansiblecore211' to suppress this message.",
|
||||
version="8.0.0",
|
||||
collection_name="community.general",
|
||||
)
|
||||
# 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.vars.set("new_collections", {}, change=True)
|
||||
self.vars.set("new_roles", {}, change=True)
|
||||
if self.vars.type != "collection":
|
||||
self.vars.installed_roles = self._list_roles()
|
||||
if self.vars.type != "roles":
|
||||
self.vars.installed_collections = self._list_collections()
|
||||
self.is_ansible29 = self.ansible_version < (2, 10)
|
||||
if self.is_ansible29:
|
||||
self._RE_INSTALL_OUTPUT = re.compile(r"^(?:.*Installing '(?P<collection>\w+\.\w+):(?P<cversion>[\d\.]+)'.*"
|
||||
r'|- (?P<role>\w+\.\w+) \((?P<rversion>[\d\.]+)\)'
|
||||
r' was installed successfully)$')
|
||||
else:
|
||||
# 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$')
|
||||
|
||||
def _list_element(self, _type, path_re, elem_re):
|
||||
def process(rc, out, err):
|
||||
@@ -313,8 +322,24 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
def _list_roles(self):
|
||||
return self._list_element('role', self._RE_LIST_PATH, self._RE_LIST_ROLE)
|
||||
|
||||
def __run__(self):
|
||||
def _setup29(self):
|
||||
self.vars.set("new_collections", {})
|
||||
self.vars.set("new_roles", {})
|
||||
self.vars.set("ansible29_change", False, change=True, output=False)
|
||||
if not (self.vars.ack_ansible29 or self.vars.ack_min_ansiblecore211):
|
||||
self.warn("Ansible 2.9 or older: unable to retrieve lists of roles and collections already installed")
|
||||
if self.vars.requirements_file is not None and self.vars.type == 'both':
|
||||
self.warn("Ansible 2.9 or older: will install only roles from requirement files")
|
||||
|
||||
def _setup210plus(self):
|
||||
self.vars.set("new_collections", {}, change=True)
|
||||
self.vars.set("new_roles", {}, change=True)
|
||||
if self.vars.type != "collection":
|
||||
self.vars.installed_roles = self._list_roles()
|
||||
if self.vars.type != "roles":
|
||||
self.vars.installed_collections = self._list_collections()
|
||||
|
||||
def __run__(self):
|
||||
def process(rc, out, err):
|
||||
for line in out.splitlines():
|
||||
match = self._RE_INSTALL_OUTPUT.match(line)
|
||||
@@ -322,9 +347,19 @@ class AnsibleGalaxyInstall(ModuleHelper):
|
||||
continue
|
||||
if match.group("collection"):
|
||||
self.vars.new_collections[match.group("collection")] = match.group("cversion")
|
||||
if self.is_ansible29:
|
||||
self.vars.ansible29_change = True
|
||||
elif match.group("role"):
|
||||
self.vars.new_roles[match.group("role")] = match.group("rversion")
|
||||
if self.is_ansible29:
|
||||
self.vars.ansible29_change = True
|
||||
|
||||
if self.is_ansible29:
|
||||
if self.vars.type == 'both':
|
||||
raise ValueError("Type 'both' not supported in Ansible 2.9")
|
||||
self._setup29()
|
||||
else:
|
||||
self._setup210plus()
|
||||
with self.runner("type galaxy_cmd force no_deps dest requirements_file name", output_process=process) as ctx:
|
||||
ctx.run(galaxy_cmd="install")
|
||||
if self.verbosity > 2:
|
||||
|
||||
@@ -35,7 +35,9 @@ options:
|
||||
default: false
|
||||
name:
|
||||
description:
|
||||
- A package name, like V(foo), or multiple packages, like V(foo, bar).
|
||||
- A package name, like V(foo), or multiple packages, like V(foo,bar).
|
||||
- Do not include additional whitespace when specifying multiple packages as a string.
|
||||
Prefer YAML lists over comma-separating multiple package names.
|
||||
type: list
|
||||
elements: str
|
||||
no_cache:
|
||||
@@ -61,7 +63,7 @@ options:
|
||||
type: str
|
||||
update_cache:
|
||||
description:
|
||||
- Update repository indexes. Can be run with other steps or on it's own.
|
||||
- Update repository indexes. Can be run with other steps or on its own.
|
||||
type: bool
|
||||
default: false
|
||||
upgrade:
|
||||
|
||||
@@ -28,9 +28,6 @@ options:
|
||||
package:
|
||||
description:
|
||||
- List of packages to install, upgrade, or remove.
|
||||
- Since community.general 8.0.0, may include paths to local C(.rpm) files
|
||||
if O(state=installed) or O(state=present), requires C(rpm) python
|
||||
module.
|
||||
aliases: [ name, pkg ]
|
||||
type: list
|
||||
elements: str
|
||||
@@ -66,9 +63,6 @@ options:
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 6.5.0
|
||||
requirements:
|
||||
- C(rpm) python package (rpm bindings), optional. Required if O(package)
|
||||
option includes local files.
|
||||
author:
|
||||
- Evgenii Terechkov (@evgkrsk)
|
||||
'''
|
||||
@@ -115,48 +109,15 @@ EXAMPLES = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import (
|
||||
AnsibleModule,
|
||||
missing_required_lib,
|
||||
)
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import rpm
|
||||
except ImportError:
|
||||
HAS_RPM_PYTHON = False
|
||||
RPM_PYTHON_IMPORT_ERROR = traceback.format_exc()
|
||||
else:
|
||||
HAS_RPM_PYTHON = True
|
||||
RPM_PYTHON_IMPORT_ERROR = None
|
||||
|
||||
APT_CACHE = "/usr/bin/apt-cache"
|
||||
APT_PATH = "/usr/bin/apt-get"
|
||||
RPM_PATH = "/usr/bin/rpm"
|
||||
APT_GET_ZERO = "\n0 upgraded, 0 newly installed"
|
||||
UPDATE_KERNEL_ZERO = "\nTry to install new kernel "
|
||||
|
||||
|
||||
def local_rpm_package_name(path):
|
||||
"""return package name of a local rpm passed in.
|
||||
Inspired by ansible.builtin.yum"""
|
||||
|
||||
ts = rpm.TransactionSet()
|
||||
ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
|
||||
fd = os.open(path, os.O_RDONLY)
|
||||
try:
|
||||
header = ts.hdrFromFdno(fd)
|
||||
except rpm.error as e:
|
||||
return None
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
return to_native(header[rpm.RPMTAG_NAME])
|
||||
|
||||
|
||||
def query_package(module, name):
|
||||
# rpm -q returns 0 if the package is installed,
|
||||
# 1 if it is not installed
|
||||
@@ -167,38 +128,11 @@ def query_package(module, name):
|
||||
return False
|
||||
|
||||
|
||||
def check_package_version(module, name):
|
||||
# compare installed and candidate version
|
||||
# if newest version already installed return True
|
||||
# otherwise return False
|
||||
|
||||
rc, out, err = module.run_command([APT_CACHE, "policy", name], environ_update={"LANG": "C"})
|
||||
installed = re.split("\n |: ", out)[2]
|
||||
candidate = re.split("\n |: ", out)[4]
|
||||
if installed >= candidate:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def query_package_provides(module, name):
|
||||
# rpm -q returns 0 if the package is installed,
|
||||
# 1 if it is not installed
|
||||
if name.endswith('.rpm'):
|
||||
# Likely a local RPM file
|
||||
if not HAS_RPM_PYTHON:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib('rpm'),
|
||||
exception=RPM_PYTHON_IMPORT_ERROR,
|
||||
)
|
||||
|
||||
name = local_rpm_package_name(name)
|
||||
|
||||
rc, out, err = module.run_command("%s -q --provides %s" % (RPM_PATH, name))
|
||||
if rc == 0:
|
||||
if check_package_version(module, name):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return rc == 0
|
||||
|
||||
|
||||
def update_package_db(module):
|
||||
@@ -270,7 +204,7 @@ def install_packages(module, pkgspec):
|
||||
rc, out, err = module.run_command("%s -y install %s" % (APT_PATH, packages), environ_update={"LANG": "C"})
|
||||
|
||||
installed = True
|
||||
for packages in pkgspec:
|
||||
for package in pkgspec:
|
||||
if not query_package_provides(module, package):
|
||||
installed = False
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ options:
|
||||
format:
|
||||
description:
|
||||
- The type of compression to use.
|
||||
- Support for xz was added in Ansible 2.5.
|
||||
type: str
|
||||
choices: [ bz2, gz, tar, xz, zip ]
|
||||
default: gz
|
||||
|
||||
@@ -21,6 +21,7 @@ notes:
|
||||
- Host should support C(atomic) command
|
||||
requirements:
|
||||
- atomic
|
||||
- "python >= 2.6"
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user