mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-29 18:06:53 +00:00
Compare commits
370 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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:
|
||||
|
||||
67
.github/BOTMETA.yml
vendored
67
.github/BOTMETA.yml
vendored
@@ -97,9 +97,6 @@ files:
|
||||
$connections/funcd.py:
|
||||
maintainers: mscherer
|
||||
$connections/iocage.py: {}
|
||||
$connections/incus.py:
|
||||
labels: incus
|
||||
maintainers: stgraber
|
||||
$connections/jail.py:
|
||||
maintainers: $team_ansible_core
|
||||
$connections/lxc.py: {}
|
||||
@@ -122,7 +119,7 @@ files:
|
||||
labels: hwc
|
||||
maintainers: $team_huawei
|
||||
$doc_fragments/nomad.py:
|
||||
maintainers: chris93111 apecnascimento
|
||||
maintainers: chris93111
|
||||
$doc_fragments/xenserver.py:
|
||||
labels: xenserver
|
||||
maintainers: bvitnik
|
||||
@@ -136,8 +133,6 @@ files:
|
||||
maintainers: giner
|
||||
$filters/from_csv.py:
|
||||
maintainers: Ajpantuso
|
||||
$filters/from_ini.py:
|
||||
maintainers: sscheib
|
||||
$filters/groupby_as_dict.py:
|
||||
maintainers: felixfontein
|
||||
$filters/hashids.py:
|
||||
@@ -158,8 +153,6 @@ files:
|
||||
maintainers: resmo
|
||||
$filters/to_hours.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_ini.py:
|
||||
maintainers: sscheib
|
||||
$filters/to_milliseconds.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_minutes.yml:
|
||||
@@ -241,8 +234,6 @@ files:
|
||||
$lookups/filetree.py:
|
||||
maintainers: dagwieers
|
||||
$lookups/flattened.py: {}
|
||||
$lookups/github_app_access_token.py:
|
||||
maintainers: weisheng-p
|
||||
$lookups/hiera.py:
|
||||
maintainers: jparrill
|
||||
$lookups/keyring.py: {}
|
||||
@@ -484,8 +475,6 @@ files:
|
||||
maintainers: russoz
|
||||
$modules/dnf_versionlock.py:
|
||||
maintainers: moreda
|
||||
$modules/dnf_config_manager.py:
|
||||
maintainers: ahyattdev
|
||||
$modules/dnsimple.py:
|
||||
maintainers: drcapulet
|
||||
$modules/dnsimple_info.py:
|
||||
@@ -508,9 +497,6 @@ files:
|
||||
$modules/facter.py:
|
||||
labels: facter
|
||||
maintainers: $team_ansible_core gamethis
|
||||
$modules/facter_facts.py:
|
||||
labels: facter
|
||||
maintainers: russoz $team_ansible_core gamethis
|
||||
$modules/filesize.py:
|
||||
maintainers: quidame
|
||||
$modules/filesystem.py:
|
||||
@@ -537,8 +523,6 @@ files:
|
||||
maintainers: russoz
|
||||
$modules/git_config.py:
|
||||
maintainers: djmattyg007 mgedmin
|
||||
$modules/git_config_info.py:
|
||||
maintainers: guenhter
|
||||
$modules/github_:
|
||||
maintainers: stpierre
|
||||
$modules/github_deploy_key.py:
|
||||
@@ -560,8 +544,6 @@ files:
|
||||
ignore: dj-wasabi
|
||||
$modules/gitlab_branch.py:
|
||||
maintainers: paytroff
|
||||
$modules/gitlab_issue.py:
|
||||
maintainers: zvaraondrej
|
||||
$modules/gitlab_merge_request.py:
|
||||
maintainers: zvaraondrej
|
||||
$modules/gitlab_project_variable.py:
|
||||
@@ -661,8 +643,6 @@ files:
|
||||
$modules/ipa_:
|
||||
maintainers: $team_ipa
|
||||
ignore: fxfitz
|
||||
$modules/ipa_dnsrecord.py:
|
||||
maintainers: $team_ipa jwbernin
|
||||
$modules/ipbase_info.py:
|
||||
maintainers: dominikkukacka
|
||||
$modules/ipa_pwpolicy.py:
|
||||
@@ -758,12 +738,8 @@ files:
|
||||
maintainers: elfelip
|
||||
$modules/keycloak_user_federation.py:
|
||||
maintainers: laurpaum
|
||||
$modules/keycloak_component_info.py:
|
||||
maintainers: desand01
|
||||
$modules/keycloak_user_rolemapping.py:
|
||||
maintainers: bratwurzt
|
||||
$modules/keycloak_realm_rolemapping.py:
|
||||
maintainers: agross mhuysamen Gaetan2907
|
||||
$modules/keyring.py:
|
||||
maintainers: ahussey-redhat
|
||||
$modules/keyring_info.py:
|
||||
@@ -892,7 +868,7 @@ files:
|
||||
$modules/nmcli.py:
|
||||
maintainers: alcamie101
|
||||
$modules/nomad_:
|
||||
maintainers: chris93111 apecnascimento
|
||||
maintainers: chris93111
|
||||
$modules/nosh.py:
|
||||
maintainers: tacatac
|
||||
$modules/npm.py:
|
||||
@@ -1051,10 +1027,6 @@ files:
|
||||
maintainers: helldorado
|
||||
$modules/proxmox_nic.py:
|
||||
maintainers: Kogelvis
|
||||
$modules/proxmox_node_info.py:
|
||||
maintainers: jwbernin
|
||||
$modules/proxmox_storage_contents_info.py:
|
||||
maintainers: l00ptr
|
||||
$modules/proxmox_tasks_info:
|
||||
maintainers: paginabianca
|
||||
$modules/proxmox_template.py:
|
||||
@@ -1425,41 +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_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
|
||||
|
||||
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 }}
|
||||
|
||||
1036
CHANGELOG.rst
1036
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.rst).
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-7/CHANGELOG.rst).
|
||||
|
||||
## 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
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_alicloud:
|
||||
|
||||
Alibaba Cloud Compute Services Guide
|
||||
====================================
|
||||
|
||||
Introduction
|
||||
````````````
|
||||
|
||||
The community.general collection contains several modules for controlling and managing Alibaba Cloud Compute Services (Alicloud). This guide
|
||||
explains how to use the Alicloud Ansible modules together.
|
||||
|
||||
All Alicloud modules require ``footmark`` - install it on your control machine with ``pip install footmark``.
|
||||
|
||||
Cloud modules, including Alicloud modules, are usually executed on your local machine (the control machine) with ``connection: local``, rather than on remote machines defined in your hosts.
|
||||
|
||||
Normally, you'll use the following pattern for plays that provision Alicloud resources:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
vars:
|
||||
- ...
|
||||
tasks:
|
||||
- ...
|
||||
|
||||
Authentication
|
||||
``````````````
|
||||
|
||||
You can specify your Alicloud authentication credentials (access key and secret key) by passing them as
|
||||
environment variables or by storing them in a vars file.
|
||||
|
||||
To pass authentication credentials as environment variables:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
export ALICLOUD_ACCESS_KEY='Alicloud123'
|
||||
export ALICLOUD_SECRET_KEY='AlicloudSecret123'
|
||||
|
||||
To store authentication credentials in a vars file, encrypt them with :ref:`Ansible Vault <vault>` to keep them secure, then list them:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
alicloud_access_key: "--REMOVED--"
|
||||
alicloud_secret_key: "--REMOVED--"
|
||||
|
||||
Note that if you store your credentials in a vars file, you need to refer to them in each Alicloud module. For example:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- community.general.ali_instance:
|
||||
alicloud_access_key: "{{ alicloud_access_key }}"
|
||||
alicloud_secret_key: "{{ alicloud_secret_key }}"
|
||||
image_id: "..."
|
||||
|
||||
Provisioning
|
||||
````````````
|
||||
|
||||
Alicloud modules create Alicloud ECS instances (:ansplugin:`community.general.ali_instance#module`) and retrieve information on these (:ansplugin:`community.general.ali_instance_info#module`).
|
||||
|
||||
You can use the ``count`` parameter to control the number of resources you create or terminate. For example, if you want exactly 5 instances tagged ``NewECS``, set the ``count`` of instances to 5 and the ``count_tag`` to ``NewECS``, as shown in the last task of the example playbook below. If there are no instances with the tag ``NewECS``, the task creates 5 new instances. If there are 2 instances with that tag, the task creates 3 more. If there are 8 instances with that tag, the task terminates 3 of those instances.
|
||||
|
||||
If you do not specify a ``count_tag``, the task creates the number of instances you specify in ``count`` with the ``instance_name`` you provide.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# alicloud_setup.yml
|
||||
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
|
||||
tasks:
|
||||
- name: Create a set of instances
|
||||
community.general.ali_instance:
|
||||
instance_type: ecs.n4.small
|
||||
image_id: "{{ ami_id }}"
|
||||
instance_name: "My-new-instance"
|
||||
instance_tags:
|
||||
Name: NewECS
|
||||
Version: 0.0.1
|
||||
count: 5
|
||||
count_tag:
|
||||
Name: NewECS
|
||||
allocate_public_ip: true
|
||||
max_bandwidth_out: 50
|
||||
register: create_instance
|
||||
|
||||
In the example playbook above, data about the instances created by this playbook is saved in the variable defined by the ``register`` keyword in the task.
|
||||
|
||||
Each Alicloud module offers a variety of parameter options. Not all options are demonstrated in the above example. See each individual module for further details and examples.
|
||||
@@ -1,49 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_online:
|
||||
|
||||
****************
|
||||
Online.net Guide
|
||||
****************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Online is a French hosting company mainly known for providing bare-metal servers named Dedibox.
|
||||
Check it out: `https://www.online.net/en <https://www.online.net/en>`_
|
||||
|
||||
Dynamic inventory for Online resources
|
||||
--------------------------------------
|
||||
|
||||
Ansible has a dynamic inventory plugin that can list your resources.
|
||||
|
||||
1. Create a YAML configuration such as ``online_inventory.yml`` with this content:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
plugin: community.general.online
|
||||
|
||||
2. Set your ``ONLINE_TOKEN`` environment variable with your token.
|
||||
|
||||
You need to open an account and log into it before you can get a token.
|
||||
You can find your token at the following page: `https://console.online.net/en/api/access <https://console.online.net/en/api/access>`_
|
||||
|
||||
3. You can test that your inventory is working by running:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ansible-inventory -v -i online_inventory.yml --list
|
||||
|
||||
|
||||
4. Now you can run your playbook or any other module with this inventory:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
$ ansible all -i online_inventory.yml -m ping
|
||||
sd-96735 | SUCCESS => {
|
||||
"changed": false,
|
||||
"ping": "pong"
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_packet:
|
||||
|
||||
**********************************
|
||||
Packet.net Guide
|
||||
**********************************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
`Packet.net <https://packet.net>`_ is a bare metal infrastructure host that is supported by the community.general collection through six cloud modules. The six modules are:
|
||||
|
||||
- :ansplugin:`community.general.packet_device#module`: manages servers on Packet. You can use this module to create, restart and delete devices.
|
||||
- :ansplugin:`community.general.packet_ip_subnet#module`: assign IP subnet to a bare metal server
|
||||
- :ansplugin:`community.general.packet_project#module`: create/delete a project in Packet host
|
||||
- :ansplugin:`community.general.packet_sshkey#module`: adds a public SSH key from file or value to the Packet infrastructure. Every subsequently-created device will have this public key installed in .ssh/authorized_keys.
|
||||
- :ansplugin:`community.general.packet_volume#module`: create/delete a volume in Packet host
|
||||
- :ansplugin:`community.general.packet_volume_attachment#module`: attach/detach a volume to a device in the Packet host
|
||||
|
||||
Note, this guide assumes you are familiar with Ansible and how it works. If you are not, have a look at their :ref:`docs <ansible_documentation>` before getting started.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
The Packet modules connect to the Packet API using the `packet-python package <https://pypi.org/project/packet-python/>`_. You can install it with pip:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install packet-python
|
||||
|
||||
In order to check the state of devices created by Ansible on Packet, it is a good idea to install one of the `Packet CLI clients <https://www.packet.net/developers/integrations/>`_. Otherwise you can check them through the `Packet portal <https://app.packet.net/portal>`_.
|
||||
|
||||
To use the modules you will need a Packet API token. You can generate an API token through the Packet portal `here <https://app.packet.net/portal#/api-keys>`__. The simplest way to authenticate yourself is to set the Packet API token in an environment variable:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export PACKET_API_TOKEN=Bfse9F24SFtfs423Gsd3ifGsd43sSdfs
|
||||
|
||||
If you are not comfortable exporting your API token, you can pass it as a parameter to the modules.
|
||||
|
||||
On Packet, devices and reserved IP addresses belong to `projects <https://www.packet.com/developers/api/#projects>`_. In order to use the packet_device module, you need to specify the UUID of the project in which you want to create or manage devices. You can find a project's UUID in the Packet portal `here <https://app.packet.net/portal#/projects/list/table/>`_ (it is just under the project table) or through one of the available `CLIs <https://www.packet.net/developers/integrations/>`_.
|
||||
|
||||
|
||||
If you want to use a new SSH key pair in this tutorial, you can generate it to ``./id_rsa`` and ``./id_rsa.pub`` as:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ssh-keygen -t rsa -f ./id_rsa
|
||||
|
||||
If you want to use an existing key pair, just copy the private and public key over to the playbook directory.
|
||||
|
||||
|
||||
Device Creation
|
||||
===============
|
||||
|
||||
The following code block is a simple playbook that creates one `Type 0 <https://www.packet.com/cloud/servers/t1-small/>`_ server (the ``plan`` parameter). You have to supply ``plan`` and ``operating_system``. ``location`` defaults to ``ewr1`` (Parsippany, NJ). You can find all the possible values for the parameters through a `CLI client <https://www.packet.net/developers/integrations/>`_.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_create.yml
|
||||
|
||||
- name: Create Ubuntu device
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_sshkey:
|
||||
key_file: ./id_rsa.pub
|
||||
label: tutorial key
|
||||
|
||||
- community.general.packet_device:
|
||||
project_id: <your_project_id>
|
||||
hostnames: myserver
|
||||
operating_system: ubuntu_16_04
|
||||
plan: baremetal_0
|
||||
facility: sjc1
|
||||
|
||||
After running ``ansible-playbook playbook_create.yml``, you should have a server provisioned on Packet. You can verify through a CLI or in the `Packet portal <https://app.packet.net/portal#/projects/list/table>`__.
|
||||
|
||||
If you get an error with the message "failed to set machine state present, error: Error 404: Not Found", please verify your project UUID.
|
||||
|
||||
|
||||
Updating Devices
|
||||
================
|
||||
|
||||
The two parameters used to uniquely identify Packet devices are: "device_ids" and "hostnames". Both parameters accept either a single string (later converted to a one-element list), or a list of strings.
|
||||
|
||||
The ``device_ids`` and ``hostnames`` parameters are mutually exclusive. The following values are all acceptable:
|
||||
|
||||
- device_ids: ``a27b7a83-fc93-435b-a128-47a5b04f2dcf``
|
||||
|
||||
- hostnames: ``mydev1``
|
||||
|
||||
- device_ids: ``[a27b7a83-fc93-435b-a128-47a5b04f2dcf, 4887130f-0ccd-49a0-99b0-323c1ceb527b]``
|
||||
|
||||
- hostnames: ``[mydev1, mydev2]``
|
||||
|
||||
In addition, hostnames can contain a special ``%d`` formatter along with a ``count`` parameter that lets you easily expand hostnames that follow a simple name and number pattern; in other words, ``hostnames: "mydev%d", count: 2`` will expand to [mydev1, mydev2].
|
||||
|
||||
If your playbook acts on existing Packet devices, you can only pass the ``hostname`` and ``device_ids`` parameters. The following playbook shows how you can reboot a specific Packet device by setting the ``hostname`` parameter:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_reboot.yml
|
||||
|
||||
- name: reboot myserver
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_device:
|
||||
project_id: <your_project_id>
|
||||
hostnames: myserver
|
||||
state: rebooted
|
||||
|
||||
You can also identify specific Packet devices with the ``device_ids`` parameter. The device's UUID can be found in the `Packet Portal <https://app.packet.net/portal>`_ or by using a `CLI <https://www.packet.net/developers/integrations/>`_. The following playbook removes a Packet device using the ``device_ids`` field:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_remove.yml
|
||||
|
||||
- name: remove a device
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_device:
|
||||
project_id: <your_project_id>
|
||||
device_ids: <myserver_device_id>
|
||||
state: absent
|
||||
|
||||
|
||||
More Complex Playbooks
|
||||
======================
|
||||
|
||||
In this example, we will create a CoreOS cluster with `user data <https://packet.com/developers/docs/servers/key-features/user-data/>`_.
|
||||
|
||||
|
||||
The CoreOS cluster will use `etcd <https://etcd.io/>`_ for discovery of other servers in the cluster. Before provisioning your servers, you will need to generate a discovery token for your cluster:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ curl -w "\n" 'https://discovery.etcd.io/new?size=3'
|
||||
|
||||
The following playbook will create an SSH key, 3 Packet servers, and then wait until SSH is ready (or until 5 minutes passed). Make sure to substitute the discovery token URL in ``user_data``, and the ``project_id`` before running ``ansible-playbook``. Also, feel free to change ``plan`` and ``facility``.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# playbook_coreos.yml
|
||||
|
||||
- name: Start 3 CoreOS nodes in Packet and wait until SSH is ready
|
||||
hosts: localhost
|
||||
tasks:
|
||||
|
||||
- community.general.packet_sshkey:
|
||||
key_file: ./id_rsa.pub
|
||||
label: new
|
||||
|
||||
- community.general.packet_device:
|
||||
hostnames: [coreos-one, coreos-two, coreos-three]
|
||||
operating_system: coreos_beta
|
||||
plan: baremetal_0
|
||||
facility: ewr1
|
||||
project_id: <your_project_id>
|
||||
wait_for_public_IPv: 4
|
||||
user_data: |
|
||||
#cloud-config
|
||||
coreos:
|
||||
etcd2:
|
||||
discovery: https://discovery.etcd.io/<token>
|
||||
advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001
|
||||
initial-advertise-peer-urls: http://$private_ipv4:2380
|
||||
listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001
|
||||
listen-peer-urls: http://$private_ipv4:2380
|
||||
fleet:
|
||||
public-ip: $private_ipv4
|
||||
units:
|
||||
- name: etcd2.service
|
||||
command: start
|
||||
- name: fleet.service
|
||||
command: start
|
||||
register: newhosts
|
||||
|
||||
- name: wait for ssh
|
||||
ansible.builtin.wait_for:
|
||||
delay: 1
|
||||
host: "{{ item.public_ipv4 }}"
|
||||
port: 22
|
||||
state: started
|
||||
timeout: 500
|
||||
loop: "{{ newhosts.results[0].devices }}"
|
||||
|
||||
|
||||
As with most Ansible modules, the default states of the Packet modules are idempotent, meaning the resources in your project will remain the same after re-runs of a playbook. Thus, we can keep the ``packet_sshkey`` module call in our playbook. If the public key is already in your Packet account, the call will have no effect.
|
||||
|
||||
The second module call provisions 3 Packet Type 0 (specified using the ``plan`` parameter) servers in the project identified by the ``project_id`` parameter. The servers are all provisioned with CoreOS beta (the ``operating_system`` parameter) and are customized with cloud-config user data passed to the ``user_data`` parameter.
|
||||
|
||||
The ``packet_device`` module has a ``wait_for_public_IPv`` that is used to specify the version of the IP address to wait for (valid values are ``4`` or ``6`` for IPv4 or IPv6). If specified, Ansible will wait until the GET API call for a device contains an Internet-routeable IP address of the specified version. When referring to an IP address of a created device in subsequent module calls, it is wise to use the ``wait_for_public_IPv`` parameter, or ``state: active`` in the packet_device module call.
|
||||
|
||||
Run the playbook:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ansible-playbook playbook_coreos.yml
|
||||
|
||||
Once the playbook quits, your new devices should be reachable through SSH. Try to connect to one and check if etcd has started properly:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
tomk@work $ ssh -i id_rsa core@$one_of_the_servers_ip
|
||||
core@coreos-one ~ $ etcdctl cluster-health
|
||||
|
||||
If you have any questions or comments let us know! help@packet.net
|
||||
@@ -1,320 +0,0 @@
|
||||
..
|
||||
Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_scaleway:
|
||||
|
||||
**************
|
||||
Scaleway Guide
|
||||
**************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
`Scaleway <https://scaleway.com>`_ is a cloud provider supported by the community.general collection through a set of plugins and modules.
|
||||
Those modules are:
|
||||
|
||||
- :ansplugin:`community.general.scaleway_compute#module`: manages servers on Scaleway. You can use this module to create, restart and delete servers.
|
||||
- :ansplugin:`community.general.scaleway_compute_private_network#module`
|
||||
- :ansplugin:`community.general.scaleway_container#module`
|
||||
- :ansplugin:`community.general.scaleway_container_info#module`
|
||||
- :ansplugin:`community.general.scaleway_container_namespace_info#module`
|
||||
- :ansplugin:`community.general.scaleway_container_namespace#module`
|
||||
- :ansplugin:`community.general.scaleway_container_registry_info#module`
|
||||
- :ansplugin:`community.general.scaleway_container_registry#module`
|
||||
- :ansplugin:`community.general.scaleway_database_backup#module`
|
||||
- :ansplugin:`community.general.scaleway_function#module`
|
||||
- :ansplugin:`community.general.scaleway_function_info#module`
|
||||
- :ansplugin:`community.general.scaleway_function_namespace_info#module`
|
||||
- :ansplugin:`community.general.scaleway_function_namespace#module`
|
||||
- :ansplugin:`community.general.scaleway_image_info#module`
|
||||
- :ansplugin:`community.general.scaleway_ip#module`
|
||||
- :ansplugin:`community.general.scaleway_ip_info#module`
|
||||
- :ansplugin:`community.general.scaleway_lb#module`
|
||||
- :ansplugin:`community.general.scaleway_organization_info#module`
|
||||
- :ansplugin:`community.general.scaleway_private_network#module`
|
||||
- :ansplugin:`community.general.scaleway_security_group#module`
|
||||
- :ansplugin:`community.general.scaleway_security_group_info#module`
|
||||
- :ansplugin:`community.general.scaleway_security_group_rule#module`
|
||||
- :ansplugin:`community.general.scaleway_server_info#module`
|
||||
- :ansplugin:`community.general.scaleway_snapshot_info#module`
|
||||
- :ansplugin:`community.general.scaleway_sshkey#module`: adds a public SSH key from a file or value to the Packet infrastructure. Every subsequently-created device will have this public key installed in .ssh/authorized_keys.
|
||||
- :ansplugin:`community.general.scaleway_user_data#module`
|
||||
- :ansplugin:`community.general.scaleway_volume#module`: manages volumes on Scaleway.
|
||||
- :ansplugin:`community.general.scaleway_volume_info#module`
|
||||
|
||||
The plugins are:
|
||||
|
||||
- :ansplugin:`community.general.scaleway#inventory`: inventory plugin
|
||||
|
||||
|
||||
.. note::
|
||||
This guide assumes you are familiar with Ansible and how it works.
|
||||
If you are not, have a look at :ref:`ansible_documentation` before getting started.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
The Scaleway modules and inventory script connect to the Scaleway API using `Scaleway REST API <https://developer.scaleway.com>`_.
|
||||
To use the modules and inventory script you will need a Scaleway API token.
|
||||
You can generate an API token through the `Scaleway console's credential page <https://cloud.scaleway.com/#/credentials>`__.
|
||||
The simplest way to authenticate yourself is to set the Scaleway API token in an environment variable:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export SCW_TOKEN=00000000-1111-2222-3333-444444444444
|
||||
|
||||
If you are not comfortable exporting your API token, you can pass it as a parameter to the modules using the ``api_token`` argument.
|
||||
|
||||
If you want to use a new SSH key pair in this tutorial, you can generate it to ``./id_rsa`` and ``./id_rsa.pub`` as:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ssh-keygen -t rsa -f ./id_rsa
|
||||
|
||||
If you want to use an existing key pair, just copy the private and public key over to the playbook directory.
|
||||
|
||||
How to add an SSH key?
|
||||
======================
|
||||
|
||||
Connection to Scaleway Compute nodes use Secure Shell.
|
||||
SSH keys are stored at the account level, which means that you can reuse the same SSH key in multiple nodes.
|
||||
The first step to configure Scaleway compute resources is to have at least one SSH key configured.
|
||||
|
||||
:ansplugin:`community.general.scaleway_sshkey#module` is a module that manages SSH keys on your Scaleway account.
|
||||
You can add an SSH key to your account by including the following task in a playbook:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Add SSH key"
|
||||
community.general.scaleway_sshkey:
|
||||
ssh_pub_key: "ssh-rsa AAAA..."
|
||||
state: "present"
|
||||
|
||||
The ``ssh_pub_key`` parameter contains your ssh public key as a string. Here is an example inside a playbook:
|
||||
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Test SSH key lifecycle on a Scaleway account
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
environment:
|
||||
SCW_API_KEY: ""
|
||||
|
||||
tasks:
|
||||
|
||||
- community.general.scaleway_sshkey:
|
||||
ssh_pub_key: "ssh-rsa AAAAB...424242 developer@example.com"
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- result is success and result is changed
|
||||
|
||||
How to create a compute instance?
|
||||
=================================
|
||||
|
||||
Now that we have an SSH key configured, the next step is to spin up a server!
|
||||
:ansplugin:`community.general.scaleway_compute#module` is a module that can create, update and delete Scaleway compute instances:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Create a server
|
||||
community.general.scaleway_compute:
|
||||
name: foobar
|
||||
state: present
|
||||
image: 00000000-1111-2222-3333-444444444444
|
||||
organization: 00000000-1111-2222-3333-444444444444
|
||||
region: ams1
|
||||
commercial_type: START1-S
|
||||
|
||||
Here are the parameter details for the example shown above:
|
||||
|
||||
- ``name`` is the name of the instance (the one that will show up in your web console).
|
||||
- ``image`` is the UUID of the system image you would like to use.
|
||||
A list of all images is available for each availability zone.
|
||||
- ``organization`` represents the organization that your account is attached to.
|
||||
- ``region`` represents the Availability Zone which your instance is in (for this example, ``par1`` and ``ams1``).
|
||||
- ``commercial_type`` represents the name of the commercial offers.
|
||||
You can check out the Scaleway pricing page to find which instance is right for you.
|
||||
|
||||
Take a look at this short playbook to see a working example using ``scaleway_compute``:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Test compute instance lifecycle on a Scaleway account
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
environment:
|
||||
SCW_API_KEY: ""
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Create a server
|
||||
register: server_creation_task
|
||||
community.general.scaleway_compute:
|
||||
name: foobar
|
||||
state: present
|
||||
image: 00000000-1111-2222-3333-444444444444
|
||||
organization: 00000000-1111-2222-3333-444444444444
|
||||
region: ams1
|
||||
commercial_type: START1-S
|
||||
wait: true
|
||||
|
||||
- ansible.builtin.debug:
|
||||
var: server_creation_task
|
||||
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- server_creation_task is success
|
||||
- server_creation_task is changed
|
||||
|
||||
- name: Run it
|
||||
community.general.scaleway_compute:
|
||||
name: foobar
|
||||
state: running
|
||||
image: 00000000-1111-2222-3333-444444444444
|
||||
organization: 00000000-1111-2222-3333-444444444444
|
||||
region: ams1
|
||||
commercial_type: START1-S
|
||||
wait: true
|
||||
tags:
|
||||
- web_server
|
||||
register: server_run_task
|
||||
|
||||
- ansible.builtin.debug:
|
||||
var: server_run_task
|
||||
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- server_run_task is success
|
||||
- server_run_task is changed
|
||||
|
||||
Dynamic Inventory Plugin
|
||||
========================
|
||||
|
||||
Ansible ships with :ansplugin:`community.general.scaleway#inventory`.
|
||||
You can now get a complete inventory of your Scaleway resources through this plugin and filter it on
|
||||
different parameters (``regions`` and ``tags`` are currently supported).
|
||||
|
||||
Let us create an example!
|
||||
Suppose that we want to get all hosts that got the tag web_server.
|
||||
Create a file named ``scaleway_inventory.yml`` with the following content:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
plugin: community.general.scaleway
|
||||
regions:
|
||||
- ams1
|
||||
- par1
|
||||
tags:
|
||||
- web_server
|
||||
|
||||
This inventory means that we want all hosts that got the tag ``web_server`` on the zones ``ams1`` and ``par1``.
|
||||
Once you have configured this file, you can get the information using the following command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ansible-inventory --list -i scaleway_inventory.yml
|
||||
|
||||
The output will be:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
"dd8e3ae9-0c7c-459e-bc7b-aba8bfa1bb8d": {
|
||||
"ansible_verbosity": 6,
|
||||
"arch": "x86_64",
|
||||
"commercial_type": "START1-S",
|
||||
"hostname": "foobar",
|
||||
"ipv4": "192.0.2.1",
|
||||
"organization": "00000000-1111-2222-3333-444444444444",
|
||||
"state": "running",
|
||||
"tags": [
|
||||
"web_server"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"all": {
|
||||
"children": [
|
||||
"ams1",
|
||||
"par1",
|
||||
"ungrouped",
|
||||
"web_server"
|
||||
]
|
||||
},
|
||||
"ams1": {},
|
||||
"par1": {
|
||||
"hosts": [
|
||||
"dd8e3ae9-0c7c-459e-bc7b-aba8bfa1bb8d"
|
||||
]
|
||||
},
|
||||
"ungrouped": {},
|
||||
"web_server": {
|
||||
"hosts": [
|
||||
"dd8e3ae9-0c7c-459e-bc7b-aba8bfa1bb8d"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
As you can see, we get different groups of hosts.
|
||||
``par1`` and ``ams1`` are groups based on location.
|
||||
``web_server`` is a group based on a tag.
|
||||
|
||||
In case a filter parameter is not defined, the plugin supposes all values possible are wanted.
|
||||
This means that for each tag that exists on your Scaleway compute nodes, a group based on each tag will be created.
|
||||
|
||||
Scaleway S3 object storage
|
||||
==========================
|
||||
|
||||
`Object Storage <https://www.scaleway.com/object-storage>`_ allows you to store any kind of objects (documents, images, videos, and so on).
|
||||
As the Scaleway API is S3 compatible, Ansible supports it natively through the amazon.aws modules: :ansplugin:`amazon.aws.s3_bucket#module`, :ansplugin:`amazon.aws.s3_object#module`.
|
||||
|
||||
You can find many examples in the `scaleway_s3 integration tests <https://github.com/ansible/ansible-legacy-tests/tree/devel/test/legacy/roles/scaleway_s3>`_.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- hosts: myserver
|
||||
vars:
|
||||
scaleway_region: nl-ams
|
||||
s3_url: https://s3.nl-ams.scw.cloud
|
||||
environment:
|
||||
# AWS_ACCESS_KEY matches your scaleway organization id available at https://cloud.scaleway.com/#/account
|
||||
AWS_ACCESS_KEY: 00000000-1111-2222-3333-444444444444
|
||||
# AWS_SECRET_KEY matches a secret token that you can retrieve at https://cloud.scaleway.com/#/credentials
|
||||
AWS_SECRET_KEY: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
|
||||
module_defaults:
|
||||
group/amazon.aws.aws:
|
||||
s3_url: '{{ s3_url }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
tasks:
|
||||
# use a fact instead of a variable, otherwise template is evaluate each time variable is used
|
||||
- ansible.builtin.set_fact:
|
||||
bucket_name: "{{ 99999999 | random | to_uuid }}"
|
||||
|
||||
# "requester_pays:" is mandatory because Scaleway does not implement related API
|
||||
# another way is to use amazon.aws.s3_object and "mode: create" !
|
||||
- amazon.aws.s3_bucket:
|
||||
name: '{{ bucket_name }}'
|
||||
requester_pays:
|
||||
|
||||
- name: Another way to create the bucket
|
||||
amazon.aws.s3_object:
|
||||
bucket: '{{ bucket_name }}'
|
||||
mode: create
|
||||
encrypt: false
|
||||
register: bucket_creation_check
|
||||
|
||||
- name: add something in the bucket
|
||||
amazon.aws.s3_object:
|
||||
mode: put
|
||||
bucket: '{{ bucket_name }}'
|
||||
src: /tmp/test.txt # needs to be created before
|
||||
object: test.txt
|
||||
encrypt: false # server side encryption must be disabled
|
||||
12
galaxy.yml
12
galaxy.yml
@@ -5,17 +5,17 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 8.2.0
|
||||
version: 7.5.3
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
description: >-
|
||||
The community.general collection is a part of the Ansible package and includes many modules and
|
||||
plugins supported by Ansible community which are not part of more specialized community collections.
|
||||
description: null
|
||||
license_file: COPYING
|
||||
tags:
|
||||
- community
|
||||
tags: [community]
|
||||
# NOTE: No dependencies are expected to be added here
|
||||
# dependencies:
|
||||
repository: https://github.com/ansible-collections/community.general
|
||||
documentation: https://docs.ansible.com/ansible/latest/collections/community/general/
|
||||
homepage: https://github.com/ansible-collections/community.general
|
||||
issues: https://github.com/ansible-collections/community.general/issues
|
||||
#type: flatmap
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
requires_ansible: '>=2.13.0'
|
||||
requires_ansible: '>=2.11.0'
|
||||
plugin_routing:
|
||||
connection:
|
||||
docker:
|
||||
|
||||
@@ -71,16 +71,6 @@ options:
|
||||
ini:
|
||||
- section: callback_mail
|
||||
key: bcc
|
||||
message_id_domain:
|
||||
description:
|
||||
- The domain name to use for the L(Message-ID header, https://en.wikipedia.org/wiki/Message-ID).
|
||||
- The default is the hostname of the control node.
|
||||
type: str
|
||||
ini:
|
||||
- section: callback_mail
|
||||
key: message_id_domain
|
||||
version_added: 8.2.0
|
||||
|
||||
'''
|
||||
|
||||
import json
|
||||
@@ -141,7 +131,7 @@ class CallbackModule(CallbackBase):
|
||||
content += 'To: %s\n' % ', '.join([email.utils.formataddr(pair) for pair in to_addresses])
|
||||
if self.cc:
|
||||
content += 'Cc: %s\n' % ', '.join([email.utils.formataddr(pair) for pair in cc_addresses])
|
||||
content += 'Message-ID: %s\n' % email.utils.make_msgid(domain=self.get_option('message_id_domain'))
|
||||
content += 'Message-ID: %s\n' % email.utils.make_msgid()
|
||||
content += 'Subject: %s\n\n' % subject.strip()
|
||||
content += body
|
||||
|
||||
|
||||
@@ -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,168 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Based on lxd.py (c) 2016, Matt Clay <matt@mystile.com>
|
||||
# (c) 2023, Stephane Graber <stgraber@stgraber.org>
|
||||
# Copyright (c) 2023 Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
author: Stéphane Graber (@stgraber)
|
||||
name: incus
|
||||
short_description: Run tasks in Incus instances via the Incus CLI.
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing Incus instance using Incus CLI.
|
||||
version_added: "8.2.0"
|
||||
options:
|
||||
remote_addr:
|
||||
description:
|
||||
- The instance identifier.
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: ansible_host
|
||||
- name: ansible_incus_host
|
||||
executable:
|
||||
description:
|
||||
- The shell to use for execution inside the instance.
|
||||
default: /bin/sh
|
||||
vars:
|
||||
- name: ansible_executable
|
||||
- name: ansible_incus_executable
|
||||
remote:
|
||||
description:
|
||||
- The name of the Incus remote to use (per C(incus remote list)).
|
||||
- Remotes are used to access multiple servers from a single client.
|
||||
default: local
|
||||
vars:
|
||||
- name: ansible_incus_remote
|
||||
project:
|
||||
description:
|
||||
- The name of the Incus project to use (per C(incus project list)).
|
||||
- Projects are used to divide the instances running on a server.
|
||||
default: default
|
||||
vars:
|
||||
- name: ansible_incus_project
|
||||
"""
|
||||
|
||||
import os
|
||||
from subprocess import call, Popen, PIPE
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Incus based connections """
|
||||
|
||||
transport = "incus"
|
||||
has_pipelining = True
|
||||
default_user = 'root'
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
||||
self._incus_cmd = get_bin_path("incus")
|
||||
|
||||
if not self._incus_cmd:
|
||||
raise AnsibleError("incus command not found in PATH")
|
||||
|
||||
def _connect(self):
|
||||
"""connect to Incus (nothing to do here) """
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self._connected:
|
||||
self._display.vvv(u"ESTABLISH Incus CONNECTION FOR USER: root",
|
||||
host=self._instance())
|
||||
self._connected = True
|
||||
|
||||
def _instance(self):
|
||||
# Return only the leading part of the FQDN as the instance name
|
||||
# as Incus instance names cannot be a FQDN.
|
||||
return self.get_option('remote_addr').split(".")[0]
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
""" execute a command on the Incus host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
self._display.vvv(u"EXEC {0}".format(cmd),
|
||||
host=self._instance())
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"exec",
|
||||
"%s:%s" % (self.get_option("remote"), self._instance()),
|
||||
"--",
|
||||
self._play_context.executable, "-c", cmd]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
||||
|
||||
process = Popen(local_cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = process.communicate(in_data)
|
||||
|
||||
stdout = to_text(stdout)
|
||||
stderr = to_text(stderr)
|
||||
|
||||
if stderr == "Error: Instance is not running.\n":
|
||||
raise AnsibleConnectionFailure("instance not running: %s" %
|
||||
self._instance())
|
||||
|
||||
if stderr == "Error: Instance not found\n":
|
||||
raise AnsibleConnectionFailure("instance not found: %s" %
|
||||
self._instance())
|
||||
|
||||
return process.returncode, stdout, stderr
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" put a file from local to Incus """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path),
|
||||
host=self._instance())
|
||||
|
||||
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound("input path is not a file: %s" % in_path)
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"file", "push", "--quiet",
|
||||
in_path,
|
||||
"%s:%s/%s" % (self.get_option("remote"),
|
||||
self._instance(),
|
||||
out_path)]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
call(local_cmd)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from Incus to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path),
|
||||
host=self._instance())
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"file", "pull", "--quiet",
|
||||
"%s:%s/%s" % (self.get_option("remote"),
|
||||
self._instance(),
|
||||
in_path),
|
||||
out_path]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
call(local_cmd)
|
||||
|
||||
def close(self):
|
||||
""" close the connection (nothing to do here) """
|
||||
super(Connection, self).close()
|
||||
|
||||
self._connected = False
|
||||
@@ -71,11 +71,10 @@ class Connection(ConnectionBase):
|
||||
msg = "lxc python bindings are not installed"
|
||||
raise errors.AnsibleError(msg)
|
||||
|
||||
container_name = self.get_option('remote_addr')
|
||||
if self.container and self.container_name == container_name:
|
||||
if self.container:
|
||||
return
|
||||
|
||||
self.container_name = container_name
|
||||
self.container_name = self.get_option('remote_addr')
|
||||
|
||||
self._display.vvv("THIS IS A LOCAL LXC DIR", host=self.container_name)
|
||||
self.container = _lxc.Container(self.container_name)
|
||||
|
||||
@@ -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 container 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
|
||||
'''
|
||||
|
||||
@@ -20,10 +20,10 @@ options:
|
||||
region:
|
||||
description:
|
||||
- The target region.
|
||||
- Regions are defined in Apache libcloud project [libcloud/common/dimensiondata.py].
|
||||
- They are also listed in U(https://libcloud.readthedocs.io/en/latest/compute/drivers/dimensiondata.html).
|
||||
- Note that the default value C(na) stands for "North America".
|
||||
- The module prepends C(dd-) to the region choice.
|
||||
- Regions are defined in Apache libcloud project [libcloud/common/dimensiondata.py]
|
||||
- They are also listed in U(https://libcloud.readthedocs.io/en/latest/compute/drivers/dimensiondata.html)
|
||||
- Note that the default value "na" stands for "North America".
|
||||
- The module prepends 'dd-' to the region choice.
|
||||
type: str
|
||||
default: na
|
||||
mcp_user:
|
||||
|
||||
@@ -34,4 +34,4 @@ options:
|
||||
- Only applicable if O(wait=true).
|
||||
type: int
|
||||
default: 2
|
||||
'''
|
||||
'''
|
||||
|
||||
@@ -39,7 +39,8 @@ options:
|
||||
default: sysadmin
|
||||
requirements:
|
||||
- An EMC VNX Storage device.
|
||||
- storops (0.5.10 or greater). Install using C(pip install storops).
|
||||
- Ansible 2.7.
|
||||
- storops (0.5.10 or greater). Install using 'pip install storops'.
|
||||
notes:
|
||||
- The modules prefixed with C(emc_vnx) are built to support the EMC VNX storage platform.
|
||||
- The modules prefixed with emc_vnx are built to support the EMC VNX storage platform.
|
||||
'''
|
||||
|
||||
@@ -29,9 +29,4 @@ options:
|
||||
- GitLab CI job token for logging in.
|
||||
type: str
|
||||
version_added: 4.2.0
|
||||
ca_path:
|
||||
description:
|
||||
- The CA certificates bundle to use to verify GitLab server certificate.
|
||||
type: str
|
||||
version_added: 8.1.0
|
||||
'''
|
||||
|
||||
@@ -19,8 +19,8 @@ options:
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- The user name to login with.
|
||||
- Currently only user names are supported, and not user IDs.
|
||||
- The user name to login with (currently only user names are
|
||||
supported, and not user IDs).
|
||||
type: str
|
||||
required: true
|
||||
password:
|
||||
@@ -31,13 +31,14 @@ options:
|
||||
domain:
|
||||
description:
|
||||
- The name of the Domain to scope to (Identity v3).
|
||||
- Currently only domain names are supported, and not domain IDs.
|
||||
(currently only domain names are supported, and not domain IDs).
|
||||
type: str
|
||||
required: true
|
||||
project:
|
||||
description:
|
||||
- The name of the Tenant (Identity v2) or Project (Identity v3).
|
||||
- Currently only project names are supported, and not project IDs.
|
||||
(currently only project names are supported, and not
|
||||
project IDs).
|
||||
type: str
|
||||
required: true
|
||||
region:
|
||||
@@ -46,20 +47,20 @@ options:
|
||||
type: str
|
||||
id:
|
||||
description:
|
||||
- The ID of resource to be managed.
|
||||
- The id of resource to be managed.
|
||||
type: str
|
||||
notes:
|
||||
- For authentication, you can set identity_endpoint using the
|
||||
E(ANSIBLE_HWC_IDENTITY_ENDPOINT) environment variable.
|
||||
E(ANSIBLE_HWC_IDENTITY_ENDPOINT) env variable.
|
||||
- For authentication, you can set user using the
|
||||
E(ANSIBLE_HWC_USER) environment variable.
|
||||
- For authentication, you can set password using the E(ANSIBLE_HWC_PASSWORD) environment
|
||||
E(ANSIBLE_HWC_USER) env variable.
|
||||
- For authentication, you can set password using the E(ANSIBLE_HWC_PASSWORD) env
|
||||
variable.
|
||||
- For authentication, you can set domain using the E(ANSIBLE_HWC_DOMAIN) environment
|
||||
- For authentication, you can set domain using the E(ANSIBLE_HWC_DOMAIN) env
|
||||
variable.
|
||||
- For authentication, you can set project using the E(ANSIBLE_HWC_PROJECT) environment
|
||||
- For authentication, you can set project using the E(ANSIBLE_HWC_PROJECT) env
|
||||
variable.
|
||||
- For authentication, you can set region using the E(ANSIBLE_HWC_REGION) environment variable.
|
||||
- For authentication, you can set region using the E(ANSIBLE_HWC_REGION) env variable.
|
||||
- Environment variables values will only be used if the playbook values are
|
||||
not set.
|
||||
'''
|
||||
|
||||
@@ -31,7 +31,8 @@ options:
|
||||
required: true
|
||||
notes:
|
||||
- This module requires pyxcli python library.
|
||||
Use C(pip install pyxcli) in order to get pyxcli.
|
||||
Use 'pip install pyxcli' in order to get pyxcli.
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- pyxcli
|
||||
'''
|
||||
|
||||
@@ -16,29 +16,32 @@ options:
|
||||
hostname:
|
||||
description:
|
||||
- The hostname or IP address on which InfluxDB server is listening.
|
||||
- Since Ansible 2.5, defaulted to localhost.
|
||||
type: str
|
||||
default: localhost
|
||||
username:
|
||||
description:
|
||||
- Username that will be used to authenticate against InfluxDB server.
|
||||
- Alias O(login_username) added in Ansible 2.5.
|
||||
type: str
|
||||
default: root
|
||||
aliases: [ login_username ]
|
||||
password:
|
||||
description:
|
||||
- Password that will be used to authenticate against InfluxDB server.
|
||||
- Alias O(login_password) added in Ansible 2.5.
|
||||
type: str
|
||||
default: root
|
||||
aliases: [ login_password ]
|
||||
port:
|
||||
description:
|
||||
- The port on which InfluxDB server is listening.
|
||||
- The port on which InfluxDB server is listening
|
||||
type: int
|
||||
default: 8086
|
||||
path:
|
||||
description:
|
||||
- The path on which InfluxDB server is accessible.
|
||||
- Only available when using python-influxdb >= 5.1.0.
|
||||
- The path on which InfluxDB server is accessible
|
||||
- Only available when using python-influxdb >= 5.1.0
|
||||
type: str
|
||||
default: ''
|
||||
version_added: '0.2.0'
|
||||
@@ -61,7 +64,7 @@ options:
|
||||
description:
|
||||
- Number of retries client will try before aborting.
|
||||
- V(0) indicates try until success.
|
||||
- Only available when using python-influxdb >= 4.1.0.
|
||||
- Only available when using python-influxdb >= 4.1.0
|
||||
type: int
|
||||
default: 3
|
||||
use_udp:
|
||||
|
||||
@@ -18,6 +18,7 @@ options:
|
||||
- Port of FreeIPA / IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_PORT) will be used instead.
|
||||
- If both the environment variable E(IPA_PORT) and the value are not specified in the task, then default value is set.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: int
|
||||
default: 443
|
||||
ipa_host:
|
||||
@@ -25,8 +26,9 @@ options:
|
||||
- IP or hostname of IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_HOST) will be used instead.
|
||||
- If both the environment variable E(IPA_HOST) and the value are not specified in the task, then DNS will be used to try to discover the FreeIPA server.
|
||||
- The relevant entry needed in FreeIPA is the C(ipa-ca) entry.
|
||||
- The relevant entry needed in FreeIPA is the 'ipa-ca' entry.
|
||||
- If neither the DNS entry, nor the environment E(IPA_HOST), nor the value are available in the task, then the default value will be used.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
default: ipa.example.com
|
||||
ipa_user:
|
||||
@@ -34,6 +36,7 @@ options:
|
||||
- Administrative account used on IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_USER) will be used instead.
|
||||
- If both the environment variable E(IPA_USER) and the value are not specified in the task, then default value is set.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
default: admin
|
||||
ipa_pass:
|
||||
@@ -44,12 +47,14 @@ options:
|
||||
- If the environment variable E(KRB5CCNAME) is available, the module will use this kerberos credentials cache to authenticate to the FreeIPA server.
|
||||
- If the environment variable E(KRB5_CLIENT_KTNAME) is available, and E(KRB5CCNAME) is not; the module will use this kerberos keytab to authenticate.
|
||||
- If GSSAPI is not available, the usage of O(ipa_pass) is required.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
ipa_prot:
|
||||
description:
|
||||
- Protocol used by IPA server.
|
||||
- If the value is not specified in the task, the value of environment variable E(IPA_PROT) will be used instead.
|
||||
- If both the environment variable E(IPA_PROT) and the value are not specified in the task, then default value is set.
|
||||
- Environment variable fallback mechanism is added in Ansible 2.5.
|
||||
type: str
|
||||
choices: [ http, https ]
|
||||
default: https
|
||||
|
||||
@@ -69,7 +69,6 @@ options:
|
||||
type: int
|
||||
default: 10
|
||||
version_added: 4.5.0
|
||||
|
||||
http_agent:
|
||||
description:
|
||||
- Configures the HTTP User-Agent header.
|
||||
|
||||
@@ -30,7 +30,7 @@ options:
|
||||
|
||||
auth_url:
|
||||
description:
|
||||
- lxca HTTPS full web address.
|
||||
- lxca https full web address
|
||||
type: str
|
||||
required: true
|
||||
|
||||
@@ -38,6 +38,7 @@ requirements:
|
||||
- pylxca
|
||||
|
||||
notes:
|
||||
- Additional detail about pylxca can be found at U(https://github.com/lenovo/pylxca).
|
||||
- Playbooks using these modules can be found at U(https://github.com/lenovo/ansible.lenovo-lxca).
|
||||
- Additional detail about pylxca can be found at U(https://github.com/lenovo/pylxca)
|
||||
- Playbooks using these modules can be found at U(https://github.com/lenovo/ansible.lenovo-lxca)
|
||||
- Check mode is not supported.
|
||||
'''
|
||||
|
||||
@@ -18,12 +18,6 @@ options:
|
||||
- FQDN of Nomad server.
|
||||
required: true
|
||||
type: str
|
||||
port:
|
||||
description:
|
||||
- Port of Nomad server.
|
||||
type: int
|
||||
default: 4646
|
||||
version_added: 8.0.0
|
||||
use_ssl:
|
||||
description:
|
||||
- Use TLS/SSL connection.
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
requirements:
|
||||
- See U(https://support.1password.com/command-line/)
|
||||
options:
|
||||
master_password:
|
||||
description: The password used to unlock the specified vault.
|
||||
aliases: ['vault_password']
|
||||
type: str
|
||||
section:
|
||||
description: Item section containing the field to retrieve (case-insensitive). If absent will return first match from any section.
|
||||
domain:
|
||||
description: Domain of 1Password.
|
||||
default: '1password.com'
|
||||
type: str
|
||||
subdomain:
|
||||
description: The 1Password subdomain to authenticate against.
|
||||
type: str
|
||||
account_id:
|
||||
description: The account ID to target.
|
||||
type: str
|
||||
username:
|
||||
description: The username used to sign in.
|
||||
type: str
|
||||
secret_key:
|
||||
description: The secret key used when performing an initial sign in.
|
||||
type: str
|
||||
service_account_token:
|
||||
description:
|
||||
- The access key for a service account.
|
||||
- Only works with 1Password CLI version 2 or later.
|
||||
type: str
|
||||
vault:
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
|
||||
type: str
|
||||
connect_host:
|
||||
description: The host for 1Password Connect. Must be used in combination with O(connect_token).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_HOST
|
||||
version_added: 8.1.0
|
||||
connect_token:
|
||||
description: The token for 1Password Connect. Must be used in combination with O(connect_host).
|
||||
type: str
|
||||
env:
|
||||
- name: OP_CONNECT_TOKEN
|
||||
version_added: 8.1.0
|
||||
'''
|
||||
|
||||
LOOKUP = r'''
|
||||
options:
|
||||
service_account_token:
|
||||
env:
|
||||
- name: OP_SERVICE_ACCOUNT_TOKEN
|
||||
version_added: 8.2.0
|
||||
notes:
|
||||
- This lookup will use an existing 1Password session if one exists. If not, and you have already
|
||||
performed an initial sign in (meaning C(~/.op/config), C(~/.config/op/config) or C(~/.config/.op/config) exists), then only the
|
||||
O(master_password) is required. You may optionally specify O(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
|
||||
- This lookup can perform an initial login by providing O(subdomain), O(username), O(secret_key), and O(master_password).
|
||||
- Can target a specific account by providing the O(account_id).
|
||||
- Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
|
||||
needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
|
||||
to the 1Password master password.
|
||||
- This lookup stores potentially sensitive data from 1Password as Ansible facts.
|
||||
Facts are subject to caching if enabled, which means this data could be stored in clear text
|
||||
on disk or in a database.
|
||||
- Tested with C(op) version 2.7.2.
|
||||
'''
|
||||
@@ -15,7 +15,7 @@ class ModuleDocFragment(object):
|
||||
options:
|
||||
config:
|
||||
description:
|
||||
- Path to a JSON configuration file containing the OneView client configuration.
|
||||
- Path to a .json configuration file containing the OneView client configuration.
|
||||
The configuration file is optional and when used should be present in the host running the ansible commands.
|
||||
If the file path is not provided, the configuration will be loaded from environment variables.
|
||||
For links to example configuration files or how to use the environment variables verify the notes section.
|
||||
@@ -42,7 +42,7 @@ options:
|
||||
type: str
|
||||
|
||||
requirements:
|
||||
- Python >= 2.7.9
|
||||
- python >= 2.7.9
|
||||
|
||||
notes:
|
||||
- "A sample configuration file for the config parameter can be found at:
|
||||
@@ -70,11 +70,11 @@ options:
|
||||
options:
|
||||
params:
|
||||
description:
|
||||
- List of parameters to delimit, filter and sort the list of resources.
|
||||
- "Parameter keys allowed are:"
|
||||
- "C(start): The first item to return, using 0-based indexing."
|
||||
- "C(count): The number of resources to return."
|
||||
- "C(filter): A general filter/query string to narrow the list of items returned."
|
||||
- "C(sort): The sort order of the returned data set."
|
||||
- List of params to delimit, filter and sort the list of resources.
|
||||
- "params allowed:
|
||||
- C(start): The first item to return, using 0-based indexing.
|
||||
- C(count): The number of resources to return.
|
||||
- C(filter): A general filter/query string to narrow the list of items returned.
|
||||
- C(sort): The sort order of the returned data set."
|
||||
type: dict
|
||||
'''
|
||||
|
||||
@@ -20,7 +20,7 @@ options:
|
||||
aliases: [ oauth_token ]
|
||||
api_url:
|
||||
description:
|
||||
- Online API URL.
|
||||
- Online API URL
|
||||
type: str
|
||||
default: 'https://api.online.net'
|
||||
aliases: [ base_url ]
|
||||
@@ -36,7 +36,7 @@ options:
|
||||
type: bool
|
||||
default: true
|
||||
notes:
|
||||
- Also see the API documentation on U(https://console.online.net/en/api/).
|
||||
- Also see the API documentation on U(https://console.online.net/en/api/)
|
||||
- If O(api_token) is not set within the module, the following
|
||||
environment variables can be used in decreasing order of precedence
|
||||
E(ONLINE_TOKEN), E(ONLINE_API_KEY), E(ONLINE_OAUTH_TOKEN), E(ONLINE_API_TOKEN).
|
||||
|
||||
@@ -64,7 +64,7 @@ options:
|
||||
description:
|
||||
- Configures the transport connection to use when connecting to the
|
||||
remote device. The transport argument supports connectivity to the
|
||||
device over SSH (V(ssh)), CLI (V(cli)), or REST (V(rest)).
|
||||
device over ssh, cli or REST.
|
||||
required: true
|
||||
type: str
|
||||
choices: [ cli, rest, ssh ]
|
||||
|
||||
@@ -10,21 +10,22 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
DOCUMENTATION = """
|
||||
requirements:
|
||||
- Python SDK for Oracle Cloud Infrastructure U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io)
|
||||
- "python >= 2.7"
|
||||
- Python SDK for Oracle Cloud Infrastructure U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io)
|
||||
notes:
|
||||
- For OCI Python SDK configuration, please refer to
|
||||
U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io/en/latest/configuration.html).
|
||||
- For OCI python sdk configuration, please refer to
|
||||
U(https://oracle-cloud-infrastructure-python-sdk.readthedocs.io/en/latest/configuration.html)
|
||||
options:
|
||||
config_file_location:
|
||||
description:
|
||||
- Path to configuration file. If not set then the value of the E(OCI_CONFIG_FILE) environment variable,
|
||||
if any, is used. Otherwise, defaults to C(~/.oci/config).
|
||||
if any, is used. Otherwise, defaults to ~/.oci/config.
|
||||
type: str
|
||||
config_profile_name:
|
||||
description:
|
||||
- The profile to load from the config file referenced by O(config_file_location). If not set, then the
|
||||
value of the E(OCI_CONFIG_PROFILE) environment variable, if any, is used. Otherwise, defaults to the
|
||||
C(DEFAULT) profile in O(config_file_location).
|
||||
"DEFAULT" profile in O(config_file_location).
|
||||
default: "DEFAULT"
|
||||
type: str
|
||||
api_user:
|
||||
@@ -69,8 +70,8 @@ class ModuleDocFragment(object):
|
||||
description:
|
||||
- OCID of your tenancy. If not set, then the value of the OCI_TENANCY variable, if any, is
|
||||
used. This option is required if the tenancy OCID is not specified through a configuration file
|
||||
(See O(config_file_location)). To get the tenancy OCID, please refer to
|
||||
U(https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/apisigningkey.htm).
|
||||
(See O(config_file_location)). To get the tenancy OCID, please refer
|
||||
U(https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/apisigningkey.htm)
|
||||
type: str
|
||||
region:
|
||||
description:
|
||||
|
||||
@@ -21,7 +21,7 @@ class ModuleDocFragment(object):
|
||||
wait_until:
|
||||
description: The lifecycle state to wait for the resource to transition into when O(wait=true). By default,
|
||||
when O(wait=true), we wait for the resource to get into ACTIVE/ATTACHED/AVAILABLE/PROVISIONED/
|
||||
RUNNING applicable lifecycle state during create operation and to get into DELETED/DETACHED/
|
||||
RUNNING applicable lifecycle state during create operation & to get into DELETED/DETACHED/
|
||||
TERMINATED lifecycle state during delete operation.
|
||||
type: str
|
||||
"""
|
||||
|
||||
@@ -32,10 +32,11 @@ options:
|
||||
- FlashBlade API token for admin privileged user.
|
||||
type: str
|
||||
notes:
|
||||
- This module requires the C(purity_fb) Python library.
|
||||
- This module requires the C(purity_fb) Python library
|
||||
- You must set E(PUREFB_URL) and E(PUREFB_API) environment variables
|
||||
if O(fb_url) and O(api_token) arguments are not passed to the module directly.
|
||||
if O(fb_url) and O(api_token) arguments are not passed to the module directly
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- purity_fb >= 1.1
|
||||
'''
|
||||
|
||||
@@ -53,9 +54,10 @@ options:
|
||||
type: str
|
||||
required: true
|
||||
notes:
|
||||
- This module requires the C(purestorage) Python library.
|
||||
- This module requires the C(purestorage) Python library
|
||||
- You must set E(PUREFA_URL) and E(PUREFA_API) environment variables
|
||||
if O(fa_url) and O(api_token) arguments are not passed to the module directly.
|
||||
if O(fa_url) and O(api_token) arguments are not passed to the module directly
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- purestorage
|
||||
'''
|
||||
|
||||
@@ -43,14 +43,15 @@ options:
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
- pyrax
|
||||
notes:
|
||||
- The following environment variables can be used, E(RAX_USERNAME),
|
||||
E(RAX_API_KEY), E(RAX_CREDS_FILE), E(RAX_CREDENTIALS), E(RAX_REGION).
|
||||
- E(RAX_CREDENTIALS) and E(RAX_CREDS_FILE) point to a credentials file
|
||||
appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating).
|
||||
- E(RAX_USERNAME) and E(RAX_API_KEY) obviate the use of a credentials file.
|
||||
- E(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...).
|
||||
appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating)
|
||||
- E(RAX_USERNAME) and E(RAX_API_KEY) obviate the use of a credentials file
|
||||
- E(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...)
|
||||
'''
|
||||
|
||||
# Documentation fragment including attributes to enable communication
|
||||
@@ -66,7 +67,7 @@ options:
|
||||
type: str
|
||||
description:
|
||||
- The URI of the authentication service.
|
||||
- If not specified will be set to U(https://identity.api.rackspacecloud.com/v2.0/).
|
||||
- If not specified will be set to U(https://identity.api.rackspacecloud.com/v2.0/)
|
||||
credentials:
|
||||
type: path
|
||||
description:
|
||||
@@ -109,12 +110,13 @@ deprecated:
|
||||
why: This module relies on the deprecated package pyrax.
|
||||
alternative: Use the Openstack modules instead.
|
||||
requirements:
|
||||
- python >= 2.6
|
||||
- pyrax
|
||||
notes:
|
||||
- The following environment variables can be used, E(RAX_USERNAME),
|
||||
E(RAX_API_KEY), E(RAX_CREDS_FILE), E(RAX_CREDENTIALS), E(RAX_REGION).
|
||||
- E(RAX_CREDENTIALS) and E(RAX_CREDS_FILE) points to a credentials file
|
||||
appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating).
|
||||
- E(RAX_USERNAME) and E(RAX_API_KEY) obviate the use of a credentials file.
|
||||
- E(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...).
|
||||
appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating)
|
||||
- E(RAX_USERNAME) and E(RAX_API_KEY) obviate the use of a credentials file
|
||||
- E(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...)
|
||||
'''
|
||||
|
||||
@@ -42,7 +42,7 @@ options:
|
||||
type: bool
|
||||
default: true
|
||||
notes:
|
||||
- Also see the API documentation on U(https://developer.scaleway.com/).
|
||||
- Also see the API documentation on U(https://developer.scaleway.com/)
|
||||
- If O(api_token) is not set within the module, the following
|
||||
environment variables can be used in decreasing order of precedence
|
||||
E(SCW_TOKEN), E(SCW_API_KEY), E(SCW_OAUTH_TOKEN) or E(SCW_API_TOKEN).
|
||||
|
||||
@@ -14,7 +14,7 @@ options:
|
||||
headers:
|
||||
description:
|
||||
- A dictionary of additional headers to be sent to POST and PUT requests.
|
||||
- Is needed for some modules.
|
||||
- Is needed for some modules
|
||||
type: dict
|
||||
required: false
|
||||
default: {}
|
||||
@@ -30,9 +30,8 @@ options:
|
||||
default: 4444
|
||||
utm_token:
|
||||
description:
|
||||
- "The token used to identify at the REST-API. See
|
||||
U(https://www.sophos.com/en-us/medialibrary/PDFs/documentation/UTMonAWS/Sophos-UTM-RESTful-API.pdf?la=en),
|
||||
Chapter 2.4.2."
|
||||
- "The token used to identify at the REST-API. See U(https://www.sophos.com/en-us/medialibrary/\
|
||||
PDFs/documentation/UTMonAWS/Sophos-UTM-RESTful-API.pdf?la=en), Chapter 2.4.2."
|
||||
type: str
|
||||
required: true
|
||||
utm_protocol:
|
||||
@@ -49,8 +48,8 @@ options:
|
||||
state:
|
||||
description:
|
||||
- The desired state of the object.
|
||||
- V(present) will create or update an object.
|
||||
- V(absent) will delete an object if it was present.
|
||||
- V(present) will create or update an object
|
||||
- V(absent) will delete an object if it was present
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
|
||||
@@ -30,13 +30,11 @@ options:
|
||||
user:
|
||||
description:
|
||||
- Vexata API user with administrative privileges.
|
||||
- Uses the E(VEXATA_USER) environment variable as a fallback.
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Vexata API user password.
|
||||
- Uses the E(VEXATA_PASSWORD) environment variable as a fallback.
|
||||
required: false
|
||||
type: str
|
||||
validate_certs:
|
||||
@@ -50,6 +48,7 @@ options:
|
||||
requirements:
|
||||
- Vexata VX100 storage array with VXOS >= v3.5.0 on storage array
|
||||
- vexatapi >= 0.0.1
|
||||
- E(VEXATA_USER) and E(VEXATA_PASSWORD) environment variables must be set if
|
||||
- python >= 2.7
|
||||
- VEXATA_USER and VEXATA_PASSWORD environment variables must be set if
|
||||
user and password arguments are not passed to the module directly.
|
||||
'''
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: from_ini
|
||||
short_description: Converts INI text input into a dictionary
|
||||
version_added: 8.2.0
|
||||
author: Steffen Scheib (@sscheib)
|
||||
description:
|
||||
- Converts INI text input into a dictionary.
|
||||
options:
|
||||
_input:
|
||||
description: A string containing an INI document.
|
||||
type: string
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Slurp an INI file
|
||||
ansible.builtin.slurp:
|
||||
src: /etc/rhsm/rhsm.conf
|
||||
register: rhsm_conf
|
||||
|
||||
- name: Display the INI file as dictionary
|
||||
ansible.builtin.debug:
|
||||
var: rhsm_conf.content | b64decode | community.general.from_ini
|
||||
|
||||
- name: Set a new dictionary fact with the contents of the INI file
|
||||
ansible.builtin.set_fact:
|
||||
rhsm_dict: >-
|
||||
{{
|
||||
rhsm_conf.content | b64decode | community.general.from_ini
|
||||
}}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: A dictionary representing the INI file.
|
||||
type: dictionary
|
||||
'''
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.six.moves import StringIO
|
||||
from ansible.module_utils.six.moves.configparser import ConfigParser
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
class IniParser(ConfigParser):
|
||||
''' Implements a configparser which is able to return a dict '''
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.optionxform = str
|
||||
|
||||
def as_dict(self):
|
||||
d = dict(self._sections)
|
||||
for k in d:
|
||||
d[k] = dict(self._defaults, **d[k])
|
||||
d[k].pop('__name__', None)
|
||||
|
||||
if self._defaults:
|
||||
d['DEFAULT'] = dict(self._defaults)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def from_ini(obj):
|
||||
''' Read the given string as INI file and return a dict '''
|
||||
|
||||
if not isinstance(obj, string_types):
|
||||
raise AnsibleFilterError(f'from_ini requires a str, got {type(obj)}')
|
||||
|
||||
parser = IniParser()
|
||||
|
||||
try:
|
||||
parser.read_file(StringIO(obj))
|
||||
except Exception as ex:
|
||||
raise AnsibleFilterError(f'from_ini failed to parse given string: '
|
||||
f'{to_native(ex)}', orig_exc=ex)
|
||||
|
||||
return parser.as_dict()
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
''' Query filter '''
|
||||
|
||||
def filters(self):
|
||||
|
||||
return {
|
||||
'from_ini': from_ini
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: to_ini
|
||||
short_description: Converts a dictionary to the INI file format
|
||||
version_added: 8.2.0
|
||||
author: Steffen Scheib (@sscheib)
|
||||
description:
|
||||
- Converts a dictionary to the INI file format.
|
||||
options:
|
||||
_input:
|
||||
description: The dictionary that should be converted to the INI format.
|
||||
type: dictionary
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Define a dictionary
|
||||
ansible.builtin.set_fact:
|
||||
my_dict:
|
||||
section_name:
|
||||
key_name: 'key value'
|
||||
|
||||
another_section:
|
||||
connection: 'ssh'
|
||||
|
||||
- name: Write dictionary to INI file
|
||||
ansible.builtin.copy:
|
||||
dest: /tmp/test.ini
|
||||
content: '{{ my_dict | community.general.to_ini }}'
|
||||
|
||||
# /tmp/test.ini will look like this:
|
||||
# [section_name]
|
||||
# key_name = key value
|
||||
#
|
||||
# [another_section]
|
||||
# connection = ssh
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
_value:
|
||||
description: A string formatted as INI file.
|
||||
type: string
|
||||
'''
|
||||
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.common._collections_compat import Mapping
|
||||
from ansible.module_utils.six.moves import StringIO
|
||||
from ansible.module_utils.six.moves.configparser import ConfigParser
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
class IniParser(ConfigParser):
|
||||
''' Implements a configparser which sets the correct optionxform '''
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.optionxform = str
|
||||
|
||||
|
||||
def to_ini(obj):
|
||||
''' Read the given dict and return an INI formatted string '''
|
||||
|
||||
if not isinstance(obj, Mapping):
|
||||
raise AnsibleFilterError(f'to_ini requires a dict, got {type(obj)}')
|
||||
|
||||
ini_parser = IniParser()
|
||||
|
||||
try:
|
||||
ini_parser.read_dict(obj)
|
||||
except Exception as ex:
|
||||
raise AnsibleFilterError('to_ini failed to parse given dict:'
|
||||
f'{to_native(ex)}', orig_exc=ex)
|
||||
|
||||
# catching empty dicts
|
||||
if obj == dict():
|
||||
raise AnsibleFilterError('to_ini received an empty dict. '
|
||||
'An empty dict cannot be converted.')
|
||||
|
||||
config = StringIO()
|
||||
ini_parser.write(config)
|
||||
|
||||
# config.getvalue() returns two \n at the end
|
||||
# with the below insanity, we remove the very last character of
|
||||
# the resulting string
|
||||
return ''.join(config.getvalue().rsplit(config.getvalue()[-1], 1))
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
''' Query filter '''
|
||||
|
||||
def filters(self):
|
||||
|
||||
return {
|
||||
'to_ini': to_ini
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -110,8 +110,6 @@ class Bitwarden(object):
|
||||
out, err = p.communicate(to_bytes(stdin))
|
||||
rc = p.wait()
|
||||
if rc != expected_rc:
|
||||
if len(args) > 2 and args[0] == 'get' and args[1] == 'item' and b'Not found.' in err:
|
||||
return 'null', ''
|
||||
raise BitwardenException(err)
|
||||
return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict')
|
||||
|
||||
@@ -120,10 +118,7 @@ class Bitwarden(object):
|
||||
"""
|
||||
|
||||
# Prepare set of params for Bitwarden CLI
|
||||
if search_field == 'id':
|
||||
params = ['get', 'item', search_value]
|
||||
else:
|
||||
params = ['list', 'items', '--search', search_value]
|
||||
params = ['list', 'items', '--search', search_value]
|
||||
|
||||
if collection_id:
|
||||
params.extend(['--collectionid', collection_id])
|
||||
@@ -132,11 +127,7 @@ class Bitwarden(object):
|
||||
|
||||
# This includes things that matched in different fields.
|
||||
initial_matches = AnsibleJSONDecoder().raw_decode(out)[0]
|
||||
if search_field == 'id':
|
||||
if initial_matches is None:
|
||||
initial_matches = []
|
||||
else:
|
||||
initial_matches = [initial_matches]
|
||||
|
||||
# Filter to only include results from the right field.
|
||||
return [item for item in initial_matches if item[search_field] == search_value]
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -34,7 +34,6 @@ except Exception:
|
||||
|
||||
def auth_argument_spec(spec=None):
|
||||
arg_spec = (dict(
|
||||
ca_path=dict(type='str'),
|
||||
api_token=dict(type='str', no_log=True),
|
||||
api_oauth_token=dict(type='str', no_log=True),
|
||||
api_job_token=dict(type='str', no_log=True),
|
||||
@@ -75,36 +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)
|
||||
)
|
||||
|
||||
@@ -2955,7 +2955,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']
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Andrew Hyatt <andy@hyatt.xyz>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: dnf_config_manager
|
||||
short_description: Enable or disable dnf repositories using config-manager
|
||||
version_added: 8.2.0
|
||||
description:
|
||||
- This module enables or disables repositories using the C(dnf config-manager) sub-command.
|
||||
author: Andrew Hyatt (@ahyattdev) <andy@hyatt.xyz>
|
||||
requirements:
|
||||
- dnf
|
||||
- dnf-plugins-core
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Repository ID, for example V(crb).
|
||||
default: []
|
||||
required: false
|
||||
type: list
|
||||
elements: str
|
||||
state:
|
||||
description:
|
||||
- Whether the repositories should be V(enabled) or V(disabled).
|
||||
default: enabled
|
||||
required: false
|
||||
type: str
|
||||
choices: [enabled, disabled]
|
||||
seealso:
|
||||
- module: ansible.builtin.dnf
|
||||
- module: ansible.builtin.yum_repository
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Ensure the crb repository is enabled
|
||||
community.general.dnf_config_manager:
|
||||
name: crb
|
||||
state: enabled
|
||||
|
||||
- name: Ensure the appstream and zfs repositories are disabled
|
||||
community.general.dnf_config_manager:
|
||||
name:
|
||||
- appstream
|
||||
- zfs
|
||||
state: disabled
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
repo_states_pre:
|
||||
description: Repo IDs before action taken.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
enabled:
|
||||
description: Enabled repository IDs.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
disabled:
|
||||
description: Disabled repository IDs.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample:
|
||||
enabled:
|
||||
- appstream
|
||||
- baseos
|
||||
- crb
|
||||
disabled:
|
||||
- appstream-debuginfo
|
||||
- appstream-source
|
||||
- baseos-debuginfo
|
||||
- baseos-source
|
||||
- crb-debug
|
||||
- crb-source
|
||||
repo_states_post:
|
||||
description: Repository states after action taken.
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
enabled:
|
||||
description: Enabled repository IDs.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
disabled:
|
||||
description: Disabled repository IDs.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample:
|
||||
enabled:
|
||||
- appstream
|
||||
- baseos
|
||||
- crb
|
||||
disabled:
|
||||
- appstream-debuginfo
|
||||
- appstream-source
|
||||
- baseos-debuginfo
|
||||
- baseos-source
|
||||
- crb-debug
|
||||
- crb-source
|
||||
changed_repos:
|
||||
description: Repositories changed.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: [ 'crb' ]
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import os
|
||||
import re
|
||||
|
||||
DNF_BIN = "/usr/bin/dnf"
|
||||
REPO_ID_RE = re.compile(r'^Repo-id\s*:\s*(\S+)$')
|
||||
REPO_STATUS_RE = re.compile(r'^Repo-status\s*:\s*(disabled|enabled)$')
|
||||
|
||||
|
||||
def get_repo_states(module):
|
||||
rc, out, err = module.run_command([DNF_BIN, 'repolist', '--all', '--verbose'], check_rc=True)
|
||||
|
||||
repos = dict()
|
||||
last_repo = ''
|
||||
for i, line in enumerate(out.split('\n')):
|
||||
m = REPO_ID_RE.match(line)
|
||||
if m:
|
||||
if len(last_repo) > 0:
|
||||
module.fail_json(msg='dnf repolist parse failure: parsed another repo id before next status')
|
||||
last_repo = m.group(1)
|
||||
continue
|
||||
m = REPO_STATUS_RE.match(line)
|
||||
if m:
|
||||
if len(last_repo) == 0:
|
||||
module.fail_json(msg='dnf repolist parse failure: parsed status before repo id')
|
||||
repos[last_repo] = m.group(1)
|
||||
last_repo = ''
|
||||
return repos
|
||||
|
||||
|
||||
def set_repo_states(module, repo_ids, state):
|
||||
module.run_command([DNF_BIN, 'config-manager', '--set-{0}'.format(state)] + repo_ids, check_rc=True)
|
||||
|
||||
|
||||
def pack_repo_states_for_return(states):
|
||||
enabled = []
|
||||
disabled = []
|
||||
for repo_id in states:
|
||||
if states[repo_id] == 'enabled':
|
||||
enabled.append(repo_id)
|
||||
else:
|
||||
disabled.append(repo_id)
|
||||
|
||||
# Sort for consistent results
|
||||
enabled.sort()
|
||||
disabled.sort()
|
||||
|
||||
return {'enabled': enabled, 'disabled': disabled}
|
||||
|
||||
|
||||
def main():
|
||||
module_args = dict(
|
||||
name=dict(type='list', elements='str', required=False, default=[]),
|
||||
state=dict(type='str', required=False, choices=['enabled', 'disabled'], default='enabled')
|
||||
)
|
||||
|
||||
result = dict(
|
||||
changed=False
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not os.path.exists(DNF_BIN):
|
||||
module.fail_json(msg="%s was not found" % DNF_BIN)
|
||||
|
||||
repo_states = get_repo_states(module)
|
||||
result['repo_states_pre'] = pack_repo_states_for_return(repo_states)
|
||||
|
||||
desired_repo_state = module.params['state']
|
||||
names = module.params['name']
|
||||
|
||||
to_change = []
|
||||
for repo_id in names:
|
||||
if repo_id not in repo_states:
|
||||
module.fail_json(msg="did not find repo with ID '{0}' in dnf repolist --all --verbose".format(repo_id))
|
||||
if repo_states[repo_id] != desired_repo_state:
|
||||
to_change.append(repo_id)
|
||||
result['changed'] = len(to_change) > 0
|
||||
result['changed_repos'] = to_change
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
if len(to_change) > 0:
|
||||
set_repo_states(module, to_change, desired_repo_state)
|
||||
|
||||
repo_states_post = get_repo_states(module)
|
||||
result['repo_states_post'] = pack_repo_states_for_return(repo_states_post)
|
||||
|
||||
for repo_id in to_change:
|
||||
if repo_states_post[repo_id] != desired_repo_state:
|
||||
module.fail_json(msg="dnf config-manager failed to make '{0}' {1}".format(repo_id, desired_repo_state))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -69,6 +69,7 @@ options:
|
||||
plugin_bin:
|
||||
description:
|
||||
- Location of the plugin binary. If this file is not found, the default plugin binaries will be used.
|
||||
- The default changed in Ansible 2.4 to None.
|
||||
type: path
|
||||
plugin_dir:
|
||||
description:
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Alexei Znamensky
|
||||
# Copyright (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: facter_facts
|
||||
short_description: Runs the discovery program C(facter) on the remote system and return Ansible facts
|
||||
version_added: 8.0.0
|
||||
description:
|
||||
- Runs the C(facter) discovery program
|
||||
(U(https://github.com/puppetlabs/facter)) on the remote system, returning Ansible facts from the
|
||||
JSON data that can be useful for inventory purposes.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes.facts
|
||||
- community.general.attributes.facts_module
|
||||
options:
|
||||
arguments:
|
||||
description:
|
||||
- Specifies arguments for facter.
|
||||
type: list
|
||||
elements: str
|
||||
requirements:
|
||||
- facter
|
||||
- ruby-json
|
||||
author:
|
||||
- Ansible Core Team
|
||||
- Michael DeHaan
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Execute facter no arguments
|
||||
community.general.facter_facts:
|
||||
|
||||
- name: Execute facter with arguments
|
||||
community.general.facter_facts:
|
||||
arguments:
|
||||
- -p
|
||||
- system_uptime
|
||||
- timezone
|
||||
- is_virtual
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
ansible_facts:
|
||||
description: Dictionary with one key C(facter).
|
||||
returned: always
|
||||
type: dict
|
||||
contains:
|
||||
facter:
|
||||
description: Dictionary containing facts discovered in the remote system.
|
||||
returned: always
|
||||
type: dict
|
||||
'''
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
arguments=dict(type='list', elements='str'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
facter_path = module.get_bin_path(
|
||||
'facter',
|
||||
opt_dirs=['/opt/puppetlabs/bin'])
|
||||
|
||||
cmd = [facter_path, "--json"]
|
||||
if module.params['arguments']:
|
||||
cmd += module.params['arguments']
|
||||
|
||||
rc, out, err = module.run_command(cmd, check_rc=True)
|
||||
module.exit_json(ansible_facts=dict(facter=json.loads(out)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -47,8 +47,9 @@ options:
|
||||
type: str
|
||||
description:
|
||||
- The action to take upon the key/value.
|
||||
- State V(get) is deprecated and will be removed in community.general 8.0.0. Please use the module M(community.general.gconftool2_info) instead.
|
||||
required: true
|
||||
choices: [ absent, present ]
|
||||
choices: [ absent, get, present ]
|
||||
config_source:
|
||||
type: str
|
||||
description:
|
||||
@@ -113,7 +114,7 @@ class GConftool(StateModuleHelper):
|
||||
key=dict(type='str', required=True, no_log=False),
|
||||
value_type=dict(type='str', choices=['bool', 'float', 'int', 'string']),
|
||||
value=dict(type='str'),
|
||||
state=dict(type='str', required=True, choices=['absent', 'present']),
|
||||
state=dict(type='str', required=True, choices=['absent', 'get', 'present']),
|
||||
direct=dict(type='bool', default=False),
|
||||
config_source=dict(type='str'),
|
||||
),
|
||||
@@ -148,6 +149,12 @@ class GConftool(StateModuleHelper):
|
||||
def _get(self):
|
||||
return self.runner("state key", output_process=self._make_process(False)).run(state="get")
|
||||
|
||||
def state_get(self):
|
||||
self.deprecate(
|
||||
msg="State 'get' is deprecated. Please use the module community.general.gconftool2_info instead",
|
||||
version="8.0.0", collection_name="community.general"
|
||||
)
|
||||
|
||||
def state_absent(self):
|
||||
with self.runner("state key", output_process=self._make_process(False)) as ctx:
|
||||
ctx.run()
|
||||
|
||||
@@ -20,7 +20,7 @@ author:
|
||||
requirements: ['git']
|
||||
short_description: Read and write git configuration
|
||||
description:
|
||||
- The M(community.general.git_config) module changes git configuration by invoking C(git config).
|
||||
- The M(community.general.git_config) module changes git configuration by invoking 'git config'.
|
||||
This is needed if you do not want to use M(ansible.builtin.template) for the entire git
|
||||
config file (for example because you need to change just C(user.email) in
|
||||
/etc/.git/config). Solutions involving M(ansible.builtin.command) are cumbersome or
|
||||
@@ -75,16 +75,6 @@ options:
|
||||
- When specifying the name of a single setting, supply a value to
|
||||
set that setting to the given value.
|
||||
type: str
|
||||
add_mode:
|
||||
description:
|
||||
- Specify if a value should replace the existing value(s) or if the new
|
||||
value should be added alongside other values with the same name.
|
||||
- This option is only relevant when adding/replacing values. If O(state=absent) or
|
||||
values are just read out, this option is not considered.
|
||||
choices: [ "add", "replace-all" ]
|
||||
type: str
|
||||
default: "replace-all"
|
||||
version_added: 8.1.0
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
@@ -128,15 +118,6 @@ EXAMPLES = '''
|
||||
name: color.ui
|
||||
value: auto
|
||||
|
||||
- name: Add several options for the same name
|
||||
community.general.git_config:
|
||||
name: push.pushoption
|
||||
value: "{{ item }}"
|
||||
add_mode: add
|
||||
loop:
|
||||
- merge_request.create
|
||||
- merge_request.draft
|
||||
|
||||
- name: Make etckeeper not complaining when it is invoked by cron
|
||||
community.general.git_config:
|
||||
name: user.email
|
||||
@@ -197,7 +178,6 @@ def main():
|
||||
name=dict(type='str'),
|
||||
repo=dict(type='path'),
|
||||
file=dict(type='path'),
|
||||
add_mode=dict(required=False, type='str', default='replace-all', choices=['add', 'replace-all']),
|
||||
scope=dict(required=False, type='str', choices=['file', 'local', 'global', 'system']),
|
||||
state=dict(required=False, type='str', default='present', choices=['present', 'absent']),
|
||||
value=dict(required=False),
|
||||
@@ -217,118 +197,94 @@ def main():
|
||||
# Set the locale to C to ensure consistent messages.
|
||||
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||
|
||||
name = params['name'] or ''
|
||||
unset = params['state'] == 'absent'
|
||||
new_value = params['value'] or ''
|
||||
add_mode = params['add_mode']
|
||||
if params['name']:
|
||||
name = params['name']
|
||||
else:
|
||||
name = None
|
||||
|
||||
scope = determine_scope(params)
|
||||
cwd = determine_cwd(scope, params)
|
||||
if params['scope']:
|
||||
scope = params['scope']
|
||||
elif params['list_all']:
|
||||
scope = None
|
||||
else:
|
||||
scope = 'system'
|
||||
|
||||
base_args = [git_path, "config", "--includes"]
|
||||
if params['state'] == 'absent':
|
||||
unset = 'unset'
|
||||
params['value'] = None
|
||||
else:
|
||||
unset = None
|
||||
|
||||
if scope == 'file':
|
||||
base_args.append('-f')
|
||||
base_args.append(params['file'])
|
||||
elif scope:
|
||||
base_args.append("--" + scope)
|
||||
|
||||
list_args = list(base_args)
|
||||
if params['value']:
|
||||
new_value = params['value']
|
||||
else:
|
||||
new_value = None
|
||||
|
||||
args = [git_path, "config", "--includes"]
|
||||
if params['list_all']:
|
||||
list_args.append('-l')
|
||||
|
||||
args.append('-l')
|
||||
if scope == 'file':
|
||||
args.append('-f')
|
||||
args.append(params['file'])
|
||||
elif scope:
|
||||
args.append("--" + scope)
|
||||
if name:
|
||||
list_args.append("--get-all")
|
||||
list_args.append(name)
|
||||
args.append(name)
|
||||
|
||||
(rc, out, err) = module.run_command(list_args, cwd=cwd, expand_user_and_vars=False)
|
||||
if scope == 'local':
|
||||
dir = params['repo']
|
||||
elif params['list_all'] and params['repo']:
|
||||
# Include local settings from a specific repo when listing all available settings
|
||||
dir = params['repo']
|
||||
else:
|
||||
# Run from root directory to avoid accidentally picking up any local config settings
|
||||
dir = "/"
|
||||
|
||||
(rc, out, err) = module.run_command(args, cwd=dir, expand_user_and_vars=False)
|
||||
if params['list_all'] and scope and rc == 128 and 'unable to read config file' in err:
|
||||
# This just means nothing has been set at the given scope
|
||||
module.exit_json(changed=False, msg='', config_values={})
|
||||
elif rc >= 2:
|
||||
# If the return code is 1, it just means the option hasn't been set yet, which is fine.
|
||||
module.fail_json(rc=rc, msg=err, cmd=' '.join(list_args))
|
||||
|
||||
old_values = out.rstrip().splitlines()
|
||||
module.fail_json(rc=rc, msg=err, cmd=' '.join(args))
|
||||
|
||||
if params['list_all']:
|
||||
values = out.rstrip().splitlines()
|
||||
config_values = {}
|
||||
for value in old_values:
|
||||
for value in values:
|
||||
k, v = value.split('=', 1)
|
||||
config_values[k] = v
|
||||
module.exit_json(changed=False, msg='', config_values=config_values)
|
||||
elif not new_value and not unset:
|
||||
module.exit_json(changed=False, msg='', config_value=old_values[0] if old_values else '')
|
||||
module.exit_json(changed=False, msg='', config_value=out.rstrip())
|
||||
elif unset and not out:
|
||||
module.exit_json(changed=False, msg='no setting to unset')
|
||||
elif new_value in old_values and (len(old_values) == 1 or add_mode == "add"):
|
||||
module.exit_json(changed=False, msg="")
|
||||
|
||||
# Until this point, the git config was just read and in case no change is needed, the module has already exited.
|
||||
|
||||
set_args = list(base_args)
|
||||
if unset:
|
||||
set_args.append("--unset-all")
|
||||
set_args.append(name)
|
||||
else:
|
||||
set_args.append("--" + add_mode)
|
||||
set_args.append(name)
|
||||
set_args.append(new_value)
|
||||
old_value = out.rstrip()
|
||||
if old_value == new_value:
|
||||
module.exit_json(changed=False, msg="")
|
||||
|
||||
if not module.check_mode:
|
||||
(rc, out, err) = module.run_command(set_args, cwd=cwd, ignore_invalid_cwd=False, expand_user_and_vars=False)
|
||||
if unset:
|
||||
args.insert(len(args) - 1, "--" + unset)
|
||||
cmd = args
|
||||
else:
|
||||
cmd = args + [new_value]
|
||||
(rc, out, err) = module.run_command(cmd, cwd=dir, ignore_invalid_cwd=False, expand_user_and_vars=False)
|
||||
if err:
|
||||
module.fail_json(rc=rc, msg=err, cmd=set_args)
|
||||
|
||||
if unset:
|
||||
after_values = []
|
||||
elif add_mode == "add":
|
||||
after_values = old_values + [new_value]
|
||||
else:
|
||||
after_values = [new_value]
|
||||
module.fail_json(rc=rc, msg=err, cmd=cmd)
|
||||
|
||||
module.exit_json(
|
||||
msg='setting changed',
|
||||
diff=dict(
|
||||
before_header=' '.join(set_args),
|
||||
before=build_diff_value(old_values),
|
||||
after_header=' '.join(set_args),
|
||||
after=build_diff_value(after_values),
|
||||
before_header=' '.join(args),
|
||||
before=old_value + "\n",
|
||||
after_header=' '.join(args),
|
||||
after=(new_value or '') + "\n"
|
||||
),
|
||||
changed=True
|
||||
)
|
||||
|
||||
|
||||
def determine_scope(params):
|
||||
if params['scope']:
|
||||
return params['scope']
|
||||
elif params['list_all']:
|
||||
return ""
|
||||
else:
|
||||
return 'system'
|
||||
|
||||
|
||||
def build_diff_value(value):
|
||||
if not value:
|
||||
return "\n"
|
||||
elif len(value) == 1:
|
||||
return value[0] + "\n"
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def determine_cwd(scope, params):
|
||||
if scope == 'local':
|
||||
return params['repo']
|
||||
elif params['list_all'] and params['repo']:
|
||||
# Include local settings from a specific repo when listing all available settings
|
||||
return params['repo']
|
||||
else:
|
||||
# Run from root directory to avoid accidentally picking up any local config settings
|
||||
return "/"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Guenther Grill <grill.guenther@gmail.com>
|
||||
#
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: git_config_info
|
||||
author:
|
||||
- Guenther Grill (@guenhter)
|
||||
version_added: 8.1.0
|
||||
requirements: ['git']
|
||||
short_description: Read git configuration
|
||||
description:
|
||||
- The M(community.general.git_config_info) module reads the git configuration
|
||||
by invoking C(git config).
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the setting to read.
|
||||
- If not provided, all settings will be returned as RV(config_values).
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- Path to a git repository or file for reading values from a specific repo.
|
||||
- If O(scope) is V(local), this must point to a repository to read from.
|
||||
- If O(scope) is V(file), this must point to specific git config file to read from.
|
||||
- Otherwise O(path) is ignored if set.
|
||||
type: path
|
||||
scope:
|
||||
description:
|
||||
- Specify which scope to read values from.
|
||||
- If set to V(global), the global git config is used. O(path) is ignored.
|
||||
- If set to V(system), the system git config is used. O(path) is ignored.
|
||||
- If set to V(local), O(path) must be set to the repo to read from.
|
||||
- If set to V(file), O(path) must be set to the config file to read from.
|
||||
choices: [ "global", "system", "local", "file" ]
|
||||
default: "system"
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Read a system wide config
|
||||
community.general.git_config_info:
|
||||
name: core.editor
|
||||
register: result
|
||||
|
||||
- name: Show value of core.editor
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result.config_value | default('(not set)', true) }}"
|
||||
|
||||
- name: Read a global config from ~/.gitconfig
|
||||
community.general.git_config_info:
|
||||
name: alias.remotev
|
||||
scope: global
|
||||
|
||||
- name: Read a project specific config
|
||||
community.general.git_config_info:
|
||||
name: color.ui
|
||||
scope: local
|
||||
path: /etc
|
||||
|
||||
- name: Read all global values
|
||||
community.general.git_config_info:
|
||||
scope: global
|
||||
|
||||
- name: Read all system wide values
|
||||
community.general.git_config_info:
|
||||
|
||||
- name: Read all values of a specific file
|
||||
community.general.git_config_info:
|
||||
scope: file
|
||||
path: /etc/gitconfig
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
config_value:
|
||||
description: >
|
||||
When O(name) is set, a string containing the value of the setting in name. If O(name) is not set, empty.
|
||||
If a config key such as V(push.pushoption) has more then one entry, just the first one is returned here.
|
||||
returned: success if O(name) is set
|
||||
type: str
|
||||
sample: "vim"
|
||||
|
||||
config_values:
|
||||
description:
|
||||
- This is a dictionary mapping a git configuration setting to a list of its values.
|
||||
- When O(name) is not set, all configuration settings are returned here.
|
||||
- When O(name) is set, only the setting specified in O(name) is returned here.
|
||||
If that setting is not set, the key will still be present, and its value will be an empty list.
|
||||
returned: success
|
||||
type: dict
|
||||
sample:
|
||||
core.editor: ["vim"]
|
||||
color.ui: ["auto"]
|
||||
push.pushoption: ["merge_request.create", "merge_request.draft"]
|
||||
alias.remotev: ["remote -v"]
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(type="str"),
|
||||
path=dict(type="path"),
|
||||
scope=dict(required=False, type="str", default="system", choices=["global", "system", "local", "file"]),
|
||||
),
|
||||
required_if=[
|
||||
("scope", "local", ["path"]),
|
||||
("scope", "file", ["path"]),
|
||||
],
|
||||
required_one_of=[],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# We check error message for a pattern, so we need to make sure the messages appear in the form we're expecting.
|
||||
# Set the locale to C to ensure consistent messages.
|
||||
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||
|
||||
name = module.params["name"]
|
||||
path = module.params["path"]
|
||||
scope = module.params["scope"]
|
||||
|
||||
run_cwd = path if scope == "local" else "/"
|
||||
args = build_args(module, name, path, scope)
|
||||
|
||||
(rc, out, err) = module.run_command(args, cwd=run_cwd, expand_user_and_vars=False)
|
||||
|
||||
if rc == 128 and "unable to read config file" in err:
|
||||
# This just means nothing has been set at the given scope
|
||||
pass
|
||||
elif rc >= 2:
|
||||
# If the return code is 1, it just means the option hasn't been set yet, which is fine.
|
||||
module.fail_json(rc=rc, msg=err, cmd=" ".join(args))
|
||||
|
||||
output_lines = out.strip("\0").split("\0") if out else []
|
||||
|
||||
if name:
|
||||
first_value = output_lines[0] if output_lines else ""
|
||||
config_values = {name: output_lines}
|
||||
module.exit_json(changed=False, msg="", config_value=first_value, config_values=config_values)
|
||||
else:
|
||||
config_values = text_to_dict(output_lines)
|
||||
module.exit_json(changed=False, msg="", config_value="", config_values=config_values)
|
||||
|
||||
|
||||
def build_args(module, name, path, scope):
|
||||
git_path = module.get_bin_path("git", True)
|
||||
args = [git_path, "config", "--includes", "--null", "--" + scope]
|
||||
|
||||
if scope == "file":
|
||||
args.append(path)
|
||||
|
||||
if name:
|
||||
args.extend(["--get-all", name])
|
||||
else:
|
||||
args.append("--list")
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def text_to_dict(text_lines):
|
||||
config_values = {}
|
||||
for value in text_lines:
|
||||
k, v = value.split("\n", 1)
|
||||
if k in config_values:
|
||||
config_values[k].append(v)
|
||||
else:
|
||||
config_values[k] = [v]
|
||||
return config_values
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -94,7 +94,7 @@ EXAMPLES = '''
|
||||
repo: testrepo
|
||||
action: latest_release
|
||||
|
||||
- name: Get latest release of test repo using username and password
|
||||
- name: Get latest release of test repo using username and password. Ansible 2.4.
|
||||
community.general.github_release:
|
||||
user: testuser
|
||||
password: secret123
|
||||
|
||||
@@ -14,6 +14,7 @@ module: github_webhook_info
|
||||
short_description: Query information about GitHub webhooks
|
||||
description:
|
||||
- "Query information about GitHub webhooks"
|
||||
- This module was called C(github_webhook_facts) before Ansible 2.9. The usage did not change.
|
||||
requirements:
|
||||
- "PyGithub >= 1.3.5"
|
||||
extends_documentation_fragment:
|
||||
|
||||
@@ -16,6 +16,7 @@ description:
|
||||
author:
|
||||
- paytroff (@paytroff)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab >= 2.3.0
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -83,7 +84,7 @@ from ansible.module_utils.api import basic_auth_argument_spec
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, gitlab
|
||||
auth_argument_spec, gitlab_authentication, gitlab, ensure_gitlab_package
|
||||
)
|
||||
|
||||
|
||||
@@ -143,9 +144,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
project = module.params['project']
|
||||
branch = module.params['branch']
|
||||
@@ -157,6 +156,7 @@ def main():
|
||||
module.fail_json(msg="community.general.gitlab_proteched_branch requires python-gitlab Python module >= 2.3.0 (installed version: [%s])."
|
||||
" Please upgrade python-gitlab to version 2.3.0 or above." % gitlab_version)
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
this_gitlab = GitlabBranch(module=module, project=project, gitlab_instance=gitlab_instance)
|
||||
|
||||
this_branch = this_gitlab.get_branch(branch)
|
||||
|
||||
@@ -20,6 +20,7 @@ author:
|
||||
- Marcus Watkins (@marwatk)
|
||||
- Guillaume Martinez (@Lunik)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab python module
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -120,7 +121,7 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, find_project, gitlab_authentication, gitlab
|
||||
auth_argument_spec, find_project, gitlab_authentication, gitlab, ensure_gitlab_package
|
||||
)
|
||||
|
||||
|
||||
@@ -260,9 +261,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
state = module.params['state']
|
||||
project_identifier = module.params['project']
|
||||
@@ -270,6 +269,8 @@ def main():
|
||||
key_keyfile = module.params['key']
|
||||
key_can_push = module.params['can_push']
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
gitlab_deploy_key = GitLabDeployKey(module, gitlab_instance)
|
||||
|
||||
project = find_project(gitlab_instance, project_identifier)
|
||||
|
||||
@@ -20,6 +20,7 @@ author:
|
||||
- Werner Dijkerman (@dj-wasabi)
|
||||
- Guillaume Martinez (@Lunik)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab python module
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -107,6 +108,7 @@ EXAMPLES = '''
|
||||
community.general.gitlab_group:
|
||||
api_url: https://gitlab.example.com/
|
||||
api_token: "{{ access_token }}"
|
||||
validate_certs: false
|
||||
name: my_first_group
|
||||
state: absent
|
||||
|
||||
@@ -176,7 +178,7 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, find_group, gitlab_authentication, gitlab
|
||||
auth_argument_spec, find_group, gitlab_authentication, gitlab, ensure_gitlab_package
|
||||
)
|
||||
|
||||
|
||||
@@ -353,9 +355,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
group_name = module.params['name']
|
||||
group_path = module.params['path']
|
||||
@@ -370,6 +370,8 @@ def main():
|
||||
avatar_path = module.params['avatar_path']
|
||||
force_delete = module.params['force_delete']
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
# Define default group_path based on group_name
|
||||
if group_path is None:
|
||||
group_path = group_name.replace(" ", "_")
|
||||
|
||||
@@ -160,7 +160,7 @@ from ansible.module_utils.api import basic_auth_argument_spec
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, gitlab
|
||||
auth_argument_spec, gitlab_authentication, gitlab, ensure_gitlab_package
|
||||
)
|
||||
|
||||
|
||||
@@ -273,9 +273,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gl = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
access_level_int = {
|
||||
'guest': gitlab.const.GUEST_ACCESS,
|
||||
@@ -293,6 +291,9 @@ def main():
|
||||
if purge_users:
|
||||
purge_users = [access_level_int[level] for level in purge_users]
|
||||
|
||||
# connect to gitlab server
|
||||
gl = gitlab_authentication(module)
|
||||
|
||||
group = GitLabGroup(module, gl)
|
||||
|
||||
gitlab_group_id = group.get_group_id(gitlab_group)
|
||||
|
||||
@@ -21,6 +21,7 @@ description:
|
||||
author:
|
||||
- Florent Madiot (@scodeman)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab python module
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -206,7 +207,7 @@ group_variable:
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.api import basic_auth_argument_spec
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, filter_returned_variables, vars_to_variables
|
||||
auth_argument_spec, gitlab_authentication, ensure_gitlab_package, filter_returned_variables, vars_to_variables
|
||||
)
|
||||
|
||||
|
||||
@@ -412,9 +413,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
purge = module.params['purge']
|
||||
var_list = module.params['vars']
|
||||
@@ -429,6 +428,8 @@ def main():
|
||||
if any(x['value'] is None for x in variables):
|
||||
module.fail_json(msg='value parameter is required in state present')
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
this_gitlab = GitlabGroupVariables(module=module, gitlab_instance=gitlab_instance)
|
||||
|
||||
changed, raw_return_value, before, after = native_python_main(this_gitlab, purge, variables, state, module)
|
||||
|
||||
@@ -21,6 +21,7 @@ author:
|
||||
- Marcus Watkins (@marwatk)
|
||||
- Guillaume Martinez (@Lunik)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab python module
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -122,6 +123,7 @@ EXAMPLES = '''
|
||||
state: present
|
||||
push_events: true
|
||||
tag_push_events: true
|
||||
hook_validate_certs: false
|
||||
token: "my-super-secret-token-that-my-ci-server-will-check"
|
||||
|
||||
- name: "Delete the previous hook"
|
||||
@@ -169,7 +171,7 @@ from ansible.module_utils.api import basic_auth_argument_spec
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, find_project, gitlab_authentication
|
||||
auth_argument_spec, find_project, gitlab_authentication, ensure_gitlab_package
|
||||
)
|
||||
|
||||
|
||||
@@ -323,9 +325,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
state = module.params['state']
|
||||
project_identifier = module.params['project']
|
||||
@@ -342,6 +342,8 @@ def main():
|
||||
enable_ssl_verification = module.params['hook_validate_certs']
|
||||
hook_token = module.params['token']
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
gitlab_hook = GitLabHook(module, gitlab_instance)
|
||||
|
||||
project = find_project(gitlab_instance, project_identifier)
|
||||
|
||||
@@ -23,6 +23,7 @@ description:
|
||||
author:
|
||||
- Benedikt Braunger (@benibr)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab python module
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -138,7 +139,7 @@ instance_variable:
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.api import basic_auth_argument_spec
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, filter_returned_variables
|
||||
auth_argument_spec, gitlab_authentication, ensure_gitlab_package, filter_returned_variables
|
||||
)
|
||||
|
||||
|
||||
@@ -325,9 +326,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
purge = module.params['purge']
|
||||
state = module.params['state']
|
||||
@@ -338,6 +337,8 @@ def main():
|
||||
if any(x['value'] is None for x in variables):
|
||||
module.fail_json(msg='value parameter is required in state present')
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
this_gitlab = GitlabInstanceVariables(module=module, gitlab_instance=gitlab_instance)
|
||||
|
||||
changed, raw_return_value, before, after = native_python_main(this_gitlab, purge, variables, state, module)
|
||||
|
||||
@@ -1,408 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Ondrej Zvara (ozvara1@gmail.com)
|
||||
# Based on code:
|
||||
# Copyright (c) 2021, Lennert Mertens (lennert@nubera.be)
|
||||
# Copyright (c) 2021, Werner Dijkerman (ikben@werner-dijkerman.nl)
|
||||
# Copyright (c) 2015, Werner Dijkerman (ikben@werner-dijkerman.nl)
|
||||
# Copyright (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
|
||||
# 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: gitlab_issue
|
||||
short_description: Create, update, or delete GitLab issues
|
||||
version_added: '8.1.0'
|
||||
description:
|
||||
- Creates an issue if it does not exist.
|
||||
- When an issue does exist, it will be updated if the provided parameters are different.
|
||||
- When an issue does exist and O(state=absent), the issue will be deleted.
|
||||
- When multiple issues are detected, the task fails.
|
||||
- Existing issues are matched based on O(title) and O(state_filter) filters.
|
||||
author:
|
||||
- zvaraondrej (@zvaraondrej)
|
||||
requirements:
|
||||
- python-gitlab >= 2.3.0
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
- community.general.gitlab
|
||||
- community.general.attributes
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
|
||||
options:
|
||||
assignee_ids:
|
||||
description:
|
||||
- A list of assignee usernames omitting V(@) character.
|
||||
- Set to an empty array to unassign all assignees.
|
||||
type: list
|
||||
elements: str
|
||||
description:
|
||||
description:
|
||||
- A description of the issue.
|
||||
- Gets overridden by a content of file specified at O(description_path), if found.
|
||||
type: str
|
||||
description_path:
|
||||
description:
|
||||
- A path of file containing issue's description.
|
||||
- Accepts MarkDown formatted files.
|
||||
type: path
|
||||
issue_type:
|
||||
description:
|
||||
- Type of the issue.
|
||||
default: issue
|
||||
type: str
|
||||
choices: ["issue", "incident", "test_case"]
|
||||
labels:
|
||||
description:
|
||||
- A list of label names.
|
||||
- Set to an empty array to remove all labels.
|
||||
type: list
|
||||
elements: str
|
||||
milestone_search:
|
||||
description:
|
||||
- The name of the milestone.
|
||||
- Set to empty string to unassign milestone.
|
||||
type: str
|
||||
milestone_group_id:
|
||||
description:
|
||||
- The path or numeric ID of the group hosting desired milestone.
|
||||
type: str
|
||||
project:
|
||||
description:
|
||||
- The path or name of the project.
|
||||
required: true
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Create or delete issue.
|
||||
default: present
|
||||
type: str
|
||||
choices: ["present", "absent"]
|
||||
state_filter:
|
||||
description:
|
||||
- Filter specifying state of issues while searching.
|
||||
type: str
|
||||
choices: ["opened", "closed"]
|
||||
default: opened
|
||||
title:
|
||||
description:
|
||||
- A title for the issue. The title is used as a unique identifier to ensure idempotency.
|
||||
type: str
|
||||
required: true
|
||||
'''
|
||||
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create Issue
|
||||
community.general.gitlab_issue:
|
||||
api_url: https://gitlab.com
|
||||
api_token: secret_access_token
|
||||
project: "group1/project1"
|
||||
title: "Ansible demo Issue"
|
||||
description: "Demo Issue description"
|
||||
labels:
|
||||
- Ansible
|
||||
- Demo
|
||||
assignee_ids:
|
||||
- testassignee
|
||||
state_filter: "opened"
|
||||
state: present
|
||||
|
||||
- name: Delete Issue
|
||||
community.general.gitlab_issue:
|
||||
api_url: https://gitlab.com
|
||||
api_token: secret_access_token
|
||||
project: "group1/project1"
|
||||
title: "Ansible demo Issue"
|
||||
state_filter: "opened"
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
msg:
|
||||
description: Success or failure message.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Success"
|
||||
|
||||
issue:
|
||||
description: API object.
|
||||
returned: success
|
||||
type: dict
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.api import basic_auth_argument_spec
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, gitlab, find_project, find_group
|
||||
)
|
||||
|
||||
|
||||
class GitlabIssue(object):
|
||||
|
||||
def __init__(self, module, project, gitlab_instance):
|
||||
self._gitlab = gitlab_instance
|
||||
self._module = module
|
||||
self.project = project
|
||||
|
||||
'''
|
||||
@param milestone_id Title of the milestone
|
||||
'''
|
||||
def get_milestone(self, milestone_id, group):
|
||||
milestones = []
|
||||
try:
|
||||
milestones = group.milestones.list(search=milestone_id)
|
||||
except gitlab.exceptions.GitlabGetError as e:
|
||||
self._module.fail_json(msg="Failed to list the Milestones: %s" % to_native(e))
|
||||
|
||||
if len(milestones) > 1:
|
||||
self._module.fail_json(msg="Multiple Milestones matched search criteria.")
|
||||
if len(milestones) < 1:
|
||||
self._module.fail_json(msg="No Milestones matched search criteria.")
|
||||
if len(milestones) == 1:
|
||||
try:
|
||||
return group.milestones.get(id=milestones[0].id)
|
||||
except gitlab.exceptions.GitlabGetError as e:
|
||||
self._module.fail_json(msg="Failed to get the Milestones: %s" % to_native(e))
|
||||
|
||||
'''
|
||||
@param title Title of the Issue
|
||||
@param state_filter Issue's state to filter on
|
||||
'''
|
||||
def get_issue(self, title, state_filter):
|
||||
issues = []
|
||||
try:
|
||||
issues = self.project.issues.list(title=title, state=state_filter)
|
||||
except gitlab.exceptions.GitlabGetError as e:
|
||||
self._module.fail_json(msg="Failed to list the Issues: %s" % to_native(e))
|
||||
|
||||
if len(issues) > 1:
|
||||
self._module.fail_json(msg="Multiple Issues matched search criteria.")
|
||||
if len(issues) == 1:
|
||||
try:
|
||||
return self.project.issues.get(id=issues[0].iid)
|
||||
except gitlab.exceptions.GitlabGetError as e:
|
||||
self._module.fail_json(msg="Failed to get the Issue: %s" % to_native(e))
|
||||
|
||||
'''
|
||||
@param username Name of the user
|
||||
'''
|
||||
def get_user(self, username):
|
||||
users = []
|
||||
try:
|
||||
users = [user for user in self.project.users.list(username=username, all=True) if user.username == username]
|
||||
except gitlab.exceptions.GitlabGetError as e:
|
||||
self._module.fail_json(msg="Failed to list the users: %s" % to_native(e))
|
||||
|
||||
if len(users) > 1:
|
||||
self._module.fail_json(msg="Multiple Users matched search criteria.")
|
||||
elif len(users) < 1:
|
||||
self._module.fail_json(msg="No User matched search criteria.")
|
||||
else:
|
||||
return users[0]
|
||||
|
||||
'''
|
||||
@param users List of usernames
|
||||
'''
|
||||
def get_user_ids(self, users):
|
||||
return [self.get_user(user).id for user in users]
|
||||
|
||||
'''
|
||||
@param options Options of the Issue
|
||||
'''
|
||||
def create_issue(self, options):
|
||||
if self._module.check_mode:
|
||||
self._module.exit_json(changed=True, msg="Successfully created Issue '%s'." % options["title"])
|
||||
|
||||
try:
|
||||
return self.project.issues.create(options)
|
||||
except gitlab.exceptions.GitlabCreateError as e:
|
||||
self._module.fail_json(msg="Failed to create Issue: %s " % to_native(e))
|
||||
|
||||
'''
|
||||
@param issue Issue object to delete
|
||||
'''
|
||||
def delete_issue(self, issue):
|
||||
if self._module.check_mode:
|
||||
self._module.exit_json(changed=True, msg="Successfully deleted Issue '%s'." % issue["title"])
|
||||
|
||||
try:
|
||||
return issue.delete()
|
||||
except gitlab.exceptions.GitlabDeleteError as e:
|
||||
self._module.fail_json(msg="Failed to delete Issue: '%s'." % to_native(e))
|
||||
|
||||
'''
|
||||
@param issue Issue object to update
|
||||
@param options Options of the Issue
|
||||
'''
|
||||
def update_issue(self, issue, options):
|
||||
if self._module.check_mode:
|
||||
self._module.exit_json(changed=True, msg="Successfully updated Issue '%s'." % issue["title"])
|
||||
|
||||
try:
|
||||
return self.project.issues.update(issue.iid, options)
|
||||
except gitlab.exceptions.GitlabUpdateError as e:
|
||||
self._module.fail_json(msg="Failed to update Issue %s." % to_native(e))
|
||||
|
||||
'''
|
||||
@param issue Issue object to evaluate
|
||||
@param options New options to update Issue with
|
||||
'''
|
||||
def issue_has_changed(self, issue, options):
|
||||
for key, value in options.items():
|
||||
if value is not None:
|
||||
|
||||
if key == 'milestone_id':
|
||||
old_milestone = getattr(issue, 'milestone')['id'] if getattr(issue, 'milestone') else ""
|
||||
if options[key] != old_milestone:
|
||||
return True
|
||||
elif key == 'assignee_ids':
|
||||
if options[key] != sorted([user["id"] for user in getattr(issue, 'assignees')]):
|
||||
return True
|
||||
|
||||
elif key == 'labels':
|
||||
if options[key] != sorted(getattr(issue, key)):
|
||||
return True
|
||||
|
||||
elif getattr(issue, key) != value:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = basic_auth_argument_spec()
|
||||
argument_spec.update(auth_argument_spec())
|
||||
argument_spec.update(
|
||||
assignee_ids=dict(type='list', elements='str', required=False),
|
||||
description=dict(type='str', required=False),
|
||||
description_path=dict(type='path', required=False),
|
||||
issue_type=dict(type='str', default='issue', choices=["issue", "incident", "test_case"], required=False),
|
||||
labels=dict(type='list', elements='str', required=False),
|
||||
milestone_search=dict(type='str', required=False),
|
||||
milestone_group_id=dict(type='str', required=False),
|
||||
project=dict(type='str', required=True),
|
||||
state=dict(type='str', default="present", choices=["absent", "present"]),
|
||||
state_filter=dict(type='str', default="opened", choices=["opened", "closed"]),
|
||||
title=dict(type='str', required=True),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
mutually_exclusive=[
|
||||
['api_username', 'api_token'],
|
||||
['api_username', 'api_oauth_token'],
|
||||
['api_username', 'api_job_token'],
|
||||
['api_token', 'api_oauth_token'],
|
||||
['api_token', 'api_job_token'],
|
||||
['description', 'description_path'],
|
||||
],
|
||||
required_together=[
|
||||
['api_username', 'api_password'],
|
||||
['milestone_search', 'milestone_group_id'],
|
||||
],
|
||||
required_one_of=[
|
||||
['api_username', 'api_token', 'api_oauth_token', 'api_job_token']
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
assignee_ids = module.params['assignee_ids']
|
||||
description = module.params['description']
|
||||
description_path = module.params['description_path']
|
||||
issue_type = module.params['issue_type']
|
||||
labels = module.params['labels']
|
||||
milestone_id = module.params['milestone_search']
|
||||
milestone_group_id = module.params['milestone_group_id']
|
||||
project = module.params['project']
|
||||
state = module.params['state']
|
||||
state_filter = module.params['state_filter']
|
||||
title = module.params['title']
|
||||
|
||||
gitlab_version = gitlab.__version__
|
||||
if LooseVersion(gitlab_version) < LooseVersion('2.3.0'):
|
||||
module.fail_json(msg="community.general.gitlab_issue requires python-gitlab Python module >= 2.3.0 (installed version: [%s])."
|
||||
" Please upgrade python-gitlab to version 2.3.0 or above." % gitlab_version)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
this_project = find_project(gitlab_instance, project)
|
||||
if this_project is None:
|
||||
module.fail_json(msg="Failed to get the project: %s" % project)
|
||||
|
||||
this_gitlab = GitlabIssue(module=module, project=this_project, gitlab_instance=gitlab_instance)
|
||||
|
||||
if milestone_id and milestone_group_id:
|
||||
this_group = find_group(gitlab_instance, milestone_group_id)
|
||||
if this_group is None:
|
||||
module.fail_json(msg="Failed to get the group: %s" % milestone_group_id)
|
||||
|
||||
milestone_id = this_gitlab.get_milestone(milestone_id, this_group).id
|
||||
|
||||
this_issue = this_gitlab.get_issue(title, state_filter)
|
||||
|
||||
if state == "present":
|
||||
if description_path:
|
||||
try:
|
||||
with open(description_path, 'rb') as f:
|
||||
description = to_text(f.read(), errors='surrogate_or_strict')
|
||||
except IOError as e:
|
||||
module.fail_json(msg='Cannot open {0}: {1}'.format(description_path, e))
|
||||
|
||||
# sorting necessary in order to properly detect changes, as we don't want to get false positive
|
||||
# results due to differences in ids ordering;
|
||||
assignee_ids = sorted(this_gitlab.get_user_ids(assignee_ids)) if assignee_ids else assignee_ids
|
||||
labels = sorted(labels) if labels else labels
|
||||
|
||||
options = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"labels": labels,
|
||||
"issue_type": issue_type,
|
||||
"milestone_id": milestone_id,
|
||||
"assignee_ids": assignee_ids,
|
||||
}
|
||||
|
||||
if not this_issue:
|
||||
issue = this_gitlab.create_issue(options)
|
||||
module.exit_json(
|
||||
changed=True, msg="Created Issue '{t}'.".format(t=title),
|
||||
issue=issue.asdict()
|
||||
)
|
||||
else:
|
||||
if this_gitlab.issue_has_changed(this_issue, options):
|
||||
issue = this_gitlab.update_issue(this_issue, options)
|
||||
module.exit_json(
|
||||
changed=True, msg="Updated Issue '{t}'.".format(t=title),
|
||||
issue=issue
|
||||
)
|
||||
else:
|
||||
module.exit_json(
|
||||
changed=False, msg="Issue '{t}' already exists".format(t=title),
|
||||
issue=this_issue.asdict()
|
||||
)
|
||||
elif state == "absent":
|
||||
if not this_issue:
|
||||
module.exit_json(changed=False, msg="Issue '{t}' does not exist or has already been deleted.".format(t=title))
|
||||
else:
|
||||
issue = this_gitlab.delete_issue(this_issue)
|
||||
module.exit_json(
|
||||
changed=True, msg="Issue '{t}' deleted.".format(t=title),
|
||||
issue=issue
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -26,6 +26,7 @@ description:
|
||||
author:
|
||||
- zvaraondrej (@zvaraondrej)
|
||||
requirements:
|
||||
- Python >= 2.7
|
||||
- python-gitlab >= 2.3.0
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -151,7 +152,7 @@ from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, gitlab, find_project
|
||||
auth_argument_spec, gitlab_authentication, gitlab, ensure_gitlab_package, find_project
|
||||
)
|
||||
|
||||
|
||||
@@ -320,9 +321,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
project = module.params['project']
|
||||
source_branch = module.params['source_branch']
|
||||
@@ -342,6 +341,8 @@ def main():
|
||||
module.fail_json(msg="community.general.gitlab_merge_request requires python-gitlab Python module >= 2.3.0 (installed version: [%s])."
|
||||
" Please upgrade python-gitlab to version 2.3.0 or above." % gitlab_version)
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
this_project = find_project(gitlab_instance, project)
|
||||
if this_project is None:
|
||||
module.fail_json(msg="Failed to get the project: %s" % project)
|
||||
|
||||
@@ -21,6 +21,7 @@ author:
|
||||
- Werner Dijkerman (@dj-wasabi)
|
||||
- Guillaume Martinez (@Lunik)
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- python-gitlab python module
|
||||
extends_documentation_fragment:
|
||||
- community.general.auth_basic
|
||||
@@ -174,10 +175,8 @@ options:
|
||||
version_added: "4.2.0"
|
||||
default_branch:
|
||||
description:
|
||||
- The default branch name for this project.
|
||||
- For project creation, this option requires O(initialize_with_readme=true).
|
||||
- For project update, the branch must exist.
|
||||
- Supports project's default branch update since community.general 8.0.0.
|
||||
- Default branch name for a new project.
|
||||
- This option is only used on creation, not for updates. This is also only used if O(initialize_with_readme=true).
|
||||
type: str
|
||||
version_added: "4.2.0"
|
||||
builds_access_level:
|
||||
@@ -273,6 +272,7 @@ EXAMPLES = r'''
|
||||
community.general.gitlab_project:
|
||||
api_url: https://gitlab.example.com/
|
||||
api_token: "{{ access_token }}"
|
||||
validate_certs: false
|
||||
name: my_first_project
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
@@ -338,7 +338,7 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, find_group, find_project, gitlab_authentication, gitlab
|
||||
auth_argument_spec, find_group, find_project, gitlab_authentication, gitlab, ensure_gitlab_package
|
||||
)
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
@@ -355,7 +355,7 @@ class GitLabProject(object):
|
||||
@param namespace Namespace Object (User or Group)
|
||||
@param options Options of the project
|
||||
'''
|
||||
def create_or_update_project(self, module, project_name, namespace, options):
|
||||
def create_or_update_project(self, project_name, namespace, options):
|
||||
changed = False
|
||||
project_options = {
|
||||
'name': project_name,
|
||||
@@ -395,8 +395,6 @@ class GitLabProject(object):
|
||||
|
||||
# Because we have already call userExists in main()
|
||||
if self.project_object is None:
|
||||
if options['default_branch'] and not options['initialize_with_readme']:
|
||||
module.fail_json(msg="Param default_branch need param initialize_with_readme set to true")
|
||||
project_options.update({
|
||||
'path': options['path'],
|
||||
'import_url': options['import_url'],
|
||||
@@ -418,8 +416,6 @@ class GitLabProject(object):
|
||||
|
||||
changed = True
|
||||
else:
|
||||
if options['default_branch']:
|
||||
project_options['default_branch'] = options['default_branch']
|
||||
changed, project = self.update_project(self.project_object, project_options)
|
||||
|
||||
self.project_object = project
|
||||
@@ -556,9 +552,7 @@ def main():
|
||||
],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# check prerequisites and connect to gitlab server
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
group_identifier = module.params['group']
|
||||
project_name = module.params['name']
|
||||
@@ -596,6 +590,11 @@ def main():
|
||||
security_and_compliance_access_level = module.params['security_and_compliance_access_level']
|
||||
topics = module.params['topics']
|
||||
|
||||
if default_branch and not initialize_with_readme:
|
||||
module.fail_json(msg="Param default_branch need param initialize_with_readme set to true")
|
||||
|
||||
gitlab_instance = gitlab_authentication(module)
|
||||
|
||||
# Set project_path to project_name if it is empty.
|
||||
if project_path is None:
|
||||
project_path = project_name.replace(" ", "_")
|
||||
@@ -637,7 +636,7 @@ def main():
|
||||
|
||||
if state == 'present':
|
||||
|
||||
if gitlab_project.create_or_update_project(module, project_name, namespace, {
|
||||
if gitlab_project.create_or_update_project(project_name, namespace, {
|
||||
"path": project_path,
|
||||
"description": project_description,
|
||||
"initialize_with_readme": initialize_with_readme,
|
||||
|
||||
@@ -97,7 +97,7 @@ from ansible.module_utils.api import basic_auth_argument_spec
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, find_project
|
||||
auth_argument_spec, gitlab_authentication, find_project, ensure_gitlab_package
|
||||
)
|
||||
|
||||
|
||||
@@ -159,12 +159,13 @@ state_strategy = {
|
||||
|
||||
|
||||
def core(module):
|
||||
# check prerequisites and connect to gitlab server
|
||||
gl = gitlab_authentication(module)
|
||||
ensure_gitlab_package(module)
|
||||
|
||||
gitlab_project = module.params['project']
|
||||
state = module.params['state']
|
||||
|
||||
gl = gitlab_authentication(module)
|
||||
|
||||
project = find_project(gl, gitlab_project)
|
||||
# project doesn't exist
|
||||
if not project:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user