mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-28 17:36:49 +00:00
Compare commits
298 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eac1dfdc78 | ||
|
|
930b64226c | ||
|
|
885bb73cc6 | ||
|
|
6afde82f2c | ||
|
|
62d53eb3cf | ||
|
|
1ae9bcc2dd | ||
|
|
1533b5b244 | ||
|
|
99295fac75 | ||
|
|
434ff80ec6 | ||
|
|
be6d3e9aa1 | ||
|
|
b06d46f4d1 | ||
|
|
acea90ceec | ||
|
|
034d09532e | ||
|
|
f815bef3d1 | ||
|
|
e205494c8c | ||
|
|
1541eecd0e | ||
|
|
6cd87580da | ||
|
|
d227ace4a0 | ||
|
|
e7770b9132 | ||
|
|
d0f1d9efd9 | ||
|
|
22f0747e03 | ||
|
|
2ee7de681a | ||
|
|
2e20e9bb8f | ||
|
|
9d9e2bd797 | ||
|
|
d9aa9e8021 | ||
|
|
a9eeced6d7 | ||
|
|
066b306deb | ||
|
|
7426c3839e | ||
|
|
4d6735bebf | ||
|
|
1ceed02048 | ||
|
|
6dc31b13c3 | ||
|
|
c7899e384a | ||
|
|
35c8bbec8a | ||
|
|
87c37ea441 | ||
|
|
0f7e39fa1a | ||
|
|
cde48c3c03 | ||
|
|
778c91caa7 | ||
|
|
61258c9216 | ||
|
|
99e0f8a3a0 | ||
|
|
2dd159493b | ||
|
|
28caeff7bd | ||
|
|
76cf21a05b | ||
|
|
64248acce6 | ||
|
|
8ed5beb978 | ||
|
|
9b7194be53 | ||
|
|
984d9d91b8 | ||
|
|
071d89acac | ||
|
|
afc620fc74 | ||
|
|
acae2a11aa | ||
|
|
f17690e7d0 | ||
|
|
4ca716a1cf | ||
|
|
92f1a33d80 | ||
|
|
c9e07d19d8 | ||
|
|
c7bffaf270 | ||
|
|
1b0f4fdd28 | ||
|
|
02ea90f680 | ||
|
|
6d08dcdef3 | ||
|
|
8dd00a2b9b | ||
|
|
a090e2ff85 | ||
|
|
ca0b1efa5b | ||
|
|
b2f01f4c20 | ||
|
|
81b390b7dc | ||
|
|
9c7eed43a8 | ||
|
|
6999881985 | ||
|
|
d746293884 | ||
|
|
7b2853d9aa | ||
|
|
e05e7babbe | ||
|
|
01773c5338 | ||
|
|
e46e6e4dd0 | ||
|
|
270e3df416 | ||
|
|
d9524bae93 | ||
|
|
77fc407a73 | ||
|
|
2d73089ddc | ||
|
|
7a185cef08 | ||
|
|
6158b5f56b | ||
|
|
1b05e03384 | ||
|
|
0f0eb53efa | ||
|
|
6b58e784af | ||
|
|
7d644ef3d4 | ||
|
|
9db69a62b2 | ||
|
|
1c23ab8d44 | ||
|
|
96c5ceee97 | ||
|
|
768512645d | ||
|
|
66656abe17 | ||
|
|
022a7834df | ||
|
|
09de2dfd77 | ||
|
|
bb910f6aa1 | ||
|
|
56d8554b70 | ||
|
|
4fa140d896 | ||
|
|
5fc3f9c766 | ||
|
|
0f53fba20a | ||
|
|
d6c3661e3e | ||
|
|
b18c88248b | ||
|
|
fd5e05cc77 | ||
|
|
56a1d3ffd6 | ||
|
|
f1477ec8db | ||
|
|
2fb1dc0cf7 | ||
|
|
b9b4837d72 | ||
|
|
9ea8f41ebb | ||
|
|
b1fe3e34f3 | ||
|
|
841286444e | ||
|
|
4c13f10a05 | ||
|
|
9b844fc8d5 | ||
|
|
92514ee143 | ||
|
|
6621eb8b87 | ||
|
|
f4b4a2813a | ||
|
|
6f2cb85fae | ||
|
|
5cdc70bda9 | ||
|
|
89498d3650 | ||
|
|
c553351563 | ||
|
|
72c1a17bd9 | ||
|
|
694584f907 | ||
|
|
73e2c2eb85 | ||
|
|
f3ddc8757d | ||
|
|
9241b853c0 | ||
|
|
1053b3c658 | ||
|
|
d9daa6b851 | ||
|
|
a876fa0262 | ||
|
|
f64ace97af | ||
|
|
b701b5893f | ||
|
|
24667e12d0 | ||
|
|
9d93760564 | ||
|
|
ec78558559 | ||
|
|
d5c8d7ddcc | ||
|
|
6338048c73 | ||
|
|
92b388817f | ||
|
|
c72b337327 | ||
|
|
e5080b7847 | ||
|
|
079925fe66 | ||
|
|
19a87874f7 | ||
|
|
809cdda9ef | ||
|
|
bec6f732ad | ||
|
|
d2cdca416c | ||
|
|
0f1ccc07c5 | ||
|
|
deb1071666 | ||
|
|
eb9c5eb796 | ||
|
|
5c8504323e | ||
|
|
ab391c2cfa | ||
|
|
a14b525bdc | ||
|
|
996ef6ab49 | ||
|
|
055c8dac9c | ||
|
|
f4a9c7cc8b | ||
|
|
0c1f96290a | ||
|
|
d260f7ffda | ||
|
|
35d81adabf | ||
|
|
10a61c9dc3 | ||
|
|
6f47bcc399 | ||
|
|
7140b456ae | ||
|
|
8c67a5bda9 | ||
|
|
4ae436a8cc | ||
|
|
5f5c07a942 | ||
|
|
1cef1359d0 | ||
|
|
0d28bfb67e | ||
|
|
ef304ed824 | ||
|
|
bf17f289b3 | ||
|
|
0eff87d0be | ||
|
|
f00fabfa48 | ||
|
|
426cbafa06 | ||
|
|
93fe1f9a3e | ||
|
|
4e944772d5 | ||
|
|
50abeee579 | ||
|
|
eccc8d88b6 | ||
|
|
6d2d364a00 | ||
|
|
e781dd3c9b | ||
|
|
362f899a99 | ||
|
|
b44f6b8114 | ||
|
|
53a145ecb0 | ||
|
|
b22b44088f | ||
|
|
e0a1aa2f46 | ||
|
|
53e7e48834 | ||
|
|
62e3a2ed2f | ||
|
|
ecede6ca99 | ||
|
|
e1ac1fa6db | ||
|
|
81cef0bd05 | ||
|
|
a2bb118e95 | ||
|
|
bf9bcd9bb4 | ||
|
|
9bfd61e117 | ||
|
|
ca81a5cf2f | ||
|
|
853dd21eab | ||
|
|
6f267d8f35 | ||
|
|
1f975eff56 | ||
|
|
0ca922248f | ||
|
|
ef7ade6a56 | ||
|
|
d721283846 | ||
|
|
af410f5572 | ||
|
|
442dabbcc6 | ||
|
|
bbb155409e | ||
|
|
a83556af80 | ||
|
|
13a5e5a1ba | ||
|
|
466bd89bd4 | ||
|
|
bd4d5fe9db | ||
|
|
cf889faf42 | ||
|
|
ea313503dd | ||
|
|
57fa6526c4 | ||
|
|
ae4bee2627 | ||
|
|
87000ae491 | ||
|
|
46e221cbc6 | ||
|
|
3f2111582d | ||
|
|
bd8634e04e | ||
|
|
1ae57fc5dd | ||
|
|
1e5e0824d2 | ||
|
|
7eaf795774 | ||
|
|
3dc25edeac | ||
|
|
a67ee6cead | ||
|
|
9c5461dc12 | ||
|
|
0b59a71ae7 | ||
|
|
720de141b5 | ||
|
|
7ec6025690 | ||
|
|
53a5cdaed7 | ||
|
|
693efb35b3 | ||
|
|
07cd51a33b | ||
|
|
c80416164b | ||
|
|
a61bc5ab34 | ||
|
|
8ac8fa0aa9 | ||
|
|
b76994ee6e | ||
|
|
746bd3ea5d | ||
|
|
68baf56ea6 | ||
|
|
87377dd23f | ||
|
|
29f028e33b | ||
|
|
196e8fe4e3 | ||
|
|
83c6d18bc0 | ||
|
|
1314b0d7b2 | ||
|
|
be94a014c8 | ||
|
|
039c3da7dc | ||
|
|
2480250f1b | ||
|
|
860f0e12c0 | ||
|
|
2f56fd7b2a | ||
|
|
084879632a | ||
|
|
4eef56b7b3 | ||
|
|
13929acf02 | ||
|
|
070bcf80c4 | ||
|
|
0cf2a5ad05 | ||
|
|
76a64ea733 | ||
|
|
115eab2cfa | ||
|
|
dbba813e23 | ||
|
|
7daf78962b | ||
|
|
cf9fff5238 | ||
|
|
d8d68babe4 | ||
|
|
3f46cdc588 | ||
|
|
ea530784b8 | ||
|
|
dc2fa05b1f | ||
|
|
b2e51272ad | ||
|
|
afba9a11af | ||
|
|
c3ac479ae2 | ||
|
|
7e367244f7 | ||
|
|
331d2c7651 | ||
|
|
b35a262378 | ||
|
|
7d400663b6 | ||
|
|
0d0884b069 | ||
|
|
dd400e8c21 | ||
|
|
a60f9bc78b | ||
|
|
47714ecf79 | ||
|
|
d15ed4135b | ||
|
|
bd61228e40 | ||
|
|
26d7c28b33 | ||
|
|
2e533daffa | ||
|
|
6c50119eab | ||
|
|
bc3435b993 | ||
|
|
370f5d8082 | ||
|
|
e77c5413c9 | ||
|
|
800ee1bae0 | ||
|
|
8de8d21062 | ||
|
|
81e71b5034 | ||
|
|
44ce63ed85 | ||
|
|
a3c9c688b9 | ||
|
|
a332ed4429 | ||
|
|
91571f8bff | ||
|
|
43856eaa6f | ||
|
|
ae87b5479a | ||
|
|
42cd462780 | ||
|
|
d871378574 | ||
|
|
983b292399 | ||
|
|
6831aa5501 | ||
|
|
2d8a94a459 | ||
|
|
f721e76fdc | ||
|
|
3eadb9d637 | ||
|
|
033582b696 | ||
|
|
974997594f | ||
|
|
fa8ce6dea8 | ||
|
|
1d90e91528 | ||
|
|
a90e2c8002 | ||
|
|
c506375f2a | ||
|
|
4def9439bd | ||
|
|
023654473b | ||
|
|
a216f15dd9 | ||
|
|
f613983cb4 | ||
|
|
c22199794d | ||
|
|
24b1d92e84 | ||
|
|
4bc44e4062 | ||
|
|
06fd6d8742 | ||
|
|
dd0ae4a003 | ||
|
|
646ca74810 | ||
|
|
d60c107818 | ||
|
|
ef2d14f24e | ||
|
|
b3cde9b8a4 | ||
|
|
dc4222df0d | ||
|
|
b9a89d6d0f | ||
|
|
f48913d91b |
48
.github/BOTMETA.yml
vendored
48
.github/BOTMETA.yml
vendored
@@ -58,8 +58,6 @@ files:
|
||||
maintainers: giner
|
||||
$filters/from_csv.py:
|
||||
maintainers: Ajpantuso
|
||||
$filters/hashids:
|
||||
maintainers: Ajpantuso
|
||||
$filters/jc.py:
|
||||
maintainers: kellyjonbrazil
|
||||
$filters/list.py:
|
||||
@@ -83,8 +81,6 @@ files:
|
||||
maintainers: $team_linode
|
||||
labels: cloud linode
|
||||
keywords: linode dynamic inventory script
|
||||
$inventories/lxd.py:
|
||||
maintainers: conloos
|
||||
$inventories/proxmox.py:
|
||||
maintainers: $team_virt ilijamt
|
||||
$inventories/scaleway.py:
|
||||
@@ -115,8 +111,6 @@ files:
|
||||
$lookups/nios:
|
||||
maintainers: $team_networking sganesh-infoblox
|
||||
labels: infoblox networking
|
||||
$lookups/random_string.py:
|
||||
maintainers: Akasurde
|
||||
$module_utils/:
|
||||
labels: module_utils
|
||||
$module_utils/gitlab.py:
|
||||
@@ -139,9 +133,6 @@ files:
|
||||
$module_utils/memset.py:
|
||||
maintainers: glitchcrab
|
||||
labels: cloud memset
|
||||
$module_utils/mh/:
|
||||
maintainers: russoz
|
||||
labels: module_helper
|
||||
$module_utils/module_helper.py:
|
||||
maintainers: russoz
|
||||
labels: module_helper
|
||||
@@ -157,6 +148,7 @@ files:
|
||||
$module_utils/redfish_utils.py:
|
||||
maintainers: $team_redfish
|
||||
labels: redfish_utils
|
||||
$module_utils/remote_management/dellemc/: rajeevarakkal
|
||||
$module_utils/remote_management/lxca/common.py: navalkp prabhosa
|
||||
$module_utils/scaleway.py:
|
||||
maintainers: $team_scaleway
|
||||
@@ -200,6 +192,8 @@ files:
|
||||
maintainers: glitchcrab
|
||||
$modules/cloud/misc/cloud_init_data_facts.py:
|
||||
maintainers: resmo
|
||||
$modules/cloud/misc/helm.py:
|
||||
maintainers: flaper87
|
||||
$modules/cloud/misc/proxmox.py:
|
||||
maintainers: $team_virt UnderGreen
|
||||
labels: proxmox virt
|
||||
@@ -344,14 +338,10 @@ files:
|
||||
$modules/database/mssql/mssql_db.py:
|
||||
maintainers: vedit Jmainguy kenichi-ogawa-1988
|
||||
labels: mssql_db
|
||||
$modules/database/saphana/hana_query.py:
|
||||
maintainers: rainerleber
|
||||
$modules/database/vertica/:
|
||||
maintainers: dareko
|
||||
$modules/files/archive.py:
|
||||
maintainers: bendoh
|
||||
$modules/files/filesize.py:
|
||||
maintainers: quidame
|
||||
$modules/files/ini_file.py:
|
||||
maintainers: jpmens noseka1
|
||||
$modules/files/iso_extract.py:
|
||||
@@ -365,6 +355,8 @@ files:
|
||||
maintainers: dagwieers magnus919 tbielawa cmprescott sm4rk0
|
||||
labels: m:xml xml
|
||||
ignore: magnus919
|
||||
$modules/identity/onepassword_facts.py:
|
||||
maintainers: Rylon
|
||||
$modules/identity/ipa/:
|
||||
maintainers: $team_ipa
|
||||
$modules/identity/ipa/ipa_pwpolicy.py:
|
||||
@@ -377,8 +369,6 @@ files:
|
||||
maintainers: $team_keycloak
|
||||
$modules/identity/keycloak/keycloak_group.py:
|
||||
maintainers: adamgoossens
|
||||
$modules/identity/keycloak/keycloak_realm.py:
|
||||
maintainers: kris2kris
|
||||
$modules/identity/onepassword_info.py:
|
||||
maintainers: Rylon
|
||||
$modules/identity/opendj/opendj_backendprop.py:
|
||||
@@ -471,6 +461,8 @@ files:
|
||||
maintainers: akostyuk
|
||||
$modules/net_tools/ipwcli_dns.py:
|
||||
maintainers: cwollinger
|
||||
$modules/net_tools/ldap/ldap_attr.py:
|
||||
maintainers: jtyr
|
||||
$modules/net_tools/ldap/ldap_attrs.py:
|
||||
maintainers: drybjed jtyr noles
|
||||
$modules/net_tools/ldap/ldap_entry.py:
|
||||
@@ -569,7 +561,7 @@ files:
|
||||
maintainers: dmtrs
|
||||
ignore: resmo
|
||||
$modules/packaging/language/cpanm.py:
|
||||
maintainers: fcuny russoz
|
||||
maintainers: fcuny
|
||||
$modules/packaging/language/easy_install.py:
|
||||
maintainers: mattupstate
|
||||
$modules/packaging/language/gem.py:
|
||||
@@ -649,9 +641,6 @@ files:
|
||||
maintainers: elasticdog indrajitr tchernomax
|
||||
labels: pacman
|
||||
ignore: elasticdog
|
||||
$modules/packaging/os/pacman_key.py:
|
||||
maintainers: grawlinson
|
||||
labels: pacman
|
||||
$modules/packaging/os/pkgin.py:
|
||||
maintainers: $team_solaris L2G jasperla szinck martinm82
|
||||
labels: pkgin solaris
|
||||
@@ -722,6 +711,12 @@ files:
|
||||
ignore: matze
|
||||
$modules/remote_management/cobbler/:
|
||||
maintainers: dagwieers
|
||||
$modules/remote_management/dellemc/:
|
||||
maintainers: rajeevarakkal
|
||||
$modules/remote_management/dellemc/idrac_server_config_profile.py:
|
||||
maintainers: jagadeeshnv
|
||||
$modules/remote_management/dellemc/ome_device_info.py:
|
||||
maintainers: Sajna-Shetty
|
||||
$modules/remote_management/hpilo/:
|
||||
maintainers: haad
|
||||
ignore: dagwieers
|
||||
@@ -741,6 +736,8 @@ files:
|
||||
maintainers: evertmulder
|
||||
$modules/remote_management/manageiq/manageiq_tenant.py:
|
||||
maintainers: evertmulder
|
||||
$modules/remote_management/oneview/oneview_datacenter_facts.py:
|
||||
maintainers: aalexmonteiro madhav-bharadwaj ricardogpsf soodpr
|
||||
$modules/remote_management/oneview/:
|
||||
maintainers: adriane-cardozo fgbulsoni tmiotto
|
||||
$modules/remote_management/oneview/oneview_datacenter_info.py:
|
||||
@@ -789,6 +786,12 @@ files:
|
||||
maintainers: yeukhon
|
||||
$modules/storage/emc/emc_vnx_sg_member.py:
|
||||
maintainers: remixtj
|
||||
$modules/storage/glusterfs/:
|
||||
maintainers: devyanikota
|
||||
$modules/storage/glusterfs/gluster_peer.py:
|
||||
maintainers: sac
|
||||
$modules/storage/glusterfs/gluster_volume.py:
|
||||
maintainers: rosmo
|
||||
$modules/storage/hpe3par/ss_3par_cpg.py:
|
||||
maintainers: farhan7500 gautamphegde
|
||||
$modules/storage/ibm/:
|
||||
@@ -810,6 +813,9 @@ files:
|
||||
maintainers: johanwiren
|
||||
$modules/storage/zfs/zfs_delegate_admin.py:
|
||||
maintainers: natefoo
|
||||
$modules/system/python_requirements_facts.py:
|
||||
maintainers: willthames
|
||||
ignore: ryansb
|
||||
$modules/system/aix:
|
||||
maintainers: $team_aix
|
||||
labels: aix
|
||||
@@ -942,6 +948,10 @@ files:
|
||||
labels: xfconf
|
||||
$modules/system/xfs_quota.py:
|
||||
maintainers: bushvin
|
||||
$modules/web_infrastructure/jenkins_job_facts.py:
|
||||
maintainers: stpierre
|
||||
$modules/web_infrastructure/nginx_status_facts.py:
|
||||
maintainers: resmo
|
||||
$modules/web_infrastructure/apache2_mod_proxy.py:
|
||||
maintainers: oboukili
|
||||
$modules/web_infrastructure/apache2_module.py:
|
||||
|
||||
135
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
135
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,135 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠
|
||||
Verify first that your issue is not [already reported on GitHub][issue search].
|
||||
Also test if the latest release and devel branch are affected too.
|
||||
*Complete **all** sections as described, this form is processed automatically.*
|
||||
|
||||
[issue search]: https://github.com/ansible-collections/community.general/search?q=is%3Aissue&type=issues
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Explain the problem briefly below.
|
||||
placeholder: >-
|
||||
When I try to do X with the collection from the main branch on GitHub, Y
|
||||
breaks in a way Z under the env E. Here are all the details I know
|
||||
about this problem...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Issue Type
|
||||
# FIXME: Once GitHub allows defining the default choice, update this
|
||||
options:
|
||||
- Bug Report
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
# For smaller collections we could use a multi-select and hardcode the list
|
||||
# May generate this list via GitHub action and walking files under https://github.com/ansible-collections/community.general/tree/main/plugins
|
||||
# Select from list, filter as you type (`mysql` would only show the 3 mysql components)
|
||||
# OR freeform - doesn't seem to be supported in adaptivecards
|
||||
label: Component Name
|
||||
description: >-
|
||||
Write the short name of the module, plugin, task or feature below,
|
||||
*use your best guess if unsure*.
|
||||
placeholder: dnf, apt, yum, pip, user etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Ansible Version
|
||||
description: >-
|
||||
Paste verbatim output from `ansible --version` between
|
||||
tripple backticks.
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible --version
|
||||
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: >-
|
||||
If this issue has an example piece of YAML that can help to reproduce this problem, please provide it.
|
||||
This can be a piece of YAML from, e.g., an automation, script, scene or configuration.
|
||||
Paste verbatim output from `ansible-config dump --only-changed` between quotes
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible-config dump --only-changed
|
||||
|
||||
```
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: OS / Environment
|
||||
description: >-
|
||||
Provide all relevant information below, e.g. target OS versions,
|
||||
network device firmware, etc.
|
||||
placeholder: RHEL 8, CentOS Stream etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: |
|
||||
Describe exactly how to reproduce the problem, using a minimal test-case. It would *really* help us understand your problem if you could also pased any playbooks, configs and commands you used.
|
||||
|
||||
**HINT:** You can paste https://gist.github.com links for larger files.
|
||||
value: |
|
||||
<!--- Paste example playbooks or commands between quotes below -->
|
||||
```yaml (paste below)
|
||||
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Results
|
||||
description: >-
|
||||
Describe what you expected to happen when running the steps above.
|
||||
placeholder: >-
|
||||
I expected X to happen because I assumed Y.
|
||||
that it did not.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual Results
|
||||
description: |
|
||||
Describe what actually happened. If possible run with extra verbosity (`-vvvv`).
|
||||
|
||||
Paste verbatim command output between quotes.
|
||||
value: |
|
||||
```console (paste below)
|
||||
|
||||
```
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: |
|
||||
Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--ansible-collections) first.
|
||||
options:
|
||||
- label: I agree to follow the Ansible Code of Conduct
|
||||
required: true
|
||||
...
|
||||
27
.github/ISSUE_TEMPLATE/config.yml
vendored
27
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,27 +0,0 @@
|
||||
---
|
||||
# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
|
||||
blank_issues_enabled: false # default: true
|
||||
contact_links:
|
||||
- name: Security bug report
|
||||
url: https://docs.ansible.com/ansible-core/devel/community/reporting_bugs_and_features.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
|
||||
about: |
|
||||
Please learn how to report security vulnerabilities here.
|
||||
|
||||
For all security related bugs, email security@ansible.com
|
||||
instead of using this issue tracker and you will receive
|
||||
a prompt response.
|
||||
|
||||
For more information, see
|
||||
https://docs.ansible.com/ansible/latest/community/reporting_bugs_and_features.html
|
||||
- name: Ansible Code of Conduct
|
||||
url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
|
||||
about: Be nice to other members of the community.
|
||||
- name: Talks to the community
|
||||
url: https://docs.ansible.com/ansible/latest/community/communication.html?utm_medium=github&utm_source=issue_template_chooser#mailing-list-information
|
||||
about: Please ask and answer usage questions here
|
||||
- name: Working groups
|
||||
url: https://github.com/ansible/community/wiki
|
||||
about: Interested in improving a specific area? Become a part of a working group!
|
||||
- name: For Enterprise
|
||||
url: https://www.ansible.com/products/engine?utm_medium=github&utm_source=issue_template_chooser_ansible_collections
|
||||
about: Red Hat offers support for the Ansible Automation Platform
|
||||
111
.github/ISSUE_TEMPLATE/documentation_report.yml
vendored
111
.github/ISSUE_TEMPLATE/documentation_report.yml
vendored
@@ -1,111 +0,0 @@
|
||||
---
|
||||
name: Documentation Report
|
||||
description: Ask us about docs
|
||||
# NOTE: issue body is enabled to allow screenshots
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠
|
||||
Verify first that your issue is not [already reported on GitHub][issue search].
|
||||
Also test if the latest release and devel branch are affected too.
|
||||
*Complete **all** sections as described, this form is processed automatically.*
|
||||
|
||||
[issue search]: https://github.com/ansible-collections/community.general/search?q=is%3Aissue&type=issues
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: |
|
||||
Explain the problem briefly below, add suggestions to wording or structure.
|
||||
|
||||
**HINT:** Did you know the documentation has an `Edit on GitHub` link on every page?
|
||||
placeholder: >-
|
||||
I was reading the Collection documentation of version X and I'm having
|
||||
problems understanding Y. It would be very helpful if that got
|
||||
rephrased as Z.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Issue Type
|
||||
# FIXME: Once GitHub allows defining the default choice, update this
|
||||
options:
|
||||
- Documentation Report
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Component Name
|
||||
description: >-
|
||||
Write the short name of the rst file, module, plugin, task or
|
||||
feature below, *use your best guess if unsure*.
|
||||
placeholder: mysql_user
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Ansible Version
|
||||
description: >-
|
||||
Paste verbatim output from `ansible --version` between
|
||||
tripple backticks.
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible --version
|
||||
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: >-
|
||||
Paste verbatim output from `ansible-config dump --only-changed` between quotes.
|
||||
value: |
|
||||
```console (paste below)
|
||||
$ ansible-config dump --only-changed
|
||||
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: OS / Environment
|
||||
description: >-
|
||||
Provide all relevant information below, e.g. OS version,
|
||||
browser, etc.
|
||||
placeholder: Fedora 33, Firefox etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Describe how this improves the documentation, e.g. before/after situation or screenshots.
|
||||
|
||||
**Tip:** It's not possible to upload the screenshot via this field directly but you can use the last textarea in this form to attach them.
|
||||
|
||||
**HINT:** You can paste https://gist.github.com links for larger files.
|
||||
placeholder: >-
|
||||
When the improvement is applied, it makes it more straightforward
|
||||
to understand X.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: |
|
||||
Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--ansible-collections) first.
|
||||
options:
|
||||
- label: I agree to follow the Ansible Code of Conduct
|
||||
required: true
|
||||
...
|
||||
69
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
69
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,69 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⚠
|
||||
Verify first that your issue is not [already reported on GitHub][issue search].
|
||||
Also test if the latest release and devel branch are affected too.
|
||||
*Complete **all** sections as described, this form is processed automatically.*
|
||||
|
||||
[issue search]: https://github.com/ansible-collections/community.general/search?q=is%3Aissue&type=issues
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the new feature/improvement briefly below.
|
||||
placeholder: >-
|
||||
I am trying to do X with the collection from the main branch on GitHub and
|
||||
I think that implementing a feature Y would be very helpful for me and
|
||||
every other user of ansible-core because of Z.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Issue Type
|
||||
# FIXME: Once GitHub allows defining the default choice, update this
|
||||
options:
|
||||
- Feature Idea
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Component Name
|
||||
description: >-
|
||||
Write the short name of the module, plugin, task or feature below,
|
||||
*use your best guess if unsure*.
|
||||
placeholder: dnf, apt, yum, pip, user etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Describe how the feature would be used, why it is needed and what it would solve.
|
||||
|
||||
**HINT:** You can paste https://gist.github.com links for larger files.
|
||||
value: |
|
||||
<!--- Paste example playbooks or commands between quotes below -->
|
||||
```yaml (paste below)
|
||||
|
||||
```
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: |
|
||||
Read the [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html?utm_medium=github&utm_source=issue_form--ansible-collections) first.
|
||||
options:
|
||||
- label: I agree to follow the Ansible Code of Conduct
|
||||
required: true
|
||||
...
|
||||
1415
CHANGELOG.rst
1415
CHANGELOG.rst
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
# Community General Collection
|
||||
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://codecov.io/gh/ansible-collections/community.general)
|
||||
|
||||
This repo contains the `community.general` Ansible Collection. The collection includes many modules and plugins supported by Ansible community which are not part of more specialized community collections.
|
||||
@@ -80,7 +80,7 @@ Basic instructions without release branches:
|
||||
|
||||
## Release notes
|
||||
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-3/CHANGELOG.rst).
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-2/CHANGELOG.rst).
|
||||
|
||||
## Roadmap
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
---
|
||||
sections:
|
||||
- title: Guides
|
||||
toctree:
|
||||
- filter_guide
|
||||
@@ -1,753 +0,0 @@
|
||||
.. _ansible_collections.community.general.docsite.filter_guide:
|
||||
|
||||
community.general Filter Guide
|
||||
==============================
|
||||
|
||||
The :ref:`community.general collection <plugins_in_community.general>` offers several useful filter plugins.
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
Paths
|
||||
-----
|
||||
|
||||
The ``path_join`` filter has been added in ansible-base 2.10. If you want to use this filter, but also need to support Ansible 2.9, you can use ``community.general``'s ``path_join`` shim, ``community.general.path_join``. This filter redirects to ``path_join`` for ansible-base 2.10 and ansible-core 2.11 or newer, and re-implements the filter for Ansible 2.9.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
# ansible-base 2.10 or newer:
|
||||
path: {{ ('/etc', path, 'subdir', file) | path_join }}
|
||||
|
||||
# Also works with Ansible 2.9:
|
||||
path: {{ ('/etc', path, 'subdir', file) | community.general.path_join }}
|
||||
|
||||
.. versionadded:: 3.0.0
|
||||
|
||||
Abstract transformations
|
||||
------------------------
|
||||
|
||||
Dictionaries
|
||||
^^^^^^^^^^^^
|
||||
|
||||
You can use the ``dict_kv`` filter to create a single-entry dictionary with ``value | community.general.dict_kv(key)``:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Create a single-entry dictionary
|
||||
debug:
|
||||
msg: "{{ myvar | community.general.dict_kv('thatsmyvar') }}"
|
||||
vars:
|
||||
myvar: myvalue
|
||||
|
||||
- name: Create a list of dictionaries where the 'server' field is taken from a list
|
||||
debug:
|
||||
msg: >-
|
||||
{{ myservers | map('community.general.dict_kv', 'server')
|
||||
| map('combine', common_config) }}
|
||||
vars:
|
||||
common_config:
|
||||
type: host
|
||||
database: all
|
||||
myservers:
|
||||
- server1
|
||||
- server2
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create a single-entry dictionary] **************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": {
|
||||
"thatsmyvar": "myvalue"
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Create a list of dictionaries where the 'server' field is taken from a list] *******
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"database": "all",
|
||||
"server": "server1",
|
||||
"type": "host"
|
||||
},
|
||||
{
|
||||
"database": "all",
|
||||
"server": "server2",
|
||||
"type": "host"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded:: 2.0.0
|
||||
|
||||
If you need to convert a list of key-value pairs to a dictionary, you can use the ``dict`` function. Unfortunately, this function cannot be used with ``map``. For this, the ``community.general.dict`` filter can be used:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Create a dictionary with the dict function
|
||||
debug:
|
||||
msg: "{{ dict([[1, 2], ['a', 'b']]) }}"
|
||||
|
||||
- name: Create a dictionary with the community.general.dict filter
|
||||
debug:
|
||||
msg: "{{ [[1, 2], ['a', 'b']] | community.general.dict }}"
|
||||
|
||||
- name: Create a list of dictionaries with map and the community.general.dict filter
|
||||
debug:
|
||||
msg: >-
|
||||
{{ values | map('zip', ['k1', 'k2', 'k3'])
|
||||
| map('map', 'reverse')
|
||||
| map('community.general.dict') }}
|
||||
vars:
|
||||
values:
|
||||
- - foo
|
||||
- 23
|
||||
- a
|
||||
- - bar
|
||||
- 42
|
||||
- b
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create a dictionary with the dict function] ****************************************
|
||||
ok: [localhost] => {
|
||||
"msg": {
|
||||
"1": 2,
|
||||
"a": "b"
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Create a dictionary with the community.general.dict filter] ************************
|
||||
ok: [localhost] => {
|
||||
"msg": {
|
||||
"1": 2,
|
||||
"a": "b"
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Create a list of dictionaries with map and the community.general.dict filter] ******
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"k1": "foo",
|
||||
"k2": 23,
|
||||
"k3": "a"
|
||||
},
|
||||
{
|
||||
"k1": "bar",
|
||||
"k2": 42,
|
||||
"k3": "b"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded:: 3.0.0
|
||||
|
||||
Grouping
|
||||
^^^^^^^^
|
||||
|
||||
If you have a list of dictionaries, the Jinja2 ``groupby`` filter allows to group the list by an attribute. This results in a list of ``(grouper, list)`` namedtuples, where ``list`` contains all dictionaries where the selected attribute equals ``grouper``. If you know that for every ``grouper``, there will be a most one entry in that list, you can use the ``community.general.groupby_as_dict`` filter to convert the original list into a dictionary which maps ``grouper`` to the corresponding dictionary.
|
||||
|
||||
One example is ``ansible_facts.mounts``, which is a list of dictionaries where each has one ``device`` element to indicate the device which is mounted. Therefore, ``ansible_facts.mounts | community.general.groupby_as_dict('device')`` is a dictionary mapping a device to the mount information:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Output mount facts grouped by device name
|
||||
debug:
|
||||
var: ansible_facts.mounts | community.general.groupby_as_dict('device')
|
||||
|
||||
- name: Output mount facts grouped by mount point
|
||||
debug:
|
||||
var: ansible_facts.mounts | community.general.groupby_as_dict('mount')
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Output mount facts grouped by device name] ******************************************
|
||||
ok: [localhost] => {
|
||||
"ansible_facts.mounts | community.general.groupby_as_dict('device')": {
|
||||
"/dev/sda1": {
|
||||
"block_available": 2000,
|
||||
"block_size": 4096,
|
||||
"block_total": 2345,
|
||||
"block_used": 345,
|
||||
"device": "/dev/sda1",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 500,
|
||||
"inode_total": 512,
|
||||
"inode_used": 12,
|
||||
"mount": "/boot",
|
||||
"options": "rw,relatime,data=ordered",
|
||||
"size_available": 56821,
|
||||
"size_total": 543210,
|
||||
"uuid": "ab31cade-d9c1-484d-8482-8a4cbee5241a"
|
||||
},
|
||||
"/dev/sda2": {
|
||||
"block_available": 1234,
|
||||
"block_size": 4096,
|
||||
"block_total": 12345,
|
||||
"block_used": 11111,
|
||||
"device": "/dev/sda2",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 1111,
|
||||
"inode_total": 1234,
|
||||
"inode_used": 123,
|
||||
"mount": "/",
|
||||
"options": "rw,relatime",
|
||||
"size_available": 42143,
|
||||
"size_total": 543210,
|
||||
"uuid": "abcdef01-2345-6789-0abc-def012345678"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TASK [Output mount facts grouped by mount point] ******************************************
|
||||
ok: [localhost] => {
|
||||
"ansible_facts.mounts | community.general.groupby_as_dict('mount')": {
|
||||
"/": {
|
||||
"block_available": 1234,
|
||||
"block_size": 4096,
|
||||
"block_total": 12345,
|
||||
"block_used": 11111,
|
||||
"device": "/dev/sda2",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 1111,
|
||||
"inode_total": 1234,
|
||||
"inode_used": 123,
|
||||
"mount": "/",
|
||||
"options": "rw,relatime",
|
||||
"size_available": 42143,
|
||||
"size_total": 543210,
|
||||
"uuid": "bdf50b7d-4859-40af-8665-c637ee7a7808"
|
||||
},
|
||||
"/boot": {
|
||||
"block_available": 2000,
|
||||
"block_size": 4096,
|
||||
"block_total": 2345,
|
||||
"block_used": 345,
|
||||
"device": "/dev/sda1",
|
||||
"fstype": "ext4",
|
||||
"inode_available": 500,
|
||||
"inode_total": 512,
|
||||
"inode_used": 12,
|
||||
"mount": "/boot",
|
||||
"options": "rw,relatime,data=ordered",
|
||||
"size_available": 56821,
|
||||
"size_total": 543210,
|
||||
"uuid": "ab31cade-d9c1-484d-8482-8a4cbee5241a"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. versionadded: 3.0.0
|
||||
|
||||
Merging lists of dictionaries
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have two lists of dictionaries and want to combine them into a list of merged dictionaries, where two dictionaries are merged if they coincide in one attribute, you can use the ``lists_mergeby`` filter.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Merge two lists by common attribute 'name'
|
||||
debug:
|
||||
var: list1 | community.general.lists_mergeby(list2, 'name')
|
||||
vars:
|
||||
list1:
|
||||
- name: foo
|
||||
extra: true
|
||||
- name: bar
|
||||
extra: false
|
||||
- name: meh
|
||||
extra: true
|
||||
list2:
|
||||
- name: foo
|
||||
path: /foo
|
||||
- name: baz
|
||||
path: /bazzz
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Merge two lists by common attribute 'name'] ****************************************
|
||||
ok: [localhost] => {
|
||||
"list1 | community.general.lists_mergeby(list2, 'name')": [
|
||||
{
|
||||
"extra": false,
|
||||
"name": "bar"
|
||||
},
|
||||
{
|
||||
"name": "baz",
|
||||
"path": "/bazzz"
|
||||
},
|
||||
{
|
||||
"extra": true,
|
||||
"name": "foo",
|
||||
"path": "/foo"
|
||||
},
|
||||
{
|
||||
"extra": true,
|
||||
"name": "meh"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded: 2.0.0
|
||||
|
||||
Working with times
|
||||
------------------
|
||||
|
||||
The ``to_time_unit`` filter allows to convert times from a human-readable string to a unit. For example, ``'4h 30min 12second' | community.general.to_time_unit('hour')`` gives the number of hours that correspond to 4 hours, 30 minutes and 12 seconds.
|
||||
|
||||
There are shorthands to directly convert to various units, like ``to_hours``, ``to_minutes``, ``to_seconds``, and so on. The following table lists all units that can be used:
|
||||
|
||||
.. list-table:: Units
|
||||
:widths: 25 25 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Unit name
|
||||
- Unit value in seconds
|
||||
- Unit strings for filter
|
||||
- Shorthand filter
|
||||
* - Millisecond
|
||||
- 1/1000 second
|
||||
- ``ms``, ``millisecond``, ``milliseconds``, ``msec``, ``msecs``, ``msecond``, ``mseconds``
|
||||
- ``to_milliseconds``
|
||||
* - Second
|
||||
- 1 second
|
||||
- ``s``, ``sec``, ``secs``, ``second``, ``seconds``
|
||||
- ``to_seconds``
|
||||
* - Minute
|
||||
- 60 seconds
|
||||
- ``m``, ``min``, ``mins``, ``minute``, ``minutes``
|
||||
- ``to_minutes``
|
||||
* - Hour
|
||||
- 60*60 seconds
|
||||
- ``h``, ``hour``, ``hours``
|
||||
- ``to_hours``
|
||||
* - Day
|
||||
- 24*60*60 seconds
|
||||
- ``d``, ``day``, ``days``
|
||||
- ``to_days``
|
||||
* - Week
|
||||
- 7*24*60*60 seconds
|
||||
- ``w``, ``week``, ``weeks``
|
||||
- ``to_weeks``
|
||||
* - Month
|
||||
- 30*24*60*60 seconds
|
||||
- ``mo``, ``month``, ``months``
|
||||
- ``to_months``
|
||||
* - Year
|
||||
- 365*24*60*60 seconds
|
||||
- ``y``, ``year``, ``years``
|
||||
- ``to_years``
|
||||
|
||||
Note that months and years are using a simplified representation: a month is 30 days, and a year is 365 days. If you need different definitions of months or years, you can pass them as keyword arguments. For example, if you want a year to be 365.25 days, and a month to be 30.5 days, you can write ``'11months 4' | community.general.to_years(year=365.25, month=30.5)``. These keyword arguments can be specified to ``to_time_unit`` and to all shorthand filters.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Convert string to seconds
|
||||
debug:
|
||||
msg: "{{ '30h 20m 10s 123ms' | community.general.to_time_unit('seconds') }}"
|
||||
|
||||
- name: Convert string to hours
|
||||
debug:
|
||||
msg: "{{ '30h 20m 10s 123ms' | community.general.to_hours }}"
|
||||
|
||||
- name: Convert string to years (using 365.25 days == 1 year)
|
||||
debug:
|
||||
msg: "{{ '400d 15h' | community.general.to_years(year=365.25) }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Convert string to seconds] **********************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "109210.123"
|
||||
}
|
||||
|
||||
TASK [Convert string to hours] ************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "30.336145277778"
|
||||
}
|
||||
|
||||
TASK [Convert string to years (using 365.25 days == 1 year)] ******************************
|
||||
ok: [localhost] => {
|
||||
"msg": "1.096851471595"
|
||||
}
|
||||
|
||||
.. versionadded: 0.2.0
|
||||
|
||||
Working with versions
|
||||
---------------------
|
||||
|
||||
If you need to sort a list of version numbers, the Jinja ``sort`` filter is problematic. Since it sorts lexicographically, ``2.10`` will come before ``2.9``. To treat version numbers correctly, you can use the ``version_sort`` filter:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Sort list by version number
|
||||
debug:
|
||||
var: ansible_versions | community.general.version_sort
|
||||
vars:
|
||||
ansible_versions:
|
||||
- '2.8.0'
|
||||
- '2.11.0'
|
||||
- '2.7.0'
|
||||
- '2.10.0'
|
||||
- '2.9.0'
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Sort list by version number] ********************************************************
|
||||
ok: [localhost] => {
|
||||
"ansible_versions | community.general.version_sort": [
|
||||
"2.7.0",
|
||||
"2.8.0",
|
||||
"2.9.0",
|
||||
"2.10.0",
|
||||
"2.11.0"
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded: 2.2.0
|
||||
|
||||
Creating identifiers
|
||||
--------------------
|
||||
|
||||
The following filters allow to create identifiers.
|
||||
|
||||
Hashids
|
||||
^^^^^^^
|
||||
|
||||
`Hashids <https://hashids.org/>`_ allow to convert sequences of integers to short unique string identifiers. This filter needs the `hashids Python library <https://pypi.org/project/hashids/>`_ installed on the controller.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Create hashid"
|
||||
debug:
|
||||
msg: "{{ [1234, 5, 6] | community.general.hashids_encode }}"
|
||||
|
||||
- name: "Decode hashid"
|
||||
debug:
|
||||
msg: "{{ 'jm2Cytn' | community.general.hashids_decode }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create hashid] **********************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "jm2Cytn"
|
||||
}
|
||||
|
||||
TASK [Decode hashid] **********************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
1234,
|
||||
5,
|
||||
6
|
||||
]
|
||||
}
|
||||
|
||||
The hashids filters accept keyword arguments to allow fine-tuning the hashids generated:
|
||||
|
||||
:salt: String to use as salt when hashing.
|
||||
:alphabet: String of 16 or more unique characters to produce a hash.
|
||||
:min_length: Minimum length of hash produced.
|
||||
|
||||
.. versionadded: 3.0.0
|
||||
|
||||
Random MACs
|
||||
^^^^^^^^^^^
|
||||
|
||||
You can use the ``random_mac`` filter to complete a partial `MAC address <https://en.wikipedia.org/wiki/MAC_address>`_ to a random 6-byte MAC address.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Create a random MAC starting with ff:"
|
||||
debug:
|
||||
msg: "{{ 'FF' | community.general.random_mac }}"
|
||||
|
||||
- name: "Create a random MAC starting with 00:11:22:"
|
||||
debug:
|
||||
msg: "{{ '00:11:22' | community.general.random_mac }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Create a random MAC starting with ff:] **********************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "ff:69:d3:78:7f:b4"
|
||||
}
|
||||
|
||||
TASK [Create a random MAC starting with 00:11:22:] ****************************************
|
||||
ok: [localhost] => {
|
||||
"msg": "00:11:22:71:5d:3b"
|
||||
}
|
||||
|
||||
You can also initialize the random number generator from a seed to create random-but-idempotent MAC addresses:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
"{{ '52:54:00' | community.general.random_mac(seed=inventory_hostname) }}"
|
||||
|
||||
Conversions
|
||||
-----------
|
||||
|
||||
Parsing CSV files
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Ansible offers the :ref:`community.general.read_csv module <ansible_collections.community.general.read_csv_module>` to read CSV files. Sometimes you need to convert strings to CSV files instead. For this, the ``from_csv`` filter exists.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: "Parse CSV from string"
|
||||
debug:
|
||||
msg: "{{ csv_string | community.general.from_csv }}"
|
||||
vars:
|
||||
csv_string: |
|
||||
foo,bar,baz
|
||||
1,2,3
|
||||
you,this,then
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Parse CSV from string] **************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"bar": "2",
|
||||
"baz": "3",
|
||||
"foo": "1"
|
||||
},
|
||||
{
|
||||
"bar": "this",
|
||||
"baz": "then",
|
||||
"foo": "you"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
The ``from_csv`` filter has several keyword arguments to control its behavior:
|
||||
|
||||
:dialect: Dialect of the CSV file. Default is ``excel``. Other possible choices are ``excel-tab`` and ``unix``. If one of ``delimiter``, ``skipinitialspace`` or ``strict`` is specified, ``dialect`` is ignored.
|
||||
:fieldnames: A set of column names to use. If not provided, the first line of the CSV is assumed to contain the column names.
|
||||
:delimiter: Sets the delimiter to use. Default depends on the dialect used.
|
||||
:skipinitialspace: Set to ``true`` to ignore space directly after the delimiter. Default depends on the dialect used (usually ``false``).
|
||||
:strict: Set to ``true`` to error out on invalid CSV input.
|
||||
|
||||
.. versionadded: 3.0.0
|
||||
|
||||
Converting to JSON
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
`JC <https://pypi.org/project/jc/>`_ is a CLI tool and Python library which allows to interpret output of various CLI programs as JSON. It is also available as a filter in community.general. This filter needs the `jc Python library <https://pypi.org/project/jc/>`_ installed on the controller.
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Run 'ls' to list files in /
|
||||
command: ls /
|
||||
register: result
|
||||
|
||||
- name: Parse the ls output
|
||||
debug:
|
||||
msg: "{{ result.stdout | community.general.jc('ls') }}"
|
||||
|
||||
This produces:
|
||||
|
||||
.. code-block:: ansible-output
|
||||
|
||||
TASK [Run 'ls' to list files in /] ********************************************************
|
||||
changed: [localhost]
|
||||
|
||||
TASK [Parse the ls output] ****************************************************************
|
||||
ok: [localhost] => {
|
||||
"msg": [
|
||||
{
|
||||
"filename": "bin"
|
||||
},
|
||||
{
|
||||
"filename": "boot"
|
||||
},
|
||||
{
|
||||
"filename": "dev"
|
||||
},
|
||||
{
|
||||
"filename": "etc"
|
||||
},
|
||||
{
|
||||
"filename": "home"
|
||||
},
|
||||
{
|
||||
"filename": "lib"
|
||||
},
|
||||
{
|
||||
"filename": "proc"
|
||||
},
|
||||
{
|
||||
"filename": "root"
|
||||
},
|
||||
{
|
||||
"filename": "run"
|
||||
},
|
||||
{
|
||||
"filename": "tmp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. versionadded: 2.0.0
|
||||
|
||||
.. _ansible_collections.community.general.docsite.json_query_filter:
|
||||
|
||||
Selecting JSON data: JSON queries
|
||||
---------------------------------
|
||||
|
||||
To select a single element or a data subset from a complex data structure in JSON format (for example, Ansible facts), use the ``json_query`` filter. The ``json_query`` filter lets you query a complex JSON structure and iterate over it using a loop structure.
|
||||
|
||||
.. note:: You must manually install the **jmespath** dependency on the Ansible controller before using this filter. This filter is built upon **jmespath**, and you can use the same syntax. For examples, see `jmespath examples <http://jmespath.org/examples.html>`_.
|
||||
|
||||
Consider this data structure:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
{
|
||||
"domain_definition": {
|
||||
"domain": {
|
||||
"cluster": [
|
||||
{
|
||||
"name": "cluster1"
|
||||
},
|
||||
{
|
||||
"name": "cluster2"
|
||||
}
|
||||
],
|
||||
"server": [
|
||||
{
|
||||
"name": "server11",
|
||||
"cluster": "cluster1",
|
||||
"port": "8080"
|
||||
},
|
||||
{
|
||||
"name": "server12",
|
||||
"cluster": "cluster1",
|
||||
"port": "8090"
|
||||
},
|
||||
{
|
||||
"name": "server21",
|
||||
"cluster": "cluster2",
|
||||
"port": "9080"
|
||||
},
|
||||
{
|
||||
"name": "server22",
|
||||
"cluster": "cluster2",
|
||||
"port": "9090"
|
||||
}
|
||||
],
|
||||
"library": [
|
||||
{
|
||||
"name": "lib1",
|
||||
"target": "cluster1"
|
||||
},
|
||||
{
|
||||
"name": "lib2",
|
||||
"target": "cluster2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
To extract all clusters from this structure, you can use the following query:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all cluster names
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.cluster[*].name') }}"
|
||||
|
||||
To extract all server names:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all server names
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.server[*].name') }}"
|
||||
|
||||
To extract ports from cluster1:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
|
||||
vars:
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster1'].port"
|
||||
|
||||
.. note:: You can use a variable to make the query more readable.
|
||||
|
||||
To print out the ports from cluster1 in a comma separated string:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1 as a string
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ domain_definition | community.general.json_query('domain.server[?cluster==`cluster1`].port') | join(', ') }}"
|
||||
|
||||
.. note:: In the example above, quoting literals using backticks avoids escaping quotes and maintains readability.
|
||||
|
||||
You can use YAML `single quote escaping <https://yaml.org/spec/current.html#id2534365>`_:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.server[?cluster==''cluster1''].port') }}"
|
||||
|
||||
.. note:: Escaping single quotes within single quotes in YAML is done by doubling the single quote.
|
||||
|
||||
To get a hash map with all ports and names of a cluster:
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all server ports and names from cluster1
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
|
||||
vars:
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster2'].{name: name, port: port}"
|
||||
|
||||
To extract ports from all clusters with name starting with 'server1':
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
|
||||
vars:
|
||||
server_name_query: "domain.server[?starts_with(name,'server1')].port"
|
||||
|
||||
To extract ports from all clusters with name containing 'server1':
|
||||
|
||||
.. code-block:: yaml+jinja
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ domain_definition | to_json | from_json | community.general.json_query(server_name_query) }}"
|
||||
vars:
|
||||
server_name_query: "domain.server[?contains(name,'server1')].port"
|
||||
|
||||
.. note:: while using ``starts_with`` and ``contains``, you have to use `` to_json | from_json `` filter for correct parsing of data structure.
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace: community
|
||||
name: general
|
||||
version: 3.2.0
|
||||
version: 2.5.3
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
320
meta/runtime.yml
320
meta/runtime.yml
@@ -1,5 +1,31 @@
|
||||
---
|
||||
requires_ansible: '>=2.9.10'
|
||||
action_groups:
|
||||
ovirt:
|
||||
- ovirt_affinity_label_facts
|
||||
- ovirt_api_facts
|
||||
- ovirt_cluster_facts
|
||||
- ovirt_datacenter_facts
|
||||
- ovirt_disk_facts
|
||||
- ovirt_event_facts
|
||||
- ovirt_external_provider_facts
|
||||
- ovirt_group_facts
|
||||
- ovirt_host_facts
|
||||
- ovirt_host_storage_facts
|
||||
- ovirt_network_facts
|
||||
- ovirt_nic_facts
|
||||
- ovirt_permission_facts
|
||||
- ovirt_quota_facts
|
||||
- ovirt_scheduling_policy_facts
|
||||
- ovirt_snapshot_facts
|
||||
- ovirt_storage_domain_facts
|
||||
- ovirt_storage_template_facts
|
||||
- ovirt_storage_vm_facts
|
||||
- ovirt_tag_facts
|
||||
- ovirt_template_facts
|
||||
- ovirt_user_facts
|
||||
- ovirt_vm_facts
|
||||
- ovirt_vmpool_facts
|
||||
plugin_routing:
|
||||
connection:
|
||||
docker:
|
||||
@@ -11,24 +37,9 @@ plugin_routing:
|
||||
redirect: community.google.gcp_storage_file
|
||||
hashi_vault:
|
||||
redirect: community.hashi_vault.hashi_vault
|
||||
nios:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios lookup plugin has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_lookup instead.
|
||||
nios_next_ip:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_next_ip lookup plugin has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_next_ip instead.
|
||||
nios_next_network:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_next_network lookup plugin has been
|
||||
deprecated. Please use infoblox.nios_modules.nios_next_network instead.
|
||||
modules:
|
||||
ali_instance_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.ali_instance_info instead.
|
||||
docker_compose:
|
||||
@@ -130,13 +141,11 @@ plugin_routing:
|
||||
gcp_forwarding_rule:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use google.cloud.gcp_compute_forwarding_rule or google.cloud.gcp_compute_global_forwarding_rule
|
||||
instead.
|
||||
warning_text: Use google.cloud.gcp_compute_forwarding_rule or google.cloud.gcp_compute_global_forwarding_rule instead.
|
||||
gcp_healthcheck:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use google.cloud.gcp_compute_health_check, google.cloud.gcp_compute_http_health_check
|
||||
or google.cloud.gcp_compute_https_health_check instead.
|
||||
warning_text: Use google.cloud.gcp_compute_health_check, google.cloud.gcp_compute_http_health_check or google.cloud.gcp_compute_https_health_check instead.
|
||||
gcp_target_proxy:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
@@ -147,22 +156,37 @@ plugin_routing:
|
||||
warning_text: Use google.cloud.gcp_compute_url_map instead.
|
||||
gcpubsub:
|
||||
redirect: community.google.gcpubsub
|
||||
gcpubsub_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.google.gcpubsub_info instead.
|
||||
gcpubsub_info:
|
||||
redirect: community.google.gcpubsub_info
|
||||
gcpubsub_facts:
|
||||
redirect: community.google.gcpubsub_info
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.google.gcpubsub_info instead.
|
||||
gcspanner:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use google.cloud.gcp_spanner_database and/or google.cloud.gcp_spanner_instance
|
||||
instead.
|
||||
warning_text: Use google.cloud.gcp_spanner_database and/or google.cloud.gcp_spanner_instance instead.
|
||||
github_hooks:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use community.general.github_webhook and community.general.github_webhook_info
|
||||
instead.
|
||||
warning_text: Use community.general.github_webhook and community.general.github_webhook_info instead.
|
||||
gluster_heal_info:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: The gluster modules have migrated to the gluster.gluster collection. Use gluster.gluster.gluster_heal_info instead.
|
||||
gluster_peer:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: The gluster modules have migrated to the gluster.gluster collection. Use gluster.gluster.gluster_peer instead.
|
||||
gluster_volume:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: The gluster modules have migrated to the gluster.gluster collection. Use gluster.gluster.gluster_volume instead.
|
||||
helm:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: The helm module in community.general has been deprecated. Use community.kubernetes.helm instead.
|
||||
hetzner_failover_ip:
|
||||
redirect: community.hrobot.failover_ip
|
||||
hetzner_failover_ip_info:
|
||||
@@ -172,19 +196,15 @@ plugin_routing:
|
||||
hetzner_firewall_info:
|
||||
redirect: community.hrobot.firewall_info
|
||||
hpilo_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.hpilo_info instead.
|
||||
idrac_firmware:
|
||||
redirect: dellemc.openmanage.idrac_firmware
|
||||
idrac_redfish_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.idrac_redfish_info instead.
|
||||
idrac_server_config_profile:
|
||||
redirect: dellemc.openmanage.idrac_server_config_profile
|
||||
jenkins_job_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.jenkins_job_info instead.
|
||||
katello:
|
||||
@@ -204,25 +224,23 @@ plugin_routing:
|
||||
kubevirt_vm:
|
||||
redirect: community.kubevirt.kubevirt_vm
|
||||
ldap_attr:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.ldap_attrs instead.
|
||||
logicmonitor:
|
||||
tombstone:
|
||||
removal_version: 1.0.0
|
||||
warning_text: The logicmonitor_facts module is no longer maintained and the
|
||||
API used has been disabled in 2017.
|
||||
warning_text: The logicmonitor_facts module is no longer maintained and the API used has been disabled in 2017.
|
||||
logicmonitor_facts:
|
||||
tombstone:
|
||||
removal_version: 1.0.0
|
||||
warning_text: The logicmonitor_facts module is no longer maintained and the
|
||||
API used has been disabled in 2017.
|
||||
warning_text: The logicmonitor_facts module is no longer maintained and the API used has been disabled in 2017.
|
||||
memset_memstore_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.memset_memstore_info instead.
|
||||
memset_server_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.memset_server_info instead.
|
||||
na_cdot_aggregate:
|
||||
@@ -258,242 +276,159 @@ plugin_routing:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use netapp.ontap.na_ontap_volume instead.
|
||||
na_ontap_gather_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use netapp.ontap.na_ontap_info instead.
|
||||
nginx_status_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.nginx_status_info instead.
|
||||
nios_a_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_a_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_a_record instead.
|
||||
nios_aaaa_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_aaaa_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_aaaa_record instead.
|
||||
nios_cname_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_cname_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_cname_record instead.
|
||||
nios_dns_view:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_dns_view module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_dns_view instead.
|
||||
nios_fixed_address:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_fixed_address module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_fixed_address instead.
|
||||
nios_host_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_host_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_host_record instead.
|
||||
nios_member:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_member module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_member instead.
|
||||
nios_mx_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_mx_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_mx_record instead.
|
||||
nios_naptr_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_naptr_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_naptr_record instead.
|
||||
nios_network:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_network module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_network instead.
|
||||
nios_network_view:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_network_view module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_network_view instead.
|
||||
nios_nsgroup:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_nsgroup module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_nsgroup instead.
|
||||
nios_ptr_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_ptr_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_ptr_record instead.
|
||||
nios_srv_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_srv_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_srv_record instead.
|
||||
nios_txt_record:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_txt_record module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_txt_record instead.
|
||||
nios_zone:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios_zone module has been deprecated.
|
||||
Please use infoblox.nios_modules.nios_zone instead.
|
||||
ome_device_info:
|
||||
redirect: dellemc.openmanage.ome_device_info
|
||||
one_image_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.one_image_info instead.
|
||||
onepassword_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.onepassword_info instead.
|
||||
oneview_datacenter_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_datacenter_info instead.
|
||||
oneview_enclosure_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_enclosure_info instead.
|
||||
oneview_ethernet_network_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_ethernet_network_info instead.
|
||||
oneview_fc_network_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_fc_network_info instead.
|
||||
oneview_fcoe_network_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_fcoe_network_info instead.
|
||||
oneview_logical_interconnect_group_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_logical_interconnect_group_info
|
||||
instead.
|
||||
warning_text: Use community.general.oneview_logical_interconnect_group_info instead.
|
||||
oneview_network_set_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_network_set_info instead.
|
||||
oneview_san_manager_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.oneview_san_manager_info instead.
|
||||
online_server_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.online_server_info instead.
|
||||
online_user_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.online_user_info instead.
|
||||
ovirt:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_vm instead.
|
||||
ovirt_affinity_label_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_affinity_label_info instead.
|
||||
ovirt_api_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_api_info instead.
|
||||
ovirt_cluster_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_cluster_info instead.
|
||||
ovirt_datacenter_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_datacenter_info instead.
|
||||
ovirt_disk_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_disk_info instead.
|
||||
ovirt_event_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_event_info instead.
|
||||
ovirt_external_provider_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_external_provider_info instead.
|
||||
ovirt_group_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_group_info instead.
|
||||
ovirt_host_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_host_info instead.
|
||||
ovirt_host_storage_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_host_storage_info instead.
|
||||
ovirt_network_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_network_info instead.
|
||||
ovirt_nic_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_nic_info instead.
|
||||
ovirt_permission_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_permission_info instead.
|
||||
ovirt_quota_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_quota_info instead.
|
||||
ovirt_scheduling_policy_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_scheduling_policy_info instead.
|
||||
ovirt_snapshot_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_snapshot_info instead.
|
||||
ovirt_storage_domain_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_storage_domain_info instead.
|
||||
ovirt_storage_template_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_storage_template_info instead.
|
||||
ovirt_storage_vm_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_storage_vm_info instead.
|
||||
ovirt_tag_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_tag_info instead.
|
||||
ovirt_template_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_template_info instead.
|
||||
ovirt_user_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_user_info instead.
|
||||
ovirt_vm_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_vm_info instead.
|
||||
ovirt_vmpool_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use ovirt.ovirt.ovirt_vmpool_info instead.
|
||||
postgresql_copy:
|
||||
@@ -536,52 +471,52 @@ plugin_routing:
|
||||
redirect: community.postgresql.postgresql_table
|
||||
postgresql_tablespace:
|
||||
redirect: community.postgresql.postgresql_tablespace
|
||||
postgresql_user:
|
||||
redirect: community.postgresql.postgresql_user
|
||||
postgresql_user_obj_stat_info:
|
||||
redirect: community.postgresql.postgresql_user_obj_stat_info
|
||||
postgresql_user:
|
||||
redirect: community.postgresql.postgresql_user
|
||||
purefa_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use purestorage.flasharray.purefa_info instead.
|
||||
purefb_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use purestorage.flashblade.purefb_info instead.
|
||||
python_requirements_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.python_requirements_info instead.
|
||||
redfish_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.redfish_info instead.
|
||||
scaleway_image_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_image_info instead.
|
||||
scaleway_ip_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_ip_info instead.
|
||||
scaleway_organization_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_organization_info instead.
|
||||
scaleway_security_group_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_security_group_info instead.
|
||||
scaleway_server_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_server_info instead.
|
||||
scaleway_snapshot_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_snapshot_info instead.
|
||||
scaleway_volume_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_volume_info instead.
|
||||
sf_account_manager:
|
||||
@@ -605,15 +540,15 @@ plugin_routing:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use netapp.elementsw.na_elementsw_volume instead.
|
||||
smartos_image_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.smartos_image_info instead.
|
||||
vertica_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.vertica_info instead.
|
||||
xenserver_guest_facts:
|
||||
tombstone:
|
||||
deprecation:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.xenserver_guest_info instead.
|
||||
doc_fragments:
|
||||
@@ -627,11 +562,6 @@ plugin_routing:
|
||||
redirect: community.kubevirt.kubevirt_common_options
|
||||
kubevirt_vm_options:
|
||||
redirect: community.kubevirt.kubevirt_vm_options
|
||||
nios:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.nios document fragment has been deprecated.
|
||||
Please use infoblox.nios_modules.nios instead.
|
||||
postgresql:
|
||||
redirect: community.postgresql.postgresql
|
||||
module_utils:
|
||||
@@ -649,33 +579,21 @@ plugin_routing:
|
||||
redirect: community.hrobot.robot
|
||||
kubevirt:
|
||||
redirect: community.kubevirt.kubevirt
|
||||
net_tools.nios.api:
|
||||
deprecation:
|
||||
removal_version: 5.0.0
|
||||
warning_text: The community.general.net_tools.nios.api module_utils has been
|
||||
deprecated. Please use infoblox.nios_modules.api instead.
|
||||
postgresql:
|
||||
redirect: community.postgresql.postgresql
|
||||
remote_management.dellemc.dellemc_idrac:
|
||||
redirect: dellemc.openmanage.dellemc_idrac
|
||||
remote_management.dellemc.ome:
|
||||
redirect: dellemc.openmanage.ome
|
||||
callback:
|
||||
actionable:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use the 'default' callback plugin with 'display_skipped_hosts
|
||||
= no' and 'display_ok_hosts = no' options.
|
||||
warning_text: Use the 'default' callback plugin with 'display_skipped_hosts = no' and 'display_ok_hosts = no' options.
|
||||
full_skip:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use the 'default' callback plugin with 'display_skipped_hosts
|
||||
= no' option.
|
||||
warning_text: Use the 'default' callback plugin with 'display_skipped_hosts = no' option.
|
||||
stderr:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use the 'default' callback plugin with 'display_failed_stderr
|
||||
= yes' option.
|
||||
warning_text: Use the 'default' callback plugin with 'display_failed_stderr = yes' option.
|
||||
inventory:
|
||||
docker_machine:
|
||||
redirect: community.docker.docker_machine
|
||||
|
||||
0
plugins/callback/__init__.py
Normal file
0
plugins/callback/__init__.py
Normal file
0
plugins/connection/__init__.py
Normal file
0
plugins/connection/__init__.py
Normal file
@@ -62,7 +62,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local chroot based connections """
|
||||
''' Local chroot based connections '''
|
||||
|
||||
transport = 'community.general.chroot'
|
||||
has_pipelining = True
|
||||
@@ -95,7 +95,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("%s does not look like a chrootable dir (/bin/sh missing)" % self.chroot)
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the chroot """
|
||||
''' connect to the chroot '''
|
||||
if os.path.isabs(self.get_option('chroot_exe')):
|
||||
self.chroot_cmd = self.get_option('chroot_exe')
|
||||
else:
|
||||
@@ -110,17 +110,17 @@ class Connection(ConnectionBase):
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
""" run a command on the chroot. This is only needed for implementing
|
||||
''' run a command on the chroot. This is only needed for implementing
|
||||
put_file() get_file() so that we don't have to read the whole file
|
||||
into memory.
|
||||
|
||||
compared to exec_command() it looses some niceties like being able to
|
||||
return the process's exit code immediately.
|
||||
"""
|
||||
'''
|
||||
executable = self.get_option('executable')
|
||||
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
|
||||
|
||||
display.vvv("EXEC %s" % local_cmd, host=self.chroot)
|
||||
display.vvv("EXEC %s" % (local_cmd), host=self.chroot)
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@@ -128,17 +128,16 @@ class Connection(ConnectionBase):
|
||||
return p
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the chroot """
|
||||
''' run a command on the chroot '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
return p.returncode, stdout, stderr
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
@staticmethod
|
||||
def _prefix_login_path(remote_path):
|
||||
""" Make sure that we put files into a standard path
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
@@ -146,13 +145,13 @@ class Connection(ConnectionBase):
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
"""
|
||||
'''
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to chroot """
|
||||
''' transfer a file from local to chroot '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.chroot)
|
||||
|
||||
@@ -178,7 +177,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from chroot to local """
|
||||
''' fetch a file from chroot to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.chroot)
|
||||
|
||||
@@ -202,6 +201,6 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
||||
|
||||
@@ -44,7 +44,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Func-based connections """
|
||||
''' Func-based connections '''
|
||||
|
||||
has_pipelining = False
|
||||
|
||||
@@ -53,7 +53,6 @@ class Connection(ConnectionBase):
|
||||
self.host = host
|
||||
# port is unused, this go on func
|
||||
self.port = port
|
||||
self.client = None
|
||||
|
||||
def connect(self, port=None):
|
||||
if not HAVE_FUNC:
|
||||
@@ -63,32 +62,31 @@ class Connection(ConnectionBase):
|
||||
return self
|
||||
|
||||
def exec_command(self, cmd, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
|
||||
""" run a command on the remote minion """
|
||||
''' run a command on the remote minion '''
|
||||
|
||||
if in_data:
|
||||
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
|
||||
# totally ignores privlege escalation
|
||||
display.vvv("EXEC %s" % cmd, host=self.host)
|
||||
display.vvv("EXEC %s" % (cmd), host=self.host)
|
||||
p = self.client.command.run(cmd)[self.host]
|
||||
return p[0], p[1], p[2]
|
||||
return (p[0], p[1], p[2])
|
||||
|
||||
@staticmethod
|
||||
def _normalize_path(path, prefix):
|
||||
def _normalize_path(self, path, prefix):
|
||||
if not path.startswith(os.path.sep):
|
||||
path = os.path.join(os.path.sep, path)
|
||||
normpath = os.path.normpath(path)
|
||||
return os.path.join(prefix, normpath[1:])
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to remote """
|
||||
''' transfer a file from local to remote '''
|
||||
|
||||
out_path = self._normalize_path(out_path, '/')
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
|
||||
self.client.local.copyfile.send(in_path, out_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from remote to local """
|
||||
''' fetch a file from remote to local '''
|
||||
|
||||
in_path = self._normalize_path(in_path, '/')
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
|
||||
@@ -101,5 +99,5 @@ class Connection(ConnectionBase):
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
pass
|
||||
|
||||
@@ -40,7 +40,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(Jail):
|
||||
""" Local iocage based connections """
|
||||
''' Local iocage based connections '''
|
||||
|
||||
transport = 'community.general.iocage'
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import traceback
|
||||
import ansible.constants as C
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
@@ -46,7 +47,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local BSD Jail based connections """
|
||||
''' Local BSD Jail based connections '''
|
||||
|
||||
modified_jailname_key = 'conn_jail_name'
|
||||
|
||||
@@ -89,20 +90,20 @@ class Connection(ConnectionBase):
|
||||
return to_text(stdout, errors='surrogate_or_strict').split()
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the jail; nothing to do here """
|
||||
''' connect to the jail; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
display.vvv(u"ESTABLISH JAIL CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self.jail)
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
""" run a command on the jail. This is only needed for implementing
|
||||
''' run a command on the jail. This is only needed for implementing
|
||||
put_file() get_file() so that we don't have to read the whole file
|
||||
into memory.
|
||||
|
||||
compared to exec_command() it looses some niceties like being able to
|
||||
return the process's exit code immediately.
|
||||
"""
|
||||
'''
|
||||
|
||||
local_cmd = [self.jexec_cmd]
|
||||
set_env = ''
|
||||
@@ -122,17 +123,16 @@ class Connection(ConnectionBase):
|
||||
return p
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the jail """
|
||||
''' run a command on the jail '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
return p.returncode, stdout, stderr
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
@staticmethod
|
||||
def _prefix_login_path(remote_path):
|
||||
""" Make sure that we put files into a standard path
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
@@ -140,13 +140,13 @@ class Connection(ConnectionBase):
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
"""
|
||||
'''
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to jail """
|
||||
''' transfer a file from local to jail '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.jail)
|
||||
|
||||
@@ -172,7 +172,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from jail to local """
|
||||
''' fetch a file from jail to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.jail)
|
||||
|
||||
@@ -196,6 +196,6 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, to_native(stdout), to_native(stderr)))
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
||||
|
||||
@@ -42,13 +42,14 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible import errors
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local lxc based connections """
|
||||
''' Local lxc based connections '''
|
||||
|
||||
transport = 'community.general.lxc'
|
||||
has_pipelining = True
|
||||
@@ -61,7 +62,7 @@ class Connection(ConnectionBase):
|
||||
self.container = None
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the lxc; nothing to do here """
|
||||
''' connect to the lxc; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not HAS_LIBLXC:
|
||||
@@ -76,8 +77,7 @@ class Connection(ConnectionBase):
|
||||
if self.container.state == "STOPPED":
|
||||
raise errors.AnsibleError("%s is not running" % self.container_name)
|
||||
|
||||
@staticmethod
|
||||
def _communicate(pid, in_data, stdin, stdout, stderr):
|
||||
def _communicate(self, pid, in_data, stdin, stdout, stderr):
|
||||
buf = {stdout: [], stderr: []}
|
||||
read_fds = [stdout, stderr]
|
||||
if in_data:
|
||||
@@ -111,7 +111,7 @@ class Connection(ConnectionBase):
|
||||
return fd
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the chroot """
|
||||
''' run a command on the chroot '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
# python2-lxc needs bytes. python3-lxc needs text.
|
||||
|
||||
@@ -37,9 +37,15 @@ DOCUMENTATION = '''
|
||||
# - name: hosts
|
||||
'''
|
||||
|
||||
import shlex
|
||||
import shutil
|
||||
|
||||
import os
|
||||
import base64
|
||||
import subprocess
|
||||
|
||||
from ansible.module_utils._text import to_bytes
|
||||
import ansible.constants as C
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.plugins.connection import ConnectionBase, ensure_connect
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -16,11 +16,14 @@ DOCUMENTATION = '''
|
||||
- This allows you to use existing Saltstack infrastructure to connect to targets.
|
||||
'''
|
||||
|
||||
import re
|
||||
import os
|
||||
import base64
|
||||
import pty
|
||||
import codecs
|
||||
import subprocess
|
||||
|
||||
from ansible import errors
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.module_utils.six.moves import cPickle
|
||||
|
||||
HAVE_SALTSTACK = False
|
||||
try:
|
||||
@@ -29,9 +32,13 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import os
|
||||
from ansible import errors
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Salt-based connections """
|
||||
''' Salt-based connections '''
|
||||
|
||||
has_pipelining = False
|
||||
# while the name of the product is salt, naming that module salt cause
|
||||
@@ -51,30 +58,29 @@ class Connection(ConnectionBase):
|
||||
return self
|
||||
|
||||
def exec_command(self, cmd, sudoable=False, in_data=None):
|
||||
""" run a command on the remote minion """
|
||||
''' run a command on the remote minion '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
if in_data:
|
||||
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
|
||||
self._display.vvv("EXEC %s" % cmd, host=self.host)
|
||||
self._display.vvv("EXEC %s" % (cmd), host=self.host)
|
||||
# need to add 'true;' to work around https://github.com/saltstack/salt/issues/28077
|
||||
res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', 'true;' + cmd])
|
||||
if self.host not in res:
|
||||
raise errors.AnsibleError("Minion %s didn't answer, check if salt-minion is running and the name is correct" % self.host)
|
||||
|
||||
p = res[self.host]
|
||||
return p['retcode'], p['stdout'], p['stderr']
|
||||
return (p['retcode'], p['stdout'], p['stderr'])
|
||||
|
||||
@staticmethod
|
||||
def _normalize_path(path, prefix):
|
||||
def _normalize_path(self, path, prefix):
|
||||
if not path.startswith(os.path.sep):
|
||||
path = os.path.join(os.path.sep, path)
|
||||
normpath = os.path.normpath(path)
|
||||
return os.path.join(prefix, normpath[1:])
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to remote """
|
||||
''' transfer a file from local to remote '''
|
||||
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
@@ -82,11 +88,11 @@ class Connection(ConnectionBase):
|
||||
self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
|
||||
with open(in_path, 'rb') as in_fh:
|
||||
content = in_fh.read()
|
||||
self.client.cmd(self.host, 'hashutil.base64_decodefile', [base64.b64encode(content), out_path])
|
||||
self.client.cmd(self.host, 'hashutil.base64_decodefile', [codecs.encode(content, 'base64'), out_path])
|
||||
|
||||
# TODO test it
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from remote to local """
|
||||
''' fetch a file from remote to local '''
|
||||
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
@@ -96,5 +102,5 @@ class Connection(ConnectionBase):
|
||||
open(out_path, 'wb').write(content)
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
pass
|
||||
|
||||
@@ -31,6 +31,7 @@ import os.path
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils._text import to_bytes
|
||||
@@ -41,7 +42,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" Local zone based connections """
|
||||
''' Local zone based connections '''
|
||||
|
||||
transport = 'community.general.zone'
|
||||
has_pipelining = True
|
||||
@@ -74,9 +75,9 @@ class Connection(ConnectionBase):
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
zones = []
|
||||
for line in process.stdout.readlines():
|
||||
for l in process.stdout.readlines():
|
||||
# 1:work:running:/zones/work:3126dc59-9a07-4829-cde9-a816e4c5040e:native:shared
|
||||
s = line.split(':')
|
||||
s = l.split(':')
|
||||
if s[1] != 'global':
|
||||
zones.append(s[1])
|
||||
|
||||
@@ -94,20 +95,20 @@ class Connection(ConnectionBase):
|
||||
return path + '/root'
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the zone; nothing to do here """
|
||||
''' connect to the zone; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
display.vvv("THIS IS A LOCAL ZONE DIR", host=self.zone)
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
""" run a command on the zone. This is only needed for implementing
|
||||
''' run a command on the zone. This is only needed for implementing
|
||||
put_file() get_file() so that we don't have to read the whole file
|
||||
into memory.
|
||||
|
||||
compared to exec_command() it looses some niceties like being able to
|
||||
return the process's exit code immediately.
|
||||
"""
|
||||
'''
|
||||
# NOTE: zlogin invokes a shell (just like ssh does) so we do not pass
|
||||
# this through /bin/sh -c here. Instead it goes through the shell
|
||||
# that zlogin selects.
|
||||
@@ -121,16 +122,16 @@ class Connection(ConnectionBase):
|
||||
return p
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
||||
""" run a command on the zone """
|
||||
''' run a command on the zone '''
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
p = self._buffered_exec_command(cmd)
|
||||
|
||||
stdout, stderr = p.communicate(in_data)
|
||||
return p.returncode, stdout, stderr
|
||||
return (p.returncode, stdout, stderr)
|
||||
|
||||
def _prefix_login_path(self, remote_path):
|
||||
""" Make sure that we put files into a standard path
|
||||
''' Make sure that we put files into a standard path
|
||||
|
||||
If a path is relative, then we need to choose where to put it.
|
||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
||||
@@ -138,13 +139,13 @@ class Connection(ConnectionBase):
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
"""
|
||||
'''
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
return os.path.normpath(remote_path)
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to zone """
|
||||
''' transfer a file from local to zone '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.zone)
|
||||
|
||||
@@ -170,7 +171,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from zone to local """
|
||||
''' fetch a file from zone to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.zone)
|
||||
|
||||
@@ -194,6 +195,6 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
||||
|
||||
0
plugins/doc_fragments/__init__.py
Normal file
0
plugins/doc_fragments/__init__.py
Normal file
@@ -30,6 +30,7 @@ options:
|
||||
description:
|
||||
- Keycloak realm name to authenticate to for API access.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
auth_client_secret:
|
||||
description:
|
||||
@@ -40,6 +41,7 @@ options:
|
||||
description:
|
||||
- Username to authenticate for API access with.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- username
|
||||
|
||||
@@ -47,15 +49,10 @@ options:
|
||||
description:
|
||||
- Password to authenticate for API access with.
|
||||
type: str
|
||||
required: true
|
||||
aliases:
|
||||
- password
|
||||
|
||||
token:
|
||||
description:
|
||||
- Authentication token for Keycloak API.
|
||||
type: str
|
||||
version_added: 3.0.0
|
||||
|
||||
validate_certs:
|
||||
description:
|
||||
- Verify TLS certificates (do not disable this in production).
|
||||
|
||||
59
plugins/doc_fragments/ovirt_facts.py
Normal file
59
plugins/doc_fragments/ovirt_facts.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016, Red Hat, Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# info standard oVirt documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
fetch_nested:
|
||||
description:
|
||||
- If I(yes) the module will fetch additional data from the API.
|
||||
- It will fetch only IDs of nested entity. It doesn't fetch multiple levels of nested attributes.
|
||||
Only the attributes of the current entity. User can configure to fetch other
|
||||
attributes of the nested entities by specifying C(nested_attributes).
|
||||
type: bool
|
||||
default: false
|
||||
nested_attributes:
|
||||
description:
|
||||
- Specifies list of the attributes which should be fetched from the API.
|
||||
- This parameter apply only when C(fetch_nested) is I(true).
|
||||
type: list
|
||||
auth:
|
||||
description:
|
||||
- "Dictionary with values needed to create HTTP/HTTPS connection to oVirt:"
|
||||
- C(username)[I(required)] - The name of the user, something like I(admin@internal).
|
||||
Default value is set by I(OVIRT_USERNAME) environment variable.
|
||||
- "C(password)[I(required)] - The password of the user. Default value is set by I(OVIRT_PASSWORD) environment variable."
|
||||
- "C(url)- A string containing the API URL of the server, usually
|
||||
something like `I(https://server.example.com/ovirt-engine/api)`. Default value is set by I(OVIRT_URL) environment variable.
|
||||
Either C(url) or C(hostname) is required."
|
||||
- "C(hostname) - A string containing the hostname of the server, usually
|
||||
something like `I(server.example.com)`. Default value is set by I(OVIRT_HOSTNAME) environment variable.
|
||||
Either C(url) or C(hostname) is required."
|
||||
- "C(token) - Token to be used instead of login with username/password. Default value is set by I(OVIRT_TOKEN) environment variable."
|
||||
- "C(insecure) - A boolean flag that indicates if the server TLS
|
||||
certificate and host name should be checked."
|
||||
- "C(ca_file) - A PEM file containing the trusted CA certificates. The
|
||||
certificate presented by the server will be verified using these CA
|
||||
certificates. If `C(ca_file)` parameter is not set, system wide
|
||||
CA certificate store is used. Default value is set by I(OVIRT_CAFILE) environment variable."
|
||||
- "C(kerberos) - A boolean flag indicating if Kerberos authentication
|
||||
should be used instead of the default basic authentication."
|
||||
- "C(headers) - Dictionary of HTTP headers to be added to each API call."
|
||||
type: dict
|
||||
required: true
|
||||
requirements:
|
||||
- python >= 2.7
|
||||
- ovirt-engine-sdk-python >= 4.3.0
|
||||
notes:
|
||||
- "In order to use this module you have to install oVirt Python SDK.
|
||||
To ensure it's installed with correct version you can create the following task:
|
||||
ansible.builtin.pip: name=ovirt-engine-sdk-python version=4.3.0"
|
||||
'''
|
||||
0
plugins/filter/__init__.py
Normal file
0
plugins/filter/__init__.py
Normal file
@@ -1,42 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.common._collections_compat import Mapping, Sequence
|
||||
|
||||
|
||||
def groupby_as_dict(sequence, attribute):
|
||||
'''
|
||||
Given a sequence of dictionaries and an attribute name, returns a dictionary mapping
|
||||
the value of this attribute to the dictionary.
|
||||
|
||||
If multiple dictionaries in the sequence have the same value for this attribute,
|
||||
the filter will fail.
|
||||
'''
|
||||
if not isinstance(sequence, Sequence):
|
||||
raise AnsibleFilterError('Input is not a sequence')
|
||||
|
||||
result = dict()
|
||||
for list_index, element in enumerate(sequence):
|
||||
if not isinstance(element, Mapping):
|
||||
raise AnsibleFilterError('Sequence element #{0} is not a mapping'.format(list_index))
|
||||
if attribute not in element:
|
||||
raise AnsibleFilterError('Attribute not contained in element #{0} of sequence'.format(list_index))
|
||||
result_index = element[attribute]
|
||||
if result_index in result:
|
||||
raise AnsibleFilterError('Multiple sequence entries have attribute value {0!r}'.format(result_index))
|
||||
result[result_index] = element
|
||||
return result
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
''' Ansible list filters '''
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'groupby_as_dict': groupby_as_dict,
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2021, Andrew Pantuso (@ajpantuso) <ajpantuso@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import (
|
||||
AnsibleError,
|
||||
AnsibleFilterError,
|
||||
AnsibleFilterTypeError,
|
||||
)
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.common.collections import is_sequence
|
||||
|
||||
try:
|
||||
from hashids import Hashids
|
||||
HAS_HASHIDS = True
|
||||
except ImportError:
|
||||
HAS_HASHIDS = False
|
||||
|
||||
|
||||
def initialize_hashids(**kwargs):
|
||||
if not HAS_HASHIDS:
|
||||
raise AnsibleError("The hashids library must be installed in order to use this plugin")
|
||||
|
||||
params = dict((k, v) for k, v in kwargs.items() if v)
|
||||
|
||||
try:
|
||||
return Hashids(**params)
|
||||
except TypeError as e:
|
||||
raise AnsibleFilterError(
|
||||
"The provided parameters %s are invalid: %s" % (
|
||||
', '.join(["%s=%s" % (k, v) for k, v in params.items()]),
|
||||
to_native(e)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def hashids_encode(nums, salt=None, alphabet=None, min_length=None):
|
||||
"""Generates a YouTube-like hash from a sequence of ints
|
||||
|
||||
:nums: Sequence of one or more ints to hash
|
||||
:salt: String to use as salt when hashing
|
||||
:alphabet: String of 16 or more unique characters to produce a hash
|
||||
:min_length: Minimum length of hash produced
|
||||
"""
|
||||
|
||||
hashids = initialize_hashids(
|
||||
salt=salt,
|
||||
alphabet=alphabet,
|
||||
min_length=min_length
|
||||
)
|
||||
|
||||
# Handles the case where a single int is not encapsulated in a list or tuple.
|
||||
# User convenience seems preferable to strict typing in this case
|
||||
# Also avoids obfuscated error messages related to single invalid inputs
|
||||
if not is_sequence(nums):
|
||||
nums = [nums]
|
||||
|
||||
try:
|
||||
hashid = hashids.encode(*nums)
|
||||
except TypeError as e:
|
||||
raise AnsibleFilterTypeError(
|
||||
"Data to encode must by a tuple or list of ints: %s" % to_native(e)
|
||||
)
|
||||
|
||||
return hashid
|
||||
|
||||
|
||||
def hashids_decode(hashid, salt=None, alphabet=None, min_length=None):
|
||||
"""Decodes a YouTube-like hash to a sequence of ints
|
||||
|
||||
:hashid: Hash string to decode
|
||||
:salt: String to use as salt when hashing
|
||||
:alphabet: String of 16 or more unique characters to produce a hash
|
||||
:min_length: Minimum length of hash produced
|
||||
"""
|
||||
|
||||
hashids = initialize_hashids(
|
||||
salt=salt,
|
||||
alphabet=alphabet,
|
||||
min_length=min_length
|
||||
)
|
||||
nums = hashids.decode(hashid)
|
||||
return list(nums)
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
|
||||
def filters(self):
|
||||
return {
|
||||
'hashids_encode': hashids_encode,
|
||||
'hashids_decode': hashids_decode,
|
||||
}
|
||||
0
plugins/inventory/__init__.py
Normal file
0
plugins/inventory/__init__.py
Normal file
@@ -1,950 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Frank Dornheim <dornheim@posteo.de>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: lxd
|
||||
short_description: Returns Ansible inventory from lxd host
|
||||
description:
|
||||
- Get inventory from the lxd.
|
||||
- Uses a YAML configuration file that ends with 'lxd.(yml|yaml)'.
|
||||
version_added: "3.0.0"
|
||||
author: "Frank Dornheim (@conloos)"
|
||||
options:
|
||||
plugin:
|
||||
description: Token that ensures this is a source file for the 'lxd' plugin.
|
||||
required: true
|
||||
choices: [ 'community.general.lxd' ]
|
||||
url:
|
||||
description:
|
||||
- The unix domain socket path or the https URL for the lxd server.
|
||||
- Sockets in filesystem have to start with C(unix:).
|
||||
- Mostly C(unix:/var/lib/lxd/unix.socket) or C(unix:/var/snap/lxd/common/lxd/unix.socket).
|
||||
default: unix:/var/snap/lxd/common/lxd/unix.socket
|
||||
type: str
|
||||
client_key:
|
||||
description:
|
||||
- The client certificate key file path.
|
||||
aliases: [ key_file ]
|
||||
default: $HOME/.config/lxc/client.key
|
||||
type: path
|
||||
client_cert:
|
||||
description:
|
||||
- The client certificate file path.
|
||||
aliases: [ cert_file ]
|
||||
default: $HOME/.config/lxc/client.crt
|
||||
type: path
|
||||
trust_password:
|
||||
description:
|
||||
- The client trusted password.
|
||||
- You need to set this password on the lxd server before
|
||||
running this module using the following command
|
||||
C(lxc config set core.trust_password <some random password>)
|
||||
See U(https://www.stgraber.org/2016/04/18/lxd-api-direct-interaction/).
|
||||
- If I(trust_password) is set, this module send a request for authentication before sending any requests.
|
||||
type: str
|
||||
state:
|
||||
description: Filter the container according to the current status.
|
||||
type: str
|
||||
default: none
|
||||
choices: [ 'STOPPED', 'STARTING', 'RUNNING', 'none' ]
|
||||
prefered_container_network_interface:
|
||||
description:
|
||||
- If a container has multiple network interfaces, select which one is the prefered as pattern.
|
||||
- Combined with the first number that can be found e.g. 'eth' + 0.
|
||||
type: str
|
||||
default: eth
|
||||
prefered_container_network_family:
|
||||
description:
|
||||
- If a container has multiple network interfaces, which one is the prefered by family.
|
||||
- Specify C(inet) for IPv4 and C(inet6) for IPv6.
|
||||
type: str
|
||||
default: inet
|
||||
choices: [ 'inet', 'inet6' ]
|
||||
groupby:
|
||||
description:
|
||||
- Create groups by the following keywords C(location), C(pattern), C(network_range), C(os), C(release), C(profile), C(vlanid).
|
||||
- See example for syntax.
|
||||
type: dict
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# simple lxd.yml
|
||||
plugin: community.general.lxd
|
||||
url: unix:/var/snap/lxd/common/lxd/unix.socket
|
||||
|
||||
# simple lxd.yml including filter
|
||||
plugin: community.general.lxd
|
||||
url: unix:/var/snap/lxd/common/lxd/unix.socket
|
||||
state: RUNNING
|
||||
|
||||
# grouping lxd.yml
|
||||
groupby:
|
||||
testpattern:
|
||||
type: pattern
|
||||
attribute: test
|
||||
vlan666:
|
||||
type: vlanid
|
||||
attribute: 666
|
||||
locationBerlin:
|
||||
type: location
|
||||
attribute: Berlin
|
||||
osUbuntu:
|
||||
type: os
|
||||
attribute: ubuntu
|
||||
releaseFocal:
|
||||
type: release
|
||||
attribute: focal
|
||||
releaseBionic:
|
||||
type: release
|
||||
attribute: bionic
|
||||
profileDefault:
|
||||
type: profile
|
||||
attribute: default
|
||||
profileX11:
|
||||
type: profile
|
||||
attribute: x11
|
||||
netRangeIPv4:
|
||||
type: network_range
|
||||
attribute: 10.98.143.0/24
|
||||
netRangeIPv6:
|
||||
type: network_range
|
||||
attribute: fd42:bd00:7b11:2167:216:3eff::/24
|
||||
'''
|
||||
|
||||
import binascii
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import os
|
||||
import socket
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible.errors import AnsibleError, AnsibleParserError
|
||||
from ansible_collections.community.general.plugins.module_utils.compat import ipaddress
|
||||
from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException
|
||||
|
||||
|
||||
class InventoryModule(BaseInventoryPlugin):
|
||||
DEBUG = 4
|
||||
NAME = 'community.general.lxd'
|
||||
SNAP_SOCKET_URL = 'unix:/var/snap/lxd/common/lxd/unix.socket'
|
||||
SOCKET_URL = 'unix:/var/lib/lxd/unix.socket'
|
||||
|
||||
@staticmethod
|
||||
def load_json_data(path):
|
||||
"""Load json data
|
||||
|
||||
Load json data from file
|
||||
|
||||
Args:
|
||||
list(path): Path elements
|
||||
str(file_name): Filename of data
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(json_data): json data"""
|
||||
try:
|
||||
with open(path, 'r') as json_file:
|
||||
return json.load(json_file)
|
||||
except (IOError, json.decoder.JSONDecodeError) as err:
|
||||
raise AnsibleParserError('Could not load the test data from {0}: {1}'.format(to_native(path), to_native(err)))
|
||||
|
||||
def save_json_data(self, path, file_name=None):
|
||||
"""save data as json
|
||||
|
||||
Save data as json file
|
||||
|
||||
Args:
|
||||
list(path): Path elements
|
||||
str(file_name): Filename of data
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
if file_name:
|
||||
path.append(file_name)
|
||||
else:
|
||||
prefix = 'lxd_data-'
|
||||
time_stamp = time.strftime('%Y%m%d-%H%M%S')
|
||||
suffix = '.atd'
|
||||
path.append(prefix + time_stamp + suffix)
|
||||
|
||||
try:
|
||||
cwd = os.path.abspath(os.path.dirname(__file__))
|
||||
with open(os.path.abspath(os.path.join(cwd, *path)), 'w') as json_file:
|
||||
json.dump(self.data, json_file)
|
||||
except IOError as err:
|
||||
raise AnsibleParserError('Could not save data: {0}'.format(to_native(err)))
|
||||
|
||||
def verify_file(self, path):
|
||||
"""Check the config
|
||||
|
||||
Return true/false if the config-file is valid for this plugin
|
||||
|
||||
Args:
|
||||
str(path): path to the config
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
bool(valid): is valid"""
|
||||
valid = False
|
||||
if super(InventoryModule, self).verify_file(path):
|
||||
if path.endswith(('lxd.yaml', 'lxd.yml')):
|
||||
valid = True
|
||||
else:
|
||||
self.display.vvv('Inventory source not ending in "lxd.yaml" or "lxd.yml"')
|
||||
return valid
|
||||
|
||||
@staticmethod
|
||||
def validate_url(url):
|
||||
"""validate url
|
||||
|
||||
check whether the url is correctly formatted
|
||||
|
||||
Args:
|
||||
url
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
AnsibleError
|
||||
Returns:
|
||||
bool"""
|
||||
if not isinstance(url, str):
|
||||
return False
|
||||
if not url.startswith(('unix:', 'https:')):
|
||||
raise AnsibleError('URL is malformed: {0}'.format(to_native(url)))
|
||||
return True
|
||||
|
||||
def _connect_to_socket(self):
|
||||
"""connect to lxd socket
|
||||
|
||||
Connect to lxd socket by provided url or defaults
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
AnsibleError
|
||||
Returns:
|
||||
None"""
|
||||
error_storage = {}
|
||||
url_list = [self.get_option('url'), self.SNAP_SOCKET_URL, self.SOCKET_URL]
|
||||
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)
|
||||
return socket_connection
|
||||
except LXDClientException as err:
|
||||
error_storage[url] = err
|
||||
raise AnsibleError('No connection to the socket: {0}'.format(to_native(error_storage)))
|
||||
|
||||
def _get_networks(self):
|
||||
"""Get Networknames
|
||||
|
||||
Returns all network config names
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
list(names): names of all network_configs"""
|
||||
# e.g. {'type': 'sync',
|
||||
# 'status': 'Success',
|
||||
# 'status_code': 200,
|
||||
# 'operation': '',
|
||||
# 'error_code': 0,
|
||||
# 'error': '',
|
||||
# 'metadata': ['/1.0/networks/lxdbr0']}
|
||||
network_configs = self.socket.do('GET', '/1.0/networks')
|
||||
return [m.split('/')[3] for m in network_configs['metadata']]
|
||||
|
||||
def _get_containers(self):
|
||||
"""Get Containernames
|
||||
|
||||
Returns all containernames
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
list(names): names of all containers"""
|
||||
# e.g. {'type': 'sync',
|
||||
# 'status': 'Success',
|
||||
# 'status_code': 200,
|
||||
# 'operation': '',
|
||||
# 'error_code': 0,
|
||||
# 'error': '',
|
||||
# 'metadata': ['/1.0/containers/udemy-ansible-ubuntu-2004']}
|
||||
containers = self.socket.do('GET', '/1.0/containers')
|
||||
return [m.split('/')[3] for m in containers['metadata']]
|
||||
|
||||
def _get_config(self, branch, name):
|
||||
"""Get inventory of container
|
||||
|
||||
Get config of container
|
||||
|
||||
Args:
|
||||
str(branch): Name oft the API-Branch
|
||||
str(name): Name of Container
|
||||
Kwargs:
|
||||
None
|
||||
Source:
|
||||
https://github.com/lxc/lxd/blob/master/doc/rest-api.md
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(config): Config of the container"""
|
||||
config = {}
|
||||
if isinstance(branch, (tuple, list)):
|
||||
config[name] = {branch[1]: self.socket.do('GET', '/1.0/{0}/{1}/{2}'.format(to_native(branch[0]), to_native(name), to_native(branch[1])))}
|
||||
else:
|
||||
config[name] = {branch: self.socket.do('GET', '/1.0/{0}/{1}'.format(to_native(branch), to_native(name)))}
|
||||
return config
|
||||
|
||||
def get_container_data(self, names):
|
||||
"""Create Inventory of the container
|
||||
|
||||
Iterate through the different branches of the containers and collect Informations.
|
||||
|
||||
Args:
|
||||
list(names): List of container names
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# tuple(('instances','metadata/templates')) to get section in branch
|
||||
# e.g. /1.0/instances/<name>/metadata/templates
|
||||
branches = ['containers', ('instances', 'state')]
|
||||
container_config = {}
|
||||
for branch in branches:
|
||||
for name in names:
|
||||
container_config['containers'] = self._get_config(branch, name)
|
||||
self.data = dict_merge(container_config, self.data)
|
||||
|
||||
def get_network_data(self, names):
|
||||
"""Create Inventory of the container
|
||||
|
||||
Iterate through the different branches of the containers and collect Informations.
|
||||
|
||||
Args:
|
||||
list(names): List of container names
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# tuple(('instances','metadata/templates')) to get section in branch
|
||||
# e.g. /1.0/instances/<name>/metadata/templates
|
||||
branches = [('networks', 'state')]
|
||||
network_config = {}
|
||||
for branch in branches:
|
||||
for name in names:
|
||||
try:
|
||||
network_config['networks'] = self._get_config(branch, name)
|
||||
except LXDClientException:
|
||||
network_config['networks'] = {name: None}
|
||||
self.data = dict_merge(network_config, self.data)
|
||||
|
||||
def extract_network_information_from_container_config(self, container_name):
|
||||
"""Returns the network interface configuration
|
||||
|
||||
Returns the network ipv4 and ipv6 config of the container without local-link
|
||||
|
||||
Args:
|
||||
str(container_name): Name oft he container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(network_configuration): network config"""
|
||||
container_network_interfaces = self._get_data_entry('containers/{0}/state/metadata/network'.format(container_name))
|
||||
network_configuration = None
|
||||
if container_network_interfaces:
|
||||
network_configuration = {}
|
||||
gen_interface_names = [interface_name for interface_name in container_network_interfaces if interface_name != 'lo']
|
||||
for interface_name in gen_interface_names:
|
||||
gen_address = [address for address in container_network_interfaces[interface_name]['addresses'] if address.get('scope') != 'link']
|
||||
network_configuration[interface_name] = []
|
||||
for address in gen_address:
|
||||
address_set = {}
|
||||
address_set['family'] = address.get('family')
|
||||
address_set['address'] = address.get('address')
|
||||
address_set['netmask'] = address.get('netmask')
|
||||
address_set['combined'] = address.get('address') + '/' + address.get('netmask')
|
||||
network_configuration[interface_name].append(address_set)
|
||||
return network_configuration
|
||||
|
||||
def get_prefered_container_network_interface(self, container_name):
|
||||
"""Helper to get the prefered interface of thr container
|
||||
|
||||
Helper to get the prefered interface provide by neme pattern from 'prefered_container_network_interface'.
|
||||
|
||||
Args:
|
||||
str(containe_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
str(prefered_interface): None or interface name"""
|
||||
container_network_interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name))
|
||||
prefered_interface = None # init
|
||||
if container_network_interfaces: # container have network interfaces
|
||||
# generator if interfaces which start with the desired pattern
|
||||
net_generator = [interface for interface in container_network_interfaces if interface.startswith(self.prefered_container_network_interface)]
|
||||
selected_interfaces = [] # init
|
||||
for interface in net_generator:
|
||||
selected_interfaces.append(interface)
|
||||
if len(selected_interfaces) > 0:
|
||||
prefered_interface = sorted(selected_interfaces)[0]
|
||||
return prefered_interface
|
||||
|
||||
def get_container_vlans(self, container_name):
|
||||
"""Get VLAN(s) from container
|
||||
|
||||
Helper to get the VLAN_ID from the container
|
||||
|
||||
Args:
|
||||
str(containe_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# get network device configuration and store {network: vlan_id}
|
||||
network_vlans = {}
|
||||
for network in self._get_data_entry('networks'):
|
||||
if self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network)):
|
||||
network_vlans[network] = self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network))
|
||||
|
||||
# get networkdevices of container and return
|
||||
# e.g.
|
||||
# "eth0":{ "name":"eth0",
|
||||
# "network":"lxdbr0",
|
||||
# "type":"nic"},
|
||||
vlan_ids = {}
|
||||
devices = self._get_data_entry('containers/{0}/containers/metadata/expanded_devices'.format(to_native(container_name)))
|
||||
for device in devices:
|
||||
if 'network' in devices[device]:
|
||||
if devices[device]['network'] in network_vlans:
|
||||
vlan_ids[devices[device].get('network')] = network_vlans[devices[device].get('network')]
|
||||
return vlan_ids if vlan_ids else None
|
||||
|
||||
def _get_data_entry(self, path, data=None, delimiter='/'):
|
||||
"""Helper to get data
|
||||
|
||||
Helper to get data from self.data by a path like 'path/to/target'
|
||||
Attention: Escaping of the delimiter is not (yet) provided.
|
||||
|
||||
Args:
|
||||
str(path): path to nested dict
|
||||
Kwargs:
|
||||
dict(data): datastore
|
||||
str(delimiter): delimiter in Path.
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
*(value)"""
|
||||
try:
|
||||
if not data:
|
||||
data = self.data
|
||||
if delimiter in path:
|
||||
path = path.split(delimiter)
|
||||
|
||||
if isinstance(path, list) and len(path) > 1:
|
||||
data = data[path.pop(0)]
|
||||
path = delimiter.join(path)
|
||||
return self._get_data_entry(path, data, delimiter) # recursion
|
||||
return data[path]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def _set_data_entry(self, container_name, key, value, path=None):
|
||||
"""Helper to save data
|
||||
|
||||
Helper to save the data in self.data
|
||||
Detect if data is allready in branch and use dict_merge() to prevent that branch is overwritten.
|
||||
|
||||
Args:
|
||||
str(container_name): name of container
|
||||
str(key): same as dict
|
||||
*(value): same as dict
|
||||
Kwargs:
|
||||
str(path): path to branch-part
|
||||
Raises:
|
||||
AnsibleParserError
|
||||
Returns:
|
||||
None"""
|
||||
if not path:
|
||||
path = self.data['inventory']
|
||||
if container_name not in path:
|
||||
path[container_name] = {}
|
||||
|
||||
try:
|
||||
if isinstance(value, dict) and key in path[container_name]:
|
||||
path[container_name] = dict_merge(value, path[container_name][key])
|
||||
else:
|
||||
path[container_name][key] = value
|
||||
except KeyError as err:
|
||||
raise AnsibleParserError("Unable to store Informations: {0}".format(to_native(err)))
|
||||
|
||||
def extract_information_from_container_configs(self):
|
||||
"""Process configuration information
|
||||
|
||||
Preparation of the data
|
||||
|
||||
Args:
|
||||
dict(configs): Container configurations
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# create branch "inventory"
|
||||
if 'inventory' not in self.data:
|
||||
self.data['inventory'] = {}
|
||||
|
||||
for container_name in self.data['containers']:
|
||||
self._set_data_entry(container_name, 'os', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/image.os'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'release', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/image.release'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'version', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/image.version'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'profile', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/profiles'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'location', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/location'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'state', self._get_data_entry(
|
||||
'containers/{0}/containers/metadata/config/volatile.last_state.power'.format(container_name)))
|
||||
self._set_data_entry(container_name, 'network_interfaces', self.extract_network_information_from_container_config(container_name))
|
||||
self._set_data_entry(container_name, 'preferred_interface', self.get_prefered_container_network_interface(container_name))
|
||||
self._set_data_entry(container_name, 'vlan_ids', self.get_container_vlans(container_name))
|
||||
|
||||
def build_inventory_network(self, container_name):
|
||||
"""Add the network interfaces of the container to the inventory
|
||||
|
||||
Logic:
|
||||
- if the container have no interface -> 'ansible_connection: local'
|
||||
- get preferred_interface & prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
|
||||
- first Interface from: network_interfaces prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
|
||||
|
||||
Args:
|
||||
str(container_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
def interface_selection(container_name):
|
||||
"""Select container Interface for inventory
|
||||
|
||||
Logic:
|
||||
- get preferred_interface & prefered_container_network_family -> str(IP)
|
||||
- first Interface from: network_interfaces prefered_container_network_family -> str(IP)
|
||||
|
||||
Args:
|
||||
str(container_name): name of container
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(interface_name: ip)"""
|
||||
prefered_interface = self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)) # name or None
|
||||
prefered_container_network_family = self.prefered_container_network_family
|
||||
|
||||
ip_address = ''
|
||||
if prefered_interface:
|
||||
interface = self._get_data_entry('inventory/{0}/network_interfaces/{1}'.format(container_name, prefered_interface))
|
||||
for config in interface:
|
||||
if config['family'] == prefered_container_network_family:
|
||||
ip_address = config['address']
|
||||
break
|
||||
else:
|
||||
interface = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name))
|
||||
for config in interface:
|
||||
if config['family'] == prefered_container_network_family:
|
||||
ip_address = config['address']
|
||||
break
|
||||
return ip_address
|
||||
|
||||
if self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name)): # container have network interfaces
|
||||
if self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)): # container have a preferred interface
|
||||
self.inventory.set_variable(container_name, 'ansible_connection', 'ssh')
|
||||
self.inventory.set_variable(container_name, 'ansible_host', interface_selection(container_name))
|
||||
else:
|
||||
self.inventory.set_variable(container_name, 'ansible_connection', 'local')
|
||||
|
||||
def build_inventory_hosts(self):
|
||||
"""Build host-part dynamic inventory
|
||||
|
||||
Build the host-part of the dynamic inventory.
|
||||
Add Hosts and host_vars to the inventory.
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
for container_name in self.data['inventory']:
|
||||
# Only consider containers that match the "state" filter, if self.state is not None
|
||||
if self.filter:
|
||||
if self.filter.lower() != self._get_data_entry('inventory/{0}/state'.format(container_name)).lower():
|
||||
continue
|
||||
# add container
|
||||
self.inventory.add_host(container_name)
|
||||
# add network informations
|
||||
self.build_inventory_network(container_name)
|
||||
# add os
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_os', self._get_data_entry('inventory/{0}/os'.format(container_name)).lower())
|
||||
# add release
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_release', self._get_data_entry('inventory/{0}/release'.format(container_name)).lower())
|
||||
# add profile
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(container_name)))
|
||||
# add state
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_state', self._get_data_entry('inventory/{0}/state'.format(container_name)).lower())
|
||||
# add location information
|
||||
if self._get_data_entry('inventory/{0}/location'.format(container_name)) != "none": # wrong type by lxd 'none' != 'None'
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_location', self._get_data_entry('inventory/{0}/location'.format(container_name)))
|
||||
# add VLAN_ID information
|
||||
if self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name)):
|
||||
self.inventory.set_variable(container_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name)))
|
||||
|
||||
def build_inventory_groups_location(self, group_name):
|
||||
"""create group by attribute: location
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
for container_name in self.inventory.hosts:
|
||||
if 'ansible_lxd_location' in self.inventory.get_host(container_name).get_vars():
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_pattern(self, group_name):
|
||||
"""create group by name pattern
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
regex_pattern = self.groupby[group_name].get('attribute')
|
||||
|
||||
for container_name in self.inventory.hosts:
|
||||
result = re.search(regex_pattern, container_name)
|
||||
if result:
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_network_range(self, group_name):
|
||||
"""check if IP is in network-class
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
try:
|
||||
network = ipaddress.ip_network(to_text(self.groupby[group_name].get('attribute')))
|
||||
except ValueError as err:
|
||||
raise AnsibleParserError(
|
||||
'Error while parsing network range {0}: {1}'.format(self.groupby[group_name].get('attribute'), to_native(err)))
|
||||
|
||||
for container_name in self.inventory.hosts:
|
||||
if self.data['inventory'][container_name].get('network_interfaces') is not None:
|
||||
for interface in self.data['inventory'][container_name].get('network_interfaces'):
|
||||
for interface_family in self.data['inventory'][container_name].get('network_interfaces')[interface]:
|
||||
try:
|
||||
address = ipaddress.ip_address(to_text(interface_family['address']))
|
||||
if address.version == network.version and address in network:
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
except ValueError:
|
||||
# Ignore invalid IP addresses returned by lxd
|
||||
pass
|
||||
|
||||
def build_inventory_groups_os(self, group_name):
|
||||
"""create group by attribute: os
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
Noneself.data['inventory'][container_name][interface]
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts
|
||||
if 'ansible_lxd_os' in self.inventory.get_host(container_name).get_vars()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_os'):
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_release(self, group_name):
|
||||
"""create group by attribute: release
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts
|
||||
if 'ansible_lxd_release' in self.inventory.get_host(container_name).get_vars()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_release'):
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_profile(self, group_name):
|
||||
"""create group by attribute: profile
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts.keys()
|
||||
if 'ansible_lxd_profile' in self.inventory.get_host(container_name).get_vars().keys()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute').lower() in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_profile'):
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups_vlanid(self, group_name):
|
||||
"""create group by attribute: vlanid
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
# maybe we just want to expand one group
|
||||
if group_name not in self.inventory.groups:
|
||||
self.inventory.add_group(group_name)
|
||||
|
||||
gen_containers = [
|
||||
container_name for container_name in self.inventory.hosts.keys()
|
||||
if 'ansible_lxd_vlan_ids' in self.inventory.get_host(container_name).get_vars().keys()]
|
||||
for container_name in gen_containers:
|
||||
if self.groupby[group_name].get('attribute') in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_vlan_ids').values():
|
||||
self.inventory.add_child(group_name, container_name)
|
||||
|
||||
def build_inventory_groups(self):
|
||||
"""Build group-part dynamic inventory
|
||||
|
||||
Build the group-part of the dynamic inventory.
|
||||
Add groups to the inventory.
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
def group_type(group_name):
|
||||
"""create groups defined by lxd.yml or defaultvalues
|
||||
|
||||
create groups defined by lxd.yml or defaultvalues
|
||||
supportetd:
|
||||
* 'location'
|
||||
* 'pattern'
|
||||
* 'network_range'
|
||||
* 'os'
|
||||
* 'release'
|
||||
* 'profile'
|
||||
* 'vlanid'
|
||||
|
||||
Args:
|
||||
str(group_name): Group name
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
# Due to the compatibility with python 2 no use of map
|
||||
if self.groupby[group_name].get('type') == 'location':
|
||||
self.build_inventory_groups_location(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'pattern':
|
||||
self.build_inventory_groups_pattern(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'network_range':
|
||||
self.build_inventory_groups_network_range(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'os':
|
||||
self.build_inventory_groups_os(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'release':
|
||||
self.build_inventory_groups_release(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'profile':
|
||||
self.build_inventory_groups_profile(group_name)
|
||||
elif self.groupby[group_name].get('type') == 'vlanid':
|
||||
self.build_inventory_groups_vlanid(group_name)
|
||||
else:
|
||||
raise AnsibleParserError('Unknown group type: {0}'.format(to_native(group_name)))
|
||||
|
||||
if self.groupby:
|
||||
for group_name in self.groupby:
|
||||
if not group_name.isalnum():
|
||||
raise AnsibleParserError('Invalid character(s) in groupname: {0}'.format(to_native(group_name)))
|
||||
group_type(group_name)
|
||||
|
||||
def build_inventory(self):
|
||||
"""Build dynamic inventory
|
||||
|
||||
Build the dynamic inventory.
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
self.build_inventory_hosts()
|
||||
self.build_inventory_groups()
|
||||
|
||||
def _populate(self):
|
||||
"""Return the hosts and groups
|
||||
|
||||
Returns the processed container configurations from the lxd import
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
if len(self.data) == 0: # If no data is injected by unittests open socket
|
||||
self.socket = self._connect_to_socket()
|
||||
self.get_container_data(self._get_containers())
|
||||
self.get_network_data(self._get_networks())
|
||||
|
||||
self.extract_information_from_container_configs()
|
||||
|
||||
# self.display.vvv(self.save_json_data([os.path.abspath(__file__)]))
|
||||
|
||||
self.build_inventory()
|
||||
|
||||
def parse(self, inventory, loader, path, cache):
|
||||
"""Return dynamic inventory from source
|
||||
|
||||
Returns the processed inventory from the lxd import
|
||||
|
||||
Args:
|
||||
str(inventory): inventory object with existing data and
|
||||
the methods to add hosts/groups/variables
|
||||
to inventory
|
||||
str(loader): Ansible's DataLoader
|
||||
str(path): path to the config
|
||||
bool(cache): use or avoid caches
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
AnsibleParserError
|
||||
Returns:
|
||||
None"""
|
||||
|
||||
super(InventoryModule, self).parse(inventory, loader, path, cache=False)
|
||||
# Read the inventory YAML file
|
||||
self._read_config_data(path)
|
||||
try:
|
||||
self.client_key = self.get_option('client_key')
|
||||
self.client_cert = self.get_option('client_cert')
|
||||
self.debug = self.DEBUG
|
||||
self.data = {} # store for inventory-data
|
||||
self.groupby = self.get_option('groupby')
|
||||
self.plugin = self.get_option('plugin')
|
||||
self.prefered_container_network_family = self.get_option('prefered_container_network_family')
|
||||
self.prefered_container_network_interface = self.get_option('prefered_container_network_interface')
|
||||
if self.get_option('state').lower() == 'none': # none in config is str()
|
||||
self.filter = None
|
||||
else:
|
||||
self.filter = self.get_option('state').lower()
|
||||
self.trust_password = self.get_option('trust_password')
|
||||
self.url = self.get_option('url')
|
||||
except Exception as err:
|
||||
raise AnsibleParserError(
|
||||
'All correct options required: {0}'.format(to_native(err)))
|
||||
# Call our internal helper to populate the dynamic inventory
|
||||
self._populate()
|
||||
@@ -70,13 +70,6 @@ DOCUMENTATION = '''
|
||||
description: Gather LXC/QEMU configuration facts.
|
||||
default: no
|
||||
type: bool
|
||||
want_proxmox_nodes_ansible_host:
|
||||
version_added: 3.0.0
|
||||
description:
|
||||
- Whether to set C(ansbile_host) for proxmox nodes.
|
||||
- When set to C(true) (default), will use the first available interface. This can be different from what you expect.
|
||||
default: true
|
||||
type: bool
|
||||
strict:
|
||||
version_added: 2.5.0
|
||||
compose:
|
||||
@@ -241,22 +234,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
)
|
||||
)['result']
|
||||
|
||||
if "error" in ifaces:
|
||||
if "class" in ifaces["error"]:
|
||||
# This happens on Windows, even though qemu agent is running, the IP address
|
||||
# cannot be fetched, as it's unsupported, also a command disabled can happen.
|
||||
errorClass = ifaces["error"]["class"]
|
||||
if errorClass in ["Unsupported"]:
|
||||
self.display.v("Retrieving network interfaces from guest agents on windows with older qemu-guest-agents is not supported")
|
||||
elif errorClass in ["CommandDisabled"]:
|
||||
self.display.v("Retrieving network interfaces from guest agents has been disabled")
|
||||
return result
|
||||
|
||||
for iface in ifaces:
|
||||
result.append({
|
||||
'name': iface['name'],
|
||||
'mac-address': iface['hardware-address'] if 'hardware-address' in iface else '',
|
||||
'ip-addresses': ["%s/%s" % (ip['ip-address'], ip['prefix']) for ip in iface['ip-addresses']] if 'ip-addresses' in iface else []
|
||||
'mac-address': iface['hardware-address'],
|
||||
'ip-addresses': [
|
||||
"%s/%s" % (ip['ip-address'], ip['prefix']) for ip in iface['ip-addresses']
|
||||
]
|
||||
})
|
||||
except requests.HTTPError:
|
||||
pass
|
||||
@@ -370,9 +354,8 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
self.inventory.add_child(nodes_group, node['node'])
|
||||
|
||||
# get node IP address
|
||||
if self.get_option("want_proxmox_nodes_ansible_host"):
|
||||
ip = self._get_node_ip(node['node'])
|
||||
self.inventory.set_variable(node['node'], 'ansible_host', ip)
|
||||
ip = self._get_node_ip(node['node'])
|
||||
self.inventory.set_variable(node['node'], 'ansible_host', ip)
|
||||
|
||||
# get LXC containers for this node
|
||||
node_lxc_group = self.to_safe('%s%s' % (self.get_option('group_prefix'), ('%s_lxc' % node['node']).lower()))
|
||||
|
||||
0
plugins/lookup/__init__.py
Normal file
0
plugins/lookup/__init__.py
Normal file
@@ -1,208 +0,0 @@
|
||||
# (c) 2015-2021, Felix Fontein <felix@fontein.de>
|
||||
# (c) 2018 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
name: dependent
|
||||
short_description: Composes a list with nested elements of other lists or dicts which can depend on previous loop variables
|
||||
version_added: 3.1.0
|
||||
description:
|
||||
- "Takes the input lists and returns a list with elements that are lists, dictionaries,
|
||||
or template expressions which evaluate to lists or dicts, composed of the elements of
|
||||
the input evaluated lists and dictionaries."
|
||||
options:
|
||||
_raw:
|
||||
description:
|
||||
- A list where the elements are one-element dictionaries, mapping a name to a string, list, or dictionary.
|
||||
The name is the index that is used in the result object. The value is iterated over as described below.
|
||||
- If the value is a list, it is simply iterated over.
|
||||
- If the value is a dictionary, it is iterated over and returned as if they would be processed by the
|
||||
R(ansible.builtin.dict2items filter,ansible_collections.ansible.builtin.dict2items_filter).
|
||||
- If the value is a string, it is evaluated as Jinja2 expressions which can access the previously chosen
|
||||
elements with C(item.<index_name>). The result must be a list or a dictionary.
|
||||
type: list
|
||||
elements: dict
|
||||
required: true
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Install/remove public keys for active admin users
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ item.admin.key }}"
|
||||
key: "{{ lookup('file', item.key.public_key) }}"
|
||||
state: "{{ 'present' if item.key.active else 'absent' }}"
|
||||
when: item.admin.value.active
|
||||
with_community.general.dependent:
|
||||
- admin: admin_user_data
|
||||
- key: admin_ssh_keys[item.admin.key]
|
||||
loop_control:
|
||||
# Makes the output readable, so that it doesn't contain the whole subdictionaries and lists
|
||||
label: "{{ [item.admin.key, 'active' if item.key.active else 'inactive', item.key.public_key] }}"
|
||||
vars:
|
||||
admin_user_data:
|
||||
admin1:
|
||||
name: Alice
|
||||
active: true
|
||||
admin2:
|
||||
name: Bob
|
||||
active: true
|
||||
admin_ssh_keys:
|
||||
admin1:
|
||||
- private_key: keys/private_key_admin1.pem
|
||||
public_key: keys/private_key_admin1.pub
|
||||
active: true
|
||||
admin2:
|
||||
- private_key: keys/private_key_admin2.pem
|
||||
public_key: keys/private_key_admin2.pub
|
||||
active: true
|
||||
- private_key: keys/private_key_admin2-old.pem
|
||||
public_key: keys/private_key_admin2-old.pub
|
||||
active: false
|
||||
|
||||
- name: Update DNS records
|
||||
community.aws.route53:
|
||||
zone: "{{ item.zone.key }}"
|
||||
record: "{{ item.prefix.key ~ '.' if item.prefix.key else '' }}{{ item.zone.key }}"
|
||||
type: "{{ item.entry.key }}"
|
||||
ttl: "{{ item.entry.value.ttl | default(3600) }}"
|
||||
value: "{{ item.entry.value.value }}"
|
||||
state: "{{ 'absent' if (item.entry.value.absent | default(False)) else 'present' }}"
|
||||
overwrite: true
|
||||
loop_control:
|
||||
# Makes the output readable, so that it doesn't contain the whole subdictionaries and lists
|
||||
label: |-
|
||||
{{ [item.zone.key, item.prefix.key, item.entry.key,
|
||||
item.entry.value.ttl | default(3600),
|
||||
item.entry.value.absent | default(False), item.entry.value.value] }}
|
||||
with_community.general.dependent:
|
||||
- zone: dns_setup
|
||||
- prefix: item.zone.value
|
||||
- entry: item.prefix.value
|
||||
vars:
|
||||
dns_setup:
|
||||
example.com:
|
||||
'':
|
||||
A:
|
||||
value:
|
||||
- 1.2.3.4
|
||||
AAAA:
|
||||
value:
|
||||
- "2a01:1:2:3::1"
|
||||
'test._domainkey':
|
||||
TXT:
|
||||
ttl: 300
|
||||
value:
|
||||
- '"k=rsa; t=s; p=MIGfMA..."'
|
||||
example.org:
|
||||
'www':
|
||||
A:
|
||||
value:
|
||||
- 1.2.3.4
|
||||
- 5.6.7.8
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
_list:
|
||||
description:
|
||||
- A list composed of dictionaries whose keys are the variable names from the input list.
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
- key1: a
|
||||
key2: test
|
||||
- key1: a
|
||||
key2: foo
|
||||
- key1: b
|
||||
key2: bar
|
||||
"""
|
||||
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.common._collections_compat import Mapping, Sequence
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.template import Templar
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def __evaluate(self, expression, templar, variables):
|
||||
"""Evaluate expression with templar.
|
||||
|
||||
``expression`` is the expression to evaluate.
|
||||
``variables`` are the variables to use.
|
||||
"""
|
||||
templar.available_variables = variables or {}
|
||||
return templar.template("{0}{1}{2}".format("{{", expression, "}}"), cache=False)
|
||||
|
||||
def __process(self, result, terms, index, current, templar, variables):
|
||||
"""Fills ``result`` list with evaluated items.
|
||||
|
||||
``result`` is a list where the resulting items are placed.
|
||||
``terms`` is the parsed list of terms
|
||||
``index`` is the current index to be processed in the list.
|
||||
``current`` is a dictionary where the first ``index`` values are filled in.
|
||||
``variables`` are the variables currently available.
|
||||
"""
|
||||
# If we are done, add to result list:
|
||||
if index == len(terms):
|
||||
result.append(current.copy())
|
||||
return
|
||||
|
||||
key, expression, values = terms[index]
|
||||
|
||||
if expression is not None:
|
||||
# Evaluate expression in current context
|
||||
vars = variables.copy()
|
||||
vars['item'] = current.copy()
|
||||
try:
|
||||
values = self.__evaluate(expression, templar, variables=vars)
|
||||
except Exception as e:
|
||||
raise AnsibleLookupError(
|
||||
'Caught "{error}" while evaluating {key!r} with item == {item!r}'.format(
|
||||
error=e, key=key, item=current))
|
||||
|
||||
if isinstance(values, Mapping):
|
||||
for idx, val in sorted(values.items()):
|
||||
current[key] = dict([('key', idx), ('value', val)])
|
||||
self.__process(result, terms, index + 1, current, templar, variables)
|
||||
elif isinstance(values, Sequence):
|
||||
for elt in values:
|
||||
current[key] = elt
|
||||
self.__process(result, terms, index + 1, current, templar, variables)
|
||||
else:
|
||||
raise AnsibleLookupError(
|
||||
'Did not obtain dictionary or list while evaluating {key!r} with item == {item!r}, but {type}'.format(
|
||||
key=key, item=current, type=type(values)))
|
||||
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
"""Generate list."""
|
||||
result = []
|
||||
if len(terms) > 0:
|
||||
templar = Templar(loader=self._templar._loader)
|
||||
data = []
|
||||
vars_so_far = set()
|
||||
for index, term in enumerate(terms):
|
||||
if not isinstance(term, Mapping):
|
||||
raise AnsibleLookupError(
|
||||
'Parameter {index} must be a dictionary, got {type}'.format(
|
||||
index=index, type=type(term)))
|
||||
if len(term) != 1:
|
||||
raise AnsibleLookupError(
|
||||
'Parameter {index} must be a one-element dictionary, got {count} elements'.format(
|
||||
index=index, count=len(term)))
|
||||
k, v = list(term.items())[0]
|
||||
if k in vars_so_far:
|
||||
raise AnsibleLookupError(
|
||||
'The variable {key!r} appears more than once'.format(key=k))
|
||||
vars_so_far.add(k)
|
||||
if isinstance(v, string_types):
|
||||
data.append((k, v, None))
|
||||
elif isinstance(v, (Sequence, Mapping)):
|
||||
data.append((k, None, v))
|
||||
else:
|
||||
raise AnsibleLookupError(
|
||||
'Parameter {key!r} (index {index}) must have a value of type string, dictionary or list, got type {type}'.format(
|
||||
index=index, key=k, type=type(v)))
|
||||
self.__process(result, data, 0, {}, templar, variables)
|
||||
return result
|
||||
@@ -25,10 +25,6 @@ DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: nios
|
||||
short_description: Query Infoblox NIOS objects
|
||||
deprecated:
|
||||
why: Please install the infoblox.nios_modules collection and use the corresponding lookup from it.
|
||||
alternative: infoblox.nios_modules.nios_lookup
|
||||
removed_in: 5.0.0
|
||||
description:
|
||||
- Uses the Infoblox WAPI API to fetch NIOS specified objects. This lookup
|
||||
supports adding additional keywords to filter the return data and specify
|
||||
|
||||
@@ -25,10 +25,6 @@ DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: nios_next_ip
|
||||
short_description: Return the next available IP address for a network
|
||||
deprecated:
|
||||
why: Please install the infoblox.nios_modules collection and use the corresponding lookup from it.
|
||||
alternative: infoblox.nios_modules.nios_next_ip
|
||||
removed_in: 5.0.0
|
||||
description:
|
||||
- Uses the Infoblox WAPI API to return the next available IP addresses
|
||||
for a given network CIDR
|
||||
|
||||
@@ -25,10 +25,6 @@ DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: nios_next_network
|
||||
short_description: Return the next available network range for a network-container
|
||||
deprecated:
|
||||
why: Please install the infoblox.nios_modules collection and use the corresponding lookup from it.
|
||||
alternative: infoblox.nios_modules.nios_next_network
|
||||
removed_in: 5.0.0
|
||||
description:
|
||||
- Uses the Infoblox WAPI API to return the next available network addresses for
|
||||
a given network CIDR
|
||||
|
||||
@@ -30,11 +30,6 @@ DOCUMENTATION = '''
|
||||
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. Default is U(1password.com).
|
||||
version_added: 3.2.0
|
||||
default: '1password.com'
|
||||
type: str
|
||||
subdomain:
|
||||
description: The 1Password subdomain to authenticate against.
|
||||
username:
|
||||
@@ -114,7 +109,6 @@ class OnePass(object):
|
||||
self.logged_in = False
|
||||
self.token = None
|
||||
self.subdomain = None
|
||||
self.domain = None
|
||||
self.username = None
|
||||
self.secret_key = None
|
||||
self.master_password = None
|
||||
@@ -174,7 +168,7 @@ class OnePass(object):
|
||||
|
||||
args = [
|
||||
'signin',
|
||||
'{0}.{1}'.format(self.subdomain, self.domain),
|
||||
'{0}.1password.com'.format(self.subdomain),
|
||||
to_bytes(self.username),
|
||||
to_bytes(self.secret_key),
|
||||
'--output=raw',
|
||||
@@ -271,7 +265,6 @@ class LookupModule(LookupBase):
|
||||
section = kwargs.get('section')
|
||||
vault = kwargs.get('vault')
|
||||
op.subdomain = kwargs.get('subdomain')
|
||||
op.domain = kwargs.get('domain', '1password.com')
|
||||
op.username = kwargs.get('username')
|
||||
op.secret_key = kwargs.get('secret_key')
|
||||
op.master_password = kwargs.get('master_password', kwargs.get('vault_password'))
|
||||
|
||||
@@ -25,9 +25,9 @@ DOCUMENTATION = '''
|
||||
env:
|
||||
- name: PASSWORD_STORE_DIR
|
||||
create:
|
||||
description: Create the password if it does not already exist. Takes precedence over C(missing).
|
||||
description: Create the password if it does not already exist.
|
||||
type: bool
|
||||
default: false
|
||||
default: 'no'
|
||||
overwrite:
|
||||
description: Overwrite the password if it does already exist.
|
||||
type: bool
|
||||
@@ -60,22 +60,6 @@ DOCUMENTATION = '''
|
||||
description: use alphanumeric characters.
|
||||
type: bool
|
||||
default: 'no'
|
||||
missing:
|
||||
description:
|
||||
- List of preference about what to do if the password file is missing.
|
||||
- If I(create=true), the value for this option is ignored and assumed to be C(create).
|
||||
- If set to C(error), the lookup will error out if the passname does not exist.
|
||||
- If set to C(create), the passname will be created with the provided length I(length) if it does not exist.
|
||||
- If set to C(empty) or C(warn), will return a C(none) in case the passname does not exist.
|
||||
When using C(lookup) and not C(query), this will be translated to an empty string.
|
||||
version_added: 3.1.0
|
||||
type: str
|
||||
default: error
|
||||
choices:
|
||||
- error
|
||||
- warn
|
||||
- empty
|
||||
- create
|
||||
'''
|
||||
EXAMPLES = """
|
||||
# Debug is used for examples, BAD IDEA to show passwords on screen
|
||||
@@ -83,28 +67,12 @@ EXAMPLES = """
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.passwordstore', 'example/test')}}"
|
||||
|
||||
- name: Basic lookup. Warns if example/test does not exist and returns empty string
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.passwordstore', 'example/test missing=warn')}}"
|
||||
|
||||
- name: Create pass with random 16 character password. If password exists just give the password
|
||||
ansible.builtin.debug:
|
||||
var: mypassword
|
||||
vars:
|
||||
mypassword: "{{ lookup('community.general.passwordstore', 'example/test create=true')}}"
|
||||
|
||||
- name: Create pass with random 16 character password. If password exists just give the password
|
||||
ansible.builtin.debug:
|
||||
var: mypassword
|
||||
vars:
|
||||
mypassword: "{{ lookup('community.general.passwordstore', 'example/test missing=create')}}"
|
||||
|
||||
- name: Prints 'abc' if example/test does not exist, just give the password otherwise
|
||||
ansible.builtin.debug:
|
||||
var: mypassword
|
||||
vars:
|
||||
mypassword: "{{ lookup('community.general.passwordstore', 'example/test missing=empty') | default('abc', true) }}"
|
||||
|
||||
- name: Different size password
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('community.general.passwordstore', 'example/test create=true length=42')}}"
|
||||
@@ -143,13 +111,10 @@ import yaml
|
||||
from distutils import util
|
||||
from ansible.errors import AnsibleError, AnsibleAssertionError
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.encrypt import random_password
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible import constants as C
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
# backhacked check_output with input for python 2.7
|
||||
# http://stackoverflow.com/questions/10103551/passing-data-to-subprocess-check-output
|
||||
@@ -213,17 +178,12 @@ class LookupModule(LookupBase):
|
||||
self.paramvals[key] = util.strtobool(self.paramvals[key])
|
||||
except (ValueError, AssertionError) as e:
|
||||
raise AnsibleError(e)
|
||||
if self.paramvals['missing'] not in ['error', 'warn', 'create', 'empty']:
|
||||
raise AnsibleError("{0} is not a valid option for missing".format(self.paramvals['missing']))
|
||||
if not isinstance(self.paramvals['length'], int):
|
||||
if self.paramvals['length'].isdigit():
|
||||
self.paramvals['length'] = int(self.paramvals['length'])
|
||||
else:
|
||||
raise AnsibleError("{0} is not a correct value for length".format(self.paramvals['length']))
|
||||
|
||||
if self.paramvals['create']:
|
||||
self.paramvals['missing'] = 'create'
|
||||
|
||||
# Collect pass environment variables from the plugin's parameters.
|
||||
self.env = os.environ.copy()
|
||||
|
||||
@@ -264,11 +224,9 @@ class LookupModule(LookupBase):
|
||||
if e.returncode != 0 and 'not in the password store' in e.output:
|
||||
# if pass returns 1 and return string contains 'is not in the password store.'
|
||||
# We need to determine if this is valid or Error.
|
||||
if self.paramvals['missing'] == 'error':
|
||||
raise AnsibleError('passwordstore: passname {0} not found and missing=error is set'.format(self.passname))
|
||||
if not self.paramvals['create']:
|
||||
raise AnsibleError('passname: {0} not found, use create=True'.format(self.passname))
|
||||
else:
|
||||
if self.paramvals['missing'] == 'warn':
|
||||
display.warning('passwordstore: passname {0} not found'.format(self.passname))
|
||||
return False
|
||||
else:
|
||||
raise AnsibleError(e)
|
||||
@@ -336,7 +294,6 @@ class LookupModule(LookupBase):
|
||||
'userpass': '',
|
||||
'length': 16,
|
||||
'backup': False,
|
||||
'missing': 'error',
|
||||
}
|
||||
|
||||
for term in terms:
|
||||
@@ -347,9 +304,6 @@ class LookupModule(LookupBase):
|
||||
else:
|
||||
result.append(self.get_passresult())
|
||||
else: # password does not exist
|
||||
if self.paramvals['missing'] == 'create':
|
||||
if self.paramvals['create']:
|
||||
result.append(self.generate_password())
|
||||
else:
|
||||
result.append(None)
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Abhijeet Kasurde <akasurde@redhat.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: random_pet
|
||||
author:
|
||||
- Abhijeet Kasurde (@Akasurde)
|
||||
short_description: Generates random pet names
|
||||
version_added: '3.1.0'
|
||||
requirements:
|
||||
- petname U(https://github.com/dustinkirkland/python-petname)
|
||||
description:
|
||||
- Generates random pet names that can be used as unique identifiers for the resources.
|
||||
options:
|
||||
words:
|
||||
description:
|
||||
- The number of words in the pet name.
|
||||
default: 2
|
||||
type: int
|
||||
length:
|
||||
description:
|
||||
- The maximal length of every component of the pet name.
|
||||
- Values below 3 will be set to 3 by petname.
|
||||
default: 6
|
||||
type: int
|
||||
prefix:
|
||||
description: A string to prefix with the name.
|
||||
type: str
|
||||
separator:
|
||||
description: The character to separate words in the pet name.
|
||||
default: "-"
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate pet name
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_pet')
|
||||
# Example result: 'loving-raptor'
|
||||
|
||||
- name: Generate pet name with 3 words
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_pet', words=3)
|
||||
# Example result: 'fully-fresh-macaw'
|
||||
|
||||
- name: Generate pet name with separator
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_pet', separator="_")
|
||||
# Example result: 'causal_snipe'
|
||||
|
||||
- name: Generate pet name with length
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_pet', length=7)
|
||||
# Example result: 'natural-peacock'
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
_raw:
|
||||
description: A one-element list containing a random pet name
|
||||
type: list
|
||||
elements: str
|
||||
'''
|
||||
|
||||
try:
|
||||
import petname
|
||||
|
||||
HAS_PETNAME = True
|
||||
except ImportError:
|
||||
HAS_PETNAME = False
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
|
||||
if not HAS_PETNAME:
|
||||
raise AnsibleError('Python petname library is required. '
|
||||
'Please install using "pip install petname"')
|
||||
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
words = self.get_option('words')
|
||||
length = self.get_option('length')
|
||||
prefix = self.get_option('prefix')
|
||||
separator = self.get_option('separator')
|
||||
|
||||
values = petname.Generate(words=words, separator=separator, letters=length)
|
||||
if prefix:
|
||||
values = "%s%s%s" % (prefix, separator, values)
|
||||
|
||||
return [values]
|
||||
@@ -1,220 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2021, Abhijeet Kasurde <akasurde@redhat.com>
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: random_string
|
||||
author:
|
||||
- Abhijeet Kasurde (@Akasurde)
|
||||
short_description: Generates random string
|
||||
version_added: '3.2.0'
|
||||
description:
|
||||
- Generates random string based upon the given constraints.
|
||||
options:
|
||||
length:
|
||||
description: The length of the string.
|
||||
default: 8
|
||||
type: int
|
||||
upper:
|
||||
description:
|
||||
- Include uppercase letters in the string.
|
||||
default: true
|
||||
type: bool
|
||||
lower:
|
||||
description:
|
||||
- Include lowercase letters in the string.
|
||||
default: true
|
||||
type: bool
|
||||
numbers:
|
||||
description:
|
||||
- Include numbers in the string.
|
||||
default: true
|
||||
type: bool
|
||||
special:
|
||||
description:
|
||||
- Include special characters in the string.
|
||||
- Special characters are taken from Python standard library C(string).
|
||||
See L(the documentation of string.punctuation,https://docs.python.org/3/library/string.html#string.punctuation)
|
||||
for which characters will be used.
|
||||
- The choice of special characters can be changed to setting I(override_special).
|
||||
default: true
|
||||
type: bool
|
||||
min_numeric:
|
||||
description:
|
||||
- Minimum number of numeric characters in the string.
|
||||
- If set, overrides I(numbers=false).
|
||||
default: 0
|
||||
type: int
|
||||
min_upper:
|
||||
description:
|
||||
- Minimum number of uppercase alphabets in the string.
|
||||
- If set, overrides I(upper=false).
|
||||
default: 0
|
||||
type: int
|
||||
min_lower:
|
||||
description:
|
||||
- Minimum number of lowercase alphabets in the string.
|
||||
- If set, overrides I(lower=false).
|
||||
default: 0
|
||||
type: int
|
||||
min_special:
|
||||
description:
|
||||
- Minimum number of special character in the string.
|
||||
default: 0
|
||||
type: int
|
||||
override_special:
|
||||
description:
|
||||
- Overide a list of special characters to use in the string.
|
||||
- If set I(min_special) should be set to a non-default value.
|
||||
type: str
|
||||
override_all:
|
||||
description:
|
||||
- Override all values of I(numbers), I(upper), I(lower), and I(special) with
|
||||
the given list of characters.
|
||||
type: str
|
||||
base64:
|
||||
description:
|
||||
- Returns base64 encoded string.
|
||||
type: bool
|
||||
default: false
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Generate random string
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string')
|
||||
# Example result: ['DeadBeeF']
|
||||
|
||||
- name: Generate random string with length 12
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string', length=12)
|
||||
# Example result: ['Uan0hUiX5kVG']
|
||||
|
||||
- name: Generate base64 encoded random string
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string', base64=True)
|
||||
# Example result: ['NHZ6eWN5Qk0=']
|
||||
|
||||
- name: Generate a random string with 1 lower, 1 upper, 1 number and 1 special char (atleast)
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.random_string', min_lower=1, min_upper=1, min_special=1, min_numeric=1)
|
||||
# Example result: ['&Qw2|E[-']
|
||||
|
||||
- name: Generate a random string with all lower case characters
|
||||
debug:
|
||||
var: query('community.general.random_string', upper=false, numbers=false, special=false)
|
||||
# Example result: ['exolxzyz']
|
||||
|
||||
- name: Generate random hexadecimal string
|
||||
debug:
|
||||
var: query('community.general.random_string', upper=false, lower=false, override_special=hex_chars, numbers=false)
|
||||
vars:
|
||||
hex_chars: '0123456789ABCDEF'
|
||||
# Example result: ['D2A40737']
|
||||
|
||||
- name: Generate random hexadecimal string with override_all
|
||||
debug:
|
||||
var: query('community.general.random_string', override_all=hex_chars)
|
||||
vars:
|
||||
hex_chars: '0123456789ABCDEF'
|
||||
# Example result: ['D2A40737']
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
_raw:
|
||||
description: A one-element list containing a random string
|
||||
type: list
|
||||
elements: str
|
||||
"""
|
||||
|
||||
import base64
|
||||
import random
|
||||
import string
|
||||
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
@staticmethod
|
||||
def get_random(random_generator, chars, length):
|
||||
if not chars:
|
||||
raise AnsibleLookupError(
|
||||
"Available characters cannot be None, please change constraints"
|
||||
)
|
||||
return "".join(random_generator.choice(chars) for dummy in range(length))
|
||||
|
||||
@staticmethod
|
||||
def b64encode(string_value, encoding="utf-8"):
|
||||
return to_text(
|
||||
base64.b64encode(
|
||||
to_bytes(string_value, encoding=encoding, errors="surrogate_or_strict")
|
||||
)
|
||||
)
|
||||
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
number_chars = string.digits
|
||||
lower_chars = string.ascii_lowercase
|
||||
upper_chars = string.ascii_uppercase
|
||||
special_chars = string.punctuation
|
||||
random_generator = random.SystemRandom()
|
||||
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
|
||||
length = self.get_option("length")
|
||||
base64_flag = self.get_option("base64")
|
||||
override_all = self.get_option("override_all")
|
||||
values = ""
|
||||
available_chars_set = ""
|
||||
|
||||
if override_all:
|
||||
# Override all the values
|
||||
available_chars_set = override_all
|
||||
else:
|
||||
upper = self.get_option("upper")
|
||||
lower = self.get_option("lower")
|
||||
numbers = self.get_option("numbers")
|
||||
special = self.get_option("special")
|
||||
override_special = self.get_option("override_special")
|
||||
|
||||
if override_special:
|
||||
special_chars = override_special
|
||||
|
||||
if upper:
|
||||
available_chars_set += upper_chars
|
||||
if lower:
|
||||
available_chars_set += lower_chars
|
||||
if numbers:
|
||||
available_chars_set += number_chars
|
||||
if special:
|
||||
available_chars_set += special_chars
|
||||
|
||||
mapping = {
|
||||
"min_numeric": number_chars,
|
||||
"min_lower": lower_chars,
|
||||
"min_upper": upper_chars,
|
||||
"min_special": special_chars,
|
||||
}
|
||||
|
||||
for m in mapping:
|
||||
if self.get_option(m):
|
||||
values += self.get_random(random_generator, mapping[m], self.get_option(m))
|
||||
|
||||
remaining_pass_len = length - len(values)
|
||||
values += self.get_random(random_generator, available_chars_set, remaining_pass_len)
|
||||
|
||||
# Get pseudo randomization
|
||||
shuffled_values = list(values)
|
||||
# Randomize the order
|
||||
random.shuffle(shuffled_values)
|
||||
|
||||
if base64_flag:
|
||||
return [self.b64encode("".join(shuffled_values))]
|
||||
|
||||
return ["".join(shuffled_values)]
|
||||
0
plugins/module_utils/__init__.py
Normal file
0
plugins/module_utils/__init__.py
Normal file
871
plugins/module_utils/_ovirt.py
Normal file
871
plugins/module_utils/_ovirt.py
Normal file
@@ -0,0 +1,871 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2016 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import time
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from datetime import datetime
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cloud import CloudRetry
|
||||
from ansible.module_utils.common._collections_compat import Mapping
|
||||
|
||||
try:
|
||||
from enum import Enum # enum is a ovirtsdk4 requirement
|
||||
import ovirtsdk4 as sdk
|
||||
import ovirtsdk4.version as sdk_version
|
||||
import ovirtsdk4.types as otypes
|
||||
HAS_SDK = LooseVersion(sdk_version.VERSION) >= LooseVersion('4.3.0')
|
||||
except ImportError:
|
||||
HAS_SDK = False
|
||||
|
||||
|
||||
BYTES_MAP = {
|
||||
'kib': 2**10,
|
||||
'mib': 2**20,
|
||||
'gib': 2**30,
|
||||
'tib': 2**40,
|
||||
'pib': 2**50,
|
||||
}
|
||||
|
||||
|
||||
def check_sdk(module):
|
||||
if not HAS_SDK:
|
||||
module.fail_json(
|
||||
msg='ovirtsdk4 version 4.3.0 or higher is required for this module'
|
||||
)
|
||||
|
||||
|
||||
def get_dict_of_struct(struct, connection=None, fetch_nested=False, attributes=None):
|
||||
"""
|
||||
Convert SDK Struct type into dictionary.
|
||||
"""
|
||||
res = {}
|
||||
|
||||
def resolve_href(value):
|
||||
# Fetch nested values of struct:
|
||||
try:
|
||||
value = connection.follow_link(value)
|
||||
except sdk.Error:
|
||||
value = None
|
||||
nested_obj = dict(
|
||||
(attr, convert_value(getattr(value, attr)))
|
||||
for attr in attributes if getattr(value, attr, None) is not None
|
||||
)
|
||||
nested_obj['id'] = getattr(value, 'id', None)
|
||||
nested_obj['href'] = getattr(value, 'href', None)
|
||||
return nested_obj
|
||||
|
||||
def remove_underscore(val):
|
||||
if val.startswith('_'):
|
||||
val = val[1:]
|
||||
remove_underscore(val)
|
||||
return val
|
||||
|
||||
def convert_value(value):
|
||||
nested = False
|
||||
|
||||
if isinstance(value, sdk.Struct):
|
||||
if not fetch_nested or not value.href:
|
||||
return get_dict_of_struct(value)
|
||||
return resolve_href(value)
|
||||
|
||||
elif isinstance(value, Enum) or isinstance(value, datetime):
|
||||
return str(value)
|
||||
elif isinstance(value, list) or isinstance(value, sdk.List):
|
||||
if isinstance(value, sdk.List) and fetch_nested and value.href:
|
||||
try:
|
||||
value = connection.follow_link(value)
|
||||
nested = True
|
||||
except sdk.Error:
|
||||
value = []
|
||||
|
||||
ret = []
|
||||
for i in value:
|
||||
if isinstance(i, sdk.Struct):
|
||||
if not nested and fetch_nested and i.href:
|
||||
ret.append(resolve_href(i))
|
||||
elif not nested:
|
||||
ret.append(get_dict_of_struct(i))
|
||||
else:
|
||||
nested_obj = dict(
|
||||
(attr, convert_value(getattr(i, attr)))
|
||||
for attr in attributes if getattr(i, attr, None)
|
||||
)
|
||||
nested_obj['id'] = getattr(i, 'id', None)
|
||||
ret.append(nested_obj)
|
||||
elif isinstance(i, Enum):
|
||||
ret.append(str(i))
|
||||
else:
|
||||
ret.append(i)
|
||||
return ret
|
||||
else:
|
||||
return value
|
||||
|
||||
if struct is not None:
|
||||
for key, value in struct.__dict__.items():
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
key = remove_underscore(key)
|
||||
res[key] = convert_value(value)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def engine_version(connection):
|
||||
"""
|
||||
Return string representation of oVirt engine version.
|
||||
"""
|
||||
engine_api = connection.system_service().get()
|
||||
engine_version = engine_api.product_info.version
|
||||
return '%s.%s' % (engine_version.major, engine_version.minor)
|
||||
|
||||
|
||||
def create_connection(auth):
|
||||
"""
|
||||
Create a connection to Python SDK, from task `auth` parameter.
|
||||
If user doesnt't have SSO token the `auth` dictionary has following parameters mandatory:
|
||||
url, username, password
|
||||
|
||||
If user has SSO token the `auth` dictionary has following parameters mandatory:
|
||||
url, token
|
||||
|
||||
The `ca_file` parameter is mandatory in case user want to use secure connection,
|
||||
in case user want to use insecure connection, it's mandatory to send insecure=True.
|
||||
|
||||
:param auth: dictionary which contains needed values for connection creation
|
||||
:return: Python SDK connection
|
||||
"""
|
||||
|
||||
url = auth.get('url')
|
||||
if url is None and auth.get('hostname') is not None:
|
||||
url = 'https://{0}/ovirt-engine/api'.format(auth.get('hostname'))
|
||||
|
||||
return sdk.Connection(
|
||||
url=url,
|
||||
username=auth.get('username'),
|
||||
password=auth.get('password'),
|
||||
ca_file=auth.get('ca_file', None),
|
||||
insecure=auth.get('insecure', False),
|
||||
token=auth.get('token', None),
|
||||
kerberos=auth.get('kerberos', None),
|
||||
headers=auth.get('headers', None),
|
||||
)
|
||||
|
||||
|
||||
def convert_to_bytes(param):
|
||||
"""
|
||||
This method convert units to bytes, which follow IEC standard.
|
||||
|
||||
:param param: value to be converted
|
||||
"""
|
||||
if param is None:
|
||||
return None
|
||||
|
||||
# Get rid of whitespaces:
|
||||
param = ''.join(param.split())
|
||||
|
||||
# Convert to bytes:
|
||||
if len(param) > 3 and param[-3].lower() in ['k', 'm', 'g', 't', 'p']:
|
||||
return int(param[:-3]) * BYTES_MAP.get(param[-3:].lower(), 1)
|
||||
elif param.isdigit():
|
||||
return int(param) * 2**10
|
||||
else:
|
||||
raise ValueError(
|
||||
"Unsupported value(IEC supported): '{value}'".format(value=param)
|
||||
)
|
||||
|
||||
|
||||
def follow_link(connection, link):
|
||||
"""
|
||||
This method returns the entity of the element which link points to.
|
||||
|
||||
:param connection: connection to the Python SDK
|
||||
:param link: link of the entity
|
||||
:return: entity which link points to
|
||||
"""
|
||||
|
||||
if link:
|
||||
return connection.follow_link(link)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_link_name(connection, link):
|
||||
"""
|
||||
This method returns the name of the element which link points to.
|
||||
|
||||
:param connection: connection to the Python SDK
|
||||
:param link: link of the entity
|
||||
:return: name of the entity, which link points to
|
||||
"""
|
||||
|
||||
if link:
|
||||
return connection.follow_link(link).name
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def equal(param1, param2, ignore_case=False):
|
||||
"""
|
||||
Compare two parameters and return if they are equal.
|
||||
This parameter doesn't run equal operation if first parameter is None.
|
||||
With this approach we don't run equal operation in case user don't
|
||||
specify parameter in their task.
|
||||
|
||||
:param param1: user inputted parameter
|
||||
:param param2: value of entity parameter
|
||||
:return: True if parameters are equal or first parameter is None, otherwise False
|
||||
"""
|
||||
if param1 is not None:
|
||||
if ignore_case:
|
||||
return param1.lower() == param2.lower()
|
||||
return param1 == param2
|
||||
return True
|
||||
|
||||
|
||||
def search_by_attributes(service, list_params=None, **kwargs):
|
||||
"""
|
||||
Search for the entity by attributes. Nested entities don't support search
|
||||
via REST, so in case using search for nested entity we return all entities
|
||||
and filter them by specified attributes.
|
||||
"""
|
||||
list_params = list_params or {}
|
||||
# Check if 'list' method support search(look for search parameter):
|
||||
if 'search' in inspect.getargspec(service.list)[0]:
|
||||
res = service.list(
|
||||
# There must be double quotes around name, because some oVirt resources it's possible to create then with space in name.
|
||||
search=' and '.join('{0}="{1}"'.format(k, v) for k, v in kwargs.items()),
|
||||
**list_params
|
||||
)
|
||||
else:
|
||||
res = [
|
||||
e for e in service.list(**list_params) if len([
|
||||
k for k, v in kwargs.items() if getattr(e, k, None) == v
|
||||
]) == len(kwargs)
|
||||
]
|
||||
|
||||
res = res or [None]
|
||||
return res[0]
|
||||
|
||||
|
||||
def search_by_name(service, name, **kwargs):
|
||||
"""
|
||||
Search for the entity by its name. Nested entities don't support search
|
||||
via REST, so in case using search for nested entity we return all entities
|
||||
and filter them by name.
|
||||
|
||||
:param service: service of the entity
|
||||
:param name: name of the entity
|
||||
:return: Entity object returned by Python SDK
|
||||
"""
|
||||
# Check if 'list' method support search(look for search parameter):
|
||||
if 'search' in inspect.getargspec(service.list)[0]:
|
||||
res = service.list(
|
||||
# There must be double quotes around name, because some oVirt resources it's possible to create then with space in name.
|
||||
search='name="{name}"'.format(name=name)
|
||||
)
|
||||
else:
|
||||
res = [e for e in service.list() if e.name == name]
|
||||
|
||||
if kwargs:
|
||||
res = [
|
||||
e for e in service.list() if len([
|
||||
k for k, v in kwargs.items() if getattr(e, k, None) == v
|
||||
]) == len(kwargs)
|
||||
]
|
||||
|
||||
res = res or [None]
|
||||
return res[0]
|
||||
|
||||
|
||||
def get_entity(service, get_params=None):
|
||||
"""
|
||||
Ignore SDK Error in case of getting an entity from service.
|
||||
"""
|
||||
entity = None
|
||||
try:
|
||||
if get_params is not None:
|
||||
entity = service.get(**get_params)
|
||||
else:
|
||||
entity = service.get()
|
||||
except sdk.Error:
|
||||
# We can get here 404, we should ignore it, in case
|
||||
# of removing entity for example.
|
||||
pass
|
||||
return entity
|
||||
|
||||
|
||||
def get_id_by_name(service, name, raise_error=True, ignore_case=False):
|
||||
"""
|
||||
Search an entity ID by it's name.
|
||||
"""
|
||||
entity = search_by_name(service, name)
|
||||
|
||||
if entity is not None:
|
||||
return entity.id
|
||||
|
||||
if raise_error:
|
||||
raise Exception("Entity '%s' was not found." % name)
|
||||
|
||||
|
||||
def wait(
|
||||
service,
|
||||
condition,
|
||||
fail_condition=lambda e: False,
|
||||
timeout=180,
|
||||
wait=True,
|
||||
poll_interval=3,
|
||||
):
|
||||
"""
|
||||
Wait until entity fulfill expected condition.
|
||||
|
||||
:param service: service of the entity
|
||||
:param condition: condition to be fulfilled
|
||||
:param fail_condition: if this condition is true, raise Exception
|
||||
:param timeout: max time to wait in seconds
|
||||
:param wait: if True wait for condition, if False don't wait
|
||||
:param poll_interval: Number of seconds we should wait until next condition check
|
||||
"""
|
||||
# Wait until the desired state of the entity:
|
||||
if wait:
|
||||
start = time.time()
|
||||
while time.time() < start + timeout:
|
||||
# Exit if the condition of entity is valid:
|
||||
entity = get_entity(service)
|
||||
if condition(entity):
|
||||
return
|
||||
elif fail_condition(entity):
|
||||
raise Exception("Error while waiting on result state of the entity.")
|
||||
|
||||
# Sleep for `poll_interval` seconds if none of the conditions apply:
|
||||
time.sleep(float(poll_interval))
|
||||
|
||||
raise Exception("Timeout exceed while waiting on result state of the entity.")
|
||||
|
||||
|
||||
def __get_auth_dict():
|
||||
OVIRT_URL = os.environ.get('OVIRT_URL')
|
||||
OVIRT_HOSTNAME = os.environ.get('OVIRT_HOSTNAME')
|
||||
OVIRT_USERNAME = os.environ.get('OVIRT_USERNAME')
|
||||
OVIRT_PASSWORD = os.environ.get('OVIRT_PASSWORD')
|
||||
OVIRT_TOKEN = os.environ.get('OVIRT_TOKEN')
|
||||
OVIRT_CAFILE = os.environ.get('OVIRT_CAFILE')
|
||||
OVIRT_INSECURE = OVIRT_CAFILE is None
|
||||
|
||||
env_vars = None
|
||||
if OVIRT_URL is None and OVIRT_HOSTNAME is not None:
|
||||
OVIRT_URL = 'https://{0}/ovirt-engine/api'.format(OVIRT_HOSTNAME)
|
||||
if OVIRT_URL and ((OVIRT_USERNAME and OVIRT_PASSWORD) or OVIRT_TOKEN):
|
||||
env_vars = {
|
||||
'url': OVIRT_URL,
|
||||
'username': OVIRT_USERNAME,
|
||||
'password': OVIRT_PASSWORD,
|
||||
'insecure': OVIRT_INSECURE,
|
||||
'token': OVIRT_TOKEN,
|
||||
'ca_file': OVIRT_CAFILE,
|
||||
}
|
||||
if env_vars is not None:
|
||||
auth = dict(default=env_vars, type='dict')
|
||||
else:
|
||||
auth = dict(required=True, type='dict')
|
||||
|
||||
return auth
|
||||
|
||||
|
||||
def ovirt_info_full_argument_spec(**kwargs):
|
||||
"""
|
||||
Extend parameters of info module with parameters which are common to all
|
||||
oVirt info modules.
|
||||
|
||||
:param kwargs: kwargs to be extended
|
||||
:return: extended dictionary with common parameters
|
||||
"""
|
||||
spec = dict(
|
||||
auth=__get_auth_dict(),
|
||||
fetch_nested=dict(default=False, type='bool'),
|
||||
nested_attributes=dict(type='list', default=list()),
|
||||
)
|
||||
spec.update(kwargs)
|
||||
return spec
|
||||
|
||||
|
||||
# Left for third-party module compatibility
|
||||
def ovirt_facts_full_argument_spec(**kwargs):
|
||||
"""
|
||||
This is deprecated. Please use ovirt_info_full_argument_spec instead!
|
||||
|
||||
:param kwargs: kwargs to be extended
|
||||
:return: extended dictionary with common parameters
|
||||
"""
|
||||
return ovirt_info_full_argument_spec(**kwargs)
|
||||
|
||||
|
||||
def ovirt_full_argument_spec(**kwargs):
|
||||
"""
|
||||
Extend parameters of module with parameters which are common to all oVirt modules.
|
||||
|
||||
:param kwargs: kwargs to be extended
|
||||
:return: extended dictionary with common parameters
|
||||
"""
|
||||
spec = dict(
|
||||
auth=__get_auth_dict(),
|
||||
timeout=dict(default=180, type='int'),
|
||||
wait=dict(default=True, type='bool'),
|
||||
poll_interval=dict(default=3, type='int'),
|
||||
fetch_nested=dict(default=False, type='bool'),
|
||||
nested_attributes=dict(type='list', default=list()),
|
||||
)
|
||||
spec.update(kwargs)
|
||||
return spec
|
||||
|
||||
|
||||
def check_params(module):
|
||||
"""
|
||||
Most modules must have either `name` or `id` specified.
|
||||
"""
|
||||
if module.params.get('name') is None and module.params.get('id') is None:
|
||||
module.fail_json(msg='"name" or "id" is required')
|
||||
|
||||
|
||||
def engine_supported(connection, version):
|
||||
return LooseVersion(engine_version(connection)) >= LooseVersion(version)
|
||||
|
||||
|
||||
def check_support(version, connection, module, params):
|
||||
"""
|
||||
Check if parameters used by user are supported by oVirt Python SDK
|
||||
and oVirt engine.
|
||||
"""
|
||||
api_version = LooseVersion(engine_version(connection))
|
||||
version = LooseVersion(version)
|
||||
for param in params:
|
||||
if module.params.get(param) is not None:
|
||||
return LooseVersion(sdk_version.VERSION) >= version and api_version >= version
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class BaseModule(object):
|
||||
"""
|
||||
This is base class for oVirt modules. oVirt modules should inherit this
|
||||
class and override method to customize specific needs of the module.
|
||||
The only abstract method of this class is `build_entity`, which must
|
||||
to be implemented in child class.
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
def __init__(self, connection, module, service, changed=False):
|
||||
self._connection = connection
|
||||
self._module = module
|
||||
self._service = service
|
||||
self._changed = changed
|
||||
self._diff = {'after': dict(), 'before': dict()}
|
||||
|
||||
@property
|
||||
def changed(self):
|
||||
return self._changed
|
||||
|
||||
@changed.setter
|
||||
def changed(self, changed):
|
||||
if not self._changed:
|
||||
self._changed = changed
|
||||
|
||||
@abstractmethod
|
||||
def build_entity(self):
|
||||
"""
|
||||
This method should return oVirt Python SDK type, which we want to
|
||||
create or update, initialized by values passed by Ansible module.
|
||||
|
||||
For example if we want to create VM, we will return following:
|
||||
types.Vm(name=self._module.params['vm_name'])
|
||||
|
||||
:return: Specific instance of sdk.Struct.
|
||||
"""
|
||||
pass
|
||||
|
||||
def param(self, name, default=None):
|
||||
"""
|
||||
Return a module parameter specified by it's name.
|
||||
"""
|
||||
return self._module.params.get(name, default)
|
||||
|
||||
def update_check(self, entity):
|
||||
"""
|
||||
This method handle checks whether the entity values are same as values
|
||||
passed to ansible module. By default we don't compare any values.
|
||||
|
||||
:param entity: Entity we want to compare with Ansible module values.
|
||||
:return: True if values are same, so we don't need to update the entity.
|
||||
"""
|
||||
return True
|
||||
|
||||
def pre_create(self, entity):
|
||||
"""
|
||||
This method is called right before entity is created.
|
||||
|
||||
:param entity: Entity to be created or updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def post_create(self, entity):
|
||||
"""
|
||||
This method is called right after entity is created.
|
||||
|
||||
:param entity: Entity which was created.
|
||||
"""
|
||||
pass
|
||||
|
||||
def post_update(self, entity):
|
||||
"""
|
||||
This method is called right after entity is updated.
|
||||
|
||||
:param entity: Entity which was updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def diff_update(self, after, update):
|
||||
for k, v in update.items():
|
||||
if isinstance(v, Mapping):
|
||||
after[k] = self.diff_update(after.get(k, dict()), v)
|
||||
else:
|
||||
after[k] = update[k]
|
||||
return after
|
||||
|
||||
def create(
|
||||
self,
|
||||
entity=None,
|
||||
result_state=None,
|
||||
fail_condition=lambda e: False,
|
||||
search_params=None,
|
||||
update_params=None,
|
||||
_wait=None,
|
||||
force_create=False,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Method which is called when state of the entity is 'present'. If user
|
||||
don't provide `entity` parameter the entity is searched using
|
||||
`search_params` parameter. If entity is found it's updated, whether
|
||||
the entity should be updated is checked by `update_check` method.
|
||||
The corresponding updated entity is build by `build_entity` method.
|
||||
|
||||
Function executed after entity is created can optionally be specified
|
||||
in `post_create` parameter. Function executed after entity is updated
|
||||
can optionally be specified in `post_update` parameter.
|
||||
|
||||
:param entity: Entity we want to update, if exists.
|
||||
:param result_state: State which should entity has in order to finish task.
|
||||
:param fail_condition: Function which checks incorrect state of entity, if it returns `True` Exception is raised.
|
||||
:param search_params: Dictionary of parameters to be used for search.
|
||||
:param update_params: The params which should be passed to update method.
|
||||
:param kwargs: Additional parameters passed when creating entity.
|
||||
:return: Dictionary with values returned by Ansible module.
|
||||
"""
|
||||
if entity is None and not force_create:
|
||||
entity = self.search_entity(search_params)
|
||||
|
||||
self.pre_create(entity)
|
||||
|
||||
if entity:
|
||||
# Entity exists, so update it:
|
||||
entity_service = self._service.service(entity.id)
|
||||
if not self.update_check(entity):
|
||||
new_entity = self.build_entity()
|
||||
if not self._module.check_mode:
|
||||
update_params = update_params or {}
|
||||
updated_entity = entity_service.update(
|
||||
new_entity,
|
||||
**update_params
|
||||
)
|
||||
self.post_update(entity)
|
||||
|
||||
# Update diffs only if user specified --diff parameter,
|
||||
# so we don't useless overload API:
|
||||
if self._module._diff:
|
||||
before = get_dict_of_struct(
|
||||
entity,
|
||||
self._connection,
|
||||
fetch_nested=True,
|
||||
attributes=['name'],
|
||||
)
|
||||
after = before.copy()
|
||||
self.diff_update(after, get_dict_of_struct(new_entity))
|
||||
self._diff['before'] = before
|
||||
self._diff['after'] = after
|
||||
|
||||
self.changed = True
|
||||
else:
|
||||
# Entity don't exists, so create it:
|
||||
if not self._module.check_mode:
|
||||
entity = self._service.add(
|
||||
self.build_entity(),
|
||||
**kwargs
|
||||
)
|
||||
self.post_create(entity)
|
||||
self.changed = True
|
||||
|
||||
if not self._module.check_mode:
|
||||
# Wait for the entity to be created and to be in the defined state:
|
||||
entity_service = self._service.service(entity.id)
|
||||
|
||||
def state_condition(entity):
|
||||
return entity
|
||||
|
||||
if result_state:
|
||||
|
||||
def state_condition(entity):
|
||||
return entity and entity.status == result_state
|
||||
|
||||
wait(
|
||||
service=entity_service,
|
||||
condition=state_condition,
|
||||
fail_condition=fail_condition,
|
||||
wait=_wait if _wait is not None else self._module.params['wait'],
|
||||
timeout=self._module.params['timeout'],
|
||||
poll_interval=self._module.params['poll_interval'],
|
||||
)
|
||||
|
||||
return {
|
||||
'changed': self.changed,
|
||||
'id': getattr(entity, 'id', None),
|
||||
type(entity).__name__.lower(): get_dict_of_struct(
|
||||
struct=entity,
|
||||
connection=self._connection,
|
||||
fetch_nested=self._module.params.get('fetch_nested'),
|
||||
attributes=self._module.params.get('nested_attributes'),
|
||||
),
|
||||
'diff': self._diff,
|
||||
}
|
||||
|
||||
def pre_remove(self, entity):
|
||||
"""
|
||||
This method is called right before entity is removed.
|
||||
|
||||
:param entity: Entity which we want to remove.
|
||||
"""
|
||||
pass
|
||||
|
||||
def entity_name(self, entity):
|
||||
return "{e_type} '{e_name}'".format(
|
||||
e_type=type(entity).__name__.lower(),
|
||||
e_name=getattr(entity, 'name', None),
|
||||
)
|
||||
|
||||
def remove(self, entity=None, search_params=None, **kwargs):
|
||||
"""
|
||||
Method which is called when state of the entity is 'absent'. If user
|
||||
don't provide `entity` parameter the entity is searched using
|
||||
`search_params` parameter. If entity is found it's removed.
|
||||
|
||||
Function executed before remove is executed can optionally be specified
|
||||
in `pre_remove` parameter.
|
||||
|
||||
:param entity: Entity we want to remove.
|
||||
:param search_params: Dictionary of parameters to be used for search.
|
||||
:param kwargs: Additional parameters passed when removing entity.
|
||||
:return: Dictionary with values returned by Ansible module.
|
||||
"""
|
||||
if entity is None:
|
||||
entity = self.search_entity(search_params)
|
||||
|
||||
if entity is None:
|
||||
return {
|
||||
'changed': self.changed,
|
||||
'msg': "Entity wasn't found."
|
||||
}
|
||||
|
||||
self.pre_remove(entity)
|
||||
|
||||
entity_service = self._service.service(entity.id)
|
||||
if not self._module.check_mode:
|
||||
entity_service.remove(**kwargs)
|
||||
wait(
|
||||
service=entity_service,
|
||||
condition=lambda entity: not entity,
|
||||
wait=self._module.params['wait'],
|
||||
timeout=self._module.params['timeout'],
|
||||
poll_interval=self._module.params['poll_interval'],
|
||||
)
|
||||
self.changed = True
|
||||
|
||||
return {
|
||||
'changed': self.changed,
|
||||
'id': entity.id,
|
||||
type(entity).__name__.lower(): get_dict_of_struct(
|
||||
struct=entity,
|
||||
connection=self._connection,
|
||||
fetch_nested=self._module.params.get('fetch_nested'),
|
||||
attributes=self._module.params.get('nested_attributes'),
|
||||
),
|
||||
}
|
||||
|
||||
def action(
|
||||
self,
|
||||
action,
|
||||
entity=None,
|
||||
action_condition=lambda e: e,
|
||||
wait_condition=lambda e: e,
|
||||
fail_condition=lambda e: False,
|
||||
pre_action=lambda e: e,
|
||||
post_action=lambda e: None,
|
||||
search_params=None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
This method is executed when we want to change the state of some oVirt
|
||||
entity. The action to be executed on oVirt service is specified by
|
||||
`action` parameter. Whether the action should be executed can be
|
||||
specified by passing `action_condition` parameter. State which the
|
||||
entity should be in after execution of the action can be specified
|
||||
by `wait_condition` parameter.
|
||||
|
||||
Function executed before an action on entity can optionally be specified
|
||||
in `pre_action` parameter. Function executed after an action on entity can
|
||||
optionally be specified in `post_action` parameter.
|
||||
|
||||
:param action: Action which should be executed by service on entity.
|
||||
:param entity: Entity we want to run action on.
|
||||
:param action_condition: Function which is executed when checking if action should be executed.
|
||||
:param fail_condition: Function which checks incorrect state of entity, if it returns `True` Exception is raised.
|
||||
:param wait_condition: Function which is executed when waiting on result state.
|
||||
:param pre_action: Function which is executed before running the action.
|
||||
:param post_action: Function which is executed after running the action.
|
||||
:param search_params: Dictionary of parameters to be used for search.
|
||||
:param kwargs: Additional parameters passed to action.
|
||||
:return: Dictionary with values returned by Ansible module.
|
||||
"""
|
||||
if entity is None:
|
||||
entity = self.search_entity(search_params)
|
||||
|
||||
entity = pre_action(entity)
|
||||
|
||||
if entity is None:
|
||||
self._module.fail_json(
|
||||
msg="Entity not found, can't run action '{0}'.".format(
|
||||
action
|
||||
)
|
||||
)
|
||||
|
||||
entity_service = self._service.service(entity.id)
|
||||
entity = entity_service.get()
|
||||
if action_condition(entity):
|
||||
if not self._module.check_mode:
|
||||
getattr(entity_service, action)(**kwargs)
|
||||
self.changed = True
|
||||
|
||||
post_action(entity)
|
||||
|
||||
wait(
|
||||
service=self._service.service(entity.id),
|
||||
condition=wait_condition,
|
||||
fail_condition=fail_condition,
|
||||
wait=self._module.params['wait'],
|
||||
timeout=self._module.params['timeout'],
|
||||
poll_interval=self._module.params['poll_interval'],
|
||||
)
|
||||
return {
|
||||
'changed': self.changed,
|
||||
'id': entity.id,
|
||||
type(entity).__name__.lower(): get_dict_of_struct(
|
||||
struct=entity,
|
||||
connection=self._connection,
|
||||
fetch_nested=self._module.params.get('fetch_nested'),
|
||||
attributes=self._module.params.get('nested_attributes'),
|
||||
),
|
||||
'diff': self._diff,
|
||||
}
|
||||
|
||||
def wait_for_import(self, condition=lambda e: True):
|
||||
if self._module.params['wait']:
|
||||
start = time.time()
|
||||
timeout = self._module.params['timeout']
|
||||
poll_interval = self._module.params['poll_interval']
|
||||
while time.time() < start + timeout:
|
||||
entity = self.search_entity()
|
||||
if entity and condition(entity):
|
||||
return entity
|
||||
time.sleep(poll_interval)
|
||||
|
||||
def search_entity(self, search_params=None, list_params=None):
|
||||
"""
|
||||
Always first try to search by `ID`, if ID isn't specified,
|
||||
check if user constructed special search in `search_params`,
|
||||
if not search by `name`.
|
||||
"""
|
||||
entity = None
|
||||
|
||||
if 'id' in self._module.params and self._module.params['id'] is not None:
|
||||
entity = get_entity(self._service.service(self._module.params['id']), get_params=list_params)
|
||||
elif search_params is not None:
|
||||
entity = search_by_attributes(self._service, list_params=list_params, **search_params)
|
||||
elif self._module.params.get('name') is not None:
|
||||
entity = search_by_attributes(self._service, list_params=list_params, name=self._module.params['name'])
|
||||
|
||||
return entity
|
||||
|
||||
def _get_major(self, full_version):
|
||||
if full_version is None or full_version == "":
|
||||
return None
|
||||
if isinstance(full_version, otypes.Version):
|
||||
return int(full_version.major)
|
||||
return int(full_version.split('.')[0])
|
||||
|
||||
def _get_minor(self, full_version):
|
||||
if full_version is None or full_version == "":
|
||||
return None
|
||||
if isinstance(full_version, otypes.Version):
|
||||
return int(full_version.minor)
|
||||
return int(full_version.split('.')[1])
|
||||
|
||||
|
||||
def _sdk4_error_maybe():
|
||||
"""
|
||||
Allow for ovirtsdk4 not being installed.
|
||||
"""
|
||||
if HAS_SDK:
|
||||
return sdk.Error
|
||||
return type(None)
|
||||
|
||||
|
||||
class OvirtRetry(CloudRetry):
|
||||
base_class = _sdk4_error_maybe()
|
||||
|
||||
@staticmethod
|
||||
def status_code_from_exception(error):
|
||||
return error.code
|
||||
|
||||
@staticmethod
|
||||
def found(response_code, catch_extra_error_codes=None):
|
||||
# This is a list of error codes to retry.
|
||||
retry_on = [
|
||||
# HTTP status: Conflict
|
||||
409,
|
||||
]
|
||||
if catch_extra_error_codes:
|
||||
retry_on.extend(catch_extra_error_codes)
|
||||
|
||||
return response_code in retry_on
|
||||
0
plugins/module_utils/identity/__init__.py
Normal file
0
plugins/module_utils/identity/__init__.py
Normal file
0
plugins/module_utils/identity/keycloak/__init__.py
Normal file
0
plugins/module_utils/identity/keycloak/__init__.py
Normal file
@@ -30,16 +30,12 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
URL_REALMS = "{url}/admin/realms"
|
||||
URL_REALM = "{url}/admin/realms/{realm}"
|
||||
|
||||
URL_TOKEN = "{url}/realms/{realm}/protocol/openid-connect/token"
|
||||
URL_CLIENT = "{url}/admin/realms/{realm}/clients/{id}"
|
||||
URL_CLIENTS = "{url}/admin/realms/{realm}/clients"
|
||||
@@ -61,12 +57,11 @@ def keycloak_argument_spec():
|
||||
return dict(
|
||||
auth_keycloak_url=dict(type='str', aliases=['url'], required=True, no_log=False),
|
||||
auth_client_id=dict(type='str', default='admin-cli'),
|
||||
auth_realm=dict(type='str'),
|
||||
auth_realm=dict(type='str', required=True),
|
||||
auth_client_secret=dict(type='str', default=None, no_log=True),
|
||||
auth_username=dict(type='str', aliases=['username']),
|
||||
auth_password=dict(type='str', aliases=['password'], no_log=True),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
token=dict(type='str', no_log=True),
|
||||
auth_username=dict(type='str', aliases=['username'], required=True),
|
||||
auth_password=dict(type='str', aliases=['password'], required=True, no_log=True),
|
||||
validate_certs=dict(type='bool', default=True)
|
||||
)
|
||||
|
||||
|
||||
@@ -78,58 +73,41 @@ class KeycloakError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_token(module_params):
|
||||
""" Obtains connection header with token for the authentication,
|
||||
token already given or obtained from credentials
|
||||
:param module_params: parameters of the module
|
||||
:return: connection header
|
||||
"""
|
||||
token = module_params.get('token')
|
||||
base_url = module_params.get('auth_keycloak_url')
|
||||
|
||||
def get_token(base_url, validate_certs, auth_realm, client_id,
|
||||
auth_username, auth_password, client_secret):
|
||||
if not base_url.lower().startswith(('http', 'https')):
|
||||
raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url)
|
||||
|
||||
if token is None:
|
||||
base_url = module_params.get('auth_keycloak_url')
|
||||
validate_certs = module_params.get('validate_certs')
|
||||
auth_realm = module_params.get('auth_realm')
|
||||
client_id = module_params.get('auth_client_id')
|
||||
auth_username = module_params.get('auth_username')
|
||||
auth_password = module_params.get('auth_password')
|
||||
client_secret = module_params.get('auth_client_secret')
|
||||
auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm)
|
||||
temp_payload = {
|
||||
'grant_type': 'password',
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'username': auth_username,
|
||||
'password': auth_password,
|
||||
}
|
||||
# Remove empty items, for instance missing client_secret
|
||||
payload = dict(
|
||||
(k, v) for k, v in temp_payload.items() if v is not None)
|
||||
try:
|
||||
r = json.loads(to_native(open_url(auth_url, method='POST',
|
||||
validate_certs=validate_certs,
|
||||
data=urlencode(payload)).read()))
|
||||
except ValueError as e:
|
||||
raise KeycloakError(
|
||||
'API returned invalid JSON when trying to obtain access token from %s: %s'
|
||||
% (auth_url, str(e)))
|
||||
except Exception as e:
|
||||
raise KeycloakError('Could not obtain access token from %s: %s'
|
||||
% (auth_url, str(e)))
|
||||
|
||||
try:
|
||||
token = r['access_token']
|
||||
except KeyError:
|
||||
raise KeycloakError(
|
||||
'Could not obtain access token from %s' % auth_url)
|
||||
return {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm)
|
||||
temp_payload = {
|
||||
'grant_type': 'password',
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'username': auth_username,
|
||||
'password': auth_password,
|
||||
}
|
||||
# Remove empty items, for instance missing client_secret
|
||||
payload = dict(
|
||||
(k, v) for k, v in temp_payload.items() if v is not None)
|
||||
try:
|
||||
r = json.loads(to_native(open_url(auth_url, method='POST',
|
||||
validate_certs=validate_certs,
|
||||
data=urlencode(payload)).read()))
|
||||
except ValueError as e:
|
||||
raise KeycloakError(
|
||||
'API returned invalid JSON when trying to obtain access token from %s: %s'
|
||||
% (auth_url, str(e)))
|
||||
except Exception as e:
|
||||
raise KeycloakError('Could not obtain access token from %s: %s'
|
||||
% (auth_url, str(e)))
|
||||
|
||||
try:
|
||||
return {
|
||||
'Authorization': 'Bearer ' + r['access_token'],
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
except KeyError:
|
||||
raise KeycloakError(
|
||||
'Could not obtain access token from %s' % auth_url)
|
||||
|
||||
|
||||
class KeycloakAPI(object):
|
||||
@@ -142,75 +120,6 @@ class KeycloakAPI(object):
|
||||
self.validate_certs = self.module.params.get('validate_certs')
|
||||
self.restheaders = connection_header
|
||||
|
||||
def get_realm_by_id(self, realm='master'):
|
||||
""" Obtain realm representation by id
|
||||
|
||||
:param realm: realm id
|
||||
:return: dict of real, representation or None if none matching exist
|
||||
"""
|
||||
realm_url = URL_REALM.format(url=self.baseurl, realm=realm)
|
||||
|
||||
try:
|
||||
return json.loads(to_native(open_url(realm_url, method='GET', headers=self.restheaders,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
|
||||
except HTTPError as e:
|
||||
if e.code == 404:
|
||||
return None
|
||||
else:
|
||||
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())
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)),
|
||||
exception=traceback.format_exc())
|
||||
|
||||
def update_realm(self, realmrep, realm="master"):
|
||||
""" Update an existing realm
|
||||
:param realmrep: corresponding (partial/full) realm representation with updates
|
||||
:param realm: realm to be updated in Keycloak
|
||||
:return: HTTPResponse object on success
|
||||
"""
|
||||
realm_url = URL_REALM.format(url=self.baseurl, realm=realm)
|
||||
|
||||
try:
|
||||
return open_url(realm_url, method='PUT', headers=self.restheaders,
|
||||
data=json.dumps(realmrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
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
|
||||
:param realmrep: Realm representation of realm to be created.
|
||||
:return: HTTPResponse object on success
|
||||
"""
|
||||
realm_url = URL_REALMS.format(url=self.baseurl)
|
||||
|
||||
try:
|
||||
return open_url(realm_url, method='POST', headers=self.restheaders,
|
||||
data=json.dumps(realmrep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
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
|
||||
|
||||
:param realm: realm to be deleted
|
||||
:return: HTTPResponse object on success
|
||||
"""
|
||||
realm_url = URL_REALM.format(url=self.baseurl, realm=realm)
|
||||
|
||||
try:
|
||||
return open_url(realm_url, method='DELETE', headers=self.restheaders,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
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
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ModuleHelperException as _MHE
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.deco import module_fails_on_exception
|
||||
|
||||
|
||||
class ModuleHelperBase(object):
|
||||
module = None
|
||||
ModuleHelperException = _MHE
|
||||
|
||||
def __init__(self, module=None):
|
||||
self._changed = False
|
||||
|
||||
if module:
|
||||
self.module = module
|
||||
|
||||
if not isinstance(self.module, AnsibleModule):
|
||||
self.module = AnsibleModule(**self.module)
|
||||
|
||||
def __init_module__(self):
|
||||
pass
|
||||
|
||||
def __run__(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __quit_module__(self):
|
||||
pass
|
||||
|
||||
def __changed__(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def changed(self):
|
||||
try:
|
||||
return self.__changed__()
|
||||
except NotImplementedError:
|
||||
return self._changed
|
||||
|
||||
@changed.setter
|
||||
def changed(self, value):
|
||||
self._changed = value
|
||||
|
||||
def has_changed(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@module_fails_on_exception
|
||||
def run(self):
|
||||
self.__init_module__()
|
||||
self.__run__()
|
||||
self.__quit_module__()
|
||||
self.module.exit_json(changed=self.has_changed(), **self.output)
|
||||
@@ -1,54 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import traceback
|
||||
from functools import wraps
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ModuleHelperException
|
||||
|
||||
|
||||
def cause_changes(on_success=None, on_failure=None):
|
||||
|
||||
def deco(func):
|
||||
if on_success is None and on_failure is None:
|
||||
return func
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
self = args[0]
|
||||
func(*args, **kwargs)
|
||||
if on_success is not None:
|
||||
self.changed = on_success
|
||||
except Exception:
|
||||
if on_failure is not None:
|
||||
self.changed = on_failure
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
return deco
|
||||
|
||||
|
||||
def module_fails_on_exception(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
func(self, *args, **kwargs)
|
||||
except SystemExit:
|
||||
raise
|
||||
except ModuleHelperException as e:
|
||||
if e.update_output:
|
||||
self.update_output(e.update_output)
|
||||
self.module.fail_json(msg=e.msg, exception=traceback.format_exc(),
|
||||
output=self.output, vars=self.vars.output(), **self.output)
|
||||
except Exception as e:
|
||||
msg = "Module failed with exception: {0}".format(str(e).strip())
|
||||
self.module.fail_json(msg=msg, exception=traceback.format_exc(),
|
||||
output=self.output, vars=self.vars.output(), **self.output)
|
||||
return wrapper
|
||||
@@ -1,22 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleHelperException(Exception):
|
||||
@staticmethod
|
||||
def _get_remove(key, kwargs):
|
||||
if key in kwargs:
|
||||
result = kwargs[key]
|
||||
del kwargs[key]
|
||||
return result
|
||||
return None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.msg = self._get_remove('msg', kwargs) or "Module failed with exception: {0}".format(self)
|
||||
self.update_output = self._get_remove('update_output', kwargs) or {}
|
||||
super(ModuleHelperException, self).__init__(*args)
|
||||
@@ -1,175 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/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
|
||||
|
||||
@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):
|
||||
"""
|
||||
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.PRINTF: printf_fmt,
|
||||
ArgFormat.FORMAT: lambda _fmt, v: [_fmt.format(v)],
|
||||
}
|
||||
|
||||
self.name = name
|
||||
self.stars = stars
|
||||
|
||||
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:
|
||||
return []
|
||||
func = self.arg_format
|
||||
return [str(p) for p in func(value)]
|
||||
|
||||
|
||||
class CmdMixin(object):
|
||||
"""
|
||||
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 _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:
|
||||
raise self.ModuleHelperException("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.module.deprecate("Cannot determine value for parameter: {0}. "
|
||||
"From version 4.0.0 onwards this will generate an exception".format(param),
|
||||
version="4.0.0", collection_name="community.general")
|
||||
continue
|
||||
|
||||
else:
|
||||
raise self.ModuleHelperException("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, *args, **kwargs):
|
||||
self.vars.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(self.vars.cmd_args, *args, **options)
|
||||
self.update_output(rc=rc, stdout=out, stderr=err)
|
||||
if process_output is None:
|
||||
_process = self.process_command_output
|
||||
else:
|
||||
_process = process_output
|
||||
|
||||
return _process(rc, out, err)
|
||||
@@ -1,58 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import ModuleHelperBase
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.deco import module_fails_on_exception
|
||||
|
||||
|
||||
class DependencyCtxMgr(object):
|
||||
def __init__(self, name, msg=None):
|
||||
self.name = name
|
||||
self.msg = msg
|
||||
self.has_it = False
|
||||
self.exc_type = None
|
||||
self.exc_val = None
|
||||
self.exc_tb = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.has_it = exc_type is None
|
||||
self.exc_type = exc_type
|
||||
self.exc_val = exc_val
|
||||
self.exc_tb = exc_tb
|
||||
return not self.has_it
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.msg or str(self.exc_val)
|
||||
|
||||
|
||||
class DependencyMixin(ModuleHelperBase):
|
||||
_dependencies = []
|
||||
|
||||
@classmethod
|
||||
def dependency(cls, name, msg):
|
||||
cls._dependencies.append(DependencyCtxMgr(name, msg))
|
||||
return cls._dependencies[-1]
|
||||
|
||||
def fail_on_missing_deps(self):
|
||||
for d in self._dependencies:
|
||||
if not d.has_it:
|
||||
self.module.fail_json(changed=False,
|
||||
exception="\n".join(traceback.format_exception(d.exc_type, d.exc_val, d.exc_tb)),
|
||||
msg=d.text,
|
||||
**self.output)
|
||||
|
||||
@module_fails_on_exception
|
||||
def run(self):
|
||||
self.fail_on_missing_deps()
|
||||
super(DependencyMixin, self).run()
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class StateMixin(object):
|
||||
state_param = 'state'
|
||||
default_state = None
|
||||
|
||||
def _state(self):
|
||||
state = self.module.params.get(self.state_param)
|
||||
return self.default_state if state is None else state
|
||||
|
||||
def _method(self, state):
|
||||
return "{0}_{1}".format(self.state_param, state)
|
||||
|
||||
def __run__(self):
|
||||
state = self._state()
|
||||
self.vars.state = state
|
||||
|
||||
# resolve aliases
|
||||
if state not in self.module.params:
|
||||
aliased = [name for name, param in self.module.argument_spec.items() if state in param.get('aliases', [])]
|
||||
if aliased:
|
||||
state = aliased[0]
|
||||
self.vars.effective_state = state
|
||||
|
||||
method = self._method(state)
|
||||
if not hasattr(self, method):
|
||||
return self.__state_fallback__()
|
||||
func = getattr(self, method)
|
||||
return func()
|
||||
|
||||
def __state_fallback__(self):
|
||||
raise ValueError("Cannot find method: {0}".format(self._method(self._state())))
|
||||
@@ -1,132 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class VarMeta(object):
|
||||
NOTHING = object()
|
||||
|
||||
def __init__(self, diff=False, output=True, change=None, fact=False):
|
||||
self.init = False
|
||||
self.initial_value = None
|
||||
self.value = None
|
||||
|
||||
self.diff = diff
|
||||
self.change = diff if change is None else change
|
||||
self.output = output
|
||||
self.fact = fact
|
||||
|
||||
def set(self, diff=None, output=None, change=None, fact=None, initial_value=NOTHING):
|
||||
if diff is not None:
|
||||
self.diff = diff
|
||||
if output is not None:
|
||||
self.output = output
|
||||
if change is not None:
|
||||
self.change = change
|
||||
if fact is not None:
|
||||
self.fact = fact
|
||||
if initial_value is not self.NOTHING:
|
||||
self.initial_value = initial_value
|
||||
|
||||
def set_value(self, value):
|
||||
if not self.init:
|
||||
self.initial_value = value
|
||||
self.init = True
|
||||
self.value = value
|
||||
return self
|
||||
|
||||
@property
|
||||
def has_changed(self):
|
||||
return self.change and (self.initial_value != self.value)
|
||||
|
||||
@property
|
||||
def diff_result(self):
|
||||
return None if not (self.diff and self.has_changed) else {
|
||||
'before': self.initial_value,
|
||||
'after': self.value,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return "<VarMeta: value={0}, initial={1}, diff={2}, output={3}, change={4}>".format(
|
||||
self.value, self.initial_value, self.diff, self.output, self.change
|
||||
)
|
||||
|
||||
|
||||
class VarDict(object):
|
||||
def __init__(self):
|
||||
self._data = dict()
|
||||
self._meta = dict()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._data[item]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.set(key, value)
|
||||
|
||||
def __getattr__(self, item):
|
||||
try:
|
||||
return self._data[item]
|
||||
except KeyError:
|
||||
return getattr(self._data, item)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in ('_data', '_meta'):
|
||||
super(VarDict, self).__setattr__(key, value)
|
||||
else:
|
||||
self.set(key, value)
|
||||
|
||||
def meta(self, name):
|
||||
return self._meta[name]
|
||||
|
||||
def set_meta(self, name, **kwargs):
|
||||
self.meta(name).set(**kwargs)
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
if name in ('_data', '_meta'):
|
||||
raise ValueError("Names _data and _meta are reserved for use by ModuleHelper")
|
||||
self._data[name] = value
|
||||
if name in self._meta:
|
||||
meta = self.meta(name)
|
||||
else:
|
||||
meta = VarMeta(**kwargs)
|
||||
meta.set_value(value)
|
||||
self._meta[name] = meta
|
||||
|
||||
def output(self):
|
||||
return dict((k, v) for k, v in self._data.items() if self.meta(k).output)
|
||||
|
||||
def diff(self):
|
||||
diff_results = [(k, self.meta(k).diff_result) for k in self._data]
|
||||
diff_results = [dr for dr in diff_results if dr[1] is not None]
|
||||
if diff_results:
|
||||
before = dict((dr[0], dr[1]['before']) for dr in diff_results)
|
||||
after = dict((dr[0], dr[1]['after']) for dr in diff_results)
|
||||
return {'before': before, 'after': after}
|
||||
return None
|
||||
|
||||
def facts(self):
|
||||
facts_result = dict((k, v) for k, v in self._data.items() if self._meta[k].fact)
|
||||
return facts_result if facts_result else None
|
||||
|
||||
def change_vars(self):
|
||||
return [v for v in self._data if self.meta(v).change]
|
||||
|
||||
def has_changed(self, v):
|
||||
return self._meta[v].has_changed
|
||||
|
||||
|
||||
class VarsMixin(object):
|
||||
|
||||
def __init__(self, module=None):
|
||||
self.vars = VarDict()
|
||||
super(VarsMixin, self).__init__(module)
|
||||
|
||||
def update_vars(self, meta=None, **kwargs):
|
||||
if meta is None:
|
||||
meta = {}
|
||||
for k, v in kwargs.items():
|
||||
self.vars.set(k, v, **meta)
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.base import ModuleHelperBase, AnsibleModule
|
||||
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, VarDict as _VD
|
||||
|
||||
|
||||
class ModuleHelper(VarsMixin, DependencyMixin, ModuleHelperBase):
|
||||
_output_conflict_list = ('msg', 'exception', 'output', 'vars', 'changed')
|
||||
facts_name = None
|
||||
output_params = ()
|
||||
diff_params = ()
|
||||
change_params = ()
|
||||
facts_params = ()
|
||||
|
||||
VarDict = _VD # for backward compatibility, will be deprecated at some point
|
||||
|
||||
def __init__(self, module=None):
|
||||
super(ModuleHelper, self).__init__(module)
|
||||
for name, value in self.module.params.items():
|
||||
self.vars.set(
|
||||
name, value,
|
||||
diff=name in self.diff_params,
|
||||
output=name in self.output_params,
|
||||
change=None if not self.change_params else name in self.change_params,
|
||||
fact=name in self.facts_params,
|
||||
)
|
||||
|
||||
def update_output(self, **kwargs):
|
||||
self.update_vars(meta={"output": True}, **kwargs)
|
||||
|
||||
def update_facts(self, **kwargs):
|
||||
self.update_vars(meta={"fact": True}, **kwargs)
|
||||
|
||||
def _vars_changed(self):
|
||||
return any(self.vars.has_changed(v) for v in self.vars.change_vars())
|
||||
|
||||
def has_changed(self):
|
||||
return self.changed or self._vars_changed()
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
result = dict(self.vars.output())
|
||||
if self.facts_name:
|
||||
facts = self.vars.facts()
|
||||
if facts is not None:
|
||||
result['ansible_facts'] = {self.facts_name: facts}
|
||||
if self.module._diff:
|
||||
diff = result.get('diff', {})
|
||||
vars_diff = self.vars.diff() or {}
|
||||
result['diff'] = dict_merge(dict(diff), vars_diff)
|
||||
|
||||
for varname in result:
|
||||
if varname in self._output_conflict_list:
|
||||
result["_" + varname] = result[varname]
|
||||
del result[varname]
|
||||
return result
|
||||
|
||||
|
||||
class StateModuleHelper(StateMixin, ModuleHelper):
|
||||
pass
|
||||
|
||||
|
||||
class CmdModuleHelper(CmdMixin, ModuleHelper):
|
||||
pass
|
||||
|
||||
|
||||
class CmdStateModuleHelper(CmdMixin, StateMixin, ModuleHelper):
|
||||
pass
|
||||
@@ -6,13 +6,509 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from functools import partial, wraps
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.module_helper import (
|
||||
ModuleHelper, StateModuleHelper, CmdModuleHelper, CmdStateModuleHelper, AnsibleModule
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.cmd import CmdMixin, ArgFormat
|
||||
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 DependencyCtxMgr
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.exceptions import ModuleHelperException
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.deco import cause_changes, module_fails_on_exception
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarMeta, VarDict
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
|
||||
|
||||
class ModuleHelperException(Exception):
|
||||
@staticmethod
|
||||
def _get_remove(key, kwargs):
|
||||
if key in kwargs:
|
||||
result = kwargs[key]
|
||||
del kwargs[key]
|
||||
return result
|
||||
return None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.msg = self._get_remove('msg', kwargs) or "Module failed with exception: {0}".format(self)
|
||||
self.update_output = self._get_remove('update_output', kwargs) or {}
|
||||
super(ModuleHelperException, self).__init__(*args)
|
||||
|
||||
|
||||
class ArgFormat(object):
|
||||
"""
|
||||
Argument formatter for use as a command line parameter. Used in CmdMixin.
|
||||
"""
|
||||
BOOLEAN = 0
|
||||
PRINTF = 1
|
||||
FORMAT = 2
|
||||
|
||||
@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):
|
||||
"""
|
||||
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.PRINTF: printf_fmt,
|
||||
ArgFormat.FORMAT: lambda _fmt, v: [_fmt.format(v)],
|
||||
}
|
||||
|
||||
self.name = name
|
||||
self.stars = stars
|
||||
|
||||
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:
|
||||
return []
|
||||
func = self.arg_format
|
||||
return [str(p) for p in func(value)]
|
||||
|
||||
|
||||
def cause_changes(on_success=None, on_failure=None):
|
||||
|
||||
def deco(func):
|
||||
if on_success is None and on_failure is None:
|
||||
return func
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
self = args[0]
|
||||
func(*args, **kwargs)
|
||||
if on_success is not None:
|
||||
self.changed = on_success
|
||||
except Exception:
|
||||
if on_failure is not None:
|
||||
self.changed = on_failure
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
return deco
|
||||
|
||||
|
||||
def module_fails_on_exception(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
func(self, *args, **kwargs)
|
||||
except SystemExit:
|
||||
raise
|
||||
except ModuleHelperException as e:
|
||||
if e.update_output:
|
||||
self.update_output(e.update_output)
|
||||
self.module.fail_json(msg=e.msg, exception=traceback.format_exc(),
|
||||
output=self.output, vars=self.vars.output(), **self.output)
|
||||
except Exception as e:
|
||||
msg = "Module failed with exception: {0}".format(str(e).strip())
|
||||
self.module.fail_json(msg=msg, exception=traceback.format_exc(),
|
||||
output=self.output, vars=self.vars.output(), **self.output)
|
||||
return wrapper
|
||||
|
||||
|
||||
class DependencyCtxMgr(object):
|
||||
def __init__(self, name, msg=None):
|
||||
self.name = name
|
||||
self.msg = msg
|
||||
self.has_it = False
|
||||
self.exc_type = None
|
||||
self.exc_val = None
|
||||
self.exc_tb = None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.has_it = exc_type is None
|
||||
self.exc_type = exc_type
|
||||
self.exc_val = exc_val
|
||||
self.exc_tb = exc_tb
|
||||
return not self.has_it
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.msg or str(self.exc_val)
|
||||
|
||||
|
||||
class VarMeta(object):
|
||||
NOTHING = object()
|
||||
|
||||
def __init__(self, diff=False, output=True, change=None, fact=False):
|
||||
self.init = False
|
||||
self.initial_value = None
|
||||
self.value = None
|
||||
|
||||
self.diff = diff
|
||||
self.change = diff if change is None else change
|
||||
self.output = output
|
||||
self.fact = fact
|
||||
|
||||
def set(self, diff=None, output=None, change=None, fact=None, initial_value=NOTHING):
|
||||
if diff is not None:
|
||||
self.diff = diff
|
||||
if output is not None:
|
||||
self.output = output
|
||||
if change is not None:
|
||||
self.change = change
|
||||
if fact is not None:
|
||||
self.fact = fact
|
||||
if initial_value is not self.NOTHING:
|
||||
self.initial_value = initial_value
|
||||
|
||||
def set_value(self, value):
|
||||
if not self.init:
|
||||
self.initial_value = value
|
||||
self.init = True
|
||||
self.value = value
|
||||
return self
|
||||
|
||||
@property
|
||||
def has_changed(self):
|
||||
return self.change and (self.initial_value != self.value)
|
||||
|
||||
@property
|
||||
def diff_result(self):
|
||||
return None if not (self.diff and self.has_changed) else {
|
||||
'before': self.initial_value,
|
||||
'after': self.value,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return "<VarMeta: value={0}, initial={1}, diff={2}, output={3}, change={4}>".format(
|
||||
self.value, self.initial_value, self.diff, self.output, self.change
|
||||
)
|
||||
|
||||
|
||||
class ModuleHelper(object):
|
||||
_output_conflict_list = ('msg', 'exception', 'output', 'vars', 'changed')
|
||||
_dependencies = []
|
||||
module = None
|
||||
facts_name = None
|
||||
output_params = ()
|
||||
diff_params = ()
|
||||
change_params = ()
|
||||
facts_params = ()
|
||||
|
||||
class VarDict(object):
|
||||
def __init__(self):
|
||||
self._data = dict()
|
||||
self._meta = dict()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._data[item]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.set(key, value)
|
||||
|
||||
def __getattr__(self, item):
|
||||
try:
|
||||
return self._data[item]
|
||||
except KeyError:
|
||||
return getattr(self._data, item)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key in ('_data', '_meta'):
|
||||
super(ModuleHelper.VarDict, self).__setattr__(key, value)
|
||||
else:
|
||||
self.set(key, value)
|
||||
|
||||
def meta(self, name):
|
||||
return self._meta[name]
|
||||
|
||||
def set_meta(self, name, **kwargs):
|
||||
self.meta(name).set(**kwargs)
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
if name in ('_data', '_meta'):
|
||||
raise ValueError("Names _data and _meta are reserved for use by ModuleHelper")
|
||||
self._data[name] = value
|
||||
if name in self._meta:
|
||||
meta = self.meta(name)
|
||||
else:
|
||||
meta = VarMeta(**kwargs)
|
||||
meta.set_value(value)
|
||||
self._meta[name] = meta
|
||||
|
||||
def output(self):
|
||||
return dict((k, v) for k, v in self._data.items() if self.meta(k).output)
|
||||
|
||||
def diff(self):
|
||||
diff_results = [(k, self.meta(k).diff_result) for k in self._data]
|
||||
diff_results = [dr for dr in diff_results if dr[1] is not None]
|
||||
if diff_results:
|
||||
before = dict((dr[0], dr[1]['before']) for dr in diff_results)
|
||||
after = dict((dr[0], dr[1]['after']) for dr in diff_results)
|
||||
return {'before': before, 'after': after}
|
||||
return None
|
||||
|
||||
def facts(self):
|
||||
facts_result = dict((k, v) for k, v in self._data.items() if self._meta[k].fact)
|
||||
return facts_result if facts_result else None
|
||||
|
||||
def change_vars(self):
|
||||
return [v for v in self._data if self.meta(v).change]
|
||||
|
||||
def has_changed(self, v):
|
||||
return self._meta[v].has_changed
|
||||
|
||||
def __init__(self, module=None):
|
||||
self.vars = ModuleHelper.VarDict()
|
||||
self._changed = False
|
||||
|
||||
if module:
|
||||
self.module = module
|
||||
|
||||
if not isinstance(self.module, AnsibleModule):
|
||||
self.module = AnsibleModule(**self.module)
|
||||
|
||||
for name, value in self.module.params.items():
|
||||
self.vars.set(
|
||||
name, value,
|
||||
diff=name in self.diff_params,
|
||||
output=name in self.output_params,
|
||||
change=None if not self.change_params else name in self.change_params,
|
||||
fact=name in self.facts_params,
|
||||
)
|
||||
|
||||
def update_vars(self, meta=None, **kwargs):
|
||||
if meta is None:
|
||||
meta = {}
|
||||
for k, v in kwargs.items():
|
||||
self.vars.set(k, v, **meta)
|
||||
|
||||
def update_output(self, **kwargs):
|
||||
self.update_vars(meta={"output": True}, **kwargs)
|
||||
|
||||
def update_facts(self, **kwargs):
|
||||
self.update_vars(meta={"fact": True}, **kwargs)
|
||||
|
||||
def __init_module__(self):
|
||||
pass
|
||||
|
||||
def __run__(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __quit_module__(self):
|
||||
pass
|
||||
|
||||
def _vars_changed(self):
|
||||
return any(self.vars.has_changed(v) for v in self.vars.change_vars())
|
||||
|
||||
@property
|
||||
def changed(self):
|
||||
return self._changed
|
||||
|
||||
@changed.setter
|
||||
def changed(self, value):
|
||||
self._changed = value
|
||||
|
||||
def has_changed(self):
|
||||
return self.changed or self._vars_changed()
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
result = dict(self.vars.output())
|
||||
if self.facts_name:
|
||||
facts = self.vars.facts()
|
||||
if facts is not None:
|
||||
result['ansible_facts'] = {self.facts_name: facts}
|
||||
if self.module._diff:
|
||||
diff = result.get('diff', {})
|
||||
vars_diff = self.vars.diff() or {}
|
||||
result['diff'] = dict_merge(dict(diff), vars_diff)
|
||||
|
||||
for varname in result:
|
||||
if varname in self._output_conflict_list:
|
||||
result["_" + varname] = result[varname]
|
||||
del result[varname]
|
||||
return result
|
||||
|
||||
@module_fails_on_exception
|
||||
def run(self):
|
||||
self.fail_on_missing_deps()
|
||||
self.__init_module__()
|
||||
self.__run__()
|
||||
self.__quit_module__()
|
||||
self.module.exit_json(changed=self.has_changed(), **self.output)
|
||||
|
||||
@classmethod
|
||||
def dependency(cls, name, msg):
|
||||
cls._dependencies.append(DependencyCtxMgr(name, msg))
|
||||
return cls._dependencies[-1]
|
||||
|
||||
def fail_on_missing_deps(self):
|
||||
for d in self._dependencies:
|
||||
if not d.has_it:
|
||||
self.module.fail_json(changed=False,
|
||||
exception="\n".join(traceback.format_exception(d.exc_type, d.exc_val, d.exc_tb)),
|
||||
msg=d.text,
|
||||
**self.output)
|
||||
|
||||
|
||||
class StateMixin(object):
|
||||
state_param = 'state'
|
||||
default_state = None
|
||||
|
||||
def _state(self):
|
||||
state = self.module.params.get(self.state_param)
|
||||
return self.default_state if state is None else state
|
||||
|
||||
def _method(self, state):
|
||||
return "{0}_{1}".format(self.state_param, state)
|
||||
|
||||
def __run__(self):
|
||||
state = self._state()
|
||||
self.vars.state = state
|
||||
|
||||
# resolve aliases
|
||||
if state not in self.module.params:
|
||||
aliased = [name for name, param in self.module.argument_spec.items() if state in param.get('aliases', [])]
|
||||
if aliased:
|
||||
state = aliased[0]
|
||||
self.vars.effective_state = state
|
||||
|
||||
method = self._method(state)
|
||||
if not hasattr(self, method):
|
||||
return self.__state_fallback__()
|
||||
func = getattr(self, method)
|
||||
return func()
|
||||
|
||||
def __state_fallback__(self):
|
||||
raise ValueError("Cannot find method: {0}".format(self._method(self._state())))
|
||||
|
||||
|
||||
class CmdMixin(object):
|
||||
"""
|
||||
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 _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.module.params.keys()
|
||||
|
||||
for param in param_list:
|
||||
if isinstance(param, dict):
|
||||
if len(param) != 1:
|
||||
raise ModuleHelperException("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.module.argument_spec:
|
||||
fmt = find_format(param)
|
||||
value = self.module.params[param]
|
||||
elif param in extra_params:
|
||||
fmt = find_format(param)
|
||||
value = extra_params[param]
|
||||
else:
|
||||
self.module.deprecate("Cannot determine value for parameter: {0}. "
|
||||
"From version 4.0.0 onwards this will generate an exception".format(param),
|
||||
version="4.0.0", collection_name="community.general")
|
||||
continue
|
||||
|
||||
else:
|
||||
raise ModuleHelperException("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, *args, **kwargs):
|
||||
self.vars.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(self.vars.cmd_args, *args, **options)
|
||||
self.update_output(rc=rc, stdout=out, stderr=err)
|
||||
return self.process_command_output(rc, out, err)
|
||||
|
||||
|
||||
class StateModuleHelper(StateMixin, ModuleHelper):
|
||||
pass
|
||||
|
||||
|
||||
class CmdModuleHelper(CmdMixin, ModuleHelper):
|
||||
pass
|
||||
|
||||
|
||||
class CmdStateModuleHelper(CmdMixin, StateMixin, ModuleHelper):
|
||||
pass
|
||||
|
||||
0
plugins/module_utils/net_tools/__init__.py
Normal file
0
plugins/module_utils/net_tools/__init__.py
Normal file
0
plugins/module_utils/net_tools/nios/__init__.py
Normal file
0
plugins/module_utils/net_tools/nios/__init__.py
Normal file
0
plugins/module_utils/net_tools/pritunl/__init__.py
Normal file
0
plugins/module_utils/net_tools/pritunl/__init__.py
Normal file
0
plugins/module_utils/oracle/__init__.py
Normal file
0
plugins/module_utils/oracle/__init__.py
Normal file
@@ -19,10 +19,11 @@ PATCH_HEADERS = {'content-type': 'application/json', 'accept': 'application/json
|
||||
'OData-Version': '4.0'}
|
||||
DELETE_HEADERS = {'accept': 'application/json', 'OData-Version': '4.0'}
|
||||
|
||||
FAIL_MSG = 'Issuing a data modification command without specifying the '\
|
||||
'ID of the target %(resource)s resource when there is more '\
|
||||
'than one %(resource)s is no longer allowed. Use the `resource_id` '\
|
||||
'option to specify the target %(resource)s ID.'
|
||||
DEPRECATE_MSG = 'Issuing a data modification command without specifying the '\
|
||||
'ID of the target %(resource)s resource when there is more '\
|
||||
'than one %(resource)s will use the first one in the '\
|
||||
'collection. Use the `resource_id` option to specify the '\
|
||||
'target %(resource)s ID'
|
||||
|
||||
|
||||
class RedfishUtils(object):
|
||||
@@ -266,7 +267,8 @@ class RedfishUtils(object):
|
||||
'ret': False,
|
||||
'msg': "System resource %s not found" % self.resource_id}
|
||||
elif len(self.systems_uris) > 1:
|
||||
self.module.fail_json(msg=FAIL_MSG % {'resource': 'System'})
|
||||
self.module.deprecate(DEPRECATE_MSG % {'resource': 'System'},
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.14
|
||||
return {'ret': True}
|
||||
|
||||
def _find_updateservice_resource(self):
|
||||
@@ -316,7 +318,8 @@ class RedfishUtils(object):
|
||||
'ret': False,
|
||||
'msg': "Chassis resource %s not found" % self.resource_id}
|
||||
elif len(self.chassis_uris) > 1:
|
||||
self.module.fail_json(msg=FAIL_MSG % {'resource': 'Chassis'})
|
||||
self.module.deprecate(DEPRECATE_MSG % {'resource': 'Chassis'},
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.14
|
||||
return {'ret': True}
|
||||
|
||||
def _find_managers_resource(self):
|
||||
@@ -345,7 +348,8 @@ class RedfishUtils(object):
|
||||
'ret': False,
|
||||
'msg': "Manager resource %s not found" % self.resource_id}
|
||||
elif len(self.manager_uris) > 1:
|
||||
self.module.fail_json(msg=FAIL_MSG % {'resource': 'Manager'})
|
||||
self.module.deprecate(DEPRECATE_MSG % {'resource': 'Manager'},
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.14
|
||||
return {'ret': True}
|
||||
|
||||
def _get_all_action_info_values(self, action):
|
||||
@@ -1671,31 +1675,19 @@ class RedfishUtils(object):
|
||||
|
||||
# Make a copy of the attributes dict
|
||||
attrs_to_patch = dict(attributes)
|
||||
# List to hold attributes not found
|
||||
attrs_bad = {}
|
||||
|
||||
# Check the attributes
|
||||
for attr_name, attr_value in attributes.items():
|
||||
# Check if attribute exists
|
||||
if attr_name not in data[u'Attributes']:
|
||||
# Remove and proceed to next attribute if this isn't valid
|
||||
attrs_bad.update({attr_name: attr_value})
|
||||
del attrs_to_patch[attr_name]
|
||||
continue
|
||||
|
||||
for attr in attributes:
|
||||
if attr not in data[u'Attributes']:
|
||||
return {'ret': False, 'msg': "BIOS attribute %s not found" % attr}
|
||||
# If already set to requested value, remove it from PATCH payload
|
||||
if data[u'Attributes'][attr_name] == attributes[attr_name]:
|
||||
del attrs_to_patch[attr_name]
|
||||
|
||||
warning = ""
|
||||
if attrs_bad:
|
||||
warning = "Incorrect attributes %s" % (attrs_bad)
|
||||
if data[u'Attributes'][attr] == attributes[attr]:
|
||||
del attrs_to_patch[attr]
|
||||
|
||||
# Return success w/ changed=False if no attrs need to be changed
|
||||
if not attrs_to_patch:
|
||||
return {'ret': True, 'changed': False,
|
||||
'msg': "BIOS attributes already set",
|
||||
'warning': warning}
|
||||
'msg': "BIOS attributes already set"}
|
||||
|
||||
# Get the SettingsObject URI
|
||||
set_bios_attr_uri = data["@Redfish.Settings"]["SettingsObject"]["@odata.id"]
|
||||
@@ -1705,9 +1697,7 @@ class RedfishUtils(object):
|
||||
response = self.patch_request(self.root_uri + set_bios_attr_uri, payload)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
return {'ret': True, 'changed': True,
|
||||
'msg': "Modified BIOS attributes %s" % (attrs_to_patch),
|
||||
'warning': warning}
|
||||
return {'ret': True, 'changed': True, 'msg': "Modified BIOS attribute"}
|
||||
|
||||
def set_boot_order(self, boot_list):
|
||||
if not boot_list:
|
||||
|
||||
0
plugins/module_utils/remote_management/__init__.py
Normal file
0
plugins/module_utils/remote_management/__init__.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Dell EMC OpenManage Ansible Modules
|
||||
# Version 1.0
|
||||
# Copyright (C) 2018 Dell Inc.
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# All rights reserved. Dell, EMC, and other trademarks are trademarks of Dell Inc. or its subsidiaries.
|
||||
# Other trademarks may be trademarks of their respective owners.
|
||||
#
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
try:
|
||||
from omsdk.sdkinfra import sdkinfra
|
||||
from omsdk.sdkcreds import UserCredentials
|
||||
from omsdk.sdkfile import FileOnShare, file_share_manager
|
||||
from omsdk.sdkprotopref import ProtoPreference, ProtocolEnum
|
||||
from omsdk.http.sdkwsmanbase import WsManOptions
|
||||
HAS_OMSDK = True
|
||||
except ImportError:
|
||||
HAS_OMSDK = False
|
||||
|
||||
|
||||
class iDRACConnection:
|
||||
|
||||
def __init__(self, module_params):
|
||||
if not HAS_OMSDK:
|
||||
raise ImportError("Dell EMC OMSDK library is required for this module")
|
||||
self.idrac_ip = module_params['idrac_ip']
|
||||
self.idrac_user = module_params['idrac_user']
|
||||
self.idrac_pwd = module_params['idrac_password']
|
||||
self.idrac_port = module_params['idrac_port']
|
||||
if not all((self.idrac_ip, self.idrac_user, self.idrac_pwd)):
|
||||
raise ValueError("hostname, username and password required")
|
||||
self.handle = None
|
||||
self.creds = UserCredentials(self.idrac_user, self.idrac_pwd)
|
||||
self.pOp = WsManOptions(port=self.idrac_port)
|
||||
self.sdk = sdkinfra()
|
||||
if self.sdk is None:
|
||||
msg = "Could not initialize iDRAC drivers."
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def __enter__(self):
|
||||
self.sdk.importPath()
|
||||
self.handle = self.sdk.get_driver(self.sdk.driver_enum.iDRAC, self.idrac_ip, self.creds, pOptions=self.pOp)
|
||||
if self.handle is None:
|
||||
msg = "Could not find device driver for iDRAC with IP Address: {0}".format(self.idrac_ip)
|
||||
raise RuntimeError(msg)
|
||||
return self.handle
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.handle.disconnect()
|
||||
return False
|
||||
163
plugins/module_utils/remote_management/dellemc/ome.py
Normal file
163
plugins/module_utils/remote_management/dellemc/ome.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Dell EMC OpenManage Ansible Modules
|
||||
# Version 1.3
|
||||
# Copyright (C) 2019 Dell Inc. or its subsidiaries. All Rights Reserved.
|
||||
#
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
from ansible.module_utils.urls import open_url, ConnectionError, SSLValidationError
|
||||
from ansible.module_utils.six.moves.urllib.error import URLError, HTTPError
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
|
||||
SESSION_RESOURCE_COLLECTION = {
|
||||
"SESSION": "SessionService/Sessions",
|
||||
"SESSION_ID": "SessionService/Sessions('{Id}')",
|
||||
}
|
||||
|
||||
|
||||
class OpenURLResponse(object):
|
||||
"""Handles HTTPResponse"""
|
||||
|
||||
def __init__(self, resp):
|
||||
self.body = None
|
||||
self.resp = resp
|
||||
if self.resp:
|
||||
self.body = self.resp.read()
|
||||
|
||||
@property
|
||||
def json_data(self):
|
||||
try:
|
||||
return json.loads(self.body)
|
||||
except ValueError:
|
||||
raise ValueError("Unable to parse json")
|
||||
|
||||
@property
|
||||
def status_code(self):
|
||||
return self.resp.getcode()
|
||||
|
||||
@property
|
||||
def success(self):
|
||||
return self.status_code in (200, 201, 202, 204)
|
||||
|
||||
@property
|
||||
def token_header(self):
|
||||
return self.resp.headers.get('X-Auth-Token')
|
||||
|
||||
|
||||
class RestOME(object):
|
||||
"""Handles OME API requests"""
|
||||
|
||||
def __init__(self, module_params=None, req_session=False):
|
||||
self.module_params = module_params
|
||||
self.hostname = self.module_params["hostname"]
|
||||
self.username = self.module_params["username"]
|
||||
self.password = self.module_params["password"]
|
||||
self.port = self.module_params["port"]
|
||||
self.req_session = req_session
|
||||
self.session_id = None
|
||||
self.protocol = 'https'
|
||||
self._headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
|
||||
|
||||
def _get_base_url(self):
|
||||
"""builds base url"""
|
||||
return '{0}://{1}:{2}/api'.format(self.protocol, self.hostname, self.port)
|
||||
|
||||
def _build_url(self, path, query_param=None):
|
||||
"""builds complete url"""
|
||||
url = path
|
||||
base_uri = self._get_base_url()
|
||||
if path:
|
||||
url = '{0}/{1}'.format(base_uri, path)
|
||||
if query_param:
|
||||
url += "?{0}".format(urlencode(query_param))
|
||||
return url
|
||||
|
||||
def _url_common_args_spec(self, method, api_timeout, headers=None):
|
||||
"""Creates an argument common spec"""
|
||||
req_header = self._headers
|
||||
if headers:
|
||||
req_header.update(headers)
|
||||
url_kwargs = {
|
||||
"method": method,
|
||||
"validate_certs": False,
|
||||
"use_proxy": True,
|
||||
"headers": req_header,
|
||||
"timeout": api_timeout,
|
||||
"follow_redirects": 'all',
|
||||
}
|
||||
return url_kwargs
|
||||
|
||||
def _args_without_session(self, method, api_timeout=30, headers=None):
|
||||
"""Creates an argument spec in case of basic authentication"""
|
||||
req_header = self._headers
|
||||
if headers:
|
||||
req_header.update(headers)
|
||||
url_kwargs = self._url_common_args_spec(method, api_timeout, headers=headers)
|
||||
url_kwargs["url_username"] = self.username
|
||||
url_kwargs["url_password"] = self.password
|
||||
url_kwargs["force_basic_auth"] = True
|
||||
return url_kwargs
|
||||
|
||||
def _args_with_session(self, method, api_timeout=30, headers=None):
|
||||
"""Creates an argument spec, in case of authentication with session"""
|
||||
url_kwargs = self._url_common_args_spec(method, api_timeout, headers=headers)
|
||||
url_kwargs["force_basic_auth"] = False
|
||||
return url_kwargs
|
||||
|
||||
def invoke_request(self, method, path, data=None, query_param=None, headers=None,
|
||||
api_timeout=30, dump=True):
|
||||
"""
|
||||
Sends a request via open_url
|
||||
Returns :class:`OpenURLResponse` object.
|
||||
:arg method: HTTP verb to use for the request
|
||||
:arg path: path to request without query parameter
|
||||
:arg data: (optional) Payload to send with the request
|
||||
:arg query_param: (optional) Dictionary of query parameter to send with request
|
||||
:arg headers: (optional) Dictionary of HTTP Headers to send with the
|
||||
request
|
||||
:arg api_timeout: (optional) How long to wait for the server to send
|
||||
data before giving up
|
||||
:arg dump: (Optional) boolean value for dumping payload data.
|
||||
:returns: OpenURLResponse
|
||||
"""
|
||||
try:
|
||||
if 'X-Auth-Token' in self._headers:
|
||||
url_kwargs = self._args_with_session(method, api_timeout, headers=headers)
|
||||
else:
|
||||
url_kwargs = self._args_without_session(method, api_timeout, headers=headers)
|
||||
if data and dump:
|
||||
data = json.dumps(data)
|
||||
url = self._build_url(path, query_param=query_param)
|
||||
resp = open_url(url, data=data, **url_kwargs)
|
||||
resp_data = OpenURLResponse(resp)
|
||||
except (HTTPError, URLError, SSLValidationError, ConnectionError) as err:
|
||||
raise err
|
||||
return resp_data
|
||||
|
||||
def __enter__(self):
|
||||
"""Creates sessions by passing it to header"""
|
||||
if self.req_session:
|
||||
payload = {'UserName': self.username,
|
||||
'Password': self.password,
|
||||
'SessionType': 'API', }
|
||||
path = SESSION_RESOURCE_COLLECTION["SESSION"]
|
||||
resp = self.invoke_request('POST', path, data=payload)
|
||||
if resp and resp.success:
|
||||
self.session_id = resp.json_data.get("Id")
|
||||
self._headers["X-Auth-Token"] = resp.token_header
|
||||
else:
|
||||
msg = "Could not create the session"
|
||||
raise ConnectionError(msg)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
"""Deletes a session id, which is in use for request"""
|
||||
if self.session_id:
|
||||
path = SESSION_RESOURCE_COLLECTION["SESSION_ID"].format(Id=self.session_id)
|
||||
self.invoke_request('DELETE', path)
|
||||
return False
|
||||
0
plugins/module_utils/source_control/__init__.py
Normal file
0
plugins/module_utils/source_control/__init__.py
Normal file
0
plugins/module_utils/storage/__init__.py
Normal file
0
plugins/module_utils/storage/__init__.py
Normal file
0
plugins/module_utils/storage/emc/__init__.py
Normal file
0
plugins/module_utils/storage/emc/__init__.py
Normal file
0
plugins/module_utils/storage/hpe3par/__init__.py
Normal file
0
plugins/module_utils/storage/hpe3par/__init__.py
Normal file
0
plugins/modules/__init__.py
Normal file
0
plugins/modules/__init__.py
Normal file
1
plugins/modules/ali_instance_facts.py
Symbolic link
1
plugins/modules/ali_instance_facts.py
Symbolic link
@@ -0,0 +1 @@
|
||||
cloud/alicloud/ali_instance_facts.py
|
||||
1
plugins/modules/cloud/alicloud/ali_instance_facts.py
Symbolic link
1
plugins/modules/cloud/alicloud/ali_instance_facts.py
Symbolic link
@@ -0,0 +1 @@
|
||||
ali_instance_info.py
|
||||
@@ -383,6 +383,9 @@ def main():
|
||||
)
|
||||
)
|
||||
module = AnsibleModule(argument_spec=argument_spec)
|
||||
if module._name in ('ali_instance_facts', 'community.general.ali_instance_facts'):
|
||||
module.deprecate("The 'ali_instance_facts' module has been renamed to 'ali_instance_info'",
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.13
|
||||
|
||||
if HAS_FOOTMARK is False:
|
||||
module.fail_json(msg=missing_required_lib('footmark'), exception=FOOTMARK_IMP_ERR)
|
||||
|
||||
@@ -30,6 +30,10 @@ options:
|
||||
required: False
|
||||
default: present
|
||||
choices: ['present','absent']
|
||||
wait:
|
||||
description:
|
||||
- This option does nothing and will be removed in community.general 3.0.0.
|
||||
type: bool
|
||||
requirements:
|
||||
- python = 2.7
|
||||
- requests >= 2.5.0
|
||||
@@ -181,6 +185,7 @@ class ClcAntiAffinityPolicy:
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
location=dict(required=True),
|
||||
wait=dict(type='bool', removed_in_version='3.0.0', removed_from_collection='community.general'), # was Ansible 2.14
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
)
|
||||
return argument_spec
|
||||
|
||||
@@ -21,10 +21,8 @@ options:
|
||||
type: str
|
||||
api_key:
|
||||
description:
|
||||
- Linode API key.
|
||||
- C(LINODE_API_KEY) env variable can be used instead.
|
||||
- Linode API key
|
||||
type: str
|
||||
required: yes
|
||||
name:
|
||||
description:
|
||||
- Name to give the instance (alphanumeric, dashes, underscore).
|
||||
@@ -48,7 +46,6 @@ options:
|
||||
- List of dictionaries for creating additional disks that are added to the Linode configuration settings.
|
||||
- Dictionary takes Size, Label, Type. Size is in MB.
|
||||
type: list
|
||||
elements: dict
|
||||
alert_bwin_enabled:
|
||||
description:
|
||||
- Set status of bandwidth in alerts.
|
||||
@@ -89,18 +86,9 @@ options:
|
||||
description:
|
||||
- Set threshold for average IO ops/sec over 2 hour period.
|
||||
type: int
|
||||
backupsenabled:
|
||||
description:
|
||||
- Deprecated parameter, it will be removed in community.general C(5.0.0).
|
||||
- To enable backups pass values to either I(backupweeklyday) or I(backupwindow).
|
||||
type: int
|
||||
backupweeklyday:
|
||||
description:
|
||||
- Day of the week to take backups.
|
||||
type: int
|
||||
backupwindow:
|
||||
description:
|
||||
- The time window in which backups will be taken.
|
||||
- Integer value for what day of the week to store weekly backups.
|
||||
type: int
|
||||
plan:
|
||||
description:
|
||||
@@ -165,6 +153,7 @@ author:
|
||||
notes:
|
||||
- Please note, linode-python does not have python 3 support.
|
||||
- This module uses the now deprecated v3 of the Linode API.
|
||||
- C(LINODE_API_KEY) env variable can be used instead.
|
||||
- Please review U(https://www.linode.com/api/linode) for determining the required parameters.
|
||||
'''
|
||||
|
||||
@@ -273,6 +262,7 @@ EXAMPLES = '''
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
|
||||
@@ -284,7 +274,7 @@ except ImportError:
|
||||
LINODE_IMP_ERR = traceback.format_exc()
|
||||
HAS_LINODE = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
def randompass():
|
||||
@@ -368,7 +358,7 @@ def linodeServers(module, api, state, name,
|
||||
if not servers:
|
||||
for arg in (name, plan, distribution, datacenter):
|
||||
if not arg:
|
||||
module.fail_json(msg='%s is required for %s state' % (arg, state))
|
||||
module.fail_json(msg='%s is required for %s state' % (arg, state)) # @TODO use required_if instead
|
||||
# Create linode entity
|
||||
new_server = True
|
||||
|
||||
@@ -393,7 +383,7 @@ def linodeServers(module, api, state, name,
|
||||
try:
|
||||
res = api.linode_ip_addprivate(LinodeID=linode_id)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'])
|
||||
|
||||
if not disks:
|
||||
for arg in (name, linode_id, distribution):
|
||||
@@ -438,7 +428,7 @@ def linodeServers(module, api, state, name,
|
||||
jobs.append(res['JobID'])
|
||||
except Exception as e:
|
||||
# TODO: destroy linode ?
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'])
|
||||
|
||||
if not configs:
|
||||
for arg in (name, linode_id, distribution):
|
||||
@@ -481,7 +471,7 @@ def linodeServers(module, api, state, name,
|
||||
Disklist=disks_list, Label='%s config' % name)
|
||||
configs = api.linode_config_list(LinodeId=linode_id)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'])
|
||||
|
||||
# Start / Ensure servers are running
|
||||
for server in servers:
|
||||
@@ -527,7 +517,10 @@ def linodeServers(module, api, state, name,
|
||||
instance['password'] = password
|
||||
instances.append(instance)
|
||||
|
||||
elif state in ('stopped',):
|
||||
elif state in ('stopped'):
|
||||
if not linode_id:
|
||||
module.fail_json(msg='linode_id is required for stopped state')
|
||||
|
||||
if not servers:
|
||||
module.fail_json(msg='Server (lid: %s) not found' % (linode_id))
|
||||
|
||||
@@ -537,14 +530,17 @@ def linodeServers(module, api, state, name,
|
||||
try:
|
||||
res = api.linode_shutdown(LinodeId=linode_id)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'])
|
||||
instance['status'] = 'Stopping'
|
||||
changed = True
|
||||
else:
|
||||
instance['status'] = 'Stopped'
|
||||
instances.append(instance)
|
||||
|
||||
elif state in ('restarted',):
|
||||
elif state in ('restarted'):
|
||||
if not linode_id:
|
||||
module.fail_json(msg='linode_id is required for restarted state')
|
||||
|
||||
if not servers:
|
||||
module.fail_json(msg='Server (lid: %s) not found' % (linode_id))
|
||||
|
||||
@@ -553,7 +549,7 @@ def linodeServers(module, api, state, name,
|
||||
try:
|
||||
res = api.linode_reboot(LinodeId=server['LINODEID'])
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'])
|
||||
instance['status'] = 'Restarting'
|
||||
changed = True
|
||||
instances.append(instance)
|
||||
@@ -564,7 +560,7 @@ def linodeServers(module, api, state, name,
|
||||
try:
|
||||
api.linode_delete(LinodeId=server['LINODEID'], skipChecks=True)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'])
|
||||
instance['status'] = 'Deleting'
|
||||
changed = True
|
||||
instances.append(instance)
|
||||
@@ -581,7 +577,7 @@ def main():
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', default='present',
|
||||
choices=['absent', 'active', 'deleted', 'present', 'restarted', 'started', 'stopped']),
|
||||
api_key=dict(type='str', no_log=True, required=True, fallback=(env_fallback, ['LINODE_API_KEY'])),
|
||||
api_key=dict(type='str', no_log=True),
|
||||
name=dict(type='str', required=True),
|
||||
alert_bwin_enabled=dict(type='bool'),
|
||||
alert_bwin_threshold=dict(type='int'),
|
||||
@@ -593,12 +589,12 @@ def main():
|
||||
alert_cpu_threshold=dict(type='int'),
|
||||
alert_diskio_enabled=dict(type='bool'),
|
||||
alert_diskio_threshold=dict(type='int'),
|
||||
backupsenabled=dict(type='int', removed_in_version='5.0.0', removed_from_collection='community.general'),
|
||||
backupsenabled=dict(type='int'),
|
||||
backupweeklyday=dict(type='int'),
|
||||
backupwindow=dict(type='int'),
|
||||
displaygroup=dict(type='str', default=''),
|
||||
plan=dict(type='int'),
|
||||
additional_disks=dict(type='list', elements='dict'),
|
||||
additional_disks=dict(type='list'),
|
||||
distribution=dict(type='int'),
|
||||
datacenter=dict(type='int'),
|
||||
kernel_id=dict(type='int'),
|
||||
@@ -612,10 +608,6 @@ def main():
|
||||
wait_timeout=dict(type='int', default=300),
|
||||
watchdog=dict(type='bool', default=True),
|
||||
),
|
||||
required_if=[
|
||||
('state', 'restarted', ['linode_id']),
|
||||
('state', 'stopped', ['linode_id']),
|
||||
]
|
||||
)
|
||||
|
||||
if not HAS_LINODE:
|
||||
@@ -634,6 +626,7 @@ def main():
|
||||
alert_cpu_threshold = module.params.get('alert_cpu_threshold')
|
||||
alert_diskio_enabled = module.params.get('alert_diskio_enabled')
|
||||
alert_diskio_threshold = module.params.get('alert_diskio_threshold')
|
||||
backupsenabled = module.params.get('backupsenabled')
|
||||
backupweeklyday = module.params.get('backupweeklyday')
|
||||
backupwindow = module.params.get('backupwindow')
|
||||
displaygroup = module.params.get('displaygroup')
|
||||
@@ -649,9 +642,10 @@ def main():
|
||||
ssh_pub_key = module.params.get('ssh_pub_key')
|
||||
swap = module.params.get('swap')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
wait_timeout = int(module.params.get('wait_timeout'))
|
||||
watchdog = int(module.params.get('watchdog'))
|
||||
|
||||
kwargs = dict()
|
||||
check_items = dict(
|
||||
alert_bwin_enabled=alert_bwin_enabled,
|
||||
alert_bwin_threshold=alert_bwin_threshold,
|
||||
@@ -667,14 +661,23 @@ def main():
|
||||
backupwindow=backupwindow,
|
||||
)
|
||||
|
||||
kwargs = dict((k, v) for k, v in check_items.items() if v is not None)
|
||||
for key, value in check_items.items():
|
||||
if value is not None:
|
||||
kwargs[key] = value
|
||||
|
||||
# Setup the api_key
|
||||
if not api_key:
|
||||
try:
|
||||
api_key = os.environ['LINODE_API_KEY']
|
||||
except KeyError as e:
|
||||
module.fail_json(msg='Unable to load %s' % e.message)
|
||||
|
||||
# setup the auth
|
||||
try:
|
||||
api = linode_api.Api(api_key)
|
||||
api.test_echo()
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'])
|
||||
|
||||
linodeServers(module, api, state, name,
|
||||
displaygroup, plan,
|
||||
|
||||
@@ -53,13 +53,6 @@ options:
|
||||
group labelling is deprecated but still supported. The encouraged
|
||||
method for marking instances is to use tags.
|
||||
type: str
|
||||
private_ip:
|
||||
description:
|
||||
- If C(true), the created Linode will have private networking enabled and
|
||||
assigned a private IPv4 address.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 3.0.0
|
||||
tags:
|
||||
description:
|
||||
- The tags that the instance should be marked under. See
|
||||
@@ -244,7 +237,6 @@ def initialise_module():
|
||||
authorized_keys=dict(type='list', elements='str', no_log=False),
|
||||
group=dict(type='str'),
|
||||
image=dict(type='str'),
|
||||
private_ip=dict(type='bool', default=False),
|
||||
region=dict(type='str'),
|
||||
root_pass=dict(type='str', no_log=True),
|
||||
tags=dict(type='list', elements='str'),
|
||||
@@ -290,7 +282,6 @@ def main():
|
||||
group=module.params['group'],
|
||||
image=module.params['image'],
|
||||
label=module.params['label'],
|
||||
private_ip=module.params['private_ip'],
|
||||
region=module.params['region'],
|
||||
root_pass=module.params['root_pass'],
|
||||
tags=module.params['tags'],
|
||||
|
||||
1
plugins/modules/cloud/memset/memset_memstore_facts.py
Symbolic link
1
plugins/modules/cloud/memset/memset_memstore_facts.py
Symbolic link
@@ -0,0 +1 @@
|
||||
memset_memstore_info.py
|
||||
@@ -151,6 +151,9 @@ def main():
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
if module._name in ('memset_memstore_facts', 'community.general.memset_memstore_facts'):
|
||||
module.deprecate("The 'memset_memstore_facts' module has been renamed to 'memset_memstore_info'",
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.13
|
||||
|
||||
# populate the dict with the user-provided vars.
|
||||
args = dict()
|
||||
|
||||
1
plugins/modules/cloud/memset/memset_server_facts.py
Symbolic link
1
plugins/modules/cloud/memset/memset_server_facts.py
Symbolic link
@@ -0,0 +1 @@
|
||||
memset_server_info.py
|
||||
@@ -276,6 +276,9 @@ def main():
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
if module._name in ('memset_server_facts', 'community.general.memset_server_facts'):
|
||||
module.deprecate("The 'memset_server_facts' module has been renamed to 'memset_server_info'",
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.13
|
||||
|
||||
# populate the dict with the user-provided vars.
|
||||
args = dict()
|
||||
|
||||
@@ -88,7 +88,7 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
|
||||
CLOUD_INIT_PATH = "/var/lib/cloud/data"
|
||||
CLOUD_INIT_PATH = "/var/lib/cloud/data/"
|
||||
|
||||
|
||||
def gather_cloud_init_data_facts(module):
|
||||
@@ -100,7 +100,7 @@ def gather_cloud_init_data_facts(module):
|
||||
filter = module.params.get('filter')
|
||||
if filter is None or filter == i:
|
||||
res['cloud_init_data_facts'][i] = dict()
|
||||
json_file = os.path.join(CLOUD_INIT_PATH, i + '.json')
|
||||
json_file = CLOUD_INIT_PATH + i + '.json'
|
||||
|
||||
if os.path.exists(json_file):
|
||||
f = open(json_file, 'rb')
|
||||
|
||||
216
plugins/modules/cloud/misc/helm.py
Normal file
216
plugins/modules/cloud/misc/helm.py
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/python
|
||||
# (c) 2016, Flavio Percoco <flavio@redhat.com>
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
deprecated:
|
||||
removed_in: 3.0.0 # was Ansible 2.14
|
||||
why: For more details https://github.com/ansible/ansible/issues/61546.
|
||||
alternative: Use M(community.kubernetes.helm) instead.
|
||||
module: helm
|
||||
short_description: Manages Kubernetes packages with the Helm package manager
|
||||
author: "Flavio Percoco (@flaper87)"
|
||||
description:
|
||||
- Install, upgrade, delete and list packages with the Helm package manager.
|
||||
requirements:
|
||||
- "pyhelm"
|
||||
- "grpcio"
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- Tiller's server host.
|
||||
type: str
|
||||
default: "localhost"
|
||||
port:
|
||||
description:
|
||||
- Tiller's server port.
|
||||
type: int
|
||||
default: 44134
|
||||
namespace:
|
||||
description:
|
||||
- Kubernetes namespace where the chart should be installed.
|
||||
type: str
|
||||
default: "default"
|
||||
name:
|
||||
description:
|
||||
- Release name to manage.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether to install C(present), remove C(absent), or purge C(purged) a package.
|
||||
choices: ['absent', 'purged', 'present']
|
||||
type: str
|
||||
default: "present"
|
||||
chart:
|
||||
description:
|
||||
- A map describing the chart to install. See examples for available options.
|
||||
type: dict
|
||||
default: {}
|
||||
values:
|
||||
description:
|
||||
- A map of value options for the chart.
|
||||
type: dict
|
||||
default: {}
|
||||
disable_hooks:
|
||||
description:
|
||||
- Whether to disable hooks during the uninstall process.
|
||||
type: bool
|
||||
default: 'no'
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Install helm chart
|
||||
community.general.helm:
|
||||
host: localhost
|
||||
chart:
|
||||
name: memcached
|
||||
version: 0.4.0
|
||||
source:
|
||||
type: repo
|
||||
location: https://kubernetes-charts.storage.googleapis.com
|
||||
state: present
|
||||
name: my-memcached
|
||||
namespace: default
|
||||
|
||||
- name: Uninstall helm chart
|
||||
community.general.helm:
|
||||
host: localhost
|
||||
state: absent
|
||||
name: my-memcached
|
||||
|
||||
- name: Install helm chart from a git repo
|
||||
community.general.helm:
|
||||
host: localhost
|
||||
chart:
|
||||
source:
|
||||
type: git
|
||||
location: https://github.com/user/helm-chart.git
|
||||
state: present
|
||||
name: my-example
|
||||
namespace: default
|
||||
values:
|
||||
foo: "bar"
|
||||
|
||||
- name: Install helm chart from a git repo specifying path
|
||||
community.general.helm:
|
||||
host: localhost
|
||||
chart:
|
||||
source:
|
||||
type: git
|
||||
location: https://github.com/helm/charts.git
|
||||
path: stable/memcached
|
||||
state: present
|
||||
name: my-memcached
|
||||
namespace: default
|
||||
values: "{{ lookup('file', '/path/to/file/values.yaml') | from_yaml }}"
|
||||
'''
|
||||
|
||||
import traceback
|
||||
HELM_IMPORT_ERR = None
|
||||
try:
|
||||
import grpc
|
||||
from pyhelm import tiller
|
||||
from pyhelm import chartbuilder
|
||||
except ImportError:
|
||||
HELM_IMPORT_ERR = traceback.format_exc()
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
def install(module, tserver):
|
||||
changed = False
|
||||
params = module.params
|
||||
name = params['name']
|
||||
values = params['values']
|
||||
chart = module.params['chart']
|
||||
namespace = module.params['namespace']
|
||||
|
||||
chartb = chartbuilder.ChartBuilder(chart)
|
||||
r_matches = (x for x in tserver.list_releases()
|
||||
if x.name == name and x.namespace == namespace)
|
||||
installed_release = next(r_matches, None)
|
||||
if installed_release:
|
||||
if installed_release.chart.metadata.version != chart['version']:
|
||||
tserver.update_release(chartb.get_helm_chart(), False,
|
||||
namespace, name=name, values=values)
|
||||
changed = True
|
||||
else:
|
||||
tserver.install_release(chartb.get_helm_chart(), namespace,
|
||||
dry_run=False, name=name,
|
||||
values=values)
|
||||
changed = True
|
||||
|
||||
return dict(changed=changed)
|
||||
|
||||
|
||||
def delete(module, tserver, purge=False):
|
||||
changed = False
|
||||
params = module.params
|
||||
|
||||
if not module.params['name']:
|
||||
module.fail_json(msg='Missing required field name')
|
||||
|
||||
name = module.params['name']
|
||||
disable_hooks = params['disable_hooks']
|
||||
|
||||
try:
|
||||
tserver.uninstall_release(name, disable_hooks, purge)
|
||||
changed = True
|
||||
except grpc._channel._Rendezvous as exc:
|
||||
if 'not found' not in str(exc):
|
||||
raise exc
|
||||
|
||||
return dict(changed=changed)
|
||||
|
||||
|
||||
def main():
|
||||
"""The main function."""
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
host=dict(type='str', default='localhost'),
|
||||
port=dict(type='int', default=44134),
|
||||
name=dict(type='str', default=''),
|
||||
chart=dict(type='dict'),
|
||||
state=dict(
|
||||
choices=['absent', 'purged', 'present'],
|
||||
default='present'
|
||||
),
|
||||
# Install options
|
||||
values=dict(type='dict'),
|
||||
namespace=dict(type='str', default='default'),
|
||||
|
||||
# Uninstall options
|
||||
disable_hooks=dict(type='bool', default=False),
|
||||
),
|
||||
supports_check_mode=True)
|
||||
|
||||
if HELM_IMPORT_ERR:
|
||||
module.fail_json(msg=missing_required_lib('pyhelm'), exception=HELM_IMPORT_ERR)
|
||||
|
||||
host = module.params['host']
|
||||
port = module.params['port']
|
||||
state = module.params['state']
|
||||
tserver = tiller.Tiller(host, port)
|
||||
|
||||
if state == 'present':
|
||||
rst = install(module, tserver)
|
||||
|
||||
if state in 'absent':
|
||||
rst = delete(module, tserver)
|
||||
|
||||
if state in 'purged':
|
||||
rst = delete(module, tserver, True)
|
||||
|
||||
module.exit_json(**rst)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
502
plugins/modules/cloud/misc/ovirt.py
Normal file
502
plugins/modules/cloud/misc/ovirt.py
Normal file
@@ -0,0 +1,502 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2013, Vincent Van der Kussen <vincent at vanderkussen.org>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ovirt
|
||||
author:
|
||||
- Vincent Van der Kussen (@vincentvdk)
|
||||
short_description: oVirt/RHEV platform management
|
||||
deprecated:
|
||||
removed_in: 3.0.0 # was Ansible 2.14
|
||||
why: This module is for deprecated version of ovirt.
|
||||
alternative: Use C(ovirt_vm) from the C(ovirt.ovirt) collection instead
|
||||
description:
|
||||
- This module only supports oVirt/RHEV version 3. A newer module M(ovirt.ovirt.ovirt_vm) supports oVirt/RHV version 4.
|
||||
- Allows you to create new instances, either from scratch or an image, in addition to deleting or stopping instances on the oVirt/RHEV platform.
|
||||
options:
|
||||
user:
|
||||
description:
|
||||
- The user to authenticate with.
|
||||
type: str
|
||||
required: true
|
||||
url:
|
||||
description:
|
||||
- The url of the oVirt instance.
|
||||
type: str
|
||||
required: true
|
||||
instance_name:
|
||||
description:
|
||||
- The name of the instance to use.
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ vmname ]
|
||||
password:
|
||||
description:
|
||||
- Password of the user to authenticate with.
|
||||
type: str
|
||||
required: true
|
||||
image:
|
||||
description:
|
||||
- The template to use for the instance.
|
||||
type: str
|
||||
resource_type:
|
||||
description:
|
||||
- Whether you want to deploy an image or create an instance from scratch.
|
||||
type: str
|
||||
choices: [ new, template ]
|
||||
zone:
|
||||
description:
|
||||
- Deploy the image to this oVirt cluster.
|
||||
type: str
|
||||
instance_disksize:
|
||||
description:
|
||||
- Size of the instance's disk in GB.
|
||||
type: str
|
||||
aliases: [ vm_disksize]
|
||||
instance_cpus:
|
||||
description:
|
||||
- The instance's number of CPUs.
|
||||
type: str
|
||||
default: 1
|
||||
aliases: [ vmcpus ]
|
||||
instance_nic:
|
||||
description:
|
||||
- The name of the network interface in oVirt/RHEV.
|
||||
type: str
|
||||
aliases: [ vmnic ]
|
||||
instance_network:
|
||||
description:
|
||||
- The logical network the machine should belong to.
|
||||
type: str
|
||||
default: rhevm
|
||||
aliases: [ vmnetwork ]
|
||||
instance_mem:
|
||||
description:
|
||||
- The instance's amount of memory in MB.
|
||||
type: str
|
||||
aliases: [ vmmem ]
|
||||
instance_type:
|
||||
description:
|
||||
- Define whether the instance is a server, desktop or high_performance.
|
||||
- I(high_performance) is supported since Ansible 2.5 and oVirt/RHV 4.2.
|
||||
type: str
|
||||
choices: [ desktop, server, high_performance ]
|
||||
default: server
|
||||
aliases: [ vmtype ]
|
||||
disk_alloc:
|
||||
description:
|
||||
- Define whether disk is thin or preallocated.
|
||||
type: str
|
||||
choices: [ preallocated, thin ]
|
||||
default: thin
|
||||
disk_int:
|
||||
description:
|
||||
- Interface type of the disk.
|
||||
type: str
|
||||
choices: [ ide, virtio ]
|
||||
default: virtio
|
||||
instance_os:
|
||||
description:
|
||||
- Type of Operating System.
|
||||
type: str
|
||||
aliases: [ vmos ]
|
||||
instance_cores:
|
||||
description:
|
||||
- Define the instance's number of cores.
|
||||
type: str
|
||||
default: 1
|
||||
aliases: [ vmcores ]
|
||||
sdomain:
|
||||
description:
|
||||
- The Storage Domain where you want to create the instance's disk on.
|
||||
type: str
|
||||
region:
|
||||
description:
|
||||
- The oVirt/RHEV datacenter where you want to deploy to.
|
||||
type: str
|
||||
instance_dns:
|
||||
description:
|
||||
- Define the instance's Primary DNS server.
|
||||
type: str
|
||||
aliases: [ dns ]
|
||||
instance_domain:
|
||||
description:
|
||||
- Define the instance's Domain.
|
||||
type: str
|
||||
aliases: [ domain ]
|
||||
instance_hostname:
|
||||
description:
|
||||
- Define the instance's Hostname.
|
||||
type: str
|
||||
aliases: [ hostname ]
|
||||
instance_ip:
|
||||
description:
|
||||
- Define the instance's IP.
|
||||
type: str
|
||||
aliases: [ ip ]
|
||||
instance_netmask:
|
||||
description:
|
||||
- Define the instance's Netmask.
|
||||
type: str
|
||||
aliases: [ netmask ]
|
||||
instance_gateway:
|
||||
description:
|
||||
- Define the instance's Gateway.
|
||||
type: str
|
||||
aliases: [ gateway ]
|
||||
instance_rootpw:
|
||||
description:
|
||||
- Define the instance's Root password.
|
||||
type: str
|
||||
aliases: [ rootpw ]
|
||||
instance_key:
|
||||
description:
|
||||
- Define the instance's Authorized key.
|
||||
type: str
|
||||
aliases: [ key ]
|
||||
state:
|
||||
description:
|
||||
- Create, terminate or remove instances.
|
||||
type: str
|
||||
choices: [ absent, present, restart, shutdown, started ]
|
||||
default: present
|
||||
requirements:
|
||||
- ovirt-engine-sdk-python
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Basic example to provision from image
|
||||
community.general.ovirt:
|
||||
user: admin@internal
|
||||
url: https://ovirt.example.com
|
||||
instance_name: ansiblevm04
|
||||
password: secret
|
||||
image: centos_64
|
||||
zone: cluster01
|
||||
resource_type: template
|
||||
|
||||
- name: Full example to create new instance from scratch
|
||||
community.general.ovirt:
|
||||
instance_name: testansible
|
||||
resource_type: new
|
||||
instance_type: server
|
||||
user: admin@internal
|
||||
password: secret
|
||||
url: https://ovirt.example.com
|
||||
instance_disksize: 10
|
||||
zone: cluster01
|
||||
region: datacenter1
|
||||
instance_cpus: 1
|
||||
instance_nic: nic1
|
||||
instance_network: rhevm
|
||||
instance_mem: 1000
|
||||
disk_alloc: thin
|
||||
sdomain: FIBER01
|
||||
instance_cores: 1
|
||||
instance_os: rhel_6x64
|
||||
disk_int: virtio
|
||||
|
||||
- name: Stopping an existing instance
|
||||
community.general.ovirt:
|
||||
instance_name: testansible
|
||||
state: stopped
|
||||
user: admin@internal
|
||||
password: secret
|
||||
url: https://ovirt.example.com
|
||||
|
||||
- name: Start an existing instance
|
||||
community.general.ovirt:
|
||||
instance_name: testansible
|
||||
state: started
|
||||
user: admin@internal
|
||||
password: secret
|
||||
url: https://ovirt.example.com
|
||||
|
||||
- name: Start an instance with cloud init information
|
||||
community.general.ovirt:
|
||||
instance_name: testansible
|
||||
state: started
|
||||
user: admin@internal
|
||||
password: secret
|
||||
url: https://ovirt.example.com
|
||||
hostname: testansible
|
||||
domain: ansible.local
|
||||
ip: 192.0.2.100
|
||||
netmask: 255.255.255.0
|
||||
gateway: 192.0.2.1
|
||||
rootpw: bigsecret
|
||||
'''
|
||||
|
||||
import time
|
||||
|
||||
try:
|
||||
from ovirtsdk.api import API
|
||||
from ovirtsdk.xml import params
|
||||
HAS_OVIRTSDK = True
|
||||
except ImportError:
|
||||
HAS_OVIRTSDK = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- #
|
||||
# create connection with API
|
||||
#
|
||||
def conn(url, user, password):
|
||||
api = API(url=url, username=user, password=password, insecure=True)
|
||||
try:
|
||||
value = api.test()
|
||||
except Exception:
|
||||
raise Exception("error connecting to the oVirt API")
|
||||
return api
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- #
|
||||
# Create VM from scratch
|
||||
def create_vm(conn, vmtype, vmname, zone, vmdisk_size, vmcpus, vmnic, vmnetwork, vmmem, vmdisk_alloc, sdomain, vmcores, vmos, vmdisk_int):
|
||||
if vmdisk_alloc == 'thin':
|
||||
# define VM params
|
||||
vmparams = params.VM(name=vmname, cluster=conn.clusters.get(name=zone), os=params.OperatingSystem(type_=vmos),
|
||||
template=conn.templates.get(name="Blank"), memory=1024 * 1024 * int(vmmem),
|
||||
cpu=params.CPU(topology=params.CpuTopology(cores=int(vmcores), sockets=vmcpus)), type_=vmtype)
|
||||
# define disk params
|
||||
vmdisk = params.Disk(size=1024 * 1024 * 1024 * int(vmdisk_size), wipe_after_delete=True, sparse=True, interface=vmdisk_int, type_="System",
|
||||
format='cow',
|
||||
storage_domains=params.StorageDomains(storage_domain=[conn.storagedomains.get(name=sdomain)]))
|
||||
# define network parameters
|
||||
network_net = params.Network(name=vmnetwork)
|
||||
nic_net1 = params.NIC(name='nic1', network=network_net, interface='virtio')
|
||||
elif vmdisk_alloc == 'preallocated':
|
||||
# define VM params
|
||||
vmparams = params.VM(name=vmname, cluster=conn.clusters.get(name=zone), os=params.OperatingSystem(type_=vmos),
|
||||
template=conn.templates.get(name="Blank"), memory=1024 * 1024 * int(vmmem),
|
||||
cpu=params.CPU(topology=params.CpuTopology(cores=int(vmcores), sockets=vmcpus)), type_=vmtype)
|
||||
# define disk params
|
||||
vmdisk = params.Disk(size=1024 * 1024 * 1024 * int(vmdisk_size), wipe_after_delete=True, sparse=False, interface=vmdisk_int, type_="System",
|
||||
format='raw', storage_domains=params.StorageDomains(storage_domain=[conn.storagedomains.get(name=sdomain)]))
|
||||
# define network parameters
|
||||
network_net = params.Network(name=vmnetwork)
|
||||
nic_net1 = params.NIC(name=vmnic, network=network_net, interface='virtio')
|
||||
|
||||
try:
|
||||
conn.vms.add(vmparams)
|
||||
except Exception:
|
||||
raise Exception("Error creating VM with specified parameters")
|
||||
vm = conn.vms.get(name=vmname)
|
||||
try:
|
||||
vm.disks.add(vmdisk)
|
||||
except Exception:
|
||||
raise Exception("Error attaching disk")
|
||||
try:
|
||||
vm.nics.add(nic_net1)
|
||||
except Exception:
|
||||
raise Exception("Error adding nic")
|
||||
|
||||
|
||||
# create an instance from a template
|
||||
def create_vm_template(conn, vmname, image, zone):
|
||||
vmparams = params.VM(name=vmname, cluster=conn.clusters.get(name=zone), template=conn.templates.get(name=image), disks=params.Disks(clone=True))
|
||||
try:
|
||||
conn.vms.add(vmparams)
|
||||
except Exception:
|
||||
raise Exception('error adding template %s' % image)
|
||||
|
||||
|
||||
# start instance
|
||||
def vm_start(conn, vmname, hostname=None, ip=None, netmask=None, gateway=None,
|
||||
domain=None, dns=None, rootpw=None, key=None):
|
||||
vm = conn.vms.get(name=vmname)
|
||||
use_cloud_init = False
|
||||
nics = None
|
||||
nic = None
|
||||
if hostname or ip or netmask or gateway or domain or dns or rootpw or key:
|
||||
use_cloud_init = True
|
||||
if ip and netmask and gateway:
|
||||
ipinfo = params.IP(address=ip, netmask=netmask, gateway=gateway)
|
||||
nic = params.GuestNicConfiguration(name='eth0', boot_protocol='STATIC', ip=ipinfo, on_boot=True)
|
||||
nics = params.Nics()
|
||||
nics = params.GuestNicsConfiguration(nic_configuration=[nic])
|
||||
initialization = params.Initialization(regenerate_ssh_keys=True, host_name=hostname, domain=domain, user_name='root',
|
||||
root_password=rootpw, nic_configurations=nics, dns_servers=dns,
|
||||
authorized_ssh_keys=key)
|
||||
action = params.Action(use_cloud_init=use_cloud_init, vm=params.VM(initialization=initialization))
|
||||
vm.start(action=action)
|
||||
|
||||
|
||||
# Stop instance
|
||||
def vm_stop(conn, vmname):
|
||||
vm = conn.vms.get(name=vmname)
|
||||
vm.stop()
|
||||
|
||||
|
||||
# restart instance
|
||||
def vm_restart(conn, vmname):
|
||||
state = vm_status(conn, vmname)
|
||||
vm = conn.vms.get(name=vmname)
|
||||
vm.stop()
|
||||
while conn.vms.get(vmname).get_status().get_state() != 'down':
|
||||
time.sleep(5)
|
||||
vm.start()
|
||||
|
||||
|
||||
# remove an instance
|
||||
def vm_remove(conn, vmname):
|
||||
vm = conn.vms.get(name=vmname)
|
||||
vm.delete()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- #
|
||||
# VM statuses
|
||||
#
|
||||
# Get the VMs status
|
||||
def vm_status(conn, vmname):
|
||||
status = conn.vms.get(name=vmname).status.state
|
||||
return status
|
||||
|
||||
|
||||
# Get VM object and return it's name if object exists
|
||||
def get_vm(conn, vmname):
|
||||
vm = conn.vms.get(name=vmname)
|
||||
if vm is None:
|
||||
name = "empty"
|
||||
else:
|
||||
name = vm.get_name()
|
||||
return name
|
||||
|
||||
# ------------------------------------------------------------------- #
|
||||
# Hypervisor operations
|
||||
#
|
||||
# not available yet
|
||||
# ------------------------------------------------------------------- #
|
||||
# Main
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', default='present', choices=['absent', 'present', 'restart', 'shutdown', 'started']),
|
||||
user=dict(type='str', required=True),
|
||||
url=dict(type='str', required=True),
|
||||
instance_name=dict(type='str', required=True, aliases=['vmname']),
|
||||
password=dict(type='str', required=True, no_log=True),
|
||||
image=dict(type='str'),
|
||||
resource_type=dict(type='str', choices=['new', 'template']),
|
||||
zone=dict(type='str'),
|
||||
instance_disksize=dict(type='str', aliases=['vm_disksize']),
|
||||
instance_cpus=dict(type='str', default=1, aliases=['vmcpus']),
|
||||
instance_nic=dict(type='str', aliases=['vmnic']),
|
||||
instance_network=dict(type='str', default='rhevm', aliases=['vmnetwork']),
|
||||
instance_mem=dict(type='str', aliases=['vmmem']),
|
||||
instance_type=dict(type='str', default='server', aliases=['vmtype'], choices=['desktop', 'server', 'high_performance']),
|
||||
disk_alloc=dict(type='str', default='thin', choices=['preallocated', 'thin']),
|
||||
disk_int=dict(type='str', default='virtio', choices=['ide', 'virtio']),
|
||||
instance_os=dict(type='str', aliases=['vmos']),
|
||||
instance_cores=dict(type='str', default=1, aliases=['vmcores']),
|
||||
instance_hostname=dict(type='str', aliases=['hostname']),
|
||||
instance_ip=dict(type='str', aliases=['ip']),
|
||||
instance_netmask=dict(type='str', aliases=['netmask']),
|
||||
instance_gateway=dict(type='str', aliases=['gateway']),
|
||||
instance_domain=dict(type='str', aliases=['domain']),
|
||||
instance_dns=dict(type='str', aliases=['dns']),
|
||||
instance_rootpw=dict(type='str', aliases=['rootpw'], no_log=True),
|
||||
instance_key=dict(type='str', aliases=['key'], no_log=True),
|
||||
sdomain=dict(type='str'),
|
||||
region=dict(type='str'),
|
||||
),
|
||||
)
|
||||
|
||||
if not HAS_OVIRTSDK:
|
||||
module.fail_json(msg='ovirtsdk required for this module')
|
||||
|
||||
state = module.params['state']
|
||||
user = module.params['user']
|
||||
url = module.params['url']
|
||||
vmname = module.params['instance_name']
|
||||
password = module.params['password']
|
||||
image = module.params['image'] # name of the image to deploy
|
||||
resource_type = module.params['resource_type'] # template or from scratch
|
||||
zone = module.params['zone'] # oVirt cluster
|
||||
vmdisk_size = module.params['instance_disksize'] # disksize
|
||||
vmcpus = module.params['instance_cpus'] # number of cpu
|
||||
vmnic = module.params['instance_nic'] # network interface
|
||||
vmnetwork = module.params['instance_network'] # logical network
|
||||
vmmem = module.params['instance_mem'] # mem size
|
||||
vmdisk_alloc = module.params['disk_alloc'] # thin, preallocated
|
||||
vmdisk_int = module.params['disk_int'] # disk interface virtio or ide
|
||||
vmos = module.params['instance_os'] # Operating System
|
||||
vmtype = module.params['instance_type'] # server, desktop or high_performance
|
||||
vmcores = module.params['instance_cores'] # number of cores
|
||||
sdomain = module.params['sdomain'] # storage domain to store disk on
|
||||
region = module.params['region'] # oVirt Datacenter
|
||||
hostname = module.params['instance_hostname']
|
||||
ip = module.params['instance_ip']
|
||||
netmask = module.params['instance_netmask']
|
||||
gateway = module.params['instance_gateway']
|
||||
domain = module.params['instance_domain']
|
||||
dns = module.params['instance_dns']
|
||||
rootpw = module.params['instance_rootpw']
|
||||
key = module.params['instance_key']
|
||||
# initialize connection
|
||||
try:
|
||||
c = conn(url + "/api", user, password)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e)
|
||||
|
||||
if state == 'present':
|
||||
if get_vm(c, vmname) == "empty":
|
||||
if resource_type == 'template':
|
||||
try:
|
||||
create_vm_template(c, vmname, image, zone)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e)
|
||||
module.exit_json(changed=True, msg="deployed VM %s from template %s" % (vmname, image))
|
||||
elif resource_type == 'new':
|
||||
# FIXME: refactor, use keyword args.
|
||||
try:
|
||||
create_vm(c, vmtype, vmname, zone, vmdisk_size, vmcpus, vmnic, vmnetwork, vmmem, vmdisk_alloc, sdomain, vmcores, vmos, vmdisk_int)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='%s' % e)
|
||||
module.exit_json(changed=True, msg="deployed VM %s from scratch" % vmname)
|
||||
else:
|
||||
module.exit_json(changed=False, msg="You did not specify a resource type")
|
||||
else:
|
||||
module.exit_json(changed=False, msg="VM %s already exists" % vmname)
|
||||
|
||||
if state == 'started':
|
||||
if vm_status(c, vmname) == 'up':
|
||||
module.exit_json(changed=False, msg="VM %s is already running" % vmname)
|
||||
else:
|
||||
# vm_start(c, vmname)
|
||||
vm_start(c, vmname, hostname, ip, netmask, gateway, domain, dns, rootpw, key)
|
||||
module.exit_json(changed=True, msg="VM %s started" % vmname)
|
||||
|
||||
if state == 'shutdown':
|
||||
if vm_status(c, vmname) == 'down':
|
||||
module.exit_json(changed=False, msg="VM %s is already shutdown" % vmname)
|
||||
else:
|
||||
vm_stop(c, vmname)
|
||||
module.exit_json(changed=True, msg="VM %s is shutting down" % vmname)
|
||||
|
||||
if state == 'restart':
|
||||
if vm_status(c, vmname) == 'up':
|
||||
vm_restart(c, vmname)
|
||||
module.exit_json(changed=True, msg="VM %s is restarted" % vmname)
|
||||
else:
|
||||
module.exit_json(changed=False, msg="VM %s is not running" % vmname)
|
||||
|
||||
if state == 'absent':
|
||||
if get_vm(c, vmname) == "empty":
|
||||
module.exit_json(changed=False, msg="VM %s does not exist" % vmname)
|
||||
else:
|
||||
vm_remove(c, vmname)
|
||||
module.exit_json(changed=True, msg="VM %s removed" % vmname)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -95,7 +95,7 @@ class ProxmoxGroup:
|
||||
self.group = dict()
|
||||
# Data representation is not the same depending on API calls
|
||||
for k, v in group.items():
|
||||
if k == 'users' and isinstance(v, str):
|
||||
if k == 'users' and type(v) == str:
|
||||
self.group['users'] = v.split(',')
|
||||
elif k == 'members':
|
||||
self.group['users'] = group['members']
|
||||
|
||||
@@ -808,23 +808,23 @@ def get_vminfo(module, proxmox, node, vmid, **kwargs):
|
||||
# Sanitize kwargs. Remove not defined args and ensure True and False converted to int.
|
||||
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
|
||||
|
||||
# Convert all dict in kwargs to elements.
|
||||
# For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n]
|
||||
# Convert all dict in kwargs to elements. For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n]
|
||||
for k in list(kwargs.keys()):
|
||||
if isinstance(kwargs[k], dict):
|
||||
kwargs.update(kwargs[k])
|
||||
del kwargs[k]
|
||||
|
||||
# Split information by type
|
||||
re_net = re.compile(r'net[0-9]')
|
||||
re_dev = re.compile(r'(virtio|ide|scsi|sata)[0-9]')
|
||||
for k, v in kwargs.items():
|
||||
if re_net.match(k):
|
||||
if re.match(r'net[0-9]', k) is not None:
|
||||
interface = k
|
||||
k = vm[k]
|
||||
k = re.search('=(.*?),', k).group(1)
|
||||
mac[interface] = k
|
||||
elif re_dev.match(k):
|
||||
if (re.match(r'virtio[0-9]', k) is not None or
|
||||
re.match(r'ide[0-9]', k) is not None or
|
||||
re.match(r'scsi[0-9]', k) is not None or
|
||||
re.match(r'sata[0-9]', k) is not None):
|
||||
device = k
|
||||
k = vm[k]
|
||||
k = re.search('(.*?),', k).group(1)
|
||||
@@ -835,13 +835,16 @@ def get_vminfo(module, proxmox, node, vmid, **kwargs):
|
||||
results['vmid'] = int(vmid)
|
||||
|
||||
|
||||
def settings(proxmox, vmid, node, **kwargs):
|
||||
def settings(module, proxmox, vmid, node, name, **kwargs):
|
||||
proxmox_node = proxmox.nodes(node)
|
||||
|
||||
# Sanitize kwargs. Remove not defined args and ensure True and False converted to int.
|
||||
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
|
||||
|
||||
return proxmox_node.qemu(vmid).config.set(**kwargs) is None
|
||||
if proxmox_node.qemu(vmid).config.set(**kwargs) is None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def wait_for_task(module, proxmox, node, taskid):
|
||||
@@ -912,8 +915,7 @@ def create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sock
|
||||
if 'pool' in kwargs:
|
||||
del kwargs['pool']
|
||||
|
||||
# Convert all dict in kwargs to elements.
|
||||
# For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n], ipconfig[n]
|
||||
# Convert all dict in kwargs to elements. For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n], ipconfig[n]
|
||||
for k in list(kwargs.keys()):
|
||||
if isinstance(kwargs[k], dict):
|
||||
kwargs.update(kwargs[k])
|
||||
@@ -936,9 +938,8 @@ def create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sock
|
||||
|
||||
# VM tags are expected to be valid and presented as a comma/semi-colon delimited string
|
||||
if 'tags' in kwargs:
|
||||
re_tag = re.compile(r'^[a-z0-9_][a-z0-9_\-\+\.]*$')
|
||||
for tag in kwargs['tags']:
|
||||
if not re_tag.match(tag):
|
||||
if not re.match(r'^[a-z0-9_][a-z0-9_\-\+\.]*$', tag):
|
||||
module.fail_json(msg='%s is not a valid tag' % tag)
|
||||
kwargs['tags'] = ",".join(kwargs['tags'])
|
||||
|
||||
@@ -970,7 +971,7 @@ def create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sock
|
||||
|
||||
if not wait_for_task(module, proxmox, node, taskid):
|
||||
module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' %
|
||||
proxmox_node.tasks(taskid).log.get()[:1])
|
||||
proxmox_node.tasks(taskid).log.get()[:1])
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -1208,14 +1209,14 @@ def main():
|
||||
|
||||
if delete is not None:
|
||||
try:
|
||||
settings(proxmox, vmid, node, delete=delete)
|
||||
settings(module, proxmox, vmid, node, name, delete=delete)
|
||||
module.exit_json(changed=True, vmid=vmid, msg="Settings has deleted on VM {0} with vmid {1}".format(name, vmid))
|
||||
except Exception as e:
|
||||
module.fail_json(vmid=vmid, msg='Unable to delete settings on VM {0} with vmid {1}: '.format(name, vmid) + str(e))
|
||||
|
||||
if revert is not None:
|
||||
try:
|
||||
settings(proxmox, vmid, node, revert=revert)
|
||||
settings(module, proxmox, vmid, node, name, revert=revert)
|
||||
module.exit_json(changed=True, vmid=vmid, msg="Settings has reverted on VM {0} with vmid {1}".format(name, vmid))
|
||||
except Exception as e:
|
||||
module.fail_json(vmid=vmid, msg='Unable to revert settings on VM {0} with vmid {1}: Maybe is not a pending task... '.format(name, vmid) + str(e))
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2021, Lammert Hellinga (@Kogelvis) <lammert@hellinga.it>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: proxmox_nic
|
||||
short_description: Management of a NIC of a Qemu(KVM) VM in a Proxmox VE cluster.
|
||||
version_added: 3.1.0
|
||||
description:
|
||||
- Allows you to create/update/delete a NIC on Qemu(KVM) Virtual Machines in a Proxmox VE cluster.
|
||||
author: "Lammert Hellinga (@Kogelvis) <lammert@hellinga.it>"
|
||||
options:
|
||||
bridge:
|
||||
description:
|
||||
- Add this interface to the specified bridge device. The Proxmox VE default bridge is called C(vmbr0).
|
||||
type: str
|
||||
firewall:
|
||||
description:
|
||||
- Whether this interface should be protected by the firewall.
|
||||
type: bool
|
||||
default: false
|
||||
interface:
|
||||
description:
|
||||
- Name of the interface, should be C(net[n]) where C(1 ≤ n ≤ 31).
|
||||
type: str
|
||||
required: true
|
||||
link_down:
|
||||
description:
|
||||
- Whether this interface should be disconnected (like pulling the plug).
|
||||
type: bool
|
||||
default: false
|
||||
mac:
|
||||
description:
|
||||
- C(XX:XX:XX:XX:XX:XX) should be a unique MAC address. This is automatically generated if not specified.
|
||||
- When not specified this module will keep the MAC address the same when changing an existing interface.
|
||||
type: str
|
||||
model:
|
||||
description:
|
||||
- The NIC emulator model.
|
||||
type: str
|
||||
choices: ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet',
|
||||
'rtl8139', 'virtio', 'vmxnet3']
|
||||
default: virtio
|
||||
mtu:
|
||||
description:
|
||||
- Force MTU, for C(virtio) model only, setting will be ignored otherwise.
|
||||
- Set to C(1) to use the bridge MTU.
|
||||
- Value should be C(1 ≤ n ≤ 65520).
|
||||
type: int
|
||||
name:
|
||||
description:
|
||||
- Specifies the VM name. Only used on the configuration web interface.
|
||||
- Required only for I(state=present).
|
||||
type: str
|
||||
queues:
|
||||
description:
|
||||
- Number of packet queues to be used on the device.
|
||||
- Value should be C(0 ≤ n ≤ 16).
|
||||
type: int
|
||||
rate:
|
||||
description:
|
||||
- Rate limit in MBps (MegaBytes per second) as floating point number.
|
||||
type: float
|
||||
state:
|
||||
description:
|
||||
- Indicates desired state of the NIC.
|
||||
type: str
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
tag:
|
||||
description:
|
||||
- VLAN tag to apply to packets on this interface.
|
||||
- Value should be C(1 ≤ n ≤ 4094).
|
||||
type: int
|
||||
trunks:
|
||||
description:
|
||||
- List of VLAN trunks to pass through this interface.
|
||||
type: list
|
||||
elements: int
|
||||
vmid:
|
||||
description:
|
||||
- Specifies the instance ID.
|
||||
type: int
|
||||
extends_documentation_fragment:
|
||||
- community.general.proxmox.documentation
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create NIC net0 targeting the vm by name
|
||||
community.general.proxmox_nic:
|
||||
api_user: root@pam
|
||||
api_password: secret
|
||||
api_host: proxmoxhost
|
||||
name: my_vm
|
||||
interface: net0
|
||||
bridge: vmbr0
|
||||
tag: 3
|
||||
|
||||
- name: Create NIC net0 targeting the vm by id
|
||||
community.general.proxmox_nic:
|
||||
api_user: root@pam
|
||||
api_password: secret
|
||||
api_host: proxmoxhost
|
||||
vmid: 103
|
||||
interface: net0
|
||||
bridge: vmbr0
|
||||
mac: "12:34:56:C0:FF:EE"
|
||||
firewall: true
|
||||
|
||||
- name: Delete NIC net0 targeting the vm by name
|
||||
community.general.proxmox_nic:
|
||||
api_user: root@pam
|
||||
api_password: secret
|
||||
api_host: proxmoxhost
|
||||
name: my_vm
|
||||
interface: net0
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
vmid:
|
||||
description: The VM vmid.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 115
|
||||
msg:
|
||||
description: A short message
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Nic net0 unchanged on VM with vmid 103"
|
||||
'''
|
||||
|
||||
try:
|
||||
from proxmoxer import ProxmoxAPI
|
||||
HAS_PROXMOXER = True
|
||||
except ImportError:
|
||||
HAS_PROXMOXER = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
from ansible_collections.community.general.plugins.module_utils.proxmox import proxmox_auth_argument_spec
|
||||
|
||||
|
||||
def get_vmid(module, proxmox, name):
|
||||
try:
|
||||
vms = [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if vm.get('name') == name]
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Error: %s occurred while retrieving VM with name = %s' % (e, name))
|
||||
|
||||
if not vms:
|
||||
module.fail_json(msg='No VM found with name: %s' % name)
|
||||
elif len(vms) > 1:
|
||||
module.fail_json(msg='Multiple VMs found with name: %s, provide vmid instead' % name)
|
||||
|
||||
return vms[0]
|
||||
|
||||
|
||||
def get_vm(proxmox, vmid):
|
||||
return [vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)]
|
||||
|
||||
|
||||
def update_nic(module, proxmox, vmid, interface, model, **kwargs):
|
||||
vm = get_vm(proxmox, vmid)
|
||||
|
||||
try:
|
||||
vminfo = proxmox.nodes(vm[0]['node']).qemu(vmid).config.get()
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e))
|
||||
|
||||
if interface in vminfo:
|
||||
# Convert the current config to a dictionary
|
||||
config = vminfo[interface].split(',')
|
||||
config.sort()
|
||||
|
||||
config_current = {}
|
||||
|
||||
for i in config:
|
||||
kv = i.split('=')
|
||||
try:
|
||||
config_current[kv[0]] = kv[1]
|
||||
except IndexError:
|
||||
config_current[kv[0]] = ''
|
||||
|
||||
# determine the current model nic and mac-address
|
||||
models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'i82551', 'i82557b',
|
||||
'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3']
|
||||
current_model = set(models) & set(config_current.keys())
|
||||
current_model = current_model.pop()
|
||||
current_mac = config_current[current_model]
|
||||
|
||||
# build nic config string
|
||||
config_provided = "{0}={1}".format(model, current_mac)
|
||||
else:
|
||||
config_provided = model
|
||||
|
||||
if kwargs['mac']:
|
||||
config_provided = "{0}={1}".format(model, kwargs['mac'])
|
||||
|
||||
if kwargs['bridge']:
|
||||
config_provided += ",bridge={0}".format(kwargs['bridge'])
|
||||
|
||||
if kwargs['firewall']:
|
||||
config_provided += ",firewall=1"
|
||||
|
||||
if kwargs['link_down']:
|
||||
config_provided += ',link_down=1'
|
||||
|
||||
if kwargs['mtu']:
|
||||
config_provided += ",mtu={0}".format(kwargs['mtu'])
|
||||
if model != 'virtio':
|
||||
module.warn(
|
||||
'Ignoring MTU for nic {0} on VM with vmid {1}, '
|
||||
'model should be set to \'virtio\': '.format(interface, vmid))
|
||||
|
||||
if kwargs['queues']:
|
||||
config_provided += ",queues={0}".format(kwargs['queues'])
|
||||
|
||||
if kwargs['rate']:
|
||||
config_provided += ",rate={0}".format(kwargs['rate'])
|
||||
|
||||
if kwargs['tag']:
|
||||
config_provided += ",tag={0}".format(kwargs['tag'])
|
||||
|
||||
if kwargs['trunks']:
|
||||
config_provided += ",trunks={0}".format(';'.join(str(x) for x in kwargs['trunks']))
|
||||
|
||||
net = {interface: config_provided}
|
||||
vm = get_vm(proxmox, vmid)
|
||||
|
||||
if ((interface not in vminfo) or (vminfo[interface] != config_provided)):
|
||||
if not module.check_mode:
|
||||
proxmox.nodes(vm[0]['node']).qemu(vmid).config.set(**net)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def delete_nic(module, proxmox, vmid, interface):
|
||||
vm = get_vm(proxmox, vmid)
|
||||
vminfo = proxmox.nodes(vm[0]['node']).qemu(vmid).config.get()
|
||||
|
||||
if interface in vminfo:
|
||||
if not module.check_mode:
|
||||
proxmox.nodes(vm[0]['node']).qemu(vmid).config.set(vmid=vmid, delete=interface)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
module_args = proxmox_auth_argument_spec()
|
||||
nic_args = dict(
|
||||
bridge=dict(type='str'),
|
||||
firewall=dict(type='bool', default=False),
|
||||
interface=dict(type='str', required=True),
|
||||
link_down=dict(type='bool', default=False),
|
||||
mac=dict(type='str'),
|
||||
model=dict(choices=['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em',
|
||||
'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet',
|
||||
'rtl8139', 'virtio', 'vmxnet3'], default='virtio'),
|
||||
mtu=dict(type='int'),
|
||||
name=dict(type='str'),
|
||||
queues=dict(type='int'),
|
||||
rate=dict(type='float'),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
tag=dict(type='int'),
|
||||
trunks=dict(type='list', elements='int'),
|
||||
vmid=dict(type='int'),
|
||||
)
|
||||
module_args.update(nic_args)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
required_together=[('api_token_id', 'api_token_secret')],
|
||||
required_one_of=[('name', 'vmid'), ('api_password', 'api_token_id')],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_PROXMOXER:
|
||||
module.fail_json(msg='proxmoxer required for this module')
|
||||
|
||||
api_host = module.params['api_host']
|
||||
api_password = module.params['api_password']
|
||||
api_token_id = module.params['api_token_id']
|
||||
api_token_secret = module.params['api_token_secret']
|
||||
api_user = module.params['api_user']
|
||||
interface = module.params['interface']
|
||||
model = module.params['model']
|
||||
name = module.params['name']
|
||||
state = module.params['state']
|
||||
validate_certs = module.params['validate_certs']
|
||||
vmid = module.params['vmid']
|
||||
|
||||
auth_args = {'user': api_user}
|
||||
if not (api_token_id and api_token_secret):
|
||||
auth_args['password'] = api_password
|
||||
else:
|
||||
auth_args['token_name'] = api_token_id
|
||||
auth_args['token_value'] = api_token_secret
|
||||
|
||||
try:
|
||||
proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e)
|
||||
|
||||
# If vmid is not defined then retrieve its value from the vm name,
|
||||
if not vmid:
|
||||
vmid = get_vmid(module, proxmox, name)
|
||||
|
||||
# Ensure VM id exists
|
||||
if not get_vm(proxmox, vmid):
|
||||
module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid)
|
||||
|
||||
if state == 'present':
|
||||
try:
|
||||
if update_nic(module, proxmox, vmid, interface, model,
|
||||
bridge=module.params['bridge'],
|
||||
firewall=module.params['firewall'],
|
||||
link_down=module.params['link_down'],
|
||||
mac=module.params['mac'],
|
||||
mtu=module.params['mtu'],
|
||||
queues=module.params['queues'],
|
||||
rate=module.params['rate'],
|
||||
tag=module.params['tag'],
|
||||
trunks=module.params['trunks']):
|
||||
module.exit_json(changed=True, vmid=vmid, msg="Nic {0} updated on VM with vmid {1}".format(interface, vmid))
|
||||
else:
|
||||
module.exit_json(vmid=vmid, msg="Nic {0} unchanged on VM with vmid {1}".format(interface, vmid))
|
||||
except Exception as e:
|
||||
module.fail_json(vmid=vmid, msg='Unable to change nic {0} on VM with vmid {1}: '.format(interface, vmid) + str(e))
|
||||
|
||||
elif state == 'absent':
|
||||
try:
|
||||
if delete_nic(module, proxmox, vmid, interface):
|
||||
module.exit_json(changed=True, vmid=vmid, msg="Nic {0} deleted on VM with vmid {1}".format(interface, vmid))
|
||||
else:
|
||||
module.exit_json(vmid=vmid, msg="Nic {0} does not exist on VM with vmid {1}".format(interface, vmid))
|
||||
except Exception as e:
|
||||
module.fail_json(vmid=vmid, msg='Unable to delete nic {0} on VM with vmid {1}: '.format(interface, vmid) + str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -547,7 +547,7 @@ class RHEVConn(object):
|
||||
|
||||
def set_Memory_Policy(self, name, memory_policy):
|
||||
VM = self.get_VM(name)
|
||||
VM.memory_policy.guaranteed = int(memory_policy) * 1024 * 1024 * 1024
|
||||
VM.memory_policy.guaranteed = int(int(memory_policy) * 1024 * 1024 * 1024)
|
||||
try:
|
||||
VM.update()
|
||||
setMsg("The memory policy has been updated.")
|
||||
@@ -1260,7 +1260,7 @@ def core(module):
|
||||
|
||||
r = RHEV(module)
|
||||
|
||||
state = module.params.get('state')
|
||||
state = module.params.get('state', 'present')
|
||||
|
||||
if state == 'ping':
|
||||
r.test()
|
||||
|
||||
@@ -139,14 +139,16 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
def read_serverless_config(module):
|
||||
path = module.params.get('service_path')
|
||||
full_path = os.path.join(path, 'serverless.yml')
|
||||
|
||||
try:
|
||||
with open(full_path) as sls_config:
|
||||
with open(os.path.join(path, 'serverless.yml')) as sls_config:
|
||||
config = yaml.safe_load(sls_config.read())
|
||||
return config
|
||||
except IOError as e:
|
||||
module.fail_json(msg="Could not open serverless.yml in {0}. err: {1}".format(full_path, str(e)))
|
||||
module.fail_json(msg="Could not open serverless.yml in {0}. err: {1}".format(path, str(e)))
|
||||
|
||||
module.fail_json(msg="Failed to open serverless config at {0}".format(
|
||||
os.path.join(path, 'serverless.yml')))
|
||||
|
||||
|
||||
def get_service_name(module, stage):
|
||||
@@ -180,6 +182,7 @@ def main():
|
||||
|
||||
service_path = module.params.get('service_path')
|
||||
state = module.params.get('state')
|
||||
functions = module.params.get('functions')
|
||||
region = module.params.get('region')
|
||||
stage = module.params.get('stage')
|
||||
deploy = module.params.get('deploy', True)
|
||||
@@ -190,7 +193,7 @@ def main():
|
||||
if serverless_bin_path is not None:
|
||||
command = serverless_bin_path + " "
|
||||
else:
|
||||
command = module.get_bin_path("serverless") + " "
|
||||
command = "serverless "
|
||||
|
||||
if state == 'present':
|
||||
command += 'deploy '
|
||||
|
||||
@@ -33,19 +33,6 @@ options:
|
||||
vars.tf/main.tf/etc to use.
|
||||
type: path
|
||||
required: true
|
||||
plugin_paths:
|
||||
description:
|
||||
- List of paths containing Terraform plugin executable files.
|
||||
- Plugin executables can be downloaded from U(https://releases.hashicorp.com/).
|
||||
- When set, the plugin discovery and auto-download behavior of Terraform is disabled.
|
||||
- The directory structure in the plugin path can be tricky. The Terraform docs
|
||||
U(https://learn.hashicorp.com/tutorials/terraform/automate-terraform#pre-installed-plugins)
|
||||
show a simple directory of files, but actually, the directory structure
|
||||
has to follow the same structure you would see if Terraform auto-downloaded the plugins.
|
||||
See the examples below for a tree output of an example plugin directory.
|
||||
type: list
|
||||
elements: path
|
||||
version_added: 3.0.0
|
||||
workspace:
|
||||
description:
|
||||
- The terraform workspace to work with.
|
||||
@@ -107,12 +94,6 @@ options:
|
||||
you intend to provision an entirely new Terraform deployment.
|
||||
default: false
|
||||
type: bool
|
||||
overwrite_init:
|
||||
description:
|
||||
- Run init even if C(.terraform/terraform.tfstate) already exists in I(project_path).
|
||||
default: true
|
||||
type: bool
|
||||
version_added: '3.2.0'
|
||||
backend_config:
|
||||
description:
|
||||
- A group of key-values to provide at init stage to the -backend-config parameter.
|
||||
@@ -160,28 +141,6 @@ EXAMPLES = """
|
||||
backend_config_files:
|
||||
- /path/to/backend_config_file_1
|
||||
- /path/to/backend_config_file_2
|
||||
|
||||
- name: Disable plugin discovery and auto-download by setting plugin_paths
|
||||
community.general.terraform:
|
||||
project_path: 'project/'
|
||||
state: "{{ state }}"
|
||||
force_init: true
|
||||
plugin_paths:
|
||||
- /path/to/plugins_dir_1
|
||||
- /path/to/plugins_dir_2
|
||||
|
||||
### Example directory structure for plugin_paths example
|
||||
# $ tree /path/to/plugins_dir_1
|
||||
# /path/to/plugins_dir_1/
|
||||
# └── registry.terraform.io
|
||||
# └── hashicorp
|
||||
# └── vsphere
|
||||
# ├── 1.24.0
|
||||
# │ └── linux_amd64
|
||||
# │ └── terraform-provider-vsphere_v1.24.0_x4
|
||||
# └── 1.26.0
|
||||
# └── linux_amd64
|
||||
# └── terraform-provider-vsphere_v1.26.0_x4
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -233,7 +192,7 @@ def get_version(bin_path):
|
||||
|
||||
|
||||
def preflight_validation(bin_path, project_path, version, variables_args=None, plan_file=None):
|
||||
if project_path is None or '/' not in project_path:
|
||||
if project_path in [None, ''] or '/' not in project_path:
|
||||
module.fail_json(msg="Path for Terraform project can not be None or ''.")
|
||||
if not os.path.exists(bin_path):
|
||||
module.fail_json(msg="Path for Terraform binary '{0}' doesn't exist on this host - check the path and try again please.".format(bin_path))
|
||||
@@ -253,7 +212,7 @@ def _state_args(state_file):
|
||||
return []
|
||||
|
||||
|
||||
def init_plugins(bin_path, project_path, backend_config, backend_config_files, init_reconfigure, plugin_paths):
|
||||
def init_plugins(bin_path, project_path, backend_config, backend_config_files, init_reconfigure):
|
||||
command = [bin_path, 'init', '-input=false']
|
||||
if backend_config:
|
||||
for key, val in backend_config.items():
|
||||
@@ -266,9 +225,6 @@ def init_plugins(bin_path, project_path, backend_config, backend_config_files, i
|
||||
command.extend(['-backend-config', f])
|
||||
if init_reconfigure:
|
||||
command.extend(['-reconfigure'])
|
||||
if plugin_paths:
|
||||
for plugin_path in plugin_paths:
|
||||
command.extend(['-plugin-dir', plugin_path])
|
||||
rc, out, err = module.run_command(command, check_rc=True, cwd=project_path)
|
||||
|
||||
|
||||
@@ -339,7 +295,6 @@ def main():
|
||||
argument_spec=dict(
|
||||
project_path=dict(required=True, type='path'),
|
||||
binary_path=dict(type='path'),
|
||||
plugin_paths=dict(type='list', elements='path'),
|
||||
workspace=dict(required=False, type='str', default='default'),
|
||||
purge_workspace=dict(type='bool', default=False),
|
||||
state=dict(default='present', choices=['present', 'absent', 'planned']),
|
||||
@@ -354,7 +309,6 @@ def main():
|
||||
backend_config=dict(type='dict', default=None),
|
||||
backend_config_files=dict(type='list', elements='path', default=None),
|
||||
init_reconfigure=dict(required=False, type='bool', default=False),
|
||||
overwrite_init=dict(type='bool', default=True),
|
||||
),
|
||||
required_if=[('state', 'planned', ['plan_file'])],
|
||||
supports_check_mode=True,
|
||||
@@ -362,7 +316,6 @@ def main():
|
||||
|
||||
project_path = module.params.get('project_path')
|
||||
bin_path = module.params.get('binary_path')
|
||||
plugin_paths = module.params.get('plugin_paths')
|
||||
workspace = module.params.get('workspace')
|
||||
purge_workspace = module.params.get('purge_workspace')
|
||||
state = module.params.get('state')
|
||||
@@ -374,7 +327,6 @@ def main():
|
||||
backend_config = module.params.get('backend_config')
|
||||
backend_config_files = module.params.get('backend_config_files')
|
||||
init_reconfigure = module.params.get('init_reconfigure')
|
||||
overwrite_init = module.params.get('overwrite_init')
|
||||
|
||||
if bin_path is not None:
|
||||
command = [bin_path]
|
||||
@@ -391,8 +343,7 @@ def main():
|
||||
APPLY_ARGS = ('apply', '-no-color', '-input=false', '-auto-approve')
|
||||
|
||||
if force_init:
|
||||
if overwrite_init or not os.path.isfile(os.path.join(project_path, ".terraform", "terraform.tfstate")):
|
||||
init_plugins(command[0], project_path, backend_config, backend_config_files, init_reconfigure, plugin_paths)
|
||||
init_plugins(command[0], project_path, backend_config, backend_config_files, init_reconfigure)
|
||||
|
||||
workspace_ctx = get_workspace_context(command[0], project_path)
|
||||
if workspace_ctx["current"] != workspace:
|
||||
|
||||
175
plugins/modules/cloud/online/online_server_facts.py
Normal file
175
plugins/modules/cloud/online/online_server_facts.py
Normal file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: online_server_facts
|
||||
deprecated:
|
||||
removed_in: 3.0.0 # was Ansible 2.13
|
||||
why: Deprecated in favour of C(_info) module.
|
||||
alternative: Use M(community.general.online_server_info) instead.
|
||||
short_description: Gather facts about Online servers.
|
||||
description:
|
||||
- Gather facts about the servers.
|
||||
- U(https://www.online.net/en/dedicated-server)
|
||||
author:
|
||||
- "Remy Leone (@sieben)"
|
||||
extends_documentation_fragment:
|
||||
- community.general.online
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Gather Online server facts
|
||||
community.general.online_server_facts:
|
||||
api_token: '0d1627e8-bbf0-44c5-a46f-5c4d3aef033f'
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
---
|
||||
online_server_facts:
|
||||
description: Response from Online API
|
||||
returned: success
|
||||
type: complex
|
||||
sample:
|
||||
"online_server_facts": [
|
||||
{
|
||||
"abuse": "abuse@example.com",
|
||||
"anti_ddos": false,
|
||||
"bmc": {
|
||||
"session_key": null
|
||||
},
|
||||
"boot_mode": "normal",
|
||||
"contacts": {
|
||||
"owner": "foobar",
|
||||
"tech": "foobar"
|
||||
},
|
||||
"disks": [
|
||||
{
|
||||
"$ref": "/api/v1/server/hardware/disk/68452"
|
||||
},
|
||||
{
|
||||
"$ref": "/api/v1/server/hardware/disk/68453"
|
||||
}
|
||||
],
|
||||
"drive_arrays": [
|
||||
{
|
||||
"disks": [
|
||||
{
|
||||
"$ref": "/api/v1/server/hardware/disk/68452"
|
||||
},
|
||||
{
|
||||
"$ref": "/api/v1/server/hardware/disk/68453"
|
||||
}
|
||||
],
|
||||
"raid_controller": {
|
||||
"$ref": "/api/v1/server/hardware/raidController/9910"
|
||||
},
|
||||
"raid_level": "RAID1"
|
||||
}
|
||||
],
|
||||
"hardware_watch": true,
|
||||
"hostname": "sd-42",
|
||||
"id": 42,
|
||||
"ip": [
|
||||
{
|
||||
"address": "195.154.172.149",
|
||||
"mac": "28:92:4a:33:5e:c6",
|
||||
"reverse": "195-154-172-149.rev.poneytelecom.eu.",
|
||||
"switch_port_state": "up",
|
||||
"type": "public"
|
||||
},
|
||||
{
|
||||
"address": "10.90.53.212",
|
||||
"mac": "28:92:4a:33:5e:c7",
|
||||
"reverse": null,
|
||||
"switch_port_state": "up",
|
||||
"type": "private"
|
||||
}
|
||||
],
|
||||
"last_reboot": "2018-08-23T08:32:03.000Z",
|
||||
"location": {
|
||||
"block": "A",
|
||||
"datacenter": "DC3",
|
||||
"position": 19,
|
||||
"rack": "A23",
|
||||
"room": "4 4-4"
|
||||
},
|
||||
"network": {
|
||||
"ip": [
|
||||
"195.154.172.149"
|
||||
],
|
||||
"ipfo": [],
|
||||
"private": [
|
||||
"10.90.53.212"
|
||||
]
|
||||
},
|
||||
"offer": "Pro-1-S-SATA",
|
||||
"os": {
|
||||
"name": "FreeBSD",
|
||||
"version": "11.1-RELEASE"
|
||||
},
|
||||
"power": "ON",
|
||||
"proactive_monitoring": false,
|
||||
"raid_controllers": [
|
||||
{
|
||||
"$ref": "/api/v1/server/hardware/raidController/9910"
|
||||
}
|
||||
],
|
||||
"support": "Basic service level"
|
||||
}
|
||||
]
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.online import (
|
||||
Online, OnlineException, online_argument_spec
|
||||
)
|
||||
|
||||
|
||||
class OnlineServerFacts(Online):
|
||||
|
||||
def __init__(self, module):
|
||||
super(OnlineServerFacts, self).__init__(module)
|
||||
self.name = 'api/v1/server'
|
||||
|
||||
def _get_server_detail(self, server_path):
|
||||
try:
|
||||
return self.get(path=server_path).json
|
||||
except OnlineException as exc:
|
||||
self.module.fail_json(msg="A problem occurred while fetching: %s (%s)" % (server_path, exc))
|
||||
|
||||
def all_detailed_servers(self):
|
||||
servers_api_path = self.get_resources()
|
||||
|
||||
server_data = (
|
||||
self._get_server_detail(server_api_path)
|
||||
for server_api_path in servers_api_path
|
||||
)
|
||||
|
||||
return [s for s in server_data if s is not None]
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=online_argument_spec(),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
try:
|
||||
servers_facts = OnlineServerFacts(module).all_detailed_servers()
|
||||
module.exit_json(
|
||||
ansible_facts={'online_server_facts': servers_facts}
|
||||
)
|
||||
except OnlineException as exc:
|
||||
module.fail_json(msg=exc.message)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -32,13 +32,11 @@ EXAMPLES = r'''
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
---
|
||||
online_server_info:
|
||||
description:
|
||||
- Response from Online API.
|
||||
- "For more details please refer to: U(https://console.online.net/en/api/)."
|
||||
description: Response from Online API
|
||||
returned: success
|
||||
type: list
|
||||
elements: dict
|
||||
type: complex
|
||||
sample:
|
||||
"online_server_info": [
|
||||
{
|
||||
|
||||
76
plugins/modules/cloud/online/online_user_facts.py
Normal file
76
plugins/modules/cloud/online/online_user_facts.py
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: online_user_facts
|
||||
deprecated:
|
||||
removed_in: 3.0.0 # was Ansible 2.13
|
||||
why: Deprecated in favour of C(_info) module.
|
||||
alternative: Use M(community.general.online_user_info) instead.
|
||||
short_description: Gather facts about Online user.
|
||||
description:
|
||||
- Gather facts about the user.
|
||||
author:
|
||||
- "Remy Leone (@sieben)"
|
||||
extends_documentation_fragment:
|
||||
- community.general.online
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Gather Online user facts
|
||||
community.general.online_user_facts:
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
---
|
||||
online_user_facts:
|
||||
description: Response from Online API
|
||||
returned: success
|
||||
type: complex
|
||||
sample:
|
||||
"online_user_facts": {
|
||||
"company": "foobar LLC",
|
||||
"email": "foobar@example.com",
|
||||
"first_name": "foo",
|
||||
"id": 42,
|
||||
"last_name": "bar",
|
||||
"login": "foobar"
|
||||
}
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.online import (
|
||||
Online, OnlineException, online_argument_spec
|
||||
)
|
||||
|
||||
|
||||
class OnlineUserFacts(Online):
|
||||
|
||||
def __init__(self, module):
|
||||
super(OnlineUserFacts, self).__init__(module)
|
||||
self.name = 'api/v1/user'
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=online_argument_spec(),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
try:
|
||||
module.exit_json(
|
||||
ansible_facts={'online_user_facts': OnlineUserFacts(module).get_resources()}
|
||||
)
|
||||
except OnlineException as exc:
|
||||
module.fail_json(msg=exc.message)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -7,6 +7,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: online_user_info
|
||||
short_description: Gather information about Online user.
|
||||
description:
|
||||
@@ -15,6 +16,7 @@ author:
|
||||
- "Remy Leone (@sieben)"
|
||||
extends_documentation_fragment:
|
||||
- community.general.online
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
@@ -27,12 +29,11 @@ EXAMPLES = r'''
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
---
|
||||
online_user_info:
|
||||
description:
|
||||
- Response from Online API.
|
||||
- "For more details please refer to: U(https://console.online.net/en/api/)."
|
||||
description: Response from Online API
|
||||
returned: success
|
||||
type: dict
|
||||
type: complex
|
||||
sample:
|
||||
"online_user_info": {
|
||||
"company": "foobar LLC",
|
||||
|
||||
@@ -31,7 +31,7 @@ short_description: Manages OpenNebula images
|
||||
description:
|
||||
- Manages OpenNebula images
|
||||
requirements:
|
||||
- pyone
|
||||
- python-oca
|
||||
options:
|
||||
api_url:
|
||||
description:
|
||||
@@ -88,7 +88,7 @@ EXAMPLES = '''
|
||||
|
||||
- name: Print the IMAGE properties
|
||||
ansible.builtin.debug:
|
||||
var: result
|
||||
msg: result
|
||||
|
||||
- name: Rename existing IMAGE
|
||||
community.general.one_image:
|
||||
@@ -168,20 +168,21 @@ running_vms:
|
||||
'''
|
||||
|
||||
try:
|
||||
import pyone
|
||||
HAS_PYONE = True
|
||||
import oca
|
||||
HAS_OCA = True
|
||||
except ImportError:
|
||||
HAS_PYONE = False
|
||||
HAS_OCA = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import os
|
||||
|
||||
|
||||
def get_image(module, client, predicate):
|
||||
pool = oca.ImagePool(client)
|
||||
# Filter -2 means fetch all images user can Use
|
||||
pool = client.imagepool.info(-2, -1, -1, -1)
|
||||
pool.info(filter=-2)
|
||||
|
||||
for image in pool.IMAGE:
|
||||
for image in pool:
|
||||
if predicate(image):
|
||||
return image
|
||||
|
||||
@@ -189,11 +190,11 @@ def get_image(module, client, predicate):
|
||||
|
||||
|
||||
def get_image_by_name(module, client, image_name):
|
||||
return get_image(module, client, lambda image: (image.NAME == image_name))
|
||||
return get_image(module, client, lambda image: (image.name == image_name))
|
||||
|
||||
|
||||
def get_image_by_id(module, client, image_id):
|
||||
return get_image(module, client, lambda image: (image.ID == image_id))
|
||||
return get_image(module, client, lambda image: (image.id == image_id))
|
||||
|
||||
|
||||
def get_image_instance(module, client, requested_id, requested_name):
|
||||
@@ -207,28 +208,30 @@ IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE',
|
||||
|
||||
|
||||
def get_image_info(image):
|
||||
image.info()
|
||||
|
||||
info = {
|
||||
'id': image.ID,
|
||||
'name': image.NAME,
|
||||
'state': IMAGE_STATES[image.STATE],
|
||||
'running_vms': image.RUNNING_VMS,
|
||||
'used': bool(image.RUNNING_VMS),
|
||||
'user_name': image.UNAME,
|
||||
'user_id': image.UID,
|
||||
'group_name': image.GNAME,
|
||||
'group_id': image.GID,
|
||||
'id': image.id,
|
||||
'name': image.name,
|
||||
'state': IMAGE_STATES[image.state],
|
||||
'running_vms': image.running_vms,
|
||||
'used': bool(image.running_vms),
|
||||
'user_name': image.uname,
|
||||
'user_id': image.uid,
|
||||
'group_name': image.gname,
|
||||
'group_id': image.gid,
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def wait_for_state(module, client, image_id, wait_timeout, state_predicate):
|
||||
def wait_for_state(module, image, wait_timeout, state_predicate):
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
while (time.time() - start_time) < wait_timeout:
|
||||
image = client.image.info(image_id)
|
||||
state = image.STATE
|
||||
image.info()
|
||||
state = image.state
|
||||
|
||||
if state_predicate(state):
|
||||
return image
|
||||
@@ -238,19 +241,19 @@ def wait_for_state(module, client, image_id, wait_timeout, state_predicate):
|
||||
module.fail_json(msg="Wait timeout has expired!")
|
||||
|
||||
|
||||
def wait_for_ready(module, client, image_id, wait_timeout=60):
|
||||
return wait_for_state(module, client, image_id, wait_timeout, lambda state: (state in [IMAGE_STATES.index('READY')]))
|
||||
def wait_for_ready(module, image, wait_timeout=60):
|
||||
return wait_for_state(module, image, wait_timeout, lambda state: (state in [IMAGE_STATES.index('READY')]))
|
||||
|
||||
|
||||
def wait_for_delete(module, client, image_id, wait_timeout=60):
|
||||
return wait_for_state(module, client, image_id, wait_timeout, lambda state: (state in [IMAGE_STATES.index('DELETE')]))
|
||||
def wait_for_delete(module, image, wait_timeout=60):
|
||||
return wait_for_state(module, image, wait_timeout, lambda state: (state in [IMAGE_STATES.index('DELETE')]))
|
||||
|
||||
|
||||
def enable_image(module, client, image, enable):
|
||||
image = client.image.info(image.ID)
|
||||
image.info()
|
||||
changed = False
|
||||
|
||||
state = image.STATE
|
||||
state = image.state
|
||||
|
||||
if state not in [IMAGE_STATES.index('READY'), IMAGE_STATES.index('DISABLED'), IMAGE_STATES.index('ERROR')]:
|
||||
if enable:
|
||||
@@ -263,7 +266,7 @@ def enable_image(module, client, image, enable):
|
||||
changed = True
|
||||
|
||||
if changed and not module.check_mode:
|
||||
client.image.enable(image.ID, enable)
|
||||
client.call('image.enable', image.id, enable)
|
||||
|
||||
result = get_image_info(image)
|
||||
result['changed'] = changed
|
||||
@@ -273,7 +276,7 @@ def enable_image(module, client, image, enable):
|
||||
|
||||
def clone_image(module, client, image, new_name):
|
||||
if new_name is None:
|
||||
new_name = "Copy of " + image.NAME
|
||||
new_name = "Copy of " + image.name
|
||||
|
||||
tmp_image = get_image_by_name(module, client, new_name)
|
||||
if tmp_image:
|
||||
@@ -281,13 +284,13 @@ def clone_image(module, client, image, new_name):
|
||||
result['changed'] = False
|
||||
return result
|
||||
|
||||
if image.STATE == IMAGE_STATES.index('DISABLED'):
|
||||
if image.state == IMAGE_STATES.index('DISABLED'):
|
||||
module.fail_json(msg="Cannot clone DISABLED image")
|
||||
|
||||
if not module.check_mode:
|
||||
new_id = client.image.clone(image.ID, new_name)
|
||||
wait_for_ready(module, client, new_id)
|
||||
image = client.image.info(new_id)
|
||||
new_id = client.call('image.clone', image.id, new_name)
|
||||
image = get_image_by_id(module, client, new_id)
|
||||
wait_for_ready(module, image)
|
||||
|
||||
result = get_image_info(image)
|
||||
result['changed'] = True
|
||||
@@ -299,7 +302,7 @@ def rename_image(module, client, image, new_name):
|
||||
if new_name is None:
|
||||
module.fail_json(msg="'new_name' option has to be specified when the state is 'renamed'")
|
||||
|
||||
if new_name == image.NAME:
|
||||
if new_name == image.name:
|
||||
result = get_image_info(image)
|
||||
result['changed'] = False
|
||||
return result
|
||||
@@ -309,7 +312,7 @@ def rename_image(module, client, image, new_name):
|
||||
module.fail_json(msg="Name '" + new_name + "' is already taken by IMAGE with id=" + str(tmp_image.id))
|
||||
|
||||
if not module.check_mode:
|
||||
client.image.rename(image.ID, new_name)
|
||||
client.call('image.rename', image.id, new_name)
|
||||
|
||||
result = get_image_info(image)
|
||||
result['changed'] = True
|
||||
@@ -321,12 +324,12 @@ def delete_image(module, client, image):
|
||||
if not image:
|
||||
return {'changed': False}
|
||||
|
||||
if image.RUNNING_VMS > 0:
|
||||
module.fail_json(msg="Cannot delete image. There are " + str(image.RUNNING_VMS) + " VMs using it.")
|
||||
if image.running_vms > 0:
|
||||
module.fail_json(msg="Cannot delete image. There are " + str(image.running_vms) + " VMs using it.")
|
||||
|
||||
if not module.check_mode:
|
||||
client.image.delete(image.ID)
|
||||
wait_for_delete(module, client, image.ID)
|
||||
client.call('image.delete', image.id)
|
||||
wait_for_delete(module, image)
|
||||
|
||||
return {'changed': True}
|
||||
|
||||
@@ -375,8 +378,8 @@ def main():
|
||||
mutually_exclusive=[['id', 'name']],
|
||||
supports_check_mode=True)
|
||||
|
||||
if not HAS_PYONE:
|
||||
module.fail_json(msg='This module requires pyone to work!')
|
||||
if not HAS_OCA:
|
||||
module.fail_json(msg='This module requires python-oca to work!')
|
||||
|
||||
auth = get_connection_info(module)
|
||||
params = module.params
|
||||
@@ -385,7 +388,7 @@ def main():
|
||||
state = params.get('state')
|
||||
enabled = params.get('enabled')
|
||||
new_name = params.get('new_name')
|
||||
client = pyone.OneServer(auth.url, session=auth.username + ':' + auth.password)
|
||||
client = oca.Client(auth.username + ':' + auth.password, auth.url)
|
||||
|
||||
result = {}
|
||||
|
||||
|
||||
1
plugins/modules/cloud/opennebula/one_image_facts.py
Symbolic link
1
plugins/modules/cloud/opennebula/one_image_facts.py
Symbolic link
@@ -0,0 +1 @@
|
||||
one_image_info.py
|
||||
@@ -261,6 +261,9 @@ def main():
|
||||
module = AnsibleModule(argument_spec=fields,
|
||||
mutually_exclusive=[['ids', 'name']],
|
||||
supports_check_mode=True)
|
||||
if module._name in ('one_image_facts', 'community.general.one_image_facts'):
|
||||
module.deprecate("The 'one_image_facts' module has been renamed to 'one_image_info'",
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.13
|
||||
|
||||
if not HAS_PYONE:
|
||||
module.fail_json(msg='This module requires pyone to work!')
|
||||
|
||||
195
plugins/modules/cloud/ovirt/ovirt_affinity_label_facts.py
Normal file
195
plugins/modules/cloud/ovirt/ovirt_affinity_label_facts.py
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2016 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ovirt_affinity_label_facts
|
||||
short_description: Retrieve information about one or more oVirt/RHV affinity labels
|
||||
author: "Ondra Machacek (@machacekondra)"
|
||||
deprecated:
|
||||
removed_in: 3.0.0 # was Ansible 2.13
|
||||
why: When migrating to collection we decided to use only _info modules.
|
||||
alternative: Use M(ovirt.ovirt.ovirt_affinity_label_info) instead.
|
||||
description:
|
||||
- "Retrieve information about one or more oVirt/RHV affinity labels."
|
||||
notes:
|
||||
- "This module returns a variable C(ovirt_affinity_labels), which
|
||||
contains a list of affinity labels. You need to register the result with
|
||||
the I(register) keyword to use it."
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- "Name of the affinity labels which should be listed."
|
||||
vm:
|
||||
description:
|
||||
- "Name of the VM, which affinity labels should be listed."
|
||||
host:
|
||||
description:
|
||||
- "Name of the host, which affinity labels should be listed."
|
||||
extends_documentation_fragment:
|
||||
- community.general.ovirt_facts
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Examples don't contain auth parameter for simplicity,
|
||||
# look at ovirt_auth module to see how to reuse authentication:
|
||||
|
||||
- name: Gather information about all affinity labels, which names start with label
|
||||
ovirt_affinity_label_info:
|
||||
name: label*
|
||||
register: result
|
||||
|
||||
- name: Print gathered information
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result.ovirt_affinity_labels }}"
|
||||
|
||||
- name: >
|
||||
Gather information about all affinity labels, which are assigned to VMs
|
||||
which names start with postgres
|
||||
ovirt_affinity_label_info:
|
||||
vm: postgres*
|
||||
register: result
|
||||
|
||||
- name: Print gathered information
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result.ovirt_affinity_labels }}"
|
||||
|
||||
- name: >
|
||||
Gather information about all affinity labels, which are assigned to hosts
|
||||
which names start with west
|
||||
ovirt_affinity_label_info:
|
||||
host: west*
|
||||
register: result
|
||||
|
||||
- name: Print gathered information
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result.ovirt_affinity_labels }}"
|
||||
|
||||
- name: >
|
||||
Gather information about all affinity labels, which are assigned to hosts
|
||||
which names start with west or VMs which names start with postgres
|
||||
ovirt_affinity_label_info:
|
||||
host: west*
|
||||
vm: postgres*
|
||||
register: result
|
||||
|
||||
- name: Print gathered information
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result.ovirt_affinity_labels }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
ovirt_affinity_labels:
|
||||
description: "List of dictionaries describing the affinity labels. Affinity labels attributes are mapped to dictionary keys,
|
||||
all affinity labels attributes can be found at following url: http://ovirt.github.io/ovirt-engine-api-model/master/#types/affinity_label."
|
||||
returned: On success.
|
||||
type: list
|
||||
'''
|
||||
|
||||
import fnmatch
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils._ovirt import (
|
||||
check_sdk,
|
||||
create_connection,
|
||||
get_dict_of_struct,
|
||||
ovirt_info_full_argument_spec,
|
||||
search_by_name,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ovirt_info_full_argument_spec(
|
||||
name=dict(default=None),
|
||||
host=dict(default=None),
|
||||
vm=dict(default=None),
|
||||
)
|
||||
module = AnsibleModule(argument_spec)
|
||||
is_old_facts = module._name in ('ovirt_affinity_label_facts', 'community.general.ovirt_affinity_label_facts')
|
||||
if is_old_facts:
|
||||
module.deprecate("The 'ovirt_affinity_label_facts' module has been renamed to 'ovirt_affinity_label_info', "
|
||||
"and the renamed one no longer returns ansible_facts",
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.13
|
||||
|
||||
check_sdk(module)
|
||||
|
||||
try:
|
||||
auth = module.params.pop('auth')
|
||||
connection = create_connection(auth)
|
||||
affinity_labels_service = connection.system_service().affinity_labels_service()
|
||||
labels = []
|
||||
all_labels = affinity_labels_service.list()
|
||||
if module.params['name']:
|
||||
labels.extend([
|
||||
l for l in all_labels
|
||||
if fnmatch.fnmatch(l.name, module.params['name'])
|
||||
])
|
||||
if module.params['host']:
|
||||
hosts_service = connection.system_service().hosts_service()
|
||||
if search_by_name(hosts_service, module.params['host']) is None:
|
||||
raise Exception("Host '%s' was not found." % module.params['host'])
|
||||
labels.extend([
|
||||
label
|
||||
for label in all_labels
|
||||
for host in connection.follow_link(label.hosts)
|
||||
if fnmatch.fnmatch(hosts_service.service(host.id).get().name, module.params['host'])
|
||||
])
|
||||
if module.params['vm']:
|
||||
vms_service = connection.system_service().vms_service()
|
||||
if search_by_name(vms_service, module.params['vm']) is None:
|
||||
raise Exception("Vm '%s' was not found." % module.params['vm'])
|
||||
labels.extend([
|
||||
label
|
||||
for label in all_labels
|
||||
for vm in connection.follow_link(label.vms)
|
||||
if fnmatch.fnmatch(vms_service.service(vm.id).get().name, module.params['vm'])
|
||||
])
|
||||
|
||||
if not (module.params['vm'] or module.params['host'] or module.params['name']):
|
||||
labels = all_labels
|
||||
|
||||
result = dict(
|
||||
ovirt_affinity_labels=[
|
||||
get_dict_of_struct(
|
||||
struct=l,
|
||||
connection=connection,
|
||||
fetch_nested=module.params.get('fetch_nested'),
|
||||
attributes=module.params.get('nested_attributes'),
|
||||
) for l in labels
|
||||
],
|
||||
)
|
||||
if is_old_facts:
|
||||
module.exit_json(changed=False, ansible_facts=result)
|
||||
else:
|
||||
module.exit_json(changed=False, **result)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e), exception=traceback.format_exc())
|
||||
finally:
|
||||
connection.close(logout=auth.get('token') is None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
97
plugins/modules/cloud/ovirt/ovirt_api_facts.py
Normal file
97
plugins/modules/cloud/ovirt/ovirt_api_facts.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ovirt_api_facts
|
||||
short_description: Retrieve information about the oVirt/RHV API
|
||||
author: "Ondra Machacek (@machacekondra)"
|
||||
deprecated:
|
||||
removed_in: 3.0.0 # was Ansible 2.13
|
||||
why: When migrating to collection we decided to use only _info modules.
|
||||
alternative: Use M(ovirt.ovirt.ovirt_api_info) instead.
|
||||
description:
|
||||
- "Retrieve information about the oVirt/RHV API."
|
||||
notes:
|
||||
- "This module returns a variable C(ovirt_api),
|
||||
which contains a information about oVirt/RHV API. You need to register the result with
|
||||
the I(register) keyword to use it."
|
||||
extends_documentation_fragment:
|
||||
- community.general.ovirt_facts
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Examples don't contain auth parameter for simplicity,
|
||||
# look at ovirt_auth module to see how to reuse authentication:
|
||||
|
||||
- name: Gather information oVirt API
|
||||
ovirt_api_info:
|
||||
register: result
|
||||
|
||||
- name: Print gathered information
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result.ovirt_api }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
ovirt_api:
|
||||
description: "Dictionary describing the oVirt API information.
|
||||
Api attributes are mapped to dictionary keys,
|
||||
all API attributes can be found at following
|
||||
url: https://ovirt.example.com/ovirt-engine/api/model#types/api."
|
||||
returned: On success.
|
||||
type: dict
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils._ovirt import (
|
||||
check_sdk,
|
||||
create_connection,
|
||||
get_dict_of_struct,
|
||||
ovirt_info_full_argument_spec,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = ovirt_info_full_argument_spec()
|
||||
module = AnsibleModule(argument_spec)
|
||||
is_old_facts = module._name in ('ovirt_api_facts', 'community.general.ovirt_api_facts')
|
||||
if is_old_facts:
|
||||
module.deprecate("The 'ovirt_api_facts' module has been renamed to 'ovirt_api_info', "
|
||||
"and the renamed one no longer returns ansible_facts",
|
||||
version='3.0.0', collection_name='community.general') # was Ansible 2.13
|
||||
check_sdk(module)
|
||||
|
||||
try:
|
||||
auth = module.params.pop('auth')
|
||||
connection = create_connection(auth)
|
||||
api = connection.system_service().get()
|
||||
result = dict(
|
||||
ovirt_api=get_dict_of_struct(
|
||||
struct=api,
|
||||
connection=connection,
|
||||
fetch_nested=module.params.get('fetch_nested'),
|
||||
attributes=module.params.get('nested_attributes'),
|
||||
)
|
||||
)
|
||||
if is_old_facts:
|
||||
module.exit_json(changed=False, ansible_facts=result)
|
||||
else:
|
||||
module.exit_json(changed=False, **result)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=str(e), exception=traceback.format_exc())
|
||||
finally:
|
||||
connection.close(logout=auth.get('token') is None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user