Compare commits

...

80 Commits
4.1.0 ... 4.3.0

Author SHA1 Message Date
Felix Fontein
c85bb8713e Release 4.3.0 2022-01-11 07:27:25 +01:00
patchback[bot]
5cdc8f4b07 New Module: Keycloak Realm Info (#3998) (#4022)
* feat(plugins/keycloak): add get_realm_info_by_id as util function

* feat(plugins/keycloak): add keycloak_realm_info module

* chore: add maintainer

* feat(plugins/keycloak): remove supports_check_mode

* feat(plugins/keycloak): add supports_check_mode back

* Update plugins/modules/identity/keycloak/keycloak_realm_info.py

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

* Update plugins/modules/identity/keycloak/keycloak_realm_info.py

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

* docs(plugins/keycloak): cleanup docs

* feat(plugins/keycloak): add unit test

* Update plugins/modules/identity/keycloak/keycloak_realm_info.py

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

* Update plugins/modules/identity/keycloak/keycloak_realm_info.py

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

* feat(plugins/keycloak): remove end_state

* docs(plugins/keycloak): complete sentences

* docs(plugins/keycloak): use dict for return type

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

Co-authored-by: Fynnnnn <fynn.cfchen@gmail.com>
2022-01-11 07:13:16 +01:00
Felix Fontein
50131f5dfa Prepare 4.3.0 release. 2022-01-10 23:06:56 +01:00
patchback[bot]
c734e7c2e5 fix alternatives parsing when they are part of a group (#3976) (#4021)
* fix alternatives parsing when they are part of a group

* add changelog fragment

Co-authored-by: Guillaume Rousse <guillaume.rousse@renater.fr>
(cherry picked from commit a675afcba9)

Co-authored-by: Guillaume Rousse <guillomovitch@gmail.com>
2022-01-10 07:27:31 +01:00
patchback[bot]
7e6e8f7749 puppet: Add documentation and remove deprecation for show_diff, keep deprecation for alias show-diff (#3980) (#4019)
* puppet: Add documentation and remove deprecation for show_diff

* Add changelog fragment

* Update changelogs/fragments/3980-puppet-show_diff.yml

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

* Update plugins/modules/system/puppet.py

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

* Update plugins/modules/system/puppet.py

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

* Fixing syntax error introduced in 29298da3

* More documentation for show_diff and fix some sanity errors

* Update changelogs/fragments/3980-puppet-show_diff.yml

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

* Update tests/sanity/ignore-2.10.txt

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

* Add validate-modules:parameter-invalid to ignores due to invalid and depricated alias

* Keep use-argspec-type-path in ignores

* Update plugins/modules/system/puppet.py

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

Co-authored-by: Benoit Vaudel <benoit@catalyst.net.nz>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit fe57cd5ac8)

Co-authored-by: Benoit Vaudel <vaudelbenoit@aol.com>
2022-01-10 07:27:22 +01:00
patchback[bot]
687acdc961 Fix example code for flattened lookup (#4013) (#4016)
Co-authored-by: Lee Garrett <lgarrett@rocketjump.eu>
(cherry picked from commit d19ab93faf)

Co-authored-by: Lee Garrett <leegarrett@users.noreply.github.com>
2022-01-09 12:29:22 +01:00
patchback[bot]
16092feaab ipmi_power: Add machine option to ensure the power state via the remote target address (#3968) (#4012)
* ipmi_power: Add machine option to ensure the power state via the remote target address

* Fix yamllint sanity check error

* Add changelog fragment entry

* Apply suggestions from the code review

* update to apply suggestions

* Add version_added.

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

Co-authored-by: mizumm <26898888+mizumm@users.noreply.github.com>
2022-01-08 16:17:56 +01:00
patchback[bot]
6676fb8fb4 New module for cargo command (#3712) (#4011)
* New module for cargo command

* Resolve CI errors

* Update plugins/modules/packaging/language/cargo.py

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

* Update plugins/modules/packaging/language/cargo.py

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

* Update plugins/modules/packaging/language/cargo.py

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

* Add maintainer

* Change installed_packages from property to function

* Allow cargo to install list of of packages

* Remove period at the end of task names

* Pass only the list of packages to take action on to cargo

* Add integration tests for cargo

* Update plugins/modules/packaging/language/cargo.py

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

* Apply suggestions from code review

* Update tests/integration/targets/cargo/tasks/setup.yml

* Update tests/integration/targets/cargo/tasks/setup.yml

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

Co-authored-by: radek-sprta <mail@radeksprta.eu>
2022-01-08 16:03:23 +01:00
patchback[bot]
a860f537dd Restrict PyNaCL to 1.4.x on RHEL8 when using Python 3.6 (#4006) (#4010)
* Restrict PyNaCL to 1.4.x on RHEL8 when using Python 3.6.

* Fix typo.

(cherry picked from commit 77a930cf6b)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-08 14:19:25 +00:00
patchback[bot]
f1a9c2f00a nmcli: Add wireguard connection type support (#3985) (#4007)
* nmcli: add wireguard connection type

* nmcli: fix wireguard unit tests

* nmcli: set ipv4.method to disabled if ip4 not set

Method 'auto' is not supported for WireGuard

* nmcli: add wireguard documentation

* nmcli: clean up wireguard documentation

* nmcli: add wireguard changelog fragment

* nmcli: fix wireguard documentation

* Apply suggestions from code review

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>
(cherry picked from commit 4ea58fba75)

Co-authored-by: Johan Wennerberg <j.wennerberg@gmail.com>
2022-01-08 14:33:02 +01:00
patchback[bot]
f8de068e32 Fix 2.9 unit tests (#4002) (#4005)
* Fix 2.9 unit tests.

* Another try.

(cherry picked from commit 26a91e811f)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-08 13:34:11 +01:00
patchback[bot]
70b4bacf0f Fix comment. (#3993) (#3995)
(cherry picked from commit a6a8cd02b6)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-06 15:10:49 +01:00
patchback[bot]
41f5d1741c proxmox: Add clone parameter (#3930) (#3992)
* proxmox: Add clone parameter

* Add changelog fragment

* Add version_added

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

* Add PR URL to changelog fragment

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

* Clarify what content_check does

* Split up try/except block to give a separate error message when creation pre-checks fail

* Create seperate case for cloning

* Prevent 'clone' argument from being removed

* Fix double argument, add todo's

* Check if to be cloned container actually exists

* Adjust module options dependencies

* Require 'storage' parameter when cloned container is not a template and ignore otherwise

* Don't only create linked clones of template containers

* Fix pylint errors

* Add extra example

* Minor language fix

* Add clone_type parameter to specify cloning behaviour

* I can't find if openvz nodes have this clone API, so just don't support it

* Remove unrelated changes

* Don't pass unused kwargs

* Revert more unrelated changes

* Remove required_together clone and clone_type because clone_type has a default choice

* Fix clone_type reference

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

* Fix missing period

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

* Fix redundant period

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

* Fix redundant period

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

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

Co-authored-by: Martijn <martijn@mrtijn.nl>
2022-01-06 08:06:55 +01:00
patchback[bot]
54ede7dd7f Fix BOTMETA and corresponding sanity test (#3989) (#3990)
* Fix BOTMETA and authors mistakes.

* Fix BOTMETA sanity test regex.

(cherry picked from commit 11205eefee)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-06 06:56:27 +01:00
patchback[bot]
7f0702b786 Use vendored copy of distutils.version. (#3984) (#3987)
(cherry picked from commit cf7a33356c)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-05 22:05:13 +01:00
patchback[bot]
89a3abe64a [Bug] Scaleway The volume is created systematically on par1 (#3964) (#3983)
* [Bug] The volume is created systematically on par1

* add change log

* added backward compatibility with organization

* add documentation

* change typo doc

* Update changelogs/fragments/3964-scaleway_volume_add_region.yml

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

* Update plugins/modules/cloud/scaleway/scaleway_volume.py

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

* Update plugins/modules/cloud/scaleway/scaleway_volume.py

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

* Update plugins/modules/cloud/scaleway/scaleway_volume.py

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

* Update plugins/modules/cloud/scaleway/scaleway_volume.py

Co-authored-by: Rémy Léone <remy.leone@gmail.com>

* optimization

Co-authored-by: Romain SCHARFF <rscharff@plussimple.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Rémy Léone <remy.leone@gmail.com>
(cherry picked from commit 125516b957)

Co-authored-by: xilmen <romain.scha@gmail.com>
2022-01-05 18:12:11 +01:00
patchback[bot]
59eff2e3e0 Re-enable snap tests (#3967) (#3981)
* Re-enable snap tests.

* Skip tests on RHEL 8.2 and 8.3.

* Refactor snap setup.

* Try to simplify setup.

(cherry picked from commit bb78d98f8f)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-05 17:49:05 +01:00
patchback[bot]
1115b463fe Sudoers (take 2) (#3746) (#3977)
* Add module and pass the andebox validate-modules

* Fixes pep8 and sanity checks

* Add tests (intending that they'll fail)

* Fix pep8 complaint

* Remove stub test_sudoers file

* Add version_added to documentation

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>

* Various improvements as suggested by reviewers

* Remove further required: false from documentation

* Make yaml indentation consistently indented

* Remove default for command argument

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>

* Refactor check_mode checking as guards

* Update documentation formatting and use to_native

* Update plugins/modules/system/sudoers.py

* Update examples and formatting

* Fix merge conflict

* Update handle

* Add some integration tests

* Update tests to pass yamllint

* Fix assertions typo

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Remove wrapping quotes from assertions

* Use >- for long example names

* Add aliases file to sudoers integration tests

* Fix integration test name

* Create new alternative sudoers directory in case /tmp doesn't exist

* Alternative assertion test for checking rule revocation

* Re-quote assertions

* Update version_added to 4.3.0

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

* Uppercase first character of short_description

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

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>
Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 1ba79f3c6a)

Co-authored-by: Jon <ellis.jp@gmail.com>
2022-01-04 21:08:02 +01:00
patchback[bot]
77bf1fedf5 Get rid of distutils.spawn and distutils.util (#3934) (#3974)
* Replace distutils.spawn.find_executable.

* Replace distutils.util.strtobool.

(cherry picked from commit 77b7b4f75b)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-04 07:22:25 +01:00
patchback[bot]
89560ea2e7 Mattermost: Add sending of attachments (#3946) (#3972)
* Add sending of attachments

* Change required arguments and add changelog

- text was still default -> changed to required_one_of text or attachments
- Add version_added
- Add changelog fragment for mattermost attachments

Co-Authored-By: Felix Fontein <felix@fontein.de>

* Fix wrong indentation

* Add trailing comma

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

* Remove default=None

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

* Fix sentence

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

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

Co-authored-by: xobtoor <313188+xobtoor@users.noreply.github.com>
2022-01-03 19:44:06 +01:00
patchback[bot]
f9919d28d4 slack - use UTF-8 charset in content-type header (#3933) (#3971)
* Use UTF-8 charset in content-type header

* Add changelog fragment

(cherry picked from commit a4ab85fd68)

Co-authored-by: bluikko <14869000+bluikko@users.noreply.github.com>
2022-01-03 19:43:50 +01:00
patchback[bot]
7b4660d28a Add support of project id for scawelay_compute (#3951) (#3961)
* Add support of project id for scawelay_compute

* Create 3951-scaleway_compute_add_project_id

* rename changelog frament

* remove useless whitespace in scaleway_compute.py

* Update changelogs/fragments/3951-scaleway_compute_add_project_id.yml

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

* Update plugins/modules/cloud/scaleway/scaleway_compute.py

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

* correct documentation

* Update changelogs/fragments/3951-scaleway_compute_add_project_id.yml

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

* Update changelogs/fragments/3951-scaleway_compute_add_project_id.yml

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

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

Co-authored-by: pmangin <96626847+pmangin@users.noreply.github.com>
2021-12-28 16:44:10 +01:00
patchback[bot]
29496be80e Restrict redis to < 4.1.0 for ansible-base 2.10. (#3955) (#3959)
(cherry picked from commit 3f2364574d)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-12-27 21:17:09 +01:00
patchback[bot]
991c96615c fix scaleway_user_data (#3940) (#3954)
* fix  scaleway_user_data

scaleway_user_data put cloud-init valuer with 2 unexpected " (begin and end of value)

If Content-Type is not change , it's jsonify ( file module_utils/scaleway.py ligne 131 )

fix the probleme  when "Content-Type" is used instead of "Content-type"

* Create 3940_fix_contenttype_scaleway_user_data.yml

* Update changelogs/fragments/3940_fix_contenttype_scaleway_user_data.yml

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

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

Co-authored-by: pmangin <96626847+pmangin@users.noreply.github.com>
2021-12-27 20:00:58 +01:00
patchback[bot]
fe5ad997c1 ipa_dnszone: add PTR synchronization support for dnszones (#3374) (#3950)
* Add PTR synchronization support for dnszones

* Add changelog fragment

* Update changelogs/fragments/3374-add-ipa-ptr-sync-support.yml

Update to reflect proper module name.

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

* Update plugins/modules/identity/ipa/ipa_dnszone.py

Add period.

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

* Update plugins/modules/identity/ipa/ipa_dnszone.py

Remove requires comment.

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

* Change type to boolean in following with API docs

* Tested with needed changes made.

* Fix documentation to max implementation

* Check for specific params; allow for modifications if needed

* Add PTR synchronization support for dnszones

* Add changelog fragment

* Update changelogs/fragments/3374-add-ipa-ptr-sync-support.yml

Update to reflect proper module name.

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

* Remove trailing whitespace

* Make use of full search and compare params

* Fix formatting errors

* Move the change flag outside of module check

* Fix itens typo to items

* Update dynamicupdate to a boolean

* Remove unnecessary flags and options

* Minor comment changes

* Update changelogs/fragments/3374-add-ipa-ptr-sync-support.yml

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

* Update plugins/modules/identity/ipa/ipa_dnszone.py

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

Co-authored-by: Anne-Marie Lee <alee@datainterfuse.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 84e45c2cc0)

Co-authored-by: Annie Lee <hambriak@gmail.com>
2021-12-27 16:22:55 +01:00
patchback[bot]
468b28bbb8 Add counter filter (#3921) (#3945)
* Add counter filter

* move counter filter doc to existing chapter

* Use existing typerror exception from Counter

* Match counter filter example task name and output

(cherry picked from commit 9642a15d34)

Co-authored-by: Rémy Keil <remy.keil@gmail.com>
2021-12-26 15:25:18 +01:00
patchback[bot]
9b57221d9a Prepare for distutils.version being removed in Python 3.12 (#3936) (#3941)
* Prepare for distutils.version being removed in Python 2.12.

* Fix copy'n'paste error.

* Re-add Loose prefix.

* Fix Python version typos.

* Improve formulation.

* Move message into own line.

* Fix casing, now that the object is no longer called Version.

(cherry picked from commit a2f72be6c8)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-12-24 19:15:47 +01:00
patchback[bot]
cd1a92d417 Fix filesystem tests (so they run on their own) (#3937) (#3939)
* Don't use loops for installing packages.

* Install util-linux-systemd on OpenSuSE so that findmnt is around.

(cherry picked from commit f34c454412)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-12-23 12:33:24 +01:00
Felix Fontein
7486e3a074 Next expected release is 4.3.0. 2021-12-21 12:24:08 +01:00
Felix Fontein
6661917370 Release 4.2.0. 2021-12-21 11:58:13 +01:00
patchback[bot]
ec0bd3143a Add additional auth support to Gitlab (#705) (#3918) (#3929)
* Add additional auth support to Gitlab (#705)

- removed unused imports from module_utils.gitlab
- fix bug in gitlab_project to check if avatar_path is provided

* add doc_fragment and argument_spec for gitlab auth

* doc fixes and remove avatar_path bug fix

* small doc changes, pass validate_certs to requests call

* update changelog

(cherry picked from commit 52ad0a5fbb)

Co-authored-by: Josh <josham@users.noreply.github.com>
2021-12-20 22:20:40 +01:00
patchback[bot]
cce68def8b fix gitlab_project avatar_path open when undefined bug (#3926) (#3927) (#3928)
* fix gitlab_project avatar_path open when undefined bug (#3926)

* remove changelog fragment

(cherry picked from commit 11fcf661bf)

Co-authored-by: Josh <josham@users.noreply.github.com>
2021-12-20 20:22:29 +01:00
patchback[bot]
6f5ad22d28 Disable snap tests. (#3922) (#3923)
(cherry picked from commit 51838adf8c)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-12-20 10:58:42 +01:00
patchback[bot]
6c53a09eef xfconf - using aggregated base class (#3919) (#3920)
* xfconf - using aggregated base class

* added changelog fragment

* fixed typo

(cherry picked from commit daabb53a2b)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-20 10:15:24 +01:00
patchback[bot]
9b6e75f7f4 Icinga2 Inventory Plugin - Error handling and inventory name changer (#3906) (#3915)
* Added inventory_attr and filter error handling

* Added inventory_attr and filter error handling

* Added inventory_attr and filter error handling

* Added inventory_attr and filter error handling

* Added changelog

* Added inventory_attr and filter error handling

* Added inventory_attr and filter error handling

* Applying requested changes

* FIxes for tests

* Added inventory_attr and filter error handling

* Error handling

* Error handling

* Error handling

* Modifications to unit tests

* Remove pitfall

(cherry picked from commit 8da2c630d8)

Co-authored-by: Cliff Hults <BongoEADGC6@users.noreply.github.com>
2021-12-19 14:18:57 +01:00
patchback[bot]
e09650140d Fix nrdp string arguments without an encoding (#3909) (#3912)
* Fix nrdp string arguments without an encoding

* added changelog fragment

Signed-off-by: Jesse Harris <zigford@gmail.com>

* Update changelogs/fragments/3909-nrdp_fix_string_args_without_encoding.yaml

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

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

Co-authored-by: Jesse Harris <zigford@gmail.com>
2021-12-17 22:40:29 +01:00
patchback[bot]
67388be1a9 jira - fixed 'body' dict key error (#3867) (#3914)
* fixed

* added changelog fragment

* improved fail output when placing JIRA API requests

* Update plugins/modules/web_infrastructure/jira.py

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

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

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-17 22:13:43 +01:00
patchback[bot]
130d07948a proxmox - fixing onboot parameter causing module failure when not defined (#3874) (#3902)
* fixing onboot parameter when not supplied

* adding changelog fragment

(cherry picked from commit 00a1152bb1)

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>
2021-12-14 07:00:32 +01:00
patchback[bot]
5d6fcaef53 LXD inventory: Support virtual machines (#3519) (#3900)
* LXD 4.x compatibility (Containers and VMs)

* add changelog fragment

* update fixture

* update plugin options

* backwards compatible alias

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

* Update changelogs/fragments/3519-inventory-support-lxd-4.yml

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

* add lxd 4.0 requirement

* filter for type of virtualization added. due to duplication in the namespace, "type" is not used as the keyword but "nature".

* add type filter

Since the first version of this inventory plugin only supports containers,
a filter function was added to filter between containers and
virtual machines or both.
By default only containers are displayed, as in the first version of the plugin.
This behavior will change in the future.

* rename C(nature) to C(type)

The term "nature" does not fit into the lxd namespace.
Therefore i renamed nature to type.

* update changelog fragment

* Update plugins/inventory/lxd.py

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

* Apply suggestions from code review

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

* rename typefilter to type_filter

* fix tests with type_filter

* Update plugins/inventory/lxd.py

* Update plugins/inventory/lxd.py

Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Frank Dornheim <“dornheim@posteo.de@users.noreply.github.com”>
(cherry picked from commit 8825ef4711)

Co-authored-by: Élie <elie@deloumeau.fr>
2021-12-14 06:42:47 +01:00
patchback[bot]
f044a83c49 Pass missing vlan-related options (flags, ingress, egress) to nmcli (#3896) (#3899)
* Pass missing vlan-related options (flags, ingress, egress) to nmcli

Signed-off-by: Jean-Francois Panisset <panisset@gmail.com>

* Follow style: comma on last parameter

Signed-off-by: Jean-Francois Panisset <panisset@gmail.com>

* PEP8 code style fix

Signed-off-by: Jean-Francois Panisset <panisset@gmail.com>

* add missing changelog fragment

Signed-off-by: Jean-Francois Panisset <panisset@gmail.com>
(cherry picked from commit 6cec2e2f58)

Co-authored-by: Jean-Francois Panisset <32653482+jfpanisset@users.noreply.github.com>
2021-12-13 21:59:37 +01:00
patchback[bot]
e3f7e8dadf Docs improvements. (#3893) (#3894)
(cherry picked from commit 59bbaeed77)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-12-12 11:46:31 +01:00
patchback[bot]
8d1a028dbd Modules for managing HPE iLO (#3740) (#3892)
* Adding HPE ilo modules

* lint fix

* symlink created

* Fan message enhancement

* Removed comments

* Added uniform constuct

* Update plugins/module_utils/redfish_utils.py

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

* Update plugins/module_utils/redfish_utils.py

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

* Update plugins/modules/remote_management/redfish/ilo_redfish_config.py

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

* Added info module and minor changes

* lint fixes

* lint fixes

* lint fixes

* lint fixes

* Added tests and modifed ilo_redfish_info

* Modified tests

* lint fix

* result overwrite fixed

* result overwrite fixed

* Added result

* Changed RESULT

* Modified contains

* Added License

* lint fix

* Changed RESULT

* lint fix

* Changed return

* Changed return

* Update plugins/modules/remote_management/redfish/ilo_redfish_info.py

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

* Update plugins/modules/remote_management/redfish/ilo_redfish_info.py

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

* Update plugins/modules/remote_management/redfish/ilo_redfish_info.py

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

* Update plugins/modules/remote_management/redfish/ilo_redfish_info.py

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

* Update plugins/modules/remote_management/redfish/ilo_redfish_config.py

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

* Update plugins/modules/remote_management/redfish/ilo_redfish_info.py

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

* Added - changed

* Modified changed attribute

* Changed modified

* lint fix

* Removed req

* Minor changes

* Update plugins/modules/remote_management/redfish/ilo_redfish_info.py

Co-authored-by: Rajeevalochana Kallur <rajeevalochana.kallur@hpe.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 8508e3fa6f)

Co-authored-by: Bhavya <44067558+Bhavya06@users.noreply.github.com>
2021-12-11 21:56:10 +01:00
patchback[bot]
8823e5c061 hponcfg - revamped the module using ModuleHelper (#3840) (#3891)
* hponcfg - revamped the module using ModuleHelper

* added changelog fragment

* fixed imports

* Update plugins/modules/remote_management/hpilo/hponcfg.py

* fixed

(cherry picked from commit 7cbe1bcf63)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-11 21:35:23 +01:00
patchback[bot]
102456d033 add dnsimple_info module, see issue #3569 (#3739) (#3890)
* add dnsimple_info module, see issue #3569

https://github.com/ansible-collections/community.general/issues/3569#issuecomment-945002861

* Update plugins/modules/net_tools/dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update BOTMETA.yml

Update dnsimple_info.py

Create dnsimple_info.py

Create dnsimple_info.py

pep8

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update dnsimple_info.py

add returns

pep8 spacing

Update dnsimple_info.py

Update dnsimple_info.py

change return results to list

fix time stamps

Update dnsimple_info.py

remove extra comma

Update plugins/modules/net_tools/dnsimple_info.py

Update test_dnsimple_info.py

Update dnsimple_info.py

fix descriptions

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

missing punctuation throughout docs

Update dnsimple_info.py

add elements in descriptions

Update dnsimple_info.py

indentation error

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

Update dnsimple_info.py

refactor, remove unneeded arguments

refactor and error handling

formatting

add unit test

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update plugins/modules/net_tools/dnsimple_info.py

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Update test_dnsimple_info.py

assert fail/exit

Update test_dnsimple_info.py

pep8 fixes

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Update test_dnsimple_info.py

Co-Authored-By: Felix Fontein <felix@fontein.de>

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

Co-authored-by: Edward Hilgendorf <edward@hilgendorf.me>
2021-12-11 21:29:27 +01:00
patchback[bot]
aad4c55d3d lxc_container - invoke run_command passing list (#3851) (#3886)
* lxc_container - invoke run_command passing list

* added changelog fragment

* Update plugins/modules/cloud/lxc/lxc_container.py

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

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

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-10 06:43:19 +01:00
patchback[bot]
e31c98f17f jira - Add support for Bearer token auth (#3838) (#3884)
* jira - Add support for Bearer token auth

* jira - Add support for Bearer token auth

* added changelog fragment

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

* fix indent issue

* fix overindent

* jira - Add support for Bearer token auth

* jira - Add support for Bearer token auth

* added changelog fragment

* minor doc fix to be clearer.

Be clear about the exclusivity between username and token
as well as password and token.

* Update changelogs/fragments/3838-jira-token.yaml

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

* Update plugins/modules/web_infrastructure/jira.py

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

* Update plugins/modules/web_infrastructure/jira.py

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

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

Co-authored-by: Kambiz Aghaiepour <kambiz@aghaiepour.com>
2021-12-09 22:05:02 +01:00
patchback[bot]
6a5dfc5579 aix_lvg - invoke run_command passing list (#3834) (#3883)
* aix_lvg - invoke run_command passing list

* added changelog fragment

(cherry picked from commit 4bddf9e12c)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-09 22:01:28 +01:00
Felix Fontein
ab7efef9df nmcli: adding ipv6 address list support (#3776) (#3885)
* rebase

* Add changelog fragment

* add suggestions

* split PR into two

* Add multiple address support but with #3768 fiexed

* rebase

* clean some merge artifacts

* update the wording

(cherry picked from commit 90c0980e8d)

Co-authored-by: Alex Groshev <38885591+haddystuff@users.noreply.github.com>
2021-12-09 22:00:33 +01:00
patchback[bot]
ca9c763b57 aix_filesystems - invoke run_command passing list (#3833) (#3882)
* aix_filesystems - invoke run_command passing list

* added changelog fragment

(cherry picked from commit 70f73f42f8)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-09 22:00:12 +01:00
patchback[bot]
cfeb40ed23 Update lxd connection to use all documented vars for options (#3798) (#3881)
* Update lxd connection to use documented vars

* Add a changelog fragment

* Add clarification to changelog description

* Shorten changelog fragment description

(cherry picked from commit 8f6866dba6)

Co-authored-by: Conner Crosby <conner@cavcrosby.tech>
2021-12-09 21:58:06 +01:00
patchback[bot]
c495d136fa add module gitlab_branch (#3795) (#3879)
* add module gitlab_branch

* Update plugins/modules/source_control/gitlab/gitlab_branch.py

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

* Update plugins/modules/source_control/gitlab/gitlab_branch.py

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

* Update plugins/modules/source_control/gitlab/gitlab_branch.py

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

* Update gitlab_branch.py

* Update gitlab_branch.py

* Update gitlab_branch.py

* add integration tests

* Update BOTMETA.yml

* Update gitlab_branch.py

* Update tests/integration/targets/gitlab_branch/aliases

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

* Update main.yml

Co-authored-by: paitrault <aymeric.paitrault@inetum.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit c69e4f4ac9)

Co-authored-by: paytroff <paytroff@gmail.com>
2021-12-09 21:19:13 +01:00
patchback[bot]
d9e2d6682b small docs update for timezone module (#3876) (#3878)
* small docs update for timezone module
fixes #3242

* Update plugins/modules/system/timezone.py

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

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

Co-authored-by: Anatoly Pugachev <matorola@gmail.com>
2021-12-09 21:19:03 +01:00
Felix Fontein
d7fe288ffd Prepare 4.2.0 release. 2021-12-08 20:22:04 +01:00
patchback[bot]
7de89699f7 update scaleway maintainers (#3472) (#3873)
* update scaleway maintainers

* Fix

* Fix sieben -> remyleone

Co-authored-by: scaleway-bot <github@scaleway.com>
(cherry picked from commit 80d650f60a)

Co-authored-by: Rémy Léone <remy.leone@gmail.com>
2021-12-08 20:20:59 +01:00
patchback[bot]
b0a9cceeb5 interfaces_file: unit tests improved (#3863) (#3869)
* interfaces_file: fixed unit tests and added README, added test cases for #3862

* typo fix for interfaces_file unit tests README.md

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

* typo fix for interfaces_file unit tests README.md

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

* typo fix for interfaces_file unit tests README.md

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

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

Co-authored-by: Roman Belyakovsky <ihryamzik@gmail.com>
2021-12-08 12:51:25 +01:00
patchback[bot]
b08f0b2f82 interfaces_file - fixed dup options bug (#3862) (#3866)
* interfaces_file - fixed dup options bug

* added changelog fragment

(cherry picked from commit 3dd5b0d343)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-08 05:54:48 +00:00
patchback[bot]
f23f409bd6 MH additional tests (#3850) (#3859)
(cherry picked from commit d50f30c618)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-05 22:14:16 +01:00
patchback[bot]
cfea62793f MH decorators - added decorators for check_mode (#3849) (#3860)
* MH decorators - added decorators for check_mode

* added changelog fragment

(cherry picked from commit fb79c2998e)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-05 22:14:08 +01:00
patchback[bot]
62bda91466 Add stable-4 to nightly CI jobs; make stable-2 weekly. (#3852) (#3857)
(cherry picked from commit 727c9a4032)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-12-05 17:41:15 +01:00
patchback[bot]
473d5fa2af Moved changelog fragment file to the right directory (#3853) (#3858)
* moved changelog fragment file to the right directory

* fixed filename

(cherry picked from commit 4f4150117d)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-05 17:40:43 +01:00
patchback[bot]
cc76d684d5 opentelemetry: honour ignore errors (#3837) (#3847)
* opentelemetry: honour the ignore_errors

* fix-encoding-pragma

* Add changelog fragment

* opentelemetry: ignore produces unset span status

(cherry picked from commit ce6d0a749e)

Co-authored-by: Victor Martinez <victormartinezrubio@gmail.com>
2021-12-04 19:55:17 +01:00
patchback[bot]
7a6770c731 nmcli - add support for addr-gen-mode and ip6-privacy options (#3802) (#3845)
* Add support for addr-gen-mode and ip6-privacy options

* Apply suggestions from code review

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

* try to solve conflict

* add suggested code + fix some of its issues

* Update plugins/modules/net_tools/nmcli.py

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

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

Co-authored-by: Alex Groshev <38885591+haddystuff@users.noreply.github.com>
2021-12-04 19:18:49 +01:00
patchback[bot]
d2214af6e8 java_cert - invoke run_command passing list (#3835) (#3842)
* java_cert - invoke run_command passing list

* added changelog fragment

(cherry picked from commit 6b91c56c4e)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-03 08:07:15 +01:00
patchback[bot]
fad1220869 monit - invoke run_command passing list (#3821) (#3832)
* monit - invoke run_command passing list

* added changelog fragment

* fixed unit test

* further adjustments

* fixed handling of command_args

* better handling of command_args

(cherry picked from commit 52d4907480)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-02 08:12:52 +01:00
patchback[bot]
fe09516235 svc - invoke run_command passing list (#3829) (#3830)
* svc - invoke run_command passing list

* added changelog fragment

(cherry picked from commit ccb74ffd7c)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-01 20:43:16 +01:00
patchback[bot]
78cd8886f4 ip_netns - invoke run_command passing list (#3822) (#3828)
* ip_netns - invoke run_command passing list

* added changelog fragment

(cherry picked from commit ba9578f12a)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-01 13:19:28 +01:00
patchback[bot]
6b99d48f06 logstash_plugin - invoke run_command passing list (#3808) (#3827)
* logstash_plugin - invoke run_command passing list

* added changelog fragment

* rogue chglog frag escaped its caged and was seen running around into a different PR

(cherry picked from commit c587d21ba0)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-01 07:09:49 +01:00
patchback[bot]
6e0e17a7e3 xattr - invoke run_command passing list (#3806) (#3820)
* xattr - invoke run_command passing list

* added changelog fragment

* Update plugins/modules/files/xattr.py

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

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

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-12-01 06:58:39 +01:00
patchback[bot]
90de95c7b2 pipx - fixed --include-apps bug (#3800) (#3818)
* pipx - fixed --include-apps bug

* added changelog fragment

* skipped freebsd for the last test

(cherry picked from commit bc619bcefc)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-30 08:33:31 +01:00
patchback[bot]
07c6b8b24e ModuleHelper - deprecate attribute VarDict (#3801) (#3819)
* ModuleHelper - deprecate attribute VarDict

* added changelog fragment

(cherry picked from commit 2896131ca7)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-30 08:32:56 +01:00
patchback[bot]
d106de6d51 python_requirements_info - improvements (#3797) (#3816)
* python_requirements_info - improvements

- returns python version broken down into its components
- minor refactoring

* adjusted indentation in the documentaiton blocks

* added changelog fragment

* fixes from PR review + assertion in test

(cherry picked from commit ff0c065ca2)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-30 08:32:42 +01:00
patchback[bot]
e96101fb3f Improve modules gitlab (#3792) (#3815)
* correction doc

* Update gitlab_group.py

* improve gitlab

* Update changelogs/3766-improve_gitlab_group_and_project.yml

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

* Update plugins/modules/source_control/gitlab/gitlab_group.py

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

* Update plugins/modules/source_control/gitlab/gitlab_group.py

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

* Update plugins/modules/source_control/gitlab/gitlab_group.py

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

* Update plugins/modules/source_control/gitlab/gitlab_group.py

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

* correction

* correction sanity project

* Update plugins/modules/source_control/gitlab/gitlab_project.py

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

* modif condition default_branch arg

* Update gitlab_project.py

change indent if defautl_branch inside if initialize_with_radme

Co-authored-by: paitrault <aymeric.paitrault@inetum.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit c6dcae5fda)

Co-authored-by: paytroff <paytroff@gmail.com>
2021-11-30 06:53:17 +01:00
patchback[bot]
a60d55f03c ansible_galaxy_install - minor documentation fix (#3804) (#3814)
* ansible_galaxy_install - minor documentation fix

* further adjustments

(cherry picked from commit 49bdc0f218)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-30 06:53:07 +01:00
patchback[bot]
d6a09ada98 iso_extract - invoke run_command passing list (#3805) (#3812)
* iso_extract - invoke run_command passing list

* added changelog fragment

(cherry picked from commit d60edc4ac1)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-30 06:53:00 +01:00
patchback[bot]
9ddb75a3a2 logentries - invoke run_command passing list (#3807) (#3811)
* logentries - invoke run_command passing list

* added changelog fragment

(cherry picked from commit cb0ade4323)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-30 06:52:49 +01:00
patchback[bot]
b85ff2a997 Fixing ip address without mask bug (#3784) (#3803)
* change ip6 type to list of str and fix problem with setting addresses without netmask

* change ip6 type to list of str and fix problem with setting addresses without netmask

* Add changelog fragment

* add suggestions

* fix no mask using bug

* Make change independed from feature branch

(cherry picked from commit aae3ae1a8e)

Co-authored-by: Alex Groshev <38885591+haddystuff@users.noreply.github.com>
2021-11-30 06:01:50 +01:00
patchback[bot]
3d1ca5638b python_requirements_info - fail when version operator used without version (#3785) (#3793)
* python_requirements_info - fail when version operator used without version

* added changelog fragment

* simplified way of achieving the same result

(cherry picked from commit 59c1859fb3)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-26 20:53:58 +01:00
patchback[bot]
35fd4700bf MH DeprecateAttrsMixin (#3727) (#3794)
* initial commit for deprecate_attrs

* completed tests

* added spaces

* test now works when tehre is more than one deprecation

* trying == instead of eq in jinja

* new approach to testing

* removed extraneous debug message

(cherry picked from commit 887b3882dc)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2021-11-26 20:53:49 +01:00
patchback[bot]
9add9df7d6 Keycloak: add sssd provider for user federation (#3780) (#3788)
* add sssd provider

* add changelog fragment

* fix message

* add version

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

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

Co-authored-by: Laurent Paumier <30328363+laurpaum@users.noreply.github.com>
2021-11-25 13:23:21 +01:00
Felix Fontein
cdb747b41d Next expected release is 4.2.0. 2021-11-23 06:44:41 +01:00
268 changed files with 6871 additions and 967 deletions

View File

@@ -24,14 +24,15 @@ schedules:
always: true always: true
branches: branches:
include: include:
- stable-2
- stable-3 - stable-3
- stable-4
- cron: 0 11 * * 0 - cron: 0 11 * * 0
displayName: Weekly (old stable branches) displayName: Weekly (old stable branches)
always: true always: true
branches: branches:
include: include:
- stable-1 - stable-1
- stable-2
variables: variables:
- name: checkoutPath - name: checkoutPath

36
.github/BOTMETA.yml vendored
View File

@@ -118,6 +118,8 @@ files:
$doc_fragments/xenserver.py: $doc_fragments/xenserver.py:
maintainers: bvitnik maintainers: bvitnik
labels: xenserver labels: xenserver
$filters/counter.py:
maintainers: keilr
$filters/dict.py: $filters/dict.py:
maintainers: felixfontein maintainers: felixfontein
$filters/dict_kv.py: $filters/dict_kv.py:
@@ -156,7 +158,7 @@ files:
maintainers: conloos maintainers: conloos
$inventories/nmap.py: {} $inventories/nmap.py: {}
$inventories/online.py: $inventories/online.py:
maintainers: sieben maintainers: remyleone
$inventories/opennebula.py: $inventories/opennebula.py:
maintainers: feldsam maintainers: feldsam
labels: cloud opennebula labels: cloud opennebula
@@ -164,9 +166,9 @@ files:
$inventories/proxmox.py: $inventories/proxmox.py:
maintainers: $team_virt ilijamt maintainers: $team_virt ilijamt
$inventories/xen_orchestra.py: $inventories/xen_orchestra.py:
maintainers: shinuza maintainers: ddelnano shinuza
$inventories/icinga2.py: $inventories/icinga2.py:
maintainers: bongoeadgc6 maintainers: BongoEADGC6
$inventories/scaleway.py: $inventories/scaleway.py:
maintainers: $team_scaleway maintainers: $team_scaleway
labels: cloud scaleway labels: cloud scaleway
@@ -322,6 +324,10 @@ files:
$modules/cloud/misc/proxmox_kvm.py: $modules/cloud/misc/proxmox_kvm.py:
maintainers: helldorado maintainers: helldorado
ignore: skvidal ignore: skvidal
$modules/cloud/misc/proxmox_nic.py:
maintainers: Kogelvis
$modules/cloud/misc/proxmox_tasks_info:
maintainers: paginabianca
$modules/cloud/misc/proxmox_template.py: $modules/cloud/misc/proxmox_template.py:
maintainers: UnderGreen maintainers: UnderGreen
ignore: skvidal ignore: skvidal
@@ -341,7 +347,7 @@ files:
$modules/cloud/oneandone/: $modules/cloud/oneandone/:
maintainers: aajdinov edevenport maintainers: aajdinov edevenport
$modules/cloud/online/: $modules/cloud/online/:
maintainers: sieben maintainers: remyleone
$modules/cloud/opennebula/: $modules/cloud/opennebula/:
maintainers: $team_opennebula maintainers: $team_opennebula
$modules/cloud/opennebula/one_host.py: $modules/cloud/opennebula/one_host.py:
@@ -411,11 +417,11 @@ files:
$modules/cloud/scaleway/scaleway_ip_info.py: $modules/cloud/scaleway/scaleway_ip_info.py:
maintainers: Spredzy maintainers: Spredzy
$modules/cloud/scaleway/scaleway_organization_info.py: $modules/cloud/scaleway/scaleway_organization_info.py:
maintainers: sieben Spredzy maintainers: Spredzy
$modules/cloud/scaleway/scaleway_security_group.py: $modules/cloud/scaleway/scaleway_security_group.py:
maintainers: DenBeke maintainers: DenBeke
$modules/cloud/scaleway/scaleway_security_group_info.py: $modules/cloud/scaleway/scaleway_security_group_info.py:
maintainers: sieben Spredzy maintainers: Spredzy
$modules/cloud/scaleway/scaleway_security_group_rule.py: $modules/cloud/scaleway/scaleway_security_group_rule.py:
maintainers: DenBeke maintainers: DenBeke
$modules/cloud/scaleway/scaleway_server_info.py: $modules/cloud/scaleway/scaleway_server_info.py:
@@ -534,6 +540,8 @@ files:
maintainers: adamgoossens maintainers: adamgoossens
$modules/identity/keycloak/keycloak_identity_provider.py: $modules/identity/keycloak/keycloak_identity_provider.py:
maintainers: laurpaum maintainers: laurpaum
$modules/identity/keycloak/keycloak_realm_info.py:
maintainers: fynncfchen
$modules/identity/keycloak/keycloak_realm.py: $modules/identity/keycloak/keycloak_realm.py:
maintainers: kris2kris maintainers: kris2kris
$modules/identity/keycloak/keycloak_role.py: $modules/identity/keycloak/keycloak_role.py:
@@ -619,6 +627,8 @@ files:
labels: cloudflare_dns labels: cloudflare_dns
$modules/net_tools/dnsimple.py: $modules/net_tools/dnsimple.py:
maintainers: drcapulet maintainers: drcapulet
$modules/net_tools/dnsimple_info.py:
maintainers: edhilgendorf
$modules/net_tools/dnsmadeeasy.py: $modules/net_tools/dnsmadeeasy.py:
maintainers: briceburg maintainers: briceburg
$modules/net_tools/gandi_livedns.py: $modules/net_tools/gandi_livedns.py:
@@ -720,6 +730,8 @@ files:
maintainers: mwarkentin maintainers: mwarkentin
$modules/packaging/language/bundler.py: $modules/packaging/language/bundler.py:
maintainers: thoiberg maintainers: thoiberg
$modules/packaging/language/cargo.py:
maintainers: radek-sprta
$modules/packaging/language/composer.py: $modules/packaging/language/composer.py:
maintainers: dmtrs maintainers: dmtrs
ignore: resmo ignore: resmo
@@ -901,6 +913,10 @@ files:
$modules/remote_management/manageiq/: $modules/remote_management/manageiq/:
labels: manageiq labels: manageiq
maintainers: $team_manageiq maintainers: $team_manageiq
$modules/remote_management/manageiq/manageiq_alert_profiles.py:
maintainers: elad661
$modules/remote_management/manageiq/manageiq_alerts.py:
maintainers: elad661
$modules/remote_management/manageiq/manageiq_group.py: $modules/remote_management/manageiq/manageiq_group.py:
maintainers: evertmulder maintainers: evertmulder
$modules/remote_management/manageiq/manageiq_tenant.py: $modules/remote_management/manageiq/manageiq_tenant.py:
@@ -951,6 +967,8 @@ files:
maintainers: SamyCoenen maintainers: SamyCoenen
$modules/source_control/gitlab/gitlab_user.py: $modules/source_control/gitlab/gitlab_user.py:
maintainers: LennertMertens stgrace maintainers: LennertMertens stgrace
$modules/source_control/gitlab/gitlab_branch.py:
maintainers: paytroff
$modules/source_control/hg.py: $modules/source_control/hg.py:
maintainers: yeukhon maintainers: yeukhon
$modules/storage/emc/emc_vnx_sg_member.py: $modules/storage/emc/emc_vnx_sg_member.py:
@@ -1086,6 +1104,8 @@ files:
keywords: beadm dladm illumos ipadm nexenta omnios openindiana pfexec smartos solaris sunos zfs zpool keywords: beadm dladm illumos ipadm nexenta omnios openindiana pfexec smartos solaris sunos zfs zpool
$modules/system/ssh_config.py: $modules/system/ssh_config.py:
maintainers: gaqzi Akasurde maintainers: gaqzi Akasurde
$modules/system/sudoers.py:
maintainers: JonEllis
$modules/system/svc.py: $modules/system/svc.py:
maintainers: bcoca maintainers: bcoca
$modules/system/syspatch.py: $modules/system/syspatch.py:
@@ -1224,9 +1244,9 @@ macros:
team_opennebula: ilicmilan meerkampdvv rsmontero xorel nilsding team_opennebula: ilicmilan meerkampdvv rsmontero xorel nilsding
team_oracle: manojmeda mross22 nalsaber team_oracle: manojmeda mross22 nalsaber
team_purestorage: bannaych dnix101 genegr lionmax opslounge raekins sdodsley sile16 team_purestorage: bannaych dnix101 genegr lionmax opslounge raekins sdodsley sile16
team_redfish: mraineri tomasg2012 xmadsen renxulei team_redfish: mraineri tomasg2012 xmadsen renxulei rajeevkallur bhavya06
team_rhn: FlossWare alikins barnabycourt vritant team_rhn: FlossWare alikins barnabycourt vritant
team_scaleway: QuentinBrosse abarbare jerome-quere kindermoumoute remyleone sieben team_scaleway: remyleone abarbare
team_solaris: bcoca fishman jasperla jpdasma mator scathatheworm troy2914 xen0l team_solaris: bcoca fishman jasperla jpdasma mator scathatheworm troy2914 xen0l
team_suse: commel dcermak evrardjp lrupp toabctl AnderEnder alxgu andytom sealor team_suse: commel dcermak evrardjp lrupp toabctl AnderEnder alxgu andytom sealor
team_virt: joshainglis karmab tleguern Thulium-Drake Ajpantuso team_virt: joshainglis karmab tleguern Thulium-Drake Ajpantuso

View File

@@ -6,6 +6,154 @@ Community General Release Notes
This changelog describes changes after version 3.0.0. This changelog describes changes after version 3.0.0.
v4.3.0
======
Release Summary
---------------
Regular feature and bugfix release.
Minor Changes
-------------
- ipa_dnszone - ``dynamicupdate`` is now a boolean parameter, instead of a string parameter accepting ``"true"`` and ``"false"``. Also the module is now idempotent with respect to ``dynamicupdate`` (https://github.com/ansible-collections/community.general/pull/3374).
- ipa_dnszone - add DNS zone synchronization support (https://github.com/ansible-collections/community.general/pull/3374).
- ipmi_power - add ``machine`` option to ensure the power state via the remote target address (https://github.com/ansible-collections/community.general/pull/3968).
- mattermost - add the possibility to send attachments instead of text messages (https://github.com/ansible-collections/community.general/pull/3946).
- nmcli - add ``wireguard`` connection type (https://github.com/ansible-collections/community.general/pull/3985).
- proxmox - add ``clone`` parameter (https://github.com/ansible-collections/community.general/pull/3930).
- puppet - remove deprecation for ``show_diff`` parameter. Its alias ``show-diff`` is still deprecated and will be removed in community.general 7.0.0 (https://github.com/ansible-collections/community.general/pull/3980).
- scaleway_compute - add possibility to use project identifier (new ``project`` option) instead of deprecated organization identifier (https://github.com/ansible-collections/community.general/pull/3951).
- scaleway_volume - all volumes are systematically created on par1 (https://github.com/ansible-collections/community.general/pull/3964).
Bugfixes
--------
- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.general/pull/3936).
- alternatives - fix output parsing for alternatives groups (https://github.com/ansible-collections/community.general/pull/3976).
- jail connection plugin - replace deprecated ``distutils.spawn.find_executable`` with Ansible's ``get_bin_path`` to find the executable (https://github.com/ansible-collections/community.general/pull/3934).
- lxd connection plugin - replace deprecated ``distutils.spawn.find_executable`` with Ansible's ``get_bin_path`` to find the ``lxc`` executable (https://github.com/ansible-collections/community.general/pull/3934).
- passwordstore lookup plugin - replace deprecated ``distutils.util.strtobool`` with Ansible's ``convert_bool.boolean`` to interpret values for the ``create``, ``returnall``, ``overwrite``, 'backup``, and ``nosymbols`` options (https://github.com/ansible-collections/community.general/pull/3934).
- say callback plugin - replace deprecated ``distutils.spawn.find_executable`` with Ansible's ``get_bin_path`` to find the ``say`` resp. ``espeak`` executables (https://github.com/ansible-collections/community.general/pull/3934).
- scaleway_user_data - fix double-quote added where no double-quote is needed to user data in scaleway's server (``Content-type`` -> ``Content-Type``) (https://github.com/ansible-collections/community.general/pull/3940).
- slack - add ``charset`` to HTTP headers to avoid Slack API warning (https://github.com/ansible-collections/community.general/issues/3932).
- zone connection plugin - replace deprecated ``distutils.spawn.find_executable`` with Ansible's ``get_bin_path`` to find the executable (https://github.com/ansible-collections/community.general/pull/3934).
New Plugins
-----------
Filter
~~~~~~
- counter - Counts hashable elements in a sequence
New Modules
-----------
Identity
~~~~~~~~
keycloak
^^^^^^^^
- keycloak_realm_info - Allows obtaining Keycloak realm public information via Keycloak API
Packaging
~~~~~~~~~
language
^^^^^^^^
- cargo - Manage Rust packages with cargo
System
~~~~~~
- sudoers - Manage sudoers files
v4.2.0
======
Release Summary
---------------
Regular bugfix and feature release.
Minor Changes
-------------
- aix_filesystem - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3833).
- aix_lvg - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3834).
- gitlab - add more token authentication support with the new options ``api_oauth_token`` and ``api_job_token`` (https://github.com/ansible-collections/community.general/issues/705).
- gitlab_group, gitlab_project - add new option ``avatar_path`` (https://github.com/ansible-collections/community.general/pull/3792).
- gitlab_project - add new option ``default_branch`` to gitlab_project (if ``readme = true``) (https://github.com/ansible-collections/community.general/pull/3792).
- hponcfg - revamped module using ModuleHelper (https://github.com/ansible-collections/community.general/pull/3840).
- icinga2 inventory plugin - added the ``display_name`` field to variables (https://github.com/ansible-collections/community.general/issues/3875, https://github.com/ansible-collections/community.general/pull/3906).
- icinga2 inventory plugin - inventory object names are changable using ``inventory_attr`` in your config file to the host object name, address, or display_name fields (https://github.com/ansible-collections/community.general/issues/3875, https://github.com/ansible-collections/community.general/pull/3906).
- ip_netns - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3822).
- iso_extract - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3805).
- java_cert - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3835).
- jira - add support for Bearer token auth (https://github.com/ansible-collections/community.general/pull/3838).
- keycloak_user_federation - add sssd user federation support (https://github.com/ansible-collections/community.general/issues/3767).
- logentries - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3807).
- logstash_plugin - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3808).
- lxc_container - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3851).
- lxd connection plugin - make sure that ``ansible_lxd_host``, ``ansible_executable``, and ``ansible_lxd_executable`` work (https://github.com/ansible-collections/community.general/pull/3798).
- lxd inventory plugin - support virtual machines (https://github.com/ansible-collections/community.general/pull/3519).
- module_helper module utils - added decorators ``check_mode_skip`` and ``check_mode_skip_returns`` for skipping methods when ``check_mode=True`` (https://github.com/ansible-collections/community.general/pull/3849).
- monit - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3821).
- nmcli - add multiple addresses support for ``ip6`` parameter (https://github.com/ansible-collections/community.general/issues/1088).
- nmcli - add support for ``eui64`` and ``ipv6privacy`` parameters (https://github.com/ansible-collections/community.general/issues/3357).
- python_requirements_info - returns python version broken down into its components, and some minor refactoring (https://github.com/ansible-collections/community.general/pull/3797).
- svc - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3829).
- xattr - calling ``run_command`` with arguments as ``list`` instead of ``str`` (https://github.com/ansible-collections/community.general/pull/3806).
- xfconf - minor refactor on the base class for the module (https://github.com/ansible-collections/community.general/pull/3919).
Deprecated Features
-------------------
- module_helper module utils - deprecated the attribute ``ModuleHelper.VarDict`` (https://github.com/ansible-collections/community.general/pull/3801).
Bugfixes
--------
- icinga2 inventory plugin - handle 404 error when filter produces no results (https://github.com/ansible-collections/community.general/issues/3875, https://github.com/ansible-collections/community.general/pull/3906).
- interfaces_file - fixed the check for existing option in interface (https://github.com/ansible-collections/community.general/issues/3841).
- jira - fixed bug where module returns error related to dictionary key ``body`` (https://github.com/ansible-collections/community.general/issues/3419).
- nmcli - fix returning "changed" when no mask set for IPv4 or IPv6 addresses on task rerun (https://github.com/ansible-collections/community.general/issues/3768).
- nmcli - pass ``flags``, ``ingress``, ``egress`` params to ``nmcli`` (https://github.com/ansible-collections/community.general/issues/1086).
- nrdp callback plugin - fix error ``string arguments without an encoding`` (https://github.com/ansible-collections/community.general/issues/3903).
- opentelemetry_plugin - honour ``ignore_errors`` when a task has failed instead of reporting an error (https://github.com/ansible-collections/community.general/pull/3837).
- pipx - passes the correct command line option ``--include-apps`` (https://github.com/ansible-collections/community.general/issues/3791).
- proxmox - fixed ``onboot`` parameter causing module failures when undefined (https://github.com/ansible-collections/community.general/issues/3844).
- python_requirements_info - fails if version operator used without version (https://github.com/ansible-collections/community.general/pull/3785).
New Modules
-----------
Net Tools
~~~~~~~~~
- dnsimple_info - Pull basic info from DNSimple API
Remote Management
~~~~~~~~~~~~~~~~~
redfish
^^^^^^^
- ilo_redfish_config - Sets or updates configuration attributes on HPE iLO with Redfish OEM extensions
- ilo_redfish_info - Gathers server information through iLO using Redfish APIs
Source Control
~~~~~~~~~~~~~~
gitlab
^^^^^^
- gitlab_branch - Create or delete a branch
v4.1.0 v4.1.0
====== ======

View File

@@ -1117,3 +1117,195 @@ releases:
name: revbitspss name: revbitspss
namespace: null namespace: null
release_date: '2021-11-23' release_date: '2021-11-23'
4.2.0:
changes:
bugfixes:
- icinga2 inventory plugin - handle 404 error when filter produces no results
(https://github.com/ansible-collections/community.general/issues/3875, https://github.com/ansible-collections/community.general/pull/3906).
- interfaces_file - fixed the check for existing option in interface (https://github.com/ansible-collections/community.general/issues/3841).
- jira - fixed bug where module returns error related to dictionary key ``body``
(https://github.com/ansible-collections/community.general/issues/3419).
- nmcli - fix returning "changed" when no mask set for IPv4 or IPv6 addresses
on task rerun (https://github.com/ansible-collections/community.general/issues/3768).
- nmcli - pass ``flags``, ``ingress``, ``egress`` params to ``nmcli`` (https://github.com/ansible-collections/community.general/issues/1086).
- nrdp callback plugin - fix error ``string arguments without an encoding``
(https://github.com/ansible-collections/community.general/issues/3903).
- opentelemetry_plugin - honour ``ignore_errors`` when a task has failed instead
of reporting an error (https://github.com/ansible-collections/community.general/pull/3837).
- pipx - passes the correct command line option ``--include-apps`` (https://github.com/ansible-collections/community.general/issues/3791).
- proxmox - fixed ``onboot`` parameter causing module failures when undefined
(https://github.com/ansible-collections/community.general/issues/3844).
- python_requirements_info - fails if version operator used without version
(https://github.com/ansible-collections/community.general/pull/3785).
deprecated_features:
- module_helper module utils - deprecated the attribute ``ModuleHelper.VarDict``
(https://github.com/ansible-collections/community.general/pull/3801).
minor_changes:
- aix_filesystem - calling ``run_command`` with arguments as ``list`` instead
of ``str`` (https://github.com/ansible-collections/community.general/pull/3833).
- aix_lvg - calling ``run_command`` with arguments as ``list`` instead of ``str``
(https://github.com/ansible-collections/community.general/pull/3834).
- gitlab - add more token authentication support with the new options ``api_oauth_token``
and ``api_job_token`` (https://github.com/ansible-collections/community.general/issues/705).
- gitlab_group, gitlab_project - add new option ``avatar_path`` (https://github.com/ansible-collections/community.general/pull/3792).
- gitlab_project - add new option ``default_branch`` to gitlab_project (if ``readme
= true``) (https://github.com/ansible-collections/community.general/pull/3792).
- hponcfg - revamped module using ModuleHelper (https://github.com/ansible-collections/community.general/pull/3840).
- icinga2 inventory plugin - added the ``display_name`` field to variables (https://github.com/ansible-collections/community.general/issues/3875,
https://github.com/ansible-collections/community.general/pull/3906).
- icinga2 inventory plugin - inventory object names are changable using ``inventory_attr``
in your config file to the host object name, address, or display_name fields
(https://github.com/ansible-collections/community.general/issues/3875, https://github.com/ansible-collections/community.general/pull/3906).
- ip_netns - calling ``run_command`` with arguments as ``list`` instead of ``str``
(https://github.com/ansible-collections/community.general/pull/3822).
- iso_extract - calling ``run_command`` with arguments as ``list`` instead of
``str`` (https://github.com/ansible-collections/community.general/pull/3805).
- java_cert - calling ``run_command`` with arguments as ``list`` instead of
``str`` (https://github.com/ansible-collections/community.general/pull/3835).
- jira - add support for Bearer token auth (https://github.com/ansible-collections/community.general/pull/3838).
- keycloak_user_federation - add sssd user federation support (https://github.com/ansible-collections/community.general/issues/3767).
- logentries - calling ``run_command`` with arguments as ``list`` instead of
``str`` (https://github.com/ansible-collections/community.general/pull/3807).
- logstash_plugin - calling ``run_command`` with arguments as ``list`` instead
of ``str`` (https://github.com/ansible-collections/community.general/pull/3808).
- lxc_container - calling ``run_command`` with arguments as ``list`` instead
of ``str`` (https://github.com/ansible-collections/community.general/pull/3851).
- lxd connection plugin - make sure that ``ansible_lxd_host``, ``ansible_executable``,
and ``ansible_lxd_executable`` work (https://github.com/ansible-collections/community.general/pull/3798).
- lxd inventory plugin - support virtual machines (https://github.com/ansible-collections/community.general/pull/3519).
- module_helper module utils - added decorators ``check_mode_skip`` and ``check_mode_skip_returns``
for skipping methods when ``check_mode=True`` (https://github.com/ansible-collections/community.general/pull/3849).
- monit - calling ``run_command`` with arguments as ``list`` instead of ``str``
(https://github.com/ansible-collections/community.general/pull/3821).
- nmcli - add multiple addresses support for ``ip6`` parameter (https://github.com/ansible-collections/community.general/issues/1088).
- nmcli - add support for ``eui64`` and ``ipv6privacy`` parameters (https://github.com/ansible-collections/community.general/issues/3357).
- python_requirements_info - returns python version broken down into its components,
and some minor refactoring (https://github.com/ansible-collections/community.general/pull/3797).
- svc - calling ``run_command`` with arguments as ``list`` instead of ``str``
(https://github.com/ansible-collections/community.general/pull/3829).
- xattr - calling ``run_command`` with arguments as ``list`` instead of ``str``
(https://github.com/ansible-collections/community.general/pull/3806).
- xfconf - minor refactor on the base class for the module (https://github.com/ansible-collections/community.general/pull/3919).
release_summary: Regular bugfix and feature release.
fragments:
- 1088-add_multiple_ipv6_address_support.yml
- 3357-nmcli-eui64-and-ipv6privacy.yml
- 3519-inventory-support-lxd-4.yml
- 3768-nmcli_fix_changed_when_no_mask_set.yml
- 3780-add-keycloak-sssd-user-federation.yml
- 3785-python_requirements_info-versionless-op.yaml
- 3792-improve_gitlab_group_and_project.yml
- 3797-python_requirements_info-improvements.yaml
- 3798-fix-lxd-connection-option-vars-support.yml
- 3800-pipx-include-apps.yaml
- 3801-mh-deprecate-vardict-attr.yaml
- 3805-iso_extract-run_command-list.yaml
- 3806-xattr-run_command-list.yaml
- 3807-logentries-run_command-list.yaml
- 3808-logstash_plugin-run_command-list.yaml
- 3821-monit-run-list.yaml
- 3822-ip_netns-run-list.yaml
- 3829-svc-run-list.yaml
- 3833-aix_filesystem-run-list.yaml
- 3834-aix-lvg-run-list.yaml
- 3835-java-cert-run-list.yaml
- 3837-opentelemetry_plugin-honour_ignore_errors.yaml
- 3838-jira-token.yaml
- 3840-hponcfg-mh-revamp.yaml
- 3849-mh-check-mode-decos.yaml
- 3851-lxc-container-run-list.yaml
- 3862-interfaces-file-fix-dup-option.yaml
- 3867-jira-fix-body.yaml
- 3874-proxmox-fix-onboot-param.yml
- 3875-icinga2-inv-fix.yml
- 3896-nmcli_vlan_missing_options.yaml
- 3909-nrdp_fix_string_args_without_encoding.yaml
- 3919-xfconf-baseclass.yaml
- 4.2.0.yml
- 705-gitlab-auth-support.yml
modules:
- description: Pull basic info from DNSimple API
name: dnsimple_info
namespace: net_tools
- description: Create or delete a branch
name: gitlab_branch
namespace: source_control.gitlab
- description: Sets or updates configuration attributes on HPE iLO with Redfish
OEM extensions
name: ilo_redfish_config
namespace: remote_management.redfish
- description: Gathers server information through iLO using Redfish APIs
name: ilo_redfish_info
namespace: remote_management.redfish
release_date: '2021-12-21'
4.3.0:
changes:
bugfixes:
- Various modules and plugins - use vendored version of ``distutils.version``
instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.general/pull/3936).
- alternatives - fix output parsing for alternatives groups (https://github.com/ansible-collections/community.general/pull/3976).
- jail connection plugin - replace deprecated ``distutils.spawn.find_executable``
with Ansible's ``get_bin_path`` to find the executable (https://github.com/ansible-collections/community.general/pull/3934).
- lxd connection plugin - replace deprecated ``distutils.spawn.find_executable``
with Ansible's ``get_bin_path`` to find the ``lxc`` executable (https://github.com/ansible-collections/community.general/pull/3934).
- passwordstore lookup plugin - replace deprecated ``distutils.util.strtobool``
with Ansible's ``convert_bool.boolean`` to interpret values for the ``create``,
``returnall``, ``overwrite``, 'backup``, and ``nosymbols`` options (https://github.com/ansible-collections/community.general/pull/3934).
- say callback plugin - replace deprecated ``distutils.spawn.find_executable``
with Ansible's ``get_bin_path`` to find the ``say`` resp. ``espeak`` executables
(https://github.com/ansible-collections/community.general/pull/3934).
- scaleway_user_data - fix double-quote added where no double-quote is needed
to user data in scaleway's server (``Content-type`` -> ``Content-Type``) (https://github.com/ansible-collections/community.general/pull/3940).
- slack - add ``charset`` to HTTP headers to avoid Slack API warning (https://github.com/ansible-collections/community.general/issues/3932).
- zone connection plugin - replace deprecated ``distutils.spawn.find_executable``
with Ansible's ``get_bin_path`` to find the executable (https://github.com/ansible-collections/community.general/pull/3934).
minor_changes:
- ipa_dnszone - ``dynamicupdate`` is now a boolean parameter, instead of a string
parameter accepting ``"true"`` and ``"false"``. Also the module is now idempotent
with respect to ``dynamicupdate`` (https://github.com/ansible-collections/community.general/pull/3374).
- ipa_dnszone - add DNS zone synchronization support (https://github.com/ansible-collections/community.general/pull/3374).
- ipmi_power - add ``machine`` option to ensure the power state via the remote
target address (https://github.com/ansible-collections/community.general/pull/3968).
- mattermost - add the possibility to send attachments instead of text messages
(https://github.com/ansible-collections/community.general/pull/3946).
- nmcli - add ``wireguard`` connection type (https://github.com/ansible-collections/community.general/pull/3985).
- proxmox - add ``clone`` parameter (https://github.com/ansible-collections/community.general/pull/3930).
- puppet - remove deprecation for ``show_diff`` parameter. Its alias ``show-diff``
is still deprecated and will be removed in community.general 7.0.0 (https://github.com/ansible-collections/community.general/pull/3980).
- scaleway_compute - add possibility to use project identifier (new ``project``
option) instead of deprecated organization identifier (https://github.com/ansible-collections/community.general/pull/3951).
- scaleway_volume - all volumes are systematically created on par1 (https://github.com/ansible-collections/community.general/pull/3964).
release_summary: Regular feature and bugfix release.
fragments:
- 3374-add-ipa-ptr-sync-support.yml
- 3921-add-counter-filter-plugin.yml
- 3930-proxmox-add-clone.yaml
- 3933-slack-charset-header.yaml
- 3934-distutils.yml
- 3936-distutils.version.yml
- 3940_fix_contenttype_scaleway_user_data.yml
- 3946-mattermost_attachments.yml
- 3951-scaleway_compute_add_project_id.yml
- 3964-scaleway_volume_add_region.yml
- 3968-ipmi_power-add-machine-option.yaml
- 3976-fix-alternatives-parsing.yml
- 3980-puppet-show_diff.yml
- 3985-nmcli-add-wireguard-connection-type.yml
- 4.3.0.yml
modules:
- description: Manage Rust packages with cargo
name: cargo
namespace: packaging.language
- description: Allows obtaining Keycloak realm public information via Keycloak
API
name: keycloak_realm_info
namespace: identity.keycloak
- description: Manage sudoers files
name: sudoers
namespace: system
plugins:
filter:
- description: Counts hashable elements in a sequence
name: counter
namespace: null
release_date: '2022-01-11'

View File

@@ -297,6 +297,84 @@ This produces:
.. versionadded: 2.0.0 .. versionadded: 2.0.0
Counting elements in a sequence
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``community.general.counter`` filter plugin allows you to count (hashable) elements in a sequence. Elements are returned as dictionary keys and their counts are stored as dictionary values.
.. code-block:: yaml+jinja
- name: Count character occurrences in a string
debug:
msg: "{{ 'abccbaabca' | community.general.counter }}"
- name: Count items in a list
debug:
msg: "{{ ['car', 'car', 'bike', 'plane', 'bike'] | community.general.counter }}"
This produces:
.. code-block:: ansible-output
TASK [Count character occurrences in a string] ********************************************
ok: [localhost] => {
"msg": {
"a": 4,
"b": 3,
"c": 3
}
}
TASK [Count items in a list] **************************************************************
ok: [localhost] => {
"msg": {
"bike": 2,
"car": 2,
"plane": 1
}
}
This plugin is useful for selecting resources based on current allocation:
.. code-block:: yaml+jinja
- name: Get ID of SCSI controller(s) with less than 4 disks attached and choose the one with the least disks
debug:
msg: >-
{{
( disks | dict2items | map(attribute='value.adapter') | list
| community.general.counter | dict2items
| rejectattr('value', '>=', 4) | sort(attribute='value') | first
).key
}}
vars:
disks:
sda:
adapter: scsi_1
sdb:
adapter: scsi_1
sdc:
adapter: scsi_1
sdd:
adapter: scsi_1
sde:
adapter: scsi_2
sdf:
adapter: scsi_3
sdg:
adapter: scsi_3
This produces:
.. code-block:: ansible-output
TASK [Get ID of SCSI controller(s) with less than 4 disks attached and choose the one with the least disks]
ok: [localhost] => {
"msg": "scsi_2"
}
.. versionadded:: 4.3.0
Working with times Working with times
------------------ ------------------

View File

@@ -1,6 +1,6 @@
namespace: community namespace: community
name: general name: general
version: 4.1.0 version: 4.3.0
readme: README.md readme: README.md
authors: authors:
- Ansible (https://github.com/ansible) - Ansible (https://github.com/ansible)

View File

@@ -70,6 +70,7 @@ import os
import json import json
from ansible.module_utils.six.moves.urllib.parse import urlencode from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils.urls import open_url from ansible.module_utils.urls import open_url
from ansible.plugins.callback import CallbackBase from ansible.plugins.callback import CallbackBase
@@ -143,7 +144,7 @@ class CallbackModule(CallbackBase):
body = { body = {
'cmd': 'submitcheck', 'cmd': 'submitcheck',
'token': self.token, 'token': self.token,
'XMLDATA': bytes(xmldata) 'XMLDATA': to_bytes(xmldata)
} }
try: try:

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# (C) 2021, Victor Martinez <VictorMartinezRubio@gmail.com> # (C) 2021, Victor Martinez <VictorMartinezRubio@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -267,6 +268,8 @@ class OpenTelemetrySource(object):
elif host_data.status == 'skipped': elif host_data.status == 'skipped':
message = res['skip_reason'] if 'skip_reason' in res else 'skipped' message = res['skip_reason'] if 'skip_reason' in res else 'skipped'
status = Status(status_code=StatusCode.UNSET) status = Status(status_code=StatusCode.UNSET)
elif host_data.status == 'ignored':
status = Status(status_code=StatusCode.UNSET)
span.set_status(status) span.set_status(status)
if isinstance(task_data.args, dict) and "gather_facts" not in task_data.action: if isinstance(task_data.args, dict) and "gather_facts" not in task_data.action:
@@ -462,10 +465,15 @@ class CallbackModule(CallbackBase):
) )
def v2_runner_on_failed(self, result, ignore_errors=False): def v2_runner_on_failed(self, result, ignore_errors=False):
self.errors += 1 if ignore_errors:
status = 'ignored'
else:
status = 'failed'
self.errors += 1
self.opentelemetry.finish_task( self.opentelemetry.finish_task(
self.tasks_data, self.tasks_data,
'failed', status,
result result
) )

View File

@@ -21,11 +21,11 @@ DOCUMENTATION = '''
- In 2.8, this callback has been renamed from C(osx_say) into M(community.general.say). - In 2.8, this callback has been renamed from C(osx_say) into M(community.general.say).
''' '''
import distutils.spawn
import platform import platform
import subprocess import subprocess
import os import os
from ansible.module_utils.common.process import get_bin_path
from ansible.plugins.callback import CallbackBase from ansible.plugins.callback import CallbackBase
@@ -47,21 +47,24 @@ class CallbackModule(CallbackBase):
self.HAPPY_VOICE = None self.HAPPY_VOICE = None
self.LASER_VOICE = None self.LASER_VOICE = None
self.synthesizer = distutils.spawn.find_executable('say') try:
if not self.synthesizer: self.synthesizer = get_bin_path('say')
self.synthesizer = distutils.spawn.find_executable('espeak') if platform.system() != 'Darwin':
if self.synthesizer: # 'say' binary available, it might be GNUstep tool which doesn't support 'voice' parameter
self._display.warning("'say' executable found but system is '%s': ignoring voice parameter" % platform.system())
else:
self.FAILED_VOICE = 'Zarvox'
self.REGULAR_VOICE = 'Trinoids'
self.HAPPY_VOICE = 'Cellos'
self.LASER_VOICE = 'Princess'
except ValueError:
try:
self.synthesizer = get_bin_path('espeak')
self.FAILED_VOICE = 'klatt' self.FAILED_VOICE = 'klatt'
self.HAPPY_VOICE = 'f5' self.HAPPY_VOICE = 'f5'
self.LASER_VOICE = 'whisper' self.LASER_VOICE = 'whisper'
elif platform.system() != 'Darwin': except ValueError:
# 'say' binary available, it might be GNUstep tool which doesn't support 'voice' parameter self.synthesizer = None
self._display.warning("'say' executable found but system is '%s': ignoring voice parameter" % platform.system())
else:
self.FAILED_VOICE = 'Zarvox'
self.REGULAR_VOICE = 'Trinoids'
self.HAPPY_VOICE = 'Cellos'
self.LASER_VOICE = 'Princess'
# plugin disable itself if say is not present # plugin disable itself if say is not present
# ansible will not call any callback if disabled is set to True # ansible will not call any callback if disabled is set to True

View File

@@ -31,7 +31,6 @@ DOCUMENTATION = '''
- name: ansible_jail_user - name: ansible_jail_user
''' '''
import distutils.spawn
import os import os
import os.path import os.path
import subprocess import subprocess
@@ -39,6 +38,7 @@ import traceback
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.module_utils.six.moves import shlex_quote from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils.common.process import get_bin_path
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.plugins.connection import ConnectionBase, BUFSIZE from ansible.plugins.connection import ConnectionBase, BUFSIZE
from ansible.utils.display import Display from ansible.utils.display import Display
@@ -75,10 +75,10 @@ class Connection(ConnectionBase):
@staticmethod @staticmethod
def _search_executable(executable): def _search_executable(executable):
cmd = distutils.spawn.find_executable(executable) try:
if not cmd: return get_bin_path(executable)
except ValueError:
raise AnsibleError("%s command not found in PATH" % executable) raise AnsibleError("%s command not found in PATH" % executable)
return cmd
def list_jails(self): def list_jails(self):
p = subprocess.Popen([self.jls_cmd, '-q', 'name'], p = subprocess.Popen([self.jls_cmd, '-q', 'name'],

View File

@@ -43,10 +43,10 @@ DOCUMENTATION = '''
''' '''
import os import os
from distutils.spawn import find_executable
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
from ansible.module_utils.common.process import get_bin_path
from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible.plugins.connection import ConnectionBase from ansible.plugins.connection import ConnectionBase
@@ -62,9 +62,9 @@ class Connection(ConnectionBase):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
self._host = self._play_context.remote_addr self._host = self._play_context.remote_addr
self._lxc_cmd = find_executable("lxc") try:
self._lxc_cmd = get_bin_path("lxc")
if not self._lxc_cmd: except ValueError:
raise AnsibleError("lxc command not found in PATH") raise AnsibleError("lxc command not found in PATH")
if self._play_context.remote_user is not None and self._play_context.remote_user != 'root': if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
@@ -89,9 +89,9 @@ class Connection(ConnectionBase):
local_cmd.extend(["--project", self.get_option("project")]) local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([ local_cmd.extend([
"exec", "exec",
"%s:%s" % (self.get_option("remote"), self._host), "%s:%s" % (self.get_option("remote"), self.get_option("remote_addr")),
"--", "--",
self._play_context.executable, "-c", cmd self.get_option("executable"), "-c", cmd
]) ])
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
@@ -126,7 +126,7 @@ class Connection(ConnectionBase):
local_cmd.extend([ local_cmd.extend([
"file", "push", "file", "push",
in_path, in_path,
"%s:%s/%s" % (self.get_option("remote"), self._host, out_path) "%s:%s/%s" % (self.get_option("remote"), self.get_option("remote_addr"), out_path)
]) ])
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
@@ -145,7 +145,7 @@ class Connection(ConnectionBase):
local_cmd.extend(["--project", self.get_option("project")]) local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([ local_cmd.extend([
"file", "pull", "file", "pull",
"%s:%s/%s" % (self.get_option("remote"), self._host, in_path), "%s:%s/%s" % (self.get_option("remote"), self.get_option("remote_addr"), in_path),
out_path out_path
]) ])

View File

@@ -26,7 +26,6 @@ DOCUMENTATION = '''
- name: ansible_zone_host - name: ansible_zone_host
''' '''
import distutils.spawn
import os import os
import os.path import os.path
import subprocess import subprocess
@@ -34,6 +33,7 @@ import traceback
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.module_utils.six.moves import shlex_quote from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils.common.process import get_bin_path
from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.common.text.converters import to_bytes
from ansible.plugins.connection import ConnectionBase, BUFSIZE from ansible.plugins.connection import ConnectionBase, BUFSIZE
from ansible.utils.display import Display from ansible.utils.display import Display
@@ -64,10 +64,10 @@ class Connection(ConnectionBase):
@staticmethod @staticmethod
def _search_executable(executable): def _search_executable(executable):
cmd = distutils.spawn.find_executable(executable) try:
if not cmd: return get_bin_path(executable)
except ValueError:
raise AnsibleError("%s command not found in PATH" % executable) raise AnsibleError("%s command not found in PATH" % executable)
return cmd
def list_zones(self): def list_zones(self):
process = subprocess.Popen([self.zoneadm_cmd, 'list', '-ip'], process = subprocess.Popen([self.zoneadm_cmd, 'list', '-ip'],

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = r'''
requirements:
- requests (Python library U(https://pypi.org/project/requests/))
options:
api_token:
description:
- GitLab access token with API permissions.
type: str
api_oauth_token:
description:
- GitLab OAuth token for logging in.
type: str
version_added: 4.2.0
api_job_token:
description:
- GitLab CI job token for logging in.
type: str
version_added: 4.2.0
'''

36
plugins/filter/counter.py Normal file
View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Remy Keil <remy.keil@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common._collections_compat import Sequence
from collections import Counter
def counter(sequence):
''' Count elements in a sequence. Returns dict with count result. '''
if not isinstance(sequence, Sequence):
raise AnsibleFilterError('Argument for community.general.counter must be a sequence (string or list). %s is %s' %
(sequence, type(sequence)))
try:
result = dict(Counter(sequence))
except TypeError as e:
raise AnsibleFilterError(
"community.general.counter needs a sequence with hashable elements (int, float or str) - %s" % (e)
)
return result
class FilterModule(object):
''' Ansible counter jinja2 filters '''
def filters(self):
filters = {
'counter': counter,
}
return filters

View File

@@ -5,7 +5,7 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from distutils.version import LooseVersion from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
def version_sort(value, reverse=False): def version_sort(value, reverse=False):

View File

@@ -68,7 +68,6 @@ user: ansible-tester
password: secure password: secure
''' '''
from distutils.version import LooseVersion
import socket import socket
from ansible.errors import AnsibleError from ansible.errors import AnsibleError

View File

@@ -35,13 +35,23 @@ DOCUMENTATION = '''
type: string type: string
required: true required: true
host_filter: host_filter:
description: An Icinga2 API valid host filter. description:
- An Icinga2 API valid host filter. Leave blank for no filtering
type: string type: string
required: false required: false
validate_certs: validate_certs:
description: Enables or disables SSL certificate verification. description: Enables or disables SSL certificate verification.
type: boolean type: boolean
default: true default: true
inventory_attr:
description:
- Allows the override of the inventory name based on different attributes.
- This allows for changing the way limits are used.
- The current default, C(address), is sometimes not unique or present. We recommend to use C(name) instead.
type: string
default: address
choices: ['name', 'display_name', 'address']
version_added: 4.2.0
''' '''
EXAMPLES = r''' EXAMPLES = r'''
@@ -52,6 +62,7 @@ user: ansible
password: secure password: secure
host_filter: \"linux-servers\" in host.groups host_filter: \"linux-servers\" in host.groups
validate_certs: false validate_certs: false
inventory_attr: name
''' '''
import json import json
@@ -59,6 +70,7 @@ import json
from ansible.errors import AnsibleParserError from ansible.errors import AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.module_utils.urls import open_url from ansible.module_utils.urls import open_url
from ansible.module_utils.six.moves.urllib.error import HTTPError
class InventoryModule(BaseInventoryPlugin, Constructable): class InventoryModule(BaseInventoryPlugin, Constructable):
@@ -76,6 +88,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
self.icinga2_password = None self.icinga2_password = None
self.ssl_verify = None self.ssl_verify = None
self.host_filter = None self.host_filter = None
self.inventory_attr = None
self.cache_key = None self.cache_key = None
self.use_cache = None self.use_cache = None
@@ -114,9 +127,21 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
if data is not None: if data is not None:
request_args['data'] = json.dumps(data) request_args['data'] = json.dumps(data)
self.display.vvv("Request Args: %s" % request_args) self.display.vvv("Request Args: %s" % request_args)
response = open_url(request_url, **request_args) try:
response = open_url(request_url, **request_args)
except HTTPError as e:
try:
error_body = json.loads(e.read().decode())
self.display.vvv("Error returned: {0}".format(error_body))
except Exception:
error_body = {"status": None}
if e.code == 404 and error_body.get('status') == "No objects found.":
raise AnsibleParserError("Host filter returned no data. Please confirm your host_filter value is valid")
raise AnsibleParserError("Unexpected data returned: {0} -- {1}".format(e, error_body))
response_body = response.read() response_body = response.read()
json_data = json.loads(response_body.decode('utf-8')) json_data = json.loads(response_body.decode('utf-8'))
self.display.vvv("Returned Data: %s" % json.dumps(json_data, indent=4, sort_keys=True))
if 200 <= response.status <= 299: if 200 <= response.status <= 299:
return json_data return json_data
if response.status == 404 and json_data['status'] == "No objects found.": if response.status == 404 and json_data['status'] == "No objects found.":
@@ -155,7 +180,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
"""Query for all hosts """ """Query for all hosts """
self.display.vvv("Querying Icinga2 for inventory") self.display.vvv("Querying Icinga2 for inventory")
query_args = { query_args = {
"attrs": ["address", "state_type", "state", "groups"], "attrs": ["address", "display_name", "state_type", "state", "groups"],
} }
if self.host_filter is not None: if self.host_filter is not None:
query_args['host_filter'] = self.host_filter query_args['host_filter'] = self.host_filter
@@ -177,24 +202,35 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
"""Convert Icinga2 API data to JSON format for Ansible""" """Convert Icinga2 API data to JSON format for Ansible"""
groups_dict = {"_meta": {"hostvars": {}}} groups_dict = {"_meta": {"hostvars": {}}}
for entry in json_data: for entry in json_data:
host_name = entry['name']
host_attrs = entry['attrs'] host_attrs = entry['attrs']
if self.inventory_attr == "name":
host_name = entry.get('name')
if self.inventory_attr == "address":
# When looking for address for inventory, if missing fallback to object name
if host_attrs.get('address', '') != '':
host_name = host_attrs.get('address')
else:
host_name = entry.get('name')
if self.inventory_attr == "display_name":
host_name = host_attrs.get('display_name')
if host_attrs['state'] == 0: if host_attrs['state'] == 0:
host_attrs['state'] = 'on' host_attrs['state'] = 'on'
else: else:
host_attrs['state'] = 'off' host_attrs['state'] = 'off'
host_groups = host_attrs['groups'] host_groups = host_attrs.get('groups')
host_addr = host_attrs['address'] self.inventory.add_host(host_name)
self.inventory.add_host(host_addr)
for group in host_groups: for group in host_groups:
if group not in self.inventory.groups.keys(): if group not in self.inventory.groups.keys():
self.inventory.add_group(group) self.inventory.add_group(group)
self.inventory.add_child(group, host_addr) self.inventory.add_child(group, host_name)
self.inventory.set_variable(host_addr, 'address', host_addr) # If the address attribute is populated, override ansible_host with the value
self.inventory.set_variable(host_addr, 'hostname', host_name) if host_attrs.get('address') != '':
self.inventory.set_variable(host_addr, 'state', self.inventory.set_variable(host_name, 'ansible_host', host_attrs.get('address'))
self.inventory.set_variable(host_name, 'hostname', entry.get('name'))
self.inventory.set_variable(host_name, 'display_name', host_attrs.get('display_name'))
self.inventory.set_variable(host_name, 'state',
host_attrs['state']) host_attrs['state'])
self.inventory.set_variable(host_addr, 'state_type', self.inventory.set_variable(host_name, 'state_type',
host_attrs['state_type']) host_attrs['state_type'])
return groups_dict return groups_dict
@@ -211,6 +247,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable):
self.icinga2_password = self.get_option('password') self.icinga2_password = self.get_option('password')
self.ssl_verify = self.get_option('validate_certs') self.ssl_verify = self.get_option('validate_certs')
self.host_filter = self.get_option('host_filter') self.host_filter = self.get_option('host_filter')
self.inventory_attr = self.get_option('inventory_attr')
# Not currently enabled # Not currently enabled
# self.cache_key = self.get_cache_key(path) # self.cache_key = self.get_cache_key(path)
# self.use_cache = cache and self.get_option('cache') # self.use_cache = cache and self.get_option('cache')

View File

@@ -15,6 +15,7 @@ DOCUMENTATION = r'''
author: "Frank Dornheim (@conloos)" author: "Frank Dornheim (@conloos)"
requirements: requirements:
- ipaddress - ipaddress
- lxd >= 4.0
options: options:
plugin: plugin:
description: Token that ensures this is a source file for the 'lxd' plugin. description: Token that ensures this is a source file for the 'lxd' plugin.
@@ -49,26 +50,38 @@ DOCUMENTATION = r'''
- If I(trust_password) is set, this module send a request for authentication before sending any requests. - If I(trust_password) is set, this module send a request for authentication before sending any requests.
type: str type: str
state: state:
description: Filter the container according to the current status. description: Filter the instance according to the current status.
type: str type: str
default: none default: none
choices: [ 'STOPPED', 'STARTING', 'RUNNING', 'none' ] choices: [ 'STOPPED', 'STARTING', 'RUNNING', 'none' ]
prefered_container_network_interface: type_filter:
description: description:
- If a container has multiple network interfaces, select which one is the prefered as pattern. - Filter the instances by type C(virtual-machine), C(container) or C(both).
- The first version of the inventory only supported containers.
type: str
default: container
choices: [ 'virtual-machine', 'container', 'both' ]
version_added: 4.2.0
prefered_instance_network_interface:
description:
- If an instance has multiple network interfaces, select which one is the prefered as pattern.
- Combined with the first number that can be found e.g. 'eth' + 0. - Combined with the first number that can be found e.g. 'eth' + 0.
- The option has been renamed from I(prefered_container_network_interface) to I(prefered_instance_network_interface) in community.general 3.8.0.
The old name still works as an alias.
type: str type: str
default: eth default: eth
prefered_container_network_family: aliases:
- prefered_container_network_interface
prefered_instance_network_family:
description: description:
- If a container has multiple network interfaces, which one is the prefered by family. - If an instance has multiple network interfaces, which one is the prefered by family.
- Specify C(inet) for IPv4 and C(inet6) for IPv6. - Specify C(inet) for IPv4 and C(inet6) for IPv6.
type: str type: str
default: inet default: inet
choices: [ 'inet', 'inet6' ] choices: [ 'inet', 'inet6' ]
groupby: groupby:
description: description:
- Create groups by the following keywords C(location), C(pattern), C(network_range), C(os), C(release), C(profile), C(vlanid). - Create groups by the following keywords C(location), C(network_range), C(os), C(pattern), C(profile), C(release), C(type), C(vlanid).
- See example for syntax. - See example for syntax.
type: dict type: dict
''' '''
@@ -83,38 +96,49 @@ plugin: community.general.lxd
url: unix:/var/snap/lxd/common/lxd/unix.socket url: unix:/var/snap/lxd/common/lxd/unix.socket
state: RUNNING state: RUNNING
# simple lxd.yml including virtual machines and containers
plugin: community.general.lxd
url: unix:/var/snap/lxd/common/lxd/unix.socket
type_filter: both
# grouping lxd.yml # grouping lxd.yml
groupby: groupby:
testpattern:
type: pattern
attribute: test
vlan666:
type: vlanid
attribute: 666
locationBerlin: locationBerlin:
type: location type: location
attribute: Berlin attribute: Berlin
osUbuntu:
type: os
attribute: ubuntu
releaseFocal:
type: release
attribute: focal
releaseBionic:
type: release
attribute: bionic
profileDefault:
type: profile
attribute: default
profileX11:
type: profile
attribute: x11
netRangeIPv4: netRangeIPv4:
type: network_range type: network_range
attribute: 10.98.143.0/24 attribute: 10.98.143.0/24
netRangeIPv6: netRangeIPv6:
type: network_range type: network_range
attribute: fd42:bd00:7b11:2167:216:3eff::/24 attribute: fd42:bd00:7b11:2167:216:3eff::/24
osUbuntu:
type: os
attribute: ubuntu
testpattern:
type: pattern
attribute: test
profileDefault:
type: profile
attribute: default
profileX11:
type: profile
attribute: x11
releaseFocal:
type: release
attribute: focal
releaseBionic:
type: release
attribute: bionic
typeVM:
type: type
attribute: virtual-machine
typeContainer:
type: type
attribute: container
vlan666:
type: vlanid
attribute: 666
''' '''
import binascii import binascii
@@ -283,10 +307,10 @@ class InventoryModule(BaseInventoryPlugin):
network_configs = self.socket.do('GET', '/1.0/networks') network_configs = self.socket.do('GET', '/1.0/networks')
return [m.split('/')[3] for m in network_configs['metadata']] return [m.split('/')[3] for m in network_configs['metadata']]
def _get_containers(self): def _get_instances(self):
"""Get Containernames """Get instancenames
Returns all containernames Returns all instancenames
Args: Args:
None None
@@ -295,25 +319,27 @@ class InventoryModule(BaseInventoryPlugin):
Raises: Raises:
None None
Returns: Returns:
list(names): names of all containers""" list(names): names of all instances"""
# e.g. {'type': 'sync', # e.g. {
# 'status': 'Success', # "metadata": [
# 'status_code': 200, # "/1.0/instances/foo",
# 'operation': '', # "/1.0/instances/bar"
# 'error_code': 0, # ],
# 'error': '', # "status": "Success",
# 'metadata': ['/1.0/containers/udemy-ansible-ubuntu-2004']} # "status_code": 200,
containers = self.socket.do('GET', '/1.0/containers') # "type": "sync"
return [m.split('/')[3] for m in containers['metadata']] # }
instances = self.socket.do('GET', '/1.0/instances')
return [m.split('/')[3] for m in instances['metadata']]
def _get_config(self, branch, name): def _get_config(self, branch, name):
"""Get inventory of container """Get inventory of instance
Get config of container Get config of instance
Args: Args:
str(branch): Name oft the API-Branch str(branch): Name oft the API-Branch
str(name): Name of Container str(name): Name of instance
Kwargs: Kwargs:
None None
Source: Source:
@@ -321,7 +347,7 @@ class InventoryModule(BaseInventoryPlugin):
Raises: Raises:
None None
Returns: Returns:
dict(config): Config of the container""" dict(config): Config of the instance"""
config = {} config = {}
if isinstance(branch, (tuple, list)): if isinstance(branch, (tuple, list)):
config[name] = {branch[1]: self.socket.do('GET', '/1.0/{0}/{1}/{2}'.format(to_native(branch[0]), to_native(name), to_native(branch[1])))} config[name] = {branch[1]: self.socket.do('GET', '/1.0/{0}/{1}/{2}'.format(to_native(branch[0]), to_native(name), to_native(branch[1])))}
@@ -329,13 +355,13 @@ class InventoryModule(BaseInventoryPlugin):
config[name] = {branch: self.socket.do('GET', '/1.0/{0}/{1}'.format(to_native(branch), to_native(name)))} config[name] = {branch: self.socket.do('GET', '/1.0/{0}/{1}'.format(to_native(branch), to_native(name)))}
return config return config
def get_container_data(self, names): def get_instance_data(self, names):
"""Create Inventory of the container """Create Inventory of the instance
Iterate through the different branches of the containers and collect Informations. Iterate through the different branches of the instances and collect Informations.
Args: Args:
list(names): List of container names list(names): List of instance names
Kwargs: Kwargs:
None None
Raises: Raises:
@@ -344,20 +370,20 @@ class InventoryModule(BaseInventoryPlugin):
None""" None"""
# tuple(('instances','metadata/templates')) to get section in branch # tuple(('instances','metadata/templates')) to get section in branch
# e.g. /1.0/instances/<name>/metadata/templates # e.g. /1.0/instances/<name>/metadata/templates
branches = ['containers', ('instances', 'state')] branches = ['instances', ('instances', 'state')]
container_config = {} instance_config = {}
for branch in branches: for branch in branches:
for name in names: for name in names:
container_config['containers'] = self._get_config(branch, name) instance_config['instances'] = self._get_config(branch, name)
self.data = dict_merge(container_config, self.data) self.data = dict_merge(instance_config, self.data)
def get_network_data(self, names): def get_network_data(self, names):
"""Create Inventory of the container """Create Inventory of the instance
Iterate through the different branches of the containers and collect Informations. Iterate through the different branches of the instances and collect Informations.
Args: Args:
list(names): List of container names list(names): List of instance names
Kwargs: Kwargs:
None None
Raises: Raises:
@@ -376,26 +402,26 @@ class InventoryModule(BaseInventoryPlugin):
network_config['networks'] = {name: None} network_config['networks'] = {name: None}
self.data = dict_merge(network_config, self.data) self.data = dict_merge(network_config, self.data)
def extract_network_information_from_container_config(self, container_name): def extract_network_information_from_instance_config(self, instance_name):
"""Returns the network interface configuration """Returns the network interface configuration
Returns the network ipv4 and ipv6 config of the container without local-link Returns the network ipv4 and ipv6 config of the instance without local-link
Args: Args:
str(container_name): Name oft he container str(instance_name): Name oft he instance
Kwargs: Kwargs:
None None
Raises: Raises:
None None
Returns: Returns:
dict(network_configuration): network config""" dict(network_configuration): network config"""
container_network_interfaces = self._get_data_entry('containers/{0}/state/metadata/network'.format(container_name)) instance_network_interfaces = self._get_data_entry('instances/{0}/state/metadata/network'.format(instance_name))
network_configuration = None network_configuration = None
if container_network_interfaces: if instance_network_interfaces:
network_configuration = {} network_configuration = {}
gen_interface_names = [interface_name for interface_name in container_network_interfaces if interface_name != 'lo'] gen_interface_names = [interface_name for interface_name in instance_network_interfaces if interface_name != 'lo']
for interface_name in gen_interface_names: for interface_name in gen_interface_names:
gen_address = [address for address in container_network_interfaces[interface_name]['addresses'] if address.get('scope') != 'link'] gen_address = [address for address in instance_network_interfaces[interface_name]['addresses'] if address.get('scope') != 'link']
network_configuration[interface_name] = [] network_configuration[interface_name] = []
for address in gen_address: for address in gen_address:
address_set = {} address_set = {}
@@ -406,24 +432,24 @@ class InventoryModule(BaseInventoryPlugin):
network_configuration[interface_name].append(address_set) network_configuration[interface_name].append(address_set)
return network_configuration return network_configuration
def get_prefered_container_network_interface(self, container_name): def get_prefered_instance_network_interface(self, instance_name):
"""Helper to get the prefered interface of thr container """Helper to get the prefered interface of thr instance
Helper to get the prefered interface provide by neme pattern from 'prefered_container_network_interface'. Helper to get the prefered interface provide by neme pattern from 'prefered_instance_network_interface'.
Args: Args:
str(containe_name): name of container str(containe_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
None None
Returns: Returns:
str(prefered_interface): None or interface name""" str(prefered_interface): None or interface name"""
container_network_interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name)) instance_network_interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(instance_name))
prefered_interface = None # init prefered_interface = None # init
if container_network_interfaces: # container have network interfaces if instance_network_interfaces: # instance have network interfaces
# generator if interfaces which start with the desired pattern # generator if interfaces which start with the desired pattern
net_generator = [interface for interface in container_network_interfaces if interface.startswith(self.prefered_container_network_interface)] net_generator = [interface for interface in instance_network_interfaces if interface.startswith(self.prefered_instance_network_interface)]
selected_interfaces = [] # init selected_interfaces = [] # init
for interface in net_generator: for interface in net_generator:
selected_interfaces.append(interface) selected_interfaces.append(interface)
@@ -431,13 +457,13 @@ class InventoryModule(BaseInventoryPlugin):
prefered_interface = sorted(selected_interfaces)[0] prefered_interface = sorted(selected_interfaces)[0]
return prefered_interface return prefered_interface
def get_container_vlans(self, container_name): def get_instance_vlans(self, instance_name):
"""Get VLAN(s) from container """Get VLAN(s) from instance
Helper to get the VLAN_ID from the container Helper to get the VLAN_ID from the instance
Args: Args:
str(containe_name): name of container str(containe_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
@@ -450,13 +476,13 @@ class InventoryModule(BaseInventoryPlugin):
if self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network)): if self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network)):
network_vlans[network] = self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network)) network_vlans[network] = self._get_data_entry('state/metadata/vlan/vid', data=self.data['networks'].get(network))
# get networkdevices of container and return # get networkdevices of instance and return
# e.g. # e.g.
# "eth0":{ "name":"eth0", # "eth0":{ "name":"eth0",
# "network":"lxdbr0", # "network":"lxdbr0",
# "type":"nic"}, # "type":"nic"},
vlan_ids = {} vlan_ids = {}
devices = self._get_data_entry('containers/{0}/containers/metadata/expanded_devices'.format(to_native(container_name))) devices = self._get_data_entry('instances/{0}/instances/metadata/expanded_devices'.format(to_native(instance_name)))
for device in devices: for device in devices:
if 'network' in devices[device]: if 'network' in devices[device]:
if devices[device]['network'] in network_vlans: if devices[device]['network'] in network_vlans:
@@ -492,14 +518,14 @@ class InventoryModule(BaseInventoryPlugin):
except KeyError: except KeyError:
return None return None
def _set_data_entry(self, container_name, key, value, path=None): def _set_data_entry(self, instance_name, key, value, path=None):
"""Helper to save data """Helper to save data
Helper to save the data in self.data Helper to save the data in self.data
Detect if data is allready in branch and use dict_merge() to prevent that branch is overwritten. Detect if data is allready in branch and use dict_merge() to prevent that branch is overwritten.
Args: Args:
str(container_name): name of container str(instance_name): name of instance
str(key): same as dict str(key): same as dict
*(value): same as dict *(value): same as dict
Kwargs: Kwargs:
@@ -510,24 +536,24 @@ class InventoryModule(BaseInventoryPlugin):
None""" None"""
if not path: if not path:
path = self.data['inventory'] path = self.data['inventory']
if container_name not in path: if instance_name not in path:
path[container_name] = {} path[instance_name] = {}
try: try:
if isinstance(value, dict) and key in path[container_name]: if isinstance(value, dict) and key in path[instance_name]:
path[container_name] = dict_merge(value, path[container_name][key]) path[instance_name] = dict_merge(value, path[instance_name][key])
else: else:
path[container_name][key] = value path[instance_name][key] = value
except KeyError as err: except KeyError as err:
raise AnsibleParserError("Unable to store Informations: {0}".format(to_native(err))) raise AnsibleParserError("Unable to store Informations: {0}".format(to_native(err)))
def extract_information_from_container_configs(self): def extract_information_from_instance_configs(self):
"""Process configuration information """Process configuration information
Preparation of the data Preparation of the data
Args: Args:
dict(configs): Container configurations dict(configs): instance configurations
Kwargs: Kwargs:
None None
Raises: Raises:
@@ -538,33 +564,35 @@ class InventoryModule(BaseInventoryPlugin):
if 'inventory' not in self.data: if 'inventory' not in self.data:
self.data['inventory'] = {} self.data['inventory'] = {}
for container_name in self.data['containers']: for instance_name in self.data['instances']:
self._set_data_entry(container_name, 'os', self._get_data_entry( self._set_data_entry(instance_name, 'os', self._get_data_entry(
'containers/{0}/containers/metadata/config/image.os'.format(container_name))) 'instances/{0}/instances/metadata/config/image.os'.format(instance_name)))
self._set_data_entry(container_name, 'release', self._get_data_entry( self._set_data_entry(instance_name, 'release', self._get_data_entry(
'containers/{0}/containers/metadata/config/image.release'.format(container_name))) 'instances/{0}/instances/metadata/config/image.release'.format(instance_name)))
self._set_data_entry(container_name, 'version', self._get_data_entry( self._set_data_entry(instance_name, 'version', self._get_data_entry(
'containers/{0}/containers/metadata/config/image.version'.format(container_name))) 'instances/{0}/instances/metadata/config/image.version'.format(instance_name)))
self._set_data_entry(container_name, 'profile', self._get_data_entry( self._set_data_entry(instance_name, 'profile', self._get_data_entry(
'containers/{0}/containers/metadata/profiles'.format(container_name))) 'instances/{0}/instances/metadata/profiles'.format(instance_name)))
self._set_data_entry(container_name, 'location', self._get_data_entry( self._set_data_entry(instance_name, 'location', self._get_data_entry(
'containers/{0}/containers/metadata/location'.format(container_name))) 'instances/{0}/instances/metadata/location'.format(instance_name)))
self._set_data_entry(container_name, 'state', self._get_data_entry( self._set_data_entry(instance_name, 'state', self._get_data_entry(
'containers/{0}/containers/metadata/config/volatile.last_state.power'.format(container_name))) 'instances/{0}/instances/metadata/config/volatile.last_state.power'.format(instance_name)))
self._set_data_entry(container_name, 'network_interfaces', self.extract_network_information_from_container_config(container_name)) self._set_data_entry(instance_name, 'type', self._get_data_entry(
self._set_data_entry(container_name, 'preferred_interface', self.get_prefered_container_network_interface(container_name)) 'instances/{0}/instances/metadata/type'.format(instance_name)))
self._set_data_entry(container_name, 'vlan_ids', self.get_container_vlans(container_name)) self._set_data_entry(instance_name, 'network_interfaces', self.extract_network_information_from_instance_config(instance_name))
self._set_data_entry(instance_name, 'preferred_interface', self.get_prefered_instance_network_interface(instance_name))
self._set_data_entry(instance_name, 'vlan_ids', self.get_instance_vlans(instance_name))
def build_inventory_network(self, container_name): def build_inventory_network(self, instance_name):
"""Add the network interfaces of the container to the inventory """Add the network interfaces of the instance to the inventory
Logic: Logic:
- if the container have no interface -> 'ansible_connection: local' - if the instance have no interface -> 'ansible_connection: local'
- get preferred_interface & prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>' - get preferred_interface & prefered_instance_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
- first Interface from: network_interfaces prefered_container_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>' - first Interface from: network_interfaces prefered_instance_network_family -> 'ansible_connection: ssh' & 'ansible_host: <IP>'
Args: Args:
str(container_name): name of container str(instance_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
@@ -572,45 +600,45 @@ class InventoryModule(BaseInventoryPlugin):
Returns: Returns:
None""" None"""
def interface_selection(container_name): def interface_selection(instance_name):
"""Select container Interface for inventory """Select instance Interface for inventory
Logic: Logic:
- get preferred_interface & prefered_container_network_family -> str(IP) - get preferred_interface & prefered_instance_network_family -> str(IP)
- first Interface from: network_interfaces prefered_container_network_family -> str(IP) - first Interface from: network_interfaces prefered_instance_network_family -> str(IP)
Args: Args:
str(container_name): name of container str(instance_name): name of instance
Kwargs: Kwargs:
None None
Raises: Raises:
None None
Returns: Returns:
dict(interface_name: ip)""" dict(interface_name: ip)"""
prefered_interface = self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)) # name or None prefered_interface = self._get_data_entry('inventory/{0}/preferred_interface'.format(instance_name)) # name or None
prefered_container_network_family = self.prefered_container_network_family prefered_instance_network_family = self.prefered_instance_network_family
ip_address = '' ip_address = ''
if prefered_interface: if prefered_interface:
interface = self._get_data_entry('inventory/{0}/network_interfaces/{1}'.format(container_name, prefered_interface)) interface = self._get_data_entry('inventory/{0}/network_interfaces/{1}'.format(instance_name, prefered_interface))
for config in interface: for config in interface:
if config['family'] == prefered_container_network_family: if config['family'] == prefered_instance_network_family:
ip_address = config['address'] ip_address = config['address']
break break
else: else:
interface = self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name)) interfaces = self._get_data_entry('inventory/{0}/network_interfaces'.format(instance_name))
for config in interface: for interface in interfaces.values():
if config['family'] == prefered_container_network_family: for config in interface:
ip_address = config['address'] if config['family'] == prefered_instance_network_family:
break ip_address = config['address']
break
return ip_address return ip_address
if self._get_data_entry('inventory/{0}/network_interfaces'.format(container_name)): # container have network interfaces if self._get_data_entry('inventory/{0}/network_interfaces'.format(instance_name)): # instance have network interfaces
if self._get_data_entry('inventory/{0}/preferred_interface'.format(container_name)): # container have a preferred interface self.inventory.set_variable(instance_name, 'ansible_connection', 'ssh')
self.inventory.set_variable(container_name, 'ansible_connection', 'ssh') self.inventory.set_variable(instance_name, 'ansible_host', interface_selection(instance_name))
self.inventory.set_variable(container_name, 'ansible_host', interface_selection(container_name))
else: else:
self.inventory.set_variable(container_name, 'ansible_connection', 'local') self.inventory.set_variable(instance_name, 'ansible_connection', 'local')
def build_inventory_hosts(self): def build_inventory_hosts(self):
"""Build host-part dynamic inventory """Build host-part dynamic inventory
@@ -626,29 +654,33 @@ class InventoryModule(BaseInventoryPlugin):
None None
Returns: Returns:
None""" None"""
for container_name in self.data['inventory']: for instance_name in self.data['inventory']:
# Only consider containers that match the "state" filter, if self.state is not None instance_state = str(self._get_data_entry('inventory/{0}/state'.format(instance_name)) or "STOPPED").lower()
# Only consider instances that match the "state" filter, if self.state is not None
if self.filter: if self.filter:
if self.filter.lower() != self._get_data_entry('inventory/{0}/state'.format(container_name)).lower(): if self.filter.lower() != instance_state:
continue continue
# add container # add instance
self.inventory.add_host(container_name) self.inventory.add_host(instance_name)
# add network informations # add network informations
self.build_inventory_network(container_name) self.build_inventory_network(instance_name)
# add os # add os
self.inventory.set_variable(container_name, 'ansible_lxd_os', self._get_data_entry('inventory/{0}/os'.format(container_name)).lower()) self.inventory.set_variable(instance_name, 'ansible_lxd_os', self._get_data_entry('inventory/{0}/os'.format(instance_name)).lower())
# add release # add release
self.inventory.set_variable(container_name, 'ansible_lxd_release', self._get_data_entry('inventory/{0}/release'.format(container_name)).lower()) self.inventory.set_variable(instance_name, 'ansible_lxd_release', self._get_data_entry('inventory/{0}/release'.format(instance_name)).lower())
# add profile # add profile
self.inventory.set_variable(container_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(container_name))) self.inventory.set_variable(instance_name, 'ansible_lxd_profile', self._get_data_entry('inventory/{0}/profile'.format(instance_name)))
# add state # add state
self.inventory.set_variable(container_name, 'ansible_lxd_state', self._get_data_entry('inventory/{0}/state'.format(container_name)).lower()) self.inventory.set_variable(instance_name, 'ansible_lxd_state', instance_state)
# add type
self.inventory.set_variable(instance_name, 'ansible_lxd_type', self._get_data_entry('inventory/{0}/type'.format(instance_name)))
# add location information # add location information
if self._get_data_entry('inventory/{0}/location'.format(container_name)) != "none": # wrong type by lxd 'none' != 'None' if self._get_data_entry('inventory/{0}/location'.format(instance_name)) != "none": # wrong type by lxd 'none' != 'None'
self.inventory.set_variable(container_name, 'ansible_lxd_location', self._get_data_entry('inventory/{0}/location'.format(container_name))) self.inventory.set_variable(instance_name, 'ansible_lxd_location', self._get_data_entry('inventory/{0}/location'.format(instance_name)))
# add VLAN_ID information # add VLAN_ID information
if self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name)): if self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)):
self.inventory.set_variable(container_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(container_name))) self.inventory.set_variable(instance_name, 'ansible_lxd_vlan_ids', self._get_data_entry('inventory/{0}/vlan_ids'.format(instance_name)))
def build_inventory_groups_location(self, group_name): def build_inventory_groups_location(self, group_name):
"""create group by attribute: location """create group by attribute: location
@@ -665,9 +697,9 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
for container_name in self.inventory.hosts: for instance_name in self.inventory.hosts:
if 'ansible_lxd_location' in self.inventory.get_host(container_name).get_vars(): if 'ansible_lxd_location' in self.inventory.get_host(instance_name).get_vars():
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_pattern(self, group_name): def build_inventory_groups_pattern(self, group_name):
"""create group by name pattern """create group by name pattern
@@ -686,10 +718,10 @@ class InventoryModule(BaseInventoryPlugin):
regex_pattern = self.groupby[group_name].get('attribute') regex_pattern = self.groupby[group_name].get('attribute')
for container_name in self.inventory.hosts: for instance_name in self.inventory.hosts:
result = re.search(regex_pattern, container_name) result = re.search(regex_pattern, instance_name)
if result: if result:
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_network_range(self, group_name): def build_inventory_groups_network_range(self, group_name):
"""check if IP is in network-class """check if IP is in network-class
@@ -712,14 +744,14 @@ class InventoryModule(BaseInventoryPlugin):
raise AnsibleParserError( raise AnsibleParserError(
'Error while parsing network range {0}: {1}'.format(self.groupby[group_name].get('attribute'), to_native(err))) 'Error while parsing network range {0}: {1}'.format(self.groupby[group_name].get('attribute'), to_native(err)))
for container_name in self.inventory.hosts: for instance_name in self.inventory.hosts:
if self.data['inventory'][container_name].get('network_interfaces') is not None: if self.data['inventory'][instance_name].get('network_interfaces') is not None:
for interface in self.data['inventory'][container_name].get('network_interfaces'): for interface in self.data['inventory'][instance_name].get('network_interfaces'):
for interface_family in self.data['inventory'][container_name].get('network_interfaces')[interface]: for interface_family in self.data['inventory'][instance_name].get('network_interfaces')[interface]:
try: try:
address = ipaddress.ip_address(to_text(interface_family['address'])) address = ipaddress.ip_address(to_text(interface_family['address']))
if address.version == network.version and address in network: if address.version == network.version and address in network:
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
except ValueError: except ValueError:
# Ignore invalid IP addresses returned by lxd # Ignore invalid IP addresses returned by lxd
pass pass
@@ -730,7 +762,7 @@ class InventoryModule(BaseInventoryPlugin):
Args: Args:
str(group_name): Group name str(group_name): Group name
Kwargs: Kwargs:
Noneself.data['inventory'][container_name][interface] None
Raises: Raises:
None None
Returns: Returns:
@@ -739,12 +771,12 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts instance_name for instance_name in self.inventory.hosts
if 'ansible_lxd_os' in self.inventory.get_host(container_name).get_vars()] if 'ansible_lxd_os' in self.inventory.get_host(instance_name).get_vars()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_os'): if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_os'):
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_release(self, group_name): def build_inventory_groups_release(self, group_name):
"""create group by attribute: release """create group by attribute: release
@@ -761,12 +793,12 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts instance_name for instance_name in self.inventory.hosts
if 'ansible_lxd_release' in self.inventory.get_host(container_name).get_vars()] if 'ansible_lxd_release' in self.inventory.get_host(instance_name).get_vars()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(container_name).get_vars().get('ansible_lxd_release'): if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_release'):
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_profile(self, group_name): def build_inventory_groups_profile(self, group_name):
"""create group by attribute: profile """create group by attribute: profile
@@ -783,12 +815,12 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts.keys() instance_name for instance_name in self.inventory.hosts.keys()
if 'ansible_lxd_profile' in self.inventory.get_host(container_name).get_vars().keys()] if 'ansible_lxd_profile' in self.inventory.get_host(instance_name).get_vars().keys()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_profile'): if self.groupby[group_name].get('attribute').lower() in self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_profile'):
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_vlanid(self, group_name): def build_inventory_groups_vlanid(self, group_name):
"""create group by attribute: vlanid """create group by attribute: vlanid
@@ -805,12 +837,34 @@ class InventoryModule(BaseInventoryPlugin):
if group_name not in self.inventory.groups: if group_name not in self.inventory.groups:
self.inventory.add_group(group_name) self.inventory.add_group(group_name)
gen_containers = [ gen_instances = [
container_name for container_name in self.inventory.hosts.keys() instance_name for instance_name in self.inventory.hosts.keys()
if 'ansible_lxd_vlan_ids' in self.inventory.get_host(container_name).get_vars().keys()] if 'ansible_lxd_vlan_ids' in self.inventory.get_host(instance_name).get_vars().keys()]
for container_name in gen_containers: for instance_name in gen_instances:
if self.groupby[group_name].get('attribute') in self.inventory.get_host(container_name).get_vars().get('ansible_lxd_vlan_ids').values(): if self.groupby[group_name].get('attribute') in self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_vlan_ids').values():
self.inventory.add_child(group_name, container_name) self.inventory.add_child(group_name, instance_name)
def build_inventory_groups_type(self, group_name):
"""create group by attribute: type
Args:
str(group_name): Group name
Kwargs:
None
Raises:
None
Returns:
None"""
# maybe we just want to expand one group
if group_name not in self.inventory.groups:
self.inventory.add_group(group_name)
gen_instances = [
instance_name for instance_name in self.inventory.hosts
if 'ansible_lxd_type' in self.inventory.get_host(instance_name).get_vars()]
for instance_name in gen_instances:
if self.groupby[group_name].get('attribute').lower() == self.inventory.get_host(instance_name).get_vars().get('ansible_lxd_type'):
self.inventory.add_child(group_name, instance_name)
def build_inventory_groups(self): def build_inventory_groups(self):
"""Build group-part dynamic inventory """Build group-part dynamic inventory
@@ -839,6 +893,7 @@ class InventoryModule(BaseInventoryPlugin):
* 'release' * 'release'
* 'profile' * 'profile'
* 'vlanid' * 'vlanid'
* 'type'
Args: Args:
str(group_name): Group name str(group_name): Group name
@@ -864,6 +919,8 @@ class InventoryModule(BaseInventoryPlugin):
self.build_inventory_groups_profile(group_name) self.build_inventory_groups_profile(group_name)
elif self.groupby[group_name].get('type') == 'vlanid': elif self.groupby[group_name].get('type') == 'vlanid':
self.build_inventory_groups_vlanid(group_name) self.build_inventory_groups_vlanid(group_name)
elif self.groupby[group_name].get('type') == 'type':
self.build_inventory_groups_type(group_name)
else: else:
raise AnsibleParserError('Unknown group type: {0}'.format(to_native(group_name))) raise AnsibleParserError('Unknown group type: {0}'.format(to_native(group_name)))
@@ -890,10 +947,30 @@ class InventoryModule(BaseInventoryPlugin):
self.build_inventory_hosts() self.build_inventory_hosts()
self.build_inventory_groups() self.build_inventory_groups()
def cleandata(self):
"""Clean the dynamic inventory
The first version of the inventory only supported container.
This will change in the future.
The following function cleans up the data and remove the all items with the wrong type.
Args:
None
Kwargs:
None
Raises:
None
Returns:
None"""
iter_keys = list(self.data['instances'].keys())
for instance_name in iter_keys:
if self._get_data_entry('instances/{0}/instances/metadata/type'.format(instance_name)) != self.type_filter:
del self.data['instances'][instance_name]
def _populate(self): def _populate(self):
"""Return the hosts and groups """Return the hosts and groups
Returns the processed container configurations from the lxd import Returns the processed instance configurations from the lxd import
Args: Args:
None None
@@ -906,10 +983,16 @@ class InventoryModule(BaseInventoryPlugin):
if len(self.data) == 0: # If no data is injected by unittests open socket if len(self.data) == 0: # If no data is injected by unittests open socket
self.socket = self._connect_to_socket() self.socket = self._connect_to_socket()
self.get_container_data(self._get_containers()) self.get_instance_data(self._get_instances())
self.get_network_data(self._get_networks()) self.get_network_data(self._get_networks())
self.extract_information_from_container_configs() # The first version of the inventory only supported containers.
# This will change in the future.
# The following function cleans up the data.
if self.type_filter != 'both':
self.cleandata()
self.extract_information_from_instance_configs()
# self.display.vvv(self.save_json_data([os.path.abspath(__file__)])) # self.display.vvv(self.save_json_data([os.path.abspath(__file__)]))
@@ -948,8 +1031,9 @@ class InventoryModule(BaseInventoryPlugin):
self.data = {} # store for inventory-data self.data = {} # store for inventory-data
self.groupby = self.get_option('groupby') self.groupby = self.get_option('groupby')
self.plugin = self.get_option('plugin') self.plugin = self.get_option('plugin')
self.prefered_container_network_family = self.get_option('prefered_container_network_family') self.prefered_instance_network_family = self.get_option('prefered_instance_network_family')
self.prefered_container_network_interface = self.get_option('prefered_container_network_interface') self.prefered_instance_network_interface = self.get_option('prefered_instance_network_interface')
self.type_filter = self.get_option('type_filter')
if self.get_option('state').lower() == 'none': # none in config is str() if self.get_option('state').lower() == 'none': # none in config is str()
self.filter = None self.filter = None
else: else:

View File

@@ -8,7 +8,7 @@ __metaclass__ = type
DOCUMENTATION = r''' DOCUMENTATION = r'''
name: online name: online
author: author:
- Remy Leone (@sieben) - Remy Leone (@remyleone)
short_description: Scaleway (previously Online SAS or Online.net) inventory source short_description: Scaleway (previously Online SAS or Online.net) inventory source
description: description:
- Get inventory hosts from Scaleway (previously Online SAS or Online.net). - Get inventory hosts from Scaleway (previously Online SAS or Online.net).

View File

@@ -119,12 +119,13 @@ compose:
import re import re
from ansible.module_utils.common._collections_compat import MutableMapping from ansible.module_utils.common._collections_compat import MutableMapping
from distutils.version import LooseVersion
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.module_utils.six.moves.urllib.parse import urlencode from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
# 3rd party imports # 3rd party imports
try: try:
import requests import requests

View File

@@ -9,7 +9,7 @@ __metaclass__ = type
DOCUMENTATION = r''' DOCUMENTATION = r'''
name: scaleway name: scaleway
author: author:
- Remy Leone (@sieben) - Remy Leone (@remyleone)
short_description: Scaleway inventory source short_description: Scaleway inventory source
description: description:
- Get inventory hosts from Scaleway. - Get inventory hosts from Scaleway.

View File

@@ -79,11 +79,11 @@ simple_config_file:
import json import json
import ssl import ssl
from distutils.version import LooseVersion
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
# 3rd party imports # 3rd party imports
try: try:
HAS_WEBSOCKET = True HAS_WEBSOCKET = True

View File

@@ -93,7 +93,7 @@ DOCUMENTATION = '''
environment variable and keep I(endpoints), I(host), and I(port) unused. environment variable and keep I(endpoints), I(host), and I(port) unused.
seealso: seealso:
- module: community.general.etcd3 - module: community.general.etcd3
- ref: etcd_lookup - ref: ansible_collections.community.general.etcd_lookup
description: The etcd v2 lookup. description: The etcd v2 lookup.
requirements: requirements:

View File

@@ -23,7 +23,7 @@ DOCUMENTATION = '''
EXAMPLES = """ EXAMPLES = """
- name: "'unnest' all elements into single list" - name: "'unnest' all elements into single list"
ansible.builtin.debug: ansible.builtin.debug:
msg: "all in one list {{lookup('community.general.flattened', [1,2,3,[5,6]], [a,b,c], [[5,6,1,3], [34,a,b,c]])}}" msg: "all in one list {{lookup('community.general.flattened', [1,2,3,[5,6]], ['a','b','c'], [[5,6,1,3], [34,'a','b','c']])}}"
""" """
RETURN = """ RETURN = """

View File

@@ -141,9 +141,9 @@ import time
import yaml import yaml
from distutils import util
from ansible.errors import AnsibleError, AnsibleAssertionError from ansible.errors import AnsibleError, AnsibleAssertionError
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.encrypt import random_password from ansible.utils.encrypt import random_password
from ansible.plugins.lookup import LookupBase from ansible.plugins.lookup import LookupBase
@@ -211,7 +211,7 @@ class LookupModule(LookupBase):
try: try:
for key in ['create', 'returnall', 'overwrite', 'backup', 'nosymbols']: for key in ['create', 'returnall', 'overwrite', 'backup', 'nosymbols']:
if not isinstance(self.paramvals[key], bool): if not isinstance(self.paramvals[key], bool):
self.paramvals[key] = util.strtobool(self.paramvals[key]) self.paramvals[key] = boolean(self.paramvals[key])
except (ValueError, AssertionError) as e: except (ValueError, AssertionError) as e:
raise AnsibleError(e) raise AnsibleError(e)
if self.paramvals['missing'] not in ['error', 'warn', 'create', 'empty']: if self.paramvals['missing'] not in ['error', 'warn', 'create', 'empty']:

View File

@@ -0,0 +1,343 @@
# Vendored copy of distutils/version.py from CPython 3.9.5
#
# Implements multiple version numbering conventions for the
# Python Module Distribution Utilities.
#
# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0)
#
"""Provides classes to represent module version numbers (one class for
each style of version numbering). There are currently two such classes
implemented: StrictVersion and LooseVersion.
Every version number class implements the following interface:
* the 'parse' method takes a string and parses it to some internal
representation; if the string is an invalid version number,
'parse' raises a ValueError exception
* the class constructor takes an optional string argument which,
if supplied, is passed to 'parse'
* __str__ reconstructs the string that was passed to 'parse' (or
an equivalent string -- ie. one that will generate an equivalent
version number instance)
* __repr__ generates Python code to recreate the version number instance
* _cmp compares the current instance with either another instance
of the same class or a string (which will be parsed to an instance
of the same class, thus must follow the same rules)
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
try:
RE_FLAGS = re.VERBOSE | re.ASCII
except AttributeError:
RE_FLAGS = re.VERBOSE
class Version:
"""Abstract base class for version numbering classes. Just provides
constructor (__init__) and reproducer (__repr__), because those
seem to be the same for all version numbering classes; and route
rich comparisons to _cmp.
"""
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def __repr__(self):
return "%s ('%s')" % (self.__class__.__name__, str(self))
def __eq__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c == 0
def __lt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c < 0
def __le__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c <= 0
def __gt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c > 0
def __ge__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c >= 0
# Interface for version-number classes -- must be implemented
# by the following classes (the concrete ones -- Version should
# be treated as an abstract class).
# __init__ (string) - create and take same action as 'parse'
# (string parameter is optional)
# parse (string) - convert a string representation to whatever
# internal representation is appropriate for
# this style of version numbering
# __str__ (self) - convert back to a string; should be very similar
# (if not identical to) the string supplied to parse
# __repr__ (self) - generate Python code to recreate
# the instance
# _cmp (self, other) - compare two version numbers ('other' may
# be an unparsed version string, or another
# instance of your version class)
class StrictVersion(Version):
"""Version numbering for anal retentives and software idealists.
Implements the standard interface for version number classes as
described above. A version number consists of two or three
dot-separated numeric components, with an optional "pre-release" tag
on the end. The pre-release tag consists of the letter 'a' or 'b'
followed by a number. If the numeric components of two version
numbers are equal, then one with a pre-release tag will always
be deemed earlier (lesser) than one without.
The following are valid version numbers (shown in the order that
would be obtained by sorting according to the supplied cmp function):
0.4 0.4.0 (these two are equivalent)
0.4.1
0.5a1
0.5b3
0.5
0.9.6
1.0
1.0.4a3
1.0.4b1
1.0.4
The following are examples of invalid version numbers:
1
2.7.2.2
1.3.a4
1.3pl1
1.3c4
The rationale for this version numbering system will be explained
in the distutils documentation.
"""
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
RE_FLAGS)
def parse(self, vstring):
match = self.version_re.match(vstring)
if not match:
raise ValueError("invalid version number '%s'" % vstring)
(major, minor, patch, prerelease, prerelease_num) = \
match.group(1, 2, 4, 5, 6)
if patch:
self.version = tuple(map(int, [major, minor, patch]))
else:
self.version = tuple(map(int, [major, minor])) + (0,)
if prerelease:
self.prerelease = (prerelease[0], int(prerelease_num))
else:
self.prerelease = None
def __str__(self):
if self.version[2] == 0:
vstring = '.'.join(map(str, self.version[0:2]))
else:
vstring = '.'.join(map(str, self.version))
if self.prerelease:
vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
return vstring
def _cmp(self, other):
if isinstance(other, str):
other = StrictVersion(other)
elif not isinstance(other, StrictVersion):
return NotImplemented
if self.version != other.version:
# numeric versions don't match
# prerelease stuff doesn't matter
if self.version < other.version:
return -1
else:
return 1
# have to compare prerelease
# case 1: neither has prerelease; they're equal
# case 2: self has prerelease, other doesn't; other is greater
# case 3: self doesn't have prerelease, other does: self is greater
# case 4: both have prerelease: must compare them!
if (not self.prerelease and not other.prerelease):
return 0
elif (self.prerelease and not other.prerelease):
return -1
elif (not self.prerelease and other.prerelease):
return 1
elif (self.prerelease and other.prerelease):
if self.prerelease == other.prerelease:
return 0
elif self.prerelease < other.prerelease:
return -1
else:
return 1
else:
raise AssertionError("never get here")
# end class StrictVersion
# The rules according to Greg Stein:
# 1) a version number has 1 or more numbers separated by a period or by
# sequences of letters. If only periods, then these are compared
# left-to-right to determine an ordering.
# 2) sequences of letters are part of the tuple for comparison and are
# compared lexicographically
# 3) recognize the numeric components may have leading zeroes
#
# The LooseVersion class below implements these rules: a version number
# string is split up into a tuple of integer and string components, and
# comparison is a simple tuple comparison. This means that version
# numbers behave in a predictable and obvious way, but a way that might
# not necessarily be how people *want* version numbers to behave. There
# wouldn't be a problem if people could stick to purely numeric version
# numbers: just split on period and compare the numbers as tuples.
# However, people insist on putting letters into their version numbers;
# the most common purpose seems to be:
# - indicating a "pre-release" version
# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
# - indicating a post-release patch ('p', 'pl', 'patch')
# but of course this can't cover all version number schemes, and there's
# no way to know what a programmer means without asking him.
#
# The problem is what to do with letters (and other non-numeric
# characters) in a version number. The current implementation does the
# obvious and predictable thing: keep them as strings and compare
# lexically within a tuple comparison. This has the desired effect if
# an appended letter sequence implies something "post-release":
# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
#
# However, if letters in a version number imply a pre-release version,
# the "obvious" thing isn't correct. Eg. you would expect that
# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
# implemented here, this just isn't so.
#
# Two possible solutions come to mind. The first is to tie the
# comparison algorithm to a particular set of semantic rules, as has
# been done in the StrictVersion class above. This works great as long
# as everyone can go along with bondage and discipline. Hopefully a
# (large) subset of Python module programmers will agree that the
# particular flavour of bondage and discipline provided by StrictVersion
# provides enough benefit to be worth using, and will submit their
# version numbering scheme to its domination. The free-thinking
# anarchists in the lot will never give in, though, and something needs
# to be done to accommodate them.
#
# Perhaps a "moderately strict" version class could be implemented that
# lets almost anything slide (syntactically), and makes some heuristic
# assumptions about non-digits in version number strings. This could
# sink into special-case-hell, though; if I was as talented and
# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
# just as happy dealing with things like "2g6" and "1.13++". I don't
# think I'm smart enough to do it right though.
#
# In any case, I've coded the test suite for this module (see
# ../test/test_version.py) specifically to fail on things like comparing
# "1.2a2" and "1.2". That's not because the *code* is doing anything
# wrong, it's because the simple, obvious design doesn't match my
# complicated, hairy expectations for real-world version numbers. It
# would be a snap to fix the test suite to say, "Yep, LooseVersion does
# the Right Thing" (ie. the code matches the conception). But I'd rather
# have a conception that matches common notions about version numbers.
class LooseVersion(Version):
"""Version numbering for anarchists and software realists.
Implements the standard interface for version number classes as
described above. A version number consists of a series of numbers,
separated by either periods or strings of letters. When comparing
version numbers, the numeric components will be compared
numerically, and the alphabetic components lexically. The following
are all valid version numbers, in no particular order:
1.5.1
1.5.2b2
161
3.10a
8.02
3.4j
1996.07.12
3.2.pl0
3.1.1.6
2g6
11g
0.960923
2.2beta29
1.13++
5.5.kw
2.0b1pl0
In fact, there is no such thing as an invalid version number under
this scheme; the rules for comparison are simple and predictable,
but may not always give the results you want (for some definition
of "want").
"""
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def parse(self, vstring):
# I've given up on thinking I can reconstruct the version string
# from the parsed tuple -- so I just store the string here for
# use by __str__
self.vstring = vstring
components = [x for x in self.component_re.split(vstring) if x and x != '.']
for i, obj in enumerate(components):
try:
components[i] = int(obj)
except ValueError:
pass
self.version = components
def __str__(self):
return self.vstring
def __repr__(self):
return "LooseVersion ('%s')" % str(self)
def _cmp(self, other):
if isinstance(other, str):
other = LooseVersion(other)
elif not isinstance(other, LooseVersion):
return NotImplemented
if self.version == other.version:
return 0
if self.version < other.version:
return -1
if self.version > other.version:
return 1
# end class LooseVersion

View File

@@ -7,29 +7,40 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import json
from distutils.version import StrictVersion
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
try: try:
from urllib import quote_plus # Python 2.X from urllib import quote_plus # Python 2.X
from urlparse import urljoin
except ImportError: except ImportError:
from urllib.parse import quote_plus # Python 3+ from urllib.parse import quote_plus, urljoin # Python 3+
import traceback import traceback
GITLAB_IMP_ERR = None GITLAB_IMP_ERR = None
try: try:
import gitlab import gitlab
import requests
HAS_GITLAB_PACKAGE = True HAS_GITLAB_PACKAGE = True
except Exception: except Exception:
GITLAB_IMP_ERR = traceback.format_exc() GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False HAS_GITLAB_PACKAGE = False
def auth_argument_spec(spec=None):
arg_spec = (dict(
api_token=dict(type='str', no_log=True),
api_oauth_token=dict(type='str', no_log=True),
api_job_token=dict(type='str', no_log=True),
))
if spec:
arg_spec.update(spec)
return arg_spec
def find_project(gitlab_instance, identifier): def find_project(gitlab_instance, identifier):
try: try:
project = gitlab_instance.projects.get(identifier) project = gitlab_instance.projects.get(identifier)
@@ -58,6 +69,8 @@ def gitlab_authentication(module):
gitlab_user = module.params['api_username'] gitlab_user = module.params['api_username']
gitlab_password = module.params['api_password'] gitlab_password = module.params['api_password']
gitlab_token = module.params['api_token'] gitlab_token = module.params['api_token']
gitlab_oauth_token = module.params['api_oauth_token']
gitlab_job_token = module.params['api_job_token']
if not HAS_GITLAB_PACKAGE: if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR) module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
@@ -66,11 +79,20 @@ def gitlab_authentication(module):
# python-gitlab library remove support for username/password authentication since 1.13.0 # python-gitlab library remove support for username/password authentication since 1.13.0
# Changelog : https://github.com/python-gitlab/python-gitlab/releases/tag/v1.13.0 # Changelog : https://github.com/python-gitlab/python-gitlab/releases/tag/v1.13.0
# This condition allow to still support older version of the python-gitlab library # This condition allow to still support older version of the python-gitlab library
if StrictVersion(gitlab.__version__) < StrictVersion("1.13.0"): if LooseVersion(gitlab.__version__) < LooseVersion("1.13.0"):
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=validate_certs, email=gitlab_user, password=gitlab_password, gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=validate_certs, email=gitlab_user, password=gitlab_password,
private_token=gitlab_token, api_version=4) private_token=gitlab_token, api_version=4)
else: else:
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=validate_certs, private_token=gitlab_token, api_version=4) # We can create an oauth_token using a username and password
# https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow
if gitlab_user:
data = {'grant_type': 'password', 'username': gitlab_user, 'password': gitlab_password}
resp = requests.post(urljoin(gitlab_url, "oauth/token"), data=data, verify=validate_certs)
resp_data = resp.json()
gitlab_oauth_token = resp_data["access_token"]
gitlab_instance = gitlab.Gitlab(url=gitlab_url, ssl_verify=validate_certs, private_token=gitlab_token,
oauth_token=gitlab_oauth_token, job_token=gitlab_job_token, api_version=4)
gitlab_instance.auth() gitlab_instance.auth()
except (gitlab.exceptions.GitlabAuthenticationError, gitlab.exceptions.GitlabGetError) as e: except (gitlab.exceptions.GitlabAuthenticationError, gitlab.exceptions.GitlabGetError) as e:

View File

@@ -38,6 +38,7 @@ from ansible.module_utils.six.moves.urllib.parse import urlencode, quote
from ansible.module_utils.six.moves.urllib.error import HTTPError from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.common.text.converters import to_native, to_text
URL_REALM_INFO = "{url}/realms/{realm}"
URL_REALMS = "{url}/admin/realms" URL_REALMS = "{url}/admin/realms"
URL_REALM = "{url}/admin/realms/{realm}" URL_REALM = "{url}/admin/realms/{realm}"
@@ -230,6 +231,31 @@ class KeycloakAPI(object):
self.validate_certs = self.module.params.get('validate_certs') self.validate_certs = self.module.params.get('validate_certs')
self.restheaders = connection_header self.restheaders = connection_header
def get_realm_info_by_id(self, realm='master'):
""" Obtain realm public info by id
:param realm: realm id
:return: dict of real, representation or None if none matching exist
"""
realm_info_url = URL_REALM_INFO.format(url=self.baseurl, realm=realm)
try:
return json.loads(to_native(open_url(realm_info_url, method='GET', headers=self.restheaders,
validate_certs=self.validate_certs).read()))
except HTTPError as e:
if e.code == 404:
return None
else:
self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)),
exception=traceback.format_exc())
except ValueError as e:
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain realm %s: %s' % (realm, str(e)),
exception=traceback.format_exc())
except Exception as e:
self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)),
exception=traceback.format_exc())
def get_realm_by_id(self, realm='master'): def get_realm_by_id(self, realm='master'):
""" Obtain realm representation by id """ Obtain realm representation by id

View File

@@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021-2022 Hewlett Packard Enterprise, Inc. All rights reserved.
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.community.general.plugins.module_utils.redfish_utils import RedfishUtils
class iLORedfishUtils(RedfishUtils):
def get_ilo_sessions(self):
result = {}
# listing all users has always been slower than other operations, why?
session_list = []
sessions_results = []
# Get these entries, but does not fail if not found
properties = ['Description', 'Id', 'Name', 'UserName']
# Changed self.sessions_uri to Hardcoded string.
response = self.get_request(
self.root_uri + self.service_root + "SessionService/Sessions/")
if not response['ret']:
return response
result['ret'] = True
data = response['data']
if 'Oem' in data:
if data["Oem"]["Hpe"]["Links"]["MySession"]["@odata.id"]:
current_session = data["Oem"]["Hpe"]["Links"]["MySession"]["@odata.id"]
for sessions in data[u'Members']:
# session_list[] are URIs
session_list.append(sessions[u'@odata.id'])
# for each session, get details
for uri in session_list:
session = {}
if uri != current_session:
response = self.get_request(self.root_uri + uri)
if not response['ret']:
return response
data = response['data']
for property in properties:
if property in data:
session[property] = data[property]
sessions_results.append(session)
result["msg"] = sessions_results
result["ret"] = True
return result
def set_ntp_server(self, mgr_attributes):
result = {}
setkey = mgr_attributes['mgr_attr_name']
nic_info = self.get_manager_ethernet_uri()
ethuri = nic_info["nic_addr"]
response = self.get_request(self.root_uri + ethuri)
if not response['ret']:
return response
result['ret'] = True
data = response['data']
payload = {"DHCPv4": {
"UseNTPServers": ""
}}
if data["DHCPv4"]["UseNTPServers"]:
payload["DHCPv4"]["UseNTPServers"] = False
res_dhv4 = self.patch_request(self.root_uri + ethuri, payload)
if not res_dhv4['ret']:
return res_dhv4
payload = {"DHCPv6": {
"UseNTPServers": ""
}}
if data["DHCPv6"]["UseNTPServers"]:
payload["DHCPv6"]["UseNTPServers"] = False
res_dhv6 = self.patch_request(self.root_uri + ethuri, payload)
if not res_dhv6['ret']:
return res_dhv6
datetime_uri = self.manager_uri + "DateTime"
response = self.get_request(self.root_uri + datetime_uri)
if not response['ret']:
return response
data = response['data']
ntp_list = data[setkey]
if(len(ntp_list) == 2):
ntp_list.pop(0)
ntp_list.append(mgr_attributes['mgr_attr_value'])
payload = {setkey: ntp_list}
response1 = self.patch_request(self.root_uri + datetime_uri, payload)
if not response1['ret']:
return response1
return {'ret': True, 'changed': True, 'msg': "Modified %s" % mgr_attributes['mgr_attr_name']}
def set_time_zone(self, attr):
key = attr['mgr_attr_name']
uri = self.manager_uri + "DateTime/"
response = self.get_request(self.root_uri + uri)
if not response['ret']:
return response
data = response["data"]
if key not in data:
return {'ret': False, 'changed': False, 'msg': "Key %s not found" % key}
timezones = data["TimeZoneList"]
index = ""
for tz in timezones:
if attr['mgr_attr_value'] in tz["Name"]:
index = tz["Index"]
break
payload = {key: {"Index": index}}
response = self.patch_request(self.root_uri + uri, payload)
if not response['ret']:
return response
return {'ret': True, 'changed': True, 'msg': "Modified %s" % attr['mgr_attr_name']}
def set_dns_server(self, attr):
key = attr['mgr_attr_name']
nic_info = self.get_manager_ethernet_uri()
uri = nic_info["nic_addr"]
response = self.get_request(self.root_uri + uri)
if not response['ret']:
return response
data = response['data']
dns_list = data["Oem"]["Hpe"]["IPv4"][key]
if len(dns_list) == 3:
dns_list.pop(0)
dns_list.append(attr['mgr_attr_value'])
payload = {
"Oem": {
"Hpe": {
"IPv4": {
key: dns_list
}
}
}
}
response = self.patch_request(self.root_uri + uri, payload)
if not response['ret']:
return response
return {'ret': True, 'changed': True, 'msg': "Modified %s" % attr['mgr_attr_name']}
def set_domain_name(self, attr):
key = attr['mgr_attr_name']
nic_info = self.get_manager_ethernet_uri()
ethuri = nic_info["nic_addr"]
response = self.get_request(self.root_uri + ethuri)
if not response['ret']:
return response
data = response['data']
payload = {"DHCPv4": {
"UseDomainName": ""
}}
if data["DHCPv4"]["UseDomainName"]:
payload["DHCPv4"]["UseDomainName"] = False
res_dhv4 = self.patch_request(self.root_uri + ethuri, payload)
if not res_dhv4['ret']:
return res_dhv4
payload = {"DHCPv6": {
"UseDomainName": ""
}}
if data["DHCPv6"]["UseDomainName"]:
payload["DHCPv6"]["UseDomainName"] = False
res_dhv6 = self.patch_request(self.root_uri + ethuri, payload)
if not res_dhv6['ret']:
return res_dhv6
domain_name = attr['mgr_attr_value']
payload = {"Oem": {
"Hpe": {
key: domain_name
}
}}
response = self.patch_request(self.root_uri + ethuri, payload)
if not response['ret']:
return response
return {'ret': True, 'changed': True, 'msg': "Modified %s" % attr['mgr_attr_name']}
def set_wins_registration(self, mgrattr):
Key = mgrattr['mgr_attr_name']
nic_info = self.get_manager_ethernet_uri()
ethuri = nic_info["nic_addr"]
payload = {
"Oem": {
"Hpe": {
"IPv4": {
Key: False
}
}
}
}
response = self.patch_request(self.root_uri + ethuri, payload)
if not response['ret']:
return response
return {'ret': True, 'changed': True, 'msg': "Modified %s" % mgrattr['mgr_attr_name']}

View File

@@ -9,7 +9,8 @@ __metaclass__ = type
import traceback import traceback
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:

View File

@@ -52,3 +52,36 @@ def module_fails_on_exception(func):
self.module.fail_json(msg=msg, exception=traceback.format_exc(), self.module.fail_json(msg=msg, exception=traceback.format_exc(),
output=self.output, vars=self.vars.output(), **self.output) output=self.output, vars=self.vars.output(), **self.output)
return wrapper return wrapper
def check_mode_skip(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
if not self.module.check_mode:
return func(self, *args, **kwargs)
return wrapper
def check_mode_skip_returns(callable=None, value=None):
def deco(func):
if callable is not None:
@wraps(func)
def wrapper_callable(self, *args, **kwargs):
if self.module.check_mode:
return callable(self, *args, **kwargs)
return func(self, *args, **kwargs)
return wrapper_callable
if value is not None:
@wraps(func)
def wrapper_value(self, *args, **kwargs):
if self.module.check_mode:
return value
return func(self, *args, **kwargs)
return wrapper_value
if callable is None and value is None:
return check_mode_skip
return deco

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# (c) 2020, Alexei Znamensky <russoz@gmail.com>
# Copyright: (c) 2020, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule
class DeprecateAttrsMixin(object):
def _deprecate_setup(self, attr, target, module):
if target is None:
target = self
if not hasattr(target, attr):
raise ValueError("Target {0} has no attribute {1}".format(target, attr))
if module is None:
if isinstance(target, AnsibleModule):
module = target
elif hasattr(target, "module") and isinstance(target.module, AnsibleModule):
module = target.module
else:
raise ValueError("Failed to automatically discover the AnsibleModule instance. Pass 'module' parameter explicitly.")
# setup internal state dicts
value_attr = "__deprecated_attr_value"
trigger_attr = "__deprecated_attr_trigger"
if not hasattr(target, value_attr):
setattr(target, value_attr, {})
if not hasattr(target, trigger_attr):
setattr(target, trigger_attr, {})
value_dict = getattr(target, value_attr)
trigger_dict = getattr(target, trigger_attr)
return target, module, value_dict, trigger_dict
def _deprecate_attr(self, attr, msg, version=None, date=None, collection_name=None, target=None, value=None, module=None):
target, module, value_dict, trigger_dict = self._deprecate_setup(attr, target, module)
value_dict[attr] = getattr(target, attr, value)
trigger_dict[attr] = False
def _trigger():
if not trigger_dict[attr]:
module.deprecate(msg, version=version, date=date, collection_name=collection_name)
trigger_dict[attr] = True
def _getter(_self):
_trigger()
return value_dict[attr]
def _setter(_self, new_value):
_trigger()
value_dict[attr] = new_value
# override attribute
prop = property(_getter)
setattr(target, attr, prop)
setattr(target, "_{0}_setter".format(attr), prop.setter(_setter))

View File

@@ -13,9 +13,10 @@ from ansible_collections.community.general.plugins.module_utils.mh.mixins.cmd im
from ansible_collections.community.general.plugins.module_utils.mh.mixins.state import StateMixin from ansible_collections.community.general.plugins.module_utils.mh.mixins.state import StateMixin
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deps import DependencyMixin from ansible_collections.community.general.plugins.module_utils.mh.mixins.deps import DependencyMixin
from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarsMixin, VarDict as _VD from ansible_collections.community.general.plugins.module_utils.mh.mixins.vars import VarsMixin, VarDict as _VD
from ansible_collections.community.general.plugins.module_utils.mh.mixins.deprecate_attrs import DeprecateAttrsMixin
class ModuleHelper(VarsMixin, DependencyMixin, ModuleHelperBase): class ModuleHelper(DeprecateAttrsMixin, VarsMixin, DependencyMixin, ModuleHelperBase):
_output_conflict_list = ('msg', 'exception', 'output', 'vars', 'changed') _output_conflict_list = ('msg', 'exception', 'output', 'vars', 'changed')
facts_name = None facts_name = None
output_params = () output_params = ()
@@ -36,6 +37,15 @@ class ModuleHelper(VarsMixin, DependencyMixin, ModuleHelperBase):
fact=name in self.facts_params, fact=name in self.facts_params,
) )
self._deprecate_attr(
attr="VarDict",
msg="ModuleHelper.VarDict attribute is deprecated, use VarDict from "
"the ansible_collections.community.general.plugins.module_utils.mh.mixins.vars module instead",
version="6.0.0",
collection_name="community.general",
target=ModuleHelper,
module=self.module)
def update_output(self, **kwargs): def update_output(self, **kwargs):
self.update_vars(meta={"output": True}, **kwargs) self.update_vars(meta={"output": True}, **kwargs)

View File

@@ -54,6 +54,17 @@ def proxmox_to_ansible_bool(value):
return True if value == 1 else False return True if value == 1 else False
def ansible_to_proxmox_bool(value):
'''Convert Ansible representation of a boolean to be proxmox-friendly'''
if value is None:
return None
if not isinstance(value, bool):
raise ValueError("%s must be of type bool not %s" % (value, type(value)))
return 1 if value else 0
class ProxmoxAnsible(object): class ProxmoxAnsible(object):
"""Base class for Proxmox modules""" """Base class for Proxmox modules"""
def __init__(self, module): def __init__(self, module):

View File

@@ -1834,12 +1834,16 @@ class RedfishUtils(object):
result['ret'] = True result['ret'] = True
data = response['data'] data = response['data']
for device in data[u'Fans']: # Checking if fans are present
fan = {} if u'Fans' in data:
for property in properties: for device in data[u'Fans']:
if property in device: fan = {}
fan[property] = device[property] for property in properties:
fan_results.append(fan) if property in device:
fan[property] = device[property]
fan_results.append(fan)
else:
return {'ret': False, 'msg': "No Fans present"}
result["entries"] = fan_results result["entries"] = fan_results
return result return result
@@ -2701,39 +2705,14 @@ class RedfishUtils(object):
return self.aggregate_managers(self.get_manager_health_report) return self.aggregate_managers(self.get_manager_health_report)
def set_manager_nic(self, nic_addr, nic_config): def set_manager_nic(self, nic_addr, nic_config):
# Get EthernetInterface collection # Get the manager ethernet interface uri
response = self.get_request(self.root_uri + self.manager_uri) nic_info = self.get_manager_ethernet_uri(nic_addr)
if response['ret'] is False:
return response
data = response['data']
if 'EthernetInterfaces' not in data:
return {'ret': False, 'msg': "EthernetInterfaces resource not found"}
ethernetinterfaces_uri = data["EthernetInterfaces"]["@odata.id"]
response = self.get_request(self.root_uri + ethernetinterfaces_uri)
if response['ret'] is False:
return response
data = response['data']
uris = [a.get('@odata.id') for a in data.get('Members', []) if
a.get('@odata.id')]
# Find target EthernetInterface if nic_info.get('nic_addr') is None:
target_ethernet_uri = None return nic_info
target_ethernet_current_setting = None else:
if nic_addr == 'null': target_ethernet_uri = nic_info['nic_addr']
# Find root_uri matched EthernetInterface when nic_addr is not specified target_ethernet_current_setting = nic_info['ethernet_setting']
nic_addr = (self.root_uri).split('/')[-1]
nic_addr = nic_addr.split(':')[0] # split port if existing
for uri in uris:
response = self.get_request(self.root_uri + uri)
if response['ret'] is False:
return response
data = response['data']
if '"' + nic_addr.lower() + '"' in str(data).lower() or "'" + nic_addr.lower() + "'" in str(data).lower():
target_ethernet_uri = uri
target_ethernet_current_setting = data
break
if target_ethernet_uri is None:
return {'ret': False, 'msg': "No matched EthernetInterface found under Manager"}
# Convert input to payload and check validity # Convert input to payload and check validity
payload = {} payload = {}
@@ -2797,6 +2776,50 @@ class RedfishUtils(object):
return response return response
return {'ret': True, 'changed': True, 'msg': "Modified Manager NIC"} return {'ret': True, 'changed': True, 'msg': "Modified Manager NIC"}
# A helper function to get the EthernetInterface URI
def get_manager_ethernet_uri(self, nic_addr='null'):
# Get EthernetInterface collection
response = self.get_request(self.root_uri + self.manager_uri)
if not response['ret']:
return response
data = response['data']
if 'EthernetInterfaces' not in data:
return {'ret': False, 'msg': "EthernetInterfaces resource not found"}
ethernetinterfaces_uri = data["EthernetInterfaces"]["@odata.id"]
response = self.get_request(self.root_uri + ethernetinterfaces_uri)
if not response['ret']:
return response
data = response['data']
uris = [a.get('@odata.id') for a in data.get('Members', []) if
a.get('@odata.id')]
# Find target EthernetInterface
target_ethernet_uri = None
target_ethernet_current_setting = None
if nic_addr == 'null':
# Find root_uri matched EthernetInterface when nic_addr is not specified
nic_addr = (self.root_uri).split('/')[-1]
nic_addr = nic_addr.split(':')[0] # split port if existing
for uri in uris:
response = self.get_request(self.root_uri + uri)
if not response['ret']:
return response
data = response['data']
data_string = json.dumps(data)
if nic_addr.lower() in data_string.lower():
target_ethernet_uri = uri
target_ethernet_current_setting = data
break
nic_info = {}
nic_info['nic_addr'] = target_ethernet_uri
nic_info['ethernet_setting'] = target_ethernet_current_setting
if target_ethernet_uri is None:
return {}
else:
return nic_info
def set_hostinterface_attributes(self, hostinterface_config, hostinterface_id=None): def set_hostinterface_attributes(self, hostinterface_config, hostinterface_id=None):
response = self.get_request(self.root_uri + self.manager_uri) response = self.get_request(self.root_uri + self.manager_uri)
if response['ret'] is False: if response['ret'] is False:

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Provide version object to compare version numbers."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can
# remove the _version.py file, and replace the following import by
#
# from ansible.module_utils.compat.version import LooseVersion
from ._version import LooseVersion

1
plugins/modules/cargo.py Symbolic link
View File

@@ -0,0 +1 @@
packaging/language/cargo.py

View File

@@ -120,7 +120,7 @@ __version__ = '${version}'
import os import os
import traceback import traceback
from distutils.version import LooseVersion from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:

View File

@@ -161,7 +161,8 @@ __version__ = '${version}'
import json import json
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:

View File

@@ -89,7 +89,8 @@ __version__ = '${version}'
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:
@@ -132,8 +133,7 @@ class ClcBlueprintPackage:
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR) self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
if not REQUESTS_FOUND: if not REQUESTS_FOUND:
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR) self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
if requests.__version__ and LooseVersion( if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
requests.__version__) < LooseVersion('2.5.0'):
self.module.fail_json( self.module.fail_json(
msg='requests library version should be >= 2.5.0') msg='requests library version should be >= 2.5.0')

View File

@@ -162,7 +162,8 @@ import os
import traceback import traceback
from ansible.module_utils.six.moves.urllib.parse import urlparse from ansible.module_utils.six.moves.urllib.parse import urlparse
from time import sleep from time import sleep
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:
@@ -203,8 +204,7 @@ class ClcFirewallPolicy:
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR) self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
if not REQUESTS_FOUND: if not REQUESTS_FOUND:
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR) self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
if requests.__version__ and LooseVersion( if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
requests.__version__) < LooseVersion('2.5.0'):
self.module.fail_json( self.module.fail_json(
msg='requests library version should be >= 2.5.0') msg='requests library version should be >= 2.5.0')

View File

@@ -207,7 +207,8 @@ __version__ = '${version}'
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:

View File

@@ -210,7 +210,8 @@ import json
import os import os
import traceback import traceback
from time import sleep from time import sleep
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:
@@ -255,8 +256,7 @@ class ClcLoadBalancer:
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR) self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
if not REQUESTS_FOUND: if not REQUESTS_FOUND:
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR) self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
if requests.__version__ and LooseVersion( if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
requests.__version__) < LooseVersion('2.5.0'):
self.module.fail_json( self.module.fail_json(
msg='requests library version should be >= 2.5.0') msg='requests library version should be >= 2.5.0')

View File

@@ -311,7 +311,8 @@ __version__ = '${version}'
import json import json
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:
@@ -355,8 +356,7 @@ class ClcModifyServer:
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR) self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
if not REQUESTS_FOUND: if not REQUESTS_FOUND:
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR) self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
if requests.__version__ and LooseVersion( if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
requests.__version__) < LooseVersion('2.5.0'):
self.module.fail_json( self.module.fail_json(
msg='requests library version should be >= 2.5.0') msg='requests library version should be >= 2.5.0')

View File

@@ -117,7 +117,8 @@ __version__ = '${version}'
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:

View File

@@ -433,7 +433,8 @@ import json
import os import os
import time import time
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:
@@ -478,8 +479,7 @@ class ClcServer:
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR) self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
if not REQUESTS_FOUND: if not REQUESTS_FOUND:
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR) self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
if requests.__version__ and LooseVersion( if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
requests.__version__) < LooseVersion('2.5.0'):
self.module.fail_json( self.module.fail_json(
msg='requests library version should be >= 2.5.0') msg='requests library version should be >= 2.5.0')

View File

@@ -101,7 +101,8 @@ __version__ = '${version}'
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:
@@ -145,8 +146,7 @@ class ClcSnapshot:
self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR) self.module.fail_json(msg=missing_required_lib('clc-sdk'), exception=CLC_IMP_ERR)
if not REQUESTS_FOUND: if not REQUESTS_FOUND:
self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR) self.module.fail_json(msg=missing_required_lib('requests'), exception=REQUESTS_IMP_ERR)
if requests.__version__ and LooseVersion( if requests.__version__ and LooseVersion(requests.__version__) < LooseVersion('2.5.0'):
requests.__version__) < LooseVersion('2.5.0'):
self.module.fail_json( self.module.fail_json(
msg='requests library version should be >= 2.5.0') msg='requests library version should be >= 2.5.0')

View File

@@ -422,6 +422,7 @@ import shutil
import subprocess import subprocess
import tempfile import tempfile
import time import time
import shlex
try: try:
import lxc import lxc
@@ -661,9 +662,8 @@ class LxcContainerManagement(object):
""" """
for key, value in variables_dict.items(): for key, value in variables_dict.items():
build_command.append( build_command.append(str(key))
'%s %s' % (key, value) build_command.append(str(value))
)
return build_command return build_command
def _get_vars(self, variables): def _get_vars(self, variables):
@@ -686,24 +686,6 @@ class LxcContainerManagement(object):
return_dict[v] = _var return_dict[v] = _var
return return_dict return return_dict
def _run_command(self, build_command, unsafe_shell=False):
"""Return information from running an Ansible Command.
This will squash the build command list into a string and then
execute the command via Ansible. The output is returned to the method.
This output is returned as `return_code`, `stdout`, `stderr`.
:param build_command: Used for the command and all options.
:type build_command: ``list``
:param unsafe_shell: Enable or Disable unsafe sell commands.
:type unsafe_shell: ``bol``
"""
return self.module.run_command(
' '.join(build_command),
use_unsafe_shell=unsafe_shell
)
def _config(self): def _config(self):
"""Configure an LXC container. """Configure an LXC container.
@@ -810,7 +792,7 @@ class LxcContainerManagement(object):
elif self.module.params.get('backing_store') == 'overlayfs': elif self.module.params.get('backing_store') == 'overlayfs':
build_command.append('--snapshot') build_command.append('--snapshot')
rc, return_data, err = self._run_command(build_command) rc, return_data, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
message = "Failed executing %s." % os.path.basename(clone_cmd) message = "Failed executing %s." % os.path.basename(clone_cmd)
self.failure( self.failure(
@@ -843,7 +825,7 @@ class LxcContainerManagement(object):
build_command = [ build_command = [
self.module.get_bin_path('lxc-create', True), self.module.get_bin_path('lxc-create', True),
'--name %s' % self.container_name, '--name', self.container_name,
'--quiet' '--quiet'
] ]
@@ -869,10 +851,12 @@ class LxcContainerManagement(object):
log_path = os.getenv('HOME') log_path = os.getenv('HOME')
build_command.extend([ build_command.extend([
'--logfile %s' % os.path.join( '--logfile',
os.path.join(
log_path, 'lxc-%s.log' % self.container_name log_path, 'lxc-%s.log' % self.container_name
), ),
'--logpriority %s' % self.module.params.get( '--logpriority',
self.module.params.get(
'container_log_level' 'container_log_level'
).upper() ).upper()
]) ])
@@ -880,9 +864,10 @@ class LxcContainerManagement(object):
# Add the template commands to the end of the command if there are any # Add the template commands to the end of the command if there are any
template_options = self.module.params.get('template_options', None) template_options = self.module.params.get('template_options', None)
if template_options: if template_options:
build_command.append('-- %s' % template_options) build_command.append('--')
build_command += shlex.split(template_options)
rc, return_data, err = self._run_command(build_command) rc, return_data, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
message = "Failed executing lxc-create." message = "Failed executing lxc-create."
self.failure( self.failure(
@@ -1186,7 +1171,7 @@ class LxcContainerManagement(object):
self.module.get_bin_path('lxc-config', True), self.module.get_bin_path('lxc-config', True),
"lxc.bdev.lvm.vg" "lxc.bdev.lvm.vg"
] ]
rc, vg, err = self._run_command(build_command) rc, vg, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1204,7 +1189,7 @@ class LxcContainerManagement(object):
build_command = [ build_command = [
self.module.get_bin_path('lvs', True) self.module.get_bin_path('lvs', True)
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1231,7 +1216,7 @@ class LxcContainerManagement(object):
'--units', '--units',
'g' 'g'
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1262,7 +1247,7 @@ class LxcContainerManagement(object):
'--units', '--units',
'g' 'g'
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1311,7 +1296,7 @@ class LxcContainerManagement(object):
os.path.join(vg, source_lv), os.path.join(vg, source_lv),
"-L%sg" % snapshot_size_gb "-L%sg" % snapshot_size_gb
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1336,7 +1321,7 @@ class LxcContainerManagement(object):
"/dev/%s/%s" % (vg, lv_name), "/dev/%s/%s" % (vg, lv_name),
mount_point, mount_point,
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1380,9 +1365,8 @@ class LxcContainerManagement(object):
'.' '.'
] ]
rc, stdout, err = self._run_command( rc, stdout, err = self.module.run_command(
build_command=build_command, build_command
unsafe_shell=True
) )
os.umask(old_umask) os.umask(old_umask)
@@ -1410,7 +1394,7 @@ class LxcContainerManagement(object):
"-f", "-f",
"%s/%s" % (vg, lv_name), "%s/%s" % (vg, lv_name),
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1442,11 +1426,10 @@ class LxcContainerManagement(object):
self.module.get_bin_path('rsync', True), self.module.get_bin_path('rsync', True),
'-aHAX', '-aHAX',
fs_path, fs_path,
temp_dir temp_dir,
] ]
rc, stdout, err = self._run_command( rc, stdout, err = self.module.run_command(
build_command, build_command,
unsafe_shell=True
) )
if rc != 0: if rc != 0:
self.failure( self.failure(
@@ -1467,7 +1450,7 @@ class LxcContainerManagement(object):
self.module.get_bin_path('umount', True), self.module.get_bin_path('umount', True),
mount_point, mount_point,
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,
@@ -1489,12 +1472,12 @@ class LxcContainerManagement(object):
build_command = [ build_command = [
self.module.get_bin_path('mount', True), self.module.get_bin_path('mount', True),
'-t overlayfs', '-t', 'overlayfs',
'-o lowerdir=%s,upperdir=%s' % (lowerdir, upperdir), '-o', 'lowerdir=%s,upperdir=%s' % (lowerdir, upperdir),
'overlayfs', 'overlayfs',
mount_point, mount_point,
] ]
rc, stdout, err = self._run_command(build_command) rc, stdout, err = self.module.run_command(build_command)
if rc != 0: if rc != 0:
self.failure( self.failure(
err=err, err=err,

View File

@@ -167,6 +167,25 @@ options:
- compatibility - compatibility
- no_defaults - no_defaults
version_added: "1.3.0" version_added: "1.3.0"
clone:
description:
- ID of the container to be cloned.
- I(description), I(hostname), and I(pool) will be copied from the cloned container if not specified.
- The type of clone created is defined by the I(clone_type) parameter.
- This operator is only supported for Proxmox clusters that use LXC containerization (PVE version >= 4).
type: int
version_added: 4.3.0
clone_type:
description:
- Type of the clone created.
- C(full) creates a full clone, and I(storage) must be specified.
- C(linked) creates a linked clone, and the cloned container must be a template container.
- C(opportunistic) creates a linked clone if the cloned container is a template container, and a full clone if not.
I(storage) may be specified, if not it will fall back to the default.
type: str
choices: ['full', 'linked', 'opportunistic']
default: opportunistic
version_added: 4.3.0
author: Sergei Antipov (@UnderGreen) author: Sergei Antipov (@UnderGreen)
extends_documentation_fragment: extends_documentation_fragment:
- community.general.proxmox.documentation - community.general.proxmox.documentation
@@ -292,6 +311,28 @@ EXAMPLES = r'''
- nesting=1 - nesting=1
- mount=cifs,nfs - mount=cifs,nfs
- name: >
Create a linked clone of the template container with id 100. The newly created container with be a
linked clone, because no storage parameter is defined
community.general.proxmox:
vmid: 201
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
clone: 100
hostname: clone.example.org
- name: Create a full clone of the container with id 100
community.general.proxmox:
vmid: 201
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
clone: 100
hostname: clone.example.org
storage: local
- name: Start container - name: Start container
community.general.proxmox: community.general.proxmox:
@@ -348,7 +389,8 @@ EXAMPLES = r'''
import time import time
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
try: try:
from proxmoxer import ProxmoxAPI from proxmoxer import ProxmoxAPI
@@ -359,6 +401,10 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule, env_fallback from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.general.plugins.module_utils.proxmox import (
ansible_to_proxmox_bool
)
VZ_TYPE = None VZ_TYPE = None
@@ -384,6 +430,13 @@ def content_check(proxmox, node, ostemplate, template_store):
return [True for cnt in proxmox.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate] return [True for cnt in proxmox.nodes(node).storage(template_store).content.get() if cnt['volid'] == ostemplate]
def is_template_container(proxmox, node, vmid):
"""Check if the specified container is a template."""
proxmox_node = proxmox.nodes(node)
config = getattr(proxmox_node, VZ_TYPE)(vmid).config.get()
return config['template']
def node_check(proxmox, node): def node_check(proxmox, node):
return [True for nd in proxmox.nodes.get() if nd['node'] == node] return [True for nd in proxmox.nodes.get() if nd['node'] == node]
@@ -393,8 +446,10 @@ def proxmox_version(proxmox):
return LooseVersion(apireturn['version']) return LooseVersion(apireturn['version'])
def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, **kwargs): def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone, **kwargs):
proxmox_node = proxmox.nodes(node) proxmox_node = proxmox.nodes(node)
# Remove all empty kwarg entries
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
if VZ_TYPE == 'lxc': if VZ_TYPE == 'lxc':
@@ -414,7 +469,49 @@ def create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, sw
kwargs['cpus'] = cpus kwargs['cpus'] = cpus
kwargs['disk'] = disk kwargs['disk'] = disk
taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs) if clone is not None:
if VZ_TYPE != 'lxc':
module.fail_json(changed=False, msg="Clone operator is only supported for LXC enabled proxmox clusters.")
clone_is_template = is_template_container(proxmox, node, clone)
# By default, create a full copy only when the cloned container is not a template.
create_full_copy = not clone_is_template
# Only accept parameters that are compatible with the clone endpoint.
valid_clone_parameters = ['hostname', 'pool', 'description']
if module.params['storage'] is not None and clone_is_template:
# Cloning a template, so create a full copy instead of a linked copy
create_full_copy = True
elif module.params['storage'] is None and not clone_is_template:
# Not cloning a template, but also no defined storage. This isn't possible.
module.fail_json(changed=False, msg="Cloned container is not a template, storage needs to be specified.")
if module.params['clone_type'] == 'linked':
if not clone_is_template:
module.fail_json(changed=False, msg="'linked' clone type is specified, but cloned container is not a template container.")
# Don't need to do more, by default create_full_copy is set to false already
elif module.params['clone_type'] == 'opportunistic':
if not clone_is_template:
# Cloned container is not a template, so we need our 'storage' parameter
valid_clone_parameters.append('storage')
elif module.params['clone_type'] == 'full':
create_full_copy = True
valid_clone_parameters.append('storage')
clone_parameters = {}
if create_full_copy:
clone_parameters['full'] = '1'
else:
clone_parameters['full'] = '0'
for param in valid_clone_parameters:
if module.params[param] is not None:
clone_parameters[param] = module.params[param]
taskid = getattr(proxmox_node, VZ_TYPE)(clone).clone.post(newid=vmid, **clone_parameters)
else:
taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, storage=storage, memory=memory, swap=swap, **kwargs)
while timeout: while timeout:
if (proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' and if (proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' and
@@ -515,10 +612,19 @@ def main():
description=dict(type='str'), description=dict(type='str'),
hookscript=dict(type='str'), hookscript=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']),
clone=dict(type='int'),
clone_type=dict(default='opportunistic', choices=['full', 'linked', 'opportunistic']),
), ),
required_if=[('state', 'present', ['node', 'hostname', 'ostemplate'])], required_if=[
required_together=[('api_token_id', 'api_token_secret')], ('state', 'present', ['node', 'hostname']),
('state', 'present', ('clone', 'ostemplate'), True), # Require one of clone and ostemplate. Together with mutually_exclusive this ensures that we
# either clone a container or create a new one from a template file.
],
required_together=[
('api_token_id', 'api_token_secret')
],
required_one_of=[('api_password', 'api_token_id')], required_one_of=[('api_password', 'api_token_id')],
mutually_exclusive=[('clone', 'ostemplate')], # Creating a new container is done either by cloning an existing one, or based on a template.
) )
if not HAS_PROXMOXER: if not HAS_PROXMOXER:
@@ -542,6 +648,7 @@ def main():
if module.params['ostemplate'] is not None: if module.params['ostemplate'] is not None:
template_store = module.params['ostemplate'].split(":")[0] template_store = module.params['ostemplate'].split(":")[0]
timeout = module.params['timeout'] timeout = module.params['timeout']
clone = module.params['clone']
if module.params['proxmox_default_behavior'] == 'compatibility': if module.params['proxmox_default_behavior'] == 'compatibility':
old_default_values = dict( old_default_values = dict(
@@ -583,7 +690,8 @@ def main():
elif not vmid: elif not vmid:
module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state) module.exit_json(changed=False, msg="Vmid could not be fetched for the following action: %s" % state)
if state == 'present': # Create a new container
if state == 'present' and clone is None:
try: try:
if get_instance(proxmox, vmid) and not module.params['force']: if get_instance(proxmox, vmid) and not module.params['force']:
module.exit_json(changed=False, msg="VM with vmid = %s is already exists" % vmid) module.exit_json(changed=False, msg="VM with vmid = %s is already exists" % vmid)
@@ -595,8 +703,11 @@ def main():
elif not content_check(proxmox, node, module.params['ostemplate'], template_store): elif not content_check(proxmox, node, module.params['ostemplate'], template_store):
module.fail_json(msg="ostemplate '%s' not exists on node %s and storage %s" module.fail_json(msg="ostemplate '%s' not exists on node %s and storage %s"
% (module.params['ostemplate'], node, template_store)) % (module.params['ostemplate'], node, template_store))
except Exception as e:
module.fail_json(msg="Pre-creation checks of {VZ_TYPE} VM {vmid} failed with exception: {e}".format(VZ_TYPE=VZ_TYPE, vmid=vmid, e=e))
create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, try:
create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone,
cores=module.params['cores'], cores=module.params['cores'],
pool=module.params['pool'], pool=module.params['pool'],
password=module.params['password'], password=module.params['password'],
@@ -605,20 +716,40 @@ def main():
netif=module.params['netif'], netif=module.params['netif'],
mounts=module.params['mounts'], mounts=module.params['mounts'],
ip_address=module.params['ip_address'], ip_address=module.params['ip_address'],
onboot=int(module.params['onboot']), onboot=ansible_to_proxmox_bool(module.params['onboot']),
cpuunits=module.params['cpuunits'], cpuunits=module.params['cpuunits'],
nameserver=module.params['nameserver'], nameserver=module.params['nameserver'],
searchdomain=module.params['searchdomain'], searchdomain=module.params['searchdomain'],
force=int(module.params['force']), force=ansible_to_proxmox_bool(module.params['force']),
pubkey=module.params['pubkey'], pubkey=module.params['pubkey'],
features=",".join(module.params['features']) if module.params['features'] is not None else None, features=",".join(module.params['features']) if module.params['features'] is not None else None,
unprivileged=int(module.params['unprivileged']), unprivileged=ansible_to_proxmox_bool(module.params['unprivileged']),
description=module.params['description'], description=module.params['description'],
hookscript=module.params['hookscript']) hookscript=module.params['hookscript'])
module.exit_json(changed=True, msg="deployed VM %s from template %s" % (vmid, module.params['ostemplate'])) module.exit_json(changed=True, msg="Deployed VM %s from template %s" % (vmid, module.params['ostemplate']))
except Exception as e: except Exception as e:
module.fail_json(msg="creation of %s VM %s failed with exception: %s" % (VZ_TYPE, vmid, e)) module.fail_json(msg="Creation of %s VM %s failed with exception: %s" % (VZ_TYPE, vmid, e))
# Clone a container
elif state == 'present' and clone is not None:
try:
if get_instance(proxmox, vmid) and not module.params['force']:
module.exit_json(changed=False, msg="VM with vmid = %s is already exists" % vmid)
# If no vmid was passed, there cannot be another VM named 'hostname'
if not module.params['vmid'] and get_vmid(proxmox, hostname) and not module.params['force']:
module.exit_json(changed=False, msg="VM with hostname %s already exists and has ID number %s" % (hostname, get_vmid(proxmox, hostname)[0]))
if not get_instance(proxmox, clone):
module.exit_json(changed=False, msg="Container to be cloned does not exist")
except Exception as e:
module.fail_json(msg="Pre-clone checks of {VZ_TYPE} VM {vmid} failed with exception: {e}".format(VZ_TYPE=VZ_TYPE, vmid=vmid, e=e))
try:
create_instance(module, proxmox, vmid, node, disk, storage, cpus, memory, swap, timeout, clone)
module.exit_json(changed=True, msg="Cloned VM %s from %s" % (vmid, clone))
except Exception as e:
module.fail_json(msg="Cloning %s VM %s failed with exception: %s" % (VZ_TYPE, vmid, e))
elif state == 'started': elif state == 'started':
try: try:

View File

@@ -725,9 +725,10 @@ msg:
import re import re
import time import time
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils.six.moves.urllib.parse import quote from ansible.module_utils.six.moves.urllib.parse import quote
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
try: try:
from proxmoxer import ProxmoxAPI from proxmoxer import ProxmoxAPI
HAS_PROXMOXER = True HAS_PROXMOXER = True

View File

@@ -230,11 +230,12 @@ command:
import os import os
import json import json
import tempfile import tempfile
from distutils.version import LooseVersion
from ansible.module_utils.six.moves import shlex_quote from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
module = None module = None

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the servers. - Gather information about the servers.
- U(https://www.online.net/en/dedicated-server) - U(https://www.online.net/en/dedicated-server)
author: author:
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
extends_documentation_fragment: extends_documentation_fragment:
- community.general.online - community.general.online

View File

@@ -12,7 +12,7 @@ short_description: Gather information about Online user.
description: description:
- Gather information about the user. - Gather information about the user.
author: author:
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
extends_documentation_fragment: extends_documentation_fragment:
- community.general.online - community.general.online
''' '''

View File

@@ -97,7 +97,7 @@ EXAMPLES = '''
register: my_volume register: my_volume
''' '''
from distutils.version import LooseVersion from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
try: try:
import pyrax import pyrax

View File

@@ -16,7 +16,7 @@ DOCUMENTATION = '''
--- ---
module: scaleway_compute module: scaleway_compute
short_description: Scaleway compute management module short_description: Scaleway compute management module
author: Remy Leone (@sieben) author: Remy Leone (@remyleone)
description: description:
- "This module manages compute instances on Scaleway." - "This module manages compute instances on Scaleway."
extends_documentation_fragment: extends_documentation_fragment:
@@ -54,8 +54,15 @@ options:
organization: organization:
type: str type: str
description: description:
- Organization identifier - Organization identifier.
required: true - Exactly one of I(project) and I(organization) must be specified.
project:
type: str
description:
- Project identifier.
- Exactly one of I(project) and I(organization) must be specified.
version_added: 4.3.0
state: state:
type: str type: str
@@ -132,7 +139,7 @@ EXAMPLES = '''
name: foobar name: foobar
state: present state: present
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
organization: 951df375-e094-4d26-97c1-ba548eeb9c42 project: 951df375-e094-4d26-97c1-ba548eeb9c42
region: ams1 region: ams1
commercial_type: VC1S commercial_type: VC1S
tags: tags:
@@ -144,7 +151,7 @@ EXAMPLES = '''
name: foobar name: foobar
state: present state: present
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
organization: 951df375-e094-4d26-97c1-ba548eeb9c42 project: 951df375-e094-4d26-97c1-ba548eeb9c42
region: ams1 region: ams1
commercial_type: VC1S commercial_type: VC1S
security_group: 4a31b633-118e-4900-bd52-facf1085fc8d security_group: 4a31b633-118e-4900-bd52-facf1085fc8d
@@ -157,7 +164,7 @@ EXAMPLES = '''
name: foobar name: foobar
state: absent state: absent
image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe image: 89ee4018-f8c3-4dc4-a6b5-bca14f985ebe
organization: 951df375-e094-4d26-97c1-ba548eeb9c42 project: 951df375-e094-4d26-97c1-ba548eeb9c42
region: ams1 region: ams1
commercial_type: VC1S commercial_type: VC1S
''' '''
@@ -269,10 +276,15 @@ def create_server(compute_api, server):
"commercial_type": server["commercial_type"], "commercial_type": server["commercial_type"],
"image": server["image"], "image": server["image"],
"dynamic_ip_required": server["dynamic_ip_required"], "dynamic_ip_required": server["dynamic_ip_required"],
"name": server["name"], "name": server["name"]
"organization": server["organization"]
} }
if server["project"]:
data["project"] = server["project"]
if server["organization"]:
data["organization"] = server["organization"]
if server["security_group"]: if server["security_group"]:
data["security_group"] = server["security_group"] data["security_group"] = server["security_group"]
@@ -628,6 +640,7 @@ def core(module):
"enable_ipv6": module.params["enable_ipv6"], "enable_ipv6": module.params["enable_ipv6"],
"tags": module.params["tags"], "tags": module.params["tags"],
"organization": module.params["organization"], "organization": module.params["organization"],
"project": module.params["project"],
"security_group": module.params["security_group"] "security_group": module.params["security_group"]
} }
module.params['api_url'] = SCALEWAY_LOCATION[region]["api_endpoint"] module.params['api_url'] = SCALEWAY_LOCATION[region]["api_endpoint"]
@@ -655,7 +668,8 @@ def main():
public_ip=dict(default="absent"), public_ip=dict(default="absent"),
state=dict(choices=list(state_strategy.keys()), default='present'), state=dict(choices=list(state_strategy.keys()), default='present'),
tags=dict(type="list", elements="str", default=[]), tags=dict(type="list", elements="str", default=[]),
organization=dict(required=True), organization=dict(),
project=dict(),
wait=dict(type="bool", default=False), wait=dict(type="bool", default=False),
wait_timeout=dict(type="int", default=300), wait_timeout=dict(type="int", default=300),
wait_sleep_time=dict(type="int", default=3), wait_sleep_time=dict(type="int", default=3),
@@ -664,6 +678,12 @@ def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=argument_spec, argument_spec=argument_spec,
supports_check_mode=True, supports_check_mode=True,
mutually_exclusive=[
('organization', 'project'),
],
required_one_of=[
('organization', 'project'),
],
) )
core(module) core(module)

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the Scaleway images available. - Gather information about the Scaleway images available.
author: author:
- "Yanis Guenane (@Spredzy)" - "Yanis Guenane (@Spredzy)"
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
extends_documentation_fragment: extends_documentation_fragment:
- community.general.scaleway - community.general.scaleway

View File

@@ -13,7 +13,7 @@ DOCUMENTATION = '''
--- ---
module: scaleway_ip module: scaleway_ip
short_description: Scaleway IP management module short_description: Scaleway IP management module
author: Remy Leone (@sieben) author: Remy Leone (@remyleone)
description: description:
- This module manages IP on Scaleway account - This module manages IP on Scaleway account
U(https://developer.scaleway.com) U(https://developer.scaleway.com)

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the Scaleway ips available. - Gather information about the Scaleway ips available.
author: author:
- "Yanis Guenane (@Spredzy)" - "Yanis Guenane (@Spredzy)"
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
extends_documentation_fragment: extends_documentation_fragment:
- community.general.scaleway - community.general.scaleway

View File

@@ -16,7 +16,7 @@ DOCUMENTATION = '''
--- ---
module: scaleway_lb module: scaleway_lb
short_description: Scaleway load-balancer management module short_description: Scaleway load-balancer management module
author: Remy Leone (@sieben) author: Remy Leone (@remyleone)
description: description:
- "This module manages load-balancers on Scaleway." - "This module manages load-balancers on Scaleway."
extends_documentation_fragment: extends_documentation_fragment:

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the Scaleway organizations available. - Gather information about the Scaleway organizations available.
author: author:
- "Yanis Guenane (@Spredzy)" - "Yanis Guenane (@Spredzy)"
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
options: options:
api_url: api_url:
description: description:

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the Scaleway security groups available. - Gather information about the Scaleway security groups available.
author: author:
- "Yanis Guenane (@Spredzy)" - "Yanis Guenane (@Spredzy)"
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
options: options:
region: region:
type: str type: str

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the Scaleway servers available. - Gather information about the Scaleway servers available.
author: author:
- "Yanis Guenane (@Spredzy)" - "Yanis Guenane (@Spredzy)"
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
extends_documentation_fragment: extends_documentation_fragment:
- community.general.scaleway - community.general.scaleway

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the Scaleway snapshot available. - Gather information about the Scaleway snapshot available.
author: author:
- "Yanis Guenane (@Spredzy)" - "Yanis Guenane (@Spredzy)"
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
extends_documentation_fragment: extends_documentation_fragment:
- community.general.scaleway - community.general.scaleway

View File

@@ -16,7 +16,7 @@ DOCUMENTATION = '''
--- ---
module: scaleway_sshkey module: scaleway_sshkey
short_description: Scaleway SSH keys management module short_description: Scaleway SSH keys management module
author: Remy Leone (@sieben) author: Remy Leone (@remyleone)
description: description:
- This module manages SSH keys on Scaleway account - This module manages SSH keys on Scaleway account
U(https://developer.scaleway.com) U(https://developer.scaleway.com)

View File

@@ -16,7 +16,7 @@ DOCUMENTATION = '''
--- ---
module: scaleway_user_data module: scaleway_user_data
short_description: Scaleway user_data management module short_description: Scaleway user_data management module
author: Remy Leone (@sieben) author: Remy Leone (@remyleone)
description: description:
- "This module manages user_data on compute instances on Scaleway." - "This module manages user_data on compute instances on Scaleway."
- "It can be used to configure cloud-init for instance" - "It can be used to configure cloud-init for instance"
@@ -75,7 +75,7 @@ def patch_user_data(compute_api, server_id, key, value):
compute_api.module.debug("Starting patching user_data attributes") compute_api.module.debug("Starting patching user_data attributes")
path = "servers/%s/user_data/%s" % (server_id, key) path = "servers/%s/user_data/%s" % (server_id, key)
response = compute_api.patch(path=path, data=value, headers={"Content-type": "text/plain"}) response = compute_api.patch(path=path, data=value, headers={"Content-Type": "text/plain"})
if not response.ok: if not response.ok:
msg = 'Error during user_data patching: %s %s' % (response.status_code, response.body) msg = 'Error during user_data patching: %s %s' % (response.status_code, response.body)
compute_api.module.fail_json(msg=msg) compute_api.module.fail_json(msg=msg)

View File

@@ -51,6 +51,11 @@ options:
description: description:
- Name used to identify the volume. - Name used to identify the volume.
required: true required: true
project:
type: str
description:
- Scaleway project ID to which volume belongs.
version_added: 4.3.0
organization: organization:
type: str type: str
description: description:
@@ -71,7 +76,7 @@ EXAMPLES = '''
name: my-volume name: my-volume
state: present state: present
region: par1 region: par1
organization: "{{ scw_org }}" project: "{{ scw_org }}"
"size": 10000000000 "size": 10000000000
volume_type: l_ssd volume_type: l_ssd
register: server_creation_check_task register: server_creation_check_task
@@ -93,7 +98,7 @@ data:
"export_uri": null, "export_uri": null,
"id": "c675f420-cfeb-48ff-ba2a-9d2a4dbe3fcd", "id": "c675f420-cfeb-48ff-ba2a-9d2a4dbe3fcd",
"name": "volume-0-3", "name": "volume-0-3",
"organization": "000a115d-2852-4b0a-9ce8-47f1134ba95a", "project": "000a115d-2852-4b0a-9ce8-47f1134ba95a",
"server": null, "server": null,
"size": 10000000000, "size": 10000000000,
"volume_type": "l_ssd" "volume_type": "l_ssd"
@@ -106,31 +111,37 @@ from ansible.module_utils.basic import AnsibleModule
def core(module): def core(module):
region = module.params["region"]
state = module.params['state'] state = module.params['state']
name = module.params['name'] name = module.params['name']
organization = module.params['organization'] organization = module.params['organization']
project = module.params['project']
size = module.params['size'] size = module.params['size']
volume_type = module.params['volume_type'] volume_type = module.params['volume_type']
module.params['api_url'] = SCALEWAY_LOCATION[region]["api_endpoint"]
account_api = Scaleway(module) account_api = Scaleway(module)
response = account_api.get('volumes') response = account_api.get('volumes')
status_code = response.status_code status_code = response.status_code
volumes_json = response.json volumes_json = response.json
if project is None:
project = organization
if not response.ok: if not response.ok:
module.fail_json(msg='Error getting volume [{0}: {1}]'.format( module.fail_json(msg='Error getting volume [{0}: {1}]'.format(
status_code, response.json['message'])) status_code, response.json['message']))
volumeByName = None volumeByName = None
for volume in volumes_json['volumes']: for volume in volumes_json['volumes']:
if volume['organization'] == organization and volume['name'] == name: if volume['project'] == project and volume['name'] == name:
volumeByName = volume volumeByName = volume
if state in ('present',): if state in ('present',):
if volumeByName is not None: if volumeByName is not None:
module.exit_json(changed=False) module.exit_json(changed=False)
payload = {'name': name, 'organization': organization, 'size': size, 'volume_type': volume_type} payload = {'name': name, 'project': project, 'size': size, 'volume_type': volume_type}
response = account_api.post('/volumes', payload) response = account_api.post('/volumes', payload)
@@ -161,6 +172,7 @@ def main():
state=dict(default='present', choices=['absent', 'present']), state=dict(default='present', choices=['absent', 'present']),
name=dict(required=True), name=dict(required=True),
size=dict(type='int'), size=dict(type='int'),
project=dict(),
organization=dict(), organization=dict(),
volume_type=dict(), volume_type=dict(),
region=dict(required=True, choices=list(SCALEWAY_LOCATION.keys())), region=dict(required=True, choices=list(SCALEWAY_LOCATION.keys())),
@@ -168,6 +180,12 @@ def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=argument_spec, argument_spec=argument_spec,
supports_check_mode=True, supports_check_mode=True,
mutually_exclusive=[
('organization', 'project'),
],
required_one_of=[
('organization', 'project'),
],
) )
core(module) core(module)

View File

@@ -15,7 +15,7 @@ description:
- Gather information about the Scaleway volumes available. - Gather information about the Scaleway volumes available.
author: author:
- "Yanis Guenane (@Spredzy)" - "Yanis Guenane (@Spredzy)"
- "Remy Leone (@sieben)" - "Remy Leone (@remyleone)"
extends_documentation_fragment: extends_documentation_fragment:
- community.general.scaleway - community.general.scaleway

View File

@@ -117,9 +117,10 @@ state:
''' '''
import os import os
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
PACKAGE_STATE_MAP = dict( PACKAGE_STATE_MAP = dict(
present="--install", present="--install",

View File

@@ -60,6 +60,7 @@ extends_documentation_fragment:
- community.general.redis.documentation - community.general.redis.documentation
seealso: seealso:
- module: community.general.redis_data_incr
- module: community.general.redis_data_info - module: community.general.redis_data_info
- module: community.general.redis - module: community.general.redis
''' '''

View File

@@ -47,7 +47,7 @@ notes:
run the C(GET) command on the key, otherwise the module will fail. run the C(GET) command on the key, otherwise the module will fail.
seealso: seealso:
- module: community.general.redis_set - module: community.general.redis_data
- module: community.general.redis_data_info - module: community.general.redis_data_info
- module: community.general.redis - module: community.general.redis
''' '''

View File

@@ -26,6 +26,8 @@ extends_documentation_fragment:
- community.general.redis - community.general.redis
seealso: seealso:
- module: community.general.redis_data
- module: community.general.redis_data_incr
- module: community.general.redis_info - module: community.general.redis_info
- module: community.general.redis - module: community.general.redis
''' '''

View File

@@ -0,0 +1 @@
./net_tools/dnsimple_info.py

View File

@@ -85,11 +85,6 @@ import os.path
import shutil import shutil
import tempfile import tempfile
try: # python 3.3+
from shlex import quote
except ImportError: # older python
from pipes import quote
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
@@ -154,9 +149,9 @@ def main():
# Use 7zip when we have a binary, otherwise try to mount # Use 7zip when we have a binary, otherwise try to mount
if binary: if binary:
cmd = '%s x "%s" -o"%s" %s' % (binary, image, tmp_dir, ' '.join([quote(f) for f in extract_files])) cmd = [binary, 'x', image, '-o%s' % tmp_dir] + extract_files
else: else:
cmd = 'mount -o loop,ro "%s" "%s"' % (image, tmp_dir) cmd = [module.get_bin_path('mount'), '-o', 'loop,ro', image, tmp_dir]
rc, out, err = module.run_command(cmd) rc, out, err = module.run_command(cmd)
if rc != 0: if rc != 0:
@@ -201,7 +196,7 @@ def main():
result['changed'] = True result['changed'] = True
finally: finally:
if not binary: if not binary:
module.run_command('umount "%s"' % tmp_dir) module.run_command([module.get_bin_path('umount'), tmp_dir])
shutil.rmtree(tmp_dir) shutil.rmtree(tmp_dir)

View File

@@ -12,9 +12,9 @@ DOCUMENTATION = '''
module: xattr module: xattr
short_description: Manage user defined extended attributes short_description: Manage user defined extended attributes
description: description:
- Manages filesystem user defined extended attributes. - Manages filesystem user defined extended attributes.
- Requires that extended attributes are enabled on the target filesystem - Requires that extended attributes are enabled on the target filesystem
and that the setfattr/getfattr utilities are present. and that the setfattr/getfattr utilities are present.
options: options:
path: path:
description: description:
@@ -34,13 +34,13 @@ options:
type: str type: str
value: value:
description: description:
- The value to set the named name/key to, it automatically sets the C(state) to 'set'. - The value to set the named name/key to, it automatically sets the I(state) to C(present).
type: str type: str
state: state:
description: description:
- defines which state you want to do. - defines which state you want to do.
C(read) retrieves the current value for a C(key) (default) C(read) retrieves the current value for a I(key) (default)
C(present) sets C(name) to C(value), default if value is set C(present) sets I(path) to C(value), default if value is set
C(all) dumps all data C(all) dumps all data
C(keys) retrieves all keys C(keys) retrieves all keys
C(absent) deletes the key C(absent) deletes the key
@@ -49,14 +49,14 @@ options:
default: read default: read
follow: follow:
description: description:
- If C(yes), dereferences symlinks and sets/gets attributes on symlink target, - If C(true), dereferences symlinks and sets/gets attributes on symlink target,
otherwise acts on symlink itself. otherwise acts on symlink itself.
type: bool type: bool
default: yes default: true
notes: notes:
- As of Ansible 2.3, the I(name) option has been changed to I(path) as default, but I(name) still works as well. - As of Ansible 2.3, the I(name) option has been changed to I(path) as default, but I(name) still works as well.
author: author:
- Brian Coca (@bcoca) - Brian Coca (@bcoca)
''' '''
EXAMPLES = ''' EXAMPLES = '''
@@ -116,7 +116,8 @@ def get_xattr(module, path, key, follow):
if key is None: if key is None:
cmd.append('-d') cmd.append('-d')
else: else:
cmd.append('-n %s' % key) cmd.append('-n')
cmd.append(key)
cmd.append(path) cmd.append(path)
return _run_xattr(module, cmd, False) return _run_xattr(module, cmd, False)
@@ -127,8 +128,10 @@ def set_xattr(module, path, key, value, follow):
cmd = [module.get_bin_path('setfattr', True)] cmd = [module.get_bin_path('setfattr', True)]
if not follow: if not follow:
cmd.append('-h') cmd.append('-h')
cmd.append('-n %s' % key) cmd.append('-n')
cmd.append('-v %s' % value) cmd.append(key)
cmd.append('-v')
cmd.append(value)
cmd.append(path) cmd.append(path)
return _run_xattr(module, cmd) return _run_xattr(module, cmd)
@@ -139,7 +142,8 @@ def rm_xattr(module, path, key, follow):
cmd = [module.get_bin_path('setfattr', True)] cmd = [module.get_bin_path('setfattr', True)]
if not follow: if not follow:
cmd.append('-h') cmd.append('-h')
cmd.append('-x %s' % key) cmd.append('-x')
cmd.append(key)
cmd.append(path) cmd.append(path)
return _run_xattr(module, cmd, False) return _run_xattr(module, cmd, False)
@@ -148,7 +152,7 @@ def rm_xattr(module, path, key, follow):
def _run_xattr(module, cmd, check_rc=True): def _run_xattr(module, cmd, check_rc=True):
try: try:
(rc, out, err) = module.run_command(' '.join(cmd), check_rc=check_rc) (rc, out, err) = module.run_command(cmd, check_rc=check_rc)
except Exception as e: except Exception as e:
module.fail_json(msg="%s!" % to_native(e)) module.fail_json(msg="%s!" % to_native(e))

View File

@@ -356,9 +356,10 @@ import os
import re import re
import traceback import traceback
from distutils.version import LooseVersion
from io import BytesIO from io import BytesIO
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
LXML_IMP_ERR = None LXML_IMP_ERR = None
try: try:
from lxml import etree, objectify from lxml import etree, objectify

View File

@@ -0,0 +1 @@
source_control/gitlab/gitlab_branch.py

View File

@@ -27,11 +27,14 @@ options:
choices: ["absent", "present"] choices: ["absent", "present"]
type: str type: str
dynamicupdate: dynamicupdate:
description: Apply dynamic update to zone description: Apply dynamic update to zone.
required: false default: false
default: "false" type: bool
choices: ["false", "true"] allowsyncptr:
type: str description: Allow synchronization of forward and reverse records in the zone.
default: false
type: bool
version_added: 4.3.0
extends_documentation_fragment: extends_documentation_fragment:
- community.general.ipa.documentation - community.general.ipa.documentation
@@ -60,6 +63,14 @@ EXAMPLES = r'''
ipa_user: admin ipa_user: admin
ipa_pass: topsecret ipa_pass: topsecret
state: absent state: absent
- name: Ensure dns zone is present and is allowing sync
community.general.ipa_dnszone:
ipa_host: spider.example.com
ipa_pass: Passw0rd!
state: present
zone_name: example.com
allowsyncptr: true
''' '''
RETURN = r''' RETURN = r'''
@@ -79,25 +90,37 @@ class DNSZoneIPAClient(IPAClient):
super(DNSZoneIPAClient, self).__init__(module, host, port, protocol) super(DNSZoneIPAClient, self).__init__(module, host, port, protocol)
def dnszone_find(self, zone_name, details=None): def dnszone_find(self, zone_name, details=None):
itens = {'idnsname': zone_name} items = {'all': 'true',
'idnsname': zone_name, }
if details is not None: if details is not None:
itens.update(details) items.update(details)
return self._post_json( return self._post_json(
method='dnszone_find', method='dnszone_find',
name=zone_name, name=zone_name,
item=itens item=items
) )
def dnszone_add(self, zone_name=None, details=None): def dnszone_add(self, zone_name=None, details=None):
itens = {} items = {}
if details is not None: if details is not None:
itens.update(details) items.update(details)
return self._post_json( return self._post_json(
method='dnszone_add', method='dnszone_add',
name=zone_name, name=zone_name,
item=itens item=items
)
def dnszone_mod(self, zone_name=None, details=None):
items = {}
if details is not None:
items.update(details)
return self._post_json(
method='dnszone_mod',
name=zone_name,
item=items
) )
def dnszone_del(self, zone_name=None, record_name=None, details=None): def dnszone_del(self, zone_name=None, record_name=None, details=None):
@@ -109,18 +132,29 @@ def ensure(module, client):
zone_name = module.params['zone_name'] zone_name = module.params['zone_name']
state = module.params['state'] state = module.params['state']
dynamicupdate = module.params['dynamicupdate'] dynamicupdate = module.params['dynamicupdate']
allowsyncptr = module.params['allowsyncptr']
ipa_dnszone = client.dnszone_find(zone_name)
changed = False changed = False
# does zone exist
ipa_dnszone = client.dnszone_find(zone_name)
if state == 'present': if state == 'present':
if not ipa_dnszone: if not ipa_dnszone:
changed = True changed = True
if not module.check_mode: if not module.check_mode:
client.dnszone_add(zone_name=zone_name, details={'idnsallowdynupdate': dynamicupdate}) 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():
changed = True
if not module.check_mode:
client.dnszone_mod(zone_name=zone_name, details={'idnsallowdynupdate': dynamicupdate, 'idnsallowsyncptr': allowsyncptr})
else: else:
changed = False changed = False
# state is absent
else: else:
# check for generic zone existence
if ipa_dnszone: if ipa_dnszone:
changed = True changed = True
if not module.check_mode: if not module.check_mode:
@@ -133,7 +167,8 @@ def main():
argument_spec = ipa_argument_spec() argument_spec = ipa_argument_spec()
argument_spec.update(zone_name=dict(type='str', required=True), argument_spec.update(zone_name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']), state=dict(type='str', default='present', choices=['present', 'absent']),
dynamicupdate=dict(type='str', required=False, default='false', choices=['true', 'false']), dynamicupdate=dict(type='bool', required=False, default=False),
allowsyncptr=dict(type='bool', required=False, default=False),
) )
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,

View File

@@ -74,11 +74,12 @@ subca:
type: dict type: dict
''' '''
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
class SubCAIPAClient(IPAClient): class SubCAIPAClient(IPAClient):
def __init__(self, module, host, port, protocol): def __init__(self, module, host, port, protocol):

View File

@@ -0,0 +1,132 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: keycloak_realm_info
short_description: Allows obtaining Keycloak realm public information via Keycloak API
version_added: 4.3.0
description:
- This module allows you to get Keycloak realm public information via the Keycloak REST API.
- The names of module options are snake_cased versions of the camelCase ones found in the
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/8.0/rest-api/index.html).
- Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and will
be returned that way by this module. You may pass single values for attributes when calling the module,
and this will be translated into a list suitable for the API.
options:
auth_keycloak_url:
description:
- URL to the Keycloak instance.
type: str
required: true
aliases:
- url
validate_certs:
description:
- Verify TLS certificates (do not disable this in production).
type: bool
default: yes
realm:
type: str
description:
- They Keycloak realm ID.
default: 'master'
author:
- Fynn Chen (@fynncfchen)
'''
EXAMPLES = '''
- name: Get a Keycloak public key
community.general.keycloak_realm_info:
realm: MyCustomRealm
auth_keycloak_url: https://auth.example.com/auth
delegate_to: localhost
'''
RETURN = '''
msg:
description: Message as to what action was taken.
returned: always
type: str
realm_info:
description:
- Representation of the realm public infomation.
returned: always
type: dict
contains:
realm:
description: Realm ID.
type: str
returned: always
sample: MyRealm
public_key:
description: Public key of the realm.
type: str
returned: always
sample: MIIBIjANBgkqhkiG9w0BAQEFAAO...
token-service:
description: Token endpoint URL.
type: str
returned: always
sample: https://auth.example.com/auth/realms/MyRealm/protocol/openid-connect
account-service:
description: Account console URL.
type: str
returned: always
sample: https://auth.example.com/auth/realms/MyRealm/account
tokens-not-before:
description: The token not before.
type: int
returned: always
sample: 0
'''
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI
from ansible.module_utils.basic import AnsibleModule
def main():
"""
Module execution
:return:
"""
argument_spec = dict(
auth_keycloak_url=dict(type='str', aliases=['url'], required=True, no_log=False),
validate_certs=dict(type='bool', default=True),
realm=dict(default='master'),
)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True)
result = dict(changed=False, msg='', realm_info='')
kc = KeycloakAPI(module, {})
realm = module.params.get('realm')
realm_info = kc.get_realm_info_by_id(realm=realm)
result['realm_info'] = realm_info
result['msg'] = 'Get realm public info successful for ID {realm}'.format(realm=realm)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -64,6 +64,7 @@ options:
choices: choices:
- ldap - ldap
- kerberos - kerberos
- sssd
provider_type: provider_type:
description: description:
@@ -83,9 +84,10 @@ options:
config: config:
description: description:
- Dict specifying the configuration options for the provider; the contents differ depending on - Dict specifying the configuration options for the provider; the contents differ depending on
the value of I(provider_id). Examples are given below for C(ldap) and C(kerberos). It is easiest the value of I(provider_id). Examples are given below for C(ldap), C(kerberos) and C(sssd).
to obtain valid config values by dumping an already-existing user federation configuration It is easiest to obtain valid config values by dumping an already-existing user federation
through check-mode in the I(existing) field. configuration through check-mode in the I(existing) field.
- The value C(sssd) has been supported since community.general 4.2.0.
type: dict type: dict
suboptions: suboptions:
enabled: enabled:
@@ -531,6 +533,22 @@ EXAMPLES = '''
allowPasswordAuthentication: false allowPasswordAuthentication: false
updateProfileFirstLogin: false updateProfileFirstLogin: false
- name: Create sssd user federation
community.general.keycloak_user_federation:
auth_keycloak_url: https://keycloak.example.com/auth
auth_realm: master
auth_username: admin
auth_password: password
realm: my-realm
name: my-sssd
state: present
provider_id: sssd
provider_type: org.keycloak.storage.UserStorageProvider
config:
priority: 0
enabled: true
cachePolicy: DEFAULT
- name: Delete user federation - name: Delete user federation
community.general.keycloak_user_federation: community.general.keycloak_user_federation:
auth_keycloak_url: https://keycloak.example.com/auth auth_keycloak_url: https://keycloak.example.com/auth
@@ -765,7 +783,7 @@ def main():
realm=dict(type='str', default='master'), realm=dict(type='str', default='master'),
id=dict(type='str'), id=dict(type='str'),
name=dict(type='str'), name=dict(type='str'),
provider_id=dict(type='str', aliases=['providerId'], choices=['ldap', 'kerberos']), provider_id=dict(type='str', aliases=['providerId'], choices=['ldap', 'kerberos', 'sssd']),
provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'), provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'),
parent_id=dict(type='str', aliases=['parentId']), parent_id=dict(type='str', aliases=['parentId']),
mappers=dict(type='list', elements='dict', options=mapper_spec), mappers=dict(type='list', elements='dict', options=mapper_spec),
@@ -843,8 +861,8 @@ def main():
# special handling of mappers list to allow change detection # special handling of mappers list to allow change detection
if module.params.get('mappers') is not None: if module.params.get('mappers') is not None:
if module.params['provider_id'] == 'kerberos': if module.params['provider_id'] in ['kerberos', 'sssd']:
module.fail_json(msg='Cannot configure mappers for Kerberos federations.') module.fail_json(msg='Cannot configure mappers for {type} provider.'.format(type=module.params['provider_id']))
for change in module.params['mappers']: for change in module.params['mappers']:
change = dict((k, v) for k, v in change.items() if change[k] is not None) change = dict((k, v) for k, v in change.items() if change[k] is not None)
if change.get('id') is None and change.get('name') is None: if change.get('id') is None and change.get('name') is None:

View File

@@ -0,0 +1 @@
remote_management/redfish/ilo_redfish_config.py

View File

@@ -0,0 +1 @@
remote_management/redfish/ilo_redfish_info.py

View File

@@ -0,0 +1 @@
./identity/keycloak/keycloak_realm_info.py

View File

@@ -143,7 +143,8 @@ annotation:
import json import json
import time import time
import traceback import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
REQUESTS_IMP_ERR = None REQUESTS_IMP_ERR = None
try: try:

View File

@@ -63,7 +63,7 @@ def query_log_status(module, le_path, path, state="present"):
""" Returns whether a log is followed or not. """ """ Returns whether a log is followed or not. """
if state == "present": if state == "present":
rc, out, err = module.run_command("%s followed %s" % (le_path, path)) rc, out, err = module.run_command([le_path, "followed", path])
if rc == 0: if rc == 0:
return True return True
@@ -87,7 +87,7 @@ def follow_log(module, le_path, logs, name=None, logtype=None):
cmd.extend(['--name', name]) cmd.extend(['--name', name])
if logtype: if logtype:
cmd.extend(['--type', logtype]) cmd.extend(['--type', logtype])
rc, out, err = module.run_command(' '.join(cmd)) rc, out, err = module.run_command(cmd)
if not query_log_status(module, le_path, log): if not query_log_status(module, le_path, log):
module.fail_json(msg="failed to follow '%s': %s" % (log, err.strip())) module.fail_json(msg="failed to follow '%s': %s" % (log, err.strip()))

View File

@@ -82,7 +82,7 @@ PACKAGE_STATE_MAP = dict(
def is_plugin_present(module, plugin_bin, plugin_name): def is_plugin_present(module, plugin_bin, plugin_name):
cmd_args = [plugin_bin, "list", plugin_name] cmd_args = [plugin_bin, "list", plugin_name]
rc, out, err = module.run_command(" ".join(cmd_args)) rc, out, err = module.run_command(cmd_args)
return rc == 0 return rc == 0

View File

@@ -122,7 +122,7 @@ class Monit(object):
return self._monit_version return self._monit_version
def _get_monit_version(self): def _get_monit_version(self):
rc, out, err = self.module.run_command('%s -V' % self.monit_bin_path, check_rc=True) rc, out, err = self.module.run_command([self.monit_bin_path, '-V'], check_rc=True)
version_line = out.split('\n')[0] version_line = out.split('\n')[0]
raw_version = re.search(r"([0-9]+\.){1,2}([0-9]+)?", version_line).group() raw_version = re.search(r"([0-9]+\.){1,2}([0-9]+)?", version_line).group()
return raw_version, tuple(map(int, raw_version.split('.'))) return raw_version, tuple(map(int, raw_version.split('.')))
@@ -140,7 +140,7 @@ class Monit(object):
@property @property
def command_args(self): def command_args(self):
return "-B" if self.monit_version() > (5, 18) else "" return ["-B"] if self.monit_version() > (5, 18) else []
def get_status(self, validate=False): def get_status(self, validate=False):
"""Return the status of the process in monit. """Return the status of the process in monit.
@@ -149,7 +149,7 @@ class Monit(object):
""" """
monit_command = "validate" if validate else "status" monit_command = "validate" if validate else "status"
check_rc = False if validate else True # 'validate' always has rc = 1 check_rc = False if validate else True # 'validate' always has rc = 1
command = ' '.join([self.monit_bin_path, monit_command, self.command_args, self.process_name]) command = [self.monit_bin_path, monit_command] + self.command_args + [self.process_name]
rc, out, err = self.module.run_command(command, check_rc=check_rc) rc, out, err = self.module.run_command(command, check_rc=check_rc)
return self._parse_status(out, err) return self._parse_status(out, err)
@@ -182,7 +182,8 @@ class Monit(object):
return status return status
def is_process_present(self): def is_process_present(self):
rc, out, err = self.module.run_command('%s summary %s' % (self.monit_bin_path, self.command_args), check_rc=True) command = [self.monit_bin_path, 'summary'] + self.command_args
rc, out, err = self.module.run_command(command, check_rc=True)
return bool(re.findall(r'\b%s\b' % self.process_name, out)) return bool(re.findall(r'\b%s\b' % self.process_name, out))
def is_process_running(self): def is_process_running(self):
@@ -190,7 +191,7 @@ class Monit(object):
def run_command(self, command): def run_command(self, command):
"""Runs a monit command, and returns the new status.""" """Runs a monit command, and returns the new status."""
return self.module.run_command('%s %s %s' % (self.monit_bin_path, command, self.process_name), check_rc=True) return self.module.run_command([self.monit_bin_path, command, self.process_name], check_rc=True)
def wait_for_status_change(self, current_status): def wait_for_status_change(self, current_status):
running_status = self.get_status() running_status = self.get_status()
@@ -228,7 +229,7 @@ class Monit(object):
return current_status return current_status
def reload(self): def reload(self):
rc, out, err = self.module.run_command('%s reload' % self.monit_bin_path) rc, out, err = self.module.run_command([self.monit_bin_path, 'reload'])
if rc != 0: if rc != 0:
self.exit_fail('monit reload failed', stdout=out, stderr=err) self.exit_fail('monit reload failed', stdout=out, stderr=err)
self.exit_success(state='reloaded') self.exit_success(state='reloaded')

View File

@@ -148,9 +148,10 @@ EXAMPLES = '''
RETURN = r"""# """ RETURN = r"""# """
import traceback import traceback
from distutils.version import LooseVersion
import re import re
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
class DNSimpleV1(): class DNSimpleV1():
"""class which uses dnsimple-python < 2""" """class which uses dnsimple-python < 2"""

View File

@@ -0,0 +1,335 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: Edward Hilgendorf, <edward@hilgendorf.me>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: dnsimple_info
short_description: Pull basic info from DNSimple API
version_added: "4.2.0"
description: Retrieve existing records and domains from DNSimple API.
options:
name:
description:
- The domain name to retrieve info from.
- Will return all associated records for this domain if specified.
- If not specified, will return all domains associated with the account ID.
type: str
account_id:
description: The account ID to query.
required: true
type: str
api_key:
description: The API key to use.
required: true
type: str
record:
description:
- The record to find.
- If specified, only this record will be returned instead of all records.
required: false
type: str
sandbox:
description: Whether or not to use sandbox environment.
required: false
default: false
type: bool
author:
- Edward Hilgendorf (@edhilgendorf)
'''
EXAMPLES = r'''
- name: Get all domains from an account
community.general.dnsimple_info:
account_id: "1234"
api_key: "1234"
- name: Get all records from a domain
community.general.dnsimple_info:
name: "example.com"
account_id: "1234"
api_key: "1234"
- name: Get all info from a matching record
community.general.dnsimple_info:
name: "example.com"
record: "subdomain"
account_id: "1234"
api_key: "1234"
'''
RETURN = r'''
dnsimple_domain_info:
description: Returns a list of dictionaries of all domains associated with the supplied account ID.
type: list
elements: dict
returned: success when I(name) is not specified
sample:
- account_id: 1234
created_at: '2021-10-16T21:25:42Z'
id: 123456
last_transferred_at:
name: example.com
reverse: false
secondary: false
updated_at: '2021-11-10T20:22:50Z'
contains:
account_id:
description: The account ID.
type: int
created_at:
description: When the domain entry was created.
type: str
id:
description: ID of the entry.
type: int
last_transferred_at:
description: Date the domain was transferred, or empty if not.
type: str
name:
description: Name of the record.
type: str
reverse:
description: Whether or not it is a reverse zone record.
type: bool
updated_at:
description: When the domain entry was updated.
type: str
dnsimple_records_info:
description: Returns a list of dictionaries with all records for the domain supplied.
type: list
elements: dict
returned: success when I(name) is specified, but I(record) is not
sample:
- content: ns1.dnsimple.com admin.dnsimple.com
created_at: '2021-10-16T19:07:34Z'
id: 12345
name: 'catheadbiscuit'
parent_id: null
priority: null
regions:
- global
system_record: true
ttl: 3600
type: SOA
updated_at: '2021-11-15T23:55:51Z'
zone_id: example.com
contains:
content:
description: Content of the returned record.
type: str
created_at:
description: When the domain entry was created.
type: str
id:
description: ID of the entry.
type: int
name:
description: Name of the record.
type: str
parent_id:
description: Parent record or null.
type: int
priority:
description: Priority setting of the record.
type: str
regions:
description: List of regions where the record is available.
type: list
system_record:
description: Whether or not it is a system record.
type: bool
ttl:
description: Record TTL.
type: int
type:
description: Record type.
type: str
updated_at:
description: When the domain entry was updated.
type: str
zone_id:
description: ID of the zone that the record is associated with.
type: str
dnsimple_record_info:
description: Returns a list of dictionaries that match the record supplied.
returned: success when I(name) and I(record) are specified
type: list
elements: dict
sample:
- content: 1.2.3.4
created_at: '2021-11-15T23:55:51Z'
id: 123456
name: catheadbiscuit
parent_id: null
priority: null
regions:
- global
system_record: false
ttl: 3600
type: A
updated_at: '2021-11-15T23:55:51Z'
zone_id: example.com
contains:
content:
description: Content of the returned record.
type: str
created_at:
description: When the domain entry was created.
type: str
id:
description: ID of the entry.
type: int
name:
description: Name of the record.
type: str
parent_id:
description: Parent record or null.
type: int
priority:
description: Priority setting of the record.
type: str
regions:
description: List of regions where the record is available.
type: list
system_record:
description: Whether or not it is a system record.
type: bool
ttl:
description: Record TTL.
type: int
type:
description: Record type.
type: str
updated_at:
description: When the domain entry was updated.
type: str
zone_id:
description: ID of the zone that the record is associated with.
type: str
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import missing_required_lib
import json
try:
from requests import Request, Session
except ImportError:
HAS_ANOTHER_LIBRARY = False
ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
HAS_ANOTHER_LIBRARY = True
def build_url(account, key, is_sandbox):
headers = {'Accept': 'application/json',
'Authorization': 'Bearer ' + key}
url = 'https://api{sandbox}.dnsimple.com/'.format(
sandbox=".sandbox" if is_sandbox else "") + 'v2/' + account
req = Request(url=url, headers=headers)
prepped_request = req.prepare()
return prepped_request
def iterate_data(module, request_object):
base_url = request_object.url
response = Session().send(request_object)
if 'pagination' in response.json():
data = response.json()["data"]
pages = response.json()["pagination"]["total_pages"]
if int(pages) > 1:
for page in range(1, pages):
page = page + 1
request_object.url = base_url + '&page=' + str(page)
new_results = Session().send(request_object)
data = data + new_results.json()["data"]
return(data)
else:
module.fail_json('API Call failed, check ID, key and sandbox values')
def record_info(dnsimple_mod, req_obj):
req_obj.url, req_obj.method = req_obj.url + '/zones/' + dnsimple_mod.params["name"] + '/records?name=' + dnsimple_mod.params["record"], 'GET'
return iterate_data(dnsimple_mod, req_obj)
def domain_info(dnsimple_mod, req_obj):
req_obj.url, req_obj.method = req_obj.url + '/zones/' + dnsimple_mod.params["name"] + '/records?per_page=100', 'GET'
return iterate_data(dnsimple_mod, req_obj)
def account_info(dnsimple_mod, req_obj):
req_obj.url, req_obj.method = req_obj.url + '/zones/?per_page=100', 'GET'
return iterate_data(dnsimple_mod, req_obj)
def main():
# define available arguments/parameters a user can pass to the module
fields = {
"account_id": {"required": True, "type": "str"},
"api_key": {"required": True, "type": "str", "no_log": True},
"name": {"required": False, "type": "str"},
"record": {"required": False, "type": "str"},
"sandbox": {"required": False, "type": "bool", "default": False}
}
result = {
'changed': False
}
module = AnsibleModule(
argument_spec=fields,
supports_check_mode=True
)
params = module.params
req = build_url(params['account_id'],
params['api_key'],
params['sandbox'])
if not HAS_ANOTHER_LIBRARY:
# Needs: from ansible.module_utils.basic import missing_required_lib
module.exit_json(
msg=missing_required_lib('another_library'),
exception=ANOTHER_LIBRARY_IMPORT_ERROR)
# At minimum we need account and key
if params['account_id'] and params['api_key']:
# If we have a record return info on that record
if params['name'] and params['record']:
result['dnsimple_record_info'] = record_info(module, req)
module.exit_json(**result)
# If we have the account only and domain, return records for the domain
elif params['name']:
result['dnsimple_records_info'] = domain_info(module, req)
module.exit_json(**result)
# If we have the account only, return domains
else:
result['dnsimple_domain_info'] = account_info(module, req)
module.exit_json(**result)
else:
module.fail_json(msg="Need at least account_id and api_key")
if __name__ == '__main__':
main()

View File

@@ -76,7 +76,7 @@ class Namespace(object):
def exists(self): def exists(self):
'''Check if the namespace already exists''' '''Check if the namespace already exists'''
rc, out, err = self.module.run_command('ip netns list') rc, out, err = self.module.run_command(['ip', 'netns', 'list'])
if rc != 0: if rc != 0:
self.module.fail_json(msg=to_text(err)) self.module.fail_json(msg=to_text(err))
return self.name in out return self.name in out

View File

@@ -55,8 +55,10 @@ options:
- Type C(generic) is added in Ansible 2.5. - Type C(generic) is added in Ansible 2.5.
- Type C(infiniband) is added in community.general 2.0.0. - Type C(infiniband) is added in community.general 2.0.0.
- Type C(gsm) is added in community.general 3.7.0. - Type C(gsm) is added in community.general 3.7.0.
- Type C(wireguard) is added in community.general 4.3.0
type: str type: str
choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi, gsm ] choices: [ bond, bond-slave, bridge, bridge-slave, dummy, ethernet, generic, gre, infiniband, ipip, sit, team, team-slave, vlan, vxlan, wifi, gsm,
wireguard ]
mode: mode:
description: description:
- This is the type of device or network connection that you wish to create for a bond or bridge. - This is the type of device or network connection that you wish to create for a bond or bridge.
@@ -70,7 +72,7 @@ options:
ip4: ip4:
description: description:
- List of IPv4 addresses to this interface. - List of IPv4 addresses to this interface.
- Use the format C(192.0.2.24/24). - Use the format C(192.0.2.24/24) or C(192.0.2.24).
- If defined and I(method4) is not specified, automatically set C(ipv4.method) to C(manual). - If defined and I(method4) is not specified, automatically set C(ipv4.method) to C(manual).
type: list type: list
elements: str elements: str
@@ -143,10 +145,11 @@ options:
version_added: 3.3.0 version_added: 3.3.0
ip6: ip6:
description: description:
- The IPv6 address to this interface. - List of IPv6 addresses to this interface.
- Use the format C(abbe::cafe). - Use the format C(abbe::cafe/128) or C(abbe::cafe).
- If defined and I(method6) is not specified, automatically set C(ipv6.method) to C(manual). - If defined and I(method6) is not specified, automatically set C(ipv6.method) to C(manual).
type: str type: list
elements: str
gw6: gw6:
description: description:
- The IPv6 gateway for this interface. - The IPv6 gateway for this interface.
@@ -183,6 +186,18 @@ options:
type: str type: str
choices: [ignore, auto, dhcp, link-local, manual, shared, disabled] choices: [ignore, auto, dhcp, link-local, manual, shared, disabled]
version_added: 2.2.0 version_added: 2.2.0
ip_privacy6:
description:
- If enabled, it makes the kernel generate a temporary IPv6 address in addition to the public one.
type: str
choices: [disabled, prefer-public-addr, prefer-temp-addr, unknown]
version_added: 4.2.0
addr_gen_mode6:
description:
- Configure method for creating the address for use with IPv6 Stateless Address Autoconfiguration.
type: str
choices: [eui64, stable-privacy]
version_added: 4.2.0
mtu: mtu:
description: description:
- The connection MTU, e.g. 9000. This can't be applied when creating the interface and is done once the interface has been created. - The connection MTU, e.g. 9000. This can't be applied when creating the interface and is done once the interface has been created.
@@ -741,6 +756,62 @@ options:
- The username used to authenticate with the network, if required. - The username used to authenticate with the network, if required.
- Many providers do not require a username, or accept any username. - Many providers do not require a username, or accept any username.
- But if a username is required, it is specified here. - But if a username is required, it is specified here.
wireguard:
description:
- The configuration of the Wireguard connection.
- Note the list of suboption attributes may vary depending on which version of NetworkManager/nmcli is installed on the host.
- 'An up-to-date list of supported attributes can be found here:
U(https://networkmanager.dev/docs/api/latest/settings-wireguard.html).'
- 'For instance to configure a listen port:
C({listen-port: 12345}).'
type: dict
version_added: 4.3.0
suboptions:
fwmark:
description:
- The 32-bit fwmark for outgoing packets.
- The use of fwmark is optional and is by default off. Setting it to 0 disables it.
- Note that I(wireguard.ip4-auto-default-route) or I(wireguard.ip6-auto-default-route) enabled, implies to automatically choose a fwmark.
type: int
ip4-auto-default-route:
description:
- Whether to enable special handling of the IPv4 default route.
- If enabled, the IPv4 default route from I(wireguard.peer-routes) will be placed to a dedicated routing-table and two policy
routing rules will be added.
- The fwmark number is also used as routing-table for the default-route, and if fwmark is zero, an unused fwmark/table is chosen
automatically. This corresponds to what wg-quick does with Table=auto and what WireGuard calls "Improved Rule-based Routing"
type: bool
ip6-auto-default-route:
description:
- Like I(wireguard.ip4-auto-default-route), but for the IPv6 default route.
type: bool
listen-port:
description: The WireGuard connection listen-port. If not specified, the port will be chosen randomly when the
interface comes up.
type: int
mtu:
description:
- If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple fragments.
- If zero a default MTU is used. Note that contrary to wg-quick's MTU setting, this does not take into account the current routes
at the time of activation.
type: int
peer-routes:
description:
- Whether to automatically add routes for the AllowedIPs ranges of the peers.
- If C(true) (the default), NetworkManager will automatically add routes in the routing tables according to C(ipv4.route-table) and
C(ipv6.route-table). Usually you want this automatism enabled.
- If C(false), no such routes are added automatically. In this case, the user may want to configure static routes in C(ipv4.routes)
and C(ipv6.routes), respectively.
- Note that if the peer's AllowedIPs is C(0.0.0.0/0) or C(::/0) and the profile's C(ipv4.never-default) or C(ipv6.never-default)
setting is enabled, the peer route for this peer won't be added automatically.
type: bool
private-key:
description: The 256 bit private-key in base64 encoding.
type: str
private-key-flags:
description: C(NMSettingSecretFlags) indicating how to handle the I(wireguard.private-key) property.
type: int
choices: [ 0, 1, 2 ]
''' '''
EXAMPLES = r''' EXAMPLES = r'''
@@ -1011,6 +1082,16 @@ EXAMPLES = r'''
- 192.0.3.100/24 - 192.0.3.100/24
state: present state: present
- name: Add second ip6 address
community.general.nmcli:
conn_name: my-eth1
ifname: eth1
type: ethernet
ip6:
- 2001:db8::cafe
- 2002:db8::cafe
state: present
- name: Add VxLan - name: Add VxLan
community.general.nmcli: community.general.nmcli:
type: vxlan type: vxlan
@@ -1103,6 +1184,17 @@ EXAMPLES = r'''
autoconnect: true autoconnect: true
state: present state: present
- name: Create a wireguard connection
community.general.nmcli:
type: wireguard
conn_name: my-wg-provider
ifname: mywg0
wireguard:
listen-port: 51820
private-key: my-private-key
autoconnect: true
state: present
''' '''
RETURN = r"""# RETURN = r"""#
@@ -1171,6 +1263,8 @@ class Nmcli(object):
self.dns6_search = module.params['dns6_search'] self.dns6_search = module.params['dns6_search']
self.dns6_ignore_auto = module.params['dns6_ignore_auto'] self.dns6_ignore_auto = module.params['dns6_ignore_auto']
self.method6 = module.params['method6'] self.method6 = module.params['method6']
self.ip_privacy6 = module.params['ip_privacy6']
self.addr_gen_mode6 = module.params['addr_gen_mode6']
self.mtu = module.params['mtu'] self.mtu = module.params['mtu']
self.stp = module.params['stp'] self.stp = module.params['stp']
self.priority = module.params['priority'] self.priority = module.params['priority']
@@ -1211,10 +1305,11 @@ class Nmcli(object):
self.wifi = module.params['wifi'] self.wifi = module.params['wifi']
self.wifi_sec = module.params['wifi_sec'] self.wifi_sec = module.params['wifi_sec']
self.gsm = module.params['gsm'] self.gsm = module.params['gsm']
self.wireguard = module.params['wireguard']
if self.method4: if self.method4:
self.ipv4_method = self.method4 self.ipv4_method = self.method4
elif self.type == 'dummy' and not self.ip4: elif self.type in ('dummy', 'wireguard') and not self.ip4:
self.ipv4_method = 'disabled' self.ipv4_method = 'disabled'
elif self.ip4: elif self.ip4:
self.ipv4_method = 'manual' self.ipv4_method = 'manual'
@@ -1223,7 +1318,7 @@ class Nmcli(object):
if self.method6: if self.method6:
self.ipv6_method = self.method6 self.ipv6_method = self.method6
elif self.type == 'dummy' and not self.ip6: elif self.type in ('dummy', 'wireguard') and not self.ip6:
self.ipv6_method = 'disabled' self.ipv6_method = 'disabled'
elif self.ip6: elif self.ip6:
self.ipv6_method = 'manual' self.ipv6_method = 'manual'
@@ -1255,7 +1350,7 @@ class Nmcli(object):
# IP address options. # IP address options.
if self.ip_conn_type and not self.master: if self.ip_conn_type and not self.master:
options.update({ options.update({
'ipv4.addresses': self.ip4, 'ipv4.addresses': self.enforce_ipv4_cidr_notation(self.ip4),
'ipv4.dhcp-client-id': self.dhcp_client_id, 'ipv4.dhcp-client-id': self.dhcp_client_id,
'ipv4.dns': self.dns4, 'ipv4.dns': self.dns4,
'ipv4.dns-search': self.dns4_search, 'ipv4.dns-search': self.dns4_search,
@@ -1268,13 +1363,15 @@ class Nmcli(object):
'ipv4.never-default': self.never_default4, 'ipv4.never-default': self.never_default4,
'ipv4.method': self.ipv4_method, 'ipv4.method': self.ipv4_method,
'ipv4.may-fail': self.may_fail4, 'ipv4.may-fail': self.may_fail4,
'ipv6.addresses': self.ip6, 'ipv6.addresses': self.enforce_ipv6_cidr_notation(self.ip6),
'ipv6.dns': self.dns6, 'ipv6.dns': self.dns6,
'ipv6.dns-search': self.dns6_search, 'ipv6.dns-search': self.dns6_search,
'ipv6.ignore-auto-dns': self.dns6_ignore_auto, 'ipv6.ignore-auto-dns': self.dns6_ignore_auto,
'ipv6.gateway': self.gw6, 'ipv6.gateway': self.gw6,
'ipv6.ignore-auto-routes': self.gw6_ignore_auto, 'ipv6.ignore-auto-routes': self.gw6_ignore_auto,
'ipv6.method': self.ipv6_method, 'ipv6.method': self.ipv6_method,
'ipv6.ip6-privacy': self.ip_privacy6,
'ipv6.addr-gen-mode': self.addr_gen_mode6
}) })
# Layer 2 options. # Layer 2 options.
@@ -1346,6 +1443,9 @@ class Nmcli(object):
options.update({ options.update({
'vlan.id': self.vlanid, 'vlan.id': self.vlanid,
'vlan.parent': self.vlandev, 'vlan.parent': self.vlandev,
'vlan.flags': self.flags,
'vlan.ingress': self.ingress,
'vlan.egress': self.egress,
}) })
elif self.type == 'vxlan': elif self.type == 'vxlan':
options.update({ options.update({
@@ -1374,6 +1474,12 @@ class Nmcli(object):
options.update({ options.update({
'gsm.%s' % name: value, 'gsm.%s' % name: value,
}) })
elif self.type == 'wireguard':
if self.wireguard:
for name, value in self.wireguard.items():
options.update({
'wireguard.%s' % name: value,
})
# Convert settings values based on the situation. # Convert settings values based on the situation.
for setting, value in options.items(): for setting, value in options.items():
setting_type = self.settings_type(setting) setting_type = self.settings_type(setting)
@@ -1388,6 +1494,8 @@ class Nmcli(object):
elif setting == self.mtu_setting: elif setting == self.mtu_setting:
# MTU is 'auto' by default when detecting changes. # MTU is 'auto' by default when detecting changes.
convert_func = self.mtu_to_string convert_func = self.mtu_to_string
elif setting == 'ipv6.ip6-privacy':
convert_func = self.ip6_privacy_to_num
elif setting_type is list: elif setting_type is list:
# Convert lists to strings for nmcli create/modify commands. # Convert lists to strings for nmcli create/modify commands.
convert_func = self.list_to_string convert_func = self.list_to_string
@@ -1413,6 +1521,7 @@ class Nmcli(object):
'vlan', 'vlan',
'wifi', 'wifi',
'gsm', 'gsm',
'wireguard',
) )
@property @property
@@ -1441,6 +1550,23 @@ class Nmcli(object):
else: else:
return to_text(mtu) return to_text(mtu)
@staticmethod
def ip6_privacy_to_num(privacy):
ip6_privacy_values = {
'disabled': '0',
'prefer-public-addr': '1 (enabled, prefer public IP)',
'prefer-temp-addr': '2 (enabled, prefer temporary IP)',
'unknown': '-1',
}
if privacy is None:
return None
if privacy not in ip6_privacy_values:
raise AssertionError('{privacy} is invalid ip_privacy6 option'.format(privacy=privacy))
return ip6_privacy_values[privacy]
@property @property
def slave_conn_type(self): def slave_conn_type(self):
return self.type in ( return self.type in (
@@ -1458,6 +1584,18 @@ class Nmcli(object):
'sit', 'sit',
) )
@staticmethod
def enforce_ipv4_cidr_notation(ip4_addresses):
if ip4_addresses is None:
return None
return [address if '/' in address else address + '/32' for address in ip4_addresses]
@staticmethod
def enforce_ipv6_cidr_notation(ip6_addresses):
if ip6_addresses is None:
return None
return [address if '/' in address else address + '/128' for address in ip6_addresses]
@staticmethod @staticmethod
def bool_to_string(boolean): def bool_to_string(boolean):
if boolean: if boolean:
@@ -1483,6 +1621,7 @@ class Nmcli(object):
'802-11-wireless.hidden'): '802-11-wireless.hidden'):
return bool return bool
elif setting in ('ipv4.addresses', elif setting in ('ipv4.addresses',
'ipv6.addresses',
'ipv4.dns', 'ipv4.dns',
'ipv4.dns-search', 'ipv4.dns-search',
'ipv4.routes', 'ipv4.routes',
@@ -1772,6 +1911,7 @@ def main():
'vxlan', 'vxlan',
'wifi', 'wifi',
'gsm', 'gsm',
'wireguard',
]), ]),
ip4=dict(type='list', elements='str'), ip4=dict(type='list', elements='str'),
gw4=dict(type='str'), gw4=dict(type='str'),
@@ -1786,13 +1926,15 @@ def main():
method4=dict(type='str', choices=['auto', 'link-local', 'manual', 'shared', 'disabled']), method4=dict(type='str', choices=['auto', 'link-local', 'manual', 'shared', 'disabled']),
may_fail4=dict(type='bool', default=True), may_fail4=dict(type='bool', default=True),
dhcp_client_id=dict(type='str'), dhcp_client_id=dict(type='str'),
ip6=dict(type='str'), ip6=dict(type='list', elements='str'),
gw6=dict(type='str'), gw6=dict(type='str'),
gw6_ignore_auto=dict(type='bool', default=False), gw6_ignore_auto=dict(type='bool', default=False),
dns6=dict(type='list', elements='str'), dns6=dict(type='list', elements='str'),
dns6_search=dict(type='list', elements='str'), dns6_search=dict(type='list', elements='str'),
dns6_ignore_auto=dict(type='bool', default=False), dns6_ignore_auto=dict(type='bool', default=False),
method6=dict(type='str', choices=['ignore', 'auto', 'dhcp', 'link-local', 'manual', 'shared', 'disabled']), method6=dict(type='str', choices=['ignore', 'auto', 'dhcp', 'link-local', 'manual', 'shared', 'disabled']),
ip_privacy6=dict(type='str', choices=['disabled', 'prefer-public-addr', 'prefer-temp-addr', 'unknown']),
addr_gen_mode6=dict(type='str', choices=['eui64', 'stable-privacy']),
# Bond Specific vars # Bond Specific vars
mode=dict(type='str', default='balance-rr', mode=dict(type='str', default='balance-rr',
choices=['802.3ad', 'active-backup', 'balance-alb', 'balance-rr', 'balance-tlb', 'balance-xor', 'broadcast']), choices=['802.3ad', 'active-backup', 'balance-alb', 'balance-rr', 'balance-tlb', 'balance-xor', 'broadcast']),
@@ -1843,6 +1985,7 @@ def main():
wifi=dict(type='dict'), wifi=dict(type='dict'),
wifi_sec=dict(type='dict', no_log=True), wifi_sec=dict(type='dict', no_log=True),
gsm=dict(type='dict'), gsm=dict(type='dict'),
wireguard=dict(type='dict'),
), ),
mutually_exclusive=[['never_default4', 'gw4']], mutually_exclusive=[['never_default4', 'gw4']],
required_if=[("type", "wifi", [("ssid")])], required_if=[("type", "wifi", [("ssid")])],

View File

@@ -127,7 +127,7 @@ ansible_sysname:
type: str type: str
sample: ubuntu-user sample: ubuntu-user
ansible_syslocation: ansible_syslocation:
description: The physical location of this node (e.g., `telephone closet, 3rd floor'). description: The physical location of this node (e.g., C(telephone closet, 3rd floor)).
returned: success returned: success
type: str type: str
sample: Sitting on the Dock of the Bay sample: Sitting on the Dock of the Bay

View File

@@ -38,7 +38,15 @@ options:
type: str type: str
description: description:
- Text to send. Note that the module does not handle escaping characters. - Text to send. Note that the module does not handle escaping characters.
required: true - Required when I(attachments) is not set.
attachments:
type: list
elements: dict
description:
- Define a list of attachments.
- For more information, see U(https://developers.mattermost.com/integrate/admin-guide/admin-message-attachments/).
- Required when I(text) is not set.
version_added: 4.3.0
channel: channel:
type: str type: str
description: description:
@@ -76,6 +84,22 @@ EXAMPLES = """
channel: notifications channel: notifications
username: 'Ansible on {{ inventory_hostname }}' username: 'Ansible on {{ inventory_hostname }}'
icon_url: http://www.example.com/some-image-file.png icon_url: http://www.example.com/some-image-file.png
- name: Send attachments message via Mattermost
community.general.mattermost:
url: http://mattermost.example.com
api_key: my_api_key
attachments:
- text: Display my system load on host A and B
color: '#ff00dd'
title: System load
fields:
- title: System A
value: "load average: 0,74, 0,66, 0,63"
short: True
- title: System B
value: 'load average: 5,16, 4,64, 2,43'
short: True
""" """
RETURN = ''' RETURN = '''
@@ -99,12 +123,16 @@ def main():
argument_spec=dict( argument_spec=dict(
url=dict(type='str', required=True), url=dict(type='str', required=True),
api_key=dict(type='str', required=True, no_log=True), api_key=dict(type='str', required=True, no_log=True),
text=dict(type='str', required=True), text=dict(type='str'),
channel=dict(type='str', default=None), channel=dict(type='str', default=None),
username=dict(type='str', default='Ansible'), username=dict(type='str', default='Ansible'),
icon_url=dict(type='str', default='https://www.ansible.com/favicon.ico'), icon_url=dict(type='str', default='https://www.ansible.com/favicon.ico'),
validate_certs=dict(default=True, type='bool'), validate_certs=dict(default=True, type='bool'),
) attachments=dict(type='list', elements='dict'),
),
required_one_of=[
('text', 'attachments'),
],
) )
# init return dict # init return dict
result = dict(changed=False, msg="OK") result = dict(changed=False, msg="OK")
@@ -115,7 +143,7 @@ def main():
# define payload # define payload
payload = {} payload = {}
for param in ['text', 'channel', 'username', 'icon_url']: for param in ['text', 'channel', 'username', 'icon_url', 'attachments']:
if module.params[param] is not None: if module.params[param] is not None:
payload[param] = module.params[param] payload[param] = module.params[param]

View File

@@ -124,7 +124,8 @@ import os
import ssl import ssl
import traceback import traceback
import platform import platform
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
HAS_PAHOMQTT = True HAS_PAHOMQTT = True
PAHOMQTT_IMP_ERR = None PAHOMQTT_IMP_ERR = None
@@ -207,7 +208,7 @@ def main():
if tls_version: if tls_version:
tls_version = tls_map.get(tls_version, ssl.PROTOCOL_SSLv23) tls_version = tls_map.get(tls_version, ssl.PROTOCOL_SSLv23)
else: else:
if LooseVersion(platform.python_version()) <= "3.5.2": if LooseVersion(platform.python_version()) <= LooseVersion("3.5.2"):
# Specifying `None` on later versions of python seems sufficient to # Specifying `None` on later versions of python seems sufficient to
# instruct python to autonegotiate the SSL/TLS connection. On versions # instruct python to autonegotiate the SSL/TLS connection. On versions
# 3.5.2 and lower though we need to specify the version. # 3.5.2 and lower though we need to specify the version.

View File

@@ -124,7 +124,7 @@ EXAMPLES = r'''
import os import os
import traceback import traceback
from distutils.version import LooseVersion from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
SENDGRID_IMP_ERR = None SENDGRID_IMP_ERR = None
try: try:

View File

@@ -346,7 +346,7 @@ def build_payload_for_slack(text, channel, thread_id, username, icon_url, icon_e
def get_slack_message(module, token, channel, ts): def get_slack_message(module, token, channel, ts):
headers = { headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json', 'Accept': 'application/json',
'Authorization': 'Bearer ' + token 'Authorization': 'Bearer ' + token
} }
@@ -383,7 +383,7 @@ def do_notify_slack(module, domain, token, payload):
slack_uri = OLD_SLACK_INCOMING_WEBHOOK % (domain, token) slack_uri = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)
headers = { headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json; charset=UTF-8',
'Accept': 'application/json', 'Accept': 'application/json',
} }
if use_webapi: if use_webapi:

View File

@@ -10,7 +10,7 @@ __metaclass__ = type
DOCUMENTATION = """ DOCUMENTATION = """
module: ansible_galaxy_install module: ansible_galaxy_install
author: author:
- "Alexei Znamensky (@russoz)" - "Alexei Znamensky (@russoz)"
short_description: Install Ansible roles or collections using ansible-galaxy short_description: Install Ansible roles or collections using ansible-galaxy
version_added: 3.5.0 version_added: 3.5.0
description: description:
@@ -24,44 +24,46 @@ requirements:
options: options:
type: type:
description: description:
- The type of installation performed by C(ansible-galaxy). - The type of installation performed by C(ansible-galaxy).
- If I(type) is C(both), then I(requirements_file) must be passed and it may contain both roles and collections. - If I(type) is C(both), then I(requirements_file) must be passed and it may contain both roles and collections.
- "Note however that the opposite is not true: if using a I(requirements_file), then I(type) can be any of the three choices." - "Note however that the opposite is not true: if using a I(requirements_file), then I(type) can be any of the three choices."
- "B(Ansible 2.9): The option C(both) will have the same effect as C(role)." - "B(Ansible 2.9): The option C(both) will have the same effect as C(role)."
type: str type: str
choices: [collection, role, both] choices: [collection, role, both]
required: true required: true
name: name:
description: description:
- Name of the collection or role being installed. - Name of the collection or role being installed.
- Versions can be specified with C(ansible-galaxy) usual formats. For example, C(community.docker:1.6.1) or C(ansistrano.deploy,3.8.0). - >
- I(name) and I(requirements_file) are mutually exclusive. Versions can be specified with C(ansible-galaxy) usual formats.
For example, the collection C(community.docker:1.6.1) or the role C(ansistrano.deploy,3.8.0).
- I(name) and I(requirements_file) are mutually exclusive.
type: str type: str
requirements_file: requirements_file:
description: description:
- Path to a file containing a list of requirements to be installed. - Path to a file containing a list of requirements to be installed.
- It works for I(type) equals to C(collection) and C(role). - It works for I(type) equals to C(collection) and C(role).
- I(name) and I(requirements_file) are mutually exclusive. - I(name) and I(requirements_file) are mutually exclusive.
- "B(Ansible 2.9): It can only be used to install either I(type=role) or I(type=collection), but not both at the same run." - "B(Ansible 2.9): It can only be used to install either I(type=role) or I(type=collection), but not both at the same run."
type: path type: path
dest: dest:
description: description:
- The path to the directory containing your collections or roles, according to the value of I(type). - The path to the directory containing your collections or roles, according to the value of I(type).
- > - >
Please notice that C(ansible-galaxy) will not install collections with I(type=both), when I(requirements_file) Please notice that C(ansible-galaxy) will not install collections with I(type=both), when I(requirements_file)
contains both roles and collections and I(dest) is specified. contains both roles and collections and I(dest) is specified.
type: path type: path
force: force:
description: description:
- Force overwriting an existing role or collection. - Force overwriting an existing role or collection.
- Using I(force=true) is mandatory when downgrading. - Using I(force=true) is mandatory when downgrading.
- "B(Ansible 2.9 and 2.10): Must be C(true) to upgrade roles and collections." - "B(Ansible 2.9 and 2.10): Must be C(true) to upgrade roles and collections."
type: bool type: bool
default: false default: false
ack_ansible29: ack_ansible29:
description: description:
- Acknowledge using Ansible 2.9 with its limitations, and prevents the module from generating warnings about them. - Acknowledge using Ansible 2.9 with its limitations, and prevents the module from generating warnings about them.
- This option is completely ignored if using a version Ansible greater than C(2.9.x). - This option is completely ignored if using a version of Ansible greater than C(2.9.x).
type: bool type: bool
default: false default: false
""" """
@@ -114,9 +116,9 @@ RETURN = """
returned: always returned: always
installed_roles: installed_roles:
description: description:
- If I(requirements_file) is specified instead, returns dictionary with all the roles installed per path. - If I(requirements_file) is specified instead, returns dictionary with all the roles installed per path.
- If I(name) is specified, returns that role name and the version installed per path. - If I(name) is specified, returns that role name and the version installed per path.
- "B(Ansible 2.9): Returns empty because C(ansible-galaxy) has no C(list) subcommand." - "B(Ansible 2.9): Returns empty because C(ansible-galaxy) has no C(list) subcommand."
type: dict type: dict
returned: always when installing roles returned: always when installing roles
contains: contains:
@@ -131,9 +133,9 @@ RETURN = """
ansistrano.deploy: 3.8.0 ansistrano.deploy: 3.8.0
installed_collections: installed_collections:
description: description:
- If I(requirements_file) is specified instead, returns dictionary with all the collections installed per path. - If I(requirements_file) is specified instead, returns dictionary with all the collections installed per path.
- If I(name) is specified, returns that collection name and the version installed per path. - If I(name) is specified, returns that collection name and the version installed per path.
- "B(Ansible 2.9): Returns empty because C(ansible-galaxy) has no C(list) subcommand." - "B(Ansible 2.9): Returns empty because C(ansible-galaxy) has no C(list) subcommand."
type: dict type: dict
returned: always when installing collections returned: always when installing collections
contains: contains:

View File

@@ -0,0 +1,205 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021 Radek Sprta <mail@radeksprta.eu>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
---
module: cargo
short_description: Manage Rust packages with cargo
version_added: 4.3.0
description:
- Manage Rust packages with cargo.
author: "Radek Sprta (@radek-sprta)"
options:
name:
description:
- The name of a Rust package to install.
type: list
elements: str
required: true
path:
description:
->
The base path where to install the Rust packages. Cargo automatically appends
C(/bin). In other words, C(/usr/local) will become C(/usr/local/bin).
type: path
version:
description:
->
The version to install. If I(name) contains multiple values, the module will
try to install all of them in this version.
type: str
required: false
state:
description:
- The state of the Rust package.
required: false
type: str
default: present
choices: [ "present", "absent", "latest" ]
requirements:
- cargo installed in bin path (recommended /usr/local/bin)
"""
EXAMPLES = r"""
- name: Install "ludusavi" Rust package
community.general.cargo:
name: ludusavi
- name: Install "ludusavi" Rust package in version 0.10.0
community.general.cargo:
name: ludusavi
version: '0.10.0'
- name: Install "ludusavi" Rust package to global location
community.general.cargo:
name: ludusavi
path: /usr/local
- name: Remove "ludusavi" Rust package
community.general.cargo:
name: ludusavi
state: absent
- name: Update "ludusavi" Rust package its latest version
community.general.cargo:
name: ludusavi
state: latest
"""
import os
import re
from ansible.module_utils.basic import AnsibleModule
class Cargo(object):
def __init__(self, module, **kwargs):
self.module = module
self.name = kwargs["name"]
self.path = kwargs["path"]
self.state = kwargs["state"]
self.version = kwargs["version"]
self.executable = [module.get_bin_path("cargo", True)]
@property
def path(self):
return self._path
@path.setter
def path(self, path):
if path is not None and not os.path.isdir(path):
self.module.fail_json(msg="Path %s is not a directory" % path)
self._path = path
def _exec(
self, args, run_in_check_mode=False, check_rc=True, add_package_name=True
):
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
cmd = self.executable + args
rc, out, err = self.module.run_command(cmd, check_rc=check_rc)
return out, err
return "", ""
def get_installed(self):
cmd = ["install", "--list"]
data, dummy = self._exec(cmd, True, False, False)
package_regex = re.compile(r"^(\w+) v(.+):$")
installed = {}
for line in data.splitlines():
package_info = package_regex.match(line)
if package_info:
installed[package_info.group(1)] = package_info.group(2)
return installed
def install(self, packages=None):
cmd = ["install"]
cmd.extend(packages or self.name)
if self.path:
cmd.append("--root")
cmd.append(self.path)
if self.version:
cmd.append("--version")
cmd.append(self.version)
return self._exec(cmd)
def is_outdated(self, name):
installed_version = self.get_installed().get(name)
cmd = ["search", name, "--limit", "1"]
data = self._exec(cmd, True, False, False)
match = re.search(r'"(.+)"', data)
if match:
latest_version = match[1]
return installed_version != latest_version
def uninstall(self, packages=None):
cmd = ["uninstall"]
cmd.extend(packages or self.name)
return self._exec(cmd)
def main():
arg_spec = dict(
name=dict(required=True, type="list", elements="str"),
path=dict(default=None, type="path"),
state=dict(default="present", choices=["present", "absent", "latest"]),
version=dict(default=None, type="str"),
)
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
name = module.params["name"]
path = module.params["path"]
state = module.params["state"]
version = module.params["version"]
if not name:
module.fail_json(msg="Package name must be specified")
# Set LANG env since we parse stdout
module.run_command_environ_update = dict(
LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C"
)
cargo = Cargo(module, name=name, path=path, state=state, version=version)
changed, out, err = False, None, None
installed_packages = cargo.get_installed()
if state == "present":
to_install = [
n
for n in name
if (n not in installed_packages)
or (version and version != installed_packages[n])
]
if to_install:
changed = True
out, err = cargo.install(to_install)
elif state == "latest":
to_update = [
n for n in name if n not in installed_packages or cargo.is_outdated(n)
]
if to_update:
changed = True
out, err = cargo.install(to_update)
else: # absent
to_uninstall = [n for n in name if n in installed_packages]
if to_uninstall:
changed = True
out, err = cargo.uninstall(to_uninstall)
module.exit_json(changed=changed, stdout=out, stderr=err)
if __name__ == "__main__":
main()

View File

@@ -167,7 +167,7 @@ class PipX(CmdStateModuleHelper):
command_args_formats = dict( command_args_formats = dict(
state=dict(fmt=lambda v: [_state_map.get(v, v)]), state=dict(fmt=lambda v: [_state_map.get(v, v)]),
name_source=dict(fmt=lambda n, s: [s] if s else [n], stars=1), name_source=dict(fmt=lambda n, s: [s] if s else [n], stars=1),
install_deps=dict(fmt="--install-deps", style=ArgFormat.BOOLEAN), install_deps=dict(fmt="--include-deps", style=ArgFormat.BOOLEAN),
inject_packages=dict(fmt=lambda v: v), inject_packages=dict(fmt=lambda v: v),
force=dict(fmt="--force", style=ArgFormat.BOOLEAN), force=dict(fmt="--force", style=ArgFormat.BOOLEAN),
include_injected=dict(fmt="--include-injected", style=ArgFormat.BOOLEAN), include_injected=dict(fmt="--include-injected", style=ArgFormat.BOOLEAN),

View File

@@ -152,11 +152,11 @@ stdout:
sample: "org.gnome.Calendar/x86_64/stable\tcurrent\norg.gnome.gitg/x86_64/stable\tcurrent\n" sample: "org.gnome.Calendar/x86_64/stable\tcurrent\norg.gnome.gitg/x86_64/stable\tcurrent\n"
''' '''
from distutils.version import StrictVersion
from ansible.module_utils.six.moves.urllib.parse import urlparse from ansible.module_utils.six.moves.urllib.parse import urlparse
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
OUTDATED_FLATPAK_VERSION_ERROR_MESSAGE = "Unknown option --columns=application" OUTDATED_FLATPAK_VERSION_ERROR_MESSAGE = "Unknown option --columns=application"
@@ -172,7 +172,7 @@ def install_flat(module, binary, remote, names, method, no_dependencies):
id_names.append(name) id_names.append(name)
base_command = [binary, "install", "--{0}".format(method)] base_command = [binary, "install", "--{0}".format(method)]
flatpak_version = _flatpak_version(module, binary) flatpak_version = _flatpak_version(module, binary)
if StrictVersion(flatpak_version) < StrictVersion('1.1.3'): if LooseVersion(flatpak_version) < LooseVersion('1.1.3'):
base_command += ["-y"] base_command += ["-y"]
else: else:
base_command += ["--noninteractive"] base_command += ["--noninteractive"]
@@ -196,7 +196,7 @@ def uninstall_flat(module, binary, names, method):
] ]
command = [binary, "uninstall"] command = [binary, "uninstall"]
flatpak_version = _flatpak_version(module, binary) flatpak_version = _flatpak_version(module, binary)
if StrictVersion(flatpak_version) < StrictVersion('1.1.3'): if LooseVersion(flatpak_version) < LooseVersion('1.1.3'):
command += ["-y"] command += ["-y"]
else: else:
command += ["--noninteractive"] command += ["--noninteractive"]

Some files were not shown because too many files have changed in this diff Show More