Compare commits

...

34 Commits
7.3.0 ... 7.4.0

Author SHA1 Message Date
Felix Fontein
89accbfa2b Release 7.4.0. 2023-09-11 20:27:32 +02:00
patchback[bot]
63210f4fc4 [PR #7219/208df2c9 backport][stable-7] nsupdate: fix 'index out of range' error when no TTL answer is given (#7236)
nsupdate: fix 'index out of range' error when no TTL answer is given (#7219)

* nsupdate: fix 'index out of range' error when no TTL answer is given

Fix a possible `list index out of range` when no answer is returned in the `ttl_changed` method
by applying the existing workaround for NS records to all record types.

Resolves #836

* fixup! nsupdate: fix 'index out of range' error when no TTL answer is given

(cherry picked from commit 208df2c9e6)

Co-authored-by: Silke Hofstra <silkeh@users.noreply.github.com>
2023-09-11 06:01:11 +02:00
patchback[bot]
01864514c2 [PR #7204/afeeb89a backport][stable-7] Improvements to the jenkins_build module and new jenkins_build_info module (#7234)
Improvements to the jenkins_build module and new jenkins_build_info module (#7204)

* Add detach option so the task doesn't wait until the Jenkins job is finished

* Add new time_between_checks to be able to configure the sleep time between requests

* New jenkins_build_info to get information about a specific build

* Add version_added to the new module

* Add changelog fragment for jenkins_build changes

* Fix tests that required the python-jenkins module

* Remove tests that failed

Doesn't really make sense to test that with a mock

* Fix pep8 error

* Update maintainers for the jenkins_build and jenkins_build_info modules

* Improve format and add link to PR

* Move version_added documentation to the right file

* Fix incorrect examples

* Improve text format

* Fix incorrect code style

* Fix incorrect YAML for documentation

* Add version_added documentation to the new options

(cherry picked from commit afeeb89af6)

Co-authored-by: Juan Manuel Casanova González <juan.casanova.922@gmail.com>
2023-09-11 06:00:57 +02:00
patchback[bot]
418589e346 [PR #7231/517e2d48 backport][stable-7] cpanm: using yaml-specified unit tests (#7233)
cpanm: using yaml-specified unit tests (#7231)

* cpanm: using yaml-specified unit tests

* add quote around URLs to appease pyaml@py26

* add changelog frag

(cherry picked from commit 517e2d48eb)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-10 21:45:36 +02:00
Felix Fontein
88fab247ca Prepare 7.4.0 release. 2023-09-10 21:30:45 +02:00
patchback[bot]
56edbfc539 [PR #7183/6012d262 backport][stable-7] feat: pagerduty_alert: Adds in use of v2 api provided (#7229)
feat: pagerduty_alert: Adds in use of v2 api provided (#7183)

* feat: pagerduty_alert: Adds in use of v2 api provided

* doc: Adds xishen1 to maintainer

* Pagerduty_alert: documentation change

* pagerduty_alert: update documentation

* pagerduty_alert: update periods

* pagerduty_alert: update documentation

(cherry picked from commit 6012d2623e)

Co-authored-by: xshen1 <araticroyal1998@gmail.com>
2023-09-10 09:00:28 +02:00
patchback[bot]
c94fa6132d [PR #7200/8fa667ee backport][stable-7] CmdRunner bugfix (#7228)
CmdRunner bugfix (#7200)

* cmd_runner module utils: fix bug when passing absolute path not in standard search paths

* improved tests

* changed /usr/bin/echo to /bin/echo for the sake of alpine

* fixed error messaging for last testcase

* add condition to test cases, and remove macos from troubling ones

* fix templating

* fix templating

* exclude centos 6 from testcases copying echo to tmp dir

* try different way of specifying version

* trying trick for old jinjas

* use os.path.isabs() to determine if path is absolute

* add changelog frag

* Update plugins/module_utils/cmd_runner.py

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

* Update changelogs/fragments/7200-cmd-runner-abs-path.yml

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

---------

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

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-10 09:00:14 +02:00
patchback[bot]
2fa17c32a3 [PR #6741/568814fc backport][stable-7] New module: pnpm package manager (#7225)
New module: pnpm package manager (#6741)

* (feat) New module pnpm added

A new module for pnpm is added. Necessary entries were written to
BOTMETA.yml.

* (feat) Basic tests added

* (feat) reduced nesting of ifs

* (fix) trying to fix up CI pipelines

* (fix) incorrect indentation of alias fixed

* (feat) fixed further indentations

* Apply suggestions from code review

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

* (fix) various linting and CI errors fixed/ignored

* (feat) reduced restriction, new install method

Some restrictions on OS are reduced. New installation method, similar to
the official installation method, is provided.

* (fix) ignoring CentOS 6 for CI.

* retrigger checks

---------

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

Co-authored-by: Aritra Sen <125266845+aretrosen@users.noreply.github.com>
2023-09-08 17:38:30 +02:00
patchback[bot]
926f627128 [PR #7205/58d89ce4 backport][stable-7] smbios option description (#7224)
smbios option description (#7205)

* smbios option description

More detailed description smbios option

* Update plugins/modules/proxmox_kvm.py

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

* Change map to string

* Update proxmox_kvm.py

---------

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

Co-authored-by: Андрей Неустроев <99169437+aneustroev@users.noreply.github.com>
2023-09-08 13:27:34 +02:00
patchback[bot]
7c6f286df2 [PR #7212/12708c38 backport][stable-7] fix typo (#7222)
fix typo (#7212)

(cherry picked from commit 12708c3848)

Co-authored-by: Андрей Неустроев <99169437+aneustroev@users.noreply.github.com>
2023-09-08 13:17:31 +02:00
patchback[bot]
b6ed6787b5 [PR #7161/a23cd6c1 backport][stable-7] Update incorrect path for pritunl organization post (#7214)
Update incorrect path for pritunl organization post (#7161)

* Update incorrect path for organization post

* Create changelog fragment

* Update changelog fragment.

---------

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

Co-authored-by: dsmackie <48046804+dsmackie@users.noreply.github.com>
2023-09-06 22:06:16 +02:00
patchback[bot]
94a350e72b [PR #7156/0862511e backport][stable-7] Ensure pritunl validate_certs is honoured in all methods (#7216)
Ensure pritunl validate_certs is honoured in all methods (#7156)

* Ensure pritunl validate_certs is honoured in all methods

* Create changelog fragment

* Rename 7156-ensure-validate-certs-parameter-is-honoured to 7156-ensure-validate-certs-parameter-is-honoured.yml

* Update changelog fragment.

---------

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

Co-authored-by: dsmackie <48046804+dsmackie@users.noreply.github.com>
2023-09-06 22:06:09 +02:00
patchback[bot]
46d454eae0 [PR #7180/9021e741 backport][stable-7] community.general.make: (#7218)
community.general.make: (#7180)

* community.general.make:

  allows parameters without value

  closes #7178

* add changelog fragment for community.general.make

* correction: v != none -> v is not None

* update fragment changelog as per developer request

* add an example

* document the modification

* update example with comments as per maintainer request

(cherry picked from commit 9021e7416d)

Co-authored-by: snail59 <25689269+snail59@users.noreply.github.com>
2023-09-06 22:05:57 +02:00
patchback[bot]
adfd73d7ed [PR #7209/9f1a9e30 backport][stable-7] plugins/modules/apache2_module: fix typo (#7211)
plugins/modules/apache2_module: fix typo (#7209)

(cherry picked from commit 9f1a9e306c)

Co-authored-by: Célestin Matte <celestin.matte@gmail.com>
2023-09-06 19:10:04 +02:00
patchback[bot]
aa2a5d9578 [PR #7184/0c03f34f backport][stable-7] plugins/inventory/cobbler: Add exclude/include_mgmt_classes (#7208)
plugins/inventory/cobbler: Add exclude/include_mgmt_classes (#7184)

(cherry picked from commit 0c03f34f54)

Co-authored-by: Orion Poplawski <orion@nwra.com>
2023-09-05 20:50:11 +02:00
patchback[bot]
0f300bddb9 [PR #7179/631d215f backport][stable-7] Add unixy support for check_mode_markers (#7201)
Add unixy support for check_mode_markers (#7179)

* Add unixy support for check_mode_markers

Modifies output on playbook start, task start, and handler start when
playbook runs in check mode.

* changelog fragment

* Address feedback

* Oops

(cherry picked from commit 631d215fe8)

Co-authored-by: akatch <akatch@users.noreply.github.com>
2023-09-03 18:17:29 +02:00
patchback[bot]
3785b656d6 [PR #7196/40cad3e7 backport][stable-7] gconftool2: using yaml-specified unit tests (#7198)
gconftool2: using yaml-specified unit tests (#7196)

* gconftool2: using yaml-specified unit tests

* gconftool2_info: using yaml-specified unit tests

* adjust code for skip and xfail

(cherry picked from commit 40cad3e7a9)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-02 18:49:53 +02:00
patchback[bot]
16499072ff [PR #7193/41bd07e3 backport][stable-7] puppet: using yaml-specified unit tests (#7197)
puppet: using yaml-specified unit tests (#7193)

(cherry picked from commit 41bd07e372)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-02 13:39:22 +02:00
patchback[bot]
cad6b30036 [PR #7192/14bc13ba backport][stable-7] further improvements (#7195)
further improvements (#7192)

* further improvements

* some renaming

(cherry picked from commit 14bc13ba3c)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-02 13:06:17 +02:00
patchback[bot]
2df1126d27 [PR #7191/c2d3302f backport][stable-7] snap: using yaml-specified unit tests (#7194)
snap: using yaml-specified unit tests (#7191)

* snap: using yaml-specified unit tests

* remove extraneous code

(cherry picked from commit c2d3302fc4)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-02 09:10:04 +02:00
patchback[bot]
0d5ec37249 [PR #7185/ce6b2bc3 backport][stable-7] remove extraneous unused constant (#7188)
remove extraneous unused constant (#7185)

(cherry picked from commit ce6b2bc362)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-01 11:28:13 +00:00
patchback[bot]
7c04aaa48f [PR #7181/d6ebba1a backport][stable-7] cmd tests improvement (#7187)
cmd tests improvement (#7181)

* cmd tests improvement

* fix sanity

* remove commented line

* fixed class init code

(cherry picked from commit d6ebba1aea)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-09-01 11:28:04 +00:00
patchback[bot]
80113063ac [PR #7170/63030966 backport][stable-7] xfconf*: using yaml-specified unit tests (#7177)
xfconf*: using yaml-specified unit tests (#7170)

(cherry picked from commit 6303096648)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-08-30 06:16:17 +02:00
patchback[bot]
1b09e8168a [PR #7172/f3a02b3e backport][stable-7] CI: make sure EXTERNALLY-MANAGED is absent on Arch Linux (#7175)
CI: make sure EXTERNALLY-MANAGED is absent on Arch Linux (#7172)

Make sure EXTERNALLY-MANAGED is absent on Arch.

(cherry picked from commit f3a02b3efb)

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-08-30 06:15:51 +02:00
patchback[bot]
aadd48461c [PR #7154/8652fd95 backport][stable-7] Refactored unit tests for modules based on CmdRunner (#7168)
Refactored unit tests for modules based on CmdRunner (#7154)

* refactored unit tests for modules based on CmdRunner

* improved/fixed test helper

* fixed sanity

* refactored yaml spec out of the python file

* small adjustments

(cherry picked from commit 8652fd9528)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-08-29 13:22:04 +02:00
patchback[bot]
d565a20013 [PR #7132/e7d8ef4c backport][stable-7] gitlab_project_variable/gitlab_group_variable: Add support for 'raw' option (#7166)
gitlab_project_variable/gitlab_group_variable: Add support for 'raw' option (#7132)

feat(gitlab_project_variable): Add support for 'raw' option

(cherry picked from commit e7d8ef4cf9)

Co-authored-by: Léo GATELLIER <26511053+lgatellier@users.noreply.github.com>
2023-08-29 13:05:58 +02:00
patchback[bot]
c69fb82ee0 [PR #7162/4c8c25bc backport][stable-7] keycloak_clientscope_type: fixed example (#7165)
keycloak_clientscope_type: fixed example (#7162)

(cherry picked from commit 4c8c25bc93)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-08-29 07:47:30 +02:00
patchback[bot]
cffc3dad11 [PR #7140/5fcb98cd backport][stable-7] redfish_info: Report Id in GetManagerInventory output (#7153)
redfish_info: Report Id in GetManagerInventory output (#7140)

(cherry picked from commit 5fcb98cd3f)

Co-authored-by: smiller248 <136365984+smiller248@users.noreply.github.com>
2023-08-25 21:07:09 +02:00
patchback[bot]
a27025946b [PR #7125/77214203 backport][stable-7] Fix inappropriate comparison on the length of a Collection (#7147)
Fix inappropriate comparison on the length of a Collection (#7125)

* Comment: Fixed inappropriate comparison on the length of a Collection. Added changlelog fragment file.

* Comment: Updated the scope of the changelog fragment based on feedback.

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

---------

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

Co-authored-by: Munawar <munawar.hafiz@gmail.com>
2023-08-23 06:14:03 +02:00
patchback[bot]
1825feb652 [PR #7135/d1d9895e backport][stable-7] minor typo on proxmox_kvm module documentation (#7142)
minor typo on proxmox_kvm module documentation (#7135)

chore(docs): minor typo

(cherry picked from commit d1d9895eb6)

Co-authored-by: Damien TOURDE <49169262+dtourde@users.noreply.github.com>
2023-08-21 22:00:45 +02:00
patchback[bot]
0c2d1eda44 [PR #6819/17dce5a2 backport][stable-7] Adding 'Links' to the parameter list for data retrieved from get_disk_inventory (#7141)
Adding 'Links' to the parameter list for data retrieved from get_disk_inventory (#6819)

* Adding 'Links' parameter to be retrieved from get_disk_inventory

* Adding changelog fragment

* Updating as per PR suggestions

* Updating to return volumes as a list of strings

* Updating code to retrieve only volumes under the Links parameter

* Updating changelog fragment

* Update changelogs/fragments/6819-redfish-utils-add-links-parameter-for-get_disk_inventory.yml

Agreed

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

---------

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

Co-authored-by: TSKushal <44438079+TSKushal@users.noreply.github.com>
2023-08-21 21:50:39 +02:00
patchback[bot]
d617f6919f [PR #7119/eaf3926c backport][stable-7] nmap inventory plugin, add use_arp_ping option (#7134)
nmap inventory plugin, add use_arp_ping option (#7119)

* nmap inventory plugin, add use_arp_ping option

* Apply suggestions from code review

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

* Update plugins/inventory/nmap.py

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

---------

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

Co-authored-by: Brian Coca <bcoca@users.noreply.github.com>
2023-08-20 14:57:58 +02:00
patchback[bot]
b17cc09b07 [PR #7124/33998a5b backport][stable-7] snap: fix case when snap list is empty (#7128)
snap: fix case when snap list is empty (#7124)

* fix case when snap list is empty

* add changelog frag

(cherry picked from commit 33998a5b70)

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2023-08-18 08:00:19 +02:00
Felix Fontein
ee7f44b09b Next expected release will be 7.4.0. 2023-08-15 07:51:00 +02:00
57 changed files with 3356 additions and 1615 deletions

9
.github/BOTMETA.yml vendored
View File

@@ -669,7 +669,9 @@ files:
labels: jboss
maintainers: $team_jboss jhoekx
$modules/jenkins_build.py:
maintainers: brettmilford unnecessary-username
maintainers: brettmilford unnecessary-username juanmcasanova
$modules/jenkins_build_info.py:
maintainers: juanmcasanova
$modules/jenkins_job.py:
maintainers: sermilrod
$modules/jenkins_job_info.py:
@@ -937,7 +939,7 @@ files:
labels: pagerduty
maintainers: suprememoocow thaumos
$modules/pagerduty_alert.py:
maintainers: ApsOps
maintainers: ApsOps xshen1
$modules/pagerduty_change.py:
maintainers: adamvaughan
$modules/pagerduty_user.py:
@@ -980,6 +982,9 @@ files:
maintainers: $team_solaris dermute
$modules/pmem.py:
maintainers: mizumm
$modules/pnpm.py:
ignore: chrishoffman
maintainers: aretrosen
$modules/portage.py:
ignore: sayap
labels: portage

View File

@@ -6,6 +6,47 @@ Community General Release Notes
This changelog describes changes after version 6.0.0.
v7.4.0
======
Release Summary
---------------
Bugfix and feature release.
Minor Changes
-------------
- cobbler inventory plugin - add ``exclude_mgmt_classes`` and ``include_mgmt_classes`` options to exclude or include hosts based on management classes (https://github.com/ansible-collections/community.general/pull/7184).
- cpanm - minor refactor when creating the ``CmdRunner`` object (https://github.com/ansible-collections/community.general/pull/7231).
- gitlab_group_variable - add support for ``raw`` variables suboption (https://github.com/ansible-collections/community.general/pull/7132).
- gitlab_project_variable - add support for ``raw`` variables suboption (https://github.com/ansible-collections/community.general/pull/7132).
- jenkins_build - add new ``detach`` option, which allows the module to exit successfully as long as the build is created (default functionality is still waiting for the build to end before exiting) (https://github.com/ansible-collections/community.general/pull/7204).
- jenkins_build - add new ``time_between_checks`` option, which allows to configure the wait time between requests to the Jenkins server (https://github.com/ansible-collections/community.general/pull/7204).
- make - allows ``params`` to be used without value (https://github.com/ansible-collections/community.general/pull/7180).
- nmap inventory plugin - now has a ``use_arp_ping`` option to allow the user to disable the default ARP ping query for a more reliable form (https://github.com/ansible-collections/community.general/pull/7119).
- pagerduty - adds in option to use v2 API for creating pagerduty incidents (https://github.com/ansible-collections/community.general/issues/6151)
- pritunl module utils - ensure ``validate_certs`` parameter is honoured in all methods (https://github.com/ansible-collections/community.general/pull/7156).
- redfish_info - report ``Id`` in the output of ``GetManagerInventory`` (https://github.com/ansible-collections/community.general/pull/7140).
- redfish_utils module utils - support ``Volumes`` in response for ``GetDiskInventory`` (https://github.com/ansible-collections/community.general/pull/6819).
- unixy callback plugin - add support for ``check_mode_markers`` option (https://github.com/ansible-collections/community.general/pull/7179).
Bugfixes
--------
- CmdRunner module utils - does not attempt to resolve path if executable is a relative or absolute path (https://github.com/ansible-collections/community.general/pull/7200).
- nmap inventory plugin - now uses ``get_option`` in all cases to get its configuration information (https://github.com/ansible-collections/community.general/pull/7119).
- nsupdate - fix a possible ``list index out of range`` exception (https://github.com/ansible-collections/community.general/issues/836).
- oci_utils module util - fix inappropriate logical comparison expressions and makes them simpler. The previous checks had logical short circuits (https://github.com/ansible-collections/community.general/pull/7125).
- pritunl module utils - fix incorrect URL parameter for orgnization add method (https://github.com/ansible-collections/community.general/pull/7161).
- snap - an exception was being raised when snap list was empty (https://github.com/ansible-collections/community.general/pull/7124, https://github.com/ansible-collections/community.general/issues/7120).
New Modules
-----------
- jenkins_build_info - Get information about Jenkins builds
- pnpm - Manage node.js packages with pnpm
v7.3.0
======

View File

@@ -1375,3 +1375,66 @@ releases:
- 7113-redfish-utils-power-cycle.yml
- lvol-pct-of-origin.yml
release_date: '2023-08-15'
7.4.0:
changes:
bugfixes:
- CmdRunner module utils - does not attempt to resolve path if executable is
a relative or absolute path (https://github.com/ansible-collections/community.general/pull/7200).
- nmap inventory plugin - now uses ``get_option`` in all cases to get its configuration
information (https://github.com/ansible-collections/community.general/pull/7119).
- nsupdate - fix a possible ``list index out of range`` exception (https://github.com/ansible-collections/community.general/issues/836).
- oci_utils module util - fix inappropriate logical comparison expressions and
makes them simpler. The previous checks had logical short circuits (https://github.com/ansible-collections/community.general/pull/7125).
- pritunl module utils - fix incorrect URL parameter for orgnization add method
(https://github.com/ansible-collections/community.general/pull/7161).
- snap - an exception was being raised when snap list was empty (https://github.com/ansible-collections/community.general/pull/7124,
https://github.com/ansible-collections/community.general/issues/7120).
minor_changes:
- cobbler inventory plugin - add ``exclude_mgmt_classes`` and ``include_mgmt_classes``
options to exclude or include hosts based on management classes (https://github.com/ansible-collections/community.general/pull/7184).
- cpanm - minor refactor when creating the ``CmdRunner`` object (https://github.com/ansible-collections/community.general/pull/7231).
- gitlab_group_variable - add support for ``raw`` variables suboption (https://github.com/ansible-collections/community.general/pull/7132).
- gitlab_project_variable - add support for ``raw`` variables suboption (https://github.com/ansible-collections/community.general/pull/7132).
- jenkins_build - add new ``detach`` option, which allows the module to exit
successfully as long as the build is created (default functionality is still
waiting for the build to end before exiting) (https://github.com/ansible-collections/community.general/pull/7204).
- jenkins_build - add new ``time_between_checks`` option, which allows to configure
the wait time between requests to the Jenkins server (https://github.com/ansible-collections/community.general/pull/7204).
- make - allows ``params`` to be used without value (https://github.com/ansible-collections/community.general/pull/7180).
- nmap inventory plugin - now has a ``use_arp_ping`` option to allow the user
to disable the default ARP ping query for a more reliable form (https://github.com/ansible-collections/community.general/pull/7119).
- pagerduty - adds in option to use v2 API for creating pagerduty incidents
(https://github.com/ansible-collections/community.general/issues/6151)
- pritunl module utils - ensure ``validate_certs`` parameter is honoured in
all methods (https://github.com/ansible-collections/community.general/pull/7156).
- redfish_info - report ``Id`` in the output of ``GetManagerInventory`` (https://github.com/ansible-collections/community.general/pull/7140).
- redfish_utils module utils - support ``Volumes`` in response for ``GetDiskInventory``
(https://github.com/ansible-collections/community.general/pull/6819).
- unixy callback plugin - add support for ``check_mode_markers`` option (https://github.com/ansible-collections/community.general/pull/7179).
release_summary: Bugfix and feature release.
fragments:
- 6819-redfish-utils-add-links-parameter-for-get_disk_inventory.yml
- 7.4.0.yml
- 7118-nmap_inv_plugin_no_arp_option.yml
- 7124-snap-empty-list.yml
- 7125-fix-inappropriate-comparison.yml
- 7132-gitlab-raw-variables.yml
- 7140-id-getmanagerinv-output.yml
- 7156-ensure-validate-certs-parameter-is-honoured.yml
- 7161-fix-incorrect-post-parameter.yml
- 7179-unixy-support-checkmode-markers.yml
- 7180-make_params_without_value.yml
- 7184-cobbler-mgmt-classes.yml
- 7200-cmd-runner-abs-path.yml
- 7219-fix-nsupdate-cname.yaml
- 7231-cpanm-adjustments.yml
- improvements-to-jenkins-build-module.yml
- update-v2-pagerduty-alert.yml
modules:
- description: Get information about Jenkins builds
name: jenkins_build_info
namespace: ''
- description: Manage node.js packages with pnpm
name: pnpm
namespace: ''
release_date: '2023-09-11'

View File

@@ -5,7 +5,7 @@
namespace: community
name: general
version: 7.3.0
version: 7.4.0
readme: README.md
authors:
- Ansible (https://github.com/ansible)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Allyson Bowles <@akatch>
# Copyright (c) 2023, Al Bowles <@akatch>
# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
@@ -11,7 +11,7 @@ __metaclass__ = type
DOCUMENTATION = '''
name: unixy
type: stdout
author: Allyson Bowles (@akatch)
author: Al Bowles (@akatch)
short_description: condensed Ansible output
description:
- Consolidated Ansible output in the style of LINUX/UNIX startup logs.
@@ -40,7 +40,6 @@ class CallbackModule(CallbackModule_default):
- Only display task names if the task runs on at least one host
- Add option to display all hostnames on a single line in the appropriate result color (failures may have a separate line)
- Consolidate stats display
- Display whether run is in --check mode
- Don't show play name if no hosts found
'''
@@ -92,19 +91,31 @@ class CallbackModule(CallbackModule_default):
def v2_playbook_on_task_start(self, task, is_conditional):
self._get_task_display_name(task)
if self.task_display_name is not None:
self._display.display("%s..." % self.task_display_name)
if task.check_mode and self.get_option('check_mode_markers'):
self._display.display("%s (check mode)..." % self.task_display_name)
else:
self._display.display("%s..." % self.task_display_name)
def v2_playbook_on_handler_task_start(self, task):
self._get_task_display_name(task)
if self.task_display_name is not None:
self._display.display("%s (via handler)... " % self.task_display_name)
if task.check_mode and self.get_option('check_mode_markers'):
self._display.display("%s (via handler in check mode)... " % self.task_display_name)
else:
self._display.display("%s (via handler)... " % self.task_display_name)
def v2_playbook_on_play_start(self, play):
name = play.get_name().strip()
if name and play.hosts:
msg = u"\n- %s on hosts: %s -" % (name, ",".join(play.hosts))
if play.check_mode and self.get_option('check_mode_markers'):
if name and play.hosts:
msg = u"\n- %s (in check mode) on hosts: %s -" % (name, ",".join(play.hosts))
else:
msg = u"- check mode -"
else:
msg = u"---"
if name and play.hosts:
msg = u"\n- %s on hosts: %s -" % (name, ",".join(play.hosts))
else:
msg = u"---"
self._display.display(msg)
@@ -227,8 +238,10 @@ class CallbackModule(CallbackModule_default):
self._display.display(" Ran out of hosts!", color=C.COLOR_ERROR)
def v2_playbook_on_start(self, playbook):
# TODO display whether this run is happening in check mode
self._display.display("Executing playbook %s" % basename(playbook._file_name))
if context.CLIARGS['check'] and self.get_option('check_mode_markers'):
self._display.display("Executing playbook %s in check mode" % basename(playbook._file_name))
else:
self._display.display("Executing playbook %s" % basename(playbook._file_name))
# show CLI arguments
if self._display.verbosity > 3:

View File

@@ -42,6 +42,12 @@ DOCUMENTATION = '''
description: Fallback to cached results if connection to cobbler fails.
type: boolean
default: false
exclude_mgmt_classes:
description: Management classes to exclude from inventory.
type: list
default: []
elements: str
version_added: 7.4.0
exclude_profiles:
description:
- Profiles to exclude from inventory.
@@ -49,6 +55,12 @@ DOCUMENTATION = '''
type: list
default: []
elements: str
include_mgmt_classes:
description: Management classes to include from inventory.
type: list
default: []
elements: str
version_added: 7.4.0
include_profiles:
description:
- Profiles to include from inventory.
@@ -216,6 +228,8 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
self.cache_key = self.get_cache_key(path)
self.use_cache = cache and self.get_option('cache')
self.exclude_mgmt_classes = self.get_option('exclude_mgmt_classes')
self.include_mgmt_classes = self.get_option('include_mgmt_classes')
self.exclude_profiles = self.get_option('exclude_profiles')
self.include_profiles = self.get_option('include_profiles')
self.group_by = self.get_option('group_by')
@@ -265,9 +279,16 @@ class InventoryModule(BaseInventoryPlugin, Cacheable):
hostname = host['hostname'] # None
interfaces = host['interfaces']
if self._exclude_profile(host['profile']):
self.display.vvvv('Excluding host %s in profile %s\n' % (host['name'], host['profile']))
continue
if set(host['mgmt_classes']) & set(self.include_mgmt_classes):
self.display.vvvv('Including host %s in mgmt_classes %s\n' % (host['name'], host['mgmt_classes']))
else:
if self._exclude_profile(host['profile']):
self.display.vvvv('Excluding host %s in profile %s\n' % (host['name'], host['profile']))
continue
if set(host['mgmt_classes']) & set(self.exclude_mgmt_classes):
self.display.vvvv('Excluding host %s in mgmt_classes %s\n' % (host['name'], host['mgmt_classes']))
continue
# hostname is often empty for non-static IP hosts
if hostname == '':

View File

@@ -85,6 +85,11 @@ DOCUMENTATION = '''
type: boolean
default: false
version_added: 6.1.0
use_arp_ping:
description: Whether to always (V(true)) use the quick ARP ping or (V(false)) a slower but more reliable method.
type: boolean
default: true
version_added: 7.4.0
notes:
- At least one of ipv4 or ipv6 is required to be True, both can be True, but they cannot both be False.
- 'TODO: add OS fingerprinting'
@@ -196,40 +201,43 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
# setup command
cmd = [self._nmap]
if self._options['sudo']:
if self.get_option['sudo']:
cmd.insert(0, 'sudo')
if self._options['port']:
if self.get_option['port']:
cmd.append('-p')
cmd.append(self._options['port'])
cmd.append(self.get_option['port'])
if not self._options['ports']:
if not self.get_option['ports']:
cmd.append('-sP')
if self._options['ipv4'] and not self._options['ipv6']:
if self.get_option['ipv4'] and not self.get_option['ipv6']:
cmd.append('-4')
elif self._options['ipv6'] and not self._options['ipv4']:
elif self.get_option['ipv6'] and not self.get_option['ipv4']:
cmd.append('-6')
elif not self._options['ipv6'] and not self._options['ipv4']:
elif not self.get_option['ipv6'] and not self.get_option['ipv4']:
raise AnsibleParserError('One of ipv4 or ipv6 must be enabled for this plugin')
if self._options['exclude']:
if self.get_option['exclude']:
cmd.append('--exclude')
cmd.append(','.join(self._options['exclude']))
cmd.append(','.join(self.get_option['exclude']))
if self._options['dns_resolve']:
if self.get_option['dns_resolve']:
cmd.append('-n')
if self._options['udp_scan']:
if self.get_option['udp_scan']:
cmd.append('-sU')
if self._options['icmp_timestamp']:
if self.get_option['icmp_timestamp']:
cmd.append('-PP')
if self._options['open']:
if self.get_option['open']:
cmd.append('--open')
cmd.append(self._options['address'])
if not self.get_option['use_arp_ping']:
cmd.append('--disable-arp-ping')
cmd.append(self.get_option['address'])
try:
# execute
p = Popen(cmd, stdout=PIPE, stderr=PIPE)

View File

@@ -6,6 +6,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os
from functools import wraps
from ansible.module_utils.common.collections import is_sequence
@@ -204,12 +205,17 @@ class CmdRunner(object):
environ_update = {}
self.environ_update = environ_update
self.command[0] = module.get_bin_path(self.command[0], opt_dirs=path_prefix, required=True)
_cmd = self.command[0]
self.command[0] = _cmd if (os.path.isabs(_cmd) or '/' in _cmd) else module.get_bin_path(_cmd, opt_dirs=path_prefix, required=True)
for mod_param_name, spec in iteritems(module.argument_spec):
if mod_param_name not in self.arg_formats:
self.arg_formats[mod_param_name] = _Format.as_default_type(spec.get('type', 'str'), mod_param_name)
@property
def binary(self):
return self.command[0]
def __call__(self, args_order=None, output_process=None, ignore_value_none=True, check_mode_skip=False, check_mode_return=None, **kwargs):
if output_process is None:
output_process = _process_as_is

View File

@@ -135,6 +135,7 @@ def vars_to_variables(vars, module):
"value": str(value),
"masked": False,
"protected": False,
"raw": False,
"variable_type": "env_var",
}
)
@@ -145,6 +146,7 @@ def vars_to_variables(vars, module):
"value": value.get('value'),
"masked": value.get('masked'),
"protected": value.get('protected'),
"raw": value.get('raw'),
"variable_type": value.get('variable_type'),
}

View File

@@ -79,7 +79,7 @@ def _post_pritunl_organization(
api_secret=api_secret,
base_url=base_url,
method="POST",
path="/organization/%s",
path="/organization",
headers={"Content-Type": "application/json"},
data=json.dumps(organization_data),
validate_certs=validate_certs,
@@ -220,7 +220,7 @@ def post_pritunl_organization(
api_secret=api_secret,
base_url=base_url,
organization_data={"name": organization_name},
validate_certs=True,
validate_certs=validate_certs,
)
if response.getcode() != 200:
@@ -248,7 +248,7 @@ def post_pritunl_user(
base_url=base_url,
organization_id=organization_id,
user_data=user_data,
validate_certs=True,
validate_certs=validate_certs,
)
if response.getcode() != 200:
@@ -267,7 +267,7 @@ def post_pritunl_user(
organization_id=organization_id,
user_data=user_data,
user_id=user_id,
validate_certs=True,
validate_certs=validate_certs,
)
if response.getcode() != 200:
@@ -287,7 +287,7 @@ def delete_pritunl_organization(
api_secret=api_secret,
base_url=base_url,
organization_id=organization_id,
validate_certs=True,
validate_certs=validate_certs,
)
if response.getcode() != 200:
@@ -307,7 +307,7 @@ def delete_pritunl_user(
base_url=base_url,
organization_id=organization_id,
user_id=user_id,
validate_certs=True,
validate_certs=validate_certs,
)
if response.getcode() != 200:

View File

@@ -561,7 +561,7 @@ def are_lists_equal(s, t):
if s is None and t is None:
return True
if (s is None and len(t) >= 0) or (t is None and len(s) >= 0) or (len(s) != len(t)):
if s is None or t is None or (len(s) != len(t)):
return False
if len(s) == 0:
@@ -1026,10 +1026,7 @@ def check_if_user_value_matches_resources_attr(
return
if (
resources_value_for_attr is None
and len(user_provided_value_for_attr) >= 0
or user_provided_value_for_attr is None
and len(resources_value_for_attr) >= 0
resources_value_for_attr is None or user_provided_value_for_attr is None
):
res[0] = False
return

View File

@@ -791,7 +791,7 @@ class RedfishUtils(object):
properties = ['BlockSizeBytes', 'CapableSpeedGbs', 'CapacityBytes',
'EncryptionAbility', 'EncryptionStatus',
'FailurePredicted', 'HotspareType', 'Id', 'Identifiers',
'Manufacturer', 'MediaType', 'Model', 'Name',
'Links', 'Manufacturer', 'MediaType', 'Model', 'Name',
'PartNumber', 'PhysicalLocation', 'Protocol', 'Revision',
'RotationSpeedRPM', 'SerialNumber', 'Status']
@@ -861,7 +861,12 @@ class RedfishUtils(object):
for property in properties:
if property in data:
if data[property] is not None:
drive_result[property] = data[property]
if property == "Links":
if "Volumes" in data["Links"].keys():
volumes = [v["@odata.id"] for v in data["Links"]["Volumes"]]
drive_result["Volumes"] = volumes
else:
drive_result[property] = data[property]
drive_results.append(drive_result)
drives = {'Controller': controller_name,
'Drives': drive_results}
@@ -3344,7 +3349,7 @@ class RedfishUtils(object):
result = {}
inventory = {}
# Get these entries, but does not fail if not found
properties = ['FirmwareVersion', 'ManagerType', 'Manufacturer', 'Model',
properties = ['Id', 'FirmwareVersion', 'ManagerType', 'Manufacturer', 'Model',
'PartNumber', 'PowerState', 'SerialNumber', 'Status', 'UUID']
response = self.get_request(self.root_uri + manager_uri)

View File

@@ -154,7 +154,7 @@ def _get_ctl_binary(module):
if ctl_binary is not None:
return ctl_binary
module.fail_json(msg="Neither of apache2ctl nor apachctl found. At least one apache control binary is necessary.")
module.fail_json(msg="Neither of apache2ctl nor apachectl found. At least one apache control binary is necessary.")
def _module_is_enabled(module):

View File

@@ -183,8 +183,9 @@ class CPANMinus(ModuleHelper):
if v.name and v.from_path:
self.do_raise("Parameters 'name' and 'from_path' are mutually exclusive when 'mode=new'")
self.command = self.get_bin_path(v.executable if v.executable else self.command)
self.vars.set("binary", self.command)
self.command = v.executable if v.executable else self.command
self.runner = CmdRunner(self.module, self.command, self.command_args_formats, check_rc=True)
self.vars.binary = self.runner.binary
def _is_package_installed(self, name, locallib, version):
def process(rc, out, err):
@@ -220,8 +221,6 @@ class CPANMinus(ModuleHelper):
self.do_raise(msg=err, cmd=self.vars.cmd_args)
return 'is up to date' not in err and 'is up to date' not in out
runner = CmdRunner(self.module, self.command, self.command_args_formats, check_rc=True)
v = self.vars
pkg_param = 'from_path' if v.from_path else 'name'
@@ -235,7 +234,7 @@ class CPANMinus(ModuleHelper):
return
pkg_spec = self.sanitize_pkg_spec_version(v[pkg_param], v.version)
with runner(['notest', 'locallib', 'mirror', 'mirror_only', 'installdeps', 'pkg_spec'], output_process=process) as ctx:
with self.runner(['notest', 'locallib', 'mirror', 'mirror_only', 'installdeps', 'pkg_spec'], output_process=process) as ctx:
self.changed = ctx.run(pkg_spec=pkg_spec)

View File

@@ -53,13 +53,14 @@ options:
type: bool
vars:
description:
- When the list element is a simple key-value pair, set masked and protected to false.
- When the list element is a dict with the keys C(value), C(masked) and C(protected), the user can
have full control about whether a value should be masked, protected or both.
- When the list element is a simple key-value pair, masked, raw and protected will be set to false.
- When the list element is a dict with the keys C(value), C(masked), C(raw) and C(protected), the user can
have full control about whether a value should be masked, raw, protected or both.
- Support for group variables requires GitLab >= 9.5.
- Support for environment_scope requires GitLab Premium >= 13.11.
- Support for protected values requires GitLab >= 9.3.
- Support for masked values requires GitLab >= 11.10.
- Support for raw values requires GitLab >= 15.7.
- A C(value) must be a string or a number.
- Field C(variable_type) must be a string with either V(env_var), which is the default, or V(file).
- When a value is masked, it must be in Base64 and have a length of at least 8 characters.
@@ -95,6 +96,13 @@ options:
- Wether variable value is protected or not.
type: bool
default: false
raw:
description:
- Wether variable value is raw or not.
- Support for raw values requires GitLab >= 15.7.
type: bool
default: false
version_added: '7.4.0'
variable_type:
description:
- Wether a variable is an environment variable (V(env_var)) or a file (V(file)).
@@ -126,6 +134,38 @@ EXAMPLES = r'''
variable_type: env_var
environment_scope: production
- name: Set or update some CI/CD variables with raw value
community.general.gitlab_group_variable:
api_url: https://gitlab.com
api_token: secret_access_token
group: scodeman/testgroup/
purge: false
vars:
ACCESS_KEY_ID: abc123
SECRET_ACCESS_KEY:
value: 3214cbad
masked: true
protected: true
raw: true
variable_type: env_var
environment_scope: '*'
- name: Set or update some CI/CD variables with expandable value
community.general.gitlab_group_variable:
api_url: https://gitlab.com
api_token: secret_access_token
group: scodeman/testgroup/
purge: false
vars:
ACCESS_KEY_ID: abc123
SECRET_ACCESS_KEY:
value: '$MY_OTHER_VARIABLE'
masked: true
protected: true
raw: false
variable_type: env_var
environment_scope: '*'
- name: Delete one variable
community.general.gitlab_group_variable:
api_url: https://gitlab.com
@@ -199,6 +239,7 @@ class GitlabGroupVariables(object):
"value": var_obj.get('value'),
"masked": var_obj.get('masked'),
"protected": var_obj.get('protected'),
"raw": var_obj.get('raw'),
"variable_type": var_obj.get('variable_type'),
}
if var_obj.get('environment_scope') is not None:
@@ -267,6 +308,8 @@ def native_python_main(this_gitlab, purge, requested_variables, state, module):
item['value'] = str(item.get('value'))
if item.get('protected') is None:
item['protected'] = False
if item.get('raw') is None:
item['raw'] = False
if item.get('masked') is None:
item['masked'] = False
if item.get('environment_scope') is None:
@@ -343,6 +386,7 @@ def main():
value=dict(type='str', no_log=True),
masked=dict(type='bool', default=False),
protected=dict(type='bool', default=False),
raw=dict(type='bool', default=False),
environment_scope=dict(type='str', default='*'),
variable_type=dict(type='str', default='env_var', choices=["env_var", "file"])
)),

View File

@@ -51,11 +51,12 @@ options:
type: bool
vars:
description:
- When the list element is a simple key-value pair, masked and protected will be set to false.
- When the list element is a dict with the keys C(value), C(masked) and C(protected), the user can
have full control about whether a value should be masked, protected or both.
- When the list element is a simple key-value pair, masked, raw and protected will be set to false.
- When the list element is a dict with the keys C(value), C(masked), C(raw) and C(protected), the user can
have full control about whether a value should be masked, raw, protected or both.
- Support for protected values requires GitLab >= 9.3.
- Support for masked values requires GitLab >= 11.10.
- Support for raw values requires GitLab >= 15.7.
- Support for environment_scope requires GitLab Premium >= 13.11.
- Support for variable_type requires GitLab >= 11.11.
- A C(value) must be a string or a number.
@@ -96,6 +97,13 @@ options:
- Support for protected values requires GitLab >= 9.3.
type: bool
default: false
raw:
description:
- Wether variable value is raw or not.
- Support for raw values requires GitLab >= 15.7.
type: bool
default: false
version_added: '7.4.0'
variable_type:
description:
- Wether a variable is an environment variable (V(env_var)) or a file (V(file)).
@@ -143,6 +151,38 @@ EXAMPLES = '''
variable_type: env_var
environment_scope: '*'
- name: Set or update some CI/CD variables with raw value
community.general.gitlab_project_variable:
api_url: https://gitlab.com
api_token: secret_access_token
project: markuman/dotfiles
purge: false
vars:
ACCESS_KEY_ID: abc123
SECRET_ACCESS_KEY:
value: 3214cbad
masked: true
protected: true
raw: true
variable_type: env_var
environment_scope: '*'
- name: Set or update some CI/CD variables with expandable value
community.general.gitlab_project_variable:
api_url: https://gitlab.com
api_token: secret_access_token
project: markuman/dotfiles
purge: false
vars:
ACCESS_KEY_ID: abc123
SECRET_ACCESS_KEY:
value: '$MY_OTHER_VARIABLE'
masked: true
protected: true
raw: false
variable_type: env_var
environment_scope: '*'
- name: Delete one variable
community.general.gitlab_project_variable:
api_url: https://gitlab.com
@@ -220,6 +260,7 @@ class GitlabProjectVariables(object):
"value": var_obj.get('value'),
"masked": var_obj.get('masked'),
"protected": var_obj.get('protected'),
"raw": var_obj.get('raw'),
"variable_type": var_obj.get('variable_type'),
}
@@ -290,6 +331,8 @@ def native_python_main(this_gitlab, purge, requested_variables, state, module):
item['value'] = str(item.get('value'))
if item.get('protected') is None:
item['protected'] = False
if item.get('raw') is None:
item['raw'] = False
if item.get('masked') is None:
item['masked'] = False
if item.get('environment_scope') is None:
@@ -366,6 +409,7 @@ def main():
value=dict(type='str', no_log=True),
masked=dict(type='bool', default=False),
protected=dict(type='bool', default=False),
raw=dict(type='bool', default=False),
environment_scope=dict(type='str', default='*'),
variable_type=dict(type='str', default='env_var', choices=["env_var", "file"]),
)),

View File

@@ -20,6 +20,7 @@ requirements:
author:
- Brett Milford (@brettmilford)
- Tong He (@unnecessary-username)
- Juan Casanova (@juanmcasanova)
extends_documentation_fragment:
- community.general.attributes
attributes:
@@ -65,6 +66,19 @@ options:
description:
- User to authenticate with the Jenkins server.
type: str
detach:
description:
- Enable detached mode to not wait for the build end.
default: false
type: bool
version_added: 7.4.0
time_between_checks:
description:
- Time in seconds to wait between requests to the Jenkins server.
- This times must be higher than the configured quiet time for the job.
default: 10
type: int
version_added: 7.4.0
'''
EXAMPLES = '''
@@ -152,6 +166,8 @@ class JenkinsBuild:
self.user = module.params.get('user')
self.jenkins_url = module.params.get('url')
self.build_number = module.params.get('build_number')
self.detach = module.params.get('detach')
self.time_between_checks = module.params.get('time_between_checks')
self.server = self.get_jenkins_connection()
self.result = {
@@ -235,7 +251,14 @@ class JenkinsBuild:
build_status = self.get_build_status()
if build_status['result'] is None:
sleep(10)
# If detached mode is active mark as success, we wouldn't be able to get here if it didn't exist
if self.detach:
result['changed'] = True
result['build_info'] = build_status
return result
sleep(self.time_between_checks)
self.get_result()
else:
if self.state == "stopped" and build_status['result'] == "ABORTED":
@@ -273,6 +296,8 @@ def main():
token=dict(no_log=True),
url=dict(default="http://localhost:8080"),
user=dict(),
detach=dict(type='bool', default=False),
time_between_checks=dict(type='int', default=10),
),
mutually_exclusive=[['password', 'token']],
required_if=[['state', 'absent', ['build_number'], True], ['state', 'stopped', ['build_number'], True]],
@@ -288,7 +313,7 @@ def main():
else:
jenkins_build.absent_build()
sleep(10)
sleep(jenkins_build.time_between_checks)
result = jenkins_build.get_result()
module.exit_json(**result)

View File

@@ -0,0 +1,210 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: jenkins_build_info
short_description: Get information about Jenkins builds
version_added: 7.4.0
description:
- Get information about Jenkins builds with Jenkins REST API.
requirements:
- "python-jenkins >= 0.4.12"
author:
- Juan Casanova (@juanmcasanova)
extends_documentation_fragment:
- community.general.attributes
- community.general.attributes.info_module
options:
name:
description:
- Name of the Jenkins job to which the build belongs.
required: true
type: str
build_number:
description:
- An integer which specifies a build of a job.
- If not specified the last build information will be returned.
type: int
password:
description:
- Password to authenticate with the Jenkins server.
type: str
token:
description:
- API token used to authenticate with the Jenkins server.
type: str
url:
description:
- URL of the Jenkins server.
default: http://localhost:8080
type: str
user:
description:
- User to authenticate with the Jenkins server.
type: str
'''
EXAMPLES = '''
- name: Get information about a jenkins build using basic authentication
community.general.jenkins_build_info:
name: "test-check"
build_number: 1
user: admin
password: asdfg
url: http://localhost:8080
- name: Get information about a jenkins build anonymously
community.general.jenkins_build_info:
name: "stop-check"
build_number: 3
url: http://localhost:8080
- name: Get information about a jenkins build using token authentication
community.general.jenkins_build_info:
name: "delete-experiment"
build_number: 30
user: Jenkins
token: abcdefghijklmnopqrstuvwxyz123456
url: http://localhost:8080
'''
RETURN = '''
---
name:
description: Name of the jenkins job.
returned: success
type: str
sample: "test-job"
state:
description: State of the jenkins job.
returned: success
type: str
sample: present
user:
description: User used for authentication.
returned: success
type: str
sample: admin
url:
description: URL to connect to the Jenkins server.
returned: success
type: str
sample: https://jenkins.mydomain.com
build_info:
description: Build info of the jenkins job.
returned: success
type: dict
'''
import traceback
JENKINS_IMP_ERR = None
try:
import jenkins
python_jenkins_installed = True
except ImportError:
JENKINS_IMP_ERR = traceback.format_exc()
python_jenkins_installed = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native
class JenkinsBuildInfo:
def __init__(self, module):
self.module = module
self.name = module.params.get('name')
self.password = module.params.get('password')
self.token = module.params.get('token')
self.user = module.params.get('user')
self.jenkins_url = module.params.get('url')
self.build_number = module.params.get('build_number')
self.server = self.get_jenkins_connection()
self.result = {
'changed': False,
'url': self.jenkins_url,
'name': self.name,
'user': self.user,
}
def get_jenkins_connection(self):
try:
if (self.user and self.password):
return jenkins.Jenkins(self.jenkins_url, self.user, self.password)
elif (self.user and self.token):
return jenkins.Jenkins(self.jenkins_url, self.user, self.token)
elif (self.user and not (self.password or self.token)):
return jenkins.Jenkins(self.jenkins_url, self.user)
else:
return jenkins.Jenkins(self.jenkins_url)
except Exception as e:
self.module.fail_json(msg='Unable to connect to Jenkins server, %s' % to_native(e))
def get_build_status(self):
try:
if self.build_number is None:
job_info = self.server.get_job_info(self.name)
self.build_number = job_info['lastBuild']['number']
return self.server.get_build_info(self.name, self.build_number)
except jenkins.JenkinsException as e:
response = {}
response["result"] = "ABSENT"
return response
except Exception as e:
self.module.fail_json(msg='Unable to fetch build information, %s' % to_native(e),
exception=traceback.format_exc())
def get_result(self):
result = self.result
build_status = self.get_build_status()
if build_status['result'] == "ABSENT":
result['failed'] = True
result['build_info'] = build_status
return result
def test_dependencies(module):
if not python_jenkins_installed:
module.fail_json(
msg=missing_required_lib("python-jenkins",
url="https://python-jenkins.readthedocs.io/en/latest/install.html"),
exception=JENKINS_IMP_ERR)
def main():
module = AnsibleModule(
argument_spec=dict(
build_number=dict(type='int'),
name=dict(required=True),
password=dict(no_log=True),
token=dict(no_log=True),
url=dict(default="http://localhost:8080"),
user=dict(),
),
mutually_exclusive=[['password', 'token']],
supports_check_mode=True,
)
test_dependencies(module)
jenkins_build_info = JenkinsBuildInfo(module)
result = jenkins_build_info.get_result()
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -67,7 +67,7 @@ author:
EXAMPLES = '''
- name: Set default client scopes on realm level
community.general.keycloak_clientsecret_info:
community.general.keycloak_clientscope_type:
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com/auth
auth_realm: master
@@ -79,7 +79,7 @@ EXAMPLES = '''
- name: Set default and optional client scopes on client level with token auth
community.general.keycloak_clientsecret_info:
community.general.keycloak_clientscope_type:
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com/auth
token: TOKEN

View File

@@ -49,6 +49,7 @@ options:
params:
description:
- Any extra parameters to pass to make.
- If the value is empty, only the key will be used. For example, V(FOO:) will produce V(FOO), not V(FOO=).
type: dict
target:
description:
@@ -90,6 +91,18 @@ EXAMPLES = r'''
chdir: /home/ubuntu/cool-project
target: all
file: /some-project/Makefile
- name: build arm64 kernel on FreeBSD, with 16 parallel jobs
community.general.make:
chdir: /usr/src
jobs: 16
target: buildkernel
params:
# This adds -DWITH_FDT to the command line:
-DWITH_FDT:
# The following adds TARGET=arm64 TARGET_ARCH=aarch64 to the command line:
TARGET: arm64
TARGET_ARCH: aarch64
'''
RETURN = r'''
@@ -190,7 +203,7 @@ def main():
# Fall back to system make
make_path = module.get_bin_path('make', required=True)
if module.params['params'] is not None:
make_parameters = [k + '=' + str(v) for k, v in iteritems(module.params['params'])]
make_parameters = [k + (('=' + str(v)) if v is not None else '') for k, v in iteritems(module.params['params'])]
else:
make_parameters = []

View File

@@ -467,10 +467,8 @@ class RecordManager(object):
if lookup.rcode() != dns.rcode.NOERROR:
self.module.fail_json(msg='Failed to lookup TTL of existing matching record.')
if self.module.params['type'] == 'NS':
current_ttl = lookup.answer[0].ttl if lookup.answer else lookup.authority[0].ttl
else:
current_ttl = lookup.answer[0].ttl
current_ttl = lookup.answer[0].ttl if lookup.answer else lookup.authority[0].ttl
return current_ttl != self.module.params['ttl']

View File

@@ -16,6 +16,7 @@ description:
- This module will let you trigger, acknowledge or resolve a PagerDuty incident by sending events
author:
- "Amanpreet Singh (@ApsOps)"
- "Xiao Shen (@xshen1)"
requirements:
- PagerDuty API access
extends_documentation_fragment:
@@ -30,20 +31,25 @@ options:
type: str
description:
- PagerDuty unique subdomain. Obsolete. It is not used with PagerDuty REST v2 API.
api_key:
type: str
description:
- The pagerduty API key (readonly access), generated on the pagerduty site.
- Required if O(api_version=v1).
integration_key:
type: str
description:
- The GUID of one of your 'Generic API' services.
- This is the 'integration key' listed on a 'Integrations' tab of PagerDuty service.
service_id:
type: str
description:
- ID of PagerDuty service when incidents will be triggered, acknowledged or resolved.
required: true
- Required if O(api_version=v1).
service_key:
type: str
description:
- The GUID of one of your "Generic API" services. Obsolete. Please use O(integration_key).
integration_key:
type: str
description:
- The GUID of one of your "Generic API" services.
- This is the "integration key" listed on a "Integrations" tab of PagerDuty service.
- The GUID of one of your 'Generic API' services. Obsolete. Please use O(integration_key).
state:
type: str
description:
@@ -53,30 +59,17 @@ options:
- 'triggered'
- 'acknowledged'
- 'resolved'
api_key:
api_version:
type: str
description:
- The pagerduty API key (readonly access), generated on the pagerduty site.
required: true
desc:
type: str
description:
- For O(state=triggered) - Required. Short description of the problem that led to this trigger. This field (or a truncated version)
will be used when generating phone calls, SMS messages and alert emails. It will also appear on the incidents tables in the PagerDuty UI.
The maximum length is 1024 characters.
- For O(state=acknowledged) or O(state=resolved) - Text that will appear in the incident's log associated with this event.
required: false
default: Created via Ansible
incident_key:
type: str
description:
- Identifies the incident to which this O(state) should be applied.
- For O(state=triggered) - If there's no open (i.e. unresolved) incident with this key, a new one will be created. If there's already an
open incident with a matching key, this event will be appended to that incident's log. The event key provides an easy way to "de-dup"
problem reports.
- For O(state=acknowledged) or O(state=resolved) - This should be the incident_key you received back when the incident was first opened by a
trigger event. Acknowledge events referencing resolved or nonexistent incidents will be discarded.
required: false
- The API version we want to use to run the module.
- V1 is more limited with option we can provide to trigger incident.
- V2 has more variables for example, O(severity), O(source), O(custom_details), etc.
default: 'v1'
choices:
- 'v1'
- 'v2'
version_added: 7.4.0
client:
type: str
description:
@@ -87,6 +80,75 @@ options:
description:
- The URL of the monitoring client that is triggering this event.
required: false
component:
type: str
description:
- Component of the source machine that is responsible for the event, for example C(mysql) or C(eth0).
required: false
version_added: 7.4.0
custom_details:
type: dict
description:
- Additional details about the event and affected system.
- A dictionary with custom keys and values.
required: false
version_added: 7.4.0
desc:
type: str
description:
- For O(state=triggered) - Required. Short description of the problem that led to this trigger. This field (or a truncated version)
will be used when generating phone calls, SMS messages and alert emails. It will also appear on the incidents tables in the PagerDuty UI.
The maximum length is 1024 characters.
- For O(state=acknowledged) or O(state=resolved) - Text that will appear in the incident's log associated with this event.
required: false
default: Created via Ansible
incident_class:
type: str
description:
- The class/type of the event, for example C(ping failure) or C(cpu load).
required: false
version_added: 7.4.0
incident_key:
type: str
description:
- Identifies the incident to which this O(state) should be applied.
- For O(state=triggered) - If there's no open (i.e. unresolved) incident with this key, a new one will be created. If there's already an
open incident with a matching key, this event will be appended to that incident's log. The event key provides an easy way to 'de-dup'
problem reports. If no O(incident_key) is provided, then it will be generated by PagerDuty.
- For O(state=acknowledged) or O(state=resolved) - This should be the incident_key you received back when the incident was first opened by a
trigger event. Acknowledge events referencing resolved or nonexistent incidents will be discarded.
required: false
link_url:
type: str
description:
- Relevant link url to the alert. For example, the website or the job link.
required: false
version_added: 7.4.0
link_text:
type: str
description:
- A short decription of the link_url.
required: false
version_added: 7.4.0
source:
type: str
description:
- The unique location of the affected system, preferably a hostname or FQDN.
- Required in case of O(state=trigger) and O(api_version=v2).
required: false
version_added: 7.4.0
severity:
type: str
description:
- The perceived severity of the status the event is describing with respect to the affected system.
- Required in case of O(state=trigger) and O(api_version=v2).
default: 'critical'
choices:
- 'critical'
- 'warning'
- 'error'
- 'info'
version_added: 7.4.0
'''
EXAMPLES = '''
@@ -127,12 +189,50 @@ EXAMPLES = '''
state: resolved
incident_key: somekey
desc: "some text for incident's log"
- name: Trigger an v2 incident with just the basic options
community.general.pagerduty_alert:
integration_key: xxx
api_version: v2
source: My Ansible Script
state: triggered
desc: problem that led to this trigger
- name: Trigger an v2 incident with more options
community.general.pagerduty_alert:
integration_key: xxx
api_version: v2
source: My Ansible Script
state: triggered
desc: problem that led to this trigger
incident_key: somekey
client: Sample Monitoring Service
client_url: http://service.example.com
component: mysql
incident_class: ping failure
link_url: https://pagerduty.com
link_text: PagerDuty
- name: Acknowledge an incident based on incident_key using v2
community.general.pagerduty_alert:
api_version: v2
integration_key: xxx
incident_key: somekey
state: acknowledged
- name: Resolve an incident based on incident_key
community.general.pagerduty_alert:
api_version: v2
integration_key: xxx
incident_key: somekey
state: resolved
'''
import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode, urlunparse
from datetime import datetime
def check(module, name, state, service_id, integration_key, api_key, incident_key=None, http_call=fetch_url):
@@ -175,8 +275,8 @@ def check(module, name, state, service_id, integration_key, api_key, incident_ke
return incidents[0], False
def send_event(module, service_key, event_type, desc,
incident_key=None, client=None, client_url=None):
def send_event_v1(module, service_key, event_type, desc,
incident_key=None, client=None, client_url=None):
url = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
headers = {
"Content-type": "application/json"
@@ -200,61 +300,127 @@ def send_event(module, service_key, event_type, desc,
return json_out
def send_event_v2(module, service_key, event_type, payload, link,
incident_key=None, client=None, client_url=None):
url = "https://events.pagerduty.com/v2/enqueue"
headers = {
"Content-type": "application/json"
}
data = {
"routing_key": service_key,
"event_action": event_type,
"payload": payload,
"client": client,
"client_url": client_url,
}
if link:
data["links"] = [link]
if incident_key:
data["dedup_key"] = incident_key
if event_type != "trigger":
data.pop("payload")
response, info = fetch_url(module, url, method="post",
headers=headers, data=json.dumps(data))
if info["status"] != 202:
module.fail_json(msg="failed to %s. Reason: %s" %
(event_type, info['msg']))
json_out = json.loads(response.read())
return json_out, True
def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(required=False),
service_id=dict(required=True),
service_key=dict(required=False, no_log=True),
api_key=dict(required=False, no_log=True),
integration_key=dict(required=False, no_log=True),
api_key=dict(required=True, no_log=True),
state=dict(required=True,
choices=['triggered', 'acknowledged', 'resolved']),
client=dict(required=False, default=None),
client_url=dict(required=False, default=None),
service_id=dict(required=False),
service_key=dict(required=False, no_log=True),
state=dict(
required=True, choices=['triggered', 'acknowledged', 'resolved']
),
api_version=dict(type='str', default='v1', choices=['v1', 'v2']),
client=dict(required=False),
client_url=dict(required=False),
component=dict(required=False),
custom_details=dict(required=False, type='dict'),
desc=dict(required=False, default='Created via Ansible'),
incident_key=dict(required=False, default=None, no_log=False)
incident_class=dict(required=False),
incident_key=dict(required=False, no_log=False),
link_url=dict(required=False),
link_text=dict(required=False),
source=dict(required=False),
severity=dict(
default='critical', choices=['critical', 'warning', 'error', 'info']
),
),
supports_check_mode=True
required_if=[
('api_version', 'v1', ['service_id', 'api_key']),
('state', 'acknowledged', ['incident_key']),
('state', 'resolved', ['incident_key']),
],
required_one_of=[('service_key', 'integration_key')],
supports_check_mode=True,
)
name = module.params['name']
service_id = module.params['service_id']
integration_key = module.params['integration_key']
service_key = module.params['service_key']
api_key = module.params['api_key']
state = module.params['state']
client = module.params['client']
client_url = module.params['client_url']
desc = module.params['desc']
incident_key = module.params['incident_key']
service_id = module.params.get('service_id')
integration_key = module.params.get('integration_key')
service_key = module.params.get('service_key')
api_key = module.params.get('api_key')
state = module.params.get('state')
client = module.params.get('client')
client_url = module.params.get('client_url')
desc = module.params.get('desc')
incident_key = module.params.get('incident_key')
payload = {
'summary': desc,
'source': module.params.get('source'),
'timestamp': datetime.now().isoformat(),
'severity': module.params.get('severity'),
'component': module.params.get('component'),
'class': module.params.get('incident_class'),
'custom_details': module.params.get('custom_details'),
}
link = {}
if module.params.get('link_url'):
link['href'] = module.params.get('link_url')
if module.params.get('link_text'):
link['text'] = module.params.get('link_text')
if integration_key is None:
if service_key is not None:
integration_key = service_key
module.warn('"service_key" is obsolete parameter and will be removed.'
' Please, use "integration_key" instead')
else:
module.fail_json(msg="'integration_key' is required parameter")
integration_key = service_key
module.warn(
'"service_key" is obsolete parameter and will be removed.'
' Please, use "integration_key" instead'
)
state_event_dict = {
'triggered': 'trigger',
'acknowledged': 'acknowledge',
'resolved': 'resolve'
'resolved': 'resolve',
}
event_type = state_event_dict[state]
if event_type != 'trigger' and incident_key is None:
module.fail_json(msg="incident_key is required for "
"acknowledge or resolve events")
out, changed = check(module, name, state, service_id,
integration_key, api_key, incident_key)
if not module.check_mode and changed is True:
out = send_event(module, integration_key, event_type, desc,
incident_key, client, client_url)
if module.params.get('api_version') == 'v1':
out, changed = check(module, name, state, service_id,
integration_key, api_key, incident_key)
if not module.check_mode and changed is True:
out = send_event_v1(module, integration_key, event_type, desc,
incident_key, client, client_url)
else:
changed = True
if event_type == 'trigger' and not payload['source']:
module.fail_json(msg='"service" is a required variable for v2 api endpoint.')
out, changed = send_event_v2(
module,
integration_key,
event_type,
payload,
link,
incident_key,
client,
client_url,
)
module.exit_json(result=out, changed=changed)

463
plugins/modules/pnpm.py Normal file
View File

@@ -0,0 +1,463 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2023 Aritra Sen <aretrosen@proton.me>
# Copyright (c) 2017 Chris Hoffman <christopher.hoffman@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
---
module: pnpm
short_description: Manage node.js packages with pnpm
version_added: 7.4.0
description:
- Manage node.js packages with the L(pnpm package manager, https://pnpm.io/).
author:
- "Aritra Sen (@aretrosen)"
- "Chris Hoffman (@chrishoffman), creator of NPM Ansible module"
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
name:
description:
- The name of a node.js library to install.
- All packages in package.json are installed if not provided.
type: str
required: false
alias:
description:
- Alias of the node.js library.
type: str
required: false
path:
description:
- The base path to install the node.js libraries.
type: path
required: false
version:
description:
- The version of the library to be installed, in semver format.
type: str
required: false
global:
description:
- Install the node.js library globally.
required: false
default: false
type: bool
executable:
description:
- The executable location for pnpm.
- The default location it searches for is E(PATH), fails if not set.
type: path
required: false
ignore_scripts:
description:
- Use the C(--ignore-scripts) flag when installing.
required: false
type: bool
default: false
no_optional:
description:
- Do not install optional packages, equivalent to C(--no-optional).
required: false
type: bool
default: false
production:
description:
- Install dependencies in production mode.
- Pnpm will ignore any dependencies under C(devDependencies) in package.json.
required: false
type: bool
default: false
dev:
description:
- Install dependencies in development mode.
- Pnpm will ignore any regular dependencies in C(package.json).
required: false
default: false
type: bool
optional:
description:
- Install dependencies in optional mode.
required: false
default: false
type: bool
state:
description:
- Installation state of the named node.js library.
- If V(absent) is selected, a name option must be provided.
type: str
required: false
default: present
choices: ["present", "absent", "latest"]
requirements:
- Pnpm executable present in E(PATH).
"""
EXAMPLES = """
- name: Install "tailwindcss" node.js package.
community.general.pnpm:
name: tailwindcss
path: /app/location
- name: Install "tailwindcss" node.js package on version 3.3.2
community.general.pnpm:
name: tailwindcss
version: 3.3.2
path: /app/location
- name: Install "tailwindcss" node.js package globally.
community.general.pnpm:
name: tailwindcss
global: true
- name: Install "tailwindcss" node.js package as dev dependency.
community.general.pnpm:
name: tailwindcss
path: /app/location
dev: true
- name: Install "tailwindcss" node.js package as optional dependency.
community.general.pnpm:
name: tailwindcss
path: /app/location
optional: true
- name: Install "tailwindcss" node.js package version 0.1.3 as tailwind-1
community.general.pnpm:
name: tailwindcss
alias: tailwind-1
version: 0.1.3
path: /app/location
- name: Remove the globally-installed package "tailwindcss".
community.general.pnpm:
name: tailwindcss
global: true
state: absent
- name: Install packages based on package.json.
community.general.pnpm:
path: /app/location
- name: Update all packages in package.json to their latest version.
community.general.pnpm:
path: /app/location
state: latest
"""
import json
import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native
class Pnpm(object):
def __init__(self, module, **kwargs):
self.module = module
self.name = kwargs["name"]
self.alias = kwargs["alias"]
self.version = kwargs["version"]
self.path = kwargs["path"]
self.globally = kwargs["globally"]
self.executable = kwargs["executable"]
self.ignore_scripts = kwargs["ignore_scripts"]
self.no_optional = kwargs["no_optional"]
self.production = kwargs["production"]
self.dev = kwargs["dev"]
self.optional = kwargs["optional"]
self.alias_name_ver = None
if self.alias is not None:
self.alias_name_ver = self.alias + "@npm:"
if self.name is not None:
self.alias_name_ver = (self.alias_name_ver or "") + self.name
if self.version is not None:
self.alias_name_ver = self.alias_name_ver + "@" + str(self.version)
def _exec(self, args, run_in_check_mode=False, check_rc=True):
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):
cmd = self.executable + args
if self.globally:
cmd.append("-g")
if self.ignore_scripts:
cmd.append("--ignore-scripts")
if self.no_optional:
cmd.append("--no-optional")
if self.production:
cmd.append("-P")
if self.dev:
cmd.append("-D")
if self.name and self.optional:
cmd.append("-O")
# If path is specified, cd into that path and run the command.
cwd = None
if self.path:
if not os.path.exists(self.path):
os.makedirs(self.path)
if not os.path.isdir(self.path):
self.module.fail_json(msg="Path %s is not a directory" % self.path)
if not self.alias_name_ver and not os.path.isfile(
os.path.join(self.path, "package.json")
):
self.module.fail_json(
msg="package.json does not exist in provided path"
)
cwd = self.path
_rc, out, err = self.module.run_command(cmd, check_rc=check_rc, cwd=cwd)
return out, err
return None, None
def missing(self):
if not os.path.isfile(os.path.join(self.path, "pnpm-lock.yaml")):
return True
cmd = ["list", "--json"]
if self.name is not None:
cmd.append(self.name)
try:
out, err = self._exec(cmd, True, False)
if err is not None and err != "":
raise Exception(out)
data = json.loads(out)
except Exception as e:
self.module.fail_json(
msg="Failed to parse pnpm output with error %s" % to_native(e)
)
if "error" in data:
return True
data = data[0]
for typedep in [
"dependencies",
"devDependencies",
"optionalDependencies",
"unsavedDependencies",
]:
if typedep not in data:
continue
for dep, prop in data[typedep].items():
if self.alias is not None and self.alias != dep:
continue
name = prop["from"] if self.alias is not None else dep
if self.name != name:
continue
if self.version is None or self.version == prop["version"]:
return False
break
return True
def install(self):
if self.alias_name_ver is not None:
return self._exec(["add", self.alias_name_ver])
return self._exec(["install"])
def update(self):
return self._exec(["update", "--latest"])
def uninstall(self):
if self.alias is not None:
return self._exec(["remove", self.alias])
return self._exec(["remove", self.name])
def list_outdated(self):
if not os.path.isfile(os.path.join(self.path, "pnpm-lock.yaml")):
return list()
cmd = ["outdated", "--format", "json"]
try:
out, err = self._exec(cmd, True, False)
# BUG: It will not show correct error sometimes, like when it has
# plain text output intermingled with a {}
if err is not None and err != "":
raise Exception(out)
# HACK: To fix the above bug, the following hack is implemented
data_lines = out.splitlines(True)
out = None
for line in data_lines:
if len(line) > 0 and line[0] == "{":
out = line
continue
if len(line) > 0 and line[0] == "}":
out += line
break
if out is not None:
out += line
data = json.loads(out)
except Exception as e:
self.module.fail_json(
msg="Failed to parse pnpm output with error %s" % to_native(e)
)
return data.keys()
def main():
arg_spec = dict(
name=dict(default=None),
alias=dict(default=None),
path=dict(default=None, type="path"),
version=dict(default=None),
executable=dict(default=None, type="path"),
ignore_scripts=dict(default=False, type="bool"),
no_optional=dict(default=False, type="bool"),
production=dict(default=False, type="bool"),
dev=dict(default=False, type="bool"),
optional=dict(default=False, type="bool"),
state=dict(default="present", choices=["present", "absent", "latest"]),
)
arg_spec["global"] = dict(default=False, type="bool")
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
name = module.params["name"]
alias = module.params["alias"]
path = module.params["path"]
version = module.params["version"]
globally = module.params["global"]
ignore_scripts = module.params["ignore_scripts"]
no_optional = module.params["no_optional"]
production = module.params["production"]
dev = module.params["dev"]
optional = module.params["optional"]
state = module.params["state"]
if module.params["executable"]:
executable = module.params["executable"].split(" ")
else:
executable = [module.get_bin_path("pnpm", True)]
if name is None and version is not None:
module.fail_json(msg="version is meaningless when name is not provided")
if name is None and alias is not None:
module.fail_json(msg="alias is meaningless when name is not provided")
if path is None and not globally:
module.fail_json(msg="path must be specified when not using global")
elif path is not None and globally:
module.fail_json(msg="Cannot specify path when doing global installation")
if globally and (production or dev or optional):
module.fail_json(
msg="Options production, dev, and optional is meaningless when installing packages globally"
)
if name is not None and path is not None and globally:
module.fail_json(msg="path should not be mentioned when installing globally")
if production and dev and optional:
module.fail_json(
msg="Options production and dev and optional don't go together"
)
if production and dev:
module.fail_json(msg="Options production and dev don't go together")
if production and optional:
module.fail_json(msg="Options production and optional don't go together")
if dev and optional:
module.fail_json(msg="Options dev and optional don't go together")
if name is not None and name[0:4] == "http" and version is not None:
module.fail_json(msg="Semver not supported on remote url downloads")
if name is None and optional:
module.fail_json(
msg="Optional not available when package name not provided, use no_optional instead"
)
if state == "absent" and name is None:
module.fail_json(msg="Package name is required for uninstalling")
if state == "latest":
version = "latest"
if globally:
_rc, out, _err = module.run_command(executable + ["root", "-g"], check_rc=True)
path, _tail = os.path.split(out.strip())
pnpm = Pnpm(
module,
name=name,
alias=alias,
path=path,
version=version,
globally=globally,
executable=executable,
ignore_scripts=ignore_scripts,
no_optional=no_optional,
production=production,
dev=dev,
optional=optional,
)
changed = False
out = ""
err = ""
if state == "present":
if pnpm.missing():
changed = True
out, err = pnpm.install()
elif state == "latest":
outdated = pnpm.list_outdated()
if name is not None:
if pnpm.missing() or name in outdated:
changed = True
out, err = pnpm.install()
elif len(outdated):
changed = True
out, err = pnpm.update()
else: # absent
if not pnpm.missing():
changed = True
out, err = pnpm.uninstall()
module.exit_json(changed=changed, out=out, err=err)
if __name__ == "__main__":
main()

View File

@@ -373,7 +373,7 @@ options:
scsi:
description:
- A hash/dictionary of volume used as SCSI hard disk or CD-ROM. O(scsi='{"key":"value", "key":"value"}').
- Keys allowed are - C(sata[n]) where 0 ≤ n ≤ 13.
- Keys allowed are - C(scsi[n]) where 0 ≤ n ≤ 13.
- Values allowed are - C("storage:size,format=value").
- C(storage) is the storage identifier where to create the disk.
- C(size) is the size of the disk in GB.
@@ -415,6 +415,14 @@ options:
smbios:
description:
- Specifies SMBIOS type 1 fields.
- "Comma separated, Base64 encoded (optional) SMBIOS properties:"
- V([base64=<1|0>] [,family=<Base64 encoded string>])
- V([,manufacturer=<Base64 encoded string>])
- V([,product=<Base64 encoded string>])
- V([,serial=<Base64 encoded string>])
- V([,sku=<Base64 encoded string>])
- V([,uuid=<UUID>])
- V([,version=<Base64 encoded string>])
type: str
snapname:
description:
@@ -524,7 +532,7 @@ options:
virtio:
description:
- A hash/dictionary of volume used as VIRTIO hard disk. O(virtio='{"key":"value", "key":"value"}').
- Keys allowed are - C(virto[n]) where 0 ≤ n ≤ 15.
- Keys allowed are - C(virtio[n]) where 0 ≤ n ≤ 15.
- Values allowed are - C("storage:size,format=value").
- C(storage) is the storage identifier where to create the disk.
- C(size) is the size of the disk in GB.

View File

@@ -326,11 +326,13 @@ class Snap(StateModuleHelper):
if x.startswith("warning: no snap found")]))
return process_(rc, out, err)
with self.runner("info name", output_process=process) as ctx:
try:
names = ctx.run(name=snaps)
finally:
self.vars.snapinfo_run_info.append(ctx.run_info)
names = []
if snaps:
with self.runner("info name", output_process=process) as ctx:
try:
names = ctx.run(name=snaps)
finally:
self.vars.snapinfo_run_info.append(ctx.run_info)
return names
def snap_status(self, snap_name, channel):

View File

@@ -21,6 +21,8 @@ from ansible_collections.community.general.plugins.module_utils.cmd_runner impor
def main():
module = AnsibleModule(
argument_spec=dict(
cmd=dict(type="str", default="echo"),
path_prefix=dict(type="str"),
arg_formats=dict(type="dict", default={}),
arg_order=dict(type="raw", required=True),
arg_values=dict(type="dict", default={}),
@@ -41,7 +43,7 @@ def main():
arg_formats[arg] = func(*args)
runner = CmdRunner(module, ['echo', '--'], arg_formats=arg_formats)
runner = CmdRunner(module, [module.params["cmd"], '--'], arg_formats=arg_formats, path_prefix=module.params["path_prefix"])
with runner.context(p['arg_order'], check_mode_skip=p['check_mode_skip']) as ctx:
result = ctx.run(**p['arg_values'])

View File

@@ -0,0 +1,7 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
dependencies:
- setup_remote_tmp_dir

View File

@@ -6,3 +6,4 @@
ansible.builtin.include_tasks:
file: test_cmd_echo.yml
loop: "{{ cmd_echo_tests }}"
when: item.condition | default(true) | bool

View File

@@ -3,17 +3,26 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: test cmd_echo [{{ item.name }}]
cmd_echo:
arg_formats: "{{ item.arg_formats|default(omit) }}"
arg_order: "{{ item.arg_order }}"
arg_values: "{{ item.arg_values|default(omit) }}"
check_mode_skip: "{{ item.check_mode_skip|default(omit) }}"
aa: "{{ item.aa|default(omit) }}"
register: test_result
check_mode: "{{ item.check_mode|default(omit) }}"
ignore_errors: "{{ item.expect_error|default(omit) }}"
- name: create copy of /bin/echo ({{ item.name }})
ansible.builtin.copy:
src: /bin/echo
dest: "{{ item.copy_to }}/echo"
mode: "755"
when: item.copy_to is defined
- name: check results [{{ item.name }}]
- name: test cmd_echo module ({{ item.name }})
cmd_echo:
cmd: "{{ item.cmd | default(omit) }}"
path_prefix: "{{ item.path_prefix | default(omit) }}"
arg_formats: "{{ item.arg_formats | default(omit) }}"
arg_order: "{{ item.arg_order }}"
arg_values: "{{ item.arg_values | default(omit) }}"
check_mode_skip: "{{ item.check_mode_skip | default(omit) }}"
aa: "{{ item.aa | default(omit) }}"
register: test_result
check_mode: "{{ item.check_mode | default(omit) }}"
ignore_errors: "{{ item.expect_error | default(omit) }}"
- name: check results ({{ item.name }})
assert:
that: "{{ item.assertions }}"

View File

@@ -138,3 +138,125 @@ cmd_echo_tests:
- test_result.rc == 0
- test_result.out == "-- --answer=11 --tt-arg potatoes\n"
- test_result.err == ""
- name: use cmd echo
cmd: echo
arg_formats:
aa:
func: as_opt_eq_val
args: [--answer]
tt:
func: as_opt_val
args: [--tt-arg]
arg_order: 'aa tt'
arg_values:
tt: potatoes
aa: 11
assertions:
- test_result.rc == 0
- test_result.out == "-- --answer=11 --tt-arg potatoes\n"
- test_result.err == ""
- name: use cmd /bin/echo
cmd: /bin/echo
arg_formats:
aa:
func: as_opt_eq_val
args: [--answer]
tt:
func: as_opt_val
args: [--tt-arg]
arg_order: 'aa tt'
arg_values:
tt: potatoes
aa: 11
assertions:
- test_result.rc == 0
- test_result.out == "-- --answer=11 --tt-arg potatoes\n"
- test_result.err == ""
# this will not be in the regular set of paths get_bin_path() searches
- name: use cmd {{ remote_tmp_dir }}/echo
condition: >
{{
ansible_distribution != "MacOSX" and
not (ansible_distribution == "CentOS" and ansible_distribution_major_version is version('7.0', '<'))
}}
copy_to: "{{ remote_tmp_dir }}"
cmd: "{{ remote_tmp_dir }}/echo"
arg_formats:
aa:
func: as_opt_eq_val
args: [--answer]
tt:
func: as_opt_val
args: [--tt-arg]
arg_order: 'aa tt'
arg_values:
tt: potatoes
aa: 11
assertions:
- test_result.rc == 0
- test_result.out == "-- --answer=11 --tt-arg potatoes\n"
- test_result.err == ""
- name: use cmd echo with path_prefix {{ remote_tmp_dir }}
cmd: echo
condition: >
{{
ansible_distribution != "MacOSX" and
not (ansible_distribution == "CentOS" and ansible_distribution_major_version is version('7.0', '<'))
}}
copy_to: "{{ remote_tmp_dir }}"
path_prefix: "{{ remote_tmp_dir }}"
arg_formats:
aa:
func: as_opt_eq_val
args: [--answer]
tt:
func: as_opt_val
args: [--tt-arg]
arg_order: 'aa tt'
arg_values:
tt: potatoes
aa: 11
assertions:
- test_result.rc == 0
- test_result.out == "-- --answer=11 --tt-arg potatoes\n"
- test_result.err == ""
- name: use cmd never-existed
cmd: never-existed
arg_formats:
aa:
func: as_opt_eq_val
args: [--answer]
tt:
func: as_opt_val
args: [--tt-arg]
arg_order: 'aa tt'
arg_values:
tt: potatoes
aa: 11
expect_error: true
assertions:
- >
"Failed to find required executable" in test_result.msg
- name: use cmd /usr/bin/never-existed
cmd: /usr/bin/never-existed
arg_formats:
aa:
func: as_opt_eq_val
args: [--answer]
tt:
func: as_opt_val
args: [--tt-arg]
arg_order: 'aa tt'
arg_values:
tt: potatoes
aa: 11
expect_error: true
assertions:
- >
"No such file or directory" in test_result.msg

View File

@@ -0,0 +1,8 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
azp/posix/2
destructive
skip/macos
skip/freebsd

View File

@@ -0,0 +1,9 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
dependencies:
- setup_pkg_mgr
- setup_gnutar
- setup_remote_tmp_dir

View File

@@ -0,0 +1,26 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# test code for the pnpm module
# Copyright (c) 2023 Aritra Sen <aretrosen@proton.me>
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# -------------------------------------------------------------
# Setup steps
- name: Run tests on OSes
include_tasks: run.yml
vars:
ansible_system_os: "{{ ansible_system | lower }}"
nodejs_version: "{{ item.node_version }}"
nodejs_path: "node-v{{ nodejs_version }}-{{ ansible_system_os }}-x{{ ansible_userspace_bits }}"
pnpm_version: "{{ item.pnpm_version }}"
pnpm_path: "pnpm-{{ 'macos' if ansible_system_os == 'darwin' else 'linuxstatic' }}-x{{ ansible_userspace_bits }}"
with_items:
- { node_version: 16.20.0, pnpm_version: 8.7.0 }
when:
- not(ansible_distribution == 'Alpine') and not(ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6')

View File

@@ -0,0 +1,311 @@
---
# Copyright (c) 2023 Aritra Sen <aretrosen@proton.me>
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Download nodejs
ansible.builtin.unarchive:
src: "https://nodejs.org/dist/v{{ nodejs_version }}/{{ nodejs_path }}.tar.gz"
dest: "{{ remote_tmp_dir }}"
remote_src: true
creates: "{{ remote_tmp_dir }}/{{ nodejs_path }}.tar.gz"
- name: Create a temporary directory for pnpm binary
ansible.builtin.tempfile:
state: directory
register: tmp_dir
- name: Download pnpm binary to the temporary directory
ansible.builtin.get_url:
url: "https://github.com/pnpm/pnpm/releases/download/v{{ pnpm_version }}/{{ pnpm_path }}"
dest: "{{ tmp_dir.path }}/pnpm"
mode: "755"
- name: Setting up pnpm via command
ansible.builtin.command: "{{ tmp_dir.path }}/pnpm setup --force"
environment:
PNPM_HOME: "{{ ansible_env.HOME }}/.local/share/pnpm"
SHELL: /bin/sh
ENV: "{{ ansible_env.HOME }}/.shrc"
- name: Remove the temporary directory
ansible.builtin.file:
path: "{{ tmp_dir.path }}"
state: absent
- name: Remove any previous Nodejs modules
ansible.builtin.file:
path: "{{ remote_tmp_dir }}/node_modules"
state: absent
- name: CI tests to run
vars:
node_bin_path: "{{ remote_tmp_dir }}/{{ nodejs_path }}/bin"
pnpm_bin_path: "{{ ansible_env.HOME }}/.local/share/pnpm"
package: "tailwindcss"
environment:
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
block:
- name: Create dummy package.json
ansible.builtin.template:
src: package.j2
dest: "{{ remote_tmp_dir }}/package.json"
mode: "644"
- name: Install reading-time package via package.json
pnpm:
path: "{{ remote_tmp_dir }}"
executable: "{{ pnpm_bin_path }}/pnpm"
state: present
environment:
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
- name: Install the same package from package.json again
pnpm:
path: "{{ remote_tmp_dir }}"
executable: "{{ pnpm_bin_path }}/pnpm"
name: "reading-time"
state: present
environment:
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_install
- name: Assert that result is not changed
ansible.builtin.assert:
that:
- not (pnpm_install is changed)
- name: Install all packages in check mode
pnpm:
path: "{{ remote_tmp_dir }}"
executable: "{{ pnpm_bin_path }}/pnpm"
state: present
environment:
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
check_mode: true
register: pnpm_install_check
- name: Verify test pnpm global installation in check mode
ansible.builtin.assert:
that:
- pnpm_install_check.err is defined
- pnpm_install_check.out is defined
- pnpm_install_check.err is none
- pnpm_install_check.out is none
- name: Install package without dependency
pnpm:
path: "{{ remote_tmp_dir }}"
executable: "{{ pnpm_bin_path }}/pnpm"
state: present
name: "{{ package }}"
environment:
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_install
- name: Assert that result is changed and successful
ansible.builtin.assert:
that:
- pnpm_install is success
- pnpm_install is changed
- name: Reinstall package without dependency
pnpm:
path: "{{ remote_tmp_dir }}"
executable: "{{ pnpm_bin_path }}/pnpm"
state: present
name: "{{ package }}"
environment:
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_reinstall
- name: Assert that there is no change
ansible.builtin.assert:
that:
- pnpm_reinstall is success
- not (pnpm_reinstall is changed)
- name: Manually delete package
ansible.builtin.file:
path: "{{ remote_tmp_dir }}/node_modules/{{ package }}"
state: absent
- name: Reinstall package
pnpm:
path: "{{ remote_tmp_dir }}"
executable: "{{ pnpm_bin_path }}/pnpm"
state: latest
name: "{{ package }}"
environment:
PATH: "{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_fix_install
- name: Assert that result is changed and successful
ansible.builtin.assert:
that:
- pnpm_fix_install is success
- pnpm_fix_install is changed
- name: Install package with version, without executable path
pnpm:
name: "{{ package }}"
version: 0.1.3
path: "{{ remote_tmp_dir }}"
state: present
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_install
- name: Assert that package with version is installed
ansible.builtin.assert:
that:
- pnpm_install is success
- pnpm_install is changed
- name: Reinstall package with version, without explicit executable path
pnpm:
name: "{{ package }}"
version: 0.1.3
path: "{{ remote_tmp_dir }}"
state: present
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_reinstall
- name: Assert that there is no change
ansible.builtin.assert:
that:
- pnpm_reinstall is success
- not (pnpm_reinstall is changed)
- name: Update package, without executable path
pnpm:
name: "{{ package }}"
path: "{{ remote_tmp_dir }}"
state: latest
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_update
- name: Assert that result is changed and successful
ansible.builtin.assert:
that:
- pnpm_update is success
- pnpm_update is changed
- name: Remove package, without executable path
pnpm:
name: "{{ package }}"
path: "{{ remote_tmp_dir }}"
state: absent
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_absent
- name: Assert that result is changed and successful
ansible.builtin.assert:
that:
- pnpm_absent is success
- pnpm_absent is changed
- name: Install package with version and alias, without executable path
pnpm:
name: "{{ package }}"
alias: tailwind-1
version: 0.1.3
path: "{{ remote_tmp_dir }}"
state: present
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_install
- name: Assert that package with version and alias is installed
ansible.builtin.assert:
that:
- pnpm_install is success
- pnpm_install is changed
- name: Reinstall package with version and alias, without explicit executable path
pnpm:
name: "{{ package }}"
alias: tailwind-1
version: 0.1.3
path: "{{ remote_tmp_dir }}"
state: present
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_reinstall
- name: Assert that there is no change
ansible.builtin.assert:
that:
- pnpm_reinstall is success
- not (pnpm_reinstall is changed)
- name: Remove package with alias, without executable path
pnpm:
name: tailwindcss
alias: tailwind-1
path: "{{ remote_tmp_dir }}"
state: absent
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
register: pnpm_absent
- name: Assert that result is changed and successful
ansible.builtin.assert:
that:
- pnpm_absent is success
- pnpm_absent is changed
- name: Install package without dependency globally
pnpm:
name: "{{ package }}"
executable: "{{ pnpm_bin_path }}/pnpm"
state: present
global: true
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
PNPM_HOME: "{{ pnpm_bin_path }}"
register: pnpm_install
- name: Assert that result is changed and successful
ansible.builtin.assert:
that:
- pnpm_install is success
- pnpm_install is changed
- name: Reinstall package globally, without explicit executable path
pnpm:
name: "{{ package }}"
state: present
global: true
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
PNPM_HOME: "{{ pnpm_bin_path }}"
register: pnpm_reinstall
- name: Assert that there is no change
ansible.builtin.assert:
that:
- pnpm_reinstall is success
- not (pnpm_reinstall is changed)
- name: Remove package without dependency globally
pnpm:
name: "{{ package }}"
executable: "{{ pnpm_bin_path }}/pnpm"
global: true
state: absent
environment:
PATH: "{{ pnpm_bin_path }}:{{ node_bin_path }}:{{ ansible_env.PATH }}"
PNPM_HOME: "{{ pnpm_bin_path }}"
register: pnpm_absent
- name: Assert that result is changed and successful
ansible.builtin.assert:
that:
- pnpm_absent is success
- pnpm_absent is changed

View File

@@ -0,0 +1,13 @@
{#
Copyright (c) Ansible Project
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
#}
{
"name": "ansible-pnpm-testing",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"reading-time": "^1.5.0"
}
}

View File

@@ -21,3 +21,8 @@
update_cache: true
upgrade: true
when: archlinux_upgrade_tag is changed
- name: Remove EXTERNALLY-MANAGED file
file:
path: /usr/lib/python{{ ansible_python.version.major }}.{{ ansible_python.version.minor }}/EXTERNALLY-MANAGED
state: absent

View File

@@ -19,3 +19,5 @@
ansible.builtin.include_tasks: test_dangerous.yml
- name: Include test_3dash
ansible.builtin.include_tasks: test_3dash.yml
- name: Include test_empty_list
ansible.builtin.include_tasks: test_empty_list.yml

View File

@@ -0,0 +1,14 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Empty list present
community.general.snap:
name: []
state: present
- name: Empty list absent
community.general.snap:
name: []
state: absent

View File

@@ -0,0 +1,136 @@
# Copyright (c) Ansible project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from collections import namedtuple
from itertools import chain, repeat
import pytest
import yaml
ModuleTestCase = namedtuple("ModuleTestCase", ["id", "input", "output", "run_command_calls", "flags"])
RunCmdCall = namedtuple("RunCmdCall", ["command", "environ", "rc", "out", "err"])
class CmdRunnerTestHelper(object):
def __init__(self, module_main, test_cases):
self.module_main = module_main
self._test_cases = test_cases
if isinstance(test_cases, (list, tuple)):
self.testcases = test_cases
else:
self.testcases = self._make_test_cases()
@property
def cmd_fixture(self):
@pytest.fixture
def patch_bin(mocker):
def mockie(self, path, *args, **kwargs):
return "/testbin/{0}".format(path)
mocker.patch('ansible.module_utils.basic.AnsibleModule.get_bin_path', mockie)
return patch_bin
def _make_test_cases(self):
test_cases = yaml.safe_load(self._test_cases)
results = []
for tc in test_cases:
for tc_param in ["input", "output", "flags"]:
if not tc.get(tc_param):
tc[tc_param] = {}
if tc.get("run_command_calls"):
tc["run_command_calls"] = [RunCmdCall(**r) for r in tc["run_command_calls"]]
else:
tc["run_command_calls"] = []
results.append(ModuleTestCase(**tc))
return results
@property
def testcases_params(self):
return [[x.input, x] for x in self.testcases]
@property
def testcases_ids(self):
return [item.id for item in self.testcases]
def __call__(self, *args, **kwargs):
return _Context(self, *args, **kwargs)
class _Context(object):
def __init__(self, helper, testcase, mocker, capfd):
self.helper = helper
self.testcase = testcase
self.mocker = mocker
self.capfd = capfd
self.run_cmd_calls = self.testcase.run_command_calls
self.mock_run_cmd = self._make_mock_run_cmd()
def _make_mock_run_cmd(self):
call_results = [(x.rc, x.out, x.err) for x in self.run_cmd_calls]
error_call_results = (123,
"OUT: testcase has not enough run_command calls",
"ERR: testcase has not enough run_command calls")
mock_run_command = self.mocker.patch('ansible.module_utils.basic.AnsibleModule.run_command',
side_effect=chain(call_results, repeat(error_call_results)))
return mock_run_command
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return False
def _run(self):
with pytest.raises(SystemExit):
self.helper.module_main()
out, err = self.capfd.readouterr()
results = json.loads(out)
self.check_results(results)
def test_flags(self, flag=None):
flags = self.testcase.flags
if flag:
flags = flags.get(flag)
return flags
def run(self):
func = self._run
test_flags = self.test_flags()
if test_flags.get("skip"):
pytest.skip(reason=test_flags["skip"])
if test_flags.get("xfail"):
pytest.xfail(reason=test_flags["xfail"])
func()
def check_results(self, results):
print("testcase =\n%s" % str(self.testcase))
print("results =\n%s" % results)
if 'exception' in results:
print("exception = \n%s" % results["exception"])
for test_result in self.testcase.output:
assert results[test_result] == self.testcase.output[test_result], \
"'{0}': '{1}' != '{2}'".format(test_result, results[test_result], self.testcase.output[test_result])
call_args_list = [(item[0][0], item[1]) for item in self.mock_run_cmd.call_args_list]
expected_call_args_list = [(item.command, item.environ) for item in self.run_cmd_calls]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert self.mock_run_cmd.call_count == len(self.run_cmd_calls)
if self.mock_run_cmd.call_count:
assert call_args_list == expected_call_args_list

View File

@@ -12,282 +12,26 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible_collections.community.general.plugins.modules import cpanm
import pytest
TESTED_MODULE = cpanm.__name__
from ansible_collections.community.general.plugins.modules import cpanm
from .cmd_runner_test_utils import CmdRunnerTestHelper
@pytest.fixture
def patch_cpanm(mocker):
"""
Function used for mocking some parts of redhat_subscription module
"""
mocker.patch('ansible.module_utils.basic.AnsibleModule.get_bin_path',
return_value='/testbin/cpanm')
TEST_CASES = [
[
{'name': 'Dancer'},
{
'id': 'install_dancer_compatibility',
'run_command.calls': [
(
['/testbin/cpanm', '-le', 'use Dancer;'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
(2, '', 'error, not installed',), # output rc, out, err
),
(
['/testbin/cpanm', 'Dancer'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
),
],
'changed': True,
}
],
[
{'name': 'Dancer'},
{
'id': 'install_dancer_already_installed_compatibility',
'run_command.calls': [
(
['/testbin/cpanm', '-le', 'use Dancer;'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
(0, '', '',), # output rc, out, err
),
],
'changed': False,
}
],
[
{'name': 'Dancer', 'mode': 'new'},
{
'id': 'install_dancer',
'run_command.calls': [(
['/testbin/cpanm', 'Dancer'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'MIYAGAWA/Plack-0.99_05.tar.gz'},
{
'id': 'install_distribution_file_compatibility',
'run_command.calls': [(
['/testbin/cpanm', 'MIYAGAWA/Plack-0.99_05.tar.gz'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'MIYAGAWA/Plack-0.99_05.tar.gz', 'mode': 'new'},
{
'id': 'install_distribution_file',
'run_command.calls': [(
['/testbin/cpanm', 'MIYAGAWA/Plack-0.99_05.tar.gz'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'Dancer', 'locallib': '/srv/webapps/my_app/extlib', 'mode': 'new'},
{
'id': 'install_into_locallib',
'run_command.calls': [(
['/testbin/cpanm', '--local-lib', '/srv/webapps/my_app/extlib', 'Dancer'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'from_path': '/srv/webapps/my_app/src/', 'mode': 'new'},
{
'id': 'install_from_local_directory',
'run_command.calls': [(
['/testbin/cpanm', '/srv/webapps/my_app/src/'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'Dancer', 'locallib': '/srv/webapps/my_app/extlib', 'notest': True, 'mode': 'new'},
{
'id': 'install_into_locallib_no_unit_testing',
'run_command.calls': [(
['/testbin/cpanm', '--notest', '--local-lib', '/srv/webapps/my_app/extlib', 'Dancer'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'Dancer', 'mirror': 'http://cpan.cpantesters.org/', 'mode': 'new'},
{
'id': 'install_from_mirror',
'run_command.calls': [(
['/testbin/cpanm', '--mirror', 'http://cpan.cpantesters.org/', 'Dancer'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'Dancer', 'system_lib': True, 'mode': 'new'},
{
'id': 'install_into_system_lib',
'run_command.calls': [],
'changed': False,
'failed': True,
}
],
[
{'name': 'Dancer', 'version': '1.0', 'mode': 'new'},
{
'id': 'install_minversion_implicit',
'run_command.calls': [(
['/testbin/cpanm', 'Dancer~1.0'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'Dancer', 'version': '~1.5', 'mode': 'new'},
{
'id': 'install_minversion_explicit',
'run_command.calls': [(
['/testbin/cpanm', 'Dancer~1.5'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
}
],
[
{'name': 'Dancer', 'version': '@1.7', 'mode': 'new'},
{
'id': 'install_specific_version',
'run_command.calls': [(
['/testbin/cpanm', 'Dancer@1.7'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
'failed': False,
}
],
[
{'name': 'MIYAGAWA/Plack-0.99_05.tar.gz', 'version': '@1.7', 'mode': 'new'},
{
'id': 'install_specific_version_from_file_error',
'run_command.calls': [],
'changed': False,
'failed': True,
'msg': "parameter 'version' must not be used when installing from a file",
}
],
[
{'from_path': '~/', 'version': '@1.7', 'mode': 'new'},
{
'id': 'install_specific_version_from_directory_error',
'run_command.calls': [],
'changed': False,
'failed': True,
'msg': "parameter 'version' must not be used when installing from a directory",
}
],
[
{'name': 'git://github.com/plack/Plack.git', 'version': '@1.7', 'mode': 'new'},
{
'id': 'install_specific_version_from_git_url_explicit',
'run_command.calls': [(
['/testbin/cpanm', 'git://github.com/plack/Plack.git@1.7'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
'failed': False,
}
],
[
{'name': 'git://github.com/plack/Plack.git', 'version': '2.5', 'mode': 'new'},
{
'id': 'install_specific_version_from_git_url_implicit',
'run_command.calls': [(
['/testbin/cpanm', 'git://github.com/plack/Plack.git@2.5'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',), # output rc, out, err
)],
'changed': True,
'failed': False,
}
],
[
{'name': 'git://github.com/plack/Plack.git', 'version': '~2.5', 'mode': 'new'},
{
'id': 'install_version_operator_from_git_url_error',
'run_command.calls': [],
'changed': False,
'failed': True,
'msg': "operator '~' not allowed in version parameter when installing from git repository",
}
],
]
TEST_CASES_IDS = [item[1]['id'] for item in TEST_CASES]
with open("tests/unit/plugins/modules/test_cpanm.yaml", "r") as TEST_CASES:
helper = CmdRunnerTestHelper(cpanm.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.mark.parametrize('patch_ansible_module, testcase',
TEST_CASES,
ids=TEST_CASES_IDS,
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_cpanm(mocker, capfd, patch_cpanm, testcase):
def test_module(mocker, capfd, patch_bin, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
# Mock function used for running commands first
call_results = [item[2] for item in testcase['run_command.calls']]
mock_run_command = mocker.patch(
'ansible_collections.community.general.plugins.module_utils.module_helper.AnsibleModule.run_command',
side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
cpanm.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("results =\n%s" % results)
assert mock_run_command.call_count == len(testcase['run_command.calls'])
if mock_run_command.call_count:
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item[0], item[1]) for item in testcase['run_command.calls']]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert call_args_list == expected_call_args_list
assert results.get('changed', False) == testcase['changed']
if 'failed' in testcase:
assert results.get('failed', False) == testcase['failed']
if 'msg' in testcase:
assert results.get('msg', '') == testcase['msg']
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alexei Znamensky (russoz@gmail.com)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
---
- id: install_dancer_compatibility
input:
name: Dancer
output:
changed: true
run_command_calls:
- command: [/testbin/perl, -le, 'use Dancer;']
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 2
out: ""
err: "error, not installed"
- command: [/testbin/cpanm, Dancer]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_dancer_already_installed_compatibility
input:
name: Dancer
output:
changed: false
run_command_calls:
- command: [/testbin/perl, -le, 'use Dancer;']
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: install_dancer
input:
name: Dancer
mode: new
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, Dancer]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_distribution_file_compatibility
input:
name: MIYAGAWA/Plack-0.99_05.tar.gz
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, MIYAGAWA/Plack-0.99_05.tar.gz]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_distribution_file
input:
name: MIYAGAWA/Plack-0.99_05.tar.gz
mode: new
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, MIYAGAWA/Plack-0.99_05.tar.gz]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_into_locallib
input:
name: Dancer
mode: new
locallib: /srv/webapps/my_app/extlib
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, --local-lib, /srv/webapps/my_app/extlib, Dancer]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_from_local_directory
input:
from_path: /srv/webapps/my_app/src/
mode: new
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, /srv/webapps/my_app/src/]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_into_locallib_no_unit_testing
input:
name: Dancer
notest: true
mode: new
locallib: /srv/webapps/my_app/extlib
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, --notest, --local-lib, /srv/webapps/my_app/extlib, Dancer]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_from_mirror
input:
name: Dancer
mode: new
mirror: "http://cpan.cpantesters.org/"
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, --mirror, "http://cpan.cpantesters.org/", Dancer]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_into_system_lib
input:
name: Dancer
mode: new
system_lib: true
output:
failed: true
run_command_calls: []
- id: install_minversion_implicit
input:
name: Dancer
mode: new
version: "1.0"
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, Dancer~1.0]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_minversion_explicit
input:
name: Dancer
mode: new
version: "~1.5"
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, Dancer~1.5]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_specific_version
input:
name: Dancer
mode: new
version: "@1.7"
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, Dancer@1.7]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_specific_version_from_file_error
input:
name: MIYAGAWA/Plack-0.99_05.tar.gz
mode: new
version: "@1.7"
output:
failed: true
msg: parameter 'version' must not be used when installing from a file
run_command_calls: []
- id: install_specific_version_from_directory_error
input:
from_path: ~/
mode: new
version: "@1.7"
output:
failed: true
msg: parameter 'version' must not be used when installing from a directory
run_command_calls: []
- id: install_specific_version_from_git_url_explicit
input:
name: "git://github.com/plack/Plack.git"
mode: new
version: "@1.7"
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, "git://github.com/plack/Plack.git@1.7"]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_specific_version_from_git_url_implicit
input:
name: "git://github.com/plack/Plack.git"
mode: new
version: "2.5"
output:
changed: true
run_command_calls:
- command: [/testbin/cpanm, "git://github.com/plack/Plack.git@2.5"]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: install_version_operator_from_git_url_error
input:
name: "git://github.com/plack/Plack.git"
mode: new
version: "~2.5"
output:
failed: true
msg: operator '~' not allowed in version parameter when installing from git repository
run_command_calls: []

View File

@@ -6,208 +6,26 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible_collections.community.general.plugins.modules import gconftool2
import pytest
TESTED_MODULE = gconftool2.__name__
from ansible_collections.community.general.plugins.modules import gconftool2 as module
from .cmd_runner_test_utils import CmdRunnerTestHelper
@pytest.fixture
def patch_gconftool2(mocker):
"""
Function used for mocking some parts of redhat_subscription module
"""
mocker.patch('ansible_collections.community.general.plugins.module_utils.mh.module_helper.AnsibleModule.get_bin_path',
return_value='/testbin/gconftool-2')
TEST_CASES = [
[
{'state': 'get', 'key': '/desktop/gnome/background/picture_filename'},
{
'id': 'test_simple_element_get',
'run_command.calls': [
(
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '100\n', '',),
),
],
}
],
[
{'state': 'get', 'key': '/desktop/gnome/background/picture_filename'},
{
'id': 'test_simple_element_get_not_found',
'run_command.calls': [
(
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', "No value set for `/desktop/gnome/background/picture_filename'\n",),
),
],
}
],
[
{'state': 'present', 'key': '/desktop/gnome/background/picture_filename', 'value': 200, 'value_type': 'int'},
{
'id': 'test_simple_element_set',
'run_command.calls': [
(
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '100\n', '',),
),
(
['/testbin/gconftool-2', '--type', 'int', '--set', '/desktop/gnome/background/picture_filename', '200'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',),
),
(
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '200\n', '',),
),
],
'new_value': '200',
'changed': True,
}
],
[
{'state': 'present', 'key': '/desktop/gnome/background/picture_filename', 'value': 200, 'value_type': 'int'},
{
'id': 'test_simple_element_set_idempotency_int',
'run_command.calls': [
(
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '200\n', '',),
),
(
['/testbin/gconftool-2', '--type', 'int', '--set', '/desktop/gnome/background/picture_filename', '200'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',),
),
(
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '200\n', '',),
),
],
'new_value': '200',
'changed': False,
}
],
[
{'state': 'present', 'key': '/apps/gnome_settings_daemon/screensaver/start_screensaver', 'value': 'false', 'value_type': 'bool'},
{
'id': 'test_simple_element_set_idempotency_bool',
'run_command.calls': [
(
['/testbin/gconftool-2', '--get', '/apps/gnome_settings_daemon/screensaver/start_screensaver'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, 'false\n', '',),
),
(
['/testbin/gconftool-2', '--type', 'bool', '--set', '/apps/gnome_settings_daemon/screensaver/start_screensaver', 'false'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',),
),
(
['/testbin/gconftool-2', '--get', '/apps/gnome_settings_daemon/screensaver/start_screensaver'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, 'false\n', '',),
),
],
'new_value': 'false',
'changed': False,
}
],
[
{'state': 'absent', 'key': '/desktop/gnome/background/picture_filename'},
{
'id': 'test_simple_element_unset',
'run_command.calls': [
(
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '200\n', '',),
),
(
['/testbin/gconftool-2', '--unset', '/desktop/gnome/background/picture_filename'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',),
),
],
'changed': True,
}
],
[
{'state': 'absent', 'key': '/apps/gnome_settings_daemon/screensaver/start_screensaver'},
{
'id': 'test_simple_element_unset_idempotency',
'run_command.calls': [
(
['/testbin/gconftool-2', '--get', '/apps/gnome_settings_daemon/screensaver/start_screensaver'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',),
),
(
['/testbin/gconftool-2', '--unset', '/apps/gnome_settings_daemon/screensaver/start_screensaver'],
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
(0, '', '',),
),
],
'changed': False,
}
],
]
TEST_CASES_IDS = [item[1]['id'] for item in TEST_CASES]
with open("tests/unit/plugins/modules/test_gconftool2.yaml", "r") as TEST_CASES:
helper = CmdRunnerTestHelper(module.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.mark.parametrize('patch_ansible_module, testcase',
TEST_CASES,
ids=TEST_CASES_IDS,
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_gconftool2(mocker, capfd, patch_gconftool2, testcase):
def test_module(mocker, capfd, patch_bin, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
# Mock function used for running commands first
call_results = [item[2] for item in testcase['run_command.calls']]
mock_run_command = mocker.patch(
'ansible_collections.community.general.plugins.module_utils.mh.module_helper.AnsibleModule.run_command',
side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
gconftool2.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("testcase =\n%s" % testcase)
print("results =\n%s" % results)
if 'changed' in testcase:
assert results.get('changed', False) == testcase['changed']
if 'new_value' in testcase:
assert results.get('new_value', None) == testcase['new_value']
for conditional_test_result in ('value',):
if conditional_test_result in testcase:
assert conditional_test_result in results, "'{0}' not found in {1}".format(conditional_test_result, results)
assert results[conditional_test_result] == testcase[conditional_test_result], \
"'{0}': '{1}' != '{2}'".format(conditional_test_result, results[conditional_test_result], testcase[conditional_test_result])
assert mock_run_command.call_count == len(testcase['run_command.calls'])
if mock_run_command.call_count:
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item[0], item[1]) for item in testcase['run_command.calls']]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert call_args_list == expected_call_args_list
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alexei Znamensky (russoz@gmail.com)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
---
- id: test_simple_element_get
input:
state: get
key: /desktop/gnome/background/picture_filename
output: {}
run_command_calls:
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "100\n"
err: ""
- id: test_simple_element_get_not_found
input:
state: get
key: /desktop/gnome/background/picture_filename
output: {}
run_command_calls:
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: "No value set for `/desktop/gnome/background/picture_filename'\n"
- id: test_simple_element_set
input:
state: present
key: /desktop/gnome/background/picture_filename
value: 200
value_type: int
output:
new_value: '200'
changed: true
run_command_calls:
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "100\n"
err: ""
- command: [/testbin/gconftool-2, --type, int, --set, /desktop/gnome/background/picture_filename, "200"]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "200\n"
err: ""
- id: test_simple_element_set_idempotency_int
input:
state: present
key: /desktop/gnome/background/picture_filename
value: 200
value_type: int
output:
new_value: '200'
changed: false
run_command_calls:
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "200\n"
err: ""
- command: [/testbin/gconftool-2, --type, int, --set, /desktop/gnome/background/picture_filename, "200"]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "200\n"
err: ""
- id: test_simple_element_set_idempotency_bool
input:
state: present
key: /apps/gnome_settings_daemon/screensaver/start_screensaver
value: false
value_type: bool
output:
new_value: 'false'
changed: false
run_command_calls:
- command: [/testbin/gconftool-2, --get, /apps/gnome_settings_daemon/screensaver/start_screensaver]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "false\n"
err: ""
- command: [/testbin/gconftool-2, --type, bool, --set, /apps/gnome_settings_daemon/screensaver/start_screensaver, "False"]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- command: [/testbin/gconftool-2, --get, /apps/gnome_settings_daemon/screensaver/start_screensaver]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "false\n"
err: ""
- id: test_simple_element_unset
input:
state: absent
key: /desktop/gnome/background/picture_filename
output:
new_value: null
changed: true
run_command_calls:
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "200\n"
err: ""
- command: [/testbin/gconftool-2, --unset, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- id: test_simple_element_unset_idempotency
input:
state: absent
key: /apps/gnome_settings_daemon/screensaver/start_screensaver
output:
new_value: null
changed: false
run_command_calls:
- command: [/testbin/gconftool-2, --get, /apps/gnome_settings_daemon/screensaver/start_screensaver]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""
- command: [/testbin/gconftool-2, --unset, /apps/gnome_settings_daemon/screensaver/start_screensaver]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: ""

View File

@@ -6,98 +6,26 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible_collections.community.general.plugins.modules import gconftool2_info
import pytest
TESTED_MODULE = gconftool2_info.__name__
from ansible_collections.community.general.plugins.modules import gconftool2_info
from .cmd_runner_test_utils import CmdRunnerTestHelper
@pytest.fixture
def patch_gconftool2_info(mocker):
"""
Function used for mocking some parts of redhat_subscription module
"""
mocker.patch('ansible_collections.community.general.plugins.module_utils.mh.module_helper.AnsibleModule.get_bin_path',
return_value='/testbin/gconftool-2')
TEST_CASES = [
[
{'key': '/desktop/gnome/background/picture_filename'},
{
'id': 'test_simple_element_get',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
# Mock of returned code, stdout and stderr
(0, '100\n', '',),
),
],
'value': '100',
}
],
[
{'key': '/desktop/gnome/background/picture_filename'},
{
'id': 'test_simple_element_get_not_found',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/gconftool-2', '--get', '/desktop/gnome/background/picture_filename'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
# Mock of returned code, stdout and stderr
(0, '', "No value set for `/desktop/gnome/background/picture_filename'\n",),
),
],
'value': None,
}
],
]
TEST_CASES_IDS = [item[1]['id'] for item in TEST_CASES]
with open("tests/unit/plugins/modules/test_gconftool2_info.yaml", "r") as TEST_CASES:
helper = CmdRunnerTestHelper(gconftool2_info.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.mark.parametrize('patch_ansible_module, testcase',
TEST_CASES,
ids=TEST_CASES_IDS,
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_gconftool2_info(mocker, capfd, patch_gconftool2_info, testcase):
def test_module(mocker, capfd, patch_bin, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
# Mock function used for running commands first
call_results = [item[2] for item in testcase['run_command.calls']]
mock_run_command = mocker.patch(
'ansible_collections.community.general.plugins.module_utils.mh.module_helper.AnsibleModule.run_command',
side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
gconftool2_info.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("testcase =\n%s" % testcase)
print("results =\n%s" % results)
for conditional_test_result in ('value',):
if conditional_test_result in testcase:
assert conditional_test_result in results, "'{0}' not found in {1}".format(conditional_test_result, results)
assert results[conditional_test_result] == testcase[conditional_test_result], \
"'{0}': '{1}' != '{2}'".format(conditional_test_result, results[conditional_test_result], testcase[conditional_test_result])
assert mock_run_command.call_count == len(testcase['run_command.calls'])
if mock_run_command.call_count:
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item[0], item[1]) for item in testcase['run_command.calls']]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert call_args_list == expected_call_args_list
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alexei Znamensky (russoz@gmail.com)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
---
- id: test_simple_element_get
input:
key: /desktop/gnome/background/picture_filename
output:
value: '100'
run_command_calls:
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "100\n"
err: ""
- id: test_simple_element_get_not_found
input:
key: /desktop/gnome/background/picture_filename
output:
value: null
run_command_calls:
- command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: ""
err: "No value set for `/desktop/gnome/background/picture_filename'\n"

View File

@@ -75,6 +75,11 @@ class JenkinsMock():
def get_build_info(self, name, build_number):
if name == "host-delete":
raise jenkins.JenkinsException("job {0} number {1} does not exist".format(name, build_number))
elif name == "create-detached":
return {
"building": True,
"result": None
}
return {
"building": True,
"result": "SUCCESS"
@@ -222,3 +227,38 @@ class TestJenkinsBuild(unittest.TestCase):
"token": "xyz"
})
jenkins_build.main()
@patch('ansible_collections.community.general.plugins.modules.jenkins_build.test_dependencies')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build.JenkinsBuild.get_jenkins_connection')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build.JenkinsBuild.get_build_status')
def test_module_create_build_without_detach(self, build_status, jenkins_connection, test_deps):
test_deps.return_value = None
jenkins_connection.return_value = JenkinsMock()
build_status.return_value = JenkinsBuildMock().get_build_status()
with self.assertRaises(AnsibleExitJson) as return_json:
set_module_args({
"name": "create-detached",
"user": "abc",
"token": "xyz"
})
jenkins_build.main()
self.assertFalse(return_json.exception.args[0]['changed'])
@patch('ansible_collections.community.general.plugins.modules.jenkins_build.test_dependencies')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build.JenkinsBuild.get_jenkins_connection')
def test_module_create_build_detached(self, jenkins_connection, test_deps):
test_deps.return_value = None
jenkins_connection.return_value = JenkinsMock()
with self.assertRaises(AnsibleExitJson) as return_json:
set_module_args({
"name": "create-detached",
"user": "abc",
"token": "xyz",
"detach": True
})
jenkins_build.main()
self.assertTrue(return_json.exception.args[0]['changed'])

View File

@@ -0,0 +1,180 @@
# Copyright (c) Ansible project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible_collections.community.general.tests.unit.compat import unittest
from ansible_collections.community.general.tests.unit.compat.mock import patch
from ansible.module_utils import basic
from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.general.plugins.modules import jenkins_build_info
import json
def set_module_args(args):
"""prepare arguments so that they will be picked up during module creation"""
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
basic._ANSIBLE_ARGS = to_bytes(args)
class AnsibleExitJson(Exception):
"""Exception class to be raised by module.exit_json and caught by the test case"""
pass
class AnsibleFailJson(Exception):
"""Exception class to be raised by module.fail_json and caught by the test case"""
pass
def exit_json(*args, **kwargs):
"""function to patch over exit_json; package return data into an exception"""
if 'changed' not in kwargs:
kwargs['changed'] = False
raise AnsibleExitJson(kwargs)
def fail_json(*args, **kwargs):
"""function to patch over fail_json; package return data into an exception"""
kwargs['failed'] = True
raise AnsibleFailJson(kwargs)
class jenkins:
class JenkinsException(Exception):
pass
class JenkinsBuildMock():
def __init__(self, name, build_number=None):
self.name = name
self.build_number = build_number
def get_build_status(self):
try:
instance = JenkinsMock()
response = JenkinsMock.get_build_info(instance, self.name, self.build_number)
return response
except jenkins.JenkinsException:
response = {}
response["result"] = "ABSENT"
return response
except Exception as e:
fail_json(msg='Unable to fetch build information, {0}'.format(e))
class JenkinsMock():
def get_build_info(self, name, build_number):
if name == "job-absent":
raise jenkins.JenkinsException()
return {
"result": "SUCCESS",
"build_info": {}
}
def get_job_info(self, name):
if name == "job-absent":
raise jenkins.JenkinsException()
return {
"lastBuild": {
"number": 123
}
}
class TestJenkinsBuildInfo(unittest.TestCase):
def setUp(self):
self.mock_module_helper = patch.multiple(basic.AnsibleModule,
exit_json=exit_json,
fail_json=fail_json)
self.mock_module_helper.start()
self.addCleanup(self.mock_module_helper.stop)
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.test_dependencies')
def test_module_fail_when_required_args_missing(self, test_deps):
test_deps.return_value = None
with self.assertRaises(AnsibleFailJson):
set_module_args({})
jenkins_build_info.main()
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.test_dependencies')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.JenkinsBuildInfo.get_jenkins_connection')
def test_module_get_build_info(self, jenkins_connection, test_deps):
test_deps.return_value = None
jenkins_connection.return_value = JenkinsMock()
with self.assertRaises(AnsibleExitJson) as return_json:
set_module_args({
"name": "job-present",
"user": "abc",
"token": "xyz",
"build_number": 30
})
jenkins_build_info.main()
self.assertFalse(return_json.exception.args[0]["changed"])
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.test_dependencies')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.JenkinsBuildInfo.get_jenkins_connection')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.JenkinsBuildInfo.get_build_status')
def test_module_get_build_info_if_build_does_not_exist(self, build_status, jenkins_connection, test_deps):
test_deps.return_value = None
jenkins_connection.return_value = JenkinsMock()
build_status.return_value = JenkinsBuildMock("job-absent", 30).get_build_status()
with self.assertRaises(AnsibleExitJson) as return_json:
set_module_args({
"name": "job-absent",
"user": "abc",
"token": "xyz",
"build_number": 30
})
jenkins_build_info.main()
self.assertFalse(return_json.exception.args[0]['changed'])
self.assertTrue(return_json.exception.args[0]['failed'])
self.assertEquals("ABSENT", return_json.exception.args[0]['build_info']['result'])
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.test_dependencies')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.JenkinsBuildInfo.get_jenkins_connection')
def test_module_get_build_info_get_last_build(self, jenkins_connection, test_deps):
test_deps.return_value = None
jenkins_connection.return_value = JenkinsMock()
with self.assertRaises(AnsibleExitJson) as return_json:
set_module_args({
"name": "job-present",
"user": "abc",
"token": "xyz"
})
jenkins_build_info.main()
self.assertFalse(return_json.exception.args[0]['changed'])
self.assertEquals("SUCCESS", return_json.exception.args[0]['build_info']['result'])
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.test_dependencies')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.JenkinsBuildInfo.get_jenkins_connection')
@patch('ansible_collections.community.general.plugins.modules.jenkins_build_info.JenkinsBuildInfo.get_build_status')
def test_module_get_build_info_if_job_does_not_exist(self, build_status, jenkins_connection, test_deps):
test_deps.return_value = None
jenkins_connection.return_value = JenkinsMock()
build_status.return_value = JenkinsBuildMock("job-absent").get_build_status()
with self.assertRaises(AnsibleExitJson) as return_json:
set_module_args({
"name": "job-absent",
"user": "abc",
"token": "xyz"
})
jenkins_build_info.main()
self.assertFalse(return_json.exception.args[0]['changed'])
self.assertTrue(return_json.exception.args[0]['failed'])
self.assertEquals("ABSENT", return_json.exception.args[0]['build_info']['result'])

View File

@@ -6,236 +6,26 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from collections import namedtuple
from ansible_collections.community.general.plugins.modules import opkg
import pytest
TESTED_MODULE = opkg.__name__
from ansible_collections.community.general.plugins.modules import opkg as module
from .cmd_runner_test_utils import CmdRunnerTestHelper
ModuleTestCase = namedtuple("ModuleTestCase", ["id", "input", "output", "run_command_calls"])
RunCmdCall = namedtuple("RunCmdCall", ["command", "environ", "rc", "out", "err"])
@pytest.fixture
def patch_opkg(mocker):
mocker.patch('ansible.module_utils.basic.AnsibleModule.get_bin_path', return_value='/testbin/opkg')
TEST_CASES = [
ModuleTestCase(
id="install_zlibdev",
input={"name": "zlib-dev", "state": "present"},
output={
"msg": "installed 1 package(s)"
},
run_command_calls=[
RunCmdCall(
command=["/testbin/opkg", "list-installed", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
RunCmdCall(
command=["/testbin/opkg", "install", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out=(
"Installing zlib-dev (1.2.11-6) to root..."
"Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib-dev_1.2.11-6_mips_24kc.ipk"
"Installing zlib (1.2.11-6) to root..."
"Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib_1.2.11-6_mips_24kc.ipk"
"Configuring zlib."
"Configuring zlib-dev."
),
err="",
),
RunCmdCall(
command=["/testbin/opkg", "list-installed", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="zlib-dev - 1.2.11-6\n",
err="",
),
],
),
ModuleTestCase(
id="install_zlibdev_present",
input={"name": "zlib-dev", "state": "present"},
output={
"msg": "package(s) already present"
},
run_command_calls=[
RunCmdCall(
command=["/testbin/opkg", "list-installed", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="zlib-dev - 1.2.11-6\n",
err="",
),
],
),
ModuleTestCase(
id="install_zlibdev_force_reinstall",
input={"name": "zlib-dev", "state": "present", "force": "reinstall"},
output={
"msg": "installed 1 package(s)"
},
run_command_calls=[
RunCmdCall(
command=["/testbin/opkg", "list-installed", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="zlib-dev - 1.2.11-6\n",
err="",
),
RunCmdCall(
command=["/testbin/opkg", "install", "--force-reinstall", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out=(
"Installing zlib-dev (1.2.11-6) to root...\n"
"Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib-dev_1.2.11-6_mips_24kc.ipk\n"
"Configuring zlib-dev.\n"
),
err="",
),
RunCmdCall(
command=["/testbin/opkg", "list-installed", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="zlib-dev - 1.2.11-6\n",
err="",
),
],
),
ModuleTestCase(
id="install_zlibdev_with_version",
input={"name": "zlib-dev=1.2.11-6", "state": "present"},
output={
"msg": "installed 1 package(s)"
},
run_command_calls=[
RunCmdCall(
command=["/testbin/opkg", "list-installed", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
RunCmdCall(
command=["/testbin/opkg", "install", "zlib-dev=1.2.11-6"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out=(
"Installing zlib-dev (1.2.11-6) to root..."
"Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib-dev_1.2.11-6_mips_24kc.ipk"
"Installing zlib (1.2.11-6) to root..."
"Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib_1.2.11-6_mips_24kc.ipk"
"Configuring zlib."
"Configuring zlib-dev."
),
err="",
),
RunCmdCall(
command=["/testbin/opkg", "list-installed", "zlib-dev"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="zlib-dev - 1.2.11-6 \n", # This output has the extra space at the end, to satisfy the behaviour of Yocto/OpenEmbedded's opkg
err="",
),
],
),
ModuleTestCase(
id="install_vim_updatecache",
input={"name": "vim-fuller", "state": "present", "update_cache": True},
output={
"msg": "installed 1 package(s)"
},
run_command_calls=[
RunCmdCall(
command=["/testbin/opkg", "update"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
RunCmdCall(
command=["/testbin/opkg", "list-installed", "vim-fuller"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
RunCmdCall(
command=["/testbin/opkg", "install", "vim-fuller"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out=(
"Multiple packages (libgcc1 and libgcc1) providing same name marked HOLD or PREFER. Using latest.\n"
"Installing vim-fuller (9.0-1) to root...\n"
"Downloading https://downloads.openwrt.org/snapshots/packages/x86_64/packages/vim-fuller_9.0-1_x86_64.ipk\n"
"Installing terminfo (6.4-2) to root...\n"
"Downloading https://downloads.openwrt.org/snapshots/packages/x86_64/base/terminfo_6.4-2_x86_64.ipk\n"
"Installing libncurses6 (6.4-2) to root...\n"
"Downloading https://downloads.openwrt.org/snapshots/packages/x86_64/base/libncurses6_6.4-2_x86_64.ipk\n"
"Configuring terminfo.\n"
"Configuring libncurses6.\n"
"Configuring vim-fuller.\n"
),
err="",
),
RunCmdCall(
command=["/testbin/opkg", "list-installed", "vim-fuller"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="vim-fuller - 9.0-1 \n", # This output has the extra space at the end, to satisfy the behaviour of Yocto/OpenEmbedded's opkg
err="",
),
],
),
]
TEST_CASES_IDS = [item.id for item in TEST_CASES]
with open("tests/unit/plugins/modules/test_opkg.yaml", "r") as TEST_CASES:
helper = CmdRunnerTestHelper(module.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.mark.parametrize('patch_ansible_module, testcase',
[[x.input, x] for x in TEST_CASES],
ids=TEST_CASES_IDS,
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_opkg(mocker, capfd, patch_opkg, testcase):
def test_module(mocker, capfd, patch_bin, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
run_cmd_calls = testcase.run_command_calls
# Mock function used for running commands first
call_results = [(x.rc, x.out, x.err) for x in run_cmd_calls]
mock_run_command = mocker.patch('ansible.module_utils.basic.AnsibleModule.run_command', side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
opkg.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("testcase =\n%s" % str(testcase))
print("results =\n%s" % results)
for test_result in testcase.output:
assert results[test_result] == testcase.output[test_result], \
"'{0}': '{1}' != '{2}'".format(test_result, results[test_result], testcase.output[test_result])
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item.command, item.environ) for item in run_cmd_calls]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert mock_run_command.call_count == len(run_cmd_calls)
if mock_run_command.call_count:
assert call_args_list == expected_call_args_list
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alexei Znamensky (russoz@gmail.com)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
---
- id: install_zlibdev
input:
name: zlib-dev
state: present
output:
msg: installed 1 package(s)
run_command_calls:
- command: [/testbin/opkg, list-installed, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- command: [/testbin/opkg, install, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
Installing zlib-dev (1.2.11-6) to root...
Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib-dev_1.2.11-6_mips_24kc.ipk
Installing zlib (1.2.11-6) to root...
Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib_1.2.11-6_mips_24kc.ipk
Configuring zlib.
Configuring zlib-dev.
err: ""
- command: [/testbin/opkg, list-installed, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
zlib-dev - 1.2.11-6
err: ""
- id: install_zlibdev_present
input:
name: zlib-dev
state: present
output:
msg: package(s) already present
run_command_calls:
- command: [/testbin/opkg, list-installed, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
zlib-dev - 1.2.11-6
err: ""
- id: install_zlibdev_force_reinstall
input:
name: zlib-dev
state: present
force: reinstall
output:
msg: installed 1 package(s)
run_command_calls:
- command: [/testbin/opkg, list-installed, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
zlib-dev - 1.2.11-6
err: ""
- command: [/testbin/opkg, install, --force-reinstall, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
Installing zlib-dev (1.2.11-6) to root...
Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib-dev_1.2.11-6_mips_24kc.ipk
Configuring zlib-dev.
err: ""
- command: [/testbin/opkg, list-installed, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
zlib-dev - 1.2.11-6
err: ""
- id: install_zlibdev_with_version
input:
name: zlib-dev=1.2.11-6
state: present
output:
msg: installed 1 package(s)
run_command_calls:
- command: [/testbin/opkg, list-installed, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- command: [/testbin/opkg, install, zlib-dev=1.2.11-6]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
Installing zlib-dev (1.2.11-6) to root...
Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib-dev_1.2.11-6_mips_24kc.ipk
Installing zlib (1.2.11-6) to root...
Downloading https://downloads.openwrt.org/releases/22.03.0/packages/mips_24kc/base/zlib_1.2.11-6_mips_24kc.ipk
Configuring zlib.
Configuring zlib-dev.
err: ""
- command: [/testbin/opkg, list-installed, zlib-dev]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "zlib-dev - 1.2.11-6 \n" # This output has the extra space at the end, to satisfy the behaviour of Yocto/OpenEmbedded's opkg
err: ""
- id: install_vim_updatecache
input:
name: vim-fuller
state: present
update_cache: true
output:
msg: installed 1 package(s)
run_command_calls:
- command: [/testbin/opkg, update]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- command: [/testbin/opkg, list-installed, vim-fuller]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- command: [/testbin/opkg, install, vim-fuller]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: |
Multiple packages (libgcc1 and libgcc1) providing same name marked HOLD or PREFER. Using latest.
Installing vim-fuller (9.0-1) to root...
Downloading https://downloads.openwrt.org/snapshots/packages/x86_64/packages/vim-fuller_9.0-1_x86_64.ipk
Installing terminfo (6.4-2) to root...
Downloading https://downloads.openwrt.org/snapshots/packages/x86_64/base/terminfo_6.4-2_x86_64.ipk
Installing libncurses6 (6.4-2) to root...
Downloading https://downloads.openwrt.org/snapshots/packages/x86_64/base/libncurses6_6.4-2_x86_64.ipk
Configuring terminfo.
Configuring libncurses6.
Configuring vim-fuller.
err: ""
- command: [/testbin/opkg, list-installed, vim-fuller]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "vim-fuller - 9.0-1 \n" # This output has the extra space at the end, to satisfy the behaviour of Yocto/OpenEmbedded's opkg
err: ""

View File

@@ -7,6 +7,10 @@ __metaclass__ = type
from ansible_collections.community.general.tests.unit.compat import unittest
from ansible_collections.community.general.plugins.modules import pagerduty_alert
import json
import pytest
from ansible_collections.community.general.tests.unit.compat.mock import patch
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
class PagerDutyAlertsTest(unittest.TestCase):
@@ -44,3 +48,106 @@ class PagerDutyAlertsTest(unittest.TestCase):
class Response(object):
def read(self):
return '{"incidents":[{"id": "incident_id", "status": "triggered"}]}'
class TestPagerDutyAlertModule(ModuleTestCase):
def setUp(self):
super(TestPagerDutyAlertModule, self).setUp()
self.module = pagerduty_alert
def tearDown(self):
super(TestPagerDutyAlertModule, self).tearDown()
@pytest.fixture
def fetch_url_mock(self, mocker):
return mocker.patch('ansible.module_utils.monitoring.pagerduty_change.fetch_url')
def test_module_fail_when_required_args_missing(self):
with self.assertRaises(AnsibleFailJson):
set_module_args({})
self.module.main()
def test_ensure_alert_created_with_minimal_data(self):
set_module_args({
'state': 'triggered',
'api_version': 'v2',
'integration_key': 'test',
'source': 'My Ansible Script',
'desc': 'Description for alert'
})
with patch.object(pagerduty_alert, 'fetch_url') as fetch_url_mock:
fetch_url_mock.return_value = (Response(), {"status": 202})
with self.assertRaises(AnsibleExitJson):
self.module.main()
assert fetch_url_mock.call_count == 1
url = fetch_url_mock.call_args[0][1]
json_data = fetch_url_mock.call_args[1]['data']
data = json.loads(json_data)
assert url == 'https://events.pagerduty.com/v2/enqueue'
assert data['routing_key'] == 'test'
assert data['event_action'] == 'trigger'
assert data['payload']['summary'] == 'Description for alert'
assert data['payload']['source'] == 'My Ansible Script'
assert data['payload']['severity'] == 'critical'
assert data['payload']['timestamp'] is not None
def test_ensure_alert_created_with_full_data(self):
set_module_args({
'api_version': 'v2',
'component': 'mysql',
'custom_details': {'environment': 'production', 'notes': 'this is a test note'},
'desc': 'Description for alert',
'incident_class': 'ping failure',
'integration_key': 'test',
'link_url': 'https://pagerduty.com',
'link_text': 'PagerDuty',
'state': 'triggered',
'source': 'My Ansible Script',
})
with patch.object(pagerduty_alert, 'fetch_url') as fetch_url_mock:
fetch_url_mock.return_value = (Response(), {"status": 202})
with self.assertRaises(AnsibleExitJson):
self.module.main()
assert fetch_url_mock.call_count == 1
url = fetch_url_mock.call_args[0][1]
json_data = fetch_url_mock.call_args[1]['data']
data = json.loads(json_data)
assert url == 'https://events.pagerduty.com/v2/enqueue'
assert data['routing_key'] == 'test'
assert data['payload']['summary'] == 'Description for alert'
assert data['payload']['source'] == 'My Ansible Script'
assert data['payload']['class'] == 'ping failure'
assert data['payload']['component'] == 'mysql'
assert data['payload']['custom_details']['environment'] == 'production'
assert data['payload']['custom_details']['notes'] == 'this is a test note'
assert data['links'][0]['href'] == 'https://pagerduty.com'
assert data['links'][0]['text'] == 'PagerDuty'
def test_ensure_alert_acknowledged(self):
set_module_args({
'state': 'acknowledged',
'api_version': 'v2',
'integration_key': 'test',
'incident_key': 'incident_test_id',
})
with patch.object(pagerduty_alert, 'fetch_url') as fetch_url_mock:
fetch_url_mock.return_value = (Response(), {"status": 202})
with self.assertRaises(AnsibleExitJson):
self.module.main()
assert fetch_url_mock.call_count == 1
url = fetch_url_mock.call_args[0][1]
json_data = fetch_url_mock.call_args[1]['data']
data = json.loads(json_data)
assert url == 'https://events.pagerduty.com/v2/enqueue'
assert data['routing_key'] == 'test'
assert data['event_action'] == 'acknowledge'
assert data['dedup_key'] == 'incident_test_id'

View File

@@ -12,216 +12,26 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from collections import namedtuple
from ansible_collections.community.general.plugins.modules import puppet
import pytest
TESTED_MODULE = puppet.__name__
from ansible_collections.community.general.plugins.modules import puppet as module
from .cmd_runner_test_utils import CmdRunnerTestHelper
ModuleTestCase = namedtuple("ModuleTestCase", ["id", "input", "output", "run_command_calls"])
RunCmdCall = namedtuple("RunCmdCall", ["command", "environ", "rc", "out", "err"])
with open("tests/unit/plugins/modules/test_puppet.yaml", "r") as TEST_CASES:
helper = CmdRunnerTestHelper(module.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.fixture
def patch_get_bin_path(mocker):
@pytest.mark.parametrize('patch_ansible_module, testcase',
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_module(mocker, capfd, patch_bin, testcase):
"""
Function used for mocking AnsibleModule.get_bin_path
"""
def mockie(self, path, *args, **kwargs):
return "/testbin/{0}".format(path)
mocker.patch("ansible.module_utils.basic.AnsibleModule.get_bin_path", mockie)
TEST_CASES = [
ModuleTestCase(
id="puppet_agent_plain",
input={},
output=dict(changed=False),
run_command_calls=[
RunCmdCall(
command=["/testbin/puppet", "config", "print", "agent_disabled_lockfile"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="blah, anything",
err="",
),
RunCmdCall(
command=[
"/testbin/timeout", "-s", "9", "30m", "/testbin/puppet", "agent", "--onetime", "--no-daemonize",
"--no-usecacheonfailure", "--no-splay", "--detailed-exitcodes", "--verbose", "--color", "0"
],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
]
),
ModuleTestCase(
id="puppet_agent_certname",
input={"certname": "potatobox"},
output=dict(changed=False),
run_command_calls=[
RunCmdCall(
command=["/testbin/puppet", "config", "print", "agent_disabled_lockfile"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="blah, anything",
err="",
),
RunCmdCall(
command=[
"/testbin/timeout", "-s", "9", "30m", "/testbin/puppet", "agent", "--onetime", "--no-daemonize",
"--no-usecacheonfailure", "--no-splay", "--detailed-exitcodes", "--verbose", "--color", "0", "--certname=potatobox"
],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
]
),
ModuleTestCase(
id="puppet_agent_tags_abc",
input={"tags": ["a", "b", "c"]},
output=dict(changed=False),
run_command_calls=[
RunCmdCall(
command=["/testbin/puppet", "config", "print", "agent_disabled_lockfile"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="blah, anything",
err="",
),
RunCmdCall(
command=[
"/testbin/timeout", "-s", "9", "30m", "/testbin/puppet", "agent", "--onetime", "--no-daemonize",
"--no-usecacheonfailure", "--no-splay", "--detailed-exitcodes", "--verbose", "--color", "0", "--tags", "a,b,c"
],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
]
),
ModuleTestCase(
id="puppet_agent_skip_tags_def",
input={"skip_tags": ["d", "e", "f"]},
output=dict(changed=False),
run_command_calls=[
RunCmdCall(
command=["/testbin/puppet", "config", "print", "agent_disabled_lockfile"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="blah, anything",
err="",
),
RunCmdCall(
command=[
"/testbin/timeout", "-s", "9", "30m", "/testbin/puppet", "agent", "--onetime", "--no-daemonize",
"--no-usecacheonfailure", "--no-splay", "--detailed-exitcodes", "--verbose", "--color", "0", "--skip_tags", "d,e,f"
],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
]
),
ModuleTestCase(
id="puppet_agent_noop_false",
input={"noop": False},
output=dict(changed=False),
run_command_calls=[
RunCmdCall(
command=["/testbin/puppet", "config", "print", "agent_disabled_lockfile"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="blah, anything",
err="",
),
RunCmdCall(
command=[
"/testbin/timeout", "-s", "9", "30m", "/testbin/puppet", "agent", "--onetime", "--no-daemonize",
"--no-usecacheonfailure", "--no-splay", "--detailed-exitcodes", "--verbose", "--color", "0", "--no-noop"
],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
]
),
ModuleTestCase(
id="puppet_agent_noop_true",
input={"noop": True},
output=dict(changed=False),
run_command_calls=[
RunCmdCall(
command=["/testbin/puppet", "config", "print", "agent_disabled_lockfile"],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="blah, anything",
err="",
),
RunCmdCall(
command=[
"/testbin/timeout", "-s", "9", "30m", "/testbin/puppet", "agent", "--onetime", "--no-daemonize",
"--no-usecacheonfailure", "--no-splay", "--detailed-exitcodes", "--verbose", "--color", "0", "--noop"
],
environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
rc=0,
out="",
err="",
),
]
),
]
TEST_CASES_IDS = [item.id for item in TEST_CASES]
@pytest.mark.parametrize("patch_ansible_module, testcase",
[[x.input, x] for x in TEST_CASES],
ids=TEST_CASES_IDS,
indirect=["patch_ansible_module"])
@pytest.mark.usefixtures("patch_ansible_module")
def test_puppet(mocker, capfd, patch_get_bin_path, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
run_cmd_calls = testcase.run_command_calls
# Mock function used for running commands first
call_results = [(x.rc, x.out, x.err) for x in run_cmd_calls]
mock_run_command = mocker.patch(
"ansible.module_utils.basic.AnsibleModule.run_command",
side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
puppet.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("testcase =\n%s" % str(testcase))
print("results =\n%s" % results)
assert mock_run_command.call_count == len(run_cmd_calls)
if mock_run_command.call_count:
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item.command, item.environ) for item in run_cmd_calls]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert call_args_list == expected_call_args_list
assert results.get("changed", False) == testcase.output["changed"]
if "failed" in testcase:
assert results.get("failed", False) == testcase.output["failed"]
if "msg" in testcase:
assert results.get("msg", "") == testcase.output["msg"]
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alexei Znamensky (russoz@gmail.com)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
---
- id: puppet_agent_plain
input: {}
output:
changed: false
run_command_calls:
- command: [/testbin/puppet, config, print, agent_disabled_lockfile]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "blah, anything"
err: ""
- command:
- /testbin/timeout
- -s
- "9"
- 30m
- /testbin/puppet
- agent
- --onetime
- --no-daemonize
- --no-usecacheonfailure
- --no-splay
- --detailed-exitcodes
- --verbose
- --color
- "0"
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: puppet_agent_certname
input:
certname: potatobox
output:
changed: false
run_command_calls:
- command: [/testbin/puppet, config, print, agent_disabled_lockfile]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "blah, anything"
err: ""
- command:
- /testbin/timeout
- -s
- "9"
- 30m
- /testbin/puppet
- agent
- --onetime
- --no-daemonize
- --no-usecacheonfailure
- --no-splay
- --detailed-exitcodes
- --verbose
- --color
- "0"
- --certname=potatobox
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: puppet_agent_tags_abc
input:
tags: [a, b, c]
output:
changed: false
run_command_calls:
- command: [/testbin/puppet, config, print, agent_disabled_lockfile]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "blah, anything"
err: ""
- command:
- /testbin/timeout
- -s
- "9"
- 30m
- /testbin/puppet
- agent
- --onetime
- --no-daemonize
- --no-usecacheonfailure
- --no-splay
- --detailed-exitcodes
- --verbose
- --color
- "0"
- --tags
- a,b,c
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: puppet_agent_skip_tags_def
input:
skip_tags: [d, e, f]
output:
changed: false
run_command_calls:
- command: [/testbin/puppet, config, print, agent_disabled_lockfile]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "blah, anything"
err: ""
- command:
- /testbin/timeout
- -s
- "9"
- 30m
- /testbin/puppet
- agent
- --onetime
- --no-daemonize
- --no-usecacheonfailure
- --no-splay
- --detailed-exitcodes
- --verbose
- --color
- "0"
- --skip_tags
- d,e,f
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: puppet_agent_noop_false
input:
noop: false
output:
changed: false
run_command_calls:
- command: [/testbin/puppet, config, print, agent_disabled_lockfile]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "blah, anything"
err: ""
- command:
- /testbin/timeout
- -s
- "9"
- 30m
- /testbin/puppet
- agent
- --onetime
- --no-daemonize
- --no-usecacheonfailure
- --no-splay
- --detailed-exitcodes
- --verbose
- --color
- "0"
- --no-noop
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: puppet_agent_noop_true
input:
noop: true
output:
changed: false
run_command_calls:
- command: [/testbin/puppet, config, print, agent_disabled_lockfile]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "blah, anything"
err: ""
- command:
- /testbin/timeout
- -s
- "9"
- 30m
- /testbin/puppet
- agent
- --onetime
- --no-daemonize
- --no-usecacheonfailure
- --no-splay
- --detailed-exitcodes
- --verbose
- --color
- "0"
- --noop
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""

View File

@@ -6,28 +6,10 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from collections import namedtuple
from ansible_collections.community.general.plugins.modules import snap
import pytest
TESTED_MODULE = snap.__name__
ModuleTestCase = namedtuple("ModuleTestCase", ["id", "input", "output", "run_command_calls"])
RunCmdCall = namedtuple("RunCmdCall", ["command", "environ", "rc", "out", "err"])
@pytest.fixture
def patch_get_bin_path(mocker):
"""
Function used for mocking AnsibleModule.get_bin_path
"""
def mockie(self, path, *args, **kwargs):
return "/testbin/{0}".format(path)
mocker.patch("ansible.module_utils.basic.AnsibleModule.get_bin_path", mockie)
from .cmd_runner_test_utils import CmdRunnerTestHelper, ModuleTestCase, RunCmdCall
from ansible_collections.community.general.plugins.modules import snap as module
issue_6803_status_out = """Name Version Rev Tracking Publisher Notes
@@ -399,6 +381,7 @@ TEST_CASES = [
id="simple case",
input={"name": ["hello-world"]},
output=dict(changed=True, snaps_installed=["hello-world"]),
flags={},
run_command_calls=[
RunCmdCall(
command=['/testbin/snap', 'info', 'hello-world'],
@@ -427,6 +410,7 @@ TEST_CASES = [
id="issue_6803",
input={"name": ["microk8s", "kubectl"], "classic": True},
output=dict(changed=True, snaps_installed=["microk8s", "kubectl"]),
flags={},
run_command_calls=[
RunCmdCall(
command=['/testbin/snap', 'info', 'microk8s', 'kubectl'],
@@ -459,50 +443,20 @@ TEST_CASES = [
]
),
]
TEST_CASES_IDS = [item.id for item in TEST_CASES]
@pytest.mark.parametrize("patch_ansible_module, testcase",
[[x.input, x] for x in TEST_CASES],
ids=TEST_CASES_IDS,
indirect=["patch_ansible_module"])
@pytest.mark.usefixtures("patch_ansible_module")
def test_snap(mocker, capfd, patch_get_bin_path, testcase):
helper = CmdRunnerTestHelper(module.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.mark.parametrize('patch_ansible_module, testcase',
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_module(mocker, capfd, patch_bin, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
run_cmd_calls = testcase.run_command_calls
# Mock function used for running commands first
call_results = [(x.rc, x.out, x.err) for x in run_cmd_calls]
mock_run_command = mocker.patch(
"ansible.module_utils.basic.AnsibleModule.run_command",
side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
snap.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("testcase =\n%s" % str(testcase))
print("results =\n%s" % results)
assert mock_run_command.call_count == len(run_cmd_calls)
if mock_run_command.call_count:
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item.command, item.environ) for item in run_cmd_calls]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
try:
assert call_args_list == expected_call_args_list
except AssertionError:
for test_call_run, expected_call_run in zip(call_args_list, expected_call_args_list):
assert test_call_run == expected_call_run
assert results.get("changed", False) == testcase.output["changed"]
if "failed" in testcase:
assert results.get("failed", False) == testcase.output["failed"]
if "msg" in testcase:
assert results.get("msg", "") == testcase.output["msg"]
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -12,301 +12,26 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible_collections.community.general.plugins.modules import xfconf
import pytest
TESTED_MODULE = xfconf.__name__
from ansible_collections.community.general.plugins.modules import xfconf as module
from .cmd_runner_test_utils import CmdRunnerTestHelper
@pytest.fixture
def patch_xfconf(mocker):
"""
Function used for mocking some parts of redhat_subscription module
"""
mocker.patch('ansible_collections.community.general.plugins.module_utils.mh.module_helper.AnsibleModule.get_bin_path',
return_value='/testbin/xfconf-query')
@pytest.mark.parametrize('patch_ansible_module', [{}], indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_without_required_parameters(capfd, patch_xfconf):
"""
Failure must occurs when all parameters are missing
"""
with pytest.raises(SystemExit):
xfconf.main()
out, err = capfd.readouterr()
results = json.loads(out)
assert results['failed']
assert 'missing required arguments' in results['msg']
TEST_CASES = [
[
{
'channel': 'xfwm4',
'property': '/general/inactive_opacity',
'state': 'present',
'value_type': 'int',
'value': 90,
},
{
'id': 'test_property_set_property',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, '100\n', '',),
),
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity',
'--create', '--type', 'int', '--set', '90'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, '', '',),
),
],
'changed': True,
'previous_value': '100',
'value_type': 'int',
'value': '90',
},
],
[
{
'channel': 'xfwm4',
'property': '/general/inactive_opacity',
'state': 'present',
'value_type': 'int',
'value': 90,
},
{
'id': 'test_property_set_property_same_value',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, '90\n', '',),
),
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity',
'--create', '--type', 'int', '--set', '90'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, '', '',),
),
],
'changed': False,
'previous_value': '90',
'value_type': 'int',
'value': '90',
},
],
[
{
'channel': 'xfce4-session',
'property': '/general/SaveOnExit',
'state': 'present',
'value_type': 'bool',
'value': False,
},
{
'id': 'test_property_set_property_bool_false',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfce4-session', '--property', '/general/SaveOnExit'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, 'true\n', '',),
),
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfce4-session', '--property', '/general/SaveOnExit',
'--create', '--type', 'bool', '--set', 'false'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, 'false\n', '',),
),
],
'changed': True,
'previous_value': 'true',
'value_type': 'bool',
'value': 'False',
},
],
[
{
'channel': 'xfwm4',
'property': '/general/workspace_names',
'state': 'present',
'value_type': 'string',
'value': ['A', 'B', 'C'],
},
{
'id': 'test_property_set_array',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, 'Value is an array with 3 items:\n\nMain\nWork\nTmp\n', '',),
),
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names',
'--create', '--force-array', '--type', 'string', '--set', 'A', '--type', 'string', '--set', 'B',
'--type', 'string', '--set', 'C'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, '', '',),
),
],
'changed': True,
'previous_value': ['Main', 'Work', 'Tmp'],
'value_type': ['str', 'str', 'str'],
'value': ['A', 'B', 'C'],
},
],
[
{
'channel': 'xfwm4',
'property': '/general/workspace_names',
'state': 'present',
'value_type': 'string',
'value': ['A', 'B', 'C'],
},
{
'id': 'test_property_set_array_to_same_value',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, 'Value is an array with 3 items:\n\nA\nB\nC\n', '',),
),
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names',
'--create', '--force-array', '--type', 'string', '--set', 'A', '--type', 'string', '--set', 'B',
'--type', 'string', '--set', 'C'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, '', '',),
),
],
'changed': False,
'previous_value': ['A', 'B', 'C'],
'value_type': ['str', 'str', 'str'],
'value': ['A', 'B', 'C'],
},
],
[
{
'channel': 'xfwm4',
'property': '/general/workspace_names',
'state': 'absent',
},
{
'id': 'test_property_reset_value',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, 'Value is an array with 3 items:\n\nA\nB\nC\n', '',),
),
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names',
'--reset'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False},
# Mock of returned code, stdout and stderr
(0, '', '',),
),
],
'changed': True,
'previous_value': ['A', 'B', 'C'],
'value_type': None,
'value': None,
},
],
]
TEST_CASES_IDS = [item[1]['id'] for item in TEST_CASES]
with open("tests/unit/plugins/modules/test_xfconf.yaml", "r") as TEST_CASES:
helper = CmdRunnerTestHelper(module.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.mark.parametrize('patch_ansible_module, testcase',
TEST_CASES,
ids=TEST_CASES_IDS,
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_xfconf(mocker, capfd, patch_xfconf, testcase):
def test_module(mocker, capfd, patch_bin, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
# Mock function used for running commands first
call_results = [item[2] for item in testcase['run_command.calls']]
mock_run_command = mocker.patch(
'ansible.module_utils.basic.AnsibleModule.run_command',
side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
xfconf.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("testcase =\n%s" % testcase)
print("results =\n%s" % results)
assert 'changed' in results
assert results['changed'] == testcase['changed']
for test_result in ('channel', 'property'):
assert test_result in results, "'{0}' not found in {1}".format(test_result, results)
assert results[test_result] == results['invocation']['module_args'][test_result], \
"'{0}': '{1}' != '{2}'".format(test_result, results[test_result], results['invocation']['module_args'][test_result])
assert mock_run_command.call_count == len(testcase['run_command.calls'])
if mock_run_command.call_count:
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item[0], item[1]) for item in testcase['run_command.calls']]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert call_args_list == expected_call_args_list
expected_cmd, dummy, expected_res = testcase['run_command.calls'][-1]
assert results['cmd'] == expected_cmd
assert results['stdout'] == expected_res[1]
assert results['stderr'] == expected_res[2]
for conditional_test_result in ('msg', 'value', 'previous_value'):
if conditional_test_result in testcase:
assert conditional_test_result in results, "'{0}' not found in {1}".format(conditional_test_result, results)
assert results[conditional_test_result] == testcase[conditional_test_result], \
"'{0}': '{1}' != '{2}'".format(conditional_test_result, results[conditional_test_result], testcase[conditional_test_result])
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alexei Znamensky (russoz@gmail.com)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
---
- id: test_missing_input
input: {}
output:
failed: true
msg: "missing required arguments: channel, property"
- id: test_property_set_property
input:
channel: xfwm4
property: /general/inactive_opacity
state: present
value_type: int
value: 90
output:
changed: true
previous_value: '100'
type: int
value: '90'
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/inactive_opacity]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "100\n"
err: ""
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/inactive_opacity, --create, --type, int, --set, '90']
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: test_property_set_property_same_value
input:
channel: xfwm4
property: /general/inactive_opacity
state: present
value_type: int
value: 90
output:
changed: false
previous_value: '90'
type: int
value: '90'
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/inactive_opacity]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "90\n"
err: ""
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/inactive_opacity, --create, --type, int, --set, '90']
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: test_property_set_property_bool_false
input:
channel: xfce4-session
property: /general/SaveOnExit
state: present
value_type: bool
value: False
output:
changed: true
previous_value: 'true'
type: bool
value: 'False'
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfce4-session, --property, /general/SaveOnExit]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "true\n"
err: ""
- command: [/testbin/xfconf-query, --channel, xfce4-session, --property, /general/SaveOnExit, --create, --type, bool, --set, 'false']
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "false\n"
err: ""
- id: test_property_set_array
input:
channel: xfwm4
property: /general/workspace_names
state: present
value_type: string
value: [A, B, C]
output:
changed: true
previous_value: [Main, Work, Tmp]
type: [string, string, string]
value: [A, B, C]
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/workspace_names]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "Value is an array with 3 items:\n\nMain\nWork\nTmp\n"
err: ""
- command:
- /testbin/xfconf-query
- --channel
- xfwm4
- --property
- /general/workspace_names
- --create
- --force-array
- --type
- string
- --set
- A
- --type
- string
- --set
- B
- --type
- string
- --set
- C
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: test_property_set_array_to_same_value
input:
channel: xfwm4
property: /general/workspace_names
state: present
value_type: string
value: [A, B, C]
output:
changed: false
previous_value: [A, B, C]
type: [string, string, string]
value: [A, B, C]
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/workspace_names]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "Value is an array with 3 items:\n\nA\nB\nC\n"
err: ""
- command:
- /testbin/xfconf-query
- --channel
- xfwm4
- --property
- /general/workspace_names
- --create
- --force-array
- --type
- string
- --set
- A
- --type
- string
- --set
- B
- --type
- string
- --set
- C
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""
- id: test_property_reset_value
input:
channel: xfwm4
property: /general/workspace_names
state: absent
output:
changed: true
previous_value: [A, B, C]
type: null
value: null
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/workspace_names]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: "Value is an array with 3 items:\n\nA\nB\nC\n"
err: ""
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/workspace_names, --reset]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false}
rc: 0
out: ""
err: ""

View File

@@ -5,168 +5,26 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible_collections.community.general.plugins.modules import xfconf_info
import pytest
TESTED_MODULE = xfconf_info.__name__
from ansible_collections.community.general.plugins.modules import xfconf_info as module
from .cmd_runner_test_utils import CmdRunnerTestHelper
@pytest.fixture
def patch_xfconf_info(mocker):
"""
Function used for mocking some parts of redhat_subscription module
"""
mocker.patch('ansible_collections.community.general.plugins.module_utils.mh.module_helper.AnsibleModule.get_bin_path',
return_value='/testbin/xfconf-query')
TEST_CASES = [
[
{'channel': 'xfwm4', 'property': '/general/inactive_opacity'},
{
'id': 'test_simple_property_get',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/inactive_opacity'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
# Mock of returned code, stdout and stderr
(0, '100\n', '',),
),
],
'is_array': False,
'value': '100',
}
],
[
{'channel': 'xfwm4', 'property': '/general/i_dont_exist'},
{
'id': 'test_simple_property_get_nonexistent',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/i_dont_exist'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
# Mock of returned code, stdout and stderr
(1, '', 'Property "/general/i_dont_exist" does not exist on channel "xfwm4".\n',),
),
],
'is_array': False,
}
],
[
{'property': '/general/i_dont_exist'},
{
'id': 'test_property_no_channel',
'run_command.calls': [],
}
],
[
{'channel': 'xfwm4', 'property': '/general/workspace_names'},
{
'id': 'test_property_get_array',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--channel', 'xfwm4', '--property', '/general/workspace_names'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
# Mock of returned code, stdout and stderr
(0, 'Value is an array with 3 items:\n\nMain\nWork\nTmp\n', '',),
),
],
'is_array': True,
'value_array': ['Main', 'Work', 'Tmp'],
},
],
[
{},
{
'id': 'get_channels',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--list'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
# Mock of returned code, stdout and stderr
(0, 'Channels:\n a\n b\n c\n', '',),
),
],
'is_array': False,
'channels': ['a', 'b', 'c'],
},
],
[
{'channel': 'xfwm4'},
{
'id': 'get_properties',
'run_command.calls': [
(
# Calling of following command will be asserted
['/testbin/xfconf-query', '--list', '--channel', 'xfwm4'],
# Was return code checked?
{'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': True},
# Mock of returned code, stdout and stderr
(0, '/general/wrap_cycle\n/general/wrap_layout\n/general/wrap_resistance\n/general/wrap_windows\n'
'/general/wrap_workspaces\n/general/zoom_desktop\n', '',),
),
],
'is_array': False,
'properties': [
'/general/wrap_cycle',
'/general/wrap_layout',
'/general/wrap_resistance',
'/general/wrap_windows',
'/general/wrap_workspaces',
'/general/zoom_desktop',
],
},
],
]
TEST_CASES_IDS = [item[1]['id'] for item in TEST_CASES]
with open("tests/unit/plugins/modules/test_xfconf_info.yaml", "r") as TEST_CASES:
helper = CmdRunnerTestHelper(module.main, test_cases=TEST_CASES)
patch_bin = helper.cmd_fixture
@pytest.mark.parametrize('patch_ansible_module, testcase',
TEST_CASES,
ids=TEST_CASES_IDS,
helper.testcases_params, ids=helper.testcases_ids,
indirect=['patch_ansible_module'])
@pytest.mark.usefixtures('patch_ansible_module')
def test_xfconf_info(mocker, capfd, patch_xfconf_info, testcase):
def test_module(mocker, capfd, patch_bin, testcase):
"""
Run unit tests for test cases listen in TEST_CASES
Run unit tests for test cases listed in TEST_CASES
"""
# Mock function used for running commands first
call_results = [item[2] for item in testcase['run_command.calls']]
mock_run_command = mocker.patch(
'ansible_collections.community.general.plugins.module_utils.mh.module_helper.AnsibleModule.run_command',
side_effect=call_results)
# Try to run test case
with pytest.raises(SystemExit):
xfconf_info.main()
out, err = capfd.readouterr()
results = json.loads(out)
print("testcase =\n%s" % testcase)
print("results =\n%s" % results)
for conditional_test_result in ('value_array', 'value', 'is_array', 'properties', 'channels'):
if conditional_test_result in testcase:
assert conditional_test_result in results, "'{0}' not found in {1}".format(conditional_test_result, results)
assert results[conditional_test_result] == testcase[conditional_test_result], \
"'{0}': '{1}' != '{2}'".format(conditional_test_result, results[conditional_test_result], testcase[conditional_test_result])
assert mock_run_command.call_count == len(testcase['run_command.calls'])
if mock_run_command.call_count:
call_args_list = [(item[0][0], item[1]) for item in mock_run_command.call_args_list]
expected_call_args_list = [(item[0], item[1]) for item in testcase['run_command.calls']]
print("call args list =\n%s" % call_args_list)
print("expected args list =\n%s" % expected_call_args_list)
assert call_args_list == expected_call_args_list
with helper(testcase, mocker, capfd) as testcase_context:
testcase_context.run()

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
# Copyright (c) Alexei Znamensky (russoz@gmail.com)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
---
- id: test_simple_property_get
input:
channel: xfwm4
property: /general/inactive_opacity
output:
value: '100'
is_array: false
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/inactive_opacity]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "100\n"
err: ""
- id: test_simple_property_get_nonexistent
input:
channel: xfwm4
property: /general/i_dont_exist
output: {}
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/i_dont_exist]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 1
out: ""
err: 'Property "/general/i_dont_exist" does not exist on channel "xfwm4".\n'
- id: test_property_no_channel
input:
property: /general/i_dont_exist
output:
failed: true
msg: "missing parameter(s) required by 'property': channel"
run_command_calls: []
- id: test_property_get_array
input:
channel: xfwm4
property: /general/workspace_names
output:
is_array: true
value_array: [Main, Work, Tmp]
run_command_calls:
- command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/workspace_names]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "Value is an array with 3 items:\n\nMain\nWork\nTmp\n"
err: ""
- id: get_channels
input: {}
output:
channels: [a, b, c]
run_command_calls:
- command: [/testbin/xfconf-query, --list]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: "Channels:\n a\n b\n c\n"
err: ""
- id: get_properties
input:
channel: xfwm4
output:
properties:
- /general/wrap_cycle
- /general/wrap_layout
- /general/wrap_resistance
- /general/wrap_windows
- /general/wrap_workspaces
- /general/zoom_desktop
run_command_calls:
- command: [/testbin/xfconf-query, --list, --channel, xfwm4]
environ: {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true}
rc: 0
out: |
/general/wrap_cycle
/general/wrap_layout
/general/wrap_resistance
/general/wrap_windows
/general/wrap_workspaces
/general/zoom_desktop
err: ""