Compare commits

...

90 Commits
7.1.0 ... 7.2.1

Author SHA1 Message Date
Felix Fontein
301711e0d3 Release 7.2.1. 2023-07-31 15:49:21 +02:00
Felix Fontein
7cf834fb3c [stable-7] Revert new features to be able to do 7.2.1 release (#7042)
* Revert "[PR #7020/b46d5d81 backport][stable-7] redfish_utils: Add support for "nextLink" property tag pagination (#7026)"

This reverts commit 1dad95370e.

* Revert "[PR #6914/17b4219b backport][stable-7] proxmox_kvm: enable 'force' restart of vm (as documented) (#6997)"

This reverts commit 7d68af57af.

* Revert "[PR #6976/d7c1a814 backport][stable-7] [proxmox_vm_info] Re-use cluster resources API to use module without requiring node param (#6993)"

This reverts commit fb3768aada.
2023-07-31 15:45:08 +02:00
patchback[bot]
eda3d160fa [PR #6983/a942545d backport][stable-7] Rundeck - fix TypeError on 404 api response (#7041)
Rundeck - fix TypeError on 404 api response (#6983)

* fix TypeError on 404 api response

* add changelog fragment

* Update changelogs/fragments/6983-rundeck-fix-typerrror-on-404-api-response.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Vincent CHARLES <vincent.charles@swatchgroup.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit a942545dd2)

Co-authored-by: Vincent CHARLES <124702855+MehuiSeklayr@users.noreply.github.com>
2023-07-31 15:44:55 +02:00
Felix Fontein
b71d8813b2 Prepare 7.2.1 release. 2023-07-31 08:47:47 +02:00
patchback[bot]
a4c0df1ded [PR #6991/00bfc3e1 backport][stable-7] Add example for ECS Fargate/EFS Jenkins authentication (#7016)
Add example for ECS Fargate/EFS Jenkins authentication (#6991)

* Add example for ECS Fargate/EFS Jenkins authentication

Since ECS Fargate is serverless, one cannot access its jenkins_home other than from a machine (EC2 for example) that actually mounts and owns its EFS storage.

That way we provide user/group of a defatul local user which has the same uid/gid 1000 as the default jenkins user inside the container and also can authenticate at Jenkins URL.

I feel this is not as straightforward from the docs and someone might benefit from such an example being present

* Added an empty line

* Float value now in single quotes

* Use UID/GID instead user/group name

(cherry picked from commit 00bfc3e131)

Co-authored-by: TeekWan <74403302+teekwan@users.noreply.github.com>
2023-07-31 08:37:15 +02:00
patchback[bot]
a2b1756bea [PR #7027/87053e52 backport][stable-7] Add tarka to ignore list. (#7032)
Add tarka to ignore list. (#7027)

* Add tarka to ignore list.

* Remove tarka from maintainers.

(cherry picked from commit 87053e5266)

Co-authored-by: Steve Smith <tarkasteve@gmail.com>
2023-07-29 21:51:25 +02:00
patchback[bot]
08d89a2f85 [PR #7028/3a7044e2 backport][stable-7] ejabberd_user: better error when command not installed (#7030)
ejabberd_user: better error when command not installed (#7028)

* ejabberd_user: better error when command not installed

* add changelog frag

(cherry picked from commit 3a7044e2b8)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-29 21:40:06 +02:00
patchback[bot]
1dad95370e [PR #7020/b46d5d81 backport][stable-7] redfish_utils: Add support for "nextLink" property tag pagination (#7026)
redfish_utils: Add support for "nextLink" property tag pagination (#7020)

* Add support for Redfish "nextLink" property tag pagination for
FirmwareInventory

* Add changelog fragment

* Fix indention

* Updated fragment per suggestion

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit b46d5d8197)

Co-authored-by: Scott Seekamp <sseekamp@coreweave.com>
2023-07-28 21:30:40 +02:00
patchback[bot]
200b858b36 [PR #7022/5d7899b3 backport][stable-7] setup_docker: handlers to stop service and remove requests (#7024)
setup_docker: handlers to stop service and remove requests (#7022)

(cherry picked from commit 5d7899b341)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-28 20:19:27 +02:00
patchback[bot]
342a5a14f9 [PR #6995/cc5e1b6f backport][stable-7] Skip java_cert and java_keystore tests on RHEL (#7000)
Skip java_cert and java_keystore tests on RHEL (#6995)

Skip java_cert and java_keystore tests on RHEL.

(cherry picked from commit cc5e1b6fe7)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-24 08:27:25 +02:00
patchback[bot]
4609907367 [PR #6994/e8150408 backport][stable-7] crypt is still deprecated in Python 3.12 (#7002)
crypt is still deprecated in Python 3.12 (#6994)

crypt is still deprecated in Python 3.12.

(cherry picked from commit e815040877)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-24 08:27:08 +02:00
patchback[bot]
7d68af57af [PR #6914/17b4219b backport][stable-7] proxmox_kvm: enable 'force' restart of vm (as documented) (#6997)
proxmox_kvm: enable 'force' restart of vm (as documented) (#6914)

* enable 'force' restart of vm

* added changelog fragment

* Update changelogs/fragments/6914-proxmox_kvm-enable-force-restart.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 17b4219b8b)

Co-authored-by: Jeff Turner <jeff@torusoft.com>
2023-07-23 22:53:02 +02:00
patchback[bot]
fb3768aada [PR #6976/d7c1a814 backport][stable-7] [proxmox_vm_info] Re-use cluster resources API to use module without requiring node param (#6993)
[proxmox_vm_info] Re-use cluster resources API to use module without requiring node param (#6976)

* [proxmox_vm_info] Re-use cluster resources API to use module without requiring node param

* More consife if

* Fix use case when requesting all vms from specific node

* Add changelog fragment

(cherry picked from commit d7c1a814ea)

Co-authored-by: Sergei Antipov <greendayonfire@gmail.com>
2023-07-23 21:55:32 +02:00
patchback[bot]
93f990a1b9 [PR #6981/f9448574 backport][stable-7] [proxmox_kvm] Don't create VM if name is used without vmid (#6992)
[proxmox_kvm] Don't create VM if name is used without vmid (#6981)

* [proxmox_kvm] Don't create VM if name is used without vmid

* Add changelog and unit tests

(cherry picked from commit f9448574bd)

Co-authored-by: Sergei Antipov <greendayonfire@gmail.com>
2023-07-23 21:48:36 +02:00
patchback[bot]
f003833c1a [PR #6980/796ad356 backport][stable-7] [proxmox] Use proxmoxer_version instead of server API version (#6982)
[proxmox] Use proxmoxer_version instead of server API version (#6980)

* Use proxmoxer_version instead of server API version

* Add changelog fragment

(cherry picked from commit 796ad3565e)

Co-authored-by: Sergei Antipov <greendayonfire@gmail.com>
2023-07-21 17:03:41 +02:00
patchback[bot]
8eb94dc36b [PR #6985/d9951cbc backport][stable-7] CI: move FreeBSD 12.4 from ansible-core devel to stable-2.15 (#6987)
CI: move FreeBSD 12.4 from ansible-core devel to stable-2.15 (#6985)

Move FreeBSD 12.4 from ansible-core devel to stable-2.15.

(cherry picked from commit d9951cbc32)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-21 16:56:03 +02:00
patchback[bot]
7bf155284f [PR #6968/f6714eda backport][stable-7] cmd_runner module utils: fix bug when argument spec has implicit type (#6978)
cmd_runner module utils: fix bug when argument spec has implicit type (#6968)

* cmd_runner module utils: fix bug when argument spec has implicit type

* add changelog frag

(cherry picked from commit f6714edabb)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-20 08:34:39 +02:00
Felix Fontein
0f5f00f41a Next expected release is 7.3.0. 2023-07-17 12:30:31 +02:00
Felix Fontein
b02ea33f9b Release 7.2.0. 2023-07-17 11:53:31 +02:00
patchback[bot]
437d1bbf7a [PR #6964/3ce4fe8d backport][stable-7] keycloak_authz_permission*: adjust to semantic markup (#6967)
keycloak_authz_permission*: adjust to semantic markup (#6964)

Adjust to semantic markup.

(cherry picked from commit 3ce4fe8dd8)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-16 13:14:11 +00:00
patchback[bot]
a1582aa8cb [PR #6906/b818afd4 backport][stable-7] Fix key error for reverse zone (#6905) (#6951)
Fix key error for reverse zone (#6905) (#6906)

* Fix key error for reverse zone (#6905)

* Add changelog fragment for #6905

* Fix changelog (6905)

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit b818afd464)

Co-authored-by: irozet12 <119814380+irozet12@users.noreply.github.com>
2023-07-16 14:39:34 +02:00
patchback[bot]
4816157c05 [PR #6923/7020b27b backport][stable-7] cobbler inventory plugin: Convert unicode to str (#6953)
cobbler inventory plugin: Convert unicode to str (#6923)

* plugins/inventory/cobbler: Convert unicode to str

* plugins/inventory/cobbler: Use text_type instead

(cherry picked from commit 7020b27b0a)

Co-authored-by: Algirdas <76789112+Algirdas-Z@users.noreply.github.com>
2023-07-16 14:39:20 +02:00
patchback[bot]
67356d287d [PR #6955/8a344ea0 backport][stable-7] 📝 Add missing commas to documentation (#6961)
📝 Add missing commas to documentation (#6955)

(cherry picked from commit 8a344ea036)

Co-authored-by: Dov Benyomin Sohacheski <b@kloud.email>
2023-07-16 14:39:08 +02:00
patchback[bot]
2b76b1f43a [PR #6954/9abdc5a9 backport][stable-7] CI: Stop restricting idna (#6958)
CI: Stop restricting idna (#6954)

Stop restricting idna.

(cherry picked from commit 9abdc5a995)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-16 14:38:56 +02:00
patchback[bot]
0f2d5136b8 [PR #6956/0be71628 backport][stable-7] Removal of myself as maintainer (#6966)
Removal of myself as maintainer (#6956)

* Removal of myself as maintainer

* Added the ignore propery to satisfy the tests

(cherry picked from commit 0be7162848)

Co-authored-by: Werner Dijkerman <iam@werner-dijkerman.nl>
2023-07-16 14:38:38 +02:00
patchback[bot]
58a4610b61 [PR #6321/528216fd backport][stable-7] Add keycloak_authz_permission module (#6963)
Add keycloak_authz_permission module (#6321)

* Add keycloak_authz_permission module

* keycloak_authz_permission: add version_added metadata

Co-authored-by: Felix Fontein <felix@fontein.de>

* keycloak_authz_permission: assume changed=True on update operations

* keycloak_authz_permission: implement check_mode

* keycloak_authz_permission: move state queries into a dedicated  _info module

* keycloak_authz_permission: bump version_added to 7.2.0

* keycloak_authz_permission: final fixes

Signed-off-by: Samuli Seppänen <samuli.seppanen@puppeteers.net>

* Update plugins/modules/keycloak_authz_permission_info.py

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Signed-off-by: Samuli Seppänen <samuli.seppanen@puppeteers.net>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 528216fd7e)

Co-authored-by: Samuli Seppänen <samuli.seppanen@gmail.com>
2023-07-16 14:24:56 +02:00
patchback[bot]
d1a412dafc [PR #6883/c70edfa8 backport][stable-7] Fix for get_volume_inventory (#6948)
Fix for get_volume_inventory (#6883)

* Fix for get_volume_inventory

* Adding changelog fragment

* Update changelogs/fragments/6883-redfish-utils-changing-variable-names-in-get-volume-inventory.yml

---------

Co-authored-by: Kushal <t-s.kushal@hpe.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit c70edfa84b)

Co-authored-by: TSKushal <44438079+TSKushal@users.noreply.github.com>
2023-07-15 17:56:27 +02:00
patchback[bot]
c82362194b [PR #6928/70503411 backport][stable-7] remove noqa exceptions (#6943)
remove noqa exceptions (#6928)

* remove noqa exceptions

* add changelog frag

* review from PR

(cherry picked from commit 70503411ee)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-15 16:28:07 +02:00
patchback[bot]
bb80ff6aee [PR #6908/ea6fb9da backport][stable-7] snap: add param "dangerous" (#6944)
snap: add param "dangerous" (#6908)

* snap: add param "dangerous"

* adjusted run_command out for simple test case

* add changelog frag

(cherry picked from commit ea6fb9da8f)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-15 16:28:00 +02:00
patchback[bot]
15b950f1cf [PR #6903/3a6955cb backport][stable-7] locale_gen: refactor (#6945)
locale_gen: refactor (#6903)

* locale_gen: refactor

* fix sanity

* add changelog frag

(cherry picked from commit 3a6955cbd7)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-15 16:27:50 +02:00
patchback[bot]
7577d5218a [PR #6925/1c4e2d51 backport][stable-7] cobbler: Properly determine cobbler_ip_address (#6940)
cobbler: Properly determine cobbler_ip_address (#6925)

cobbler: Properly determine cobbler_ipv4/6_address
(cherry picked from commit 1c4e2d51b2)

Co-authored-by: Orion Poplawski <orion@nwra.com>
2023-07-15 16:27:36 +02:00
patchback[bot]
f317fd924a [PR #6935/24feb1dd backport][stable-7] machinectl become plugin: mark as require_tty (#6942)
machinectl become plugin: mark as require_tty (#6935)

Mark machinectl become plugin as require_tty.

(cherry picked from commit 24feb1dd10)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-15 16:27:26 +02:00
patchback[bot]
6070dc80d4 [PR #6837/e0324cdc backport][stable-7] rhsm_repository: refactor parsing of "subscription-manager repos" output (#6946)
rhsm_repository: refactor parsing of "subscription-manager repos" output (#6837)

Simplify a bit (and possibly speed it up a little) the parsing of the
output of `subscription-manager repos --list`:
- simplify skipping the lines that are not interesting: check the first
  character only, as it is enough to determine whether it contains
  repository data or not
- check the start of each line manually, rather than with regexp: a
  simple slice + lstrip() gives the same result

(cherry picked from commit e0324cdc90)

Co-authored-by: Pino Toscano <ptoscano@redhat.com>
2023-07-15 16:27:12 +02:00
Felix Fontein
b3fad4fa87 Prepare 7.2.0 release. 2023-07-15 13:06:52 +02:00
patchback[bot]
76626eb7e8 [PR #6937/e427857d backport][stable-7] CI: remove no longer needed ignore (#6939)
CI: remove no longer needed ignore (#6937)

Remove no longer needed ignore.

(cherry picked from commit e427857db7)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-15 12:51:31 +02:00
patchback[bot]
37ba1d0e5e [PR #6930/680f3f89 backport][stable-7] flowdock module: deprecation (#6934)
flowdock module: deprecation (#6930)

* flowdock module: deprecation

* add changelog frag

(cherry picked from commit 680f3f8970)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-14 20:59:48 +02:00
patchback[bot]
57d1e74f3d [PR #6881/3e56da53 backport][stable-7] tss lookup plugin - Fetch secret by path (#6933)
tss lookup plugin - Fetch secret by path (#6881)

* Added support to fetch secret by path

* Fixed linting issue

* Fixed PR comments

* Chnaged description of secret path

(cherry picked from commit 3e56da5371)

Co-authored-by: delinea-sagar <131447653+delinea-sagar@users.noreply.github.com>
2023-07-14 20:03:43 +02:00
patchback[bot]
f6b5b793c8 [PR #6927/4b382ed1 backport][stable-7] remove pylint exceptions (#6929)
remove pylint exceptions (#6927)

* remove pylint exceptions

* add changelog frag

(cherry picked from commit 4b382ed1df)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-14 07:41:55 +02:00
patchback[bot]
6584348d05 [PR #6902/cc8e2d67 backport][stable-7] Added support in nmcli for ipv4.dns-options (#6926)
Added support in nmcli for ipv4.dns-options (#6902)

* Added support for ipv4.dns_options in nmcli module

* added support for dns6-options

* Added version added

Co-authored-by: Felix Fontein <felix@fontein.de>

* added version_added: 4.6.0 for dns6 options

* added changelog fragment

* Rename 4308-added-support-in-nmcli-for-ipvx-dns-options to 4308-added-support-in-nmcli-for-ipvx-dns-options.yml

* Update changelogs/fragments/4308-added-support-in-nmcli-for-ipvx-dns-options.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Fix and add tests

* Update PR number and version_added

---------

Co-authored-by: Matteo Caruso <m.caruso425@gmail.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit cc8e2d676a)

Co-authored-by: Chih-Hsuan Yen <1937689+yan12125@users.noreply.github.com>
2023-07-13 22:42:05 +02:00
patchback[bot]
a610e27853 [PR #6887/065ce3a1 backport][stable-7] stackdriver: deprecate module (#6924)
stackdriver: deprecate module (#6887)

* stackdriver: deprecate module

* add changelog frag

* Update changelogs/fragments/6887-deprecate-stackdrive.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* fix removal version

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 065ce3a134)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-13 20:30:57 +02:00
patchback[bot]
01220475dc [PR #6913/d048ea95 backport][stable-7] Remove tleguern from team_virt (#6921)
Remove tleguern from team_virt (#6913)

* Remove tleguern from team_virt

Stepping down as I do not work on proxmox anymore.

* Add ignore: tleguern

(cherry picked from commit d048ea9586)

Co-authored-by: Tristan Le Guern <tleguern@bouledef.eu>
2023-07-13 06:14:46 +02:00
patchback[bot]
0a1b53a10e [PR #6861/2bf85cca backport][stable-7] feature(yum_versionlock): add support to pin specific package versions (#6920)
feature(yum_versionlock): add support to pin specific package versions (#6861)

feature(yum_versionlock): add support to pin specific package versions instead of only the package itself

(cherry picked from commit 2bf85cca51)

Co-authored-by: Florian <4150400+gyptazy@users.noreply.github.com>
2023-07-13 06:14:34 +02:00
patchback[bot]
db8f38ea3a [PR #6871/9adc82d5 backport][stable-7] Redfish: Added support for displaying and setting account types (#6919)
Redfish: Added support for displaying and setting account types (#6871)

* Redfish: Added support for displaying and setting account types

Signed-off-by: Mike Raineri <michael.raineri@dell.com>

* Update 6823-redfish-add-account-type-management.yml

* CI fixes

Signed-off-by: Mike Raineri <michael.raineri@dell.com>

---------

Signed-off-by: Mike Raineri <michael.raineri@dell.com>
(cherry picked from commit 9adc82d5d1)

Co-authored-by: Mike Raineri <michael.raineri@dell.com>
2023-07-12 22:25:47 +02:00
patchback[bot]
7c0e4bda35 [PR #6909/43dee97e backport][stable-7] webfaction modules: deprecation (#6916)
webfaction modules: deprecation (#6909)

* webfaction modules: deprecation

* add changelog frag

(cherry picked from commit 43dee97e43)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-12 22:24:09 +02:00
patchback[bot]
50425a49ec [PR #6882/0ae8f9d6 backport][stable-7] make: allow multiple targets to be specified (#6918)
make: allow multiple targets to be specified (#6882)

* make: allow multiple targets to be specified

* add changelog frag

* Update changelogs/fragments/6882-make-multiple-targets.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* change to extra param

* adjust changelog frag

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 0ae8f9d631)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-12 22:23:58 +02:00
patchback[bot]
ce30e0732b [PR #6715/33af903b backport][stable-7] Add consul_policy module from dormant PR (#6917)
Add consul_policy module from dormant PR (#6715)

Squash commits to make rebasing easier

Co-authored-by: Valerio Poggi <vrpoggigmail.com>
(cherry picked from commit 33af903b24)

Co-authored-by: Valerio Poggi <106782233+valeriopoggi@users.noreply.github.com>
2023-07-12 22:23:31 +02:00
patchback[bot]
c2cbac062e [PR #6904/d074af8d backport][stable-7] minor quoting fix in doc (#6915)
minor quoting fix in doc (#6904)

(cherry picked from commit d074af8d09)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-12 19:39:27 +02:00
patchback[bot]
ed4bc4c1d2 [PR #6811/fc74f9a4 backport][stable-7] [datadog_downtime] - added 'type' to recurrence object when rrule param is present (#6891)
[datadog_downtime] - added 'type' to recurrence object when rrule param is present (#6811)

* added 'type' to recurrence object when rrule param is present

* formatting cleanup

(cherry picked from commit fc74f9a4f2)

Co-authored-by: Simon Keil <freezer_xx@gmx.de>
2023-07-08 18:10:16 +02:00
patchback[bot]
cda63f7221 [PR #6785/704a3019 backport][stable-7] Handle pkg_info(1) error message "Can't find" (#6893)
Handle pkg_info(1) error message "Can't find" (#6785)

* Handle pkg_info(1) error message "Can't find"

* Update plugins/modules/openbsd_pkg.py

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 704a3019b7)

Co-authored-by: Rafael Sadowski <rafael@sizeofvoid.org>
2023-07-08 18:10:08 +02:00
patchback[bot]
ebaf490653 [PR #6734/77480026 backport][stable-7] Fix Keycloak authentication management indexing (#6894)
Fix Keycloak authentication management indexing (#6734)

* fix: keep original index

* refac: provider id choice

* refac: default

* fix: missing docs

* fix: ident

* fix: yaml formatting

* chore: bugfix changelog fragment

* fix: remove minor changes form current PR

* fix: missing yml extension

* Update changelogs/fragments/6734-keycloak-auth-management-indexing.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Skrekulko <Skrekulko@users.noreply.github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 7748002636)

Co-authored-by: Skrekulko <111891715+Skrekulko@users.noreply.github.com>
2023-07-08 18:10:01 +02:00
patchback[bot]
9027c367d4 [PR #6713/f6ee2177 backport][stable-7] pacman: support yay as root (#6896)
pacman: support yay as root (#6713)

* pacman: support yay as root

* make pylint happy

* minor adjustments

* rollback some test actions

* removal of user and pkgs in handlers

* add comment to note

* add changelog frag

* fix doc

* Update tests/integration/targets/pacman/tasks/yay-become.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update tests/integration/targets/pacman/tasks/yay-become.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* simplify pkg install in int. tests

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit f6ee2177a2)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-08 18:09:52 +02:00
patchback[bot]
e69ea28662 [PR #6389/5365647e backport][stable-7] New lookup plug-in: Bitwarden Secrets Manager (#6889)
New lookup plug-in: Bitwarden Secrets Manager (#6389)

* add Bitwarden Secrets Manager lookup

* fix pep8 and yamllint complaints

* fix version_added, add maintainer and copyright notice

* document BWS_ACCESS_TOKEN env var and declare as required

* avoid returning nested list

* update 'value of a secret' example after f6c4492c

* update copyright notice in bitwarden_secrets_manager plugin

thx felixfontein

Co-authored-by: Felix Fontein <felix@fontein.de>

* rename classes to distinguish from existing bw plugin

* use AnsibleLookupError, formatting

* bump version_added to 7.0.0

Co-authored-by: Felix Fontein <felix@fontein.de>

* ci fix: python style guide calls for excessive blank lines

https://peps.python.org/pep-0008/#blank-lines

* first attempt at unit tests for bws lookup

* ci fix: remove trailing newline

* attempt to fix tests object not callable error

* address formatting, tests and pyright suggestions

* reduce scope of mocked code for more real test coverage

only the actual bws CLI call is mocked now, this should enable the
exception thrown test to succeed if I didn't add new problems

* fix undefined variable 'expected_rc'

* fix mocked _run method to return correct data types

* keep list of one element for test case comparison

* bump version_added to 7.2.0

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: jantari <jantari@github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 5365647ee7)

Co-authored-by: jantari <jantari@outlook.de>
2023-07-08 18:09:39 +02:00
patchback[bot]
eccc41eadc [PR #6835/dc0d0045 backport][stable-7] snap: add track 'latest' if no track is specified (#6888)
snap: add track 'latest' if no track is specified (#6835)

* snap: add track 'latest' if no track is specified

See https://snapcraft.io/docs/channels for more details.

* snap: assume track latest if channel does not specify it

(cherry picked from commit dc0d00452f)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-08 18:09:28 +02:00
patchback[bot]
b5d56463a6 [PR #6852/7adb9985 backport][stable-7] [proxmox_vm_info] New module to retrieve virtual machines information from Proxmox VE API (#6897)
[proxmox_vm_info] New module to retrieve virtual machines information from Proxmox VE API (#6852)

* [proxmox_vm_info] New module to retrieve virtual machines information from Proxmox VE API

* Address review comments

* Fix seealso fragment

* Update plugins/modules/proxmox_vm_info.py

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 7adb99855a)

Co-authored-by: Sergei Antipov <greendayonfire@gmail.com>
2023-07-08 18:09:16 +02:00
patchback[bot]
3c5094d971 [PR #6824/59479b4a backport][stable-7] chroot: added an example (#6899)
chroot: added an example (#6824)

* chroot: added an example

Added a simple example of chroot connection plugin

Fixes: #6365

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 59479b4abd)

Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
2023-07-08 18:09:06 +02:00
patchback[bot]
15cbc9665e [PR #6838/49349fce backport][stable-7] proxmox: default value when checking for dict key (#6886)
proxmox: default value when checking for dict key (#6838)

* proxmox: default value when checking for dict key

* add changelog frag

(cherry picked from commit 49349fce5e)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-08 10:10:05 +02:00
Pierre-yves Fontaniere
4259792751 Add redfish processor architecture to Redfish CPU inventory (#6864)
* Add Processor Architecture to CPU Inventory details

* Add changelog fragment

* Changelog fragment writing guide formatting

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Pierre-yves FONTANIERE <pyf@cc.in2p3.fr>
Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-07 22:24:45 +02:00
patchback[bot]
fe4099c163 [PR #6755/242258eb backport][stable-7] Refactor consul_session to support authentication with tokens (#6879)
Refactor consul_session to support authentication with tokens (#6755)

* Split into separate PR

* Refactor test, add author to inactive maintainers

* Add changelog fragment and correct requirements section on module documentation

* Add changelog fragment and correct requirements section on module documentation

* Update changelogs/fragments/6755-refactor-consul-session-to-use-requests-lib-instead-of-consul.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Valerio Poggi <vrpoggigmail.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 242258eb53)

Co-authored-by: Valerio Poggi <106782233+valeriopoggi@users.noreply.github.com>
2023-07-07 08:09:28 +02:00
patchback[bot]
b2417accbf [PR #6874/53c1ed18 backport][stable-7] Temporarily disable pipx tests (#6877)
Temporarily disable pipx tests (#6874)

Temporarily disable pipx tests.

(cherry picked from commit 53c1ed184d)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-07 08:02:36 +02:00
patchback[bot]
9b21b0d31c [PR #6862/91a68187 backport][stable-7] opkg: add executable parameter (#6870)
opkg: add executable parameter (#6862)

* opkg: add executable parameter

* add changelog frag

* Update plugins/modules/opkg.py

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 91a681870e)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-06 19:55:58 +00:00
patchback[bot]
330b0304ef [PR #6769/9d8bec14 backport][stable-7] nmcli: fix empty list to replace / remove values (#6869)
nmcli: fix empty list to replace / remove values (#6769)

(cherry picked from commit 9d8bec14c0)

Co-authored-by: genofire <geno+dev@fireorbit.de>
2023-07-06 21:39:07 +02:00
patchback[bot]
f8fc18412c [PR #6820/601fa0df backport][stable-7] locale_gen: refactor module to use CmdRunner (#6867)
locale_gen: refactor module to use CmdRunner (#6820)

* refactor module to use CmdRunner

* add changelog frag

(cherry picked from commit 601fa0df62)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-06 21:38:59 +02:00
patchback[bot]
abd2a85709 [PR #6827/216962a9 backport][stable-7] proxmox_kvm: Add required timeout arg when force deleting (#6866)
proxmox_kvm: Add required timeout arg when force deleting (#6827)

* Add required timeout arg when force deleting

* Add changelog fragment

* Update changelogs/fragments/6827-proxmox_kvm-force-delete-bug-fix.yaml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 216962a98c)

Co-authored-by: Drew Brown <drew@drewburr.com>
2023-07-06 21:38:51 +02:00
patchback[bot]
c1536a3501 [PR #6854/99a35b16 backport][stable-7] Add example for github_key in a single task (#6860)
Add example for github_key in a single task (#6854)

* Add example for github_key in a single task

* Update plugins/modules/github_key.py

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 99a35b1664)

Co-authored-by: Seige <charliefjennings+git@gmail.com>
2023-07-06 08:02:57 +02:00
patchback[bot]
4fa1f1a6dd [PR #6841/4d704c03 backport][stable-7] htpasswd: deprecate crypt_scheme (#6858)
htpasswd: deprecate crypt_scheme (#6841)

* htpasswd: rename crypt_scheme with hash_scheme

* add changelog frag

* fixed chglog frag

* adjusted code for parameter name change

(cherry picked from commit 4d704c03df)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-06 06:42:34 +02:00
patchback[bot]
42cc5280d9 [PR #6848/11406715 backport][stable-7] npm: replace test with required_if (#6856)
npm: replace test with required_if (#6848)

* npm: replace test with required_if

* add changelog frag

(cherry picked from commit 11406715f5)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-06 06:42:25 +02:00
patchback[bot]
1c8fbed36c [PR #6836/c34a2271 backport][stable-7] proxmox: deprecate old compatibility feature flag (#6851)
proxmox: deprecate old compatibility feature flag (#6836)

* proxmox: deprecate old compatibility feature flag

* add changelog frag

* bump deprecation to 10

(cherry picked from commit c34a22717e)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-04 20:31:39 +02:00
patchback[bot]
f8d0d07fed [PR #6847/41e3f4d5 backport][stable-7] htpasswd: add integration tests (#6850)
htpasswd: add integration tests (#6847)

(cherry picked from commit 41e3f4d5fa)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-04 20:31:30 +02:00
patchback[bot]
3ee01ddb7f [PR #6839/e5dc6978 backport][stable-7] proxmox module utils: better error msg when token fails with old proxmoxer (#6846)
proxmox module utils: better error msg when token fails with old proxmoxer (#6839)

* proxmox module utils: clear msg when token fails with old proxmoxer

* add changelog frag

* better way to determine version

(cherry picked from commit e5dc697887)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-03 22:57:39 +02:00
patchback[bot]
5d5befdf96 [PR #6840/b6883492 backport][stable-7] htpasswd: minor updates in the docs (#6844)
htpasswd: minor updates in the docs (#6840)

* htpasswd: minor updates in the docs

* remove double spaces

(cherry picked from commit b6883492c7)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-03 21:42:06 +02:00
patchback[bot]
98cea930f0 [PR #6832/92ea63e2 backport][stable-7] Revert "proxmox_kvm: remove non-working example (#6498)" (#6834)
Revert "proxmox_kvm: remove non-working example (#6498)" (#6832)

This reverts commit 61a0dc4370.

(cherry picked from commit 92ea63e20b)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-07-03 05:59:28 +02:00
patchback[bot]
9036d8edd0 [PR #6757/2d6e369d backport][stable-7] [proxmox_template] Fix error while uploading big ISO to Proxmox VE cluster (#6831)
[proxmox_template] Fix error while uploading big ISO to Proxmox VE cluster (#6757)

* [proxmox_template] Fix error while uploading big ISO to Proxmox VE cluster

* Fix pep8 test

* Add changelog fragment

* Add notes about requests_toolbelt

* Check versions and file size

* Fix typo in notes

* Add unit test. Move try inside of each function.

* Fix sanity tests

* Add proxmoxer in requirements file

* Update integration tests

* Add proxmoxer into constraints.txt

* Address review comments

* Don't run tests on 2.6 python

* Disable Python 2.6 tests for other proxmox modules

(cherry picked from commit 2d6e369d81)

Co-authored-by: Sergei Antipov <greendayonfire@gmail.com>
2023-07-02 22:25:14 +02:00
patchback[bot]
72d1af86f3 [PR #6783/867704dd backport][stable-7] rhsm_repository: refactor handling of subscription-manager (#6830)
rhsm_repository: refactor handling of subscription-manager (#6783)

Create a small helper class Rhsm, so all the logic related to the
interaction with subscription-manager is grouped there:
- create the Rhsm object in main(), once the initial checks are done
- search subscription-manager as required (so there is no need to
  manually check it), and store its path for reuse
- store the common arguments for running subscription-manager
- move run_subscription_manager() to Rhsm as run_repos()
- get rid of the different list parameters: we list only all the
  repositories, so the other cases are not needed (and can be added
  easily, if needed)
- move get_repository_list() to Rhsm as list_repositories()

The execution of subscription-manager is improved as well:
- pass the arguments to run_command() directly as list, rather than
  joining the arguments to string, which run_command() will need to
  split again
- move the "repos" parameter directly in run_repos()
- explicitly disable the shell, already off by default
- disable the expansions of variables, as there are none

Adapt the unit test to the different way run_command() is called.

There should be no behaviour changes.

(cherry picked from commit 867704dd75)

Co-authored-by: Pino Toscano <ptoscano@redhat.com>
2023-07-02 22:01:53 +02:00
patchback[bot]
6c718a4f55 [PR #6826/eb734d03 backport][stable-7] snap: fix bug when processing output (#6829)
snap: fix bug when processing output (#6826)

* snap: fix bug when processing output

* add changelog frag

(cherry picked from commit eb734d03c0)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-07-02 21:45:16 +02:00
patchback[bot]
751e2400e6 [PR #6774/3fd4cdb1 backport][stable-7] locale_gen: works with C.UTF-8 (#6818)
locale_gen: works with C.UTF-8 (#6774)

* locale_gen: fix

* test working with C.UTF-8

* working with locale eo

* handle C.UTF-8 edge cases

* grammatic pedantism

* add changelog frag

* add doc about specific OS support

* update changelog frag

(cherry picked from commit 3fd4cdb119)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-06-30 13:13:16 +02:00
patchback[bot]
c2ae3dd026 [PR #6691/89ad18d1 backport][stable-7] random_string docs to say it's cryptographically secure (#6816)
random_string docs to say it's cryptographically secure (#6691)

Modify random_string docs to state that randomness is cryptographically secure

(cherry picked from commit 89ad18d1a7)

Co-authored-by: Matthew Davis <7035647+mdavis-xyz@users.noreply.github.com>
2023-06-30 06:39:17 +02:00
patchback[bot]
9a97d5e14a [PR #6804/c4a2801f backport][stable-7] Remove pre-commit config (#6807)
Remove pre-commit config (#6804)

Remove pre-commit config.

(cherry picked from commit c4a2801f99)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-29 05:52:49 +02:00
patchback[bot]
f794ba17c9 [PR #6763/a53bf9d2 backport][stable-7] Keycloak authentication provider ID choices (#6799)
Keycloak authentication provider ID choices (#6763)

* refac: provider id choices

* feat: minor changes fragment

* Update changelogs/fragments/6763-keycloak-auth-provider-choices.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Skrekulko <Skrekulko@users.noreply.github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit a53bf9d261)

Co-authored-by: Skrekulko <111891715+Skrekulko@users.noreply.github.com>
2023-06-28 07:07:30 +02:00
patchback[bot]
f4575816be [PR #6719/22efbcc6 backport][stable-7] Fixing bug in get_volume_inventory (#6792)
Fixing bug in get_volume_inventory (#6719)

* Fixing bug in get_volume_inventory

* Adding changelog fragment

* sanity fix

* Update changelogs/fragments/6719-redfish-utils-fix-for-get-volume-inventory.yml

Agreed

Co-authored-by: Felix Fontein <felix@fontein.de>

* Updating changelog fragment

* Update changelogs/fragments/6719-redfish-utils-fix-for-get-volume-inventory.yml

Agreed

Co-authored-by: Felix Fontein <felix@fontein.de>

* Updating changes as per PR comments

* PR comment changes

---------

Co-authored-by: Kushal <t-s.kushal@hpe.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 22efbcc627)

Co-authored-by: TSKushal <44438079+TSKushal@users.noreply.github.com>
2023-06-27 07:08:08 +02:00
patchback[bot]
fd3bc75fb3 [PR #6772/93f5a48b backport][stable-7] Redfish: Filter empty account slots from ListUsers (#6794)
Redfish: Filter empty account slots from ListUsers (#6772)

* Redfish: Filter empty account slots from ListUsers

Signed-off-by: Mike Raineri <michael.raineri@dell.com>

* Update 6771-redfish-filter-empty-account-slots.yml

* Update changelogs/fragments/6771-redfish-filter-empty-account-slots.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Signed-off-by: Mike Raineri <michael.raineri@dell.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 93f5a48b8c)

Co-authored-by: Mike Raineri <mraineri@gmail.com>
2023-06-27 07:07:58 +02:00
patchback[bot]
dc898dfdf8 [PR #6770/70e2048d backport][stable-7] proxmox_disk: fix failure to create cdrom (#6796)
proxmox_disk: fix failure to create cdrom (#6770)

* proxmox_disk: fix failure to create cdrom

* Add changelog fragment 6770

* Update changelogs/fragments/6770-proxmox_disk_create_cdrom.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 70e2048d8c)

Co-authored-by: exodious <10215736+exodious@users.noreply.github.com>
2023-06-27 07:07:39 +02:00
patchback[bot]
28c7a62989 [PR #6773/3571df83 backport][stable-7] Module proxmox_kvm restarted state bug fix (#6797)
Module `proxmox_kvm` `restarted` state bug fix (#6773)

* Change proxmox_kvm restart to use new method

Previously, the `restarted` state used both stop and start vm functions to restart a vm.
This change introduces the a new function that utilizes the proxmox reboot endpoint instead for a more reliable method of restarting a vm.

* Create 6773-proxmox_kvm-restarted-state-bug-fix.yaml

* Fix typo

* Add link to PR

* Chanel log fragment formatting changes

* Move try/catch to `restart_vm` function

* Update changelogs/fragments/6773-proxmox_kvm-restarted-state-bug-fix.yaml

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 3571df837d)

Co-authored-by: Juan Estupinan <65736594+juan210012@users.noreply.github.com>
2023-06-27 07:07:27 +02:00
patchback[bot]
f490bc1dba [PR #6788/7b404fd4 backport][stable-7] CI: Remove Fedora 37 from devel; add Fedora 38 (#6790)
CI: Remove Fedora 37 from devel; add Fedora 38 (#6788)

Remove Fedora 37 from devel; add Fedora 38.

(cherry picked from commit 7b404fd45d)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-26 23:10:37 +02:00
patchback[bot]
5bd671b8bf [PR #6779/8b5dc27d backport][stable-7] Add Debian Bookworm to CI (#6782)
Add Debian Bookworm to CI (#6779)

* Add Debian Bookworm to CI.

* The package has been netcat-openbsd for some time now. The transitional package was removed in Debian 12.

(cherry picked from commit 8b5dc27d23)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-24 23:56:09 +02:00
patchback[bot]
0057908705 [PR #6775/cfaeed04 backport][stable-7] Bump AZP container (#6778)
Bump AZP container (#6775)

Bump AZP container.

(cherry picked from commit cfaeed0492)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-24 16:36:23 +02:00
patchback[bot]
39d83fefee [PR #6758/08fa05f0 backport][stable-7] rhsm_repository: add an initial unit test (#6781)
rhsm_repository: add an initial unit test (#6758)

So far there are no tests for the rhsm_repository module, which makes it
difficult to do even basic changes. Hence, add an initial version of
unit tests, heavily based on the approach of the redhat_subscription
unit tests.

(cherry picked from commit 08fa05f05f)

Co-authored-by: Pino Toscano <ptoscano@redhat.com>
2023-06-24 16:32:40 +02:00
patchback[bot]
145b4e7433 [PR #6766/fd63f583 backport][stable-7] Improve seealso (#6767)
Improve seealso (#6766)

* Improve seealso.

* Update ignore files.

(cherry picked from commit fd63f583ef)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-22 13:46:25 +02:00
patchback[bot]
d45b112cc0 [PR #6759/f88f4008 backport][stable-7] ipify_facts: skip tests on Python 2.6 (#6762)
ipify_facts: skip tests on Python 2.6 (#6759)

Needs some special requirements on Python 2.6 so that it can contact the server.

These seem to be installed when running all tests. I don't want to figure out what exactly
is missing, so let's just skip the tests on Python 2.6 for now.

(cherry picked from commit f88f40086d)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-21 10:42:49 +02:00
Felix Fontein
fc64490f89 Fix link in changelog. 2023-06-20 20:36:51 +02:00
Felix Fontein
4a0276261b Next expected release is 7.2.0. 2023-06-20 20:36:18 +02:00
121 changed files with 6262 additions and 793 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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)$

View File

@@ -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.

View File

@@ -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`.

View 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'

View File

@@ -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)

View File

@@ -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:

View File

@@ -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):

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View 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()

View File

@@ -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 = '''

View File

@@ -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"

View File

@@ -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)
"""

View File

@@ -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.

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View 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()

View File

@@ -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),
)

View File

@@ -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():

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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
)

View File

@@ -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:

View File

@@ -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,

View File

@@ -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})

View File

@@ -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
#

View File

@@ -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',

View 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()

View 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()

View File

@@ -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'
% (

View File

@@ -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__':

View File

@@ -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'],

View 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',

View File

@@ -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,

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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')

View File

@@ -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"

View File

@@ -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)

View File

@@ -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:

View 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()

View File

@@ -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

View File

@@ -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']

View File

@@ -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__':

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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,

View File

@@ -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,

View File

@@ -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']),

View File

@@ -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,
)

View File

@@ -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 == ""

View 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

View File

@@ -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:

View File

@@ -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

View File

@@ -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"
}

View 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

View 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

View 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

View 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

View 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"

View File

@@ -3,3 +3,4 @@
# SPDX-License-Identifier: GPL-3.0-or-later
azp/posix/2
skip/python2.6

View File

@@ -9,3 +9,4 @@ skip/osx
skip/macos
skip/freebsd
needs/root
skip/rhel # FIXME: keytool seems to be broken on newer RHELs

View File

@@ -9,3 +9,4 @@ skip/osx
skip/macos
skip/freebsd
needs/root
skip/rhel # FIXME: keytool seems to be broken on newer RHELs

View 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
keycloak_authz_permission_info

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -13,7 +13,7 @@
ansible.builtin.package:
name:
- net-tools
- netcat
- netcat-openbsd
state: latest
when: ansible_os_family == "Debian"

View File

@@ -6,3 +6,5 @@ azp/posix/3
destructive
needs/root
skip/aix
skip/freebsd
skip/macos

View 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) != []

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View 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

View File

@@ -17,3 +17,4 @@
- include_tasks: 'update_cache.yml'
- include_tasks: 'locally_installed_package.yml'
- include_tasks: 'reason.yml'
- include_tasks: 'yay-become.yml'

View 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

View File

@@ -6,3 +6,4 @@ azp/posix/2
destructive
skip/python2
skip/python3.5
disabled # TODO

View File

@@ -6,3 +6,4 @@ azp/posix/3
destructive
skip/python2
skip/python3.5
disabled # TODO

View 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

View 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