mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-28 09:26:44 +00:00
Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0c1b1065a | ||
|
|
a44356c966 | ||
|
|
33f9f0b05f | ||
|
|
f0f0704d64 | ||
|
|
55fe140230 | ||
|
|
ac543f5ef0 | ||
|
|
dbc0fe8859 | ||
|
|
42a1318fe3 | ||
|
|
d25352dc06 | ||
|
|
55682c52df | ||
|
|
46781d9fd1 | ||
|
|
4545d1c91e | ||
|
|
6570dfeb7d | ||
|
|
94c368f7df | ||
|
|
4cba1e60d9 | ||
|
|
321fb6c974 | ||
|
|
eb4d7a4199 | ||
|
|
4b07d45b7e | ||
|
|
d4a33433b4 | ||
|
|
e30b91cb8d | ||
|
|
b2b65c431b | ||
|
|
9ade4f6dd6 | ||
|
|
635d4f2138 | ||
|
|
6549e41ab8 | ||
|
|
6faface39e | ||
|
|
3b893ec421 | ||
|
|
65805e2dd6 | ||
|
|
297b50fb96 | ||
|
|
2edadb42fb | ||
|
|
4e1bf2d4ba | ||
|
|
b1a4a0ff21 | ||
|
|
e74ea7c8b8 | ||
|
|
6590f5e082 | ||
|
|
7483f71d31 | ||
|
|
6b215e3a9c | ||
|
|
3723e458d3 | ||
|
|
0f8bb43723 | ||
|
|
f33530dd61 | ||
|
|
8f3043058e | ||
|
|
3987b8a291 | ||
|
|
f7403a0b34 | ||
|
|
0a676406b3 | ||
|
|
5a7d234d80 | ||
|
|
fb9730f75e | ||
|
|
928aeafe1d | ||
|
|
5b68665571 | ||
|
|
e6b84acd1e | ||
|
|
c242993291 | ||
|
|
4f3de5658e | ||
|
|
301fcc3b7e | ||
|
|
0f0e9b2dca | ||
|
|
ed0636dc27 | ||
|
|
057321c6c6 | ||
|
|
1a4814de53 | ||
|
|
89b67a014b | ||
|
|
57bfbdc407 | ||
|
|
e19dffbf29 | ||
|
|
113e7cdfa0 | ||
|
|
c12be67a69 | ||
|
|
3a076fd585 | ||
|
|
4ef05a6483 | ||
|
|
936dd28395 | ||
|
|
e3b47899c5 | ||
|
|
fd8193e0bd | ||
|
|
fa477ebb35 | ||
|
|
43e766dd44 | ||
|
|
b25e0f360c | ||
|
|
658e95c5ca | ||
|
|
26c2876f50 | ||
|
|
62043463f3 | ||
|
|
f1dab6d4a7 | ||
|
|
d43764da79 | ||
|
|
de2feb2567 | ||
|
|
6e56bae0f3 | ||
|
|
1f7047e725 | ||
|
|
b2e4485567 | ||
|
|
b78254fe24 | ||
|
|
38aa0ec8ad | ||
|
|
42f28048a8 | ||
|
|
b699aaff7b | ||
|
|
af85b6c203 | ||
|
|
ec2e7cad3e | ||
|
|
7753fa4219 | ||
|
|
69ea487005 | ||
|
|
048f15fe68 | ||
|
|
aa1aa1d540 | ||
|
|
e78517ca93 | ||
|
|
bf185573a6 | ||
|
|
145435cdd9 | ||
|
|
6013c77c2b | ||
|
|
ad5482f63d | ||
|
|
f5594aefd5 | ||
|
|
ab5b379b30 | ||
|
|
1c5e44c649 | ||
|
|
23da67cc72 | ||
|
|
4032dd6b08 | ||
|
|
4cb6f39a80 | ||
|
|
3539957bac | ||
|
|
e05769d4bf | ||
|
|
19c03cff96 | ||
|
|
703660c81d | ||
|
|
fd32af1ac3 | ||
|
|
80fbcf2f98 | ||
|
|
a722e038cc | ||
|
|
19c8d2164d | ||
|
|
d4656ffca2 | ||
|
|
b49607f12d | ||
|
|
af0ce4284f | ||
|
|
f5f862617a | ||
|
|
a1a4ba4337 | ||
|
|
b0b783f8ff | ||
|
|
e670ca666a | ||
|
|
49b991527e | ||
|
|
e6cc671a0d | ||
|
|
797ea23e50 | ||
|
|
4d23b7a48b | ||
|
|
020b47a1a9 | ||
|
|
0da9d956a0 | ||
|
|
5691e3aff3 | ||
|
|
007333dbfe | ||
|
|
05666b0e4d | ||
|
|
c934d9aeb5 | ||
|
|
5b15e4089a | ||
|
|
a6379e45ce | ||
|
|
b95176dbc8 | ||
|
|
b752fea121 | ||
|
|
cf50990fed | ||
|
|
45343e6bc0 | ||
|
|
51540f6345 | ||
|
|
74eba52028 | ||
|
|
b920e8abf2 | ||
|
|
75c0004e1e | ||
|
|
be42fd4af7 | ||
|
|
1c05908ff6 | ||
|
|
ea42b75378 | ||
|
|
0330f4b52c | ||
|
|
1d8c659ba2 |
@@ -14,12 +14,24 @@ pr:
|
||||
|
||||
schedules:
|
||||
- cron: 0 8 * * *
|
||||
displayName: Nightly
|
||||
displayName: Nightly (main)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- stable-*
|
||||
- cron: 0 10 * * *
|
||||
displayName: Nightly (active stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-2
|
||||
- stable-3
|
||||
- cron: 0 11 * * 0
|
||||
displayName: Weekly (old stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-1
|
||||
|
||||
variables:
|
||||
- name: checkoutPath
|
||||
@@ -112,6 +124,7 @@ stages:
|
||||
- test: 3.7
|
||||
- test: 3.8
|
||||
- test: 3.9
|
||||
- test: '3.10'
|
||||
- stage: Units_2_11
|
||||
displayName: Units 2.11
|
||||
dependsOn: []
|
||||
@@ -256,10 +269,10 @@ stages:
|
||||
test: centos7
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- name: Fedora 32
|
||||
test: fedora32
|
||||
- name: Fedora 33
|
||||
test: fedora33
|
||||
- name: Fedora 34
|
||||
test: fedora34
|
||||
- name: openSUSE 15 py2
|
||||
test: opensuse15py2
|
||||
- name: openSUSE 15 py3
|
||||
@@ -282,7 +295,7 @@ stages:
|
||||
targets:
|
||||
- name: CentOS 8
|
||||
test: centos8
|
||||
- name: Fedora 32
|
||||
- name: Fedora 33
|
||||
test: fedora33
|
||||
- name: openSUSE 15 py3
|
||||
test: opensuse15
|
||||
|
||||
@@ -7,7 +7,7 @@ set -o pipefail -eu
|
||||
|
||||
output_path="$1"
|
||||
|
||||
curl --silent --show-error https://codecov.io/bash > codecov.sh
|
||||
curl --silent --show-error https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh > codecov.sh
|
||||
|
||||
for file in "${output_path}"/reports/coverage*.xml; do
|
||||
name="${file}"
|
||||
|
||||
38
.github/BOTMETA.yml
vendored
38
.github/BOTMETA.yml
vendored
@@ -1,17 +1,12 @@
|
||||
automerge: true
|
||||
files:
|
||||
plugins/:
|
||||
supershipit: aminvakil russoz
|
||||
changelogs/fragments/:
|
||||
support: community
|
||||
$actions:
|
||||
labels: action
|
||||
$actions/aireos.py:
|
||||
labels: aireos cisco networking
|
||||
$actions/ironware.py:
|
||||
maintainers: paulquack
|
||||
labels: ironware networking
|
||||
$actions/shutdown.py:
|
||||
$actions/system/iptables_state.py:
|
||||
maintainers: quidame
|
||||
$actions/system/shutdown.py:
|
||||
maintainers: nitzmahone samdoran aminvakil
|
||||
$becomes/:
|
||||
labels: become
|
||||
@@ -88,6 +83,8 @@ 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:
|
||||
@@ -118,6 +115,8 @@ 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:
|
||||
@@ -140,6 +139,9 @@ 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
|
||||
@@ -225,7 +227,7 @@ files:
|
||||
$modules/cloud/misc/:
|
||||
ignore: ryansb
|
||||
$modules/cloud/misc/terraform.py:
|
||||
maintainers: m-yosefpor
|
||||
maintainers: m-yosefpor rainerleber
|
||||
$modules/cloud/misc/xenserver_facts.py:
|
||||
maintainers: caphrim007 cheese
|
||||
labels: xenserver_facts
|
||||
@@ -342,6 +344,8 @@ 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:
|
||||
@@ -373,6 +377,8 @@ 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:
|
||||
@@ -560,7 +566,8 @@ files:
|
||||
$modules/packaging/language/bundler.py:
|
||||
maintainers: thoiberg
|
||||
$modules/packaging/language/composer.py:
|
||||
maintainers: dmtrs resmo
|
||||
maintainers: dmtrs
|
||||
ignore: resmo
|
||||
$modules/packaging/language/cpanm.py:
|
||||
maintainers: fcuny russoz
|
||||
$modules/packaging/language/easy_install.py:
|
||||
@@ -642,6 +649,9 @@ 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
|
||||
@@ -707,7 +717,9 @@ files:
|
||||
labels: zypper
|
||||
ignore: dirtyharrycallahan robinro
|
||||
$modules/packaging/os/zypper_repository.py:
|
||||
maintainers: matze
|
||||
maintainers: $team_suse
|
||||
labels: zypper
|
||||
ignore: matze
|
||||
$modules/remote_management/cobbler/:
|
||||
maintainers: dagwieers
|
||||
$modules/remote_management/hpilo/:
|
||||
@@ -836,6 +848,8 @@ files:
|
||||
labels: interfaces_file
|
||||
$modules/system/iptables_state.py:
|
||||
maintainers: quidame
|
||||
$modules/system/shutdown.py:
|
||||
maintainers: nitzmahone samdoran aminvakil
|
||||
$modules/system/java_cert.py:
|
||||
maintainers: haad absynth76
|
||||
$modules/system/java_keystore.py:
|
||||
@@ -1025,5 +1039,5 @@ macros:
|
||||
team_rhn: FlossWare alikins barnabycourt vritant
|
||||
team_scaleway: QuentinBrosse abarbare jerome-quere kindermoumoute remyleone sieben
|
||||
team_solaris: bcoca fishman jasperla jpdasma mator scathatheworm troy2914 xen0l
|
||||
team_suse: commel dcermak evrardjp lrupp toabctl AnderEnder alxgu andytom
|
||||
team_suse: commel dcermak evrardjp lrupp toabctl AnderEnder alxgu andytom sealor
|
||||
team_virt: joshainglis karmab tleguern Thulium-Drake Ajpantuso
|
||||
|
||||
220
CHANGELOG.rst
220
CHANGELOG.rst
@@ -6,6 +6,226 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 2.0.0.
|
||||
|
||||
v3.2.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- Remove unnecessary ``__init__.py`` files from ``plugins/`` (https://github.com/ansible-collections/community.general/pull/2632).
|
||||
- archive - added ``exclusion_patterns`` option to exclude files or subdirectories from archives (https://github.com/ansible-collections/community.general/pull/2616).
|
||||
- cloud_init_data_facts - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- composer - add ``composer_executable`` option (https://github.com/ansible-collections/community.general/issues/2649).
|
||||
- flatpak - add ``no_dependencies`` parameter (https://github.com/ansible/ansible/pull/55452, https://github.com/ansible-collections/community.general/pull/2751).
|
||||
- ini_file - opening file with encoding ``utf-8-sig`` (https://github.com/ansible-collections/community.general/issues/2189).
|
||||
- jira - add comment visibility parameter for comment operation (https://github.com/ansible-collections/community.general/pull/2556).
|
||||
- maven_artifact - added ``checksum_alg`` option to support SHA1 checksums in order to support FIPS systems (https://github.com/ansible-collections/community.general/pull/2662).
|
||||
- module_helper module utils - method ``CmdMixin.run_command()`` now accepts ``process_output`` specifying a function to process the outcome of the underlying ``module.run_command()`` (https://github.com/ansible-collections/community.general/pull/2564).
|
||||
- nmcli - add new options to ignore automatic DNS servers and gateways (https://github.com/ansible-collections/community.general/issues/1087).
|
||||
- onepassword lookup plugin - add ``domain`` option (https://github.com/ansible-collections/community.general/issues/2734).
|
||||
- open_iscsi - add ``auto_portal_startup`` parameter to allow ``node.startup`` setting per portal (https://github.com/ansible-collections/community.general/issues/2685).
|
||||
- open_iscsi - also consider ``portal`` and ``port`` to check if already logged in or not (https://github.com/ansible-collections/community.general/issues/2683).
|
||||
- proxmox_group_info - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- proxmox_kvm - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- rhevm - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- serverless - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- stacki_host - minor refactoring (https://github.com/ansible-collections/community.general/pull/2681).
|
||||
- terraform - add option ``overwrite_init`` to skip init if exists (https://github.com/ansible-collections/community.general/pull/2573).
|
||||
- terraform - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- All inventory and vault scripts will be removed from community.general in version 4.0.0. If you are referencing them, please update your references to the new `contrib-scripts GitHub repository <https://github.com/ansible-community/contrib-scripts>`_ so your workflow will not break once community.general 4.0.0 is released (https://github.com/ansible-collections/community.general/pull/2697).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- consul_kv lookup plugin - allow to set ``recurse``, ``index``, ``datacenter`` and ``token`` as keyword arguments (https://github.com/ansible-collections/community.general/issues/2124).
|
||||
- cpanm - also use ``LC_ALL`` to enforce locale choice (https://github.com/ansible-collections/community.general/pull/2731).
|
||||
- influxdb_user - fix bug which removed current privileges instead of appending them to existing ones (https://github.com/ansible-collections/community.general/issues/2609, https://github.com/ansible-collections/community.general/pull/2614).
|
||||
- iptables_state - call ``async_status`` action plugin rather than its module (https://github.com/ansible-collections/community.general/issues/2700).
|
||||
- iptables_state - fix a broken query of ``async_status`` result with current ansible-core development version (https://github.com/ansible-collections/community.general/issues/2627, https://github.com/ansible-collections/community.general/pull/2671).
|
||||
- java_cert - fix issue with incorrect alias used on PKCS#12 certificate import (https://github.com/ansible-collections/community.general/pull/2560).
|
||||
- jenkins_plugin - use POST method for sending request to jenkins API when ``state`` option is one of ``enabled``, ``disabled``, ``pinned``, ``unpinned``, or ``absent`` (https://github.com/ansible-collections/community.general/issues/2510).
|
||||
- json_query filter plugin - avoid 'unknown type' errors for more Ansible internal types (https://github.com/ansible-collections/community.general/pull/2607).
|
||||
- keycloak_realm - ``ssl_required`` changed from a boolean type to accept the strings ``none``, ``external`` or ``all``. This is not a breaking change since the module always failed when a boolean was supplied (https://github.com/ansible-collections/community.general/pull/2693).
|
||||
- keycloak_realm - remove warning that ``reset_password_allowed`` needs to be marked as ``no_log`` (https://github.com/ansible-collections/community.general/pull/2694).
|
||||
- module_helper module utils - ``CmdMixin`` must also use ``LC_ALL`` to enforce locale choice (https://github.com/ansible-collections/community.general/pull/2731).
|
||||
- netcup_dns - use ``str(ex)`` instead of unreliable ``ex.message`` in exception handling to fix ``AttributeError`` in error cases (https://github.com/ansible-collections/community.general/pull/2590).
|
||||
- ovir4 inventory script - improve configparser creation to avoid crashes for options without values (https://github.com/ansible-collections/community.general/issues/674).
|
||||
- proxmox_kvm - fixed ``vmid`` return value when VM with ``name`` already exists (https://github.com/ansible-collections/community.general/issues/2648).
|
||||
- redis cache - improved connection string parsing (https://github.com/ansible-collections/community.general/issues/497).
|
||||
- rhsm_release - fix the issue that module considers 8, 7Client and 7Workstation as invalid releases (https://github.com/ansible-collections/community.general/pull/2571).
|
||||
- ssh_config - reduce stormssh searches based on host (https://github.com/ansible-collections/community.general/pull/2568/).
|
||||
- stacki_host - when adding a new server, ``rack`` and ``rank`` must be passed, and network parameters are optional (https://github.com/ansible-collections/community.general/pull/2681).
|
||||
- terraform - ensure the workspace is set back to its previous value when the apply fails (https://github.com/ansible-collections/community.general/pull/2634).
|
||||
- xfconf - also use ``LC_ALL`` to enforce locale choice (https://github.com/ansible-collections/community.general/issues/2715).
|
||||
- zypper_repository - fix idempotency on adding repository with ``$releasever`` and ``$basearch`` variables (https://github.com/ansible-collections/community.general/issues/1985).
|
||||
|
||||
New Plugins
|
||||
-----------
|
||||
|
||||
Lookup
|
||||
~~~~~~
|
||||
|
||||
- random_string - Generates random string
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
Database
|
||||
~~~~~~~~
|
||||
|
||||
saphana
|
||||
^^^^^^^
|
||||
|
||||
- hana_query - Execute SQL on HANA
|
||||
|
||||
Files
|
||||
~~~~~
|
||||
|
||||
- sapcar_extract - Manages SAP SAPCAR archives
|
||||
|
||||
Packaging
|
||||
~~~~~~~~~
|
||||
|
||||
os
|
||||
^^
|
||||
|
||||
- pacman_key - Manage pacman's list of trusted keys
|
||||
|
||||
v3.1.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular feature and bugfix release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- ModuleHelper module utils - improved mechanism for customizing the calculation of ``changed`` (https://github.com/ansible-collections/community.general/pull/2514).
|
||||
- chroot connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- cmd (Module Helper) module utils - ``CmdMixin`` now pulls the value for ``run_command()`` params from ``self.vars``, as opposed to previously retrieving those from ``self.module.params`` (https://github.com/ansible-collections/community.general/pull/2517).
|
||||
- filesystem - cleanup and revamp module, tests and doc. Pass all commands to ``module.run_command()`` as lists. Move the device-vs-mountpoint logic to ``grow()`` method. Give to all ``get_fs_size()`` the same logic and error handling. (https://github.com/ansible-collections/community.general/pull/2472).
|
||||
- funcd connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- gitlab_user - add ``expires_at`` option (https://github.com/ansible-collections/community.general/issues/2325).
|
||||
- idrac_redfish_config - modified set_manager_attributes function to skip invalid attribute instead of returning. Added skipped attributes to output. Modified module exit to add warning variable (https://github.com/ansible-collections/community.general/issues/1995).
|
||||
- influxdb_retention_policy - add ``state`` parameter with allowed values ``present`` and ``absent`` to support deletion of existing retention policies (https://github.com/ansible-collections/community.general/issues/2383).
|
||||
- influxdb_retention_policy - simplify duration logic parsing (https://github.com/ansible-collections/community.general/pull/2385).
|
||||
- iocage connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- jail connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- java_keystore - added ``ssl_backend`` parameter for using the cryptography library instead of the OpenSSL binary (https://github.com/ansible-collections/community.general/pull/2485).
|
||||
- java_keystore - replace envvar by stdin to pass secret to ``keytool`` (https://github.com/ansible-collections/community.general/pull/2526).
|
||||
- linode - added proper traceback when failing due to exceptions (https://github.com/ansible-collections/community.general/pull/2410).
|
||||
- linode - parameter ``additional_disks`` is now validated as a list of dictionaries (https://github.com/ansible-collections/community.general/pull/2410).
|
||||
- lxc connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- module_helper module utils - break down of the long file into smaller pieces (https://github.com/ansible-collections/community.general/pull/2393).
|
||||
- nmcli - remove dead code, ``options`` never contains keys from ``param_alias`` (https://github.com/ansible-collections/community.general/pull/2417).
|
||||
- pacman - add ``executable`` option to use an alternative pacman binary (https://github.com/ansible-collections/community.general/issues/2524).
|
||||
- passwordstore lookup - add option ``missing`` to choose what to do if the password file is missing (https://github.com/ansible-collections/community.general/pull/2500).
|
||||
- qubes connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- redfish_config - modified module exit to add warning variable (https://github.com/ansible-collections/community.general/issues/1995).
|
||||
- redfish_utils module utils - modified set_bios_attributes function to skip invalid attribute instead of returning. Added skipped attributes to output (https://github.com/ansible-collections/community.general/issues/1995).
|
||||
- saltstack connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- spotinst_aws_elastigroup - elements of list parameters are now validated (https://github.com/ansible-collections/community.general/pull/2355).
|
||||
- zfs_delegate_admin - drop choices from permissions, allowing any permission supported by the underlying zfs commands (https://github.com/ansible-collections/community.general/pull/2540).
|
||||
- zone connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- The nios, nios_next_ip, nios_next_network lookup plugins, the nios documentation fragment, and the nios_host_record, nios_ptr_record, nios_mx_record, nios_fixed_address, nios_zone, nios_member, nios_a_record, nios_aaaa_record, nios_network, nios_dns_view, nios_txt_record, nios_naptr_record, nios_srv_record, nios_cname_record, nios_nsgroup, and nios_network_view module have been deprecated and will be removed from community.general 5.0.0. Please install the `infoblox.nios_modules <https://galaxy.ansible.com/infoblox/nios_modules>`_ collection instead and use its plugins and modules (https://github.com/ansible-collections/community.general/pull/2458).
|
||||
- The vendored copy of ``ipaddress`` will be removed in community.general 4.0.0. Please switch to ``ipaddress`` from the Python 3 standard library, or `from pypi <https://pypi.org/project/ipaddress/>`_, if your code relies on the vendored version of ``ipaddress`` (https://github.com/ansible-collections/community.general/pull/2459).
|
||||
- linode - parameter ``backupsenabled`` is deprecated and will be removed in community.general 5.0.0 (https://github.com/ansible-collections/community.general/pull/2410).
|
||||
- lxd inventory plugin - the plugin will require ``ipaddress`` installed when used with Python 2 from community.general 4.0.0 on. ``ipaddress`` is part of the Python 3 standard library, but can be installed for Python 2 from pypi (https://github.com/ansible-collections/community.general/pull/2459).
|
||||
- scaleway_security_group_rule - the module will require ``ipaddress`` installed when used with Python 2 from community.general 4.0.0 on. ``ipaddress`` is part of the Python 3 standard library, but can be installed for Python 2 from pypi (https://github.com/ansible-collections/community.general/pull/2459).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- consul_acl - update the hcl allowlist to include all supported options (https://github.com/ansible-collections/community.general/pull/2495).
|
||||
- filesystem - repair ``reiserfs`` fstype support after adding it to integration tests (https://github.com/ansible-collections/community.general/pull/2472).
|
||||
- influxdb_user - allow creation of admin users when InfluxDB authentication is enabled but no other user exists on the database. In this scenario, InfluxDB 1.x allows only ``CREATE USER`` queries and rejects any other query (https://github.com/ansible-collections/community.general/issues/2364).
|
||||
- influxdb_user - fix bug where an influxdb user has no privileges for 2 or more databases (https://github.com/ansible-collections/community.general/pull/2499).
|
||||
- iptables_state - fix a 'FutureWarning' in a regex and do some basic code clean up (https://github.com/ansible-collections/community.general/pull/2525).
|
||||
- iptables_state - fix initialization of iptables from null state when adressing more than one table (https://github.com/ansible-collections/community.general/issues/2523).
|
||||
- nmap inventory plugin - fix local variable error when cache is disabled (https://github.com/ansible-collections/community.general/issues/2512).
|
||||
|
||||
New Plugins
|
||||
-----------
|
||||
|
||||
Filter
|
||||
~~~~~~
|
||||
|
||||
- groupby_as_dict - Transform a sequence of dictionaries to a dictionary where the dictionaries are indexed by an attribute
|
||||
|
||||
Lookup
|
||||
~~~~~~
|
||||
|
||||
- dependent - Composes a list with nested elements of other lists or dicts which can depend on previous loop variables
|
||||
- random_pet - Generates random pet names
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
Cloud
|
||||
~~~~~
|
||||
|
||||
misc
|
||||
^^^^
|
||||
|
||||
- proxmox_nic - Management of a NIC of a Qemu(KVM) VM in a Proxmox VE cluster.
|
||||
|
||||
Notification
|
||||
~~~~~~~~~~~~
|
||||
|
||||
- discord - Send Discord messages
|
||||
|
||||
v3.0.2
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release for the first Ansible 4.0.0 release candidate.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- stackpath_compute inventory script - fix broken validation checks for client ID and client secret (https://github.com/ansible-collections/community.general/pull/2448).
|
||||
- zfs - certain ZFS properties, especially sizes, would lead to a task being falsely marked as "changed" even when no actual change was made (https://github.com/ansible-collections/community.general/issues/975, https://github.com/ansible-collections/community.general/pull/2454).
|
||||
|
||||
v3.0.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release for the next Ansible 4.0.0 beta.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- composer - use ``no-interaction`` option when discovering available options to avoid an issue where composer hangs (https://github.com/ansible-collections/community.general/pull/2348).
|
||||
- influxdb_retention_policy - fix bug where ``INF`` duration values failed parsing (https://github.com/ansible-collections/community.general/pull/2385).
|
||||
- inventory and vault scripts - change file permissions to make vendored inventory and vault scripts exectuable (https://github.com/ansible-collections/community.general/pull/2337).
|
||||
- linode_v4 - changed the error message to point to the correct bugtracker URL (https://github.com/ansible-collections/community.general/pull/2430).
|
||||
- lvol - fixed rounding errors (https://github.com/ansible-collections/community.general/issues/2370).
|
||||
- lvol - fixed size unit capitalization to match units used between different tools for comparison (https://github.com/ansible-collections/community.general/issues/2360).
|
||||
- nmcli - compare MAC addresses case insensitively to fix idempotency issue (https://github.com/ansible-collections/community.general/issues/2409).
|
||||
- nmcli - if type is ``bridge-slave`` add ``slave-type bridge`` to ``nmcli`` command (https://github.com/ansible-collections/community.general/issues/2408).
|
||||
- one_vm - Allow missing NIC keys (https://github.com/ansible-collections/community.general/pull/2435).
|
||||
- puppet - replace ``console` with ``stdout`` in ``logdest`` option when ``all`` has been chosen (https://github.com/ansible-collections/community.general/issues/1190).
|
||||
- svr4pkg - convert string to a bytes-like object to avoid ``TypeError`` with Python 3 (https://github.com/ansible-collections/community.general/issues/2373).
|
||||
|
||||
v3.0.0
|
||||
======
|
||||
|
||||
|
||||
32
CONTRIBUTING.md
Normal file
32
CONTRIBUTING.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Contributing
|
||||
|
||||
We follow [Ansible Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) in all our contributions and interactions within this repository.
|
||||
|
||||
If you are a committer, also refer to the [collection's committer guidelines](https://github.com/ansible-collections/community.general/blob/main/commit-rights.md).
|
||||
|
||||
## Issue tracker
|
||||
|
||||
Whether you are looking for an opportunity to contribute or you found a bug and already know how to solve it, please go to the [issue tracker](https://github.com/ansible-collections/community.general/issues).
|
||||
There you can find feature ideas to implement, reports about bugs to solve, or submit an issue to discuss your idea before implementing it which can help choose a right direction at the beginning of your work and potentially save a lot of time and effort.
|
||||
Also somebody may already have started discussing or working on implementing the same or a similar idea,
|
||||
so you can cooperate to create a better solution together.
|
||||
|
||||
* If you are interested in starting with an easy issue, look for [issues with an `easyfix` label](https://github.com/ansible-collections/community.general/labels/easyfix).
|
||||
* Often issues that are waiting for contributors to pick up have [the `waiting_on_contributor` label](https://github.com/ansible-collections/community.general/labels/waiting_on_contributor).
|
||||
|
||||
## Open pull requests
|
||||
|
||||
Look through currently [open pull requests](https://github.com/ansible-collections/community.general/pulls).
|
||||
You can help by reviewing them. Reviews help move pull requests to merge state. Some good pull requests cannot be merged only due to a lack of reviews. And it is always worth saying that good reviews are often more valuable than pull requests themselves.
|
||||
Note that reviewing does not only mean code review, but also offering comments on new interfaces added to existing plugins/modules, interfaces of new plugins/modules, improving language (not everyone is a native english speaker), or testing bugfixes and new features!
|
||||
|
||||
Also, consider taking up a valuable, reviewed, but abandoned pull request which you could politely ask the original authors to complete yourself.
|
||||
|
||||
* Try committing your changes with an informative but short commit message.
|
||||
* All commits of a pull request branch will be squashed into one commit at last. That does not mean you must have only one commit on your pull request, though!
|
||||
* Please try not to force-push if it is not needed, so reviewers and other users looking at your pull request later can see the pull request commit history.
|
||||
* Do not add merge commits to your PR. The bot will complain and you will have to rebase ([instructions for rebasing](https://docs.ansible.com/ansible/latest/dev_guide/developing_rebasing.html)) to remove them before your PR can be merged. To avoid that git automatically does merges during pulls, you can configure it to do rebases instead by running `git config pull.rebase true` inside the respository checkout.
|
||||
|
||||
You can also read [our Quick-start development guide](https://github.com/ansible/community-docs/blob/main/create_pr_quick_start_guide.rst).
|
||||
|
||||
If you find any inconsistencies or places in this document which can be improved, feel free to raise an issue or pull request to fix it.
|
||||
10
README.md
10
README.md
@@ -7,6 +7,8 @@ This repo contains the `community.general` Ansible Collection. The collection in
|
||||
|
||||
You can find [documentation for this collection on the Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/general/).
|
||||
|
||||
Please note that this collection does **not** support Windows targets. Only connection plugins included in this collection might support Windows targets, and will explicitly mention that in their documentation if they do so.
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
Tested with the current Ansible 2.9, ansible-base 2.10 and ansible-core 2.11 releases and the current development version of ansible-core. Ansible versions before 2.9.10 are not supported.
|
||||
@@ -48,6 +50,8 @@ export COLLECTIONS_PATH=$(pwd)/collections:$COLLECTIONS_PATH
|
||||
|
||||
You can find more information in the [developer guide for collections](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections), and in the [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html).
|
||||
|
||||
Also for some notes specific to this collection see [our CONTRIBUTING documentation](https://github.com/ansible-collections/community.general/blob/main/CONTRIBUTING.md).
|
||||
|
||||
### Running tests
|
||||
|
||||
See [here](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#testing-collections).
|
||||
@@ -56,10 +60,10 @@ See [here](https://docs.ansible.com/ansible/devel/dev_guide/developing_collectio
|
||||
|
||||
We have a dedicated Working Group for Ansible development.
|
||||
|
||||
You can find other people interested on the following Freenode IRC channels -
|
||||
You can find other people interested on the following [Libera.chat](https://libera.chat/) IRC channels -
|
||||
- `#ansible` - For general use questions and support.
|
||||
- `#ansible-devel` - For discussions on developer topics and code related to features or bugs.
|
||||
- `#ansible-community` - For discussions on community topics and community meetings.
|
||||
- `#ansible-devel` - For discussions on developer topics and code related to features or bugs in ansible-core.
|
||||
- `#ansible-community` - For discussions on community topics and community meetings, and for general development questions for community collections.
|
||||
|
||||
For more information about communities, meetings and agendas see [Community Wiki](https://github.com/ansible/community/wiki/Community).
|
||||
|
||||
|
||||
@@ -973,3 +973,316 @@ releases:
|
||||
name: lxd
|
||||
namespace: null
|
||||
release_date: '2021-04-26'
|
||||
3.0.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- composer - use ``no-interaction`` option when discovering available options
|
||||
to avoid an issue where composer hangs (https://github.com/ansible-collections/community.general/pull/2348).
|
||||
- influxdb_retention_policy - fix bug where ``INF`` duration values failed parsing
|
||||
(https://github.com/ansible-collections/community.general/pull/2385).
|
||||
- inventory and vault scripts - change file permissions to make vendored inventory
|
||||
and vault scripts exectuable (https://github.com/ansible-collections/community.general/pull/2337).
|
||||
- linode_v4 - changed the error message to point to the correct bugtracker URL
|
||||
(https://github.com/ansible-collections/community.general/pull/2430).
|
||||
- lvol - fixed rounding errors (https://github.com/ansible-collections/community.general/issues/2370).
|
||||
- lvol - fixed size unit capitalization to match units used between different
|
||||
tools for comparison (https://github.com/ansible-collections/community.general/issues/2360).
|
||||
- nmcli - compare MAC addresses case insensitively to fix idempotency issue
|
||||
(https://github.com/ansible-collections/community.general/issues/2409).
|
||||
- nmcli - if type is ``bridge-slave`` add ``slave-type bridge`` to ``nmcli``
|
||||
command (https://github.com/ansible-collections/community.general/issues/2408).
|
||||
- one_vm - Allow missing NIC keys (https://github.com/ansible-collections/community.general/pull/2435).
|
||||
- puppet - replace ``console` with ``stdout`` in ``logdest`` option when ``all``
|
||||
has been chosen (https://github.com/ansible-collections/community.general/issues/1190).
|
||||
- svr4pkg - convert string to a bytes-like object to avoid ``TypeError`` with
|
||||
Python 3 (https://github.com/ansible-collections/community.general/issues/2373).
|
||||
release_summary: Bugfix release for the next Ansible 4.0.0 beta.
|
||||
fragments:
|
||||
- 2284-influxdb_retention_policy-fix_duration_parsing.yml
|
||||
- 2337-mark-inventory-scripts-executable.yml
|
||||
- 2348-composer-no-interaction-option-discovery-to-avoid-hang.yaml
|
||||
- 2369-lvol_size_bug_fixes.yml
|
||||
- 2373-svr4pkg-fix-typeerror.yml
|
||||
- 2407-puppet-change_stdout_to_console.yaml
|
||||
- 2409-nmcli_add_slave-type_bridge_to_nmcli_command_if_type_is_bridge-slave.yml
|
||||
- 2416-nmcli_compare_mac_addresses_case_insensitively.yml
|
||||
- 2430-linodev4-error-message.yml
|
||||
- 2435-one_vm-fix_missing_keys.yml
|
||||
- 3.0.1.yml
|
||||
release_date: '2021-05-04'
|
||||
3.0.2:
|
||||
changes:
|
||||
bugfixes:
|
||||
- stackpath_compute inventory script - fix broken validation checks for client
|
||||
ID and client secret (https://github.com/ansible-collections/community.general/pull/2448).
|
||||
- zfs - certain ZFS properties, especially sizes, would lead to a task being
|
||||
falsely marked as "changed" even when no actual change was made (https://github.com/ansible-collections/community.general/issues/975,
|
||||
https://github.com/ansible-collections/community.general/pull/2454).
|
||||
release_summary: Bugfix release for the first Ansible 4.0.0 release candidate.
|
||||
fragments:
|
||||
- 2448-stackpath_compute-fix.yml
|
||||
- 2454-detect_zfs_changed.yml
|
||||
- 3.0.2.yml
|
||||
release_date: '2021-05-11'
|
||||
3.1.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- consul_acl - update the hcl allowlist to include all supported options (https://github.com/ansible-collections/community.general/pull/2495).
|
||||
- filesystem - repair ``reiserfs`` fstype support after adding it to integration
|
||||
tests (https://github.com/ansible-collections/community.general/pull/2472).
|
||||
- influxdb_user - allow creation of admin users when InfluxDB authentication
|
||||
is enabled but no other user exists on the database. In this scenario, InfluxDB
|
||||
1.x allows only ``CREATE USER`` queries and rejects any other query (https://github.com/ansible-collections/community.general/issues/2364).
|
||||
- influxdb_user - fix bug where an influxdb user has no privileges for 2 or
|
||||
more databases (https://github.com/ansible-collections/community.general/pull/2499).
|
||||
- iptables_state - fix a 'FutureWarning' in a regex and do some basic code clean
|
||||
up (https://github.com/ansible-collections/community.general/pull/2525).
|
||||
- iptables_state - fix initialization of iptables from null state when adressing
|
||||
more than one table (https://github.com/ansible-collections/community.general/issues/2523).
|
||||
- nmap inventory plugin - fix local variable error when cache is disabled (https://github.com/ansible-collections/community.general/issues/2512).
|
||||
deprecated_features:
|
||||
- The nios, nios_next_ip, nios_next_network lookup plugins, the nios documentation
|
||||
fragment, and the nios_host_record, nios_ptr_record, nios_mx_record, nios_fixed_address,
|
||||
nios_zone, nios_member, nios_a_record, nios_aaaa_record, nios_network, nios_dns_view,
|
||||
nios_txt_record, nios_naptr_record, nios_srv_record, nios_cname_record, nios_nsgroup,
|
||||
and nios_network_view module have been deprecated and will be removed from
|
||||
community.general 5.0.0. Please install the `infoblox.nios_modules <https://galaxy.ansible.com/infoblox/nios_modules>`_
|
||||
collection instead and use its plugins and modules (https://github.com/ansible-collections/community.general/pull/2458).
|
||||
- The vendored copy of ``ipaddress`` will be removed in community.general 4.0.0.
|
||||
Please switch to ``ipaddress`` from the Python 3 standard library, or `from
|
||||
pypi <https://pypi.org/project/ipaddress/>`_, if your code relies on the vendored
|
||||
version of ``ipaddress`` (https://github.com/ansible-collections/community.general/pull/2459).
|
||||
- linode - parameter ``backupsenabled`` is deprecated and will be removed in
|
||||
community.general 5.0.0 (https://github.com/ansible-collections/community.general/pull/2410).
|
||||
- lxd inventory plugin - the plugin will require ``ipaddress`` installed when
|
||||
used with Python 2 from community.general 4.0.0 on. ``ipaddress`` is part
|
||||
of the Python 3 standard library, but can be installed for Python 2 from pypi
|
||||
(https://github.com/ansible-collections/community.general/pull/2459).
|
||||
- scaleway_security_group_rule - the module will require ``ipaddress`` installed
|
||||
when used with Python 2 from community.general 4.0.0 on. ``ipaddress`` is
|
||||
part of the Python 3 standard library, but can be installed for Python 2 from
|
||||
pypi (https://github.com/ansible-collections/community.general/pull/2459).
|
||||
minor_changes:
|
||||
- ModuleHelper module utils - improved mechanism for customizing the calculation
|
||||
of ``changed`` (https://github.com/ansible-collections/community.general/pull/2514).
|
||||
- chroot connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- cmd (Module Helper) module utils - ``CmdMixin`` now pulls the value for ``run_command()``
|
||||
params from ``self.vars``, as opposed to previously retrieving those from
|
||||
``self.module.params`` (https://github.com/ansible-collections/community.general/pull/2517).
|
||||
- filesystem - cleanup and revamp module, tests and doc. Pass all commands to
|
||||
``module.run_command()`` as lists. Move the device-vs-mountpoint logic to
|
||||
``grow()`` method. Give to all ``get_fs_size()`` the same logic and error
|
||||
handling. (https://github.com/ansible-collections/community.general/pull/2472).
|
||||
- funcd connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- gitlab_user - add ``expires_at`` option (https://github.com/ansible-collections/community.general/issues/2325).
|
||||
- idrac_redfish_config - modified set_manager_attributes function to skip invalid
|
||||
attribute instead of returning. Added skipped attributes to output. Modified
|
||||
module exit to add warning variable (https://github.com/ansible-collections/community.general/issues/1995).
|
||||
- influxdb_retention_policy - add ``state`` parameter with allowed values ``present``
|
||||
and ``absent`` to support deletion of existing retention policies (https://github.com/ansible-collections/community.general/issues/2383).
|
||||
- influxdb_retention_policy - simplify duration logic parsing (https://github.com/ansible-collections/community.general/pull/2385).
|
||||
- iocage connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- jail connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- java_keystore - added ``ssl_backend`` parameter for using the cryptography
|
||||
library instead of the OpenSSL binary (https://github.com/ansible-collections/community.general/pull/2485).
|
||||
- java_keystore - replace envvar by stdin to pass secret to ``keytool`` (https://github.com/ansible-collections/community.general/pull/2526).
|
||||
- linode - added proper traceback when failing due to exceptions (https://github.com/ansible-collections/community.general/pull/2410).
|
||||
- linode - parameter ``additional_disks`` is now validated as a list of dictionaries
|
||||
(https://github.com/ansible-collections/community.general/pull/2410).
|
||||
- lxc connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- module_helper module utils - break down of the long file into smaller pieces
|
||||
(https://github.com/ansible-collections/community.general/pull/2393).
|
||||
- nmcli - remove dead code, ``options`` never contains keys from ``param_alias``
|
||||
(https://github.com/ansible-collections/community.general/pull/2417).
|
||||
- pacman - add ``executable`` option to use an alternative pacman binary (https://github.com/ansible-collections/community.general/issues/2524).
|
||||
- passwordstore lookup - add option ``missing`` to choose what to do if the
|
||||
password file is missing (https://github.com/ansible-collections/community.general/pull/2500).
|
||||
- qubes connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- redfish_config - modified module exit to add warning variable (https://github.com/ansible-collections/community.general/issues/1995).
|
||||
- redfish_utils module utils - modified set_bios_attributes function to skip
|
||||
invalid attribute instead of returning. Added skipped attributes to output
|
||||
(https://github.com/ansible-collections/community.general/issues/1995).
|
||||
- saltstack connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
- spotinst_aws_elastigroup - elements of list parameters are now validated (https://github.com/ansible-collections/community.general/pull/2355).
|
||||
- zfs_delegate_admin - drop choices from permissions, allowing any permission
|
||||
supported by the underlying zfs commands (https://github.com/ansible-collections/community.general/pull/2540).
|
||||
- zone connection - minor refactor to make lints and IDEs happy (https://github.com/ansible-collections/community.general/pull/2520).
|
||||
release_summary: Regular feature and bugfix release.
|
||||
fragments:
|
||||
- 1085-consul-acl-hcl-whitelist-update.yml
|
||||
- 2323-groupby_as_dict-filter.yml
|
||||
- 2334-redfish_config-skip-incorrect-attributes.yml
|
||||
- 2355-spotinst_aws_elastigroup-list-elements.yml
|
||||
- 2364-influxdb_user-first_user.yml
|
||||
- 2383-influxdb_retention_policy-add-state-option.yml
|
||||
- 2393-module_helper-breakdown.yml
|
||||
- 2410-linode-improvements.yml
|
||||
- 2417-nmcli_remove_dead_code.yml
|
||||
- 2450-gitlab_user-add_expires_at_option.yaml
|
||||
- 2472_filesystem_module_revamp.yml
|
||||
- 2485-java_keystore-ssl_backend-parameter.yml
|
||||
- 2499-influxdb_user-fix-multiple-no-privileges.yml
|
||||
- 2500-passwordstore-add_option_ignore_missing.yml
|
||||
- 2514-mh-improved-changed.yml
|
||||
- 2517-cmd-params-from-vars.yml
|
||||
- 2518-nmap-fix-cache-disabled.yml
|
||||
- 2520-connection-refactors.yml
|
||||
- 2524-pacman_add_bin_option.yml
|
||||
- 2525-iptables_state-fix-initialization-command.yml
|
||||
- 2526-java_keystore-password-via-stdin.yml
|
||||
- 2540-zfs-delegate-choices.yml
|
||||
- 3.1.0.yml
|
||||
- deprecate-ipaddress.yml
|
||||
- nios-deprecation.yml
|
||||
modules:
|
||||
- description: Send Discord messages
|
||||
name: discord
|
||||
namespace: notification
|
||||
- description: Management of a NIC of a Qemu(KVM) VM in a Proxmox VE cluster.
|
||||
name: proxmox_nic
|
||||
namespace: cloud.misc
|
||||
plugins:
|
||||
filter:
|
||||
- description: Transform a sequence of dictionaries to a dictionary where the
|
||||
dictionaries are indexed by an attribute
|
||||
name: groupby_as_dict
|
||||
namespace: null
|
||||
lookup:
|
||||
- description: Composes a list with nested elements of other lists or dicts
|
||||
which can depend on previous loop variables
|
||||
name: dependent
|
||||
namespace: null
|
||||
- description: Generates random pet names
|
||||
name: random_pet
|
||||
namespace: null
|
||||
release_date: '2021-05-18'
|
||||
3.2.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- consul_kv lookup plugin - allow to set ``recurse``, ``index``, ``datacenter``
|
||||
and ``token`` as keyword arguments (https://github.com/ansible-collections/community.general/issues/2124).
|
||||
- cpanm - also use ``LC_ALL`` to enforce locale choice (https://github.com/ansible-collections/community.general/pull/2731).
|
||||
- influxdb_user - fix bug which removed current privileges instead of appending
|
||||
them to existing ones (https://github.com/ansible-collections/community.general/issues/2609,
|
||||
https://github.com/ansible-collections/community.general/pull/2614).
|
||||
- iptables_state - call ``async_status`` action plugin rather than its module
|
||||
(https://github.com/ansible-collections/community.general/issues/2700).
|
||||
- iptables_state - fix a broken query of ``async_status`` result with current
|
||||
ansible-core development version (https://github.com/ansible-collections/community.general/issues/2627,
|
||||
https://github.com/ansible-collections/community.general/pull/2671).
|
||||
- java_cert - fix issue with incorrect alias used on PKCS#12 certificate import
|
||||
(https://github.com/ansible-collections/community.general/pull/2560).
|
||||
- jenkins_plugin - use POST method for sending request to jenkins API when ``state``
|
||||
option is one of ``enabled``, ``disabled``, ``pinned``, ``unpinned``, or ``absent``
|
||||
(https://github.com/ansible-collections/community.general/issues/2510).
|
||||
- json_query filter plugin - avoid 'unknown type' errors for more Ansible internal
|
||||
types (https://github.com/ansible-collections/community.general/pull/2607).
|
||||
- keycloak_realm - ``ssl_required`` changed from a boolean type to accept the
|
||||
strings ``none``, ``external`` or ``all``. This is not a breaking change since
|
||||
the module always failed when a boolean was supplied (https://github.com/ansible-collections/community.general/pull/2693).
|
||||
- keycloak_realm - remove warning that ``reset_password_allowed`` needs to be
|
||||
marked as ``no_log`` (https://github.com/ansible-collections/community.general/pull/2694).
|
||||
- module_helper module utils - ``CmdMixin`` must also use ``LC_ALL`` to enforce
|
||||
locale choice (https://github.com/ansible-collections/community.general/pull/2731).
|
||||
- netcup_dns - use ``str(ex)`` instead of unreliable ``ex.message`` in exception
|
||||
handling to fix ``AttributeError`` in error cases (https://github.com/ansible-collections/community.general/pull/2590).
|
||||
- ovir4 inventory script - improve configparser creation to avoid crashes for
|
||||
options without values (https://github.com/ansible-collections/community.general/issues/674).
|
||||
- proxmox_kvm - fixed ``vmid`` return value when VM with ``name`` already exists
|
||||
(https://github.com/ansible-collections/community.general/issues/2648).
|
||||
- redis cache - improved connection string parsing (https://github.com/ansible-collections/community.general/issues/497).
|
||||
- rhsm_release - fix the issue that module considers 8, 7Client and 7Workstation
|
||||
as invalid releases (https://github.com/ansible-collections/community.general/pull/2571).
|
||||
- ssh_config - reduce stormssh searches based on host (https://github.com/ansible-collections/community.general/pull/2568/).
|
||||
- stacki_host - when adding a new server, ``rack`` and ``rank`` must be passed,
|
||||
and network parameters are optional (https://github.com/ansible-collections/community.general/pull/2681).
|
||||
- terraform - ensure the workspace is set back to its previous value when the
|
||||
apply fails (https://github.com/ansible-collections/community.general/pull/2634).
|
||||
- xfconf - also use ``LC_ALL`` to enforce locale choice (https://github.com/ansible-collections/community.general/issues/2715).
|
||||
- zypper_repository - fix idempotency on adding repository with ``$releasever``
|
||||
and ``$basearch`` variables (https://github.com/ansible-collections/community.general/issues/1985).
|
||||
deprecated_features:
|
||||
- All inventory and vault scripts will be removed from community.general in
|
||||
version 4.0.0. If you are referencing them, please update your references
|
||||
to the new `contrib-scripts GitHub repository <https://github.com/ansible-community/contrib-scripts>`_
|
||||
so your workflow will not break once community.general 4.0.0 is released (https://github.com/ansible-collections/community.general/pull/2697).
|
||||
minor_changes:
|
||||
- Remove unnecessary ``__init__.py`` files from ``plugins/`` (https://github.com/ansible-collections/community.general/pull/2632).
|
||||
- archive - added ``exclusion_patterns`` option to exclude files or subdirectories
|
||||
from archives (https://github.com/ansible-collections/community.general/pull/2616).
|
||||
- cloud_init_data_facts - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- composer - add ``composer_executable`` option (https://github.com/ansible-collections/community.general/issues/2649).
|
||||
- flatpak - add ``no_dependencies`` parameter (https://github.com/ansible/ansible/pull/55452,
|
||||
https://github.com/ansible-collections/community.general/pull/2751).
|
||||
- ini_file - opening file with encoding ``utf-8-sig`` (https://github.com/ansible-collections/community.general/issues/2189).
|
||||
- jira - add comment visibility parameter for comment operation (https://github.com/ansible-collections/community.general/pull/2556).
|
||||
- maven_artifact - added ``checksum_alg`` option to support SHA1 checksums in
|
||||
order to support FIPS systems (https://github.com/ansible-collections/community.general/pull/2662).
|
||||
- module_helper module utils - method ``CmdMixin.run_command()`` now accepts
|
||||
``process_output`` specifying a function to process the outcome of the underlying
|
||||
``module.run_command()`` (https://github.com/ansible-collections/community.general/pull/2564).
|
||||
- nmcli - add new options to ignore automatic DNS servers and gateways (https://github.com/ansible-collections/community.general/issues/1087).
|
||||
- onepassword lookup plugin - add ``domain`` option (https://github.com/ansible-collections/community.general/issues/2734).
|
||||
- open_iscsi - add ``auto_portal_startup`` parameter to allow ``node.startup``
|
||||
setting per portal (https://github.com/ansible-collections/community.general/issues/2685).
|
||||
- open_iscsi - also consider ``portal`` and ``port`` to check if already logged
|
||||
in or not (https://github.com/ansible-collections/community.general/issues/2683).
|
||||
- proxmox_group_info - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- proxmox_kvm - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- rhevm - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- serverless - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
- stacki_host - minor refactoring (https://github.com/ansible-collections/community.general/pull/2681).
|
||||
- terraform - add option ``overwrite_init`` to skip init if exists (https://github.com/ansible-collections/community.general/pull/2573).
|
||||
- terraform - minor refactor (https://github.com/ansible-collections/community.general/pull/2557).
|
||||
release_summary: Regular bugfix and feature release.
|
||||
fragments:
|
||||
- 2126-consul_kv-pass-token.yml
|
||||
- 2461-ovirt4-fix-configparser.yml
|
||||
- 2510-jenkins_plugin_use_post_method.yml
|
||||
- 2556-add-comment_visibility-parameter-for-comment-operation-of-jira-module.yml
|
||||
- 2557-cloud-misc-refactor.yml
|
||||
- 2560-java_cert-pkcs12-alias-bugfix.yml
|
||||
- 2564-mh-cmd-process-output.yml
|
||||
- 2568-ssh_config-reduce-stormssh-searches-based-on-host.yml
|
||||
- 2571-rhsm_release-fix-release_matcher.yaml
|
||||
- 2573-terraform-overwrite-init.yml
|
||||
- 2578-ini-file-utf8-bom.yml
|
||||
- 2579-redis-cache-ipv6.yml
|
||||
- 2590-netcup_dns-exception-no-message-attr.yml
|
||||
- 2614-influxdb_user-fix-issue-introduced-in-PR#2499.yml
|
||||
- 2616-archive-exclusion_patterns-option.yml
|
||||
- 2632-cleanup.yml
|
||||
- 2634-terraform-switch-workspace.yml
|
||||
- 2635-nmcli-add-ignore-auto-arguments.yml
|
||||
- 2648-proxmox_kvm-fix-vmid-return-value.yml
|
||||
- 2650-composer-add_composer_executable.yml
|
||||
- 2661-maven_artifact-add-sha1-option.yml
|
||||
- 2671-fix-broken-query-of-async_status-result.yml
|
||||
- 2681-stacki-host-bugfix.yml
|
||||
- 2684-open_iscsi-single-target-multiple-portal-overrides.yml
|
||||
- 2711-fix-iptables_state-2700-async_status-call.yml
|
||||
- 2722-zypper_repository-fix_idempotency_on_adding_repo_with_releasever.yml
|
||||
- 2731-mh-cmd-locale.yml
|
||||
- 2735-onepassword-add_domain_option.yml
|
||||
- 2751-flatpak-no_dependencies.yml
|
||||
- 3.2.0.yml
|
||||
- json_query_more_types.yml
|
||||
- keycloak-realm-no-log-password-reset.yml
|
||||
- keycloak_realm_ssl_required.yml
|
||||
- script-removal.yml
|
||||
modules:
|
||||
- description: Execute SQL on HANA
|
||||
name: hana_query
|
||||
namespace: database.saphana
|
||||
- description: Manage pacman's list of trusted keys
|
||||
name: pacman_key
|
||||
namespace: packaging.os
|
||||
- description: Manages SAP SAPCAR archives
|
||||
name: sapcar_extract
|
||||
namespace: files
|
||||
plugins:
|
||||
lookup:
|
||||
- description: Generates random string
|
||||
name: random_string
|
||||
namespace: null
|
||||
release_date: '2021-06-08'
|
||||
|
||||
@@ -67,6 +67,8 @@ Individuals who have been asked to become a part of this group have generally be
|
||||
|
||||
| Name | GitHub ID | IRC Nick | Other |
|
||||
| ------------------- | -------------------- | ------------------ | -------------------- |
|
||||
| Alexei Znamensky | russoz | russoz | |
|
||||
| Amin Vakil | aminvakil | aminvakil | |
|
||||
| Andrew Klychkov | andersson007 | andersson007_ | |
|
||||
| Felix Fontein | felixfontein | felixfontein | |
|
||||
| John R Barker | gundalow | gundalow | |
|
||||
|
||||
5
docs/docsite/extra-docs.yml
Normal file
5
docs/docsite/extra-docs.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
sections:
|
||||
- title: Guides
|
||||
toctree:
|
||||
- filter_guide
|
||||
753
docs/docsite/rst/filter_guide.rst
Normal file
753
docs/docsite/rst/filter_guide.rst
Normal file
@@ -0,0 +1,753 @@
|
||||
.. _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.0.0
|
||||
version: 3.2.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
194
meta/runtime.yml
194
meta/runtime.yml
@@ -1,31 +1,5 @@
|
||||
---
|
||||
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:
|
||||
@@ -37,6 +11,21 @@ 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:
|
||||
@@ -141,11 +130,13 @@ 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
|
||||
@@ -156,37 +147,22 @@ plugin_routing:
|
||||
warning_text: Use google.cloud.gcp_compute_url_map instead.
|
||||
gcpubsub:
|
||||
redirect: community.google.gcpubsub
|
||||
gcpubsub_info:
|
||||
redirect: community.google.gcpubsub_info
|
||||
gcpubsub_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.google.gcpubsub_info instead.
|
||||
gcpubsub_info:
|
||||
redirect: community.google.gcpubsub_info
|
||||
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.
|
||||
# Adding tombstones burns the old name, so we simply remove the entries:
|
||||
# gluster_heal_info:
|
||||
# tombstone:
|
||||
# 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:
|
||||
# tombstone:
|
||||
# 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:
|
||||
# tombstone:
|
||||
# removal_version: 3.0.0
|
||||
# warning_text: The gluster modules have migrated to the gluster.gluster collection. Use gluster.gluster.gluster_volume instead.
|
||||
# helm:
|
||||
# tombstone:
|
||||
# removal_version: 3.0.0
|
||||
# warning_text: Use community.kubernetes.helm instead.
|
||||
warning_text: Use community.general.github_webhook and community.general.github_webhook_info
|
||||
instead.
|
||||
hetzner_failover_ip:
|
||||
redirect: community.hrobot.failover_ip
|
||||
hetzner_failover_ip_info:
|
||||
@@ -234,11 +210,13 @@ plugin_routing:
|
||||
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:
|
||||
removal_version: 3.0.0
|
||||
@@ -287,6 +265,86 @@ plugin_routing:
|
||||
tombstone:
|
||||
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:
|
||||
@@ -320,7 +378,8 @@ plugin_routing:
|
||||
oneview_logical_interconnect_group_facts:
|
||||
tombstone:
|
||||
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:
|
||||
removal_version: 3.0.0
|
||||
@@ -477,10 +536,10 @@ plugin_routing:
|
||||
redirect: community.postgresql.postgresql_table
|
||||
postgresql_tablespace:
|
||||
redirect: community.postgresql.postgresql_tablespace
|
||||
postgresql_user_obj_stat_info:
|
||||
redirect: community.postgresql.postgresql_user_obj_stat_info
|
||||
postgresql_user:
|
||||
redirect: community.postgresql.postgresql_user
|
||||
postgresql_user_obj_stat_info:
|
||||
redirect: community.postgresql.postgresql_user_obj_stat_info
|
||||
purefa_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
@@ -568,11 +627,14 @@ 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:
|
||||
remote_management.dellemc.dellemc_idrac:
|
||||
redirect: dellemc.openmanage.dellemc_idrac
|
||||
docker.common:
|
||||
redirect: community.docker.common
|
||||
docker.swarm:
|
||||
@@ -587,23 +649,33 @@ plugin_routing:
|
||||
redirect: community.hrobot.robot
|
||||
kubevirt:
|
||||
redirect: community.kubevirt.kubevirt
|
||||
remote_management.dellemc.ome:
|
||||
redirect: dellemc.openmanage.ome
|
||||
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
|
||||
|
||||
@@ -7,7 +7,7 @@ __metaclass__ = type
|
||||
import time
|
||||
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.errors import AnsibleError, AnsibleActionFail, AnsibleConnectionFailure
|
||||
from ansible.errors import AnsibleActionFail, AnsibleConnectionFailure
|
||||
from ansible.utils.vars import merge_hash
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -40,19 +40,27 @@ class ActionModule(ActionBase):
|
||||
"(=%s) to 0, and 'async' (=%s) to a value >2 and not greater than "
|
||||
"'ansible_timeout' (=%s) (recommended).")
|
||||
|
||||
def _async_result(self, module_args, task_vars, timeout):
|
||||
def _async_result(self, async_status_args, task_vars, timeout):
|
||||
'''
|
||||
Retrieve results of the asynchonous task, and display them in place of
|
||||
the async wrapper results (those with the ansible_job_id key).
|
||||
'''
|
||||
async_status = self._task.copy()
|
||||
async_status.args = async_status_args
|
||||
async_status.action = 'ansible.builtin.async_status'
|
||||
async_status.async_val = 0
|
||||
async_action = self._shared_loader_obj.action_loader.get(
|
||||
async_status.action, task=async_status, connection=self._connection,
|
||||
play_context=self._play_context, loader=self._loader, templar=self._templar,
|
||||
shared_loader_obj=self._shared_loader_obj)
|
||||
|
||||
if async_status.args['mode'] == 'cleanup':
|
||||
return async_action.run(task_vars=task_vars)
|
||||
|
||||
# At least one iteration is required, even if timeout is 0.
|
||||
for i in range(max(1, timeout)):
|
||||
async_result = self._execute_module(
|
||||
module_name='ansible.builtin.async_status',
|
||||
module_args=module_args,
|
||||
task_vars=task_vars,
|
||||
wrap_async=False)
|
||||
if async_result['finished'] == 1:
|
||||
for dummy in range(max(1, timeout)):
|
||||
async_result = async_action.run(task_vars=task_vars)
|
||||
if async_result.get('finished', 0) == 1:
|
||||
break
|
||||
time.sleep(min(1, timeout))
|
||||
|
||||
@@ -76,7 +84,6 @@ class ActionModule(ActionBase):
|
||||
task_async = self._task.async_val
|
||||
check_mode = self._play_context.check_mode
|
||||
max_timeout = self._connection._play_context.timeout
|
||||
module_name = self._task.action
|
||||
module_args = self._task.args
|
||||
|
||||
if module_args.get('state', None) == 'restored':
|
||||
@@ -107,7 +114,7 @@ class ActionModule(ActionBase):
|
||||
# longer on the controller); and set a backup file path.
|
||||
module_args['_timeout'] = task_async
|
||||
module_args['_back'] = '%s/iptables.state' % async_dir
|
||||
async_status_args = dict(_async_dir=async_dir)
|
||||
async_status_args = dict(mode='status')
|
||||
confirm_cmd = 'rm -f %s' % module_args['_back']
|
||||
starter_cmd = 'touch %s.starter' % module_args['_back']
|
||||
remaining_time = max(task_async, max_timeout)
|
||||
@@ -133,7 +140,7 @@ class ActionModule(ActionBase):
|
||||
# The module is aware to not process the main iptables-restore
|
||||
# command before finding (and deleting) the 'starter' cookie on
|
||||
# the host, so the previous query will not reach ssh timeout.
|
||||
garbage = self._low_level_execute_command(starter_cmd, sudoable=self.DEFAULT_SUDOABLE)
|
||||
dummy = self._low_level_execute_command(starter_cmd, sudoable=self.DEFAULT_SUDOABLE)
|
||||
|
||||
# As the main command is not yet executed on the target, here
|
||||
# 'finished' means 'failed before main command be executed'.
|
||||
@@ -143,7 +150,7 @@ class ActionModule(ActionBase):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for x in range(max_timeout):
|
||||
for dummy in range(max_timeout):
|
||||
time.sleep(1)
|
||||
remaining_time -= 1
|
||||
# - AnsibleConnectionFailure covers rejected requests (i.e.
|
||||
@@ -151,7 +158,7 @@ class ActionModule(ActionBase):
|
||||
# - ansible_timeout is able to cover dropped requests (due
|
||||
# to a rule or policy DROP) if not lower than async_val.
|
||||
try:
|
||||
garbage = self._low_level_execute_command(confirm_cmd, sudoable=self.DEFAULT_SUDOABLE)
|
||||
dummy = self._low_level_execute_command(confirm_cmd, sudoable=self.DEFAULT_SUDOABLE)
|
||||
break
|
||||
except AnsibleConnectionFailure:
|
||||
continue
|
||||
@@ -164,16 +171,12 @@ class ActionModule(ActionBase):
|
||||
del result[key]
|
||||
|
||||
if result.get('invocation', {}).get('module_args'):
|
||||
if '_timeout' in result['invocation']['module_args']:
|
||||
del result['invocation']['module_args']['_back']
|
||||
del result['invocation']['module_args']['_timeout']
|
||||
for key in ('_back', '_timeout', '_async_dir', 'jid'):
|
||||
if result['invocation']['module_args'].get(key):
|
||||
del result['invocation']['module_args'][key]
|
||||
|
||||
async_status_args['mode'] = 'cleanup'
|
||||
garbage = self._execute_module(
|
||||
module_name='ansible.builtin.async_status',
|
||||
module_args=async_status_args,
|
||||
task_vars=task_vars,
|
||||
wrap_async=False)
|
||||
dummy = self._async_result(async_status_args, task_vars, 0)
|
||||
|
||||
if not wrap_async:
|
||||
# remove a temporary path we created
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
become: sudosu
|
||||
name: sudosu
|
||||
short_description: Run tasks using sudo su -
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the C(sudo) and C(su) utilities combined.
|
||||
|
||||
0
plugins/cache/__init__.py
vendored
0
plugins/cache/__init__.py
vendored
14
plugins/cache/redis.py
vendored
14
plugins/cache/redis.py
vendored
@@ -61,6 +61,7 @@ DOCUMENTATION = '''
|
||||
type: integer
|
||||
'''
|
||||
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
|
||||
@@ -91,6 +92,8 @@ class CacheModule(BaseCacheModule):
|
||||
performance.
|
||||
"""
|
||||
_sentinel_service_name = None
|
||||
re_url_conn = re.compile(r'^([^:]+|\[[^]]+\]):(\d+):(\d+)(?::(.*))?$')
|
||||
re_sent_conn = re.compile(r'^(.*):(\d+)$')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
uri = ''
|
||||
@@ -130,11 +133,18 @@ class CacheModule(BaseCacheModule):
|
||||
self._db = self._get_sentinel_connection(uri, kw)
|
||||
# normal connection
|
||||
else:
|
||||
connection = uri.split(':')
|
||||
connection = self._parse_connection(self.re_url_conn, uri)
|
||||
self._db = StrictRedis(*connection, **kw)
|
||||
|
||||
display.vv('Redis connection: %s' % self._db)
|
||||
|
||||
@staticmethod
|
||||
def _parse_connection(re_patt, uri):
|
||||
match = re_patt.match(uri)
|
||||
if not match:
|
||||
raise AnsibleError("Unable to parse connection string")
|
||||
return match.groups()
|
||||
|
||||
def _get_sentinel_connection(self, uri, kw):
|
||||
"""
|
||||
get sentinel connection details from _uri
|
||||
@@ -158,7 +168,7 @@ class CacheModule(BaseCacheModule):
|
||||
except IndexError:
|
||||
pass # password is optional
|
||||
|
||||
sentinels = [tuple(shost.split(':')) for shost in connections]
|
||||
sentinels = [self._parse_connection(self.re_sent_conn, shost) for shost in connections]
|
||||
display.vv('\nUsing redis sentinels: %s' % sentinels)
|
||||
scon = Sentinel(sentinels, **kw)
|
||||
try:
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: loganalytics
|
||||
name: loganalytics
|
||||
type: aggregate
|
||||
short_description: Posts task results to Azure Log Analytics
|
||||
author: "Cyrus Li (@zhcli) <cyrus1006@gmail.com>"
|
||||
|
||||
@@ -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,16 +128,17 @@ 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
|
||||
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
@staticmethod
|
||||
def _prefix_login_path(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
|
||||
@@ -145,13 +146,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)
|
||||
|
||||
@@ -177,7 +178,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)
|
||||
|
||||
@@ -201,6 +202,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,6 +53,7 @@ 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:
|
||||
@@ -62,31 +63,32 @@ 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]
|
||||
|
||||
def _normalize_path(self, path, prefix):
|
||||
@staticmethod
|
||||
def _normalize_path(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)
|
||||
@@ -99,5 +101,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,7 +35,6 @@ 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
|
||||
@@ -47,7 +46,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local BSD Jail based connections '''
|
||||
""" Local BSD Jail based connections """
|
||||
|
||||
modified_jailname_key = 'conn_jail_name'
|
||||
|
||||
@@ -90,20 +89,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 = ''
|
||||
@@ -123,16 +122,17 @@ 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
|
||||
|
||||
def _prefix_login_path(self, remote_path):
|
||||
''' Make sure that we put files into a standard path
|
||||
@staticmethod
|
||||
def _prefix_login_path(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,14 +42,13 @@ 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
|
||||
@@ -62,7 +61,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:
|
||||
@@ -77,7 +76,8 @@ class Connection(ConnectionBase):
|
||||
if self.container.state == "STOPPED":
|
||||
raise errors.AnsibleError("%s is not running" % self.container_name)
|
||||
|
||||
def _communicate(self, pid, in_data, stdin, stdout, stderr):
|
||||
@staticmethod
|
||||
def _communicate(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,15 +37,9 @@ DOCUMENTATION = '''
|
||||
# - name: hosts
|
||||
'''
|
||||
|
||||
import shlex
|
||||
import shutil
|
||||
|
||||
import os
|
||||
import base64
|
||||
import subprocess
|
||||
|
||||
import ansible.constants as C
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.plugins.connection import ConnectionBase, ensure_connect
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -16,14 +16,11 @@ DOCUMENTATION = '''
|
||||
- This allows you to use existing Saltstack infrastructure to connect to targets.
|
||||
'''
|
||||
|
||||
import re
|
||||
import os
|
||||
import pty
|
||||
import codecs
|
||||
import subprocess
|
||||
import base64
|
||||
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.module_utils.six.moves import cPickle
|
||||
from ansible import errors
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
|
||||
HAVE_SALTSTACK = False
|
||||
try:
|
||||
@@ -32,13 +29,9 @@ 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
|
||||
@@ -58,29 +51,30 @@ 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']
|
||||
|
||||
def _normalize_path(self, path, prefix):
|
||||
@staticmethod
|
||||
def _normalize_path(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)
|
||||
|
||||
@@ -88,11 +82,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', [codecs.encode(content, 'base64'), out_path])
|
||||
self.client.cmd(self.host, 'hashutil.base64_decodefile', [base64.b64encode(content), 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)
|
||||
|
||||
@@ -102,5 +96,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,7 +31,6 @@ 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
|
||||
@@ -42,7 +41,7 @@ display = Display()
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local zone based connections '''
|
||||
""" Local zone based connections """
|
||||
|
||||
transport = 'community.general.zone'
|
||||
has_pipelining = True
|
||||
@@ -75,9 +74,9 @@ class Connection(ConnectionBase):
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
zones = []
|
||||
for l in process.stdout.readlines():
|
||||
for line in process.stdout.readlines():
|
||||
# 1:work:running:/zones/work:3126dc59-9a07-4829-cde9-a816e4c5040e:native:shared
|
||||
s = l.split(':')
|
||||
s = line.split(':')
|
||||
if s[1] != 'global':
|
||||
zones.append(s[1])
|
||||
|
||||
@@ -95,20 +94,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.
|
||||
@@ -122,16 +121,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
|
||||
@@ -139,13 +138,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)
|
||||
|
||||
@@ -171,7 +170,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)
|
||||
|
||||
@@ -195,6 +194,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
|
||||
|
||||
42
plugins/filter/groupby.py
Normal file
42
plugins/filter/groupby.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- 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,
|
||||
}
|
||||
@@ -35,9 +35,11 @@ def json_query(data, expr):
|
||||
raise AnsibleError('You need to install "jmespath" prior to running '
|
||||
'json_query filter')
|
||||
|
||||
# Hack to handle Ansible String Types
|
||||
# Hack to handle Ansible Unsafe text, AnsibleMapping and AnsibleSequence
|
||||
# See issue: https://github.com/ansible-collections/community.general/issues/320
|
||||
jmespath.functions.REVERSE_TYPES_MAP['string'] = jmespath.functions.REVERSE_TYPES_MAP['string'] + ('AnsibleUnicode', 'AnsibleUnsafeText', )
|
||||
jmespath.functions.REVERSE_TYPES_MAP['array'] = jmespath.functions.REVERSE_TYPES_MAP['array'] + ('AnsibleSequence', )
|
||||
jmespath.functions.REVERSE_TYPES_MAP['object'] = jmespath.functions.REVERSE_TYPES_MAP['object'] + ('AnsibleMapping', )
|
||||
try:
|
||||
return jmespath.search(expr, data)
|
||||
except jmespath.exceptions.JMESPathError as e:
|
||||
|
||||
@@ -6,7 +6,7 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
name: community.general.lxd
|
||||
name: lxd
|
||||
short_description: Returns Ansible inventory from lxd host
|
||||
description:
|
||||
- Get inventory from the lxd.
|
||||
@@ -68,7 +68,7 @@ DOCUMENTATION = r'''
|
||||
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: json
|
||||
type: dict
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
@@ -130,7 +130,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
# This occurs if the cache_key is not in the cache or if the cache_key expired, so the cache needs to be updated
|
||||
cache_needs_update = True
|
||||
|
||||
if cache_needs_update:
|
||||
if not user_cache_setting or cache_needs_update:
|
||||
# setup command
|
||||
cmd = [self._nmap]
|
||||
if not self._options['ports']:
|
||||
@@ -207,6 +207,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
except Exception as e:
|
||||
raise AnsibleParserError("failed to parse %s: %s " % (to_native(path), to_native(e)))
|
||||
|
||||
if cache_needs_update:
|
||||
self._cache[cache_key] = results
|
||||
|
||||
self._populate(results)
|
||||
|
||||
@@ -10,6 +10,8 @@ DOCUMENTATION = '''
|
||||
name: stackpath_compute
|
||||
short_description: StackPath Edge Computing inventory source
|
||||
version_added: 1.2.0
|
||||
author:
|
||||
- UNKNOWN (@shayrybak)
|
||||
extends_documentation_fragment:
|
||||
- inventory_cache
|
||||
- constructed
|
||||
@@ -102,13 +104,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
raise AnsibleError("plugin doesn't match this plugin")
|
||||
try:
|
||||
client_id = config['client_id']
|
||||
if client_id != 32:
|
||||
if len(client_id) != 32:
|
||||
raise AnsibleError("client_id must be 32 characters long")
|
||||
except KeyError:
|
||||
raise AnsibleError("config missing client_id, a required option")
|
||||
try:
|
||||
client_secret = config['client_secret']
|
||||
if client_secret != 64:
|
||||
if len(client_secret) != 64:
|
||||
raise AnsibleError("client_secret must be 64 characters long")
|
||||
except KeyError:
|
||||
raise AnsibleError("config missing client_id, a required option")
|
||||
|
||||
@@ -171,10 +171,10 @@ class LookupModule(LookupBase):
|
||||
|
||||
paramvals = {
|
||||
'key': params[0],
|
||||
'token': None,
|
||||
'recurse': False,
|
||||
'index': None,
|
||||
'datacenter': None
|
||||
'token': self.get_option('token'),
|
||||
'recurse': self.get_option('recurse'),
|
||||
'index': self.get_option('index'),
|
||||
'datacenter': self.get_option('datacenter')
|
||||
}
|
||||
|
||||
# parameters specified?
|
||||
|
||||
208
plugins/lookup/dependent.py
Normal file
208
plugins/lookup/dependent.py
Normal file
@@ -0,0 +1,208 @@
|
||||
# (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,6 +25,10 @@ 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,6 +25,10 @@ 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,6 +25,10 @@ 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,6 +30,11 @@ 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:
|
||||
@@ -109,6 +114,7 @@ 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
|
||||
@@ -168,7 +174,7 @@ class OnePass(object):
|
||||
|
||||
args = [
|
||||
'signin',
|
||||
'{0}.1password.com'.format(self.subdomain),
|
||||
'{0}.{1}'.format(self.subdomain, self.domain),
|
||||
to_bytes(self.username),
|
||||
to_bytes(self.secret_key),
|
||||
'--output=raw',
|
||||
@@ -265,6 +271,7 @@ 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.
|
||||
description: Create the password if it does not already exist. Takes precedence over C(missing).
|
||||
type: bool
|
||||
default: 'no'
|
||||
default: false
|
||||
overwrite:
|
||||
description: Overwrite the password if it does already exist.
|
||||
type: bool
|
||||
@@ -60,6 +60,22 @@ 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
|
||||
@@ -67,12 +83,28 @@ 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')}}"
|
||||
@@ -111,10 +143,13 @@ 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
|
||||
@@ -178,12 +213,17 @@ 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()
|
||||
|
||||
@@ -224,9 +264,11 @@ 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 not self.paramvals['create']:
|
||||
raise AnsibleError('passname: {0} not found, use create=True'.format(self.passname))
|
||||
if self.paramvals['missing'] == 'error':
|
||||
raise AnsibleError('passwordstore: passname {0} not found and missing=error is set'.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)
|
||||
@@ -294,6 +336,7 @@ class LookupModule(LookupBase):
|
||||
'userpass': '',
|
||||
'length': 16,
|
||||
'backup': False,
|
||||
'missing': 'error',
|
||||
}
|
||||
|
||||
for term in terms:
|
||||
@@ -304,6 +347,9 @@ class LookupModule(LookupBase):
|
||||
else:
|
||||
result.append(self.get_passresult())
|
||||
else: # password does not exist
|
||||
if self.paramvals['create']:
|
||||
if self.paramvals['missing'] == 'create':
|
||||
result.append(self.generate_password())
|
||||
else:
|
||||
result.append(None)
|
||||
|
||||
return result
|
||||
|
||||
99
plugins/lookup/random_pet.py
Normal file
99
plugins/lookup/random_pet.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# -*- 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]
|
||||
220
plugins/lookup/random_string.py
Normal file
220
plugins/lookup/random_string.py
Normal file
@@ -0,0 +1,220 @@
|
||||
# -*- 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)]
|
||||
@@ -103,6 +103,14 @@ EXAMPLES = r"""
|
||||
| items2dict(key_name='slug',
|
||||
value_name='itemValue'))['password']
|
||||
}}
|
||||
|
||||
- hosts: localhost
|
||||
vars:
|
||||
secret_password: >-
|
||||
{{ ((lookup('community.general.tss', 1) | from_json).get('items') | items2dict(key_name='slug', value_name='itemValue'))['password'] }}"
|
||||
tasks:
|
||||
- ansible.builtin.debug:
|
||||
msg: the password is {{ secret_password }}
|
||||
"""
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||
|
||||
62
plugins/module_utils/mh/base.py
Normal file
62
plugins/module_utils/mh/base.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- 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)
|
||||
54
plugins/module_utils/mh/deco.py
Normal file
54
plugins/module_utils/mh/deco.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# -*- 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
|
||||
22
plugins/module_utils/mh/exceptions.py
Normal file
22
plugins/module_utils/mh/exceptions.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- 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)
|
||||
175
plugins/module_utils/mh/mixins/cmd.py
Normal file
175
plugins/module_utils/mh/mixins/cmd.py
Normal file
@@ -0,0 +1,175 @@
|
||||
# -*- 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)
|
||||
58
plugins/module_utils/mh/mixins/deps.py
Normal file
58
plugins/module_utils/mh/mixins/deps.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# -*- 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()
|
||||
39
plugins/module_utils/mh/mixins/state.py
Normal file
39
plugins/module_utils/mh/mixins/state.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- 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())))
|
||||
132
plugins/module_utils/mh/mixins/vars.py
Normal file
132
plugins/module_utils/mh/mixins/vars.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# -*- 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)
|
||||
79
plugins/module_utils/mh/module_helper.py
Normal file
79
plugins/module_utils/mh/module_helper.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# -*- 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,506 +6,13 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from functools import partial, wraps
|
||||
import traceback
|
||||
|
||||
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)
|
||||
env_update = dict(options.get('environ_update', {}))
|
||||
options['check_rc'] = options.get('check_rc', self.check_rc)
|
||||
if self.force_lang:
|
||||
env_update.update({'LANGUAGE': self.force_lang})
|
||||
self.update_output(force_lang=self.force_lang)
|
||||
options['environ_update'] = env_update
|
||||
options.update(kwargs)
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1671,19 +1671,31 @@ 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 in attributes:
|
||||
if attr not in data[u'Attributes']:
|
||||
return {'ret': False, 'msg': "BIOS attribute %s not found" % attr}
|
||||
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
|
||||
|
||||
# If already set to requested value, remove it from PATCH payload
|
||||
if data[u'Attributes'][attr] == attributes[attr]:
|
||||
del attrs_to_patch[attr]
|
||||
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)
|
||||
|
||||
# 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"}
|
||||
'msg': "BIOS attributes already set",
|
||||
'warning': warning}
|
||||
|
||||
# Get the SettingsObject URI
|
||||
set_bios_attr_uri = data["@Redfish.Settings"]["SettingsObject"]["@odata.id"]
|
||||
@@ -1693,7 +1705,9 @@ 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 attribute"}
|
||||
return {'ret': True, 'changed': True,
|
||||
'msg': "Modified BIOS attributes %s" % (attrs_to_patch),
|
||||
'warning': warning}
|
||||
|
||||
def set_boot_order(self, boot_list):
|
||||
if not boot_list:
|
||||
|
||||
@@ -21,8 +21,10 @@ options:
|
||||
type: str
|
||||
api_key:
|
||||
description:
|
||||
- Linode API key
|
||||
- Linode API key.
|
||||
- C(LINODE_API_KEY) env variable can be used instead.
|
||||
type: str
|
||||
required: yes
|
||||
name:
|
||||
description:
|
||||
- Name to give the instance (alphanumeric, dashes, underscore).
|
||||
@@ -46,6 +48,7 @@ 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.
|
||||
@@ -86,9 +89,18 @@ 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:
|
||||
- Integer value for what day of the week to store weekly backups.
|
||||
- Day of the week to take backups.
|
||||
type: int
|
||||
backupwindow:
|
||||
description:
|
||||
- The time window in which backups will be taken.
|
||||
type: int
|
||||
plan:
|
||||
description:
|
||||
@@ -153,7 +165,6 @@ 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.
|
||||
'''
|
||||
|
||||
@@ -262,7 +273,6 @@ EXAMPLES = '''
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
|
||||
@@ -274,7 +284,7 @@ except ImportError:
|
||||
LINODE_IMP_ERR = traceback.format_exc()
|
||||
HAS_LINODE = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||
|
||||
|
||||
def randompass():
|
||||
@@ -358,7 +368,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)) # @TODO use required_if instead
|
||||
module.fail_json(msg='%s is required for %s state' % (arg, state))
|
||||
# Create linode entity
|
||||
new_server = True
|
||||
|
||||
@@ -383,7 +393,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'])
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
|
||||
if not disks:
|
||||
for arg in (name, linode_id, distribution):
|
||||
@@ -428,7 +438,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'])
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
|
||||
if not configs:
|
||||
for arg in (name, linode_id, distribution):
|
||||
@@ -471,7 +481,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'])
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
|
||||
# Start / Ensure servers are running
|
||||
for server in servers:
|
||||
@@ -517,10 +527,7 @@ def linodeServers(module, api, state, name,
|
||||
instance['password'] = password
|
||||
instances.append(instance)
|
||||
|
||||
elif state in ('stopped'):
|
||||
if not linode_id:
|
||||
module.fail_json(msg='linode_id is required for stopped state')
|
||||
|
||||
elif state in ('stopped',):
|
||||
if not servers:
|
||||
module.fail_json(msg='Server (lid: %s) not found' % (linode_id))
|
||||
|
||||
@@ -530,17 +537,14 @@ 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'])
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
instance['status'] = 'Stopping'
|
||||
changed = True
|
||||
else:
|
||||
instance['status'] = 'Stopped'
|
||||
instances.append(instance)
|
||||
|
||||
elif state in ('restarted'):
|
||||
if not linode_id:
|
||||
module.fail_json(msg='linode_id is required for restarted state')
|
||||
|
||||
elif state in ('restarted',):
|
||||
if not servers:
|
||||
module.fail_json(msg='Server (lid: %s) not found' % (linode_id))
|
||||
|
||||
@@ -549,7 +553,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'])
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
instance['status'] = 'Restarting'
|
||||
changed = True
|
||||
instances.append(instance)
|
||||
@@ -560,7 +564,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'])
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
instance['status'] = 'Deleting'
|
||||
changed = True
|
||||
instances.append(instance)
|
||||
@@ -577,7 +581,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),
|
||||
api_key=dict(type='str', no_log=True, required=True, fallback=(env_fallback, ['LINODE_API_KEY'])),
|
||||
name=dict(type='str', required=True),
|
||||
alert_bwin_enabled=dict(type='bool'),
|
||||
alert_bwin_threshold=dict(type='int'),
|
||||
@@ -589,12 +593,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'),
|
||||
backupsenabled=dict(type='int', removed_in_version='5.0.0', removed_from_collection='community.general'),
|
||||
backupweeklyday=dict(type='int'),
|
||||
backupwindow=dict(type='int'),
|
||||
displaygroup=dict(type='str', default=''),
|
||||
plan=dict(type='int'),
|
||||
additional_disks=dict(type='list'),
|
||||
additional_disks=dict(type='list', elements='dict'),
|
||||
distribution=dict(type='int'),
|
||||
datacenter=dict(type='int'),
|
||||
kernel_id=dict(type='int'),
|
||||
@@ -608,6 +612,10 @@ 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:
|
||||
@@ -626,7 +634,6 @@ 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')
|
||||
@@ -642,10 +649,9 @@ def main():
|
||||
ssh_pub_key = module.params.get('ssh_pub_key')
|
||||
swap = module.params.get('swap')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = int(module.params.get('wait_timeout'))
|
||||
wait_timeout = 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,
|
||||
@@ -661,23 +667,14 @@ def main():
|
||||
backupwindow=backupwindow,
|
||||
)
|
||||
|
||||
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)
|
||||
kwargs = dict((k, v) for k, v in check_items.items() if v is not None)
|
||||
|
||||
# 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'])
|
||||
module.fail_json(msg='%s' % e.value[0]['ERRORMESSAGE'], exception=traceback.format_exc())
|
||||
|
||||
linodeServers(module, api, state, name,
|
||||
displaygroup, plan,
|
||||
|
||||
@@ -208,9 +208,8 @@ def create_linode(module, client, **kwargs):
|
||||
else:
|
||||
return response._raw_json
|
||||
except TypeError:
|
||||
module.fail_json(msg='Unable to parse Linode instance creation'
|
||||
' response. Please raise a bug against this'
|
||||
' module on https://github.com/ansible/ansible/issues'
|
||||
module.fail_json(msg='Unable to parse Linode instance creation response. Please raise a bug against this'
|
||||
' module on https://github.com/ansible-collections/community.general/issues'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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 = CLOUD_INIT_PATH + i + '.json'
|
||||
json_file = os.path.join(CLOUD_INIT_PATH, i + '.json')
|
||||
|
||||
if os.path.exists(json_file):
|
||||
f = open(json_file, 'rb')
|
||||
|
||||
@@ -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 type(v) == str:
|
||||
if k == 'users' and isinstance(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.match(r'net[0-9]', k) is not None:
|
||||
if re_net.match(k):
|
||||
interface = k
|
||||
k = vm[k]
|
||||
k = re.search('=(.*?),', k).group(1)
|
||||
mac[interface] = 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):
|
||||
elif re_dev.match(k):
|
||||
device = k
|
||||
k = vm[k]
|
||||
k = re.search('(.*?),', k).group(1)
|
||||
@@ -835,16 +835,13 @@ def get_vminfo(module, proxmox, node, vmid, **kwargs):
|
||||
results['vmid'] = int(vmid)
|
||||
|
||||
|
||||
def settings(module, proxmox, vmid, node, name, **kwargs):
|
||||
def settings(proxmox, vmid, node, **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)
|
||||
|
||||
if proxmox_node.qemu(vmid).config.set(**kwargs) is None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return proxmox_node.qemu(vmid).config.set(**kwargs) is None
|
||||
|
||||
|
||||
def wait_for_task(module, proxmox, node, taskid):
|
||||
@@ -915,7 +912,8 @@ 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])
|
||||
@@ -938,8 +936,9 @@ 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.match(r'^[a-z0-9_][a-z0-9_\-\+\.]*$', tag):
|
||||
if not re_tag.match(tag):
|
||||
module.fail_json(msg='%s is not a valid tag' % tag)
|
||||
kwargs['tags'] = ",".join(kwargs['tags'])
|
||||
|
||||
@@ -971,7 +970,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
|
||||
|
||||
@@ -1209,14 +1208,14 @@ def main():
|
||||
|
||||
if delete is not None:
|
||||
try:
|
||||
settings(module, proxmox, vmid, node, name, delete=delete)
|
||||
settings(proxmox, vmid, node, 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(module, proxmox, vmid, node, name, revert=revert)
|
||||
settings(proxmox, vmid, node, 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))
|
||||
@@ -1226,7 +1225,7 @@ def main():
|
||||
if get_vm(proxmox, vmid) and not (update or clone):
|
||||
module.exit_json(changed=False, vmid=vmid, msg="VM with vmid <%s> already exists" % vmid)
|
||||
elif get_vmid(proxmox, name) and not (update or clone):
|
||||
module.exit_json(changed=False, vmid=vmid, msg="VM with name <%s> already exists" % name)
|
||||
module.exit_json(changed=False, vmid=get_vmid(proxmox, name)[0], msg="VM with name <%s> already exists" % name)
|
||||
elif not (node, name):
|
||||
module.fail_json(msg='node, name is mandatory for creating/updating vm')
|
||||
elif not node_check(proxmox, node):
|
||||
|
||||
348
plugins/modules/cloud/misc/proxmox_nic.py
Normal file
348
plugins/modules/cloud/misc/proxmox_nic.py
Normal file
@@ -0,0 +1,348 @@
|
||||
#!/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(int(memory_policy) * 1024 * 1024 * 1024)
|
||||
VM.memory_policy.guaranteed = 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', 'present')
|
||||
state = module.params.get('state')
|
||||
|
||||
if state == 'ping':
|
||||
r.test()
|
||||
|
||||
@@ -139,16 +139,14 @@ 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(os.path.join(path, 'serverless.yml')) as sls_config:
|
||||
with open(full_path) 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(path, str(e)))
|
||||
|
||||
module.fail_json(msg="Failed to open serverless config at {0}".format(
|
||||
os.path.join(path, 'serverless.yml')))
|
||||
module.fail_json(msg="Could not open serverless.yml in {0}. err: {1}".format(full_path, str(e)))
|
||||
|
||||
|
||||
def get_service_name(module, stage):
|
||||
@@ -182,7 +180,6 @@ 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)
|
||||
@@ -193,7 +190,7 @@ def main():
|
||||
if serverless_bin_path is not None:
|
||||
command = serverless_bin_path + " "
|
||||
else:
|
||||
command = "serverless "
|
||||
command = module.get_bin_path("serverless") + " "
|
||||
|
||||
if state == 'present':
|
||||
command += 'deploy '
|
||||
|
||||
@@ -107,6 +107,12 @@ 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.
|
||||
@@ -227,7 +233,7 @@ def get_version(bin_path):
|
||||
|
||||
|
||||
def preflight_validation(bin_path, project_path, version, variables_args=None, plan_file=None):
|
||||
if project_path in [None, ''] or '/' not in project_path:
|
||||
if project_path is 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))
|
||||
@@ -348,6 +354,7 @@ 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,
|
||||
@@ -367,6 +374,7 @@ 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]
|
||||
@@ -383,7 +391,8 @@ def main():
|
||||
APPLY_ARGS = ('apply', '-no-color', '-input=false', '-auto-approve')
|
||||
|
||||
if force_init:
|
||||
init_plugins(command[0], project_path, backend_config, backend_config_files, init_reconfigure, plugin_paths)
|
||||
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)
|
||||
|
||||
workspace_ctx = get_workspace_context(command[0], project_path)
|
||||
if workspace_ctx["current"] != workspace:
|
||||
@@ -438,7 +447,14 @@ def main():
|
||||
command.append(plan_file)
|
||||
|
||||
if needs_application and not module.check_mode and not state == 'planned':
|
||||
rc, out, err = module.run_command(command, check_rc=True, cwd=project_path)
|
||||
rc, out, err = module.run_command(command, check_rc=False, cwd=project_path)
|
||||
if rc != 0:
|
||||
if workspace_ctx["current"] != workspace:
|
||||
select_workspace(command[0], project_path, workspace_ctx["current"])
|
||||
module.fail_json(msg=err.rstrip(), rc=rc, stdout=out,
|
||||
stdout_lines=out.splitlines(), stderr=err,
|
||||
stderr_lines=err.splitlines(),
|
||||
cmd=' '.join(command))
|
||||
# checks out to decide if changes were made during execution
|
||||
if ' 0 added, 0 changed' not in out and not state == "absent" or ' 0 destroyed' not in out:
|
||||
changed = True
|
||||
|
||||
@@ -32,11 +32,13 @@ EXAMPLES = r'''
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
---
|
||||
online_server_info:
|
||||
description: Response from Online API
|
||||
description:
|
||||
- Response from Online API.
|
||||
- "For more details please refer to: U(https://console.online.net/en/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
"online_server_info": [
|
||||
{
|
||||
|
||||
@@ -7,7 +7,6 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: online_user_info
|
||||
short_description: Gather information about Online user.
|
||||
description:
|
||||
@@ -16,7 +15,6 @@ author:
|
||||
- "Remy Leone (@sieben)"
|
||||
extends_documentation_fragment:
|
||||
- community.general.online
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
@@ -29,11 +27,12 @@ EXAMPLES = r'''
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
---
|
||||
online_user_info:
|
||||
description: Response from Online API
|
||||
description:
|
||||
- Response from Online API.
|
||||
- "For more details please refer to: U(https://console.online.net/en/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: dict
|
||||
sample:
|
||||
"online_user_info": {
|
||||
"company": "foobar LLC",
|
||||
|
||||
@@ -752,11 +752,20 @@ def get_vm_info(client, vm):
|
||||
if 'NIC' in vm.TEMPLATE:
|
||||
if isinstance(vm.TEMPLATE['NIC'], list):
|
||||
for nic in vm.TEMPLATE['NIC']:
|
||||
networks_info.append({'ip': nic['IP'], 'mac': nic['MAC'], 'name': nic['NETWORK'], 'security_groups': nic['SECURITY_GROUPS']})
|
||||
networks_info.append({
|
||||
'ip': nic.get('IP', ''),
|
||||
'mac': nic.get('MAC', ''),
|
||||
'name': nic.get('NETWORK', ''),
|
||||
'security_groups': nic.get('SECURITY_GROUPS', '')
|
||||
})
|
||||
else:
|
||||
networks_info.append(
|
||||
{'ip': vm.TEMPLATE['NIC']['IP'], 'mac': vm.TEMPLATE['NIC']['MAC'],
|
||||
'name': vm.TEMPLATE['NIC']['NETWORK'], 'security_groups': vm.TEMPLATE['NIC']['SECURITY_GROUPS']})
|
||||
networks_info.append({
|
||||
'ip': vm.TEMPLATE['NIC'].get('IP', ''),
|
||||
'mac': vm.TEMPLATE['NIC'].get('MAC', ''),
|
||||
'name': vm.TEMPLATE['NIC'].get('NETWORK', ''),
|
||||
'security_groups':
|
||||
vm.TEMPLATE['NIC'].get('SECURITY_GROUPS', '')
|
||||
})
|
||||
import time
|
||||
|
||||
current_time = time.localtime()
|
||||
|
||||
@@ -19,9 +19,7 @@ author:
|
||||
extends_documentation_fragment:
|
||||
- community.general.scaleway
|
||||
|
||||
|
||||
options:
|
||||
|
||||
region:
|
||||
type: str
|
||||
description:
|
||||
@@ -51,9 +49,12 @@ EXAMPLES = r'''
|
||||
RETURN = r'''
|
||||
---
|
||||
scaleway_image_info:
|
||||
description: Response from Scaleway API
|
||||
description:
|
||||
- Response from Scaleway API.
|
||||
- "For more details please refer to: U(https://developers.scaleway.com/en/products/instance/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
"scaleway_image_info": [
|
||||
{
|
||||
|
||||
@@ -49,9 +49,12 @@ EXAMPLES = r'''
|
||||
RETURN = r'''
|
||||
---
|
||||
scaleway_ip_info:
|
||||
description: Response from Scaleway API
|
||||
description:
|
||||
- Response from Scaleway API.
|
||||
- "For more details please refer to: U(https://developers.scaleway.com/en/products/instance/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
"scaleway_ip_info": [
|
||||
{
|
||||
|
||||
@@ -49,9 +49,12 @@ EXAMPLES = r'''
|
||||
RETURN = r'''
|
||||
---
|
||||
scaleway_security_group_info:
|
||||
description: Response from Scaleway API
|
||||
description:
|
||||
- Response from Scaleway API.
|
||||
- "For more details please refer to: U(https://developers.scaleway.com/en/products/instance/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
"scaleway_security_group_info": [
|
||||
{
|
||||
|
||||
@@ -49,9 +49,12 @@ EXAMPLES = r'''
|
||||
RETURN = r'''
|
||||
---
|
||||
scaleway_server_info:
|
||||
description: Response from Scaleway API
|
||||
description:
|
||||
- Response from Scaleway API.
|
||||
- "For more details please refer to: U(https://developers.scaleway.com/en/products/instance/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
"scaleway_server_info": [
|
||||
{
|
||||
|
||||
@@ -49,9 +49,12 @@ EXAMPLES = r'''
|
||||
RETURN = r'''
|
||||
---
|
||||
scaleway_snapshot_info:
|
||||
description: Response from Scaleway API
|
||||
description:
|
||||
- Response from Scaleway API.
|
||||
- "For more details please refer to: U(https://developers.scaleway.com/en/products/instance/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
"scaleway_snapshot_info": [
|
||||
{
|
||||
|
||||
@@ -49,9 +49,12 @@ EXAMPLES = r'''
|
||||
RETURN = r'''
|
||||
---
|
||||
scaleway_volume_info:
|
||||
description: Response from Scaleway API
|
||||
description:
|
||||
- Response from Scaleway API.
|
||||
- "For more details please refer to: U(https://developers.scaleway.com/en/products/instance/api/)."
|
||||
returned: success
|
||||
type: complex
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
"scaleway_volume_info": [
|
||||
{
|
||||
|
||||
@@ -23,26 +23,26 @@ options:
|
||||
|
||||
credentials_path:
|
||||
description:
|
||||
- (Path) Optional parameter that allows to set a non-default credentials path.
|
||||
- Optional parameter that allows to set a non-default credentials path.
|
||||
default: ~/.spotinst/credentials
|
||||
type: path
|
||||
|
||||
account_id:
|
||||
description:
|
||||
- (String) Optional parameter that allows to set an account-id inside the module configuration
|
||||
By default this is retrieved from the credentials path
|
||||
- Optional parameter that allows to set an account-id inside the module configuration.
|
||||
By default this is retrieved from the credentials path.
|
||||
type: str
|
||||
|
||||
availability_vs_cost:
|
||||
description:
|
||||
- (String) The strategy orientation.
|
||||
- The strategy orientation.
|
||||
- "The choices available are: C(availabilityOriented), C(costOriented), C(balanced)."
|
||||
required: true
|
||||
type: str
|
||||
|
||||
availability_zones:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of Availability Zones that are configured in the elastigroup;
|
||||
- A list of hash/dictionaries of Availability Zones that are configured in the elastigroup;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are
|
||||
name (String),
|
||||
@@ -50,10 +50,11 @@ options:
|
||||
placement_group_name (String),
|
||||
required: true
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
block_device_mappings:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of Block Device Mappings for elastigroup instances;
|
||||
- A list of hash/dictionaries of Block Device Mappings for elastigroup instances;
|
||||
You can specify virtual devices and EBS volumes.;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are
|
||||
@@ -68,10 +69,11 @@ options:
|
||||
volume_type(String),
|
||||
volume_size(Integer))
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
chef:
|
||||
description:
|
||||
- (Object) The Chef integration configuration.;
|
||||
- The Chef integration configuration.;
|
||||
Expects the following keys - chef_server (String),
|
||||
organization (String),
|
||||
user (String),
|
||||
@@ -81,92 +83,94 @@ options:
|
||||
|
||||
draining_timeout:
|
||||
description:
|
||||
- (Integer) Time for instance to be drained from incoming requests and deregistered from ELB before termination.
|
||||
- Time for instance to be drained from incoming requests and deregistered from ELB before termination.
|
||||
type: int
|
||||
|
||||
ebs_optimized:
|
||||
description:
|
||||
- (Boolean) Enable EBS optimization for supported instances which are not enabled by default.;
|
||||
- Enable EBS optimization for supported instances which are not enabled by default.;
|
||||
Note - additional charges will be applied.
|
||||
type: bool
|
||||
|
||||
ebs_volume_pool:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of EBS devices to reattach to the elastigroup when available;
|
||||
- A list of hash/dictionaries of EBS devices to reattach to the elastigroup when available;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are -
|
||||
volume_ids (List of Strings),
|
||||
device_name (String)
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
ecs:
|
||||
description:
|
||||
- (Object) The ECS integration configuration.;
|
||||
- The ECS integration configuration.;
|
||||
Expects the following key -
|
||||
cluster_name (String)
|
||||
type: dict
|
||||
|
||||
elastic_ips:
|
||||
description:
|
||||
- (List of Strings) List of ElasticIps Allocation Ids (Example C(eipalloc-9d4e16f8)) to associate to the group instances
|
||||
- List of ElasticIps Allocation Ids (Example C(eipalloc-9d4e16f8)) to associate to the group instances
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
fallback_to_od:
|
||||
description:
|
||||
- (Boolean) In case of no spots available, Elastigroup will launch an On-demand instance instead
|
||||
- In case of no spots available, Elastigroup will launch an On-demand instance instead
|
||||
type: bool
|
||||
|
||||
health_check_grace_period:
|
||||
description:
|
||||
- (Integer) The amount of time, in seconds, after the instance has launched to start and check its health.
|
||||
- The amount of time, in seconds, after the instance has launched to start and check its health.
|
||||
- If not specified, it defaults to C(300).
|
||||
type: int
|
||||
|
||||
health_check_unhealthy_duration_before_replacement:
|
||||
description:
|
||||
- (Integer) Minimal mount of time instance should be unhealthy for us to consider it unhealthy.
|
||||
- Minimal mount of time instance should be unhealthy for us to consider it unhealthy.
|
||||
type: int
|
||||
|
||||
health_check_type:
|
||||
description:
|
||||
- (String) The service to use for the health check.
|
||||
- The service to use for the health check.
|
||||
- "The choices available are: C(ELB), C(HCS), C(TARGET_GROUP), C(MLB), C(EC2)."
|
||||
type: str
|
||||
|
||||
iam_role_name:
|
||||
description:
|
||||
- (String) The instance profile iamRole name
|
||||
- The instance profile iamRole name
|
||||
- Only use iam_role_arn, or iam_role_name
|
||||
type: str
|
||||
|
||||
iam_role_arn:
|
||||
description:
|
||||
- (String) The instance profile iamRole arn
|
||||
- The instance profile iamRole arn
|
||||
- Only use iam_role_arn, or iam_role_name
|
||||
type: str
|
||||
|
||||
id:
|
||||
description:
|
||||
- (String) The group id if it already exists and you want to update, or delete it.
|
||||
- The group id if it already exists and you want to update, or delete it.
|
||||
This will not work unless the uniqueness_by field is set to id.
|
||||
When this is set, and the uniqueness_by field is set, the group will either be updated or deleted, but not created.
|
||||
type: str
|
||||
|
||||
image_id:
|
||||
description:
|
||||
- (String) The image Id used to launch the instance.;
|
||||
- The image Id used to launch the instance.;
|
||||
In case of conflict between Instance type and image type, an error will be returned
|
||||
required: true
|
||||
type: str
|
||||
|
||||
key_pair:
|
||||
description:
|
||||
- (String) Specify a Key Pair to attach to the instances
|
||||
- Specify a Key Pair to attach to the instances
|
||||
type: str
|
||||
|
||||
kubernetes:
|
||||
description:
|
||||
- (Object) The Kubernetes integration configuration.
|
||||
- The Kubernetes integration configuration.
|
||||
Expects the following keys -
|
||||
api_server (String),
|
||||
token (String)
|
||||
@@ -174,47 +178,48 @@ options:
|
||||
|
||||
lifetime_period:
|
||||
description:
|
||||
- (Integer) lifetime period
|
||||
- Lifetime period
|
||||
type: int
|
||||
|
||||
load_balancers:
|
||||
description:
|
||||
- (List of Strings) List of classic ELB names
|
||||
- List of classic ELB names
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
max_size:
|
||||
description:
|
||||
- (Integer) The upper limit number of instances that you can scale up to
|
||||
- The upper limit number of instances that you can scale up to
|
||||
required: true
|
||||
type: int
|
||||
|
||||
mesosphere:
|
||||
description:
|
||||
- (Object) The Mesosphere integration configuration.
|
||||
- The Mesosphere integration configuration.
|
||||
Expects the following key -
|
||||
api_server (String)
|
||||
type: dict
|
||||
|
||||
min_size:
|
||||
description:
|
||||
- (Integer) The lower limit number of instances that you can scale down to
|
||||
- The lower limit number of instances that you can scale down to
|
||||
required: true
|
||||
type: int
|
||||
|
||||
monitoring:
|
||||
description:
|
||||
- (String) Describes whether instance Enhanced Monitoring is enabled
|
||||
- Describes whether instance Enhanced Monitoring is enabled
|
||||
type: str
|
||||
|
||||
name:
|
||||
description:
|
||||
- (String) Unique name for elastigroup to be created, updated or deleted
|
||||
- Unique name for elastigroup to be created, updated or deleted
|
||||
required: true
|
||||
type: str
|
||||
|
||||
network_interfaces:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of network interfaces to add to the elastigroup;
|
||||
- A list of hash/dictionaries of network interfaces to add to the elastigroup;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are -
|
||||
description (String),
|
||||
@@ -229,29 +234,30 @@ options:
|
||||
associate_ipv6_address (Boolean),
|
||||
private_ip_addresses (List of Objects, Keys are privateIpAddress (String, required) and primary (Boolean))
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
on_demand_count:
|
||||
description:
|
||||
- (Integer) Required if risk is not set
|
||||
- Required if risk is not set
|
||||
- Number of on demand instances to launch. All other instances will be spot instances.;
|
||||
Either set this parameter or the risk parameter
|
||||
type: int
|
||||
|
||||
on_demand_instance_type:
|
||||
description:
|
||||
- (String) On-demand instance type that will be provisioned
|
||||
- On-demand instance type that will be provisioned
|
||||
type: str
|
||||
|
||||
opsworks:
|
||||
description:
|
||||
- (Object) The elastigroup OpsWorks integration configration.;
|
||||
- The elastigroup OpsWorks integration configration.;
|
||||
Expects the following key -
|
||||
layer_id (String)
|
||||
type: dict
|
||||
|
||||
persistence:
|
||||
description:
|
||||
- (Object) The Stateful elastigroup configration.;
|
||||
- The Stateful elastigroup configration.;
|
||||
Accepts the following keys -
|
||||
should_persist_root_device (Boolean),
|
||||
should_persist_block_devices (Boolean),
|
||||
@@ -260,14 +266,14 @@ options:
|
||||
|
||||
product:
|
||||
description:
|
||||
- (String) Operation system type.
|
||||
- Operation system type.
|
||||
- "Available choices are: C(Linux/UNIX), C(SUSE Linux), C(Windows), C(Linux/UNIX (Amazon VPC)), C(SUSE Linux (Amazon VPC))."
|
||||
required: true
|
||||
type: str
|
||||
|
||||
rancher:
|
||||
description:
|
||||
- (Object) The Rancher integration configuration.;
|
||||
- The Rancher integration configuration.;
|
||||
Expects the following keys -
|
||||
version (String),
|
||||
access_key (String),
|
||||
@@ -277,7 +283,7 @@ options:
|
||||
|
||||
right_scale:
|
||||
description:
|
||||
- (Object) The Rightscale integration configuration.;
|
||||
- The Rightscale integration configuration.;
|
||||
Expects the following keys -
|
||||
account_id (String),
|
||||
refresh_token (String)
|
||||
@@ -285,12 +291,12 @@ options:
|
||||
|
||||
risk:
|
||||
description:
|
||||
- (Integer) required if on demand is not set. The percentage of Spot instances to launch (0 - 100).
|
||||
- Required if on demand is not set. The percentage of Spot instances to launch (0 - 100).
|
||||
type: int
|
||||
|
||||
roll_config:
|
||||
description:
|
||||
- (Object) Roll configuration.;
|
||||
- Roll configuration.;
|
||||
If you would like the group to roll after updating, please use this feature.
|
||||
Accepts the following keys -
|
||||
batch_size_percentage(Integer, Required),
|
||||
@@ -300,7 +306,7 @@ options:
|
||||
|
||||
scheduled_tasks:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of scheduled tasks to configure in the elastigroup;
|
||||
- A list of hash/dictionaries of scheduled tasks to configure in the elastigroup;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are -
|
||||
adjustment (Integer),
|
||||
@@ -315,84 +321,90 @@ options:
|
||||
task_type (String, required),
|
||||
is_enabled (Boolean)
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
security_group_ids:
|
||||
description:
|
||||
- (List of Strings) One or more security group IDs. ;
|
||||
- One or more security group IDs. ;
|
||||
In case of update it will override the existing Security Group with the new given array
|
||||
required: true
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
shutdown_script:
|
||||
description:
|
||||
- (String) The Base64-encoded shutdown script that executes prior to instance termination.
|
||||
- The Base64-encoded shutdown script that executes prior to instance termination.
|
||||
Encode before setting.
|
||||
type: str
|
||||
|
||||
signals:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of signals to configure in the elastigroup;
|
||||
- A list of hash/dictionaries of signals to configure in the elastigroup;
|
||||
keys allowed are -
|
||||
name (String, required),
|
||||
timeout (Integer)
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
spin_up_time:
|
||||
description:
|
||||
- (Integer) spin up time, in seconds, for the instance
|
||||
- Spin up time, in seconds, for the instance
|
||||
type: int
|
||||
|
||||
spot_instance_types:
|
||||
description:
|
||||
- (List of Strings) Spot instance type that will be provisioned.
|
||||
- Spot instance type that will be provisioned.
|
||||
required: true
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
state:
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
description:
|
||||
- (String) create or delete the elastigroup
|
||||
- Create or delete the elastigroup
|
||||
default: present
|
||||
type: str
|
||||
|
||||
tags:
|
||||
description:
|
||||
- (List of tagKey:tagValue pairs) a list of tags to configure in the elastigroup. Please specify list of keys and values (key colon value);
|
||||
- A list of tags to configure in the elastigroup. Please specify list of keys and values (key colon value);
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
target:
|
||||
description:
|
||||
- (Integer) The number of instances to launch
|
||||
- The number of instances to launch
|
||||
required: true
|
||||
type: int
|
||||
|
||||
target_group_arns:
|
||||
description:
|
||||
- (List of Strings) List of target group arns instances should be registered to
|
||||
- List of target group arns instances should be registered to
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
tenancy:
|
||||
description:
|
||||
- (String) dedicated vs shared tenancy.
|
||||
- Dedicated vs shared tenancy.
|
||||
- "The available choices are: C(default), C(dedicated)."
|
||||
type: str
|
||||
|
||||
terminate_at_end_of_billing_hour:
|
||||
description:
|
||||
- (Boolean) terminate at the end of billing hour
|
||||
- Terminate at the end of billing hour
|
||||
type: bool
|
||||
|
||||
unit:
|
||||
description:
|
||||
- (String) The capacity unit to launch instances by.
|
||||
- The capacity unit to launch instances by.
|
||||
- "The available choices are: C(instance), C(weight)."
|
||||
type: str
|
||||
|
||||
up_scaling_policies:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of scaling policies to configure in the elastigroup;
|
||||
- A list of hash/dictionaries of scaling policies to configure in the elastigroup;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are -
|
||||
policy_name (String, required),
|
||||
@@ -413,10 +425,11 @@ options:
|
||||
maximum (String),
|
||||
minimum (String)
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
down_scaling_policies:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of scaling policies to configure in the elastigroup;
|
||||
- A list of hash/dictionaries of scaling policies to configure in the elastigroup;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are -
|
||||
policy_name (String, required),
|
||||
@@ -437,10 +450,11 @@ options:
|
||||
maximum (String),
|
||||
minimum (String)
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
target_tracking_policies:
|
||||
description:
|
||||
- (List of Objects) a list of hash/dictionaries of target tracking policies to configure in the elastigroup;
|
||||
- A list of hash/dictionaries of target tracking policies to configure in the elastigroup;
|
||||
'[{"key":"value", "key":"value"}]';
|
||||
keys allowed are -
|
||||
policy_name (String, required),
|
||||
@@ -452,37 +466,38 @@ options:
|
||||
cooldown (String, required),
|
||||
target (String, required)
|
||||
type: list
|
||||
elements: dict
|
||||
|
||||
uniqueness_by:
|
||||
choices:
|
||||
- id
|
||||
- name
|
||||
description:
|
||||
- (String) If your group names are not unique, you may use this feature to update or delete a specific group.
|
||||
- If your group names are not unique, you may use this feature to update or delete a specific group.
|
||||
Whenever this property is set, you must set a group_id in order to update or delete a group, otherwise a group will be created.
|
||||
default: name
|
||||
type: str
|
||||
|
||||
user_data:
|
||||
description:
|
||||
- (String) Base64-encoded MIME user data. Encode before setting the value.
|
||||
- Base64-encoded MIME user data. Encode before setting the value.
|
||||
type: str
|
||||
|
||||
utilize_reserved_instances:
|
||||
description:
|
||||
- (Boolean) In case of any available Reserved Instances,
|
||||
- In case of any available Reserved Instances,
|
||||
Elastigroup will utilize your reservations before purchasing Spot instances.
|
||||
type: bool
|
||||
|
||||
wait_for_instances:
|
||||
description:
|
||||
- (Boolean) Whether or not the elastigroup creation / update actions should wait for the instances to spin
|
||||
- Whether or not the elastigroup creation / update actions should wait for the instances to spin
|
||||
type: bool
|
||||
default: false
|
||||
|
||||
wait_timeout:
|
||||
description:
|
||||
- (Integer) How long the module should wait for instances before failing the action.;
|
||||
- How long the module should wait for instances before failing the action.;
|
||||
Only works if wait_for_instances is True.
|
||||
type: int
|
||||
|
||||
@@ -1428,18 +1443,18 @@ def main():
|
||||
fields = dict(
|
||||
account_id=dict(type='str'),
|
||||
availability_vs_cost=dict(type='str', required=True),
|
||||
availability_zones=dict(type='list', required=True),
|
||||
block_device_mappings=dict(type='list'),
|
||||
availability_zones=dict(type='list', elements='dict', required=True),
|
||||
block_device_mappings=dict(type='list', elements='dict'),
|
||||
chef=dict(type='dict'),
|
||||
credentials_path=dict(type='path', default="~/.spotinst/credentials"),
|
||||
do_not_update=dict(default=[], type='list'),
|
||||
down_scaling_policies=dict(type='list'),
|
||||
down_scaling_policies=dict(type='list', elements='dict'),
|
||||
draining_timeout=dict(type='int'),
|
||||
ebs_optimized=dict(type='bool'),
|
||||
ebs_volume_pool=dict(type='list'),
|
||||
ebs_volume_pool=dict(type='list', elements='dict'),
|
||||
ecs=dict(type='dict'),
|
||||
elastic_beanstalk=dict(type='dict'),
|
||||
elastic_ips=dict(type='list'),
|
||||
elastic_ips=dict(type='list', elements='str'),
|
||||
fallback_to_od=dict(type='bool'),
|
||||
id=dict(type='str'),
|
||||
health_check_grace_period=dict(type='int'),
|
||||
@@ -1451,7 +1466,7 @@ def main():
|
||||
key_pair=dict(type='str', no_log=False),
|
||||
kubernetes=dict(type='dict'),
|
||||
lifetime_period=dict(type='int'),
|
||||
load_balancers=dict(type='list'),
|
||||
load_balancers=dict(type='list', elements='str'),
|
||||
max_size=dict(type='int', required=True),
|
||||
mesosphere=dict(type='dict'),
|
||||
min_size=dict(type='int', required=True),
|
||||
@@ -1459,7 +1474,7 @@ def main():
|
||||
multai_load_balancers=dict(type='list'),
|
||||
multai_token=dict(type='str', no_log=True),
|
||||
name=dict(type='str', required=True),
|
||||
network_interfaces=dict(type='list'),
|
||||
network_interfaces=dict(type='list', elements='dict'),
|
||||
on_demand_count=dict(type='int'),
|
||||
on_demand_instance_type=dict(type='str'),
|
||||
opsworks=dict(type='dict'),
|
||||
@@ -1469,16 +1484,16 @@ def main():
|
||||
right_scale=dict(type='dict'),
|
||||
risk=dict(type='int'),
|
||||
roll_config=dict(type='dict'),
|
||||
scheduled_tasks=dict(type='list'),
|
||||
security_group_ids=dict(type='list', required=True),
|
||||
scheduled_tasks=dict(type='list', elements='dict'),
|
||||
security_group_ids=dict(type='list', elements='str', required=True),
|
||||
shutdown_script=dict(type='str'),
|
||||
signals=dict(type='list'),
|
||||
signals=dict(type='list', elements='dict'),
|
||||
spin_up_time=dict(type='int'),
|
||||
spot_instance_types=dict(type='list', required=True),
|
||||
spot_instance_types=dict(type='list', elements='str', required=True),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
tags=dict(type='list'),
|
||||
tags=dict(type='list', elements='dict'),
|
||||
target=dict(type='int', required=True),
|
||||
target_group_arns=dict(type='list'),
|
||||
target_group_arns=dict(type='list', elements='str'),
|
||||
tenancy=dict(type='str'),
|
||||
terminate_at_end_of_billing_hour=dict(type='bool'),
|
||||
token=dict(type='str', no_log=True),
|
||||
@@ -1486,8 +1501,8 @@ def main():
|
||||
user_data=dict(type='str'),
|
||||
utilize_reserved_instances=dict(type='bool'),
|
||||
uniqueness_by=dict(default='name', choices=['name', 'id']),
|
||||
up_scaling_policies=dict(type='list'),
|
||||
target_tracking_policies=dict(type='list'),
|
||||
up_scaling_policies=dict(type='list', elements='dict'),
|
||||
target_tracking_policies=dict(type='list', elements='dict'),
|
||||
wait_for_instances=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='int')
|
||||
)
|
||||
|
||||
@@ -189,7 +189,24 @@ from collections import defaultdict
|
||||
from ansible.module_utils.basic import to_text, AnsibleModule
|
||||
|
||||
|
||||
RULE_SCOPES = ["agent", "event", "key", "keyring", "node", "operator", "query", "service", "session"]
|
||||
RULE_SCOPES = [
|
||||
"agent",
|
||||
"agent_prefix",
|
||||
"event",
|
||||
"event_prefix",
|
||||
"key",
|
||||
"key_prefix",
|
||||
"keyring",
|
||||
"node",
|
||||
"node_prefix",
|
||||
"operator",
|
||||
"query",
|
||||
"query_prefix",
|
||||
"service",
|
||||
"service_prefix",
|
||||
"session",
|
||||
"session_prefix",
|
||||
]
|
||||
|
||||
MANAGEMENT_PARAMETER_NAME = "mgmt_token"
|
||||
HOST_PARAMETER_NAME = "host"
|
||||
|
||||
@@ -29,17 +29,24 @@ options:
|
||||
- Name of the retention policy.
|
||||
required: true
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- State of the retention policy.
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
type: str
|
||||
version_added: 3.1.0
|
||||
duration:
|
||||
description:
|
||||
- Determines how long InfluxDB should keep the data. If specified, it
|
||||
should be C(INF) or at least one hour. If not specified, C(INF) is
|
||||
assumed. Supports complex duration expressions with multiple units.
|
||||
required: true
|
||||
- Required only if I(state) is set to C(present).
|
||||
type: str
|
||||
replication:
|
||||
description:
|
||||
- Determines how many independent copies of each point are stored in the cluster.
|
||||
required: true
|
||||
- Required only if I(state) is set to C(present).
|
||||
type: int
|
||||
default:
|
||||
description:
|
||||
@@ -63,53 +70,65 @@ EXAMPLES = r'''
|
||||
# Example influxdb_retention_policy command from Ansible Playbooks
|
||||
- name: Create 1 hour retention policy
|
||||
community.general.influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
hostname: "{{ influxdb_ip_address }}"
|
||||
database_name: "{{ influxdb_database_name }}"
|
||||
policy_name: test
|
||||
duration: 1h
|
||||
replication: 1
|
||||
ssl: yes
|
||||
validate_certs: yes
|
||||
state: present
|
||||
|
||||
- name: Create 1 day retention policy with 1 hour shard group duration
|
||||
community.general.influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
hostname: "{{ influxdb_ip_address }}"
|
||||
database_name: "{{ influxdb_database_name }}"
|
||||
policy_name: test
|
||||
duration: 1d
|
||||
replication: 1
|
||||
shard_group_duration: 1h
|
||||
state: present
|
||||
|
||||
- name: Create 1 week retention policy with 1 day shard group duration
|
||||
community.general.influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
hostname: "{{ influxdb_ip_address }}"
|
||||
database_name: "{{ influxdb_database_name }}"
|
||||
policy_name: test
|
||||
duration: 1w
|
||||
replication: 1
|
||||
shard_group_duration: 1d
|
||||
state: present
|
||||
|
||||
- name: Create infinite retention policy with 1 week of shard group duration
|
||||
community.general.influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
hostname: "{{ influxdb_ip_address }}"
|
||||
database_name: "{{ influxdb_database_name }}"
|
||||
policy_name: test
|
||||
duration: INF
|
||||
replication: 1
|
||||
ssl: no
|
||||
validate_certs: no
|
||||
shard_group_duration: 1w
|
||||
state: present
|
||||
|
||||
- name: Create retention policy with complex durations
|
||||
community.general.influxdb_retention_policy:
|
||||
hostname: "{{influxdb_ip_address}}"
|
||||
database_name: "{{influxdb_database_name}}"
|
||||
hostname: "{{ influxdb_ip_address }}"
|
||||
database_name: "{{ influxdb_database_name }}"
|
||||
policy_name: test
|
||||
duration: 5d1h30m
|
||||
replication: 1
|
||||
ssl: no
|
||||
validate_certs: no
|
||||
shard_group_duration: 1d10h30m
|
||||
state: present
|
||||
|
||||
- name: Drop retention policy
|
||||
community.general.influxdb_retention_policy:
|
||||
hostname: "{{ influxdb_ip_address }}"
|
||||
database_name: "{{ influxdb_database_name }}"
|
||||
policy_name: test
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
@@ -129,11 +148,26 @@ from ansible_collections.community.general.plugins.module_utils.influxdb import
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
VALID_DURATION_REGEX = re.compile(r'^(\d+(ns|u|µ|ms|s|m|h|d|w))+$')
|
||||
VALID_DURATION_REGEX = re.compile(r'^(INF|(\d+(ns|u|µ|ms|s|m|h|d|w)))+$')
|
||||
|
||||
DURATION_REGEX = re.compile(r'(\d+)(ns|u|µ|ms|s|m|h|d|w)')
|
||||
EXTENDED_DURATION_REGEX = re.compile(r'(?:(\d+)(ns|u|µ|ms|m|h|d|w)|(\d+(?:\.\d+)?)(s))')
|
||||
|
||||
DURATION_UNIT_NANOSECS = {
|
||||
'ns': 1,
|
||||
'u': 1000,
|
||||
'µ': 1000,
|
||||
'ms': 1000 * 1000,
|
||||
's': 1000 * 1000 * 1000,
|
||||
'm': 1000 * 1000 * 1000 * 60,
|
||||
'h': 1000 * 1000 * 1000 * 60 * 60,
|
||||
'd': 1000 * 1000 * 1000 * 60 * 60 * 24,
|
||||
'w': 1000 * 1000 * 1000 * 60 * 60 * 24 * 7,
|
||||
}
|
||||
|
||||
MINIMUM_VALID_DURATION = 1 * DURATION_UNIT_NANOSECS['h']
|
||||
MINIMUM_VALID_SHARD_GROUP_DURATION = 1 * DURATION_UNIT_NANOSECS['h']
|
||||
|
||||
|
||||
def check_duration_literal(value):
|
||||
return VALID_DURATION_REGEX.search(value) is not None
|
||||
@@ -148,28 +182,9 @@ def parse_duration_literal(value, extended=False):
|
||||
lookup = (EXTENDED_DURATION_REGEX if extended else DURATION_REGEX).findall(value)
|
||||
|
||||
for duration_literal in lookup:
|
||||
if extended and duration_literal[3] == 's':
|
||||
duration_val = float(duration_literal[2])
|
||||
duration += duration_val * 1000 * 1000 * 1000
|
||||
else:
|
||||
duration_val = int(duration_literal[0])
|
||||
|
||||
if duration_literal[1] == 'ns':
|
||||
duration += duration_val
|
||||
elif duration_literal[1] == 'u' or duration_literal[1] == 'µ':
|
||||
duration += duration_val * 1000
|
||||
elif duration_literal[1] == 'ms':
|
||||
duration += duration_val * 1000 * 1000
|
||||
elif duration_literal[1] == 's':
|
||||
duration += duration_val * 1000 * 1000 * 1000
|
||||
elif duration_literal[1] == 'm':
|
||||
duration += duration_val * 1000 * 1000 * 1000 * 60
|
||||
elif duration_literal[1] == 'h':
|
||||
duration += duration_val * 1000 * 1000 * 1000 * 60 * 60
|
||||
elif duration_literal[1] == 'd':
|
||||
duration += duration_val * 1000 * 1000 * 1000 * 60 * 60 * 24
|
||||
elif duration_literal[1] == 'w':
|
||||
duration += duration_val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7
|
||||
filtered_literal = list(filter(None, duration_literal))
|
||||
duration_val = float(filtered_literal[0])
|
||||
duration += duration_val * DURATION_UNIT_NANOSECS[filtered_literal[1]]
|
||||
|
||||
return duration
|
||||
|
||||
@@ -208,7 +223,7 @@ def create_retention_policy(module, client):
|
||||
module.fail_json(msg="Failed to parse value of duration")
|
||||
|
||||
influxdb_duration_format = parse_duration_literal(duration)
|
||||
if influxdb_duration_format != 0 and influxdb_duration_format < 3600000000000:
|
||||
if influxdb_duration_format != 0 and influxdb_duration_format < MINIMUM_VALID_DURATION:
|
||||
module.fail_json(msg="duration value must be at least 1h")
|
||||
|
||||
if shard_group_duration is not None:
|
||||
@@ -216,8 +231,8 @@ def create_retention_policy(module, client):
|
||||
module.fail_json(msg="Failed to parse value of shard_group_duration")
|
||||
|
||||
influxdb_shard_group_duration_format = parse_duration_literal(shard_group_duration)
|
||||
if influxdb_shard_group_duration_format < 3600000000000:
|
||||
module.fail_json(msg="shard_group_duration value must be at least 1h")
|
||||
if influxdb_shard_group_duration_format < MINIMUM_VALID_SHARD_GROUP_DURATION:
|
||||
module.fail_json(msg="shard_group_duration value must be finite and at least 1h")
|
||||
|
||||
if not module.check_mode:
|
||||
try:
|
||||
@@ -245,7 +260,7 @@ def alter_retention_policy(module, client, retention_policy):
|
||||
module.fail_json(msg="Failed to parse value of duration")
|
||||
|
||||
influxdb_duration_format = parse_duration_literal(duration)
|
||||
if influxdb_duration_format != 0 and influxdb_duration_format < 3600000000000:
|
||||
if influxdb_duration_format != 0 and influxdb_duration_format < MINIMUM_VALID_DURATION:
|
||||
module.fail_json(msg="duration value must be at least 1h")
|
||||
|
||||
if shard_group_duration is None:
|
||||
@@ -255,8 +270,8 @@ def alter_retention_policy(module, client, retention_policy):
|
||||
module.fail_json(msg="Failed to parse value of shard_group_duration")
|
||||
|
||||
influxdb_shard_group_duration_format = parse_duration_literal(shard_group_duration)
|
||||
if influxdb_shard_group_duration_format < 3600000000000:
|
||||
module.fail_json(msg="shard_group_duration value must be at least 1h")
|
||||
if influxdb_shard_group_duration_format < MINIMUM_VALID_SHARD_GROUP_DURATION:
|
||||
module.fail_json(msg="shard_group_duration value must be finite and at least 1h")
|
||||
|
||||
if (retention_policy['duration'] != influxdb_duration_format or
|
||||
retention_policy['shardGroupDuration'] != influxdb_shard_group_duration_format or
|
||||
@@ -272,30 +287,55 @@ def alter_retention_policy(module, client, retention_policy):
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
|
||||
def drop_retention_policy(module, client):
|
||||
database_name = module.params['database_name']
|
||||
policy_name = module.params['policy_name']
|
||||
|
||||
if not module.check_mode:
|
||||
try:
|
||||
client.drop_retention_policy(policy_name, database_name)
|
||||
except exceptions.InfluxDBClientError as e:
|
||||
module.fail_json(msg=e.content)
|
||||
module.exit_json(changed=True)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = InfluxDb.influxdb_argument_spec()
|
||||
argument_spec.update(
|
||||
state=dict(default='present', type='str', choices=['present', 'absent']),
|
||||
database_name=dict(required=True, type='str'),
|
||||
policy_name=dict(required=True, type='str'),
|
||||
duration=dict(required=True, type='str'),
|
||||
replication=dict(required=True, type='int'),
|
||||
duration=dict(type='str'),
|
||||
replication=dict(type='int'),
|
||||
default=dict(default=False, type='bool'),
|
||||
shard_group_duration=dict(required=False, type='str'),
|
||||
shard_group_duration=dict(type='str'),
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True
|
||||
supports_check_mode=True,
|
||||
required_if=(
|
||||
('state', 'present', ['duration', 'replication']),
|
||||
),
|
||||
)
|
||||
|
||||
state = module.params['state']
|
||||
|
||||
influxdb = InfluxDb(module)
|
||||
client = influxdb.connect_to_influxdb()
|
||||
|
||||
retention_policy = find_retention_policy(module, client)
|
||||
|
||||
if retention_policy:
|
||||
alter_retention_policy(module, client, retention_policy)
|
||||
else:
|
||||
create_retention_policy(module, client)
|
||||
if state == 'present':
|
||||
if retention_policy:
|
||||
alter_retention_policy(module, client, retention_policy)
|
||||
else:
|
||||
create_retention_policy(module, client)
|
||||
|
||||
if state == 'absent':
|
||||
if retention_policy:
|
||||
drop_retention_policy(module, client)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -100,6 +100,8 @@ RETURN = r'''
|
||||
#only defaults
|
||||
'''
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils.urls import ConnectionError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
@@ -115,7 +117,7 @@ def find_user(module, client, user_name):
|
||||
if user['user'] == user_name:
|
||||
user_result = user
|
||||
break
|
||||
except (ConnectionError, influx.exceptions.InfluxDBClientError) as e:
|
||||
except ConnectionError as e:
|
||||
module.fail_json(msg=to_native(e))
|
||||
return user_result
|
||||
|
||||
@@ -166,16 +168,16 @@ def set_user_grants(module, client, user_name, grants):
|
||||
|
||||
try:
|
||||
current_grants = client.get_list_privileges(user_name)
|
||||
parsed_grants = []
|
||||
# Fix privileges wording
|
||||
for i, v in enumerate(current_grants):
|
||||
if v['privilege'] == 'ALL PRIVILEGES':
|
||||
v['privilege'] = 'ALL'
|
||||
current_grants[i] = v
|
||||
elif v['privilege'] == 'NO PRIVILEGES':
|
||||
del(current_grants[i])
|
||||
if v['privilege'] != 'NO PRIVILEGES':
|
||||
if v['privilege'] == 'ALL PRIVILEGES':
|
||||
v['privilege'] = 'ALL'
|
||||
parsed_grants.append(v)
|
||||
|
||||
# check if the current grants are included in the desired ones
|
||||
for current_grant in current_grants:
|
||||
for current_grant in parsed_grants:
|
||||
if current_grant not in grants:
|
||||
if not module.check_mode:
|
||||
client.revoke_privilege(current_grant['privilege'],
|
||||
@@ -185,7 +187,7 @@ def set_user_grants(module, client, user_name, grants):
|
||||
|
||||
# check if the desired grants are included in the current ones
|
||||
for grant in grants:
|
||||
if grant not in current_grants:
|
||||
if grant not in parsed_grants:
|
||||
if not module.check_mode:
|
||||
client.grant_privilege(grant['privilege'],
|
||||
grant['database'],
|
||||
@@ -198,6 +200,9 @@ def set_user_grants(module, client, user_name, grants):
|
||||
return changed
|
||||
|
||||
|
||||
INFLUX_AUTH_FIRST_USER_REQUIRED = "error authorizing query: create admin user first or disable authentication"
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = influx.InfluxDb.influxdb_argument_spec()
|
||||
argument_spec.update(
|
||||
@@ -219,7 +224,23 @@ def main():
|
||||
grants = module.params['grants']
|
||||
influxdb = influx.InfluxDb(module)
|
||||
client = influxdb.connect_to_influxdb()
|
||||
user = find_user(module, client, user_name)
|
||||
|
||||
user = None
|
||||
try:
|
||||
user = find_user(module, client, user_name)
|
||||
except influx.exceptions.InfluxDBClientError as e:
|
||||
if e.code == 403:
|
||||
reason = None
|
||||
try:
|
||||
msg = json.loads(e.content)
|
||||
reason = msg["error"]
|
||||
except (KeyError, ValueError):
|
||||
module.fail_json(msg=to_native(e))
|
||||
|
||||
if reason != INFLUX_AUTH_FIRST_USER_REQUIRED:
|
||||
module.fail_json(msg=to_native(e))
|
||||
else:
|
||||
module.fail_json(msg=to_native(e))
|
||||
|
||||
changed = False
|
||||
|
||||
|
||||
187
plugins/modules/database/saphana/hana_query.py
Normal file
187
plugins/modules/database/saphana/hana_query.py
Normal file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2021, Rainer Leber <rainerleber@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
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: hana_query
|
||||
short_description: Execute SQL on HANA
|
||||
version_added: 3.2.0
|
||||
description: This module executes SQL statements on HANA with hdbsql.
|
||||
options:
|
||||
sid:
|
||||
description: The system ID.
|
||||
type: str
|
||||
required: true
|
||||
instance:
|
||||
description: The instance number.
|
||||
type: str
|
||||
required: true
|
||||
user:
|
||||
description: A dedicated username. Defaults to C(SYSTEM).
|
||||
type: str
|
||||
default: SYSTEM
|
||||
password:
|
||||
description: The password to connect to the database.
|
||||
type: str
|
||||
required: true
|
||||
autocommit:
|
||||
description: Autocommit the statement.
|
||||
type: bool
|
||||
default: true
|
||||
host:
|
||||
description: The Host IP address. The port can be defined as well.
|
||||
type: str
|
||||
database:
|
||||
description: Define the database on which to connect.
|
||||
type: str
|
||||
encrypted:
|
||||
description: Use encrypted connection. Defaults to C(false).
|
||||
type: bool
|
||||
default: false
|
||||
filepath:
|
||||
description:
|
||||
- One or more files each containing one SQL query to run.
|
||||
- Must be a string or list containing strings.
|
||||
type: list
|
||||
elements: path
|
||||
query:
|
||||
description:
|
||||
- SQL query to run.
|
||||
- Must be a string or list containing strings. Please note that if you supply a string, it will be split by commas (C(,)) to a list.
|
||||
It is better to supply a one-element list instead to avoid mangled input.
|
||||
type: list
|
||||
elements: str
|
||||
notes:
|
||||
- Does not support C(check_mode).
|
||||
author:
|
||||
- Rainer Leber (@rainerleber)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Simple select query
|
||||
community.general.hana_query:
|
||||
sid: "hdb"
|
||||
instance: "01"
|
||||
password: "Test123"
|
||||
query: "select user_name from users"
|
||||
|
||||
- name: Run several queries
|
||||
community.general.hana_query:
|
||||
sid: "hdb"
|
||||
instance: "01"
|
||||
password: "Test123"
|
||||
query:
|
||||
- "select user_name from users;"
|
||||
- select * from SYSTEM;
|
||||
host: "localhost"
|
||||
autocommit: False
|
||||
|
||||
- name: Run several queries from file
|
||||
community.general.hana_query:
|
||||
sid: "hdb"
|
||||
instance: "01"
|
||||
password: "Test123"
|
||||
filepath:
|
||||
- /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt
|
||||
- /tmp/HANA.txt
|
||||
host: "localhost"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
query_result:
|
||||
description: List containing results of all queries executed (one sublist for every query).
|
||||
returned: on success
|
||||
type: list
|
||||
elements: list
|
||||
sample: [[{"Column": "Value1"}, {"Column": "Value2"}], [{"Column": "Value1"}, {"Column": "Value2"}]]
|
||||
'''
|
||||
|
||||
import csv
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import StringIO
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def csv_to_list(rawcsv):
|
||||
reader_raw = csv.DictReader(StringIO(rawcsv))
|
||||
reader = [dict((k, v.strip()) for k, v in row.items()) for row in reader_raw]
|
||||
return list(reader)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
sid=dict(type='str', required=True),
|
||||
instance=dict(type='str', required=True),
|
||||
encrypted=dict(type='bool', required=False, default=False),
|
||||
host=dict(type='str', required=False),
|
||||
user=dict(type='str', required=False, default="SYSTEM"),
|
||||
password=dict(type='str', required=True, no_log=True),
|
||||
database=dict(type='str', required=False),
|
||||
query=dict(type='list', elements='str', required=False),
|
||||
filepath=dict(type='list', elements='path', required=False),
|
||||
autocommit=dict(type='bool', required=False, default=True),
|
||||
),
|
||||
required_one_of=[('query', 'filepath')],
|
||||
supports_check_mode=False,
|
||||
)
|
||||
rc, out, err, out_raw = [0, [], "", ""]
|
||||
|
||||
params = module.params
|
||||
|
||||
sid = (params['sid']).upper()
|
||||
instance = params['instance']
|
||||
user = params['user']
|
||||
password = params['password']
|
||||
autocommit = params['autocommit']
|
||||
host = params['host']
|
||||
database = params['database']
|
||||
encrypted = params['encrypted']
|
||||
|
||||
filepath = params['filepath']
|
||||
query = params['query']
|
||||
|
||||
bin_path = "/usr/sap/{sid}/HDB{instance}/exe/hdbsql".format(sid=sid, instance=instance)
|
||||
|
||||
try:
|
||||
command = [module.get_bin_path(bin_path, required=True)]
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Failed to find hdbsql at the expected path "{0}". Please check SID and instance number: "{1}"'.format(bin_path, to_native(e)))
|
||||
|
||||
if encrypted is True:
|
||||
command.extend(['-attemptencrypt'])
|
||||
if autocommit is False:
|
||||
command.extend(['-z'])
|
||||
if host is not None:
|
||||
command.extend(['-n', host])
|
||||
if database is not None:
|
||||
command.extend(['-d', database])
|
||||
# -x Suppresses additional output, such as the number of selected rows in a result set.
|
||||
command.extend(['-x', '-i', instance, '-u', user, '-p', password])
|
||||
|
||||
if filepath is not None:
|
||||
command.extend(['-I'])
|
||||
for p in filepath:
|
||||
# makes a command like hdbsql -i 01 -u SYSTEM -p secret123# -I /tmp/HANA_CPU_UtilizationPerCore_2.00.020+.txt,
|
||||
# iterates through files and append the output to var out.
|
||||
query_command = command + [p]
|
||||
(rc, out_raw, err) = module.run_command(query_command)
|
||||
out.append(csv_to_list(out_raw))
|
||||
if query is not None:
|
||||
for q in query:
|
||||
# makes a command like hdbsql -i 01 -u SYSTEM -p secret123# "select user_name from users",
|
||||
# iterates through multiple commands and append the output to var out.
|
||||
query_command = command + [q]
|
||||
(rc, out_raw, err) = module.run_command(query_command)
|
||||
out.append(csv_to_list(out_raw))
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, rc=rc, query_result=out, stderr=err)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
plugins/modules/discord.py
Symbolic link
1
plugins/modules/discord.py
Symbolic link
@@ -0,0 +1 @@
|
||||
./notification/discord.py
|
||||
@@ -41,8 +41,16 @@ options:
|
||||
exclude_path:
|
||||
description:
|
||||
- Remote absolute path, glob, or list of paths or globs for the file or files to exclude from I(path) list and glob expansion.
|
||||
- Use I(exclusion_patterns) to instead exclude files or subdirectories below any of the paths from the I(path) list.
|
||||
type: list
|
||||
elements: path
|
||||
exclusion_patterns:
|
||||
description:
|
||||
- Glob style patterns to exclude files or directories from the resulting archive.
|
||||
- This differs from I(exclude_path) which applies only to the source paths from I(path).
|
||||
type: list
|
||||
elements: path
|
||||
version_added: 3.2.0
|
||||
force_archive:
|
||||
description:
|
||||
- Allows you to force the module to treat this as an archive even if only a single file is specified.
|
||||
@@ -163,6 +171,8 @@ import re
|
||||
import shutil
|
||||
import tarfile
|
||||
import zipfile
|
||||
from fnmatch import fnmatch
|
||||
from sys import version_info
|
||||
from traceback import format_exc
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
@@ -186,6 +196,8 @@ else:
|
||||
LZMA_IMP_ERR = format_exc()
|
||||
HAS_LZMA = False
|
||||
|
||||
PY27 = version_info[0:2] >= (2, 7)
|
||||
|
||||
|
||||
def to_b(s):
|
||||
return to_bytes(s, errors='surrogate_or_strict')
|
||||
@@ -214,6 +226,59 @@ def expand_paths(paths):
|
||||
return expanded_path, is_globby
|
||||
|
||||
|
||||
def matches_exclusion_patterns(path, exclusion_patterns):
|
||||
return any(fnmatch(path, p) for p in exclusion_patterns)
|
||||
|
||||
|
||||
def get_filter(exclusion_patterns, format):
|
||||
def zip_filter(path):
|
||||
return matches_exclusion_patterns(path, exclusion_patterns)
|
||||
|
||||
def tar_filter(tarinfo):
|
||||
return None if matches_exclusion_patterns(tarinfo.name, exclusion_patterns) else tarinfo
|
||||
|
||||
return zip_filter if format == 'zip' or not PY27 else tar_filter
|
||||
|
||||
|
||||
def get_archive_contains(format):
|
||||
def archive_contains(archive, name):
|
||||
try:
|
||||
if format == 'zip':
|
||||
archive.getinfo(name)
|
||||
else:
|
||||
archive.getmember(name)
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
return archive_contains
|
||||
|
||||
|
||||
def get_add_to_archive(format, filter):
|
||||
def add_to_zip_archive(archive_file, path, archive_name):
|
||||
try:
|
||||
if not filter(path):
|
||||
archive_file.write(path, archive_name)
|
||||
except Exception as e:
|
||||
return e
|
||||
|
||||
return None
|
||||
|
||||
def add_to_tar_archive(archive_file, path, archive_name):
|
||||
try:
|
||||
if PY27:
|
||||
archive_file.add(path, archive_name, recursive=False, filter=filter)
|
||||
else:
|
||||
archive_file.add(path, archive_name, recursive=False, exclude=filter)
|
||||
except Exception as e:
|
||||
return e
|
||||
|
||||
return None
|
||||
|
||||
return add_to_zip_archive if format == 'zip' else add_to_tar_archive
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
@@ -221,6 +286,7 @@ def main():
|
||||
format=dict(type='str', default='gz', choices=['bz2', 'gz', 'tar', 'xz', 'zip']),
|
||||
dest=dict(type='path'),
|
||||
exclude_path=dict(type='list', elements='path'),
|
||||
exclusion_patterns=dict(type='list', elements='path'),
|
||||
force_archive=dict(type='bool', default=False),
|
||||
remove=dict(type='bool', default=False),
|
||||
),
|
||||
@@ -242,6 +308,8 @@ def main():
|
||||
changed = False
|
||||
state = 'absent'
|
||||
|
||||
exclusion_patterns = params['exclusion_patterns'] or []
|
||||
|
||||
# Simple or archive file compression (inapplicable with 'zip' since it's always an archive)
|
||||
b_successes = []
|
||||
|
||||
@@ -262,6 +330,10 @@ def main():
|
||||
# Only attempt to expand the exclude paths if it exists
|
||||
b_expanded_exclude_paths = expand_paths(exclude_paths)[0] if exclude_paths else []
|
||||
|
||||
filter = get_filter(exclusion_patterns, fmt)
|
||||
archive_contains = get_archive_contains(fmt)
|
||||
add_to_archive = get_add_to_archive(fmt, filter)
|
||||
|
||||
# Only try to determine if we are working with an archive or not if we haven't set archive to true
|
||||
if not force_archive:
|
||||
# If we actually matched multiple files or TRIED to, then
|
||||
@@ -384,38 +456,31 @@ def main():
|
||||
n_fullpath = to_na(b_fullpath)
|
||||
n_arcname = to_native(b_match_root.sub(b'', b_fullpath), errors='surrogate_or_strict')
|
||||
|
||||
try:
|
||||
if fmt == 'zip':
|
||||
arcfile.write(n_fullpath, n_arcname)
|
||||
else:
|
||||
arcfile.add(n_fullpath, n_arcname, recursive=False)
|
||||
|
||||
except Exception as e:
|
||||
errors.append('%s: %s' % (n_fullpath, to_native(e)))
|
||||
err = add_to_archive(arcfile, n_fullpath, n_arcname)
|
||||
if err:
|
||||
errors.append('%s: %s' % (n_fullpath, to_native(err)))
|
||||
|
||||
for b_filename in b_filenames:
|
||||
b_fullpath = b_dirpath + b_filename
|
||||
n_fullpath = to_na(b_fullpath)
|
||||
n_arcname = to_n(b_match_root.sub(b'', b_fullpath))
|
||||
|
||||
try:
|
||||
if fmt == 'zip':
|
||||
arcfile.write(n_fullpath, n_arcname)
|
||||
else:
|
||||
arcfile.add(n_fullpath, n_arcname, recursive=False)
|
||||
err = add_to_archive(arcfile, n_fullpath, n_arcname)
|
||||
if err:
|
||||
errors.append('Adding %s: %s' % (to_native(b_path), to_native(err)))
|
||||
|
||||
if archive_contains(arcfile, n_arcname):
|
||||
b_successes.append(b_fullpath)
|
||||
except Exception as e:
|
||||
errors.append('Adding %s: %s' % (to_native(b_path), to_native(e)))
|
||||
else:
|
||||
path = to_na(b_path)
|
||||
arcname = to_n(b_match_root.sub(b'', b_path))
|
||||
if fmt == 'zip':
|
||||
arcfile.write(path, arcname)
|
||||
else:
|
||||
arcfile.add(path, arcname, recursive=False)
|
||||
|
||||
b_successes.append(b_path)
|
||||
err = add_to_archive(arcfile, path, arcname)
|
||||
if err:
|
||||
errors.append('Adding %s: %s' % (to_native(b_path), to_native(err)))
|
||||
|
||||
if archive_contains(arcfile, arcname):
|
||||
b_successes.append(b_path)
|
||||
|
||||
except Exception as e:
|
||||
expanded_fmt = 'zip' if fmt == 'zip' else ('tar.' + fmt)
|
||||
|
||||
@@ -79,6 +79,7 @@ options:
|
||||
notes:
|
||||
- While it is possible to add an I(option) without specifying a I(value), this makes no sense.
|
||||
- As of Ansible 2.3, the I(dest) option has been changed to I(path) as default, but I(dest) still works as well.
|
||||
- As of community.general 3.2.0, UTF-8 BOM markers are discarded when reading files.
|
||||
author:
|
||||
- Jan-Piet Mens (@jpmens)
|
||||
- Ales Nosek (@noseka1)
|
||||
@@ -104,6 +105,7 @@ EXAMPLES = r'''
|
||||
backup: yes
|
||||
'''
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
@@ -141,7 +143,7 @@ def do_ini(module, filename, section=None, option=None, value=None,
|
||||
os.makedirs(destpath)
|
||||
ini_lines = []
|
||||
else:
|
||||
with open(filename, 'r') as ini_file:
|
||||
with io.open(filename, 'r', encoding="utf-8-sig") as ini_file:
|
||||
ini_lines = ini_file.readlines()
|
||||
|
||||
if module._diff:
|
||||
|
||||
219
plugins/modules/files/sapcar_extract.py
Normal file
219
plugins/modules/files/sapcar_extract.py
Normal file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2021, Rainer Leber <rainerleber@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
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: sapcar_extract
|
||||
short_description: Manages SAP SAPCAR archives
|
||||
version_added: "3.2.0"
|
||||
description:
|
||||
- Provides support for unpacking C(sar)/C(car) files with the SAPCAR binary from SAP and pulling
|
||||
information back into Ansible.
|
||||
options:
|
||||
path:
|
||||
description: The path to the SAR/CAR file.
|
||||
type: path
|
||||
required: true
|
||||
dest:
|
||||
description:
|
||||
- The destination where SAPCAR extracts the SAR file. Missing folders will be created.
|
||||
If this parameter is not provided it will unpack in the same folder as the SAR file.
|
||||
type: path
|
||||
binary_path:
|
||||
description:
|
||||
- The path to the SAPCAR binary, for example, C(/home/dummy/sapcar) or C(https://myserver/SAPCAR).
|
||||
If this parameter is not provided the module will look in C(PATH).
|
||||
type: path
|
||||
signature:
|
||||
description:
|
||||
- If C(true) the signature will be extracted.
|
||||
default: false
|
||||
type: bool
|
||||
security_library:
|
||||
description:
|
||||
- The path to the security library, for example, C(/usr/sap/hostctrl/exe/libsapcrytp.so), for signature operations.
|
||||
type: path
|
||||
manifest:
|
||||
description:
|
||||
- The name of the manifest.
|
||||
default: "SIGNATURE.SMF"
|
||||
type: str
|
||||
remove:
|
||||
description:
|
||||
- If C(true) the SAR/CAR file will be removed. B(This should be used with caution!)
|
||||
default: false
|
||||
type: bool
|
||||
author:
|
||||
- Rainer Leber (@RainerLeber)
|
||||
notes:
|
||||
- Always returns C(changed=true) in C(check_mode).
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Extract SAR file
|
||||
community.general.sapcar_extract:
|
||||
path: "~/source/hana.sar"
|
||||
|
||||
- name: Extract SAR file with destination
|
||||
community.general.sapcar_extract:
|
||||
path: "~/source/hana.sar"
|
||||
dest: "~/test/"
|
||||
|
||||
- name: Extract SAR file with destination and download from webserver can be a fileshare as well
|
||||
community.general.sapcar_extract:
|
||||
path: "~/source/hana.sar"
|
||||
dest: "~/dest/"
|
||||
binary_path: "https://myserver/SAPCAR"
|
||||
|
||||
- name: Extract SAR file and delete SAR after extract
|
||||
community.general.sapcar_extract:
|
||||
path: "~/source/hana.sar"
|
||||
remove: true
|
||||
|
||||
- name: Extract SAR file with manifest
|
||||
community.general.sapcar_extract:
|
||||
path: "~/source/hana.sar"
|
||||
signature: true
|
||||
|
||||
- name: Extract SAR file with manifest and rename it
|
||||
community.general.sapcar_extract:
|
||||
path: "~/source/hana.sar"
|
||||
manifest: "MyNewSignature.SMF"
|
||||
signature: true
|
||||
"""
|
||||
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def get_list_of_files(dir_name):
|
||||
# create a list of file and directories
|
||||
# names in the given directory
|
||||
list_of_file = os.listdir(dir_name)
|
||||
allFiles = list()
|
||||
# Iterate over all the entries
|
||||
for entry in list_of_file:
|
||||
# Create full path
|
||||
fullPath = os.path.join(dir_name, entry)
|
||||
# If entry is a directory then get the list of files in this directory
|
||||
if os.path.isdir(fullPath):
|
||||
allFiles = allFiles + [fullPath]
|
||||
allFiles = allFiles + get_list_of_files(fullPath)
|
||||
else:
|
||||
allFiles.append(fullPath)
|
||||
return allFiles
|
||||
|
||||
|
||||
def download_SAPCAR(binary_path, module):
|
||||
bin_path = None
|
||||
# download sapcar binary if url is provided otherwise path is returned
|
||||
if binary_path is not None:
|
||||
if binary_path.startswith('https://') or binary_path.startswith('http://'):
|
||||
random_file = NamedTemporaryFile(delete=False)
|
||||
with open_url(binary_path) as response:
|
||||
with random_file as out_file:
|
||||
data = response.read()
|
||||
out_file.write(data)
|
||||
os.chmod(out_file.name, 0o700)
|
||||
bin_path = out_file.name
|
||||
module.add_cleanup_file(bin_path)
|
||||
else:
|
||||
bin_path = binary_path
|
||||
return bin_path
|
||||
|
||||
|
||||
def check_if_present(command, path, dest, signature, manifest, module):
|
||||
# manipuliating output from SAR file for compare with already extracted files
|
||||
iter_command = [command, '-tvf', path]
|
||||
sar_out = module.run_command(iter_command)[1]
|
||||
sar_raw = sar_out.split("\n")[1:]
|
||||
if dest[-1] != "/":
|
||||
dest = dest + "/"
|
||||
sar_files = [dest + x.split(" ")[-1] for x in sar_raw if x]
|
||||
# remove any SIGNATURE.SMF from list because it will not unpacked if signature is false
|
||||
if not signature:
|
||||
sar_files = [item for item in sar_files if '.SMF' not in item]
|
||||
# if signature is renamed manipulate files in list of sar file for compare.
|
||||
if manifest != "SIGNATURE.SMF":
|
||||
sar_files = [item for item in sar_files if '.SMF' not in item]
|
||||
sar_files = sar_files + [manifest]
|
||||
# get extracted files if present
|
||||
files_extracted = get_list_of_files(dest)
|
||||
# compare extracted files with files in sar file
|
||||
present = all(elem in files_extracted for elem in sar_files)
|
||||
return present
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path', required=True),
|
||||
dest=dict(type='path'),
|
||||
binary_path=dict(type='path'),
|
||||
signature=dict(type='bool', default=False),
|
||||
security_library=dict(type='path'),
|
||||
manifest=dict(type='str', default="SIGNATURE.SMF"),
|
||||
remove=dict(type='bool', default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
rc, out, err = [0, "", ""]
|
||||
params = module.params
|
||||
check_mode = module.check_mode
|
||||
|
||||
path = params['path']
|
||||
dest = params['dest']
|
||||
signature = params['signature']
|
||||
security_library = params['security_library']
|
||||
manifest = params['manifest']
|
||||
remove = params['remove']
|
||||
|
||||
bin_path = download_SAPCAR(params['binary_path'], module)
|
||||
|
||||
if dest is None:
|
||||
dest_head_tail = os.path.split(path)
|
||||
dest = dest_head_tail[0] + '/'
|
||||
else:
|
||||
if not os.path.exists(dest):
|
||||
os.makedirs(dest, 0o755)
|
||||
|
||||
if bin_path is not None:
|
||||
command = [module.get_bin_path(bin_path, required=True)]
|
||||
else:
|
||||
try:
|
||||
command = [module.get_bin_path('sapcar', required=True)]
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Failed to find SAPCAR at the expected path or URL "{0}". Please check whether it is available: {1}'
|
||||
.format(bin_path, to_native(e)))
|
||||
|
||||
present = check_if_present(command[0], path, dest, signature, manifest, module)
|
||||
|
||||
if not present:
|
||||
command.extend(['-xvf', path, '-R', dest])
|
||||
if security_library:
|
||||
command.extend(['-L', security_library])
|
||||
if signature:
|
||||
command.extend(['-manifest', manifest])
|
||||
if not check_mode:
|
||||
(rc, out, err) = module.run_command(command, check_rc=True)
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
out = "allready unpacked"
|
||||
|
||||
if remove:
|
||||
os.remove(path)
|
||||
|
||||
module.exit_json(changed=changed, message=rc, stdout=out,
|
||||
stderr=err, command=' '.join(command))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -285,6 +285,39 @@ EXAMPLES = r'''
|
||||
z: http://z.test
|
||||
attribute: z:my_namespaced_attribute
|
||||
value: 'false'
|
||||
|
||||
- name: Adding building nodes with floor subnodes from a YAML variable
|
||||
community.general.xml:
|
||||
path: /foo/bar.xml
|
||||
xpath: /business
|
||||
add_children:
|
||||
- building:
|
||||
# Attributes
|
||||
name: Scumm bar
|
||||
location: Monkey island
|
||||
# Subnodes
|
||||
_:
|
||||
- floor: Pirate hall
|
||||
- floor: Grog storage
|
||||
- construction_date: "1990" # Only strings are valid
|
||||
- building: Grog factory
|
||||
|
||||
# Consider this XML for following example -
|
||||
#
|
||||
# <config>
|
||||
# <element name="test1">
|
||||
# <text>part to remove</text>
|
||||
# </element>
|
||||
# <element name="test2">
|
||||
# <text>part to keep</text>
|
||||
# </element>
|
||||
# </config>
|
||||
|
||||
- name: Delete element node based upon attribute
|
||||
community.general.xml:
|
||||
path: bar.xml
|
||||
xpath: /config/element[@name='test1']
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
|
||||
1
plugins/modules/hana_query.py
Symbolic link
1
plugins/modules/hana_query.py
Symbolic link
@@ -0,0 +1 @@
|
||||
./database/saphana/hana_query.py
|
||||
@@ -439,9 +439,10 @@ options:
|
||||
ssl_required:
|
||||
description:
|
||||
- The realm ssl required option.
|
||||
choices: ['all', 'external', 'none']
|
||||
aliases:
|
||||
- sslRequired
|
||||
type: bool
|
||||
type: str
|
||||
sso_session_idle_timeout:
|
||||
description:
|
||||
- The realm sso session idle timeout.
|
||||
@@ -654,10 +655,10 @@ def main():
|
||||
registration_flow=dict(type='str', aliases=['registrationFlow']),
|
||||
remember_me=dict(type='bool', aliases=['rememberMe']),
|
||||
reset_credentials_flow=dict(type='str', aliases=['resetCredentialsFlow']),
|
||||
reset_password_allowed=dict(type='bool', aliases=['resetPasswordAllowed']),
|
||||
reset_password_allowed=dict(type='bool', aliases=['resetPasswordAllowed'], no_log=False),
|
||||
revoke_refresh_token=dict(type='bool', aliases=['revokeRefreshToken']),
|
||||
smtp_server=dict(type='dict', aliases=['smtpServer']),
|
||||
ssl_required=dict(type='bool', aliases=['sslRequired']),
|
||||
ssl_required=dict(choices=["external", "all", "none"], aliases=['sslRequired']),
|
||||
sso_session_idle_timeout=dict(type='int', aliases=['ssoSessionIdleTimeout']),
|
||||
sso_session_idle_timeout_remember_me=dict(type='int', aliases=['ssoSessionIdleTimeoutRememberMe']),
|
||||
sso_session_max_lifespan=dict(type='int', aliases=['ssoSessionMaxLifespan']),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user