mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 18:36:28 +00:00
Compare commits
402 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -247,10 +247,10 @@ stages:
|
||||
parameters:
|
||||
testFormat: 2.14/{0}
|
||||
targets:
|
||||
#- name: macOS 12.0
|
||||
# test: macos/12.0
|
||||
- name: RHEL 9.0
|
||||
test: rhel/9.0
|
||||
#- name: macOS 12.0
|
||||
# test: macos/12.0
|
||||
#- name: FreeBSD 12.4
|
||||
# test: freebsd/12.4
|
||||
groups:
|
||||
|
||||
107
.github/BOTMETA.yml
vendored
107
.github/BOTMETA.yml
vendored
@@ -50,8 +50,6 @@ files:
|
||||
$callbacks/cgroup_memory_recap.py: {}
|
||||
$callbacks/context_demo.py: {}
|
||||
$callbacks/counter_enabled.py: {}
|
||||
$callbacks/default_without_diff.py:
|
||||
maintainers: felixfontein
|
||||
$callbacks/dense.py:
|
||||
maintainers: dagwieers
|
||||
$callbacks/diy.py:
|
||||
@@ -99,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: {}
|
||||
@@ -124,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
|
||||
@@ -138,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:
|
||||
@@ -151,18 +144,8 @@ files:
|
||||
$filters/jc.py:
|
||||
maintainers: kellyjonbrazil
|
||||
$filters/json_query.py: {}
|
||||
$filters/lists.py:
|
||||
maintainers: cfiehe
|
||||
$filters/lists_difference.yml:
|
||||
maintainers: cfiehe
|
||||
$filters/lists_intersect.yml:
|
||||
maintainers: cfiehe
|
||||
$filters/lists_mergeby.py:
|
||||
maintainers: vbotka
|
||||
$filters/lists_symmetric_difference.yml:
|
||||
maintainers: cfiehe
|
||||
$filters/lists_union.yml:
|
||||
maintainers: cfiehe
|
||||
$filters/random_mac.py: {}
|
||||
$filters/time.py:
|
||||
maintainers: resmo
|
||||
@@ -170,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:
|
||||
@@ -212,7 +193,7 @@ files:
|
||||
labels: cloud opennebula
|
||||
maintainers: feldsam
|
||||
$inventories/proxmox.py:
|
||||
maintainers: $team_virt ilijamt krauthosting
|
||||
maintainers: $team_virt ilijamt
|
||||
$inventories/scaleway.py:
|
||||
labels: cloud scaleway
|
||||
maintainers: $team_scaleway
|
||||
@@ -253,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: {}
|
||||
@@ -496,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:
|
||||
@@ -520,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:
|
||||
@@ -549,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:
|
||||
@@ -572,14 +544,8 @@ files:
|
||||
ignore: dj-wasabi
|
||||
$modules/gitlab_branch.py:
|
||||
maintainers: paytroff
|
||||
$modules/gitlab_issue.py:
|
||||
maintainers: zvaraondrej
|
||||
$modules/gitlab_label.py:
|
||||
maintainers: gpongelli
|
||||
$modules/gitlab_merge_request.py:
|
||||
maintainers: zvaraondrej
|
||||
$modules/gitlab_milestone.py:
|
||||
maintainers: gpongelli
|
||||
$modules/gitlab_project_variable.py:
|
||||
maintainers: markuman
|
||||
$modules/gitlab_instance_variable.py:
|
||||
@@ -588,10 +554,6 @@ files:
|
||||
maintainers: SamyCoenen
|
||||
$modules/gitlab_user.py:
|
||||
maintainers: LennertMertens stgrace
|
||||
$modules/gitlab_group_access_token.py:
|
||||
maintainers: pixslx
|
||||
$modules/gitlab_project_access_token.py:
|
||||
maintainers: pixslx
|
||||
$modules/grove.py:
|
||||
maintainers: zimbatm
|
||||
$modules/gunicorn.py:
|
||||
@@ -681,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:
|
||||
@@ -778,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:
|
||||
@@ -912,7 +868,7 @@ files:
|
||||
$modules/nmcli.py:
|
||||
maintainers: alcamie101
|
||||
$modules/nomad_:
|
||||
maintainers: chris93111 apecnascimento
|
||||
maintainers: chris93111
|
||||
$modules/nosh.py:
|
||||
maintainers: tacatac
|
||||
$modules/npm.py:
|
||||
@@ -1059,27 +1015,23 @@ files:
|
||||
$modules/proxmox:
|
||||
keywords: kvm libvirt proxmox qemu
|
||||
labels: proxmox virt
|
||||
maintainers: $team_virt UnderGreen krauthosting
|
||||
maintainers: $team_virt UnderGreen
|
||||
ignore: tleguern
|
||||
$modules/proxmox.py:
|
||||
ignore: skvidal
|
||||
maintainers: UnderGreen krauthosting
|
||||
maintainers: UnderGreen
|
||||
$modules/proxmox_disk.py:
|
||||
maintainers: castorsky krauthosting
|
||||
maintainers: castorsky
|
||||
$modules/proxmox_kvm.py:
|
||||
ignore: skvidal
|
||||
maintainers: helldorado krauthosting
|
||||
maintainers: helldorado
|
||||
$modules/proxmox_nic.py:
|
||||
maintainers: Kogelvis krauthosting
|
||||
$modules/proxmox_node_info.py:
|
||||
maintainers: jwbernin krauthosting
|
||||
$modules/proxmox_storage_contents_info.py:
|
||||
maintainers: l00ptr krauthosting
|
||||
maintainers: Kogelvis
|
||||
$modules/proxmox_tasks_info:
|
||||
maintainers: paginabianca krauthosting
|
||||
maintainers: paginabianca
|
||||
$modules/proxmox_template.py:
|
||||
ignore: skvidal
|
||||
maintainers: UnderGreen krauthosting
|
||||
maintainers: UnderGreen
|
||||
$modules/pubnub_blocks.py:
|
||||
maintainers: parfeon pubnub
|
||||
$modules/pulp_repo.py:
|
||||
@@ -1445,43 +1397,6 @@ files:
|
||||
maintainers: $team_suse
|
||||
$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_lists_helper.rst:
|
||||
maintainers: cfiehe
|
||||
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
|
||||
@@ -1512,7 +1427,7 @@ macros:
|
||||
team_ansible_core:
|
||||
team_aix: MorrisA bcoca d-little flynn1973 gforster kairoaraujo marvin-sinister mator molekuul ramooncamacho wtcross
|
||||
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
|
||||
team_consul: sgargan apollo13
|
||||
team_consul: sgargan
|
||||
team_cyberark_conjur: jvanderhoof ryanprior
|
||||
team_e_spirit: MatrixCrawler getjack
|
||||
team_flatpak: JayKayy oolongbrothers
|
||||
|
||||
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,17 +29,23 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
ansible:
|
||||
- '2.11'
|
||||
- '2.12'
|
||||
- '2.13'
|
||||
# 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 }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pull-request-change-detection: 'true'
|
||||
@@ -51,7 +57,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,9 +73,13 @@ 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'
|
||||
@@ -79,6 +92,7 @@ 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 }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pre-test-cmd: >-
|
||||
@@ -95,7 +109,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 +128,94 @@ jobs:
|
||||
exclude:
|
||||
- ansible: ''
|
||||
include:
|
||||
# 2.11
|
||||
- ansible: '2.11'
|
||||
docker: fedora32
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.11'
|
||||
docker: fedora32
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.11'
|
||||
docker: fedora32
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
- ansible: '2.11'
|
||||
docker: fedora33
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.11'
|
||||
docker: fedora33
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.11'
|
||||
docker: fedora33
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
- 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
|
||||
@@ -161,6 +266,7 @@ 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 }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
docker-image: ${{ matrix.docker }}
|
||||
|
||||
1145
CHANGELOG.md
1145
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
1109
CHANGELOG.rst
1109
CHANGELOG.rst
File diff suppressed because it is too large
Load Diff
20
README.md
20
README.md
@@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Community General Collection
|
||||
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://github.com/ansible-collections/community.general/actions)
|
||||
[](https://codecov.io/gh/ansible-collections/community.general)
|
||||
|
||||
@@ -24,7 +24,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 releases and the current development version of ansible-core. 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 +73,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 +89,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 +117,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.md).
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-7/CHANGELOG.md).
|
||||
|
||||
## Roadmap
|
||||
|
||||
@@ -134,8 +136,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
@@ -8,9 +8,3 @@ sections:
|
||||
toctree:
|
||||
- filter_guide
|
||||
- test_guide
|
||||
- title: Cloud Guides
|
||||
toctree:
|
||||
- guide_alicloud
|
||||
- guide_online
|
||||
- guide_packet
|
||||
- guide_scaleway
|
||||
|
||||
@@ -12,5 +12,4 @@ Abstract transformations
|
||||
filter_guide_abstract_informations_dictionaries
|
||||
filter_guide_abstract_informations_grouping
|
||||
filter_guide_abstract_informations_merging_lists_of_dictionaries
|
||||
filter_guide_abstract_informations_lists_helper
|
||||
filter_guide_abstract_informations_counting_elements_in_sequence
|
||||
|
||||
@@ -1,81 +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
|
||||
|
||||
Union, intersection and difference of lists
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Starting with Ansible Core 2.16, the builtin filters :ansplugin:`ansible.builtin.union#filter`, :ansplugin:`ansible.builtin.intersect#filter`, :ansplugin:`ansible.builtin.difference#filter` and :ansplugin:`ansible.builtin.symmetric_difference#filter` began to behave differently and do no longer preserve the item order. Items in the resulting lists are returned in arbitrary order and the order can vary between subsequent runs.
|
||||
|
||||
The Ansible community.general collection provides the following additional list filters:
|
||||
|
||||
- :ansplugin:`community.general.lists_union#filter`
|
||||
- :ansplugin:`community.general.lists_intersect#filter`
|
||||
- :ansplugin:`community.general.lists_difference#filter`
|
||||
- :ansplugin:`community.general.lists_symmetric_difference#filter`
|
||||
|
||||
These filters preserve the item order, eliminate duplicates and are an extended version of the builtin ones, because they can operate on more than two lists.
|
||||
|
||||
.. note:: Stick to the builtin filters, when item order is not important or when you do not need the n-ary operating mode. The builtin filters are faster, because they rely mostly on sets as their underlying datastructure.
|
||||
|
||||
Let us use the lists below in the following examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
A: [9, 5, 7, 1, 9, 4, 10, 5, 9, 7]
|
||||
B: [4, 1, 2, 8, 3, 1, 7]
|
||||
C: [10, 2, 1, 9, 1]
|
||||
|
||||
The union of ``A`` and ``B`` can be written as:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
result: "{{ A | community.general.lists_union(B) }}"
|
||||
|
||||
This statement produces:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
result: [9, 5, 7, 1, 4, 10, 2, 8, 3]
|
||||
|
||||
If you want to calculate the intersection of ``A``, ``B`` and ``C``, you can use the following statement:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
result: "{{ A | community.general.lists_intersect(B, C) }}"
|
||||
|
||||
Alternatively, you can use a list of lists as an input of the filter
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
result: "{{ [A, B] | community.general.lists_intersect(C) }}"
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
result: "{{ [A, B, C] | community.general.lists_intersect(flatten=true) }}"
|
||||
|
||||
All three statements are equivalent and give:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
result: [1]
|
||||
|
||||
.. note:: Be aware that in most cases, filter calls without any argument require ``flatten=true``, otherwise the input is returned as result. The reason for this is, that the input is considered as a variable argument and is wrapped by an additional outer list. ``flatten=true`` ensures that this list is removed before the input is processed by the filter logic.
|
||||
|
||||
The filters ansplugin:`community.general.lists_difference#filter` or :ansplugin:`community.general.lists_symmetric_difference#filter` can be used in the same way as the filters in the examples above. They calculate the difference or the symmetric difference between two or more lists and preserve the item order.
|
||||
|
||||
For example, the symmetric difference of ``A``, ``B`` and ``C`` may be written as:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
result: "{{ A | community.general.lists_symmetric_difference(B, C) }}"
|
||||
|
||||
This gives:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
result: [5, 8, 3, 1]
|
||||
|
||||
@@ -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.4.0
|
||||
version: 7.5.5
|
||||
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,15 +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'
|
||||
action_groups:
|
||||
consul:
|
||||
- consul_auth_method
|
||||
- consul_binding_rule
|
||||
- consul_policy
|
||||
- consul_role
|
||||
- consul_session
|
||||
- consul_token
|
||||
requires_ansible: '>=2.11.0'
|
||||
plugin_routing:
|
||||
connection:
|
||||
docker:
|
||||
@@ -30,10 +22,6 @@ plugin_routing:
|
||||
nios_next_network:
|
||||
redirect: infoblox.nios_modules.nios_next_network
|
||||
modules:
|
||||
consul_acl:
|
||||
deprecation:
|
||||
removal_version: 10.0.0
|
||||
warning_text: Use community.general.consul_token and/or community.general.consul_policy instead.
|
||||
rax_cbs_attachments:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
@@ -165,9 +153,9 @@ plugin_routing:
|
||||
stackdriver:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore,
|
||||
and any new development in the direction of providing an alternative should
|
||||
happen in the context of the google.cloud collection.
|
||||
warning_text: >
|
||||
This module relies on HTTPS APIs that do not exist anymore, and any new development in the
|
||||
direction of providing an alternative should happen in the context of the google.cloud collection.
|
||||
system.aix_devices:
|
||||
redirect: community.general.aix_devices
|
||||
deprecation:
|
||||
@@ -819,8 +807,7 @@ plugin_routing:
|
||||
flowdock:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||
there is no clear path to update.
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
notification.flowdock:
|
||||
redirect: community.general.flowdock
|
||||
deprecation:
|
||||
@@ -4459,8 +4446,7 @@ plugin_routing:
|
||||
webfaction_app:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||
there is no clear path to update.
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_app:
|
||||
redirect: community.general.webfaction_app
|
||||
deprecation:
|
||||
@@ -4471,8 +4457,7 @@ plugin_routing:
|
||||
webfaction_db:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||
there is no clear path to update.
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_db:
|
||||
redirect: community.general.webfaction_db
|
||||
deprecation:
|
||||
@@ -4483,8 +4468,7 @@ plugin_routing:
|
||||
webfaction_domain:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||
there is no clear path to update.
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_domain:
|
||||
redirect: community.general.webfaction_domain
|
||||
deprecation:
|
||||
@@ -4495,8 +4479,7 @@ plugin_routing:
|
||||
webfaction_mailbox:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||
there is no clear path to update.
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_mailbox:
|
||||
redirect: community.general.webfaction_mailbox
|
||||
deprecation:
|
||||
@@ -4507,8 +4490,7 @@ plugin_routing:
|
||||
webfaction_site:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and
|
||||
there is no clear path to update.
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_site:
|
||||
redirect: community.general.webfaction_site
|
||||
deprecation:
|
||||
@@ -4664,8 +4646,7 @@ plugin_routing:
|
||||
rackspace:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This doc fragment is used by rax modules, that rely on the deprecated
|
||||
package pyrax.
|
||||
warning_text: This doc fragment is used by rax modules, that rely on the deprecated package pyrax.
|
||||
_gcp:
|
||||
redirect: community.google._gcp
|
||||
docker:
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: default_without_diff
|
||||
type: stdout
|
||||
short_description: The default ansible callback without diff output
|
||||
version_added: 8.4.0
|
||||
description:
|
||||
- This is basically the default ansible callback plugin (P(ansible.builtin.default#callback)) without
|
||||
showing diff output. This can be useful when using another callback which sends more detailed information
|
||||
to another service, like the L(ARA, https://ara.recordsansible.org/) callback, and you want diff output
|
||||
sent to that plugin but not shown on the console output.
|
||||
author: Felix Fontein (@felixfontein)
|
||||
extends_documentation_fragment:
|
||||
- ansible.builtin.default_callback
|
||||
- ansible.builtin.result_format_callback
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Enable callback in ansible.cfg:
|
||||
ansible_config: |
|
||||
[defaults]
|
||||
stdout_callback = community.general.default_without_diff
|
||||
|
||||
# Enable callback with environment variables:
|
||||
environment_variable: |
|
||||
ANSIBLE_STDOUT_CALLBACK=community.general.default_without_diff
|
||||
'''
|
||||
|
||||
from ansible.plugins.callback.default import CallbackModule as Default
|
||||
|
||||
|
||||
class CallbackModule(Default):
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'stdout'
|
||||
CALLBACK_NAME = 'community.general.default_without_diff'
|
||||
|
||||
def v2_on_file_diff(self, result):
|
||||
pass
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,169 +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: inventory_hostname
|
||||
- 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)
|
||||
|
||||
@@ -16,9 +16,7 @@ DOCUMENTATION = '''
|
||||
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
|
||||
@@ -73,36 +71,30 @@ class Connection(ConnectionBase):
|
||||
if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
|
||||
self._display.warning('lxd does not support remote_user, using default: root')
|
||||
|
||||
def _host(self):
|
||||
""" translate remote_addr to lxd (short) hostname """
|
||||
return self.get_option("remote_addr").split(".", 1)[0]
|
||||
|
||||
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
|
||||
'''
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) Ansible project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment:
|
||||
# Common parameters for Consul modules
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- Host of the consul agent, defaults to V(localhost).
|
||||
default: localhost
|
||||
type: str
|
||||
port:
|
||||
type: int
|
||||
description:
|
||||
- The port on which the consul agent is running.
|
||||
default: 8500
|
||||
scheme:
|
||||
description:
|
||||
- The protocol scheme on which the consul agent is running.
|
||||
Defaults to V(http) and can be set to V(https) for secure connections.
|
||||
default: http
|
||||
type: str
|
||||
validate_certs:
|
||||
type: bool
|
||||
description:
|
||||
- Whether to verify the TLS certificate of the consul agent.
|
||||
default: true
|
||||
ca_path:
|
||||
description:
|
||||
- The CA bundle to use for https connections
|
||||
type: str
|
||||
"""
|
||||
|
||||
TOKEN = r"""
|
||||
options:
|
||||
token:
|
||||
description:
|
||||
- The token to use for authorization.
|
||||
type: str
|
||||
"""
|
||||
|
||||
ACTIONGROUP_CONSUL = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
action_group:
|
||||
description: Use C(group/community.general.consul) in C(module_defaults) to set defaults for this module.
|
||||
support: full
|
||||
membership:
|
||||
- community.general.consul
|
||||
version_added: 8.3.0
|
||||
"""
|
||||
@@ -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,210 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.common.collections import is_sequence
|
||||
|
||||
|
||||
def remove_duplicates(lst):
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
result = []
|
||||
for item in lst:
|
||||
try:
|
||||
if item not in seen:
|
||||
seen_add(item)
|
||||
result.append(item)
|
||||
except TypeError:
|
||||
# This happens for unhashable values `item`. If this happens,
|
||||
# convert `seen` to a list and continue.
|
||||
seen = list(seen)
|
||||
seen_add = seen.append
|
||||
if item not in seen:
|
||||
seen_add(item)
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def flatten_list(lst):
|
||||
result = []
|
||||
for sublist in lst:
|
||||
if not is_sequence(sublist):
|
||||
msg = ("All arguments must be lists. %s is %s")
|
||||
raise AnsibleFilterError(msg % (sublist, type(sublist)))
|
||||
if len(sublist) > 0:
|
||||
if all(is_sequence(sub) for sub in sublist):
|
||||
for item in sublist:
|
||||
result.append(item)
|
||||
else:
|
||||
result.append(sublist)
|
||||
return result
|
||||
|
||||
|
||||
def lists_union(*args, **kwargs):
|
||||
lists = args
|
||||
flatten = kwargs.pop('flatten', False)
|
||||
|
||||
if kwargs:
|
||||
# Some unused kwargs remain
|
||||
raise AnsibleFilterError(
|
||||
"lists_union() got unexpected keywords arguments: {0}".format(
|
||||
", ".join(kwargs.keys())
|
||||
)
|
||||
)
|
||||
|
||||
if flatten:
|
||||
lists = flatten_list(args)
|
||||
|
||||
if not lists:
|
||||
return []
|
||||
|
||||
if len(lists) == 1:
|
||||
return lists[0]
|
||||
|
||||
a = lists[0]
|
||||
for b in lists[1:]:
|
||||
a = do_union(a, b)
|
||||
return remove_duplicates(a)
|
||||
|
||||
|
||||
def do_union(a, b):
|
||||
return a + b
|
||||
|
||||
|
||||
def lists_intersect(*args, **kwargs):
|
||||
lists = args
|
||||
flatten = kwargs.pop('flatten', False)
|
||||
|
||||
if kwargs:
|
||||
# Some unused kwargs remain
|
||||
raise AnsibleFilterError(
|
||||
"lists_intersect() got unexpected keywords arguments: {0}".format(
|
||||
", ".join(kwargs.keys())
|
||||
)
|
||||
)
|
||||
|
||||
if flatten:
|
||||
lists = flatten_list(args)
|
||||
|
||||
if not lists:
|
||||
return []
|
||||
|
||||
if len(lists) == 1:
|
||||
return lists[0]
|
||||
|
||||
a = remove_duplicates(lists[0])
|
||||
for b in lists[1:]:
|
||||
a = do_intersect(a, b)
|
||||
return a
|
||||
|
||||
|
||||
def do_intersect(a, b):
|
||||
isect = []
|
||||
try:
|
||||
other = set(b)
|
||||
isect = [item for item in a if item in other]
|
||||
except TypeError:
|
||||
# This happens for unhashable values,
|
||||
# use a list instead and redo.
|
||||
other = list(b)
|
||||
isect = [item for item in a if item in other]
|
||||
return isect
|
||||
|
||||
|
||||
def lists_difference(*args, **kwargs):
|
||||
lists = args
|
||||
flatten = kwargs.pop('flatten', False)
|
||||
|
||||
if kwargs:
|
||||
# Some unused kwargs remain
|
||||
raise AnsibleFilterError(
|
||||
"lists_difference() got unexpected keywords arguments: {0}".format(
|
||||
", ".join(kwargs.keys())
|
||||
)
|
||||
)
|
||||
|
||||
if flatten:
|
||||
lists = flatten_list(args)
|
||||
|
||||
if not lists:
|
||||
return []
|
||||
|
||||
if len(lists) == 1:
|
||||
return lists[0]
|
||||
|
||||
a = remove_duplicates(lists[0])
|
||||
for b in lists[1:]:
|
||||
a = do_difference(a, b)
|
||||
return a
|
||||
|
||||
|
||||
def do_difference(a, b):
|
||||
diff = []
|
||||
try:
|
||||
other = set(b)
|
||||
diff = [item for item in a if item not in other]
|
||||
except TypeError:
|
||||
# This happens for unhashable values,
|
||||
# use a list instead and redo.
|
||||
other = list(b)
|
||||
diff = [item for item in a if item not in other]
|
||||
return diff
|
||||
|
||||
|
||||
def lists_symmetric_difference(*args, **kwargs):
|
||||
lists = args
|
||||
flatten = kwargs.pop('flatten', False)
|
||||
|
||||
if kwargs:
|
||||
# Some unused kwargs remain
|
||||
raise AnsibleFilterError(
|
||||
"lists_difference() got unexpected keywords arguments: {0}".format(
|
||||
", ".join(kwargs.keys())
|
||||
)
|
||||
)
|
||||
|
||||
if flatten:
|
||||
lists = flatten_list(args)
|
||||
|
||||
if not lists:
|
||||
return []
|
||||
|
||||
if len(lists) == 1:
|
||||
return lists[0]
|
||||
|
||||
a = lists[0]
|
||||
for b in lists[1:]:
|
||||
a = do_symmetric_difference(a, b)
|
||||
return a
|
||||
|
||||
|
||||
def do_symmetric_difference(a, b):
|
||||
sym_diff = []
|
||||
union = lists_union(a, b)
|
||||
try:
|
||||
isect = set(a) & set(b)
|
||||
sym_diff = [item for item in union if item not in isect]
|
||||
except TypeError:
|
||||
# This happens for unhashable values,
|
||||
# build the intersection of `a` and `b` backed
|
||||
# by a list instead of a set and redo.
|
||||
isect = lists_intersect(a, b)
|
||||
sym_diff = [item for item in union if item not in isect]
|
||||
return sym_diff
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
''' Ansible lists jinja2 filters '''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'lists_union': lists_union,
|
||||
'lists_intersect': lists_intersect,
|
||||
'lists_difference': lists_difference,
|
||||
'lists_symmetric_difference': lists_symmetric_difference,
|
||||
}
|
||||
@@ -1,48 +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
|
||||
|
||||
DOCUMENTATION:
|
||||
name: lists_difference
|
||||
short_description: Difference of lists with a predictive order
|
||||
version_added: 8.4.0
|
||||
description:
|
||||
- Provide a unique list of all the elements from the first which do not appear in the other lists.
|
||||
- The order of the items in the resulting list is preserved.
|
||||
options:
|
||||
_input:
|
||||
description: A list.
|
||||
type: list
|
||||
elements: any
|
||||
required: true
|
||||
flatten:
|
||||
description: Whether to remove one hierarchy level from the input list.
|
||||
type: boolean
|
||||
default: false
|
||||
author:
|
||||
- Christoph Fiehe (@cfiehe)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Return the difference of list1 and list2.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ list1 | community.general.lists_difference(list2) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
# => [10]
|
||||
|
||||
- name: Return the difference of list1, list2 and list3.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ [list1, list2, list3] | community.general.lists_difference(flatten=true) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
list3: [1, 2, 3, 4, 5, 10, 99, 101]
|
||||
# => []
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: A unique list of all the elements from the first list that do not appear on the other lists.
|
||||
type: list
|
||||
elements: any
|
||||
@@ -1,48 +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
|
||||
|
||||
DOCUMENTATION:
|
||||
name: lists_intersect
|
||||
short_description: Intersection of lists with a predictive order
|
||||
version_added: 8.4.0
|
||||
description:
|
||||
- Provide a unique list of all the common elements of two or more lists.
|
||||
- The order of the items in the resulting list is preserved.
|
||||
options:
|
||||
_input:
|
||||
description: A list.
|
||||
type: list
|
||||
elements: any
|
||||
required: true
|
||||
flatten:
|
||||
description: Whether to remove one hierarchy level from the input list.
|
||||
type: boolean
|
||||
default: false
|
||||
author:
|
||||
- Christoph Fiehe (@cfiehe)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Return the intersection of list1 and list2.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ list1 | community.general.lists_intersect(list2) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
# => [1, 2, 5, 3, 4]
|
||||
|
||||
- name: Return the intersection of list1, list2 and list3.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ [list1, list2, list3] | community.general.lists_intersect(flatten=true) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
list3: [1, 2, 3, 4, 5, 10, 99, 101]
|
||||
# => [1, 2, 5, 3, 4]
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: A unique list of all the common elements from the provided lists.
|
||||
type: list
|
||||
elements: any
|
||||
@@ -1,48 +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
|
||||
|
||||
DOCUMENTATION:
|
||||
name: lists_symmetric_difference
|
||||
short_description: Symmetric Difference of lists with a predictive order
|
||||
version_added: 8.4.0
|
||||
description:
|
||||
- Provide a unique list containing the symmetric difference of two or more lists.
|
||||
- The order of the items in the resulting list is preserved.
|
||||
options:
|
||||
_input:
|
||||
description: A list.
|
||||
type: list
|
||||
elements: any
|
||||
required: true
|
||||
flatten:
|
||||
description: Whether to remove one hierarchy level from the input list.
|
||||
type: boolean
|
||||
default: false
|
||||
author:
|
||||
- Christoph Fiehe (@cfiehe)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Return the symmetric difference of list1 and list2.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ list1 | community.general.lists_symmetric_difference(list2) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
# => [10, 11, 99]
|
||||
|
||||
- name: Return the symmetric difference of list1, list2 and list3.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ [list1, list2, list3] | community.general.lists_symmetric_difference(flatten=true) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
list3: [1, 2, 3, 4, 5, 10, 99, 101]
|
||||
# => [11, 1, 2, 3, 4, 5, 101]
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: A unique list containing the symmetric difference of two or more lists.
|
||||
type: list
|
||||
elements: any
|
||||
@@ -1,48 +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
|
||||
|
||||
DOCUMENTATION:
|
||||
name: lists_union
|
||||
short_description: Union of lists with a predictive order
|
||||
version_added: 8.4.0
|
||||
description:
|
||||
- Provide a unique list of all the elements of two or more lists.
|
||||
- The order of the items in the resulting list is preserved.
|
||||
options:
|
||||
_input:
|
||||
description: A list.
|
||||
type: list
|
||||
elements: any
|
||||
required: true
|
||||
flatten:
|
||||
description: Whether to remove one hierarchy level from the input list.
|
||||
type: boolean
|
||||
default: false
|
||||
author:
|
||||
- Christoph Fiehe (@cfiehe)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Return the union of list1, list2 and list3.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ list1 | community.general.lists_union(list2, list3) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
list3: [1, 2, 3, 4, 5, 10, 99, 101]
|
||||
# => [1, 2, 5, 3, 4, 10, 11, 99, 101]
|
||||
|
||||
- name: Return the union of list1 and list2.
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ [list1, list2] | community.general.lists_union(flatten=true) }}"
|
||||
vars:
|
||||
list1: [1, 2, 5, 3, 4, 10]
|
||||
list2: [1, 2, 3, 4, 5, 11, 99]
|
||||
# => [1, 2, 5, 3, 4, 10, 11, 99]
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: A unique list of all the elements from the provided lists.
|
||||
type: list
|
||||
elements: any
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -277,22 +277,12 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
self._read_config_data(path)
|
||||
|
||||
# Store the options from the YAML file
|
||||
self.icinga2_url = self.get_option('url')
|
||||
self.icinga2_url = self.get_option('url').rstrip('/') + '/v1'
|
||||
self.icinga2_user = self.get_option('user')
|
||||
self.icinga2_password = self.get_option('password')
|
||||
self.ssl_verify = self.get_option('validate_certs')
|
||||
self.host_filter = self.get_option('host_filter')
|
||||
self.inventory_attr = self.get_option('inventory_attr')
|
||||
|
||||
if self.templar.is_template(self.icinga2_url):
|
||||
self.icinga2_url = self.templar.template(variable=self.icinga2_url, disable_lookups=False)
|
||||
if self.templar.is_template(self.icinga2_user):
|
||||
self.icinga2_user = self.templar.template(variable=self.icinga2_user, disable_lookups=False)
|
||||
if self.templar.is_template(self.icinga2_password):
|
||||
self.icinga2_password = self.templar.template(variable=self.icinga2_password, disable_lookups=False)
|
||||
|
||||
self.icinga2_url = self.icinga2_url.rstrip('/') + '/v1'
|
||||
|
||||
# Not currently enabled
|
||||
# self.cache_key = self.get_cache_key(path)
|
||||
# self.use_cache = cache and self.get_option('cache')
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -300,7 +286,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
|
||||
@@ -1092,8 +1078,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
|
||||
|
||||
@@ -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:
|
||||
@@ -569,9 +565,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 +577,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 +635,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')
|
||||
|
||||
@@ -39,10 +39,6 @@ DOCUMENTATION = """
|
||||
description: Collection ID to filter results by collection. Leave unset to skip filtering.
|
||||
type: str
|
||||
version_added: 6.3.0
|
||||
bw_session:
|
||||
description: Pass session key instead of reading from env.
|
||||
type: str
|
||||
version_added: 8.4.0
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
@@ -70,11 +66,6 @@ EXAMPLES = """
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ lookup('community.general.bitwarden', 'a_test', field='api_key') }}
|
||||
|
||||
- name: "Get 'password' from all Bitwarden records named 'a_test', using given session key"
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ lookup('community.general.bitwarden', 'a_test', field='password', bw_session='bXZ9B5TXi6...') }}
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -103,20 +94,11 @@ class Bitwarden(object):
|
||||
|
||||
def __init__(self, path='bw'):
|
||||
self._cli_path = path
|
||||
self._session = None
|
||||
|
||||
@property
|
||||
def cli_path(self):
|
||||
return self._cli_path
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
return self._session
|
||||
|
||||
@session.setter
|
||||
def session(self, value):
|
||||
self._session = value
|
||||
|
||||
@property
|
||||
def unlocked(self):
|
||||
out, err = self._run(['status'], stdin="")
|
||||
@@ -124,15 +106,10 @@ class Bitwarden(object):
|
||||
return decoded['status'] == 'unlocked'
|
||||
|
||||
def _run(self, args, stdin=None, expected_rc=0):
|
||||
if self.session:
|
||||
args += ['--session', self.session]
|
||||
|
||||
p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
||||
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')
|
||||
|
||||
@@ -141,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])
|
||||
@@ -153,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]
|
||||
|
||||
@@ -200,8 +170,6 @@ class LookupModule(LookupBase):
|
||||
field = self.get_option('field')
|
||||
search_field = self.get_option('search')
|
||||
collection_id = self.get_option('collection_id')
|
||||
_bitwarden.session = self.get_option('bw_session')
|
||||
|
||||
if not _bitwarden.unlocked:
|
||||
raise AnsibleError("Bitwarden Vault locked. Run 'bw unlock'.")
|
||||
|
||||
|
||||
@@ -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]
|
||||
@@ -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.")
|
||||
@@ -498,9 +521,6 @@ class OnePassCLIv2(OnePassCLIBase):
|
||||
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):
|
||||
|
||||
@@ -5,317 +5,25 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
from ansible.module_utils.six.moves.urllib import error as urllib_error
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
|
||||
def get_consul_url(configuration):
|
||||
return "%s://%s:%s/v1" % (
|
||||
configuration.scheme,
|
||||
configuration.host,
|
||||
configuration.port,
|
||||
)
|
||||
return '%s://%s:%s/v1' % (configuration.scheme,
|
||||
configuration.host, configuration.port)
|
||||
|
||||
|
||||
def get_auth_headers(configuration):
|
||||
if configuration.token is None:
|
||||
return {}
|
||||
else:
|
||||
return {"X-Consul-Token": configuration.token}
|
||||
return {'X-Consul-Token': configuration.token}
|
||||
|
||||
|
||||
class RequestError(Exception):
|
||||
def __init__(self, status, response_data=None):
|
||||
self.status = status
|
||||
self.response_data = response_data
|
||||
|
||||
def __str__(self):
|
||||
if self.response_data is None:
|
||||
# self.status is already the message (backwards compat)
|
||||
return self.status
|
||||
return "HTTP %d: %s" % (self.status, self.response_data)
|
||||
pass
|
||||
|
||||
|
||||
def handle_consul_response_error(response):
|
||||
if 400 <= response.status_code < 600:
|
||||
raise RequestError("%d %s" % (response.status_code, response.content))
|
||||
|
||||
|
||||
AUTH_ARGUMENTS_SPEC = dict(
|
||||
host=dict(default="localhost"),
|
||||
port=dict(type="int", default=8500),
|
||||
scheme=dict(default="http"),
|
||||
validate_certs=dict(type="bool", default=True),
|
||||
token=dict(no_log=True),
|
||||
ca_path=dict(),
|
||||
)
|
||||
|
||||
|
||||
def camel_case_key(key):
|
||||
parts = []
|
||||
for part in key.split("_"):
|
||||
if part in {"id", "ttl", "jwks", "jwt", "oidc", "iam", "sts"}:
|
||||
parts.append(part.upper())
|
||||
else:
|
||||
parts.append(part.capitalize())
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
STATE_PARAMETER = "state"
|
||||
STATE_PRESENT = "present"
|
||||
STATE_ABSENT = "absent"
|
||||
|
||||
OPERATION_READ = "read"
|
||||
OPERATION_CREATE = "create"
|
||||
OPERATION_UPDATE = "update"
|
||||
OPERATION_DELETE = "remove"
|
||||
|
||||
|
||||
def _normalize_params(params, arg_spec):
|
||||
final_params = {}
|
||||
for k, v in params.items():
|
||||
if k not in arg_spec: # Alias
|
||||
continue
|
||||
spec = arg_spec[k]
|
||||
if (
|
||||
spec.get("type") == "list"
|
||||
and spec.get("elements") == "dict"
|
||||
and spec.get("options")
|
||||
and v
|
||||
):
|
||||
v = [_normalize_params(d, spec["options"]) for d in v]
|
||||
elif spec.get("type") == "dict" and spec.get("options") and v:
|
||||
v = _normalize_params(v, spec["options"])
|
||||
final_params[k] = v
|
||||
return final_params
|
||||
|
||||
|
||||
class _ConsulModule:
|
||||
"""Base class for Consul modules.
|
||||
|
||||
This class is considered private, till the API is fully fleshed out.
|
||||
As such backwards incompatible changes can occur even in bugfix releases.
|
||||
"""
|
||||
|
||||
api_endpoint = None # type: str
|
||||
unique_identifier = None # type: str
|
||||
result_key = None # type: str
|
||||
create_only_fields = set()
|
||||
params = {}
|
||||
|
||||
def __init__(self, module):
|
||||
self._module = module
|
||||
self.params = _normalize_params(module.params, module.argument_spec)
|
||||
self.api_params = {
|
||||
k: camel_case_key(k)
|
||||
for k in self.params
|
||||
if k not in STATE_PARAMETER and k not in AUTH_ARGUMENTS_SPEC
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
obj = self.read_object()
|
||||
|
||||
changed = False
|
||||
diff = {}
|
||||
if self.params[STATE_PARAMETER] == STATE_PRESENT:
|
||||
obj_from_module = self.module_to_obj(obj is not None)
|
||||
if obj is None:
|
||||
operation = OPERATION_CREATE
|
||||
new_obj = self.create_object(obj_from_module)
|
||||
diff = {"before": {}, "after": new_obj}
|
||||
changed = True
|
||||
else:
|
||||
operation = OPERATION_UPDATE
|
||||
if self._needs_update(obj, obj_from_module):
|
||||
new_obj = self.update_object(obj, obj_from_module)
|
||||
diff = {"before": obj, "after": new_obj}
|
||||
changed = True
|
||||
else:
|
||||
new_obj = obj
|
||||
elif self.params[STATE_PARAMETER] == STATE_ABSENT:
|
||||
operation = OPERATION_DELETE
|
||||
if obj is not None:
|
||||
self.delete_object(obj)
|
||||
changed = True
|
||||
diff = {"before": obj, "after": {}}
|
||||
else:
|
||||
diff = {"before": {}, "after": {}}
|
||||
new_obj = None
|
||||
else:
|
||||
raise RuntimeError("Unknown state supplied.")
|
||||
|
||||
result = {"changed": changed}
|
||||
if changed:
|
||||
result["operation"] = operation
|
||||
if self._module._diff:
|
||||
result["diff"] = diff
|
||||
if self.result_key:
|
||||
result[self.result_key] = new_obj
|
||||
self._module.exit_json(**result)
|
||||
|
||||
def module_to_obj(self, is_update):
|
||||
obj = {}
|
||||
for k, v in self.params.items():
|
||||
result = self.map_param(k, v, is_update)
|
||||
if result:
|
||||
obj[result[0]] = result[1]
|
||||
return obj
|
||||
|
||||
def map_param(self, k, v, is_update):
|
||||
def helper(item):
|
||||
return {camel_case_key(k): v for k, v in item.items()}
|
||||
|
||||
def needs_camel_case(k):
|
||||
spec = self._module.argument_spec[k]
|
||||
return (
|
||||
spec.get("type") == "list"
|
||||
and spec.get("elements") == "dict"
|
||||
and spec.get("options")
|
||||
) or (spec.get("type") == "dict" and spec.get("options"))
|
||||
|
||||
if k in self.api_params and v is not None:
|
||||
if isinstance(v, dict) and needs_camel_case(k):
|
||||
v = helper(v)
|
||||
elif isinstance(v, (list, tuple)) and needs_camel_case(k):
|
||||
v = [helper(i) for i in v]
|
||||
if is_update and k in self.create_only_fields:
|
||||
return
|
||||
return camel_case_key(k), v
|
||||
|
||||
def _needs_update(self, api_obj, module_obj):
|
||||
api_obj = copy.deepcopy(api_obj)
|
||||
module_obj = copy.deepcopy(module_obj)
|
||||
return self.needs_update(api_obj, module_obj)
|
||||
|
||||
def needs_update(self, api_obj, module_obj):
|
||||
for k, v in module_obj.items():
|
||||
if k not in api_obj:
|
||||
return True
|
||||
if api_obj[k] != v:
|
||||
return True
|
||||
return False
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
operational_attributes = {"CreateIndex", "CreateTime", "Hash", "ModifyIndex"}
|
||||
existing = {
|
||||
k: v for k, v in existing.items() if k not in operational_attributes
|
||||
}
|
||||
for k, v in obj.items():
|
||||
existing[k] = v
|
||||
return existing
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_CREATE:
|
||||
return self.api_endpoint
|
||||
elif identifier:
|
||||
return "/".join([self.api_endpoint, identifier])
|
||||
raise RuntimeError("invalid arguments passed")
|
||||
|
||||
def read_object(self):
|
||||
url = self.endpoint_url(OPERATION_READ, self.params.get(self.unique_identifier))
|
||||
try:
|
||||
return self.get(url)
|
||||
except RequestError as e:
|
||||
if e.status == 404:
|
||||
return
|
||||
elif e.status == 403 and b"ACL not found" in e.response_data:
|
||||
return
|
||||
raise
|
||||
|
||||
def create_object(self, obj):
|
||||
if self._module.check_mode:
|
||||
return obj
|
||||
else:
|
||||
return self.put(self.api_endpoint, data=self.prepare_object({}, obj))
|
||||
|
||||
def update_object(self, existing, obj):
|
||||
url = self.endpoint_url(
|
||||
OPERATION_UPDATE, existing.get(camel_case_key(self.unique_identifier))
|
||||
)
|
||||
merged_object = self.prepare_object(existing, obj)
|
||||
if self._module.check_mode:
|
||||
return merged_object
|
||||
else:
|
||||
return self.put(url, data=merged_object)
|
||||
|
||||
def delete_object(self, obj):
|
||||
if self._module.check_mode:
|
||||
return {}
|
||||
else:
|
||||
url = self.endpoint_url(
|
||||
OPERATION_DELETE, obj.get(camel_case_key(self.unique_identifier))
|
||||
)
|
||||
return self.delete(url)
|
||||
|
||||
def _request(self, method, url_parts, data=None, params=None):
|
||||
module_params = self.params
|
||||
|
||||
if not isinstance(url_parts, (tuple, list)):
|
||||
url_parts = [url_parts]
|
||||
if params:
|
||||
# Remove values that are None
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
ca_path = module_params.get("ca_path")
|
||||
base_url = "%s://%s:%s/v1" % (
|
||||
module_params["scheme"],
|
||||
module_params["host"],
|
||||
module_params["port"],
|
||||
)
|
||||
url = "/".join([base_url] + list(url_parts))
|
||||
|
||||
headers = {}
|
||||
token = self.params.get("token")
|
||||
if token:
|
||||
headers["X-Consul-Token"] = token
|
||||
|
||||
try:
|
||||
if data:
|
||||
data = json.dumps(data)
|
||||
headers["Content-Type"] = "application/json"
|
||||
if params:
|
||||
url = "%s?%s" % (url, urlencode(params))
|
||||
response = open_url(
|
||||
url,
|
||||
method=method,
|
||||
data=data,
|
||||
headers=headers,
|
||||
validate_certs=module_params["validate_certs"],
|
||||
ca_path=ca_path,
|
||||
)
|
||||
response_data = response.read()
|
||||
status = (
|
||||
response.status if hasattr(response, "status") else response.getcode()
|
||||
)
|
||||
|
||||
except urllib_error.URLError as e:
|
||||
if isinstance(e, urllib_error.HTTPError):
|
||||
status = e.code
|
||||
response_data = e.fp.read()
|
||||
else:
|
||||
self._module.fail_json(
|
||||
msg="Could not connect to consul agent at %s:%s, error was %s"
|
||||
% (module_params["host"], module_params["port"], str(e))
|
||||
)
|
||||
raise
|
||||
|
||||
if 400 <= status < 600:
|
||||
raise RequestError(status, response_data)
|
||||
|
||||
return json.loads(response_data)
|
||||
|
||||
def get(self, url_parts, **kwargs):
|
||||
return self._request("GET", url_parts, **kwargs)
|
||||
|
||||
def put(self, url_parts, **kwargs):
|
||||
return self._request("PUT", url_parts, **kwargs)
|
||||
|
||||
def delete(self, url_parts, **kwargs):
|
||||
return self._request("DELETE", url_parts, **kwargs)
|
||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
||||
|
||||
@@ -21,35 +21,19 @@ 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),
|
||||
@@ -74,11 +58,11 @@ def find_project(gitlab_instance, identifier):
|
||||
|
||||
def find_group(gitlab_instance, identifier):
|
||||
try:
|
||||
group = gitlab_instance.groups.get(identifier)
|
||||
project = gitlab_instance.groups.get(identifier)
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
return group
|
||||
return project
|
||||
|
||||
|
||||
def ensure_gitlab_package(module):
|
||||
@@ -90,36 +74,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)
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -2962,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']
|
||||
@@ -3372,8 +3372,7 @@ class RedfishUtils(object):
|
||||
inventory = {}
|
||||
# Get these entries, but does not fail if not found
|
||||
properties = ['Id', 'FirmwareVersion', 'ManagerType', 'Manufacturer', 'Model',
|
||||
'PartNumber', 'PowerState', 'SerialNumber', 'ServiceIdentification',
|
||||
'Status', 'UUID']
|
||||
'PartNumber', 'PowerState', 'SerialNumber', 'Status', 'UUID']
|
||||
|
||||
response = self.get_request(self.root_uri + manager_uri)
|
||||
if response['ret'] is False:
|
||||
@@ -3391,35 +3390,6 @@ class RedfishUtils(object):
|
||||
def get_multi_manager_inventory(self):
|
||||
return self.aggregate_managers(self.get_manager_inventory)
|
||||
|
||||
def get_service_identification(self, manager):
|
||||
result = {}
|
||||
if manager is None:
|
||||
if len(self.manager_uris) == 1:
|
||||
manager = self.manager_uris[0].split('/')[-1]
|
||||
elif len(self.manager_uris) > 1:
|
||||
entries = self.get_multi_manager_inventory()['entries']
|
||||
managers = [m[0]['manager_uri'] for m in entries if m[1].get('ServiceIdentification')]
|
||||
if len(managers) == 1:
|
||||
manager = managers[0].split('/')[-1]
|
||||
else:
|
||||
self.module.fail_json(msg=[
|
||||
"Multiple managers with ServiceIdentification were found: %s" % str(managers),
|
||||
"Please specify by using the 'manager' parameter in your playbook"])
|
||||
elif len(self.manager_uris) == 0:
|
||||
self.module.fail_json(msg="No manager identities were found")
|
||||
response = self.get_request(self.root_uri + '/redfish/v1/Managers/' + manager, override_headers=None)
|
||||
try:
|
||||
result['service_identification'] = response['data']['ServiceIdentification']
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Service ID not found for manager %s" % manager)
|
||||
result['ret'] = True
|
||||
return result
|
||||
|
||||
def set_service_identification(self, service_id):
|
||||
data = {"ServiceIdentification": service_id}
|
||||
resp = self.patch_request(self.root_uri + '/redfish/v1/Managers/' + self.resource_id, data, check_pyld=True)
|
||||
return resp
|
||||
|
||||
def set_session_service(self, sessions_config):
|
||||
if sessions_config is None:
|
||||
return {'ret': False, 'msg':
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -21,6 +21,7 @@ notes:
|
||||
- Host should be an atomic platform (verified by existence of '/run/ostree-booted' file).
|
||||
requirements:
|
||||
- atomic
|
||||
- python >= 2.6
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
@@ -21,6 +21,7 @@ notes:
|
||||
- Host should support C(atomic) command.
|
||||
requirements:
|
||||
- atomic
|
||||
- python >= 2.6
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
|
||||
@@ -45,7 +45,8 @@ options:
|
||||
force:
|
||||
description:
|
||||
- If V(true), any modified files in the working
|
||||
tree will be discarded.
|
||||
tree will be discarded. Before Ansible 1.9 the default
|
||||
value was V(true).
|
||||
type: bool
|
||||
default: false
|
||||
executable:
|
||||
|
||||
@@ -13,6 +13,8 @@ DOCUMENTATION = r'''
|
||||
module: cloudflare_dns
|
||||
author:
|
||||
- Michael Gruener (@mgruener)
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
short_description: Manage Cloudflare DNS records
|
||||
description:
|
||||
- "Manages dns records via the Cloudflare API, see the docs: U(https://api.cloudflare.com/)."
|
||||
@@ -57,20 +59,6 @@ options:
|
||||
- Required for O(type=TLSA) when O(state=present).
|
||||
type: int
|
||||
choices: [ 0, 1, 2, 3 ]
|
||||
flag:
|
||||
description:
|
||||
- Issuer Critical Flag.
|
||||
- Required for O(type=CAA) when O(state=present).
|
||||
type: int
|
||||
choices: [ 0, 1 ]
|
||||
version_added: 8.0.0
|
||||
tag:
|
||||
description:
|
||||
- CAA issue restriction.
|
||||
- Required for O(type=CAA) when O(state=present).
|
||||
type: str
|
||||
choices: [ issue, issuewild, iodef ]
|
||||
version_added: 8.0.0
|
||||
hash_type:
|
||||
description:
|
||||
- Hash type number.
|
||||
@@ -97,6 +85,7 @@ options:
|
||||
description:
|
||||
- Service protocol. Required for O(type=SRV) and O(type=TLSA).
|
||||
- Common values are TCP and UDP.
|
||||
- Before Ansible 2.6 only TCP and UDP were available.
|
||||
type: str
|
||||
proxied:
|
||||
description:
|
||||
@@ -148,9 +137,10 @@ options:
|
||||
type:
|
||||
description:
|
||||
- The type of DNS record to create. Required if O(state=present).
|
||||
- O(type=DS), O(type=SSHFP), and O(type=TLSA) were added in Ansible 2.7.
|
||||
- Note that V(SPF) is no longer supported by CloudFlare. Support for it will be removed from community.general 9.0.0.
|
||||
type: str
|
||||
choices: [ A, AAAA, CNAME, DS, MX, NS, SPF, SRV, SSHFP, TLSA, CAA, TXT ]
|
||||
choices: [ A, AAAA, CNAME, DS, MX, NS, SPF, SRV, SSHFP, TLSA, TXT ]
|
||||
value:
|
||||
description:
|
||||
- The record value.
|
||||
@@ -273,15 +263,6 @@ EXAMPLES = r'''
|
||||
hash_type: 1
|
||||
value: 6b76d034492b493e15a7376fccd08e63befdad0edab8e442562f532338364bf3
|
||||
|
||||
- name: Create a CAA record subdomain.example.com
|
||||
community.general.cloudflare_dns:
|
||||
zone: example.com
|
||||
record: subdomain
|
||||
type: CAA
|
||||
flag: 0
|
||||
tag: issue
|
||||
value: ca.example.com
|
||||
|
||||
- name: Create a DS record for subdomain.example.com
|
||||
community.general.cloudflare_dns:
|
||||
zone: example.com
|
||||
@@ -311,7 +292,7 @@ record:
|
||||
sample: "2016-03-25T19:09:42.516553Z"
|
||||
data:
|
||||
description: Additional record data.
|
||||
returned: success, if type is SRV, DS, SSHFP TLSA or CAA
|
||||
returned: success, if type is SRV, DS, SSHFP or TLSA
|
||||
type: dict
|
||||
sample: {
|
||||
name: "jabber",
|
||||
@@ -411,8 +392,6 @@ class CloudflareAPI(object):
|
||||
self.algorithm = module.params['algorithm']
|
||||
self.cert_usage = module.params['cert_usage']
|
||||
self.hash_type = module.params['hash_type']
|
||||
self.flag = module.params['flag']
|
||||
self.tag = module.params['tag']
|
||||
self.key_tag = module.params['key_tag']
|
||||
self.port = module.params['port']
|
||||
self.priority = module.params['priority']
|
||||
@@ -617,7 +596,7 @@ class CloudflareAPI(object):
|
||||
def delete_dns_records(self, **kwargs):
|
||||
params = {}
|
||||
for param in ['port', 'proto', 'service', 'solo', 'type', 'record', 'value', 'weight', 'zone',
|
||||
'algorithm', 'cert_usage', 'hash_type', 'selector', 'key_tag', 'flag', 'tag']:
|
||||
'algorithm', 'cert_usage', 'hash_type', 'selector', 'key_tag']:
|
||||
if param in kwargs:
|
||||
params[param] = kwargs[param]
|
||||
else:
|
||||
@@ -662,7 +641,7 @@ class CloudflareAPI(object):
|
||||
def ensure_dns_record(self, **kwargs):
|
||||
params = {}
|
||||
for param in ['port', 'priority', 'proto', 'proxied', 'service', 'ttl', 'type', 'record', 'value', 'weight', 'zone',
|
||||
'algorithm', 'cert_usage', 'hash_type', 'selector', 'key_tag', 'flag', 'tag']:
|
||||
'algorithm', 'cert_usage', 'hash_type', 'selector', 'key_tag']:
|
||||
if param in kwargs:
|
||||
params[param] = kwargs[param]
|
||||
else:
|
||||
@@ -779,36 +758,12 @@ class CloudflareAPI(object):
|
||||
}
|
||||
search_value = str(params['cert_usage']) + '\t' + str(params['selector']) + '\t' + str(params['hash_type']) + '\t' + params['value']
|
||||
|
||||
if params['type'] == 'CAA':
|
||||
for attr in [params['flag'], params['tag'], params['value']]:
|
||||
if (attr is None) or (attr == ''):
|
||||
self.module.fail_json(msg="You must provide flag, tag and a value to create this record type")
|
||||
caa_data = {
|
||||
"flags": params['flag'],
|
||||
"tag": params['tag'],
|
||||
"value": params['value'],
|
||||
}
|
||||
new_record = {
|
||||
"type": params['type'],
|
||||
"name": params['record'],
|
||||
'data': caa_data,
|
||||
"ttl": params['ttl'],
|
||||
}
|
||||
search_value = None
|
||||
|
||||
zone_id = self._get_zone_id(params['zone'])
|
||||
records = self.get_dns_records(params['zone'], params['type'], search_record, search_value)
|
||||
# in theory this should be impossible as cloudflare does not allow
|
||||
# the creation of duplicate records but lets cover it anyways
|
||||
if len(records) > 1:
|
||||
# As Cloudflare API cannot filter record containing quotes
|
||||
# CAA records must be compared locally
|
||||
if params['type'] == 'CAA':
|
||||
for rr in records:
|
||||
if rr['data']['flags'] == caa_data['flags'] and rr['data']['tag'] == caa_data['tag'] and rr['data']['value'] == caa_data['value']:
|
||||
return rr, self.changed
|
||||
else:
|
||||
self.module.fail_json(msg="More than one record already exists for the given attributes. That should be impossible, please open an issue!")
|
||||
self.module.fail_json(msg="More than one record already exists for the given attributes. That should be impossible, please open an issue!")
|
||||
# record already exists, check if it must be updated
|
||||
if len(records) == 1:
|
||||
cur_record = records[0]
|
||||
@@ -857,8 +812,6 @@ def main():
|
||||
hash_type=dict(type='int', choices=[1, 2]),
|
||||
key_tag=dict(type='int', no_log=False),
|
||||
port=dict(type='int'),
|
||||
flag=dict(type='int', choices=[0, 1]),
|
||||
tag=dict(type='str', choices=['issue', 'issuewild', 'iodef']),
|
||||
priority=dict(type='int', default=1),
|
||||
proto=dict(type='str'),
|
||||
proxied=dict(type='bool', default=False),
|
||||
@@ -869,7 +822,7 @@ def main():
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
timeout=dict(type='int', default=30),
|
||||
ttl=dict(type='int', default=1),
|
||||
type=dict(type='str', choices=['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'SPF', 'SRV', 'SSHFP', 'TLSA', 'CAA', 'TXT']),
|
||||
type=dict(type='str', choices=['A', 'AAAA', 'CNAME', 'DS', 'MX', 'NS', 'SPF', 'SRV', 'SSHFP', 'TLSA', 'TXT']),
|
||||
value=dict(type='str', aliases=['content']),
|
||||
weight=dict(type='int', default=1),
|
||||
zone=dict(type='str', required=True, aliases=['domain']),
|
||||
@@ -880,7 +833,6 @@ def main():
|
||||
('state', 'absent', ['record']),
|
||||
('type', 'SRV', ['proto', 'service']),
|
||||
('type', 'TLSA', ['proto', 'port']),
|
||||
('type', 'CAA', ['flag', 'tag']),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -907,13 +859,6 @@ def main():
|
||||
and (module.params['value'] is None or module.params['value'] == ''))):
|
||||
module.fail_json(msg="For TLSA records the params cert_usage, selector, hash_type and value all need to be defined, or not at all.")
|
||||
|
||||
if module.params['type'] == 'CAA':
|
||||
if not ((module.params['flag'] is not None and module.params['tag'] is not None
|
||||
and not (module.params['value'] is None or module.params['value'] == ''))
|
||||
or (module.params['flag'] is None and module.params['tag'] is None
|
||||
and (module.params['value'] is None or module.params['value'] == ''))):
|
||||
module.fail_json(msg="For CAA records the params flag, tag and value all need to be defined, or not at all.")
|
||||
|
||||
if module.params['type'] == 'DS':
|
||||
if not ((module.params['key_tag'] is not None and module.params['algorithm'] is not None and module.params['hash_type'] is not None
|
||||
and not (module.params['value'] is None or module.params['value'] == ''))
|
||||
|
||||
@@ -104,7 +104,6 @@ options:
|
||||
description:
|
||||
- The script/command that will be run periodically to check the health of the service.
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(ttl), O(tcp) and O(http).
|
||||
interval:
|
||||
type: str
|
||||
description:
|
||||
@@ -122,16 +121,6 @@ options:
|
||||
description:
|
||||
- Name for the service check. Required if standalone, ignored if
|
||||
part of service definition.
|
||||
check_node:
|
||||
description:
|
||||
- Node name.
|
||||
# TODO: properly document!
|
||||
type: str
|
||||
check_host:
|
||||
description:
|
||||
- Host name.
|
||||
# TODO: properly document!
|
||||
type: str
|
||||
ttl:
|
||||
type: str
|
||||
description:
|
||||
@@ -142,7 +131,6 @@ options:
|
||||
Similar to the interval this is a number with a V(s) or V(m) suffix to
|
||||
signify the units of seconds or minutes, for example V(15s) or V(1m).
|
||||
If no suffix is supplied V(s) will be used by default, for example V(10) will be V(10s).
|
||||
- Mutually exclusive with O(script), O(tcp) and O(http).
|
||||
tcp:
|
||||
type: str
|
||||
description:
|
||||
@@ -150,7 +138,6 @@ options:
|
||||
will check if the connection attempt to that port is successful (that is, the port is currently accepting connections).
|
||||
The format is V(host:port), for example V(localhost:80).
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(script), O(ttl) and O(http).
|
||||
version_added: '1.3.0'
|
||||
http:
|
||||
type: str
|
||||
@@ -158,7 +145,6 @@ options:
|
||||
- Checks can be registered with an HTTP endpoint. This means that consul
|
||||
will check that the http endpoint returns a successful HTTP status.
|
||||
- Requires O(interval) to be provided.
|
||||
- Mutually exclusive with O(script), O(ttl) and O(tcp).
|
||||
timeout:
|
||||
type: str
|
||||
description:
|
||||
@@ -173,7 +159,7 @@ options:
|
||||
ack_params_state_absent:
|
||||
type: bool
|
||||
description:
|
||||
- This parameter has no more effect and is deprecated. It will be removed in community.general 10.0.0.
|
||||
- Disable deprecation warning when using parameters incompatible with O(state=absent).
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
@@ -391,7 +377,13 @@ def get_service_by_id_or_name(consul_api, service_id_or_name):
|
||||
|
||||
|
||||
def parse_check(module):
|
||||
if module.params['check_id'] or any(module.params[p] is not None for p in ('script', 'ttl', 'tcp', 'http')):
|
||||
_checks = [module.params[p] for p in ('script', 'ttl', 'tcp', 'http') if module.params[p]]
|
||||
|
||||
if len(_checks) > 1:
|
||||
module.fail_json(
|
||||
msg='checks are either script, tcp, http or ttl driven, supplying more than one does not make sense')
|
||||
|
||||
if module.params['check_id'] or _checks:
|
||||
return ConsulCheck(
|
||||
module.params['check_id'],
|
||||
module.params['check_name'],
|
||||
@@ -509,9 +501,15 @@ class ConsulCheck(object):
|
||||
self.check = consul.Check.ttl(self.ttl)
|
||||
|
||||
if http:
|
||||
if interval is None:
|
||||
raise Exception('http check must specify interval')
|
||||
|
||||
self.check = consul.Check.http(http, self.interval, self.timeout)
|
||||
|
||||
if tcp:
|
||||
if interval is None:
|
||||
raise Exception('tcp check must specify interval')
|
||||
|
||||
regex = r"(?P<host>.*):(?P<port>(?:[0-9]+))$"
|
||||
match = re.match(regex, tcp)
|
||||
|
||||
@@ -598,33 +596,30 @@ def main():
|
||||
timeout=dict(type='str'),
|
||||
tags=dict(type='list', elements='str'),
|
||||
token=dict(no_log=True),
|
||||
ack_params_state_absent=dict(
|
||||
type='bool',
|
||||
removed_in_version='10.0.0',
|
||||
removed_from_collection='community.general',
|
||||
),
|
||||
ack_params_state_absent=dict(type='bool'),
|
||||
),
|
||||
mutually_exclusive=[
|
||||
('script', 'ttl', 'tcp', 'http'),
|
||||
],
|
||||
required_if=[
|
||||
('state', 'present', ['service_name']),
|
||||
('state', 'absent', ['service_id', 'service_name', 'check_id', 'check_name'], True),
|
||||
],
|
||||
required_by={
|
||||
'script': 'interval',
|
||||
'http': 'interval',
|
||||
'tcp': 'interval',
|
||||
},
|
||||
supports_check_mode=False,
|
||||
)
|
||||
p = module.params
|
||||
|
||||
test_dependencies(module)
|
||||
if p['state'] == 'absent' and any(p[x] for x in ['script', 'ttl', 'tcp', 'http', 'interval']):
|
||||
module.fail_json(
|
||||
msg="The use of parameters 'script', 'ttl', 'tcp', 'http', 'interval' along with 'state=absent' is no longer allowed."
|
||||
if p['state'] == 'absent' and any(p[x] for x in ['script', 'ttl', 'tcp', 'http', 'interval']) and not p['ack_params_state_absent']:
|
||||
module.deprecate(
|
||||
"The use of parameters 'script', 'ttl', 'tcp', 'http', 'interval' along with 'state=absent' is deprecated. "
|
||||
"In community.general 8.0.0 their use will become an error. "
|
||||
"To suppress this deprecation notice, set parameter ack_params_state_absent=true.",
|
||||
version="8.0.0",
|
||||
collection_name="community.general",
|
||||
)
|
||||
# When reaching c.g 8.0.0:
|
||||
# - Replace the deprecation with a fail_json(), remove the "ack_params_state_absent" condition from the "if"
|
||||
# - Add mutually_exclusive for ('script', 'ttl', 'tcp', 'http'), then remove that validation from parse_check()
|
||||
# - Add required_by {'script': 'interval', 'http': 'interval', 'tcp': 'interval'}, then remove checks for 'interval' in ConsulCheck.__init__()
|
||||
# - Deprecate the parameter ack_params_state_absent
|
||||
|
||||
try:
|
||||
register_with_consul(module)
|
||||
|
||||
@@ -26,10 +26,6 @@ attributes:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
deprecated:
|
||||
removed_in: 10.0.0
|
||||
why: The legacy ACL system was removed from Consul.
|
||||
alternative: Use M(community.general.consul_token) and/or M(community.general.consul_policy) instead.
|
||||
options:
|
||||
mgmt_token:
|
||||
description:
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: consul_acl_bootstrap
|
||||
short_description: Bootstrap ACLs in Consul
|
||||
version_added: 8.3.0
|
||||
description:
|
||||
- Allows bootstrapping of ACLs in a Consul cluster, see
|
||||
U(https://developer.hashicorp.com/consul/api-docs/acl#bootstrap-acls) for details.
|
||||
author:
|
||||
- Florian Apolloner (@apollo13)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the token should be present or absent.
|
||||
choices: ['present', 'bootstrapped']
|
||||
default: present
|
||||
type: str
|
||||
bootstrap_secret:
|
||||
description:
|
||||
- The secret to be used as secret ID for the initial token.
|
||||
- Needs to be an UUID.
|
||||
type: str
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Bootstrap the ACL system
|
||||
community.general.consul_acl_bootstrap:
|
||||
bootstrap_secret: 22eaeed1-bdbd-4651-724e-42ae6c43e387
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
result:
|
||||
description:
|
||||
- The bootstrap result as returned by the consul HTTP API.
|
||||
- "B(Note:) If O(bootstrap_secret) has been specified the C(SecretID) and
|
||||
C(ID) will not contain the secret but C(VALUE_SPECIFIED_IN_NO_LOG_PARAMETER).
|
||||
If you pass O(bootstrap_secret), make sure your playbook/role does not depend
|
||||
on this return value!"
|
||||
returned: changed
|
||||
type: dict
|
||||
sample:
|
||||
AccessorID: 834a5881-10a9-a45b-f63c-490e28743557
|
||||
CreateIndex: 25
|
||||
CreateTime: '2024-01-21T20:26:27.114612038+01:00'
|
||||
Description: Bootstrap Token (Global Management)
|
||||
Hash: X2AgaFhnQGRhSSF/h0m6qpX1wj/HJWbyXcxkEM/5GrY=
|
||||
ID: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
|
||||
Local: false
|
||||
ModifyIndex: 25
|
||||
Policies:
|
||||
- ID: 00000000-0000-0000-0000-000000000001
|
||||
Name: global-management
|
||||
SecretID: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
RequestError,
|
||||
_ConsulModule,
|
||||
)
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"state": dict(type="str", choices=["present", "bootstrapped"], default="present"),
|
||||
"bootstrap_secret": dict(type="str", no_log=True),
|
||||
}
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
_ARGUMENT_SPEC.pop("token")
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(_ARGUMENT_SPEC)
|
||||
consul_module = _ConsulModule(module)
|
||||
|
||||
data = {}
|
||||
if "bootstrap_secret" in module.params:
|
||||
data["BootstrapSecret"] = module.params["bootstrap_secret"]
|
||||
|
||||
try:
|
||||
response = consul_module.put("acl/bootstrap", data=data)
|
||||
except RequestError as e:
|
||||
if e.status == 403 and b"ACL bootstrap no longer allowed" in e.response_data:
|
||||
return module.exit_json(changed=False)
|
||||
raise
|
||||
else:
|
||||
return module.exit_json(changed=True, result=response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,207 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: consul_auth_method
|
||||
short_description: Manipulate Consul auth methods
|
||||
version_added: 8.3.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of auth methods in a consul
|
||||
cluster via the agent. For more details on using and configuring ACLs,
|
||||
see U(https://www.consul.io/docs/guides/acl.html).
|
||||
author:
|
||||
- Florian Apolloner (@apollo13)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the token should be present or absent.
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Specifies a name for the ACL auth method.
|
||||
- The name can contain alphanumeric characters, dashes C(-), and underscores C(_).
|
||||
type: str
|
||||
required: true
|
||||
type:
|
||||
description:
|
||||
- The type of auth method being configured.
|
||||
- This field is immutable.
|
||||
- Required when the auth method is created.
|
||||
type: str
|
||||
choices: ['kubernetes', 'jwt', 'oidc', 'aws-iam']
|
||||
description:
|
||||
description:
|
||||
- Free form human readable description of the auth method.
|
||||
type: str
|
||||
display_name:
|
||||
description:
|
||||
- An optional name to use instead of O(name) when displaying information about this auth method.
|
||||
type: str
|
||||
max_token_ttl:
|
||||
description:
|
||||
- This specifies the maximum life of any token created by this auth method.
|
||||
- Can be specified in the form of V(60s) or V(5m) (that is, 60 seconds or 5 minutes, respectively).
|
||||
type: str
|
||||
token_locality:
|
||||
description:
|
||||
- Defines the kind of token that this auth method should produce.
|
||||
type: str
|
||||
choices: ['local', 'global']
|
||||
config:
|
||||
description:
|
||||
- The raw configuration to use for the chosen auth method.
|
||||
- Contents will vary depending upon the type chosen.
|
||||
- Required when the auth method is created.
|
||||
type: dict
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create an auth method
|
||||
community.general.consul_auth_method:
|
||||
name: test
|
||||
type: jwt
|
||||
config:
|
||||
jwt_validation_pubkeys:
|
||||
- |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||
mwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
token: "{{ consul_management_token }}"
|
||||
|
||||
- name: Delete auth method
|
||||
community.general.consul_auth_method:
|
||||
name: test
|
||||
state: absent
|
||||
token: "{{ consul_management_token }}"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
auth_method:
|
||||
description: The auth method as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
Config:
|
||||
JWTValidationPubkeys:
|
||||
- |-
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
|
||||
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
|
||||
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
|
||||
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
|
||||
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
|
||||
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
|
||||
mwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
CreateIndex: 416
|
||||
ModifyIndex: 487
|
||||
Name: test
|
||||
Type: jwt
|
||||
operation:
|
||||
description: The operation performed.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
_ConsulModule,
|
||||
camel_case_key,
|
||||
)
|
||||
|
||||
|
||||
def normalize_ttl(ttl):
|
||||
matches = re.findall(r"(\d+)(:h|m|s)", ttl)
|
||||
ttl = 0
|
||||
for value, unit in matches:
|
||||
value = int(value)
|
||||
if unit == "m":
|
||||
value *= 60
|
||||
elif unit == "h":
|
||||
value *= 60 * 60
|
||||
ttl += value
|
||||
|
||||
new_ttl = ""
|
||||
hours, remainder = divmod(ttl, 3600)
|
||||
if hours:
|
||||
new_ttl += "{0}h".format(hours)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
if minutes:
|
||||
new_ttl += "{0}m".format(minutes)
|
||||
if seconds:
|
||||
new_ttl += "{0}s".format(seconds)
|
||||
return new_ttl
|
||||
|
||||
|
||||
class ConsulAuthMethodModule(_ConsulModule):
|
||||
api_endpoint = "acl/auth-method"
|
||||
result_key = "auth_method"
|
||||
unique_identifier = "name"
|
||||
|
||||
def map_param(self, k, v, is_update):
|
||||
if k == "config" and v:
|
||||
v = {camel_case_key(k2): v2 for k2, v2 in v.items()}
|
||||
return super(ConsulAuthMethodModule, self).map_param(k, v, is_update)
|
||||
|
||||
def needs_update(self, api_obj, module_obj):
|
||||
if "MaxTokenTTL" in module_obj:
|
||||
module_obj["MaxTokenTTL"] = normalize_ttl(module_obj["MaxTokenTTL"])
|
||||
return super(ConsulAuthMethodModule, self).needs_update(api_obj, module_obj)
|
||||
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"name": dict(type="str", required=True),
|
||||
"type": dict(type="str", choices=["kubernetes", "jwt", "oidc", "aws-iam"]),
|
||||
"description": dict(type="str"),
|
||||
"display_name": dict(type="str"),
|
||||
"max_token_ttl": dict(type="str", no_log=False),
|
||||
"token_locality": dict(type="str", choices=["local", "global"]),
|
||||
"config": dict(type="dict"),
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
}
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
consul_module = ConsulAuthMethodModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,183 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: consul_binding_rule
|
||||
short_description: Manipulate Consul binding rules
|
||||
version_added: 8.3.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of binding rules in a consul
|
||||
cluster via the agent. For more details on using and configuring binding rules,
|
||||
see U(https://developer.hashicorp.com/consul/api-docs/acl/binding-rules).
|
||||
author:
|
||||
- Florian Apolloner (@apollo13)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the binding rule should be present or absent.
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Specifies a name for the binding rule.
|
||||
- 'Note: This is used to identify the binding rule. But since the API does not support a name, it is prefixed to the description.'
|
||||
type: str
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- Free form human readable description of the binding rule.
|
||||
type: str
|
||||
auth_method:
|
||||
description:
|
||||
- The name of the auth method that this rule applies to.
|
||||
type: str
|
||||
required: true
|
||||
selector:
|
||||
description:
|
||||
- Specifies the expression used to match this rule against valid identities returned from an auth method validation.
|
||||
- If empty this binding rule matches all valid identities returned from the auth method.
|
||||
type: str
|
||||
bind_type:
|
||||
description:
|
||||
- Specifies the way the binding rule affects a token created at login.
|
||||
type: str
|
||||
choices: [service, node, role, templated-policy]
|
||||
bind_name:
|
||||
description:
|
||||
- The name to bind to a token at login-time.
|
||||
- What it binds to can be adjusted with different values of the O(bind_type) parameter.
|
||||
type: str
|
||||
bind_vars:
|
||||
description:
|
||||
- Specifies the templated policy variables when O(bind_type) is set to V(templated-policy).
|
||||
type: dict
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create a binding rule
|
||||
community.general.consul_binding_rule:
|
||||
name: my_name
|
||||
description: example rule
|
||||
auth_method: minikube
|
||||
bind_type: service
|
||||
bind_name: "{{ serviceaccount.name }}"
|
||||
token: "{{ consul_management_token }}"
|
||||
|
||||
- name: Remove a binding rule
|
||||
community.general.consul_binding_rule:
|
||||
name: my_name
|
||||
auth_method: minikube
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
binding_rule:
|
||||
description: The binding rule as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
Description: "my_name: example rule"
|
||||
AuthMethod: minikube
|
||||
Selector: serviceaccount.namespace==default
|
||||
BindType: service
|
||||
BindName: "{{ serviceaccount.name }}"
|
||||
CreateIndex: 30
|
||||
ID: 59c8a237-e481-4239-9202-45f117950c5f
|
||||
ModifyIndex: 33
|
||||
operation:
|
||||
description: The operation performed.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
RequestError,
|
||||
_ConsulModule,
|
||||
)
|
||||
|
||||
|
||||
class ConsulBindingRuleModule(_ConsulModule):
|
||||
api_endpoint = "acl/binding-rule"
|
||||
result_key = "binding_rule"
|
||||
unique_identifier = "id"
|
||||
|
||||
def read_object(self):
|
||||
url = "acl/binding-rules?authmethod={0}".format(self.params["auth_method"])
|
||||
try:
|
||||
results = self.get(url)
|
||||
for result in results:
|
||||
if result.get("Description").startswith(
|
||||
"{0}: ".format(self.params["name"])
|
||||
):
|
||||
return result
|
||||
except RequestError as e:
|
||||
if e.status == 404:
|
||||
return
|
||||
elif e.status == 403 and b"ACL not found" in e.response_data:
|
||||
return
|
||||
raise
|
||||
|
||||
def module_to_obj(self, is_update):
|
||||
obj = super(ConsulBindingRuleModule, self).module_to_obj(is_update)
|
||||
del obj["Name"]
|
||||
return obj
|
||||
|
||||
def prepare_object(self, existing, obj):
|
||||
final = super(ConsulBindingRuleModule, self).prepare_object(existing, obj)
|
||||
name = self.params["name"]
|
||||
description = final.pop("Description", "").split(": ", 1)[-1]
|
||||
final["Description"] = "{0}: {1}".format(name, description)
|
||||
return final
|
||||
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"name": dict(type="str", required=True),
|
||||
"description": dict(type="str"),
|
||||
"auth_method": dict(type="str", required=True),
|
||||
"selector": dict(type="str"),
|
||||
"bind_type": dict(
|
||||
type="str", choices=["service", "node", "role", "templated-policy"]
|
||||
),
|
||||
"bind_name": dict(type="str"),
|
||||
"bind_vars": dict(type="dict"),
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
}
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
consul_module = ConsulBindingRuleModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -6,10 +6,9 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
DOCUMENTATION = '''
|
||||
module: consul_policy
|
||||
short_description: Manipulate Consul policies
|
||||
version_added: 7.2.0
|
||||
@@ -20,29 +19,24 @@ description:
|
||||
author:
|
||||
- Håkon Lerring (@Hakon)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
version_added: 8.3.0
|
||||
support: none
|
||||
diff_mode:
|
||||
support: partial
|
||||
version_added: 8.3.0
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
support: none
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the policy should be present or absent.
|
||||
required: false
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
valid_datacenters:
|
||||
description:
|
||||
- Valid datacenters for the policy. All if list is empty.
|
||||
default: []
|
||||
type: list
|
||||
elements: str
|
||||
name:
|
||||
@@ -54,12 +48,45 @@ options:
|
||||
description:
|
||||
description:
|
||||
- Description of the policy.
|
||||
required: false
|
||||
type: str
|
||||
default: ''
|
||||
rules:
|
||||
type: str
|
||||
description:
|
||||
- Rule document that should be associated with the current policy.
|
||||
"""
|
||||
required: false
|
||||
host:
|
||||
description:
|
||||
- Host of the consul agent, defaults to localhost.
|
||||
required: false
|
||||
default: localhost
|
||||
type: str
|
||||
port:
|
||||
type: int
|
||||
description:
|
||||
- The port on which the consul agent is running.
|
||||
required: false
|
||||
default: 8500
|
||||
scheme:
|
||||
description:
|
||||
- The protocol scheme on which the consul agent is running.
|
||||
required: false
|
||||
default: http
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- A management token is required to manipulate the policies.
|
||||
type: str
|
||||
validate_certs:
|
||||
type: bool
|
||||
description:
|
||||
- Whether to verify the TLS certificate of the consul agent or not.
|
||||
required: false
|
||||
default: true
|
||||
requirements:
|
||||
- requests
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create a policy with rules
|
||||
@@ -100,64 +127,246 @@ EXAMPLES = """
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
policy:
|
||||
description: The policy as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
CreateIndex: 632
|
||||
Description: Testing
|
||||
Hash: rj5PeDHddHslkpW7Ij4OD6N4bbSXiecXFmiw2SYXg2A=
|
||||
Name: foo-access
|
||||
Rules: |-
|
||||
key "foo" {
|
||||
policy = "read"
|
||||
}
|
||||
key "private/foo" {
|
||||
policy = "deny"
|
||||
}
|
||||
operation:
|
||||
description: The operation performed.
|
||||
description: The operation performed on the policy.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
OPERATION_READ,
|
||||
_ConsulModule,
|
||||
)
|
||||
|
||||
try:
|
||||
from requests.exceptions import ConnectionError
|
||||
import requests
|
||||
has_requests = True
|
||||
except ImportError:
|
||||
has_requests = False
|
||||
|
||||
|
||||
TOKEN_PARAMETER_NAME = "token"
|
||||
HOST_PARAMETER_NAME = "host"
|
||||
SCHEME_PARAMETER_NAME = "scheme"
|
||||
VALIDATE_CERTS_PARAMETER_NAME = "validate_certs"
|
||||
NAME_PARAMETER_NAME = "name"
|
||||
DESCRIPTION_PARAMETER_NAME = "description"
|
||||
PORT_PARAMETER_NAME = "port"
|
||||
RULES_PARAMETER_NAME = "rules"
|
||||
VALID_DATACENTERS_PARAMETER_NAME = "valid_datacenters"
|
||||
STATE_PARAMETER_NAME = "state"
|
||||
|
||||
|
||||
PRESENT_STATE_VALUE = "present"
|
||||
ABSENT_STATE_VALUE = "absent"
|
||||
|
||||
REMOVE_OPERATION = "remove"
|
||||
UPDATE_OPERATION = "update"
|
||||
CREATE_OPERATION = "create"
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"name": dict(required=True),
|
||||
"description": dict(required=False, type="str"),
|
||||
"rules": dict(type="str"),
|
||||
"valid_datacenters": dict(type="list", elements="str"),
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
NAME_PARAMETER_NAME: dict(required=True),
|
||||
DESCRIPTION_PARAMETER_NAME: dict(required=False, type='str', default=''),
|
||||
PORT_PARAMETER_NAME: dict(default=8500, type='int'),
|
||||
RULES_PARAMETER_NAME: dict(type='str'),
|
||||
VALID_DATACENTERS_PARAMETER_NAME: dict(type='list', elements='str', default=[]),
|
||||
HOST_PARAMETER_NAME: dict(default='localhost'),
|
||||
SCHEME_PARAMETER_NAME: dict(default='http'),
|
||||
TOKEN_PARAMETER_NAME: dict(no_log=True),
|
||||
VALIDATE_CERTS_PARAMETER_NAME: dict(type='bool', default=True),
|
||||
STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE]),
|
||||
}
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
class ConsulPolicyModule(_ConsulModule):
|
||||
api_endpoint = "acl/policy"
|
||||
result_key = "policy"
|
||||
unique_identifier = "id"
|
||||
def get_consul_url(configuration):
|
||||
return '%s://%s:%s/v1' % (configuration.scheme,
|
||||
configuration.host, configuration.port)
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
return [self.api_endpoint, "name", self.params["name"]]
|
||||
return super(ConsulPolicyModule, self).endpoint_url(operation, identifier)
|
||||
|
||||
def get_auth_headers(configuration):
|
||||
if configuration.token is None:
|
||||
return {}
|
||||
else:
|
||||
return {'X-Consul-Token': configuration.token}
|
||||
|
||||
|
||||
class RequestError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def handle_consul_response_error(response):
|
||||
if 400 <= response.status_code < 600:
|
||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
||||
|
||||
|
||||
def update_policy(policy, configuration):
|
||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration), policy['ID'])
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.put(url, headers=headers, json={
|
||||
'Name': configuration.name, # should be equal at this point.
|
||||
'Description': configuration.description,
|
||||
'Rules': configuration.rules,
|
||||
'Datacenters': configuration.valid_datacenters
|
||||
}, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
updated_policy = response.json()
|
||||
|
||||
changed = (
|
||||
policy.get('Rules', "") != updated_policy.get('Rules', "") or
|
||||
policy.get('Description', "") != updated_policy.get('Description', "") or
|
||||
policy.get('Datacenters', []) != updated_policy.get('Datacenters', [])
|
||||
)
|
||||
|
||||
return Output(changed=changed, operation=UPDATE_OPERATION, policy=updated_policy)
|
||||
|
||||
|
||||
def create_policy(configuration):
|
||||
url = '%s/acl/policy' % get_consul_url(configuration)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.put(url, headers=headers, json={
|
||||
'Name': configuration.name,
|
||||
'Description': configuration.description,
|
||||
'Rules': configuration.rules,
|
||||
'Datacenters': configuration.valid_datacenters
|
||||
}, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
created_policy = response.json()
|
||||
|
||||
return Output(changed=True, operation=CREATE_OPERATION, policy=created_policy)
|
||||
|
||||
|
||||
def remove_policy(configuration):
|
||||
policies = get_policies(configuration)
|
||||
|
||||
if configuration.name in policies:
|
||||
|
||||
policy_id = policies[configuration.name]['ID']
|
||||
policy = get_policy(policy_id, configuration)
|
||||
|
||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration),
|
||||
policy['ID'])
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.delete(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
return Output(changed=changed, operation=REMOVE_OPERATION)
|
||||
|
||||
|
||||
def get_policies(configuration):
|
||||
url = '%s/acl/policies' % get_consul_url(configuration)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
policies = response.json()
|
||||
existing_policies_mapped_by_name = dict(
|
||||
(policy['Name'], policy) for policy in policies if policy['Name'] is not None)
|
||||
return existing_policies_mapped_by_name
|
||||
|
||||
|
||||
def get_policy(id, configuration):
|
||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration), id)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def set_policy(configuration):
|
||||
policies = get_policies(configuration)
|
||||
|
||||
if configuration.name in policies:
|
||||
index_policy_object = policies[configuration.name]
|
||||
policy_id = policies[configuration.name]['ID']
|
||||
rest_policy_object = get_policy(policy_id, configuration)
|
||||
# merge dicts as some keys are only available in the partial policy
|
||||
policy = index_policy_object.copy()
|
||||
policy.update(rest_policy_object)
|
||||
return update_policy(policy, configuration)
|
||||
else:
|
||||
return create_policy(configuration)
|
||||
|
||||
|
||||
class Configuration:
|
||||
"""
|
||||
Configuration for this module.
|
||||
"""
|
||||
|
||||
def __init__(self, token=None, host=None, scheme=None, validate_certs=None, name=None, description=None, port=None,
|
||||
rules=None, valid_datacenters=None, state=None):
|
||||
self.token = token # type: str
|
||||
self.host = host # type: str
|
||||
self.scheme = scheme # type: str
|
||||
self.validate_certs = validate_certs # type: bool
|
||||
self.name = name # type: str
|
||||
self.description = description # type: str
|
||||
self.port = port # type: int
|
||||
self.rules = rules # type: str
|
||||
self.valid_datacenters = valid_datacenters # type: str
|
||||
self.state = state # type: str
|
||||
|
||||
|
||||
class Output:
|
||||
"""
|
||||
Output of an action of this module.
|
||||
"""
|
||||
|
||||
def __init__(self, changed=None, operation=None, policy=None):
|
||||
self.changed = changed # type: bool
|
||||
self.operation = operation # type: str
|
||||
self.policy = policy # type: dict
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
"""
|
||||
Checks that the required dependencies have been imported.
|
||||
:exception ImportError: if it is detected that any of the required dependencies have not been imported
|
||||
"""
|
||||
|
||||
if not has_requests:
|
||||
raise ImportError(
|
||||
"requests required for this module. See https://pypi.org/project/requests/")
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
supports_check_mode=True,
|
||||
"""
|
||||
Main method.
|
||||
"""
|
||||
module = AnsibleModule(_ARGUMENT_SPEC, supports_check_mode=False)
|
||||
|
||||
try:
|
||||
check_dependencies()
|
||||
except ImportError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
configuration = Configuration(
|
||||
token=module.params.get(TOKEN_PARAMETER_NAME),
|
||||
host=module.params.get(HOST_PARAMETER_NAME),
|
||||
scheme=module.params.get(SCHEME_PARAMETER_NAME),
|
||||
validate_certs=module.params.get(VALIDATE_CERTS_PARAMETER_NAME),
|
||||
name=module.params.get(NAME_PARAMETER_NAME),
|
||||
description=module.params.get(DESCRIPTION_PARAMETER_NAME),
|
||||
port=module.params.get(PORT_PARAMETER_NAME),
|
||||
rules=module.params.get(RULES_PARAMETER_NAME),
|
||||
valid_datacenters=module.params.get(VALID_DATACENTERS_PARAMETER_NAME),
|
||||
state=module.params.get(STATE_PARAMETER_NAME),
|
||||
)
|
||||
consul_module = ConsulPolicyModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
try:
|
||||
if configuration.state == PRESENT_STATE_VALUE:
|
||||
output = set_policy(configuration)
|
||||
else:
|
||||
output = remove_policy(configuration)
|
||||
except ConnectionError as e:
|
||||
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||
configuration.host, configuration.port, str(e)))
|
||||
raise
|
||||
|
||||
return_values = dict(changed=output.changed, operation=output.operation, policy=output.policy)
|
||||
module.exit_json(**return_values)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
DOCUMENTATION = '''
|
||||
module: consul_role
|
||||
short_description: Manipulate Consul roles
|
||||
version_added: 7.5.0
|
||||
@@ -20,18 +19,12 @@ description:
|
||||
author:
|
||||
- Håkon Lerring (@Hakon)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.token
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
version_added: 8.3.0
|
||||
support: none
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
@@ -41,6 +34,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- whether the role should be present or absent.
|
||||
required: false
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
@@ -48,6 +42,7 @@ options:
|
||||
description:
|
||||
- Description of the role.
|
||||
- If not specified, the assigned description will not be changed.
|
||||
required: false
|
||||
type: str
|
||||
policies:
|
||||
type: list
|
||||
@@ -56,6 +51,7 @@ options:
|
||||
- List of policies to attach to the role. Each policy is a dict.
|
||||
- If the parameter is left blank, any policies currently assigned will not be changed.
|
||||
- Any empty array (V([])) will clear any policies previously set.
|
||||
required: false
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
@@ -67,23 +63,6 @@ options:
|
||||
- The ID of the policy to attach to this role; see M(community.general.consul_policy) for more info.
|
||||
- Either this or O(policies[].name) must be specified.
|
||||
type: str
|
||||
templated_policies:
|
||||
description:
|
||||
- The list of templated policies that should be applied to the role.
|
||||
type: list
|
||||
elements: dict
|
||||
version_added: 8.3.0
|
||||
suboptions:
|
||||
template_name:
|
||||
description:
|
||||
- The templated policy name.
|
||||
type: str
|
||||
required: true
|
||||
template_variables:
|
||||
description:
|
||||
- The templated policy variables.
|
||||
- Not all templated policies require variables.
|
||||
type: dict
|
||||
service_identities:
|
||||
type: list
|
||||
elements: dict
|
||||
@@ -91,18 +70,15 @@ options:
|
||||
- List of service identities to attach to the role.
|
||||
- If not specified, any service identities currently assigned will not be changed.
|
||||
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||
required: false
|
||||
suboptions:
|
||||
service_name:
|
||||
name:
|
||||
description:
|
||||
- The name of the node.
|
||||
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||
- May only contain lowercase alphanumeric characters as well as - and _.
|
||||
- This suboption has been renamed from O(service_identities[].name) to O(service_identities[].service_name)
|
||||
in community.general 8.3.0. The old name can still be used.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- name
|
||||
datacenters:
|
||||
description:
|
||||
- The datacenters the policies will be effective.
|
||||
@@ -111,6 +87,7 @@ options:
|
||||
- including those which do not yet exist but may in the future.
|
||||
type: list
|
||||
elements: str
|
||||
required: true
|
||||
node_identities:
|
||||
type: list
|
||||
elements: dict
|
||||
@@ -118,25 +95,52 @@ options:
|
||||
- List of node identities to attach to the role.
|
||||
- If not specified, any node identities currently assigned will not be changed.
|
||||
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||
required: false
|
||||
suboptions:
|
||||
node_name:
|
||||
name:
|
||||
description:
|
||||
- The name of the node.
|
||||
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||
- May only contain lowercase alphanumeric characters as well as - and _.
|
||||
- This suboption has been renamed from O(node_identities[].name) to O(node_identities[].node_name)
|
||||
in community.general 8.3.0. The old name can still be used.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- name
|
||||
datacenter:
|
||||
description:
|
||||
- The nodes datacenter.
|
||||
- This will result in effective policy only being valid in this datacenter.
|
||||
type: str
|
||||
required: true
|
||||
"""
|
||||
host:
|
||||
description:
|
||||
- Host of the consul agent, defaults to V(localhost).
|
||||
required: false
|
||||
default: localhost
|
||||
type: str
|
||||
port:
|
||||
type: int
|
||||
description:
|
||||
- The port on which the consul agent is running.
|
||||
required: false
|
||||
default: 8500
|
||||
scheme:
|
||||
description:
|
||||
- The protocol scheme on which the consul agent is running.
|
||||
required: false
|
||||
default: http
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- A management token is required to manipulate the roles.
|
||||
type: str
|
||||
validate_certs:
|
||||
type: bool
|
||||
description:
|
||||
- Whether to verify the TLS certificate of the consul agent.
|
||||
required: false
|
||||
default: true
|
||||
requirements:
|
||||
- requests
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create a role with 2 policies
|
||||
@@ -200,81 +204,440 @@ operation:
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
OPERATION_READ,
|
||||
_ConsulModule,
|
||||
get_consul_url, get_auth_headers, handle_consul_response_error)
|
||||
import traceback
|
||||
|
||||
REQUESTS_IMP_ERR = None
|
||||
|
||||
try:
|
||||
from requests.exceptions import ConnectionError
|
||||
import requests
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
HAS_REQUESTS = False
|
||||
REQUESTS_IMP_ERR = traceback.format_exc()
|
||||
|
||||
TOKEN_PARAMETER_NAME = "token"
|
||||
HOST_PARAMETER_NAME = "host"
|
||||
SCHEME_PARAMETER_NAME = "scheme"
|
||||
VALIDATE_CERTS_PARAMETER_NAME = "validate_certs"
|
||||
NAME_PARAMETER_NAME = "name"
|
||||
DESCRIPTION_PARAMETER_NAME = "description"
|
||||
PORT_PARAMETER_NAME = "port"
|
||||
POLICIES_PARAMETER_NAME = "policies"
|
||||
SERVICE_IDENTITIES_PARAMETER_NAME = "service_identities"
|
||||
NODE_IDENTITIES_PARAMETER_NAME = "node_identities"
|
||||
STATE_PARAMETER_NAME = "state"
|
||||
|
||||
PRESENT_STATE_VALUE = "present"
|
||||
ABSENT_STATE_VALUE = "absent"
|
||||
|
||||
REMOVE_OPERATION = "remove"
|
||||
UPDATE_OPERATION = "update"
|
||||
CREATE_OPERATION = "create"
|
||||
|
||||
POLICY_RULE_SPEC = dict(
|
||||
name=dict(type='str'),
|
||||
id=dict(type='str'),
|
||||
)
|
||||
|
||||
|
||||
class ConsulRoleModule(_ConsulModule):
|
||||
api_endpoint = "acl/role"
|
||||
result_key = "role"
|
||||
unique_identifier = "id"
|
||||
|
||||
def endpoint_url(self, operation, identifier=None):
|
||||
if operation == OPERATION_READ:
|
||||
return [self.api_endpoint, "name", self.params["name"]]
|
||||
return super(ConsulRoleModule, self).endpoint_url(operation, identifier)
|
||||
|
||||
|
||||
NAME_ID_SPEC = dict(
|
||||
name=dict(type="str"),
|
||||
id=dict(type="str"),
|
||||
NODE_ID_RULE_SPEC = dict(
|
||||
name=dict(type='str', required=True),
|
||||
datacenter=dict(type='str', required=True),
|
||||
)
|
||||
|
||||
NODE_ID_SPEC = dict(
|
||||
node_name=dict(type="str", required=True, aliases=["name"]),
|
||||
datacenter=dict(type="str", required=True),
|
||||
)
|
||||
|
||||
SERVICE_ID_SPEC = dict(
|
||||
service_name=dict(type="str", required=True, aliases=["name"]),
|
||||
datacenters=dict(type="list", elements="str"),
|
||||
)
|
||||
|
||||
TEMPLATE_POLICY_SPEC = dict(
|
||||
template_name=dict(type="str", required=True),
|
||||
template_variables=dict(type="dict"),
|
||||
SERVICE_ID_RULE_SPEC = dict(
|
||||
name=dict(type='str', required=True),
|
||||
datacenters=dict(type='list', elements='str', required=True),
|
||||
)
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"name": dict(type="str", required=True),
|
||||
"description": dict(type="str"),
|
||||
"policies": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=NAME_ID_SPEC,
|
||||
mutually_exclusive=[("name", "id")],
|
||||
required_one_of=[("name", "id")],
|
||||
),
|
||||
"templated_policies": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=TEMPLATE_POLICY_SPEC,
|
||||
),
|
||||
"node_identities": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=NODE_ID_SPEC,
|
||||
),
|
||||
"service_identities": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=SERVICE_ID_SPEC,
|
||||
),
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
TOKEN_PARAMETER_NAME: dict(no_log=True),
|
||||
PORT_PARAMETER_NAME: dict(default=8500, type='int'),
|
||||
HOST_PARAMETER_NAME: dict(default='localhost'),
|
||||
SCHEME_PARAMETER_NAME: dict(default='http'),
|
||||
VALIDATE_CERTS_PARAMETER_NAME: dict(type='bool', default=True),
|
||||
NAME_PARAMETER_NAME: dict(required=True),
|
||||
DESCRIPTION_PARAMETER_NAME: dict(required=False, type='str', default=None),
|
||||
POLICIES_PARAMETER_NAME: dict(type='list', elements='dict', options=POLICY_RULE_SPEC,
|
||||
mutually_exclusive=[('name', 'id')], required_one_of=[('name', 'id')], default=None),
|
||||
SERVICE_IDENTITIES_PARAMETER_NAME: dict(type='list', elements='dict', options=SERVICE_ID_RULE_SPEC, default=None),
|
||||
NODE_IDENTITIES_PARAMETER_NAME: dict(type='list', elements='dict', options=NODE_ID_RULE_SPEC, default=None),
|
||||
STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE]),
|
||||
}
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
def compare_consul_api_role_policy_objects(first, second):
|
||||
# compare two lists of dictionaries, ignoring the ID element
|
||||
for x in first:
|
||||
x.pop('ID', None)
|
||||
|
||||
for x in second:
|
||||
x.pop('ID', None)
|
||||
|
||||
return first == second
|
||||
|
||||
|
||||
def update_role(role, configuration):
|
||||
url = '%s/acl/role/%s' % (get_consul_url(configuration),
|
||||
role['ID'])
|
||||
headers = get_auth_headers(configuration)
|
||||
|
||||
update_role_data = {
|
||||
'Name': configuration.name,
|
||||
'Description': configuration.description,
|
||||
}
|
||||
|
||||
# check if the user omitted the description, policies, service identities, or node identities
|
||||
|
||||
description_specified = configuration.description is not None
|
||||
|
||||
policy_specified = True
|
||||
if len(configuration.policies) == 1 and configuration.policies[0] is None:
|
||||
policy_specified = False
|
||||
|
||||
service_id_specified = True
|
||||
if len(configuration.service_identities) == 1 and configuration.service_identities[0] is None:
|
||||
service_id_specified = False
|
||||
|
||||
node_id_specified = True
|
||||
if len(configuration.node_identities) == 1 and configuration.node_identities[0] is None:
|
||||
node_id_specified = False
|
||||
|
||||
if description_specified:
|
||||
update_role_data["Description"] = configuration.description
|
||||
|
||||
if policy_specified:
|
||||
update_role_data["Policies"] = [x.to_dict() for x in configuration.policies]
|
||||
|
||||
if configuration.version >= ConsulVersion("1.5.0") and service_id_specified:
|
||||
update_role_data["ServiceIdentities"] = [
|
||||
x.to_dict() for x in configuration.service_identities]
|
||||
|
||||
if configuration.version >= ConsulVersion("1.8.0") and node_id_specified:
|
||||
update_role_data["NodeIdentities"] = [
|
||||
x.to_dict() for x in configuration.node_identities]
|
||||
|
||||
if configuration.check_mode:
|
||||
description_changed = False
|
||||
if description_specified:
|
||||
description_changed = role.get('Description') != update_role_data["Description"]
|
||||
else:
|
||||
update_role_data["Description"] = role.get("Description")
|
||||
|
||||
policies_changed = False
|
||||
if policy_specified:
|
||||
policies_changed = not (
|
||||
compare_consul_api_role_policy_objects(role.get('Policies', []), update_role_data.get('Policies', [])))
|
||||
else:
|
||||
if role.get('Policies') is not None:
|
||||
update_role_data["Policies"] = role.get('Policies')
|
||||
|
||||
service_ids_changed = False
|
||||
if service_id_specified:
|
||||
service_ids_changed = role.get('ServiceIdentities') != update_role_data.get('ServiceIdentities')
|
||||
else:
|
||||
if role.get('ServiceIdentities') is not None:
|
||||
update_role_data["ServiceIdentities"] = role.get('ServiceIdentities')
|
||||
|
||||
node_ids_changed = False
|
||||
if node_id_specified:
|
||||
node_ids_changed = role.get('NodeIdentities') != update_role_data.get('NodeIdentities')
|
||||
else:
|
||||
if role.get('NodeIdentities'):
|
||||
update_role_data["NodeIdentities"] = role.get('NodeIdentities')
|
||||
|
||||
changed = (
|
||||
description_changed or
|
||||
policies_changed or
|
||||
service_ids_changed or
|
||||
node_ids_changed
|
||||
)
|
||||
return Output(changed=changed, operation=UPDATE_OPERATION, role=update_role_data)
|
||||
else:
|
||||
# if description, policies, service or node id are not specified; we need to get the existing value and apply it
|
||||
if not description_specified and role.get('Description') is not None:
|
||||
update_role_data["Description"] = role.get('Description')
|
||||
|
||||
if not policy_specified and role.get('Policies') is not None:
|
||||
update_role_data["Policies"] = role.get('Policies')
|
||||
|
||||
if not service_id_specified and role.get('ServiceIdentities') is not None:
|
||||
update_role_data["ServiceIdentities"] = role.get('ServiceIdentities')
|
||||
|
||||
if not node_id_specified and role.get('NodeIdentities') is not None:
|
||||
update_role_data["NodeIdentities"] = role.get('NodeIdentities')
|
||||
|
||||
response = requests.put(url, headers=headers, json=update_role_data, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
resulting_role = response.json()
|
||||
changed = (
|
||||
role['Description'] != resulting_role['Description'] or
|
||||
role.get('Policies', None) != resulting_role.get('Policies', None) or
|
||||
role.get('ServiceIdentities', None) != resulting_role.get('ServiceIdentities', None) or
|
||||
role.get('NodeIdentities', None) != resulting_role.get('NodeIdentities', None)
|
||||
)
|
||||
|
||||
return Output(changed=changed, operation=UPDATE_OPERATION, role=resulting_role)
|
||||
|
||||
|
||||
def create_role(configuration):
|
||||
url = '%s/acl/role' % get_consul_url(configuration)
|
||||
headers = get_auth_headers(configuration)
|
||||
|
||||
# check if the user omitted policies, service identities, or node identities
|
||||
policy_specified = True
|
||||
if len(configuration.policies) == 1 and configuration.policies[0] is None:
|
||||
policy_specified = False
|
||||
|
||||
service_id_specified = True
|
||||
if len(configuration.service_identities) == 1 and configuration.service_identities[0] is None:
|
||||
service_id_specified = False
|
||||
|
||||
node_id_specified = True
|
||||
if len(configuration.node_identities) == 1 and configuration.node_identities[0] is None:
|
||||
node_id_specified = False
|
||||
|
||||
# get rid of None item so we can set an empty list for policies, service identities and node identities
|
||||
if not policy_specified:
|
||||
configuration.policies.pop()
|
||||
|
||||
if not service_id_specified:
|
||||
configuration.service_identities.pop()
|
||||
|
||||
if not node_id_specified:
|
||||
configuration.node_identities.pop()
|
||||
|
||||
create_role_data = {
|
||||
'Name': configuration.name,
|
||||
'Description': configuration.description,
|
||||
'Policies': [x.to_dict() for x in configuration.policies],
|
||||
}
|
||||
if configuration.version >= ConsulVersion("1.5.0"):
|
||||
create_role_data["ServiceIdentities"] = [x.to_dict() for x in configuration.service_identities]
|
||||
|
||||
if configuration.version >= ConsulVersion("1.8.0"):
|
||||
create_role_data["NodeIdentities"] = [x.to_dict() for x in configuration.node_identities]
|
||||
|
||||
if not configuration.check_mode:
|
||||
response = requests.put(url, headers=headers, json=create_role_data, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
resulting_role = response.json()
|
||||
|
||||
return Output(changed=True, operation=CREATE_OPERATION, role=resulting_role)
|
||||
else:
|
||||
return Output(changed=True, operation=CREATE_OPERATION)
|
||||
|
||||
|
||||
def remove_role(configuration):
|
||||
roles = get_roles(configuration)
|
||||
|
||||
if configuration.name in roles:
|
||||
|
||||
role_id = roles[configuration.name]['ID']
|
||||
|
||||
if not configuration.check_mode:
|
||||
url = '%s/acl/role/%s' % (get_consul_url(configuration), role_id)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.delete(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
return Output(changed=changed, operation=REMOVE_OPERATION)
|
||||
|
||||
|
||||
def get_roles(configuration):
|
||||
url = '%s/acl/roles' % get_consul_url(configuration)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
roles = response.json()
|
||||
existing_roles_mapped_by_id = dict((role['Name'], role) for role in roles if role['Name'] is not None)
|
||||
return existing_roles_mapped_by_id
|
||||
|
||||
|
||||
def get_consul_version(configuration):
|
||||
url = '%s/agent/self' % get_consul_url(configuration)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
config = response.json()["Config"]
|
||||
return ConsulVersion(config["Version"])
|
||||
|
||||
|
||||
def set_role(configuration):
|
||||
roles = get_roles(configuration)
|
||||
|
||||
if configuration.name in roles:
|
||||
role = roles[configuration.name]
|
||||
return update_role(role, configuration)
|
||||
else:
|
||||
return create_role(configuration)
|
||||
|
||||
|
||||
class ConsulVersion:
|
||||
def __init__(self, version_string):
|
||||
split = version_string.split('.')
|
||||
self.major = split[0]
|
||||
self.minor = split[1]
|
||||
self.patch = split[2]
|
||||
|
||||
def __ge__(self, other):
|
||||
return int(self.major + self.minor +
|
||||
self.patch) >= int(other.major + other.minor + other.patch)
|
||||
|
||||
def __le__(self, other):
|
||||
return int(self.major + self.minor +
|
||||
self.patch) <= int(other.major + other.minor + other.patch)
|
||||
|
||||
|
||||
class ServiceIdentity:
|
||||
def __init__(self, input):
|
||||
if not isinstance(input, dict) or 'name' not in input:
|
||||
raise ValueError(
|
||||
"Each element of service_identities must be a dict with the keys name and optionally datacenters")
|
||||
self.name = input["name"]
|
||||
self.datacenters = input["datacenters"] if "datacenters" in input else None
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"ServiceName": self.name,
|
||||
"Datacenters": self.datacenters
|
||||
}
|
||||
|
||||
|
||||
class NodeIdentity:
|
||||
def __init__(self, input):
|
||||
if not isinstance(input, dict) or 'name' not in input:
|
||||
raise ValueError(
|
||||
"Each element of node_identities must be a dict with the keys name and optionally datacenter")
|
||||
self.name = input["name"]
|
||||
self.datacenter = input["datacenter"] if "datacenter" in input else None
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"NodeName": self.name,
|
||||
"Datacenter": self.datacenter
|
||||
}
|
||||
|
||||
|
||||
class RoleLink:
|
||||
def __init__(self, dict):
|
||||
self.id = dict.get("id", None)
|
||||
self.name = dict.get("name", None)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"ID": self.id,
|
||||
"Name": self.name
|
||||
}
|
||||
|
||||
|
||||
class PolicyLink:
|
||||
def __init__(self, dict):
|
||||
self.id = dict.get("id", None)
|
||||
self.name = dict.get("name", None)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"ID": self.id,
|
||||
"Name": self.name
|
||||
}
|
||||
|
||||
|
||||
class Configuration:
|
||||
"""
|
||||
Configuration for this module.
|
||||
"""
|
||||
|
||||
def __init__(self, token=None, host=None, scheme=None, validate_certs=None, name=None, description=None, port=None,
|
||||
policies=None, service_identities=None, node_identities=None, state=None, check_mode=None):
|
||||
self.token = token # type: str
|
||||
self.host = host # type: str
|
||||
self.port = port # type: int
|
||||
self.scheme = scheme # type: str
|
||||
self.validate_certs = validate_certs # type: bool
|
||||
self.name = name # type: str
|
||||
self.description = description # type: str
|
||||
if policies is not None:
|
||||
self.policies = [PolicyLink(p) for p in policies] # type: list(PolicyLink)
|
||||
else:
|
||||
self.policies = [None]
|
||||
if service_identities is not None:
|
||||
self.service_identities = [ServiceIdentity(s) for s in service_identities] # type: list(ServiceIdentity)
|
||||
else:
|
||||
self.service_identities = [None]
|
||||
if node_identities is not None:
|
||||
self.node_identities = [NodeIdentity(n) for n in node_identities] # type: list(NodeIdentity)
|
||||
else:
|
||||
self.node_identities = [None]
|
||||
self.state = state # type: str
|
||||
self.check_mode = check_mode # type: bool
|
||||
|
||||
|
||||
class Output:
|
||||
"""
|
||||
Output of an action of this module.
|
||||
"""
|
||||
|
||||
def __init__(self, changed=None, operation=None, role=None):
|
||||
self.changed = changed # type: bool
|
||||
self.operation = operation # type: str
|
||||
self.role = role # type: dict
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
consul_module = ConsulRoleModule(module)
|
||||
consul_module.execute()
|
||||
"""
|
||||
Main method.
|
||||
"""
|
||||
module = AnsibleModule(_ARGUMENT_SPEC, supports_check_mode=True)
|
||||
|
||||
if not HAS_REQUESTS:
|
||||
module.fail_json(msg=missing_required_lib("requests"),
|
||||
exception=REQUESTS_IMP_ERR)
|
||||
|
||||
try:
|
||||
configuration = Configuration(
|
||||
token=module.params.get(TOKEN_PARAMETER_NAME),
|
||||
host=module.params.get(HOST_PARAMETER_NAME),
|
||||
port=module.params.get(PORT_PARAMETER_NAME),
|
||||
scheme=module.params.get(SCHEME_PARAMETER_NAME),
|
||||
validate_certs=module.params.get(VALIDATE_CERTS_PARAMETER_NAME),
|
||||
name=module.params.get(NAME_PARAMETER_NAME),
|
||||
description=module.params.get(DESCRIPTION_PARAMETER_NAME),
|
||||
policies=module.params.get(POLICIES_PARAMETER_NAME),
|
||||
service_identities=module.params.get(SERVICE_IDENTITIES_PARAMETER_NAME),
|
||||
node_identities=module.params.get(NODE_IDENTITIES_PARAMETER_NAME),
|
||||
state=module.params.get(STATE_PARAMETER_NAME),
|
||||
check_mode=module.check_mode
|
||||
|
||||
)
|
||||
except ValueError as err:
|
||||
module.fail_json(msg='Configuration error: %s' % str(err))
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
version = get_consul_version(configuration)
|
||||
configuration.version = version
|
||||
|
||||
if configuration.state == PRESENT_STATE_VALUE:
|
||||
output = set_role(configuration)
|
||||
else:
|
||||
output = remove_role(configuration)
|
||||
except ConnectionError as e:
|
||||
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||
configuration.host, configuration.port, str(e)))
|
||||
raise
|
||||
|
||||
return_values = dict(changed=output.changed, operation=output.operation, role=output.role)
|
||||
module.exit_json(**return_values)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -16,13 +16,12 @@ description:
|
||||
cluster. These sessions can then be used in conjunction with key value pairs
|
||||
to implement distributed locks. In depth documentation for working with
|
||||
sessions can be found at http://www.consul.io/docs/internals/sessions.html
|
||||
requirements:
|
||||
- requests
|
||||
author:
|
||||
- Steve Gargan (@sgargan)
|
||||
- Håkon Lerring (@Hakon)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.consul.token
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
@@ -77,6 +76,26 @@ options:
|
||||
the associated lock delay has expired.
|
||||
type: list
|
||||
elements: str
|
||||
host:
|
||||
description:
|
||||
- The host of the consul agent defaults to localhost.
|
||||
type: str
|
||||
default: localhost
|
||||
port:
|
||||
description:
|
||||
- The port on which the consul agent is running.
|
||||
type: int
|
||||
default: 8500
|
||||
scheme:
|
||||
description:
|
||||
- The protocol scheme on which the consul agent is running.
|
||||
type: str
|
||||
default: http
|
||||
validate_certs:
|
||||
description:
|
||||
- Whether to verify the TLS certificate of the consul agent.
|
||||
type: bool
|
||||
default: true
|
||||
behavior:
|
||||
description:
|
||||
- The optional behavior that can be attached to the session when it
|
||||
@@ -90,6 +109,10 @@ options:
|
||||
type: int
|
||||
version_added: 5.4.0
|
||||
token:
|
||||
description:
|
||||
- The token key identifying an ACL rule set that controls access to
|
||||
the key value pair.
|
||||
type: str
|
||||
version_added: 5.6.0
|
||||
'''
|
||||
|
||||
@@ -125,49 +148,95 @@ EXAMPLES = '''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC, _ConsulModule
|
||||
)
|
||||
|
||||
try:
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError
|
||||
has_requests = True
|
||||
except ImportError:
|
||||
has_requests = False
|
||||
|
||||
|
||||
def execute(module, consul_module):
|
||||
def execute(module):
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state in ['info', 'list', 'node']:
|
||||
lookup_sessions(module, consul_module)
|
||||
lookup_sessions(module)
|
||||
elif state == 'present':
|
||||
update_session(module, consul_module)
|
||||
update_session(module)
|
||||
else:
|
||||
remove_session(module, consul_module)
|
||||
remove_session(module)
|
||||
|
||||
|
||||
def list_sessions(consul_module, datacenter):
|
||||
return consul_module.get(
|
||||
'session/list',
|
||||
params={'dc': datacenter})
|
||||
class RequestError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def list_sessions_for_node(consul_module, node, datacenter):
|
||||
return consul_module.get(
|
||||
('session', 'node', node),
|
||||
params={'dc': datacenter})
|
||||
def handle_consul_response_error(response):
|
||||
if 400 <= response.status_code < 600:
|
||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
||||
|
||||
|
||||
def get_session_info(consul_module, session_id, datacenter):
|
||||
return consul_module.get(
|
||||
('session', 'info', session_id),
|
||||
params={'dc': datacenter})
|
||||
def get_consul_url(module):
|
||||
return '%s://%s:%s/v1' % (module.params.get('scheme'),
|
||||
module.params.get('host'), module.params.get('port'))
|
||||
|
||||
|
||||
def lookup_sessions(module, consul_module):
|
||||
def get_auth_headers(module):
|
||||
if 'token' in module.params and module.params.get('token') is not None:
|
||||
return {'X-Consul-Token': module.params.get('token')}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def list_sessions(module, datacenter):
|
||||
url = '%s/session/list' % get_consul_url(module)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def list_sessions_for_node(module, node, datacenter):
|
||||
url = '%s/session/node/%s' % (get_consul_url(module), node)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_session_info(module, session_id, datacenter):
|
||||
url = '%s/session/info/%s' % (get_consul_url(module), session_id)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def lookup_sessions(module):
|
||||
|
||||
datacenter = module.params.get('datacenter')
|
||||
|
||||
state = module.params.get('state')
|
||||
try:
|
||||
if state == 'list':
|
||||
sessions_list = list_sessions(consul_module, datacenter)
|
||||
sessions_list = list_sessions(module, datacenter)
|
||||
# Ditch the index, this can be grabbed from the results
|
||||
if sessions_list and len(sessions_list) >= 2:
|
||||
sessions_list = sessions_list[1]
|
||||
@@ -175,14 +244,14 @@ def lookup_sessions(module, consul_module):
|
||||
sessions=sessions_list)
|
||||
elif state == 'node':
|
||||
node = module.params.get('node')
|
||||
sessions = list_sessions_for_node(consul_module, node, datacenter)
|
||||
sessions = list_sessions_for_node(module, node, datacenter)
|
||||
module.exit_json(changed=True,
|
||||
node=node,
|
||||
sessions=sessions)
|
||||
elif state == 'info':
|
||||
session_id = module.params.get('id')
|
||||
|
||||
session_by_id = get_session_info(consul_module, session_id, datacenter)
|
||||
session_by_id = get_session_info(module, session_id, datacenter)
|
||||
module.exit_json(changed=True,
|
||||
session_id=session_id,
|
||||
sessions=session_by_id)
|
||||
@@ -191,8 +260,10 @@ def lookup_sessions(module, consul_module):
|
||||
module.fail_json(msg="Could not retrieve session info %s" % e)
|
||||
|
||||
|
||||
def create_session(consul_module, name, behavior, ttl, node,
|
||||
def create_session(module, name, behavior, ttl, node,
|
||||
lock_delay, datacenter, checks):
|
||||
url = '%s/session/create' % get_consul_url(module)
|
||||
headers = get_auth_headers(module)
|
||||
create_data = {
|
||||
"LockDelay": lock_delay,
|
||||
"Node": node,
|
||||
@@ -202,15 +273,19 @@ def create_session(consul_module, name, behavior, ttl, node,
|
||||
}
|
||||
if ttl is not None:
|
||||
create_data["TTL"] = "%ss" % str(ttl) # TTL is in seconds
|
||||
create_session_response_dict = consul_module.put(
|
||||
'session/create',
|
||||
response = requests.put(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
data=create_data)
|
||||
json=create_data,
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
create_session_response_dict = response.json()
|
||||
return create_session_response_dict["ID"]
|
||||
|
||||
|
||||
def update_session(module, consul_module):
|
||||
def update_session(module):
|
||||
|
||||
name = module.params.get('name')
|
||||
delay = module.params.get('delay')
|
||||
@@ -221,7 +296,7 @@ def update_session(module, consul_module):
|
||||
ttl = module.params.get('ttl')
|
||||
|
||||
try:
|
||||
session = create_session(consul_module,
|
||||
session = create_session(module,
|
||||
name=name,
|
||||
behavior=behavior,
|
||||
ttl=ttl,
|
||||
@@ -242,15 +317,22 @@ def update_session(module, consul_module):
|
||||
module.fail_json(msg="Could not create/update session %s" % e)
|
||||
|
||||
|
||||
def destroy_session(consul_module, session_id):
|
||||
return consul_module.put(('session', 'destroy', session_id))
|
||||
def destroy_session(module, session_id):
|
||||
url = '%s/session/destroy/%s' % (get_consul_url(module), session_id)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.put(
|
||||
url,
|
||||
headers=headers,
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.content == "true"
|
||||
|
||||
|
||||
def remove_session(module, consul_module):
|
||||
def remove_session(module):
|
||||
session_id = module.params.get('id')
|
||||
|
||||
try:
|
||||
destroy_session(consul_module, session_id)
|
||||
destroy_session(module, session_id)
|
||||
|
||||
module.exit_json(changed=True,
|
||||
session_id=session_id)
|
||||
@@ -259,6 +341,12 @@ def remove_session(module, consul_module):
|
||||
session_id, e))
|
||||
|
||||
|
||||
def test_dependencies(module):
|
||||
if not has_requests:
|
||||
raise ImportError(
|
||||
"requests required for this module. See https://pypi.org/project/requests/")
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
checks=dict(type='list', elements='str'),
|
||||
@@ -270,6 +358,10 @@ def main():
|
||||
'release',
|
||||
'delete']),
|
||||
ttl=dict(type='int'),
|
||||
host=dict(type='str', default='localhost'),
|
||||
port=dict(type='int', default=8500),
|
||||
scheme=dict(type='str', default='http'),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
id=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
node=dict(type='str'),
|
||||
@@ -283,7 +375,7 @@ def main():
|
||||
'node',
|
||||
'present']),
|
||||
datacenter=dict(type='str'),
|
||||
**AUTH_ARGUMENTS_SPEC
|
||||
token=dict(type='str', no_log=True),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
@@ -295,10 +387,14 @@ def main():
|
||||
],
|
||||
supports_check_mode=False
|
||||
)
|
||||
consul_module = _ConsulModule(module)
|
||||
|
||||
test_dependencies(module)
|
||||
|
||||
try:
|
||||
execute(module, consul_module)
|
||||
execute(module)
|
||||
except ConnectionError as e:
|
||||
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||
module.params.get('host'), module.params.get('port'), e))
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2024, Florian Apolloner (@apollo13)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: consul_token
|
||||
short_description: Manipulate Consul tokens
|
||||
version_added: 8.3.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of tokens in a consul
|
||||
cluster via the agent. For more details on using and configuring ACLs,
|
||||
see U(https://www.consul.io/docs/guides/acl.html).
|
||||
author:
|
||||
- Florian Apolloner (@apollo13)
|
||||
extends_documentation_fragment:
|
||||
- community.general.consul
|
||||
- community.general.consul.token
|
||||
- community.general.consul.actiongroup_consul
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: partial
|
||||
details:
|
||||
- In check mode the diff will miss operational attributes.
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the token should be present or absent.
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
accessor_id:
|
||||
description:
|
||||
- Specifies a UUID to use as the token's Accessor ID.
|
||||
If not specified a UUID will be generated for this field.
|
||||
type: str
|
||||
secret_id:
|
||||
description:
|
||||
- Specifies a UUID to use as the token's Secret ID.
|
||||
If not specified a UUID will be generated for this field.
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- Free form human readable description of the token.
|
||||
type: str
|
||||
policies:
|
||||
type: list
|
||||
elements: dict
|
||||
description:
|
||||
- List of policies to attach to the token. Each policy is a dict.
|
||||
- If the parameter is left blank, any policies currently assigned will not be changed.
|
||||
- Any empty array (V([])) will clear any policies previously set.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the policy to attach to this token; see M(community.general.consul_policy) for more info.
|
||||
- Either this or O(policies[].id) must be specified.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- The ID of the policy to attach to this token; see M(community.general.consul_policy) for more info.
|
||||
- Either this or O(policies[].name) must be specified.
|
||||
type: str
|
||||
roles:
|
||||
type: list
|
||||
elements: dict
|
||||
description:
|
||||
- List of roles to attach to the token. Each role is a dict.
|
||||
- If the parameter is left blank, any roles currently assigned will not be changed.
|
||||
- Any empty array (V([])) will clear any roles previously set.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The name of the role to attach to this token; see M(community.general.consul_role) for more info.
|
||||
- Either this or O(roles[].id) must be specified.
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- The ID of the role to attach to this token; see M(community.general.consul_role) for more info.
|
||||
- Either this or O(roles[].name) must be specified.
|
||||
type: str
|
||||
templated_policies:
|
||||
description:
|
||||
- The list of templated policies that should be applied to the role.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
template_name:
|
||||
description:
|
||||
- The templated policy name.
|
||||
type: str
|
||||
required: true
|
||||
template_variables:
|
||||
description:
|
||||
- The templated policy variables.
|
||||
- Not all templated policies require variables.
|
||||
type: dict
|
||||
service_identities:
|
||||
type: list
|
||||
elements: dict
|
||||
description:
|
||||
- List of service identities to attach to the token.
|
||||
- If not specified, any service identities currently assigned will not be changed.
|
||||
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||
suboptions:
|
||||
service_name:
|
||||
description:
|
||||
- The name of the service.
|
||||
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||
- May only contain lowercase alphanumeric characters as well as V(-) and V(_).
|
||||
type: str
|
||||
required: true
|
||||
datacenters:
|
||||
description:
|
||||
- The datacenters the token will be effective.
|
||||
- If an empty array (V([])) is specified, the token will valid in all datacenters.
|
||||
- including those which do not yet exist but may in the future.
|
||||
type: list
|
||||
elements: str
|
||||
node_identities:
|
||||
type: list
|
||||
elements: dict
|
||||
description:
|
||||
- List of node identities to attach to the token.
|
||||
- If not specified, any node identities currently assigned will not be changed.
|
||||
- If the parameter is an empty array (V([])), any node identities assigned will be unassigned.
|
||||
suboptions:
|
||||
node_name:
|
||||
description:
|
||||
- The name of the node.
|
||||
- Must not be longer than 256 characters, must start and end with a lowercase alphanumeric character.
|
||||
- May only contain lowercase alphanumeric characters as well as V(-) and V(_).
|
||||
type: str
|
||||
required: true
|
||||
datacenter:
|
||||
description:
|
||||
- The nodes datacenter.
|
||||
- This will result in effective token only being valid in this datacenter.
|
||||
type: str
|
||||
required: true
|
||||
local:
|
||||
description:
|
||||
- If true, indicates that the token should not be replicated globally
|
||||
and instead be local to the current datacenter.
|
||||
type: bool
|
||||
expiration_ttl:
|
||||
description:
|
||||
- This is a convenience field and if set will initialize the C(expiration_time).
|
||||
Can be specified in the form of V(60s) or V(5m) (that is, 60 seconds or 5 minutes,
|
||||
respectively). Ingored when the token is updated!
|
||||
type: str
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create / Update a token by accessor_id
|
||||
community.general.consul_token:
|
||||
state: present
|
||||
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||
token: 8adddd91-0bd6-d41d-ae1a-3b49cfa9a0e8
|
||||
roles:
|
||||
- name: role1
|
||||
- name: role2
|
||||
service_identities:
|
||||
- service_name: service1
|
||||
datacenters: [dc1, dc2]
|
||||
node_identities:
|
||||
- node_name: node1
|
||||
datacenter: dc1
|
||||
expiration_ttl: 50m
|
||||
|
||||
- name: Delete a token
|
||||
community.general.consul_token:
|
||||
state: absent
|
||||
accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||
token: 8adddd91-0bd6-d41d-ae1a-3b49cfa9a0e8
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
token:
|
||||
description: The token as returned by the consul HTTP API.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
AccessorID: 07a7de84-c9c7-448a-99cc-beaf682efd21
|
||||
CreateIndex: 632
|
||||
CreateTime: "2024-01-14T21:53:01.402749174+01:00"
|
||||
Description: Testing
|
||||
Hash: rj5PeDHddHslkpW7Ij4OD6N4bbSXiecXFmiw2SYXg2A=
|
||||
Local: false
|
||||
ModifyIndex: 633
|
||||
SecretID: bd380fba-da17-7cee-8576-8d6427c6c930
|
||||
ServiceIdentities: [{"ServiceName": "test"}]
|
||||
operation:
|
||||
description: The operation performed.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.consul import (
|
||||
AUTH_ARGUMENTS_SPEC,
|
||||
_ConsulModule,
|
||||
)
|
||||
|
||||
|
||||
def normalize_link_obj(api_obj, module_obj, key):
|
||||
api_objs = api_obj.get(key)
|
||||
module_objs = module_obj.get(key)
|
||||
if api_objs is None or module_objs is None:
|
||||
return
|
||||
name_to_id = {i["Name"]: i["ID"] for i in api_objs}
|
||||
id_to_name = {i["ID"]: i["Name"] for i in api_objs}
|
||||
|
||||
for obj in module_objs:
|
||||
identifier = obj.get("ID")
|
||||
name = obj.get("Name)")
|
||||
if identifier and not name and identifier in id_to_name:
|
||||
obj["Name"] = id_to_name[identifier]
|
||||
if not identifier and name and name in name_to_id:
|
||||
obj["ID"] = name_to_id[name]
|
||||
|
||||
|
||||
class ConsulTokenModule(_ConsulModule):
|
||||
api_endpoint = "acl/token"
|
||||
result_key = "token"
|
||||
unique_identifier = "accessor_id"
|
||||
|
||||
create_only_fields = {"expiration_ttl"}
|
||||
|
||||
def needs_update(self, api_obj, module_obj):
|
||||
# SecretID is usually not supplied
|
||||
if "SecretID" not in module_obj and "SecretID" in api_obj:
|
||||
del api_obj["SecretID"]
|
||||
normalize_link_obj(api_obj, module_obj, "Roles")
|
||||
normalize_link_obj(api_obj, module_obj, "Policies")
|
||||
# ExpirationTTL is only supported on create, not for update
|
||||
# it writes to ExpirationTime, so we need to remove that as well
|
||||
if "ExpirationTTL" in module_obj:
|
||||
del module_obj["ExpirationTTL"]
|
||||
return super(ConsulTokenModule, self).needs_update(api_obj, module_obj)
|
||||
|
||||
|
||||
NAME_ID_SPEC = dict(
|
||||
name=dict(type="str"),
|
||||
id=dict(type="str"),
|
||||
)
|
||||
|
||||
NODE_ID_SPEC = dict(
|
||||
node_name=dict(type="str", required=True),
|
||||
datacenter=dict(type="str", required=True),
|
||||
)
|
||||
|
||||
SERVICE_ID_SPEC = dict(
|
||||
service_name=dict(type="str", required=True),
|
||||
datacenters=dict(type="list", elements="str"),
|
||||
)
|
||||
|
||||
TEMPLATE_POLICY_SPEC = dict(
|
||||
template_name=dict(type="str", required=True),
|
||||
template_variables=dict(type="dict"),
|
||||
)
|
||||
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
"description": dict(),
|
||||
"accessor_id": dict(),
|
||||
"secret_id": dict(no_log=True),
|
||||
"roles": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=NAME_ID_SPEC,
|
||||
mutually_exclusive=[("name", "id")],
|
||||
required_one_of=[("name", "id")],
|
||||
),
|
||||
"policies": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=NAME_ID_SPEC,
|
||||
mutually_exclusive=[("name", "id")],
|
||||
required_one_of=[("name", "id")],
|
||||
),
|
||||
"templated_policies": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=TEMPLATE_POLICY_SPEC,
|
||||
),
|
||||
"node_identities": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=NODE_ID_SPEC,
|
||||
),
|
||||
"service_identities": dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=SERVICE_ID_SPEC,
|
||||
),
|
||||
"local": dict(type="bool"),
|
||||
"expiration_ttl": dict(type="str"),
|
||||
"state": dict(default="present", choices=["present", "absent"]),
|
||||
}
|
||||
_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
_ARGUMENT_SPEC,
|
||||
required_if=[("state", "absent", ["accessor_id"])],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
consul_module = ConsulTokenModule(module)
|
||||
consul_module.execute()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user