mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-30 10:26:52 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba9ec71dba | ||
|
|
ee4e1d997b | ||
|
|
1327d81dd7 | ||
|
|
6ceaee2c9f | ||
|
|
502cc3900e | ||
|
|
2947acb77c | ||
|
|
a4d8929a7e | ||
|
|
e80519dc71 | ||
|
|
53f9958b25 | ||
|
|
a925e020dc | ||
|
|
9512a389f3 | ||
|
|
7c987d7498 | ||
|
|
45d461c0ec | ||
|
|
d1877e1915 | ||
|
|
fa67df12a9 | ||
|
|
65dde57c1a | ||
|
|
6f4167fb01 | ||
|
|
8e37b46912 | ||
|
|
1e83d0a5a8 | ||
|
|
329b01c7af | ||
|
|
96f609d1f2 | ||
|
|
03b128aeff | ||
|
|
ab9a4cb58a | ||
|
|
6b21599def | ||
|
|
ca93145e76 | ||
|
|
a163ec3afa | ||
|
|
868a6303be | ||
|
|
759e82d403 | ||
|
|
ed0c768aaf | ||
|
|
e933ed782f | ||
|
|
69e5a0dbf1 | ||
|
|
c4d166d3bc | ||
|
|
9ae8e544cb | ||
|
|
94aef4526d | ||
|
|
aeece5a107 | ||
|
|
bdc4ee496f | ||
|
|
5f59ec2d01 | ||
|
|
a25e4f679e | ||
|
|
3876df9052 | ||
|
|
12f2ba251b | ||
|
|
e43a9b6974 | ||
|
|
9e2cb4363c | ||
|
|
b61cb29023 | ||
|
|
90d31b9403 | ||
|
|
4d22d0790d | ||
|
|
bffe4c2a3b | ||
|
|
dfdb0a6fe6 | ||
|
|
dd04e11094 | ||
|
|
5b029c66c5 | ||
|
|
760843b9e5 | ||
|
|
19ba15a783 | ||
|
|
70a3dae965 | ||
|
|
26d5409a87 | ||
|
|
2f3a7a981d | ||
|
|
6a74c46e1c | ||
|
|
bec382df87 | ||
|
|
78f69224be | ||
|
|
34682addb8 | ||
|
|
2c106d66a4 | ||
|
|
9c4fd63a4d | ||
|
|
d04c18ffce | ||
|
|
41fe6663d9 | ||
|
|
9f8612f34e |
@@ -280,8 +280,8 @@ stages:
|
||||
test: rhel/7.9
|
||||
- name: RHEL 8.3
|
||||
test: rhel/8.3
|
||||
- name: FreeBSD 12.2
|
||||
test: freebsd/12.2
|
||||
#- name: FreeBSD 12.2
|
||||
# test: freebsd/12.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -312,8 +312,8 @@ stages:
|
||||
test: rhel/8.2
|
||||
- name: RHEL 7.8
|
||||
test: rhel/7.8
|
||||
- name: FreeBSD 12.0
|
||||
test: freebsd/12.0
|
||||
#- name: FreeBSD 12.0
|
||||
# test: freebsd/12.0
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
|
||||
36
.github/BOTMETA.yml
vendored
36
.github/BOTMETA.yml
vendored
@@ -126,15 +126,23 @@ files:
|
||||
maintainers: giner
|
||||
$filters/from_csv.py:
|
||||
maintainers: Ajpantuso
|
||||
$filters/groupby:
|
||||
$filters/groupby.py:
|
||||
maintainers: felixfontein
|
||||
$filters/hashids:
|
||||
$filters/groupby_as_dict.yml:
|
||||
maintainers: felixfontein
|
||||
$filters/hashids.py:
|
||||
maintainers: Ajpantuso
|
||||
$filters/hashids_decode.yml:
|
||||
maintainers: Ajpantuso
|
||||
$filters/hashids_encode.yml:
|
||||
maintainers: Ajpantuso
|
||||
$filters/jc.py:
|
||||
maintainers: kellyjonbrazil
|
||||
$filters/json_query.py: {}
|
||||
$filters/list.py:
|
||||
maintainers: vbotka
|
||||
$filters/lists_mergeby.yml:
|
||||
maintainers: vbotka
|
||||
$filters/path_join_shim.py:
|
||||
maintainers: felixfontein
|
||||
$filters/random_mac.py: {}
|
||||
@@ -142,6 +150,24 @@ files:
|
||||
maintainers: resmo
|
||||
$filters/unicode_normalize.py:
|
||||
maintainers: Ajpantuso
|
||||
$filters/to_days.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_hours.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_milliseconds.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_minutes.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_months.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_seconds.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_time_unit.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_weeks.yml:
|
||||
maintainers: resmo
|
||||
$filters/to_years.yml:
|
||||
maintainers: resmo
|
||||
$filters/version_sort.py:
|
||||
maintainers: ericzolf
|
||||
$inventories/:
|
||||
@@ -260,6 +286,8 @@ files:
|
||||
$module_utils/module_helper.py:
|
||||
maintainers: russoz
|
||||
labels: module_helper
|
||||
$module_utils/net_tools/pritunl/:
|
||||
maintainers: Lowess
|
||||
$module_utils/oracle/oci_utils.py:
|
||||
maintainers: $team_oracle
|
||||
labels: cloud
|
||||
@@ -310,6 +338,8 @@ files:
|
||||
ignore: hnakamur
|
||||
$modules/cloud/lxd/lxd_profile.py:
|
||||
maintainers: conloos
|
||||
$modules/cloud/lxd/lxd_project.py:
|
||||
maintainers: we10710aa
|
||||
$modules/cloud/memset/:
|
||||
maintainers: glitchcrab
|
||||
$modules/cloud/misc/cloud_init_data_facts.py:
|
||||
@@ -558,6 +588,8 @@ files:
|
||||
maintainers: phumpal
|
||||
labels: airbrake_deployment
|
||||
ignore: bpennypacker
|
||||
$modules/monitoring/alerta_customer.py:
|
||||
maintainers: cwollinger
|
||||
$modules/monitoring/bigpanda.py:
|
||||
maintainers: hkariti
|
||||
$modules/monitoring/circonus_annotation.py:
|
||||
|
||||
104
.gitignore
vendored
104
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/vim,git,macos,linux,pydev,emacs,dotenv,python,windows,webstorm,pycharm+all,jupyternotebooks
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=vim,git,macos,linux,pydev,emacs,dotenv,python,windows,webstorm,pycharm+all,jupyternotebooks
|
||||
|
||||
### dotenv ###
|
||||
.env
|
||||
@@ -71,7 +71,19 @@ flycheck_*.el
|
||||
*_LOCAL_*.txt
|
||||
*_REMOTE_*.txt
|
||||
|
||||
#!! ERROR: jupyternotebook is undefined. Use list command to see defined gitignore types !!#
|
||||
### JupyterNotebooks ###
|
||||
# gitignore template for Jupyter Notebooks
|
||||
# website: http://jupyter.org/
|
||||
|
||||
.ipynb_checkpoints
|
||||
*/.ipynb_checkpoints/*
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# Remove previous ipynb_checkpoints
|
||||
# git rm -r .ipynb_checkpoints/
|
||||
|
||||
### Linux ###
|
||||
|
||||
@@ -87,6 +99,39 @@ flycheck_*.el
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### PyCharm+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
@@ -151,6 +196,9 @@ atlassian-ide-plugin.xml
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
@@ -164,20 +212,13 @@ fabric.properties
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### PyCharm+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
# Ignore everything but code style settings and run configurations
|
||||
# that are supposed to be shared within teams.
|
||||
|
||||
.idea/
|
||||
.idea/*
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
!.idea/codeStyles
|
||||
!.idea/runConfigurations
|
||||
|
||||
### pydev ###
|
||||
.pydevproject
|
||||
@@ -260,16 +301,13 @@ docs/_build/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
.python-version
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
@@ -278,7 +316,22 @@ ipython_config.py
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
@@ -320,6 +373,13 @@ dmypy.json
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
@@ -381,6 +441,8 @@ tags
|
||||
|
||||
# Cursive Clojure plugin
|
||||
|
||||
# SonarLint plugin
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
|
||||
# Editor-based Rest Client
|
||||
@@ -443,4 +505,4 @@ $RECYCLE.BIN/
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
||||
# End of https://www.toptal.com/developers/gitignore/api/vim,git,macos,linux,pydev,emacs,dotenv,python,windows,webstorm,pycharm+all,jupyternotebooks
|
||||
105
CHANGELOG.rst
105
CHANGELOG.rst
@@ -6,6 +6,111 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 3.0.0.
|
||||
|
||||
v4.8.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix release. From now on, only bugfix release will be made of the community.general 4.x.y release stream.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
|
||||
- consul - fixed bug where class ``ConsulService`` was overwriting the field ``checks``, preventing the addition of checks to a service (https://github.com/ansible-collections/community.general/pull/4590).
|
||||
- gconftool2 - properly escape values when passing them to ``gconftool-2`` (https://github.com/ansible-collections/community.general/pull/4647).
|
||||
- onepassword - search all valid configuration locations and use the first found (https://github.com/ansible-collections/community.general/pull/4640).
|
||||
- opentelemetry callback plugin - fix task message attribute that is reported failed regardless of the task result (https://github.com/ansible-collections/community.general/pull/4624).
|
||||
- opentelemetry callback plugin - fix warning for the include_tasks (https://github.com/ansible-collections/community.general/pull/4623).
|
||||
- terraform - fix list initialization to support both Python 2 and Python 3 (https://github.com/ansible-collections/community.general/issues/4531).
|
||||
|
||||
v4.8.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular feature and bugfix release. Please note that this is the last minor 4.x.0 release. Further releases with major version 4 will be bugfix releases 4.8.y.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- alternatives - add ``state`` parameter, which provides control over whether the alternative should be set as the active selection for its alternatives group (https://github.com/ansible-collections/community.general/issues/4543, https://github.com/ansible-collections/community.general/pull/4557).
|
||||
- atomic_container - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- clc_alert_policy - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- clc_group - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- clc_loadbalancer - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- clc_server - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- cmd_runner module util - reusable command runner with consistent argument formatting and sensible defaults (https://github.com/ansible-collections/community.general/pull/4476).
|
||||
- datadog_monitor - support new datadog event monitor of type `event-v2 alert` (https://github.com/ansible-collections/community.general/pull/4457)
|
||||
- filesystem - add support for resizing btrfs (https://github.com/ansible-collections/community.general/issues/4465).
|
||||
- lxd_container - adds ``project`` option to allow selecting project for LXD instance (https://github.com/ansible-collections/community.general/pull/4479).
|
||||
- lxd_profile - adds ``project`` option to allow selecting project for LXD profile (https://github.com/ansible-collections/community.general/pull/4479).
|
||||
- nmap inventory plugin - add ``sudo`` option in plugin in order to execute ``sudo nmap`` so that ``nmap`` runs with elevated privileges (https://github.com/ansible-collections/community.general/pull/4506).
|
||||
- nomad_job - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- nomad_job_info - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- packet_device - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- packet_sshkey - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- packet_volume - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- profitbricks - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- proxmox - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- proxmox inventory plugin - add token authentication as an alternative to username/password (https://github.com/ansible-collections/community.general/pull/4540).
|
||||
- proxmox inventory plugin - parse LXC configs returned by the proxmox API (https://github.com/ansible-collections/community.general/pull/4472).
|
||||
- proxmox_snap - add restore snapshot option (https://github.com/ansible-collections/community.general/pull/4377).
|
||||
- proxmox_snap - fixed timeout value to correctly reflect time in seconds. The timeout was off by one second (https://github.com/ansible-collections/community.general/pull/4377).
|
||||
- redfish_command - add ``IndicatorLedOn``, ``IndicatorLedOff``, and ``IndicatorLedBlink`` commands to the Systems category for controling system LEDs (https://github.com/ansible-collections/community.general/issues/4084).
|
||||
- seport - minor refactoring (https://github.com/ansible-collections/community.general/pull/4471).
|
||||
- smartos_image_info - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- terraform - adds ``terraform_upgrade`` parameter which allows ``terraform init`` to satisfy new provider constraints in an existing Terraform project (https://github.com/ansible-collections/community.general/issues/4333).
|
||||
- udm_group - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- udm_share - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- vmadm - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- webfaction_app - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- webfaction_db - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- xfconf - added missing value types ``char``, ``uchar``, ``int64`` and ``uint64`` (https://github.com/ansible-collections/community.general/pull/4534).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- nmcli - deprecate default hairpin mode for a bridge. This so we can change it to ``false`` in community.general 7.0.0, as this is also the default in ``nmcli`` (https://github.com/ansible-collections/community.general/pull/4334).
|
||||
- proxmox inventory plugin - the current default ``true`` of the ``want_proxmox_nodes_ansible_host`` option has been deprecated. The default will change to ``false`` in community.general 6.0.0. To keep the current behavior, explicitly set ``want_proxmox_nodes_ansible_host`` to ``true`` in your inventory configuration. We suggest to already switch to the new behavior by explicitly setting it to ``false``, and by using ``compose:`` to set ``ansible_host`` to the correct value. See the examples in the plugin documentation for details (https://github.com/ansible-collections/community.general/pull/4466).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- dnsmadeeasy - fix failure on deleting DNS entries when API response does not contain monitor value (https://github.com/ansible-collections/community.general/issues/3620).
|
||||
- git_branch - remove deprecated and unnecessary branch ``unprotect`` method (https://github.com/ansible-collections/community.general/pull/4496).
|
||||
- gitlab_group - improve searching for projects inside group on deletion (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
- gitlab_group_members - handle more than 20 groups when finding a group (https://github.com/ansible-collections/community.general/pull/4491, https://github.com/ansible-collections/community.general/issues/4460, https://github.com/ansible-collections/community.general/issues/3729).
|
||||
- gitlab_hook - handle more than 20 hooks when finding a hook (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
- gitlab_project - handle more than 20 namespaces when finding a namespace (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
- gitlab_project_members - handle more than 20 projects and users when finding a project resp. user (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
- gitlab_user - handle more than 20 users and SSH keys when finding a user resp. SSH key (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
- keycloak - fix parameters types for ``defaultDefaultClientScopes`` and ``defaultOptionalClientScopes`` from list of dictionaries to list of strings (https://github.com/ansible-collections/community.general/pull/4526).
|
||||
- opennebula inventory plugin - complete the implementation of ``constructable`` for opennebula inventory plugin. Now ``keyed_groups``, ``compose``, ``groups`` actually work (https://github.com/ansible-collections/community.general/issues/4497).
|
||||
- pacman - fixed bug where ``absent`` state did not work for locally installed packages (https://github.com/ansible-collections/community.general/pull/4464).
|
||||
- pritunl - fixed bug where pritunl plugin api add unneeded data in ``auth_string`` parameter (https://github.com/ansible-collections/community.general/issues/4527).
|
||||
- proxmox inventory plugin - fix error when parsing container with LXC configs (https://github.com/ansible-collections/community.general/issues/4472, https://github.com/ansible-collections/community.general/pull/4472).
|
||||
- proxmox_kvm - fix a bug when getting a state of VM without name will fail (https://github.com/ansible-collections/community.general/pull/4508).
|
||||
- xbps - fix error message that is reported when installing packages fails (https://github.com/ansible-collections/community.general/pull/4438).
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
Cloud
|
||||
~~~~~
|
||||
|
||||
lxd
|
||||
^^^
|
||||
|
||||
- lxd_project - Manage LXD projects
|
||||
|
||||
Monitoring
|
||||
~~~~~~~~~~
|
||||
|
||||
- alerta_customer - Manage customers in Alerta
|
||||
|
||||
v4.7.0
|
||||
======
|
||||
|
||||
|
||||
48
PSF-license.txt
Normal file
48
PSF-license.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
|
||||
All Rights Reserved" are retained in Python alone or in any derivative version
|
||||
prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
@@ -1641,3 +1641,170 @@ releases:
|
||||
- 4422-warn-user-if-incorrect-SDK-version-is-installed.yaml
|
||||
- 4429-keycloak-client-add-always-display-in-console.yml
|
||||
release_date: '2022-04-05'
|
||||
4.8.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- dnsmadeeasy - fix failure on deleting DNS entries when API response does not
|
||||
contain monitor value (https://github.com/ansible-collections/community.general/issues/3620).
|
||||
- git_branch - remove deprecated and unnecessary branch ``unprotect`` method
|
||||
(https://github.com/ansible-collections/community.general/pull/4496).
|
||||
- 'gitlab_group - improve searching for projects inside group on deletion (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
|
||||
'
|
||||
- 'gitlab_group_members - handle more than 20 groups when finding a group (https://github.com/ansible-collections/community.general/pull/4491,
|
||||
https://github.com/ansible-collections/community.general/issues/4460, https://github.com/ansible-collections/community.general/issues/3729).
|
||||
|
||||
'
|
||||
- 'gitlab_hook - handle more than 20 hooks when finding a hook (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
|
||||
'
|
||||
- 'gitlab_project - handle more than 20 namespaces when finding a namespace
|
||||
(https://github.com/ansible-collections/community.general/pull/4491).
|
||||
|
||||
'
|
||||
- 'gitlab_project_members - handle more than 20 projects and users when finding
|
||||
a project resp. user (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
|
||||
'
|
||||
- 'gitlab_user - handle more than 20 users and SSH keys when finding a user
|
||||
resp. SSH key (https://github.com/ansible-collections/community.general/pull/4491).
|
||||
|
||||
'
|
||||
- keycloak - fix parameters types for ``defaultDefaultClientScopes`` and ``defaultOptionalClientScopes``
|
||||
from list of dictionaries to list of strings (https://github.com/ansible-collections/community.general/pull/4526).
|
||||
- opennebula inventory plugin - complete the implementation of ``constructable``
|
||||
for opennebula inventory plugin. Now ``keyed_groups``, ``compose``, ``groups``
|
||||
actually work (https://github.com/ansible-collections/community.general/issues/4497).
|
||||
- pacman - fixed bug where ``absent`` state did not work for locally installed
|
||||
packages (https://github.com/ansible-collections/community.general/pull/4464).
|
||||
- pritunl - fixed bug where pritunl plugin api add unneeded data in ``auth_string``
|
||||
parameter (https://github.com/ansible-collections/community.general/issues/4527).
|
||||
- proxmox inventory plugin - fix error when parsing container with LXC configs
|
||||
(https://github.com/ansible-collections/community.general/issues/4472, https://github.com/ansible-collections/community.general/pull/4472).
|
||||
- proxmox_kvm - fix a bug when getting a state of VM without name will fail
|
||||
(https://github.com/ansible-collections/community.general/pull/4508).
|
||||
- xbps - fix error message that is reported when installing packages fails (https://github.com/ansible-collections/community.general/pull/4438).
|
||||
deprecated_features:
|
||||
- nmcli - deprecate default hairpin mode for a bridge. This so we can change
|
||||
it to ``false`` in community.general 7.0.0, as this is also the default in
|
||||
``nmcli`` (https://github.com/ansible-collections/community.general/pull/4334).
|
||||
- proxmox inventory plugin - the current default ``true`` of the ``want_proxmox_nodes_ansible_host``
|
||||
option has been deprecated. The default will change to ``false`` in community.general
|
||||
6.0.0. To keep the current behavior, explicitly set ``want_proxmox_nodes_ansible_host``
|
||||
to ``true`` in your inventory configuration. We suggest to already switch
|
||||
to the new behavior by explicitly setting it to ``false``, and by using ``compose:``
|
||||
to set ``ansible_host`` to the correct value. See the examples in the plugin
|
||||
documentation for details (https://github.com/ansible-collections/community.general/pull/4466).
|
||||
minor_changes:
|
||||
- alternatives - add ``state`` parameter, which provides control over whether
|
||||
the alternative should be set as the active selection for its alternatives
|
||||
group (https://github.com/ansible-collections/community.general/issues/4543,
|
||||
https://github.com/ansible-collections/community.general/pull/4557).
|
||||
- atomic_container - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- clc_alert_policy - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- clc_group - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- clc_loadbalancer - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- clc_server - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- cmd_runner module util - reusable command runner with consistent argument
|
||||
formatting and sensible defaults (https://github.com/ansible-collections/community.general/pull/4476).
|
||||
- datadog_monitor - support new datadog event monitor of type `event-v2 alert`
|
||||
(https://github.com/ansible-collections/community.general/pull/4457)
|
||||
- filesystem - add support for resizing btrfs (https://github.com/ansible-collections/community.general/issues/4465).
|
||||
- lxd_container - adds ``project`` option to allow selecting project for LXD
|
||||
instance (https://github.com/ansible-collections/community.general/pull/4479).
|
||||
- lxd_profile - adds ``project`` option to allow selecting project for LXD profile
|
||||
(https://github.com/ansible-collections/community.general/pull/4479).
|
||||
- nmap inventory plugin - add ``sudo`` option in plugin in order to execute
|
||||
``sudo nmap`` so that ``nmap`` runs with elevated privileges (https://github.com/ansible-collections/community.general/pull/4506).
|
||||
- nomad_job - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- nomad_job_info - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- packet_device - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- packet_sshkey - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- packet_volume - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- profitbricks - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- proxmox - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- proxmox inventory plugin - add token authentication as an alternative to username/password
|
||||
(https://github.com/ansible-collections/community.general/pull/4540).
|
||||
- proxmox inventory plugin - parse LXC configs returned by the proxmox API (https://github.com/ansible-collections/community.general/pull/4472).
|
||||
- proxmox_snap - add restore snapshot option (https://github.com/ansible-collections/community.general/pull/4377).
|
||||
- proxmox_snap - fixed timeout value to correctly reflect time in seconds. The
|
||||
timeout was off by one second (https://github.com/ansible-collections/community.general/pull/4377).
|
||||
- redfish_command - add ``IndicatorLedOn``, ``IndicatorLedOff``, and ``IndicatorLedBlink``
|
||||
commands to the Systems category for controling system LEDs (https://github.com/ansible-collections/community.general/issues/4084).
|
||||
- seport - minor refactoring (https://github.com/ansible-collections/community.general/pull/4471).
|
||||
- smartos_image_info - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- terraform - adds ``terraform_upgrade`` parameter which allows ``terraform
|
||||
init`` to satisfy new provider constraints in an existing Terraform project
|
||||
(https://github.com/ansible-collections/community.general/issues/4333).
|
||||
- udm_group - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- udm_share - minor refactoring (https://github.com/ansible-collections/community.general/pull/4556).
|
||||
- vmadm - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- webfaction_app - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- webfaction_db - minor refactoring (https://github.com/ansible-collections/community.general/pull/4567).
|
||||
- xfconf - added missing value types ``char``, ``uchar``, ``int64`` and ``uint64``
|
||||
(https://github.com/ansible-collections/community.general/pull/4534).
|
||||
release_summary: Regular feature and bugfix release. Please note that this is
|
||||
the last minor 4.x.0 release. Further releases with major version 4 will be
|
||||
bugfix releases 4.8.y.
|
||||
fragments:
|
||||
- 4.8.0.yml
|
||||
- 4084-add-redfish-system-indicator-led.yml
|
||||
- 4320-nmcli-hairpin.yml
|
||||
- 4377-allow-proxmox-snapshot-restoring.yml
|
||||
- 4438-fix-error-message.yaml
|
||||
- 4455-terraform-provider-upgrade.yml
|
||||
- 4457-support-datadog-monitors-type-event-v2.yaml
|
||||
- 4459-only-get-monitor-if-it-is-not-null-api-response.yaml
|
||||
- 4464-pacman-fix-local-remove.yaml
|
||||
- 4465-btrfs-resize.yml
|
||||
- 4466-proxmox-ansible_host-deprecation.yml
|
||||
- 4471-seport-refactor.yaml
|
||||
- 4476-cmd_runner.yml
|
||||
- 4479-add-project-support-for-lxd_container-and-lxd_profile.yml
|
||||
- 4491-specify_all_in_list_calls.yaml
|
||||
- 4492-proxmox_kvm_fix_vm_without_name.yaml
|
||||
- 4496-remove-deprecated-method-in-gitlab-branch-module.yml
|
||||
- 4506-sudo-in-nmap-inv-plugin.yaml
|
||||
- 4524-update-opennebula-inventory-plugin-to-match-documentation.yaml
|
||||
- 4526-keycloak-realm-types.yaml
|
||||
- 4530-fix-unauthorized-pritunl-request.yaml
|
||||
- 4534-xfconf-added-value-types.yaml
|
||||
- 4540-proxmox-inventory-token-auth.yml
|
||||
- 4555-proxmox-lxc-key.yml
|
||||
- 4556-remove-default-none-1.yml
|
||||
- 4557-alternatives-add-state-parameter.yml
|
||||
- 4567-remove-default-none-2.yml
|
||||
modules:
|
||||
- description: Manage customers in Alerta
|
||||
name: alerta_customer
|
||||
namespace: monitoring
|
||||
- description: Manage LXD projects
|
||||
name: lxd_project
|
||||
namespace: cloud.lxd
|
||||
release_date: '2022-04-26'
|
||||
4.8.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
|
||||
- consul - fixed bug where class ``ConsulService`` was overwriting the field
|
||||
``checks``, preventing the addition of checks to a service (https://github.com/ansible-collections/community.general/pull/4590).
|
||||
- gconftool2 - properly escape values when passing them to ``gconftool-2`` (https://github.com/ansible-collections/community.general/pull/4647).
|
||||
- onepassword - search all valid configuration locations and use the first found
|
||||
(https://github.com/ansible-collections/community.general/pull/4640).
|
||||
- opentelemetry callback plugin - fix task message attribute that is reported
|
||||
failed regardless of the task result (https://github.com/ansible-collections/community.general/pull/4624).
|
||||
- opentelemetry callback plugin - fix warning for the include_tasks (https://github.com/ansible-collections/community.general/pull/4623).
|
||||
- terraform - fix list initialization to support both Python 2 and Python 3
|
||||
(https://github.com/ansible-collections/community.general/issues/4531).
|
||||
release_summary: Regular bugfix release. From now on, only bugfix release will
|
||||
be made of the community.general 4.x.y release stream.
|
||||
fragments:
|
||||
- 4.8.1.yml
|
||||
- 4065-onepassword-config.yml
|
||||
- 4590-consul-fix-service-checks.yaml
|
||||
- 4621-terraform-py2-compat.yml
|
||||
- 4623-opentelemetry_bug_fix_include_tasks.yml
|
||||
- 4624-opentelemetry_bug_fix_hardcoded_value.yml
|
||||
- 4647-gconftool2-command-arg.yaml
|
||||
- psf-license.yml
|
||||
release_date: '2022-05-16'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace: community
|
||||
name: general
|
||||
version: 4.7.0
|
||||
version: 4.8.1
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
@@ -45,7 +45,7 @@ DOCUMENTATION = r'''
|
||||
version_added: 1.0.0
|
||||
default: ansible
|
||||
pre_command:
|
||||
description: Executes command before run and result put to ansible_pre_command_output field.
|
||||
description: Executes command before run and its result is added to the C(ansible_pre_command_output) logstash field.
|
||||
version_added: 2.0.0
|
||||
ini:
|
||||
- section: callback_logstash
|
||||
|
||||
@@ -197,7 +197,7 @@ class OpenTelemetrySource(object):
|
||||
|
||||
task = tasks_data[task_uuid]
|
||||
|
||||
if self.ansible_version is None and result._task_fields['args'].get('_ansible_version'):
|
||||
if self.ansible_version is None and hasattr(result, '_task_fields') and result._task_fields['args'].get('_ansible_version'):
|
||||
self.ansible_version = result._task_fields['args'].get('_ansible_version')
|
||||
|
||||
task.add_host(HostData(host_uuid, host_name, status, result))
|
||||
@@ -258,8 +258,9 @@ class OpenTelemetrySource(object):
|
||||
else:
|
||||
res = host_data.result._result
|
||||
rc = res.get('rc', 0)
|
||||
message = self.get_error_message(res)
|
||||
enriched_error_message = self.enrich_error_message(res)
|
||||
if host_data.status == 'failed':
|
||||
message = self.get_error_message(res)
|
||||
enriched_error_message = self.enrich_error_message(res)
|
||||
|
||||
if host_data.status == 'failed':
|
||||
status = Status(status_code=StatusCode.ERROR, description=message)
|
||||
|
||||
@@ -5,6 +5,35 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: counter
|
||||
short_description: Counts hashable elements in a sequence
|
||||
version_added: 4.3.0
|
||||
author: Rémy Keil (@keilr)
|
||||
description:
|
||||
- Counts hashable elements in a sequence.
|
||||
options:
|
||||
_input:
|
||||
description: A sequence.
|
||||
type: list
|
||||
elements: any
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Count occurences
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ [1, 'a', 2, 2, 'a', 'b', 'a'] | community.general.counter }}
|
||||
# Produces: {1: 1, 'a': 3, 2: 2, 'b': 1}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: A dictionary with the elements of the sequence as keys, and their number of occurance in the sequence as values.
|
||||
type: dictionary
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.common._collections_compat import Sequence
|
||||
from collections import Counter
|
||||
|
||||
@@ -6,6 +6,60 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: dict
|
||||
short_description: Convert a list of tuples into a dictionary
|
||||
version_added: 3.0.0
|
||||
author: Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Convert a list of tuples into a dictionary. This is a filter version of the C(dict) function.
|
||||
options:
|
||||
_input:
|
||||
description: A list of tuples (with exactly two elements).
|
||||
type: list
|
||||
elements: tuple
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Convert list of tuples into dictionary
|
||||
ansible.builtin.set_fact:
|
||||
dictionary: "{{ [[1, 2], ['a', 'b']] | community.general.dict }}"
|
||||
# Result is {1: 2, 'a': 'b'}
|
||||
|
||||
- name: Create a list of dictionaries with map and the community.general.dict filter
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ values | map('zip', ['k1', 'k2', 'k3'])
|
||||
| map('map', 'reverse')
|
||||
| map('community.general.dict') }}
|
||||
vars:
|
||||
values:
|
||||
- - foo
|
||||
- 23
|
||||
- a
|
||||
- - bar
|
||||
- 42
|
||||
- b
|
||||
# Produces the following list of dictionaries:
|
||||
# {
|
||||
# "k1": "foo",
|
||||
# "k2": 23,
|
||||
# "k3": "a"
|
||||
# },
|
||||
# {
|
||||
# "k1": "bar",
|
||||
# "k2": 42,
|
||||
# "k3": "b"
|
||||
# }
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The dictionary having the provided key-value pairs.
|
||||
type: boolean
|
||||
'''
|
||||
|
||||
|
||||
def dict_filter(sequence):
|
||||
'''Convert a list of tuples to a dictionary.
|
||||
|
||||
@@ -5,6 +5,38 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: dict_kv
|
||||
short_description: Convert a value to a dictionary with a single key-value pair
|
||||
version_added: 1.3.0
|
||||
author: Stanislav German-Evtushenko (@giner)
|
||||
description:
|
||||
- Convert a value to a dictionary with a single key-value pair.
|
||||
positional: key
|
||||
options:
|
||||
_input:
|
||||
description: The value for the single key-value pair.
|
||||
type: any
|
||||
required: true
|
||||
key:
|
||||
description: The key for the single key-value pair.
|
||||
type: any
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a one-element dictionary from a value
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ 'myvalue' | dict_kv('mykey') }}"
|
||||
# Produces the dictionary {'mykey': 'myvalue'}
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: A dictionary with a single key-value pair.
|
||||
type: dictionary
|
||||
'''
|
||||
|
||||
|
||||
def dict_kv(value, key):
|
||||
'''Return a dictionary with a single key-value pair
|
||||
|
||||
@@ -7,6 +7,78 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: from_csv
|
||||
short_description: Converts CSV text input into list of dicts
|
||||
version_added: 2.3.0
|
||||
author: Andrew Pantuso (@Ajpantuso)
|
||||
description:
|
||||
- Converts CSV text input into list of dictionaries.
|
||||
options:
|
||||
_input:
|
||||
description: A string containing a CSV document.
|
||||
type: string
|
||||
required: true
|
||||
dialect:
|
||||
description:
|
||||
- The CSV dialect to use when parsing the CSV file.
|
||||
- Possible values include C(excel), C(excel-tab) or C(unix).
|
||||
type: str
|
||||
default: excel
|
||||
fieldnames:
|
||||
description:
|
||||
- A list of field names for every column.
|
||||
- This is needed if the CSV does not have a header.
|
||||
type: list
|
||||
elements: str
|
||||
delimiter:
|
||||
description:
|
||||
- A one-character string used to separate fields.
|
||||
- When using this parameter, you change the default value used by I(dialect).
|
||||
- The default value depends on the dialect used.
|
||||
type: str
|
||||
skipinitialspace:
|
||||
description:
|
||||
- Whether to ignore any whitespaces immediately following the delimiter.
|
||||
- When using this parameter, you change the default value used by I(dialect).
|
||||
- The default value depends on the dialect used.
|
||||
type: bool
|
||||
strict:
|
||||
description:
|
||||
- Whether to raise an exception on bad CSV input.
|
||||
- When using this parameter, you change the default value used by I(dialect).
|
||||
- The default value depends on the dialect used.
|
||||
type: bool
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Parse a CSV file's contents
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ csv_data | community.genera.from_csv(dialect='unix') }}
|
||||
vars:
|
||||
csv_data: |
|
||||
Column 1,Value
|
||||
foo,23
|
||||
bar,42
|
||||
# Produces the following list of dictionaries:
|
||||
# {
|
||||
# "Column 1": "foo",
|
||||
# "Value": "23",
|
||||
# },
|
||||
# {
|
||||
# "Column 1": "bar",
|
||||
# "Value": "42",
|
||||
# }
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: A list with one dictionary per row.
|
||||
type: list
|
||||
elements: dictionary
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
42
plugins/filter/groupby_as_dict.yml
Normal file
42
plugins/filter/groupby_as_dict.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
DOCUMENTATION:
|
||||
name: groupby_as_dict
|
||||
short_description: Transform a sequence of dictionaries to a dictionary where the dictionaries are indexed by an attribute
|
||||
version_added: 3.1.0
|
||||
author: Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Transform a sequence of dictionaries to a dictionary where the dictionaries are indexed by an attribute.
|
||||
positional: attribute
|
||||
options:
|
||||
_input:
|
||||
description: A list of dictionaries
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
attribute:
|
||||
description: The attribute to use as the key.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Arrange a list of dictionaries as a dictionary of dictionaries
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ sequence | community.general.groupby_as_dict('key') }}"
|
||||
vars:
|
||||
sequence:
|
||||
- key: value
|
||||
foo: bar
|
||||
- key: other_value
|
||||
baz: bar
|
||||
# Produces the following nested structure:
|
||||
#
|
||||
# value:
|
||||
# key: value
|
||||
# foo: bar
|
||||
# other_value:
|
||||
# key: other_value
|
||||
# baz: bar
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: A dictionary containing the dictionaries from the list as values.
|
||||
type: dictionary
|
||||
38
plugins/filter/hashids_decode.yml
Normal file
38
plugins/filter/hashids_decode.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
DOCUMENTATION:
|
||||
name: hashids_decode
|
||||
short_description: Decodes a sequence of numbers from a YouTube-like hash
|
||||
version_added: 3.0.0
|
||||
author: Andrew Pantuso (@Ajpantuso)
|
||||
description:
|
||||
- Decodes a sequence of numbers from a YouTube-like hash.
|
||||
options:
|
||||
_input:
|
||||
description: A YouTube-like hash.
|
||||
type: string
|
||||
required: true
|
||||
salt:
|
||||
description:
|
||||
- String to use as salt when hashing.
|
||||
type: str
|
||||
default: excel
|
||||
alphabet:
|
||||
description:
|
||||
- String of 16 or more unique characters to produce a hash.
|
||||
type: list
|
||||
elements: str
|
||||
min_length:
|
||||
description:
|
||||
- Minimum length of hash produced.
|
||||
type: integer
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert hash to list of integers
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ 'o2fXhV' | community.general.hashids_decode }}"
|
||||
# Produces: [1, 2, 3]
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: A list of integers.
|
||||
type: list
|
||||
elements: integer
|
||||
38
plugins/filter/hashids_encode.yml
Normal file
38
plugins/filter/hashids_encode.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
DOCUMENTATION:
|
||||
name: hashids_encode
|
||||
short_description: Encodes YouTube-like hashes from a sequence of integers
|
||||
version_added: 3.0.0
|
||||
author: Andrew Pantuso (@Ajpantuso)
|
||||
description:
|
||||
- Encodes YouTube-like hashes from a sequence of integers.
|
||||
options:
|
||||
_input:
|
||||
description: A list of integers.
|
||||
type: list
|
||||
elements: integer
|
||||
required: true
|
||||
salt:
|
||||
description:
|
||||
- String to use as salt when hashing.
|
||||
type: str
|
||||
default: excel
|
||||
alphabet:
|
||||
description:
|
||||
- String of 16 or more unique characters to produce a hash.
|
||||
type: list
|
||||
elements: str
|
||||
min_length:
|
||||
description:
|
||||
- Minimum length of hash produced.
|
||||
type: integer
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert list of integers to hash
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ [1, 2, 3] | community.general.hashids_encode }}"
|
||||
# Produces: 'o2fXhV'
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: A YouTube-like hash.
|
||||
type: string
|
||||
@@ -21,6 +21,67 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: jc
|
||||
short_description: Convert output of many shell commands and file-types to JSON
|
||||
version_added: 1.1.0
|
||||
author: Kelly Brazil (@kellyjonbrazil)
|
||||
description:
|
||||
- Convert output of many shell commands and file-types to JSON.
|
||||
- Uses the L(jc library,https://github.com/kellyjonbrazil/jc).
|
||||
positional: parser
|
||||
options:
|
||||
_input:
|
||||
description: The data to convert.
|
||||
type: string
|
||||
required: true
|
||||
parser:
|
||||
description:
|
||||
- The correct parser for the input data.
|
||||
- For exmaple C(ifconfig).
|
||||
- See U(https://github.com/kellyjonbrazil/jc#parsers) for the latest list of parsers.
|
||||
type: string
|
||||
required: true
|
||||
quiet:
|
||||
description: Set to C(false) to not suppress warnings.
|
||||
type: boolean
|
||||
default: true
|
||||
raw:
|
||||
description: Set to C(true) to return pre-processed JSON.
|
||||
type: boolean
|
||||
default: false
|
||||
requirements:
|
||||
- jc (https://github.com/kellyjonbrazil/jc)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Run command
|
||||
ansible.builtin.command: uname -a
|
||||
register: result
|
||||
|
||||
- name: Convert command's result to JSON
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ result.stdout | community.general.jc('uname') }}"
|
||||
# Possible output:
|
||||
#
|
||||
# "msg": {
|
||||
# "hardware_platform": "x86_64",
|
||||
# "kernel_name": "Linux",
|
||||
# "kernel_release": "4.15.0-112-generic",
|
||||
# "kernel_version": "#113-Ubuntu SMP Thu Jul 9 23:41:39 UTC 2020",
|
||||
# "machine": "x86_64",
|
||||
# "node_name": "kbrazil-ubuntu",
|
||||
# "operating_system": "GNU/Linux",
|
||||
# "processor": "x86_64"
|
||||
# }
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The processed output.
|
||||
type: any
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleFilterError
|
||||
import importlib
|
||||
|
||||
|
||||
@@ -19,6 +19,107 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: json_query
|
||||
short_description: Select a single element or a data subset from a complex data structure
|
||||
description:
|
||||
- This filter lets you query a complex JSON structure and iterate over it using a loop structure.
|
||||
positional: expr
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The JSON data to query.
|
||||
type: any
|
||||
required: true
|
||||
expr:
|
||||
description:
|
||||
- The query expression.
|
||||
- See U(http://jmespath.org/examples.html) for examples.
|
||||
type: string
|
||||
required: true
|
||||
requirements:
|
||||
- jmespath
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Define data to work on in the examples below
|
||||
ansible.builtin.set_fact:
|
||||
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
|
||||
|
||||
- name: Display all cluster names
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.cluster[*].name') }}"
|
||||
|
||||
- name: Display all server names
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.server[*].name') }}"
|
||||
|
||||
- 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"
|
||||
|
||||
- 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(', ') }}"
|
||||
|
||||
- name: Display all ports from cluster1
|
||||
ansible.builtin.debug:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query('domain.server[?cluster==''cluster1''].port') }}"
|
||||
|
||||
- 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}"
|
||||
|
||||
- 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"
|
||||
|
||||
- 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"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The result of the query.
|
||||
type: any
|
||||
'''
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleFilterError
|
||||
|
||||
try:
|
||||
|
||||
88
plugins/filter/lists_mergeby.yml
Normal file
88
plugins/filter/lists_mergeby.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
DOCUMENTATION:
|
||||
name: lists_mergeby
|
||||
short_description: Merge two or more lists of dictionaries by a given attribute
|
||||
version_added: 2.0.0
|
||||
author: Vladimir Botka (@vbotka)
|
||||
description:
|
||||
- Merge two or more lists by attribute I(index). Optional parameters 'recursive' and 'list_merge'
|
||||
control the merging of the lists in values. The function merge_hash from ansible.utils.vars
|
||||
is used. To learn details on how to use the parameters 'recursive' and 'list_merge' see
|
||||
Ansible User's Guide chapter "Using filters to manipulate data" section "Combining
|
||||
hashes/dictionaries".
|
||||
positional: another_list, index
|
||||
options:
|
||||
_input:
|
||||
description: A list of dictionaries.
|
||||
type: list
|
||||
elements: dictionary
|
||||
required: true
|
||||
another_list:
|
||||
description: Another list of dictionaries. This parameter can be specified multiple times.
|
||||
type: list
|
||||
elements: dictionary
|
||||
index:
|
||||
description:
|
||||
- The dictionary key that must be present in every dictionary in every list that is used to
|
||||
merge the lists.
|
||||
type: string
|
||||
required: true
|
||||
recursive:
|
||||
description:
|
||||
- Should the combine recursively merge nested dictionaries (hashes).
|
||||
- "B(Note:) It does not depend on the value of the C(hash_behaviour) setting in C(ansible.cfg)."
|
||||
type: boolean
|
||||
default: false
|
||||
list_merge:
|
||||
description:
|
||||
- Modifies the behaviour when the dictionaries (hashes) to merge contain arrays/lists.
|
||||
type: string
|
||||
default: replace
|
||||
choices:
|
||||
- replace
|
||||
- keep
|
||||
- append
|
||||
- prepend
|
||||
- append_rp
|
||||
- prepend_rp
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Merge two lists
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ list1 | community.general.lists_mergeby(
|
||||
list2,
|
||||
'index',
|
||||
recursive=True,
|
||||
list_merge='append'
|
||||
) }}"
|
||||
vars:
|
||||
list1:
|
||||
- index: a
|
||||
value: 123
|
||||
- index: b
|
||||
value: 42
|
||||
list2:
|
||||
- index: a
|
||||
foo: bar
|
||||
- index: c
|
||||
foo: baz
|
||||
# Produces the following list of dictionaries:
|
||||
# {
|
||||
# "index": "a",
|
||||
# "foo": "bar",
|
||||
# "value": 123
|
||||
# },
|
||||
# {
|
||||
# "index": "b",
|
||||
# "value": 42
|
||||
# },
|
||||
# {
|
||||
# "index": "c",
|
||||
# "foo": "baz"
|
||||
# }
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: The merged list.
|
||||
type: list
|
||||
elements: dictionary
|
||||
@@ -20,6 +20,41 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: random_mac
|
||||
short_description: Generate a random MAC address
|
||||
description:
|
||||
- Generates random networking interfaces MAC addresses for a given prefix.
|
||||
options:
|
||||
_input:
|
||||
description: A string prefix to use as a basis for the random MAC generated.
|
||||
type: string
|
||||
required: true
|
||||
seed:
|
||||
description:
|
||||
- A randomization seed to initialize the process, used to get repeatable results.
|
||||
- If no seed is provided, a system random source such as C(/dev/urandom) is used.
|
||||
required: false
|
||||
type: string
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Random MAC given a prefix
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '52:54:00' | community.general.random_mac }}"
|
||||
# => '52:54:00:ef:1c:03'
|
||||
|
||||
- name: With a seed
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '52:54:00' | community.general.random_mac(seed=inventory_hostname) }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The generated MAC.
|
||||
type: string
|
||||
'''
|
||||
|
||||
import re
|
||||
from random import Random, SystemRandom
|
||||
|
||||
|
||||
40
plugins/filter/to_days.yml
Normal file
40
plugins/filter/to_days.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_days
|
||||
short_description: Converte a duration string to days
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to days.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into days
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '1y 7m 5d 30h' | community.general.to_days }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of days.
|
||||
type: float
|
||||
40
plugins/filter/to_hours.yml
Normal file
40
plugins/filter/to_hours.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_hours
|
||||
short_description: Converte a duration string to hours
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to hours.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into hours
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '7d 30h 20m 10s 123ms' | community.general.to_hours }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of hours.
|
||||
type: float
|
||||
40
plugins/filter/to_milliseconds.yml
Normal file
40
plugins/filter/to_milliseconds.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_milliseconds
|
||||
short_description: Converte a duration string to milliseconds
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to milliseconds.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into milliseconds
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '30h 20m 10s 123ms' | community.general.to_milliseconds }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of milliseconds.
|
||||
type: float
|
||||
40
plugins/filter/to_minutes.yml
Normal file
40
plugins/filter/to_minutes.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_minutes
|
||||
short_description: Converte a duration string to minutes
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to minutes.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into minutes
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '30h 20m 10s 123ms' | community.general.to_minutes }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of minutes.
|
||||
type: float
|
||||
40
plugins/filter/to_months.yml
Normal file
40
plugins/filter/to_months.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_months
|
||||
short_description: Converte a duration string to months
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to months.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into months
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '1y 7m 5d 30h' | community.general.to_months }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of months.
|
||||
type: float
|
||||
40
plugins/filter/to_seconds.yml
Normal file
40
plugins/filter/to_seconds.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_seconds
|
||||
short_description: Converte a duration string to seconds
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to seconds.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into seconds
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '30h 20m 10s 123ms' | community.general.to_seconds }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of seconds.
|
||||
type: float
|
||||
84
plugins/filter/to_time_unit.yml
Normal file
84
plugins/filter/to_time_unit.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
DOCUMENTATION:
|
||||
name: to_time_unit
|
||||
short_description: Converte a duration string to the given time unit
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to the given time unit.
|
||||
positional: unit
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
unit:
|
||||
description:
|
||||
- Time unit to convert the duration to.
|
||||
default: ms
|
||||
choices:
|
||||
- millisecond
|
||||
- milliseconds
|
||||
- ms
|
||||
- msec
|
||||
- msecs
|
||||
- msecond
|
||||
- mseconds
|
||||
- s
|
||||
- sec
|
||||
- secs
|
||||
- second
|
||||
- seconds
|
||||
- h
|
||||
- hour
|
||||
- hours
|
||||
- hs
|
||||
- m
|
||||
- min
|
||||
- mins
|
||||
- minute
|
||||
- minutes
|
||||
- d
|
||||
- ds
|
||||
- day
|
||||
- days
|
||||
- w
|
||||
- ws
|
||||
- week
|
||||
- weeks
|
||||
- mo
|
||||
- mos
|
||||
- month
|
||||
- months
|
||||
- y
|
||||
- ys
|
||||
- year
|
||||
- years
|
||||
type: string
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into seconds
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '1053d 17h 53m -10s 391ms' | community.general.to_time_unit('s') }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of time units.
|
||||
type: float
|
||||
40
plugins/filter/to_weeks.yml
Normal file
40
plugins/filter/to_weeks.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_weeks
|
||||
short_description: Converte a duration string to weeks
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to weeks.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into weeks
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '1y 7m 5d 30h' | community.general.to_weeks }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of weeks.
|
||||
type: float
|
||||
40
plugins/filter/to_years.yml
Normal file
40
plugins/filter/to_years.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
DOCUMENTATION:
|
||||
name: to_years
|
||||
short_description: Converte a duration string to years
|
||||
version_added: 0.2.0
|
||||
description:
|
||||
- Parse a human readable time duration string and convert to years.
|
||||
options:
|
||||
_input:
|
||||
description:
|
||||
- The time string to convert.
|
||||
- Can use the units C(y) and C(year) for a year, C(mo) and C(month) for a month, C(w) and C(week) for a week,
|
||||
C(d) and C(day) for a day, C(h) and C(hour) for a hour, C(m), C(min) and C(minute) for minutes, C(s), C(sec)
|
||||
and C(second) for seconds, C(ms), C(msec), C(msecond) and C(millisecond) for milliseconds. The suffix C(s)
|
||||
can be added to a unit as well, so C(seconds) is the same as C(second).
|
||||
- Valid strings are space separated combinations of an integer with an optional minus sign and a unit.
|
||||
- Examples are C(1h), C(-5m), and C(3h -5m 6s).
|
||||
type: string
|
||||
required: true
|
||||
year:
|
||||
description:
|
||||
- Number of days per year.
|
||||
default: 365
|
||||
type: float
|
||||
month:
|
||||
description:
|
||||
- Number of days per month.
|
||||
default: 30
|
||||
type: float
|
||||
author:
|
||||
- René Moser (@resmo)
|
||||
|
||||
EXAMPLES: |
|
||||
- name: Convert a duration into years
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ '1053d 30h' | community.general.to_years }}"
|
||||
|
||||
RETURN:
|
||||
_value:
|
||||
description: Number of years.
|
||||
type: float
|
||||
@@ -6,6 +6,46 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: unicode_normalize
|
||||
short_description: Normalizes unicode strings to facilitate comparison of characters with normalized forms
|
||||
version_added: 3.7.0
|
||||
author: Andrew Pantuso (@Ajpantuso)
|
||||
description:
|
||||
- Normalizes unicode strings to facilitate comparison of characters with normalized forms.
|
||||
positional: form
|
||||
options:
|
||||
_input:
|
||||
description: A unicode string.
|
||||
type: string
|
||||
required: true
|
||||
form:
|
||||
description:
|
||||
- The normal form to use.
|
||||
- See U(https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize) for details.
|
||||
type: string
|
||||
default: NFC
|
||||
choices:
|
||||
- NFC
|
||||
- NFD
|
||||
- NFKC
|
||||
- NFKD
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Normalize unicode string
|
||||
ansible.builtin.set_fact:
|
||||
dictionary: "{{ 'ä' | community.general.unicode_normalize('NFKD') }}"
|
||||
# The resulting string has length 2: one letter is 'a', the other
|
||||
# the diacritic combiner.
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The normalized unicode string of the specified normal form.
|
||||
type: string
|
||||
'''
|
||||
|
||||
from unicodedata import normalize
|
||||
|
||||
from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError
|
||||
|
||||
@@ -5,6 +5,35 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: version_sort
|
||||
short_description: Sort a list according to version order instead of pure alphabetical one
|
||||
version_added: 2.2.0
|
||||
author: Eric L. (@ericzolf)
|
||||
description:
|
||||
- Sort a list according to version order instead of pure alphabetical one.
|
||||
options:
|
||||
_input:
|
||||
description: A list of strings to sort.
|
||||
type: list
|
||||
elements: string
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Convert list of tuples into dictionary
|
||||
ansible.builtin.set_fact:
|
||||
dictionary: "{{ ['2.1', '2.10', '2.9'] | community.general.version_sort }}"
|
||||
# Result is ['2.1', '2.9', '2.10']
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: The list of strings sorted by version.
|
||||
type: list
|
||||
elements: string
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@ DOCUMENTATION = '''
|
||||
description: token that ensures this is a source file for the 'nmap' plugin.
|
||||
required: True
|
||||
choices: ['nmap', 'community.general.nmap']
|
||||
sudo:
|
||||
description: Set to C(true) to execute a C(sudo nmap) plugin scan.
|
||||
version_added: 4.8.0
|
||||
default: false
|
||||
type: boolean
|
||||
address:
|
||||
description: Network IP or range of IPs to scan, you can use a simple range (10.2.2.15-25) or CIDR notation.
|
||||
required: True
|
||||
@@ -49,6 +54,13 @@ EXAMPLES = '''
|
||||
plugin: community.general.nmap
|
||||
strict: False
|
||||
address: 192.168.0.0/24
|
||||
|
||||
|
||||
# a sudo nmap scan to fully use nmap scan power.
|
||||
plugin: community.general.nmap
|
||||
sudo: true
|
||||
strict: False
|
||||
address: 192.168.0.0/24
|
||||
'''
|
||||
|
||||
import os
|
||||
@@ -135,6 +147,10 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
if not user_cache_setting or cache_needs_update:
|
||||
# setup command
|
||||
cmd = [self._nmap]
|
||||
|
||||
if self._options['sudo']:
|
||||
cmd.insert(0, 'sudo')
|
||||
|
||||
if not self._options['ports']:
|
||||
cmd.append('-sP')
|
||||
|
||||
|
||||
@@ -206,28 +206,40 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
|
||||
def _populate(self):
|
||||
hostname_preference = self.get_option('hostname')
|
||||
group_by_labels = self.get_option('group_by_labels')
|
||||
strict = self.get_option('strict')
|
||||
|
||||
# Add a top group 'one'
|
||||
self.inventory.add_group(group='all')
|
||||
|
||||
filter_by_label = self.get_option('filter_by_label')
|
||||
for server in self._retrieve_servers(filter_by_label):
|
||||
servers = self._retrieve_servers(filter_by_label)
|
||||
for server in servers:
|
||||
hostname = server['name']
|
||||
# check for labels
|
||||
if group_by_labels and server['LABELS']:
|
||||
for label in server['LABELS']:
|
||||
self.inventory.add_group(group=label)
|
||||
self.inventory.add_host(host=server['name'], group=label)
|
||||
self.inventory.add_host(host=hostname, group=label)
|
||||
|
||||
self.inventory.add_host(host=server['name'], group='all')
|
||||
self.inventory.add_host(host=hostname, group='all')
|
||||
|
||||
for attribute, value in server.items():
|
||||
self.inventory.set_variable(server['name'], attribute, value)
|
||||
self.inventory.set_variable(hostname, attribute, value)
|
||||
|
||||
if hostname_preference != 'name':
|
||||
self.inventory.set_variable(server['name'], 'ansible_host', server[hostname_preference])
|
||||
self.inventory.set_variable(hostname, 'ansible_host', server[hostname_preference])
|
||||
|
||||
if server.get('SSH_PORT'):
|
||||
self.inventory.set_variable(server['name'], 'ansible_port', server['SSH_PORT'])
|
||||
self.inventory.set_variable(hostname, 'ansible_port', server['SSH_PORT'])
|
||||
|
||||
# handle construcable implementation: get composed variables if any
|
||||
self._set_composite_vars(self.get_option('compose'), server, hostname, strict=strict)
|
||||
|
||||
# groups based on jinja conditionals get added to specific groups
|
||||
self._add_host_to_composed_groups(self.get_option('groups'), server, hostname, strict=strict)
|
||||
|
||||
# groups based on variables associated with them in the inventory
|
||||
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), server, hostname, strict=strict)
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
if not HAS_PYONE:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# 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 = '''
|
||||
@@ -52,11 +53,32 @@ DOCUMENTATION = '''
|
||||
- Proxmox authentication password.
|
||||
- If the value is not specified in the inventory configuration, the value of environment variable C(PROXMOX_PASSWORD) will be used instead.
|
||||
- Since community.general 4.7.0 you can also use templating to specify the value of the I(password).
|
||||
required: yes
|
||||
- If you do not specify a password, you must set I(token_id) and I(token_secret) instead.
|
||||
type: str
|
||||
env:
|
||||
- name: PROXMOX_PASSWORD
|
||||
version_added: 2.0.0
|
||||
token_id:
|
||||
description:
|
||||
- Proxmox authentication token ID.
|
||||
- If the value is not specified in the inventory configuration, the value of environment variable C(PROXMOX_TOKEN_ID) will be used instead.
|
||||
- To use token authentication, you must also specify I(token_secret). If you do not specify I(token_id) and I(token_secret),
|
||||
you must set a password instead.
|
||||
- Make sure to grant explicit pve permissions to the token or disable 'privilege separation' to use the users' privileges instead.
|
||||
version_added: 4.8.0
|
||||
type: str
|
||||
env:
|
||||
- name: PROXMOX_TOKEN_ID
|
||||
token_secret:
|
||||
description:
|
||||
- Proxmox authentication token secret.
|
||||
- If the value is not specified in the inventory configuration, the value of environment variable C(PROXMOX_TOKEN_SECRET) will be used instead.
|
||||
- To use token authentication, you must also specify I(token_id). If you do not specify I(token_id) and I(token_secret),
|
||||
you must set a password instead.
|
||||
version_added: 4.8.0
|
||||
type: str
|
||||
env:
|
||||
- name: PROXMOX_TOKEN_SECRET
|
||||
validate_certs:
|
||||
description: Verify SSL certificate if using HTTPS.
|
||||
type: boolean
|
||||
@@ -78,7 +100,9 @@ DOCUMENTATION = '''
|
||||
description:
|
||||
- Whether to set C(ansbile_host) for proxmox nodes.
|
||||
- When set to C(true) (default), will use the first available interface. This can be different from what you expect.
|
||||
default: true
|
||||
- This currently defaults to C(true), but the default is deprecated since community.general 4.8.0.
|
||||
The default will change to C(false) in community.general 6.0.0. To avoid a deprecation warning, please
|
||||
set this parameter explicitly.
|
||||
type: bool
|
||||
filters:
|
||||
version_added: 4.6.0
|
||||
@@ -103,6 +127,25 @@ EXAMPLES = '''
|
||||
plugin: community.general.proxmox
|
||||
user: ansible@pve
|
||||
password: secure
|
||||
# Note that this can easily give you wrong values as ansible_host. See further below for
|
||||
# an example where this is set to `false` and where ansible_host is set with `compose`.
|
||||
want_proxmox_nodes_ansible_host: true
|
||||
|
||||
# Instead of login with password, proxmox supports api token authentication since release 6.2.
|
||||
plugin: community.general.proxmox
|
||||
user: ci@pve
|
||||
token_id: gitlab-1
|
||||
token_secret: fa256e9c-26ab-41ec-82da-707a2c079829
|
||||
|
||||
# The secret can also be a vault string or passed via the environment variable TOKEN_SECRET.
|
||||
token_secret: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
62353634333163633336343265623632626339313032653563653165313262343931643431656138
|
||||
6134333736323265656466646539663134306166666237630a653363623262636663333762316136
|
||||
34616361326263383766366663393837626437316462313332663736623066656237386531663731
|
||||
3037646432383064630a663165303564623338666131353366373630656661333437393937343331
|
||||
32643131386134396336623736393634373936356332623632306561356361323737313663633633
|
||||
6231313333666361656537343562333337323030623732323833
|
||||
|
||||
# More complete example demonstrating the use of 'want_facts' and the constructed options
|
||||
# Note that using facts returned by 'want_facts' in constructed options requires 'want_facts=true'
|
||||
@@ -123,6 +166,9 @@ groups:
|
||||
mailservers: "'mail' in (proxmox_tags_parsed|list)"
|
||||
compose:
|
||||
ansible_port: 2222
|
||||
# Note that this can easily give you wrong values as ansible_host. See further below for
|
||||
# an example where this is set to `false` and where ansible_host is set with `compose`.
|
||||
want_proxmox_nodes_ansible_host: true
|
||||
|
||||
# Using the inventory to allow ansible to connect via the first IP address of the VM / Container
|
||||
# (Default is connection by name of QEMU/LXC guests)
|
||||
@@ -134,6 +180,7 @@ user: ansible@pve
|
||||
password: secure
|
||||
validate_certs: false
|
||||
want_facts: true
|
||||
want_proxmox_nodes_ansible_host: false
|
||||
compose:
|
||||
ansible_host: proxmox_ipconfig0.ip | default(proxmox_net0.ip) | ipaddr('address')
|
||||
my_inv_var_1: "'my_var1_value'"
|
||||
@@ -146,6 +193,9 @@ plugin: community.general.proxmox
|
||||
url: "{{ lookup('ansible.builtin.ini', 'url', section='proxmox', file='file.ini') }}"
|
||||
user: "{{ lookup('ansible.builtin.env','PM_USER') | default('ansible@pve') }}"
|
||||
password: "{{ lookup('community.general.random_string', base64=True) }}"
|
||||
# Note that this can easily give you wrong values as ansible_host. See further up for
|
||||
# an example where this is set to `false` and where ansible_host is set with `compose`.
|
||||
want_proxmox_nodes_ansible_host: true
|
||||
|
||||
'''
|
||||
|
||||
@@ -157,6 +207,7 @@ from ansible.module_utils.common._collections_compat import MutableMapping
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.utils.display import Display
|
||||
from ansible.template import Templar
|
||||
@@ -210,15 +261,24 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
def _get_auth(self):
|
||||
credentials = urlencode({'username': self.proxmox_user, 'password': self.proxmox_password, })
|
||||
|
||||
a = self._get_session()
|
||||
ret = a.post('%s/api2/json/access/ticket' % self.proxmox_url, data=credentials)
|
||||
if self.proxmox_password:
|
||||
|
||||
json = ret.json()
|
||||
credentials = urlencode({'username': self.proxmox_user, 'password': self.proxmox_password, })
|
||||
|
||||
self.credentials = {
|
||||
'ticket': json['data']['ticket'],
|
||||
'CSRFPreventionToken': json['data']['CSRFPreventionToken'],
|
||||
}
|
||||
a = self._get_session()
|
||||
ret = a.post('%s/api2/json/access/ticket' % self.proxmox_url, data=credentials)
|
||||
|
||||
json = ret.json()
|
||||
|
||||
self.headers = {
|
||||
# only required for POST/PUT/DELETE methods, which we are not using currently
|
||||
# 'CSRFPreventionToken': json['data']['CSRFPreventionToken'],
|
||||
'Cookie': 'PVEAuthCookie={0}'.format(json['data']['ticket'])
|
||||
}
|
||||
|
||||
else:
|
||||
|
||||
self.headers = {'Authorization': 'PVEAPIToken={0}!{1}={2}'.format(self.proxmox_user, self.proxmox_token_id, self.proxmox_token_secret)}
|
||||
|
||||
def _get_json(self, url, ignore_errors=None):
|
||||
|
||||
@@ -230,8 +290,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
data = []
|
||||
s = self._get_session()
|
||||
while True:
|
||||
headers = {'Cookie': 'PVEAuthCookie={0}'.format(self.credentials['ticket'])}
|
||||
ret = s.get(url, headers=headers)
|
||||
ret = s.get(url, headers=self.headers)
|
||||
if ignore_errors and ret.status_code in ignore_errors:
|
||||
break
|
||||
ret.raise_for_status()
|
||||
@@ -348,7 +407,16 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
agent_iface_key = self.to_safe('%s%s' % (key, "_interfaces"))
|
||||
properties[agent_iface_key] = agent_iface_value
|
||||
|
||||
if config not in plaintext_configs and not isinstance(value, int) and all("=" in v for v in value.split(",")):
|
||||
if config == 'lxc':
|
||||
out_val = {}
|
||||
for k, v in value:
|
||||
if k.startswith('lxc.'):
|
||||
k = k[len('lxc.'):]
|
||||
out_val[k] = v
|
||||
value = out_val
|
||||
|
||||
if config not in plaintext_configs and isinstance(value, string_types) \
|
||||
and all("=" in v for v in value.split(",")):
|
||||
# split off strings with commas to a dict
|
||||
# skip over any keys that cannot be processed
|
||||
try:
|
||||
@@ -467,6 +535,16 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
nodes_group = self._group('nodes')
|
||||
self.inventory.add_group(nodes_group)
|
||||
|
||||
want_proxmox_nodes_ansible_host = self.get_option("want_proxmox_nodes_ansible_host")
|
||||
if want_proxmox_nodes_ansible_host is None:
|
||||
display.deprecated(
|
||||
'The want_proxmox_nodes_ansible_host option of the community.general.proxmox inventory plugin'
|
||||
' currently defaults to `true`, but this default has been deprecated and will change to `false`'
|
||||
' in community.general 6.0.0. To keep the current behavior and remove this deprecation warning,'
|
||||
' explicitly set `want_proxmox_nodes_ansible_host` to `true` in your inventory configuration',
|
||||
version='6.0.0', collection_name='community.general')
|
||||
want_proxmox_nodes_ansible_host = True
|
||||
|
||||
# gather vm's on nodes
|
||||
self._get_auth()
|
||||
hosts = []
|
||||
@@ -482,7 +560,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
continue
|
||||
|
||||
# get node IP address
|
||||
if self.get_option("want_proxmox_nodes_ansible_host"):
|
||||
if want_proxmox_nodes_ansible_host:
|
||||
ip = self._get_node_ip(node['node'])
|
||||
self.inventory.set_variable(node['node'], 'ansible_host', ip)
|
||||
|
||||
@@ -530,6 +608,19 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
||||
proxmox_password = t.template(variable=proxmox_password, disable_lookups=False)
|
||||
self.proxmox_password = proxmox_password
|
||||
|
||||
proxmox_token_id = self.get_option('token_id')
|
||||
if t.is_template(proxmox_token_id):
|
||||
proxmox_token_id = t.template(variable=proxmox_token_id, disable_lookups=False)
|
||||
self.proxmox_token_id = proxmox_token_id
|
||||
|
||||
proxmox_token_secret = self.get_option('token_secret')
|
||||
if t.is_template(proxmox_token_secret):
|
||||
proxmox_token_secret = t.template(variable=proxmox_token_secret, disable_lookups=False)
|
||||
self.proxmox_token_secret = proxmox_token_secret
|
||||
|
||||
if proxmox_password is None and (proxmox_token_id is None or proxmox_token_secret is None):
|
||||
raise AnsibleError('You must specify either a password or both token_id and token_secret.')
|
||||
|
||||
self.cache_key = self.get_cache_key(path)
|
||||
self.use_cache = cache and self.get_option('cache')
|
||||
self.host_filters = self.get_option('filters')
|
||||
|
||||
@@ -57,14 +57,19 @@ EXAMPLES = """
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
password:
|
||||
description:
|
||||
- The actual value stored
|
||||
passprops:
|
||||
description: properties assigned to the entry
|
||||
type: dictionary
|
||||
passwordchangeinprocess:
|
||||
description: did the password change?
|
||||
_result:
|
||||
description: A list containing one dictionary.
|
||||
type: list
|
||||
elements: dictionary
|
||||
contains:
|
||||
password:
|
||||
description:
|
||||
- The actual value stored
|
||||
passprops:
|
||||
description: properties assigned to the entry
|
||||
type: dictionary
|
||||
passwordchangeinprocess:
|
||||
description: did the password change?
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
@@ -45,8 +45,8 @@ DOCUMENTATION = '''
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
|
||||
notes:
|
||||
- This lookup will use an existing 1Password session if one exists. If not, and you have already
|
||||
performed an initial sign in (meaning C(~/.op/config exists)), then only the C(master_password) is required.
|
||||
You may optionally specify C(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
|
||||
performed an initial sign in (meaning C(~/.op/config), C(~/.config/op/config) or C(~/.config/.op/config) exists), then only the
|
||||
C(master_password) is required. You may optionally specify C(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
|
||||
- This lookup can perform an initial login by providing C(subdomain), C(username), C(secret_key), and C(master_password).
|
||||
- Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
|
||||
needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
|
||||
@@ -105,12 +105,12 @@ from ansible.plugins.lookup import LookupBase
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.onepassword import OnePasswordConfig
|
||||
|
||||
|
||||
class OnePass(object):
|
||||
|
||||
def __init__(self, path='op'):
|
||||
self.cli_path = path
|
||||
self.config_file_path = os.path.expanduser('~/.op/config')
|
||||
self.logged_in = False
|
||||
self.token = None
|
||||
self.subdomain = None
|
||||
@@ -119,9 +119,11 @@ class OnePass(object):
|
||||
self.secret_key = None
|
||||
self.master_password = None
|
||||
|
||||
self._config = OnePasswordConfig()
|
||||
|
||||
def get_token(self):
|
||||
# If the config file exists, assume an initial signin has taken place and try basic sign in
|
||||
if os.path.isfile(self.config_file_path):
|
||||
if os.path.isfile(self._config.config_file_path):
|
||||
|
||||
if not self.master_password:
|
||||
raise AnsibleLookupError('Unable to sign in to 1Password. master_password is required.')
|
||||
@@ -281,4 +283,5 @@ class LookupModule(LookupBase):
|
||||
values = []
|
||||
for term in terms:
|
||||
values.append(op.get_field(term, field, section, vault))
|
||||
|
||||
return values
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Implements multiple version numbering conventions for the
|
||||
# Python Module Distribution Utilities.
|
||||
#
|
||||
# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0)
|
||||
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
|
||||
#
|
||||
|
||||
"""Provides classes to represent module version numbers (one class for
|
||||
|
||||
291
plugins/module_utils/cmd_runner.py
Normal file
291
plugins/module_utils/cmd_runner.py
Normal file
@@ -0,0 +1,291 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2022, Alexei Znamensky <russoz@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from ansible.module_utils.common.collections import is_sequence
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
|
||||
def _ensure_list(value):
|
||||
return list(value) if is_sequence(value) else [value]
|
||||
|
||||
|
||||
def _process_as_is(rc, out, err):
|
||||
return rc, out, err
|
||||
|
||||
|
||||
class CmdRunnerException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MissingArgumentFormat(CmdRunnerException):
|
||||
def __init__(self, arg, args_order, args_formats):
|
||||
self.args_order = args_order
|
||||
self.arg = arg
|
||||
self.args_formats = args_formats
|
||||
|
||||
def __repr__(self):
|
||||
return "MissingArgumentFormat({0!r}, {1!r}, {2!r})".format(
|
||||
self.arg,
|
||||
self.args_order,
|
||||
self.args_formats,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "Cannot find format for parameter {0} {1} in: {2}".format(
|
||||
self.arg,
|
||||
self.args_order,
|
||||
self.args_formats,
|
||||
)
|
||||
|
||||
|
||||
class MissingArgumentValue(CmdRunnerException):
|
||||
def __init__(self, args_order, arg):
|
||||
self.args_order = args_order
|
||||
self.arg = arg
|
||||
|
||||
def __repr__(self):
|
||||
return "MissingArgumentValue({0!r}, {1!r})".format(
|
||||
self.args_order,
|
||||
self.arg,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "Cannot find value for parameter {0} in {1}".format(
|
||||
self.arg,
|
||||
self.args_order,
|
||||
)
|
||||
|
||||
|
||||
class FormatError(CmdRunnerException):
|
||||
def __init__(self, name, value, args_formats, exc):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.args_formats = args_formats
|
||||
self.exc = exc
|
||||
super(FormatError, self).__init__()
|
||||
|
||||
def __repr__(self):
|
||||
return "FormatError({0!r}, {1!r}, {2!r}, {3!r})".format(
|
||||
self.name,
|
||||
self.value,
|
||||
self.args_formats,
|
||||
self.exc,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "Failed to format parameter {0} with value {1}: {2}".format(
|
||||
self.name,
|
||||
self.value,
|
||||
self.exc,
|
||||
)
|
||||
|
||||
|
||||
class _ArgFormat(object):
|
||||
def __init__(self, func, ignore_none=None):
|
||||
self.func = func
|
||||
self.ignore_none = ignore_none
|
||||
|
||||
def __call__(self, value, ctx_ignore_none):
|
||||
ignore_none = self.ignore_none if self.ignore_none is not None else ctx_ignore_none
|
||||
if value is None and ignore_none:
|
||||
return []
|
||||
f = self.func
|
||||
return [str(x) for x in f(value)]
|
||||
|
||||
|
||||
class _Format(object):
|
||||
@staticmethod
|
||||
def as_bool(args):
|
||||
return _ArgFormat(lambda value: _ensure_list(args) if value else [])
|
||||
|
||||
@staticmethod
|
||||
def as_bool_not(args):
|
||||
return _ArgFormat(lambda value: [] if value else _ensure_list(args), ignore_none=False)
|
||||
|
||||
@staticmethod
|
||||
def as_optval(arg, ignore_none=None):
|
||||
return _ArgFormat(lambda value: ["{0}{1}".format(arg, value)], ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_opt_val(arg, ignore_none=None):
|
||||
return _ArgFormat(lambda value: [arg, value], ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_opt_eq_val(arg, ignore_none=None):
|
||||
return _ArgFormat(lambda value: ["{0}={1}".format(arg, value)], ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_list(ignore_none=None):
|
||||
return _ArgFormat(_ensure_list, ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_fixed(args):
|
||||
return _ArgFormat(lambda value: _ensure_list(args), ignore_none=False)
|
||||
|
||||
@staticmethod
|
||||
def as_func(func, ignore_none=None):
|
||||
return _ArgFormat(func, ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_map(_map, default=None, ignore_none=None):
|
||||
return _ArgFormat(lambda value: _ensure_list(_map.get(value, default)), ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def as_default_type(_type, arg="", ignore_none=None):
|
||||
fmt = _Format
|
||||
if _type == "dict":
|
||||
return fmt.as_func(lambda d: ["--{0}={1}".format(*a) for a in iteritems(d)],
|
||||
ignore_none=ignore_none)
|
||||
if _type == "list":
|
||||
return fmt.as_func(lambda value: ["--{0}".format(x) for x in value], ignore_none=ignore_none)
|
||||
if _type == "bool":
|
||||
return fmt.as_bool("--{0}".format(arg))
|
||||
|
||||
return fmt.as_opt_val("--{0}".format(arg), ignore_none=ignore_none)
|
||||
|
||||
@staticmethod
|
||||
def unpack_args(func):
|
||||
@wraps(func)
|
||||
def wrapper(v):
|
||||
return func(*v)
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def unpack_kwargs(func):
|
||||
@wraps(func)
|
||||
def wrapper(v):
|
||||
return func(**v)
|
||||
return wrapper
|
||||
|
||||
|
||||
class CmdRunner(object):
|
||||
"""
|
||||
Wrapper for ``AnsibleModule.run_command()``.
|
||||
|
||||
It aims to provide a reusable runner with consistent argument formatting
|
||||
and sensible defaults.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _prepare_args_order(order):
|
||||
return tuple(order) if is_sequence(order) else tuple(order.split())
|
||||
|
||||
def __init__(self, module, command, arg_formats=None, default_args_order=(),
|
||||
check_rc=False, force_lang="C", path_prefix=None, environ_update=None):
|
||||
self.module = module
|
||||
self.command = _ensure_list(command)
|
||||
self.default_args_order = self._prepare_args_order(default_args_order)
|
||||
if arg_formats is None:
|
||||
arg_formats = {}
|
||||
self.arg_formats = dict(arg_formats)
|
||||
self.check_rc = check_rc
|
||||
self.force_lang = force_lang
|
||||
self.path_prefix = path_prefix
|
||||
if environ_update is None:
|
||||
environ_update = {}
|
||||
self.environ_update = environ_update
|
||||
|
||||
self.command[0] = module.get_bin_path(command[0], opt_dirs=path_prefix, required=True)
|
||||
|
||||
for mod_param_name, spec in iteritems(module.argument_spec):
|
||||
if mod_param_name not in self.arg_formats:
|
||||
self.arg_formats[mod_param_name] = _Format.as_default_type(spec['type'], mod_param_name)
|
||||
|
||||
def context(self, args_order=None, output_process=None, ignore_value_none=True, **kwargs):
|
||||
if output_process is None:
|
||||
output_process = _process_as_is
|
||||
if args_order is None:
|
||||
args_order = self.default_args_order
|
||||
args_order = self._prepare_args_order(args_order)
|
||||
for p in args_order:
|
||||
if p not in self.arg_formats:
|
||||
raise MissingArgumentFormat(p, args_order, tuple(self.arg_formats.keys()))
|
||||
return _CmdRunnerContext(runner=self,
|
||||
args_order=args_order,
|
||||
output_process=output_process,
|
||||
ignore_value_none=ignore_value_none, **kwargs)
|
||||
|
||||
def has_arg_format(self, arg):
|
||||
return arg in self.arg_formats
|
||||
|
||||
|
||||
class _CmdRunnerContext(object):
|
||||
def __init__(self, runner, args_order, output_process, ignore_value_none, **kwargs):
|
||||
self.runner = runner
|
||||
self.args_order = tuple(args_order)
|
||||
self.output_process = output_process
|
||||
self.ignore_value_none = ignore_value_none
|
||||
self.run_command_args = dict(kwargs)
|
||||
|
||||
self.environ_update = runner.environ_update
|
||||
self.environ_update.update(self.run_command_args.get('environ_update', {}))
|
||||
if runner.force_lang:
|
||||
self.environ_update.update({
|
||||
'LANGUAGE': runner.force_lang,
|
||||
'LC_ALL': runner.force_lang,
|
||||
})
|
||||
self.run_command_args['environ_update'] = self.environ_update
|
||||
|
||||
if 'check_rc' not in self.run_command_args:
|
||||
self.run_command_args['check_rc'] = runner.check_rc
|
||||
self.check_rc = self.run_command_args['check_rc']
|
||||
|
||||
self.cmd = None
|
||||
self.results_rc = None
|
||||
self.results_out = None
|
||||
self.results_err = None
|
||||
self.results_processed = None
|
||||
|
||||
def run(self, **kwargs):
|
||||
runner = self.runner
|
||||
module = self.runner.module
|
||||
self.cmd = list(runner.command)
|
||||
self.context_run_args = dict(kwargs)
|
||||
|
||||
named_args = dict(module.params)
|
||||
named_args.update(kwargs)
|
||||
for arg_name in self.args_order:
|
||||
value = None
|
||||
try:
|
||||
value = named_args[arg_name]
|
||||
self.cmd.extend(runner.arg_formats[arg_name](value, ctx_ignore_none=self.ignore_value_none))
|
||||
except KeyError:
|
||||
raise MissingArgumentValue(self.args_order, arg_name)
|
||||
except Exception as e:
|
||||
raise FormatError(arg_name, value, runner.arg_formats[arg_name], e)
|
||||
|
||||
results = module.run_command(self.cmd, **self.run_command_args)
|
||||
self.results_rc, self.results_out, self.results_err = results
|
||||
self.results_processed = self.output_process(*results)
|
||||
return self.results_processed
|
||||
|
||||
@property
|
||||
def run_info(self):
|
||||
return dict(
|
||||
ignore_value_none=self.ignore_value_none,
|
||||
check_rc=self.check_rc,
|
||||
environ_update=self.environ_update,
|
||||
args_order=self.args_order,
|
||||
cmd=self.cmd,
|
||||
run_command_args=self.run_command_args,
|
||||
context_run_args=self.context_run_args,
|
||||
results_rc=self.results_rc,
|
||||
results_out=self.results_out,
|
||||
results_err=self.results_err,
|
||||
results_processed=self.results_processed,
|
||||
)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
return False
|
||||
|
||||
|
||||
fmt = _Format()
|
||||
@@ -337,7 +337,6 @@ def pritunl_auth_request(
|
||||
|
||||
auth_string = "&".join(
|
||||
[api_token, auth_timestamp, auth_nonce, method.upper(), path]
|
||||
+ ([data] if data else [])
|
||||
)
|
||||
|
||||
auth_signature = base64.b64encode(
|
||||
|
||||
29
plugins/module_utils/onepassword.py
Normal file
29
plugins/module_utils/onepassword.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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 os
|
||||
|
||||
|
||||
class OnePasswordConfig(object):
|
||||
_config_file_paths = (
|
||||
"~/.op/config",
|
||||
"~/.config/op/config",
|
||||
"~/.config/.op/config",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self._config_file_path = ""
|
||||
|
||||
@property
|
||||
def config_file_path(self):
|
||||
if self._config_file_path:
|
||||
return self._config_file_path
|
||||
|
||||
for path in self._config_file_paths:
|
||||
realpath = os.path.expanduser(path)
|
||||
if os.path.exists(realpath):
|
||||
self._config_file_path = realpath
|
||||
return self._config_file_path
|
||||
@@ -732,14 +732,22 @@ class RedfishUtils(object):
|
||||
def get_multi_volume_inventory(self):
|
||||
return self.aggregate_systems(self.get_volume_inventory)
|
||||
|
||||
def manage_indicator_led(self, command):
|
||||
def manage_system_indicator_led(self, command):
|
||||
return self.manage_indicator_led(command, self.systems_uri)
|
||||
|
||||
def manage_chassis_indicator_led(self, command):
|
||||
return self.manage_indicator_led(command, self.chassis_uri)
|
||||
|
||||
def manage_indicator_led(self, command, resource_uri=None):
|
||||
result = {}
|
||||
key = 'IndicatorLED'
|
||||
if resource_uri is None:
|
||||
resource_uri = self.chassis_uri
|
||||
|
||||
payloads = {'IndicatorLedOn': 'Lit', 'IndicatorLedOff': 'Off', "IndicatorLedBlink": 'Blinking'}
|
||||
|
||||
result = {}
|
||||
response = self.get_request(self.root_uri + self.chassis_uri)
|
||||
response = self.get_request(self.root_uri + resource_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
result['ret'] = True
|
||||
@@ -749,7 +757,7 @@ class RedfishUtils(object):
|
||||
|
||||
if command in payloads.keys():
|
||||
payload = {'IndicatorLED': payloads[command]}
|
||||
response = self.patch_request(self.root_uri + self.chassis_uri, payload)
|
||||
response = self.patch_request(self.root_uri + resource_uri, payload)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
else:
|
||||
|
||||
1
plugins/modules/alerta_customer.py
Symbolic link
1
plugins/modules/alerta_customer.py
Symbolic link
@@ -0,0 +1 @@
|
||||
./monitoring/alerta_customer.py
|
||||
@@ -182,10 +182,10 @@ def core(module):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
mode=dict(default=None, choices=['user', 'system']),
|
||||
mode=dict(choices=['user', 'system']),
|
||||
name=dict(required=True),
|
||||
image=dict(required=True),
|
||||
rootfs=dict(default=None),
|
||||
rootfs=dict(),
|
||||
state=dict(default='latest', choices=['present', 'absent', 'latest', 'rollback']),
|
||||
backend=dict(required=True, choices=['docker', 'ostree']),
|
||||
values=dict(type='list', default=[], elements='str'),
|
||||
|
||||
@@ -228,8 +228,7 @@ class ClcAlertPolicy:
|
||||
choices=[
|
||||
'cpu',
|
||||
'memory',
|
||||
'disk'],
|
||||
default=None),
|
||||
'disk']),
|
||||
duration=dict(type='str'),
|
||||
threshold=dict(type='int'),
|
||||
state=dict(default='present', choices=['present', 'absent'])
|
||||
|
||||
@@ -297,9 +297,9 @@ class ClcGroup(object):
|
||||
"""
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
description=dict(default=None),
|
||||
parent=dict(default=None),
|
||||
location=dict(default=None),
|
||||
description=dict(),
|
||||
parent=dict(),
|
||||
location=dict(),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
wait=dict(type='bool', default=True))
|
||||
|
||||
|
||||
@@ -865,7 +865,7 @@ class ClcLoadBalancer:
|
||||
"""
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
description=dict(default=None),
|
||||
description=dict(),
|
||||
location=dict(required=True),
|
||||
alias=dict(required=True),
|
||||
port=dict(choices=[80, 443]),
|
||||
|
||||
@@ -567,31 +567,31 @@ class ClcServer:
|
||||
template=dict(),
|
||||
group=dict(default='Default Group'),
|
||||
network_id=dict(),
|
||||
location=dict(default=None),
|
||||
location=dict(),
|
||||
cpu=dict(default=1, type='int'),
|
||||
memory=dict(default=1, type='int'),
|
||||
alias=dict(default=None),
|
||||
password=dict(default=None, no_log=True),
|
||||
ip_address=dict(default=None),
|
||||
alias=dict(),
|
||||
password=dict(no_log=True),
|
||||
ip_address=dict(),
|
||||
storage_type=dict(
|
||||
default='standard',
|
||||
choices=[
|
||||
'standard',
|
||||
'hyperscale']),
|
||||
type=dict(default='standard', choices=['standard', 'hyperscale', 'bareMetal']),
|
||||
primary_dns=dict(default=None),
|
||||
secondary_dns=dict(default=None),
|
||||
primary_dns=dict(),
|
||||
secondary_dns=dict(),
|
||||
additional_disks=dict(type='list', default=[], elements='dict'),
|
||||
custom_fields=dict(type='list', default=[], elements='dict'),
|
||||
ttl=dict(default=None),
|
||||
ttl=dict(),
|
||||
managed_os=dict(type='bool', default=False),
|
||||
description=dict(default=None),
|
||||
source_server_password=dict(default=None, no_log=True),
|
||||
cpu_autoscale_policy_id=dict(default=None),
|
||||
anti_affinity_policy_id=dict(default=None),
|
||||
anti_affinity_policy_name=dict(default=None),
|
||||
alert_policy_id=dict(default=None),
|
||||
alert_policy_name=dict(default=None),
|
||||
description=dict(),
|
||||
source_server_password=dict(no_log=True),
|
||||
cpu_autoscale_policy_id=dict(),
|
||||
anti_affinity_policy_id=dict(),
|
||||
anti_affinity_policy_name=dict(),
|
||||
alert_policy_id=dict(),
|
||||
alert_policy_name=dict(),
|
||||
packages=dict(type='list', default=[], elements='dict'),
|
||||
state=dict(
|
||||
default='present',
|
||||
@@ -601,7 +601,7 @@ class ClcServer:
|
||||
'started',
|
||||
'stopped']),
|
||||
count=dict(type='int', default=1),
|
||||
exact_count=dict(type='int', default=None),
|
||||
exact_count=dict(type='int', ),
|
||||
count_group=dict(),
|
||||
server_ids=dict(type='list', default=[], elements='str'),
|
||||
add_public_ip=dict(type='bool', default=False),
|
||||
@@ -612,14 +612,13 @@ class ClcServer:
|
||||
'UDP',
|
||||
'ICMP']),
|
||||
public_ip_ports=dict(type='list', default=[], elements='dict'),
|
||||
configuration_id=dict(default=None),
|
||||
os_type=dict(default=None,
|
||||
choices=[
|
||||
'redHat6_64Bit',
|
||||
'centOS6_64Bit',
|
||||
'windows2012R2Standard_64Bit',
|
||||
'ubuntu14_64Bit'
|
||||
]),
|
||||
configuration_id=dict(),
|
||||
os_type=dict(choices=[
|
||||
'redHat6_64Bit',
|
||||
'centOS6_64Bit',
|
||||
'windows2012R2Standard_64Bit',
|
||||
'ubuntu14_64Bit'
|
||||
]),
|
||||
wait=dict(type='bool', default=True))
|
||||
|
||||
mutually_exclusive = [
|
||||
|
||||
@@ -21,6 +21,13 @@ options:
|
||||
- Name of an instance.
|
||||
type: str
|
||||
required: true
|
||||
project:
|
||||
description:
|
||||
- 'Project of an instance.
|
||||
See U(https://github.com/lxc/lxd/blob/master/doc/projects.md).'
|
||||
required: false
|
||||
type: str
|
||||
version_added: 4.8.0
|
||||
architecture:
|
||||
description:
|
||||
- 'The architecture for the instance (for example C(x86_64) or C(i686)).
|
||||
@@ -248,6 +255,26 @@ EXAMPLES = '''
|
||||
wait_for_ipv4_addresses: true
|
||||
timeout: 600
|
||||
|
||||
# An example for creating container in project other than default
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create a started container in project mytestproject
|
||||
community.general.lxd_container:
|
||||
name: mycontainer
|
||||
project: mytestproject
|
||||
ignore_volatile_options: true
|
||||
state: started
|
||||
source:
|
||||
protocol: simplestreams
|
||||
type: image
|
||||
mode: pull
|
||||
server: https://images.linuxcontainers.org
|
||||
alias: ubuntu/20.04/cloud
|
||||
profiles: ["default"]
|
||||
wait_for_ipv4_addresses: true
|
||||
timeout: 600
|
||||
|
||||
# An example for deleting a container
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
@@ -412,6 +439,7 @@ class LXDContainerManagement(object):
|
||||
"""
|
||||
self.module = module
|
||||
self.name = self.module.params['name']
|
||||
self.project = self.module.params['project']
|
||||
self._build_config()
|
||||
|
||||
self.state = self.module.params['state']
|
||||
@@ -468,16 +496,16 @@ class LXDContainerManagement(object):
|
||||
self.config[attr] = param_val
|
||||
|
||||
def _get_instance_json(self):
|
||||
return self.client.do(
|
||||
'GET', '{0}/{1}'.format(self.api_endpoint, self.name),
|
||||
ok_error_codes=[404]
|
||||
)
|
||||
url = '{0}/{1}'.format(self.api_endpoint, self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
return self.client.do('GET', url, ok_error_codes=[404])
|
||||
|
||||
def _get_instance_state_json(self):
|
||||
return self.client.do(
|
||||
'GET', '{0}/{1}/state'.format(self.api_endpoint, self.name),
|
||||
ok_error_codes=[404]
|
||||
)
|
||||
url = '{0}/{1}/state'.format(self.api_endpoint, self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
return self.client.do('GET', url, ok_error_codes=[404])
|
||||
|
||||
@staticmethod
|
||||
def _instance_json_to_module_state(resp_json):
|
||||
@@ -486,18 +514,26 @@ class LXDContainerManagement(object):
|
||||
return ANSIBLE_LXD_STATES[resp_json['metadata']['status']]
|
||||
|
||||
def _change_state(self, action, force_stop=False):
|
||||
url = '{0}/{1}/state'.format(self.api_endpoint, self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
body_json = {'action': action, 'timeout': self.timeout}
|
||||
if force_stop:
|
||||
body_json['force'] = True
|
||||
return self.client.do('PUT', '{0}/{1}/state'.format(self.api_endpoint, self.name), body_json=body_json)
|
||||
return self.client.do('PUT', url, body_json=body_json)
|
||||
|
||||
def _create_instance(self):
|
||||
url = self.api_endpoint
|
||||
url_params = dict()
|
||||
if self.target:
|
||||
url_params['target'] = self.target
|
||||
if self.project:
|
||||
url_params['project'] = self.project
|
||||
if url_params:
|
||||
url = '{0}?{1}'.format(url, urlencode(url_params))
|
||||
config = self.config.copy()
|
||||
config['name'] = self.name
|
||||
if self.target:
|
||||
self.client.do('POST', '{0}?{1}'.format(self.api_endpoint, urlencode(dict(target=self.target))), config, wait_for_container=self.wait_for_container)
|
||||
else:
|
||||
self.client.do('POST', self.api_endpoint, config, wait_for_container=self.wait_for_container)
|
||||
self.client.do('POST', url, config, wait_for_container=self.wait_for_container)
|
||||
self.actions.append('create')
|
||||
|
||||
def _start_instance(self):
|
||||
@@ -513,7 +549,10 @@ class LXDContainerManagement(object):
|
||||
self.actions.append('restart')
|
||||
|
||||
def _delete_instance(self):
|
||||
self.client.do('DELETE', '{0}/{1}'.format(self.api_endpoint, self.name))
|
||||
url = '{0}/{1}'.format(self.api_endpoint, self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
self.client.do('DELETE', url)
|
||||
self.actions.append('delete')
|
||||
|
||||
def _freeze_instance(self):
|
||||
@@ -666,7 +705,10 @@ class LXDContainerManagement(object):
|
||||
if self._needs_to_change_instance_config('profiles'):
|
||||
body_json['profiles'] = self.config['profiles']
|
||||
|
||||
self.client.do('PUT', '{0}/{1}'.format(self.api_endpoint, self.name), body_json=body_json)
|
||||
url = '{0}/{1}'.format(self.api_endpoint, self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
self.client.do('PUT', url, body_json=body_json)
|
||||
self.actions.append('apply_instance_configs')
|
||||
|
||||
def run(self):
|
||||
@@ -715,6 +757,9 @@ def main():
|
||||
type='str',
|
||||
required=True
|
||||
),
|
||||
project=dict(
|
||||
type='str',
|
||||
),
|
||||
architecture=dict(
|
||||
type='str',
|
||||
),
|
||||
|
||||
@@ -21,6 +21,13 @@ options:
|
||||
- Name of a profile.
|
||||
required: true
|
||||
type: str
|
||||
project:
|
||||
description:
|
||||
- 'Project of a profile.
|
||||
See U(https://github.com/lxc/lxd/blob/master/doc/projects.md).'
|
||||
type: str
|
||||
required: false
|
||||
version_added: 4.8.0
|
||||
description:
|
||||
description:
|
||||
- Description of the profile.
|
||||
@@ -129,6 +136,19 @@ EXAMPLES = '''
|
||||
parent: br0
|
||||
type: nic
|
||||
|
||||
# An example for creating a profile in project mytestproject
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create a profile
|
||||
community.general.lxd_profile:
|
||||
name: testprofile
|
||||
project: mytestproject
|
||||
state: present
|
||||
config: {}
|
||||
description: test profile in project mytestproject
|
||||
devices: {}
|
||||
|
||||
# An example for creating a profile via http connection
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
@@ -208,6 +228,7 @@ actions:
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
|
||||
# ANSIBLE_LXD_DEFAULT_URL is a default value of the lxd endpoint
|
||||
ANSIBLE_LXD_DEFAULT_URL = 'unix:/var/lib/lxd/unix.socket'
|
||||
@@ -232,6 +253,7 @@ class LXDProfileManagement(object):
|
||||
"""
|
||||
self.module = module
|
||||
self.name = self.module.params['name']
|
||||
self.project = self.module.params['project']
|
||||
self._build_config()
|
||||
self.state = self.module.params['state']
|
||||
self.new_name = self.module.params.get('new_name', None)
|
||||
@@ -272,10 +294,10 @@ class LXDProfileManagement(object):
|
||||
self.config[attr] = param_val
|
||||
|
||||
def _get_profile_json(self):
|
||||
return self.client.do(
|
||||
'GET', '/1.0/profiles/{0}'.format(self.name),
|
||||
ok_error_codes=[404]
|
||||
)
|
||||
url = '/1.0/profiles/{0}'.format(self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
return self.client.do('GET', url, ok_error_codes=[404])
|
||||
|
||||
@staticmethod
|
||||
def _profile_json_to_module_state(resp_json):
|
||||
@@ -307,14 +329,20 @@ class LXDProfileManagement(object):
|
||||
changed=False)
|
||||
|
||||
def _create_profile(self):
|
||||
url = '/1.0/profiles'
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
config = self.config.copy()
|
||||
config['name'] = self.name
|
||||
self.client.do('POST', '/1.0/profiles', config)
|
||||
self.client.do('POST', url, config)
|
||||
self.actions.append('create')
|
||||
|
||||
def _rename_profile(self):
|
||||
url = '/1.0/profiles/{0}'.format(self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
config = {'name': self.new_name}
|
||||
self.client.do('POST', '/1.0/profiles/{0}'.format(self.name), config)
|
||||
self.client.do('POST', url, config)
|
||||
self.actions.append('rename')
|
||||
self.name = self.new_name
|
||||
|
||||
@@ -421,11 +449,17 @@ class LXDProfileManagement(object):
|
||||
config = self._generate_new_config(config)
|
||||
|
||||
# upload config to lxd
|
||||
self.client.do('PUT', '/1.0/profiles/{0}'.format(self.name), config)
|
||||
url = '/1.0/profiles/{0}'.format(self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
self.client.do('PUT', url, config)
|
||||
self.actions.append('apply_profile_configs')
|
||||
|
||||
def _delete_profile(self):
|
||||
self.client.do('DELETE', '/1.0/profiles/{0}'.format(self.name))
|
||||
url = '/1.0/profiles/{0}'.format(self.name)
|
||||
if self.project:
|
||||
url = '{0}?{1}'.format(url, urlencode(dict(project=self.project)))
|
||||
self.client.do('DELETE', url)
|
||||
self.actions.append('delete')
|
||||
|
||||
def run(self):
|
||||
@@ -469,6 +503,9 @@ def main():
|
||||
type='str',
|
||||
required=True
|
||||
),
|
||||
project=dict(
|
||||
type='str',
|
||||
),
|
||||
new_name=dict(
|
||||
type='str',
|
||||
),
|
||||
|
||||
451
plugins/modules/cloud/lxd/lxd_project.py
Normal file
451
plugins/modules/cloud/lxd/lxd_project.py
Normal file
@@ -0,0 +1,451 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: lxd_project
|
||||
short_description: Manage LXD projects
|
||||
version_added: 4.8.0
|
||||
description:
|
||||
- Management of LXD projects.
|
||||
author: "Raymond Chang (@we10710aa)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the project.
|
||||
required: true
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- Description of the project.
|
||||
type: str
|
||||
config:
|
||||
description:
|
||||
- 'The config for the project (for example C({"features.profiles": "true"})).
|
||||
See U(https://linuxcontainers.org/lxd/docs/master/projects/).'
|
||||
- If the project already exists and its "config" value in metadata
|
||||
obtained from
|
||||
C(GET /1.0/projects/<name>)
|
||||
U(https://linuxcontainers.org/lxd/docs/master/api/#/projects/project_get)
|
||||
are different, then this module tries to apply the configurations.
|
||||
type: dict
|
||||
new_name:
|
||||
description:
|
||||
- A new name of a project.
|
||||
- If this parameter is specified a project will be renamed to this name.
|
||||
See U(https://linuxcontainers.org/lxd/docs/master/api/#/projects/project_post).
|
||||
required: false
|
||||
type: str
|
||||
merge_project:
|
||||
description:
|
||||
- Merge the configuration of the present project with the new desired configuration,
|
||||
instead of replacing it. If configuration is the same after merged, no change will be made.
|
||||
required: false
|
||||
default: false
|
||||
type: bool
|
||||
state:
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
description:
|
||||
- Define the state of a project.
|
||||
required: false
|
||||
default: present
|
||||
type: str
|
||||
url:
|
||||
description:
|
||||
- The Unix domain socket path or the https URL for the LXD server.
|
||||
required: false
|
||||
default: unix:/var/lib/lxd/unix.socket
|
||||
type: str
|
||||
snap_url:
|
||||
description:
|
||||
- The Unix domain socket path when LXD is installed by snap package manager.
|
||||
required: false
|
||||
default: unix:/var/snap/lxd/common/lxd/unix.socket
|
||||
type: str
|
||||
client_key:
|
||||
description:
|
||||
- The client certificate key file path.
|
||||
- If not specified, it defaults to C($HOME/.config/lxc/client.key).
|
||||
required: false
|
||||
aliases: [ key_file ]
|
||||
type: path
|
||||
client_cert:
|
||||
description:
|
||||
- The client certificate file path.
|
||||
- If not specified, it defaults to C($HOME/.config/lxc/client.crt).
|
||||
required: false
|
||||
aliases: [ cert_file ]
|
||||
type: path
|
||||
trust_password:
|
||||
description:
|
||||
- The client trusted password.
|
||||
- 'You need to set this password on the LXD server before
|
||||
running this module using the following command:
|
||||
C(lxc config set core.trust_password <some random password>)
|
||||
See U(https://www.stgraber.org/2016/04/18/lxd-api-direct-interaction/).'
|
||||
- If I(trust_password) is set, this module send a request for
|
||||
authentication before sending any requests.
|
||||
required: false
|
||||
type: str
|
||||
notes:
|
||||
- Projects must have a unique name. If you attempt to create a project
|
||||
with a name that already existed in the users namespace the module will
|
||||
simply return as "unchanged".
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# An example for creating a project
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Create a project
|
||||
community.general.lxd_project:
|
||||
name: ansible-test-project
|
||||
state: present
|
||||
config: {}
|
||||
description: my new project
|
||||
|
||||
# An example for renaming a project
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
tasks:
|
||||
- name: Rename ansible-test-project to ansible-test-project-new-name
|
||||
community.general.lxd_project:
|
||||
name: ansible-test-project
|
||||
new_name: ansible-test-project-new-name
|
||||
state: present
|
||||
config: {}
|
||||
description: my new project
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
old_state:
|
||||
description: The old state of the project.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "absent"
|
||||
logs:
|
||||
description: The logs of requests and responses.
|
||||
returned: when ansible-playbook is invoked with -vvvv.
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
type:
|
||||
description: Type of actions performed, currently only C(sent request).
|
||||
type: str
|
||||
sample: "sent request"
|
||||
request:
|
||||
description: HTTP request sent to LXD server.
|
||||
type: dict
|
||||
contains:
|
||||
method:
|
||||
description: Method of HTTP request.
|
||||
type: str
|
||||
sample: "GET"
|
||||
url:
|
||||
description: URL path of HTTP request.
|
||||
type: str
|
||||
sample: "/1.0/projects/test-project"
|
||||
json:
|
||||
description: JSON body of HTTP request.
|
||||
type: str
|
||||
sample: "(too long to be placed here)"
|
||||
timeout:
|
||||
description: Timeout of HTTP request, C(null) if unset.
|
||||
type: int
|
||||
sample: null
|
||||
response:
|
||||
description: HTTP response received from LXD server.
|
||||
type: dict
|
||||
contains:
|
||||
json:
|
||||
description: JSON of HTTP response.
|
||||
type: str
|
||||
sample: "(too long to be placed here)"
|
||||
actions:
|
||||
description: List of actions performed for the project.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ["create"]
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
import os
|
||||
|
||||
# ANSIBLE_LXD_DEFAULT_URL is a default value of the lxd endpoint
|
||||
ANSIBLE_LXD_DEFAULT_URL = 'unix:/var/lib/lxd/unix.socket'
|
||||
|
||||
# PROJECTS_STATES is a list for states supported
|
||||
PROJECTS_STATES = [
|
||||
'present', 'absent'
|
||||
]
|
||||
|
||||
# CONFIG_PARAMS is a list of config attribute names.
|
||||
CONFIG_PARAMS = [
|
||||
'config', 'description'
|
||||
]
|
||||
|
||||
|
||||
class LXDProjectManagement(object):
|
||||
def __init__(self, module):
|
||||
"""Management of LXC projects via Ansible.
|
||||
|
||||
:param module: Processed Ansible Module.
|
||||
:type module: ``object``
|
||||
"""
|
||||
self.module = module
|
||||
self.name = self.module.params['name']
|
||||
self._build_config()
|
||||
self.state = self.module.params['state']
|
||||
self.new_name = self.module.params.get('new_name', None)
|
||||
|
||||
self.key_file = self.module.params.get('client_key')
|
||||
if self.key_file is None:
|
||||
self.key_file = os.path.expanduser('~/.config/lxc/client.key')
|
||||
self.cert_file = self.module.params.get('client_cert')
|
||||
if self.cert_file is None:
|
||||
self.cert_file = os.path.expanduser('~/.config/lxc/client.crt')
|
||||
self.debug = self.module._verbosity >= 4
|
||||
|
||||
try:
|
||||
if self.module.params['url'] != ANSIBLE_LXD_DEFAULT_URL:
|
||||
self.url = self.module.params['url']
|
||||
elif os.path.exists(self.module.params['snap_url'].replace('unix:', '')):
|
||||
self.url = self.module.params['snap_url']
|
||||
else:
|
||||
self.url = self.module.params['url']
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg=e.msg)
|
||||
|
||||
try:
|
||||
self.client = LXDClient(
|
||||
self.url, key_file=self.key_file, cert_file=self.cert_file,
|
||||
debug=self.debug
|
||||
)
|
||||
except LXDClientException as e:
|
||||
self.module.fail_json(msg=e.msg)
|
||||
self.trust_password = self.module.params.get('trust_password', None)
|
||||
self.actions = []
|
||||
|
||||
def _build_config(self):
|
||||
self.config = {}
|
||||
for attr in CONFIG_PARAMS:
|
||||
param_val = self.module.params.get(attr, None)
|
||||
if param_val is not None:
|
||||
self.config[attr] = param_val
|
||||
|
||||
def _get_project_json(self):
|
||||
return self.client.do(
|
||||
'GET', '/1.0/projects/{0}'.format(self.name),
|
||||
ok_error_codes=[404]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _project_json_to_module_state(resp_json):
|
||||
if resp_json['type'] == 'error':
|
||||
return 'absent'
|
||||
return 'present'
|
||||
|
||||
def _update_project(self):
|
||||
if self.state == 'present':
|
||||
if self.old_state == 'absent':
|
||||
if self.new_name is None:
|
||||
self._create_project()
|
||||
else:
|
||||
self.module.fail_json(
|
||||
msg='new_name must not be set when the project does not exist and the state is present',
|
||||
changed=False)
|
||||
else:
|
||||
if self.new_name is not None and self.new_name != self.name:
|
||||
self._rename_project()
|
||||
if self._needs_to_apply_project_configs():
|
||||
self._apply_project_configs()
|
||||
elif self.state == 'absent':
|
||||
if self.old_state == 'present':
|
||||
if self.new_name is None:
|
||||
self._delete_project()
|
||||
else:
|
||||
self.module.fail_json(
|
||||
msg='new_name must not be set when the project exists and the specified state is absent',
|
||||
changed=False)
|
||||
|
||||
def _create_project(self):
|
||||
config = self.config.copy()
|
||||
config['name'] = self.name
|
||||
self.client.do('POST', '/1.0/projects', config)
|
||||
self.actions.append('create')
|
||||
|
||||
def _rename_project(self):
|
||||
config = {'name': self.new_name}
|
||||
self.client.do('POST', '/1.0/projects/{0}'.format(self.name), config)
|
||||
self.actions.append('rename')
|
||||
self.name = self.new_name
|
||||
|
||||
def _needs_to_change_project_config(self, key):
|
||||
if key not in self.config:
|
||||
return False
|
||||
old_configs = self.old_project_json['metadata'].get(key, None)
|
||||
return self.config[key] != old_configs
|
||||
|
||||
def _needs_to_apply_project_configs(self):
|
||||
return (
|
||||
self._needs_to_change_project_config('config') or
|
||||
self._needs_to_change_project_config('description')
|
||||
)
|
||||
|
||||
def _merge_dicts(self, source, destination):
|
||||
""" Return a new dict taht merge two dict,
|
||||
with values in source dict overwrite destination dict
|
||||
|
||||
Args:
|
||||
dict(source): source dict
|
||||
dict(destination): destination dict
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
dict(destination): merged dict"""
|
||||
result = destination.copy()
|
||||
for key, value in source.items():
|
||||
if isinstance(value, dict):
|
||||
# get node or create one
|
||||
node = result.setdefault(key, {})
|
||||
self._merge_dicts(value, node)
|
||||
else:
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
def _apply_project_configs(self):
|
||||
""" Selection of the procedure: rebuild or merge
|
||||
|
||||
The standard behavior is that all information not contained
|
||||
in the play is discarded.
|
||||
|
||||
If "merge_project" is provides in the play and "True", then existing
|
||||
configurations from the project and new ones defined are merged.
|
||||
|
||||
Args:
|
||||
None
|
||||
Kwargs:
|
||||
None
|
||||
Raises:
|
||||
None
|
||||
Returns:
|
||||
None"""
|
||||
old_config = dict()
|
||||
old_metadata = self.old_project_json['metadata'].copy()
|
||||
for attr in CONFIG_PARAMS:
|
||||
old_config[attr] = old_metadata[attr]
|
||||
|
||||
if self.module.params['merge_project']:
|
||||
config = self._merge_dicts(self.config, old_config)
|
||||
if config == old_config:
|
||||
# no need to call api if merged config is the same
|
||||
# as old config
|
||||
return
|
||||
else:
|
||||
config = self.config.copy()
|
||||
# upload config to lxd
|
||||
self.client.do('PUT', '/1.0/projects/{0}'.format(self.name), config)
|
||||
self.actions.append('apply_projects_configs')
|
||||
|
||||
def _delete_project(self):
|
||||
self.client.do('DELETE', '/1.0/projects/{0}'.format(self.name))
|
||||
self.actions.append('delete')
|
||||
|
||||
def run(self):
|
||||
"""Run the main method."""
|
||||
|
||||
try:
|
||||
if self.trust_password is not None:
|
||||
self.client.authenticate(self.trust_password)
|
||||
|
||||
self.old_project_json = self._get_project_json()
|
||||
self.old_state = self._project_json_to_module_state(
|
||||
self.old_project_json)
|
||||
self._update_project()
|
||||
|
||||
state_changed = len(self.actions) > 0
|
||||
result_json = {
|
||||
'changed': state_changed,
|
||||
'old_state': self.old_state,
|
||||
'actions': self.actions
|
||||
}
|
||||
if self.client.debug:
|
||||
result_json['logs'] = self.client.logs
|
||||
self.module.exit_json(**result_json)
|
||||
except LXDClientException as e:
|
||||
state_changed = len(self.actions) > 0
|
||||
fail_params = {
|
||||
'msg': e.msg,
|
||||
'changed': state_changed,
|
||||
'actions': self.actions
|
||||
}
|
||||
if self.client.debug:
|
||||
fail_params['logs'] = e.kwargs['logs']
|
||||
self.module.fail_json(**fail_params)
|
||||
|
||||
|
||||
def main():
|
||||
"""Ansible Main module."""
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(
|
||||
type='str',
|
||||
required=True
|
||||
),
|
||||
new_name=dict(
|
||||
type='str',
|
||||
),
|
||||
config=dict(
|
||||
type='dict',
|
||||
),
|
||||
description=dict(
|
||||
type='str',
|
||||
),
|
||||
merge_project=dict(
|
||||
type='bool',
|
||||
default=False
|
||||
),
|
||||
state=dict(
|
||||
choices=PROJECTS_STATES,
|
||||
default='present'
|
||||
),
|
||||
url=dict(
|
||||
type='str',
|
||||
default=ANSIBLE_LXD_DEFAULT_URL
|
||||
),
|
||||
snap_url=dict(
|
||||
type='str',
|
||||
default='unix:/var/snap/lxd/common/lxd/unix.socket'
|
||||
),
|
||||
client_key=dict(
|
||||
type='path',
|
||||
aliases=['key_file']
|
||||
),
|
||||
client_cert=dict(
|
||||
type='path',
|
||||
aliases=['cert_file']
|
||||
),
|
||||
trust_password=dict(type='str', no_log=True)
|
||||
),
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
lxd_manage = LXDProjectManagement(module=module)
|
||||
lxd_manage.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -564,7 +564,7 @@ def main():
|
||||
force=dict(type='bool', default=False),
|
||||
purge=dict(type='bool', default=False),
|
||||
state=dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted']),
|
||||
pubkey=dict(type='str', default=None),
|
||||
pubkey=dict(type='str'),
|
||||
unprivileged=dict(type='bool', default=False),
|
||||
description=dict(type='str'),
|
||||
hookscript=dict(type='str'),
|
||||
|
||||
@@ -1397,7 +1397,7 @@ def main():
|
||||
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
||||
vm = proxmox.get_vm(vmid)
|
||||
if not name:
|
||||
name = vm['name']
|
||||
name = vm.get('name', '(unnamed)')
|
||||
current = proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).status.current.get()['status']
|
||||
status['status'] = current
|
||||
if status:
|
||||
|
||||
@@ -13,7 +13,7 @@ module: proxmox_snap
|
||||
short_description: Snapshot management of instances in Proxmox VE cluster
|
||||
version_added: 2.0.0
|
||||
description:
|
||||
- Allows you to create/delete snapshots from instances in Proxmox VE cluster.
|
||||
- Allows you to create/delete/restore snapshots from instances in Proxmox VE cluster.
|
||||
- Supports both KVM and LXC, OpenVZ has not been tested, as it is no longer supported on Proxmox VE.
|
||||
options:
|
||||
hostname:
|
||||
@@ -28,7 +28,8 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the instance snapshot.
|
||||
choices: ['present', 'absent']
|
||||
- The C(rollback) value was added in community.general 4.8.0.
|
||||
choices: ['present', 'absent', 'rollback']
|
||||
default: present
|
||||
type: str
|
||||
force:
|
||||
@@ -53,7 +54,7 @@ options:
|
||||
type: int
|
||||
snapname:
|
||||
description:
|
||||
- Name of the snapshot that has to be created.
|
||||
- Name of the snapshot that has to be created/deleted/restored.
|
||||
default: 'ansible_snap'
|
||||
type: str
|
||||
|
||||
@@ -84,6 +85,15 @@ EXAMPLES = r'''
|
||||
vmid: 100
|
||||
state: absent
|
||||
snapname: pre-updates
|
||||
|
||||
- name: Rollback container snapshot
|
||||
community.general.proxmox_snap:
|
||||
api_user: root@pam
|
||||
api_password: 1q2w3e
|
||||
api_host: node1
|
||||
vmid: 100
|
||||
state: rollback
|
||||
snapname: pre-updates
|
||||
'''
|
||||
|
||||
RETURN = r'''#'''
|
||||
@@ -109,15 +119,15 @@ class ProxmoxSnapAnsible(ProxmoxAnsible):
|
||||
else:
|
||||
taskid = self.snapshot(vm, vmid).post(snapname=snapname, description=description, vmstate=int(vmstate))
|
||||
while timeout:
|
||||
if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and
|
||||
self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'):
|
||||
status_data = self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()
|
||||
if status_data['status'] == 'stopped' and status_data['exitstatus'] == 'OK':
|
||||
return True
|
||||
timeout -= 1
|
||||
if timeout == 0:
|
||||
self.module.fail_json(msg='Reached timeout while waiting for creating VM snapshot. Last line in task before timeout: %s' %
|
||||
self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1])
|
||||
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
return False
|
||||
|
||||
def snapshot_remove(self, vm, vmid, timeout, snapname, force):
|
||||
@@ -126,15 +136,32 @@ class ProxmoxSnapAnsible(ProxmoxAnsible):
|
||||
|
||||
taskid = self.snapshot(vm, vmid).delete(snapname, force=int(force))
|
||||
while timeout:
|
||||
if (self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['status'] == 'stopped' and
|
||||
self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'):
|
||||
status_data = self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()
|
||||
if status_data['status'] == 'stopped' and status_data['exitstatus'] == 'OK':
|
||||
return True
|
||||
timeout -= 1
|
||||
if timeout == 0:
|
||||
self.module.fail_json(msg='Reached timeout while waiting for removing VM snapshot. Last line in task before timeout: %s' %
|
||||
self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1])
|
||||
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
return False
|
||||
|
||||
def snapshot_rollback(self, vm, vmid, timeout, snapname):
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
|
||||
taskid = self.snapshot(vm, vmid)(snapname).post("rollback")
|
||||
while timeout:
|
||||
status_data = self.proxmox_api.nodes(vm['node']).tasks(taskid).status.get()
|
||||
if status_data['status'] == 'stopped' and status_data['exitstatus'] == 'OK':
|
||||
return True
|
||||
if timeout == 0:
|
||||
self.module.fail_json(msg='Reached timeout while waiting for rolling back VM snapshot. Last line in task before timeout: %s' %
|
||||
self.proxmox_api.nodes(vm['node']).tasks(taskid).log.get()[:1])
|
||||
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
return False
|
||||
|
||||
|
||||
@@ -144,7 +171,7 @@ def main():
|
||||
vmid=dict(required=False),
|
||||
hostname=dict(),
|
||||
timeout=dict(type='int', default=30),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
state=dict(default='present', choices=['present', 'absent', 'rollback']),
|
||||
description=dict(type='str'),
|
||||
snapname=dict(type='str', default='ansible_snap'),
|
||||
force=dict(type='bool', default='no'),
|
||||
@@ -211,6 +238,25 @@ def main():
|
||||
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Removing snapshot %s of VM %s failed with exception: %s" % (snapname, vmid, to_native(e)))
|
||||
elif state == 'rollback':
|
||||
try:
|
||||
snap_exist = False
|
||||
|
||||
for i in proxmox.snapshot(vm, vmid).get():
|
||||
if i['name'] == snapname:
|
||||
snap_exist = True
|
||||
continue
|
||||
|
||||
if not snap_exist:
|
||||
module.exit_json(changed=False, msg="Snapshot %s does not exist" % snapname)
|
||||
if proxmox.snapshot_rollback(vm, vmid, timeout, snapname):
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, msg="Snapshot %s would be rolled back" % snapname)
|
||||
else:
|
||||
module.exit_json(changed=True, msg="Snapshot %s rolled back" % snapname)
|
||||
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Rollback of snapshot %s of VM %s failed with exception: %s" % (snapname, vmid, to_native(e)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -124,6 +124,12 @@ options:
|
||||
type: list
|
||||
elements: path
|
||||
version_added: '0.2.0'
|
||||
provider_upgrade:
|
||||
description:
|
||||
- Allows Terraform init to upgrade providers to versions specified in the project's version constraints.
|
||||
default: false
|
||||
type: bool
|
||||
version_added: 4.8.0
|
||||
init_reconfigure:
|
||||
description:
|
||||
- Forces backend reconfiguration during init.
|
||||
@@ -266,7 +272,7 @@ def _state_args(state_file):
|
||||
return []
|
||||
|
||||
|
||||
def init_plugins(bin_path, project_path, backend_config, backend_config_files, init_reconfigure, plugin_paths):
|
||||
def init_plugins(bin_path, project_path, backend_config, backend_config_files, init_reconfigure, provider_upgrade, plugin_paths):
|
||||
command = [bin_path, 'init', '-input=false']
|
||||
if backend_config:
|
||||
for key, val in backend_config.items():
|
||||
@@ -279,6 +285,8 @@ def init_plugins(bin_path, project_path, backend_config, backend_config_files, i
|
||||
command.extend(['-backend-config', f])
|
||||
if init_reconfigure:
|
||||
command.extend(['-reconfigure'])
|
||||
if provider_upgrade:
|
||||
command.extend(['-upgrade'])
|
||||
if plugin_paths:
|
||||
for plugin_path in plugin_paths:
|
||||
command.extend(['-plugin-dir', plugin_path])
|
||||
@@ -324,7 +332,7 @@ def build_plan(command, project_path, variables_args, state_file, targets, state
|
||||
if plan_path is None:
|
||||
f, plan_path = tempfile.mkstemp(suffix='.tfplan')
|
||||
|
||||
local_command = command.copy()
|
||||
local_command = command[:]
|
||||
|
||||
plan_command = [command[0], 'plan']
|
||||
|
||||
@@ -384,6 +392,7 @@ def main():
|
||||
overwrite_init=dict(type='bool', default=True),
|
||||
check_destroy=dict(type='bool', default=False),
|
||||
parallelism=dict(type='int'),
|
||||
provider_upgrade=dict(type='bool', default=False),
|
||||
),
|
||||
required_if=[('state', 'planned', ['plan_file'])],
|
||||
supports_check_mode=True,
|
||||
@@ -405,6 +414,7 @@ def main():
|
||||
init_reconfigure = module.params.get('init_reconfigure')
|
||||
overwrite_init = module.params.get('overwrite_init')
|
||||
check_destroy = module.params.get('check_destroy')
|
||||
provider_upgrade = module.params.get('provider_upgrade')
|
||||
|
||||
if bin_path is not None:
|
||||
command = [bin_path]
|
||||
@@ -422,7 +432,7 @@ def main():
|
||||
|
||||
if force_init:
|
||||
if overwrite_init or not os.path.isfile(os.path.join(project_path, ".terraform", "terraform.tfstate")):
|
||||
init_plugins(command[0], project_path, backend_config, backend_config_files, init_reconfigure, plugin_paths)
|
||||
init_plugins(command[0], project_path, backend_config, backend_config_files, init_reconfigure, provider_upgrade, plugin_paths)
|
||||
|
||||
workspace_ctx = get_workspace_context(command[0], project_path)
|
||||
if workspace_ctx["current"] != workspace:
|
||||
|
||||
@@ -630,7 +630,7 @@ def main():
|
||||
plan=dict(),
|
||||
project_id=dict(required=True),
|
||||
state=dict(choices=ALLOWED_STATES, default='present'),
|
||||
user_data=dict(default=None),
|
||||
user_data=dict(),
|
||||
wait_for_public_IPv=dict(type='int', choices=[4, 6]),
|
||||
wait_timeout=dict(type='int', default=900),
|
||||
ipxe_script_url=dict(default=''),
|
||||
|
||||
@@ -226,11 +226,11 @@ def main():
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
auth_token=dict(default=os.environ.get(PACKET_API_TOKEN_ENV_VAR),
|
||||
no_log=True),
|
||||
label=dict(type='str', aliases=['name'], default=None),
|
||||
id=dict(type='str', default=None),
|
||||
fingerprint=dict(type='str', default=None),
|
||||
key=dict(type='str', default=None, no_log=True),
|
||||
key_file=dict(type='path', default=None),
|
||||
label=dict(type='str', aliases=['name']),
|
||||
id=dict(type='str'),
|
||||
fingerprint=dict(type='str'),
|
||||
key=dict(type='str', no_log=True),
|
||||
key_file=dict(type='path'),
|
||||
),
|
||||
mutually_exclusive=[
|
||||
('label', 'id'),
|
||||
|
||||
@@ -263,9 +263,9 @@ def act_on_volume(target_state, module, packet_conn):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
id=dict(type='str', default=None),
|
||||
description=dict(type="str", default=None),
|
||||
name=dict(type='str', default=None),
|
||||
id=dict(type='str'),
|
||||
description=dict(type="str"),
|
||||
name=dict(type='str'),
|
||||
state=dict(choices=VOLUME_STATES, default="present"),
|
||||
auth_token=dict(
|
||||
type='str',
|
||||
@@ -277,7 +277,7 @@ def main():
|
||||
facility=dict(type="str"),
|
||||
size=dict(type="int"),
|
||||
locked=dict(type="bool", default=False),
|
||||
snapshot_policy=dict(type='dict', default=None),
|
||||
snapshot_policy=dict(type='dict'),
|
||||
billing_cycle=dict(type='str', choices=BILLING, default="hourly"),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
|
||||
@@ -583,7 +583,7 @@ def main():
|
||||
default='AMD_OPTERON'),
|
||||
volume_size=dict(type='int', default=10),
|
||||
disk_type=dict(choices=['HDD', 'SSD'], default='HDD'),
|
||||
image_password=dict(default=None, no_log=True),
|
||||
image_password=dict(no_log=True),
|
||||
ssh_keys=dict(type='list', elements='str', default=[], no_log=False),
|
||||
bus=dict(choices=['VIRTIO', 'IDE'], default='VIRTIO'),
|
||||
lan=dict(type='int', default=1),
|
||||
|
||||
@@ -95,7 +95,7 @@ class ImageFacts(object):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
filters=dict(default=None),
|
||||
filters=dict(),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -684,7 +684,7 @@ def main():
|
||||
choices=['present', 'running', 'absent', 'deleted', 'stopped', 'created', 'restarted', 'rebooted']
|
||||
),
|
||||
name=dict(
|
||||
default=None, type='str',
|
||||
type='str',
|
||||
aliases=['alias']
|
||||
),
|
||||
brand=dict(
|
||||
@@ -709,7 +709,7 @@ def main():
|
||||
# Add our 'simple' options to options dict.
|
||||
for type in properties:
|
||||
for p in properties[type]:
|
||||
option = dict(default=None, type=type)
|
||||
option = dict(type=type)
|
||||
options[p] = option
|
||||
|
||||
module = AnsibleModule(
|
||||
|
||||
@@ -95,8 +95,7 @@ def main():
|
||||
argument_spec=dict(
|
||||
name=dict(required=True,
|
||||
type='str'),
|
||||
description=dict(default=None,
|
||||
type='str'),
|
||||
description=dict(type='str'),
|
||||
position=dict(default='',
|
||||
type='str'),
|
||||
ou=dict(default='',
|
||||
|
||||
@@ -354,12 +354,10 @@ def main():
|
||||
default='0'),
|
||||
group=dict(type='str',
|
||||
default='0'),
|
||||
path=dict(type='path',
|
||||
default=None),
|
||||
path=dict(type='path'),
|
||||
directorymode=dict(type='str',
|
||||
default='00755'),
|
||||
host=dict(type='str',
|
||||
default=None),
|
||||
host=dict(type='str'),
|
||||
root_squash=dict(type='bool',
|
||||
default=True),
|
||||
subtree_checking=dict(type='bool',
|
||||
@@ -369,8 +367,7 @@ def main():
|
||||
writeable=dict(type='bool',
|
||||
default=True),
|
||||
sambaBlockSize=dict(type='str',
|
||||
aliases=['samba_block_size'],
|
||||
default=None),
|
||||
aliases=['samba_block_size']),
|
||||
sambaBlockingLocks=dict(type='bool',
|
||||
aliases=['samba_blocking_locks'],
|
||||
default=True),
|
||||
@@ -408,17 +405,14 @@ def main():
|
||||
aliases=['samba_force_directory_security_mode'],
|
||||
default=False),
|
||||
sambaForceGroup=dict(type='str',
|
||||
aliases=['samba_force_group'],
|
||||
default=None),
|
||||
aliases=['samba_force_group']),
|
||||
sambaForceSecurityMode=dict(type='bool',
|
||||
aliases=['samba_force_security_mode'],
|
||||
default=False),
|
||||
sambaForceUser=dict(type='str',
|
||||
aliases=['samba_force_user'],
|
||||
default=None),
|
||||
aliases=['samba_force_user']),
|
||||
sambaHideFiles=dict(type='str',
|
||||
aliases=['samba_hide_files'],
|
||||
default=None),
|
||||
aliases=['samba_hide_files']),
|
||||
sambaHideUnreadable=dict(type='bool',
|
||||
aliases=['samba_hide_unreadable'],
|
||||
default=False),
|
||||
@@ -438,8 +432,7 @@ def main():
|
||||
aliases=['samba_inherit_permissions'],
|
||||
default=False),
|
||||
sambaInvalidUsers=dict(type='str',
|
||||
aliases=['samba_invalid_users'],
|
||||
default=None),
|
||||
aliases=['samba_invalid_users']),
|
||||
sambaLevel2Oplocks=dict(type='bool',
|
||||
aliases=['samba_level_2_oplocks'],
|
||||
default=True),
|
||||
@@ -450,8 +443,7 @@ def main():
|
||||
aliases=['samba_msdfs_root'],
|
||||
default=False),
|
||||
sambaName=dict(type='str',
|
||||
aliases=['samba_name'],
|
||||
default=None),
|
||||
aliases=['samba_name']),
|
||||
sambaNtAclSupport=dict(type='bool',
|
||||
aliases=['samba_nt_acl_support'],
|
||||
default=True),
|
||||
@@ -459,11 +451,9 @@ def main():
|
||||
aliases=['samba_oplocks'],
|
||||
default=True),
|
||||
sambaPostexec=dict(type='str',
|
||||
aliases=['samba_postexec'],
|
||||
default=None),
|
||||
aliases=['samba_postexec']),
|
||||
sambaPreexec=dict(type='str',
|
||||
aliases=['samba_preexec'],
|
||||
default=None),
|
||||
aliases=['samba_preexec']),
|
||||
sambaPublic=dict(type='bool',
|
||||
aliases=['samba_public'],
|
||||
default=False),
|
||||
@@ -474,14 +464,11 @@ def main():
|
||||
aliases=['samba_strict_locking'],
|
||||
default='Auto'),
|
||||
sambaVFSObjects=dict(type='str',
|
||||
aliases=['samba_vfs_objects'],
|
||||
default=None),
|
||||
aliases=['samba_vfs_objects']),
|
||||
sambaValidUsers=dict(type='str',
|
||||
aliases=['samba_valid_users'],
|
||||
default=None),
|
||||
aliases=['samba_valid_users']),
|
||||
sambaWriteList=dict(type='str',
|
||||
aliases=['samba_write_list'],
|
||||
default=None),
|
||||
aliases=['samba_write_list']),
|
||||
sambaWriteable=dict(type='bool',
|
||||
aliases=['samba_writeable'],
|
||||
default=True),
|
||||
|
||||
@@ -110,14 +110,14 @@ def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(required=True),
|
||||
state=dict(required=False, choices=['present', 'absent'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
type=dict(required=True),
|
||||
autostart=dict(required=False, type='bool', default=False),
|
||||
extra_info=dict(required=False, default=""),
|
||||
port_open=dict(required=False, type='bool', default=False),
|
||||
autostart=dict(type='bool', default=False),
|
||||
extra_info=dict(default=""),
|
||||
port_open=dict(type='bool', default=False),
|
||||
login_name=dict(required=True),
|
||||
login_password=dict(required=True, no_log=True),
|
||||
machine=dict(required=False, default=None),
|
||||
machine=dict(),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
@@ -101,13 +101,13 @@ def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(required=True),
|
||||
state=dict(required=False, choices=['present', 'absent'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
# You can specify an IP address or hostname.
|
||||
type=dict(required=True, choices=['mysql', 'postgresql']),
|
||||
password=dict(required=False, default=None, no_log=True),
|
||||
password=dict(no_log=True),
|
||||
login_name=dict(required=True),
|
||||
login_password=dict(required=True, no_log=True),
|
||||
machine=dict(required=False, default=None),
|
||||
machine=dict(),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
@@ -411,7 +411,7 @@ class ConsulService(object):
|
||||
self.address = address
|
||||
self.port = port
|
||||
self.tags = tags
|
||||
self.checks = []
|
||||
self._checks = []
|
||||
if loaded:
|
||||
self.id = loaded['ID']
|
||||
self.name = loaded['Service']
|
||||
@@ -424,8 +424,8 @@ class ConsulService(object):
|
||||
if self.port:
|
||||
optional['port'] = self.port
|
||||
|
||||
if len(self.checks) > 0:
|
||||
optional['check'] = self.checks[0].check
|
||||
if len(self._checks) > 0:
|
||||
optional['check'] = self._checks[0].check
|
||||
|
||||
consul_api.agent.service.register(
|
||||
self.name,
|
||||
@@ -435,13 +435,13 @@ class ConsulService(object):
|
||||
**optional)
|
||||
|
||||
def add_check(self, check):
|
||||
self.checks.append(check)
|
||||
self._checks.append(check)
|
||||
|
||||
def checks(self):
|
||||
return self.checks
|
||||
return self._checks
|
||||
|
||||
def has_checks(self):
|
||||
return len(self.checks) > 0
|
||||
return len(self._checks) > 0
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, self.__class__) and
|
||||
@@ -459,8 +459,8 @@ class ConsulService(object):
|
||||
data['port'] = self.port
|
||||
if self.tags and len(self.tags) > 0:
|
||||
data['tags'] = self.tags
|
||||
if len(self.checks) > 0:
|
||||
data['check'] = self.checks[0].to_dict()
|
||||
if len(self._checks) > 0:
|
||||
data['check'] = self._checks[0].to_dict()
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -102,14 +102,14 @@ def run():
|
||||
use_ssl=dict(type='bool', default=True),
|
||||
timeout=dict(type='int', default=5),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
client_cert=dict(type='path', default=None),
|
||||
client_key=dict(type='path', default=None),
|
||||
namespace=dict(type='str', default=None),
|
||||
name=dict(type='str', default=None),
|
||||
client_cert=dict(type='path'),
|
||||
client_key=dict(type='path'),
|
||||
namespace=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
content_format=dict(choices=['hcl', 'json'], default='hcl'),
|
||||
content=dict(type='str', default=None),
|
||||
content=dict(type='str'),
|
||||
force_start=dict(type='bool', default=False),
|
||||
token=dict(type='str', default=None, no_log=True)
|
||||
token=dict(type='str', no_log=True)
|
||||
),
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=[
|
||||
|
||||
@@ -287,11 +287,11 @@ def run():
|
||||
use_ssl=dict(type='bool', default=True),
|
||||
timeout=dict(type='int', default=5),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
client_cert=dict(type='path', default=None),
|
||||
client_key=dict(type='path', default=None),
|
||||
namespace=dict(type='str', default=None),
|
||||
name=dict(type='str', default=None),
|
||||
token=dict(type='str', default=None, no_log=True)
|
||||
client_cert=dict(type='path'),
|
||||
client_key=dict(type='path'),
|
||||
namespace=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
token=dict(type='str', no_log=True)
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
@@ -48,19 +48,19 @@ options:
|
||||
delimiter:
|
||||
description:
|
||||
- A one-character string used to separate fields.
|
||||
- When using this parameter, you change the default value used by C(dialect).
|
||||
- When using this parameter, you change the default value used by I(dialect).
|
||||
- The default value depends on the dialect used.
|
||||
type: str
|
||||
skipinitialspace:
|
||||
description:
|
||||
- Whether to ignore any whitespaces immediately following the delimiter.
|
||||
- When using this parameter, you change the default value used by C(dialect).
|
||||
- When using this parameter, you change the default value used by I(dialect).
|
||||
- The default value depends on the dialect used.
|
||||
type: bool
|
||||
strict:
|
||||
description:
|
||||
- Whether to raise an exception on bad CSV input.
|
||||
- When using this parameter, you change the default value used by C(dialect).
|
||||
- When using this parameter, you change the default value used by I(dialect).
|
||||
- The default value depends on the dialect used.
|
||||
type: bool
|
||||
notes:
|
||||
|
||||
@@ -156,7 +156,7 @@ options:
|
||||
aliases:
|
||||
- defaultDefaultClientScopes
|
||||
type: list
|
||||
elements: dict
|
||||
elements: str
|
||||
default_groups:
|
||||
description:
|
||||
- The realm default groups.
|
||||
@@ -176,7 +176,7 @@ options:
|
||||
aliases:
|
||||
- defaultOptionalClientScopes
|
||||
type: list
|
||||
elements: dict
|
||||
elements: str
|
||||
default_roles:
|
||||
description:
|
||||
- The realm default roles.
|
||||
@@ -621,10 +621,10 @@ def main():
|
||||
brute_force_protected=dict(type='bool', aliases=['bruteForceProtected']),
|
||||
client_authentication_flow=dict(type='str', aliases=['clientAuthenticationFlow']),
|
||||
client_scope_mappings=dict(type='dict', aliases=['clientScopeMappings']),
|
||||
default_default_client_scopes=dict(type='list', elements='dict', aliases=['defaultDefaultClientScopes']),
|
||||
default_default_client_scopes=dict(type='list', elements='str', aliases=['defaultDefaultClientScopes']),
|
||||
default_groups=dict(type='list', elements='dict', aliases=['defaultGroups']),
|
||||
default_locale=dict(type='str', aliases=['defaultLocale']),
|
||||
default_optional_client_scopes=dict(type='list', elements='dict', aliases=['defaultOptionalClientScopes']),
|
||||
default_optional_client_scopes=dict(type='list', elements='str', aliases=['defaultOptionalClientScopes']),
|
||||
default_roles=dict(type='list', elements='dict', aliases=['defaultRoles']),
|
||||
default_signature_algorithm=dict(type='str', aliases=['defaultSignatureAlgorithm']),
|
||||
direct_grant_flow=dict(type='str', aliases=['directGrantFlow']),
|
||||
|
||||
@@ -166,6 +166,8 @@ from subprocess import Popen, PIPE
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.onepassword import OnePasswordConfig
|
||||
|
||||
|
||||
class AnsibleModuleError(Exception):
|
||||
def __init__(self, results):
|
||||
@@ -179,7 +181,6 @@ class OnePasswordInfo(object):
|
||||
|
||||
def __init__(self):
|
||||
self.cli_path = module.params.get('cli_path')
|
||||
self.config_file_path = '~/.op/config'
|
||||
self.auto_login = module.params.get('auto_login')
|
||||
self.logged_in = False
|
||||
self.token = None
|
||||
@@ -187,6 +188,8 @@ class OnePasswordInfo(object):
|
||||
terms = module.params.get('search_terms')
|
||||
self.terms = self.parse_search_terms(terms)
|
||||
|
||||
self._config = OnePasswordConfig()
|
||||
|
||||
def _run(self, args, expected_rc=0, command_input=None, ignore_errors=False):
|
||||
if self.token:
|
||||
# Adds the session token to all commands if we're logged in.
|
||||
@@ -299,7 +302,7 @@ class OnePasswordInfo(object):
|
||||
|
||||
def get_token(self):
|
||||
# If the config file exists, assume an initial signin has taken place and try basic sign in
|
||||
if os.path.isfile(self.config_file_path):
|
||||
if os.path.isfile(self._config.config_file_path):
|
||||
|
||||
if self.auto_login is not None:
|
||||
|
||||
|
||||
1
plugins/modules/lxd_project.py
Symbolic link
1
plugins/modules/lxd_project.py
Symbolic link
@@ -0,0 +1 @@
|
||||
./cloud/lxd/lxd_project.py
|
||||
199
plugins/modules/monitoring/alerta_customer.py
Normal file
199
plugins/modules/monitoring/alerta_customer.py
Normal file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2022, Christian Wollinger <@cwollinger>
|
||||
# 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: alerta_customer
|
||||
short_description: Manage customers in Alerta
|
||||
version_added: 4.8.0
|
||||
description:
|
||||
- Create or delete customers in Alerta with the REST API.
|
||||
author: Christian Wollinger (@cwollinger)
|
||||
seealso:
|
||||
- name: API documentation
|
||||
description: Documentation for Alerta API
|
||||
link: https://docs.alerta.io/api/reference.html#customers
|
||||
options:
|
||||
customer:
|
||||
description:
|
||||
- Name of the customer.
|
||||
required: true
|
||||
type: str
|
||||
match:
|
||||
description:
|
||||
- The matching logged in user for the customer.
|
||||
required: true
|
||||
type: str
|
||||
alerta_url:
|
||||
description:
|
||||
- The Alerta API endpoint.
|
||||
required: true
|
||||
type: str
|
||||
api_username:
|
||||
description:
|
||||
- The username for the API using basic auth.
|
||||
type: str
|
||||
api_password:
|
||||
description:
|
||||
- The password for the API using basic auth.
|
||||
type: str
|
||||
api_key:
|
||||
description:
|
||||
- The access token for the API.
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- Whether the customer should exist or not.
|
||||
- Both I(customer) and I(match) identify a customer that should be added or removed.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create customer
|
||||
community.general.alerta_customer:
|
||||
alerta_url: https://alerta.example.com
|
||||
api_username: admin@example.com
|
||||
api_password: password
|
||||
customer: Developer
|
||||
match: dev@example.com
|
||||
|
||||
- name: Delete customer
|
||||
community.general.alerta_customer:
|
||||
alerta_url: https://alerta.example.com
|
||||
api_username: admin@example.com
|
||||
api_password: password
|
||||
customer: Developer
|
||||
match: dev@example.com
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
msg:
|
||||
description:
|
||||
- Success or failure message.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Customer customer1 created
|
||||
response:
|
||||
description:
|
||||
- The response from the API.
|
||||
returned: always
|
||||
type: dict
|
||||
"""
|
||||
|
||||
from ansible.module_utils.urls import fetch_url, basic_auth_header
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
class AlertaInterface(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.state = module.params['state']
|
||||
self.customer = module.params['customer']
|
||||
self.match = module.params['match']
|
||||
self.alerta_url = module.params['alerta_url']
|
||||
self.headers = {"Content-Type": "application/json"}
|
||||
|
||||
if module.params.get('api_key', None):
|
||||
self.headers["Authorization"] = "Key %s" % module.params['api_key']
|
||||
else:
|
||||
self.headers["Authorization"] = basic_auth_header(module.params['api_username'], module.params['api_password'])
|
||||
|
||||
def send_request(self, url, data=None, method="GET"):
|
||||
response, info = fetch_url(self.module, url, data=data, headers=self.headers, method=method)
|
||||
|
||||
status_code = info["status"]
|
||||
if status_code == 401:
|
||||
self.module.fail_json(failed=True, response=info, msg="Unauthorized to request '%s' on '%s'" % (method, url))
|
||||
elif status_code == 403:
|
||||
self.module.fail_json(failed=True, response=info, msg="Permission Denied for '%s' on '%s'" % (method, url))
|
||||
elif status_code == 404:
|
||||
self.module.fail_json(failed=True, response=info, msg="Not found for request '%s' on '%s'" % (method, url))
|
||||
elif status_code in (200, 201):
|
||||
return self.module.from_json(response.read())
|
||||
self.module.fail_json(failed=True, response=info, msg="Alerta API error with HTTP %d for %s" % (status_code, url))
|
||||
|
||||
def get_customers(self):
|
||||
url = "%s/api/customers" % self.alerta_url
|
||||
response = self.send_request(url)
|
||||
pages = response["pages"]
|
||||
if pages > 1:
|
||||
for page in range(2, pages + 1):
|
||||
page_url = url + '?page=' + str(page)
|
||||
new_results = self.send_request(page_url)
|
||||
response.update(new_results)
|
||||
return response
|
||||
|
||||
def create_customer(self):
|
||||
url = "%s/api/customer" % self.alerta_url
|
||||
|
||||
payload = {
|
||||
'customer': self.customer,
|
||||
'match': self.match,
|
||||
}
|
||||
|
||||
payload = self.module.jsonify(payload)
|
||||
response = self.send_request(url, payload, 'POST')
|
||||
return response
|
||||
|
||||
def delete_customer(self, id):
|
||||
url = "%s/api/customer/%s" % (self.alerta_url, id)
|
||||
|
||||
response = self.send_request(url, None, 'DELETE')
|
||||
return response
|
||||
|
||||
def find_customer_id(self, customer):
|
||||
for i in customer['customers']:
|
||||
if self.customer == i['customer'] and self.match == i['match']:
|
||||
return i['id']
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
customer=dict(type='str', required=True),
|
||||
match=dict(type='str', required=True),
|
||||
alerta_url=dict(type='str', required=True),
|
||||
api_username=dict(type='str'),
|
||||
api_password=dict(type='str', no_log=True),
|
||||
api_key=dict(type='str', no_log=True),
|
||||
),
|
||||
required_together=[['api_username', 'api_password']],
|
||||
mutually_exclusive=[['api_username', 'api_key']],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
alerta_iface = AlertaInterface(module)
|
||||
|
||||
if alerta_iface.state == 'present':
|
||||
response = alerta_iface.get_customers()
|
||||
if alerta_iface.find_customer_id(response):
|
||||
module.exit_json(changed=False, response=response, msg="Customer %s already exists" % alerta_iface.customer)
|
||||
else:
|
||||
if not module.check_mode:
|
||||
response = alerta_iface.create_customer()
|
||||
module.exit_json(changed=True, response=response, msg="Customer %s created" % alerta_iface.customer)
|
||||
else:
|
||||
response = alerta_iface.get_customers()
|
||||
id = alerta_iface.find_customer_id(response)
|
||||
if id:
|
||||
if not module.check_mode:
|
||||
alerta_iface.delete_customer(id)
|
||||
module.exit_json(changed=True, response=response, msg="Customer %s with id %s deleted" % (alerta_iface.customer, id))
|
||||
else:
|
||||
module.exit_json(changed=False, response=response, msg="Customer %s does not exists" % alerta_iface.customer)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -15,6 +15,7 @@ short_description: Manages Datadog monitors
|
||||
description:
|
||||
- Manages monitors within Datadog.
|
||||
- Options as described on https://docs.datadoghq.com/api/.
|
||||
- The type C(event-v2) was added in community.general 4.8.0.
|
||||
author: Sebastian Kornehl (@skornehl)
|
||||
requirements: [datadog]
|
||||
options:
|
||||
@@ -56,6 +57,7 @@ options:
|
||||
- metric alert
|
||||
- service check
|
||||
- event alert
|
||||
- event-v2 alert
|
||||
- process alert
|
||||
- log alert
|
||||
- query alert
|
||||
@@ -222,7 +224,7 @@ def main():
|
||||
api_host=dict(),
|
||||
app_key=dict(required=True, no_log=True),
|
||||
state=dict(required=True, choices=['present', 'absent', 'mute', 'unmute']),
|
||||
type=dict(choices=['metric alert', 'service check', 'event alert', 'process alert',
|
||||
type=dict(choices=['metric alert', 'service check', 'event alert', 'event-v2 alert', 'process alert',
|
||||
'log alert', 'query alert', 'trace-analytics alert',
|
||||
'rum alert', 'composite']),
|
||||
name=dict(required=True),
|
||||
|
||||
@@ -623,7 +623,7 @@ def main():
|
||||
# Fetch existing monitor if the A record indicates it should exist and build the new monitor
|
||||
current_monitor = dict()
|
||||
new_monitor = dict()
|
||||
if current_record and current_record['type'] == 'A':
|
||||
if current_record and current_record['type'] == 'A' and current_record.get('monitor'):
|
||||
current_monitor = DME.getMonitor(current_record['id'])
|
||||
|
||||
# Build the new monitor
|
||||
|
||||
@@ -374,8 +374,9 @@ options:
|
||||
description:
|
||||
- This is only used with 'bridge-slave' - 'hairpin mode' for the slave, which allows frames to be sent back out through the slave the
|
||||
frame was received on.
|
||||
- The default value is C(true), but that is being deprecated
|
||||
and it will be changed to C(false) in community.general 7.0.0.
|
||||
type: bool
|
||||
default: yes
|
||||
runner:
|
||||
description:
|
||||
- This is the type of device or network connection that you wish to create for a team.
|
||||
@@ -1376,7 +1377,8 @@ class Nmcli(object):
|
||||
self.hellotime = module.params['hellotime']
|
||||
self.maxage = module.params['maxage']
|
||||
self.ageingtime = module.params['ageingtime']
|
||||
self.hairpin = module.params['hairpin']
|
||||
# hairpin should be back to normal in 7.0.0
|
||||
self._hairpin = module.params['hairpin']
|
||||
self.path_cost = module.params['path_cost']
|
||||
self.mac = module.params['mac']
|
||||
self.runner = module.params['runner']
|
||||
@@ -1423,6 +1425,18 @@ class Nmcli(object):
|
||||
|
||||
self.edit_commands = []
|
||||
|
||||
@property
|
||||
def hairpin(self):
|
||||
if self._hairpin is None:
|
||||
self.module.deprecate(
|
||||
"Parameter 'hairpin' default value will change from true to false in community.general 7.0.0. "
|
||||
"Set the value explicitly to supress this warning.",
|
||||
version='7.0.0', collection_name='community.general',
|
||||
)
|
||||
# Should be False in 7.0.0 but then that should be in argument_specs
|
||||
self._hairpin = True
|
||||
return self._hairpin
|
||||
|
||||
def execute_command(self, cmd, use_unsafe_shell=False, data=None):
|
||||
if isinstance(cmd, list):
|
||||
cmd = [to_text(item) for item in cmd]
|
||||
@@ -2119,7 +2133,7 @@ def main():
|
||||
hellotime=dict(type='int', default=2),
|
||||
maxage=dict(type='int', default=20),
|
||||
ageingtime=dict(type='int', default=300),
|
||||
hairpin=dict(type='bool', default=True),
|
||||
hairpin=dict(type='bool'),
|
||||
path_cost=dict(type='int', default=100),
|
||||
# team specific vars
|
||||
runner=dict(type='str', default='roundrobin',
|
||||
|
||||
@@ -72,8 +72,8 @@ options:
|
||||
default: false
|
||||
description:
|
||||
- Avoid loading any C(.gemrc) file. Ignored for RubyGems prior to 2.5.2.
|
||||
- "The current default value will be deprecated in community.general 4.0.0: if the value is not explicitly specified, a deprecation message will be shown."
|
||||
- From community.general 5.0.0 on, the default will be changed to C(true).
|
||||
- "The current default value will be deprecated in community.general 5.0.0: if the value is not explicitly specified, a deprecation message will be shown."
|
||||
- From community.general 6.0.0 on, the default will be changed to C(true).
|
||||
version_added: 3.3.0
|
||||
env_shebang:
|
||||
description:
|
||||
|
||||
@@ -610,8 +610,9 @@ class Pacman(object):
|
||||
# Expand group members
|
||||
for group_member in self.inventory["available_groups"][pkg]:
|
||||
pkg_list.append(Package(name=group_member, source=group_member))
|
||||
elif pkg in self.inventory["available_pkgs"]:
|
||||
# just a regular pkg
|
||||
elif pkg in self.inventory["available_pkgs"] or pkg in self.inventory["installed_pkgs"]:
|
||||
# Just a regular pkg, either available in the repositories,
|
||||
# or locally installed, which we need to know for absent state
|
||||
pkg_list.append(Package(name=pkg, source=pkg))
|
||||
else:
|
||||
# Last resort, call out to pacman to extract the info,
|
||||
|
||||
@@ -230,7 +230,9 @@ def install_packages(module, xbps_path, state, packages):
|
||||
module.params['upgrade_xbps'] = False
|
||||
install_packages(module, xbps_path, state, packages)
|
||||
elif rc != 0 and not (state == 'latest' and rc == 17):
|
||||
module.fail_json(msg="failed to install %s" % (package))
|
||||
module.fail_json(msg="failed to install %s packages(s)"
|
||||
% (len(toInstall)),
|
||||
packages=toInstall)
|
||||
|
||||
module.exit_json(changed=True, msg="installed %s package(s)"
|
||||
% (len(toInstall)),
|
||||
|
||||
@@ -318,6 +318,14 @@ EXAMPLES = '''
|
||||
category: Systems
|
||||
command: DisableBootOverride
|
||||
|
||||
- name: Set system indicator LED to blink using security token for auth
|
||||
community.general.redfish_command:
|
||||
category: Systems
|
||||
command: IndicatorLedBlink
|
||||
resource_id: 437XR1138R2
|
||||
baseuri: "{{ baseuri }}"
|
||||
auth_token: "{{ result.session.token }}"
|
||||
|
||||
- name: Add user
|
||||
community.general.redfish_command:
|
||||
category: Accounts
|
||||
@@ -583,7 +591,8 @@ from ansible.module_utils.common.text.converters import to_native
|
||||
# More will be added as module features are expanded
|
||||
CATEGORY_COMMANDS_ALL = {
|
||||
"Systems": ["PowerOn", "PowerForceOff", "PowerForceRestart", "PowerGracefulRestart",
|
||||
"PowerGracefulShutdown", "PowerReboot", "SetOneTimeBoot", "EnableContinuousBootOverride", "DisableBootOverride"],
|
||||
"PowerGracefulShutdown", "PowerReboot", "SetOneTimeBoot", "EnableContinuousBootOverride", "DisableBootOverride",
|
||||
"IndicatorLedOn", "IndicatorLedOff", "IndicatorLedBlink"],
|
||||
"Chassis": ["IndicatorLedOn", "IndicatorLedOff", "IndicatorLedBlink"],
|
||||
"Accounts": ["AddUser", "EnableUser", "DeleteUser", "DisableUser",
|
||||
"UpdateUserRole", "UpdateUserPassword", "UpdateUserName",
|
||||
@@ -754,6 +763,8 @@ def main():
|
||||
elif command == "DisableBootOverride":
|
||||
boot_opts['override_enabled'] = 'Disabled'
|
||||
result = rf_utils.set_boot_override(boot_opts)
|
||||
elif command.startswith('IndicatorLed'):
|
||||
result = rf_utils.manage_system_indicator_led(command)
|
||||
|
||||
elif category == "Chassis":
|
||||
result = rf_utils._find_chassis_resource()
|
||||
@@ -769,7 +780,7 @@ def main():
|
||||
else:
|
||||
for command in command_list:
|
||||
if command in led_commands:
|
||||
result = rf_utils.manage_indicator_led(command)
|
||||
result = rf_utils.manage_chassis_indicator_led(command)
|
||||
|
||||
elif category == "Sessions":
|
||||
# execute only if we find SessionService resources
|
||||
|
||||
@@ -110,7 +110,6 @@ class GitlabBranch(object):
|
||||
return self.project.branches.create({'branch': branch, 'ref': ref_branch})
|
||||
|
||||
def delete_branch(self, branch):
|
||||
branch.unprotect()
|
||||
return branch.delete()
|
||||
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ class GitLabGroup(object):
|
||||
def delete_group(self):
|
||||
group = self.group_object
|
||||
|
||||
if len(group.projects.list()) >= 1:
|
||||
if len(group.projects.list(all=False)) >= 1:
|
||||
self._module.fail_json(
|
||||
msg="There are still projects in this group. These needs to be moved or deleted before this group can be removed.")
|
||||
else:
|
||||
|
||||
@@ -172,13 +172,13 @@ class GitLabGroup(object):
|
||||
|
||||
# get user id if the user exists
|
||||
def get_user_id(self, gitlab_user):
|
||||
user_exists = self._gitlab.users.list(username=gitlab_user)
|
||||
user_exists = self._gitlab.users.list(username=gitlab_user, all=True)
|
||||
if user_exists:
|
||||
return user_exists[0].id
|
||||
|
||||
# get group id if group exists
|
||||
def get_group_id(self, gitlab_group):
|
||||
groups = self._gitlab.groups.list(search=gitlab_group)
|
||||
groups = self._gitlab.groups.list(search=gitlab_group, all=True)
|
||||
for group in groups:
|
||||
if group.full_path == gitlab_group:
|
||||
return group.id
|
||||
|
||||
@@ -268,7 +268,7 @@ class GitLabHook(object):
|
||||
@param hook_url Url to call on event
|
||||
'''
|
||||
def find_hook(self, project, hook_url):
|
||||
hooks = project.hooks.list()
|
||||
hooks = project.hooks.list(all=True)
|
||||
for hook in hooks:
|
||||
if (hook.url == hook_url):
|
||||
return hook
|
||||
|
||||
@@ -494,9 +494,9 @@ def main():
|
||||
namespace_id = group.id
|
||||
else:
|
||||
if username:
|
||||
namespace = gitlab_instance.namespaces.list(search=username)[0]
|
||||
namespace = gitlab_instance.namespaces.list(search=username, all=False)[0]
|
||||
else:
|
||||
namespace = gitlab_instance.namespaces.list(search=gitlab_instance.user.username)[0]
|
||||
namespace = gitlab_instance.namespaces.list(search=gitlab_instance.user.username, all=False)[0]
|
||||
namespace_id = namespace.id
|
||||
|
||||
if not namespace_id:
|
||||
|
||||
@@ -178,12 +178,12 @@ class GitLabProjectMembers(object):
|
||||
project_exists = self._gitlab.projects.get(project_name)
|
||||
return project_exists.id
|
||||
except gitlab.exceptions.GitlabGetError as e:
|
||||
project_exists = self._gitlab.projects.list(search=project_name)
|
||||
project_exists = self._gitlab.projects.list(search=project_name, all=False)
|
||||
if project_exists:
|
||||
return project_exists[0].id
|
||||
|
||||
def get_user_id(self, gitlab_user):
|
||||
user_exists = self._gitlab.users.list(username=gitlab_user)
|
||||
user_exists = self._gitlab.users.list(username=gitlab_user, all=False)
|
||||
if user_exists:
|
||||
return user_exists[0].id
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ options:
|
||||
description:
|
||||
- The password of the user.
|
||||
- GitLab server enforces minimum password length to 8, set this value with 8 or more characters.
|
||||
- Required only if C(state) is set to C(present).
|
||||
type: str
|
||||
reset_password:
|
||||
description:
|
||||
@@ -349,7 +348,7 @@ class GitLabUser(object):
|
||||
@param sshkey_name Name of the ssh key
|
||||
'''
|
||||
def ssh_key_exists(self, user, sshkey_name):
|
||||
keyList = map(lambda k: k.title, user.keys.list())
|
||||
keyList = map(lambda k: k.title, user.keys.list(all=True))
|
||||
|
||||
return sshkey_name in keyList
|
||||
|
||||
@@ -519,7 +518,7 @@ class GitLabUser(object):
|
||||
@param username Username of the user
|
||||
'''
|
||||
def find_user(self, username):
|
||||
users = self._gitlab.users.list(search=username)
|
||||
users = self._gitlab.users.list(search=username, all=True)
|
||||
for user in users:
|
||||
if (user.username == username):
|
||||
return user
|
||||
|
||||
@@ -41,6 +41,16 @@ options:
|
||||
- The priority of the alternative.
|
||||
type: int
|
||||
default: 50
|
||||
state:
|
||||
description:
|
||||
- C(present) - install the alternative (if not already installed), but do
|
||||
not set it as the currently selected alternative for the group.
|
||||
- C(selected) - install the alternative (if not already installed), and
|
||||
set it as the currently selected alternative for the group.
|
||||
choices: [ present, selected ]
|
||||
default: selected
|
||||
type: str
|
||||
version_added: 4.8.0
|
||||
requirements: [ update-alternatives ]
|
||||
'''
|
||||
|
||||
@@ -61,6 +71,13 @@ EXAMPLES = r'''
|
||||
name: java
|
||||
path: /usr/lib/jvm/java-7-openjdk-i386/jre/bin/java
|
||||
priority: -10
|
||||
|
||||
- name: Install Python 3.5 but do not select it
|
||||
community.general.alternatives:
|
||||
name: python
|
||||
path: /usr/bin/python3.5
|
||||
link: /usr/bin/python
|
||||
state: present
|
||||
'''
|
||||
|
||||
import os
|
||||
@@ -70,6 +87,15 @@ import subprocess
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
class AlternativeState:
|
||||
PRESENT = "present"
|
||||
SELECTED = "selected"
|
||||
|
||||
@classmethod
|
||||
def to_list(cls):
|
||||
return [cls.PRESENT, cls.SELECTED]
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
@@ -78,6 +104,11 @@ def main():
|
||||
path=dict(type='path', required=True),
|
||||
link=dict(type='path'),
|
||||
priority=dict(type='int', default=50),
|
||||
state=dict(
|
||||
type='str',
|
||||
choices=AlternativeState.to_list(),
|
||||
default=AlternativeState.SELECTED,
|
||||
),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
@@ -87,6 +118,7 @@ def main():
|
||||
path = params['path']
|
||||
link = params['link']
|
||||
priority = params['priority']
|
||||
state = params['state']
|
||||
|
||||
UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives', True)
|
||||
|
||||
@@ -126,9 +158,20 @@ def main():
|
||||
link = line.split()[1]
|
||||
break
|
||||
|
||||
changed = False
|
||||
if current_path != path:
|
||||
|
||||
# Check mode: expect a change if this alternative is not already
|
||||
# installed, or if it is to be set as the current selection.
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, current_path=current_path)
|
||||
module.exit_json(
|
||||
changed=(
|
||||
path not in all_alternatives or
|
||||
state == AlternativeState.SELECTED
|
||||
),
|
||||
current_path=current_path,
|
||||
)
|
||||
|
||||
try:
|
||||
# install the requested path if necessary
|
||||
if path not in all_alternatives:
|
||||
@@ -141,18 +184,34 @@ def main():
|
||||
[UPDATE_ALTERNATIVES, '--install', link, name, path, str(priority)],
|
||||
check_rc=True
|
||||
)
|
||||
changed = True
|
||||
|
||||
# select the requested path
|
||||
module.run_command(
|
||||
[UPDATE_ALTERNATIVES, '--set', name, path],
|
||||
check_rc=True
|
||||
)
|
||||
# set the current selection to this path (if requested)
|
||||
if state == AlternativeState.SELECTED:
|
||||
module.run_command(
|
||||
[UPDATE_ALTERNATIVES, '--set', name, path],
|
||||
check_rc=True
|
||||
)
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=True)
|
||||
except subprocess.CalledProcessError as cpe:
|
||||
module.fail_json(msg=str(dir(cpe)))
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
elif current_path == path and state == AlternativeState.PRESENT:
|
||||
# Case where alternative is currently selected, but state is set
|
||||
# to 'present'. In this case, we set to auto mode.
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, current_path=current_path)
|
||||
|
||||
changed = True
|
||||
try:
|
||||
module.run_command(
|
||||
[UPDATE_ALTERNATIVES, '--auto', name],
|
||||
check_rc=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as cpe:
|
||||
module.fail_json(msg=str(dir(cpe)))
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -59,7 +59,7 @@ options:
|
||||
resizefs:
|
||||
description:
|
||||
- If C(yes), if the block device and filesystem size differ, grow the filesystem into the space.
|
||||
- Supported for C(ext2), C(ext3), C(ext4), C(ext4dev), C(f2fs), C(lvm), C(xfs), C(ufs) and C(vfat) filesystems.
|
||||
- Supported for C(btrfs), C(ext2), C(ext3), C(ext4), C(ext4dev), C(f2fs), C(lvm), C(xfs), C(ufs) and C(vfat) filesystems.
|
||||
Attempts to resize other filesystem types will fail.
|
||||
- XFS Will only grow if mounted. Currently, the module is based on commands
|
||||
from C(util-linux) package to perform operations, so resizing of XFS is
|
||||
@@ -331,6 +331,10 @@ class Reiserfs(Filesystem):
|
||||
|
||||
class Btrfs(Filesystem):
|
||||
MKFS = 'mkfs.btrfs'
|
||||
INFO = 'btrfs'
|
||||
GROW = 'btrfs'
|
||||
GROW_MAX_SPACE_FLAGS = ['filesystem', 'resize', 'max']
|
||||
GROW_MOUNTPOINT_ONLY = True
|
||||
|
||||
def __init__(self, module):
|
||||
super(Btrfs, self).__init__(module)
|
||||
@@ -349,6 +353,19 @@ class Btrfs(Filesystem):
|
||||
self.MKFS_FORCE_FLAGS = ['-f']
|
||||
self.module.warn('Unable to identify mkfs.btrfs version (%r, %r)' % (stdout, stderr))
|
||||
|
||||
def get_fs_size(self, dev):
|
||||
"""Return size in bytes of filesystem on device (integer)."""
|
||||
mountpoint = dev.get_mountpoint()
|
||||
if not mountpoint:
|
||||
self.module.fail_json(msg="%s needs to be mounted for %s operations" % (dev, self.fstype))
|
||||
|
||||
dummy, stdout, dummy = self.module.run_command([self.module.get_bin_path(self.INFO),
|
||||
'filesystem', 'usage', '-b', mountpoint], check_rc=True)
|
||||
for line in stdout.splitlines():
|
||||
if "Device size" in line:
|
||||
return int(line.split()[-1])
|
||||
raise ValueError(stdout)
|
||||
|
||||
|
||||
class Ocfs2(Filesystem):
|
||||
MKFS = 'mkfs.ocfs2'
|
||||
|
||||
@@ -99,45 +99,41 @@ class GConf2Preference(object):
|
||||
|
||||
def call(self, call_type, fail_onerr=True):
|
||||
""" Helper function to perform gconftool-2 operations """
|
||||
config_source = ''
|
||||
direct = ''
|
||||
config_source = []
|
||||
direct = []
|
||||
changed = False
|
||||
out = ''
|
||||
|
||||
# If the configuration source is different from the default, create
|
||||
# the argument
|
||||
if self.config_source is not None and len(self.config_source) > 0:
|
||||
config_source = "--config-source " + self.config_source
|
||||
config_source = ["--config-source", self.config_source]
|
||||
|
||||
# If direct is true, create the argument
|
||||
if self.direct:
|
||||
direct = "--direct"
|
||||
direct = ["--direct"]
|
||||
|
||||
# Execute the call
|
||||
cmd = "gconftool-2 "
|
||||
cmd = ["gconftool-2"]
|
||||
try:
|
||||
# If the call is "get", then we don't need as many parameters and
|
||||
# we can ignore some
|
||||
if call_type == 'get':
|
||||
cmd += "--get {0}".format(self.key)
|
||||
cmd.extend(["--get", self.key])
|
||||
# Otherwise, we will use all relevant parameters
|
||||
elif call_type == 'set':
|
||||
cmd += "{0} {1} --type {2} --{3} {4} \"{5}\"".format(direct,
|
||||
config_source,
|
||||
self.value_type,
|
||||
call_type,
|
||||
self.key,
|
||||
self.value)
|
||||
cmd.extend(direct)
|
||||
cmd.extend(config_source)
|
||||
cmd.extend(["--type", self.value_type, "--{3}".format(call_type), self.key, self.value])
|
||||
elif call_type == 'unset':
|
||||
cmd += "--unset {0}".format(self.key)
|
||||
cmd.extend(["--unset", self.key])
|
||||
|
||||
# Start external command
|
||||
rc, out, err = self.ansible.run_command(cmd, use_unsafe_shell=True)
|
||||
rc, out, err = self.ansible.run_command(cmd)
|
||||
|
||||
if len(err) > 0:
|
||||
if fail_onerr:
|
||||
self.ansible.fail_json(msg='gconftool-2 failed with '
|
||||
'error: %s' % (str(err)))
|
||||
if err and fail_onerr:
|
||||
self.ansible.fail_json(msg='gconftool-2 failed with '
|
||||
'error: %s' % (str(err)))
|
||||
else:
|
||||
changed = True
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
|
||||
def get_runtime_status(ignore_selinux_state=False):
|
||||
return True if ignore_selinux_state is True else selinux.is_selinux_enabled()
|
||||
return ignore_selinux_state or selinux.is_selinux_enabled()
|
||||
|
||||
|
||||
def semanage_port_get_ports(seport, setype, proto):
|
||||
@@ -161,10 +161,7 @@ def semanage_port_get_type(seport, port, proto):
|
||||
key = (int(ports[0]), int(ports[1]), proto)
|
||||
|
||||
records = seport.get_all()
|
||||
if key in records:
|
||||
return records[key]
|
||||
else:
|
||||
return None
|
||||
return records.get(key)
|
||||
|
||||
|
||||
def semanage_port_add(module, ports, proto, setype, do_reload, serange='s0', sestore=''):
|
||||
@@ -194,19 +191,23 @@ def semanage_port_add(module, ports, proto, setype, do_reload, serange='s0', ses
|
||||
:rtype: bool
|
||||
:return: True if the policy was changed, otherwise False
|
||||
"""
|
||||
change = False
|
||||
try:
|
||||
seport = seobject.portRecords(sestore)
|
||||
seport.set_reload(do_reload)
|
||||
change = False
|
||||
ports_by_type = semanage_port_get_ports(seport, setype, proto)
|
||||
for port in ports:
|
||||
if port not in ports_by_type:
|
||||
change = True
|
||||
port_type = semanage_port_get_type(seport, port, proto)
|
||||
if port_type is None and not module.check_mode:
|
||||
seport.add(port, proto, serange, setype)
|
||||
elif port_type is not None and not module.check_mode:
|
||||
seport.modify(port, proto, serange, setype)
|
||||
if port in ports_by_type:
|
||||
continue
|
||||
|
||||
change = True
|
||||
if module.check_mode:
|
||||
continue
|
||||
port_type = semanage_port_get_type(seport, port, proto)
|
||||
if port_type is None:
|
||||
seport.add(port, proto, serange, setype)
|
||||
else:
|
||||
seport.modify(port, proto, serange, setype)
|
||||
|
||||
except (ValueError, IOError, KeyError, OSError, RuntimeError) as e:
|
||||
module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, to_native(e)), exception=traceback.format_exc())
|
||||
@@ -238,10 +239,10 @@ def semanage_port_del(module, ports, proto, setype, do_reload, sestore=''):
|
||||
:rtype: bool
|
||||
:return: True if the policy was changed, otherwise False
|
||||
"""
|
||||
change = False
|
||||
try:
|
||||
seport = seobject.portRecords(sestore)
|
||||
seport.set_reload(do_reload)
|
||||
change = False
|
||||
ports_by_type = semanage_port_get_ports(seport, setype, proto)
|
||||
for port in ports:
|
||||
if port in ports_by_type:
|
||||
|
||||
@@ -23,6 +23,7 @@ options:
|
||||
description:
|
||||
- The commands allowed by the sudoers rule.
|
||||
- Multiple can be added by passing a list of commands.
|
||||
- Use C(ALL) for all commands.
|
||||
type: list
|
||||
elements: str
|
||||
group:
|
||||
@@ -80,7 +81,7 @@ EXAMPLES = '''
|
||||
state: present
|
||||
user: bob
|
||||
runas: alice
|
||||
commands: ANY
|
||||
commands: ALL
|
||||
|
||||
- name: >-
|
||||
Allow the monitoring group to run sudo /usr/local/bin/gather-app-metrics
|
||||
|
||||
@@ -10,58 +10,75 @@ __metaclass__ = type
|
||||
DOCUMENTATION = '''
|
||||
module: xfconf
|
||||
author:
|
||||
- "Joseph Benden (@jbenden)"
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
- "Joseph Benden (@jbenden)"
|
||||
- "Alexei Znamensky (@russoz)"
|
||||
short_description: Edit XFCE4 Configurations
|
||||
description:
|
||||
- This module allows for the manipulation of Xfce 4 Configuration with the help of
|
||||
xfconf-query. Please see the xfconf-query(1) man pages for more details.
|
||||
seealso:
|
||||
- name: C(xfconf-query) man page
|
||||
description: Manual page of the C(xfconf-query) tool at the XFCE documentation site.
|
||||
link: 'https://docs.xfce.org/xfce/xfconf/xfconf-query'
|
||||
|
||||
- name: xfconf - Configuration Storage System
|
||||
description: XFCE documentation for the Xfconf configuration system.
|
||||
link: 'https://docs.xfce.org/xfce/xfconf/start'
|
||||
|
||||
options:
|
||||
channel:
|
||||
description:
|
||||
- A Xfconf preference channel is a top-level tree key, inside of the
|
||||
Xfconf repository that corresponds to the location for which all
|
||||
application properties/keys are stored. See man xfconf-query(1)
|
||||
required: yes
|
||||
- A Xfconf preference channel is a top-level tree key, inside of the
|
||||
Xfconf repository that corresponds to the location for which all
|
||||
application properties/keys are stored. See man xfconf-query(1)
|
||||
required: true
|
||||
type: str
|
||||
property:
|
||||
description:
|
||||
- A Xfce preference key is an element in the Xfconf repository
|
||||
that corresponds to an application preference. See man xfconf-query(1)
|
||||
required: yes
|
||||
- A Xfce preference key is an element in the Xfconf repository
|
||||
that corresponds to an application preference. See man xfconf-query(1)
|
||||
required: true
|
||||
type: str
|
||||
value:
|
||||
description:
|
||||
- Preference properties typically have simple values such as strings,
|
||||
integers, or lists of strings and integers. This is ignored if the state
|
||||
is "get". For array mode, use a list of values. See man xfconf-query(1)
|
||||
- Preference properties typically have simple values such as strings,
|
||||
integers, or lists of strings and integers. This is ignored if the state
|
||||
is "get". For array mode, use a list of values. See man xfconf-query(1)
|
||||
type: list
|
||||
elements: raw
|
||||
value_type:
|
||||
description:
|
||||
- The type of value being set. This is ignored if the state is "get".
|
||||
For array mode, use a list of types.
|
||||
- The type of value being set. This is ignored if the state is "get".
|
||||
- When providing more than one I(value_type), the length of the list must
|
||||
be equal to the length of I(value).
|
||||
- If only one I(value_type) is provided, but I(value) contains more than
|
||||
on element, that I(value_type) will be applied to all elements of I(value).
|
||||
- If the I(property) being set is an array and it can possibly have ony one
|
||||
element in the array, then I(force_array=true) must be used to ensure
|
||||
that C(xfconf-query) will interpret the value as an array rather than a
|
||||
scalar.
|
||||
- Support for C(uchar), C(char), C(uint64), and C(int64) has been added in community.general 4.8.0.
|
||||
type: list
|
||||
elements: str
|
||||
choices: [ int, uint, bool, float, double, string ]
|
||||
choices: [ string, int, double, bool, uint, uchar, char, uint64, int64, float ]
|
||||
state:
|
||||
type: str
|
||||
description:
|
||||
- The action to take upon the property/value.
|
||||
- State C(get) is deprecated and will be removed in community.general 5.0.0. Please use the module M(community.general.xfconf_info) instead.
|
||||
- The action to take upon the property/value.
|
||||
- State C(get) is deprecated and will be removed in community.general 5.0.0. Please use the module M(community.general.xfconf_info) instead.
|
||||
choices: [ get, present, absent ]
|
||||
default: "present"
|
||||
force_array:
|
||||
description:
|
||||
- Force array even if only one element
|
||||
- Force array even if only one element
|
||||
type: bool
|
||||
default: 'no'
|
||||
aliases: ['array']
|
||||
version_added: 1.0.0
|
||||
disable_facts:
|
||||
description:
|
||||
- The value C(false) is no longer allowed since community.general 4.0.0.
|
||||
- This option will be deprecated in a future version, and eventually be removed.
|
||||
- The value C(false) is no longer allowed since community.general 4.0.0.
|
||||
- This option will be deprecated in a future version, and eventually be removed.
|
||||
type: bool
|
||||
default: true
|
||||
version_added: 2.1.0
|
||||
@@ -88,7 +105,7 @@ EXAMPLES = """
|
||||
property: /general/workspace_names
|
||||
value_type: string
|
||||
value: ['Main']
|
||||
force_array: yes
|
||||
force_array: true
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
@@ -104,27 +121,27 @@ RETURN = '''
|
||||
sample: "/Xft/DPI"
|
||||
value_type:
|
||||
description:
|
||||
- The type of the value that was changed (C(none) for C(get) and C(reset)
|
||||
state). Either a single string value or a list of strings for array
|
||||
types.
|
||||
- This is a string or a list of strings.
|
||||
- The type of the value that was changed (C(none) for C(get) and C(reset)
|
||||
state). Either a single string value or a list of strings for array
|
||||
types.
|
||||
- This is a string or a list of strings.
|
||||
returned: success
|
||||
type: any
|
||||
sample: '"int" or ["str", "str", "str"]'
|
||||
value:
|
||||
description:
|
||||
- The value of the preference key after executing the module. Either a
|
||||
single string value or a list of strings for array types.
|
||||
- This is a string or a list of strings.
|
||||
- The value of the preference key after executing the module. Either a
|
||||
single string value or a list of strings for array types.
|
||||
- This is a string or a list of strings.
|
||||
returned: success
|
||||
type: any
|
||||
sample: '"192" or ["orange", "yellow", "violet"]'
|
||||
previous_value:
|
||||
description:
|
||||
- The value of the preference key before executing the module (C(none) for
|
||||
C(get) state). Either a single string value or a list of strings for array
|
||||
types.
|
||||
- This is a string or a list of strings.
|
||||
- The value of the preference key before executing the module (C(none) for
|
||||
C(get) state). Either a single string value or a list of strings for array
|
||||
types.
|
||||
- This is a string or a list of strings.
|
||||
returned: success
|
||||
type: any
|
||||
sample: '"96" or ["red", "blue", "green"]'
|
||||
@@ -161,15 +178,13 @@ class XFConfProperty(CmdStateModuleHelper):
|
||||
facts_params = ('property', 'channel', 'value')
|
||||
module = dict(
|
||||
argument_spec=dict(
|
||||
state=dict(default="present",
|
||||
choices=("present", "get", "absent"),
|
||||
type='str'),
|
||||
channel=dict(required=True, type='str'),
|
||||
property=dict(required=True, type='str'),
|
||||
value_type=dict(required=False, type='list',
|
||||
elements='str', choices=('int', 'uint', 'bool', 'float', 'double', 'string')),
|
||||
value=dict(required=False, type='list', elements='raw'),
|
||||
force_array=dict(default=False, type='bool', aliases=['array']),
|
||||
state=dict(type='str', choices=("present", "get", "absent"), default="present"),
|
||||
channel=dict(type='str', required=True),
|
||||
property=dict(type='str', required=True),
|
||||
value_type=dict(type='list', elements='str',
|
||||
choices=('string', 'int', 'double', 'bool', 'uint', 'uchar', 'char', 'uint64', 'int64', 'float')),
|
||||
value=dict(type='list', elements='raw'),
|
||||
force_array=dict(type='bool', default=False, aliases=['array']),
|
||||
disable_facts=dict(type='bool', default=True),
|
||||
),
|
||||
required_if=[('state', 'present', ['value', 'value_type'])],
|
||||
|
||||
@@ -4,6 +4,39 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: a_module
|
||||
short_description: Test whether a given string refers to an existing module or action plugin
|
||||
version_added: 4.0.0
|
||||
author: Felix Fontein (@felixfontein)
|
||||
description:
|
||||
- Test whether a given string refers to an existing module or action plugin.
|
||||
- This can be useful in roles, which can use this to ensure that required modules are present ahead of time.
|
||||
options:
|
||||
_input:
|
||||
description: A string denoting a fully qualified collection name (FQCN) of a module or action plugin.
|
||||
type: string
|
||||
required: true
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Make sure that community.aws.route53 is available
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >
|
||||
'community.aws.route53' is community.general.a_module
|
||||
|
||||
- name: Make sure that community.general.does_not_exist is not a module or action plugin
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'community.general.does_not_exist' is not community.general.a_module"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_value:
|
||||
description: Whether the module or action plugin denoted by the input exists.
|
||||
type: boolean
|
||||
'''
|
||||
|
||||
from ansible.plugins.loader import action_loader, module_loader
|
||||
|
||||
|
||||
2
tests/integration/targets/alerta_customer/aliases
Normal file
2
tests/integration/targets/alerta_customer/aliases
Normal file
@@ -0,0 +1,2 @@
|
||||
shippable/posix/group1
|
||||
disabled
|
||||
@@ -0,0 +1,4 @@
|
||||
alerta_url: http://localhost:8080/
|
||||
alerta_user: admin@example.com
|
||||
alerta_password: password
|
||||
alerta_key: demo-key
|
||||
151
tests/integration/targets/alerta_customer/tasks/main.yml
Normal file
151
tests/integration/targets/alerta_customer/tasks/main.yml
Normal file
@@ -0,0 +1,151 @@
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
- name: Create customer (check mode)
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_username: "{{ alerta_user }}"
|
||||
api_password: "{{ alerta_password }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
check_mode: true
|
||||
register: result
|
||||
|
||||
- name: Check result (check mode)
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Create customer
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_username: "{{ alerta_user }}"
|
||||
api_password: "{{ alerta_password }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
register: result
|
||||
|
||||
- name: Check customer creation
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Test customer creation idempotency
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_username: "{{ alerta_user }}"
|
||||
api_password: "{{ alerta_password }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
register: result
|
||||
|
||||
- name: Check customer creation idempotency
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Delete customer (check mode)
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_username: "{{ alerta_user }}"
|
||||
api_password: "{{ alerta_password }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
state: absent
|
||||
check_mode: true
|
||||
register: result
|
||||
|
||||
- name: Check customer deletion (check mode)
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Delete customer
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_username: "{{ alerta_user }}"
|
||||
api_password: "{{ alerta_password }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Check customer deletion
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Test customer deletion idempotency
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_username: "{{ alerta_user }}"
|
||||
api_password: "{{ alerta_password }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Check customer deletion idempotency
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Delete non-existing customer (check mode)
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_username: "{{ alerta_user }}"
|
||||
api_password: "{{ alerta_password }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
state: absent
|
||||
check_mode: true
|
||||
register: result
|
||||
|
||||
- name: Check non-existing customer deletion (check mode)
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Create customer with api key
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_key: "{{ alerta_key }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
register: result
|
||||
|
||||
- name: Check customer creation with api key
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Delete customer with api key
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_key: "{{ alerta_key }}"
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Check customer deletion with api key
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Use wrong api key
|
||||
alerta_customer:
|
||||
alerta_url: "{{ alerta_url }}"
|
||||
api_key: wrong_key
|
||||
customer: customer1
|
||||
match: admin@admin.admin
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Check customer creation with api key
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result is failed
|
||||
@@ -49,6 +49,12 @@
|
||||
# Test that path is checked: alternatives must fail when path is nonexistent
|
||||
- import_tasks: path_is_checked.yml
|
||||
|
||||
# Test operation of the 'state' parameter
|
||||
- block:
|
||||
- include_tasks: remove_links.yml
|
||||
- include_tasks: tests_state.yml
|
||||
|
||||
# Cleanup
|
||||
always:
|
||||
- include_tasks: remove_links.yml
|
||||
|
||||
@@ -62,6 +68,7 @@
|
||||
path: '/usr/bin/dummy{{ item }}'
|
||||
state: absent
|
||||
with_sequence: start=1 end=4
|
||||
|
||||
# *Disable tests on Fedora 24*
|
||||
# Shippable Fedora 24 image provides chkconfig-1.7-2.fc24.x86_64 but not the
|
||||
# latest available version (chkconfig-1.8-1.fc24.x86_64). update-alternatives
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
path: '{{ item }}'
|
||||
state: absent
|
||||
with_items:
|
||||
- "{{ alternatives_dir }}/dummy"
|
||||
- /etc/alternatives/dummy
|
||||
- /usr/bin/dummy
|
||||
|
||||
71
tests/integration/targets/alternatives/tasks/tests_state.yml
Normal file
71
tests/integration/targets/alternatives/tasks/tests_state.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
# Add a few dummy alternatives with state = present and make sure that the
|
||||
# group is in 'auto' mode and the highest priority alternative is selected.
|
||||
- name: Add some dummy alternatives with state = present
|
||||
alternatives:
|
||||
name: dummy
|
||||
path: "/usr/bin/dummy{{ item.n }}"
|
||||
link: /usr/bin/dummy
|
||||
priority: "{{ item.priority }}"
|
||||
state: present
|
||||
loop:
|
||||
- { n: 1, priority: 50 }
|
||||
- { n: 2, priority: 70 }
|
||||
- { n: 3, priority: 25 }
|
||||
|
||||
- name: Ensure that the link group is in auto mode
|
||||
shell: 'head -n1 {{ alternatives_dir }}/dummy | grep "^auto$"'
|
||||
|
||||
# Execute current selected 'dummy' and ensure it's the alternative we expect
|
||||
- name: Execute the current dummy command
|
||||
shell: dummy
|
||||
register: cmd
|
||||
|
||||
- name: Ensure that the expected command was executed
|
||||
assert:
|
||||
that:
|
||||
- cmd.stdout == "dummy2"
|
||||
|
||||
# Add another alternative with state = 'selected' and make sure that
|
||||
# this change results in the group being set to manual mode, and the
|
||||
# new alternative being the selected one.
|
||||
- name: Add another dummy alternative with state = selected
|
||||
alternatives:
|
||||
name: dummy
|
||||
path: /usr/bin/dummy4
|
||||
link: /usr/bin/dummy
|
||||
priority: 10
|
||||
state: selected
|
||||
|
||||
- name: Ensure that the link group is in manual mode
|
||||
shell: 'head -n1 {{ alternatives_dir }}/dummy | grep "^manual$"'
|
||||
|
||||
- name: Execute the current dummy command
|
||||
shell: dummy
|
||||
register: cmd
|
||||
|
||||
- name: Ensure that the expected command was executed
|
||||
assert:
|
||||
that:
|
||||
- cmd.stdout == "dummy4"
|
||||
|
||||
# Set the currently selected alternative to state = 'present' (was previously
|
||||
# selected), and ensure that this results in the group being set to 'auto'
|
||||
# mode, and the highest priority alternative is selected.
|
||||
- name: Set current selected dummy to state = present
|
||||
alternatives:
|
||||
name: dummy
|
||||
path: /usr/bin/dummy4
|
||||
link: /usr/bin/dummy
|
||||
state: present
|
||||
|
||||
- name: Ensure that the link group is in auto mode
|
||||
shell: 'head -n1 {{ alternatives_dir }}/dummy | grep "^auto$"'
|
||||
|
||||
- name: Execute the current dummy command
|
||||
shell: dummy
|
||||
register: cmd
|
||||
|
||||
- name: Ensure that the expected command was executed
|
||||
assert:
|
||||
that:
|
||||
- cmd.stdout == "dummy2"
|
||||
@@ -15,6 +15,7 @@
|
||||
when:
|
||||
- not (ansible_distribution == "Ubuntu" and ansible_distribution_major_version|int == 14)
|
||||
- not (ansible_os_family == "Suse" and ansible_distribution_major_version|int != 42 and ansible_python.version.major != 3)
|
||||
- not (ansible_distribution == "CentOS" and ansible_distribution_major_version|int == 8) # TODO: cannot start service
|
||||
- not (ansible_distribution == 'Archlinux') # TODO: package seems to be broken, cannot be downloaded from mirrors?
|
||||
- not (ansible_distribution == 'Alpine') # TODO: not sure what's wrong here, the module doesn't return what the tests expect
|
||||
block:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user