mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-29 09:56:53 +00:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
301711e0d3 | ||
|
|
7cf834fb3c | ||
|
|
eda3d160fa | ||
|
|
b71d8813b2 | ||
|
|
a4c0df1ded | ||
|
|
a2b1756bea | ||
|
|
08d89a2f85 | ||
|
|
1dad95370e | ||
|
|
200b858b36 | ||
|
|
342a5a14f9 | ||
|
|
4609907367 | ||
|
|
7d68af57af | ||
|
|
fb3768aada | ||
|
|
93f990a1b9 | ||
|
|
f003833c1a | ||
|
|
8eb94dc36b | ||
|
|
7bf155284f | ||
|
|
0f5f00f41a | ||
|
|
b02ea33f9b | ||
|
|
437d1bbf7a | ||
|
|
a1582aa8cb | ||
|
|
4816157c05 | ||
|
|
67356d287d | ||
|
|
2b76b1f43a | ||
|
|
0f2d5136b8 | ||
|
|
58a4610b61 | ||
|
|
d1a412dafc | ||
|
|
c82362194b | ||
|
|
bb80ff6aee | ||
|
|
15b950f1cf | ||
|
|
7577d5218a | ||
|
|
f317fd924a | ||
|
|
6070dc80d4 | ||
|
|
b3fad4fa87 | ||
|
|
76626eb7e8 | ||
|
|
37ba1d0e5e | ||
|
|
57d1e74f3d | ||
|
|
f6b5b793c8 | ||
|
|
6584348d05 | ||
|
|
a610e27853 | ||
|
|
01220475dc | ||
|
|
0a1b53a10e | ||
|
|
db8f38ea3a | ||
|
|
7c0e4bda35 | ||
|
|
50425a49ec | ||
|
|
ce30e0732b | ||
|
|
c2cbac062e | ||
|
|
ed4bc4c1d2 | ||
|
|
cda63f7221 | ||
|
|
ebaf490653 | ||
|
|
9027c367d4 | ||
|
|
e69ea28662 | ||
|
|
eccc41eadc | ||
|
|
b5d56463a6 | ||
|
|
3c5094d971 | ||
|
|
15cbc9665e | ||
|
|
4259792751 | ||
|
|
fe4099c163 | ||
|
|
b2417accbf | ||
|
|
9b21b0d31c | ||
|
|
330b0304ef | ||
|
|
f8fc18412c | ||
|
|
abd2a85709 | ||
|
|
c1536a3501 | ||
|
|
4fa1f1a6dd | ||
|
|
42cc5280d9 | ||
|
|
1c8fbed36c | ||
|
|
f8d0d07fed | ||
|
|
3ee01ddb7f | ||
|
|
5d5befdf96 | ||
|
|
98cea930f0 | ||
|
|
9036d8edd0 | ||
|
|
72d1af86f3 | ||
|
|
6c718a4f55 | ||
|
|
751e2400e6 | ||
|
|
c2ae3dd026 | ||
|
|
9a97d5e14a | ||
|
|
f794ba17c9 | ||
|
|
f4575816be | ||
|
|
fd3bc75fb3 | ||
|
|
dc898dfdf8 | ||
|
|
28c7a62989 | ||
|
|
f490bc1dba | ||
|
|
5bd671b8bf | ||
|
|
0057908705 | ||
|
|
39d83fefee | ||
|
|
145b4e7433 | ||
|
|
d45b112cc0 | ||
|
|
fc64490f89 | ||
|
|
4a0276261b |
@@ -53,7 +53,7 @@ variables:
|
||||
resources:
|
||||
containers:
|
||||
- container: default
|
||||
image: quay.io/ansible/azure-pipelines-test-container:3.0.0
|
||||
image: quay.io/ansible/azure-pipelines-test-container:4.0.1
|
||||
|
||||
pool: Standard
|
||||
|
||||
@@ -173,8 +173,8 @@ stages:
|
||||
targets:
|
||||
- name: Alpine 3.17
|
||||
test: alpine/3.17
|
||||
# - name: Fedora 37
|
||||
# test: fedora/37
|
||||
# - name: Fedora 38
|
||||
# test: fedora/38
|
||||
- name: Ubuntu 22.04
|
||||
test: ubuntu/22.04
|
||||
groups:
|
||||
@@ -195,8 +195,6 @@ stages:
|
||||
test: rhel/8.8
|
||||
- name: FreeBSD 13.2
|
||||
test: freebsd/13.2
|
||||
- name: FreeBSD 12.4
|
||||
test: freebsd/12.4
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -217,6 +215,8 @@ stages:
|
||||
test: rhel/7.9
|
||||
- name: FreeBSD 13.1
|
||||
test: freebsd/13.1
|
||||
- name: FreeBSD 12.4
|
||||
test: freebsd/12.4
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
@@ -265,8 +265,8 @@ stages:
|
||||
parameters:
|
||||
testFormat: devel/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 37
|
||||
test: fedora37
|
||||
- name: Fedora 38
|
||||
test: fedora38
|
||||
- name: openSUSE 15
|
||||
test: opensuse15
|
||||
- name: Ubuntu 20.04
|
||||
@@ -287,6 +287,8 @@ stages:
|
||||
parameters:
|
||||
testFormat: 2.15/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 37
|
||||
test: fedora37
|
||||
- name: CentOS 7
|
||||
test: centos7
|
||||
groups:
|
||||
@@ -337,6 +339,8 @@ stages:
|
||||
targets:
|
||||
- name: Debian Bullseye
|
||||
test: debian-bullseye/3.9
|
||||
- name: Debian Bookworm
|
||||
test: debian-bookworm/3.11
|
||||
- name: ArchLinux
|
||||
test: archlinux/3.11
|
||||
- name: CentOS Stream 8
|
||||
|
||||
18
.github/BOTMETA.yml
vendored
18
.github/BOTMETA.yml
vendored
@@ -204,6 +204,8 @@ files:
|
||||
maintainers: ddelnano shinuza
|
||||
$lookups/:
|
||||
labels: lookups
|
||||
$lookups/bitwarden_secrets_manager.py:
|
||||
maintainers: jantari
|
||||
$lookups/bitwarden.py:
|
||||
maintainers: lungj
|
||||
$lookups/cartesian.py: {}
|
||||
@@ -431,7 +433,7 @@ files:
|
||||
ignore: resmo
|
||||
maintainers: dmtrs
|
||||
$modules/consul:
|
||||
ignore: colin-nolan
|
||||
ignore: colin-nolan Hakon
|
||||
maintainers: $team_consul
|
||||
$modules/copr.py:
|
||||
maintainers: schlupov
|
||||
@@ -530,6 +532,7 @@ files:
|
||||
keywords: gitlab source_control
|
||||
maintainers: $team_gitlab
|
||||
notify: jlozadad
|
||||
ignore: dj-wasabi
|
||||
$modules/gitlab_branch.py:
|
||||
maintainers: paytroff
|
||||
$modules/gitlab_merge_request.py:
|
||||
@@ -676,9 +679,9 @@ files:
|
||||
$modules/jenkins_script.py:
|
||||
maintainers: hogarthj
|
||||
$modules/jira.py:
|
||||
ignore: DWSR
|
||||
ignore: DWSR tarka
|
||||
labels: jira
|
||||
maintainers: Slezhuk tarka pertoft
|
||||
maintainers: Slezhuk pertoft
|
||||
$modules/kdeconfig.py:
|
||||
maintainers: smeso
|
||||
$modules/kernel_blacklist.py:
|
||||
@@ -691,6 +694,10 @@ files:
|
||||
maintainers: Skrekulko
|
||||
$modules/keycloak_authz_authorization_scope.py:
|
||||
maintainers: mattock
|
||||
$modules/keycloak_authz_permission.py:
|
||||
maintainers: mattock
|
||||
$modules/keycloak_authz_permission_info.py:
|
||||
maintainers: mattock
|
||||
$modules/keycloak_client_rolemapping.py:
|
||||
maintainers: Gaetan2907
|
||||
$modules/keycloak_clientscope.py:
|
||||
@@ -990,6 +997,7 @@ files:
|
||||
keywords: kvm libvirt proxmox qemu
|
||||
labels: proxmox virt
|
||||
maintainers: $team_virt UnderGreen
|
||||
ignore: tleguern
|
||||
$modules/proxmox.py:
|
||||
ignore: skvidal
|
||||
maintainers: UnderGreen
|
||||
@@ -1402,7 +1410,7 @@ macros:
|
||||
team_cyberark_conjur: jvanderhoof ryanprior
|
||||
team_e_spirit: MatrixCrawler getjack
|
||||
team_flatpak: JayKayy oolongbrothers
|
||||
team_gitlab: Lunik Shaps dj-wasabi marwatk waheedi zanssa scodeman metanovii sh0shin nejch lgatellier suukit
|
||||
team_gitlab: Lunik Shaps marwatk waheedi zanssa scodeman metanovii sh0shin nejch lgatellier suukit
|
||||
team_hpux: bcoca davx8342
|
||||
team_huawei: QijunPan TommyLike edisonxiang freesky-edward hwDCN niuzhenguo xuxiaowei0512 yanzhangi zengchen1024 zhongjun2
|
||||
team_ipa: Akasurde Nosmoht fxfitz justchris1
|
||||
@@ -1421,5 +1429,5 @@ macros:
|
||||
team_scaleway: remyleone abarbare
|
||||
team_solaris: bcoca fishman jasperla jpdasma mator scathatheworm troy2914 xen0l
|
||||
team_suse: commel evrardjp lrupp toabctl AnderEnder alxgu andytom sealor
|
||||
team_virt: joshainglis karmab tleguern Thulium-Drake Ajpantuso
|
||||
team_virt: joshainglis karmab Thulium-Drake Ajpantuso
|
||||
team_wdc: mikemoerk
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
args: [--fix=lf]
|
||||
- id: fix-encoding-pragma
|
||||
- id: check-ast
|
||||
- id: check-merge-conflict
|
||||
- id: check-symlinks
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.9.0
|
||||
hooks:
|
||||
- id: rst-backticks
|
||||
types: [file]
|
||||
files: changelogs/fragments/.*\.(yml|yaml)$
|
||||
112
CHANGELOG.rst
112
CHANGELOG.rst
@@ -6,6 +6,116 @@ Community General Release Notes
|
||||
|
||||
This changelog describes changes after version 6.0.0.
|
||||
|
||||
v7.2.1
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Bugfix release.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- cmd_runner module utils - when a parameter in ``argument_spec`` has no type, meaning it is implicitly a ``str``, ``CmdRunner`` would fail trying to find the ``type`` key in that dictionary (https://github.com/ansible-collections/community.general/pull/6968).
|
||||
- ejabberd_user - provide meaningful error message when the ``ejabberdctl`` command is not found (https://github.com/ansible-collections/community.general/pull/7028, https://github.com/ansible-collections/community.general/issues/6949).
|
||||
- proxmox module utils - fix proxmoxer library version check (https://github.com/ansible-collections/community.general/issues/6974, https://github.com/ansible-collections/community.general/issues/6975, https://github.com/ansible-collections/community.general/pull/6980).
|
||||
- proxmox_kvm - when ``name`` option is provided without ``vmid`` and VM with that name already exists then no new VM will be created (https://github.com/ansible-collections/community.general/issues/6911, https://github.com/ansible-collections/community.general/pull/6981).
|
||||
- rundeck - fix ``TypeError`` on 404 API response (https://github.com/ansible-collections/community.general/pull/6983).
|
||||
|
||||
v7.2.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Regular bugfix and feature release.
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- cobbler inventory plugin - convert Ansible unicode strings to native Python unicode strings before passing user/password to XMLRPC client (https://github.com/ansible-collections/community.general/pull/6923).
|
||||
- consul_session - drops requirement for the ``python-consul`` library to communicate with the Consul API, instead relying on the existing ``requests`` library requirement (https://github.com/ansible-collections/community.general/pull/6755).
|
||||
- gitlab_project_variable - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- gitlab_runner - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6927).
|
||||
- htpasswd - the parameter ``crypt_scheme`` is being renamed as ``hash_scheme`` and added as an alias to it (https://github.com/ansible-collections/community.general/pull/6841).
|
||||
- keycloak_authentication - added provider ID choices, since Keycloak supports only those two specific ones (https://github.com/ansible-collections/community.general/pull/6763).
|
||||
- keyring - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6927).
|
||||
- locale_gen - module has been refactored to use ``ModuleHelper`` and ``CmdRunner`` (https://github.com/ansible-collections/community.general/pull/6903).
|
||||
- locale_gen - module now using ``CmdRunner`` to execute external commands (https://github.com/ansible-collections/community.general/pull/6820).
|
||||
- make - add new ``targets`` parameter allowing multiple targets to be used with ``make`` (https://github.com/ansible-collections/community.general/pull/6882, https://github.com/ansible-collections/community.general/issues/4919).
|
||||
- nmcli - add support for ``ipv4.dns-options`` and ``ipv6.dns-options`` (https://github.com/ansible-collections/community.general/pull/6902).
|
||||
- npm - minor improvement on parameter validation (https://github.com/ansible-collections/community.general/pull/6848).
|
||||
- opkg - add ``executable`` parameter allowing to specify the path of the ``opkg`` command (https://github.com/ansible-collections/community.general/pull/6862).
|
||||
- pubnub_blocks - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- redfish_command - add ``account_types`` and ``oem_account_types`` as optional inputs to ``AddUser`` (https://github.com/ansible-collections/community.general/issues/6823, https://github.com/ansible-collections/community.general/pull/6871).
|
||||
- redfish_info - add ``AccountTypes`` and ``OEMAccountTypes`` to the output of ``ListUsers`` (https://github.com/ansible-collections/community.general/issues/6823, https://github.com/ansible-collections/community.general/pull/6871).
|
||||
- redfish_info - adds ``ProcessorArchitecture`` to CPU inventory (https://github.com/ansible-collections/community.general/pull/6864).
|
||||
- redfish_info - fix for ``GetVolumeInventory``, Controller name was getting populated incorrectly and duplicates were seen in the volumes retrieved (https://github.com/ansible-collections/community.general/pull/6719).
|
||||
- rhsm_repository - the interaction with ``subscription-manager`` was
|
||||
refactored by grouping things together, removing unused bits, and hardening
|
||||
the way it is run; also, the parsing of ``subscription-manager repos --list``
|
||||
was improved and made slightly faster; no behaviour change is expected
|
||||
(https://github.com/ansible-collections/community.general/pull/6783,
|
||||
https://github.com/ansible-collections/community.general/pull/6837).
|
||||
- scaleway_security_group_rule - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- snap - add option ``dangerous`` to the module, that will map into the command line argument ``--dangerous``, allowing unsigned snap files to be installed (https://github.com/ansible-collections/community.general/pull/6908, https://github.com/ansible-collections/community.general/issues/5715).
|
||||
- tss lookup plugin - allow to fetch secret by path. Previously, we could not fetch secret by path but now use ``secret_path`` option to indicate to fetch secret by secret path (https://github.com/ansible-collections/community.general/pull/6881).
|
||||
- xenserver_guest_info - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- xenserver_guest_powerstate - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- yum_versionlock - add support to pin specific package versions instead of only the package itself (https://github.com/ansible-collections/community.general/pull/6861, https://github.com/ansible-collections/community.general/issues/4470).
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- flowdock - module relies entirely on no longer responsive API endpoints, and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6930).
|
||||
- proxmox - old feature flag ``proxmox_default_behavior`` will be removed in community.general 10.0.0 (https://github.com/ansible-collections/community.general/pull/6836).
|
||||
- stackdriver - module relies entirely on no longer existent API endpoints, and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6887).
|
||||
- webfaction_app - module relies entirely on no longer existent API endpoints, and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_db - module relies entirely on no longer existent API endpoints, and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_domain - module relies entirely on no longer existent API endpoints, and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_mailbox - module relies entirely on no longer existent API endpoints, and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_site - module relies entirely on no longer existent API endpoints, and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- cobbler inventory plugin - fix calculation of cobbler_ipv4/6_address (https://github.com/ansible-collections/community.general/pull/6925).
|
||||
- datadog_downtime - presence of ``rrule`` param lead to the Datadog API returning Bad Request due to a missing recurrence type (https://github.com/ansible-collections/community.general/pull/6811).
|
||||
- ipa_dnszone - fix 'idnsallowsyncptr' key error for reverse zone (https://github.com/ansible-collections/community.general/pull/6906, https://github.com/ansible-collections/community.general/issues/6905).
|
||||
- keycloak_authentication - fix Keycloak authentication flow (step or sub-flow) indexing during update, if not specified by the user (https://github.com/ansible-collections/community.general/pull/6734).
|
||||
- locale_gen - now works for locales without the underscore character such as ``C.UTF-8`` (https://github.com/ansible-collections/community.general/pull/6774, https://github.com/ansible-collections/community.general/issues/5142, https://github.com/ansible-collections/community.general/issues/4305).
|
||||
- machinectl become plugin - mark plugin as ``require_tty`` to automatically disable pipelining, with which this plugin is not compatible (https://github.com/ansible-collections/community.general/issues/6932, https://github.com/ansible-collections/community.general/pull/6935).
|
||||
- nmcli - fix support for empty list (in compare and scrape) (https://github.com/ansible-collections/community.general/pull/6769).
|
||||
- openbsd_pkg - the pkg_info(1) behavior has changed in OpenBSD >7.3. The error message ``Can't find`` should not lead to an error case (https://github.com/ansible-collections/community.general/pull/6785).
|
||||
- pacman - module recognizes the output of ``yay`` running as ``root`` (https://github.com/ansible-collections/community.general/pull/6713).
|
||||
- proxmox - fix error when a configuration had no ``template`` field (https://github.com/ansible-collections/community.general/pull/6838, https://github.com/ansible-collections/community.general/issues/5372).
|
||||
- proxmox module utils - add logic to detect whether an old Promoxer complains about the ``token_name`` and ``token_value`` parameters and provide a better error message when that happens (https://github.com/ansible-collections/community.general/pull/6839, https://github.com/ansible-collections/community.general/issues/5371).
|
||||
- proxmox_disk - fix unable to create ``cdrom`` media due to ``size`` always being appended (https://github.com/ansible-collections/community.general/pull/6770).
|
||||
- proxmox_kvm - ``absent`` state with ``force`` specified failed to stop the VM due to the ``timeout`` value not being passed to ``stop_vm`` (https://github.com/ansible-collections/community.general/pull/6827).
|
||||
- proxmox_kvm - ``restarted`` state did not actually restart a VM in some VM configurations. The state now uses the Proxmox reboot endpoint instead of calling the ``stop_vm`` and ``start_vm`` functions (https://github.com/ansible-collections/community.general/pull/6773).
|
||||
- proxmox_template - require ``requests_toolbelt`` module to fix issue with uploading large templates (https://github.com/ansible-collections/community.general/issues/5579, https://github.com/ansible-collections/community.general/pull/6757).
|
||||
- redfish_info - fix ``ListUsers`` to not show empty account slots (https://github.com/ansible-collections/community.general/issues/6771, https://github.com/ansible-collections/community.general/pull/6772).
|
||||
- refish_utils module utils - changing variable names to avoid issues occuring when fetching Volumes data (https://github.com/ansible-collections/community.general/pull/6883).
|
||||
- snap - assume default track ``latest`` in parameter ``channel`` when not specified (https://github.com/ansible-collections/community.general/pull/6835, https://github.com/ansible-collections/community.general/issues/6821).
|
||||
- snap - fix the processing of the commands' output, stripping spaces and newlines from it (https://github.com/ansible-collections/community.general/pull/6826, https://github.com/ansible-collections/community.general/issues/6803).
|
||||
|
||||
New Plugins
|
||||
-----------
|
||||
|
||||
Lookup
|
||||
~~~~~~
|
||||
|
||||
- bitwarden_secrets_manager - Retrieve secrets from Bitwarden Secrets Manager
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- consul_policy - Manipulate Consul policies
|
||||
- keycloak_authz_permission - Allows administration of Keycloak client authorization permissions via Keycloak API
|
||||
- keycloak_authz_permission_info - Query Keycloak client authorization permissions information
|
||||
- proxmox_vm_info - Retrieve information about one or more Proxmox VE virtual machines
|
||||
|
||||
v7.1.0
|
||||
======
|
||||
|
||||
@@ -20,7 +130,7 @@ in its documentation. If you look at documentation with the ansible-doc CLI tool
|
||||
from ansible-core before 2.15, please note that it does not render the markup
|
||||
correctly. You should be still able to read it in most cases, but you need
|
||||
ansible-core 2.15 or later to see it as it is intended. Alternatively you can
|
||||
look at `the devel docsite <https://docs.ansible.com/ansible/devel/collections/community/general/>__`
|
||||
look at `the devel docsite <https://docs.ansible.com/ansible/devel/collections/community/general/>`__
|
||||
for the rendered HTML version of the documentation of the latest release.
|
||||
|
||||
|
||||
|
||||
@@ -121,19 +121,3 @@ Creating new modules and plugins requires a bit more work than other Pull Reques
|
||||
listed as `maintainers` will be pinged for new issues and PRs that modify the module/plugin or its tests.
|
||||
|
||||
When you add a new plugin/module, we expect that you perform maintainer duty for at least some time after contributing it.
|
||||
|
||||
## pre-commit
|
||||
|
||||
To help ensure high-quality contributions this repository includes a [pre-commit](https://pre-commit.com) configuration which
|
||||
corrects and tests against common issues that would otherwise cause CI to fail. To begin using these pre-commit hooks see
|
||||
the [Installation](#installation) section below.
|
||||
|
||||
This is optional and not required to contribute to this repository.
|
||||
|
||||
### Installation
|
||||
|
||||
Follow the [instructions](https://pre-commit.com/#install) provided with pre-commit and run `pre-commit install` under the repository base. If for any reason you would like to disable the pre-commit hooks run `pre-commit uninstall`.
|
||||
|
||||
This is optional to run it locally.
|
||||
|
||||
You can trigger it locally with `pre-commit run --all-files` or even to run only for a given file `pre-commit run --files YOUR_FILE`.
|
||||
|
||||
@@ -1007,7 +1007,7 @@ releases:
|
||||
ansible-core 2.15 or later to see it as it is intended. Alternatively you
|
||||
can
|
||||
|
||||
look at `the devel docsite <https://docs.ansible.com/ansible/devel/collections/community/general/>__`
|
||||
look at `the devel docsite <https://docs.ansible.com/ansible/devel/collections/community/general/>`__
|
||||
|
||||
for the rendered HTML version of the documentation of the latest release.
|
||||
|
||||
@@ -1082,3 +1082,216 @@ releases:
|
||||
name: proxmox_pool_member
|
||||
namespace: ''
|
||||
release_date: '2023-06-20'
|
||||
7.2.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- cobbler inventory plugin - fix calculation of cobbler_ipv4/6_address (https://github.com/ansible-collections/community.general/pull/6925).
|
||||
- datadog_downtime - presence of ``rrule`` param lead to the Datadog API returning
|
||||
Bad Request due to a missing recurrence type (https://github.com/ansible-collections/community.general/pull/6811).
|
||||
- ipa_dnszone - fix 'idnsallowsyncptr' key error for reverse zone (https://github.com/ansible-collections/community.general/pull/6906,
|
||||
https://github.com/ansible-collections/community.general/issues/6905).
|
||||
- keycloak_authentication - fix Keycloak authentication flow (step or sub-flow)
|
||||
indexing during update, if not specified by the user (https://github.com/ansible-collections/community.general/pull/6734).
|
||||
- locale_gen - now works for locales without the underscore character such as
|
||||
``C.UTF-8`` (https://github.com/ansible-collections/community.general/pull/6774,
|
||||
https://github.com/ansible-collections/community.general/issues/5142, https://github.com/ansible-collections/community.general/issues/4305).
|
||||
- machinectl become plugin - mark plugin as ``require_tty`` to automatically
|
||||
disable pipelining, with which this plugin is not compatible (https://github.com/ansible-collections/community.general/issues/6932,
|
||||
https://github.com/ansible-collections/community.general/pull/6935).
|
||||
- nmcli - fix support for empty list (in compare and scrape) (https://github.com/ansible-collections/community.general/pull/6769).
|
||||
- openbsd_pkg - the pkg_info(1) behavior has changed in OpenBSD >7.3. The error
|
||||
message ``Can't find`` should not lead to an error case (https://github.com/ansible-collections/community.general/pull/6785).
|
||||
- pacman - module recognizes the output of ``yay`` running as ``root`` (https://github.com/ansible-collections/community.general/pull/6713).
|
||||
- proxmox - fix error when a configuration had no ``template`` field (https://github.com/ansible-collections/community.general/pull/6838,
|
||||
https://github.com/ansible-collections/community.general/issues/5372).
|
||||
- proxmox module utils - add logic to detect whether an old Promoxer complains
|
||||
about the ``token_name`` and ``token_value`` parameters and provide a better
|
||||
error message when that happens (https://github.com/ansible-collections/community.general/pull/6839,
|
||||
https://github.com/ansible-collections/community.general/issues/5371).
|
||||
- proxmox_disk - fix unable to create ``cdrom`` media due to ``size`` always
|
||||
being appended (https://github.com/ansible-collections/community.general/pull/6770).
|
||||
- proxmox_kvm - ``absent`` state with ``force`` specified failed to stop the
|
||||
VM due to the ``timeout`` value not being passed to ``stop_vm`` (https://github.com/ansible-collections/community.general/pull/6827).
|
||||
- proxmox_kvm - ``restarted`` state did not actually restart a VM in some VM
|
||||
configurations. The state now uses the Proxmox reboot endpoint instead of
|
||||
calling the ``stop_vm`` and ``start_vm`` functions (https://github.com/ansible-collections/community.general/pull/6773).
|
||||
- proxmox_template - require ``requests_toolbelt`` module to fix issue with
|
||||
uploading large templates (https://github.com/ansible-collections/community.general/issues/5579,
|
||||
https://github.com/ansible-collections/community.general/pull/6757).
|
||||
- redfish_info - fix ``ListUsers`` to not show empty account slots (https://github.com/ansible-collections/community.general/issues/6771,
|
||||
https://github.com/ansible-collections/community.general/pull/6772).
|
||||
- refish_utils module utils - changing variable names to avoid issues occuring
|
||||
when fetching Volumes data (https://github.com/ansible-collections/community.general/pull/6883).
|
||||
- snap - assume default track ``latest`` in parameter ``channel`` when not specified
|
||||
(https://github.com/ansible-collections/community.general/pull/6835, https://github.com/ansible-collections/community.general/issues/6821).
|
||||
- snap - fix the processing of the commands' output, stripping spaces and newlines
|
||||
from it (https://github.com/ansible-collections/community.general/pull/6826,
|
||||
https://github.com/ansible-collections/community.general/issues/6803).
|
||||
deprecated_features:
|
||||
- flowdock - module relies entirely on no longer responsive API endpoints, and
|
||||
it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6930).
|
||||
- proxmox - old feature flag ``proxmox_default_behavior`` will be removed in
|
||||
community.general 10.0.0 (https://github.com/ansible-collections/community.general/pull/6836).
|
||||
- stackdriver - module relies entirely on no longer existent API endpoints,
|
||||
and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6887).
|
||||
- webfaction_app - module relies entirely on no longer existent API endpoints,
|
||||
and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_db - module relies entirely on no longer existent API endpoints,
|
||||
and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_domain - module relies entirely on no longer existent API endpoints,
|
||||
and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_mailbox - module relies entirely on no longer existent API endpoints,
|
||||
and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
- webfaction_site - module relies entirely on no longer existent API endpoints,
|
||||
and it will be removed in community.general 9.0.0 (https://github.com/ansible-collections/community.general/pull/6909).
|
||||
minor_changes:
|
||||
- cobbler inventory plugin - convert Ansible unicode strings to native Python
|
||||
unicode strings before passing user/password to XMLRPC client (https://github.com/ansible-collections/community.general/pull/6923).
|
||||
- consul_session - drops requirement for the ``python-consul`` library to communicate
|
||||
with the Consul API, instead relying on the existing ``requests`` library
|
||||
requirement (https://github.com/ansible-collections/community.general/pull/6755).
|
||||
- gitlab_project_variable - minor refactor removing unnecessary code statements
|
||||
(https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- gitlab_runner - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6927).
|
||||
- htpasswd - the parameter ``crypt_scheme`` is being renamed as ``hash_scheme``
|
||||
and added as an alias to it (https://github.com/ansible-collections/community.general/pull/6841).
|
||||
- keycloak_authentication - added provider ID choices, since Keycloak supports
|
||||
only those two specific ones (https://github.com/ansible-collections/community.general/pull/6763).
|
||||
- keyring - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6927).
|
||||
- locale_gen - module has been refactored to use ``ModuleHelper`` and ``CmdRunner``
|
||||
(https://github.com/ansible-collections/community.general/pull/6903).
|
||||
- locale_gen - module now using ``CmdRunner`` to execute external commands (https://github.com/ansible-collections/community.general/pull/6820).
|
||||
- make - add new ``targets`` parameter allowing multiple targets to be used
|
||||
with ``make`` (https://github.com/ansible-collections/community.general/pull/6882,
|
||||
https://github.com/ansible-collections/community.general/issues/4919).
|
||||
- nmcli - add support for ``ipv4.dns-options`` and ``ipv6.dns-options`` (https://github.com/ansible-collections/community.general/pull/6902).
|
||||
- npm - minor improvement on parameter validation (https://github.com/ansible-collections/community.general/pull/6848).
|
||||
- opkg - add ``executable`` parameter allowing to specify the path of the ``opkg``
|
||||
command (https://github.com/ansible-collections/community.general/pull/6862).
|
||||
- pubnub_blocks - minor refactor removing unnecessary code statements (https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- redfish_command - add ``account_types`` and ``oem_account_types`` as optional
|
||||
inputs to ``AddUser`` (https://github.com/ansible-collections/community.general/issues/6823,
|
||||
https://github.com/ansible-collections/community.general/pull/6871).
|
||||
- redfish_info - add ``AccountTypes`` and ``OEMAccountTypes`` to the output
|
||||
of ``ListUsers`` (https://github.com/ansible-collections/community.general/issues/6823,
|
||||
https://github.com/ansible-collections/community.general/pull/6871).
|
||||
- redfish_info - adds ``ProcessorArchitecture`` to CPU inventory (https://github.com/ansible-collections/community.general/pull/6864).
|
||||
- redfish_info - fix for ``GetVolumeInventory``, Controller name was getting
|
||||
populated incorrectly and duplicates were seen in the volumes retrieved (https://github.com/ansible-collections/community.general/pull/6719).
|
||||
- 'rhsm_repository - the interaction with ``subscription-manager`` was
|
||||
|
||||
refactored by grouping things together, removing unused bits, and hardening
|
||||
|
||||
the way it is run; also, the parsing of ``subscription-manager repos --list``
|
||||
|
||||
was improved and made slightly faster; no behaviour change is expected
|
||||
|
||||
(https://github.com/ansible-collections/community.general/pull/6783,
|
||||
|
||||
https://github.com/ansible-collections/community.general/pull/6837).
|
||||
|
||||
'
|
||||
- scaleway_security_group_rule - minor refactor removing unnecessary code statements
|
||||
(https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- snap - add option ``dangerous`` to the module, that will map into the command
|
||||
line argument ``--dangerous``, allowing unsigned snap files to be installed
|
||||
(https://github.com/ansible-collections/community.general/pull/6908, https://github.com/ansible-collections/community.general/issues/5715).
|
||||
- tss lookup plugin - allow to fetch secret by path. Previously, we could not
|
||||
fetch secret by path but now use ``secret_path`` option to indicate to fetch
|
||||
secret by secret path (https://github.com/ansible-collections/community.general/pull/6881).
|
||||
- xenserver_guest_info - minor refactor removing unnecessary code statements
|
||||
(https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- xenserver_guest_powerstate - minor refactor removing unnecessary code statements
|
||||
(https://github.com/ansible-collections/community.general/pull/6928).
|
||||
- yum_versionlock - add support to pin specific package versions instead of
|
||||
only the package itself (https://github.com/ansible-collections/community.general/pull/6861,
|
||||
https://github.com/ansible-collections/community.general/issues/4470).
|
||||
release_summary: Regular bugfix and feature release.
|
||||
fragments:
|
||||
- 6713-yay-become.yml
|
||||
- 6719-redfish-utils-fix-for-get-volume-inventory.yml
|
||||
- 6734-keycloak-auth-management-indexing.yml
|
||||
- 6755-refactor-consul-session-to-use-requests-lib-instead-of-consul.yml
|
||||
- 6757-proxmox-template-fix-upload-error.yml
|
||||
- 6763-keycloak-auth-provider-choices.yml
|
||||
- 6769-nmcli-fix-empty-list.yml
|
||||
- 6770-proxmox_disk_create_cdrom.yml
|
||||
- 6771-redfish-filter-empty-account-slots.yml
|
||||
- 6773-proxmox_kvm-restarted-state-bug-fix.yaml
|
||||
- 6774-locale-gen-fix.yml
|
||||
- 6783-6837-rhsm_repository-internal-refactor.yml
|
||||
- 6785-openbsd_pkg_pkg_info_handling.yml
|
||||
- 6811-datadog-downtime-rrule-type.yaml
|
||||
- 6820-locale-gen-cmdrunner.yml
|
||||
- 6823-redfish-add-account-type-management.yml
|
||||
- 6826-snap-out-strip.yml
|
||||
- 6827-proxmox_kvm-force-delete-bug-fix.yaml
|
||||
- 6835-snap-missing-track.yml
|
||||
- 6836-proxmox-deprecate-compatibility.yml
|
||||
- 6838-proxmox-dict-template.yml
|
||||
- 6839-promoxer-tokens.yml
|
||||
- 6841-htpasswd-crypt-scheme.yml
|
||||
- 6848-npm-required-if.yml
|
||||
- 6861-yum_versionlock_minor_change_add-pinning-specific-versions.yml
|
||||
- 6862-opkg-exec.yml
|
||||
- 6864-redfish-utils-fix-for-processorarchitecture-in-cpu-inventory.yaml
|
||||
- 6882-make-multiple-targets.yml
|
||||
- 6883-redfish-utils-changing-variable-names-in-get-volume-inventory.yml
|
||||
- 6887-deprecate-stackdrive.yml
|
||||
- 6902-added-support-in-nmcli-for-ipvx-dns-options.yml
|
||||
- 6903-locale-gen-refactor.yml
|
||||
- 6905-ipa_dnszone-key-error-fix.yml
|
||||
- 6908-snap-dangerous.yml
|
||||
- 6909-deprecate-webfaction.yml
|
||||
- 6923-cobbler-inventory_unicode.yml
|
||||
- 6925-cobbler-inventory-bugfix.yml
|
||||
- 6927-pylint-comments.yml
|
||||
- 6928-noqa-comments.yml
|
||||
- 6930-deprecate-flowdock.yml
|
||||
- 6935-machinectl-become.yml
|
||||
- 7.2.0.yml
|
||||
- get-secret-by-path.yml
|
||||
modules:
|
||||
- description: Manipulate Consul policies
|
||||
name: consul_policy
|
||||
namespace: ''
|
||||
- description: Allows administration of Keycloak client authorization permissions
|
||||
via Keycloak API
|
||||
name: keycloak_authz_permission
|
||||
namespace: ''
|
||||
- description: Query Keycloak client authorization permissions information
|
||||
name: keycloak_authz_permission_info
|
||||
namespace: ''
|
||||
- description: Retrieve information about one or more Proxmox VE virtual machines
|
||||
name: proxmox_vm_info
|
||||
namespace: ''
|
||||
plugins:
|
||||
lookup:
|
||||
- description: Retrieve secrets from Bitwarden Secrets Manager
|
||||
name: bitwarden_secrets_manager
|
||||
namespace: null
|
||||
release_date: '2023-07-17'
|
||||
7.2.1:
|
||||
changes:
|
||||
bugfixes:
|
||||
- cmd_runner module utils - when a parameter in ``argument_spec`` has no type,
|
||||
meaning it is implicitly a ``str``, ``CmdRunner`` would fail trying to find
|
||||
the ``type`` key in that dictionary (https://github.com/ansible-collections/community.general/pull/6968).
|
||||
- ejabberd_user - provide meaningful error message when the ``ejabberdctl``
|
||||
command is not found (https://github.com/ansible-collections/community.general/pull/7028,
|
||||
https://github.com/ansible-collections/community.general/issues/6949).
|
||||
- proxmox module utils - fix proxmoxer library version check (https://github.com/ansible-collections/community.general/issues/6974,
|
||||
https://github.com/ansible-collections/community.general/issues/6975, https://github.com/ansible-collections/community.general/pull/6980).
|
||||
- proxmox_kvm - when ``name`` option is provided without ``vmid`` and VM with
|
||||
that name already exists then no new VM will be created (https://github.com/ansible-collections/community.general/issues/6911,
|
||||
https://github.com/ansible-collections/community.general/pull/6981).
|
||||
- rundeck - fix ``TypeError`` on 404 API response (https://github.com/ansible-collections/community.general/pull/6983).
|
||||
release_summary: Bugfix release.
|
||||
fragments:
|
||||
- 6949-ejabberdctl-error.yml
|
||||
- 6968-cmdrunner-implicit-type.yml
|
||||
- 6980-proxmox-fix-token-auth.yml
|
||||
- 6981-proxmox-fix-vm-creation-when-only-name-provided.yml
|
||||
- 6983-rundeck-fix-typerrror-on-404-api-response.yml
|
||||
- 7.2.1.yml
|
||||
release_date: '2023-07-31'
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 7.1.0
|
||||
version: 7.2.1
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
@@ -150,6 +150,12 @@ plugin_routing:
|
||||
warning_text: You are using an internal name to access the community.general.airbrake_deployment
|
||||
modules. This has never been supported or documented, and will stop working
|
||||
in community.general 9.0.0.
|
||||
stackdriver:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: >
|
||||
This module relies on HTTPS APIs that do not exist anymore, and any new development in the
|
||||
direction of providing an alternative should happen in the context of the google.cloud collection.
|
||||
system.aix_devices:
|
||||
redirect: community.general.aix_devices
|
||||
deprecation:
|
||||
@@ -798,6 +804,10 @@ plugin_routing:
|
||||
warning_text: You are using an internal name to access the community.general.flatpak_remote
|
||||
modules. This has never been supported or documented, and will stop working
|
||||
in community.general 9.0.0.
|
||||
flowdock:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
notification.flowdock:
|
||||
redirect: community.general.flowdock
|
||||
deprecation:
|
||||
@@ -4433,6 +4443,10 @@ plugin_routing:
|
||||
warning_text: You are using an internal name to access the community.general.wdc_redfish_info
|
||||
modules. This has never been supported or documented, and will stop working
|
||||
in community.general 9.0.0.
|
||||
webfaction_app:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_app:
|
||||
redirect: community.general.webfaction_app
|
||||
deprecation:
|
||||
@@ -4440,6 +4454,10 @@ plugin_routing:
|
||||
warning_text: You are using an internal name to access the community.general.webfaction_app
|
||||
modules. This has never been supported or documented, and will stop working
|
||||
in community.general 9.0.0.
|
||||
webfaction_db:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_db:
|
||||
redirect: community.general.webfaction_db
|
||||
deprecation:
|
||||
@@ -4447,6 +4465,10 @@ plugin_routing:
|
||||
warning_text: You are using an internal name to access the community.general.webfaction_db
|
||||
modules. This has never been supported or documented, and will stop working
|
||||
in community.general 9.0.0.
|
||||
webfaction_domain:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_domain:
|
||||
redirect: community.general.webfaction_domain
|
||||
deprecation:
|
||||
@@ -4454,6 +4476,10 @@ plugin_routing:
|
||||
warning_text: You are using an internal name to access the community.general.webfaction_domain
|
||||
modules. This has never been supported or documented, and will stop working
|
||||
in community.general 9.0.0.
|
||||
webfaction_mailbox:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_mailbox:
|
||||
redirect: community.general.webfaction_mailbox
|
||||
deprecation:
|
||||
@@ -4461,6 +4487,10 @@ plugin_routing:
|
||||
warning_text: You are using an internal name to access the community.general.webfaction_mailbox
|
||||
modules. This has never been supported or documented, and will stop working
|
||||
in community.general 9.0.0.
|
||||
webfaction_site:
|
||||
deprecation:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relies on HTTPS APIs that do not exist anymore and there is no clear path to update.
|
||||
cloud.webfaction.webfaction_site:
|
||||
redirect: community.general.webfaction_site
|
||||
deprecation:
|
||||
|
||||
@@ -102,6 +102,7 @@ class BecomeModule(BecomeBase):
|
||||
prompt = 'Password: '
|
||||
fail = ('==== AUTHENTICATION FAILED ====',)
|
||||
success = ('==== AUTHENTICATION COMPLETE ====',)
|
||||
require_tty = True # see https://github.com/ansible-collections/community.general/issues/6932
|
||||
|
||||
@staticmethod
|
||||
def remove_ansi_codes(line):
|
||||
|
||||
@@ -48,6 +48,25 @@ DOCUMENTATION = '''
|
||||
default: chroot
|
||||
'''
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Static inventory file
|
||||
#
|
||||
# [chroots]
|
||||
# /path/to/debootstrap
|
||||
# /path/to/feboostrap
|
||||
# /path/to/lxc-image
|
||||
# /path/to/chroot
|
||||
|
||||
# playbook
|
||||
---
|
||||
- hosts: chroots
|
||||
connection: community.general.chroot
|
||||
tasks:
|
||||
- debug:
|
||||
msg: "This is coming from chroot environment"
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
@@ -29,11 +29,13 @@ options:
|
||||
api_token_id:
|
||||
description:
|
||||
- Specify the token ID.
|
||||
- Requires C(proxmoxer>=1.1.0) to work.
|
||||
type: str
|
||||
version_added: 1.3.0
|
||||
api_token_secret:
|
||||
description:
|
||||
- Specify the token secret.
|
||||
- Requires C(proxmoxer>=1.1.0) to work.
|
||||
type: str
|
||||
version_added: 1.3.0
|
||||
validate_certs:
|
||||
|
||||
@@ -104,6 +104,7 @@ import socket
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, to_safe_group_name
|
||||
from ansible.module_utils.six import text_type
|
||||
|
||||
# xmlrpc
|
||||
try:
|
||||
@@ -145,7 +146,7 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
|
||||
self.connection = xmlrpc_client.Server(self.cobbler_url, allow_none=True)
|
||||
self.token = None
|
||||
if self.get_option('user') is not None:
|
||||
self.token = self.connection.login(self.get_option('user'), self.get_option('password'))
|
||||
self.token = self.connection.login(text_type(self.get_option('user')), text_type(self.get_option('password')))
|
||||
return self.connection
|
||||
|
||||
def _init_cache(self):
|
||||
@@ -307,20 +308,22 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
|
||||
|
||||
# Add host variables
|
||||
ip_address = None
|
||||
ip_address_first = None
|
||||
ipv6_address = None
|
||||
ipv6_address_first = None
|
||||
for iname, ivalue in interfaces.items():
|
||||
# Set to first interface or management interface if defined or hostname matches dns_name
|
||||
if ivalue['ip_address'] != "":
|
||||
if ip_address is None:
|
||||
ip_address = ivalue['ip_address']
|
||||
elif ivalue['management']:
|
||||
if ip_address_first is None:
|
||||
ip_address_first = ivalue['ip_address']
|
||||
if ivalue['management']:
|
||||
ip_address = ivalue['ip_address']
|
||||
elif ivalue['dns_name'] == hostname and ip_address is None:
|
||||
ip_address = ivalue['ip_address']
|
||||
if ivalue['ipv6_address'] != "":
|
||||
if ipv6_address is None:
|
||||
ipv6_address = ivalue['ipv6_address']
|
||||
elif ivalue['management']:
|
||||
if ipv6_address_first is None:
|
||||
ipv6_address_first = ivalue['ipv6_address']
|
||||
if ivalue['management']:
|
||||
ipv6_address = ivalue['ipv6_address']
|
||||
elif ivalue['dns_name'] == hostname and ipv6_address is None:
|
||||
ipv6_address = ivalue['ipv6_address']
|
||||
@@ -333,9 +336,13 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
|
||||
if ivalue['ipv6_address'] != "":
|
||||
ip_addresses[ivalue['dns_name']] = ivalue['ipv6_address']
|
||||
|
||||
# Add ip_address to host if defined
|
||||
# Add ip_address to host if defined, use first if no management or matched dns_name
|
||||
if ip_address is None and ip_address_first is not None:
|
||||
ip_address = ip_address_first
|
||||
if ip_address is not None:
|
||||
self.inventory.set_variable(hostname, 'cobbler_ipv4_address', ip_address)
|
||||
if ipv6_address is None and ipv6_address_first is not None:
|
||||
ipv6_address = ipv6_address_first
|
||||
if ipv6_address is not None:
|
||||
self.inventory.set_variable(hostname, 'cobbler_ipv6_address', ipv6_address)
|
||||
|
||||
|
||||
125
plugins/lookup/bitwarden_secrets_manager.py
Normal file
125
plugins/lookup/bitwarden_secrets_manager.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2023, jantari (https://github.com/jantari)
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
name: bitwarden_secrets_manager
|
||||
author:
|
||||
- jantari (@jantari)
|
||||
requirements:
|
||||
- bws (command line utility)
|
||||
short_description: Retrieve secrets from Bitwarden Secrets Manager
|
||||
version_added: 7.2.0
|
||||
description:
|
||||
- Retrieve secrets from Bitwarden Secrets Manager.
|
||||
options:
|
||||
_terms:
|
||||
description: Secret ID(s) to fetch values for.
|
||||
required: true
|
||||
type: list
|
||||
elements: str
|
||||
bws_access_token:
|
||||
description: The BWS access token to use for this lookup.
|
||||
env:
|
||||
- name: BWS_ACCESS_TOKEN
|
||||
required: true
|
||||
type: str
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Get a secret relying on the BWS_ACCESS_TOKEN environment variable for authentication
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ lookup("community.general.bitwarden_secrets_manager", "2bc23e48-4932-40de-a047-5524b7ddc972") }}
|
||||
|
||||
- name: Get a secret passing an explicit access token for authentication
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{
|
||||
lookup(
|
||||
"community.general.bitwarden_secrets_manager",
|
||||
"2bc23e48-4932-40de-a047-5524b7ddc972",
|
||||
bws_access_token="9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg=="
|
||||
)
|
||||
}}
|
||||
|
||||
- name: Get two different secrets each using a different access token for authentication
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- '{{ lookup("community.general.bitwarden_secrets_manager", "2bc23e48-4932-40de-a047-5524b7ddc972", bws_access_token=token1) }}'
|
||||
- '{{ lookup("community.general.bitwarden_secrets_manager", "9d89af4c-eb5d-41f5-bb0f-4ae81215c768", bws_access_token=token2) }}'
|
||||
vars:
|
||||
token1: "9.4f570d14-4b54-42f5-bc07-60f4450b1db5.YmluYXJ5LXNvbWV0aGluZy0xMjMK:d2h5IGhlbGxvIHRoZXJlCg=="
|
||||
token2: "1.69b72797-6ea9-4687-a11e-848e41a30ae6.YW5zaWJsZSBpcyBncmVhdD8K:YW5zaWJsZSBpcyBncmVhdAo="
|
||||
|
||||
- name: Get just the value of a secret
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ lookup("community.general.bitwarden_secrets_manager", "2bc23e48-4932-40de-a047-5524b7ddc972").value }}
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
_raw:
|
||||
description: List containing one or more secrets.
|
||||
type: list
|
||||
elements: dict
|
||||
"""
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from ansible.errors import AnsibleLookupError
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.parsing.ajson import AnsibleJSONDecoder
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
class BitwardenSecretsManagerException(AnsibleLookupError):
|
||||
pass
|
||||
|
||||
|
||||
class BitwardenSecretsManager(object):
|
||||
def __init__(self, path='bws'):
|
||||
self._cli_path = path
|
||||
|
||||
@property
|
||||
def cli_path(self):
|
||||
return self._cli_path
|
||||
|
||||
def _run(self, args, stdin=None):
|
||||
p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
||||
out, err = p.communicate(stdin)
|
||||
rc = p.wait()
|
||||
return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict'), rc
|
||||
|
||||
def get_secret(self, secret_id, bws_access_token):
|
||||
"""Get and return the secret with the given secret_id.
|
||||
"""
|
||||
|
||||
# Prepare set of params for Bitwarden Secrets Manager CLI
|
||||
# Color output was not always disabled correctly with the default 'auto' setting so explicitly disable it.
|
||||
params = [
|
||||
'--color', 'no',
|
||||
'--access-token', bws_access_token,
|
||||
'get', 'secret', secret_id
|
||||
]
|
||||
|
||||
out, err, rc = self._run(params)
|
||||
if rc != 0:
|
||||
raise BitwardenSecretsManagerException(to_text(err))
|
||||
|
||||
return AnsibleJSONDecoder().raw_decode(out)[0]
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def run(self, terms, variables=None, **kwargs):
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
bws_access_token = self.get_option('bws_access_token')
|
||||
|
||||
return [_bitwarden_secrets_manager.get_secret(term, bws_access_token) for term in terms]
|
||||
|
||||
|
||||
_bitwarden_secrets_manager = BitwardenSecretsManager()
|
||||
@@ -39,6 +39,10 @@ DOCUMENTATION = '''
|
||||
- toggle checking that the ssl certificates are valid, you normally only want to turn this off with self-signed certs.
|
||||
default: true
|
||||
type: boolean
|
||||
seealso:
|
||||
- module: community.general.etcd3
|
||||
- plugin: community.general.etcd3
|
||||
plugin_type: lookup
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
@@ -94,8 +94,8 @@ DOCUMENTATION = '''
|
||||
environment variable and keep O(endpoints), O(host), and O(port) unused.
|
||||
seealso:
|
||||
- module: community.general.etcd3
|
||||
- ref: ansible_collections.community.general.etcd_lookup
|
||||
description: The etcd v2 lookup.
|
||||
- plugin: community.general.etcd
|
||||
plugin_type: lookup
|
||||
|
||||
requirements:
|
||||
- "etcd3 >= 0.10"
|
||||
|
||||
@@ -80,18 +80,18 @@ EXAMPLES = """
|
||||
|
||||
- name: Retrieve password for HAL when not signed in to 1Password
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.onepassword'
|
||||
'HAL 9000'
|
||||
subdomain='Discovery'
|
||||
var: lookup('community.general.onepassword',
|
||||
'HAL 9000',
|
||||
subdomain='Discovery',
|
||||
master_password=vault_master_password)
|
||||
|
||||
- name: Retrieve password for HAL when never signed in to 1Password
|
||||
ansible.builtin.debug:
|
||||
var: lookup('community.general.onepassword'
|
||||
'HAL 9000'
|
||||
subdomain='Discovery'
|
||||
master_password=vault_master_password
|
||||
username='tweety@acme.com'
|
||||
var: lookup('community.general.onepassword',
|
||||
'HAL 9000',
|
||||
subdomain='Discovery',
|
||||
master_password=vault_master_password,
|
||||
username='tweety@acme.com',
|
||||
secret_key=vault_secret_key)
|
||||
"""
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ DOCUMENTATION = r"""
|
||||
version_added: '3.2.0'
|
||||
description:
|
||||
- Generates random string based upon the given constraints.
|
||||
- Uses L(random.SystemRandom,https://docs.python.org/3/library/random.html#random.SystemRandom),
|
||||
so should be strong enough for cryptographic purposes.
|
||||
options:
|
||||
length:
|
||||
description: The length of the string.
|
||||
|
||||
@@ -26,6 +26,11 @@ options:
|
||||
description: The integer ID of the secret.
|
||||
required: true
|
||||
type: int
|
||||
secret_path:
|
||||
description: Indicate a full path of secret including folder and secret name when the secret ID is set to 0.
|
||||
required: false
|
||||
type: str
|
||||
version_added: 7.2.0
|
||||
fetch_secret_ids_from_folder:
|
||||
description:
|
||||
- Boolean flag which indicates whether secret ids are in a folder is fetched by folder ID or not.
|
||||
@@ -221,6 +226,29 @@ EXAMPLES = r"""
|
||||
the secret id's are {{
|
||||
secret
|
||||
}}
|
||||
|
||||
# If secret ID is 0 and secret_path has value then secret is fetched by secret path
|
||||
- hosts: localhost
|
||||
vars:
|
||||
secret: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.general.tss',
|
||||
0,
|
||||
secret_path='\folderName\secretName'
|
||||
base_url='https://secretserver.domain.com/SecretServer/',
|
||||
username='user.name',
|
||||
password='password'
|
||||
)
|
||||
}}
|
||||
tasks:
|
||||
- ansible.builtin.debug:
|
||||
msg: >
|
||||
the password is {{
|
||||
(secret['items']
|
||||
| items2dict(key_name='slug',
|
||||
value_name='itemValue'))['password']
|
||||
}}
|
||||
"""
|
||||
|
||||
import abc
|
||||
@@ -231,32 +259,23 @@ from ansible.plugins.lookup import LookupBase
|
||||
from ansible.utils.display import Display
|
||||
|
||||
try:
|
||||
from delinea.secrets.server import SecretServer, SecretServerError
|
||||
from delinea.secrets.server import SecretServer, SecretServerError, PasswordGrantAuthorizer, DomainPasswordGrantAuthorizer, AccessTokenAuthorizer
|
||||
|
||||
HAS_TSS_SDK = True
|
||||
HAS_DELINEA_SS_SDK = True
|
||||
HAS_TSS_AUTHORIZER = True
|
||||
except ImportError:
|
||||
try:
|
||||
from thycotic.secrets.server import SecretServer, SecretServerError
|
||||
from thycotic.secrets.server import SecretServer, SecretServerError, PasswordGrantAuthorizer, DomainPasswordGrantAuthorizer, AccessTokenAuthorizer
|
||||
|
||||
HAS_TSS_SDK = True
|
||||
HAS_DELINEA_SS_SDK = False
|
||||
HAS_TSS_AUTHORIZER = True
|
||||
except ImportError:
|
||||
SecretServer = None
|
||||
SecretServerError = None
|
||||
HAS_TSS_SDK = False
|
||||
HAS_DELINEA_SS_SDK = False
|
||||
|
||||
try:
|
||||
from thycotic.secrets.server import PasswordGrantAuthorizer, DomainPasswordGrantAuthorizer, AccessTokenAuthorizer
|
||||
|
||||
HAS_TSS_AUTHORIZER = True
|
||||
except ImportError:
|
||||
try:
|
||||
from delinea.secrets.server import PasswordGrantAuthorizer, DomainPasswordGrantAuthorizer, AccessTokenAuthorizer
|
||||
|
||||
HAS_TSS_AUTHORIZER = True
|
||||
except ImportError:
|
||||
PasswordGrantAuthorizer = None
|
||||
DomainPasswordGrantAuthorizer = None
|
||||
AccessTokenAuthorizer = None
|
||||
@@ -278,13 +297,21 @@ class TSSClient(object):
|
||||
else:
|
||||
return TSSClientV0(**server_parameters)
|
||||
|
||||
def get_secret(self, term, fetch_file_attachments, file_download_path):
|
||||
def get_secret(self, term, secret_path, fetch_file_attachments, file_download_path):
|
||||
display.debug("tss_lookup term: %s" % term)
|
||||
secret_id = self._term_to_secret_id(term)
|
||||
display.vvv(u"Secret Server lookup of Secret with ID %d" % secret_id)
|
||||
if secret_id == 0 and secret_path:
|
||||
fetch_secret_by_path = True
|
||||
display.vvv(u"Secret Server lookup of Secret with path %s" % secret_path)
|
||||
else:
|
||||
fetch_secret_by_path = False
|
||||
display.vvv(u"Secret Server lookup of Secret with ID %d" % secret_id)
|
||||
|
||||
if fetch_file_attachments:
|
||||
obj = self._client.get_secret(secret_id, fetch_file_attachments)
|
||||
if fetch_secret_by_path:
|
||||
obj = self._client.get_secret_by_path(secret_path, fetch_file_attachments)
|
||||
else:
|
||||
obj = self._client.get_secret(secret_id, fetch_file_attachments)
|
||||
for i in obj['items']:
|
||||
if file_download_path and os.path.isdir(file_download_path):
|
||||
if i['isFile']:
|
||||
@@ -302,7 +329,10 @@ class TSSClient(object):
|
||||
raise AnsibleOptionsError("File download path does not exist")
|
||||
return obj
|
||||
else:
|
||||
return self._client.get_secret_json(secret_id)
|
||||
if fetch_secret_by_path:
|
||||
return self._client.get_secret_by_path(secret_path, False)
|
||||
else:
|
||||
return self._client.get_secret_json(secret_id)
|
||||
|
||||
def get_secret_ids_by_folderid(self, term):
|
||||
display.debug("tss_lookup term: %s" % term)
|
||||
@@ -399,6 +429,14 @@ class LookupModule(LookupBase):
|
||||
else:
|
||||
raise AnsibleError("latest python-tss-sdk must be installed to use this plugin")
|
||||
else:
|
||||
return [tss.get_secret(term, self.get_option("fetch_attachments"), self.get_option("file_download_path")) for term in terms]
|
||||
return [
|
||||
tss.get_secret(
|
||||
term,
|
||||
self.get_option("secret_path"),
|
||||
self.get_option("fetch_attachments"),
|
||||
self.get_option("file_download_path"),
|
||||
)
|
||||
for term in terms
|
||||
]
|
||||
except SecretServerError as error:
|
||||
raise AnsibleError("Secret Server lookup failure: %s" % error.message)
|
||||
|
||||
@@ -208,7 +208,7 @@ class CmdRunner(object):
|
||||
|
||||
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)
|
||||
self.arg_formats[mod_param_name] = _Format.as_default_type(spec.get('type', 'str'), mod_param_name)
|
||||
|
||||
def __call__(self, args_order=None, output_process=None, ignore_value_none=True, check_mode_skip=False, check_mode_return=None, **kwargs):
|
||||
if output_process is None:
|
||||
|
||||
@@ -105,6 +105,17 @@ URL_COMPONENT = "{url}/admin/realms/{realm}/components/{id}"
|
||||
URL_AUTHZ_AUTHORIZATION_SCOPE = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/scope/{id}"
|
||||
URL_AUTHZ_AUTHORIZATION_SCOPES = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/scope"
|
||||
|
||||
# This URL is used for:
|
||||
# - Querying client authorization permissions
|
||||
# - Removing client authorization permissions
|
||||
URL_AUTHZ_POLICIES = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/policy"
|
||||
URL_AUTHZ_POLICY = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/policy/{id}"
|
||||
|
||||
URL_AUTHZ_PERMISSION = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/permission/{permission_type}/{id}"
|
||||
URL_AUTHZ_PERMISSIONS = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/permission/{permission_type}"
|
||||
|
||||
URL_AUTHZ_RESOURCES = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/resource"
|
||||
|
||||
|
||||
def keycloak_argument_spec():
|
||||
"""
|
||||
@@ -2892,3 +2903,69 @@ class KeycloakAPI(object):
|
||||
group_dict['name'] = group
|
||||
list_of_groups.append(group_dict)
|
||||
return list_of_groups
|
||||
|
||||
def get_authz_permission_by_name(self, name, client_id, realm):
|
||||
"""Get authorization permission by name"""
|
||||
url = URL_AUTHZ_POLICIES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
||||
search_url = "%s/search?name=%s" % (url, name.replace(' ', '%20'))
|
||||
|
||||
try:
|
||||
return json.loads(to_native(open_url(search_url, method='GET', http_agent=self.http_agent, headers=self.restheaders,
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def create_authz_permission(self, payload, permission_type, client_id, realm):
|
||||
"""Create an authorization permission for a Keycloak client"""
|
||||
url = URL_AUTHZ_PERMISSIONS.format(url=self.baseurl, permission_type=permission_type, client_id=client_id, realm=realm)
|
||||
|
||||
try:
|
||||
return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(payload), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
|
||||
def remove_authz_permission(self, id, client_id, realm):
|
||||
"""Create an authorization permission for a Keycloak client"""
|
||||
url = URL_AUTHZ_POLICY.format(url=self.baseurl, id=id, client_id=client_id, realm=realm)
|
||||
|
||||
try:
|
||||
return open_url(url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not delete permission %s for client %s in realm %s: %s' % (id, client_id, realm, str(e)))
|
||||
|
||||
def update_authz_permission(self, payload, permission_type, id, client_id, realm):
|
||||
"""Update a permission for a Keycloak client"""
|
||||
url = URL_AUTHZ_PERMISSION.format(url=self.baseurl, permission_type=permission_type, id=id, client_id=client_id, realm=realm)
|
||||
|
||||
try:
|
||||
return open_url(url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(payload), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Could not create update permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e)))
|
||||
|
||||
def get_authz_resource_by_name(self, name, client_id, realm):
|
||||
"""Get authorization resource by name"""
|
||||
url = URL_AUTHZ_RESOURCES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
||||
search_url = "%s/search?name=%s" % (url, name.replace(' ', '%20'))
|
||||
|
||||
try:
|
||||
return json.loads(to_native(open_url(search_url, method='GET', http_agent=self.http_agent, headers=self.restheaders,
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_authz_policy_by_name(self, name, client_id, realm):
|
||||
"""Get authorization policy by name"""
|
||||
url = URL_AUTHZ_POLICIES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
||||
search_url = "%s/search?name=%s&permission=false" % (url, name.replace(' ', '%20'))
|
||||
|
||||
try:
|
||||
return json.loads(to_native(open_url(search_url, method='GET', http_agent=self.http_agent, headers=self.restheaders,
|
||||
timeout=self.connection_timeout,
|
||||
validate_certs=self.validate_certs).read()))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
31
plugins/module_utils/locale_gen.py
Normal file
31
plugins/module_utils/locale_gen.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2023, Alexei Znamensky <russoz@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
|
||||
|
||||
def locale_runner(module):
|
||||
runner = CmdRunner(
|
||||
module,
|
||||
command=["locale", "-a"],
|
||||
check_rc=True,
|
||||
)
|
||||
return runner
|
||||
|
||||
|
||||
def locale_gen_runner(module):
|
||||
runner = CmdRunner(
|
||||
module,
|
||||
command="locale-gen",
|
||||
arg_formats=dict(
|
||||
name=cmd_runner_fmt.as_list(),
|
||||
purge=cmd_runner_fmt.as_fixed('--purge'),
|
||||
),
|
||||
check_rc=True,
|
||||
)
|
||||
return runner
|
||||
@@ -18,6 +18,7 @@ import traceback
|
||||
PROXMOXER_IMP_ERR = None
|
||||
try:
|
||||
from proxmoxer import ProxmoxAPI
|
||||
from proxmoxer import __version__ as proxmoxer_version
|
||||
HAS_PROXMOXER = True
|
||||
except ImportError:
|
||||
HAS_PROXMOXER = False
|
||||
@@ -79,6 +80,7 @@ class ProxmoxAnsible(object):
|
||||
module.fail_json(msg=missing_required_lib('proxmoxer'), exception=PROXMOXER_IMP_ERR)
|
||||
|
||||
self.module = module
|
||||
self.proxmoxer_version = proxmoxer_version
|
||||
self.proxmox_api = self._connect()
|
||||
# Test token validity
|
||||
try:
|
||||
@@ -98,6 +100,8 @@ class ProxmoxAnsible(object):
|
||||
if api_password:
|
||||
auth_args['password'] = api_password
|
||||
else:
|
||||
if self.proxmoxer_version < LooseVersion('1.1.0'):
|
||||
self.module.fail_json('Using "token_name" and "token_value" require proxmoxer>=1.1.0')
|
||||
auth_args['token_name'] = api_token_id
|
||||
auth_args['token_value'] = api_token_secret
|
||||
|
||||
|
||||
@@ -897,13 +897,13 @@ class RedfishUtils(object):
|
||||
if data.get('Members'):
|
||||
for controller in data[u'Members']:
|
||||
controller_list.append(controller[u'@odata.id'])
|
||||
for c in controller_list:
|
||||
for idx, c in enumerate(controller_list):
|
||||
uri = self.root_uri + c
|
||||
response = self.get_request(uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
controller_name = 'Controller 1'
|
||||
controller_name = 'Controller %s' % str(idx)
|
||||
if 'StorageControllers' in data:
|
||||
sc = data['StorageControllers']
|
||||
if sc:
|
||||
@@ -912,7 +912,26 @@ class RedfishUtils(object):
|
||||
else:
|
||||
sc_id = sc[0].get('Id', '1')
|
||||
controller_name = 'Controller %s' % sc_id
|
||||
elif 'Controllers' in data:
|
||||
response = self.get_request(self.root_uri + data['Controllers'][u'@odata.id'])
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
c_data = response['data']
|
||||
|
||||
if c_data.get('Members') and c_data['Members']:
|
||||
response = self.get_request(self.root_uri + c_data['Members'][0][u'@odata.id'])
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
member_data = response['data']
|
||||
|
||||
if member_data:
|
||||
if 'Name' in member_data:
|
||||
controller_name = member_data['Name']
|
||||
else:
|
||||
controller_id = member_data.get('Id', '1')
|
||||
controller_name = 'Controller %s' % controller_id
|
||||
volume_results = []
|
||||
volume_list = []
|
||||
if 'Volumes' in data:
|
||||
# Get a list of all volumes and build respective URIs
|
||||
volumes_uri = data[u'Volumes'][u'@odata.id']
|
||||
@@ -1121,7 +1140,8 @@ class RedfishUtils(object):
|
||||
user_list = []
|
||||
users_results = []
|
||||
# Get these entries, but does not fail if not found
|
||||
properties = ['Id', 'Name', 'UserName', 'RoleId', 'Locked', 'Enabled']
|
||||
properties = ['Id', 'Name', 'UserName', 'RoleId', 'Locked', 'Enabled',
|
||||
'AccountTypes', 'OEMAccountTypes']
|
||||
|
||||
response = self.get_request(self.root_uri + self.accounts_uri)
|
||||
if response['ret'] is False:
|
||||
@@ -1144,6 +1164,12 @@ class RedfishUtils(object):
|
||||
if property in data:
|
||||
user[property] = data[property]
|
||||
|
||||
# Filter out empty account slots
|
||||
# An empty account slot can be detected if the username is an empty
|
||||
# string and if the account is disabled
|
||||
if user.get('UserName', '') == '' and not user.get('Enabled', False):
|
||||
continue
|
||||
|
||||
users_results.append(user)
|
||||
result["entries"] = users_results
|
||||
return result
|
||||
@@ -1166,6 +1192,10 @@ class RedfishUtils(object):
|
||||
payload['Password'] = user.get('account_password')
|
||||
if user.get('account_roleid'):
|
||||
payload['RoleId'] = user.get('account_roleid')
|
||||
if user.get('account_accounttypes'):
|
||||
payload['AccountTypes'] = user.get('account_accounttypes')
|
||||
if user.get('account_oemaccounttypes'):
|
||||
payload['OEMAccountTypes'] = user.get('account_oemaccounttypes')
|
||||
return self.patch_request(self.root_uri + uri, payload, check_pyld=True)
|
||||
|
||||
def add_user(self, user):
|
||||
@@ -1196,6 +1226,10 @@ class RedfishUtils(object):
|
||||
payload['Password'] = user.get('account_password')
|
||||
if user.get('account_roleid'):
|
||||
payload['RoleId'] = user.get('account_roleid')
|
||||
if user.get('account_accounttypes'):
|
||||
payload['AccountTypes'] = user.get('account_accounttypes')
|
||||
if user.get('account_oemaccounttypes'):
|
||||
payload['OEMAccountTypes'] = user.get('account_oemaccounttypes')
|
||||
if user.get('account_id'):
|
||||
payload['Id'] = user.get('account_id')
|
||||
|
||||
@@ -2262,7 +2296,7 @@ class RedfishUtils(object):
|
||||
key = "Processors"
|
||||
# Get these entries, but does not fail if not found
|
||||
properties = ['Id', 'Name', 'Manufacturer', 'Model', 'MaxSpeedMHz',
|
||||
'TotalCores', 'TotalThreads', 'Status']
|
||||
'ProcessorArchitecture', 'TotalCores', 'TotalThreads', 'Status']
|
||||
|
||||
# Search for 'key' entry and extract URI from it
|
||||
response = self.get_request(self.root_uri + systems_uri)
|
||||
|
||||
@@ -72,7 +72,9 @@ def api_request(module, endpoint, data=None, method="GET"):
|
||||
if info["status"] == 403:
|
||||
module.fail_json(msg="Token authorization failed",
|
||||
execution_info=json.loads(info["body"]))
|
||||
if info["status"] == 409:
|
||||
elif info["status"] == 404:
|
||||
return None, info
|
||||
elif info["status"] == 409:
|
||||
module.fail_json(msg="Job executions limit reached",
|
||||
execution_info=json.loads(info["body"]))
|
||||
elif info["status"] >= 500:
|
||||
|
||||
@@ -39,6 +39,8 @@ def snap_runner(module, **kwargs):
|
||||
classic=cmd_runner_fmt.as_bool("--classic"),
|
||||
channel=cmd_runner_fmt.as_func(lambda v: [] if v == 'stable' else ['--channel', '{0}'.format(v)]),
|
||||
options=cmd_runner_fmt.as_list(),
|
||||
info=cmd_runner_fmt.as_fixed("info"),
|
||||
dangerous=cmd_runner_fmt.as_bool("--dangerous"),
|
||||
),
|
||||
check_rc=False,
|
||||
**kwargs
|
||||
|
||||
@@ -72,7 +72,7 @@ options:
|
||||
description:
|
||||
- Base URL of the API server.
|
||||
required: false
|
||||
default: https://api.bigpanda.io
|
||||
default: "https://api.bigpanda.io"
|
||||
validate_certs:
|
||||
description:
|
||||
- If V(false), SSL certificates for the target url will not be validated. This should only be used
|
||||
|
||||
373
plugins/modules/consul_policy.py
Normal file
373
plugins/modules/consul_policy.py
Normal file
@@ -0,0 +1,373 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022, Håkon Lerring
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: consul_policy
|
||||
short_description: Manipulate Consul policies
|
||||
version_added: 7.2.0
|
||||
description:
|
||||
- Allows the addition, modification and deletion of policies in a consul
|
||||
cluster via the agent. For more details on using and configuring ACLs,
|
||||
see U(https://www.consul.io/docs/guides/acl.html).
|
||||
author:
|
||||
- Håkon Lerring (@Hakon)
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
check_mode:
|
||||
support: none
|
||||
diff_mode:
|
||||
support: none
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the policy should be present or absent.
|
||||
required: false
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
type: str
|
||||
valid_datacenters:
|
||||
description:
|
||||
- Valid datacenters for the policy. All if list is empty.
|
||||
default: []
|
||||
type: list
|
||||
elements: str
|
||||
name:
|
||||
description:
|
||||
- The name that should be associated with the policy, this is opaque
|
||||
to Consul.
|
||||
required: true
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- Description of the policy.
|
||||
required: false
|
||||
type: str
|
||||
default: ''
|
||||
rules:
|
||||
type: str
|
||||
description:
|
||||
- Rule document that should be associated with the current policy.
|
||||
required: false
|
||||
host:
|
||||
description:
|
||||
- Host of the consul agent, defaults to localhost.
|
||||
required: false
|
||||
default: localhost
|
||||
type: str
|
||||
port:
|
||||
type: int
|
||||
description:
|
||||
- The port on which the consul agent is running.
|
||||
required: false
|
||||
default: 8500
|
||||
scheme:
|
||||
description:
|
||||
- The protocol scheme on which the consul agent is running.
|
||||
required: false
|
||||
default: http
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- A management token is required to manipulate the policies.
|
||||
type: str
|
||||
validate_certs:
|
||||
type: bool
|
||||
description:
|
||||
- Whether to verify the TLS certificate of the consul agent or not.
|
||||
required: false
|
||||
default: true
|
||||
requirements:
|
||||
- requests
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
- name: Create a policy with rules
|
||||
community.general.consul_policy:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: foo-access
|
||||
rules: |
|
||||
key "foo" {
|
||||
policy = "read"
|
||||
}
|
||||
key "private/foo" {
|
||||
policy = "deny"
|
||||
}
|
||||
|
||||
- name: Update the rules associated to a policy
|
||||
community.general.consul_policy:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: foo-access
|
||||
rules: |
|
||||
key "foo" {
|
||||
policy = "read"
|
||||
}
|
||||
key "private/foo" {
|
||||
policy = "deny"
|
||||
}
|
||||
event "bbq" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
- name: Remove a policy
|
||||
community.general.consul_policy:
|
||||
host: consul1.example.com
|
||||
token: some_management_acl
|
||||
name: foo-access
|
||||
state: absent
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
operation:
|
||||
description: The operation performed on the policy.
|
||||
returned: changed
|
||||
type: str
|
||||
sample: update
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
from requests.exceptions import ConnectionError
|
||||
import requests
|
||||
has_requests = True
|
||||
except ImportError:
|
||||
has_requests = False
|
||||
|
||||
|
||||
TOKEN_PARAMETER_NAME = "token"
|
||||
HOST_PARAMETER_NAME = "host"
|
||||
SCHEME_PARAMETER_NAME = "scheme"
|
||||
VALIDATE_CERTS_PARAMETER_NAME = "validate_certs"
|
||||
NAME_PARAMETER_NAME = "name"
|
||||
DESCRIPTION_PARAMETER_NAME = "description"
|
||||
PORT_PARAMETER_NAME = "port"
|
||||
RULES_PARAMETER_NAME = "rules"
|
||||
VALID_DATACENTERS_PARAMETER_NAME = "valid_datacenters"
|
||||
STATE_PARAMETER_NAME = "state"
|
||||
|
||||
|
||||
PRESENT_STATE_VALUE = "present"
|
||||
ABSENT_STATE_VALUE = "absent"
|
||||
|
||||
REMOVE_OPERATION = "remove"
|
||||
UPDATE_OPERATION = "update"
|
||||
CREATE_OPERATION = "create"
|
||||
|
||||
_ARGUMENT_SPEC = {
|
||||
NAME_PARAMETER_NAME: dict(required=True),
|
||||
DESCRIPTION_PARAMETER_NAME: dict(required=False, type='str', default=''),
|
||||
PORT_PARAMETER_NAME: dict(default=8500, type='int'),
|
||||
RULES_PARAMETER_NAME: dict(type='str'),
|
||||
VALID_DATACENTERS_PARAMETER_NAME: dict(type='list', elements='str', default=[]),
|
||||
HOST_PARAMETER_NAME: dict(default='localhost'),
|
||||
SCHEME_PARAMETER_NAME: dict(default='http'),
|
||||
TOKEN_PARAMETER_NAME: dict(no_log=True),
|
||||
VALIDATE_CERTS_PARAMETER_NAME: dict(type='bool', default=True),
|
||||
STATE_PARAMETER_NAME: dict(default=PRESENT_STATE_VALUE, choices=[PRESENT_STATE_VALUE, ABSENT_STATE_VALUE]),
|
||||
}
|
||||
|
||||
|
||||
def get_consul_url(configuration):
|
||||
return '%s://%s:%s/v1' % (configuration.scheme,
|
||||
configuration.host, configuration.port)
|
||||
|
||||
|
||||
def get_auth_headers(configuration):
|
||||
if configuration.token is None:
|
||||
return {}
|
||||
else:
|
||||
return {'X-Consul-Token': configuration.token}
|
||||
|
||||
|
||||
class RequestError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def handle_consul_response_error(response):
|
||||
if 400 <= response.status_code < 600:
|
||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
||||
|
||||
|
||||
def update_policy(policy, configuration):
|
||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration), policy['ID'])
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.put(url, headers=headers, json={
|
||||
'Name': configuration.name, # should be equal at this point.
|
||||
'Description': configuration.description,
|
||||
'Rules': configuration.rules,
|
||||
'Datacenters': configuration.valid_datacenters
|
||||
}, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
updated_policy = response.json()
|
||||
|
||||
changed = (
|
||||
policy.get('Rules', "") != updated_policy.get('Rules', "") or
|
||||
policy.get('Description', "") != updated_policy.get('Description', "") or
|
||||
policy.get('Datacenters', []) != updated_policy.get('Datacenters', [])
|
||||
)
|
||||
|
||||
return Output(changed=changed, operation=UPDATE_OPERATION, policy=updated_policy)
|
||||
|
||||
|
||||
def create_policy(configuration):
|
||||
url = '%s/acl/policy' % get_consul_url(configuration)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.put(url, headers=headers, json={
|
||||
'Name': configuration.name,
|
||||
'Description': configuration.description,
|
||||
'Rules': configuration.rules,
|
||||
'Datacenters': configuration.valid_datacenters
|
||||
}, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
created_policy = response.json()
|
||||
|
||||
return Output(changed=True, operation=CREATE_OPERATION, policy=created_policy)
|
||||
|
||||
|
||||
def remove_policy(configuration):
|
||||
policies = get_policies(configuration)
|
||||
|
||||
if configuration.name in policies:
|
||||
|
||||
policy_id = policies[configuration.name]['ID']
|
||||
policy = get_policy(policy_id, configuration)
|
||||
|
||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration),
|
||||
policy['ID'])
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.delete(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
return Output(changed=changed, operation=REMOVE_OPERATION)
|
||||
|
||||
|
||||
def get_policies(configuration):
|
||||
url = '%s/acl/policies' % get_consul_url(configuration)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
policies = response.json()
|
||||
existing_policies_mapped_by_name = dict(
|
||||
(policy['Name'], policy) for policy in policies if policy['Name'] is not None)
|
||||
return existing_policies_mapped_by_name
|
||||
|
||||
|
||||
def get_policy(id, configuration):
|
||||
url = '%s/acl/policy/%s' % (get_consul_url(configuration), id)
|
||||
headers = get_auth_headers(configuration)
|
||||
response = requests.get(url, headers=headers, verify=configuration.validate_certs)
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def set_policy(configuration):
|
||||
policies = get_policies(configuration)
|
||||
|
||||
if configuration.name in policies:
|
||||
index_policy_object = policies[configuration.name]
|
||||
policy_id = policies[configuration.name]['ID']
|
||||
rest_policy_object = get_policy(policy_id, configuration)
|
||||
# merge dicts as some keys are only available in the partial policy
|
||||
policy = index_policy_object.copy()
|
||||
policy.update(rest_policy_object)
|
||||
return update_policy(policy, configuration)
|
||||
else:
|
||||
return create_policy(configuration)
|
||||
|
||||
|
||||
class Configuration:
|
||||
"""
|
||||
Configuration for this module.
|
||||
"""
|
||||
|
||||
def __init__(self, token=None, host=None, scheme=None, validate_certs=None, name=None, description=None, port=None,
|
||||
rules=None, valid_datacenters=None, state=None):
|
||||
self.token = token # type: str
|
||||
self.host = host # type: str
|
||||
self.scheme = scheme # type: str
|
||||
self.validate_certs = validate_certs # type: bool
|
||||
self.name = name # type: str
|
||||
self.description = description # type: str
|
||||
self.port = port # type: int
|
||||
self.rules = rules # type: str
|
||||
self.valid_datacenters = valid_datacenters # type: str
|
||||
self.state = state # type: str
|
||||
|
||||
|
||||
class Output:
|
||||
"""
|
||||
Output of an action of this module.
|
||||
"""
|
||||
|
||||
def __init__(self, changed=None, operation=None, policy=None):
|
||||
self.changed = changed # type: bool
|
||||
self.operation = operation # type: str
|
||||
self.policy = policy # type: dict
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
"""
|
||||
Checks that the required dependencies have been imported.
|
||||
:exception ImportError: if it is detected that any of the required dependencies have not been imported
|
||||
"""
|
||||
|
||||
if not has_requests:
|
||||
raise ImportError(
|
||||
"requests required for this module. See https://pypi.org/project/requests/")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main method.
|
||||
"""
|
||||
module = AnsibleModule(_ARGUMENT_SPEC, supports_check_mode=False)
|
||||
|
||||
try:
|
||||
check_dependencies()
|
||||
except ImportError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
configuration = Configuration(
|
||||
token=module.params.get(TOKEN_PARAMETER_NAME),
|
||||
host=module.params.get(HOST_PARAMETER_NAME),
|
||||
scheme=module.params.get(SCHEME_PARAMETER_NAME),
|
||||
validate_certs=module.params.get(VALIDATE_CERTS_PARAMETER_NAME),
|
||||
name=module.params.get(NAME_PARAMETER_NAME),
|
||||
description=module.params.get(DESCRIPTION_PARAMETER_NAME),
|
||||
port=module.params.get(PORT_PARAMETER_NAME),
|
||||
rules=module.params.get(RULES_PARAMETER_NAME),
|
||||
valid_datacenters=module.params.get(VALID_DATACENTERS_PARAMETER_NAME),
|
||||
state=module.params.get(STATE_PARAMETER_NAME),
|
||||
)
|
||||
|
||||
try:
|
||||
if configuration.state == PRESENT_STATE_VALUE:
|
||||
output = set_policy(configuration)
|
||||
else:
|
||||
output = remove_policy(configuration)
|
||||
except ConnectionError as e:
|
||||
module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
|
||||
configuration.host, configuration.port, str(e)))
|
||||
raise
|
||||
|
||||
return_values = dict(changed=output.changed, operation=output.operation, policy=output.policy)
|
||||
module.exit_json(**return_values)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -17,10 +17,10 @@ description:
|
||||
to implement distributed locks. In depth documentation for working with
|
||||
sessions can be found at http://www.consul.io/docs/internals/sessions.html
|
||||
requirements:
|
||||
- python-consul
|
||||
- requests
|
||||
author:
|
||||
- Steve Gargan (@sgargan)
|
||||
- Håkon Lerring (@Hakon)
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
@@ -147,15 +147,15 @@ EXAMPLES = '''
|
||||
ttl: 600 # sec
|
||||
'''
|
||||
|
||||
try:
|
||||
import consul
|
||||
from requests.exceptions import ConnectionError
|
||||
python_consul_installed = True
|
||||
except ImportError:
|
||||
python_consul_installed = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
try:
|
||||
import requests
|
||||
from requests.exceptions import ConnectionError
|
||||
has_requests = True
|
||||
except ImportError:
|
||||
has_requests = False
|
||||
|
||||
|
||||
def execute(module):
|
||||
|
||||
@@ -169,15 +169,74 @@ def execute(module):
|
||||
remove_session(module)
|
||||
|
||||
|
||||
class RequestError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def handle_consul_response_error(response):
|
||||
if 400 <= response.status_code < 600:
|
||||
raise RequestError('%d %s' % (response.status_code, response.content))
|
||||
|
||||
|
||||
def get_consul_url(module):
|
||||
return '%s://%s:%s/v1' % (module.params.get('scheme'),
|
||||
module.params.get('host'), module.params.get('port'))
|
||||
|
||||
|
||||
def get_auth_headers(module):
|
||||
if 'token' in module.params and module.params.get('token') is not None:
|
||||
return {'X-Consul-Token': module.params.get('token')}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
def list_sessions(module, datacenter):
|
||||
url = '%s/session/list' % get_consul_url(module)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def list_sessions_for_node(module, node, datacenter):
|
||||
url = '%s/session/node/%s' % (get_consul_url(module), node)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_session_info(module, session_id, datacenter):
|
||||
url = '%s/session/info/%s' % (get_consul_url(module), session_id)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.json()
|
||||
|
||||
|
||||
def lookup_sessions(module):
|
||||
|
||||
datacenter = module.params.get('datacenter')
|
||||
|
||||
state = module.params.get('state')
|
||||
consul_client = get_consul_api(module)
|
||||
try:
|
||||
if state == 'list':
|
||||
sessions_list = consul_client.session.list(dc=datacenter)
|
||||
sessions_list = list_sessions(module, datacenter)
|
||||
# Ditch the index, this can be grabbed from the results
|
||||
if sessions_list and len(sessions_list) >= 2:
|
||||
sessions_list = sessions_list[1]
|
||||
@@ -185,14 +244,14 @@ def lookup_sessions(module):
|
||||
sessions=sessions_list)
|
||||
elif state == 'node':
|
||||
node = module.params.get('node')
|
||||
sessions = consul_client.session.node(node, dc=datacenter)
|
||||
sessions = list_sessions_for_node(module, node, datacenter)
|
||||
module.exit_json(changed=True,
|
||||
node=node,
|
||||
sessions=sessions)
|
||||
elif state == 'info':
|
||||
session_id = module.params.get('id')
|
||||
|
||||
session_by_id = consul_client.session.info(session_id, dc=datacenter)
|
||||
session_by_id = get_session_info(module, session_id, datacenter)
|
||||
module.exit_json(changed=True,
|
||||
session_id=session_id,
|
||||
sessions=session_by_id)
|
||||
@@ -201,6 +260,31 @@ def lookup_sessions(module):
|
||||
module.fail_json(msg="Could not retrieve session info %s" % e)
|
||||
|
||||
|
||||
def create_session(module, name, behavior, ttl, node,
|
||||
lock_delay, datacenter, checks):
|
||||
url = '%s/session/create' % get_consul_url(module)
|
||||
headers = get_auth_headers(module)
|
||||
create_data = {
|
||||
"LockDelay": lock_delay,
|
||||
"Node": node,
|
||||
"Name": name,
|
||||
"Checks": checks,
|
||||
"Behavior": behavior,
|
||||
}
|
||||
if ttl is not None:
|
||||
create_data["TTL"] = "%ss" % str(ttl) # TTL is in seconds
|
||||
response = requests.put(
|
||||
url,
|
||||
headers=headers,
|
||||
params={
|
||||
'dc': datacenter},
|
||||
json=create_data,
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
create_session_response_dict = response.json()
|
||||
return create_session_response_dict["ID"]
|
||||
|
||||
|
||||
def update_session(module):
|
||||
|
||||
name = module.params.get('name')
|
||||
@@ -211,18 +295,16 @@ def update_session(module):
|
||||
behavior = module.params.get('behavior')
|
||||
ttl = module.params.get('ttl')
|
||||
|
||||
consul_client = get_consul_api(module)
|
||||
|
||||
try:
|
||||
session = consul_client.session.create(
|
||||
name=name,
|
||||
behavior=behavior,
|
||||
ttl=ttl,
|
||||
node=node,
|
||||
lock_delay=delay,
|
||||
dc=datacenter,
|
||||
checks=checks
|
||||
)
|
||||
session = create_session(module,
|
||||
name=name,
|
||||
behavior=behavior,
|
||||
ttl=ttl,
|
||||
node=node,
|
||||
lock_delay=delay,
|
||||
datacenter=datacenter,
|
||||
checks=checks
|
||||
)
|
||||
module.exit_json(changed=True,
|
||||
session_id=session,
|
||||
name=name,
|
||||
@@ -235,13 +317,22 @@ def update_session(module):
|
||||
module.fail_json(msg="Could not create/update session %s" % e)
|
||||
|
||||
|
||||
def destroy_session(module, session_id):
|
||||
url = '%s/session/destroy/%s' % (get_consul_url(module), session_id)
|
||||
headers = get_auth_headers(module)
|
||||
response = requests.put(
|
||||
url,
|
||||
headers=headers,
|
||||
verify=module.params.get('validate_certs'))
|
||||
handle_consul_response_error(response)
|
||||
return response.content == "true"
|
||||
|
||||
|
||||
def remove_session(module):
|
||||
session_id = module.params.get('id')
|
||||
|
||||
consul_client = get_consul_api(module)
|
||||
|
||||
try:
|
||||
consul_client.session.destroy(session_id)
|
||||
destroy_session(module, session_id)
|
||||
|
||||
module.exit_json(changed=True,
|
||||
session_id=session_id)
|
||||
@@ -250,25 +341,22 @@ def remove_session(module):
|
||||
session_id, e))
|
||||
|
||||
|
||||
def get_consul_api(module):
|
||||
return consul.Consul(host=module.params.get('host'),
|
||||
port=module.params.get('port'),
|
||||
scheme=module.params.get('scheme'),
|
||||
verify=module.params.get('validate_certs'),
|
||||
token=module.params.get('token'))
|
||||
|
||||
|
||||
def test_dependencies(module):
|
||||
if not python_consul_installed:
|
||||
module.fail_json(msg="python-consul required for this module. "
|
||||
"see https://python-consul.readthedocs.io/en/latest/#installation")
|
||||
if not has_requests:
|
||||
raise ImportError(
|
||||
"requests required for this module. See https://pypi.org/project/requests/")
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
checks=dict(type='list', elements='str'),
|
||||
delay=dict(type='int', default='15'),
|
||||
behavior=dict(type='str', default='release', choices=['release', 'delete']),
|
||||
behavior=dict(
|
||||
type='str',
|
||||
default='release',
|
||||
choices=[
|
||||
'release',
|
||||
'delete']),
|
||||
ttl=dict(type='int'),
|
||||
host=dict(type='str', default='localhost'),
|
||||
port=dict(type='int', default=8500),
|
||||
@@ -277,7 +365,15 @@ def main():
|
||||
id=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
node=dict(type='str'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'info', 'list', 'node', 'present']),
|
||||
state=dict(
|
||||
type='str',
|
||||
default='present',
|
||||
choices=[
|
||||
'absent',
|
||||
'info',
|
||||
'list',
|
||||
'node',
|
||||
'present']),
|
||||
datacenter=dict(type='str'),
|
||||
token=dict(type='str', no_log=True),
|
||||
)
|
||||
|
||||
@@ -100,9 +100,9 @@ except ImportError:
|
||||
from ansible.module_utils.common import respawn
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils import distro # pylint: disable=import-error
|
||||
from ansible.module_utils.basic import AnsibleModule # pylint: disable=import-error
|
||||
from ansible.module_utils.urls import open_url # pylint: disable=import-error
|
||||
from ansible.module_utils import distro
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
|
||||
def _respawn_dnf():
|
||||
|
||||
@@ -248,7 +248,8 @@ def build_downtime(module):
|
||||
downtime.timezone = module.params["timezone"]
|
||||
if module.params["rrule"]:
|
||||
downtime.recurrence = DowntimeRecurrence(
|
||||
rrule=module.params["rrule"]
|
||||
rrule=module.params["rrule"],
|
||||
type="rrule",
|
||||
)
|
||||
return downtime
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ class EjabberdUser(object):
|
||||
""" This method will run the any command specified and return the
|
||||
returns using the Ansible common module
|
||||
"""
|
||||
cmd = [self.module.get_bin_path('ejabberdctl'), cmd] + options
|
||||
cmd = [self.module.get_bin_path('ejabberdctl', required=True), cmd] + options
|
||||
self.log('command: %s' % " ".join(cmd))
|
||||
return self.module.run_command(cmd)
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: the endpoints this module relies on do not exist any more and do not resolve to IPs in DNS.
|
||||
alternative: no known alternative at this point
|
||||
|
||||
module: flowdock
|
||||
author: "Matt Coddington (@mcodd)"
|
||||
short_description: Send a message to a flowdock
|
||||
|
||||
@@ -82,8 +82,14 @@ EXAMPLES = '''
|
||||
name: Access Key for Some Machine
|
||||
token: '{{ github_access_token }}'
|
||||
pubkey: '{{ ssh_pub_key.stdout }}'
|
||||
'''
|
||||
|
||||
# Alternatively, a single task can be used reading a key from a file on the controller
|
||||
- name: Authorize key with GitHub
|
||||
community.general.github_key:
|
||||
name: Access Key for Some Machine
|
||||
token: '{{ github_access_token }}'
|
||||
pubkey: "{{ lookup('ansible.builtin.file', '/home/foo/.ssh/id_rsa.pub') }}"
|
||||
'''
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
@@ -181,20 +181,13 @@ project_variable:
|
||||
sample: ['ACCESS_KEY_ID', 'SECRET_ACCESS_KEY']
|
||||
'''
|
||||
|
||||
import traceback
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.api import basic_auth_argument_spec
|
||||
|
||||
GITLAB_IMP_ERR = None
|
||||
try:
|
||||
import gitlab # noqa: F401, pylint: disable=unused-import
|
||||
HAS_GITLAB_PACKAGE = True
|
||||
except Exception:
|
||||
GITLAB_IMP_ERR = traceback.format_exc()
|
||||
HAS_GITLAB_PACKAGE = False
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
auth_argument_spec, gitlab_authentication, ensure_gitlab_package, filter_returned_variables, vars_to_variables
|
||||
auth_argument_spec, gitlab_authentication, ensure_gitlab_package, filter_returned_variables, vars_to_variables,
|
||||
HAS_GITLAB_PACKAGE, GITLAB_IMP_ERR
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -210,13 +210,6 @@ from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
cmp # pylint: disable=used-before-assignment
|
||||
except NameError:
|
||||
def cmp(a, b):
|
||||
return (a > b) - (a < b)
|
||||
|
||||
|
||||
class GitLabRunner(object):
|
||||
def __init__(self, module, gitlab_instance, group=None, project=None):
|
||||
self._module = module
|
||||
@@ -302,7 +295,7 @@ class GitLabRunner(object):
|
||||
list1.sort()
|
||||
list2 = arguments[arg_key]
|
||||
list2.sort()
|
||||
if cmp(list1, list2):
|
||||
if list1 != list2:
|
||||
setattr(runner, arg_key, arguments[arg_key])
|
||||
changed = True
|
||||
else:
|
||||
|
||||
@@ -26,47 +26,48 @@ options:
|
||||
required: true
|
||||
aliases: [ dest, destfile ]
|
||||
description:
|
||||
- Path to the file that contains the usernames and passwords
|
||||
- Path to the file that contains the usernames and passwords.
|
||||
name:
|
||||
type: str
|
||||
required: true
|
||||
aliases: [ username ]
|
||||
description:
|
||||
- User name to add or remove
|
||||
- User name to add or remove.
|
||||
password:
|
||||
type: str
|
||||
required: false
|
||||
description:
|
||||
- Password associated with user.
|
||||
- Must be specified if user does not exist yet.
|
||||
crypt_scheme:
|
||||
hash_scheme:
|
||||
type: str
|
||||
required: false
|
||||
default: "apr_md5_crypt"
|
||||
description:
|
||||
- Encryption scheme to be used. As well as the four choices listed
|
||||
- Hashing scheme to be used. As well as the four choices listed
|
||||
here, you can also use any other hash supported by passlib, such as
|
||||
V(portable_apache22) and V(host_apache24); or V(md5_crypt) and V(sha256_crypt),
|
||||
which are Linux passwd hashes. Only some schemes in addition to
|
||||
which are Linux passwd hashes. Only some schemes in addition to
|
||||
the four choices below will be compatible with Apache or Nginx, and
|
||||
supported schemes depend on passlib version and its dependencies.
|
||||
- See U(https://passlib.readthedocs.io/en/stable/lib/passlib.apache.html#passlib.apache.HtpasswdFile) parameter C(default_scheme).
|
||||
- 'Some of the available choices might be: V(apr_md5_crypt), V(des_crypt), V(ldap_sha1), V(plaintext).'
|
||||
aliases: [crypt_scheme]
|
||||
state:
|
||||
type: str
|
||||
required: false
|
||||
choices: [ present, absent ]
|
||||
default: "present"
|
||||
description:
|
||||
- Whether the user entry should be present or not
|
||||
- Whether the user entry should be present or not.
|
||||
create:
|
||||
required: false
|
||||
type: bool
|
||||
default: true
|
||||
description:
|
||||
- Used with O(state=present). If specified, the file will be created
|
||||
if it does not already exist. If set to V(false), will fail if the
|
||||
file does not exist
|
||||
- Used with O(state=present). If V(true), the file will be created
|
||||
if it does not exist. Conversely, if set to V(false) and the file
|
||||
does not exist it will fail.
|
||||
notes:
|
||||
- "This module depends on the C(passlib) Python library, which needs to be installed on all target systems."
|
||||
- "On Debian, Ubuntu, or Fedora: install C(python-passlib)."
|
||||
@@ -99,7 +100,7 @@ EXAMPLES = """
|
||||
path: /etc/mail/passwords
|
||||
name: alex
|
||||
password: oedu2eGh
|
||||
crypt_scheme: md5_crypt
|
||||
hash_scheme: md5_crypt
|
||||
"""
|
||||
|
||||
|
||||
@@ -131,14 +132,14 @@ def create_missing_directories(dest):
|
||||
os.makedirs(destpath)
|
||||
|
||||
|
||||
def present(dest, username, password, crypt_scheme, create, check_mode):
|
||||
def present(dest, username, password, hash_scheme, create, check_mode):
|
||||
""" Ensures user is present
|
||||
|
||||
Returns (msg, changed) """
|
||||
if crypt_scheme in apache_hashes:
|
||||
if hash_scheme in apache_hashes:
|
||||
context = htpasswd_context
|
||||
else:
|
||||
context = CryptContext(schemes=[crypt_scheme] + apache_hashes)
|
||||
context = CryptContext(schemes=[hash_scheme] + apache_hashes)
|
||||
if not os.path.exists(dest):
|
||||
if not create:
|
||||
raise ValueError('Destination %s does not exist' % dest)
|
||||
@@ -146,9 +147,9 @@ def present(dest, username, password, crypt_scheme, create, check_mode):
|
||||
return ("Create %s" % dest, True)
|
||||
create_missing_directories(dest)
|
||||
if LooseVersion(passlib.__version__) >= LooseVersion('1.6'):
|
||||
ht = HtpasswdFile(dest, new=True, default_scheme=crypt_scheme, context=context)
|
||||
ht = HtpasswdFile(dest, new=True, default_scheme=hash_scheme, context=context)
|
||||
else:
|
||||
ht = HtpasswdFile(dest, autoload=False, default=crypt_scheme, context=context)
|
||||
ht = HtpasswdFile(dest, autoload=False, default=hash_scheme, context=context)
|
||||
if getattr(ht, 'set_password', None):
|
||||
ht.set_password(username, password)
|
||||
else:
|
||||
@@ -157,9 +158,9 @@ def present(dest, username, password, crypt_scheme, create, check_mode):
|
||||
return ("Created %s and added %s" % (dest, username), True)
|
||||
else:
|
||||
if LooseVersion(passlib.__version__) >= LooseVersion('1.6'):
|
||||
ht = HtpasswdFile(dest, new=False, default_scheme=crypt_scheme, context=context)
|
||||
ht = HtpasswdFile(dest, new=False, default_scheme=hash_scheme, context=context)
|
||||
else:
|
||||
ht = HtpasswdFile(dest, default=crypt_scheme, context=context)
|
||||
ht = HtpasswdFile(dest, default=hash_scheme, context=context)
|
||||
|
||||
found = None
|
||||
if getattr(ht, 'check_password', None):
|
||||
@@ -215,7 +216,7 @@ def main():
|
||||
path=dict(type='path', required=True, aliases=["dest", "destfile"]),
|
||||
name=dict(type='str', required=True, aliases=["username"]),
|
||||
password=dict(type='str', required=False, default=None, no_log=True),
|
||||
crypt_scheme=dict(type='str', required=False, default="apr_md5_crypt"),
|
||||
hash_scheme=dict(type='str', required=False, default="apr_md5_crypt", aliases=["crypt_scheme"]),
|
||||
state=dict(type='str', required=False, default="present", choices=["present", "absent"]),
|
||||
create=dict(type='bool', default=True),
|
||||
|
||||
@@ -227,7 +228,7 @@ def main():
|
||||
path = module.params['path']
|
||||
username = module.params['name']
|
||||
password = module.params['password']
|
||||
crypt_scheme = module.params['crypt_scheme']
|
||||
hash_scheme = module.params['hash_scheme']
|
||||
state = module.params['state']
|
||||
create = module.params['create']
|
||||
check_mode = module.check_mode
|
||||
@@ -267,7 +268,7 @@ def main():
|
||||
|
||||
try:
|
||||
if state == 'present':
|
||||
(msg, changed) = present(path, username, password, crypt_scheme, create, check_mode)
|
||||
(msg, changed) = present(path, username, password, hash_scheme, create, check_mode)
|
||||
elif state == 'absent':
|
||||
if not os.path.exists(path):
|
||||
module.exit_json(msg="%s not present" % username,
|
||||
|
||||
@@ -152,7 +152,8 @@ def ensure(module, client):
|
||||
changed = True
|
||||
if not module.check_mode:
|
||||
client.dnszone_add(zone_name=zone_name, details={'idnsallowdynupdate': dynamicupdate, 'idnsallowsyncptr': allowsyncptr})
|
||||
elif ipa_dnszone['idnsallowdynupdate'][0] != str(dynamicupdate).upper() or ipa_dnszone['idnsallowsyncptr'][0] != str(allowsyncptr).upper():
|
||||
elif ipa_dnszone['idnsallowdynupdate'][0] != str(dynamicupdate).upper() or \
|
||||
ipa_dnszone.get('idnsallowsyncptr') and ipa_dnszone['idnsallowsyncptr'][0] != str(allowsyncptr).upper():
|
||||
changed = True
|
||||
if not module.check_mode:
|
||||
client.dnszone_mod(zone_name=zone_name, details={'idnsallowdynupdate': dynamicupdate, 'idnsallowsyncptr': allowsyncptr})
|
||||
|
||||
@@ -27,7 +27,7 @@ options:
|
||||
group:
|
||||
type: str
|
||||
description:
|
||||
- Name of the Jenkins group on the OS.
|
||||
- GID or name of the Jenkins group on the OS.
|
||||
default: jenkins
|
||||
jenkins_home:
|
||||
type: path
|
||||
@@ -47,7 +47,7 @@ options:
|
||||
owner:
|
||||
type: str
|
||||
description:
|
||||
- Name of the Jenkins user on the OS.
|
||||
- UID or name of the Jenkins user on the OS.
|
||||
default: jenkins
|
||||
state:
|
||||
type: str
|
||||
@@ -195,6 +195,29 @@ EXAMPLES = '''
|
||||
url_password: p4ssw0rd
|
||||
url: http://localhost:8888
|
||||
|
||||
#
|
||||
# Example of how to authenticate with serverless deployment
|
||||
#
|
||||
- name: Update plugins on ECS Fargate Jenkins instance
|
||||
community.general.jenkins_plugin:
|
||||
# plugin name and version
|
||||
name: ws-cleanup
|
||||
version: '0.45'
|
||||
# Jenkins home path mounted on ec2-helper VM (example)
|
||||
jenkins_home: "/mnt/{{ jenkins_instance }}"
|
||||
# matching the UID/GID to one in official Jenkins image
|
||||
owner: 1000
|
||||
group: 1000
|
||||
# Jenkins instance URL and admin credentials
|
||||
url: "https://{{ jenkins_instance }}.com/"
|
||||
url_username: admin
|
||||
url_password: p4ssw0rd
|
||||
# make module work from EC2 which has local access
|
||||
# to EFS mount as well as Jenkins URL
|
||||
delegate_to: ec2-helper
|
||||
vars:
|
||||
jenkins_instance: foobar
|
||||
|
||||
#
|
||||
# Example of a Play which handles Jenkins restarts during the state changes
|
||||
#
|
||||
|
||||
@@ -43,6 +43,7 @@ options:
|
||||
providerId:
|
||||
description:
|
||||
- C(providerId) for the new flow when not copied from an existing flow.
|
||||
choices: [ "basic-flow", "client-flow" ]
|
||||
type: str
|
||||
copyFrom:
|
||||
description:
|
||||
@@ -109,77 +110,77 @@ author:
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create an authentication flow from first broker login and add an execution to it.
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
copyFrom: "first broker login"
|
||||
authenticationExecutions:
|
||||
- providerId: "test-execution1"
|
||||
requirement: "REQUIRED"
|
||||
authenticationConfig:
|
||||
alias: "test.execution1.property"
|
||||
config:
|
||||
test1.property: "value"
|
||||
- providerId: "test-execution2"
|
||||
requirement: "REQUIRED"
|
||||
authenticationConfig:
|
||||
alias: "test.execution2.property"
|
||||
config:
|
||||
test2.property: "value"
|
||||
state: present
|
||||
- name: Create an authentication flow from first broker login and add an execution to it.
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
copyFrom: "first broker login"
|
||||
authenticationExecutions:
|
||||
- providerId: "test-execution1"
|
||||
requirement: "REQUIRED"
|
||||
authenticationConfig:
|
||||
alias: "test.execution1.property"
|
||||
config:
|
||||
test1.property: "value"
|
||||
- providerId: "test-execution2"
|
||||
requirement: "REQUIRED"
|
||||
authenticationConfig:
|
||||
alias: "test.execution2.property"
|
||||
config:
|
||||
test2.property: "value"
|
||||
state: present
|
||||
|
||||
- name: Re-create the authentication flow
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
copyFrom: "first broker login"
|
||||
authenticationExecutions:
|
||||
- providerId: "test-provisioning"
|
||||
requirement: "REQUIRED"
|
||||
authenticationConfig:
|
||||
alias: "test.provisioning.property"
|
||||
config:
|
||||
test.provisioning.property: "value"
|
||||
state: present
|
||||
force: true
|
||||
- name: Re-create the authentication flow
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
copyFrom: "first broker login"
|
||||
authenticationExecutions:
|
||||
- providerId: "test-provisioning"
|
||||
requirement: "REQUIRED"
|
||||
authenticationConfig:
|
||||
alias: "test.provisioning.property"
|
||||
config:
|
||||
test.provisioning.property: "value"
|
||||
state: present
|
||||
force: true
|
||||
|
||||
- name: Create an authentication flow with subflow containing an execution.
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
copyFrom: "first broker login"
|
||||
authenticationExecutions:
|
||||
- providerId: "test-execution1"
|
||||
requirement: "REQUIRED"
|
||||
- displayName: "New Subflow"
|
||||
requirement: "REQUIRED"
|
||||
- providerId: "auth-cookie"
|
||||
requirement: "REQUIRED"
|
||||
flowAlias: "New Sublow"
|
||||
state: present
|
||||
- name: Create an authentication flow with subflow containing an execution.
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
copyFrom: "first broker login"
|
||||
authenticationExecutions:
|
||||
- providerId: "test-execution1"
|
||||
requirement: "REQUIRED"
|
||||
- displayName: "New Subflow"
|
||||
requirement: "REQUIRED"
|
||||
- providerId: "auth-cookie"
|
||||
requirement: "REQUIRED"
|
||||
flowAlias: "New Sublow"
|
||||
state: present
|
||||
|
||||
- name: Remove authentication.
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
state: absent
|
||||
- name: Remove authentication.
|
||||
community.general.keycloak_authentication:
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: password
|
||||
realm: master
|
||||
alias: "Copy of first broker login"
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -279,6 +280,8 @@ def create_or_update_executions(kc, config, realm='master'):
|
||||
# Compare the executions to see if it need changes
|
||||
if not is_struct_included(new_exec, existing_executions[exec_index], exclude_key) or exec_index != new_exec_index:
|
||||
exec_found = True
|
||||
if new_exec['index'] is None:
|
||||
new_exec_index = exec_index
|
||||
before += str(existing_executions[exec_index]) + '\n'
|
||||
id_to_update = existing_executions[exec_index]["id"]
|
||||
# Remove exec from list in case 2 exec with same name
|
||||
@@ -331,7 +334,7 @@ def main():
|
||||
meta_args = dict(
|
||||
realm=dict(type='str', required=True),
|
||||
alias=dict(type='str', required=True),
|
||||
providerId=dict(type='str'),
|
||||
providerId=dict(type='str', choices=["basic-flow", "client-flow"]),
|
||||
description=dict(type='str'),
|
||||
copyFrom=dict(type='str'),
|
||||
authenticationExecutions=dict(type='list', elements='dict',
|
||||
|
||||
433
plugins/modules/keycloak_authz_permission.py
Normal file
433
plugins/modules/keycloak_authz_permission.py
Normal file
@@ -0,0 +1,433 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2017, Eike Frost <ei@kefro.st>
|
||||
# Copyright (c) 2021, Christophe Gilles <christophe.gilles54@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: keycloak_authz_permission
|
||||
|
||||
version_added: 7.2.0
|
||||
|
||||
short_description: Allows administration of Keycloak client authorization permissions via Keycloak API
|
||||
|
||||
description:
|
||||
- This module allows the administration of Keycloak client authorization permissions via the Keycloak REST
|
||||
API. Authorization permissions are only available if a client has Authorization enabled.
|
||||
|
||||
- There are some peculiarities in JSON paths and payloads for authorization permissions. In particular
|
||||
POST and PUT operations are targeted at permission endpoints, whereas GET requests go to policies
|
||||
endpoint. To make matters more interesting the JSON responses from GET requests return data in a
|
||||
different format than what is expected for POST and PUT. The end result is that it is not possible to
|
||||
detect changes to things like policies, scopes or resources - at least not without a large number of
|
||||
additional API calls. Therefore this module always updates authorization permissions instead of
|
||||
attempting to determine if changes are truly needed.
|
||||
|
||||
- This module requires access to the REST API via OpenID Connect; the user connecting and the realm
|
||||
being used must have the requisite access rights. In a default Keycloak installation, admin-cli
|
||||
and an admin user would work, as would a separate realm definition with the scope tailored
|
||||
to your needs and a user having the expected roles.
|
||||
|
||||
- The names of module options are snake_cased versions of the camelCase options used by Keycloak.
|
||||
The Authorization Services paths and payloads have not officially been documented by the Keycloak project.
|
||||
U(https://www.puppeteers.net/blog/keycloak-authorization-services-rest-api-paths-and-payload/)
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- State of the authorization permission.
|
||||
- On V(present), the authorization permission will be created (or updated if it exists already).
|
||||
- On V(absent), the authorization permission will be removed if it exists.
|
||||
choices: ['present', 'absent']
|
||||
default: 'present'
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name of the authorization permission to create.
|
||||
type: str
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- The description of the authorization permission.
|
||||
type: str
|
||||
required: false
|
||||
permission_type:
|
||||
description:
|
||||
- The type of authorization permission.
|
||||
- On V(scope) create a scope-based permission.
|
||||
- On V(resource) create a resource-based permission.
|
||||
type: str
|
||||
required: true
|
||||
choices:
|
||||
- resource
|
||||
- scope
|
||||
decision_strategy:
|
||||
description:
|
||||
- The decision strategy to use with this permission.
|
||||
type: str
|
||||
default: UNANIMOUS
|
||||
required: false
|
||||
choices:
|
||||
- UNANIMOUS
|
||||
- AFFIRMATIVE
|
||||
- CONSENSUS
|
||||
resources:
|
||||
description:
|
||||
- Resource names to attach to this permission.
|
||||
- Scope-based permissions can only include one resource.
|
||||
- Resource-based permissions can include multiple resources.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
required: false
|
||||
scopes:
|
||||
description:
|
||||
- Scope names to attach to this permission.
|
||||
- Resource-based permissions cannot have scopes attached to them.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
required: false
|
||||
policies:
|
||||
description:
|
||||
- Policy names to attach to this permission.
|
||||
type: list
|
||||
elements: str
|
||||
default: []
|
||||
required: false
|
||||
client_id:
|
||||
description:
|
||||
- The clientId of the keycloak client that should have the authorization scope.
|
||||
- This is usually a human-readable name of the Keycloak client.
|
||||
type: str
|
||||
required: true
|
||||
realm:
|
||||
description:
|
||||
- The name of the Keycloak realm the Keycloak client is in.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.keycloak
|
||||
- community.general.attributes
|
||||
|
||||
author:
|
||||
- Samuli Seppänen (@mattock)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Manage scope-based Keycloak authorization permission
|
||||
community.general.keycloak_authz_permission:
|
||||
name: ScopePermission
|
||||
state: present
|
||||
description: Scope permission
|
||||
permission_type: scope
|
||||
scopes:
|
||||
- file:delete
|
||||
policies:
|
||||
- Default Policy
|
||||
client_id: myclient
|
||||
realm: myrealm
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_username: keycloak
|
||||
auth_password: keycloak
|
||||
auth_realm: master
|
||||
|
||||
- name: Manage resource-based Keycloak authorization permission
|
||||
community.general.keycloak_authz_permission:
|
||||
name: ResourcePermission
|
||||
state: present
|
||||
description: Resource permission
|
||||
permission_type: resource
|
||||
resources:
|
||||
- Default Resource
|
||||
policies:
|
||||
- Default Policy
|
||||
client_id: myclient
|
||||
realm: myrealm
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_username: keycloak
|
||||
auth_password: keycloak
|
||||
auth_realm: master
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
msg:
|
||||
description: Message as to what action was taken.
|
||||
returned: always
|
||||
type: str
|
||||
|
||||
end_state:
|
||||
description: Representation of the authorization permission after module execution.
|
||||
returned: on success
|
||||
type: complex
|
||||
contains:
|
||||
id:
|
||||
description: ID of the authorization permission.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: 9da05cd2-b273-4354-bbd8-0c133918a454
|
||||
name:
|
||||
description: Name of the authorization permission.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: ResourcePermission
|
||||
description:
|
||||
description: Description of the authorization permission.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: Resource Permission
|
||||
type:
|
||||
description: Type of the authorization permission.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: resource
|
||||
decisionStrategy:
|
||||
description: The decision strategy to use.
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: UNANIMOUS
|
||||
logic:
|
||||
description: The logic used for the permission (part of the payload, but has a fixed value).
|
||||
type: str
|
||||
returned: when O(state=present)
|
||||
sample: POSITIVE
|
||||
resources:
|
||||
description: IDs of resources attached to this permission.
|
||||
type: list
|
||||
returned: when O(state=present)
|
||||
sample:
|
||||
- 49e052ff-100d-4b79-a9dd-52669ed3c11d
|
||||
scopes:
|
||||
description: IDs of scopes attached to this permission.
|
||||
type: list
|
||||
returned: when O(state=present)
|
||||
sample:
|
||||
- 9da05cd2-b273-4354-bbd8-0c133918a454
|
||||
policies:
|
||||
description: IDs of policies attached to this permission.
|
||||
type: list
|
||||
returned: when O(state=present)
|
||||
sample:
|
||||
- 9da05cd2-b273-4354-bbd8-0c133918a454
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \
|
||||
keycloak_argument_spec, get_token, KeycloakError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Module execution
|
||||
|
||||
:return:
|
||||
"""
|
||||
argument_spec = keycloak_argument_spec()
|
||||
|
||||
meta_args = dict(
|
||||
state=dict(type='str', default='present',
|
||||
choices=['present', 'absent']),
|
||||
name=dict(type='str', required=True),
|
||||
description=dict(type='str', required=False),
|
||||
permission_type=dict(type='str', choices=['scope', 'resource'], required=True),
|
||||
decision_strategy=dict(type='str', default='UNANIMOUS',
|
||||
choices=['UNANIMOUS', 'AFFIRMATIVE', 'CONSENSUS']),
|
||||
resources=dict(type='list', elements='str', default=[], required=False),
|
||||
scopes=dict(type='list', elements='str', default=[], required=False),
|
||||
policies=dict(type='list', elements='str', default=[], required=False),
|
||||
client_id=dict(type='str', required=True),
|
||||
realm=dict(type='str', required=True)
|
||||
)
|
||||
|
||||
argument_spec.update(meta_args)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_one_of=(
|
||||
[['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||
|
||||
# Convenience variables
|
||||
state = module.params.get('state')
|
||||
name = module.params.get('name')
|
||||
description = module.params.get('description')
|
||||
permission_type = module.params.get('permission_type')
|
||||
decision_strategy = module.params.get('decision_strategy')
|
||||
realm = module.params.get('realm')
|
||||
client_id = module.params.get('client_id')
|
||||
realm = module.params.get('realm')
|
||||
resources = module.params.get('resources')
|
||||
scopes = module.params.get('scopes')
|
||||
policies = module.params.get('policies')
|
||||
|
||||
if permission_type == 'scope' and state == 'present':
|
||||
if scopes == []:
|
||||
module.fail_json(msg='Scopes need to defined when permission type is set to scope!')
|
||||
if len(resources) > 1:
|
||||
module.fail_json(msg='Only one resource can be defined for a scope permission!')
|
||||
|
||||
if permission_type == 'resource' and state == 'present':
|
||||
if resources == []:
|
||||
module.fail_json(msg='A resource need to defined when permission type is set to resource!')
|
||||
if scopes != []:
|
||||
module.fail_json(msg='Scopes cannot be defined when permission type is set to resource!')
|
||||
|
||||
result = dict(changed=False, msg='', end_state={})
|
||||
|
||||
# Obtain access token, initialize API
|
||||
try:
|
||||
connection_header = get_token(module.params)
|
||||
except KeycloakError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
kc = KeycloakAPI(module, connection_header)
|
||||
|
||||
# Get id of the client based on client_id
|
||||
cid = kc.get_client_id(client_id, realm=realm)
|
||||
if not cid:
|
||||
module.fail_json(msg='Invalid client %s for realm %s' %
|
||||
(client_id, realm))
|
||||
|
||||
# Get current state of the permission using its name as the search
|
||||
# filter. This returns False if it is not found.
|
||||
permission = kc.get_authz_permission_by_name(
|
||||
name=name, client_id=cid, realm=realm)
|
||||
|
||||
# Generate a JSON payload for Keycloak Admin API. This is needed for
|
||||
# "create" and "update" operations.
|
||||
payload = {}
|
||||
payload['name'] = name
|
||||
payload['description'] = description
|
||||
payload['type'] = permission_type
|
||||
payload['decisionStrategy'] = decision_strategy
|
||||
payload['logic'] = 'POSITIVE'
|
||||
payload['scopes'] = []
|
||||
payload['resources'] = []
|
||||
payload['policies'] = []
|
||||
|
||||
if permission_type == 'scope':
|
||||
# Add the resource id, if any, to the payload. While the data type is a
|
||||
# list, it is only possible to have one entry in it based on what Keycloak
|
||||
# Admin Console does.
|
||||
r = False
|
||||
resource_scopes = []
|
||||
|
||||
if resources:
|
||||
r = kc.get_authz_resource_by_name(resources[0], cid, realm)
|
||||
if not r:
|
||||
module.fail_json(msg='Unable to find authorization resource with name %s for client %s in realm %s' % (resources[0], cid, realm))
|
||||
else:
|
||||
payload['resources'] = r['_id']
|
||||
|
||||
for rs in r['scopes']:
|
||||
resource_scopes.append(rs['id'])
|
||||
|
||||
# Generate a list of scope ids based on scope names. Fail if the
|
||||
# defined resource does not include all those scopes.
|
||||
for scope in scopes:
|
||||
s = kc.get_authz_authorization_scope_by_name(scope, cid, realm)
|
||||
if r and not s['id'] in resource_scopes:
|
||||
module.fail_json(msg='Resource %s does not include scope %s for client %s in realm %s' % (resources[0], scope, client_id, realm))
|
||||
else:
|
||||
payload['scopes'].append(s['id'])
|
||||
|
||||
elif permission_type == 'resource':
|
||||
if resources:
|
||||
for resource in resources:
|
||||
r = kc.get_authz_resource_by_name(resource, cid, realm)
|
||||
if not r:
|
||||
module.fail_json(msg='Unable to find authorization resource with name %s for client %s in realm %s' % (resource, cid, realm))
|
||||
else:
|
||||
payload['resources'].append(r['_id'])
|
||||
|
||||
# Add policy ids, if any, to the payload.
|
||||
if policies:
|
||||
for policy in policies:
|
||||
p = kc.get_authz_policy_by_name(policy, cid, realm)
|
||||
|
||||
if p:
|
||||
payload['policies'].append(p['id'])
|
||||
else:
|
||||
module.fail_json(msg='Unable to find authorization policy with name %s for client %s in realm %s' % (policy, client_id, realm))
|
||||
|
||||
# Add "id" to payload for update operations
|
||||
if permission:
|
||||
payload['id'] = permission['id']
|
||||
|
||||
# Handle the special case where the user attempts to change an already
|
||||
# existing permission's type - something that can't be done without a
|
||||
# full delete -> (re)create cycle.
|
||||
if permission['type'] != payload['type']:
|
||||
module.fail_json(msg='Modifying the type of permission (scope/resource) is not supported: \
|
||||
permission %s of client %s in realm %s unchanged' % (permission['id'], cid, realm))
|
||||
|
||||
# Updating an authorization permission is tricky for several reasons.
|
||||
# Firstly, the current permission is retrieved using a _policy_ endpoint,
|
||||
# not from a permission endpoint. Also, the data that is returned is in a
|
||||
# different format than what is expected by the payload. So, comparing the
|
||||
# current state attribute by attribute to the payload is not possible. For
|
||||
# example the data contains a JSON object "config" which may contain the
|
||||
# authorization type, but which is no required in the payload. Moreover,
|
||||
# information about resources, scopes and policies is _not_ present in the
|
||||
# data. So, there is no way to determine if any of those fields have
|
||||
# changed. Therefore the best options we have are
|
||||
#
|
||||
# a) Always apply the payload without checking the current state
|
||||
# b) Refuse to make any changes to any settings (only support create and delete)
|
||||
#
|
||||
# The approach taken here is a).
|
||||
#
|
||||
if permission and state == 'present':
|
||||
if module.check_mode:
|
||||
result['msg'] = 'Notice: unable to check current resources, scopes and policies for permission. \
|
||||
Would apply desired state without checking the current state.'
|
||||
else:
|
||||
kc.update_authz_permission(payload=payload, permission_type=permission_type, id=permission['id'], client_id=cid, realm=realm)
|
||||
result['msg'] = 'Notice: unable to check current resources, scopes and policies for permission. \
|
||||
Applying desired state without checking the current state.'
|
||||
|
||||
# Assume that something changed, although we don't know if that is the case.
|
||||
result['changed'] = True
|
||||
result['end_state'] = payload
|
||||
elif not permission and state == 'present':
|
||||
if module.check_mode:
|
||||
result['msg'] = 'Would create permission'
|
||||
else:
|
||||
kc.create_authz_permission(payload=payload, permission_type=permission_type, client_id=cid, realm=realm)
|
||||
result['msg'] = 'Permission created'
|
||||
|
||||
result['changed'] = True
|
||||
result['end_state'] = payload
|
||||
elif permission and state == 'absent':
|
||||
if module.check_mode:
|
||||
result['msg'] = 'Would remove permission'
|
||||
else:
|
||||
kc.remove_authz_permission(id=permission['id'], client_id=cid, realm=realm)
|
||||
result['msg'] = 'Permission removed'
|
||||
|
||||
result['changed'] = True
|
||||
|
||||
elif not permission and state == 'absent':
|
||||
result['changed'] = False
|
||||
else:
|
||||
module.fail_json(msg='Unable to determine what to do with permission %s of client %s in realm %s' % (
|
||||
name, client_id, realm))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
173
plugins/modules/keycloak_authz_permission_info.py
Normal file
173
plugins/modules/keycloak_authz_permission_info.py
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2017, Eike Frost <ei@kefro.st>
|
||||
# Copyright (c) 2021, Christophe Gilles <christophe.gilles54@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
|
||||
# https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: keycloak_authz_permission_info
|
||||
|
||||
version_added: 7.2.0
|
||||
|
||||
short_description: Query Keycloak client authorization permissions information
|
||||
|
||||
description:
|
||||
- This module allows querying information about Keycloak client authorization permissions from the
|
||||
resources endpoint via the Keycloak REST API. Authorization permissions are only available if a
|
||||
client has Authorization enabled.
|
||||
|
||||
- This module requires access to the REST API via OpenID Connect; the user connecting and the realm
|
||||
being used must have the requisite access rights. In a default Keycloak installation, admin-cli
|
||||
and an admin user would work, as would a separate realm definition with the scope tailored
|
||||
to your needs and a user having the expected roles.
|
||||
|
||||
- The names of module options are snake_cased versions of the camelCase options used by Keycloak.
|
||||
The Authorization Services paths and payloads have not officially been documented by the Keycloak project.
|
||||
U(https://www.puppeteers.net/blog/keycloak-authorization-services-rest-api-paths-and-payload/)
|
||||
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the authorization permission to create.
|
||||
type: str
|
||||
required: true
|
||||
client_id:
|
||||
description:
|
||||
- The clientId of the keycloak client that should have the authorization scope.
|
||||
- This is usually a human-readable name of the Keycloak client.
|
||||
type: str
|
||||
required: true
|
||||
realm:
|
||||
description:
|
||||
- The name of the Keycloak realm the Keycloak client is in.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.general.keycloak
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
|
||||
author:
|
||||
- Samuli Seppänen (@mattock)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Query Keycloak authorization permission
|
||||
community.general.keycloak_authz_permission_info:
|
||||
name: ScopePermission
|
||||
client_id: myclient
|
||||
realm: myrealm
|
||||
auth_keycloak_url: http://localhost:8080/auth
|
||||
auth_username: keycloak
|
||||
auth_password: keycloak
|
||||
auth_realm: master
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
msg:
|
||||
description: Message as to what action was taken.
|
||||
returned: always
|
||||
type: str
|
||||
|
||||
queried_state:
|
||||
description: State of the resource (a policy) as seen by Keycloak.
|
||||
returned: on success
|
||||
type: complex
|
||||
contains:
|
||||
id:
|
||||
description: ID of the authorization permission.
|
||||
type: str
|
||||
sample: 9da05cd2-b273-4354-bbd8-0c133918a454
|
||||
name:
|
||||
description: Name of the authorization permission.
|
||||
type: str
|
||||
sample: ResourcePermission
|
||||
description:
|
||||
description: Description of the authorization permission.
|
||||
type: str
|
||||
sample: Resource Permission
|
||||
type:
|
||||
description: Type of the authorization permission.
|
||||
type: str
|
||||
sample: resource
|
||||
decisionStrategy:
|
||||
description: The decision strategy.
|
||||
type: str
|
||||
sample: UNANIMOUS
|
||||
logic:
|
||||
description: The logic used for the permission (part of the payload, but has a fixed value).
|
||||
type: str
|
||||
sample: POSITIVE
|
||||
config:
|
||||
description: Configuration of the permission (empty in all observed cases).
|
||||
type: dict
|
||||
sample: {}
|
||||
'''
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \
|
||||
keycloak_argument_spec, get_token, KeycloakError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Module execution
|
||||
|
||||
:return:
|
||||
"""
|
||||
argument_spec = keycloak_argument_spec()
|
||||
|
||||
meta_args = dict(
|
||||
name=dict(type='str', required=True),
|
||||
client_id=dict(type='str', required=True),
|
||||
realm=dict(type='str', required=True)
|
||||
)
|
||||
|
||||
argument_spec.update(meta_args)
|
||||
|
||||
module = AnsibleModule(argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_one_of=(
|
||||
[['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||
|
||||
# Convenience variables
|
||||
name = module.params.get('name')
|
||||
client_id = module.params.get('client_id')
|
||||
realm = module.params.get('realm')
|
||||
|
||||
result = dict(changed=False, msg='', queried_state={})
|
||||
|
||||
# Obtain access token, initialize API
|
||||
try:
|
||||
connection_header = get_token(module.params)
|
||||
except KeycloakError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
kc = KeycloakAPI(module, connection_header)
|
||||
|
||||
# Get id of the client based on client_id
|
||||
cid = kc.get_client_id(client_id, realm=realm)
|
||||
if not cid:
|
||||
module.fail_json(msg='Invalid client %s for realm %s' %
|
||||
(client_id, realm))
|
||||
|
||||
# Get current state of the permission using its name as the search
|
||||
# filter. This returns False if it is not found.
|
||||
permission = kc.get_authz_permission_by_name(
|
||||
name=name, client_id=cid, realm=realm)
|
||||
|
||||
result['queried_state'] = permission
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -106,7 +106,7 @@ def del_passphrase(module):
|
||||
try:
|
||||
keyring.delete_password(module.params["service"], module.params["username"])
|
||||
return None
|
||||
except keyring.errors.KeyringLocked as keyring_locked_err: # pylint: disable=unused-variable
|
||||
except keyring.errors.KeyringLocked:
|
||||
delete_argument = (
|
||||
'echo "%s" | gnome-keyring-daemon --unlock\nkeyring del %s %s\n'
|
||||
% (
|
||||
@@ -140,7 +140,7 @@ def set_passphrase(module):
|
||||
module.params["user_password"],
|
||||
)
|
||||
return None
|
||||
except keyring.errors.KeyringLocked as keyring_locked_err: # pylint: disable=unused-variable
|
||||
except keyring.errors.KeyringLocked:
|
||||
set_argument = (
|
||||
'echo "%s" | gnome-keyring-daemon --unlock\nkeyring set %s %s\n%s\n'
|
||||
% (
|
||||
|
||||
@@ -35,6 +35,8 @@ options:
|
||||
- Whether the locale shall be present.
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
notes:
|
||||
- This module does not support RHEL-based systems.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
@@ -46,154 +48,31 @@ EXAMPLES = '''
|
||||
|
||||
import os
|
||||
import re
|
||||
from subprocess import Popen, PIPE, call
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
|
||||
from ansible_collections.community.general.plugins.module_utils.mh.deco import check_mode_skip
|
||||
|
||||
LOCALE_NORMALIZATION = {
|
||||
".utf8": ".UTF-8",
|
||||
".eucjp": ".EUC-JP",
|
||||
".iso885915": ".ISO-8859-15",
|
||||
".cp1251": ".CP1251",
|
||||
".koi8r": ".KOI8-R",
|
||||
".armscii8": ".ARMSCII-8",
|
||||
".euckr": ".EUC-KR",
|
||||
".gbk": ".GBK",
|
||||
".gb18030": ".GB18030",
|
||||
".euctw": ".EUC-TW",
|
||||
}
|
||||
from ansible_collections.community.general.plugins.module_utils.locale_gen import locale_runner, locale_gen_runner
|
||||
|
||||
|
||||
# ===========================================
|
||||
# location module specific support methods.
|
||||
#
|
||||
class LocaleGen(StateModuleHelper):
|
||||
LOCALE_NORMALIZATION = {
|
||||
".utf8": ".UTF-8",
|
||||
".eucjp": ".EUC-JP",
|
||||
".iso885915": ".ISO-8859-15",
|
||||
".cp1251": ".CP1251",
|
||||
".koi8r": ".KOI8-R",
|
||||
".armscii8": ".ARMSCII-8",
|
||||
".euckr": ".EUC-KR",
|
||||
".gbk": ".GBK",
|
||||
".gb18030": ".GB18030",
|
||||
".euctw": ".EUC-TW",
|
||||
}
|
||||
LOCALE_GEN = "/etc/locale.gen"
|
||||
LOCALE_SUPPORTED = "/var/lib/locales/supported.d/"
|
||||
|
||||
def is_available(name, ubuntuMode):
|
||||
"""Check if the given locale is available on the system. This is done by
|
||||
checking either :
|
||||
* if the locale is present in /etc/locales.gen
|
||||
* or if the locale is present in /usr/share/i18n/SUPPORTED"""
|
||||
if ubuntuMode:
|
||||
__regexp = r'^(?P<locale>\S+_\S+) (?P<charset>\S+)\s*$'
|
||||
__locales_available = '/usr/share/i18n/SUPPORTED'
|
||||
else:
|
||||
__regexp = r'^#{0,1}\s*(?P<locale>\S+_\S+) (?P<charset>\S+)\s*$'
|
||||
__locales_available = '/etc/locale.gen'
|
||||
|
||||
re_compiled = re.compile(__regexp)
|
||||
fd = open(__locales_available, 'r')
|
||||
for line in fd:
|
||||
result = re_compiled.match(line)
|
||||
if result and result.group('locale') == name:
|
||||
return True
|
||||
fd.close()
|
||||
return False
|
||||
|
||||
|
||||
def is_present(name):
|
||||
"""Checks if the given locale is currently installed."""
|
||||
output = Popen(["locale", "-a"], stdout=PIPE).communicate()[0]
|
||||
output = to_native(output)
|
||||
return any(fix_case(name) == fix_case(line) for line in output.splitlines())
|
||||
|
||||
|
||||
def fix_case(name):
|
||||
"""locale -a might return the encoding in either lower or upper case.
|
||||
Passing through this function makes them uniform for comparisons."""
|
||||
for s, r in LOCALE_NORMALIZATION.items():
|
||||
name = name.replace(s, r)
|
||||
return name
|
||||
|
||||
|
||||
def replace_line(existing_line, new_line):
|
||||
"""Replaces lines in /etc/locale.gen"""
|
||||
try:
|
||||
f = open("/etc/locale.gen", "r")
|
||||
lines = [line.replace(existing_line, new_line) for line in f]
|
||||
finally:
|
||||
f.close()
|
||||
try:
|
||||
f = open("/etc/locale.gen", "w")
|
||||
f.write("".join(lines))
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
|
||||
def set_locale(name, enabled=True):
|
||||
""" Sets the state of the locale. Defaults to enabled. """
|
||||
search_string = r'#{0,1}\s*%s (?P<charset>.+)' % name
|
||||
if enabled:
|
||||
new_string = r'%s \g<charset>' % (name)
|
||||
else:
|
||||
new_string = r'# %s \g<charset>' % (name)
|
||||
try:
|
||||
f = open("/etc/locale.gen", "r")
|
||||
lines = [re.sub(search_string, new_string, line) for line in f]
|
||||
finally:
|
||||
f.close()
|
||||
try:
|
||||
f = open("/etc/locale.gen", "w")
|
||||
f.write("".join(lines))
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
|
||||
def apply_change(targetState, name):
|
||||
"""Create or remove locale.
|
||||
|
||||
Keyword arguments:
|
||||
targetState -- Desired state, either present or absent.
|
||||
name -- Name including encoding such as de_CH.UTF-8.
|
||||
"""
|
||||
if targetState == "present":
|
||||
# Create locale.
|
||||
set_locale(name, enabled=True)
|
||||
else:
|
||||
# Delete locale.
|
||||
set_locale(name, enabled=False)
|
||||
|
||||
localeGenExitValue = call("locale-gen")
|
||||
if localeGenExitValue != 0:
|
||||
raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned " + str(localeGenExitValue))
|
||||
|
||||
|
||||
def apply_change_ubuntu(targetState, name):
|
||||
"""Create or remove locale.
|
||||
|
||||
Keyword arguments:
|
||||
targetState -- Desired state, either present or absent.
|
||||
name -- Name including encoding such as de_CH.UTF-8.
|
||||
"""
|
||||
if targetState == "present":
|
||||
# Create locale.
|
||||
# Ubuntu's patched locale-gen automatically adds the new locale to /var/lib/locales/supported.d/local
|
||||
localeGenExitValue = call(["locale-gen", name])
|
||||
else:
|
||||
# Delete locale involves discarding the locale from /var/lib/locales/supported.d/local and regenerating all locales.
|
||||
try:
|
||||
f = open("/var/lib/locales/supported.d/local", "r")
|
||||
content = f.readlines()
|
||||
finally:
|
||||
f.close()
|
||||
try:
|
||||
f = open("/var/lib/locales/supported.d/local", "w")
|
||||
for line in content:
|
||||
locale, charset = line.split(' ')
|
||||
if locale != name:
|
||||
f.write(line)
|
||||
finally:
|
||||
f.close()
|
||||
# Purge locales and regenerate.
|
||||
# Please provide a patch if you know how to avoid regenerating the locales to keep!
|
||||
localeGenExitValue = call(["locale-gen", "--purge"])
|
||||
|
||||
if localeGenExitValue != 0:
|
||||
raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned " + str(localeGenExitValue))
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
output_params = ["name"]
|
||||
module = dict(
|
||||
argument_spec=dict(
|
||||
name=dict(type='str', required=True),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
@@ -201,42 +80,133 @@ def main():
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
name = module.params['name']
|
||||
state = module.params['state']
|
||||
|
||||
if not os.path.exists("/var/lib/locales/supported.d/"):
|
||||
if os.path.exists("/etc/locale.gen"):
|
||||
# We found the common way to manage locales.
|
||||
ubuntuMode = False
|
||||
def __init_module__(self):
|
||||
self.vars.set("ubuntu_mode", False)
|
||||
if os.path.exists(self.LOCALE_SUPPORTED):
|
||||
self.vars.ubuntu_mode = True
|
||||
else:
|
||||
module.fail_json(msg="/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package \"locales\" installed?")
|
||||
else:
|
||||
# Ubuntu created its own system to manage locales.
|
||||
ubuntuMode = True
|
||||
if not os.path.exists(self.LOCALE_GEN):
|
||||
self.do_raise("{0} and {1} are missing. Is the package \"locales\" installed?".format(
|
||||
self.LOCALE_SUPPORTED, self.LOCALE_GEN
|
||||
))
|
||||
|
||||
if not is_available(name, ubuntuMode):
|
||||
module.fail_json(msg="The locale you've entered is not available "
|
||||
"on your system.")
|
||||
if not self.is_available():
|
||||
self.do_raise("The locale you've entered is not available on your system.")
|
||||
|
||||
if is_present(name):
|
||||
prev_state = "present"
|
||||
else:
|
||||
prev_state = "absent"
|
||||
changed = (prev_state != state)
|
||||
self.vars.set("is_present", self.is_present(), output=False)
|
||||
self.vars.set("state_tracking", self._state_name(self.vars.is_present), output=False, change=True)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=changed)
|
||||
else:
|
||||
if changed:
|
||||
try:
|
||||
if ubuntuMode is False:
|
||||
apply_change(state, name)
|
||||
else:
|
||||
apply_change_ubuntu(state, name)
|
||||
except EnvironmentError as e:
|
||||
module.fail_json(msg=to_native(e), exitValue=e.errno)
|
||||
def __quit_module__(self):
|
||||
self.vars.state_tracking = self._state_name(self.is_present())
|
||||
|
||||
module.exit_json(name=name, changed=changed, msg="OK")
|
||||
@staticmethod
|
||||
def _state_name(present):
|
||||
return "present" if present else "absent"
|
||||
|
||||
def is_available(self):
|
||||
"""Check if the given locale is available on the system. This is done by
|
||||
checking either :
|
||||
* if the locale is present in /etc/locales.gen
|
||||
* or if the locale is present in /usr/share/i18n/SUPPORTED"""
|
||||
__regexp = r'^#?\s*(?P<locale>\S+[\._\S]+) (?P<charset>\S+)\s*$'
|
||||
if self.vars.ubuntu_mode:
|
||||
__locales_available = '/usr/share/i18n/SUPPORTED'
|
||||
else:
|
||||
__locales_available = '/etc/locale.gen'
|
||||
|
||||
re_compiled = re.compile(__regexp)
|
||||
with open(__locales_available, 'r') as fd:
|
||||
lines = fd.readlines()
|
||||
res = [re_compiled.match(line) for line in lines]
|
||||
if self.verbosity >= 4:
|
||||
self.vars.available_lines = lines
|
||||
if any(r.group("locale") == self.vars.name for r in res if r):
|
||||
return True
|
||||
# locale may be installed but not listed in the file, for example C.UTF-8 in some systems
|
||||
return self.is_present()
|
||||
|
||||
def is_present(self):
|
||||
runner = locale_runner(self.module)
|
||||
with runner() as ctx:
|
||||
rc, out, err = ctx.run()
|
||||
if self.verbosity >= 4:
|
||||
self.vars.locale_run_info = ctx.run_info
|
||||
return any(self.fix_case(self.vars.name) == self.fix_case(line) for line in out.splitlines())
|
||||
|
||||
def fix_case(self, name):
|
||||
"""locale -a might return the encoding in either lower or upper case.
|
||||
Passing through this function makes them uniform for comparisons."""
|
||||
for s, r in self.LOCALE_NORMALIZATION.items():
|
||||
name = name.replace(s, r)
|
||||
return name
|
||||
|
||||
def set_locale(self, name, enabled=True):
|
||||
""" Sets the state of the locale. Defaults to enabled. """
|
||||
search_string = r'#?\s*%s (?P<charset>.+)' % re.escape(name)
|
||||
if enabled:
|
||||
new_string = r'%s \g<charset>' % (name)
|
||||
else:
|
||||
new_string = r'# %s \g<charset>' % (name)
|
||||
re_search = re.compile(search_string)
|
||||
with open("/etc/locale.gen", "r") as fr:
|
||||
lines = [re_search.sub(new_string, line) for line in fr]
|
||||
with open("/etc/locale.gen", "w") as fw:
|
||||
fw.write("".join(lines))
|
||||
|
||||
def apply_change(self, targetState, name):
|
||||
"""Create or remove locale.
|
||||
|
||||
Keyword arguments:
|
||||
targetState -- Desired state, either present or absent.
|
||||
name -- Name including encoding such as de_CH.UTF-8.
|
||||
"""
|
||||
|
||||
self.set_locale(name, enabled=(targetState == "present"))
|
||||
|
||||
runner = locale_gen_runner(self.module)
|
||||
with runner() as ctx:
|
||||
ctx.run()
|
||||
|
||||
def apply_change_ubuntu(self, targetState, name):
|
||||
"""Create or remove locale.
|
||||
|
||||
Keyword arguments:
|
||||
targetState -- Desired state, either present or absent.
|
||||
name -- Name including encoding such as de_CH.UTF-8.
|
||||
"""
|
||||
runner = locale_gen_runner(self.module)
|
||||
|
||||
if targetState == "present":
|
||||
# Create locale.
|
||||
# Ubuntu's patched locale-gen automatically adds the new locale to /var/lib/locales/supported.d/local
|
||||
with runner() as ctx:
|
||||
ctx.run()
|
||||
else:
|
||||
# Delete locale involves discarding the locale from /var/lib/locales/supported.d/local and regenerating all locales.
|
||||
with open("/var/lib/locales/supported.d/local", "r") as fr:
|
||||
content = fr.readlines()
|
||||
with open("/var/lib/locales/supported.d/local", "w") as fw:
|
||||
for line in content:
|
||||
locale, charset = line.split(' ')
|
||||
if locale != name:
|
||||
fw.write(line)
|
||||
# Purge locales and regenerate.
|
||||
# Please provide a patch if you know how to avoid regenerating the locales to keep!
|
||||
with runner("purge") as ctx:
|
||||
ctx.run()
|
||||
|
||||
@check_mode_skip
|
||||
def __state_fallback__(self):
|
||||
if self.vars.state_tracking == self.vars.state:
|
||||
return
|
||||
if self.vars.ubuntu_mode:
|
||||
self.apply_change_ubuntu(self.vars.state, self.vars.name)
|
||||
else:
|
||||
self.apply_change(self.vars.state, self.vars.name)
|
||||
|
||||
|
||||
def main():
|
||||
LocaleGen.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -54,7 +54,16 @@ options:
|
||||
description:
|
||||
- The target to run.
|
||||
- Typically this would be something like V(install), V(test), or V(all).
|
||||
- O(target) and O(targets) are mutually exclusive.
|
||||
type: str
|
||||
targets:
|
||||
description:
|
||||
- The list of targets to run.
|
||||
- Typically this would be something like V(install), V(test), or V(all).
|
||||
- O(target) and O(targets) are mutually exclusive.
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 7.2.0
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
@@ -115,6 +124,12 @@ target:
|
||||
- The value of the module parameter O(target).
|
||||
type: str
|
||||
returned: success
|
||||
targets:
|
||||
description:
|
||||
- The value of the module parameter O(targets).
|
||||
type: str
|
||||
returned: success
|
||||
version_added: 7.2.0
|
||||
'''
|
||||
|
||||
from ansible.module_utils.six import iteritems
|
||||
@@ -155,12 +170,14 @@ def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
target=dict(type='str'),
|
||||
targets=dict(type='list', elements='str'),
|
||||
params=dict(type='dict'),
|
||||
chdir=dict(type='path', required=True),
|
||||
file=dict(type='path'),
|
||||
make=dict(type='path'),
|
||||
jobs=dict(type='int'),
|
||||
),
|
||||
mutually_exclusive=[('target', 'targets')],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -172,7 +189,6 @@ def main():
|
||||
if not make_path:
|
||||
# Fall back to system make
|
||||
make_path = module.get_bin_path('make', required=True)
|
||||
make_target = module.params['target']
|
||||
if module.params['params'] is not None:
|
||||
make_parameters = [k + '=' + str(v) for k, v in iteritems(module.params['params'])]
|
||||
else:
|
||||
@@ -188,7 +204,10 @@ def main():
|
||||
base_command.extend(["-f", module.params['file']])
|
||||
|
||||
# add make target
|
||||
base_command.append(make_target)
|
||||
if module.params['target']:
|
||||
base_command.append(module.params['target'])
|
||||
elif module.params['targets']:
|
||||
base_command.extend(module.params['targets'])
|
||||
|
||||
# add makefile parameters
|
||||
base_command.extend(make_parameters)
|
||||
@@ -206,8 +225,7 @@ def main():
|
||||
changed = False
|
||||
else:
|
||||
# The target isn't up to date, so we need to run it
|
||||
rc, out, err = run_command(base_command, module,
|
||||
check_rc=True)
|
||||
rc, out, err = run_command(base_command, module, check_rc=True)
|
||||
changed = True
|
||||
|
||||
# We don't report the return code, as if this module failed
|
||||
@@ -221,6 +239,7 @@ def main():
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
target=module.params['target'],
|
||||
targets=module.params['targets'],
|
||||
params=module.params['params'],
|
||||
chdir=module.params['chdir'],
|
||||
file=module.params['file'],
|
||||
|
||||
@@ -191,6 +191,12 @@ options:
|
||||
- A list of DNS search domains.
|
||||
elements: str
|
||||
type: list
|
||||
dns4_options:
|
||||
description:
|
||||
- A list of DNS options.
|
||||
elements: str
|
||||
type: list
|
||||
version_added: 7.2.0
|
||||
dns4_ignore_auto:
|
||||
description:
|
||||
- Ignore automatically configured IPv4 name servers.
|
||||
@@ -290,6 +296,12 @@ options:
|
||||
- A list of DNS search domains.
|
||||
elements: str
|
||||
type: list
|
||||
dns6_options:
|
||||
description:
|
||||
- A list of DNS options.
|
||||
elements: str
|
||||
type: list
|
||||
version_added: 7.2.0
|
||||
dns6_ignore_auto:
|
||||
description:
|
||||
- Ignore automatically configured IPv6 name servers.
|
||||
@@ -1537,6 +1549,7 @@ class Nmcli(object):
|
||||
self.never_default4 = module.params['never_default4']
|
||||
self.dns4 = module.params['dns4']
|
||||
self.dns4_search = module.params['dns4_search']
|
||||
self.dns4_options = module.params['dns4_options']
|
||||
self.dns4_ignore_auto = module.params['dns4_ignore_auto']
|
||||
self.method4 = module.params['method4']
|
||||
self.may_fail4 = module.params['may_fail4']
|
||||
@@ -1548,6 +1561,7 @@ class Nmcli(object):
|
||||
self.route_metric6 = module.params['route_metric6']
|
||||
self.dns6 = module.params['dns6']
|
||||
self.dns6_search = module.params['dns6_search']
|
||||
self.dns6_options = module.params['dns6_options']
|
||||
self.dns6_ignore_auto = module.params['dns6_ignore_auto']
|
||||
self.method6 = module.params['method6']
|
||||
self.ip_privacy6 = module.params['ip_privacy6']
|
||||
@@ -1654,6 +1668,7 @@ class Nmcli(object):
|
||||
'ipv4.dhcp-client-id': self.dhcp_client_id,
|
||||
'ipv4.dns': self.dns4,
|
||||
'ipv4.dns-search': self.dns4_search,
|
||||
'ipv4.dns-options': self.dns4_options,
|
||||
'ipv4.ignore-auto-dns': self.dns4_ignore_auto,
|
||||
'ipv4.gateway': self.gw4,
|
||||
'ipv4.ignore-auto-routes': self.gw4_ignore_auto,
|
||||
@@ -1666,6 +1681,7 @@ class Nmcli(object):
|
||||
'ipv6.addresses': self.enforce_ipv6_cidr_notation(self.ip6),
|
||||
'ipv6.dns': self.dns6,
|
||||
'ipv6.dns-search': self.dns6_search,
|
||||
'ipv6.dns-options': self.dns6_options,
|
||||
'ipv6.ignore-auto-dns': self.dns6_ignore_auto,
|
||||
'ipv6.gateway': self.gw6,
|
||||
'ipv6.ignore-auto-routes': self.gw6_ignore_auto,
|
||||
@@ -2042,10 +2058,12 @@ class Nmcli(object):
|
||||
'ipv6.addresses',
|
||||
'ipv4.dns',
|
||||
'ipv4.dns-search',
|
||||
'ipv4.dns-options',
|
||||
'ipv4.routes',
|
||||
'ipv4.routing-rules',
|
||||
'ipv6.dns',
|
||||
'ipv6.dns-search',
|
||||
'ipv6.dns-options',
|
||||
'ipv6.routes',
|
||||
'802-11-wireless-security.group',
|
||||
'802-11-wireless-security.leap-password-flags',
|
||||
@@ -2183,7 +2201,10 @@ class Nmcli(object):
|
||||
if key and len(pair) > 1:
|
||||
raw_value = pair[1].lstrip()
|
||||
if raw_value == '--':
|
||||
conn_info[key] = None
|
||||
if key_type == list:
|
||||
conn_info[key] = []
|
||||
else:
|
||||
conn_info[key] = None
|
||||
elif key == 'bond.options':
|
||||
# Aliases such as 'miimon', 'downdelay' are equivalent to the +bond.options 'option=value' syntax.
|
||||
opts = raw_value.split(',')
|
||||
@@ -2270,7 +2291,7 @@ class Nmcli(object):
|
||||
# We can't just do `if not value` because then if there's a value
|
||||
# of 0 specified as an integer it'll be interpreted as empty when
|
||||
# it actually isn't.
|
||||
if value != 0 and not value:
|
||||
if value not in (0, []) and not value:
|
||||
continue
|
||||
|
||||
if key in conn_info:
|
||||
@@ -2401,6 +2422,7 @@ def main():
|
||||
never_default4=dict(type='bool', default=False),
|
||||
dns4=dict(type='list', elements='str'),
|
||||
dns4_search=dict(type='list', elements='str'),
|
||||
dns4_options=dict(type='list', elements='str'),
|
||||
dns4_ignore_auto=dict(type='bool', default=False),
|
||||
method4=dict(type='str', choices=['auto', 'link-local', 'manual', 'shared', 'disabled']),
|
||||
may_fail4=dict(type='bool', default=True),
|
||||
@@ -2410,6 +2432,7 @@ def main():
|
||||
gw6_ignore_auto=dict(type='bool', default=False),
|
||||
dns6=dict(type='list', elements='str'),
|
||||
dns6_search=dict(type='list', elements='str'),
|
||||
dns6_options=dict(type='list', elements='str'),
|
||||
dns6_ignore_auto=dict(type='bool', default=False),
|
||||
routes6=dict(type='list', elements='str'),
|
||||
routes6_extended=dict(type='list',
|
||||
|
||||
@@ -285,7 +285,8 @@ def main():
|
||||
arg_spec['global'] = dict(default=False, type='bool')
|
||||
module = AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
supports_check_mode=True
|
||||
required_if=[('state', 'absent', ['name'])],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
name = module.params['name']
|
||||
@@ -304,8 +305,6 @@ def main():
|
||||
|
||||
if not path and not glbl:
|
||||
module.fail_json(msg='path must be specified when not using global')
|
||||
if state == 'absent' and not name:
|
||||
module.fail_json(msg='uninstalling a package is only available for named packages')
|
||||
|
||||
npm = Npm(module, name=name, path=path, version=version, glbl=glbl, production=production,
|
||||
executable=executable, registry=registry, ignore_scripts=ignore_scripts,
|
||||
|
||||
@@ -169,7 +169,11 @@ def get_package_state(names, pkg_spec, module):
|
||||
rc, stdout, stderr = execute_command(command, module)
|
||||
|
||||
if stderr:
|
||||
module.fail_json(msg="failed in get_package_state(): " + stderr)
|
||||
match = re.search(r"^Can't find inst:%s$" % re.escape(name), stderr)
|
||||
if match:
|
||||
pkg_spec[name]['installed_state'] = False
|
||||
else:
|
||||
module.fail_json(msg="failed in get_package_state(): " + stderr)
|
||||
|
||||
if stdout:
|
||||
# If the requested package name is just a stem, like "python", we may
|
||||
|
||||
@@ -66,6 +66,11 @@ options:
|
||||
- Update the package DB first.
|
||||
default: false
|
||||
type: bool
|
||||
executable:
|
||||
description:
|
||||
- The executable location for C(opkg).
|
||||
type: path
|
||||
version_added: 7.2.0
|
||||
requirements:
|
||||
- opkg
|
||||
- python
|
||||
@@ -106,6 +111,7 @@ EXAMPLES = '''
|
||||
force: overwrite
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
|
||||
|
||||
@@ -118,6 +124,7 @@ class Opkg(StateModuleHelper):
|
||||
force=dict(choices=["", "depends", "maintainer", "reinstall", "overwrite", "downgrade", "space",
|
||||
"postinstall", "remove", "checksum", "removal-of-dependent-packages"]),
|
||||
update_cache=dict(default=False, type='bool'),
|
||||
executable=dict(type="path"),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -138,15 +145,18 @@ class Opkg(StateModuleHelper):
|
||||
value = None
|
||||
return cmd_runner_fmt.as_optval("--force-")(value, ctx_ignore_none=True)
|
||||
|
||||
dir, cmd = os.path.split(self.vars.executable) if self.vars.executable else (None, "opkg")
|
||||
|
||||
self.runner = CmdRunner(
|
||||
self.module,
|
||||
command="opkg",
|
||||
command=cmd,
|
||||
arg_formats=dict(
|
||||
package=cmd_runner_fmt.as_list(),
|
||||
state=cmd_runner_fmt.as_map(state_map),
|
||||
force=cmd_runner_fmt.as_func(_force),
|
||||
update_cache=cmd_runner_fmt.as_bool("update")
|
||||
update_cache=cmd_runner_fmt.as_bool("update"),
|
||||
),
|
||||
path_prefix=dir,
|
||||
)
|
||||
|
||||
if self.vars.update_cache:
|
||||
|
||||
@@ -133,6 +133,9 @@ notes:
|
||||
it is much more efficient to pass the list directly to the O(name) option.
|
||||
- To use an AUR helper (O(executable) option), a few extra setup steps might be required beforehand.
|
||||
For example, a dedicated build user with permissions to install packages could be necessary.
|
||||
- >
|
||||
In the tests, while using C(yay) as the O(executable) option, the module failed to install AUR packages
|
||||
with the error: C(error: target not found: <pkg>).
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -263,6 +266,7 @@ EXAMPLES = """
|
||||
reason_for: all
|
||||
"""
|
||||
|
||||
import re
|
||||
import shlex
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from collections import defaultdict, namedtuple
|
||||
@@ -418,7 +422,7 @@ class Pacman(object):
|
||||
for p in name_ver:
|
||||
# With Pacman v6.0.1 - libalpm v13.0.1, --upgrade outputs "loading packages..." on stdout. strip that.
|
||||
# When installing from URLs, pacman can also output a 'nothing to do' message. strip that too.
|
||||
if "loading packages" in p or "there is nothing to do" in p:
|
||||
if "loading packages" in p or "there is nothing to do" in p or 'Avoid running' in p:
|
||||
continue
|
||||
name, version = p.split()
|
||||
if name in self.inventory["installed_pkgs"]:
|
||||
@@ -706,11 +710,12 @@ class Pacman(object):
|
||||
installed_pkgs = {}
|
||||
dummy, stdout, dummy = self.m.run_command([self.pacman_path, "--query"], check_rc=True)
|
||||
# Format of a line: "pacman 6.0.1-2"
|
||||
query_re = re.compile(r'^\s*(?P<pkg>\S+)\s+(?P<ver>\S+)\s*$')
|
||||
for l in stdout.splitlines():
|
||||
l = l.strip()
|
||||
if not l:
|
||||
query_match = query_re.match(l)
|
||||
if not query_match:
|
||||
continue
|
||||
pkg, ver = l.split()
|
||||
pkg, ver = query_match.groups()
|
||||
installed_pkgs[pkg] = ver
|
||||
|
||||
installed_groups = defaultdict(set)
|
||||
@@ -721,11 +726,12 @@ class Pacman(object):
|
||||
# base-devel file
|
||||
# base-devel findutils
|
||||
# ...
|
||||
query_groups_re = re.compile(r'^\s*(?P<group>\S+)\s+(?P<pkg>\S+)\s*$')
|
||||
for l in stdout.splitlines():
|
||||
l = l.strip()
|
||||
if not l:
|
||||
query_groups_match = query_groups_re.match(l)
|
||||
if not query_groups_match:
|
||||
continue
|
||||
group, pkgname = l.split()
|
||||
group, pkgname = query_groups_match.groups()
|
||||
installed_groups[group].add(pkgname)
|
||||
|
||||
available_pkgs = {}
|
||||
@@ -747,11 +753,12 @@ class Pacman(object):
|
||||
# vim-plugins vim-airline-themes
|
||||
# vim-plugins vim-ale
|
||||
# ...
|
||||
sync_groups_re = re.compile(r'^\s*(?P<group>\S+)\s+(?P<pkg>\S+)\s*$')
|
||||
for l in stdout.splitlines():
|
||||
l = l.strip()
|
||||
if not l:
|
||||
sync_groups_match = sync_groups_re.match(l)
|
||||
if not sync_groups_match:
|
||||
continue
|
||||
group, pkg = l.split()
|
||||
group, pkg = sync_groups_match.groups()
|
||||
available_groups[group].add(pkg)
|
||||
|
||||
upgradable_pkgs = {}
|
||||
@@ -759,9 +766,14 @@ class Pacman(object):
|
||||
[self.pacman_path, "--query", "--upgrades"], check_rc=False
|
||||
)
|
||||
|
||||
stdout = stdout.splitlines()
|
||||
if stdout and "Avoid running" in stdout[0]:
|
||||
stdout = stdout[1:]
|
||||
stdout = "\n".join(stdout)
|
||||
|
||||
# non-zero exit with nothing in stdout -> nothing to upgrade, all good
|
||||
# stderr can have warnings, so not checked here
|
||||
if rc == 1 and stdout == "":
|
||||
if rc == 1 and not stdout:
|
||||
pass # nothing to upgrade
|
||||
elif rc == 0:
|
||||
# Format of lines:
|
||||
@@ -771,7 +783,7 @@ class Pacman(object):
|
||||
l = l.strip()
|
||||
if not l:
|
||||
continue
|
||||
if "[ignored]" in l:
|
||||
if "[ignored]" in l or "Avoid running" in l:
|
||||
continue
|
||||
s = l.split()
|
||||
if len(s) != 4:
|
||||
|
||||
@@ -183,6 +183,10 @@ options:
|
||||
are used when the values are not explicitly specified by the user. The new default is V(no_defaults),
|
||||
which makes sure these options have no defaults.
|
||||
- This affects the O(disk), O(cores), O(cpus), O(memory), O(onboot), O(swap), and O(cpuunits) options.
|
||||
- >
|
||||
This parameter is now B(deprecated) and it will be removed in community.general 10.0.0.
|
||||
By then, the module's behavior should be to not set default values, equivalent to V(no_defaults).
|
||||
If a consistent set of defaults is needed, the playbook or role should be responsible for setting it.
|
||||
type: str
|
||||
default: no_defaults
|
||||
choices:
|
||||
@@ -209,6 +213,8 @@ options:
|
||||
default: opportunistic
|
||||
version_added: 4.3.0
|
||||
author: Sergei Antipov (@UnderGreen)
|
||||
seealso:
|
||||
- module: community.general.proxmox_vm_info
|
||||
extends_documentation_fragment:
|
||||
- community.general.proxmox.documentation
|
||||
- community.general.proxmox.selection
|
||||
@@ -444,7 +450,7 @@ class ProxmoxLxcAnsible(ProxmoxAnsible):
|
||||
"""Check if the specified container is a template."""
|
||||
proxmox_node = self.proxmox_api.nodes(node)
|
||||
config = getattr(proxmox_node, VZ_TYPE)(vmid).config.get()
|
||||
return config['template']
|
||||
return config.get('template', False)
|
||||
|
||||
def create_instance(self, vmid, node, disk, storage, cpus, memory, swap, timeout, clone, **kwargs):
|
||||
|
||||
@@ -621,7 +627,8 @@ def main():
|
||||
description=dict(type='str'),
|
||||
hookscript=dict(type='str'),
|
||||
timezone=dict(type='str'),
|
||||
proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']),
|
||||
proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults'],
|
||||
removed_in_version='9.0.0', removed_from_collection='community.general'),
|
||||
clone=dict(type='int'),
|
||||
clone_type=dict(default='opportunistic', choices=['full', 'linked', 'opportunistic']),
|
||||
tags=dict(type='list', elements='str')
|
||||
|
||||
@@ -505,7 +505,9 @@ class ProxmoxDiskAnsible(ProxmoxAnsible):
|
||||
timeout_str = "Reached timeout while importing VM disk. Last line in task before timeout: %s"
|
||||
ok_str = "Disk %s imported into VM %s"
|
||||
else:
|
||||
config_str = "%s:%s" % (self.module.params["storage"], self.module.params["size"])
|
||||
config_str = self.module.params["storage"]
|
||||
if self.module.params.get("media") != "cdrom":
|
||||
config_str += ":%s" % (self.module.params["size"])
|
||||
ok_str = "Disk %s created in VM %s"
|
||||
timeout_str = "Reached timeout while creating VM disk. Last line in task before timeout: %s"
|
||||
|
||||
|
||||
@@ -287,8 +287,9 @@ options:
|
||||
type: int
|
||||
name:
|
||||
description:
|
||||
- Specifies the VM name. Only used on the configuration web interface.
|
||||
- Specifies the VM name. Name could be non-unique across the cluster.
|
||||
- Required only for O(state=present).
|
||||
- With O(state=present) if O(vmid) not provided and VM with name exists in the cluster then no changes will be made.
|
||||
type: str
|
||||
nameservers:
|
||||
description:
|
||||
@@ -551,6 +552,8 @@ options:
|
||||
- compatibility
|
||||
- no_defaults
|
||||
version_added: "1.3.0"
|
||||
seealso:
|
||||
- module: community.general.proxmox_vm_info
|
||||
extends_documentation_fragment:
|
||||
- community.general.proxmox.documentation
|
||||
- community.general.proxmox.selection
|
||||
@@ -788,6 +791,15 @@ EXAMPLES = '''
|
||||
node: sabrewulf
|
||||
state: absent
|
||||
|
||||
- name: Get VM current state
|
||||
community.general.proxmox_kvm:
|
||||
api_user: root@pam
|
||||
api_password: secret
|
||||
api_host: helldorado
|
||||
name: spynal
|
||||
node: sabrewulf
|
||||
state: current
|
||||
|
||||
- name: Update VM configuration
|
||||
community.general.proxmox_kvm:
|
||||
api_user: root@pam
|
||||
@@ -1099,6 +1111,20 @@ class ProxmoxKvmAnsible(ProxmoxAnsible):
|
||||
return False
|
||||
return True
|
||||
|
||||
def restart_vm(self, vm, **status):
|
||||
vmid = vm['vmid']
|
||||
try:
|
||||
proxmox_node = self.proxmox_api.nodes(vm['node'])
|
||||
taskid = proxmox_node.qemu(vmid).status.reboot.post()
|
||||
if not self.wait_for_task(vm['node'], taskid):
|
||||
self.module.fail_json(msg='Reached timeout while waiting for rebooting VM. Last line in task before timeout: %s' %
|
||||
proxmox_node.tasks(taskid).log.get()[:1])
|
||||
return False
|
||||
return True
|
||||
except Exception as e:
|
||||
self.module.fail_json(vmid=vmid, msg="restarting of VM %s failed with exception: %s" % (vmid, e))
|
||||
return False
|
||||
|
||||
def migrate_vm(self, vm, target_node):
|
||||
vmid = vm['vmid']
|
||||
proxmox_node = self.proxmox_api.nodes(vm['node'])
|
||||
@@ -1264,10 +1290,14 @@ def main():
|
||||
# the cloned vm name or retrieve the next free VM id from ProxmoxAPI.
|
||||
if not vmid:
|
||||
if state == 'present' and not update and not clone and not delete and not revert and not migrate:
|
||||
try:
|
||||
vmid = proxmox.get_nextvmid()
|
||||
except Exception:
|
||||
module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name))
|
||||
existing_vmid = proxmox.get_vmid(name, ignore_missing=True)
|
||||
if existing_vmid:
|
||||
vmid = existing_vmid
|
||||
else:
|
||||
try:
|
||||
vmid = proxmox.get_nextvmid()
|
||||
except Exception:
|
||||
module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name))
|
||||
else:
|
||||
clone_target = clone or name
|
||||
vmid = proxmox.get_vmid(clone_target, ignore_missing=True)
|
||||
@@ -1458,16 +1488,13 @@ def main():
|
||||
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
|
||||
|
||||
status = {}
|
||||
try:
|
||||
vm = proxmox.get_vm(vmid)
|
||||
status['status'] = vm['status']
|
||||
if vm['status'] == 'stopped':
|
||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is not running" % vmid, **status)
|
||||
vm = proxmox.get_vm(vmid)
|
||||
status['status'] = vm['status']
|
||||
if vm['status'] == 'stopped':
|
||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is not running" % vmid, **status)
|
||||
|
||||
if proxmox.stop_vm(vm, force=module.params['force']) and proxmox.start_vm(vm):
|
||||
module.exit_json(changed=True, vmid=vmid, msg="VM %s is restarted" % vmid, **status)
|
||||
except Exception as e:
|
||||
module.fail_json(vmid=vmid, msg="restarting of VM %s failed with exception: %s" % (vmid, e), **status)
|
||||
if proxmox.restart_vm(vm):
|
||||
module.exit_json(changed=True, vmid=vmid, msg="VM %s is restarted" % vmid, **status)
|
||||
|
||||
elif state == 'absent':
|
||||
status = {}
|
||||
@@ -1483,7 +1510,7 @@ def main():
|
||||
status['status'] = vm['status']
|
||||
if vm['status'] == 'running':
|
||||
if module.params['force']:
|
||||
proxmox.stop_vm(vm, True)
|
||||
proxmox.stop_vm(vm, True, timeout=module.params['timeout'])
|
||||
else:
|
||||
module.exit_json(changed=False, vmid=vmid, msg="VM %s is running. Stop it before deletion or use force=true." % vmid)
|
||||
taskid = proxmox_node.qemu.delete(vmid)
|
||||
|
||||
@@ -65,7 +65,8 @@ options:
|
||||
choices: ['present', 'absent']
|
||||
default: present
|
||||
notes:
|
||||
- Requires C(proxmoxer) and C(requests) modules on host. This modules can be installed with M(ansible.builtin.pip).
|
||||
- Requires C(proxmoxer) and C(requests) modules on host. Those modules can be installed with M(ansible.builtin.pip).
|
||||
- C(proxmoxer) >= 1.2.0 requires C(requests_toolbelt) to upload files larger than 256 MB.
|
||||
author: Sergei Antipov (@UnderGreen)
|
||||
extends_documentation_fragment:
|
||||
- community.general.proxmox.documentation
|
||||
@@ -123,15 +124,29 @@ EXAMPLES = '''
|
||||
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
|
||||
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
||||
|
||||
REQUESTS_TOOLBELT_ERR = None
|
||||
try:
|
||||
# requests_toolbelt is used internally by proxmoxer module
|
||||
import requests_toolbelt # noqa: F401, pylint: disable=unused-import
|
||||
HAS_REQUESTS_TOOLBELT = True
|
||||
except ImportError:
|
||||
HAS_REQUESTS_TOOLBELT = False
|
||||
REQUESTS_TOOLBELT_ERR = traceback.format_exc()
|
||||
|
||||
|
||||
class ProxmoxTemplateAnsible(ProxmoxAnsible):
|
||||
def get_template(self, node, storage, content_type, template):
|
||||
return [True for tmpl in self.proxmox_api.nodes(node).storage(storage).content.get()
|
||||
if tmpl['volid'] == '%s:%s/%s' % (storage, content_type, template)]
|
||||
try:
|
||||
return [True for tmpl in self.proxmox_api.nodes(node).storage(storage).content.get()
|
||||
if tmpl['volid'] == '%s:%s/%s' % (storage, content_type, template)]
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to retrieve template '%s:%s/%s': %s" % (storage, content_type, template, e))
|
||||
|
||||
def task_status(self, node, taskid, timeout):
|
||||
"""
|
||||
@@ -149,12 +164,24 @@ class ProxmoxTemplateAnsible(ProxmoxAnsible):
|
||||
return False
|
||||
|
||||
def upload_template(self, node, storage, content_type, realpath, timeout):
|
||||
taskid = self.proxmox_api.nodes(node).storage(storage).upload.post(content=content_type, filename=open(realpath, 'rb'))
|
||||
return self.task_status(node, taskid, timeout)
|
||||
stats = os.stat(realpath)
|
||||
if (LooseVersion(self.proxmoxer_version) >= LooseVersion('1.2.0') and
|
||||
stats.st_size > 268435456 and not HAS_REQUESTS_TOOLBELT):
|
||||
self.module.fail_json(msg="'requests_toolbelt' module is required to upload files larger than 256MB",
|
||||
exception=missing_required_lib('requests_toolbelt'))
|
||||
|
||||
try:
|
||||
taskid = self.proxmox_api.nodes(node).storage(storage).upload.post(content=content_type, filename=open(realpath, 'rb'))
|
||||
return self.task_status(node, taskid, timeout)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Uploading template %s failed with error: %s" % (realpath, e))
|
||||
|
||||
def download_template(self, node, storage, template, timeout):
|
||||
taskid = self.proxmox_api.nodes(node).aplinfo.post(storage=storage, template=template)
|
||||
return self.task_status(node, taskid, timeout)
|
||||
try:
|
||||
taskid = self.proxmox_api.nodes(node).aplinfo.post(storage=storage, template=template)
|
||||
return self.task_status(node, taskid, timeout)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Downloading template %s failed with error: %s" % (template, e))
|
||||
|
||||
def delete_template(self, node, storage, content_type, template, timeout):
|
||||
volid = '%s:%s/%s' % (storage, content_type, template)
|
||||
@@ -199,35 +226,32 @@ def main():
|
||||
timeout = module.params['timeout']
|
||||
|
||||
if state == 'present':
|
||||
try:
|
||||
content_type = module.params['content_type']
|
||||
src = module.params['src']
|
||||
content_type = module.params['content_type']
|
||||
src = module.params['src']
|
||||
|
||||
# download appliance template
|
||||
if content_type == 'vztmpl' and not src:
|
||||
template = module.params['template']
|
||||
# download appliance template
|
||||
if content_type == 'vztmpl' and not src:
|
||||
template = module.params['template']
|
||||
|
||||
if not template:
|
||||
module.fail_json(msg='template param for downloading appliance template is mandatory')
|
||||
if not template:
|
||||
module.fail_json(msg='template param for downloading appliance template is mandatory')
|
||||
|
||||
if proxmox.get_template(node, storage, content_type, template) and not module.params['force']:
|
||||
module.exit_json(changed=False, msg='template with volid=%s:%s/%s already exists' % (storage, content_type, template))
|
||||
|
||||
if proxmox.download_template(node, storage, template, timeout):
|
||||
module.exit_json(changed=True, msg='template with volid=%s:%s/%s downloaded' % (storage, content_type, template))
|
||||
|
||||
template = os.path.basename(src)
|
||||
if proxmox.get_template(node, storage, content_type, template) and not module.params['force']:
|
||||
module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already exists' % (storage, content_type, template))
|
||||
elif not src:
|
||||
module.fail_json(msg='src param to uploading template file is mandatory')
|
||||
elif not (os.path.exists(src) and os.path.isfile(src)):
|
||||
module.fail_json(msg='template file on path %s not exists' % src)
|
||||
module.exit_json(changed=False, msg='template with volid=%s:%s/%s already exists' % (storage, content_type, template))
|
||||
|
||||
if proxmox.upload_template(node, storage, content_type, src, timeout):
|
||||
module.exit_json(changed=True, msg='template with volid=%s:%s/%s uploaded' % (storage, content_type, template))
|
||||
except Exception as e:
|
||||
module.fail_json(msg="uploading/downloading of template %s failed with exception: %s" % (template, e))
|
||||
if proxmox.download_template(node, storage, template, timeout):
|
||||
module.exit_json(changed=True, msg='template with volid=%s:%s/%s downloaded' % (storage, content_type, template))
|
||||
|
||||
template = os.path.basename(src)
|
||||
if proxmox.get_template(node, storage, content_type, template) and not module.params['force']:
|
||||
module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already exists' % (storage, content_type, template))
|
||||
elif not src:
|
||||
module.fail_json(msg='src param to uploading template file is mandatory')
|
||||
elif not (os.path.exists(src) and os.path.isfile(src)):
|
||||
module.fail_json(msg='template file on path %s not exists' % src)
|
||||
|
||||
if proxmox.upload_template(node, storage, content_type, src, timeout):
|
||||
module.exit_json(changed=True, msg='template with volid=%s:%s/%s uploaded' % (storage, content_type, template))
|
||||
|
||||
elif state == 'absent':
|
||||
try:
|
||||
|
||||
216
plugins/modules/proxmox_vm_info.py
Normal file
216
plugins/modules/proxmox_vm_info.py
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: proxmox_vm_info
|
||||
short_description: Retrieve information about one or more Proxmox VE virtual machines
|
||||
version_added: 7.2.0
|
||||
description:
|
||||
- Retrieve information about one or more Proxmox VE virtual machines.
|
||||
author: 'Sergei Antipov (@UnderGreen) <greendayonfire at gmail dot com>'
|
||||
options:
|
||||
node:
|
||||
description:
|
||||
- Node where to get virtual machines info.
|
||||
required: true
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- Restrict results to a specific virtual machine(s) type.
|
||||
type: str
|
||||
choices:
|
||||
- all
|
||||
- qemu
|
||||
- lxc
|
||||
default: all
|
||||
vmid:
|
||||
description:
|
||||
- Restrict results to a specific virtual machine by using its ID.
|
||||
type: int
|
||||
name:
|
||||
description:
|
||||
- Restrict results to a specific virtual machine by using its name.
|
||||
- If multiple virtual machines have the same name then vmid must be used instead.
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.general.proxmox.documentation
|
||||
- community.general.attributes
|
||||
- community.general.attributes.info_module
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: List all existing virtual machines on node
|
||||
community.general.proxmox_vm_info:
|
||||
api_host: proxmoxhost
|
||||
api_user: root@pam
|
||||
api_token_id: '{{ token_id | default(omit) }}'
|
||||
api_token_secret: '{{ token_secret | default(omit) }}'
|
||||
node: node01
|
||||
|
||||
- name: List all QEMU virtual machines on node
|
||||
community.general.proxmox_vm_info:
|
||||
api_host: proxmoxhost
|
||||
api_user: root@pam
|
||||
api_password: '{{ password | default(omit) }}'
|
||||
node: node01
|
||||
type: qemu
|
||||
|
||||
- name: Retrieve information about specific VM by ID
|
||||
community.general.proxmox_vm_info:
|
||||
api_host: proxmoxhost
|
||||
api_user: root@pam
|
||||
api_password: '{{ password | default(omit) }}'
|
||||
node: node01
|
||||
type: qemu
|
||||
vmid: 101
|
||||
|
||||
- name: Retrieve information about specific VM by name
|
||||
community.general.proxmox_vm_info:
|
||||
api_host: proxmoxhost
|
||||
api_user: root@pam
|
||||
api_password: '{{ password | default(omit) }}'
|
||||
node: node01
|
||||
type: lxc
|
||||
name: lxc05.home.arpa
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
proxmox_vms:
|
||||
description: List of virtual machines.
|
||||
returned: on success
|
||||
type: list
|
||||
elements: dict
|
||||
sample:
|
||||
[
|
||||
{
|
||||
"cpu": 0.258944410905281,
|
||||
"cpus": 1,
|
||||
"disk": 0,
|
||||
"diskread": 0,
|
||||
"diskwrite": 0,
|
||||
"maxdisk": 34359738368,
|
||||
"maxmem": 4294967296,
|
||||
"mem": 35158379,
|
||||
"name": "pxe.home.arpa",
|
||||
"netin": 99715803,
|
||||
"netout": 14237835,
|
||||
"pid": 1947197,
|
||||
"status": "running",
|
||||
"type": "qemu",
|
||||
"uptime": 135530,
|
||||
"vmid": 100
|
||||
},
|
||||
{
|
||||
"cpu": 0,
|
||||
"cpus": 1,
|
||||
"disk": 0,
|
||||
"diskread": 0,
|
||||
"diskwrite": 0,
|
||||
"maxdisk": 0,
|
||||
"maxmem": 536870912,
|
||||
"mem": 0,
|
||||
"name": "test1",
|
||||
"netin": 0,
|
||||
"netout": 0,
|
||||
"status": "stopped",
|
||||
"type": "qemu",
|
||||
"uptime": 0,
|
||||
"vmid": 101
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.proxmox import (
|
||||
proxmox_auth_argument_spec,
|
||||
ProxmoxAnsible,
|
||||
)
|
||||
|
||||
|
||||
class ProxmoxVmInfoAnsible(ProxmoxAnsible):
|
||||
def get_qemu_vms(self, node, vmid=None):
|
||||
try:
|
||||
vms = self.proxmox_api.nodes(node).qemu().get()
|
||||
for vm in vms:
|
||||
vm["vmid"] = int(vm["vmid"])
|
||||
vm["type"] = "qemu"
|
||||
if vmid is None:
|
||||
return vms
|
||||
return [vm for vm in vms if vm["vmid"] == vmid]
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to retrieve QEMU VMs information: %s" % e)
|
||||
|
||||
def get_lxc_vms(self, node, vmid=None):
|
||||
try:
|
||||
vms = self.proxmox_api.nodes(node).lxc().get()
|
||||
for vm in vms:
|
||||
vm["vmid"] = int(vm["vmid"])
|
||||
if vmid is None:
|
||||
return vms
|
||||
return [vm for vm in vms if vm["vmid"] == vmid]
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to retrieve LXC VMs information: %s" % e)
|
||||
|
||||
|
||||
def main():
|
||||
module_args = proxmox_auth_argument_spec()
|
||||
vm_info_args = dict(
|
||||
node=dict(type="str", required=True),
|
||||
type=dict(
|
||||
type="str", choices=["lxc", "qemu", "all"], default="all", required=False
|
||||
),
|
||||
vmid=dict(type="int", required=False),
|
||||
name=dict(type="str", required=False),
|
||||
)
|
||||
module_args.update(vm_info_args)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
required_together=[("api_token_id", "api_token_secret")],
|
||||
required_one_of=[("api_password", "api_token_id")],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
proxmox = ProxmoxVmInfoAnsible(module)
|
||||
node = module.params["node"]
|
||||
type = module.params["type"]
|
||||
vmid = module.params["vmid"]
|
||||
name = module.params["name"]
|
||||
|
||||
result = dict(changed=False)
|
||||
|
||||
if proxmox.get_node(node) is None:
|
||||
module.fail_json(msg="Node %s doesn't exist in PVE cluster" % node)
|
||||
|
||||
if not vmid and name:
|
||||
vmid = int(proxmox.get_vmid(name, ignore_missing=False))
|
||||
|
||||
vms = None
|
||||
if type == "lxc":
|
||||
vms = proxmox.get_lxc_vms(node, vmid=vmid)
|
||||
elif type == "qemu":
|
||||
vms = proxmox.get_qemu_vms(node, vmid=vmid)
|
||||
else:
|
||||
vms = proxmox.get_qemu_vms(node, vmid=vmid) + proxmox.get_lxc_vms(
|
||||
node, vmid=vmid
|
||||
)
|
||||
|
||||
if vms or vmid is None:
|
||||
result["proxmox_vms"] = vms
|
||||
module.exit_json(**result)
|
||||
else:
|
||||
result["msg"] = "VM with vmid %s doesn't exist on node %s" % (vmid, node)
|
||||
module.fail_json(**result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -243,7 +243,7 @@ import os
|
||||
|
||||
try:
|
||||
# Import PubNub BLOCKS client.
|
||||
from pubnub_blocks_client import User, Account, Owner, Application, Keyset # noqa: F401, pylint: disable=unused-import
|
||||
from pubnub_blocks_client import User
|
||||
from pubnub_blocks_client import Block, EventHandler
|
||||
from pubnub_blocks_client import exceptions
|
||||
HAS_PUBNUB_BLOCKS_CLIENT = True
|
||||
|
||||
@@ -85,6 +85,22 @@ options:
|
||||
description:
|
||||
- Role of account to add/modify.
|
||||
type: str
|
||||
account_types:
|
||||
required: false
|
||||
aliases: [ account_accounttypes ]
|
||||
description:
|
||||
- Array of account types to apply to a user account.
|
||||
type: list
|
||||
elements: str
|
||||
version_added: '7.2.0'
|
||||
oem_account_types:
|
||||
required: false
|
||||
aliases: [ account_oemaccounttypes ]
|
||||
description:
|
||||
- Array of OEM account types to apply to a user account.
|
||||
type: list
|
||||
elements: str
|
||||
version_added: '7.2.0'
|
||||
bootdevice:
|
||||
required: false
|
||||
description:
|
||||
@@ -380,6 +396,20 @@ EXAMPLES = '''
|
||||
new_password: "{{ new_password }}"
|
||||
roleid: "{{ roleid }}"
|
||||
|
||||
- name: Add user with specified account types
|
||||
community.general.redfish_command:
|
||||
category: Accounts
|
||||
command: AddUser
|
||||
baseuri: "{{ baseuri }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
new_username: "{{ new_username }}"
|
||||
new_password: "{{ new_password }}"
|
||||
roleid: "{{ roleid }}"
|
||||
account_types:
|
||||
- Redfish
|
||||
- WebUI
|
||||
|
||||
- name: Add user using new option aliases
|
||||
community.general.redfish_command:
|
||||
category: Accounts
|
||||
@@ -747,6 +777,8 @@ def main():
|
||||
new_username=dict(aliases=["account_username"]),
|
||||
new_password=dict(aliases=["account_password"], no_log=True),
|
||||
roleid=dict(aliases=["account_roleid"]),
|
||||
account_types=dict(type='list', elements='str', aliases=["account_accounttypes"]),
|
||||
oem_account_types=dict(type='list', elements='str', aliases=["account_oemaccounttypes"]),
|
||||
update_username=dict(type='str', aliases=["account_updatename"]),
|
||||
account_properties=dict(type='dict', default={}),
|
||||
bootdevice=dict(),
|
||||
@@ -806,12 +838,16 @@ def main():
|
||||
'token': module.params['auth_token']}
|
||||
|
||||
# user to add/modify/delete
|
||||
user = {'account_id': module.params['id'],
|
||||
'account_username': module.params['new_username'],
|
||||
'account_password': module.params['new_password'],
|
||||
'account_roleid': module.params['roleid'],
|
||||
'account_updatename': module.params['update_username'],
|
||||
'account_properties': module.params['account_properties']}
|
||||
user = {
|
||||
'account_id': module.params['id'],
|
||||
'account_username': module.params['new_username'],
|
||||
'account_password': module.params['new_password'],
|
||||
'account_roleid': module.params['roleid'],
|
||||
'account_accounttypes': module.params['account_types'],
|
||||
'account_oemaccounttypes': module.params['oem_account_types'],
|
||||
'account_updatename': module.params['update_username'],
|
||||
'account_properties': module.params['account_properties'],
|
||||
}
|
||||
|
||||
# timeout
|
||||
timeout = module.params['timeout']
|
||||
|
||||
@@ -90,93 +90,88 @@ repositories:
|
||||
type: list
|
||||
'''
|
||||
|
||||
import re
|
||||
import os
|
||||
from fnmatch import fnmatch
|
||||
from copy import deepcopy
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def run_subscription_manager(module, arguments):
|
||||
# Execute subscription-manager with arguments and manage common errors
|
||||
rhsm_bin = module.get_bin_path('subscription-manager')
|
||||
if not rhsm_bin:
|
||||
module.fail_json(msg='The executable file subscription-manager was not found in PATH')
|
||||
class Rhsm(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.rhsm_bin = self.module.get_bin_path('subscription-manager', required=True)
|
||||
self.rhsm_kwargs = {
|
||||
'environ_update': dict(LANG='C', LC_ALL='C', LC_MESSAGES='C'),
|
||||
'expand_user_and_vars': False,
|
||||
'use_unsafe_shell': False,
|
||||
}
|
||||
|
||||
lang_env = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')
|
||||
rc, out, err = module.run_command("%s %s" % (rhsm_bin, " ".join(arguments)), environ_update=lang_env)
|
||||
def run_repos(self, arguments):
|
||||
"""
|
||||
Execute `subscription-manager repos` with arguments and manage common errors
|
||||
"""
|
||||
rc, out, err = self.module.run_command(
|
||||
[self.rhsm_bin, 'repos'] + arguments,
|
||||
**self.rhsm_kwargs
|
||||
)
|
||||
|
||||
if rc == 0 and out == 'This system has no repositories available through subscriptions.\n':
|
||||
module.fail_json(msg='This system has no repositories available through subscriptions')
|
||||
elif rc == 1:
|
||||
module.fail_json(msg='subscription-manager failed with the following error: %s' % err)
|
||||
else:
|
||||
return rc, out, err
|
||||
if rc == 0 and out == 'This system has no repositories available through subscriptions.\n':
|
||||
self.module.fail_json(msg='This system has no repositories available through subscriptions')
|
||||
elif rc == 1:
|
||||
self.module.fail_json(msg='subscription-manager failed with the following error: %s' % err)
|
||||
else:
|
||||
return rc, out, err
|
||||
|
||||
def list_repositories(self):
|
||||
"""
|
||||
Generate RHSM repository list and return a list of dict
|
||||
"""
|
||||
rc, out, err = self.run_repos(['--list'])
|
||||
|
||||
repo_id = ''
|
||||
repo_name = ''
|
||||
repo_url = ''
|
||||
repo_enabled = ''
|
||||
|
||||
repo_result = []
|
||||
for line in out.splitlines():
|
||||
# ignore lines that are:
|
||||
# - empty
|
||||
# - "+---------[...]" -- i.e. header
|
||||
# - " Available Repositories [...]" -- i.e. header
|
||||
if line == '' or line[0] == '+' or line[0] == ' ':
|
||||
continue
|
||||
|
||||
if line.startswith('Repo ID: '):
|
||||
repo_id = line[9:].lstrip()
|
||||
continue
|
||||
|
||||
if line.startswith('Repo Name: '):
|
||||
repo_name = line[11:].lstrip()
|
||||
continue
|
||||
|
||||
if line.startswith('Repo URL: '):
|
||||
repo_url = line[10:].lstrip()
|
||||
continue
|
||||
|
||||
if line.startswith('Enabled: '):
|
||||
repo_enabled = line[9:].lstrip()
|
||||
|
||||
repo = {
|
||||
"id": repo_id,
|
||||
"name": repo_name,
|
||||
"url": repo_url,
|
||||
"enabled": True if repo_enabled == '1' else False
|
||||
}
|
||||
|
||||
repo_result.append(repo)
|
||||
|
||||
return repo_result
|
||||
|
||||
|
||||
def get_repository_list(module, list_parameter):
|
||||
# Generate RHSM repository list and return a list of dict
|
||||
if list_parameter == 'list_enabled':
|
||||
rhsm_arguments = ['repos', '--list-enabled']
|
||||
elif list_parameter == 'list_disabled':
|
||||
rhsm_arguments = ['repos', '--list-disabled']
|
||||
elif list_parameter == 'list':
|
||||
rhsm_arguments = ['repos', '--list']
|
||||
rc, out, err = run_subscription_manager(module, rhsm_arguments)
|
||||
|
||||
skip_lines = [
|
||||
'+----------------------------------------------------------+',
|
||||
' Available Repositories in /etc/yum.repos.d/redhat.repo'
|
||||
]
|
||||
repo_id_re = re.compile(r'Repo ID:\s+(.*)')
|
||||
repo_name_re = re.compile(r'Repo Name:\s+(.*)')
|
||||
repo_url_re = re.compile(r'Repo URL:\s+(.*)')
|
||||
repo_enabled_re = re.compile(r'Enabled:\s+(.*)')
|
||||
|
||||
repo_id = ''
|
||||
repo_name = ''
|
||||
repo_url = ''
|
||||
repo_enabled = ''
|
||||
|
||||
repo_result = []
|
||||
for line in out.splitlines():
|
||||
if line == '' or line in skip_lines:
|
||||
continue
|
||||
|
||||
repo_id_match = repo_id_re.match(line)
|
||||
if repo_id_match:
|
||||
repo_id = repo_id_match.group(1)
|
||||
continue
|
||||
|
||||
repo_name_match = repo_name_re.match(line)
|
||||
if repo_name_match:
|
||||
repo_name = repo_name_match.group(1)
|
||||
continue
|
||||
|
||||
repo_url_match = repo_url_re.match(line)
|
||||
if repo_url_match:
|
||||
repo_url = repo_url_match.group(1)
|
||||
continue
|
||||
|
||||
repo_enabled_match = repo_enabled_re.match(line)
|
||||
if repo_enabled_match:
|
||||
repo_enabled = repo_enabled_match.group(1)
|
||||
|
||||
repo = {
|
||||
"id": repo_id,
|
||||
"name": repo_name,
|
||||
"url": repo_url,
|
||||
"enabled": True if repo_enabled == '1' else False
|
||||
}
|
||||
|
||||
repo_result.append(repo)
|
||||
|
||||
return repo_result
|
||||
|
||||
|
||||
def repository_modify(module, state, name, purge=False):
|
||||
def repository_modify(module, rhsm, state, name, purge=False):
|
||||
name = set(name)
|
||||
current_repo_list = get_repository_list(module, 'list')
|
||||
current_repo_list = rhsm.list_repositories()
|
||||
updated_repo_list = deepcopy(current_repo_list)
|
||||
matched_existing_repo = {}
|
||||
for repoid in name:
|
||||
@@ -191,7 +186,7 @@ def repository_modify(module, state, name, purge=False):
|
||||
results = []
|
||||
diff_before = ""
|
||||
diff_after = ""
|
||||
rhsm_arguments = ['repos']
|
||||
rhsm_arguments = []
|
||||
|
||||
for repoid in matched_existing_repo:
|
||||
if len(matched_existing_repo[repoid]) == 0:
|
||||
@@ -236,7 +231,7 @@ def repository_modify(module, state, name, purge=False):
|
||||
'after_header': "RHSM repositories"}
|
||||
|
||||
if not module.check_mode and changed:
|
||||
rc, out, err = run_subscription_manager(module, rhsm_arguments)
|
||||
rc, out, err = rhsm.run_repos(rhsm_arguments)
|
||||
results = out.splitlines()
|
||||
module.exit_json(results=results, changed=changed, repositories=updated_repo_list, diff=diff)
|
||||
|
||||
@@ -256,6 +251,8 @@ def main():
|
||||
msg="Interacting with subscription-manager requires root permissions ('become: true')"
|
||||
)
|
||||
|
||||
rhsm = Rhsm(module)
|
||||
|
||||
name = module.params['name']
|
||||
state = module.params['state']
|
||||
purge = module.params['purge']
|
||||
@@ -268,7 +265,7 @@ def main():
|
||||
collection_name='community.general',
|
||||
)
|
||||
|
||||
repository_modify(module, state, name, purge)
|
||||
repository_modify(module, rhsm, state, name, purge)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -22,8 +22,6 @@ description:
|
||||
extends_documentation_fragment:
|
||||
- community.general.scaleway
|
||||
- community.general.attributes
|
||||
requirements:
|
||||
- ipaddress
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
@@ -137,19 +135,8 @@ data:
|
||||
}
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.scaleway import SCALEWAY_LOCATION, scaleway_argument_spec, Scaleway, payload_from_object
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
try:
|
||||
from ipaddress import ip_network # noqa: F401, pylint: disable=unused-import
|
||||
except ImportError:
|
||||
IPADDRESS_IMP_ERR = traceback.format_exc()
|
||||
HAS_IPADDRESS = False
|
||||
else:
|
||||
IPADDRESS_IMP_ERR = None
|
||||
HAS_IPADDRESS = True
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def get_sgr_from_api(security_group_rules, security_group_rule):
|
||||
@@ -272,8 +259,6 @@ def main():
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
if not HAS_IPADDRESS:
|
||||
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMP_ERR)
|
||||
|
||||
core(module)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ DOCUMENTATION = '''
|
||||
module: snap
|
||||
short_description: Manages snaps
|
||||
description:
|
||||
- "Manages snaps packages."
|
||||
- Manages snaps packages.
|
||||
extends_documentation_fragment:
|
||||
- community.general.attributes
|
||||
attributes:
|
||||
@@ -28,7 +28,11 @@ attributes:
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the snaps.
|
||||
- Name of the snaps to be installed.
|
||||
- Any named snap accepted by the C(snap) command is valid.
|
||||
- >
|
||||
Notice that snap files might require O(dangerous=true) to ignore the error
|
||||
"cannot find signatures with metadata for snap".
|
||||
required: true
|
||||
type: list
|
||||
elements: str
|
||||
@@ -45,7 +49,7 @@ options:
|
||||
description:
|
||||
- Confinement policy. The classic confinement allows a snap to have
|
||||
the same level of access to the system as "classic" packages,
|
||||
like those managed by APT. This option corresponds to the --classic argument.
|
||||
like those managed by APT. This option corresponds to the C(--classic) argument.
|
||||
This option can only be specified if there is a single snap in the task.
|
||||
type: bool
|
||||
required: false
|
||||
@@ -55,6 +59,9 @@ options:
|
||||
- Define which release of a snap is installed and tracked for updates.
|
||||
This option can only be specified if there is a single snap in the task.
|
||||
- If not passed, the C(snap) command will default to V(stable).
|
||||
- If the value passed does not contain the C(track), it will default to C(latest).
|
||||
For example, if V(edge) is passed, the module will assume the channel to be V(latest/edge).
|
||||
- See U(https://snapcraft.io/docs/channels) for more details about snap channels.
|
||||
type: str
|
||||
required: false
|
||||
options:
|
||||
@@ -66,6 +73,14 @@ options:
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 4.4.0
|
||||
dangerous:
|
||||
description:
|
||||
- Install the given snap file even if there are no pre-acknowledged signatures for it,
|
||||
meaning it was not verified and could be dangerous.
|
||||
type: bool
|
||||
required: false
|
||||
default: false
|
||||
version_added: 7.2.0
|
||||
|
||||
author:
|
||||
- Victor Carceler (@vcarceler) <vcarceler@iespuigcastellar.xeill.net>
|
||||
@@ -176,6 +191,7 @@ class Snap(StateModuleHelper):
|
||||
'classic': dict(type='bool', default=False),
|
||||
'channel': dict(type='str'),
|
||||
'options': dict(type='list', elements='str'),
|
||||
'dangerous': dict(type='bool', default=False),
|
||||
},
|
||||
supports_check_mode=True,
|
||||
)
|
||||
@@ -190,7 +206,16 @@ class Snap(StateModuleHelper):
|
||||
|
||||
def __init_module__(self):
|
||||
self.runner = snap_runner(self.module)
|
||||
self.vars.set("snap_status", self.snap_status(self.vars.name, self.vars.channel), output=False)
|
||||
# if state=present there might be file names passed in 'name', in
|
||||
# which case they must be converted to their actual snap names, which
|
||||
# is done using the names_from_snaps() method calling 'snap info'.
|
||||
if self.vars.state == "present":
|
||||
self.vars.set("snapinfo_run_info", [], output=(self.verbosity >= 4))
|
||||
self.vars.set("snap_names", self.names_from_snaps(self.vars.name))
|
||||
status_var = "snap_names"
|
||||
else:
|
||||
status_var = "name"
|
||||
self.vars.set("snap_status", self.snap_status(self.vars[status_var], self.vars.channel), output=False)
|
||||
self.vars.set("snap_status_map", dict(zip(self.vars.name, self.vars.snap_status)), output=False)
|
||||
|
||||
def _run_multiple_commands(self, commands, actionable_names, bundle=True, refresh=False):
|
||||
@@ -207,25 +232,25 @@ class Snap(StateModuleHelper):
|
||||
rc, out, err = ctx.run(state=state, name=actionable_names)
|
||||
results_cmd.append(commands + actionable_names)
|
||||
results_rc.append(rc)
|
||||
results_out.append(out)
|
||||
results_err.append(err)
|
||||
results_out.append(out.strip())
|
||||
results_err.append(err.strip())
|
||||
results_run_info.append(ctx.run_info)
|
||||
else:
|
||||
for name in actionable_names:
|
||||
rc, out, err = ctx.run(state=state, name=name)
|
||||
results_cmd.append(commands + [name])
|
||||
results_rc.append(rc)
|
||||
results_out.append(out)
|
||||
results_err.append(err)
|
||||
results_out.append(out.strip())
|
||||
results_err.append(err.strip())
|
||||
results_run_info.append(ctx.run_info)
|
||||
|
||||
return [
|
||||
return (
|
||||
'; '.join([to_native(x) for x in results_cmd]),
|
||||
self._first_non_zero(results_rc),
|
||||
'\n'.join(results_out),
|
||||
'\n'.join(results_err),
|
||||
results_run_info,
|
||||
]
|
||||
)
|
||||
|
||||
def __quit_module__(self):
|
||||
if self.vars.channel is None:
|
||||
@@ -266,32 +291,65 @@ class Snap(StateModuleHelper):
|
||||
|
||||
try:
|
||||
option_map = self.convert_json_to_map(out)
|
||||
return option_map
|
||||
except Exception as e:
|
||||
self.do_raise(
|
||||
msg="Parsing option map returned by 'snap get {0}' triggers exception '{1}', output:\n'{2}'".format(snap_name, str(e), out))
|
||||
|
||||
return option_map
|
||||
def names_from_snaps(self, snaps):
|
||||
def process_one(rc, out, err):
|
||||
res = [line for line in out.split("\n") if line.startswith("name:")]
|
||||
name = res[0].split()[1]
|
||||
return [name]
|
||||
|
||||
def process_many(rc, out, err):
|
||||
outputs = out.split("---")
|
||||
res = []
|
||||
for sout in outputs:
|
||||
res.extend(process_one(rc, sout, ""))
|
||||
return res
|
||||
|
||||
def process(rc, out, err):
|
||||
if len(snaps) == 1:
|
||||
check_error = err
|
||||
process_ = process_one
|
||||
else:
|
||||
check_error = out
|
||||
process_ = process_many
|
||||
|
||||
if "warning: no snap found" in check_error:
|
||||
self.do_raise("Snaps not found: {0}.".format([x.split()[-1]
|
||||
for x in out.split('\n')
|
||||
if x.startswith("warning: no snap found")]))
|
||||
return process_(rc, out, err)
|
||||
|
||||
with self.runner("info name", output_process=process) as ctx:
|
||||
try:
|
||||
names = ctx.run(name=snaps)
|
||||
finally:
|
||||
self.vars.snapinfo_run_info.append(ctx.run_info)
|
||||
return names
|
||||
|
||||
def snap_status(self, snap_name, channel):
|
||||
def _status_check(name, channel, installed):
|
||||
match = [c for n, c in installed if n == name]
|
||||
if not match:
|
||||
return Snap.NOT_INSTALLED
|
||||
if channel and channel != match[0]:
|
||||
if channel and match[0] not in (channel, "latest/{0}".format(channel)):
|
||||
return Snap.CHANNEL_MISMATCH
|
||||
else:
|
||||
return Snap.INSTALLED
|
||||
|
||||
with self.runner("_list") as ctx:
|
||||
rc, out, err = ctx.run(check_rc=True)
|
||||
out = out.split('\n')[1:]
|
||||
out = [self.__list_re.match(x) for x in out]
|
||||
out = [(m.group('name'), m.group('channel')) for m in out if m]
|
||||
list_out = out.split('\n')[1:]
|
||||
list_out = [self.__list_re.match(x) for x in list_out]
|
||||
list_out = [(m.group('name'), m.group('channel')) for m in list_out if m]
|
||||
if self.verbosity >= 4:
|
||||
self.vars.status_out = out
|
||||
self.vars.status_out = list_out
|
||||
self.vars.status_run_info = ctx.run_info
|
||||
|
||||
return [_status_check(n, channel, out) for n in snap_name]
|
||||
return [_status_check(n, channel, list_out) for n in snap_name]
|
||||
|
||||
def is_snap_enabled(self, snap_name):
|
||||
with self.runner("_list name") as ctx:
|
||||
@@ -312,7 +370,7 @@ class Snap(StateModuleHelper):
|
||||
if self.check_mode:
|
||||
return
|
||||
|
||||
params = ['state', 'classic', 'channel'] # get base cmd parts
|
||||
params = ['state', 'classic', 'channel', 'dangerous'] # get base cmd parts
|
||||
has_one_pkg_params = bool(self.vars.classic) or self.vars.channel != 'stable'
|
||||
has_multiple_snaps = len(actionable_snaps) > 1
|
||||
|
||||
@@ -324,8 +382,8 @@ class Snap(StateModuleHelper):
|
||||
self.vars.run_info = run_info
|
||||
|
||||
if rc == 0:
|
||||
match_install = [self.__install_re.match(line) for line in out.split('\n')]
|
||||
match_install = [m.group('name') in actionable_snaps for m in match_install if m]
|
||||
match_install2 = [self.__install_re.match(line) for line in out.split('\n')]
|
||||
match_install = [m.group('name') in actionable_snaps for m in match_install2 if m]
|
||||
if len(match_install) == len(actionable_snaps):
|
||||
return
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: the endpoints this module relies on do not exist any more and do not resolve to IPs in DNS.
|
||||
alternative: no known alternative at this point
|
||||
|
||||
module: stackdriver
|
||||
short_description: Send code deploy and annotation events to stackdriver
|
||||
description:
|
||||
|
||||
@@ -19,6 +19,12 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: the endpoints this module relies on do not exist any more and do not resolve to IPs in DNS.
|
||||
alternative: no known alternative at this point
|
||||
|
||||
module: webfaction_app
|
||||
short_description: Add or remove applications on a Webfaction host
|
||||
description:
|
||||
|
||||
@@ -16,6 +16,12 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: the endpoints this module relies on do not exist any more and do not resolve to IPs in DNS.
|
||||
alternative: no known alternative at this point
|
||||
|
||||
module: webfaction_db
|
||||
short_description: Add or remove a database on Webfaction
|
||||
description:
|
||||
|
||||
@@ -13,6 +13,12 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: the endpoints this module relies on do not exist any more and do not resolve to IPs in DNS.
|
||||
alternative: no known alternative at this point
|
||||
|
||||
module: webfaction_domain
|
||||
short_description: Add or remove domains and subdomains on Webfaction
|
||||
description:
|
||||
|
||||
@@ -13,6 +13,12 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: the endpoints this module relies on do not exist any more and do not resolve to IPs in DNS.
|
||||
alternative: no known alternative at this point
|
||||
|
||||
module: webfaction_mailbox
|
||||
short_description: Add or remove mailboxes on Webfaction
|
||||
description:
|
||||
|
||||
@@ -13,6 +13,12 @@ __metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
|
||||
deprecated:
|
||||
removed_in: 9.0.0
|
||||
why: the endpoints this module relies on do not exist any more and do not resolve to IPs in DNS.
|
||||
alternative: no known alternative at this point
|
||||
|
||||
module: webfaction_site
|
||||
short_description: Add or remove a website on a Webfaction host
|
||||
description:
|
||||
|
||||
@@ -151,12 +151,6 @@ instance:
|
||||
}
|
||||
'''
|
||||
|
||||
HAS_XENAPI = False
|
||||
try:
|
||||
import XenAPI # noqa: F401, pylint: disable=unused-import
|
||||
HAS_XENAPI = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.xenserver import (xenserver_common_argument_spec, XenServerObject, get_object_ref,
|
||||
|
||||
@@ -177,12 +177,6 @@ instance:
|
||||
}
|
||||
'''
|
||||
|
||||
HAS_XENAPI = False
|
||||
try:
|
||||
import XenAPI # noqa: F401, pylint: disable=unused-import
|
||||
HAS_XENAPI = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.general.plugins.module_utils.xenserver import (xenserver_common_argument_spec, XenServerObject, get_object_ref,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Florian Paul Azim Hoberg <florian.hoberg@credativ.de>
|
||||
# Copyright (c) 2018, Florian Paul Azim Hoberg (@gyptazy) <gyptazy@gyptazy.ch>
|
||||
#
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
@@ -25,7 +25,8 @@ attributes:
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Package name or a list of package names with optional wildcards.
|
||||
- Package name or a list of package names with optional version or wildcards.
|
||||
- Specifying versions is supported since community.general 7.2.0.
|
||||
type: list
|
||||
required: true
|
||||
elements: str
|
||||
@@ -50,7 +51,14 @@ EXAMPLES = r'''
|
||||
- name: Prevent Apache / httpd from being updated
|
||||
community.general.yum_versionlock:
|
||||
state: present
|
||||
name: httpd
|
||||
name:
|
||||
- httpd
|
||||
|
||||
- name: Prevent Apache / httpd version 2.4.57-2 from being updated
|
||||
community.general.yum_versionlock:
|
||||
state: present
|
||||
name:
|
||||
- httpd-0:2.4.57-2.el9
|
||||
|
||||
- name: Prevent multiple packages from being updated
|
||||
community.general.yum_versionlock:
|
||||
@@ -111,22 +119,30 @@ class YumVersionLock:
|
||||
def ensure_state(self, packages, command):
|
||||
""" Ensure packages state """
|
||||
rc, out, err = self.module.run_command([self.yum_bin, "-q", "versionlock", command] + packages)
|
||||
# If no package can be found this will be written on stdout with rc 0
|
||||
if 'No package found for' in out:
|
||||
self.module.fail_json(msg=out)
|
||||
if rc == 0:
|
||||
return True
|
||||
self.module.fail_json(msg="Error: " + to_native(err) + to_native(out))
|
||||
|
||||
|
||||
def match(entry, name):
|
||||
match = False
|
||||
m = NEVRA_RE_YUM.match(entry)
|
||||
if not m:
|
||||
m = NEVRA_RE_DNF.match(entry)
|
||||
if not m:
|
||||
return False
|
||||
return fnmatch(m.group("name"), name)
|
||||
if fnmatch(m.group("name"), name):
|
||||
match = True
|
||||
if entry.rstrip('.*') == name:
|
||||
match = True
|
||||
return match
|
||||
|
||||
|
||||
def main():
|
||||
""" start main program to add/remove a package to yum versionlock"""
|
||||
""" start main program to add/delete a package to yum versionlock """
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
|
||||
@@ -26,6 +26,7 @@ def main():
|
||||
arg_values=dict(type="dict", default={}),
|
||||
check_mode_skip=dict(type="bool", default=False),
|
||||
aa=dict(type="raw"),
|
||||
tt=dict(),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -121,3 +121,20 @@ cmd_echo_tests:
|
||||
- test_result.rc == None
|
||||
- test_result.out == None
|
||||
- test_result.err == None
|
||||
|
||||
- name: set aa and tt value
|
||||
arg_formats:
|
||||
aa:
|
||||
func: as_opt_eq_val
|
||||
args: [--answer]
|
||||
tt:
|
||||
func: as_opt_val
|
||||
args: [--tt-arg]
|
||||
arg_order: 'aa tt'
|
||||
arg_values:
|
||||
tt: potatoes
|
||||
aa: 11
|
||||
assertions:
|
||||
- test_result.rc == 0
|
||||
- test_result.out == "-- --answer=11 --tt-arg potatoes\n"
|
||||
- test_result.err == ""
|
||||
|
||||
67
tests/integration/targets/consul/tasks/consul_policy.yml
Normal file
67
tests/integration/targets/consul/tasks/consul_policy.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Create a policy with rules
|
||||
consul_policy:
|
||||
name: foo-access
|
||||
rules: |
|
||||
key "foo" {
|
||||
policy = "read"
|
||||
}
|
||||
key "private/foo" {
|
||||
policy = "deny"
|
||||
}
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result['policy']['Name'] == 'foo-access'
|
||||
- name: Update the rules associated to a policy
|
||||
consul_policy:
|
||||
name: foo-access
|
||||
rules: |
|
||||
key "foo" {
|
||||
policy = "read"
|
||||
}
|
||||
key "private/foo" {
|
||||
policy = "deny"
|
||||
}
|
||||
event "bbq" {
|
||||
policy = "write"
|
||||
}
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- name: Update reports not changed when updating again without changes
|
||||
consul_policy:
|
||||
name: foo-access
|
||||
rules: |
|
||||
key "foo" {
|
||||
policy = "read"
|
||||
}
|
||||
key "private/foo" {
|
||||
policy = "deny"
|
||||
}
|
||||
event "bbq" {
|
||||
policy = "write"
|
||||
}
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- name: Remove a policy
|
||||
consul_policy:
|
||||
name: foo-access
|
||||
token: "{{ consul_management_token }}"
|
||||
state: absent
|
||||
register: result
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
@@ -6,6 +6,7 @@
|
||||
- name: list sessions
|
||||
consul_session:
|
||||
state: list
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
@@ -17,6 +18,7 @@
|
||||
consul_session:
|
||||
state: present
|
||||
name: testsession
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
@@ -31,6 +33,7 @@
|
||||
- name: list sessions after creation
|
||||
consul_session:
|
||||
state: list
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- set_fact:
|
||||
@@ -52,12 +55,13 @@
|
||||
- name: ensure session was created
|
||||
assert:
|
||||
that:
|
||||
- test_session_found|default(False)
|
||||
- test_session_found|default(false)
|
||||
|
||||
- name: fetch info about a session
|
||||
consul_session:
|
||||
state: info
|
||||
id: '{{ session_id }}'
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
@@ -68,6 +72,7 @@
|
||||
consul_session:
|
||||
state: info
|
||||
name: test
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
@@ -80,6 +85,7 @@
|
||||
state: info
|
||||
id: '{{ session_id }}'
|
||||
scheme: non_existent
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
@@ -93,6 +99,7 @@
|
||||
id: '{{ session_id }}'
|
||||
port: 8501
|
||||
scheme: https
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
@@ -108,6 +115,7 @@
|
||||
id: '{{ session_id }}'
|
||||
port: 8501
|
||||
scheme: https
|
||||
token: "{{ consul_management_token }}"
|
||||
validate_certs: false
|
||||
register: result
|
||||
|
||||
@@ -122,6 +130,7 @@
|
||||
id: '{{ session_id }}'
|
||||
port: 8501
|
||||
scheme: https
|
||||
token: "{{ consul_management_token }}"
|
||||
environment:
|
||||
REQUESTS_CA_BUNDLE: '{{ remote_dir }}/cert.pem'
|
||||
register: result
|
||||
@@ -134,6 +143,7 @@
|
||||
consul_session:
|
||||
state: absent
|
||||
id: '{{ session_id }}'
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
@@ -143,6 +153,7 @@
|
||||
- name: list sessions after deletion
|
||||
consul_session:
|
||||
state: list
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
@@ -169,6 +180,7 @@
|
||||
state: present
|
||||
name: session-with-ttl
|
||||
ttl: 180 # sec
|
||||
token: "{{ consul_management_token }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
- name: Install Consul and test
|
||||
vars:
|
||||
consul_version: 1.5.0
|
||||
consul_uri: https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/consul/consul_{{ consul_version }}_{{ ansible_system | lower }}_{{ consul_arch }}.zip
|
||||
consul_version: 1.13.2
|
||||
consul_uri: https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_{{ ansible_system | lower }}_{{ consul_arch }}.zip
|
||||
consul_cmd: '{{ remote_tmp_dir }}/consul'
|
||||
block:
|
||||
- name: Install requests<2.20 (CentOS/RHEL 6)
|
||||
@@ -76,14 +76,22 @@
|
||||
dest: '{{ remote_tmp_dir }}/consul_config.hcl'
|
||||
- name: Start Consul (dev mode enabled)
|
||||
shell: nohup {{ consul_cmd }} agent -dev -config-file {{ remote_tmp_dir }}/consul_config.hcl </dev/null >/dev/null 2>&1 &
|
||||
- name: Bootstrap ACL
|
||||
command: '{{ consul_cmd }} acl bootstrap --format=json'
|
||||
register: consul_bootstrap_result_string
|
||||
- set_fact:
|
||||
consul_management_token: '{{ consul_bootstrap_json_result["SecretID"] }}'
|
||||
vars:
|
||||
consul_bootstrap_json_result: '{{ consul_bootstrap_result_string.stdout | from_json }}'
|
||||
- name: Create some data
|
||||
command: '{{ consul_cmd }} kv put data/value{{ item }} foo{{ item }}'
|
||||
command: '{{ consul_cmd }} kv put -token={{consul_management_token}} data/value{{ item }} foo{{ item }}'
|
||||
loop:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- import_tasks: consul_session.yml
|
||||
- import_tasks: consul_policy.yml
|
||||
always:
|
||||
- name: Kill consul process
|
||||
shell: kill $(cat {{ remote_tmp_dir }}/consul.pid)
|
||||
ignore_errors: true
|
||||
ignore_errors: true
|
||||
@@ -12,3 +12,8 @@ ports {
|
||||
}
|
||||
key_file = "{{ remote_dir }}/privatekey.pem"
|
||||
cert_file = "{{ remote_dir }}/cert.pem"
|
||||
acl {
|
||||
enabled = true
|
||||
default_policy = "deny"
|
||||
down_policy = "extend-cache"
|
||||
}
|
||||
|
||||
7
tests/integration/targets/htpasswd/aliases
Normal file
7
tests/integration/targets/htpasswd/aliases
Normal file
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
azp/posix/2
|
||||
destructive
|
||||
needs/root
|
||||
9
tests/integration/targets/htpasswd/handlers/main.yml
Normal file
9
tests/integration/targets/htpasswd/handlers/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: remove passlib
|
||||
ansible.builtin.pip:
|
||||
name: passlib
|
||||
state: absent
|
||||
7
tests/integration/targets/htpasswd/meta/main.yml
Normal file
7
tests/integration/targets/htpasswd/meta/main.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
dependencies:
|
||||
- setup_remote_tmp_dir
|
||||
83
tests/integration/targets/htpasswd/tasks/main.yml
Normal file
83
tests/integration/targets/htpasswd/tasks/main.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: install passlib
|
||||
ansible.builtin.pip:
|
||||
name: passlib
|
||||
notify: remove passlib
|
||||
|
||||
- name: add bob (check mode)
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
password: c00lbob
|
||||
check_mode: true
|
||||
register: add_bob_check
|
||||
|
||||
- name: add bob
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
password: c00lbob
|
||||
register: add_bob
|
||||
|
||||
- name: add bob (idempotency)
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
password: c00lbob
|
||||
register: add_bob_idempot
|
||||
|
||||
- name: add bob new password
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
password: SUPERsecret
|
||||
register: add_bob_newpw
|
||||
|
||||
- name: add bob new password (idempotency)
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
password: SUPERsecret
|
||||
register: add_bob_newpw_idempot
|
||||
|
||||
- name: test add bob assertions
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- add_bob_check is changed
|
||||
- add_bob is changed
|
||||
- add_bob_idempot is not changed
|
||||
- add_bob_newpw is changed
|
||||
- add_bob_newpw_idempot is not changed
|
||||
|
||||
- name: remove bob (check mode)
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
state: absent
|
||||
check_mode: true
|
||||
register: del_bob_check
|
||||
|
||||
- name: remove bob
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
state: absent
|
||||
register: del_bob
|
||||
|
||||
- name: remove bob (idempotency)
|
||||
community.general.htpasswd:
|
||||
path: "{{ htpasswd_path }}"
|
||||
name: bob
|
||||
state: absent
|
||||
register: del_bob_idempot
|
||||
|
||||
- name: test remove bob assertions
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- del_bob_check is changed
|
||||
- del_bob is changed
|
||||
- del_bob_idempot is not changed
|
||||
6
tests/integration/targets/htpasswd/vars/main.yml
Normal file
6
tests/integration/targets/htpasswd/vars/main.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
htpasswd_path: "{{ remote_tmp_dir }}/dot_htpasswd"
|
||||
@@ -3,3 +3,4 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
azp/posix/2
|
||||
skip/python2.6
|
||||
|
||||
@@ -9,3 +9,4 @@ skip/osx
|
||||
skip/macos
|
||||
skip/freebsd
|
||||
needs/root
|
||||
skip/rhel # FIXME: keytool seems to be broken on newer RHELs
|
||||
|
||||
@@ -9,3 +9,4 @@ skip/osx
|
||||
skip/macos
|
||||
skip/freebsd
|
||||
needs/root
|
||||
skip/rhel # FIXME: keytool seems to be broken on newer RHELs
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
unsupported
|
||||
keycloak_authz_permission_info
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Ansible Project
|
||||
// GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
To be able to run these integration tests a keycloak server must be
|
||||
reachable under a specific url with a specific admin user and password.
|
||||
The exact values expected for these parameters can be found in
|
||||
'vars/main.yml' file. A simple way to do this is to use the official
|
||||
keycloak docker images like this:
|
||||
|
||||
----
|
||||
docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=<url-path> -e KEYCLOAK_ADMIN=<admin_user> -e KEYCLOAK_ADMIN_PASSWORD=<admin_password> quay.io/keycloak/keycloak:20.0.2 start-dev
|
||||
----
|
||||
|
||||
Example with concrete values inserted:
|
||||
|
||||
----
|
||||
docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=/auth -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:20.0.2 start-dev
|
||||
----
|
||||
|
||||
This test suite can run against a fresh unconfigured server instance
|
||||
(no preconfiguration required) and cleans up after itself (undoes all
|
||||
its config changes) as long as it runs through completly. While its active
|
||||
it changes the server configuration in the following ways:
|
||||
|
||||
* creating, modifying and deleting some keycloak groups
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
- name: Remove keycloak client to avoid failures from previous failed runs
|
||||
community.general.keycloak_client:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
client_id: "{{ client_id }}"
|
||||
state: absent
|
||||
|
||||
- name: Create keycloak client with authorization services enabled
|
||||
community.general.keycloak_client:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
client_id: "{{ client_id }}"
|
||||
state: present
|
||||
enabled: true
|
||||
public_client: false
|
||||
service_accounts_enabled: true
|
||||
authorization_services_enabled: true
|
||||
|
||||
- name: Create file:create authorization scope
|
||||
community.general.keycloak_authz_authorization_scope:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "file:create"
|
||||
display_name: "File create"
|
||||
icon_uri: "http://localhost/icon.png"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Create file:delete authorization scope
|
||||
community.general.keycloak_authz_authorization_scope:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "file:delete"
|
||||
display_name: "File delete"
|
||||
icon_uri: "http://localhost/icon.png"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Create permission without type (test for failure)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
failed_when: result.msg.find('missing required arguments') == -1
|
||||
|
||||
- name: Create scope permission without scopes (test for failure)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission"
|
||||
permission_type: scope
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
failed_when: result.msg.find('Scopes need to defined when permission type is set to scope!') == -1
|
||||
|
||||
- name: Create scope permission with multiple resources (test for failure)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission"
|
||||
resources:
|
||||
- "Default Resource"
|
||||
- "Other Resource"
|
||||
permission_type: scope
|
||||
scopes:
|
||||
- "file:delete"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
failed_when: result.msg.find('Only one resource can be defined for a scope permission!') == -1
|
||||
|
||||
- name: Create scope permission with invalid policy name (test for failure)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission"
|
||||
permission_type: scope
|
||||
scopes:
|
||||
- "file:delete"
|
||||
policies:
|
||||
- "Missing Policy"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
failed_when: result.msg.find('Unable to find authorization policy with name') == -1
|
||||
|
||||
- name: Create scope permission
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission"
|
||||
permission_type: scope
|
||||
scopes:
|
||||
- "file:delete"
|
||||
policies:
|
||||
- "Default Policy"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that scope permission was created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "ScopePermission"
|
||||
- result.end_state.description == "Scope permission"
|
||||
- result.end_state.type == "scope"
|
||||
- result.end_state.resources == []
|
||||
- result.end_state.policies|length == 1
|
||||
- result.end_state.scopes|length == 1
|
||||
|
||||
- name: Query state
|
||||
community.general.keycloak_authz_permission_info:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
name: "ScopePermission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that queried state matches desired end state
|
||||
assert:
|
||||
that:
|
||||
- result.queried_state.name == "ScopePermission"
|
||||
- result.queried_state.description == "Scope permission"
|
||||
|
||||
- name: Create scope permission (test for idempotency)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission"
|
||||
permission_type: scope
|
||||
scopes:
|
||||
- "file:delete"
|
||||
policies:
|
||||
- "Default Policy"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "ScopePermission"
|
||||
- result.end_state.description == "Scope permission"
|
||||
- result.end_state.type == "scope"
|
||||
- result.end_state.resources == []
|
||||
- result.end_state.policies|length == 1
|
||||
- result.end_state.scopes|length == 1
|
||||
|
||||
- name: Query state
|
||||
community.general.keycloak_authz_permission_info:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
name: "ScopePermission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that queried state matches desired end state
|
||||
assert:
|
||||
that:
|
||||
- result.queried_state.name == "ScopePermission"
|
||||
- result.queried_state.description == "Scope permission"
|
||||
|
||||
- name: Update scope permission
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission changed"
|
||||
permission_type: scope
|
||||
decision_strategy: 'AFFIRMATIVE'
|
||||
scopes:
|
||||
- "file:create"
|
||||
- "file:delete"
|
||||
policies: []
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that scope permission was updated correctly
|
||||
assert:
|
||||
that:
|
||||
- result.changed == True
|
||||
- result.end_state != {}
|
||||
- result.end_state.scopes|length == 2
|
||||
- result.end_state.policies == []
|
||||
- result.end_state.resources == []
|
||||
- result.end_state.name == "ScopePermission"
|
||||
- result.end_state.description == "Scope permission changed"
|
||||
|
||||
- name: Query state
|
||||
community.general.keycloak_authz_permission_info:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
name: "ScopePermission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that queried state matches desired end state
|
||||
assert:
|
||||
that:
|
||||
- result.queried_state.name == "ScopePermission"
|
||||
- result.queried_state.description == "Scope permission changed"
|
||||
|
||||
- name: Update scope permission (test for idempotency)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ScopePermission"
|
||||
description: "Scope permission changed"
|
||||
permission_type: scope
|
||||
decision_strategy: 'AFFIRMATIVE'
|
||||
scopes:
|
||||
- "file:create"
|
||||
- "file:delete"
|
||||
policies: []
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- result.changed == True
|
||||
- result.end_state != {}
|
||||
- result.end_state.scopes|length == 2
|
||||
- result.end_state.policies == []
|
||||
- result.end_state.resources == []
|
||||
- result.end_state.name == "ScopePermission"
|
||||
- result.end_state.description == "Scope permission changed"
|
||||
|
||||
- name: Query state
|
||||
community.general.keycloak_authz_permission_info:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
name: "ScopePermission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that queried state matches desired end state
|
||||
assert:
|
||||
that:
|
||||
- result.queried_state.name == "ScopePermission"
|
||||
- result.queried_state.description == "Scope permission changed"
|
||||
|
||||
- name: Remove scope permission
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: absent
|
||||
name: "ScopePermission"
|
||||
permission_type: scope
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that scope permission was removed
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Remove scope permission (test for idempotency)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: absent
|
||||
name: "ScopePermission"
|
||||
permission_type: scope
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Create resource permission without resources (test for failure)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ResourcePermission"
|
||||
description: "Resource permission"
|
||||
permission_type: resource
|
||||
policies:
|
||||
- "Default Policy"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
failed_when: result.msg.find('A resource need to defined when permission type is set to resource!') == -1
|
||||
|
||||
- name: Create resource permission with scopes (test for failure)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ResourcePermission"
|
||||
description: "Resource permission"
|
||||
permission_type: resource
|
||||
resources:
|
||||
- "Default Resource"
|
||||
policies:
|
||||
- "Default Policy"
|
||||
scopes:
|
||||
- "file:delete"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
failed_when: result.msg.find('Scopes cannot be defined when permission type is set to resource!') == -1
|
||||
|
||||
- name: Create resource permission
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ResourcePermission"
|
||||
description: "Resource permission"
|
||||
resources:
|
||||
- "Default Resource"
|
||||
permission_type: resource
|
||||
policies:
|
||||
- "Default Policy"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that resource permission was created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.policies|length == 1
|
||||
- result.end_state.resources|length == 1
|
||||
- result.end_state.name == "ResourcePermission"
|
||||
- result.end_state.description == "Resource permission"
|
||||
|
||||
- name: Query state
|
||||
community.general.keycloak_authz_permission_info:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
name: "ResourcePermission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that queried state matches desired end state
|
||||
assert:
|
||||
that:
|
||||
- result.queried_state.name == "ResourcePermission"
|
||||
- result.queried_state.description == "Resource permission"
|
||||
|
||||
- name: Create resource permission (test for idempotency)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ResourcePermission"
|
||||
description: "Resource permission"
|
||||
resources:
|
||||
- "Default Resource"
|
||||
permission_type: resource
|
||||
policies:
|
||||
- "Default Policy"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result.end_state != {}
|
||||
- result.end_state.policies|length == 1
|
||||
- result.end_state.resources|length == 1
|
||||
- result.end_state.name == "ResourcePermission"
|
||||
- result.end_state.description == "Resource permission"
|
||||
|
||||
- name: Query state
|
||||
community.general.keycloak_authz_permission_info:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
name: "ResourcePermission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that queried state matches desired end state
|
||||
assert:
|
||||
that:
|
||||
- result.queried_state.name == "ResourcePermission"
|
||||
- result.queried_state.description == "Resource permission"
|
||||
|
||||
- name: Update resource permission
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: present
|
||||
name: "ResourcePermission"
|
||||
description: "Resource permission changed"
|
||||
resources:
|
||||
- "Default Resource"
|
||||
permission_type: resource
|
||||
policies: []
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that resource permission was updated correctly
|
||||
assert:
|
||||
that:
|
||||
- result.changed == True
|
||||
- result.end_state != {}
|
||||
- result.end_state.policies == []
|
||||
- result.end_state.resources|length == 1
|
||||
- result.end_state.name == "ResourcePermission"
|
||||
- result.end_state.description == "Resource permission changed"
|
||||
|
||||
- name: Query state
|
||||
community.general.keycloak_authz_permission_info:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
name: "ResourcePermission"
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that queried state matches desired end state
|
||||
assert:
|
||||
that:
|
||||
- result.queried_state.name == "ResourcePermission"
|
||||
- result.queried_state.description == "Resource permission changed"
|
||||
|
||||
- name: Remove resource permission
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: absent
|
||||
name: "ResourcePermission"
|
||||
permission_type: resource
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that resource permission was removed
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Remove resource permission (test for idempotency)
|
||||
community.general.keycloak_authz_permission:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
state: absent
|
||||
name: "ResourcePermission"
|
||||
permission_type: resource
|
||||
client_id: "{{ client_id }}"
|
||||
realm: "{{ realm }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Remove keycloak client
|
||||
community.general.keycloak_client:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
client_id: "{{ client_id }}"
|
||||
state: absent
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
url: http://localhost:8080/auth
|
||||
admin_realm: master
|
||||
admin_user: admin
|
||||
admin_password: password
|
||||
realm: master
|
||||
client_id: authz
|
||||
@@ -13,7 +13,7 @@
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- net-tools
|
||||
- netcat
|
||||
- netcat-openbsd
|
||||
state: latest
|
||||
when: ansible_os_family == "Debian"
|
||||
|
||||
|
||||
@@ -6,3 +6,5 @@ azp/posix/3
|
||||
destructive
|
||||
needs/root
|
||||
skip/aix
|
||||
skip/freebsd
|
||||
skip/macos
|
||||
|
||||
102
tests/integration/targets/locale_gen/tasks/basic.yml
Normal file
102
tests/integration/targets/locale_gen/tasks/basic.yml
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Is the locale we're going to test against installed? {{ locale_basic.localegen }}
|
||||
command: locale -a
|
||||
register: initial_state
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is not installed {{ locale_basic.localegen }}
|
||||
locale_gen:
|
||||
name: "{{ locale_basic.localegen }}"
|
||||
state: absent
|
||||
|
||||
- name: Is the locale present? {{ locale_basic.localegen }}
|
||||
command: locale -a
|
||||
register: cleaned
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is not present {{ locale_basic.localegen }}
|
||||
assert:
|
||||
that:
|
||||
- locale_basic.skip_removal or locale_basic.locales | intersect(cleaned.stdout_lines) == []
|
||||
|
||||
- name: Install the locale {{ locale_basic.localegen }}
|
||||
locale_gen:
|
||||
name: "{{ locale_basic.localegen }}"
|
||||
state: present
|
||||
register: output_present
|
||||
|
||||
- name: Is the locale present? {{ locale_basic.localegen }}
|
||||
command: locale -a
|
||||
register: post_check_output_present
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is present and we say we installed it {{ locale_basic.localegen }}
|
||||
assert:
|
||||
that:
|
||||
- locale_basic.locales | intersect(post_check_output_present.stdout_lines) != []
|
||||
- locale_basic.skip_removal or output_present is changed
|
||||
|
||||
- name: Install the locale a second time {{ locale_basic.localegen }}
|
||||
locale_gen:
|
||||
name: "{{ locale_basic.localegen }}"
|
||||
state: present
|
||||
register: output_present_idempotent
|
||||
|
||||
- name: Is the locale present? {{ locale_basic.localegen }}
|
||||
command: locale -a
|
||||
register: post_check_output_present_idempotent
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is present and we reported no change {{ locale_basic.localegen }}
|
||||
assert:
|
||||
that:
|
||||
- locale_basic.locales | intersect(post_check_output_present_idempotent.stdout_lines) != []
|
||||
- output_present_idempotent is not changed
|
||||
|
||||
- name: Removals
|
||||
when: locale_basic.skip_removal is false
|
||||
block:
|
||||
- name: Remove the locale {{ locale_basic.localegen }}
|
||||
locale_gen:
|
||||
name: "{{ locale_basic.localegen }}"
|
||||
state: absent
|
||||
register: output_absent
|
||||
|
||||
- name: Is the locale present? {{ locale_basic.localegen }}
|
||||
command: locale -a
|
||||
register: post_check_output_absent
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is absent and we reported a change {{ locale_basic.localegen }}
|
||||
assert:
|
||||
that:
|
||||
- locale_basic.locales | intersect(post_check_output_absent.stdout_lines) == []
|
||||
- output_absent is changed
|
||||
|
||||
- name: Remove the locale a second time {{ locale_basic.localegen }}
|
||||
locale_gen:
|
||||
name: "{{ locale_basic.localegen }}"
|
||||
state: absent
|
||||
register: output_absent_idempotent
|
||||
|
||||
- name: Is the locale present? {{ locale_basic.localegen }}
|
||||
command: locale -a
|
||||
register: post_check_output_absent_idempotent
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is absent and we reported no change {{ locale_basic.localegen }}
|
||||
assert:
|
||||
that:
|
||||
- locale_basic.locales | intersect(post_check_output_absent_idempotent.stdout_lines) == []
|
||||
- output_absent_idempotent is not changed
|
||||
|
||||
# Cleanup
|
||||
- name: Reinstall the locale we tested against if it was initially installed {{ locale_basic.localegen }}
|
||||
locale_gen:
|
||||
name: "{{ locale_basic.localegen }}"
|
||||
state: present
|
||||
when: locale_basic.locales | intersect(initial_state.stdout_lines) != []
|
||||
@@ -1,99 +0,0 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Is the locale we're going to test against installed?
|
||||
shell: locale -a | grep pt_BR
|
||||
register: initial_state
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is not installed
|
||||
locale_gen:
|
||||
name: pt_BR
|
||||
state: absent
|
||||
|
||||
- name: Is the locale present?
|
||||
shell: locale -a | grep pt_BR
|
||||
register: cleaned
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is not present
|
||||
assert:
|
||||
that:
|
||||
- "cleaned.rc == 1"
|
||||
|
||||
- name: Install the locale
|
||||
locale_gen:
|
||||
name: pt_BR
|
||||
state: present
|
||||
register: output
|
||||
|
||||
- name: Is the locale present?
|
||||
shell: locale -a | grep pt_BR
|
||||
register: post_check_output
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is present and we say we installed it
|
||||
assert:
|
||||
that:
|
||||
- "post_check_output.rc == 0"
|
||||
- "output.changed"
|
||||
|
||||
- name: Install the locale a second time
|
||||
locale_gen:
|
||||
name: pt_BR
|
||||
state: present
|
||||
register: output
|
||||
|
||||
- name: Is the locale present?
|
||||
shell: locale -a | grep pt_BR
|
||||
register: post_check_output
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is present and we reported no change
|
||||
assert:
|
||||
that:
|
||||
- "post_check_output.rc == 0"
|
||||
- "not output.changed"
|
||||
|
||||
- name: Remove the locale
|
||||
locale_gen:
|
||||
name: pt_BR
|
||||
state: absent
|
||||
register: output
|
||||
|
||||
- name: Is the locale present?
|
||||
shell: locale -a | grep pt_BR
|
||||
register: post_check_output
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is absent and we reported a change
|
||||
assert:
|
||||
that:
|
||||
- "post_check_output.rc == 1"
|
||||
- "output.changed"
|
||||
|
||||
- name: Remove the locale a second time
|
||||
locale_gen:
|
||||
name: pt_BR
|
||||
state: absent
|
||||
register: output
|
||||
|
||||
- name: Is the locale present?
|
||||
shell: locale -a | grep pt_BR
|
||||
register: post_check_output
|
||||
ignore_errors: true
|
||||
|
||||
- name: Make sure the locale is absent and we reported no change
|
||||
assert:
|
||||
that:
|
||||
- "post_check_output.rc == 1"
|
||||
- "not output.changed"
|
||||
|
||||
# Cleanup
|
||||
- name: Reinstall the locale we tested against if it was initially installed
|
||||
locale_gen:
|
||||
name: pt_BR
|
||||
state: present
|
||||
when: initial_state.rc == 0
|
||||
@@ -8,5 +8,11 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- include_tasks: 'locale_gen.yml'
|
||||
when: ansible_distribution in ('Ubuntu', 'Debian')
|
||||
- name: Bail out if not supported
|
||||
ansible.builtin.meta: end_play
|
||||
when: ansible_distribution not in ('Ubuntu', 'Debian')
|
||||
|
||||
- include_tasks: basic.yml
|
||||
loop: "{{ locale_list_basic }}"
|
||||
loop_control:
|
||||
loop_var: locale_basic
|
||||
|
||||
17
tests/integration/targets/locale_gen/vars/main.yml
Normal file
17
tests/integration/targets/locale_gen/vars/main.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# locale_basic: pt_BR
|
||||
|
||||
locale_list_basic:
|
||||
- localegen: pt_BR
|
||||
locales: [pt_BR]
|
||||
skip_removal: false
|
||||
- localegen: C.UTF-8
|
||||
locales: [C.utf8, C.UTF-8]
|
||||
skip_removal: true
|
||||
- localegen: eo
|
||||
locales: [eo]
|
||||
skip_removal: false
|
||||
@@ -12,10 +12,10 @@
|
||||
# sample: node-v8.2.0-linux-x64.tar.xz
|
||||
node_path: '{{ remote_dir }}/{{ nodejs_path }}/bin'
|
||||
package: 'iconv-lite'
|
||||
environment:
|
||||
PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
|
||||
block:
|
||||
- shell: npm --version
|
||||
environment:
|
||||
PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
|
||||
register: npm_version
|
||||
|
||||
- debug:
|
||||
@@ -24,11 +24,8 @@
|
||||
- name: 'Install simple package without dependency'
|
||||
npm:
|
||||
path: '{{ remote_dir }}'
|
||||
executable: '{{ node_path }}/npm'
|
||||
state: present
|
||||
name: '{{ package }}'
|
||||
environment:
|
||||
PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
|
||||
register: npm_install
|
||||
|
||||
- assert:
|
||||
@@ -39,11 +36,8 @@
|
||||
- name: 'Reinstall simple package without dependency'
|
||||
npm:
|
||||
path: '{{ remote_dir }}'
|
||||
executable: '{{ node_path }}/npm'
|
||||
state: present
|
||||
name: '{{ package }}'
|
||||
environment:
|
||||
PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
|
||||
register: npm_reinstall
|
||||
|
||||
- name: Check there is no change
|
||||
@@ -60,11 +54,8 @@
|
||||
- name: 'reinstall simple package'
|
||||
npm:
|
||||
path: '{{ remote_dir }}'
|
||||
executable: '{{ node_path }}/npm'
|
||||
state: present
|
||||
name: '{{ package }}'
|
||||
environment:
|
||||
PATH: '{{ node_path }}:{{ ansible_env.PATH }}'
|
||||
register: npm_fix_install
|
||||
|
||||
- name: Check result is changed and successful
|
||||
|
||||
23
tests/integration/targets/pacman/handlers/main.yml
Normal file
23
tests/integration/targets/pacman/handlers/main.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Remove user yaybuilder
|
||||
ansible.builtin.user:
|
||||
name: yaybuilder
|
||||
state: absent
|
||||
|
||||
- name: Remove yay
|
||||
ansible.builtin.package:
|
||||
name: yay
|
||||
state: absent
|
||||
|
||||
- name: Remove packages for yay-become
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- base-devel
|
||||
- yay
|
||||
- git
|
||||
- nmap
|
||||
state: absent
|
||||
@@ -17,3 +17,4 @@
|
||||
- include_tasks: 'update_cache.yml'
|
||||
- include_tasks: 'locally_installed_package.yml'
|
||||
- include_tasks: 'reason.yml'
|
||||
- include_tasks: 'yay-become.yml'
|
||||
|
||||
66
tests/integration/targets/pacman/tasks/yay-become.yml
Normal file
66
tests/integration/targets/pacman/tasks/yay-become.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This is more convoluted that one might expect, because:
|
||||
# - yay is not available or installation in ArchLinux (as it is in Manjaro - issue 6184 reports using it)
|
||||
# - to install yay in ArchLinux requires building the package
|
||||
# - makepkg cannot be run as root, but the user running it must have sudo to install the resulting package
|
||||
|
||||
- name: create user
|
||||
ansible.builtin.user:
|
||||
name: yaybuilder
|
||||
state: present
|
||||
notify: Remove user yaybuilder
|
||||
|
||||
- name: grant sudo powers to builder
|
||||
community.general.sudoers:
|
||||
name: yaybuilder
|
||||
user: yaybuilder
|
||||
commands: ALL
|
||||
nopassword: true
|
||||
|
||||
- name: Install base packages
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- base-devel
|
||||
- git
|
||||
- go
|
||||
state: present
|
||||
notify: Remove packages for yay-become
|
||||
|
||||
- name: Hack permssions for the remote_tmp_dir
|
||||
ansible.builtin.file:
|
||||
path: "{{ remote_tmp_dir }}"
|
||||
mode: '0777'
|
||||
|
||||
- name: Create temp directory for builder
|
||||
ansible.builtin.file:
|
||||
path: "{{ remote_tmp_dir }}/builder"
|
||||
owner: yaybuilder
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: clone yay git repo
|
||||
become: true
|
||||
become_user: yaybuilder
|
||||
ansible.builtin.git:
|
||||
repo: https://aur.archlinux.org/yay.git
|
||||
dest: "{{ remote_tmp_dir }}/builder/yay"
|
||||
depth: 1
|
||||
|
||||
- name: make package
|
||||
become: true
|
||||
become_user: yaybuilder
|
||||
ansible.builtin.command:
|
||||
chdir: "{{ remote_tmp_dir }}/builder/yay"
|
||||
cmd: makepkg -si --noconfirm
|
||||
notify: Remove yay
|
||||
|
||||
- name: Install nmap
|
||||
community.general.pacman:
|
||||
name: nmap
|
||||
state: present
|
||||
executable: yay
|
||||
extra_args: --builddir /var/cache/yay
|
||||
@@ -6,3 +6,4 @@ azp/posix/2
|
||||
destructive
|
||||
skip/python2
|
||||
skip/python3.5
|
||||
disabled # TODO
|
||||
|
||||
@@ -6,3 +6,4 @@ azp/posix/3
|
||||
destructive
|
||||
skip/python2
|
||||
skip/python3.5
|
||||
disabled # TODO
|
||||
|
||||
6
tests/integration/targets/proxmox_template/aliases
Normal file
6
tests/integration/targets/proxmox_template/aliases
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
unsupported
|
||||
proxmox_template
|
||||
136
tests/integration/targets/proxmox_template/tasks/main.yml
Normal file
136
tests/integration/targets/proxmox_template/tasks/main.yml
Normal file
@@ -0,0 +1,136 @@
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
- name: Proxmox VE virtual machines templates management
|
||||
tags: ['template']
|
||||
vars:
|
||||
filename: /tmp/dummy.iso
|
||||
block:
|
||||
- name: Create dummy ISO file
|
||||
ansible.builtin.command:
|
||||
cmd: 'truncate -s 300M {{ filename }}'
|
||||
|
||||
- name: Delete requests_toolbelt module if it is installed
|
||||
ansible.builtin.pip:
|
||||
name: requests_toolbelt
|
||||
state: absent
|
||||
|
||||
- name: Install latest proxmoxer
|
||||
ansible.builtin.pip:
|
||||
name: proxmoxer
|
||||
state: latest
|
||||
|
||||
- name: Upload ISO as template to Proxmox VE cluster should fail
|
||||
proxmox_template:
|
||||
api_host: '{{ api_host }}'
|
||||
api_user: '{{ user }}@{{ domain }}'
|
||||
api_password: '{{ api_password | default(omit) }}'
|
||||
api_token_id: '{{ api_token_id | default(omit) }}'
|
||||
api_token_secret: '{{ api_token_secret | default(omit) }}'
|
||||
validate_certs: '{{ validate_certs }}'
|
||||
node: '{{ node }}'
|
||||
src: '{{ filename }}'
|
||||
content_type: iso
|
||||
force: true
|
||||
register: result
|
||||
ignore_errors: true
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result.msg is match('\'requests_toolbelt\' module is required to upload files larger than 256MB')
|
||||
|
||||
- name: Install old (1.1.2) version of proxmoxer
|
||||
ansible.builtin.pip:
|
||||
name: proxmoxer==1.1.1
|
||||
state: present
|
||||
|
||||
- name: Upload ISO as template to Proxmox VE cluster should be successful
|
||||
proxmox_template:
|
||||
api_host: '{{ api_host }}'
|
||||
api_user: '{{ user }}@{{ domain }}'
|
||||
api_password: '{{ api_password | default(omit) }}'
|
||||
api_token_id: '{{ api_token_id | default(omit) }}'
|
||||
api_token_secret: '{{ api_token_secret | default(omit) }}'
|
||||
validate_certs: '{{ validate_certs }}'
|
||||
node: '{{ node }}'
|
||||
src: '{{ filename }}'
|
||||
content_type: iso
|
||||
force: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result is success
|
||||
- result.msg is match('template with volid=local:iso/dummy.iso uploaded')
|
||||
|
||||
- name: Install latest proxmoxer
|
||||
ansible.builtin.pip:
|
||||
name: proxmoxer
|
||||
state: latest
|
||||
|
||||
- name: Make smaller dummy file
|
||||
ansible.builtin.command:
|
||||
cmd: 'truncate -s 128M {{ filename }}'
|
||||
|
||||
- name: Upload ISO as template to Proxmox VE cluster should be successful
|
||||
proxmox_template:
|
||||
api_host: '{{ api_host }}'
|
||||
api_user: '{{ user }}@{{ domain }}'
|
||||
api_password: '{{ api_password | default(omit) }}'
|
||||
api_token_id: '{{ api_token_id | default(omit) }}'
|
||||
api_token_secret: '{{ api_token_secret | default(omit) }}'
|
||||
validate_certs: '{{ validate_certs }}'
|
||||
node: '{{ node }}'
|
||||
src: '{{ filename }}'
|
||||
content_type: iso
|
||||
force: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result is success
|
||||
- result.msg is match('template with volid=local:iso/dummy.iso uploaded')
|
||||
|
||||
- name: Install requests_toolbelt
|
||||
ansible.builtin.pip:
|
||||
name: requests_toolbelt
|
||||
state: present
|
||||
|
||||
- name: Make big dummy file
|
||||
ansible.builtin.command:
|
||||
cmd: 'truncate -s 300M {{ filename }}'
|
||||
|
||||
- name: Upload ISO as template to Proxmox VE cluster should be successful
|
||||
proxmox_template:
|
||||
api_host: '{{ api_host }}'
|
||||
api_user: '{{ user }}@{{ domain }}'
|
||||
api_password: '{{ api_password | default(omit) }}'
|
||||
api_token_id: '{{ api_token_id | default(omit) }}'
|
||||
api_token_secret: '{{ api_token_secret | default(omit) }}'
|
||||
validate_certs: '{{ validate_certs }}'
|
||||
node: '{{ node }}'
|
||||
src: '{{ filename }}'
|
||||
content_type: iso
|
||||
force: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result is success
|
||||
- result.msg is match('template with volid=local:iso/dummy.iso uploaded')
|
||||
|
||||
always:
|
||||
- name: Delete ISO file from host
|
||||
ansible.builtin.file:
|
||||
path: '{{ filename }}'
|
||||
state: absent
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user